blob: 2b43895ab06a8601491e29899c64fd2c0b0ffab6 [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% %
cristy7e41fe82010-12-04 23:12:08 +000020% Copyright 1999-2011 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"
50#include "MagickCore/composite-private.h"
51#include "MagickCore/enhance.h"
52#include "MagickCore/exception.h"
53#include "MagickCore/exception-private.h"
54#include "MagickCore/fx.h"
55#include "MagickCore/gem.h"
56#include "MagickCore/geometry.h"
57#include "MagickCore/histogram.h"
58#include "MagickCore/image.h"
59#include "MagickCore/image-private.h"
60#include "MagickCore/memory_.h"
61#include "MagickCore/monitor.h"
62#include "MagickCore/monitor-private.h"
63#include "MagickCore/option.h"
64#include "MagickCore/pixel-accessor.h"
65#include "MagickCore/quantum.h"
66#include "MagickCore/quantum-private.h"
67#include "MagickCore/resample.h"
68#include "MagickCore/resample-private.h"
69#include "MagickCore/statistic.h"
70#include "MagickCore/string_.h"
71#include "MagickCore/string-private.h"
72#include "MagickCore/thread-private.h"
73#include "MagickCore/token.h"
74#include "MagickCore/xml-tree.h"
cristy3ed852e2009-09-05 21:47:34 +000075
76/*
77%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
78% %
79% %
80% %
81% A u t o G a m m a I m a g e %
82% %
83% %
84% %
85%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
86%
87% AutoGammaImage() extract the 'mean' from the image and adjust the image
88% to try make set its gamma appropriatally.
89%
cristy308b4e62009-09-21 14:40:44 +000090% The format of the AutoGammaImage method is:
cristy3ed852e2009-09-05 21:47:34 +000091%
92% MagickBooleanType AutoGammaImage(Image *image)
cristy3ed852e2009-09-05 21:47:34 +000093%
94% A description of each parameter follows:
95%
96% o image: The image to auto-level
97%
cristy3ed852e2009-09-05 21:47:34 +000098*/
cristy3ed852e2009-09-05 21:47:34 +000099MagickExport MagickBooleanType AutoGammaImage(Image *image)
100{
cristy3ed852e2009-09-05 21:47:34 +0000101 MagickStatusType
102 status;
103
104 double
cristy4c08aed2011-07-01 19:47:50 +0000105 gamma,
106 log_mean,
107 mean,
108 sans;
anthony4efe5972009-09-11 06:46:40 +0000109
cristy4c08aed2011-07-01 19:47:50 +0000110 log_mean=log(0.5);
cristyab015852011-07-06 01:03:32 +0000111 if (image->sync != MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +0000112 {
113 /*
114 Apply gamma correction equally accross all given channels
115 */
cristyd42d9952011-07-08 14:21:50 +0000116 (void) GetImageMean(image,&mean,&sans,&image->exception);
cristy4c08aed2011-07-01 19:47:50 +0000117 gamma=log(mean*QuantumScale)/log_mean;
cristyf89cb1d2011-07-07 01:24:37 +0000118 return(LevelImage(image,0.0,(double) QuantumRange,gamma));
cristy3ed852e2009-09-05 21:47:34 +0000119 }
cristy3ed852e2009-09-05 21:47:34 +0000120 /*
cristy4c08aed2011-07-01 19:47:50 +0000121 Auto-gamma each channel separately.
cristy3ed852e2009-09-05 21:47:34 +0000122 */
cristy4c08aed2011-07-01 19:47:50 +0000123 status=MagickTrue;
cristy2b9582a2011-07-04 17:38:56 +0000124 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +0000125 {
cristy490408a2011-07-07 14:42:05 +0000126 PushPixelComponentMap(image,RedChannel);
cristyd42d9952011-07-08 14:21:50 +0000127 (void) GetImageMean(image,&mean,&sans,&image->exception);
128 gamma=log(mean*QuantumScale)/log_mean;
cristy490408a2011-07-07 14:42:05 +0000129 status=status && LevelImage(image,0.0,(double) QuantumRange,gamma);
130 PopPixelComponentMap(image);
cristy3ed852e2009-09-05 21:47:34 +0000131 }
cristy2b9582a2011-07-04 17:38:56 +0000132 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +0000133 {
cristy490408a2011-07-07 14:42:05 +0000134 PushPixelComponentMap(image,GreenChannel);
cristyd42d9952011-07-08 14:21:50 +0000135 (void) GetImageMean(image,&mean,&sans,&image->exception);
136 gamma=log(mean*QuantumScale)/log_mean;
cristy490408a2011-07-07 14:42:05 +0000137 status=status && LevelImage(image,0.0,(double) QuantumRange,gamma);
138 PopPixelComponentMap(image);
cristy3ed852e2009-09-05 21:47:34 +0000139 }
cristy2b9582a2011-07-04 17:38:56 +0000140 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +0000141 {
cristy490408a2011-07-07 14:42:05 +0000142 PushPixelComponentMap(image,BlueChannel);
cristyd42d9952011-07-08 14:21:50 +0000143 (void) GetImageMean(image,&mean,&sans,&image->exception);
144 gamma=log(mean*QuantumScale)/log_mean;
cristy490408a2011-07-07 14:42:05 +0000145 status=status && LevelImage(image,0.0,(double) QuantumRange,gamma);
146 PopPixelComponentMap(image);
cristy4c08aed2011-07-01 19:47:50 +0000147 }
cristy2b9582a2011-07-04 17:38:56 +0000148 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +0000149 (image->colorspace == CMYKColorspace))
150 {
cristy490408a2011-07-07 14:42:05 +0000151 PushPixelComponentMap(image,BlackChannel);
cristyd42d9952011-07-08 14:21:50 +0000152 (void) GetImageMean(image,&mean,&sans,&image->exception);
153 gamma=log(mean*QuantumScale)/log_mean;
cristy490408a2011-07-07 14:42:05 +0000154 status=status && LevelImage(image,0.0,(double) QuantumRange,gamma);
155 PopPixelComponentMap(image);
cristy3ed852e2009-09-05 21:47:34 +0000156 }
cristy2b9582a2011-07-04 17:38:56 +0000157 if (((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +0000158 (image->matte == MagickTrue))
159 {
cristy490408a2011-07-07 14:42:05 +0000160 PushPixelComponentMap(image,AlphaChannel);
cristyd42d9952011-07-08 14:21:50 +0000161 (void) GetImageMean(image,&mean,&sans,&image->exception);
162 gamma=log(mean*QuantumScale)/log_mean;
cristy490408a2011-07-07 14:42:05 +0000163 status=status && LevelImage(image,0.0,(double) QuantumRange,gamma);
164 PopPixelComponentMap(image);
cristy3ed852e2009-09-05 21:47:34 +0000165 }
166 return(status != 0 ? MagickTrue : MagickFalse);
167}
168
169/*
170%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
171% %
172% %
173% %
174% A u t o L e v e l I m a g e %
175% %
176% %
177% %
178%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
179%
180% AutoLevelImage() adjusts the levels of a particular image channel by
181% scaling the minimum and maximum values to the full quantum range.
182%
183% The format of the LevelImage method is:
184%
185% MagickBooleanType AutoLevelImage(Image *image)
cristy3ed852e2009-09-05 21:47:34 +0000186%
187% A description of each parameter follows:
188%
189% o image: The image to auto-level
190%
cristy3ed852e2009-09-05 21:47:34 +0000191*/
cristy3ed852e2009-09-05 21:47:34 +0000192MagickExport MagickBooleanType AutoLevelImage(Image *image)
193{
cristy490408a2011-07-07 14:42:05 +0000194 return(MinMaxStretchImage(image,0.0,0.0));
cristy3ed852e2009-09-05 21:47:34 +0000195}
196
197/*
198%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
199% %
200% %
201% %
cristya28d6b82010-01-11 20:03:47 +0000202% B r i g h t n e s s C o n t r a s t I m a g e %
203% %
204% %
205% %
206%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
207%
208% Use BrightnessContrastImage() to change the brightness and/or contrast of
209% an image. It converts the brightness and contrast parameters into slope
210% and intercept and calls a polynomical function to apply to the image.
211%
212% The format of the BrightnessContrastImage method is:
213%
214% MagickBooleanType BrightnessContrastImage(Image *image,
215% const double brightness,const double contrast)
cristya28d6b82010-01-11 20:03:47 +0000216%
217% A description of each parameter follows:
218%
219% o image: the image.
220%
cristya28d6b82010-01-11 20:03:47 +0000221% o brightness: the brightness percent (-100 .. 100).
222%
223% o contrast: the contrast percent (-100 .. 100).
224%
225*/
cristya28d6b82010-01-11 20:03:47 +0000226MagickExport MagickBooleanType BrightnessContrastImage(Image *image,
227 const double brightness,const double contrast)
228{
cristya28d6b82010-01-11 20:03:47 +0000229#define BrightnessContastImageTag "BrightnessContast/Image"
230
231 double
232 alpha,
233 intercept,
234 coefficients[2],
235 slope;
236
237 MagickBooleanType
238 status;
239
240 /*
241 Compute slope and intercept.
242 */
243 assert(image != (Image *) NULL);
244 assert(image->signature == MagickSignature);
245 if (image->debug != MagickFalse)
246 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
247 alpha=contrast;
cristy4205a3c2010-09-12 20:19:59 +0000248 slope=tan((double) (MagickPI*(alpha/100.0+1.0)/4.0));
cristya28d6b82010-01-11 20:03:47 +0000249 if (slope < 0.0)
250 slope=0.0;
251 intercept=brightness/100.0+((100-brightness)/200.0)*(1.0-slope);
252 coefficients[0]=slope;
253 coefficients[1]=intercept;
cristyd42d9952011-07-08 14:21:50 +0000254 status=FunctionImage(image,PolynomialFunction,2,coefficients,
255 &image->exception);
cristya28d6b82010-01-11 20:03:47 +0000256 return(status);
257}
258
259/*
260%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
261% %
262% %
263% %
cristy3ed852e2009-09-05 21:47:34 +0000264% C o l o r D e c i s i o n L i s t I m a g e %
265% %
266% %
267% %
268%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
269%
270% ColorDecisionListImage() accepts a lightweight Color Correction Collection
271% (CCC) file which solely contains one or more color corrections and applies
272% the correction to the image. Here is a sample CCC file:
273%
274% <ColorCorrectionCollection xmlns="urn:ASC:CDL:v1.2">
275% <ColorCorrection id="cc03345">
276% <SOPNode>
277% <Slope> 0.9 1.2 0.5 </Slope>
278% <Offset> 0.4 -0.5 0.6 </Offset>
279% <Power> 1.0 0.8 1.5 </Power>
280% </SOPNode>
281% <SATNode>
282% <Saturation> 0.85 </Saturation>
283% </SATNode>
284% </ColorCorrection>
285% </ColorCorrectionCollection>
286%
287% which includes the slop, offset, and power for each of the RGB channels
288% as well as the saturation.
289%
290% The format of the ColorDecisionListImage method is:
291%
292% MagickBooleanType ColorDecisionListImage(Image *image,
293% const char *color_correction_collection)
294%
295% A description of each parameter follows:
296%
297% o image: the image.
298%
299% o color_correction_collection: the color correction collection in XML.
300%
301*/
302MagickExport MagickBooleanType ColorDecisionListImage(Image *image,
303 const char *color_correction_collection)
304{
305#define ColorDecisionListCorrectImageTag "ColorDecisionList/Image"
306
307 typedef struct _Correction
308 {
309 double
310 slope,
311 offset,
312 power;
313 } Correction;
314
315 typedef struct _ColorCorrection
316 {
317 Correction
318 red,
319 green,
320 blue;
321
322 double
323 saturation;
324 } ColorCorrection;
325
cristyc4c8d132010-01-07 01:58:38 +0000326 CacheView
327 *image_view;
328
cristy3ed852e2009-09-05 21:47:34 +0000329 char
330 token[MaxTextExtent];
331
332 ColorCorrection
333 color_correction;
334
335 const char
336 *content,
337 *p;
338
339 ExceptionInfo
340 *exception;
341
cristy3ed852e2009-09-05 21:47:34 +0000342 MagickBooleanType
343 status;
344
cristybb503372010-05-27 20:51:26 +0000345 MagickOffsetType
346 progress;
347
cristy3ed852e2009-09-05 21:47:34 +0000348 PixelPacket
349 *cdl_map;
350
cristybb503372010-05-27 20:51:26 +0000351 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000352 i;
353
cristybb503372010-05-27 20:51:26 +0000354 ssize_t
355 y;
356
cristy3ed852e2009-09-05 21:47:34 +0000357 XMLTreeInfo
358 *cc,
359 *ccc,
360 *sat,
361 *sop;
362
cristy3ed852e2009-09-05 21:47:34 +0000363 /*
364 Allocate and initialize cdl maps.
365 */
366 assert(image != (Image *) NULL);
367 assert(image->signature == MagickSignature);
368 if (image->debug != MagickFalse)
369 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
370 if (color_correction_collection == (const char *) NULL)
371 return(MagickFalse);
372 ccc=NewXMLTree((const char *) color_correction_collection,&image->exception);
373 if (ccc == (XMLTreeInfo *) NULL)
374 return(MagickFalse);
375 cc=GetXMLTreeChild(ccc,"ColorCorrection");
376 if (cc == (XMLTreeInfo *) NULL)
377 {
378 ccc=DestroyXMLTree(ccc);
379 return(MagickFalse);
380 }
381 color_correction.red.slope=1.0;
382 color_correction.red.offset=0.0;
383 color_correction.red.power=1.0;
384 color_correction.green.slope=1.0;
385 color_correction.green.offset=0.0;
386 color_correction.green.power=1.0;
387 color_correction.blue.slope=1.0;
388 color_correction.blue.offset=0.0;
389 color_correction.blue.power=1.0;
390 color_correction.saturation=0.0;
391 sop=GetXMLTreeChild(cc,"SOPNode");
392 if (sop != (XMLTreeInfo *) NULL)
393 {
394 XMLTreeInfo
395 *offset,
396 *power,
397 *slope;
398
399 slope=GetXMLTreeChild(sop,"Slope");
400 if (slope != (XMLTreeInfo *) NULL)
401 {
402 content=GetXMLTreeContent(slope);
403 p=(const char *) content;
404 for (i=0; (*p != '\0') && (i < 3); i++)
405 {
406 GetMagickToken(p,&p,token);
407 if (*token == ',')
408 GetMagickToken(p,&p,token);
409 switch (i)
410 {
cristyc1acd842011-05-19 23:05:47 +0000411 case 0:
412 {
413 color_correction.red.slope=InterpretLocaleValue(token,
414 (char **) NULL);
415 break;
416 }
417 case 1:
418 {
419 color_correction.green.slope=InterpretLocaleValue(token,
420 (char **) NULL);
421 break;
422 }
423 case 2:
424 {
425 color_correction.blue.slope=InterpretLocaleValue(token,
426 (char **) NULL);
427 break;
428 }
cristy3ed852e2009-09-05 21:47:34 +0000429 }
430 }
431 }
432 offset=GetXMLTreeChild(sop,"Offset");
433 if (offset != (XMLTreeInfo *) NULL)
434 {
435 content=GetXMLTreeContent(offset);
436 p=(const char *) content;
437 for (i=0; (*p != '\0') && (i < 3); i++)
438 {
439 GetMagickToken(p,&p,token);
440 if (*token == ',')
441 GetMagickToken(p,&p,token);
442 switch (i)
443 {
cristyc1acd842011-05-19 23:05:47 +0000444 case 0:
445 {
446 color_correction.red.offset=InterpretLocaleValue(token,
447 (char **) NULL);
448 break;
449 }
450 case 1:
451 {
452 color_correction.green.offset=InterpretLocaleValue(token,
453 (char **) NULL);
454 break;
455 }
456 case 2:
457 {
458 color_correction.blue.offset=InterpretLocaleValue(token,
459 (char **) NULL);
460 break;
461 }
cristy3ed852e2009-09-05 21:47:34 +0000462 }
463 }
464 }
465 power=GetXMLTreeChild(sop,"Power");
466 if (power != (XMLTreeInfo *) NULL)
467 {
468 content=GetXMLTreeContent(power);
469 p=(const char *) content;
470 for (i=0; (*p != '\0') && (i < 3); i++)
471 {
472 GetMagickToken(p,&p,token);
473 if (*token == ',')
474 GetMagickToken(p,&p,token);
475 switch (i)
476 {
cristyc1acd842011-05-19 23:05:47 +0000477 case 0:
478 {
479 color_correction.red.power=InterpretLocaleValue(token,
480 (char **) NULL);
481 break;
482 }
483 case 1:
484 {
485 color_correction.green.power=InterpretLocaleValue(token,
486 (char **) NULL);
487 break;
488 }
489 case 2:
490 {
491 color_correction.blue.power=InterpretLocaleValue(token,
492 (char **) NULL);
493 break;
494 }
cristy3ed852e2009-09-05 21:47:34 +0000495 }
496 }
497 }
498 }
499 sat=GetXMLTreeChild(cc,"SATNode");
500 if (sat != (XMLTreeInfo *) NULL)
501 {
502 XMLTreeInfo
503 *saturation;
504
505 saturation=GetXMLTreeChild(sat,"Saturation");
506 if (saturation != (XMLTreeInfo *) NULL)
507 {
508 content=GetXMLTreeContent(saturation);
509 p=(const char *) content;
510 GetMagickToken(p,&p,token);
cristyc1acd842011-05-19 23:05:47 +0000511 color_correction.saturation=InterpretLocaleValue(token,
512 (char **) NULL);
cristy3ed852e2009-09-05 21:47:34 +0000513 }
514 }
515 ccc=DestroyXMLTree(ccc);
516 if (image->debug != MagickFalse)
517 {
518 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
519 " Color Correction Collection:");
520 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000521 " color_correction.red.slope: %g",color_correction.red.slope);
cristy3ed852e2009-09-05 21:47:34 +0000522 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000523 " color_correction.red.offset: %g",color_correction.red.offset);
cristy3ed852e2009-09-05 21:47:34 +0000524 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000525 " color_correction.red.power: %g",color_correction.red.power);
cristy3ed852e2009-09-05 21:47:34 +0000526 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000527 " color_correction.green.slope: %g",color_correction.green.slope);
cristy3ed852e2009-09-05 21:47:34 +0000528 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000529 " color_correction.green.offset: %g",color_correction.green.offset);
cristy3ed852e2009-09-05 21:47:34 +0000530 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000531 " color_correction.green.power: %g",color_correction.green.power);
cristy3ed852e2009-09-05 21:47:34 +0000532 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000533 " color_correction.blue.slope: %g",color_correction.blue.slope);
cristy3ed852e2009-09-05 21:47:34 +0000534 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000535 " color_correction.blue.offset: %g",color_correction.blue.offset);
cristy3ed852e2009-09-05 21:47:34 +0000536 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000537 " color_correction.blue.power: %g",color_correction.blue.power);
cristy3ed852e2009-09-05 21:47:34 +0000538 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000539 " color_correction.saturation: %g",color_correction.saturation);
cristy3ed852e2009-09-05 21:47:34 +0000540 }
541 cdl_map=(PixelPacket *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*cdl_map));
542 if (cdl_map == (PixelPacket *) NULL)
543 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
544 image->filename);
cristyb5d5f722009-11-04 03:03:49 +0000545#if defined(MAGICKCORE_OPENMP_SUPPORT)
546 #pragma omp parallel for schedule(dynamic,4)
cristy3ed852e2009-09-05 21:47:34 +0000547#endif
cristybb503372010-05-27 20:51:26 +0000548 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +0000549 {
cristyce70c172010-01-07 17:15:30 +0000550 cdl_map[i].red=ClampToQuantum((MagickRealType) ScaleMapToQuantum((
cristy3ed852e2009-09-05 21:47:34 +0000551 MagickRealType) (MaxMap*(pow(color_correction.red.slope*i/MaxMap+
552 color_correction.red.offset,color_correction.red.power)))));
cristyce70c172010-01-07 17:15:30 +0000553 cdl_map[i].green=ClampToQuantum((MagickRealType) ScaleMapToQuantum((
cristy3ed852e2009-09-05 21:47:34 +0000554 MagickRealType) (MaxMap*(pow(color_correction.green.slope*i/MaxMap+
555 color_correction.green.offset,color_correction.green.power)))));
cristyce70c172010-01-07 17:15:30 +0000556 cdl_map[i].blue=ClampToQuantum((MagickRealType) ScaleMapToQuantum((
cristy3ed852e2009-09-05 21:47:34 +0000557 MagickRealType) (MaxMap*(pow(color_correction.blue.slope*i/MaxMap+
558 color_correction.blue.offset,color_correction.blue.power)))));
559 }
560 if (image->storage_class == PseudoClass)
561 {
562 /*
563 Apply transfer function to colormap.
564 */
cristyb5d5f722009-11-04 03:03:49 +0000565#if defined(MAGICKCORE_OPENMP_SUPPORT)
566 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000567#endif
cristybb503372010-05-27 20:51:26 +0000568 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +0000569 {
570 double
571 luma;
572
573 luma=0.2126*image->colormap[i].red+0.7152*image->colormap[i].green+
574 0.0722*image->colormap[i].blue;
cristyce70c172010-01-07 17:15:30 +0000575 image->colormap[i].red=ClampToQuantum(luma+color_correction.saturation*
cristy3ed852e2009-09-05 21:47:34 +0000576 cdl_map[ScaleQuantumToMap(image->colormap[i].red)].red-luma);
cristyce70c172010-01-07 17:15:30 +0000577 image->colormap[i].green=ClampToQuantum(luma+
cristy3ed852e2009-09-05 21:47:34 +0000578 color_correction.saturation*cdl_map[ScaleQuantumToMap(
579 image->colormap[i].green)].green-luma);
cristyce70c172010-01-07 17:15:30 +0000580 image->colormap[i].blue=ClampToQuantum(luma+color_correction.saturation*
cristy3ed852e2009-09-05 21:47:34 +0000581 cdl_map[ScaleQuantumToMap(image->colormap[i].blue)].blue-luma);
582 }
583 }
584 /*
585 Apply transfer function to image.
586 */
587 status=MagickTrue;
588 progress=0;
589 exception=(&image->exception);
590 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +0000591#if defined(MAGICKCORE_OPENMP_SUPPORT)
592 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000593#endif
cristybb503372010-05-27 20:51:26 +0000594 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000595 {
596 double
597 luma;
598
cristy4c08aed2011-07-01 19:47:50 +0000599 register Quantum
cristyc47d1f82009-11-26 01:44:43 +0000600 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000601
cristy8d4629b2010-08-30 17:59:46 +0000602 register ssize_t
603 x;
604
cristy3ed852e2009-09-05 21:47:34 +0000605 if (status == MagickFalse)
606 continue;
607 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +0000608 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +0000609 {
610 status=MagickFalse;
611 continue;
612 }
cristybb503372010-05-27 20:51:26 +0000613 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000614 {
cristy4c08aed2011-07-01 19:47:50 +0000615 luma=0.2126*GetPixelRed(image,q)+0.7152*GetPixelGreen(image,q)+0.0722*
616 GetPixelBlue(image,q);
617 SetPixelRed(image,ClampToQuantum(luma+color_correction.saturation*
618 (cdl_map[ScaleQuantumToMap(GetPixelRed(image,q))].red-luma)),q);
619 SetPixelGreen(image,ClampToQuantum(luma+color_correction.saturation*
620 (cdl_map[ScaleQuantumToMap(GetPixelGreen(image,q))].green-luma)),q);
621 SetPixelBlue(image,ClampToQuantum(luma+color_correction.saturation*
622 (cdl_map[ScaleQuantumToMap(GetPixelBlue(image,q))].blue-luma)),q);
cristydcfc1ad2011-07-07 16:25:41 +0000623 q+=GetPixelComponents(image);
cristy3ed852e2009-09-05 21:47:34 +0000624 }
625 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
626 status=MagickFalse;
627 if (image->progress_monitor != (MagickProgressMonitor) NULL)
628 {
629 MagickBooleanType
630 proceed;
631
cristyb5d5f722009-11-04 03:03:49 +0000632#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +0000633 #pragma omp critical (MagickCore_ColorDecisionListImageChannel)
634#endif
635 proceed=SetImageProgress(image,ColorDecisionListCorrectImageTag,
636 progress++,image->rows);
637 if (proceed == MagickFalse)
638 status=MagickFalse;
639 }
640 }
641 image_view=DestroyCacheView(image_view);
642 cdl_map=(PixelPacket *) RelinquishMagickMemory(cdl_map);
643 return(status);
644}
645
646/*
647%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
648% %
649% %
650% %
651% C l u t I m a g e %
652% %
653% %
654% %
655%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
656%
657% ClutImage() replaces each color value in the given image, by using it as an
658% index to lookup a replacement color value in a Color Look UP Table in the
cristycee97112010-05-28 00:44:52 +0000659% form of an image. The values are extracted along a diagonal of the CLUT
cristy3ed852e2009-09-05 21:47:34 +0000660% image so either a horizontal or vertial gradient image can be used.
661%
662% Typically this is used to either re-color a gray-scale image according to a
663% color gradient in the CLUT image, or to perform a freeform histogram
664% (level) adjustment according to the (typically gray-scale) gradient in the
665% CLUT image.
666%
667% When the 'channel' mask includes the matte/alpha transparency channel but
668% one image has no such channel it is assumed that that image is a simple
669% gray-scale image that will effect the alpha channel values, either for
670% gray-scale coloring (with transparent or semi-transparent colors), or
671% a histogram adjustment of existing alpha channel values. If both images
672% have matte channels, direct and normal indexing is applied, which is rarely
673% used.
674%
675% The format of the ClutImage method is:
676%
677% MagickBooleanType ClutImage(Image *image,Image *clut_image)
cristy3ed852e2009-09-05 21:47:34 +0000678%
679% A description of each parameter follows:
680%
681% o image: the image, which is replaced by indexed CLUT values
682%
683% o clut_image: the color lookup table image for replacement color values.
684%
685% o channel: the channel.
686%
687*/
cristy3ed852e2009-09-05 21:47:34 +0000688MagickExport MagickBooleanType ClutImage(Image *image,const Image *clut_image)
689{
cristy4c08aed2011-07-01 19:47:50 +0000690#define ClampAlphaPixelComponent(pixel) ClampToQuantum((pixel)->alpha)
691#define ClampBlackPixelComponent(pixel) ClampToQuantum((pixel)->black)
692#define ClampBluePixelComponent(pixel) ClampToQuantum((pixel)->blue)
693#define ClampGreenPixelComponent(pixel) ClampToQuantum((pixel)->green)
694#define ClampRedPixelComponent(pixel) ClampToQuantum((pixel)->red)
cristy3ed852e2009-09-05 21:47:34 +0000695#define ClutImageTag "Clut/Image"
696
cristyfa112112010-01-04 17:48:07 +0000697 CacheView
cristy708333f2011-03-26 01:25:07 +0000698 *clut_view,
cristyfa112112010-01-04 17:48:07 +0000699 *image_view;
700
cristy3ed852e2009-09-05 21:47:34 +0000701 ExceptionInfo
702 *exception;
703
cristy3ed852e2009-09-05 21:47:34 +0000704 MagickBooleanType
705 status;
706
cristybb503372010-05-27 20:51:26 +0000707 MagickOffsetType
708 progress;
709
cristy4c08aed2011-07-01 19:47:50 +0000710 PixelInfo
cristy49f37242011-03-22 18:18:23 +0000711 *clut_map;
712
713 register ssize_t
714 i;
cristy3ed852e2009-09-05 21:47:34 +0000715
cristybb503372010-05-27 20:51:26 +0000716 ssize_t
717 adjust,
718 y;
719
cristy3ed852e2009-09-05 21:47:34 +0000720 assert(image != (Image *) NULL);
721 assert(image->signature == MagickSignature);
722 if (image->debug != MagickFalse)
723 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
724 assert(clut_image != (Image *) NULL);
725 assert(clut_image->signature == MagickSignature);
726 if (SetImageStorageClass(image,DirectClass) == MagickFalse)
727 return(MagickFalse);
cristy4c08aed2011-07-01 19:47:50 +0000728 clut_map=(PixelInfo *) AcquireQuantumMemory(MaxMap+1UL,
cristy49f37242011-03-22 18:18:23 +0000729 sizeof(*clut_map));
cristy4c08aed2011-07-01 19:47:50 +0000730 if (clut_map == (PixelInfo *) NULL)
cristy49f37242011-03-22 18:18:23 +0000731 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
732 image->filename);
cristy3ed852e2009-09-05 21:47:34 +0000733 /*
734 Clut image.
735 */
736 status=MagickTrue;
737 progress=0;
cristybb503372010-05-27 20:51:26 +0000738 adjust=(ssize_t) (clut_image->interpolate == IntegerInterpolatePixel ? 0 : 1);
cristy3ed852e2009-09-05 21:47:34 +0000739 exception=(&image->exception);
cristy708333f2011-03-26 01:25:07 +0000740 clut_view=AcquireCacheView(clut_image);
cristyaf6bc722011-03-25 19:16:14 +0000741#if defined(MAGICKCORE_OPENMP_SUPPORT)
742 #pragma omp parallel for schedule(dynamic,4)
743#endif
cristy49f37242011-03-22 18:18:23 +0000744 for (i=0; i <= (ssize_t) MaxMap; i++)
745 {
cristy4c08aed2011-07-01 19:47:50 +0000746 GetPixelInfo(clut_image,clut_map+i);
747 (void) InterpolatePixelInfo(clut_image,clut_view,
cristy8a7c3e82011-03-26 02:10:53 +0000748 UndefinedInterpolatePixel,QuantumScale*i*(clut_image->columns-adjust),
749 QuantumScale*i*(clut_image->rows-adjust),clut_map+i,exception);
cristy49f37242011-03-22 18:18:23 +0000750 }
cristy708333f2011-03-26 01:25:07 +0000751 clut_view=DestroyCacheView(clut_view);
752 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +0000753#if defined(MAGICKCORE_OPENMP_SUPPORT)
754 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000755#endif
cristybb503372010-05-27 20:51:26 +0000756 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000757 {
cristy4c08aed2011-07-01 19:47:50 +0000758 PixelInfo
cristy3635df22011-03-25 00:16:16 +0000759 pixel;
760
cristy4c08aed2011-07-01 19:47:50 +0000761 register Quantum
cristyc47d1f82009-11-26 01:44:43 +0000762 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000763
cristy8d4629b2010-08-30 17:59:46 +0000764 register ssize_t
765 x;
766
cristy3ed852e2009-09-05 21:47:34 +0000767 if (status == MagickFalse)
768 continue;
769 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +0000770 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +0000771 {
772 status=MagickFalse;
773 continue;
774 }
cristy4c08aed2011-07-01 19:47:50 +0000775 GetPixelInfo(image,&pixel);
cristybb503372010-05-27 20:51:26 +0000776 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000777 {
cristy4c08aed2011-07-01 19:47:50 +0000778 SetPixelInfo(image,q,&pixel);
cristy2b9582a2011-07-04 17:38:56 +0000779 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000780 SetPixelRed(image,ClampRedPixelComponent(clut_map+
781 ScaleQuantumToMap(GetPixelRed(image,q))),q);
cristy2b9582a2011-07-04 17:38:56 +0000782 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000783 SetPixelGreen(image,ClampGreenPixelComponent(clut_map+
784 ScaleQuantumToMap(GetPixelGreen(image,q))),q);
cristy2b9582a2011-07-04 17:38:56 +0000785 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000786 SetPixelBlue(image,ClampBluePixelComponent(clut_map+
787 ScaleQuantumToMap(GetPixelBlue(image,q))),q);
cristy2b9582a2011-07-04 17:38:56 +0000788 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +0000789 (image->colorspace == CMYKColorspace))
790 SetPixelBlack(image,ClampBlackPixelComponent(clut_map+
791 ScaleQuantumToMap(GetPixelBlack(image,q))),q);
cristy2b9582a2011-07-04 17:38:56 +0000792 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy3635df22011-03-25 00:16:16 +0000793 {
794 if (clut_image->matte == MagickFalse)
cristy4c08aed2011-07-01 19:47:50 +0000795 SetPixelAlpha(image,GetPixelInfoIntensity(clut_map+
796 ScaleQuantumToMap((Quantum) GetPixelAlpha(image,q))),q);
cristy3635df22011-03-25 00:16:16 +0000797 else
798 if (image->matte == MagickFalse)
cristy4c08aed2011-07-01 19:47:50 +0000799 SetPixelAlpha(image,ClampAlphaPixelComponent(clut_map+
800 ScaleQuantumToMap((Quantum) GetPixelInfoIntensity(&pixel))),q);
cristy3635df22011-03-25 00:16:16 +0000801 else
cristy4c08aed2011-07-01 19:47:50 +0000802 SetPixelAlpha(image,ClampAlphaPixelComponent(clut_map+
803 ScaleQuantumToMap(GetPixelAlpha(image,q))),q);
cristy3635df22011-03-25 00:16:16 +0000804 }
cristydcfc1ad2011-07-07 16:25:41 +0000805 q+=GetPixelComponents(image);
cristy3ed852e2009-09-05 21:47:34 +0000806 }
807 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
808 status=MagickFalse;
809 if (image->progress_monitor != (MagickProgressMonitor) NULL)
810 {
811 MagickBooleanType
812 proceed;
813
cristyb5d5f722009-11-04 03:03:49 +0000814#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf89cb1d2011-07-07 01:24:37 +0000815 #pragma omp critical (MagickCore_ClutImage)
cristy3ed852e2009-09-05 21:47:34 +0000816#endif
817 proceed=SetImageProgress(image,ClutImageTag,progress++,image->rows);
818 if (proceed == MagickFalse)
819 status=MagickFalse;
820 }
821 }
822 image_view=DestroyCacheView(image_view);
cristy4c08aed2011-07-01 19:47:50 +0000823 clut_map=(PixelInfo *) RelinquishMagickMemory(clut_map);
cristy2b9582a2011-07-04 17:38:56 +0000824 if ((clut_image->matte != MagickFalse) &&
825 ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0))
cristy3ed852e2009-09-05 21:47:34 +0000826 (void) SetImageAlphaChannel(image,ActivateAlphaChannel);
827 return(status);
828}
829
830/*
831%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
832% %
833% %
834% %
835% C o n t r a s t I m a g e %
836% %
837% %
838% %
839%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
840%
841% ContrastImage() enhances the intensity differences between the lighter and
842% darker elements of the image. Set sharpen to a MagickTrue to increase the
843% image contrast otherwise the contrast is reduced.
844%
845% The format of the ContrastImage method is:
846%
847% MagickBooleanType ContrastImage(Image *image,
848% const MagickBooleanType sharpen)
849%
850% A description of each parameter follows:
851%
852% o image: the image.
853%
854% o sharpen: Increase or decrease image contrast.
855%
856*/
857
858static void Contrast(const int sign,Quantum *red,Quantum *green,Quantum *blue)
859{
860 double
861 brightness,
862 hue,
863 saturation;
864
865 /*
866 Enhance contrast: dark color become darker, light color become lighter.
867 */
868 assert(red != (Quantum *) NULL);
869 assert(green != (Quantum *) NULL);
870 assert(blue != (Quantum *) NULL);
871 hue=0.0;
872 saturation=0.0;
873 brightness=0.0;
874 ConvertRGBToHSB(*red,*green,*blue,&hue,&saturation,&brightness);
cristy31ac0f02011-03-12 02:04:47 +0000875 brightness+=0.5*sign*(0.5*(sin((double) (MagickPI*(brightness-0.5)))+1.0)-
cristy4205a3c2010-09-12 20:19:59 +0000876 brightness);
cristy3ed852e2009-09-05 21:47:34 +0000877 if (brightness > 1.0)
878 brightness=1.0;
879 else
880 if (brightness < 0.0)
881 brightness=0.0;
882 ConvertHSBToRGB(hue,saturation,brightness,red,green,blue);
883}
884
885MagickExport MagickBooleanType ContrastImage(Image *image,
886 const MagickBooleanType sharpen)
887{
888#define ContrastImageTag "Contrast/Image"
889
cristyc4c8d132010-01-07 01:58:38 +0000890 CacheView
891 *image_view;
892
cristy3ed852e2009-09-05 21:47:34 +0000893 ExceptionInfo
894 *exception;
895
896 int
897 sign;
898
cristy3ed852e2009-09-05 21:47:34 +0000899 MagickBooleanType
900 status;
901
cristybb503372010-05-27 20:51:26 +0000902 MagickOffsetType
903 progress;
904
905 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000906 i;
907
cristybb503372010-05-27 20:51:26 +0000908 ssize_t
909 y;
910
cristy3ed852e2009-09-05 21:47:34 +0000911 assert(image != (Image *) NULL);
912 assert(image->signature == MagickSignature);
913 if (image->debug != MagickFalse)
914 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
915 sign=sharpen != MagickFalse ? 1 : -1;
916 if (image->storage_class == PseudoClass)
917 {
918 /*
919 Contrast enhance colormap.
920 */
cristybb503372010-05-27 20:51:26 +0000921 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +0000922 Contrast(sign,&image->colormap[i].red,&image->colormap[i].green,
923 &image->colormap[i].blue);
924 }
925 /*
926 Contrast enhance image.
927 */
928 status=MagickTrue;
929 progress=0;
930 exception=(&image->exception);
931 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +0000932#if defined(MAGICKCORE_OPENMP_SUPPORT)
933 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000934#endif
cristybb503372010-05-27 20:51:26 +0000935 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000936 {
cristy5afeab82011-04-30 01:30:09 +0000937 Quantum
938 blue,
939 green,
940 red;
941
cristy4c08aed2011-07-01 19:47:50 +0000942 register Quantum
cristyc47d1f82009-11-26 01:44:43 +0000943 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000944
cristy8d4629b2010-08-30 17:59:46 +0000945 register ssize_t
946 x;
947
cristy3ed852e2009-09-05 21:47:34 +0000948 if (status == MagickFalse)
949 continue;
950 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +0000951 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +0000952 {
953 status=MagickFalse;
954 continue;
955 }
cristybb503372010-05-27 20:51:26 +0000956 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000957 {
cristy4c08aed2011-07-01 19:47:50 +0000958 red=GetPixelRed(image,q);
959 green=GetPixelGreen(image,q);
960 blue=GetPixelBlue(image,q);
cristy5afeab82011-04-30 01:30:09 +0000961 Contrast(sign,&red,&green,&blue);
cristy4c08aed2011-07-01 19:47:50 +0000962 SetPixelRed(image,red,q);
963 SetPixelGreen(image,green,q);
964 SetPixelBlue(image,blue,q);
cristydcfc1ad2011-07-07 16:25:41 +0000965 q+=GetPixelComponents(image);
cristy3ed852e2009-09-05 21:47:34 +0000966 }
967 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
968 status=MagickFalse;
969 if (image->progress_monitor != (MagickProgressMonitor) NULL)
970 {
971 MagickBooleanType
972 proceed;
973
cristyb5d5f722009-11-04 03:03:49 +0000974#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +0000975 #pragma omp critical (MagickCore_ContrastImage)
976#endif
977 proceed=SetImageProgress(image,ContrastImageTag,progress++,image->rows);
978 if (proceed == MagickFalse)
979 status=MagickFalse;
980 }
981 }
982 image_view=DestroyCacheView(image_view);
983 return(status);
984}
985
986/*
987%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
988% %
989% %
990% %
991% C o n t r a s t S t r e t c h I m a g e %
992% %
993% %
994% %
995%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
996%
997% The ContrastStretchImage() is a simple image enhancement technique that
998% attempts to improve the contrast in an image by `stretching' the range of
999% intensity values it contains to span a desired range of values. It differs
1000% from the more sophisticated histogram equalization in that it can only
1001% apply % a linear scaling function to the image pixel values. As a result
1002% the `enhancement' is less harsh.
1003%
1004% The format of the ContrastStretchImage method is:
1005%
1006% MagickBooleanType ContrastStretchImage(Image *image,
1007% const char *levels)
cristy3ed852e2009-09-05 21:47:34 +00001008%
1009% A description of each parameter follows:
1010%
1011% o image: the image.
1012%
cristy3ed852e2009-09-05 21:47:34 +00001013% o black_point: the black point.
1014%
1015% o white_point: the white point.
1016%
1017% o levels: Specify the levels where the black and white points have the
1018% range of 0 to number-of-pixels (e.g. 1%, 10x90%, etc.).
1019%
1020*/
cristy3ed852e2009-09-05 21:47:34 +00001021MagickExport MagickBooleanType ContrastStretchImage(Image *image,
cristy50fbc382011-07-07 02:19:17 +00001022 const double black_point,const double white_point)
cristy3ed852e2009-09-05 21:47:34 +00001023{
1024#define MaxRange(color) ((MagickRealType) ScaleQuantumToMap((Quantum) (color)))
1025#define ContrastStretchImageTag "ContrastStretch/Image"
1026
cristyc4c8d132010-01-07 01:58:38 +00001027 CacheView
1028 *image_view;
1029
cristy3ed852e2009-09-05 21:47:34 +00001030 double
1031 intensity;
1032
1033 ExceptionInfo
1034 *exception;
1035
cristy3ed852e2009-09-05 21:47:34 +00001036 MagickBooleanType
1037 status;
1038
cristybb503372010-05-27 20:51:26 +00001039 MagickOffsetType
1040 progress;
1041
cristy4c08aed2011-07-01 19:47:50 +00001042 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00001043 black,
1044 *histogram,
1045 *stretch_map,
1046 white;
1047
cristybb503372010-05-27 20:51:26 +00001048 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001049 i;
1050
cristybb503372010-05-27 20:51:26 +00001051 ssize_t
1052 y;
1053
cristy3ed852e2009-09-05 21:47:34 +00001054 /*
1055 Allocate histogram and stretch map.
1056 */
1057 assert(image != (Image *) NULL);
1058 assert(image->signature == MagickSignature);
1059 if (image->debug != MagickFalse)
1060 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
cristy4c08aed2011-07-01 19:47:50 +00001061 histogram=(PixelInfo *) AcquireQuantumMemory(MaxMap+1UL,
cristy3ed852e2009-09-05 21:47:34 +00001062 sizeof(*histogram));
cristy4c08aed2011-07-01 19:47:50 +00001063 stretch_map=(PixelInfo *) AcquireQuantumMemory(MaxMap+1UL,
cristy3ed852e2009-09-05 21:47:34 +00001064 sizeof(*stretch_map));
cristy4c08aed2011-07-01 19:47:50 +00001065 if ((histogram == (PixelInfo *) NULL) ||
1066 (stretch_map == (PixelInfo *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00001067 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
1068 image->filename);
1069 /*
1070 Form histogram.
1071 */
1072 status=MagickTrue;
1073 exception=(&image->exception);
1074 (void) ResetMagickMemory(histogram,0,(MaxMap+1)*sizeof(*histogram));
1075 image_view=AcquireCacheView(image);
cristybb503372010-05-27 20:51:26 +00001076 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001077 {
cristy4c08aed2011-07-01 19:47:50 +00001078 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00001079 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00001080
cristybb503372010-05-27 20:51:26 +00001081 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001082 x;
1083
1084 if (status == MagickFalse)
1085 continue;
1086 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00001087 if (p == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001088 {
1089 status=MagickFalse;
1090 continue;
1091 }
cristy50fbc382011-07-07 02:19:17 +00001092 if (image->sync != MagickFalse)
cristybb503372010-05-27 20:51:26 +00001093 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001094 {
1095 Quantum
1096 intensity;
1097
cristy4c08aed2011-07-01 19:47:50 +00001098 intensity=GetPixelIntensity(image,p);
cristy3ed852e2009-09-05 21:47:34 +00001099 histogram[ScaleQuantumToMap(intensity)].red++;
1100 histogram[ScaleQuantumToMap(intensity)].green++;
1101 histogram[ScaleQuantumToMap(intensity)].blue++;
cristy4c08aed2011-07-01 19:47:50 +00001102 histogram[ScaleQuantumToMap(intensity)].black++;
cristydcfc1ad2011-07-07 16:25:41 +00001103 p+=GetPixelComponents(image);
cristy3ed852e2009-09-05 21:47:34 +00001104 }
1105 else
cristybb503372010-05-27 20:51:26 +00001106 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001107 {
cristy2b9582a2011-07-04 17:38:56 +00001108 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001109 histogram[ScaleQuantumToMap(GetPixelRed(image,p))].red++;
cristy2b9582a2011-07-04 17:38:56 +00001110 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001111 histogram[ScaleQuantumToMap(GetPixelGreen(image,p))].green++;
cristy2b9582a2011-07-04 17:38:56 +00001112 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001113 histogram[ScaleQuantumToMap(GetPixelBlue(image,p))].blue++;
cristy2b9582a2011-07-04 17:38:56 +00001114 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00001115 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +00001116 histogram[ScaleQuantumToMap(GetPixelBlack(image,p))].black++;
cristy2b9582a2011-07-04 17:38:56 +00001117 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001118 histogram[ScaleQuantumToMap(GetPixelAlpha(image,p))].alpha++;
cristydcfc1ad2011-07-07 16:25:41 +00001119 p+=GetPixelComponents(image);
cristy3ed852e2009-09-05 21:47:34 +00001120 }
1121 }
1122 /*
1123 Find the histogram boundaries by locating the black/white levels.
1124 */
1125 black.red=0.0;
1126 white.red=MaxRange(QuantumRange);
cristy2b9582a2011-07-04 17:38:56 +00001127 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001128 {
1129 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00001130 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +00001131 {
1132 intensity+=histogram[i].red;
1133 if (intensity > black_point)
1134 break;
1135 }
1136 black.red=(MagickRealType) i;
1137 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00001138 for (i=(ssize_t) MaxMap; i != 0; i--)
cristy3ed852e2009-09-05 21:47:34 +00001139 {
1140 intensity+=histogram[i].red;
1141 if (intensity > ((double) image->columns*image->rows-white_point))
1142 break;
1143 }
1144 white.red=(MagickRealType) i;
1145 }
1146 black.green=0.0;
1147 white.green=MaxRange(QuantumRange);
cristy2b9582a2011-07-04 17:38:56 +00001148 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001149 {
1150 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00001151 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +00001152 {
1153 intensity+=histogram[i].green;
1154 if (intensity > black_point)
1155 break;
1156 }
1157 black.green=(MagickRealType) i;
1158 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00001159 for (i=(ssize_t) MaxMap; i != 0; i--)
cristy3ed852e2009-09-05 21:47:34 +00001160 {
1161 intensity+=histogram[i].green;
1162 if (intensity > ((double) image->columns*image->rows-white_point))
1163 break;
1164 }
1165 white.green=(MagickRealType) i;
1166 }
1167 black.blue=0.0;
1168 white.blue=MaxRange(QuantumRange);
cristy2b9582a2011-07-04 17:38:56 +00001169 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001170 {
1171 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00001172 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +00001173 {
1174 intensity+=histogram[i].blue;
1175 if (intensity > black_point)
1176 break;
1177 }
1178 black.blue=(MagickRealType) i;
1179 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00001180 for (i=(ssize_t) MaxMap; i != 0; i--)
cristy3ed852e2009-09-05 21:47:34 +00001181 {
1182 intensity+=histogram[i].blue;
1183 if (intensity > ((double) image->columns*image->rows-white_point))
1184 break;
1185 }
1186 white.blue=(MagickRealType) i;
1187 }
cristy4c08aed2011-07-01 19:47:50 +00001188 black.alpha=0.0;
1189 white.alpha=MaxRange(QuantumRange);
cristy2b9582a2011-07-04 17:38:56 +00001190 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001191 {
1192 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00001193 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +00001194 {
cristy4c08aed2011-07-01 19:47:50 +00001195 intensity+=histogram[i].alpha;
cristy3ed852e2009-09-05 21:47:34 +00001196 if (intensity > black_point)
1197 break;
1198 }
cristy4c08aed2011-07-01 19:47:50 +00001199 black.alpha=(MagickRealType) i;
cristy3ed852e2009-09-05 21:47:34 +00001200 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00001201 for (i=(ssize_t) MaxMap; i != 0; i--)
cristy3ed852e2009-09-05 21:47:34 +00001202 {
cristy4c08aed2011-07-01 19:47:50 +00001203 intensity+=histogram[i].alpha;
cristy3ed852e2009-09-05 21:47:34 +00001204 if (intensity > ((double) image->columns*image->rows-white_point))
1205 break;
1206 }
cristy4c08aed2011-07-01 19:47:50 +00001207 white.alpha=(MagickRealType) i;
cristy3ed852e2009-09-05 21:47:34 +00001208 }
cristy4c08aed2011-07-01 19:47:50 +00001209 black.black=0.0;
1210 white.black=MaxRange(QuantumRange);
cristy2b9582a2011-07-04 17:38:56 +00001211 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) && (image->colorspace == CMYKColorspace))
cristy3ed852e2009-09-05 21:47:34 +00001212 {
1213 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00001214 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +00001215 {
cristy4c08aed2011-07-01 19:47:50 +00001216 intensity+=histogram[i].black;
cristy3ed852e2009-09-05 21:47:34 +00001217 if (intensity > black_point)
1218 break;
1219 }
cristy4c08aed2011-07-01 19:47:50 +00001220 black.black=(MagickRealType) i;
cristy3ed852e2009-09-05 21:47:34 +00001221 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00001222 for (i=(ssize_t) MaxMap; i != 0; i--)
cristy3ed852e2009-09-05 21:47:34 +00001223 {
cristy4c08aed2011-07-01 19:47:50 +00001224 intensity+=histogram[i].black;
cristy3ed852e2009-09-05 21:47:34 +00001225 if (intensity > ((double) image->columns*image->rows-white_point))
1226 break;
1227 }
cristy4c08aed2011-07-01 19:47:50 +00001228 white.black=(MagickRealType) i;
cristy3ed852e2009-09-05 21:47:34 +00001229 }
cristy4c08aed2011-07-01 19:47:50 +00001230 histogram=(PixelInfo *) RelinquishMagickMemory(histogram);
cristy3ed852e2009-09-05 21:47:34 +00001231 /*
1232 Stretch the histogram to create the stretched image mapping.
1233 */
1234 (void) ResetMagickMemory(stretch_map,0,(MaxMap+1)*sizeof(*stretch_map));
cristyb5d5f722009-11-04 03:03:49 +00001235#if defined(MAGICKCORE_OPENMP_SUPPORT)
1236 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001237#endif
cristybb503372010-05-27 20:51:26 +00001238 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +00001239 {
cristy2b9582a2011-07-04 17:38:56 +00001240 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001241 {
cristybb503372010-05-27 20:51:26 +00001242 if (i < (ssize_t) black.red)
cristy3ed852e2009-09-05 21:47:34 +00001243 stretch_map[i].red=0.0;
1244 else
cristybb503372010-05-27 20:51:26 +00001245 if (i > (ssize_t) white.red)
cristy3ed852e2009-09-05 21:47:34 +00001246 stretch_map[i].red=(MagickRealType) QuantumRange;
1247 else
1248 if (black.red != white.red)
1249 stretch_map[i].red=(MagickRealType) ScaleMapToQuantum(
1250 (MagickRealType) (MaxMap*(i-black.red)/(white.red-black.red)));
1251 }
cristy2b9582a2011-07-04 17:38:56 +00001252 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001253 {
cristybb503372010-05-27 20:51:26 +00001254 if (i < (ssize_t) black.green)
cristy3ed852e2009-09-05 21:47:34 +00001255 stretch_map[i].green=0.0;
1256 else
cristybb503372010-05-27 20:51:26 +00001257 if (i > (ssize_t) white.green)
cristy3ed852e2009-09-05 21:47:34 +00001258 stretch_map[i].green=(MagickRealType) QuantumRange;
1259 else
1260 if (black.green != white.green)
1261 stretch_map[i].green=(MagickRealType) ScaleMapToQuantum(
1262 (MagickRealType) (MaxMap*(i-black.green)/(white.green-
1263 black.green)));
1264 }
cristy2b9582a2011-07-04 17:38:56 +00001265 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001266 {
cristybb503372010-05-27 20:51:26 +00001267 if (i < (ssize_t) black.blue)
cristy3ed852e2009-09-05 21:47:34 +00001268 stretch_map[i].blue=0.0;
1269 else
cristybb503372010-05-27 20:51:26 +00001270 if (i > (ssize_t) white.blue)
cristy3ed852e2009-09-05 21:47:34 +00001271 stretch_map[i].blue=(MagickRealType) QuantumRange;
1272 else
1273 if (black.blue != white.blue)
1274 stretch_map[i].blue=(MagickRealType) ScaleMapToQuantum(
1275 (MagickRealType) (MaxMap*(i-black.blue)/(white.blue-
1276 black.blue)));
1277 }
cristy2b9582a2011-07-04 17:38:56 +00001278 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001279 {
cristy4c08aed2011-07-01 19:47:50 +00001280 if (i < (ssize_t) black.alpha)
1281 stretch_map[i].alpha=0.0;
cristy3ed852e2009-09-05 21:47:34 +00001282 else
cristy4c08aed2011-07-01 19:47:50 +00001283 if (i > (ssize_t) white.alpha)
1284 stretch_map[i].alpha=(MagickRealType) QuantumRange;
cristy3ed852e2009-09-05 21:47:34 +00001285 else
cristy4c08aed2011-07-01 19:47:50 +00001286 if (black.alpha != white.alpha)
1287 stretch_map[i].alpha=(MagickRealType) ScaleMapToQuantum(
1288 (MagickRealType) (MaxMap*(i-black.alpha)/(white.alpha-
1289 black.alpha)));
cristy3ed852e2009-09-05 21:47:34 +00001290 }
cristy2b9582a2011-07-04 17:38:56 +00001291 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00001292 (image->colorspace == CMYKColorspace))
1293 {
cristy4c08aed2011-07-01 19:47:50 +00001294 if (i < (ssize_t) black.black)
1295 stretch_map[i].black=0.0;
cristy3ed852e2009-09-05 21:47:34 +00001296 else
cristy4c08aed2011-07-01 19:47:50 +00001297 if (i > (ssize_t) white.black)
1298 stretch_map[i].black=(MagickRealType) QuantumRange;
cristy3ed852e2009-09-05 21:47:34 +00001299 else
cristy4c08aed2011-07-01 19:47:50 +00001300 if (black.black != white.black)
1301 stretch_map[i].black=(MagickRealType) ScaleMapToQuantum(
1302 (MagickRealType) (MaxMap*(i-black.black)/(white.black-
1303 black.black)));
cristy3ed852e2009-09-05 21:47:34 +00001304 }
1305 }
1306 /*
1307 Stretch the image.
1308 */
cristy2b9582a2011-07-04 17:38:56 +00001309 if (((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0) || (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00001310 (image->colorspace == CMYKColorspace)))
1311 image->storage_class=DirectClass;
1312 if (image->storage_class == PseudoClass)
1313 {
1314 /*
1315 Stretch colormap.
1316 */
cristyb5d5f722009-11-04 03:03:49 +00001317#if defined(MAGICKCORE_OPENMP_SUPPORT)
1318 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001319#endif
cristybb503372010-05-27 20:51:26 +00001320 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00001321 {
cristy2b9582a2011-07-04 17:38:56 +00001322 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001323 {
1324 if (black.red != white.red)
cristyce70c172010-01-07 17:15:30 +00001325 image->colormap[i].red=ClampToQuantum(stretch_map[
cristy3ed852e2009-09-05 21:47:34 +00001326 ScaleQuantumToMap(image->colormap[i].red)].red);
1327 }
cristy2b9582a2011-07-04 17:38:56 +00001328 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001329 {
1330 if (black.green != white.green)
cristyce70c172010-01-07 17:15:30 +00001331 image->colormap[i].green=ClampToQuantum(stretch_map[
cristy3ed852e2009-09-05 21:47:34 +00001332 ScaleQuantumToMap(image->colormap[i].green)].green);
1333 }
cristy2b9582a2011-07-04 17:38:56 +00001334 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001335 {
1336 if (black.blue != white.blue)
cristyce70c172010-01-07 17:15:30 +00001337 image->colormap[i].blue=ClampToQuantum(stretch_map[
cristy3ed852e2009-09-05 21:47:34 +00001338 ScaleQuantumToMap(image->colormap[i].blue)].blue);
1339 }
cristy2b9582a2011-07-04 17:38:56 +00001340 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001341 {
cristy4c08aed2011-07-01 19:47:50 +00001342 if (black.alpha != white.alpha)
1343 image->colormap[i].alpha=ClampToQuantum(stretch_map[
1344 ScaleQuantumToMap(image->colormap[i].alpha)].alpha);
cristy3ed852e2009-09-05 21:47:34 +00001345 }
1346 }
1347 }
1348 /*
1349 Stretch image.
1350 */
1351 status=MagickTrue;
1352 progress=0;
cristyb5d5f722009-11-04 03:03:49 +00001353#if defined(MAGICKCORE_OPENMP_SUPPORT)
1354 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001355#endif
cristybb503372010-05-27 20:51:26 +00001356 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001357 {
cristy4c08aed2011-07-01 19:47:50 +00001358 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00001359 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001360
cristy8d4629b2010-08-30 17:59:46 +00001361 register ssize_t
1362 x;
1363
cristy3ed852e2009-09-05 21:47:34 +00001364 if (status == MagickFalse)
1365 continue;
1366 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00001367 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001368 {
1369 status=MagickFalse;
1370 continue;
1371 }
cristybb503372010-05-27 20:51:26 +00001372 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001373 {
cristy2b9582a2011-07-04 17:38:56 +00001374 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001375 {
1376 if (black.red != white.red)
cristy4c08aed2011-07-01 19:47:50 +00001377 SetPixelRed(image,ClampToQuantum(stretch_map[ScaleQuantumToMap(
1378 GetPixelRed(image,q))].red),q);
cristy3ed852e2009-09-05 21:47:34 +00001379 }
cristy2b9582a2011-07-04 17:38:56 +00001380 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001381 {
1382 if (black.green != white.green)
cristy4c08aed2011-07-01 19:47:50 +00001383 SetPixelGreen(image,ClampToQuantum(stretch_map[ScaleQuantumToMap(
1384 GetPixelGreen(image,q))].green),q);
cristy3ed852e2009-09-05 21:47:34 +00001385 }
cristy2b9582a2011-07-04 17:38:56 +00001386 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001387 {
1388 if (black.blue != white.blue)
cristy4c08aed2011-07-01 19:47:50 +00001389 SetPixelBlue(image,ClampToQuantum(stretch_map[ScaleQuantumToMap(
1390 GetPixelBlue(image,q))].blue),q);
1391 }
cristy2b9582a2011-07-04 17:38:56 +00001392 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +00001393 (image->colorspace == CMYKColorspace))
1394 {
1395 if (black.black != white.black)
1396 SetPixelBlack(image,ClampToQuantum(stretch_map[ScaleQuantumToMap(
1397 GetPixelBlack(image,q))].black),q);
cristy3ed852e2009-09-05 21:47:34 +00001398 }
cristy2b9582a2011-07-04 17:38:56 +00001399 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001400 {
cristy4c08aed2011-07-01 19:47:50 +00001401 if (black.alpha != white.alpha)
1402 SetPixelAlpha(image,ClampToQuantum(stretch_map[ScaleQuantumToMap(
1403 GetPixelAlpha(image,q))].alpha),q);
cristy3ed852e2009-09-05 21:47:34 +00001404 }
cristydcfc1ad2011-07-07 16:25:41 +00001405 q+=GetPixelComponents(image);
cristy3ed852e2009-09-05 21:47:34 +00001406 }
1407 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
1408 status=MagickFalse;
1409 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1410 {
1411 MagickBooleanType
1412 proceed;
1413
cristyb5d5f722009-11-04 03:03:49 +00001414#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy50fbc382011-07-07 02:19:17 +00001415 #pragma omp critical (MagickCore_ContrastStretchImage)
cristy3ed852e2009-09-05 21:47:34 +00001416#endif
1417 proceed=SetImageProgress(image,ContrastStretchImageTag,progress++,
1418 image->rows);
1419 if (proceed == MagickFalse)
1420 status=MagickFalse;
1421 }
1422 }
1423 image_view=DestroyCacheView(image_view);
cristy4c08aed2011-07-01 19:47:50 +00001424 stretch_map=(PixelInfo *) RelinquishMagickMemory(stretch_map);
cristy3ed852e2009-09-05 21:47:34 +00001425 return(status);
1426}
1427
1428/*
1429%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1430% %
1431% %
1432% %
1433% E n h a n c e I m a g e %
1434% %
1435% %
1436% %
1437%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1438%
1439% EnhanceImage() applies a digital filter that improves the quality of a
1440% noisy image.
1441%
1442% The format of the EnhanceImage method is:
1443%
1444% Image *EnhanceImage(const Image *image,ExceptionInfo *exception)
1445%
1446% A description of each parameter follows:
1447%
1448% o image: the image.
1449%
1450% o exception: return any errors or warnings in this structure.
1451%
1452*/
1453MagickExport Image *EnhanceImage(const Image *image,ExceptionInfo *exception)
1454{
1455#define Enhance(weight) \
cristy4c08aed2011-07-01 19:47:50 +00001456 mean=((MagickRealType) GetPixelRed(image,r)+pixel.red)/2; \
1457 distance=(MagickRealType) GetPixelRed(image,r)-(MagickRealType) pixel.red; \
cristy3ed852e2009-09-05 21:47:34 +00001458 distance_squared=QuantumScale*(2.0*((MagickRealType) QuantumRange+1.0)+ \
1459 mean)*distance*distance; \
cristy4c08aed2011-07-01 19:47:50 +00001460 mean=((MagickRealType) GetPixelGreen(image,r)+pixel.green)/2; \
1461 distance=(MagickRealType) GetPixelGreen(image,r)- \
1462 (MagickRealType) pixel.green; \
cristy3ed852e2009-09-05 21:47:34 +00001463 distance_squared+=4.0*distance*distance; \
cristy4c08aed2011-07-01 19:47:50 +00001464 mean=((MagickRealType) GetPixelBlue(image,r)+pixel.blue)/2; \
1465 distance=(MagickRealType) GetPixelBlue(image,r)- \
1466 (MagickRealType) pixel.blue; \
cristy3ed852e2009-09-05 21:47:34 +00001467 distance_squared+=QuantumScale*(3.0*((MagickRealType) \
1468 QuantumRange+1.0)-1.0-mean)*distance*distance; \
cristy4c08aed2011-07-01 19:47:50 +00001469 mean=((MagickRealType) GetPixelAlpha(image,r)+pixel.alpha)/2; \
1470 distance=(MagickRealType) GetPixelAlpha(image,r)-(MagickRealType) pixel.alpha; \
cristy3ed852e2009-09-05 21:47:34 +00001471 distance_squared+=QuantumScale*(3.0*((MagickRealType) \
1472 QuantumRange+1.0)-1.0-mean)*distance*distance; \
1473 if (distance_squared < ((MagickRealType) QuantumRange*(MagickRealType) \
1474 QuantumRange/25.0f)) \
1475 { \
cristy4c08aed2011-07-01 19:47:50 +00001476 aggregate.red+=(weight)*GetPixelRed(image,r); \
1477 aggregate.green+=(weight)*GetPixelGreen(image,r); \
1478 aggregate.blue+=(weight)*GetPixelBlue(image,r); \
1479 aggregate.alpha+=(weight)*GetPixelAlpha(image,r); \
cristy3ed852e2009-09-05 21:47:34 +00001480 total_weight+=(weight); \
1481 } \
1482 r++;
1483#define EnhanceImageTag "Enhance/Image"
1484
cristyc4c8d132010-01-07 01:58:38 +00001485 CacheView
1486 *enhance_view,
1487 *image_view;
1488
cristy3ed852e2009-09-05 21:47:34 +00001489 Image
1490 *enhance_image;
1491
cristy3ed852e2009-09-05 21:47:34 +00001492 MagickBooleanType
1493 status;
1494
cristybb503372010-05-27 20:51:26 +00001495 MagickOffsetType
1496 progress;
1497
cristy4c08aed2011-07-01 19:47:50 +00001498 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00001499 zero;
1500
cristybb503372010-05-27 20:51:26 +00001501 ssize_t
1502 y;
1503
cristy3ed852e2009-09-05 21:47:34 +00001504 /*
1505 Initialize enhanced image attributes.
1506 */
1507 assert(image != (const Image *) NULL);
1508 assert(image->signature == MagickSignature);
1509 if (image->debug != MagickFalse)
1510 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1511 assert(exception != (ExceptionInfo *) NULL);
1512 assert(exception->signature == MagickSignature);
1513 if ((image->columns < 5) || (image->rows < 5))
1514 return((Image *) NULL);
1515 enhance_image=CloneImage(image,image->columns,image->rows,MagickTrue,
1516 exception);
1517 if (enhance_image == (Image *) NULL)
1518 return((Image *) NULL);
1519 if (SetImageStorageClass(enhance_image,DirectClass) == MagickFalse)
1520 {
1521 InheritException(exception,&enhance_image->exception);
1522 enhance_image=DestroyImage(enhance_image);
1523 return((Image *) NULL);
1524 }
1525 /*
1526 Enhance image.
1527 */
1528 status=MagickTrue;
1529 progress=0;
1530 (void) ResetMagickMemory(&zero,0,sizeof(zero));
1531 image_view=AcquireCacheView(image);
1532 enhance_view=AcquireCacheView(enhance_image);
cristyb5d5f722009-11-04 03:03:49 +00001533#if defined(MAGICKCORE_OPENMP_SUPPORT)
1534 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001535#endif
cristybb503372010-05-27 20:51:26 +00001536 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001537 {
cristy4c08aed2011-07-01 19:47:50 +00001538 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00001539 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00001540
cristy4c08aed2011-07-01 19:47:50 +00001541 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00001542 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001543
cristy8d4629b2010-08-30 17:59:46 +00001544 register ssize_t
1545 x;
1546
cristy3ed852e2009-09-05 21:47:34 +00001547 /*
1548 Read another scan line.
1549 */
1550 if (status == MagickFalse)
1551 continue;
1552 p=GetCacheViewVirtualPixels(image_view,-2,y-2,image->columns+4,5,exception);
1553 q=QueueCacheViewAuthenticPixels(enhance_view,0,y,enhance_image->columns,1,
1554 exception);
cristy4c08aed2011-07-01 19:47:50 +00001555 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00001556 {
1557 status=MagickFalse;
1558 continue;
1559 }
cristybb503372010-05-27 20:51:26 +00001560 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001561 {
cristy4c08aed2011-07-01 19:47:50 +00001562 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00001563 aggregate;
1564
1565 MagickRealType
1566 distance,
1567 distance_squared,
1568 mean,
1569 total_weight;
1570
1571 PixelPacket
1572 pixel;
1573
cristy4c08aed2011-07-01 19:47:50 +00001574 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00001575 *restrict r;
cristy3ed852e2009-09-05 21:47:34 +00001576
1577 /*
1578 Compute weighted average of target pixel color components.
1579 */
1580 aggregate=zero;
1581 total_weight=0.0;
1582 r=p+2*(image->columns+4)+2;
cristy4c08aed2011-07-01 19:47:50 +00001583 GetPixelPacket(image,r,&pixel);
cristy3ed852e2009-09-05 21:47:34 +00001584 r=p;
1585 Enhance(5.0); Enhance(8.0); Enhance(10.0); Enhance(8.0); Enhance(5.0);
1586 r=p+(image->columns+4);
1587 Enhance(8.0); Enhance(20.0); Enhance(40.0); Enhance(20.0); Enhance(8.0);
1588 r=p+2*(image->columns+4);
1589 Enhance(10.0); Enhance(40.0); Enhance(80.0); Enhance(40.0); Enhance(10.0);
1590 r=p+3*(image->columns+4);
1591 Enhance(8.0); Enhance(20.0); Enhance(40.0); Enhance(20.0); Enhance(8.0);
1592 r=p+4*(image->columns+4);
1593 Enhance(5.0); Enhance(8.0); Enhance(10.0); Enhance(8.0); Enhance(5.0);
cristy4c08aed2011-07-01 19:47:50 +00001594 SetPixelRed(enhance_image,(Quantum) ((aggregate.red+
1595 (total_weight/2)-1)/total_weight),q);
1596 SetPixelGreen(enhance_image,(Quantum) ((aggregate.green+
1597 (total_weight/2)-1)/total_weight),q);
1598 SetPixelBlue(enhance_image,(Quantum) ((aggregate.blue+
1599 (total_weight/2)-1)/total_weight),q);
1600 SetPixelAlpha(enhance_image,(Quantum) ((aggregate.alpha+
1601 (total_weight/2)-1)/total_weight),q);
cristydcfc1ad2011-07-07 16:25:41 +00001602 p+=GetPixelComponents(image);
1603 q+=GetPixelComponents(enhance_image);
cristy3ed852e2009-09-05 21:47:34 +00001604 }
1605 if (SyncCacheViewAuthenticPixels(enhance_view,exception) == MagickFalse)
1606 status=MagickFalse;
1607 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1608 {
1609 MagickBooleanType
1610 proceed;
1611
cristyb5d5f722009-11-04 03:03:49 +00001612#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00001613 #pragma omp critical (MagickCore_EnhanceImage)
1614#endif
1615 proceed=SetImageProgress(image,EnhanceImageTag,progress++,image->rows);
1616 if (proceed == MagickFalse)
1617 status=MagickFalse;
1618 }
1619 }
1620 enhance_view=DestroyCacheView(enhance_view);
1621 image_view=DestroyCacheView(image_view);
1622 return(enhance_image);
1623}
1624
1625/*
1626%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1627% %
1628% %
1629% %
1630% E q u a l i z e I m a g e %
1631% %
1632% %
1633% %
1634%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1635%
1636% EqualizeImage() applies a histogram equalization to the image.
1637%
1638% The format of the EqualizeImage method is:
1639%
1640% MagickBooleanType EqualizeImage(Image *image)
cristy3ed852e2009-09-05 21:47:34 +00001641%
1642% A description of each parameter follows:
1643%
1644% o image: the image.
1645%
1646% o channel: the channel.
1647%
1648*/
cristy3ed852e2009-09-05 21:47:34 +00001649MagickExport MagickBooleanType EqualizeImage(Image *image)
1650{
cristy3ed852e2009-09-05 21:47:34 +00001651#define EqualizeImageTag "Equalize/Image"
1652
cristyc4c8d132010-01-07 01:58:38 +00001653 CacheView
1654 *image_view;
1655
cristy3ed852e2009-09-05 21:47:34 +00001656 ExceptionInfo
1657 *exception;
1658
cristy3ed852e2009-09-05 21:47:34 +00001659 MagickBooleanType
1660 status;
1661
cristybb503372010-05-27 20:51:26 +00001662 MagickOffsetType
1663 progress;
1664
cristy4c08aed2011-07-01 19:47:50 +00001665 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00001666 black,
1667 *equalize_map,
1668 *histogram,
1669 intensity,
1670 *map,
1671 white;
1672
cristybb503372010-05-27 20:51:26 +00001673 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001674 i;
1675
cristybb503372010-05-27 20:51:26 +00001676 ssize_t
1677 y;
1678
cristy3ed852e2009-09-05 21:47:34 +00001679 /*
1680 Allocate and initialize histogram arrays.
1681 */
1682 assert(image != (Image *) NULL);
1683 assert(image->signature == MagickSignature);
1684 if (image->debug != MagickFalse)
1685 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
cristy4c08aed2011-07-01 19:47:50 +00001686 equalize_map=(PixelInfo *) AcquireQuantumMemory(MaxMap+1UL,
cristy3ed852e2009-09-05 21:47:34 +00001687 sizeof(*equalize_map));
cristy4c08aed2011-07-01 19:47:50 +00001688 histogram=(PixelInfo *) AcquireQuantumMemory(MaxMap+1UL,
cristy3ed852e2009-09-05 21:47:34 +00001689 sizeof(*histogram));
cristy4c08aed2011-07-01 19:47:50 +00001690 map=(PixelInfo *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*map));
1691 if ((equalize_map == (PixelInfo *) NULL) ||
1692 (histogram == (PixelInfo *) NULL) ||
1693 (map == (PixelInfo *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00001694 {
cristy4c08aed2011-07-01 19:47:50 +00001695 if (map != (PixelInfo *) NULL)
1696 map=(PixelInfo *) RelinquishMagickMemory(map);
1697 if (histogram != (PixelInfo *) NULL)
1698 histogram=(PixelInfo *) RelinquishMagickMemory(histogram);
1699 if (equalize_map != (PixelInfo *) NULL)
1700 equalize_map=(PixelInfo *) RelinquishMagickMemory(equalize_map);
cristy3ed852e2009-09-05 21:47:34 +00001701 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
1702 image->filename);
1703 }
1704 /*
1705 Form histogram.
1706 */
1707 (void) ResetMagickMemory(histogram,0,(MaxMap+1)*sizeof(*histogram));
1708 exception=(&image->exception);
cristybb503372010-05-27 20:51:26 +00001709 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001710 {
cristy4c08aed2011-07-01 19:47:50 +00001711 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00001712 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00001713
cristybb503372010-05-27 20:51:26 +00001714 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001715 x;
1716
1717 p=GetVirtualPixels(image,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00001718 if (p == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001719 break;
cristybb503372010-05-27 20:51:26 +00001720 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001721 {
cristy2b9582a2011-07-04 17:38:56 +00001722 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001723 histogram[ScaleQuantumToMap(GetPixelRed(image,p))].red++;
cristy2b9582a2011-07-04 17:38:56 +00001724 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001725 histogram[ScaleQuantumToMap(GetPixelGreen(image,p))].green++;
cristy2b9582a2011-07-04 17:38:56 +00001726 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001727 histogram[ScaleQuantumToMap(GetPixelBlue(image,p))].blue++;
cristy2b9582a2011-07-04 17:38:56 +00001728 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00001729 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +00001730 histogram[ScaleQuantumToMap(GetPixelBlack(image,p))].black++;
cristy2b9582a2011-07-04 17:38:56 +00001731 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001732 histogram[ScaleQuantumToMap(GetPixelAlpha(image,p))].alpha++;
cristydcfc1ad2011-07-07 16:25:41 +00001733 p+=GetPixelComponents(image);
cristy3ed852e2009-09-05 21:47:34 +00001734 }
1735 }
1736 /*
1737 Integrate the histogram to get the equalization map.
1738 */
1739 (void) ResetMagickMemory(&intensity,0,sizeof(intensity));
cristybb503372010-05-27 20:51:26 +00001740 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +00001741 {
cristy2b9582a2011-07-04 17:38:56 +00001742 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001743 intensity.red+=histogram[i].red;
cristy2b9582a2011-07-04 17:38:56 +00001744 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001745 intensity.green+=histogram[i].green;
cristy2b9582a2011-07-04 17:38:56 +00001746 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001747 intensity.blue+=histogram[i].blue;
cristy2b9582a2011-07-04 17:38:56 +00001748 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00001749 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +00001750 intensity.black+=histogram[i].black;
cristy2b9582a2011-07-04 17:38:56 +00001751 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001752 intensity.alpha+=histogram[i].alpha;
cristy3ed852e2009-09-05 21:47:34 +00001753 map[i]=intensity;
1754 }
1755 black=map[0];
1756 white=map[(int) MaxMap];
1757 (void) ResetMagickMemory(equalize_map,0,(MaxMap+1)*sizeof(*equalize_map));
cristyb5d5f722009-11-04 03:03:49 +00001758#if defined(MAGICKCORE_OPENMP_SUPPORT)
1759 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001760#endif
cristybb503372010-05-27 20:51:26 +00001761 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +00001762 {
cristy2b9582a2011-07-04 17:38:56 +00001763 if (((GetPixelRedTraits(image) & ActivePixelTrait) != 0) &&
1764 (white.red != black.red))
cristy3ed852e2009-09-05 21:47:34 +00001765 equalize_map[i].red=(MagickRealType) ScaleMapToQuantum((MagickRealType)
1766 ((MaxMap*(map[i].red-black.red))/(white.red-black.red)));
cristy2b9582a2011-07-04 17:38:56 +00001767 if (((GetPixelGreenTraits(image) & ActivePixelTrait) != 0) &&
1768 (white.green != black.green))
cristy3ed852e2009-09-05 21:47:34 +00001769 equalize_map[i].green=(MagickRealType) ScaleMapToQuantum((MagickRealType)
1770 ((MaxMap*(map[i].green-black.green))/(white.green-black.green)));
cristy2b9582a2011-07-04 17:38:56 +00001771 if (((GetPixelBlueTraits(image) & ActivePixelTrait) != 0) &&
1772 (white.blue != black.blue))
cristy3ed852e2009-09-05 21:47:34 +00001773 equalize_map[i].blue=(MagickRealType) ScaleMapToQuantum((MagickRealType)
1774 ((MaxMap*(map[i].blue-black.blue))/(white.blue-black.blue)));
cristy2b9582a2011-07-04 17:38:56 +00001775 if ((((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00001776 (image->colorspace == CMYKColorspace)) &&
cristy4c08aed2011-07-01 19:47:50 +00001777 (white.black != black.black))
1778 equalize_map[i].black=(MagickRealType) ScaleMapToQuantum((MagickRealType)
1779 ((MaxMap*(map[i].black-black.black))/(white.black-black.black)));
cristy2b9582a2011-07-04 17:38:56 +00001780 if (((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0) &&
1781 (white.alpha != black.alpha))
cristy4c08aed2011-07-01 19:47:50 +00001782 equalize_map[i].alpha=(MagickRealType) ScaleMapToQuantum(
1783 (MagickRealType) ((MaxMap*(map[i].alpha-black.alpha))/
1784 (white.alpha-black.alpha)));
cristy3ed852e2009-09-05 21:47:34 +00001785 }
cristy4c08aed2011-07-01 19:47:50 +00001786 histogram=(PixelInfo *) RelinquishMagickMemory(histogram);
1787 map=(PixelInfo *) RelinquishMagickMemory(map);
cristy3ed852e2009-09-05 21:47:34 +00001788 if (image->storage_class == PseudoClass)
1789 {
1790 /*
1791 Equalize colormap.
1792 */
cristyb5d5f722009-11-04 03:03:49 +00001793#if defined(MAGICKCORE_OPENMP_SUPPORT)
1794 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001795#endif
cristybb503372010-05-27 20:51:26 +00001796 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00001797 {
cristy2b9582a2011-07-04 17:38:56 +00001798 if (((GetPixelRedTraits(image) & ActivePixelTrait) != 0) &&
1799 (white.red != black.red))
cristyce70c172010-01-07 17:15:30 +00001800 image->colormap[i].red=ClampToQuantum(equalize_map[
cristy3ed852e2009-09-05 21:47:34 +00001801 ScaleQuantumToMap(image->colormap[i].red)].red);
cristy2b9582a2011-07-04 17:38:56 +00001802 if (((GetPixelGreenTraits(image) & ActivePixelTrait) != 0) &&
1803 (white.green != black.green))
cristyce70c172010-01-07 17:15:30 +00001804 image->colormap[i].green=ClampToQuantum(equalize_map[
cristy3ed852e2009-09-05 21:47:34 +00001805 ScaleQuantumToMap(image->colormap[i].green)].green);
cristy2b9582a2011-07-04 17:38:56 +00001806 if (((GetPixelBlueTraits(image) & ActivePixelTrait) != 0) &&
1807 (white.blue != black.blue))
cristyce70c172010-01-07 17:15:30 +00001808 image->colormap[i].blue=ClampToQuantum(equalize_map[
cristy3ed852e2009-09-05 21:47:34 +00001809 ScaleQuantumToMap(image->colormap[i].blue)].blue);
cristy2b9582a2011-07-04 17:38:56 +00001810 if (((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +00001811 (white.alpha != black.alpha))
1812 image->colormap[i].alpha=ClampToQuantum(equalize_map[
1813 ScaleQuantumToMap(image->colormap[i].alpha)].alpha);
cristy3ed852e2009-09-05 21:47:34 +00001814 }
1815 }
1816 /*
1817 Equalize image.
1818 */
1819 status=MagickTrue;
1820 progress=0;
1821 exception=(&image->exception);
1822 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00001823#if defined(MAGICKCORE_OPENMP_SUPPORT)
1824 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001825#endif
cristybb503372010-05-27 20:51:26 +00001826 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001827 {
cristy4c08aed2011-07-01 19:47:50 +00001828 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00001829 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001830
cristy8d4629b2010-08-30 17:59:46 +00001831 register ssize_t
1832 x;
1833
cristy3ed852e2009-09-05 21:47:34 +00001834 if (status == MagickFalse)
1835 continue;
1836 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00001837 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001838 {
1839 status=MagickFalse;
1840 continue;
1841 }
cristybb503372010-05-27 20:51:26 +00001842 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001843 {
cristy2b9582a2011-07-04 17:38:56 +00001844 if (((GetPixelRedTraits(image) & ActivePixelTrait) != 0) &&
1845 (white.red != black.red))
cristy4c08aed2011-07-01 19:47:50 +00001846 SetPixelRed(image,ClampToQuantum(equalize_map[
1847 ScaleQuantumToMap(GetPixelRed(image,q))].red),q);
cristy2b9582a2011-07-04 17:38:56 +00001848 if (((GetPixelGreenTraits(image) & ActivePixelTrait) != 0) &&
1849 (white.green != black.green))
cristy4c08aed2011-07-01 19:47:50 +00001850 SetPixelGreen(image,ClampToQuantum(equalize_map[
1851 ScaleQuantumToMap(GetPixelGreen(image,q))].green),q);
cristy2b9582a2011-07-04 17:38:56 +00001852 if (((GetPixelBlueTraits(image) & ActivePixelTrait) != 0) &&
1853 (white.blue != black.blue))
cristy4c08aed2011-07-01 19:47:50 +00001854 SetPixelBlue(image,ClampToQuantum(equalize_map[
1855 ScaleQuantumToMap(GetPixelBlue(image,q))].blue),q);
cristy2b9582a2011-07-04 17:38:56 +00001856 if ((((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00001857 (image->colorspace == CMYKColorspace)) &&
cristy4c08aed2011-07-01 19:47:50 +00001858 (white.black != black.black))
1859 SetPixelBlack(image,ClampToQuantum(equalize_map[
1860 ScaleQuantumToMap(GetPixelBlack(image,q))].black),q);
cristy2b9582a2011-07-04 17:38:56 +00001861 if (((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0) &&
1862 (white.alpha != black.alpha))
cristy4c08aed2011-07-01 19:47:50 +00001863 SetPixelAlpha(image,ClampToQuantum(equalize_map[
1864 ScaleQuantumToMap(GetPixelAlpha(image,q))].alpha),q);
cristydcfc1ad2011-07-07 16:25:41 +00001865 q+=GetPixelComponents(image);
cristy3ed852e2009-09-05 21:47:34 +00001866 }
1867 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
1868 status=MagickFalse;
1869 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1870 {
1871 MagickBooleanType
1872 proceed;
1873
cristyb5d5f722009-11-04 03:03:49 +00001874#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy50fbc382011-07-07 02:19:17 +00001875 #pragma omp critical (MagickCore_EqualizeImage)
cristy3ed852e2009-09-05 21:47:34 +00001876#endif
1877 proceed=SetImageProgress(image,EqualizeImageTag,progress++,image->rows);
1878 if (proceed == MagickFalse)
1879 status=MagickFalse;
1880 }
1881 }
1882 image_view=DestroyCacheView(image_view);
cristy4c08aed2011-07-01 19:47:50 +00001883 equalize_map=(PixelInfo *) RelinquishMagickMemory(equalize_map);
cristy3ed852e2009-09-05 21:47:34 +00001884 return(status);
1885}
1886
1887/*
1888%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1889% %
1890% %
1891% %
1892% G a m m a I m a g e %
1893% %
1894% %
1895% %
1896%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1897%
1898% GammaImage() gamma-corrects a particular image channel. The same
1899% image viewed on different devices will have perceptual differences in the
1900% way the image's intensities are represented on the screen. Specify
1901% individual gamma levels for the red, green, and blue channels, or adjust
1902% all three with the gamma parameter. Values typically range from 0.8 to 2.3.
1903%
1904% You can also reduce the influence of a particular channel with a gamma
1905% value of 0.
1906%
1907% The format of the GammaImage method is:
1908%
cristy50fbc382011-07-07 02:19:17 +00001909% MagickBooleanType GammaImage(Image *image,const double gamma)
cristy3ed852e2009-09-05 21:47:34 +00001910%
1911% A description of each parameter follows:
1912%
1913% o image: the image.
1914%
cristya6360142011-03-23 23:08:04 +00001915% o level: the image gamma as a string (e.g. 1.6,1.2,1.0).
1916%
cristy3ed852e2009-09-05 21:47:34 +00001917% o gamma: the image gamma.
1918%
1919*/
cristy50fbc382011-07-07 02:19:17 +00001920MagickExport MagickBooleanType GammaImage(Image *image,const double gamma)
cristy3ed852e2009-09-05 21:47:34 +00001921{
1922#define GammaCorrectImageTag "GammaCorrect/Image"
1923
cristyc4c8d132010-01-07 01:58:38 +00001924 CacheView
1925 *image_view;
1926
cristy3ed852e2009-09-05 21:47:34 +00001927 ExceptionInfo
1928 *exception;
1929
cristy3ed852e2009-09-05 21:47:34 +00001930 MagickBooleanType
1931 status;
1932
cristybb503372010-05-27 20:51:26 +00001933 MagickOffsetType
1934 progress;
1935
cristy3ed852e2009-09-05 21:47:34 +00001936 Quantum
1937 *gamma_map;
1938
cristybb503372010-05-27 20:51:26 +00001939 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001940 i;
1941
cristybb503372010-05-27 20:51:26 +00001942 ssize_t
1943 y;
1944
cristy3ed852e2009-09-05 21:47:34 +00001945 /*
1946 Allocate and initialize gamma maps.
1947 */
1948 assert(image != (Image *) NULL);
1949 assert(image->signature == MagickSignature);
1950 if (image->debug != MagickFalse)
1951 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1952 if (gamma == 1.0)
1953 return(MagickTrue);
1954 gamma_map=(Quantum *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*gamma_map));
1955 if (gamma_map == (Quantum *) NULL)
1956 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
1957 image->filename);
1958 (void) ResetMagickMemory(gamma_map,0,(MaxMap+1)*sizeof(*gamma_map));
1959 if (gamma != 0.0)
cristyb5d5f722009-11-04 03:03:49 +00001960#if defined(MAGICKCORE_OPENMP_SUPPORT)
1961 #pragma omp parallel for schedule(dynamic,4)
cristy3ed852e2009-09-05 21:47:34 +00001962#endif
cristybb503372010-05-27 20:51:26 +00001963 for (i=0; i <= (ssize_t) MaxMap; i++)
cristyce70c172010-01-07 17:15:30 +00001964 gamma_map[i]=ClampToQuantum((MagickRealType) ScaleMapToQuantum((
cristy3ed852e2009-09-05 21:47:34 +00001965 MagickRealType) (MaxMap*pow((double) i/MaxMap,1.0/gamma))));
1966 if (image->storage_class == PseudoClass)
1967 {
1968 /*
1969 Gamma-correct colormap.
1970 */
cristyb5d5f722009-11-04 03:03:49 +00001971#if defined(MAGICKCORE_OPENMP_SUPPORT)
1972 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001973#endif
cristybb503372010-05-27 20:51:26 +00001974 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00001975 {
cristy2b9582a2011-07-04 17:38:56 +00001976 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001977 image->colormap[i].red=gamma_map[
1978 ScaleQuantumToMap(image->colormap[i].red)];
cristy2b9582a2011-07-04 17:38:56 +00001979 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001980 image->colormap[i].green=gamma_map[
1981 ScaleQuantumToMap(image->colormap[i].green)];
cristy2b9582a2011-07-04 17:38:56 +00001982 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001983 image->colormap[i].blue=gamma_map[
1984 ScaleQuantumToMap(image->colormap[i].blue)];
cristy2b9582a2011-07-04 17:38:56 +00001985 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001986 image->colormap[i].alpha=gamma_map[
1987 ScaleQuantumToMap(image->colormap[i].alpha)];
cristy3ed852e2009-09-05 21:47:34 +00001988 }
1989 }
1990 /*
1991 Gamma-correct image.
1992 */
1993 status=MagickTrue;
1994 progress=0;
1995 exception=(&image->exception);
1996 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00001997#if defined(MAGICKCORE_OPENMP_SUPPORT)
1998 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001999#endif
cristybb503372010-05-27 20:51:26 +00002000 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002001 {
cristy4c08aed2011-07-01 19:47:50 +00002002 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00002003 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002004
cristy8d4629b2010-08-30 17:59:46 +00002005 register ssize_t
2006 x;
2007
cristy3ed852e2009-09-05 21:47:34 +00002008 if (status == MagickFalse)
2009 continue;
2010 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00002011 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00002012 {
2013 status=MagickFalse;
2014 continue;
2015 }
cristybb503372010-05-27 20:51:26 +00002016 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002017 {
cristy50fbc382011-07-07 02:19:17 +00002018 if (image->sync != MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00002019 {
cristy50fbc382011-07-07 02:19:17 +00002020 SetPixelRed(image,gamma_map[ScaleQuantumToMap(
2021 GetPixelRed(image,q))],q);
2022 SetPixelGreen(image,gamma_map[ScaleQuantumToMap(
2023 GetPixelGreen(image,q))],q);
cristy4c08aed2011-07-01 19:47:50 +00002024 SetPixelBlue(image,gamma_map[ScaleQuantumToMap(
2025 GetPixelBlue(image,q))],q);
cristy6cbd7f52009-10-17 16:06:51 +00002026 }
2027 else
2028 {
cristy2b9582a2011-07-04 17:38:56 +00002029 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002030 SetPixelRed(image,gamma_map[ScaleQuantumToMap(
2031 GetPixelRed(image,q))],q);
cristy2b9582a2011-07-04 17:38:56 +00002032 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002033 SetPixelGreen(image,gamma_map[
2034 ScaleQuantumToMap(GetPixelGreen(image,q))],q);
cristy2b9582a2011-07-04 17:38:56 +00002035 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002036 SetPixelBlue(image,gamma_map[
2037 ScaleQuantumToMap(GetPixelBlue(image,q))],q);
cristy2b9582a2011-07-04 17:38:56 +00002038 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy6cbd7f52009-10-17 16:06:51 +00002039 {
2040 if (image->matte == MagickFalse)
cristy4c08aed2011-07-01 19:47:50 +00002041 SetPixelAlpha(image,gamma_map[
2042 ScaleQuantumToMap(GetPixelAlpha(image,q))],q);
cristy6cbd7f52009-10-17 16:06:51 +00002043 else
cristy4c08aed2011-07-01 19:47:50 +00002044 SetPixelAlpha(image,gamma_map[
2045 ScaleQuantumToMap(GetPixelAlpha(image,q))],q);
cristy6cbd7f52009-10-17 16:06:51 +00002046 }
cristy3ed852e2009-09-05 21:47:34 +00002047 }
cristydcfc1ad2011-07-07 16:25:41 +00002048 q+=GetPixelComponents(image);
cristy3ed852e2009-09-05 21:47:34 +00002049 }
cristy2b9582a2011-07-04 17:38:56 +00002050 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00002051 (image->colorspace == CMYKColorspace))
cristybb503372010-05-27 20:51:26 +00002052 for (x=0; x < (ssize_t) image->columns; x++)
cristy4c08aed2011-07-01 19:47:50 +00002053 SetPixelBlack(image,gamma_map[ScaleQuantumToMap(
2054 GetPixelBlack(image,q))],q);
cristy3ed852e2009-09-05 21:47:34 +00002055 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2056 status=MagickFalse;
2057 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2058 {
2059 MagickBooleanType
2060 proceed;
2061
cristyb5d5f722009-11-04 03:03:49 +00002062#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy50fbc382011-07-07 02:19:17 +00002063 #pragma omp critical (MagickCore_GammaImage)
cristy3ed852e2009-09-05 21:47:34 +00002064#endif
2065 proceed=SetImageProgress(image,GammaCorrectImageTag,progress++,
2066 image->rows);
2067 if (proceed == MagickFalse)
2068 status=MagickFalse;
2069 }
2070 }
2071 image_view=DestroyCacheView(image_view);
2072 gamma_map=(Quantum *) RelinquishMagickMemory(gamma_map);
2073 if (image->gamma != 0.0)
2074 image->gamma*=gamma;
2075 return(status);
2076}
2077
2078/*
2079%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2080% %
2081% %
2082% %
2083% H a l d C l u t I m a g e %
2084% %
2085% %
2086% %
2087%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2088%
2089% HaldClutImage() applies a Hald color lookup table to the image. A Hald
2090% color lookup table is a 3-dimensional color cube mapped to 2 dimensions.
2091% Create it with the HALD coder. You can apply any color transformation to
2092% the Hald image and then use this method to apply the transform to the
2093% image.
2094%
2095% The format of the HaldClutImage method is:
2096%
2097% MagickBooleanType HaldClutImage(Image *image,Image *hald_image)
cristy3ed852e2009-09-05 21:47:34 +00002098%
2099% A description of each parameter follows:
2100%
2101% o image: the image, which is replaced by indexed CLUT values
2102%
2103% o hald_image: the color lookup table image for replacement color values.
2104%
cristy3ed852e2009-09-05 21:47:34 +00002105*/
2106
2107static inline size_t MagickMin(const size_t x,const size_t y)
2108{
2109 if (x < y)
2110 return(x);
2111 return(y);
2112}
2113
2114MagickExport MagickBooleanType HaldClutImage(Image *image,
2115 const Image *hald_image)
2116{
cristy3ed852e2009-09-05 21:47:34 +00002117#define HaldClutImageTag "Clut/Image"
2118
2119 typedef struct _HaldInfo
2120 {
2121 MagickRealType
2122 x,
2123 y,
2124 z;
2125 } HaldInfo;
2126
cristyfa112112010-01-04 17:48:07 +00002127 CacheView
cristyd551fbc2011-03-31 18:07:46 +00002128 *hald_view,
cristyfa112112010-01-04 17:48:07 +00002129 *image_view;
2130
cristy3ed852e2009-09-05 21:47:34 +00002131 double
2132 width;
2133
2134 ExceptionInfo
2135 *exception;
2136
cristy3ed852e2009-09-05 21:47:34 +00002137 MagickBooleanType
2138 status;
2139
cristybb503372010-05-27 20:51:26 +00002140 MagickOffsetType
2141 progress;
2142
cristy4c08aed2011-07-01 19:47:50 +00002143 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00002144 zero;
2145
cristy3ed852e2009-09-05 21:47:34 +00002146 size_t
2147 cube_size,
2148 length,
2149 level;
2150
cristybb503372010-05-27 20:51:26 +00002151 ssize_t
2152 y;
2153
cristy3ed852e2009-09-05 21:47:34 +00002154 assert(image != (Image *) NULL);
2155 assert(image->signature == MagickSignature);
2156 if (image->debug != MagickFalse)
2157 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2158 assert(hald_image != (Image *) NULL);
2159 assert(hald_image->signature == MagickSignature);
2160 if (SetImageStorageClass(image,DirectClass) == MagickFalse)
2161 return(MagickFalse);
2162 if (image->matte == MagickFalse)
2163 (void) SetImageAlphaChannel(image,OpaqueAlphaChannel);
2164 /*
2165 Hald clut image.
2166 */
2167 status=MagickTrue;
2168 progress=0;
2169 length=MagickMin(hald_image->columns,hald_image->rows);
2170 for (level=2; (level*level*level) < length; level++) ;
2171 level*=level;
2172 cube_size=level*level;
2173 width=(double) hald_image->columns;
cristy4c08aed2011-07-01 19:47:50 +00002174 GetPixelInfo(hald_image,&zero);
cristy3ed852e2009-09-05 21:47:34 +00002175 exception=(&image->exception);
cristy3ed852e2009-09-05 21:47:34 +00002176 image_view=AcquireCacheView(image);
cristyd551fbc2011-03-31 18:07:46 +00002177 hald_view=AcquireCacheView(hald_image);
cristyb5d5f722009-11-04 03:03:49 +00002178#if defined(MAGICKCORE_OPENMP_SUPPORT)
2179 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002180#endif
cristybb503372010-05-27 20:51:26 +00002181 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002182 {
2183 double
2184 offset;
2185
2186 HaldInfo
2187 point;
2188
cristy4c08aed2011-07-01 19:47:50 +00002189 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00002190 pixel,
2191 pixel1,
2192 pixel2,
2193 pixel3,
2194 pixel4;
2195
cristy4c08aed2011-07-01 19:47:50 +00002196 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00002197 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002198
cristy8d4629b2010-08-30 17:59:46 +00002199 register ssize_t
2200 x;
2201
cristy3ed852e2009-09-05 21:47:34 +00002202 if (status == MagickFalse)
2203 continue;
2204 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00002205 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00002206 {
2207 status=MagickFalse;
2208 continue;
2209 }
cristy3ed852e2009-09-05 21:47:34 +00002210 pixel=zero;
2211 pixel1=zero;
2212 pixel2=zero;
2213 pixel3=zero;
2214 pixel4=zero;
cristybb503372010-05-27 20:51:26 +00002215 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002216 {
cristy4c08aed2011-07-01 19:47:50 +00002217 point.x=QuantumScale*(level-1.0)*GetPixelRed(image,q);
2218 point.y=QuantumScale*(level-1.0)*GetPixelGreen(image,q);
2219 point.z=QuantumScale*(level-1.0)*GetPixelBlue(image,q);
cristy3ed852e2009-09-05 21:47:34 +00002220 offset=point.x+level*floor(point.y)+cube_size*floor(point.z);
2221 point.x-=floor(point.x);
2222 point.y-=floor(point.y);
2223 point.z-=floor(point.z);
cristy4c08aed2011-07-01 19:47:50 +00002224 (void) InterpolatePixelInfo(image,hald_view,
cristy8a7c3e82011-03-26 02:10:53 +00002225 UndefinedInterpolatePixel,fmod(offset,width),floor(offset/width),
2226 &pixel1,exception);
cristy4c08aed2011-07-01 19:47:50 +00002227 (void) InterpolatePixelInfo(image,hald_view,
cristy8a7c3e82011-03-26 02:10:53 +00002228 UndefinedInterpolatePixel,fmod(offset+level,width),floor((offset+level)/
2229 width),&pixel2,exception);
cristy4c08aed2011-07-01 19:47:50 +00002230 CompositePixelInfoAreaBlend(&pixel1,pixel1.alpha,&pixel2,
2231 pixel2.alpha,point.y,&pixel3);
cristy3ed852e2009-09-05 21:47:34 +00002232 offset+=cube_size;
cristy4c08aed2011-07-01 19:47:50 +00002233 (void) InterpolatePixelInfo(image,hald_view,
cristy8a7c3e82011-03-26 02:10:53 +00002234 UndefinedInterpolatePixel,fmod(offset,width),floor(offset/width),
2235 &pixel1,exception);
cristy4c08aed2011-07-01 19:47:50 +00002236 (void) InterpolatePixelInfo(image,hald_view,
cristy8a7c3e82011-03-26 02:10:53 +00002237 UndefinedInterpolatePixel,fmod(offset+level,width),floor((offset+level)/
2238 width),&pixel2,exception);
cristy4c08aed2011-07-01 19:47:50 +00002239 CompositePixelInfoAreaBlend(&pixel1,pixel1.alpha,&pixel2,
2240 pixel2.alpha,point.y,&pixel4);
2241 CompositePixelInfoAreaBlend(&pixel3,pixel3.alpha,&pixel4,
2242 pixel4.alpha,point.z,&pixel);
cristy2b9582a2011-07-04 17:38:56 +00002243 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002244 SetPixelRed(image,
2245 ClampToQuantum(pixel.red),q);
cristy2b9582a2011-07-04 17:38:56 +00002246 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002247 SetPixelGreen(image,
2248 ClampToQuantum(pixel.green),q);
cristy2b9582a2011-07-04 17:38:56 +00002249 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002250 SetPixelBlue(image,
2251 ClampToQuantum(pixel.blue),q);
cristy2b9582a2011-07-04 17:38:56 +00002252 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00002253 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +00002254 SetPixelBlack(image,
2255 ClampToQuantum(pixel.black),q);
cristy2b9582a2011-07-04 17:38:56 +00002256 if (((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0) && (image->matte != MagickFalse))
cristy4c08aed2011-07-01 19:47:50 +00002257 SetPixelAlpha(image,
2258 ClampToQuantum(pixel.alpha),q);
cristydcfc1ad2011-07-07 16:25:41 +00002259 q+=GetPixelComponents(image);
cristy3ed852e2009-09-05 21:47:34 +00002260 }
2261 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2262 status=MagickFalse;
2263 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2264 {
2265 MagickBooleanType
2266 proceed;
2267
cristyb5d5f722009-11-04 03:03:49 +00002268#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf89cb1d2011-07-07 01:24:37 +00002269 #pragma omp critical (MagickCore_HaldClutImage)
cristy3ed852e2009-09-05 21:47:34 +00002270#endif
2271 proceed=SetImageProgress(image,HaldClutImageTag,progress++,image->rows);
2272 if (proceed == MagickFalse)
2273 status=MagickFalse;
2274 }
2275 }
cristyd551fbc2011-03-31 18:07:46 +00002276 hald_view=DestroyCacheView(hald_view);
cristy3ed852e2009-09-05 21:47:34 +00002277 image_view=DestroyCacheView(image_view);
cristy3ed852e2009-09-05 21:47:34 +00002278 return(status);
2279}
2280
2281/*
2282%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2283% %
2284% %
2285% %
2286% L e v e l I m a g e %
2287% %
2288% %
2289% %
2290%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2291%
2292% LevelImage() adjusts the levels of a particular image channel by
2293% scaling the colors falling between specified white and black points to
2294% the full available quantum range.
2295%
2296% The parameters provided represent the black, and white points. The black
2297% point specifies the darkest color in the image. Colors darker than the
2298% black point are set to zero. White point specifies the lightest color in
2299% the image. Colors brighter than the white point are set to the maximum
2300% quantum value.
2301%
2302% If a '!' flag is given, map black and white colors to the given levels
2303% rather than mapping those levels to black and white. See
cristy50fbc382011-07-07 02:19:17 +00002304% LevelizeImage() below.
cristy3ed852e2009-09-05 21:47:34 +00002305%
2306% Gamma specifies a gamma correction to apply to the image.
2307%
2308% The format of the LevelImage method is:
2309%
2310% MagickBooleanType LevelImage(Image *image,const char *levels)
2311%
2312% A description of each parameter follows:
2313%
2314% o image: the image.
2315%
2316% o levels: Specify the levels where the black and white points have the
2317% range of 0-QuantumRange, and gamma has the range 0-10 (e.g. 10x90%+2).
2318% A '!' flag inverts the re-mapping.
2319%
2320*/
cristyf89cb1d2011-07-07 01:24:37 +00002321MagickExport MagickBooleanType LevelImage(Image *image,
2322 const double black_point,const double white_point,const double gamma)
cristy3ed852e2009-09-05 21:47:34 +00002323{
2324#define LevelImageTag "Level/Image"
cristybcfb0432010-05-06 01:45:33 +00002325#define LevelQuantum(x) (ClampToQuantum((MagickRealType) QuantumRange* \
cristyc1f508d2010-04-22 01:21:47 +00002326 pow(scale*((double) (x)-black_point),1.0/gamma)))
cristy3ed852e2009-09-05 21:47:34 +00002327
cristyc4c8d132010-01-07 01:58:38 +00002328 CacheView
2329 *image_view;
2330
cristy3ed852e2009-09-05 21:47:34 +00002331 ExceptionInfo
2332 *exception;
2333
cristy3ed852e2009-09-05 21:47:34 +00002334 MagickBooleanType
2335 status;
2336
cristybb503372010-05-27 20:51:26 +00002337 MagickOffsetType
2338 progress;
2339
anthony7fe39fc2010-04-06 03:19:20 +00002340 register double
2341 scale;
2342
cristy8d4629b2010-08-30 17:59:46 +00002343 register ssize_t
2344 i;
2345
cristybb503372010-05-27 20:51:26 +00002346 ssize_t
2347 y;
2348
cristy3ed852e2009-09-05 21:47:34 +00002349 /*
2350 Allocate and initialize levels map.
2351 */
2352 assert(image != (Image *) NULL);
2353 assert(image->signature == MagickSignature);
2354 if (image->debug != MagickFalse)
2355 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
cristy8d4629b2010-08-30 17:59:46 +00002356 scale=(white_point != black_point) ? 1.0/(white_point-black_point) : 1.0;
cristy3ed852e2009-09-05 21:47:34 +00002357 if (image->storage_class == PseudoClass)
cristyb5d5f722009-11-04 03:03:49 +00002358#if defined(MAGICKCORE_OPENMP_SUPPORT)
2359 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002360#endif
cristybb503372010-05-27 20:51:26 +00002361 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00002362 {
2363 /*
2364 Level colormap.
2365 */
cristy2b9582a2011-07-04 17:38:56 +00002366 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristyc1f508d2010-04-22 01:21:47 +00002367 image->colormap[i].red=LevelQuantum(image->colormap[i].red);
cristy2b9582a2011-07-04 17:38:56 +00002368 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristyc1f508d2010-04-22 01:21:47 +00002369 image->colormap[i].green=LevelQuantum(image->colormap[i].green);
cristy2b9582a2011-07-04 17:38:56 +00002370 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristyc1f508d2010-04-22 01:21:47 +00002371 image->colormap[i].blue=LevelQuantum(image->colormap[i].blue);
cristy2b9582a2011-07-04 17:38:56 +00002372 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002373 image->colormap[i].alpha=LevelQuantum(image->colormap[i].alpha);
cristy3ed852e2009-09-05 21:47:34 +00002374 }
2375 /*
2376 Level image.
2377 */
2378 status=MagickTrue;
2379 progress=0;
2380 exception=(&image->exception);
2381 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00002382#if defined(MAGICKCORE_OPENMP_SUPPORT)
2383 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002384#endif
cristybb503372010-05-27 20:51:26 +00002385 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002386 {
cristy4c08aed2011-07-01 19:47:50 +00002387 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00002388 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002389
cristy8d4629b2010-08-30 17:59:46 +00002390 register ssize_t
2391 x;
2392
cristy3ed852e2009-09-05 21:47:34 +00002393 if (status == MagickFalse)
2394 continue;
2395 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00002396 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00002397 {
2398 status=MagickFalse;
2399 continue;
2400 }
cristybb503372010-05-27 20:51:26 +00002401 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002402 {
cristy2b9582a2011-07-04 17:38:56 +00002403 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002404 SetPixelRed(image,LevelQuantum(
2405 GetPixelRed(image,q)),q);
cristy2b9582a2011-07-04 17:38:56 +00002406 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002407 SetPixelGreen(image,
2408 LevelQuantum(GetPixelGreen(image,q)),q);
cristy2b9582a2011-07-04 17:38:56 +00002409 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002410 SetPixelBlue(image,
2411 LevelQuantum(GetPixelBlue(image,q)),q);
cristy2b9582a2011-07-04 17:38:56 +00002412 if (((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00002413 (image->matte == MagickTrue))
cristy4c08aed2011-07-01 19:47:50 +00002414 SetPixelAlpha(image,
2415 LevelQuantum(GetPixelAlpha(image,q)),q);
cristy2b9582a2011-07-04 17:38:56 +00002416 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00002417 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +00002418 SetPixelBlack(image,
2419 LevelQuantum(GetPixelBlack(image,q)),q);
cristydcfc1ad2011-07-07 16:25:41 +00002420 q+=GetPixelComponents(image);
cristy3ed852e2009-09-05 21:47:34 +00002421 }
2422 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2423 status=MagickFalse;
2424 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2425 {
2426 MagickBooleanType
2427 proceed;
2428
cristyb5d5f722009-11-04 03:03:49 +00002429#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf89cb1d2011-07-07 01:24:37 +00002430 #pragma omp critical (MagickCore_LevelImage)
cristy3ed852e2009-09-05 21:47:34 +00002431#endif
2432 proceed=SetImageProgress(image,LevelImageTag,progress++,image->rows);
2433 if (proceed == MagickFalse)
2434 status=MagickFalse;
2435 }
2436 }
2437 image_view=DestroyCacheView(image_view);
2438 return(status);
2439}
2440
2441/*
2442%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2443% %
2444% %
2445% %
2446% L e v e l i z e I m a g e C h a n n e l %
2447% %
2448% %
2449% %
2450%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2451%
cristy50fbc382011-07-07 02:19:17 +00002452% LevelizeImage() applies the reversed LevelImage() operation to just
cristy3ed852e2009-09-05 21:47:34 +00002453% the specific channels specified. It compresses the full range of color
2454% values, so that they lie between the given black and white points. Gamma is
2455% applied before the values are mapped.
2456%
cristy50fbc382011-07-07 02:19:17 +00002457% LevelizeImage() can be called with by using a +level command line
cristy3ed852e2009-09-05 21:47:34 +00002458% API option, or using a '!' on a -level or LevelImage() geometry string.
2459%
2460% It can be used for example de-contrast a greyscale image to the exact
2461% levels specified. Or by using specific levels for each channel of an image
2462% you can convert a gray-scale image to any linear color gradient, according
2463% to those levels.
2464%
cristy50fbc382011-07-07 02:19:17 +00002465% The format of the LevelizeImage method is:
cristy3ed852e2009-09-05 21:47:34 +00002466%
cristy50fbc382011-07-07 02:19:17 +00002467% MagickBooleanType LevelizeImage(Image *image,const double black_point,
2468% const double white_point,const double gamma)
cristy3ed852e2009-09-05 21:47:34 +00002469%
2470% A description of each parameter follows:
2471%
2472% o image: the image.
2473%
cristy3ed852e2009-09-05 21:47:34 +00002474% o black_point: The level to map zero (black) to.
2475%
2476% o white_point: The level to map QuantiumRange (white) to.
2477%
2478% o gamma: adjust gamma by this factor before mapping values.
2479%
2480*/
cristyd1a2c0f2011-02-09 14:14:50 +00002481MagickExport MagickBooleanType LevelizeImage(Image *image,
2482 const double black_point,const double white_point,const double gamma)
2483{
cristy3ed852e2009-09-05 21:47:34 +00002484#define LevelizeImageTag "Levelize/Image"
cristyce70c172010-01-07 17:15:30 +00002485#define LevelizeValue(x) (ClampToQuantum(((MagickRealType) \
cristy50fbc382011-07-07 02:19:17 +00002486 pow((double) (QuantumScale*(x)),1.0/gamma))*(white_point-black_point)+ \
cristy3ed852e2009-09-05 21:47:34 +00002487 black_point))
2488
cristyc4c8d132010-01-07 01:58:38 +00002489 CacheView
2490 *image_view;
2491
cristy3ed852e2009-09-05 21:47:34 +00002492 ExceptionInfo
2493 *exception;
2494
cristy3ed852e2009-09-05 21:47:34 +00002495 MagickBooleanType
2496 status;
2497
cristybb503372010-05-27 20:51:26 +00002498 MagickOffsetType
2499 progress;
2500
2501 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002502 i;
2503
cristybb503372010-05-27 20:51:26 +00002504 ssize_t
2505 y;
2506
cristy3ed852e2009-09-05 21:47:34 +00002507 /*
2508 Allocate and initialize levels map.
2509 */
2510 assert(image != (Image *) NULL);
2511 assert(image->signature == MagickSignature);
2512 if (image->debug != MagickFalse)
2513 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2514 if (image->storage_class == PseudoClass)
cristyb5d5f722009-11-04 03:03:49 +00002515#if defined(MAGICKCORE_OPENMP_SUPPORT)
2516 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002517#endif
cristybb503372010-05-27 20:51:26 +00002518 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00002519 {
2520 /*
2521 Level colormap.
2522 */
cristy2b9582a2011-07-04 17:38:56 +00002523 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00002524 image->colormap[i].red=LevelizeValue(image->colormap[i].red);
cristy2b9582a2011-07-04 17:38:56 +00002525 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00002526 image->colormap[i].green=LevelizeValue(image->colormap[i].green);
cristy2b9582a2011-07-04 17:38:56 +00002527 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00002528 image->colormap[i].blue=LevelizeValue(image->colormap[i].blue);
cristy2b9582a2011-07-04 17:38:56 +00002529 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002530 image->colormap[i].alpha=LevelizeValue(image->colormap[i].alpha);
cristy3ed852e2009-09-05 21:47:34 +00002531 }
2532 /*
2533 Level image.
2534 */
2535 status=MagickTrue;
2536 progress=0;
2537 exception=(&image->exception);
2538 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00002539#if defined(MAGICKCORE_OPENMP_SUPPORT)
2540 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002541#endif
cristybb503372010-05-27 20:51:26 +00002542 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002543 {
cristy4c08aed2011-07-01 19:47:50 +00002544 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00002545 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002546
cristy8d4629b2010-08-30 17:59:46 +00002547 register ssize_t
2548 x;
2549
cristy3ed852e2009-09-05 21:47:34 +00002550 if (status == MagickFalse)
2551 continue;
2552 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00002553 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00002554 {
2555 status=MagickFalse;
2556 continue;
2557 }
cristybb503372010-05-27 20:51:26 +00002558 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002559 {
cristy2b9582a2011-07-04 17:38:56 +00002560 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002561 SetPixelRed(image,LevelizeValue(GetPixelRed(image,q)),q);
cristy2b9582a2011-07-04 17:38:56 +00002562 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002563 SetPixelGreen(image,LevelizeValue(GetPixelGreen(image,q)),q);
cristy2b9582a2011-07-04 17:38:56 +00002564 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002565 SetPixelBlue(image,LevelizeValue(GetPixelBlue(image,q)),q);
cristy2b9582a2011-07-04 17:38:56 +00002566 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +00002567 (image->colorspace == CMYKColorspace))
2568 SetPixelBlack(image,LevelizeValue(GetPixelBlack(image,q)),q);
cristy2b9582a2011-07-04 17:38:56 +00002569 if (((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00002570 (image->matte == MagickTrue))
cristy4c08aed2011-07-01 19:47:50 +00002571 SetPixelAlpha(image,LevelizeValue(GetPixelAlpha(image,q)),q);
cristydcfc1ad2011-07-07 16:25:41 +00002572 q+=GetPixelComponents(image);
cristy3ed852e2009-09-05 21:47:34 +00002573 }
2574 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2575 status=MagickFalse;
2576 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2577 {
2578 MagickBooleanType
2579 proceed;
2580
cristyb5d5f722009-11-04 03:03:49 +00002581#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy50fbc382011-07-07 02:19:17 +00002582 #pragma omp critical (MagickCore_LevelizeImage)
cristy3ed852e2009-09-05 21:47:34 +00002583#endif
2584 proceed=SetImageProgress(image,LevelizeImageTag,progress++,image->rows);
2585 if (proceed == MagickFalse)
2586 status=MagickFalse;
2587 }
2588 }
cristy8d4629b2010-08-30 17:59:46 +00002589 image_view=DestroyCacheView(image_view);
cristy3ed852e2009-09-05 21:47:34 +00002590 return(status);
2591}
2592
2593/*
2594%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2595% %
2596% %
2597% %
2598% L e v e l I m a g e C o l o r s %
2599% %
2600% %
2601% %
2602%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2603%
cristy490408a2011-07-07 14:42:05 +00002604% LevelImageColors() maps the given color to "black" and "white" values,
cristyee0f8d72009-09-19 00:58:29 +00002605% linearly spreading out the colors, and level values on a channel by channel
2606% bases, as per LevelImage(). The given colors allows you to specify
glennrp1e7f7bc2011-03-02 19:25:28 +00002607% different level ranges for each of the color channels separately.
cristy3ed852e2009-09-05 21:47:34 +00002608%
2609% If the boolean 'invert' is set true the image values will modifyed in the
2610% reverse direction. That is any existing "black" and "white" colors in the
2611% image will become the color values given, with all other values compressed
2612% appropriatally. This effectivally maps a greyscale gradient into the given
2613% color gradient.
2614%
cristy490408a2011-07-07 14:42:05 +00002615% The format of the LevelImageColors method is:
cristy3ed852e2009-09-05 21:47:34 +00002616%
cristy490408a2011-07-07 14:42:05 +00002617% MagickBooleanType LevelImageColors(Image *image,
2618% const PixelInfo *black_color,const PixelInfo *white_color,
2619% const MagickBooleanType invert)
cristy3ed852e2009-09-05 21:47:34 +00002620%
2621% A description of each parameter follows:
2622%
2623% o image: the image.
2624%
cristy3ed852e2009-09-05 21:47:34 +00002625% o black_color: The color to map black to/from
2626%
2627% o white_point: The color to map white to/from
2628%
2629% o invert: if true map the colors (levelize), rather than from (level)
2630%
2631*/
cristy490408a2011-07-07 14:42:05 +00002632MagickExport MagickBooleanType LevelImageColors(Image *image,
cristy4c08aed2011-07-01 19:47:50 +00002633 const PixelInfo *black_color,const PixelInfo *white_color,
cristy3ed852e2009-09-05 21:47:34 +00002634 const MagickBooleanType invert)
2635{
cristy3ed852e2009-09-05 21:47:34 +00002636 MagickStatusType
2637 status;
2638
2639 /*
2640 Allocate and initialize levels map.
2641 */
2642 assert(image != (Image *) NULL);
2643 assert(image->signature == MagickSignature);
2644 if (image->debug != MagickFalse)
2645 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2646 status=MagickFalse;
2647 if (invert == MagickFalse)
2648 {
cristy2b9582a2011-07-04 17:38:56 +00002649 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristyf89cb1d2011-07-07 01:24:37 +00002650 {
cristy490408a2011-07-07 14:42:05 +00002651 PushPixelComponentMap(image,RedChannel);
cristyf89cb1d2011-07-07 01:24:37 +00002652 status|=LevelImage(image,black_color->red,white_color->red,1.0);
cristy490408a2011-07-07 14:42:05 +00002653 PopPixelComponentMap(image);
cristyf89cb1d2011-07-07 01:24:37 +00002654 }
cristy2b9582a2011-07-04 17:38:56 +00002655 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristyf89cb1d2011-07-07 01:24:37 +00002656 {
cristy490408a2011-07-07 14:42:05 +00002657 PushPixelComponentMap(image,GreenChannel);
cristyf89cb1d2011-07-07 01:24:37 +00002658 status|=LevelImage(image,black_color->green,white_color->green,1.0);
cristy490408a2011-07-07 14:42:05 +00002659 PopPixelComponentMap(image);
cristyf89cb1d2011-07-07 01:24:37 +00002660 }
cristy2b9582a2011-07-04 17:38:56 +00002661 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristyf89cb1d2011-07-07 01:24:37 +00002662 {
cristy490408a2011-07-07 14:42:05 +00002663 PushPixelComponentMap(image,BlueChannel);
cristyf89cb1d2011-07-07 01:24:37 +00002664 status|=LevelImage(image,black_color->blue,white_color->blue,1.0);
cristy490408a2011-07-07 14:42:05 +00002665 PopPixelComponentMap(image);
cristyf89cb1d2011-07-07 01:24:37 +00002666 }
cristy2b9582a2011-07-04 17:38:56 +00002667 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +00002668 (image->colorspace == CMYKColorspace))
cristyf89cb1d2011-07-07 01:24:37 +00002669 {
cristy490408a2011-07-07 14:42:05 +00002670 PushPixelComponentMap(image,BlackChannel);
cristyf89cb1d2011-07-07 01:24:37 +00002671 status|=LevelImage(image,black_color->black,white_color->black,1.0);
cristy490408a2011-07-07 14:42:05 +00002672 PopPixelComponentMap(image);
cristyf89cb1d2011-07-07 01:24:37 +00002673 }
cristy2b9582a2011-07-04 17:38:56 +00002674 if (((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00002675 (image->matte == MagickTrue))
cristyf89cb1d2011-07-07 01:24:37 +00002676 {
cristy490408a2011-07-07 14:42:05 +00002677 PushPixelComponentMap(image,AlphaChannel);
cristyf89cb1d2011-07-07 01:24:37 +00002678 status|=LevelImage(image,black_color->alpha,white_color->alpha,1.0);
cristy490408a2011-07-07 14:42:05 +00002679 PopPixelComponentMap(image);
cristyf89cb1d2011-07-07 01:24:37 +00002680 }
cristy3ed852e2009-09-05 21:47:34 +00002681 }
2682 else
2683 {
cristy2b9582a2011-07-04 17:38:56 +00002684 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristyf89cb1d2011-07-07 01:24:37 +00002685 {
cristy490408a2011-07-07 14:42:05 +00002686 PushPixelComponentMap(image,RedChannel);
cristy50fbc382011-07-07 02:19:17 +00002687 status|=LevelizeImage(image,black_color->red,white_color->red,1.0);
cristy490408a2011-07-07 14:42:05 +00002688 PopPixelComponentMap(image);
cristyf89cb1d2011-07-07 01:24:37 +00002689 }
cristy2b9582a2011-07-04 17:38:56 +00002690 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristyf89cb1d2011-07-07 01:24:37 +00002691 {
cristy490408a2011-07-07 14:42:05 +00002692 PushPixelComponentMap(image,GreenChannel);
cristy50fbc382011-07-07 02:19:17 +00002693 status|=LevelizeImage(image,black_color->green,white_color->green,
2694 1.0);
cristy490408a2011-07-07 14:42:05 +00002695 PopPixelComponentMap(image);
cristyf89cb1d2011-07-07 01:24:37 +00002696 }
cristy2b9582a2011-07-04 17:38:56 +00002697 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristyf89cb1d2011-07-07 01:24:37 +00002698 {
cristy490408a2011-07-07 14:42:05 +00002699 PushPixelComponentMap(image,BlueChannel);
cristy50fbc382011-07-07 02:19:17 +00002700 status|=LevelizeImage(image,black_color->blue,white_color->blue,1.0);
cristy490408a2011-07-07 14:42:05 +00002701 PopPixelComponentMap(image);
cristyf89cb1d2011-07-07 01:24:37 +00002702 }
cristy2b9582a2011-07-04 17:38:56 +00002703 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +00002704 (image->colorspace == CMYKColorspace))
cristyf89cb1d2011-07-07 01:24:37 +00002705 {
cristy490408a2011-07-07 14:42:05 +00002706 PushPixelComponentMap(image,BlackChannel);
cristy50fbc382011-07-07 02:19:17 +00002707 status|=LevelizeImage(image,black_color->black,white_color->black,
2708 1.0);
cristy490408a2011-07-07 14:42:05 +00002709 PopPixelComponentMap(image);
cristyf89cb1d2011-07-07 01:24:37 +00002710 }
cristy2b9582a2011-07-04 17:38:56 +00002711 if (((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00002712 (image->matte == MagickTrue))
cristyf89cb1d2011-07-07 01:24:37 +00002713 {
cristy490408a2011-07-07 14:42:05 +00002714 PushPixelComponentMap(image,AlphaChannel);
cristy50fbc382011-07-07 02:19:17 +00002715 status|=LevelizeImage(image,black_color->alpha,white_color->alpha,
2716 1.0);
cristy490408a2011-07-07 14:42:05 +00002717 PopPixelComponentMap(image);
cristyf89cb1d2011-07-07 01:24:37 +00002718 }
cristy3ed852e2009-09-05 21:47:34 +00002719 }
2720 return(status == 0 ? MagickFalse : MagickTrue);
2721}
2722
2723/*
2724%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2725% %
2726% %
2727% %
2728% L i n e a r S t r e t c h I m a g e %
2729% %
2730% %
2731% %
2732%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2733%
2734% The LinearStretchImage() discards any pixels below the black point and
2735% above the white point and levels the remaining pixels.
2736%
2737% The format of the LinearStretchImage method is:
2738%
2739% MagickBooleanType LinearStretchImage(Image *image,
2740% const double black_point,const double white_point)
2741%
2742% A description of each parameter follows:
2743%
2744% o image: the image.
2745%
2746% o black_point: the black point.
2747%
2748% o white_point: the white point.
2749%
2750*/
2751MagickExport MagickBooleanType LinearStretchImage(Image *image,
2752 const double black_point,const double white_point)
2753{
2754#define LinearStretchImageTag "LinearStretch/Image"
2755
2756 ExceptionInfo
2757 *exception;
2758
cristy3ed852e2009-09-05 21:47:34 +00002759 MagickBooleanType
2760 status;
2761
2762 MagickRealType
2763 *histogram,
2764 intensity;
2765
cristy8d4629b2010-08-30 17:59:46 +00002766 ssize_t
2767 black,
2768 white,
2769 y;
2770
cristy3ed852e2009-09-05 21:47:34 +00002771 /*
2772 Allocate histogram and linear map.
2773 */
2774 assert(image != (Image *) NULL);
2775 assert(image->signature == MagickSignature);
2776 histogram=(MagickRealType *) AcquireQuantumMemory(MaxMap+1UL,
2777 sizeof(*histogram));
2778 if (histogram == (MagickRealType *) NULL)
2779 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
2780 image->filename);
2781 /*
2782 Form histogram.
2783 */
2784 (void) ResetMagickMemory(histogram,0,(MaxMap+1)*sizeof(*histogram));
2785 exception=(&image->exception);
cristybb503372010-05-27 20:51:26 +00002786 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002787 {
cristy4c08aed2011-07-01 19:47:50 +00002788 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00002789 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00002790
cristybb503372010-05-27 20:51:26 +00002791 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002792 x;
2793
2794 p=GetVirtualPixels(image,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00002795 if (p == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00002796 break;
cristybb503372010-05-27 20:51:26 +00002797 for (x=(ssize_t) image->columns-1; x >= 0; x--)
cristy3ed852e2009-09-05 21:47:34 +00002798 {
cristy4c08aed2011-07-01 19:47:50 +00002799 histogram[ScaleQuantumToMap(GetPixelIntensity(image,p))]++;
cristydcfc1ad2011-07-07 16:25:41 +00002800 p+=GetPixelComponents(image);
cristy3ed852e2009-09-05 21:47:34 +00002801 }
2802 }
2803 /*
2804 Find the histogram boundaries by locating the black and white point levels.
2805 */
cristy3ed852e2009-09-05 21:47:34 +00002806 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00002807 for (black=0; black < (ssize_t) MaxMap; black++)
cristy3ed852e2009-09-05 21:47:34 +00002808 {
2809 intensity+=histogram[black];
2810 if (intensity >= black_point)
2811 break;
2812 }
2813 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00002814 for (white=(ssize_t) MaxMap; white != 0; white--)
cristy3ed852e2009-09-05 21:47:34 +00002815 {
2816 intensity+=histogram[white];
2817 if (intensity >= white_point)
2818 break;
2819 }
2820 histogram=(MagickRealType *) RelinquishMagickMemory(histogram);
cristyf89cb1d2011-07-07 01:24:37 +00002821 status=LevelImage(image,(double) black,(double) white,1.0);
cristy3ed852e2009-09-05 21:47:34 +00002822 return(status);
2823}
2824
2825/*
2826%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2827% %
2828% %
2829% %
2830% M o d u l a t e I m a g e %
2831% %
2832% %
2833% %
2834%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2835%
2836% ModulateImage() lets you control the brightness, saturation, and hue
2837% of an image. Modulate represents the brightness, saturation, and hue
2838% as one parameter (e.g. 90,150,100). If the image colorspace is HSL, the
2839% modulation is lightness, saturation, and hue. And if the colorspace is
2840% HWB, use blackness, whiteness, and hue.
2841%
2842% The format of the ModulateImage method is:
2843%
2844% MagickBooleanType ModulateImage(Image *image,const char *modulate)
2845%
2846% A description of each parameter follows:
2847%
2848% o image: the image.
2849%
2850% o modulate: Define the percent change in brightness, saturation, and
2851% hue.
2852%
2853*/
2854
2855static void ModulateHSB(const double percent_hue,
2856 const double percent_saturation,const double percent_brightness,
2857 Quantum *red,Quantum *green,Quantum *blue)
2858{
2859 double
2860 brightness,
2861 hue,
2862 saturation;
2863
2864 /*
2865 Increase or decrease color brightness, saturation, or hue.
2866 */
2867 assert(red != (Quantum *) NULL);
2868 assert(green != (Quantum *) NULL);
2869 assert(blue != (Quantum *) NULL);
2870 ConvertRGBToHSB(*red,*green,*blue,&hue,&saturation,&brightness);
2871 hue+=0.5*(0.01*percent_hue-1.0);
2872 while (hue < 0.0)
2873 hue+=1.0;
2874 while (hue > 1.0)
2875 hue-=1.0;
2876 saturation*=0.01*percent_saturation;
2877 brightness*=0.01*percent_brightness;
2878 ConvertHSBToRGB(hue,saturation,brightness,red,green,blue);
2879}
2880
2881static void ModulateHSL(const double percent_hue,
2882 const double percent_saturation,const double percent_lightness,
2883 Quantum *red,Quantum *green,Quantum *blue)
2884{
2885 double
2886 hue,
2887 lightness,
2888 saturation;
2889
2890 /*
2891 Increase or decrease color lightness, saturation, or hue.
2892 */
2893 assert(red != (Quantum *) NULL);
2894 assert(green != (Quantum *) NULL);
2895 assert(blue != (Quantum *) NULL);
2896 ConvertRGBToHSL(*red,*green,*blue,&hue,&saturation,&lightness);
2897 hue+=0.5*(0.01*percent_hue-1.0);
2898 while (hue < 0.0)
2899 hue+=1.0;
2900 while (hue > 1.0)
2901 hue-=1.0;
2902 saturation*=0.01*percent_saturation;
2903 lightness*=0.01*percent_lightness;
2904 ConvertHSLToRGB(hue,saturation,lightness,red,green,blue);
2905}
2906
2907static void ModulateHWB(const double percent_hue,const double percent_whiteness, const double percent_blackness,Quantum *red,Quantum *green,Quantum *blue)
2908{
2909 double
2910 blackness,
2911 hue,
2912 whiteness;
2913
2914 /*
2915 Increase or decrease color blackness, whiteness, or hue.
2916 */
2917 assert(red != (Quantum *) NULL);
2918 assert(green != (Quantum *) NULL);
2919 assert(blue != (Quantum *) NULL);
2920 ConvertRGBToHWB(*red,*green,*blue,&hue,&whiteness,&blackness);
2921 hue+=0.5*(0.01*percent_hue-1.0);
2922 while (hue < 0.0)
2923 hue+=1.0;
2924 while (hue > 1.0)
2925 hue-=1.0;
2926 blackness*=0.01*percent_blackness;
2927 whiteness*=0.01*percent_whiteness;
2928 ConvertHWBToRGB(hue,whiteness,blackness,red,green,blue);
2929}
2930
2931MagickExport MagickBooleanType ModulateImage(Image *image,const char *modulate)
2932{
2933#define ModulateImageTag "Modulate/Image"
2934
cristyc4c8d132010-01-07 01:58:38 +00002935 CacheView
2936 *image_view;
2937
cristy3ed852e2009-09-05 21:47:34 +00002938 ColorspaceType
2939 colorspace;
2940
2941 const char
2942 *artifact;
2943
2944 double
2945 percent_brightness,
2946 percent_hue,
2947 percent_saturation;
2948
2949 ExceptionInfo
2950 *exception;
2951
2952 GeometryInfo
2953 geometry_info;
2954
cristy3ed852e2009-09-05 21:47:34 +00002955 MagickBooleanType
2956 status;
2957
cristybb503372010-05-27 20:51:26 +00002958 MagickOffsetType
2959 progress;
2960
cristy3ed852e2009-09-05 21:47:34 +00002961 MagickStatusType
2962 flags;
2963
cristybb503372010-05-27 20:51:26 +00002964 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002965 i;
2966
cristybb503372010-05-27 20:51:26 +00002967 ssize_t
2968 y;
2969
cristy3ed852e2009-09-05 21:47:34 +00002970 /*
cristy2b726bd2010-01-11 01:05:39 +00002971 Initialize modulate table.
cristy3ed852e2009-09-05 21:47:34 +00002972 */
2973 assert(image != (Image *) NULL);
2974 assert(image->signature == MagickSignature);
2975 if (image->debug != MagickFalse)
2976 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2977 if (modulate == (char *) NULL)
2978 return(MagickFalse);
2979 flags=ParseGeometry(modulate,&geometry_info);
2980 percent_brightness=geometry_info.rho;
2981 percent_saturation=geometry_info.sigma;
2982 if ((flags & SigmaValue) == 0)
2983 percent_saturation=100.0;
2984 percent_hue=geometry_info.xi;
2985 if ((flags & XiValue) == 0)
2986 percent_hue=100.0;
2987 colorspace=UndefinedColorspace;
2988 artifact=GetImageArtifact(image,"modulate:colorspace");
2989 if (artifact != (const char *) NULL)
cristy042ee782011-04-22 18:48:30 +00002990 colorspace=(ColorspaceType) ParseCommandOption(MagickColorspaceOptions,
cristy3ed852e2009-09-05 21:47:34 +00002991 MagickFalse,artifact);
2992 if (image->storage_class == PseudoClass)
2993 {
2994 /*
2995 Modulate colormap.
2996 */
cristyb5d5f722009-11-04 03:03:49 +00002997#if defined(MAGICKCORE_OPENMP_SUPPORT)
2998 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002999#endif
cristybb503372010-05-27 20:51:26 +00003000 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00003001 switch (colorspace)
3002 {
3003 case HSBColorspace:
3004 {
3005 ModulateHSB(percent_hue,percent_saturation,percent_brightness,
3006 &image->colormap[i].red,&image->colormap[i].green,
3007 &image->colormap[i].blue);
3008 break;
3009 }
3010 case HSLColorspace:
3011 default:
3012 {
3013 ModulateHSL(percent_hue,percent_saturation,percent_brightness,
3014 &image->colormap[i].red,&image->colormap[i].green,
3015 &image->colormap[i].blue);
3016 break;
3017 }
3018 case HWBColorspace:
3019 {
3020 ModulateHWB(percent_hue,percent_saturation,percent_brightness,
3021 &image->colormap[i].red,&image->colormap[i].green,
3022 &image->colormap[i].blue);
3023 break;
3024 }
3025 }
3026 }
3027 /*
3028 Modulate image.
3029 */
3030 status=MagickTrue;
3031 progress=0;
3032 exception=(&image->exception);
3033 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00003034#if defined(MAGICKCORE_OPENMP_SUPPORT)
3035 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003036#endif
cristybb503372010-05-27 20:51:26 +00003037 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003038 {
cristy5afeab82011-04-30 01:30:09 +00003039 Quantum
3040 blue,
3041 green,
3042 red;
3043
cristy4c08aed2011-07-01 19:47:50 +00003044 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00003045 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003046
cristy8d4629b2010-08-30 17:59:46 +00003047 register ssize_t
3048 x;
3049
cristy3ed852e2009-09-05 21:47:34 +00003050 if (status == MagickFalse)
3051 continue;
3052 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00003053 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00003054 {
3055 status=MagickFalse;
3056 continue;
3057 }
cristybb503372010-05-27 20:51:26 +00003058 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003059 {
cristy4c08aed2011-07-01 19:47:50 +00003060 red=GetPixelRed(image,q);
3061 green=GetPixelGreen(image,q);
3062 blue=GetPixelBlue(image,q);
cristy3ed852e2009-09-05 21:47:34 +00003063 switch (colorspace)
3064 {
3065 case HSBColorspace:
3066 {
3067 ModulateHSB(percent_hue,percent_saturation,percent_brightness,
cristy5afeab82011-04-30 01:30:09 +00003068 &red,&green,&blue);
cristy3ed852e2009-09-05 21:47:34 +00003069 break;
3070 }
3071 case HSLColorspace:
3072 default:
3073 {
3074 ModulateHSL(percent_hue,percent_saturation,percent_brightness,
cristy5afeab82011-04-30 01:30:09 +00003075 &red,&green,&blue);
cristy3ed852e2009-09-05 21:47:34 +00003076 break;
3077 }
3078 case HWBColorspace:
3079 {
3080 ModulateHWB(percent_hue,percent_saturation,percent_brightness,
cristy5afeab82011-04-30 01:30:09 +00003081 &red,&green,&blue);
cristy3ed852e2009-09-05 21:47:34 +00003082 break;
3083 }
3084 }
cristy4c08aed2011-07-01 19:47:50 +00003085 SetPixelRed(image,red,q);
3086 SetPixelGreen(image,green,q);
3087 SetPixelBlue(image,blue,q);
cristydcfc1ad2011-07-07 16:25:41 +00003088 q+=GetPixelComponents(image);
cristy3ed852e2009-09-05 21:47:34 +00003089 }
3090 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
3091 status=MagickFalse;
3092 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3093 {
3094 MagickBooleanType
3095 proceed;
3096
cristyb5d5f722009-11-04 03:03:49 +00003097#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00003098 #pragma omp critical (MagickCore_ModulateImage)
3099#endif
3100 proceed=SetImageProgress(image,ModulateImageTag,progress++,image->rows);
3101 if (proceed == MagickFalse)
3102 status=MagickFalse;
3103 }
3104 }
3105 image_view=DestroyCacheView(image_view);
3106 return(status);
3107}
3108
3109/*
3110%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3111% %
3112% %
3113% %
3114% N e g a t e I m a g e %
3115% %
3116% %
3117% %
3118%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3119%
3120% NegateImage() negates the colors in the reference image. The grayscale
3121% option means that only grayscale values within the image are negated.
3122%
cristy50fbc382011-07-07 02:19:17 +00003123% The format of the NegateImage method is:
cristy3ed852e2009-09-05 21:47:34 +00003124%
3125% MagickBooleanType NegateImage(Image *image,
3126% const MagickBooleanType grayscale)
cristy3ed852e2009-09-05 21:47:34 +00003127%
3128% A description of each parameter follows:
3129%
3130% o image: the image.
3131%
cristy3ed852e2009-09-05 21:47:34 +00003132% o grayscale: If MagickTrue, only negate grayscale pixels within the image.
3133%
3134*/
cristy3ed852e2009-09-05 21:47:34 +00003135MagickExport MagickBooleanType NegateImage(Image *image,
3136 const MagickBooleanType grayscale)
3137{
cristy3ed852e2009-09-05 21:47:34 +00003138#define NegateImageTag "Negate/Image"
3139
cristyc4c8d132010-01-07 01:58:38 +00003140 CacheView
3141 *image_view;
3142
cristy3ed852e2009-09-05 21:47:34 +00003143 ExceptionInfo
3144 *exception;
3145
cristy3ed852e2009-09-05 21:47:34 +00003146 MagickBooleanType
3147 status;
3148
cristybb503372010-05-27 20:51:26 +00003149 MagickOffsetType
3150 progress;
3151
3152 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003153 i;
3154
cristybb503372010-05-27 20:51:26 +00003155 ssize_t
3156 y;
3157
cristy3ed852e2009-09-05 21:47:34 +00003158 assert(image != (Image *) NULL);
3159 assert(image->signature == MagickSignature);
3160 if (image->debug != MagickFalse)
3161 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3162 if (image->storage_class == PseudoClass)
3163 {
3164 /*
3165 Negate colormap.
3166 */
cristyb5d5f722009-11-04 03:03:49 +00003167#if defined(MAGICKCORE_OPENMP_SUPPORT)
3168 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003169#endif
cristybb503372010-05-27 20:51:26 +00003170 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00003171 {
3172 if (grayscale != MagickFalse)
3173 if ((image->colormap[i].red != image->colormap[i].green) ||
3174 (image->colormap[i].green != image->colormap[i].blue))
3175 continue;
cristy2b9582a2011-07-04 17:38:56 +00003176 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00003177 image->colormap[i].red=(Quantum) QuantumRange-
3178 image->colormap[i].red;
cristy2b9582a2011-07-04 17:38:56 +00003179 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00003180 image->colormap[i].green=(Quantum) QuantumRange-
3181 image->colormap[i].green;
cristy2b9582a2011-07-04 17:38:56 +00003182 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00003183 image->colormap[i].blue=(Quantum) QuantumRange-
3184 image->colormap[i].blue;
3185 }
3186 }
3187 /*
3188 Negate image.
3189 */
3190 status=MagickTrue;
3191 progress=0;
3192 exception=(&image->exception);
3193 image_view=AcquireCacheView(image);
3194 if (grayscale != MagickFalse)
3195 {
cristyb5d5f722009-11-04 03:03:49 +00003196#if defined(MAGICKCORE_OPENMP_SUPPORT)
3197 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003198#endif
cristybb503372010-05-27 20:51:26 +00003199 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003200 {
3201 MagickBooleanType
3202 sync;
3203
cristy4c08aed2011-07-01 19:47:50 +00003204 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00003205 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003206
cristy8d4629b2010-08-30 17:59:46 +00003207 register ssize_t
3208 x;
3209
cristy3ed852e2009-09-05 21:47:34 +00003210 if (status == MagickFalse)
3211 continue;
3212 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,
3213 exception);
cristy4c08aed2011-07-01 19:47:50 +00003214 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00003215 {
3216 status=MagickFalse;
3217 continue;
3218 }
cristybb503372010-05-27 20:51:26 +00003219 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003220 {
cristy4c08aed2011-07-01 19:47:50 +00003221 if ((GetPixelRed(image,q) != GetPixelGreen(image,q)) ||
3222 (GetPixelGreen(image,q) != GetPixelBlue(image,q)))
cristy3ed852e2009-09-05 21:47:34 +00003223 {
cristydcfc1ad2011-07-07 16:25:41 +00003224 q+=GetPixelComponents(image);
cristy3ed852e2009-09-05 21:47:34 +00003225 continue;
3226 }
cristy2b9582a2011-07-04 17:38:56 +00003227 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003228 SetPixelRed(image,QuantumRange-GetPixelRed(image,q),q);
cristy2b9582a2011-07-04 17:38:56 +00003229 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003230 SetPixelGreen(image,QuantumRange-GetPixelGreen(image,q),q);
cristy2b9582a2011-07-04 17:38:56 +00003231 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003232 SetPixelBlue(image,QuantumRange-GetPixelBlue(image,q),q);
cristy2b9582a2011-07-04 17:38:56 +00003233 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00003234 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +00003235 SetPixelBlack(image,QuantumRange-GetPixelBlack(image,q),q);
cristy2b9582a2011-07-04 17:38:56 +00003236 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003237 SetPixelAlpha(image,QuantumRange-GetPixelAlpha(image,q),q);
cristydcfc1ad2011-07-07 16:25:41 +00003238 q+=GetPixelComponents(image);
cristy3ed852e2009-09-05 21:47:34 +00003239 }
3240 sync=SyncCacheViewAuthenticPixels(image_view,exception);
3241 if (sync == MagickFalse)
3242 status=MagickFalse;
3243 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3244 {
3245 MagickBooleanType
3246 proceed;
3247
cristyb5d5f722009-11-04 03:03:49 +00003248#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy50fbc382011-07-07 02:19:17 +00003249 #pragma omp critical (MagickCore_NegateImage)
cristy3ed852e2009-09-05 21:47:34 +00003250#endif
3251 proceed=SetImageProgress(image,NegateImageTag,progress++,
3252 image->rows);
3253 if (proceed == MagickFalse)
3254 status=MagickFalse;
3255 }
3256 }
3257 image_view=DestroyCacheView(image_view);
3258 return(MagickTrue);
3259 }
3260 /*
3261 Negate image.
3262 */
cristyb5d5f722009-11-04 03:03:49 +00003263#if defined(MAGICKCORE_OPENMP_SUPPORT)
3264 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003265#endif
cristybb503372010-05-27 20:51:26 +00003266 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003267 {
cristy4c08aed2011-07-01 19:47:50 +00003268 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00003269 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003270
cristy8d4629b2010-08-30 17:59:46 +00003271 register ssize_t
3272 x;
3273
cristy3ed852e2009-09-05 21:47:34 +00003274 if (status == MagickFalse)
3275 continue;
3276 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00003277 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00003278 {
3279 status=MagickFalse;
3280 continue;
3281 }
cristybb503372010-05-27 20:51:26 +00003282 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003283 {
cristy2b9582a2011-07-04 17:38:56 +00003284 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003285 SetPixelRed(image,QuantumRange-GetPixelRed(image,q),q);
cristy2b9582a2011-07-04 17:38:56 +00003286 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003287 SetPixelGreen(image,QuantumRange-GetPixelGreen(image,q),q);
cristy2b9582a2011-07-04 17:38:56 +00003288 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003289 SetPixelBlue(image,QuantumRange-GetPixelBlue(image,q),q);
cristy2b9582a2011-07-04 17:38:56 +00003290 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00003291 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +00003292 SetPixelBlack(image,QuantumRange-GetPixelBlack(image,q),q);
cristy2b9582a2011-07-04 17:38:56 +00003293 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003294 SetPixelAlpha(image,QuantumRange-GetPixelAlpha(image,q),q);
cristydcfc1ad2011-07-07 16:25:41 +00003295 q+=GetPixelComponents(image);
cristy3ed852e2009-09-05 21:47:34 +00003296 }
3297 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
3298 status=MagickFalse;
3299 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3300 {
3301 MagickBooleanType
3302 proceed;
3303
cristyb5d5f722009-11-04 03:03:49 +00003304#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy50fbc382011-07-07 02:19:17 +00003305 #pragma omp critical (MagickCore_NegateImage)
cristy3ed852e2009-09-05 21:47:34 +00003306#endif
3307 proceed=SetImageProgress(image,NegateImageTag,progress++,image->rows);
3308 if (proceed == MagickFalse)
3309 status=MagickFalse;
3310 }
3311 }
3312 image_view=DestroyCacheView(image_view);
3313 return(status);
3314}
3315
3316/*
3317%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3318% %
3319% %
3320% %
3321% N o r m a l i z e I m a g e %
3322% %
3323% %
3324% %
3325%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3326%
cristya4dfb122011-07-07 19:01:57 +00003327% NormalizeImage() enhances the contrast of a color image by mapping the
3328% darkest 2 percent of all pixel to black and the brightest 1 percent to white.
cristy3ed852e2009-09-05 21:47:34 +00003329%
3330% The format of the NormalizeImage method is:
3331%
3332% MagickBooleanType NormalizeImage(Image *image)
cristy3ed852e2009-09-05 21:47:34 +00003333%
3334% A description of each parameter follows:
3335%
3336% o image: the image.
3337%
cristy3ed852e2009-09-05 21:47:34 +00003338*/
cristy3ed852e2009-09-05 21:47:34 +00003339MagickExport MagickBooleanType NormalizeImage(Image *image)
3340{
cristy3ed852e2009-09-05 21:47:34 +00003341 double
3342 black_point,
3343 white_point;
3344
cristy530239c2010-07-25 17:34:26 +00003345 black_point=(double) image->columns*image->rows*0.0015;
3346 white_point=(double) image->columns*image->rows*0.9995;
cristy50fbc382011-07-07 02:19:17 +00003347 return(ContrastStretchImage(image,black_point,white_point));
cristy3ed852e2009-09-05 21:47:34 +00003348}
3349
3350/*
3351%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3352% %
3353% %
3354% %
3355% S i g m o i d a l C o n t r a s t I m a g e %
3356% %
3357% %
3358% %
3359%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3360%
3361% SigmoidalContrastImage() adjusts the contrast of an image with a non-linear
3362% sigmoidal contrast algorithm. Increase the contrast of the image using a
3363% sigmoidal transfer function without saturating highlights or shadows.
3364% Contrast indicates how much to increase the contrast (0 is none; 3 is
3365% typical; 20 is pushing it); mid-point indicates where midtones fall in the
3366% resultant image (0 is white; 50% is middle-gray; 100% is black). Set
3367% sharpen to MagickTrue to increase the image contrast otherwise the contrast
3368% is reduced.
3369%
3370% The format of the SigmoidalContrastImage method is:
3371%
3372% MagickBooleanType SigmoidalContrastImage(Image *image,
3373% const MagickBooleanType sharpen,const char *levels)
cristy3ed852e2009-09-05 21:47:34 +00003374%
3375% A description of each parameter follows:
3376%
3377% o image: the image.
3378%
cristy3ed852e2009-09-05 21:47:34 +00003379% o sharpen: Increase or decrease image contrast.
3380%
cristyfa769582010-09-30 23:30:03 +00003381% o alpha: strength of the contrast, the larger the number the more
3382% 'threshold-like' it becomes.
cristy3ed852e2009-09-05 21:47:34 +00003383%
cristyfa769582010-09-30 23:30:03 +00003384% o beta: midpoint of the function as a color value 0 to QuantumRange.
cristy3ed852e2009-09-05 21:47:34 +00003385%
3386*/
cristy3ed852e2009-09-05 21:47:34 +00003387MagickExport MagickBooleanType SigmoidalContrastImage(Image *image,
cristy9ee60942011-07-06 14:54:38 +00003388 const MagickBooleanType sharpen,const double contrast,const double midpoint)
cristy3ed852e2009-09-05 21:47:34 +00003389{
3390#define SigmoidalContrastImageTag "SigmoidalContrast/Image"
3391
cristyc4c8d132010-01-07 01:58:38 +00003392 CacheView
3393 *image_view;
3394
cristy3ed852e2009-09-05 21:47:34 +00003395 ExceptionInfo
3396 *exception;
3397
cristy3ed852e2009-09-05 21:47:34 +00003398 MagickBooleanType
3399 status;
3400
cristybb503372010-05-27 20:51:26 +00003401 MagickOffsetType
3402 progress;
3403
cristy3ed852e2009-09-05 21:47:34 +00003404 MagickRealType
3405 *sigmoidal_map;
3406
cristybb503372010-05-27 20:51:26 +00003407 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003408 i;
3409
cristybb503372010-05-27 20:51:26 +00003410 ssize_t
3411 y;
3412
cristy3ed852e2009-09-05 21:47:34 +00003413 /*
3414 Allocate and initialize sigmoidal maps.
3415 */
3416 assert(image != (Image *) NULL);
3417 assert(image->signature == MagickSignature);
3418 if (image->debug != MagickFalse)
3419 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3420 sigmoidal_map=(MagickRealType *) AcquireQuantumMemory(MaxMap+1UL,
3421 sizeof(*sigmoidal_map));
3422 if (sigmoidal_map == (MagickRealType *) NULL)
3423 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
3424 image->filename);
3425 (void) ResetMagickMemory(sigmoidal_map,0,(MaxMap+1)*sizeof(*sigmoidal_map));
cristyb5d5f722009-11-04 03:03:49 +00003426#if defined(MAGICKCORE_OPENMP_SUPPORT)
3427 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003428#endif
cristybb503372010-05-27 20:51:26 +00003429 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +00003430 {
3431 if (sharpen != MagickFalse)
3432 {
3433 sigmoidal_map[i]=(MagickRealType) ScaleMapToQuantum((MagickRealType)
3434 (MaxMap*((1.0/(1.0+exp(contrast*(midpoint/(double) QuantumRange-
3435 (double) i/MaxMap))))-(1.0/(1.0+exp(contrast*(midpoint/
3436 (double) QuantumRange)))))/((1.0/(1.0+exp(contrast*(midpoint/
3437 (double) QuantumRange-1.0))))-(1.0/(1.0+exp(contrast*(midpoint/
3438 (double) QuantumRange)))))+0.5));
3439 continue;
3440 }
3441 sigmoidal_map[i]=(MagickRealType) ScaleMapToQuantum((MagickRealType)
3442 (MaxMap*(QuantumScale*midpoint-log((1.0-(1.0/(1.0+exp(midpoint/
3443 (double) QuantumRange*contrast))+((double) i/MaxMap)*((1.0/
3444 (1.0+exp(contrast*(midpoint/(double) QuantumRange-1.0))))-(1.0/
3445 (1.0+exp(midpoint/(double) QuantumRange*contrast))))))/
3446 (1.0/(1.0+exp(midpoint/(double) QuantumRange*contrast))+
3447 ((double) i/MaxMap)*((1.0/(1.0+exp(contrast*(midpoint/
3448 (double) QuantumRange-1.0))))-(1.0/(1.0+exp(midpoint/
3449 (double) QuantumRange*contrast))))))/contrast)));
3450 }
3451 if (image->storage_class == PseudoClass)
3452 {
3453 /*
3454 Sigmoidal-contrast enhance colormap.
3455 */
cristyb5d5f722009-11-04 03:03:49 +00003456#if defined(MAGICKCORE_OPENMP_SUPPORT)
3457 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003458#endif
cristybb503372010-05-27 20:51:26 +00003459 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00003460 {
cristy2b9582a2011-07-04 17:38:56 +00003461 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristyce70c172010-01-07 17:15:30 +00003462 image->colormap[i].red=ClampToQuantum(sigmoidal_map[
cristy3ed852e2009-09-05 21:47:34 +00003463 ScaleQuantumToMap(image->colormap[i].red)]);
cristy2b9582a2011-07-04 17:38:56 +00003464 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristyce70c172010-01-07 17:15:30 +00003465 image->colormap[i].green=ClampToQuantum(sigmoidal_map[
cristy3ed852e2009-09-05 21:47:34 +00003466 ScaleQuantumToMap(image->colormap[i].green)]);
cristy2b9582a2011-07-04 17:38:56 +00003467 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristyce70c172010-01-07 17:15:30 +00003468 image->colormap[i].blue=ClampToQuantum(sigmoidal_map[
cristy3ed852e2009-09-05 21:47:34 +00003469 ScaleQuantumToMap(image->colormap[i].blue)]);
cristy2b9582a2011-07-04 17:38:56 +00003470 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003471 image->colormap[i].alpha=ClampToQuantum(sigmoidal_map[
3472 ScaleQuantumToMap(image->colormap[i].alpha)]);
cristy3ed852e2009-09-05 21:47:34 +00003473 }
3474 }
3475 /*
3476 Sigmoidal-contrast enhance image.
3477 */
3478 status=MagickTrue;
3479 progress=0;
3480 exception=(&image->exception);
3481 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00003482#if defined(MAGICKCORE_OPENMP_SUPPORT)
3483 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003484#endif
cristybb503372010-05-27 20:51:26 +00003485 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003486 {
cristy4c08aed2011-07-01 19:47:50 +00003487 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00003488 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003489
cristy8d4629b2010-08-30 17:59:46 +00003490 register ssize_t
3491 x;
3492
cristy3ed852e2009-09-05 21:47:34 +00003493 if (status == MagickFalse)
3494 continue;
3495 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00003496 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00003497 {
3498 status=MagickFalse;
3499 continue;
3500 }
cristybb503372010-05-27 20:51:26 +00003501 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003502 {
cristy2b9582a2011-07-04 17:38:56 +00003503 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003504 SetPixelRed(image,ClampToQuantum(sigmoidal_map[ScaleQuantumToMap(
3505 GetPixelRed(image,q))]),q);
cristy2b9582a2011-07-04 17:38:56 +00003506 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003507 SetPixelGreen(image,ClampToQuantum(sigmoidal_map[ScaleQuantumToMap(
3508 GetPixelGreen(image,q))]),q);
cristy2b9582a2011-07-04 17:38:56 +00003509 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003510 SetPixelBlue(image,ClampToQuantum(sigmoidal_map[ScaleQuantumToMap(
3511 GetPixelBlue(image,q))]),q);
cristy2b9582a2011-07-04 17:38:56 +00003512 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00003513 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +00003514 SetPixelBlack(image,ClampToQuantum(sigmoidal_map[ScaleQuantumToMap(
3515 GetPixelBlack(image,q))]),q);
cristy2b9582a2011-07-04 17:38:56 +00003516 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003517 SetPixelAlpha(image,ClampToQuantum(sigmoidal_map[ScaleQuantumToMap(
3518 GetPixelAlpha(image,q))]),q);
cristydcfc1ad2011-07-07 16:25:41 +00003519 q+=GetPixelComponents(image);
cristy3ed852e2009-09-05 21:47:34 +00003520 }
3521 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
3522 status=MagickFalse;
3523 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3524 {
3525 MagickBooleanType
3526 proceed;
3527
cristyb5d5f722009-11-04 03:03:49 +00003528#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy9ee60942011-07-06 14:54:38 +00003529 #pragma omp critical (MagickCore_SigmoidalContrastImage)
cristy3ed852e2009-09-05 21:47:34 +00003530#endif
3531 proceed=SetImageProgress(image,SigmoidalContrastImageTag,progress++,
3532 image->rows);
3533 if (proceed == MagickFalse)
3534 status=MagickFalse;
3535 }
3536 }
3537 image_view=DestroyCacheView(image_view);
3538 sigmoidal_map=(MagickRealType *) RelinquishMagickMemory(sigmoidal_map);
3539 return(status);
3540}