blob: 14265bb05e04dba5114686754055d74c4838cf36 [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 */
cristyab015852011-07-06 01:03:32 +0000116 (void) GetImageChannelMean(image,DefaultChannels,&mean,&sans,
117 &image->exception);
cristy4c08aed2011-07-01 19:47:50 +0000118 gamma=log(mean*QuantumScale)/log_mean;
cristyf89cb1d2011-07-07 01:24:37 +0000119 return(LevelImage(image,0.0,(double) QuantumRange,gamma));
cristy3ed852e2009-09-05 21:47:34 +0000120 }
cristy3ed852e2009-09-05 21:47:34 +0000121 /*
cristy4c08aed2011-07-01 19:47:50 +0000122 Auto-gamma each channel separately.
cristy3ed852e2009-09-05 21:47:34 +0000123 */
cristy4c08aed2011-07-01 19:47:50 +0000124 status=MagickTrue;
cristy2b9582a2011-07-04 17:38:56 +0000125 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +0000126 {
cristy2b726bd2010-01-11 01:05:39 +0000127 (void) GetImageChannelMean(image,RedChannel,&mean,&sans,
128 &image->exception);
cristy4c08aed2011-07-01 19:47:50 +0000129 gamma=log(mean*QuantumScale)/log_mean;
cristy490408a2011-07-07 14:42:05 +0000130 PushPixelComponentMap(image,RedChannel);
131 status=status && LevelImage(image,0.0,(double) QuantumRange,gamma);
132 PopPixelComponentMap(image);
cristy3ed852e2009-09-05 21:47:34 +0000133 }
cristy2b9582a2011-07-04 17:38:56 +0000134 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +0000135 {
cristy2b726bd2010-01-11 01:05:39 +0000136 (void) GetImageChannelMean(image,GreenChannel,&mean,&sans,
137 &image->exception);
cristy4c08aed2011-07-01 19:47:50 +0000138 gamma=log(mean*QuantumScale)/log_mean;
cristy490408a2011-07-07 14:42:05 +0000139 PushPixelComponentMap(image,GreenChannel);
140 status=status && LevelImage(image,0.0,(double) QuantumRange,gamma);
141 PopPixelComponentMap(image);
cristy3ed852e2009-09-05 21:47:34 +0000142 }
cristy2b9582a2011-07-04 17:38:56 +0000143 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +0000144 {
cristy2b726bd2010-01-11 01:05:39 +0000145 (void) GetImageChannelMean(image,BlueChannel,&mean,&sans,
146 &image->exception);
cristy4c08aed2011-07-01 19:47:50 +0000147 gamma=log(mean*QuantumScale)/log_mean;
cristy490408a2011-07-07 14:42:05 +0000148 PushPixelComponentMap(image,BlueChannel);
149 status=status && LevelImage(image,0.0,(double) QuantumRange,gamma);
150 PopPixelComponentMap(image);
cristy4c08aed2011-07-01 19:47:50 +0000151 }
cristy2b9582a2011-07-04 17:38:56 +0000152 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +0000153 (image->colorspace == CMYKColorspace))
154 {
155 (void) GetImageChannelMean(image,BlackChannel,&mean,&sans,
156 &image->exception);
157 gamma=log(mean*QuantumScale)/log_mean;
cristy490408a2011-07-07 14:42:05 +0000158 PushPixelComponentMap(image,BlackChannel);
159 status=status && LevelImage(image,0.0,(double) QuantumRange,gamma);
160 PopPixelComponentMap(image);
cristy3ed852e2009-09-05 21:47:34 +0000161 }
cristy2b9582a2011-07-04 17:38:56 +0000162 if (((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +0000163 (image->matte == MagickTrue))
164 {
cristy2b726bd2010-01-11 01:05:39 +0000165 (void) GetImageChannelMean(image,OpacityChannel,&mean,&sans,
166 &image->exception);
cristy4c08aed2011-07-01 19:47:50 +0000167 gamma=log(mean*QuantumScale)/log_mean;
cristy490408a2011-07-07 14:42:05 +0000168 PushPixelComponentMap(image,AlphaChannel);
169 status=status && LevelImage(image,0.0,(double) QuantumRange,gamma);
170 PopPixelComponentMap(image);
cristy3ed852e2009-09-05 21:47:34 +0000171 }
172 return(status != 0 ? MagickTrue : MagickFalse);
173}
174
175/*
176%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
177% %
178% %
179% %
180% A u t o L e v e l I m a g e %
181% %
182% %
183% %
184%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
185%
186% AutoLevelImage() adjusts the levels of a particular image channel by
187% scaling the minimum and maximum values to the full quantum range.
188%
189% The format of the LevelImage method is:
190%
191% MagickBooleanType AutoLevelImage(Image *image)
cristy3ed852e2009-09-05 21:47:34 +0000192%
193% A description of each parameter follows:
194%
195% o image: The image to auto-level
196%
cristy3ed852e2009-09-05 21:47:34 +0000197*/
cristy3ed852e2009-09-05 21:47:34 +0000198MagickExport MagickBooleanType AutoLevelImage(Image *image)
199{
cristy490408a2011-07-07 14:42:05 +0000200 return(MinMaxStretchImage(image,0.0,0.0));
cristy3ed852e2009-09-05 21:47:34 +0000201}
202
203/*
204%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
205% %
206% %
207% %
cristya28d6b82010-01-11 20:03:47 +0000208% B r i g h t n e s s C o n t r a s t I m a g e %
209% %
210% %
211% %
212%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
213%
214% Use BrightnessContrastImage() to change the brightness and/or contrast of
215% an image. It converts the brightness and contrast parameters into slope
216% and intercept and calls a polynomical function to apply to the image.
217%
218% The format of the BrightnessContrastImage method is:
219%
220% MagickBooleanType BrightnessContrastImage(Image *image,
221% const double brightness,const double contrast)
cristya28d6b82010-01-11 20:03:47 +0000222%
223% A description of each parameter follows:
224%
225% o image: the image.
226%
cristya28d6b82010-01-11 20:03:47 +0000227% o brightness: the brightness percent (-100 .. 100).
228%
229% o contrast: the contrast percent (-100 .. 100).
230%
231*/
cristya28d6b82010-01-11 20:03:47 +0000232MagickExport MagickBooleanType BrightnessContrastImage(Image *image,
233 const double brightness,const double contrast)
234{
cristya28d6b82010-01-11 20:03:47 +0000235#define BrightnessContastImageTag "BrightnessContast/Image"
236
237 double
238 alpha,
239 intercept,
240 coefficients[2],
241 slope;
242
243 MagickBooleanType
244 status;
245
246 /*
247 Compute slope and intercept.
248 */
249 assert(image != (Image *) NULL);
250 assert(image->signature == MagickSignature);
251 if (image->debug != MagickFalse)
252 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
253 alpha=contrast;
cristy4205a3c2010-09-12 20:19:59 +0000254 slope=tan((double) (MagickPI*(alpha/100.0+1.0)/4.0));
cristya28d6b82010-01-11 20:03:47 +0000255 if (slope < 0.0)
256 slope=0.0;
257 intercept=brightness/100.0+((100-brightness)/200.0)*(1.0-slope);
258 coefficients[0]=slope;
259 coefficients[1]=intercept;
cristy9ee60942011-07-06 14:54:38 +0000260 status=FunctionImageChannel(image,DefaultChannels,PolynomialFunction,2,
261 coefficients,&image->exception);
cristya28d6b82010-01-11 20:03:47 +0000262 return(status);
263}
264
265/*
266%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
267% %
268% %
269% %
cristy3ed852e2009-09-05 21:47:34 +0000270% C o l o r D e c i s i o n L i s t I m a g e %
271% %
272% %
273% %
274%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
275%
276% ColorDecisionListImage() accepts a lightweight Color Correction Collection
277% (CCC) file which solely contains one or more color corrections and applies
278% the correction to the image. Here is a sample CCC file:
279%
280% <ColorCorrectionCollection xmlns="urn:ASC:CDL:v1.2">
281% <ColorCorrection id="cc03345">
282% <SOPNode>
283% <Slope> 0.9 1.2 0.5 </Slope>
284% <Offset> 0.4 -0.5 0.6 </Offset>
285% <Power> 1.0 0.8 1.5 </Power>
286% </SOPNode>
287% <SATNode>
288% <Saturation> 0.85 </Saturation>
289% </SATNode>
290% </ColorCorrection>
291% </ColorCorrectionCollection>
292%
293% which includes the slop, offset, and power for each of the RGB channels
294% as well as the saturation.
295%
296% The format of the ColorDecisionListImage method is:
297%
298% MagickBooleanType ColorDecisionListImage(Image *image,
299% const char *color_correction_collection)
300%
301% A description of each parameter follows:
302%
303% o image: the image.
304%
305% o color_correction_collection: the color correction collection in XML.
306%
307*/
308MagickExport MagickBooleanType ColorDecisionListImage(Image *image,
309 const char *color_correction_collection)
310{
311#define ColorDecisionListCorrectImageTag "ColorDecisionList/Image"
312
313 typedef struct _Correction
314 {
315 double
316 slope,
317 offset,
318 power;
319 } Correction;
320
321 typedef struct _ColorCorrection
322 {
323 Correction
324 red,
325 green,
326 blue;
327
328 double
329 saturation;
330 } ColorCorrection;
331
cristyc4c8d132010-01-07 01:58:38 +0000332 CacheView
333 *image_view;
334
cristy3ed852e2009-09-05 21:47:34 +0000335 char
336 token[MaxTextExtent];
337
338 ColorCorrection
339 color_correction;
340
341 const char
342 *content,
343 *p;
344
345 ExceptionInfo
346 *exception;
347
cristy3ed852e2009-09-05 21:47:34 +0000348 MagickBooleanType
349 status;
350
cristybb503372010-05-27 20:51:26 +0000351 MagickOffsetType
352 progress;
353
cristy3ed852e2009-09-05 21:47:34 +0000354 PixelPacket
355 *cdl_map;
356
cristybb503372010-05-27 20:51:26 +0000357 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000358 i;
359
cristybb503372010-05-27 20:51:26 +0000360 ssize_t
361 y;
362
cristy3ed852e2009-09-05 21:47:34 +0000363 XMLTreeInfo
364 *cc,
365 *ccc,
366 *sat,
367 *sop;
368
cristy3ed852e2009-09-05 21:47:34 +0000369 /*
370 Allocate and initialize cdl maps.
371 */
372 assert(image != (Image *) NULL);
373 assert(image->signature == MagickSignature);
374 if (image->debug != MagickFalse)
375 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
376 if (color_correction_collection == (const char *) NULL)
377 return(MagickFalse);
378 ccc=NewXMLTree((const char *) color_correction_collection,&image->exception);
379 if (ccc == (XMLTreeInfo *) NULL)
380 return(MagickFalse);
381 cc=GetXMLTreeChild(ccc,"ColorCorrection");
382 if (cc == (XMLTreeInfo *) NULL)
383 {
384 ccc=DestroyXMLTree(ccc);
385 return(MagickFalse);
386 }
387 color_correction.red.slope=1.0;
388 color_correction.red.offset=0.0;
389 color_correction.red.power=1.0;
390 color_correction.green.slope=1.0;
391 color_correction.green.offset=0.0;
392 color_correction.green.power=1.0;
393 color_correction.blue.slope=1.0;
394 color_correction.blue.offset=0.0;
395 color_correction.blue.power=1.0;
396 color_correction.saturation=0.0;
397 sop=GetXMLTreeChild(cc,"SOPNode");
398 if (sop != (XMLTreeInfo *) NULL)
399 {
400 XMLTreeInfo
401 *offset,
402 *power,
403 *slope;
404
405 slope=GetXMLTreeChild(sop,"Slope");
406 if (slope != (XMLTreeInfo *) NULL)
407 {
408 content=GetXMLTreeContent(slope);
409 p=(const char *) content;
410 for (i=0; (*p != '\0') && (i < 3); i++)
411 {
412 GetMagickToken(p,&p,token);
413 if (*token == ',')
414 GetMagickToken(p,&p,token);
415 switch (i)
416 {
cristyc1acd842011-05-19 23:05:47 +0000417 case 0:
418 {
419 color_correction.red.slope=InterpretLocaleValue(token,
420 (char **) NULL);
421 break;
422 }
423 case 1:
424 {
425 color_correction.green.slope=InterpretLocaleValue(token,
426 (char **) NULL);
427 break;
428 }
429 case 2:
430 {
431 color_correction.blue.slope=InterpretLocaleValue(token,
432 (char **) NULL);
433 break;
434 }
cristy3ed852e2009-09-05 21:47:34 +0000435 }
436 }
437 }
438 offset=GetXMLTreeChild(sop,"Offset");
439 if (offset != (XMLTreeInfo *) NULL)
440 {
441 content=GetXMLTreeContent(offset);
442 p=(const char *) content;
443 for (i=0; (*p != '\0') && (i < 3); i++)
444 {
445 GetMagickToken(p,&p,token);
446 if (*token == ',')
447 GetMagickToken(p,&p,token);
448 switch (i)
449 {
cristyc1acd842011-05-19 23:05:47 +0000450 case 0:
451 {
452 color_correction.red.offset=InterpretLocaleValue(token,
453 (char **) NULL);
454 break;
455 }
456 case 1:
457 {
458 color_correction.green.offset=InterpretLocaleValue(token,
459 (char **) NULL);
460 break;
461 }
462 case 2:
463 {
464 color_correction.blue.offset=InterpretLocaleValue(token,
465 (char **) NULL);
466 break;
467 }
cristy3ed852e2009-09-05 21:47:34 +0000468 }
469 }
470 }
471 power=GetXMLTreeChild(sop,"Power");
472 if (power != (XMLTreeInfo *) NULL)
473 {
474 content=GetXMLTreeContent(power);
475 p=(const char *) content;
476 for (i=0; (*p != '\0') && (i < 3); i++)
477 {
478 GetMagickToken(p,&p,token);
479 if (*token == ',')
480 GetMagickToken(p,&p,token);
481 switch (i)
482 {
cristyc1acd842011-05-19 23:05:47 +0000483 case 0:
484 {
485 color_correction.red.power=InterpretLocaleValue(token,
486 (char **) NULL);
487 break;
488 }
489 case 1:
490 {
491 color_correction.green.power=InterpretLocaleValue(token,
492 (char **) NULL);
493 break;
494 }
495 case 2:
496 {
497 color_correction.blue.power=InterpretLocaleValue(token,
498 (char **) NULL);
499 break;
500 }
cristy3ed852e2009-09-05 21:47:34 +0000501 }
502 }
503 }
504 }
505 sat=GetXMLTreeChild(cc,"SATNode");
506 if (sat != (XMLTreeInfo *) NULL)
507 {
508 XMLTreeInfo
509 *saturation;
510
511 saturation=GetXMLTreeChild(sat,"Saturation");
512 if (saturation != (XMLTreeInfo *) NULL)
513 {
514 content=GetXMLTreeContent(saturation);
515 p=(const char *) content;
516 GetMagickToken(p,&p,token);
cristyc1acd842011-05-19 23:05:47 +0000517 color_correction.saturation=InterpretLocaleValue(token,
518 (char **) NULL);
cristy3ed852e2009-09-05 21:47:34 +0000519 }
520 }
521 ccc=DestroyXMLTree(ccc);
522 if (image->debug != MagickFalse)
523 {
524 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
525 " Color Correction Collection:");
526 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000527 " color_correction.red.slope: %g",color_correction.red.slope);
cristy3ed852e2009-09-05 21:47:34 +0000528 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000529 " color_correction.red.offset: %g",color_correction.red.offset);
cristy3ed852e2009-09-05 21:47:34 +0000530 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000531 " color_correction.red.power: %g",color_correction.red.power);
cristy3ed852e2009-09-05 21:47:34 +0000532 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000533 " color_correction.green.slope: %g",color_correction.green.slope);
cristy3ed852e2009-09-05 21:47:34 +0000534 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000535 " color_correction.green.offset: %g",color_correction.green.offset);
cristy3ed852e2009-09-05 21:47:34 +0000536 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000537 " color_correction.green.power: %g",color_correction.green.power);
cristy3ed852e2009-09-05 21:47:34 +0000538 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000539 " color_correction.blue.slope: %g",color_correction.blue.slope);
cristy3ed852e2009-09-05 21:47:34 +0000540 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000541 " color_correction.blue.offset: %g",color_correction.blue.offset);
cristy3ed852e2009-09-05 21:47:34 +0000542 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000543 " color_correction.blue.power: %g",color_correction.blue.power);
cristy3ed852e2009-09-05 21:47:34 +0000544 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000545 " color_correction.saturation: %g",color_correction.saturation);
cristy3ed852e2009-09-05 21:47:34 +0000546 }
547 cdl_map=(PixelPacket *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*cdl_map));
548 if (cdl_map == (PixelPacket *) NULL)
549 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
550 image->filename);
cristyb5d5f722009-11-04 03:03:49 +0000551#if defined(MAGICKCORE_OPENMP_SUPPORT)
552 #pragma omp parallel for schedule(dynamic,4)
cristy3ed852e2009-09-05 21:47:34 +0000553#endif
cristybb503372010-05-27 20:51:26 +0000554 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +0000555 {
cristyce70c172010-01-07 17:15:30 +0000556 cdl_map[i].red=ClampToQuantum((MagickRealType) ScaleMapToQuantum((
cristy3ed852e2009-09-05 21:47:34 +0000557 MagickRealType) (MaxMap*(pow(color_correction.red.slope*i/MaxMap+
558 color_correction.red.offset,color_correction.red.power)))));
cristyce70c172010-01-07 17:15:30 +0000559 cdl_map[i].green=ClampToQuantum((MagickRealType) ScaleMapToQuantum((
cristy3ed852e2009-09-05 21:47:34 +0000560 MagickRealType) (MaxMap*(pow(color_correction.green.slope*i/MaxMap+
561 color_correction.green.offset,color_correction.green.power)))));
cristyce70c172010-01-07 17:15:30 +0000562 cdl_map[i].blue=ClampToQuantum((MagickRealType) ScaleMapToQuantum((
cristy3ed852e2009-09-05 21:47:34 +0000563 MagickRealType) (MaxMap*(pow(color_correction.blue.slope*i/MaxMap+
564 color_correction.blue.offset,color_correction.blue.power)))));
565 }
566 if (image->storage_class == PseudoClass)
567 {
568 /*
569 Apply transfer function to colormap.
570 */
cristyb5d5f722009-11-04 03:03:49 +0000571#if defined(MAGICKCORE_OPENMP_SUPPORT)
572 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000573#endif
cristybb503372010-05-27 20:51:26 +0000574 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +0000575 {
576 double
577 luma;
578
579 luma=0.2126*image->colormap[i].red+0.7152*image->colormap[i].green+
580 0.0722*image->colormap[i].blue;
cristyce70c172010-01-07 17:15:30 +0000581 image->colormap[i].red=ClampToQuantum(luma+color_correction.saturation*
cristy3ed852e2009-09-05 21:47:34 +0000582 cdl_map[ScaleQuantumToMap(image->colormap[i].red)].red-luma);
cristyce70c172010-01-07 17:15:30 +0000583 image->colormap[i].green=ClampToQuantum(luma+
cristy3ed852e2009-09-05 21:47:34 +0000584 color_correction.saturation*cdl_map[ScaleQuantumToMap(
585 image->colormap[i].green)].green-luma);
cristyce70c172010-01-07 17:15:30 +0000586 image->colormap[i].blue=ClampToQuantum(luma+color_correction.saturation*
cristy3ed852e2009-09-05 21:47:34 +0000587 cdl_map[ScaleQuantumToMap(image->colormap[i].blue)].blue-luma);
588 }
589 }
590 /*
591 Apply transfer function to image.
592 */
593 status=MagickTrue;
594 progress=0;
595 exception=(&image->exception);
596 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +0000597#if defined(MAGICKCORE_OPENMP_SUPPORT)
598 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000599#endif
cristybb503372010-05-27 20:51:26 +0000600 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000601 {
602 double
603 luma;
604
cristy4c08aed2011-07-01 19:47:50 +0000605 register Quantum
cristyc47d1f82009-11-26 01:44:43 +0000606 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000607
cristy8d4629b2010-08-30 17:59:46 +0000608 register ssize_t
609 x;
610
cristy3ed852e2009-09-05 21:47:34 +0000611 if (status == MagickFalse)
612 continue;
613 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +0000614 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +0000615 {
616 status=MagickFalse;
617 continue;
618 }
cristybb503372010-05-27 20:51:26 +0000619 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000620 {
cristy4c08aed2011-07-01 19:47:50 +0000621 luma=0.2126*GetPixelRed(image,q)+0.7152*GetPixelGreen(image,q)+0.0722*
622 GetPixelBlue(image,q);
623 SetPixelRed(image,ClampToQuantum(luma+color_correction.saturation*
624 (cdl_map[ScaleQuantumToMap(GetPixelRed(image,q))].red-luma)),q);
625 SetPixelGreen(image,ClampToQuantum(luma+color_correction.saturation*
626 (cdl_map[ScaleQuantumToMap(GetPixelGreen(image,q))].green-luma)),q);
627 SetPixelBlue(image,ClampToQuantum(luma+color_correction.saturation*
628 (cdl_map[ScaleQuantumToMap(GetPixelBlue(image,q))].blue-luma)),q);
cristydcfc1ad2011-07-07 16:25:41 +0000629 q+=GetPixelComponents(image);
cristy3ed852e2009-09-05 21:47:34 +0000630 }
631 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
632 status=MagickFalse;
633 if (image->progress_monitor != (MagickProgressMonitor) NULL)
634 {
635 MagickBooleanType
636 proceed;
637
cristyb5d5f722009-11-04 03:03:49 +0000638#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +0000639 #pragma omp critical (MagickCore_ColorDecisionListImageChannel)
640#endif
641 proceed=SetImageProgress(image,ColorDecisionListCorrectImageTag,
642 progress++,image->rows);
643 if (proceed == MagickFalse)
644 status=MagickFalse;
645 }
646 }
647 image_view=DestroyCacheView(image_view);
648 cdl_map=(PixelPacket *) RelinquishMagickMemory(cdl_map);
649 return(status);
650}
651
652/*
653%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
654% %
655% %
656% %
657% C l u t I m a g e %
658% %
659% %
660% %
661%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
662%
663% ClutImage() replaces each color value in the given image, by using it as an
664% index to lookup a replacement color value in a Color Look UP Table in the
cristycee97112010-05-28 00:44:52 +0000665% form of an image. The values are extracted along a diagonal of the CLUT
cristy3ed852e2009-09-05 21:47:34 +0000666% image so either a horizontal or vertial gradient image can be used.
667%
668% Typically this is used to either re-color a gray-scale image according to a
669% color gradient in the CLUT image, or to perform a freeform histogram
670% (level) adjustment according to the (typically gray-scale) gradient in the
671% CLUT image.
672%
673% When the 'channel' mask includes the matte/alpha transparency channel but
674% one image has no such channel it is assumed that that image is a simple
675% gray-scale image that will effect the alpha channel values, either for
676% gray-scale coloring (with transparent or semi-transparent colors), or
677% a histogram adjustment of existing alpha channel values. If both images
678% have matte channels, direct and normal indexing is applied, which is rarely
679% used.
680%
681% The format of the ClutImage method is:
682%
683% MagickBooleanType ClutImage(Image *image,Image *clut_image)
cristy3ed852e2009-09-05 21:47:34 +0000684%
685% A description of each parameter follows:
686%
687% o image: the image, which is replaced by indexed CLUT values
688%
689% o clut_image: the color lookup table image for replacement color values.
690%
691% o channel: the channel.
692%
693*/
cristy3ed852e2009-09-05 21:47:34 +0000694MagickExport MagickBooleanType ClutImage(Image *image,const Image *clut_image)
695{
cristy4c08aed2011-07-01 19:47:50 +0000696#define ClampAlphaPixelComponent(pixel) ClampToQuantum((pixel)->alpha)
697#define ClampBlackPixelComponent(pixel) ClampToQuantum((pixel)->black)
698#define ClampBluePixelComponent(pixel) ClampToQuantum((pixel)->blue)
699#define ClampGreenPixelComponent(pixel) ClampToQuantum((pixel)->green)
700#define ClampRedPixelComponent(pixel) ClampToQuantum((pixel)->red)
cristy3ed852e2009-09-05 21:47:34 +0000701#define ClutImageTag "Clut/Image"
702
cristyfa112112010-01-04 17:48:07 +0000703 CacheView
cristy708333f2011-03-26 01:25:07 +0000704 *clut_view,
cristyfa112112010-01-04 17:48:07 +0000705 *image_view;
706
cristy3ed852e2009-09-05 21:47:34 +0000707 ExceptionInfo
708 *exception;
709
cristy3ed852e2009-09-05 21:47:34 +0000710 MagickBooleanType
711 status;
712
cristybb503372010-05-27 20:51:26 +0000713 MagickOffsetType
714 progress;
715
cristy4c08aed2011-07-01 19:47:50 +0000716 PixelInfo
cristy49f37242011-03-22 18:18:23 +0000717 *clut_map;
718
719 register ssize_t
720 i;
cristy3ed852e2009-09-05 21:47:34 +0000721
cristybb503372010-05-27 20:51:26 +0000722 ssize_t
723 adjust,
724 y;
725
cristy3ed852e2009-09-05 21:47:34 +0000726 assert(image != (Image *) NULL);
727 assert(image->signature == MagickSignature);
728 if (image->debug != MagickFalse)
729 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
730 assert(clut_image != (Image *) NULL);
731 assert(clut_image->signature == MagickSignature);
732 if (SetImageStorageClass(image,DirectClass) == MagickFalse)
733 return(MagickFalse);
cristy4c08aed2011-07-01 19:47:50 +0000734 clut_map=(PixelInfo *) AcquireQuantumMemory(MaxMap+1UL,
cristy49f37242011-03-22 18:18:23 +0000735 sizeof(*clut_map));
cristy4c08aed2011-07-01 19:47:50 +0000736 if (clut_map == (PixelInfo *) NULL)
cristy49f37242011-03-22 18:18:23 +0000737 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
738 image->filename);
cristy3ed852e2009-09-05 21:47:34 +0000739 /*
740 Clut image.
741 */
742 status=MagickTrue;
743 progress=0;
cristybb503372010-05-27 20:51:26 +0000744 adjust=(ssize_t) (clut_image->interpolate == IntegerInterpolatePixel ? 0 : 1);
cristy3ed852e2009-09-05 21:47:34 +0000745 exception=(&image->exception);
cristy708333f2011-03-26 01:25:07 +0000746 clut_view=AcquireCacheView(clut_image);
cristyaf6bc722011-03-25 19:16:14 +0000747#if defined(MAGICKCORE_OPENMP_SUPPORT)
748 #pragma omp parallel for schedule(dynamic,4)
749#endif
cristy49f37242011-03-22 18:18:23 +0000750 for (i=0; i <= (ssize_t) MaxMap; i++)
751 {
cristy4c08aed2011-07-01 19:47:50 +0000752 GetPixelInfo(clut_image,clut_map+i);
753 (void) InterpolatePixelInfo(clut_image,clut_view,
cristy8a7c3e82011-03-26 02:10:53 +0000754 UndefinedInterpolatePixel,QuantumScale*i*(clut_image->columns-adjust),
755 QuantumScale*i*(clut_image->rows-adjust),clut_map+i,exception);
cristy49f37242011-03-22 18:18:23 +0000756 }
cristy708333f2011-03-26 01:25:07 +0000757 clut_view=DestroyCacheView(clut_view);
758 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +0000759#if defined(MAGICKCORE_OPENMP_SUPPORT)
760 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000761#endif
cristybb503372010-05-27 20:51:26 +0000762 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000763 {
cristy4c08aed2011-07-01 19:47:50 +0000764 PixelInfo
cristy3635df22011-03-25 00:16:16 +0000765 pixel;
766
cristy4c08aed2011-07-01 19:47:50 +0000767 register Quantum
cristyc47d1f82009-11-26 01:44:43 +0000768 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000769
cristy8d4629b2010-08-30 17:59:46 +0000770 register ssize_t
771 x;
772
cristy3ed852e2009-09-05 21:47:34 +0000773 if (status == MagickFalse)
774 continue;
775 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +0000776 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +0000777 {
778 status=MagickFalse;
779 continue;
780 }
cristy4c08aed2011-07-01 19:47:50 +0000781 GetPixelInfo(image,&pixel);
cristybb503372010-05-27 20:51:26 +0000782 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000783 {
cristy4c08aed2011-07-01 19:47:50 +0000784 SetPixelInfo(image,q,&pixel);
cristy2b9582a2011-07-04 17:38:56 +0000785 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000786 SetPixelRed(image,ClampRedPixelComponent(clut_map+
787 ScaleQuantumToMap(GetPixelRed(image,q))),q);
cristy2b9582a2011-07-04 17:38:56 +0000788 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000789 SetPixelGreen(image,ClampGreenPixelComponent(clut_map+
790 ScaleQuantumToMap(GetPixelGreen(image,q))),q);
cristy2b9582a2011-07-04 17:38:56 +0000791 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000792 SetPixelBlue(image,ClampBluePixelComponent(clut_map+
793 ScaleQuantumToMap(GetPixelBlue(image,q))),q);
cristy2b9582a2011-07-04 17:38:56 +0000794 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +0000795 (image->colorspace == CMYKColorspace))
796 SetPixelBlack(image,ClampBlackPixelComponent(clut_map+
797 ScaleQuantumToMap(GetPixelBlack(image,q))),q);
cristy2b9582a2011-07-04 17:38:56 +0000798 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy3635df22011-03-25 00:16:16 +0000799 {
800 if (clut_image->matte == MagickFalse)
cristy4c08aed2011-07-01 19:47:50 +0000801 SetPixelAlpha(image,GetPixelInfoIntensity(clut_map+
802 ScaleQuantumToMap((Quantum) GetPixelAlpha(image,q))),q);
cristy3635df22011-03-25 00:16:16 +0000803 else
804 if (image->matte == MagickFalse)
cristy4c08aed2011-07-01 19:47:50 +0000805 SetPixelAlpha(image,ClampAlphaPixelComponent(clut_map+
806 ScaleQuantumToMap((Quantum) GetPixelInfoIntensity(&pixel))),q);
cristy3635df22011-03-25 00:16:16 +0000807 else
cristy4c08aed2011-07-01 19:47:50 +0000808 SetPixelAlpha(image,ClampAlphaPixelComponent(clut_map+
809 ScaleQuantumToMap(GetPixelAlpha(image,q))),q);
cristy3635df22011-03-25 00:16:16 +0000810 }
cristydcfc1ad2011-07-07 16:25:41 +0000811 q+=GetPixelComponents(image);
cristy3ed852e2009-09-05 21:47:34 +0000812 }
813 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
814 status=MagickFalse;
815 if (image->progress_monitor != (MagickProgressMonitor) NULL)
816 {
817 MagickBooleanType
818 proceed;
819
cristyb5d5f722009-11-04 03:03:49 +0000820#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf89cb1d2011-07-07 01:24:37 +0000821 #pragma omp critical (MagickCore_ClutImage)
cristy3ed852e2009-09-05 21:47:34 +0000822#endif
823 proceed=SetImageProgress(image,ClutImageTag,progress++,image->rows);
824 if (proceed == MagickFalse)
825 status=MagickFalse;
826 }
827 }
828 image_view=DestroyCacheView(image_view);
cristy4c08aed2011-07-01 19:47:50 +0000829 clut_map=(PixelInfo *) RelinquishMagickMemory(clut_map);
cristy2b9582a2011-07-04 17:38:56 +0000830 if ((clut_image->matte != MagickFalse) &&
831 ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0))
cristy3ed852e2009-09-05 21:47:34 +0000832 (void) SetImageAlphaChannel(image,ActivateAlphaChannel);
833 return(status);
834}
835
836/*
837%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
838% %
839% %
840% %
841% C o n t r a s t I m a g e %
842% %
843% %
844% %
845%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
846%
847% ContrastImage() enhances the intensity differences between the lighter and
848% darker elements of the image. Set sharpen to a MagickTrue to increase the
849% image contrast otherwise the contrast is reduced.
850%
851% The format of the ContrastImage method is:
852%
853% MagickBooleanType ContrastImage(Image *image,
854% const MagickBooleanType sharpen)
855%
856% A description of each parameter follows:
857%
858% o image: the image.
859%
860% o sharpen: Increase or decrease image contrast.
861%
862*/
863
864static void Contrast(const int sign,Quantum *red,Quantum *green,Quantum *blue)
865{
866 double
867 brightness,
868 hue,
869 saturation;
870
871 /*
872 Enhance contrast: dark color become darker, light color become lighter.
873 */
874 assert(red != (Quantum *) NULL);
875 assert(green != (Quantum *) NULL);
876 assert(blue != (Quantum *) NULL);
877 hue=0.0;
878 saturation=0.0;
879 brightness=0.0;
880 ConvertRGBToHSB(*red,*green,*blue,&hue,&saturation,&brightness);
cristy31ac0f02011-03-12 02:04:47 +0000881 brightness+=0.5*sign*(0.5*(sin((double) (MagickPI*(brightness-0.5)))+1.0)-
cristy4205a3c2010-09-12 20:19:59 +0000882 brightness);
cristy3ed852e2009-09-05 21:47:34 +0000883 if (brightness > 1.0)
884 brightness=1.0;
885 else
886 if (brightness < 0.0)
887 brightness=0.0;
888 ConvertHSBToRGB(hue,saturation,brightness,red,green,blue);
889}
890
891MagickExport MagickBooleanType ContrastImage(Image *image,
892 const MagickBooleanType sharpen)
893{
894#define ContrastImageTag "Contrast/Image"
895
cristyc4c8d132010-01-07 01:58:38 +0000896 CacheView
897 *image_view;
898
cristy3ed852e2009-09-05 21:47:34 +0000899 ExceptionInfo
900 *exception;
901
902 int
903 sign;
904
cristy3ed852e2009-09-05 21:47:34 +0000905 MagickBooleanType
906 status;
907
cristybb503372010-05-27 20:51:26 +0000908 MagickOffsetType
909 progress;
910
911 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000912 i;
913
cristybb503372010-05-27 20:51:26 +0000914 ssize_t
915 y;
916
cristy3ed852e2009-09-05 21:47:34 +0000917 assert(image != (Image *) NULL);
918 assert(image->signature == MagickSignature);
919 if (image->debug != MagickFalse)
920 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
921 sign=sharpen != MagickFalse ? 1 : -1;
922 if (image->storage_class == PseudoClass)
923 {
924 /*
925 Contrast enhance colormap.
926 */
cristybb503372010-05-27 20:51:26 +0000927 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +0000928 Contrast(sign,&image->colormap[i].red,&image->colormap[i].green,
929 &image->colormap[i].blue);
930 }
931 /*
932 Contrast enhance image.
933 */
934 status=MagickTrue;
935 progress=0;
936 exception=(&image->exception);
937 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +0000938#if defined(MAGICKCORE_OPENMP_SUPPORT)
939 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000940#endif
cristybb503372010-05-27 20:51:26 +0000941 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000942 {
cristy5afeab82011-04-30 01:30:09 +0000943 Quantum
944 blue,
945 green,
946 red;
947
cristy4c08aed2011-07-01 19:47:50 +0000948 register Quantum
cristyc47d1f82009-11-26 01:44:43 +0000949 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000950
cristy8d4629b2010-08-30 17:59:46 +0000951 register ssize_t
952 x;
953
cristy3ed852e2009-09-05 21:47:34 +0000954 if (status == MagickFalse)
955 continue;
956 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +0000957 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +0000958 {
959 status=MagickFalse;
960 continue;
961 }
cristybb503372010-05-27 20:51:26 +0000962 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000963 {
cristy4c08aed2011-07-01 19:47:50 +0000964 red=GetPixelRed(image,q);
965 green=GetPixelGreen(image,q);
966 blue=GetPixelBlue(image,q);
cristy5afeab82011-04-30 01:30:09 +0000967 Contrast(sign,&red,&green,&blue);
cristy4c08aed2011-07-01 19:47:50 +0000968 SetPixelRed(image,red,q);
969 SetPixelGreen(image,green,q);
970 SetPixelBlue(image,blue,q);
cristydcfc1ad2011-07-07 16:25:41 +0000971 q+=GetPixelComponents(image);
cristy3ed852e2009-09-05 21:47:34 +0000972 }
973 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
974 status=MagickFalse;
975 if (image->progress_monitor != (MagickProgressMonitor) NULL)
976 {
977 MagickBooleanType
978 proceed;
979
cristyb5d5f722009-11-04 03:03:49 +0000980#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +0000981 #pragma omp critical (MagickCore_ContrastImage)
982#endif
983 proceed=SetImageProgress(image,ContrastImageTag,progress++,image->rows);
984 if (proceed == MagickFalse)
985 status=MagickFalse;
986 }
987 }
988 image_view=DestroyCacheView(image_view);
989 return(status);
990}
991
992/*
993%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
994% %
995% %
996% %
997% C o n t r a s t S t r e t c h I m a g e %
998% %
999% %
1000% %
1001%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1002%
1003% The ContrastStretchImage() is a simple image enhancement technique that
1004% attempts to improve the contrast in an image by `stretching' the range of
1005% intensity values it contains to span a desired range of values. It differs
1006% from the more sophisticated histogram equalization in that it can only
1007% apply % a linear scaling function to the image pixel values. As a result
1008% the `enhancement' is less harsh.
1009%
1010% The format of the ContrastStretchImage method is:
1011%
1012% MagickBooleanType ContrastStretchImage(Image *image,
1013% const char *levels)
cristy3ed852e2009-09-05 21:47:34 +00001014%
1015% A description of each parameter follows:
1016%
1017% o image: the image.
1018%
cristy3ed852e2009-09-05 21:47:34 +00001019% o black_point: the black point.
1020%
1021% o white_point: the white point.
1022%
1023% o levels: Specify the levels where the black and white points have the
1024% range of 0 to number-of-pixels (e.g. 1%, 10x90%, etc.).
1025%
1026*/
cristy3ed852e2009-09-05 21:47:34 +00001027MagickExport MagickBooleanType ContrastStretchImage(Image *image,
cristy50fbc382011-07-07 02:19:17 +00001028 const double black_point,const double white_point)
cristy3ed852e2009-09-05 21:47:34 +00001029{
1030#define MaxRange(color) ((MagickRealType) ScaleQuantumToMap((Quantum) (color)))
1031#define ContrastStretchImageTag "ContrastStretch/Image"
1032
cristyc4c8d132010-01-07 01:58:38 +00001033 CacheView
1034 *image_view;
1035
cristy3ed852e2009-09-05 21:47:34 +00001036 double
1037 intensity;
1038
1039 ExceptionInfo
1040 *exception;
1041
cristy3ed852e2009-09-05 21:47:34 +00001042 MagickBooleanType
1043 status;
1044
cristybb503372010-05-27 20:51:26 +00001045 MagickOffsetType
1046 progress;
1047
cristy4c08aed2011-07-01 19:47:50 +00001048 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00001049 black,
1050 *histogram,
1051 *stretch_map,
1052 white;
1053
cristybb503372010-05-27 20:51:26 +00001054 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001055 i;
1056
cristybb503372010-05-27 20:51:26 +00001057 ssize_t
1058 y;
1059
cristy3ed852e2009-09-05 21:47:34 +00001060 /*
1061 Allocate histogram and stretch map.
1062 */
1063 assert(image != (Image *) NULL);
1064 assert(image->signature == MagickSignature);
1065 if (image->debug != MagickFalse)
1066 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
cristy4c08aed2011-07-01 19:47:50 +00001067 histogram=(PixelInfo *) AcquireQuantumMemory(MaxMap+1UL,
cristy3ed852e2009-09-05 21:47:34 +00001068 sizeof(*histogram));
cristy4c08aed2011-07-01 19:47:50 +00001069 stretch_map=(PixelInfo *) AcquireQuantumMemory(MaxMap+1UL,
cristy3ed852e2009-09-05 21:47:34 +00001070 sizeof(*stretch_map));
cristy4c08aed2011-07-01 19:47:50 +00001071 if ((histogram == (PixelInfo *) NULL) ||
1072 (stretch_map == (PixelInfo *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00001073 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
1074 image->filename);
1075 /*
1076 Form histogram.
1077 */
1078 status=MagickTrue;
1079 exception=(&image->exception);
1080 (void) ResetMagickMemory(histogram,0,(MaxMap+1)*sizeof(*histogram));
1081 image_view=AcquireCacheView(image);
cristybb503372010-05-27 20:51:26 +00001082 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001083 {
cristy4c08aed2011-07-01 19:47:50 +00001084 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00001085 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00001086
cristybb503372010-05-27 20:51:26 +00001087 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001088 x;
1089
1090 if (status == MagickFalse)
1091 continue;
1092 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00001093 if (p == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001094 {
1095 status=MagickFalse;
1096 continue;
1097 }
cristy50fbc382011-07-07 02:19:17 +00001098 if (image->sync != MagickFalse)
cristybb503372010-05-27 20:51:26 +00001099 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001100 {
1101 Quantum
1102 intensity;
1103
cristy4c08aed2011-07-01 19:47:50 +00001104 intensity=GetPixelIntensity(image,p);
cristy3ed852e2009-09-05 21:47:34 +00001105 histogram[ScaleQuantumToMap(intensity)].red++;
1106 histogram[ScaleQuantumToMap(intensity)].green++;
1107 histogram[ScaleQuantumToMap(intensity)].blue++;
cristy4c08aed2011-07-01 19:47:50 +00001108 histogram[ScaleQuantumToMap(intensity)].black++;
cristydcfc1ad2011-07-07 16:25:41 +00001109 p+=GetPixelComponents(image);
cristy3ed852e2009-09-05 21:47:34 +00001110 }
1111 else
cristybb503372010-05-27 20:51:26 +00001112 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001113 {
cristy2b9582a2011-07-04 17:38:56 +00001114 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001115 histogram[ScaleQuantumToMap(GetPixelRed(image,p))].red++;
cristy2b9582a2011-07-04 17:38:56 +00001116 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001117 histogram[ScaleQuantumToMap(GetPixelGreen(image,p))].green++;
cristy2b9582a2011-07-04 17:38:56 +00001118 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001119 histogram[ScaleQuantumToMap(GetPixelBlue(image,p))].blue++;
cristy2b9582a2011-07-04 17:38:56 +00001120 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00001121 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +00001122 histogram[ScaleQuantumToMap(GetPixelBlack(image,p))].black++;
cristy2b9582a2011-07-04 17:38:56 +00001123 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001124 histogram[ScaleQuantumToMap(GetPixelAlpha(image,p))].alpha++;
cristydcfc1ad2011-07-07 16:25:41 +00001125 p+=GetPixelComponents(image);
cristy3ed852e2009-09-05 21:47:34 +00001126 }
1127 }
1128 /*
1129 Find the histogram boundaries by locating the black/white levels.
1130 */
1131 black.red=0.0;
1132 white.red=MaxRange(QuantumRange);
cristy2b9582a2011-07-04 17:38:56 +00001133 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001134 {
1135 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00001136 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +00001137 {
1138 intensity+=histogram[i].red;
1139 if (intensity > black_point)
1140 break;
1141 }
1142 black.red=(MagickRealType) i;
1143 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00001144 for (i=(ssize_t) MaxMap; i != 0; i--)
cristy3ed852e2009-09-05 21:47:34 +00001145 {
1146 intensity+=histogram[i].red;
1147 if (intensity > ((double) image->columns*image->rows-white_point))
1148 break;
1149 }
1150 white.red=(MagickRealType) i;
1151 }
1152 black.green=0.0;
1153 white.green=MaxRange(QuantumRange);
cristy2b9582a2011-07-04 17:38:56 +00001154 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001155 {
1156 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00001157 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +00001158 {
1159 intensity+=histogram[i].green;
1160 if (intensity > black_point)
1161 break;
1162 }
1163 black.green=(MagickRealType) i;
1164 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00001165 for (i=(ssize_t) MaxMap; i != 0; i--)
cristy3ed852e2009-09-05 21:47:34 +00001166 {
1167 intensity+=histogram[i].green;
1168 if (intensity > ((double) image->columns*image->rows-white_point))
1169 break;
1170 }
1171 white.green=(MagickRealType) i;
1172 }
1173 black.blue=0.0;
1174 white.blue=MaxRange(QuantumRange);
cristy2b9582a2011-07-04 17:38:56 +00001175 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001176 {
1177 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00001178 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +00001179 {
1180 intensity+=histogram[i].blue;
1181 if (intensity > black_point)
1182 break;
1183 }
1184 black.blue=(MagickRealType) i;
1185 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00001186 for (i=(ssize_t) MaxMap; i != 0; i--)
cristy3ed852e2009-09-05 21:47:34 +00001187 {
1188 intensity+=histogram[i].blue;
1189 if (intensity > ((double) image->columns*image->rows-white_point))
1190 break;
1191 }
1192 white.blue=(MagickRealType) i;
1193 }
cristy4c08aed2011-07-01 19:47:50 +00001194 black.alpha=0.0;
1195 white.alpha=MaxRange(QuantumRange);
cristy2b9582a2011-07-04 17:38:56 +00001196 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001197 {
1198 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00001199 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +00001200 {
cristy4c08aed2011-07-01 19:47:50 +00001201 intensity+=histogram[i].alpha;
cristy3ed852e2009-09-05 21:47:34 +00001202 if (intensity > black_point)
1203 break;
1204 }
cristy4c08aed2011-07-01 19:47:50 +00001205 black.alpha=(MagickRealType) i;
cristy3ed852e2009-09-05 21:47:34 +00001206 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00001207 for (i=(ssize_t) MaxMap; i != 0; i--)
cristy3ed852e2009-09-05 21:47:34 +00001208 {
cristy4c08aed2011-07-01 19:47:50 +00001209 intensity+=histogram[i].alpha;
cristy3ed852e2009-09-05 21:47:34 +00001210 if (intensity > ((double) image->columns*image->rows-white_point))
1211 break;
1212 }
cristy4c08aed2011-07-01 19:47:50 +00001213 white.alpha=(MagickRealType) i;
cristy3ed852e2009-09-05 21:47:34 +00001214 }
cristy4c08aed2011-07-01 19:47:50 +00001215 black.black=0.0;
1216 white.black=MaxRange(QuantumRange);
cristy2b9582a2011-07-04 17:38:56 +00001217 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) && (image->colorspace == CMYKColorspace))
cristy3ed852e2009-09-05 21:47:34 +00001218 {
1219 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00001220 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +00001221 {
cristy4c08aed2011-07-01 19:47:50 +00001222 intensity+=histogram[i].black;
cristy3ed852e2009-09-05 21:47:34 +00001223 if (intensity > black_point)
1224 break;
1225 }
cristy4c08aed2011-07-01 19:47:50 +00001226 black.black=(MagickRealType) i;
cristy3ed852e2009-09-05 21:47:34 +00001227 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00001228 for (i=(ssize_t) MaxMap; i != 0; i--)
cristy3ed852e2009-09-05 21:47:34 +00001229 {
cristy4c08aed2011-07-01 19:47:50 +00001230 intensity+=histogram[i].black;
cristy3ed852e2009-09-05 21:47:34 +00001231 if (intensity > ((double) image->columns*image->rows-white_point))
1232 break;
1233 }
cristy4c08aed2011-07-01 19:47:50 +00001234 white.black=(MagickRealType) i;
cristy3ed852e2009-09-05 21:47:34 +00001235 }
cristy4c08aed2011-07-01 19:47:50 +00001236 histogram=(PixelInfo *) RelinquishMagickMemory(histogram);
cristy3ed852e2009-09-05 21:47:34 +00001237 /*
1238 Stretch the histogram to create the stretched image mapping.
1239 */
1240 (void) ResetMagickMemory(stretch_map,0,(MaxMap+1)*sizeof(*stretch_map));
cristyb5d5f722009-11-04 03:03:49 +00001241#if defined(MAGICKCORE_OPENMP_SUPPORT)
1242 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001243#endif
cristybb503372010-05-27 20:51:26 +00001244 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +00001245 {
cristy2b9582a2011-07-04 17:38:56 +00001246 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001247 {
cristybb503372010-05-27 20:51:26 +00001248 if (i < (ssize_t) black.red)
cristy3ed852e2009-09-05 21:47:34 +00001249 stretch_map[i].red=0.0;
1250 else
cristybb503372010-05-27 20:51:26 +00001251 if (i > (ssize_t) white.red)
cristy3ed852e2009-09-05 21:47:34 +00001252 stretch_map[i].red=(MagickRealType) QuantumRange;
1253 else
1254 if (black.red != white.red)
1255 stretch_map[i].red=(MagickRealType) ScaleMapToQuantum(
1256 (MagickRealType) (MaxMap*(i-black.red)/(white.red-black.red)));
1257 }
cristy2b9582a2011-07-04 17:38:56 +00001258 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001259 {
cristybb503372010-05-27 20:51:26 +00001260 if (i < (ssize_t) black.green)
cristy3ed852e2009-09-05 21:47:34 +00001261 stretch_map[i].green=0.0;
1262 else
cristybb503372010-05-27 20:51:26 +00001263 if (i > (ssize_t) white.green)
cristy3ed852e2009-09-05 21:47:34 +00001264 stretch_map[i].green=(MagickRealType) QuantumRange;
1265 else
1266 if (black.green != white.green)
1267 stretch_map[i].green=(MagickRealType) ScaleMapToQuantum(
1268 (MagickRealType) (MaxMap*(i-black.green)/(white.green-
1269 black.green)));
1270 }
cristy2b9582a2011-07-04 17:38:56 +00001271 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001272 {
cristybb503372010-05-27 20:51:26 +00001273 if (i < (ssize_t) black.blue)
cristy3ed852e2009-09-05 21:47:34 +00001274 stretch_map[i].blue=0.0;
1275 else
cristybb503372010-05-27 20:51:26 +00001276 if (i > (ssize_t) white.blue)
cristy3ed852e2009-09-05 21:47:34 +00001277 stretch_map[i].blue=(MagickRealType) QuantumRange;
1278 else
1279 if (black.blue != white.blue)
1280 stretch_map[i].blue=(MagickRealType) ScaleMapToQuantum(
1281 (MagickRealType) (MaxMap*(i-black.blue)/(white.blue-
1282 black.blue)));
1283 }
cristy2b9582a2011-07-04 17:38:56 +00001284 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001285 {
cristy4c08aed2011-07-01 19:47:50 +00001286 if (i < (ssize_t) black.alpha)
1287 stretch_map[i].alpha=0.0;
cristy3ed852e2009-09-05 21:47:34 +00001288 else
cristy4c08aed2011-07-01 19:47:50 +00001289 if (i > (ssize_t) white.alpha)
1290 stretch_map[i].alpha=(MagickRealType) QuantumRange;
cristy3ed852e2009-09-05 21:47:34 +00001291 else
cristy4c08aed2011-07-01 19:47:50 +00001292 if (black.alpha != white.alpha)
1293 stretch_map[i].alpha=(MagickRealType) ScaleMapToQuantum(
1294 (MagickRealType) (MaxMap*(i-black.alpha)/(white.alpha-
1295 black.alpha)));
cristy3ed852e2009-09-05 21:47:34 +00001296 }
cristy2b9582a2011-07-04 17:38:56 +00001297 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00001298 (image->colorspace == CMYKColorspace))
1299 {
cristy4c08aed2011-07-01 19:47:50 +00001300 if (i < (ssize_t) black.black)
1301 stretch_map[i].black=0.0;
cristy3ed852e2009-09-05 21:47:34 +00001302 else
cristy4c08aed2011-07-01 19:47:50 +00001303 if (i > (ssize_t) white.black)
1304 stretch_map[i].black=(MagickRealType) QuantumRange;
cristy3ed852e2009-09-05 21:47:34 +00001305 else
cristy4c08aed2011-07-01 19:47:50 +00001306 if (black.black != white.black)
1307 stretch_map[i].black=(MagickRealType) ScaleMapToQuantum(
1308 (MagickRealType) (MaxMap*(i-black.black)/(white.black-
1309 black.black)));
cristy3ed852e2009-09-05 21:47:34 +00001310 }
1311 }
1312 /*
1313 Stretch the image.
1314 */
cristy2b9582a2011-07-04 17:38:56 +00001315 if (((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0) || (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00001316 (image->colorspace == CMYKColorspace)))
1317 image->storage_class=DirectClass;
1318 if (image->storage_class == PseudoClass)
1319 {
1320 /*
1321 Stretch colormap.
1322 */
cristyb5d5f722009-11-04 03:03:49 +00001323#if defined(MAGICKCORE_OPENMP_SUPPORT)
1324 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001325#endif
cristybb503372010-05-27 20:51:26 +00001326 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00001327 {
cristy2b9582a2011-07-04 17:38:56 +00001328 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001329 {
1330 if (black.red != white.red)
cristyce70c172010-01-07 17:15:30 +00001331 image->colormap[i].red=ClampToQuantum(stretch_map[
cristy3ed852e2009-09-05 21:47:34 +00001332 ScaleQuantumToMap(image->colormap[i].red)].red);
1333 }
cristy2b9582a2011-07-04 17:38:56 +00001334 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001335 {
1336 if (black.green != white.green)
cristyce70c172010-01-07 17:15:30 +00001337 image->colormap[i].green=ClampToQuantum(stretch_map[
cristy3ed852e2009-09-05 21:47:34 +00001338 ScaleQuantumToMap(image->colormap[i].green)].green);
1339 }
cristy2b9582a2011-07-04 17:38:56 +00001340 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001341 {
1342 if (black.blue != white.blue)
cristyce70c172010-01-07 17:15:30 +00001343 image->colormap[i].blue=ClampToQuantum(stretch_map[
cristy3ed852e2009-09-05 21:47:34 +00001344 ScaleQuantumToMap(image->colormap[i].blue)].blue);
1345 }
cristy2b9582a2011-07-04 17:38:56 +00001346 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001347 {
cristy4c08aed2011-07-01 19:47:50 +00001348 if (black.alpha != white.alpha)
1349 image->colormap[i].alpha=ClampToQuantum(stretch_map[
1350 ScaleQuantumToMap(image->colormap[i].alpha)].alpha);
cristy3ed852e2009-09-05 21:47:34 +00001351 }
1352 }
1353 }
1354 /*
1355 Stretch image.
1356 */
1357 status=MagickTrue;
1358 progress=0;
cristyb5d5f722009-11-04 03:03:49 +00001359#if defined(MAGICKCORE_OPENMP_SUPPORT)
1360 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001361#endif
cristybb503372010-05-27 20:51:26 +00001362 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001363 {
cristy4c08aed2011-07-01 19:47:50 +00001364 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00001365 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001366
cristy8d4629b2010-08-30 17:59:46 +00001367 register ssize_t
1368 x;
1369
cristy3ed852e2009-09-05 21:47:34 +00001370 if (status == MagickFalse)
1371 continue;
1372 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00001373 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001374 {
1375 status=MagickFalse;
1376 continue;
1377 }
cristybb503372010-05-27 20:51:26 +00001378 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001379 {
cristy2b9582a2011-07-04 17:38:56 +00001380 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001381 {
1382 if (black.red != white.red)
cristy4c08aed2011-07-01 19:47:50 +00001383 SetPixelRed(image,ClampToQuantum(stretch_map[ScaleQuantumToMap(
1384 GetPixelRed(image,q))].red),q);
cristy3ed852e2009-09-05 21:47:34 +00001385 }
cristy2b9582a2011-07-04 17:38:56 +00001386 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001387 {
1388 if (black.green != white.green)
cristy4c08aed2011-07-01 19:47:50 +00001389 SetPixelGreen(image,ClampToQuantum(stretch_map[ScaleQuantumToMap(
1390 GetPixelGreen(image,q))].green),q);
cristy3ed852e2009-09-05 21:47:34 +00001391 }
cristy2b9582a2011-07-04 17:38:56 +00001392 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001393 {
1394 if (black.blue != white.blue)
cristy4c08aed2011-07-01 19:47:50 +00001395 SetPixelBlue(image,ClampToQuantum(stretch_map[ScaleQuantumToMap(
1396 GetPixelBlue(image,q))].blue),q);
1397 }
cristy2b9582a2011-07-04 17:38:56 +00001398 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +00001399 (image->colorspace == CMYKColorspace))
1400 {
1401 if (black.black != white.black)
1402 SetPixelBlack(image,ClampToQuantum(stretch_map[ScaleQuantumToMap(
1403 GetPixelBlack(image,q))].black),q);
cristy3ed852e2009-09-05 21:47:34 +00001404 }
cristy2b9582a2011-07-04 17:38:56 +00001405 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001406 {
cristy4c08aed2011-07-01 19:47:50 +00001407 if (black.alpha != white.alpha)
1408 SetPixelAlpha(image,ClampToQuantum(stretch_map[ScaleQuantumToMap(
1409 GetPixelAlpha(image,q))].alpha),q);
cristy3ed852e2009-09-05 21:47:34 +00001410 }
cristydcfc1ad2011-07-07 16:25:41 +00001411 q+=GetPixelComponents(image);
cristy3ed852e2009-09-05 21:47:34 +00001412 }
1413 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
1414 status=MagickFalse;
1415 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1416 {
1417 MagickBooleanType
1418 proceed;
1419
cristyb5d5f722009-11-04 03:03:49 +00001420#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy50fbc382011-07-07 02:19:17 +00001421 #pragma omp critical (MagickCore_ContrastStretchImage)
cristy3ed852e2009-09-05 21:47:34 +00001422#endif
1423 proceed=SetImageProgress(image,ContrastStretchImageTag,progress++,
1424 image->rows);
1425 if (proceed == MagickFalse)
1426 status=MagickFalse;
1427 }
1428 }
1429 image_view=DestroyCacheView(image_view);
cristy4c08aed2011-07-01 19:47:50 +00001430 stretch_map=(PixelInfo *) RelinquishMagickMemory(stretch_map);
cristy3ed852e2009-09-05 21:47:34 +00001431 return(status);
1432}
1433
1434/*
1435%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1436% %
1437% %
1438% %
1439% E n h a n c e I m a g e %
1440% %
1441% %
1442% %
1443%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1444%
1445% EnhanceImage() applies a digital filter that improves the quality of a
1446% noisy image.
1447%
1448% The format of the EnhanceImage method is:
1449%
1450% Image *EnhanceImage(const Image *image,ExceptionInfo *exception)
1451%
1452% A description of each parameter follows:
1453%
1454% o image: the image.
1455%
1456% o exception: return any errors or warnings in this structure.
1457%
1458*/
1459MagickExport Image *EnhanceImage(const Image *image,ExceptionInfo *exception)
1460{
1461#define Enhance(weight) \
cristy4c08aed2011-07-01 19:47:50 +00001462 mean=((MagickRealType) GetPixelRed(image,r)+pixel.red)/2; \
1463 distance=(MagickRealType) GetPixelRed(image,r)-(MagickRealType) pixel.red; \
cristy3ed852e2009-09-05 21:47:34 +00001464 distance_squared=QuantumScale*(2.0*((MagickRealType) QuantumRange+1.0)+ \
1465 mean)*distance*distance; \
cristy4c08aed2011-07-01 19:47:50 +00001466 mean=((MagickRealType) GetPixelGreen(image,r)+pixel.green)/2; \
1467 distance=(MagickRealType) GetPixelGreen(image,r)- \
1468 (MagickRealType) pixel.green; \
cristy3ed852e2009-09-05 21:47:34 +00001469 distance_squared+=4.0*distance*distance; \
cristy4c08aed2011-07-01 19:47:50 +00001470 mean=((MagickRealType) GetPixelBlue(image,r)+pixel.blue)/2; \
1471 distance=(MagickRealType) GetPixelBlue(image,r)- \
1472 (MagickRealType) pixel.blue; \
cristy3ed852e2009-09-05 21:47:34 +00001473 distance_squared+=QuantumScale*(3.0*((MagickRealType) \
1474 QuantumRange+1.0)-1.0-mean)*distance*distance; \
cristy4c08aed2011-07-01 19:47:50 +00001475 mean=((MagickRealType) GetPixelAlpha(image,r)+pixel.alpha)/2; \
1476 distance=(MagickRealType) GetPixelAlpha(image,r)-(MagickRealType) pixel.alpha; \
cristy3ed852e2009-09-05 21:47:34 +00001477 distance_squared+=QuantumScale*(3.0*((MagickRealType) \
1478 QuantumRange+1.0)-1.0-mean)*distance*distance; \
1479 if (distance_squared < ((MagickRealType) QuantumRange*(MagickRealType) \
1480 QuantumRange/25.0f)) \
1481 { \
cristy4c08aed2011-07-01 19:47:50 +00001482 aggregate.red+=(weight)*GetPixelRed(image,r); \
1483 aggregate.green+=(weight)*GetPixelGreen(image,r); \
1484 aggregate.blue+=(weight)*GetPixelBlue(image,r); \
1485 aggregate.alpha+=(weight)*GetPixelAlpha(image,r); \
cristy3ed852e2009-09-05 21:47:34 +00001486 total_weight+=(weight); \
1487 } \
1488 r++;
1489#define EnhanceImageTag "Enhance/Image"
1490
cristyc4c8d132010-01-07 01:58:38 +00001491 CacheView
1492 *enhance_view,
1493 *image_view;
1494
cristy3ed852e2009-09-05 21:47:34 +00001495 Image
1496 *enhance_image;
1497
cristy3ed852e2009-09-05 21:47:34 +00001498 MagickBooleanType
1499 status;
1500
cristybb503372010-05-27 20:51:26 +00001501 MagickOffsetType
1502 progress;
1503
cristy4c08aed2011-07-01 19:47:50 +00001504 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00001505 zero;
1506
cristybb503372010-05-27 20:51:26 +00001507 ssize_t
1508 y;
1509
cristy3ed852e2009-09-05 21:47:34 +00001510 /*
1511 Initialize enhanced image attributes.
1512 */
1513 assert(image != (const Image *) NULL);
1514 assert(image->signature == MagickSignature);
1515 if (image->debug != MagickFalse)
1516 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1517 assert(exception != (ExceptionInfo *) NULL);
1518 assert(exception->signature == MagickSignature);
1519 if ((image->columns < 5) || (image->rows < 5))
1520 return((Image *) NULL);
1521 enhance_image=CloneImage(image,image->columns,image->rows,MagickTrue,
1522 exception);
1523 if (enhance_image == (Image *) NULL)
1524 return((Image *) NULL);
1525 if (SetImageStorageClass(enhance_image,DirectClass) == MagickFalse)
1526 {
1527 InheritException(exception,&enhance_image->exception);
1528 enhance_image=DestroyImage(enhance_image);
1529 return((Image *) NULL);
1530 }
1531 /*
1532 Enhance image.
1533 */
1534 status=MagickTrue;
1535 progress=0;
1536 (void) ResetMagickMemory(&zero,0,sizeof(zero));
1537 image_view=AcquireCacheView(image);
1538 enhance_view=AcquireCacheView(enhance_image);
cristyb5d5f722009-11-04 03:03:49 +00001539#if defined(MAGICKCORE_OPENMP_SUPPORT)
1540 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001541#endif
cristybb503372010-05-27 20:51:26 +00001542 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001543 {
cristy4c08aed2011-07-01 19:47:50 +00001544 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00001545 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00001546
cristy4c08aed2011-07-01 19:47:50 +00001547 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00001548 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001549
cristy8d4629b2010-08-30 17:59:46 +00001550 register ssize_t
1551 x;
1552
cristy3ed852e2009-09-05 21:47:34 +00001553 /*
1554 Read another scan line.
1555 */
1556 if (status == MagickFalse)
1557 continue;
1558 p=GetCacheViewVirtualPixels(image_view,-2,y-2,image->columns+4,5,exception);
1559 q=QueueCacheViewAuthenticPixels(enhance_view,0,y,enhance_image->columns,1,
1560 exception);
cristy4c08aed2011-07-01 19:47:50 +00001561 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00001562 {
1563 status=MagickFalse;
1564 continue;
1565 }
cristybb503372010-05-27 20:51:26 +00001566 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001567 {
cristy4c08aed2011-07-01 19:47:50 +00001568 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00001569 aggregate;
1570
1571 MagickRealType
1572 distance,
1573 distance_squared,
1574 mean,
1575 total_weight;
1576
1577 PixelPacket
1578 pixel;
1579
cristy4c08aed2011-07-01 19:47:50 +00001580 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00001581 *restrict r;
cristy3ed852e2009-09-05 21:47:34 +00001582
1583 /*
1584 Compute weighted average of target pixel color components.
1585 */
1586 aggregate=zero;
1587 total_weight=0.0;
1588 r=p+2*(image->columns+4)+2;
cristy4c08aed2011-07-01 19:47:50 +00001589 GetPixelPacket(image,r,&pixel);
cristy3ed852e2009-09-05 21:47:34 +00001590 r=p;
1591 Enhance(5.0); Enhance(8.0); Enhance(10.0); Enhance(8.0); Enhance(5.0);
1592 r=p+(image->columns+4);
1593 Enhance(8.0); Enhance(20.0); Enhance(40.0); Enhance(20.0); Enhance(8.0);
1594 r=p+2*(image->columns+4);
1595 Enhance(10.0); Enhance(40.0); Enhance(80.0); Enhance(40.0); Enhance(10.0);
1596 r=p+3*(image->columns+4);
1597 Enhance(8.0); Enhance(20.0); Enhance(40.0); Enhance(20.0); Enhance(8.0);
1598 r=p+4*(image->columns+4);
1599 Enhance(5.0); Enhance(8.0); Enhance(10.0); Enhance(8.0); Enhance(5.0);
cristy4c08aed2011-07-01 19:47:50 +00001600 SetPixelRed(enhance_image,(Quantum) ((aggregate.red+
1601 (total_weight/2)-1)/total_weight),q);
1602 SetPixelGreen(enhance_image,(Quantum) ((aggregate.green+
1603 (total_weight/2)-1)/total_weight),q);
1604 SetPixelBlue(enhance_image,(Quantum) ((aggregate.blue+
1605 (total_weight/2)-1)/total_weight),q);
1606 SetPixelAlpha(enhance_image,(Quantum) ((aggregate.alpha+
1607 (total_weight/2)-1)/total_weight),q);
cristydcfc1ad2011-07-07 16:25:41 +00001608 p+=GetPixelComponents(image);
1609 q+=GetPixelComponents(enhance_image);
cristy3ed852e2009-09-05 21:47:34 +00001610 }
1611 if (SyncCacheViewAuthenticPixels(enhance_view,exception) == MagickFalse)
1612 status=MagickFalse;
1613 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1614 {
1615 MagickBooleanType
1616 proceed;
1617
cristyb5d5f722009-11-04 03:03:49 +00001618#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00001619 #pragma omp critical (MagickCore_EnhanceImage)
1620#endif
1621 proceed=SetImageProgress(image,EnhanceImageTag,progress++,image->rows);
1622 if (proceed == MagickFalse)
1623 status=MagickFalse;
1624 }
1625 }
1626 enhance_view=DestroyCacheView(enhance_view);
1627 image_view=DestroyCacheView(image_view);
1628 return(enhance_image);
1629}
1630
1631/*
1632%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1633% %
1634% %
1635% %
1636% E q u a l i z e I m a g e %
1637% %
1638% %
1639% %
1640%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1641%
1642% EqualizeImage() applies a histogram equalization to the image.
1643%
1644% The format of the EqualizeImage method is:
1645%
1646% MagickBooleanType EqualizeImage(Image *image)
cristy3ed852e2009-09-05 21:47:34 +00001647%
1648% A description of each parameter follows:
1649%
1650% o image: the image.
1651%
1652% o channel: the channel.
1653%
1654*/
cristy3ed852e2009-09-05 21:47:34 +00001655MagickExport MagickBooleanType EqualizeImage(Image *image)
1656{
cristy3ed852e2009-09-05 21:47:34 +00001657#define EqualizeImageTag "Equalize/Image"
1658
cristyc4c8d132010-01-07 01:58:38 +00001659 CacheView
1660 *image_view;
1661
cristy3ed852e2009-09-05 21:47:34 +00001662 ExceptionInfo
1663 *exception;
1664
cristy3ed852e2009-09-05 21:47:34 +00001665 MagickBooleanType
1666 status;
1667
cristybb503372010-05-27 20:51:26 +00001668 MagickOffsetType
1669 progress;
1670
cristy4c08aed2011-07-01 19:47:50 +00001671 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00001672 black,
1673 *equalize_map,
1674 *histogram,
1675 intensity,
1676 *map,
1677 white;
1678
cristybb503372010-05-27 20:51:26 +00001679 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001680 i;
1681
cristybb503372010-05-27 20:51:26 +00001682 ssize_t
1683 y;
1684
cristy3ed852e2009-09-05 21:47:34 +00001685 /*
1686 Allocate and initialize histogram arrays.
1687 */
1688 assert(image != (Image *) NULL);
1689 assert(image->signature == MagickSignature);
1690 if (image->debug != MagickFalse)
1691 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
cristy4c08aed2011-07-01 19:47:50 +00001692 equalize_map=(PixelInfo *) AcquireQuantumMemory(MaxMap+1UL,
cristy3ed852e2009-09-05 21:47:34 +00001693 sizeof(*equalize_map));
cristy4c08aed2011-07-01 19:47:50 +00001694 histogram=(PixelInfo *) AcquireQuantumMemory(MaxMap+1UL,
cristy3ed852e2009-09-05 21:47:34 +00001695 sizeof(*histogram));
cristy4c08aed2011-07-01 19:47:50 +00001696 map=(PixelInfo *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*map));
1697 if ((equalize_map == (PixelInfo *) NULL) ||
1698 (histogram == (PixelInfo *) NULL) ||
1699 (map == (PixelInfo *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00001700 {
cristy4c08aed2011-07-01 19:47:50 +00001701 if (map != (PixelInfo *) NULL)
1702 map=(PixelInfo *) RelinquishMagickMemory(map);
1703 if (histogram != (PixelInfo *) NULL)
1704 histogram=(PixelInfo *) RelinquishMagickMemory(histogram);
1705 if (equalize_map != (PixelInfo *) NULL)
1706 equalize_map=(PixelInfo *) RelinquishMagickMemory(equalize_map);
cristy3ed852e2009-09-05 21:47:34 +00001707 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
1708 image->filename);
1709 }
1710 /*
1711 Form histogram.
1712 */
1713 (void) ResetMagickMemory(histogram,0,(MaxMap+1)*sizeof(*histogram));
1714 exception=(&image->exception);
cristybb503372010-05-27 20:51:26 +00001715 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001716 {
cristy4c08aed2011-07-01 19:47:50 +00001717 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00001718 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00001719
cristybb503372010-05-27 20:51:26 +00001720 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001721 x;
1722
1723 p=GetVirtualPixels(image,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00001724 if (p == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001725 break;
cristybb503372010-05-27 20:51:26 +00001726 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001727 {
cristy2b9582a2011-07-04 17:38:56 +00001728 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001729 histogram[ScaleQuantumToMap(GetPixelRed(image,p))].red++;
cristy2b9582a2011-07-04 17:38:56 +00001730 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001731 histogram[ScaleQuantumToMap(GetPixelGreen(image,p))].green++;
cristy2b9582a2011-07-04 17:38:56 +00001732 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001733 histogram[ScaleQuantumToMap(GetPixelBlue(image,p))].blue++;
cristy2b9582a2011-07-04 17:38:56 +00001734 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00001735 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +00001736 histogram[ScaleQuantumToMap(GetPixelBlack(image,p))].black++;
cristy2b9582a2011-07-04 17:38:56 +00001737 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001738 histogram[ScaleQuantumToMap(GetPixelAlpha(image,p))].alpha++;
cristydcfc1ad2011-07-07 16:25:41 +00001739 p+=GetPixelComponents(image);
cristy3ed852e2009-09-05 21:47:34 +00001740 }
1741 }
1742 /*
1743 Integrate the histogram to get the equalization map.
1744 */
1745 (void) ResetMagickMemory(&intensity,0,sizeof(intensity));
cristybb503372010-05-27 20:51:26 +00001746 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +00001747 {
cristy2b9582a2011-07-04 17:38:56 +00001748 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001749 intensity.red+=histogram[i].red;
cristy2b9582a2011-07-04 17:38:56 +00001750 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001751 intensity.green+=histogram[i].green;
cristy2b9582a2011-07-04 17:38:56 +00001752 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001753 intensity.blue+=histogram[i].blue;
cristy2b9582a2011-07-04 17:38:56 +00001754 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00001755 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +00001756 intensity.black+=histogram[i].black;
cristy2b9582a2011-07-04 17:38:56 +00001757 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001758 intensity.alpha+=histogram[i].alpha;
cristy3ed852e2009-09-05 21:47:34 +00001759 map[i]=intensity;
1760 }
1761 black=map[0];
1762 white=map[(int) MaxMap];
1763 (void) ResetMagickMemory(equalize_map,0,(MaxMap+1)*sizeof(*equalize_map));
cristyb5d5f722009-11-04 03:03:49 +00001764#if defined(MAGICKCORE_OPENMP_SUPPORT)
1765 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001766#endif
cristybb503372010-05-27 20:51:26 +00001767 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +00001768 {
cristy2b9582a2011-07-04 17:38:56 +00001769 if (((GetPixelRedTraits(image) & ActivePixelTrait) != 0) &&
1770 (white.red != black.red))
cristy3ed852e2009-09-05 21:47:34 +00001771 equalize_map[i].red=(MagickRealType) ScaleMapToQuantum((MagickRealType)
1772 ((MaxMap*(map[i].red-black.red))/(white.red-black.red)));
cristy2b9582a2011-07-04 17:38:56 +00001773 if (((GetPixelGreenTraits(image) & ActivePixelTrait) != 0) &&
1774 (white.green != black.green))
cristy3ed852e2009-09-05 21:47:34 +00001775 equalize_map[i].green=(MagickRealType) ScaleMapToQuantum((MagickRealType)
1776 ((MaxMap*(map[i].green-black.green))/(white.green-black.green)));
cristy2b9582a2011-07-04 17:38:56 +00001777 if (((GetPixelBlueTraits(image) & ActivePixelTrait) != 0) &&
1778 (white.blue != black.blue))
cristy3ed852e2009-09-05 21:47:34 +00001779 equalize_map[i].blue=(MagickRealType) ScaleMapToQuantum((MagickRealType)
1780 ((MaxMap*(map[i].blue-black.blue))/(white.blue-black.blue)));
cristy2b9582a2011-07-04 17:38:56 +00001781 if ((((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00001782 (image->colorspace == CMYKColorspace)) &&
cristy4c08aed2011-07-01 19:47:50 +00001783 (white.black != black.black))
1784 equalize_map[i].black=(MagickRealType) ScaleMapToQuantum((MagickRealType)
1785 ((MaxMap*(map[i].black-black.black))/(white.black-black.black)));
cristy2b9582a2011-07-04 17:38:56 +00001786 if (((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0) &&
1787 (white.alpha != black.alpha))
cristy4c08aed2011-07-01 19:47:50 +00001788 equalize_map[i].alpha=(MagickRealType) ScaleMapToQuantum(
1789 (MagickRealType) ((MaxMap*(map[i].alpha-black.alpha))/
1790 (white.alpha-black.alpha)));
cristy3ed852e2009-09-05 21:47:34 +00001791 }
cristy4c08aed2011-07-01 19:47:50 +00001792 histogram=(PixelInfo *) RelinquishMagickMemory(histogram);
1793 map=(PixelInfo *) RelinquishMagickMemory(map);
cristy3ed852e2009-09-05 21:47:34 +00001794 if (image->storage_class == PseudoClass)
1795 {
1796 /*
1797 Equalize colormap.
1798 */
cristyb5d5f722009-11-04 03:03:49 +00001799#if defined(MAGICKCORE_OPENMP_SUPPORT)
1800 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001801#endif
cristybb503372010-05-27 20:51:26 +00001802 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00001803 {
cristy2b9582a2011-07-04 17:38:56 +00001804 if (((GetPixelRedTraits(image) & ActivePixelTrait) != 0) &&
1805 (white.red != black.red))
cristyce70c172010-01-07 17:15:30 +00001806 image->colormap[i].red=ClampToQuantum(equalize_map[
cristy3ed852e2009-09-05 21:47:34 +00001807 ScaleQuantumToMap(image->colormap[i].red)].red);
cristy2b9582a2011-07-04 17:38:56 +00001808 if (((GetPixelGreenTraits(image) & ActivePixelTrait) != 0) &&
1809 (white.green != black.green))
cristyce70c172010-01-07 17:15:30 +00001810 image->colormap[i].green=ClampToQuantum(equalize_map[
cristy3ed852e2009-09-05 21:47:34 +00001811 ScaleQuantumToMap(image->colormap[i].green)].green);
cristy2b9582a2011-07-04 17:38:56 +00001812 if (((GetPixelBlueTraits(image) & ActivePixelTrait) != 0) &&
1813 (white.blue != black.blue))
cristyce70c172010-01-07 17:15:30 +00001814 image->colormap[i].blue=ClampToQuantum(equalize_map[
cristy3ed852e2009-09-05 21:47:34 +00001815 ScaleQuantumToMap(image->colormap[i].blue)].blue);
cristy2b9582a2011-07-04 17:38:56 +00001816 if (((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +00001817 (white.alpha != black.alpha))
1818 image->colormap[i].alpha=ClampToQuantum(equalize_map[
1819 ScaleQuantumToMap(image->colormap[i].alpha)].alpha);
cristy3ed852e2009-09-05 21:47:34 +00001820 }
1821 }
1822 /*
1823 Equalize image.
1824 */
1825 status=MagickTrue;
1826 progress=0;
1827 exception=(&image->exception);
1828 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00001829#if defined(MAGICKCORE_OPENMP_SUPPORT)
1830 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001831#endif
cristybb503372010-05-27 20:51:26 +00001832 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001833 {
cristy4c08aed2011-07-01 19:47:50 +00001834 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00001835 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001836
cristy8d4629b2010-08-30 17:59:46 +00001837 register ssize_t
1838 x;
1839
cristy3ed852e2009-09-05 21:47:34 +00001840 if (status == MagickFalse)
1841 continue;
1842 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00001843 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001844 {
1845 status=MagickFalse;
1846 continue;
1847 }
cristybb503372010-05-27 20:51:26 +00001848 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001849 {
cristy2b9582a2011-07-04 17:38:56 +00001850 if (((GetPixelRedTraits(image) & ActivePixelTrait) != 0) &&
1851 (white.red != black.red))
cristy4c08aed2011-07-01 19:47:50 +00001852 SetPixelRed(image,ClampToQuantum(equalize_map[
1853 ScaleQuantumToMap(GetPixelRed(image,q))].red),q);
cristy2b9582a2011-07-04 17:38:56 +00001854 if (((GetPixelGreenTraits(image) & ActivePixelTrait) != 0) &&
1855 (white.green != black.green))
cristy4c08aed2011-07-01 19:47:50 +00001856 SetPixelGreen(image,ClampToQuantum(equalize_map[
1857 ScaleQuantumToMap(GetPixelGreen(image,q))].green),q);
cristy2b9582a2011-07-04 17:38:56 +00001858 if (((GetPixelBlueTraits(image) & ActivePixelTrait) != 0) &&
1859 (white.blue != black.blue))
cristy4c08aed2011-07-01 19:47:50 +00001860 SetPixelBlue(image,ClampToQuantum(equalize_map[
1861 ScaleQuantumToMap(GetPixelBlue(image,q))].blue),q);
cristy2b9582a2011-07-04 17:38:56 +00001862 if ((((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00001863 (image->colorspace == CMYKColorspace)) &&
cristy4c08aed2011-07-01 19:47:50 +00001864 (white.black != black.black))
1865 SetPixelBlack(image,ClampToQuantum(equalize_map[
1866 ScaleQuantumToMap(GetPixelBlack(image,q))].black),q);
cristy2b9582a2011-07-04 17:38:56 +00001867 if (((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0) &&
1868 (white.alpha != black.alpha))
cristy4c08aed2011-07-01 19:47:50 +00001869 SetPixelAlpha(image,ClampToQuantum(equalize_map[
1870 ScaleQuantumToMap(GetPixelAlpha(image,q))].alpha),q);
cristydcfc1ad2011-07-07 16:25:41 +00001871 q+=GetPixelComponents(image);
cristy3ed852e2009-09-05 21:47:34 +00001872 }
1873 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
1874 status=MagickFalse;
1875 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1876 {
1877 MagickBooleanType
1878 proceed;
1879
cristyb5d5f722009-11-04 03:03:49 +00001880#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy50fbc382011-07-07 02:19:17 +00001881 #pragma omp critical (MagickCore_EqualizeImage)
cristy3ed852e2009-09-05 21:47:34 +00001882#endif
1883 proceed=SetImageProgress(image,EqualizeImageTag,progress++,image->rows);
1884 if (proceed == MagickFalse)
1885 status=MagickFalse;
1886 }
1887 }
1888 image_view=DestroyCacheView(image_view);
cristy4c08aed2011-07-01 19:47:50 +00001889 equalize_map=(PixelInfo *) RelinquishMagickMemory(equalize_map);
cristy3ed852e2009-09-05 21:47:34 +00001890 return(status);
1891}
1892
1893/*
1894%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1895% %
1896% %
1897% %
1898% G a m m a I m a g e %
1899% %
1900% %
1901% %
1902%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1903%
1904% GammaImage() gamma-corrects a particular image channel. The same
1905% image viewed on different devices will have perceptual differences in the
1906% way the image's intensities are represented on the screen. Specify
1907% individual gamma levels for the red, green, and blue channels, or adjust
1908% all three with the gamma parameter. Values typically range from 0.8 to 2.3.
1909%
1910% You can also reduce the influence of a particular channel with a gamma
1911% value of 0.
1912%
1913% The format of the GammaImage method is:
1914%
cristy50fbc382011-07-07 02:19:17 +00001915% MagickBooleanType GammaImage(Image *image,const double gamma)
cristy3ed852e2009-09-05 21:47:34 +00001916%
1917% A description of each parameter follows:
1918%
1919% o image: the image.
1920%
cristya6360142011-03-23 23:08:04 +00001921% o level: the image gamma as a string (e.g. 1.6,1.2,1.0).
1922%
cristy3ed852e2009-09-05 21:47:34 +00001923% o gamma: the image gamma.
1924%
1925*/
cristy50fbc382011-07-07 02:19:17 +00001926MagickExport MagickBooleanType GammaImage(Image *image,const double gamma)
cristy3ed852e2009-09-05 21:47:34 +00001927{
1928#define GammaCorrectImageTag "GammaCorrect/Image"
1929
cristyc4c8d132010-01-07 01:58:38 +00001930 CacheView
1931 *image_view;
1932
cristy3ed852e2009-09-05 21:47:34 +00001933 ExceptionInfo
1934 *exception;
1935
cristy3ed852e2009-09-05 21:47:34 +00001936 MagickBooleanType
1937 status;
1938
cristybb503372010-05-27 20:51:26 +00001939 MagickOffsetType
1940 progress;
1941
cristy3ed852e2009-09-05 21:47:34 +00001942 Quantum
1943 *gamma_map;
1944
cristybb503372010-05-27 20:51:26 +00001945 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001946 i;
1947
cristybb503372010-05-27 20:51:26 +00001948 ssize_t
1949 y;
1950
cristy3ed852e2009-09-05 21:47:34 +00001951 /*
1952 Allocate and initialize gamma maps.
1953 */
1954 assert(image != (Image *) NULL);
1955 assert(image->signature == MagickSignature);
1956 if (image->debug != MagickFalse)
1957 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1958 if (gamma == 1.0)
1959 return(MagickTrue);
1960 gamma_map=(Quantum *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*gamma_map));
1961 if (gamma_map == (Quantum *) NULL)
1962 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
1963 image->filename);
1964 (void) ResetMagickMemory(gamma_map,0,(MaxMap+1)*sizeof(*gamma_map));
1965 if (gamma != 0.0)
cristyb5d5f722009-11-04 03:03:49 +00001966#if defined(MAGICKCORE_OPENMP_SUPPORT)
1967 #pragma omp parallel for schedule(dynamic,4)
cristy3ed852e2009-09-05 21:47:34 +00001968#endif
cristybb503372010-05-27 20:51:26 +00001969 for (i=0; i <= (ssize_t) MaxMap; i++)
cristyce70c172010-01-07 17:15:30 +00001970 gamma_map[i]=ClampToQuantum((MagickRealType) ScaleMapToQuantum((
cristy3ed852e2009-09-05 21:47:34 +00001971 MagickRealType) (MaxMap*pow((double) i/MaxMap,1.0/gamma))));
1972 if (image->storage_class == PseudoClass)
1973 {
1974 /*
1975 Gamma-correct colormap.
1976 */
cristyb5d5f722009-11-04 03:03:49 +00001977#if defined(MAGICKCORE_OPENMP_SUPPORT)
1978 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001979#endif
cristybb503372010-05-27 20:51:26 +00001980 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00001981 {
cristy2b9582a2011-07-04 17:38:56 +00001982 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001983 image->colormap[i].red=gamma_map[
1984 ScaleQuantumToMap(image->colormap[i].red)];
cristy2b9582a2011-07-04 17:38:56 +00001985 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001986 image->colormap[i].green=gamma_map[
1987 ScaleQuantumToMap(image->colormap[i].green)];
cristy2b9582a2011-07-04 17:38:56 +00001988 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001989 image->colormap[i].blue=gamma_map[
1990 ScaleQuantumToMap(image->colormap[i].blue)];
cristy2b9582a2011-07-04 17:38:56 +00001991 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001992 image->colormap[i].alpha=gamma_map[
1993 ScaleQuantumToMap(image->colormap[i].alpha)];
cristy3ed852e2009-09-05 21:47:34 +00001994 }
1995 }
1996 /*
1997 Gamma-correct image.
1998 */
1999 status=MagickTrue;
2000 progress=0;
2001 exception=(&image->exception);
2002 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00002003#if defined(MAGICKCORE_OPENMP_SUPPORT)
2004 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002005#endif
cristybb503372010-05-27 20:51:26 +00002006 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002007 {
cristy4c08aed2011-07-01 19:47:50 +00002008 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00002009 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002010
cristy8d4629b2010-08-30 17:59:46 +00002011 register ssize_t
2012 x;
2013
cristy3ed852e2009-09-05 21:47:34 +00002014 if (status == MagickFalse)
2015 continue;
2016 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00002017 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00002018 {
2019 status=MagickFalse;
2020 continue;
2021 }
cristybb503372010-05-27 20:51:26 +00002022 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002023 {
cristy50fbc382011-07-07 02:19:17 +00002024 if (image->sync != MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00002025 {
cristy50fbc382011-07-07 02:19:17 +00002026 SetPixelRed(image,gamma_map[ScaleQuantumToMap(
2027 GetPixelRed(image,q))],q);
2028 SetPixelGreen(image,gamma_map[ScaleQuantumToMap(
2029 GetPixelGreen(image,q))],q);
cristy4c08aed2011-07-01 19:47:50 +00002030 SetPixelBlue(image,gamma_map[ScaleQuantumToMap(
2031 GetPixelBlue(image,q))],q);
cristy6cbd7f52009-10-17 16:06:51 +00002032 }
2033 else
2034 {
cristy2b9582a2011-07-04 17:38:56 +00002035 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002036 SetPixelRed(image,gamma_map[ScaleQuantumToMap(
2037 GetPixelRed(image,q))],q);
cristy2b9582a2011-07-04 17:38:56 +00002038 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002039 SetPixelGreen(image,gamma_map[
2040 ScaleQuantumToMap(GetPixelGreen(image,q))],q);
cristy2b9582a2011-07-04 17:38:56 +00002041 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002042 SetPixelBlue(image,gamma_map[
2043 ScaleQuantumToMap(GetPixelBlue(image,q))],q);
cristy2b9582a2011-07-04 17:38:56 +00002044 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy6cbd7f52009-10-17 16:06:51 +00002045 {
2046 if (image->matte == MagickFalse)
cristy4c08aed2011-07-01 19:47:50 +00002047 SetPixelAlpha(image,gamma_map[
2048 ScaleQuantumToMap(GetPixelAlpha(image,q))],q);
cristy6cbd7f52009-10-17 16:06:51 +00002049 else
cristy4c08aed2011-07-01 19:47:50 +00002050 SetPixelAlpha(image,gamma_map[
2051 ScaleQuantumToMap(GetPixelAlpha(image,q))],q);
cristy6cbd7f52009-10-17 16:06:51 +00002052 }
cristy3ed852e2009-09-05 21:47:34 +00002053 }
cristydcfc1ad2011-07-07 16:25:41 +00002054 q+=GetPixelComponents(image);
cristy3ed852e2009-09-05 21:47:34 +00002055 }
cristy2b9582a2011-07-04 17:38:56 +00002056 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00002057 (image->colorspace == CMYKColorspace))
cristybb503372010-05-27 20:51:26 +00002058 for (x=0; x < (ssize_t) image->columns; x++)
cristy4c08aed2011-07-01 19:47:50 +00002059 SetPixelBlack(image,gamma_map[ScaleQuantumToMap(
2060 GetPixelBlack(image,q))],q);
cristy3ed852e2009-09-05 21:47:34 +00002061 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2062 status=MagickFalse;
2063 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2064 {
2065 MagickBooleanType
2066 proceed;
2067
cristyb5d5f722009-11-04 03:03:49 +00002068#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy50fbc382011-07-07 02:19:17 +00002069 #pragma omp critical (MagickCore_GammaImage)
cristy3ed852e2009-09-05 21:47:34 +00002070#endif
2071 proceed=SetImageProgress(image,GammaCorrectImageTag,progress++,
2072 image->rows);
2073 if (proceed == MagickFalse)
2074 status=MagickFalse;
2075 }
2076 }
2077 image_view=DestroyCacheView(image_view);
2078 gamma_map=(Quantum *) RelinquishMagickMemory(gamma_map);
2079 if (image->gamma != 0.0)
2080 image->gamma*=gamma;
2081 return(status);
2082}
2083
2084/*
2085%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2086% %
2087% %
2088% %
2089% H a l d C l u t I m a g e %
2090% %
2091% %
2092% %
2093%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2094%
2095% HaldClutImage() applies a Hald color lookup table to the image. A Hald
2096% color lookup table is a 3-dimensional color cube mapped to 2 dimensions.
2097% Create it with the HALD coder. You can apply any color transformation to
2098% the Hald image and then use this method to apply the transform to the
2099% image.
2100%
2101% The format of the HaldClutImage method is:
2102%
2103% MagickBooleanType HaldClutImage(Image *image,Image *hald_image)
cristy3ed852e2009-09-05 21:47:34 +00002104%
2105% A description of each parameter follows:
2106%
2107% o image: the image, which is replaced by indexed CLUT values
2108%
2109% o hald_image: the color lookup table image for replacement color values.
2110%
cristy3ed852e2009-09-05 21:47:34 +00002111*/
2112
2113static inline size_t MagickMin(const size_t x,const size_t y)
2114{
2115 if (x < y)
2116 return(x);
2117 return(y);
2118}
2119
2120MagickExport MagickBooleanType HaldClutImage(Image *image,
2121 const Image *hald_image)
2122{
cristy3ed852e2009-09-05 21:47:34 +00002123#define HaldClutImageTag "Clut/Image"
2124
2125 typedef struct _HaldInfo
2126 {
2127 MagickRealType
2128 x,
2129 y,
2130 z;
2131 } HaldInfo;
2132
cristyfa112112010-01-04 17:48:07 +00002133 CacheView
cristyd551fbc2011-03-31 18:07:46 +00002134 *hald_view,
cristyfa112112010-01-04 17:48:07 +00002135 *image_view;
2136
cristy3ed852e2009-09-05 21:47:34 +00002137 double
2138 width;
2139
2140 ExceptionInfo
2141 *exception;
2142
cristy3ed852e2009-09-05 21:47:34 +00002143 MagickBooleanType
2144 status;
2145
cristybb503372010-05-27 20:51:26 +00002146 MagickOffsetType
2147 progress;
2148
cristy4c08aed2011-07-01 19:47:50 +00002149 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00002150 zero;
2151
cristy3ed852e2009-09-05 21:47:34 +00002152 size_t
2153 cube_size,
2154 length,
2155 level;
2156
cristybb503372010-05-27 20:51:26 +00002157 ssize_t
2158 y;
2159
cristy3ed852e2009-09-05 21:47:34 +00002160 assert(image != (Image *) NULL);
2161 assert(image->signature == MagickSignature);
2162 if (image->debug != MagickFalse)
2163 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2164 assert(hald_image != (Image *) NULL);
2165 assert(hald_image->signature == MagickSignature);
2166 if (SetImageStorageClass(image,DirectClass) == MagickFalse)
2167 return(MagickFalse);
2168 if (image->matte == MagickFalse)
2169 (void) SetImageAlphaChannel(image,OpaqueAlphaChannel);
2170 /*
2171 Hald clut image.
2172 */
2173 status=MagickTrue;
2174 progress=0;
2175 length=MagickMin(hald_image->columns,hald_image->rows);
2176 for (level=2; (level*level*level) < length; level++) ;
2177 level*=level;
2178 cube_size=level*level;
2179 width=(double) hald_image->columns;
cristy4c08aed2011-07-01 19:47:50 +00002180 GetPixelInfo(hald_image,&zero);
cristy3ed852e2009-09-05 21:47:34 +00002181 exception=(&image->exception);
cristy3ed852e2009-09-05 21:47:34 +00002182 image_view=AcquireCacheView(image);
cristyd551fbc2011-03-31 18:07:46 +00002183 hald_view=AcquireCacheView(hald_image);
cristyb5d5f722009-11-04 03:03:49 +00002184#if defined(MAGICKCORE_OPENMP_SUPPORT)
2185 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002186#endif
cristybb503372010-05-27 20:51:26 +00002187 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002188 {
2189 double
2190 offset;
2191
2192 HaldInfo
2193 point;
2194
cristy4c08aed2011-07-01 19:47:50 +00002195 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00002196 pixel,
2197 pixel1,
2198 pixel2,
2199 pixel3,
2200 pixel4;
2201
cristy4c08aed2011-07-01 19:47:50 +00002202 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00002203 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002204
cristy8d4629b2010-08-30 17:59:46 +00002205 register ssize_t
2206 x;
2207
cristy3ed852e2009-09-05 21:47:34 +00002208 if (status == MagickFalse)
2209 continue;
2210 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00002211 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00002212 {
2213 status=MagickFalse;
2214 continue;
2215 }
cristy3ed852e2009-09-05 21:47:34 +00002216 pixel=zero;
2217 pixel1=zero;
2218 pixel2=zero;
2219 pixel3=zero;
2220 pixel4=zero;
cristybb503372010-05-27 20:51:26 +00002221 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002222 {
cristy4c08aed2011-07-01 19:47:50 +00002223 point.x=QuantumScale*(level-1.0)*GetPixelRed(image,q);
2224 point.y=QuantumScale*(level-1.0)*GetPixelGreen(image,q);
2225 point.z=QuantumScale*(level-1.0)*GetPixelBlue(image,q);
cristy3ed852e2009-09-05 21:47:34 +00002226 offset=point.x+level*floor(point.y)+cube_size*floor(point.z);
2227 point.x-=floor(point.x);
2228 point.y-=floor(point.y);
2229 point.z-=floor(point.z);
cristy4c08aed2011-07-01 19:47:50 +00002230 (void) InterpolatePixelInfo(image,hald_view,
cristy8a7c3e82011-03-26 02:10:53 +00002231 UndefinedInterpolatePixel,fmod(offset,width),floor(offset/width),
2232 &pixel1,exception);
cristy4c08aed2011-07-01 19:47:50 +00002233 (void) InterpolatePixelInfo(image,hald_view,
cristy8a7c3e82011-03-26 02:10:53 +00002234 UndefinedInterpolatePixel,fmod(offset+level,width),floor((offset+level)/
2235 width),&pixel2,exception);
cristy4c08aed2011-07-01 19:47:50 +00002236 CompositePixelInfoAreaBlend(&pixel1,pixel1.alpha,&pixel2,
2237 pixel2.alpha,point.y,&pixel3);
cristy3ed852e2009-09-05 21:47:34 +00002238 offset+=cube_size;
cristy4c08aed2011-07-01 19:47:50 +00002239 (void) InterpolatePixelInfo(image,hald_view,
cristy8a7c3e82011-03-26 02:10:53 +00002240 UndefinedInterpolatePixel,fmod(offset,width),floor(offset/width),
2241 &pixel1,exception);
cristy4c08aed2011-07-01 19:47:50 +00002242 (void) InterpolatePixelInfo(image,hald_view,
cristy8a7c3e82011-03-26 02:10:53 +00002243 UndefinedInterpolatePixel,fmod(offset+level,width),floor((offset+level)/
2244 width),&pixel2,exception);
cristy4c08aed2011-07-01 19:47:50 +00002245 CompositePixelInfoAreaBlend(&pixel1,pixel1.alpha,&pixel2,
2246 pixel2.alpha,point.y,&pixel4);
2247 CompositePixelInfoAreaBlend(&pixel3,pixel3.alpha,&pixel4,
2248 pixel4.alpha,point.z,&pixel);
cristy2b9582a2011-07-04 17:38:56 +00002249 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002250 SetPixelRed(image,
2251 ClampToQuantum(pixel.red),q);
cristy2b9582a2011-07-04 17:38:56 +00002252 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002253 SetPixelGreen(image,
2254 ClampToQuantum(pixel.green),q);
cristy2b9582a2011-07-04 17:38:56 +00002255 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002256 SetPixelBlue(image,
2257 ClampToQuantum(pixel.blue),q);
cristy2b9582a2011-07-04 17:38:56 +00002258 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00002259 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +00002260 SetPixelBlack(image,
2261 ClampToQuantum(pixel.black),q);
cristy2b9582a2011-07-04 17:38:56 +00002262 if (((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0) && (image->matte != MagickFalse))
cristy4c08aed2011-07-01 19:47:50 +00002263 SetPixelAlpha(image,
2264 ClampToQuantum(pixel.alpha),q);
cristydcfc1ad2011-07-07 16:25:41 +00002265 q+=GetPixelComponents(image);
cristy3ed852e2009-09-05 21:47:34 +00002266 }
2267 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2268 status=MagickFalse;
2269 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2270 {
2271 MagickBooleanType
2272 proceed;
2273
cristyb5d5f722009-11-04 03:03:49 +00002274#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf89cb1d2011-07-07 01:24:37 +00002275 #pragma omp critical (MagickCore_HaldClutImage)
cristy3ed852e2009-09-05 21:47:34 +00002276#endif
2277 proceed=SetImageProgress(image,HaldClutImageTag,progress++,image->rows);
2278 if (proceed == MagickFalse)
2279 status=MagickFalse;
2280 }
2281 }
cristyd551fbc2011-03-31 18:07:46 +00002282 hald_view=DestroyCacheView(hald_view);
cristy3ed852e2009-09-05 21:47:34 +00002283 image_view=DestroyCacheView(image_view);
cristy3ed852e2009-09-05 21:47:34 +00002284 return(status);
2285}
2286
2287/*
2288%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2289% %
2290% %
2291% %
2292% L e v e l I m a g e %
2293% %
2294% %
2295% %
2296%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2297%
2298% LevelImage() adjusts the levels of a particular image channel by
2299% scaling the colors falling between specified white and black points to
2300% the full available quantum range.
2301%
2302% The parameters provided represent the black, and white points. The black
2303% point specifies the darkest color in the image. Colors darker than the
2304% black point are set to zero. White point specifies the lightest color in
2305% the image. Colors brighter than the white point are set to the maximum
2306% quantum value.
2307%
2308% If a '!' flag is given, map black and white colors to the given levels
2309% rather than mapping those levels to black and white. See
cristy50fbc382011-07-07 02:19:17 +00002310% LevelizeImage() below.
cristy3ed852e2009-09-05 21:47:34 +00002311%
2312% Gamma specifies a gamma correction to apply to the image.
2313%
2314% The format of the LevelImage method is:
2315%
2316% MagickBooleanType LevelImage(Image *image,const char *levels)
2317%
2318% A description of each parameter follows:
2319%
2320% o image: the image.
2321%
2322% o levels: Specify the levels where the black and white points have the
2323% range of 0-QuantumRange, and gamma has the range 0-10 (e.g. 10x90%+2).
2324% A '!' flag inverts the re-mapping.
2325%
2326*/
cristyf89cb1d2011-07-07 01:24:37 +00002327MagickExport MagickBooleanType LevelImage(Image *image,
2328 const double black_point,const double white_point,const double gamma)
cristy3ed852e2009-09-05 21:47:34 +00002329{
2330#define LevelImageTag "Level/Image"
cristybcfb0432010-05-06 01:45:33 +00002331#define LevelQuantum(x) (ClampToQuantum((MagickRealType) QuantumRange* \
cristyc1f508d2010-04-22 01:21:47 +00002332 pow(scale*((double) (x)-black_point),1.0/gamma)))
cristy3ed852e2009-09-05 21:47:34 +00002333
cristyc4c8d132010-01-07 01:58:38 +00002334 CacheView
2335 *image_view;
2336
cristy3ed852e2009-09-05 21:47:34 +00002337 ExceptionInfo
2338 *exception;
2339
cristy3ed852e2009-09-05 21:47:34 +00002340 MagickBooleanType
2341 status;
2342
cristybb503372010-05-27 20:51:26 +00002343 MagickOffsetType
2344 progress;
2345
anthony7fe39fc2010-04-06 03:19:20 +00002346 register double
2347 scale;
2348
cristy8d4629b2010-08-30 17:59:46 +00002349 register ssize_t
2350 i;
2351
cristybb503372010-05-27 20:51:26 +00002352 ssize_t
2353 y;
2354
cristy3ed852e2009-09-05 21:47:34 +00002355 /*
2356 Allocate and initialize levels map.
2357 */
2358 assert(image != (Image *) NULL);
2359 assert(image->signature == MagickSignature);
2360 if (image->debug != MagickFalse)
2361 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
cristy8d4629b2010-08-30 17:59:46 +00002362 scale=(white_point != black_point) ? 1.0/(white_point-black_point) : 1.0;
cristy3ed852e2009-09-05 21:47:34 +00002363 if (image->storage_class == PseudoClass)
cristyb5d5f722009-11-04 03:03:49 +00002364#if defined(MAGICKCORE_OPENMP_SUPPORT)
2365 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002366#endif
cristybb503372010-05-27 20:51:26 +00002367 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00002368 {
2369 /*
2370 Level colormap.
2371 */
cristy2b9582a2011-07-04 17:38:56 +00002372 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristyc1f508d2010-04-22 01:21:47 +00002373 image->colormap[i].red=LevelQuantum(image->colormap[i].red);
cristy2b9582a2011-07-04 17:38:56 +00002374 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristyc1f508d2010-04-22 01:21:47 +00002375 image->colormap[i].green=LevelQuantum(image->colormap[i].green);
cristy2b9582a2011-07-04 17:38:56 +00002376 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristyc1f508d2010-04-22 01:21:47 +00002377 image->colormap[i].blue=LevelQuantum(image->colormap[i].blue);
cristy2b9582a2011-07-04 17:38:56 +00002378 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002379 image->colormap[i].alpha=LevelQuantum(image->colormap[i].alpha);
cristy3ed852e2009-09-05 21:47:34 +00002380 }
2381 /*
2382 Level image.
2383 */
2384 status=MagickTrue;
2385 progress=0;
2386 exception=(&image->exception);
2387 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00002388#if defined(MAGICKCORE_OPENMP_SUPPORT)
2389 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002390#endif
cristybb503372010-05-27 20:51:26 +00002391 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002392 {
cristy4c08aed2011-07-01 19:47:50 +00002393 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00002394 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002395
cristy8d4629b2010-08-30 17:59:46 +00002396 register ssize_t
2397 x;
2398
cristy3ed852e2009-09-05 21:47:34 +00002399 if (status == MagickFalse)
2400 continue;
2401 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00002402 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00002403 {
2404 status=MagickFalse;
2405 continue;
2406 }
cristybb503372010-05-27 20:51:26 +00002407 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002408 {
cristy2b9582a2011-07-04 17:38:56 +00002409 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002410 SetPixelRed(image,LevelQuantum(
2411 GetPixelRed(image,q)),q);
cristy2b9582a2011-07-04 17:38:56 +00002412 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002413 SetPixelGreen(image,
2414 LevelQuantum(GetPixelGreen(image,q)),q);
cristy2b9582a2011-07-04 17:38:56 +00002415 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002416 SetPixelBlue(image,
2417 LevelQuantum(GetPixelBlue(image,q)),q);
cristy2b9582a2011-07-04 17:38:56 +00002418 if (((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00002419 (image->matte == MagickTrue))
cristy4c08aed2011-07-01 19:47:50 +00002420 SetPixelAlpha(image,
2421 LevelQuantum(GetPixelAlpha(image,q)),q);
cristy2b9582a2011-07-04 17:38:56 +00002422 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00002423 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +00002424 SetPixelBlack(image,
2425 LevelQuantum(GetPixelBlack(image,q)),q);
cristydcfc1ad2011-07-07 16:25:41 +00002426 q+=GetPixelComponents(image);
cristy3ed852e2009-09-05 21:47:34 +00002427 }
2428 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2429 status=MagickFalse;
2430 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2431 {
2432 MagickBooleanType
2433 proceed;
2434
cristyb5d5f722009-11-04 03:03:49 +00002435#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf89cb1d2011-07-07 01:24:37 +00002436 #pragma omp critical (MagickCore_LevelImage)
cristy3ed852e2009-09-05 21:47:34 +00002437#endif
2438 proceed=SetImageProgress(image,LevelImageTag,progress++,image->rows);
2439 if (proceed == MagickFalse)
2440 status=MagickFalse;
2441 }
2442 }
2443 image_view=DestroyCacheView(image_view);
2444 return(status);
2445}
2446
2447/*
2448%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2449% %
2450% %
2451% %
2452% L e v e l i z e I m a g e C h a n n e l %
2453% %
2454% %
2455% %
2456%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2457%
cristy50fbc382011-07-07 02:19:17 +00002458% LevelizeImage() applies the reversed LevelImage() operation to just
cristy3ed852e2009-09-05 21:47:34 +00002459% the specific channels specified. It compresses the full range of color
2460% values, so that they lie between the given black and white points. Gamma is
2461% applied before the values are mapped.
2462%
cristy50fbc382011-07-07 02:19:17 +00002463% LevelizeImage() can be called with by using a +level command line
cristy3ed852e2009-09-05 21:47:34 +00002464% API option, or using a '!' on a -level or LevelImage() geometry string.
2465%
2466% It can be used for example de-contrast a greyscale image to the exact
2467% levels specified. Or by using specific levels for each channel of an image
2468% you can convert a gray-scale image to any linear color gradient, according
2469% to those levels.
2470%
cristy50fbc382011-07-07 02:19:17 +00002471% The format of the LevelizeImage method is:
cristy3ed852e2009-09-05 21:47:34 +00002472%
cristy50fbc382011-07-07 02:19:17 +00002473% MagickBooleanType LevelizeImage(Image *image,const double black_point,
2474% const double white_point,const double gamma)
cristy3ed852e2009-09-05 21:47:34 +00002475%
2476% A description of each parameter follows:
2477%
2478% o image: the image.
2479%
cristy3ed852e2009-09-05 21:47:34 +00002480% o black_point: The level to map zero (black) to.
2481%
2482% o white_point: The level to map QuantiumRange (white) to.
2483%
2484% o gamma: adjust gamma by this factor before mapping values.
2485%
2486*/
cristyd1a2c0f2011-02-09 14:14:50 +00002487MagickExport MagickBooleanType LevelizeImage(Image *image,
2488 const double black_point,const double white_point,const double gamma)
2489{
cristy3ed852e2009-09-05 21:47:34 +00002490#define LevelizeImageTag "Levelize/Image"
cristyce70c172010-01-07 17:15:30 +00002491#define LevelizeValue(x) (ClampToQuantum(((MagickRealType) \
cristy50fbc382011-07-07 02:19:17 +00002492 pow((double) (QuantumScale*(x)),1.0/gamma))*(white_point-black_point)+ \
cristy3ed852e2009-09-05 21:47:34 +00002493 black_point))
2494
cristyc4c8d132010-01-07 01:58:38 +00002495 CacheView
2496 *image_view;
2497
cristy3ed852e2009-09-05 21:47:34 +00002498 ExceptionInfo
2499 *exception;
2500
cristy3ed852e2009-09-05 21:47:34 +00002501 MagickBooleanType
2502 status;
2503
cristybb503372010-05-27 20:51:26 +00002504 MagickOffsetType
2505 progress;
2506
2507 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002508 i;
2509
cristybb503372010-05-27 20:51:26 +00002510 ssize_t
2511 y;
2512
cristy3ed852e2009-09-05 21:47:34 +00002513 /*
2514 Allocate and initialize levels map.
2515 */
2516 assert(image != (Image *) NULL);
2517 assert(image->signature == MagickSignature);
2518 if (image->debug != MagickFalse)
2519 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2520 if (image->storage_class == PseudoClass)
cristyb5d5f722009-11-04 03:03:49 +00002521#if defined(MAGICKCORE_OPENMP_SUPPORT)
2522 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002523#endif
cristybb503372010-05-27 20:51:26 +00002524 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00002525 {
2526 /*
2527 Level colormap.
2528 */
cristy2b9582a2011-07-04 17:38:56 +00002529 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00002530 image->colormap[i].red=LevelizeValue(image->colormap[i].red);
cristy2b9582a2011-07-04 17:38:56 +00002531 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00002532 image->colormap[i].green=LevelizeValue(image->colormap[i].green);
cristy2b9582a2011-07-04 17:38:56 +00002533 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00002534 image->colormap[i].blue=LevelizeValue(image->colormap[i].blue);
cristy2b9582a2011-07-04 17:38:56 +00002535 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002536 image->colormap[i].alpha=LevelizeValue(image->colormap[i].alpha);
cristy3ed852e2009-09-05 21:47:34 +00002537 }
2538 /*
2539 Level image.
2540 */
2541 status=MagickTrue;
2542 progress=0;
2543 exception=(&image->exception);
2544 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00002545#if defined(MAGICKCORE_OPENMP_SUPPORT)
2546 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002547#endif
cristybb503372010-05-27 20:51:26 +00002548 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002549 {
cristy4c08aed2011-07-01 19:47:50 +00002550 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00002551 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002552
cristy8d4629b2010-08-30 17:59:46 +00002553 register ssize_t
2554 x;
2555
cristy3ed852e2009-09-05 21:47:34 +00002556 if (status == MagickFalse)
2557 continue;
2558 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00002559 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00002560 {
2561 status=MagickFalse;
2562 continue;
2563 }
cristybb503372010-05-27 20:51:26 +00002564 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002565 {
cristy2b9582a2011-07-04 17:38:56 +00002566 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002567 SetPixelRed(image,LevelizeValue(GetPixelRed(image,q)),q);
cristy2b9582a2011-07-04 17:38:56 +00002568 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002569 SetPixelGreen(image,LevelizeValue(GetPixelGreen(image,q)),q);
cristy2b9582a2011-07-04 17:38:56 +00002570 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002571 SetPixelBlue(image,LevelizeValue(GetPixelBlue(image,q)),q);
cristy2b9582a2011-07-04 17:38:56 +00002572 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +00002573 (image->colorspace == CMYKColorspace))
2574 SetPixelBlack(image,LevelizeValue(GetPixelBlack(image,q)),q);
cristy2b9582a2011-07-04 17:38:56 +00002575 if (((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00002576 (image->matte == MagickTrue))
cristy4c08aed2011-07-01 19:47:50 +00002577 SetPixelAlpha(image,LevelizeValue(GetPixelAlpha(image,q)),q);
cristydcfc1ad2011-07-07 16:25:41 +00002578 q+=GetPixelComponents(image);
cristy3ed852e2009-09-05 21:47:34 +00002579 }
2580 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2581 status=MagickFalse;
2582 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2583 {
2584 MagickBooleanType
2585 proceed;
2586
cristyb5d5f722009-11-04 03:03:49 +00002587#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy50fbc382011-07-07 02:19:17 +00002588 #pragma omp critical (MagickCore_LevelizeImage)
cristy3ed852e2009-09-05 21:47:34 +00002589#endif
2590 proceed=SetImageProgress(image,LevelizeImageTag,progress++,image->rows);
2591 if (proceed == MagickFalse)
2592 status=MagickFalse;
2593 }
2594 }
cristy8d4629b2010-08-30 17:59:46 +00002595 image_view=DestroyCacheView(image_view);
cristy3ed852e2009-09-05 21:47:34 +00002596 return(status);
2597}
2598
2599/*
2600%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2601% %
2602% %
2603% %
2604% L e v e l I m a g e C o l o r s %
2605% %
2606% %
2607% %
2608%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2609%
cristy490408a2011-07-07 14:42:05 +00002610% LevelImageColors() maps the given color to "black" and "white" values,
cristyee0f8d72009-09-19 00:58:29 +00002611% linearly spreading out the colors, and level values on a channel by channel
2612% bases, as per LevelImage(). The given colors allows you to specify
glennrp1e7f7bc2011-03-02 19:25:28 +00002613% different level ranges for each of the color channels separately.
cristy3ed852e2009-09-05 21:47:34 +00002614%
2615% If the boolean 'invert' is set true the image values will modifyed in the
2616% reverse direction. That is any existing "black" and "white" colors in the
2617% image will become the color values given, with all other values compressed
2618% appropriatally. This effectivally maps a greyscale gradient into the given
2619% color gradient.
2620%
cristy490408a2011-07-07 14:42:05 +00002621% The format of the LevelImageColors method is:
cristy3ed852e2009-09-05 21:47:34 +00002622%
cristy490408a2011-07-07 14:42:05 +00002623% MagickBooleanType LevelImageColors(Image *image,
2624% const PixelInfo *black_color,const PixelInfo *white_color,
2625% const MagickBooleanType invert)
cristy3ed852e2009-09-05 21:47:34 +00002626%
2627% A description of each parameter follows:
2628%
2629% o image: the image.
2630%
cristy3ed852e2009-09-05 21:47:34 +00002631% o black_color: The color to map black to/from
2632%
2633% o white_point: The color to map white to/from
2634%
2635% o invert: if true map the colors (levelize), rather than from (level)
2636%
2637*/
cristy490408a2011-07-07 14:42:05 +00002638MagickExport MagickBooleanType LevelImageColors(Image *image,
cristy4c08aed2011-07-01 19:47:50 +00002639 const PixelInfo *black_color,const PixelInfo *white_color,
cristy3ed852e2009-09-05 21:47:34 +00002640 const MagickBooleanType invert)
2641{
cristy3ed852e2009-09-05 21:47:34 +00002642 MagickStatusType
2643 status;
2644
2645 /*
2646 Allocate and initialize levels map.
2647 */
2648 assert(image != (Image *) NULL);
2649 assert(image->signature == MagickSignature);
2650 if (image->debug != MagickFalse)
2651 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2652 status=MagickFalse;
2653 if (invert == MagickFalse)
2654 {
cristy2b9582a2011-07-04 17:38:56 +00002655 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristyf89cb1d2011-07-07 01:24:37 +00002656 {
cristy490408a2011-07-07 14:42:05 +00002657 PushPixelComponentMap(image,RedChannel);
cristyf89cb1d2011-07-07 01:24:37 +00002658 status|=LevelImage(image,black_color->red,white_color->red,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 ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristyf89cb1d2011-07-07 01:24:37 +00002662 {
cristy490408a2011-07-07 14:42:05 +00002663 PushPixelComponentMap(image,GreenChannel);
cristyf89cb1d2011-07-07 01:24:37 +00002664 status|=LevelImage(image,black_color->green,white_color->green,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 ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristyf89cb1d2011-07-07 01:24:37 +00002668 {
cristy490408a2011-07-07 14:42:05 +00002669 PushPixelComponentMap(image,BlueChannel);
cristyf89cb1d2011-07-07 01:24:37 +00002670 status|=LevelImage(image,black_color->blue,white_color->blue,1.0);
cristy490408a2011-07-07 14:42:05 +00002671 PopPixelComponentMap(image);
cristyf89cb1d2011-07-07 01:24:37 +00002672 }
cristy2b9582a2011-07-04 17:38:56 +00002673 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +00002674 (image->colorspace == CMYKColorspace))
cristyf89cb1d2011-07-07 01:24:37 +00002675 {
cristy490408a2011-07-07 14:42:05 +00002676 PushPixelComponentMap(image,BlackChannel);
cristyf89cb1d2011-07-07 01:24:37 +00002677 status|=LevelImage(image,black_color->black,white_color->black,1.0);
cristy490408a2011-07-07 14:42:05 +00002678 PopPixelComponentMap(image);
cristyf89cb1d2011-07-07 01:24:37 +00002679 }
cristy2b9582a2011-07-04 17:38:56 +00002680 if (((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00002681 (image->matte == MagickTrue))
cristyf89cb1d2011-07-07 01:24:37 +00002682 {
cristy490408a2011-07-07 14:42:05 +00002683 PushPixelComponentMap(image,AlphaChannel);
cristyf89cb1d2011-07-07 01:24:37 +00002684 status|=LevelImage(image,black_color->alpha,white_color->alpha,1.0);
cristy490408a2011-07-07 14:42:05 +00002685 PopPixelComponentMap(image);
cristyf89cb1d2011-07-07 01:24:37 +00002686 }
cristy3ed852e2009-09-05 21:47:34 +00002687 }
2688 else
2689 {
cristy2b9582a2011-07-04 17:38:56 +00002690 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristyf89cb1d2011-07-07 01:24:37 +00002691 {
cristy490408a2011-07-07 14:42:05 +00002692 PushPixelComponentMap(image,RedChannel);
cristy50fbc382011-07-07 02:19:17 +00002693 status|=LevelizeImage(image,black_color->red,white_color->red,1.0);
cristy490408a2011-07-07 14:42:05 +00002694 PopPixelComponentMap(image);
cristyf89cb1d2011-07-07 01:24:37 +00002695 }
cristy2b9582a2011-07-04 17:38:56 +00002696 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristyf89cb1d2011-07-07 01:24:37 +00002697 {
cristy490408a2011-07-07 14:42:05 +00002698 PushPixelComponentMap(image,GreenChannel);
cristy50fbc382011-07-07 02:19:17 +00002699 status|=LevelizeImage(image,black_color->green,white_color->green,
2700 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 ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristyf89cb1d2011-07-07 01:24:37 +00002704 {
cristy490408a2011-07-07 14:42:05 +00002705 PushPixelComponentMap(image,BlueChannel);
cristy50fbc382011-07-07 02:19:17 +00002706 status|=LevelizeImage(image,black_color->blue,white_color->blue,1.0);
cristy490408a2011-07-07 14:42:05 +00002707 PopPixelComponentMap(image);
cristyf89cb1d2011-07-07 01:24:37 +00002708 }
cristy2b9582a2011-07-04 17:38:56 +00002709 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +00002710 (image->colorspace == CMYKColorspace))
cristyf89cb1d2011-07-07 01:24:37 +00002711 {
cristy490408a2011-07-07 14:42:05 +00002712 PushPixelComponentMap(image,BlackChannel);
cristy50fbc382011-07-07 02:19:17 +00002713 status|=LevelizeImage(image,black_color->black,white_color->black,
2714 1.0);
cristy490408a2011-07-07 14:42:05 +00002715 PopPixelComponentMap(image);
cristyf89cb1d2011-07-07 01:24:37 +00002716 }
cristy2b9582a2011-07-04 17:38:56 +00002717 if (((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00002718 (image->matte == MagickTrue))
cristyf89cb1d2011-07-07 01:24:37 +00002719 {
cristy490408a2011-07-07 14:42:05 +00002720 PushPixelComponentMap(image,AlphaChannel);
cristy50fbc382011-07-07 02:19:17 +00002721 status|=LevelizeImage(image,black_color->alpha,white_color->alpha,
2722 1.0);
cristy490408a2011-07-07 14:42:05 +00002723 PopPixelComponentMap(image);
cristyf89cb1d2011-07-07 01:24:37 +00002724 }
cristy3ed852e2009-09-05 21:47:34 +00002725 }
2726 return(status == 0 ? MagickFalse : MagickTrue);
2727}
2728
2729/*
2730%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2731% %
2732% %
2733% %
2734% L i n e a r S t r e t c h I m a g e %
2735% %
2736% %
2737% %
2738%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2739%
2740% The LinearStretchImage() discards any pixels below the black point and
2741% above the white point and levels the remaining pixels.
2742%
2743% The format of the LinearStretchImage method is:
2744%
2745% MagickBooleanType LinearStretchImage(Image *image,
2746% const double black_point,const double white_point)
2747%
2748% A description of each parameter follows:
2749%
2750% o image: the image.
2751%
2752% o black_point: the black point.
2753%
2754% o white_point: the white point.
2755%
2756*/
2757MagickExport MagickBooleanType LinearStretchImage(Image *image,
2758 const double black_point,const double white_point)
2759{
2760#define LinearStretchImageTag "LinearStretch/Image"
2761
2762 ExceptionInfo
2763 *exception;
2764
cristy3ed852e2009-09-05 21:47:34 +00002765 MagickBooleanType
2766 status;
2767
2768 MagickRealType
2769 *histogram,
2770 intensity;
2771
cristy8d4629b2010-08-30 17:59:46 +00002772 ssize_t
2773 black,
2774 white,
2775 y;
2776
cristy3ed852e2009-09-05 21:47:34 +00002777 /*
2778 Allocate histogram and linear map.
2779 */
2780 assert(image != (Image *) NULL);
2781 assert(image->signature == MagickSignature);
2782 histogram=(MagickRealType *) AcquireQuantumMemory(MaxMap+1UL,
2783 sizeof(*histogram));
2784 if (histogram == (MagickRealType *) NULL)
2785 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
2786 image->filename);
2787 /*
2788 Form histogram.
2789 */
2790 (void) ResetMagickMemory(histogram,0,(MaxMap+1)*sizeof(*histogram));
2791 exception=(&image->exception);
cristybb503372010-05-27 20:51:26 +00002792 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002793 {
cristy4c08aed2011-07-01 19:47:50 +00002794 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00002795 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00002796
cristybb503372010-05-27 20:51:26 +00002797 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002798 x;
2799
2800 p=GetVirtualPixels(image,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00002801 if (p == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00002802 break;
cristybb503372010-05-27 20:51:26 +00002803 for (x=(ssize_t) image->columns-1; x >= 0; x--)
cristy3ed852e2009-09-05 21:47:34 +00002804 {
cristy4c08aed2011-07-01 19:47:50 +00002805 histogram[ScaleQuantumToMap(GetPixelIntensity(image,p))]++;
cristydcfc1ad2011-07-07 16:25:41 +00002806 p+=GetPixelComponents(image);
cristy3ed852e2009-09-05 21:47:34 +00002807 }
2808 }
2809 /*
2810 Find the histogram boundaries by locating the black and white point levels.
2811 */
cristy3ed852e2009-09-05 21:47:34 +00002812 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00002813 for (black=0; black < (ssize_t) MaxMap; black++)
cristy3ed852e2009-09-05 21:47:34 +00002814 {
2815 intensity+=histogram[black];
2816 if (intensity >= black_point)
2817 break;
2818 }
2819 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00002820 for (white=(ssize_t) MaxMap; white != 0; white--)
cristy3ed852e2009-09-05 21:47:34 +00002821 {
2822 intensity+=histogram[white];
2823 if (intensity >= white_point)
2824 break;
2825 }
2826 histogram=(MagickRealType *) RelinquishMagickMemory(histogram);
cristyf89cb1d2011-07-07 01:24:37 +00002827 status=LevelImage(image,(double) black,(double) white,1.0);
cristy3ed852e2009-09-05 21:47:34 +00002828 return(status);
2829}
2830
2831/*
2832%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2833% %
2834% %
2835% %
2836% M o d u l a t e I m a g e %
2837% %
2838% %
2839% %
2840%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2841%
2842% ModulateImage() lets you control the brightness, saturation, and hue
2843% of an image. Modulate represents the brightness, saturation, and hue
2844% as one parameter (e.g. 90,150,100). If the image colorspace is HSL, the
2845% modulation is lightness, saturation, and hue. And if the colorspace is
2846% HWB, use blackness, whiteness, and hue.
2847%
2848% The format of the ModulateImage method is:
2849%
2850% MagickBooleanType ModulateImage(Image *image,const char *modulate)
2851%
2852% A description of each parameter follows:
2853%
2854% o image: the image.
2855%
2856% o modulate: Define the percent change in brightness, saturation, and
2857% hue.
2858%
2859*/
2860
2861static void ModulateHSB(const double percent_hue,
2862 const double percent_saturation,const double percent_brightness,
2863 Quantum *red,Quantum *green,Quantum *blue)
2864{
2865 double
2866 brightness,
2867 hue,
2868 saturation;
2869
2870 /*
2871 Increase or decrease color brightness, saturation, or hue.
2872 */
2873 assert(red != (Quantum *) NULL);
2874 assert(green != (Quantum *) NULL);
2875 assert(blue != (Quantum *) NULL);
2876 ConvertRGBToHSB(*red,*green,*blue,&hue,&saturation,&brightness);
2877 hue+=0.5*(0.01*percent_hue-1.0);
2878 while (hue < 0.0)
2879 hue+=1.0;
2880 while (hue > 1.0)
2881 hue-=1.0;
2882 saturation*=0.01*percent_saturation;
2883 brightness*=0.01*percent_brightness;
2884 ConvertHSBToRGB(hue,saturation,brightness,red,green,blue);
2885}
2886
2887static void ModulateHSL(const double percent_hue,
2888 const double percent_saturation,const double percent_lightness,
2889 Quantum *red,Quantum *green,Quantum *blue)
2890{
2891 double
2892 hue,
2893 lightness,
2894 saturation;
2895
2896 /*
2897 Increase or decrease color lightness, saturation, or hue.
2898 */
2899 assert(red != (Quantum *) NULL);
2900 assert(green != (Quantum *) NULL);
2901 assert(blue != (Quantum *) NULL);
2902 ConvertRGBToHSL(*red,*green,*blue,&hue,&saturation,&lightness);
2903 hue+=0.5*(0.01*percent_hue-1.0);
2904 while (hue < 0.0)
2905 hue+=1.0;
2906 while (hue > 1.0)
2907 hue-=1.0;
2908 saturation*=0.01*percent_saturation;
2909 lightness*=0.01*percent_lightness;
2910 ConvertHSLToRGB(hue,saturation,lightness,red,green,blue);
2911}
2912
2913static void ModulateHWB(const double percent_hue,const double percent_whiteness, const double percent_blackness,Quantum *red,Quantum *green,Quantum *blue)
2914{
2915 double
2916 blackness,
2917 hue,
2918 whiteness;
2919
2920 /*
2921 Increase or decrease color blackness, whiteness, or hue.
2922 */
2923 assert(red != (Quantum *) NULL);
2924 assert(green != (Quantum *) NULL);
2925 assert(blue != (Quantum *) NULL);
2926 ConvertRGBToHWB(*red,*green,*blue,&hue,&whiteness,&blackness);
2927 hue+=0.5*(0.01*percent_hue-1.0);
2928 while (hue < 0.0)
2929 hue+=1.0;
2930 while (hue > 1.0)
2931 hue-=1.0;
2932 blackness*=0.01*percent_blackness;
2933 whiteness*=0.01*percent_whiteness;
2934 ConvertHWBToRGB(hue,whiteness,blackness,red,green,blue);
2935}
2936
2937MagickExport MagickBooleanType ModulateImage(Image *image,const char *modulate)
2938{
2939#define ModulateImageTag "Modulate/Image"
2940
cristyc4c8d132010-01-07 01:58:38 +00002941 CacheView
2942 *image_view;
2943
cristy3ed852e2009-09-05 21:47:34 +00002944 ColorspaceType
2945 colorspace;
2946
2947 const char
2948 *artifact;
2949
2950 double
2951 percent_brightness,
2952 percent_hue,
2953 percent_saturation;
2954
2955 ExceptionInfo
2956 *exception;
2957
2958 GeometryInfo
2959 geometry_info;
2960
cristy3ed852e2009-09-05 21:47:34 +00002961 MagickBooleanType
2962 status;
2963
cristybb503372010-05-27 20:51:26 +00002964 MagickOffsetType
2965 progress;
2966
cristy3ed852e2009-09-05 21:47:34 +00002967 MagickStatusType
2968 flags;
2969
cristybb503372010-05-27 20:51:26 +00002970 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002971 i;
2972
cristybb503372010-05-27 20:51:26 +00002973 ssize_t
2974 y;
2975
cristy3ed852e2009-09-05 21:47:34 +00002976 /*
cristy2b726bd2010-01-11 01:05:39 +00002977 Initialize modulate table.
cristy3ed852e2009-09-05 21:47:34 +00002978 */
2979 assert(image != (Image *) NULL);
2980 assert(image->signature == MagickSignature);
2981 if (image->debug != MagickFalse)
2982 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2983 if (modulate == (char *) NULL)
2984 return(MagickFalse);
2985 flags=ParseGeometry(modulate,&geometry_info);
2986 percent_brightness=geometry_info.rho;
2987 percent_saturation=geometry_info.sigma;
2988 if ((flags & SigmaValue) == 0)
2989 percent_saturation=100.0;
2990 percent_hue=geometry_info.xi;
2991 if ((flags & XiValue) == 0)
2992 percent_hue=100.0;
2993 colorspace=UndefinedColorspace;
2994 artifact=GetImageArtifact(image,"modulate:colorspace");
2995 if (artifact != (const char *) NULL)
cristy042ee782011-04-22 18:48:30 +00002996 colorspace=(ColorspaceType) ParseCommandOption(MagickColorspaceOptions,
cristy3ed852e2009-09-05 21:47:34 +00002997 MagickFalse,artifact);
2998 if (image->storage_class == PseudoClass)
2999 {
3000 /*
3001 Modulate colormap.
3002 */
cristyb5d5f722009-11-04 03:03:49 +00003003#if defined(MAGICKCORE_OPENMP_SUPPORT)
3004 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003005#endif
cristybb503372010-05-27 20:51:26 +00003006 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00003007 switch (colorspace)
3008 {
3009 case HSBColorspace:
3010 {
3011 ModulateHSB(percent_hue,percent_saturation,percent_brightness,
3012 &image->colormap[i].red,&image->colormap[i].green,
3013 &image->colormap[i].blue);
3014 break;
3015 }
3016 case HSLColorspace:
3017 default:
3018 {
3019 ModulateHSL(percent_hue,percent_saturation,percent_brightness,
3020 &image->colormap[i].red,&image->colormap[i].green,
3021 &image->colormap[i].blue);
3022 break;
3023 }
3024 case HWBColorspace:
3025 {
3026 ModulateHWB(percent_hue,percent_saturation,percent_brightness,
3027 &image->colormap[i].red,&image->colormap[i].green,
3028 &image->colormap[i].blue);
3029 break;
3030 }
3031 }
3032 }
3033 /*
3034 Modulate image.
3035 */
3036 status=MagickTrue;
3037 progress=0;
3038 exception=(&image->exception);
3039 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00003040#if defined(MAGICKCORE_OPENMP_SUPPORT)
3041 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003042#endif
cristybb503372010-05-27 20:51:26 +00003043 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003044 {
cristy5afeab82011-04-30 01:30:09 +00003045 Quantum
3046 blue,
3047 green,
3048 red;
3049
cristy4c08aed2011-07-01 19:47:50 +00003050 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00003051 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003052
cristy8d4629b2010-08-30 17:59:46 +00003053 register ssize_t
3054 x;
3055
cristy3ed852e2009-09-05 21:47:34 +00003056 if (status == MagickFalse)
3057 continue;
3058 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00003059 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00003060 {
3061 status=MagickFalse;
3062 continue;
3063 }
cristybb503372010-05-27 20:51:26 +00003064 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003065 {
cristy4c08aed2011-07-01 19:47:50 +00003066 red=GetPixelRed(image,q);
3067 green=GetPixelGreen(image,q);
3068 blue=GetPixelBlue(image,q);
cristy3ed852e2009-09-05 21:47:34 +00003069 switch (colorspace)
3070 {
3071 case HSBColorspace:
3072 {
3073 ModulateHSB(percent_hue,percent_saturation,percent_brightness,
cristy5afeab82011-04-30 01:30:09 +00003074 &red,&green,&blue);
cristy3ed852e2009-09-05 21:47:34 +00003075 break;
3076 }
3077 case HSLColorspace:
3078 default:
3079 {
3080 ModulateHSL(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 case HWBColorspace:
3085 {
3086 ModulateHWB(percent_hue,percent_saturation,percent_brightness,
cristy5afeab82011-04-30 01:30:09 +00003087 &red,&green,&blue);
cristy3ed852e2009-09-05 21:47:34 +00003088 break;
3089 }
3090 }
cristy4c08aed2011-07-01 19:47:50 +00003091 SetPixelRed(image,red,q);
3092 SetPixelGreen(image,green,q);
3093 SetPixelBlue(image,blue,q);
cristydcfc1ad2011-07-07 16:25:41 +00003094 q+=GetPixelComponents(image);
cristy3ed852e2009-09-05 21:47:34 +00003095 }
3096 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
3097 status=MagickFalse;
3098 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3099 {
3100 MagickBooleanType
3101 proceed;
3102
cristyb5d5f722009-11-04 03:03:49 +00003103#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00003104 #pragma omp critical (MagickCore_ModulateImage)
3105#endif
3106 proceed=SetImageProgress(image,ModulateImageTag,progress++,image->rows);
3107 if (proceed == MagickFalse)
3108 status=MagickFalse;
3109 }
3110 }
3111 image_view=DestroyCacheView(image_view);
3112 return(status);
3113}
3114
3115/*
3116%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3117% %
3118% %
3119% %
3120% N e g a t e I m a g e %
3121% %
3122% %
3123% %
3124%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3125%
3126% NegateImage() negates the colors in the reference image. The grayscale
3127% option means that only grayscale values within the image are negated.
3128%
cristy50fbc382011-07-07 02:19:17 +00003129% The format of the NegateImage method is:
cristy3ed852e2009-09-05 21:47:34 +00003130%
3131% MagickBooleanType NegateImage(Image *image,
3132% const MagickBooleanType grayscale)
cristy3ed852e2009-09-05 21:47:34 +00003133%
3134% A description of each parameter follows:
3135%
3136% o image: the image.
3137%
cristy3ed852e2009-09-05 21:47:34 +00003138% o grayscale: If MagickTrue, only negate grayscale pixels within the image.
3139%
3140*/
cristy3ed852e2009-09-05 21:47:34 +00003141MagickExport MagickBooleanType NegateImage(Image *image,
3142 const MagickBooleanType grayscale)
3143{
cristy3ed852e2009-09-05 21:47:34 +00003144#define NegateImageTag "Negate/Image"
3145
cristyc4c8d132010-01-07 01:58:38 +00003146 CacheView
3147 *image_view;
3148
cristy3ed852e2009-09-05 21:47:34 +00003149 ExceptionInfo
3150 *exception;
3151
cristy3ed852e2009-09-05 21:47:34 +00003152 MagickBooleanType
3153 status;
3154
cristybb503372010-05-27 20:51:26 +00003155 MagickOffsetType
3156 progress;
3157
3158 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003159 i;
3160
cristybb503372010-05-27 20:51:26 +00003161 ssize_t
3162 y;
3163
cristy3ed852e2009-09-05 21:47:34 +00003164 assert(image != (Image *) NULL);
3165 assert(image->signature == MagickSignature);
3166 if (image->debug != MagickFalse)
3167 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3168 if (image->storage_class == PseudoClass)
3169 {
3170 /*
3171 Negate colormap.
3172 */
cristyb5d5f722009-11-04 03:03:49 +00003173#if defined(MAGICKCORE_OPENMP_SUPPORT)
3174 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003175#endif
cristybb503372010-05-27 20:51:26 +00003176 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00003177 {
3178 if (grayscale != MagickFalse)
3179 if ((image->colormap[i].red != image->colormap[i].green) ||
3180 (image->colormap[i].green != image->colormap[i].blue))
3181 continue;
cristy2b9582a2011-07-04 17:38:56 +00003182 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00003183 image->colormap[i].red=(Quantum) QuantumRange-
3184 image->colormap[i].red;
cristy2b9582a2011-07-04 17:38:56 +00003185 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00003186 image->colormap[i].green=(Quantum) QuantumRange-
3187 image->colormap[i].green;
cristy2b9582a2011-07-04 17:38:56 +00003188 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00003189 image->colormap[i].blue=(Quantum) QuantumRange-
3190 image->colormap[i].blue;
3191 }
3192 }
3193 /*
3194 Negate image.
3195 */
3196 status=MagickTrue;
3197 progress=0;
3198 exception=(&image->exception);
3199 image_view=AcquireCacheView(image);
3200 if (grayscale != MagickFalse)
3201 {
cristyb5d5f722009-11-04 03:03:49 +00003202#if defined(MAGICKCORE_OPENMP_SUPPORT)
3203 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003204#endif
cristybb503372010-05-27 20:51:26 +00003205 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003206 {
3207 MagickBooleanType
3208 sync;
3209
cristy4c08aed2011-07-01 19:47:50 +00003210 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00003211 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003212
cristy8d4629b2010-08-30 17:59:46 +00003213 register ssize_t
3214 x;
3215
cristy3ed852e2009-09-05 21:47:34 +00003216 if (status == MagickFalse)
3217 continue;
3218 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,
3219 exception);
cristy4c08aed2011-07-01 19:47:50 +00003220 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00003221 {
3222 status=MagickFalse;
3223 continue;
3224 }
cristybb503372010-05-27 20:51:26 +00003225 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003226 {
cristy4c08aed2011-07-01 19:47:50 +00003227 if ((GetPixelRed(image,q) != GetPixelGreen(image,q)) ||
3228 (GetPixelGreen(image,q) != GetPixelBlue(image,q)))
cristy3ed852e2009-09-05 21:47:34 +00003229 {
cristydcfc1ad2011-07-07 16:25:41 +00003230 q+=GetPixelComponents(image);
cristy3ed852e2009-09-05 21:47:34 +00003231 continue;
3232 }
cristy2b9582a2011-07-04 17:38:56 +00003233 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003234 SetPixelRed(image,QuantumRange-GetPixelRed(image,q),q);
cristy2b9582a2011-07-04 17:38:56 +00003235 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003236 SetPixelGreen(image,QuantumRange-GetPixelGreen(image,q),q);
cristy2b9582a2011-07-04 17:38:56 +00003237 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003238 SetPixelBlue(image,QuantumRange-GetPixelBlue(image,q),q);
cristy2b9582a2011-07-04 17:38:56 +00003239 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00003240 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +00003241 SetPixelBlack(image,QuantumRange-GetPixelBlack(image,q),q);
cristy2b9582a2011-07-04 17:38:56 +00003242 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003243 SetPixelAlpha(image,QuantumRange-GetPixelAlpha(image,q),q);
cristydcfc1ad2011-07-07 16:25:41 +00003244 q+=GetPixelComponents(image);
cristy3ed852e2009-09-05 21:47:34 +00003245 }
3246 sync=SyncCacheViewAuthenticPixels(image_view,exception);
3247 if (sync == MagickFalse)
3248 status=MagickFalse;
3249 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3250 {
3251 MagickBooleanType
3252 proceed;
3253
cristyb5d5f722009-11-04 03:03:49 +00003254#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy50fbc382011-07-07 02:19:17 +00003255 #pragma omp critical (MagickCore_NegateImage)
cristy3ed852e2009-09-05 21:47:34 +00003256#endif
3257 proceed=SetImageProgress(image,NegateImageTag,progress++,
3258 image->rows);
3259 if (proceed == MagickFalse)
3260 status=MagickFalse;
3261 }
3262 }
3263 image_view=DestroyCacheView(image_view);
3264 return(MagickTrue);
3265 }
3266 /*
3267 Negate image.
3268 */
cristyb5d5f722009-11-04 03:03:49 +00003269#if defined(MAGICKCORE_OPENMP_SUPPORT)
3270 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003271#endif
cristybb503372010-05-27 20:51:26 +00003272 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003273 {
cristy4c08aed2011-07-01 19:47:50 +00003274 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00003275 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003276
cristy8d4629b2010-08-30 17:59:46 +00003277 register ssize_t
3278 x;
3279
cristy3ed852e2009-09-05 21:47:34 +00003280 if (status == MagickFalse)
3281 continue;
3282 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00003283 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00003284 {
3285 status=MagickFalse;
3286 continue;
3287 }
cristybb503372010-05-27 20:51:26 +00003288 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003289 {
cristy2b9582a2011-07-04 17:38:56 +00003290 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003291 SetPixelRed(image,QuantumRange-GetPixelRed(image,q),q);
cristy2b9582a2011-07-04 17:38:56 +00003292 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003293 SetPixelGreen(image,QuantumRange-GetPixelGreen(image,q),q);
cristy2b9582a2011-07-04 17:38:56 +00003294 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003295 SetPixelBlue(image,QuantumRange-GetPixelBlue(image,q),q);
cristy2b9582a2011-07-04 17:38:56 +00003296 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00003297 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +00003298 SetPixelBlack(image,QuantumRange-GetPixelBlack(image,q),q);
cristy2b9582a2011-07-04 17:38:56 +00003299 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003300 SetPixelAlpha(image,QuantumRange-GetPixelAlpha(image,q),q);
cristydcfc1ad2011-07-07 16:25:41 +00003301 q+=GetPixelComponents(image);
cristy3ed852e2009-09-05 21:47:34 +00003302 }
3303 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
3304 status=MagickFalse;
3305 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3306 {
3307 MagickBooleanType
3308 proceed;
3309
cristyb5d5f722009-11-04 03:03:49 +00003310#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy50fbc382011-07-07 02:19:17 +00003311 #pragma omp critical (MagickCore_NegateImage)
cristy3ed852e2009-09-05 21:47:34 +00003312#endif
3313 proceed=SetImageProgress(image,NegateImageTag,progress++,image->rows);
3314 if (proceed == MagickFalse)
3315 status=MagickFalse;
3316 }
3317 }
3318 image_view=DestroyCacheView(image_view);
3319 return(status);
3320}
3321
3322/*
3323%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3324% %
3325% %
3326% %
3327% N o r m a l i z e I m a g e %
3328% %
3329% %
3330% %
3331%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3332%
cristya4dfb122011-07-07 19:01:57 +00003333% NormalizeImage() enhances the contrast of a color image by mapping the
3334% darkest 2 percent of all pixel to black and the brightest 1 percent to white.
cristy3ed852e2009-09-05 21:47:34 +00003335%
3336% The format of the NormalizeImage method is:
3337%
3338% MagickBooleanType NormalizeImage(Image *image)
cristy3ed852e2009-09-05 21:47:34 +00003339%
3340% A description of each parameter follows:
3341%
3342% o image: the image.
3343%
cristy3ed852e2009-09-05 21:47:34 +00003344*/
cristy3ed852e2009-09-05 21:47:34 +00003345MagickExport MagickBooleanType NormalizeImage(Image *image)
3346{
cristy3ed852e2009-09-05 21:47:34 +00003347 double
3348 black_point,
3349 white_point;
3350
cristy530239c2010-07-25 17:34:26 +00003351 black_point=(double) image->columns*image->rows*0.0015;
3352 white_point=(double) image->columns*image->rows*0.9995;
cristy50fbc382011-07-07 02:19:17 +00003353 return(ContrastStretchImage(image,black_point,white_point));
cristy3ed852e2009-09-05 21:47:34 +00003354}
3355
3356/*
3357%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3358% %
3359% %
3360% %
3361% S i g m o i d a l C o n t r a s t I m a g e %
3362% %
3363% %
3364% %
3365%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3366%
3367% SigmoidalContrastImage() adjusts the contrast of an image with a non-linear
3368% sigmoidal contrast algorithm. Increase the contrast of the image using a
3369% sigmoidal transfer function without saturating highlights or shadows.
3370% Contrast indicates how much to increase the contrast (0 is none; 3 is
3371% typical; 20 is pushing it); mid-point indicates where midtones fall in the
3372% resultant image (0 is white; 50% is middle-gray; 100% is black). Set
3373% sharpen to MagickTrue to increase the image contrast otherwise the contrast
3374% is reduced.
3375%
3376% The format of the SigmoidalContrastImage method is:
3377%
3378% MagickBooleanType SigmoidalContrastImage(Image *image,
3379% const MagickBooleanType sharpen,const char *levels)
cristy3ed852e2009-09-05 21:47:34 +00003380%
3381% A description of each parameter follows:
3382%
3383% o image: the image.
3384%
cristy3ed852e2009-09-05 21:47:34 +00003385% o sharpen: Increase or decrease image contrast.
3386%
cristyfa769582010-09-30 23:30:03 +00003387% o alpha: strength of the contrast, the larger the number the more
3388% 'threshold-like' it becomes.
cristy3ed852e2009-09-05 21:47:34 +00003389%
cristyfa769582010-09-30 23:30:03 +00003390% o beta: midpoint of the function as a color value 0 to QuantumRange.
cristy3ed852e2009-09-05 21:47:34 +00003391%
3392*/
cristy3ed852e2009-09-05 21:47:34 +00003393MagickExport MagickBooleanType SigmoidalContrastImage(Image *image,
cristy9ee60942011-07-06 14:54:38 +00003394 const MagickBooleanType sharpen,const double contrast,const double midpoint)
cristy3ed852e2009-09-05 21:47:34 +00003395{
3396#define SigmoidalContrastImageTag "SigmoidalContrast/Image"
3397
cristyc4c8d132010-01-07 01:58:38 +00003398 CacheView
3399 *image_view;
3400
cristy3ed852e2009-09-05 21:47:34 +00003401 ExceptionInfo
3402 *exception;
3403
cristy3ed852e2009-09-05 21:47:34 +00003404 MagickBooleanType
3405 status;
3406
cristybb503372010-05-27 20:51:26 +00003407 MagickOffsetType
3408 progress;
3409
cristy3ed852e2009-09-05 21:47:34 +00003410 MagickRealType
3411 *sigmoidal_map;
3412
cristybb503372010-05-27 20:51:26 +00003413 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003414 i;
3415
cristybb503372010-05-27 20:51:26 +00003416 ssize_t
3417 y;
3418
cristy3ed852e2009-09-05 21:47:34 +00003419 /*
3420 Allocate and initialize sigmoidal maps.
3421 */
3422 assert(image != (Image *) NULL);
3423 assert(image->signature == MagickSignature);
3424 if (image->debug != MagickFalse)
3425 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3426 sigmoidal_map=(MagickRealType *) AcquireQuantumMemory(MaxMap+1UL,
3427 sizeof(*sigmoidal_map));
3428 if (sigmoidal_map == (MagickRealType *) NULL)
3429 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
3430 image->filename);
3431 (void) ResetMagickMemory(sigmoidal_map,0,(MaxMap+1)*sizeof(*sigmoidal_map));
cristyb5d5f722009-11-04 03:03:49 +00003432#if defined(MAGICKCORE_OPENMP_SUPPORT)
3433 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003434#endif
cristybb503372010-05-27 20:51:26 +00003435 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +00003436 {
3437 if (sharpen != MagickFalse)
3438 {
3439 sigmoidal_map[i]=(MagickRealType) ScaleMapToQuantum((MagickRealType)
3440 (MaxMap*((1.0/(1.0+exp(contrast*(midpoint/(double) QuantumRange-
3441 (double) i/MaxMap))))-(1.0/(1.0+exp(contrast*(midpoint/
3442 (double) QuantumRange)))))/((1.0/(1.0+exp(contrast*(midpoint/
3443 (double) QuantumRange-1.0))))-(1.0/(1.0+exp(contrast*(midpoint/
3444 (double) QuantumRange)))))+0.5));
3445 continue;
3446 }
3447 sigmoidal_map[i]=(MagickRealType) ScaleMapToQuantum((MagickRealType)
3448 (MaxMap*(QuantumScale*midpoint-log((1.0-(1.0/(1.0+exp(midpoint/
3449 (double) QuantumRange*contrast))+((double) i/MaxMap)*((1.0/
3450 (1.0+exp(contrast*(midpoint/(double) QuantumRange-1.0))))-(1.0/
3451 (1.0+exp(midpoint/(double) QuantumRange*contrast))))))/
3452 (1.0/(1.0+exp(midpoint/(double) QuantumRange*contrast))+
3453 ((double) i/MaxMap)*((1.0/(1.0+exp(contrast*(midpoint/
3454 (double) QuantumRange-1.0))))-(1.0/(1.0+exp(midpoint/
3455 (double) QuantumRange*contrast))))))/contrast)));
3456 }
3457 if (image->storage_class == PseudoClass)
3458 {
3459 /*
3460 Sigmoidal-contrast enhance colormap.
3461 */
cristyb5d5f722009-11-04 03:03:49 +00003462#if defined(MAGICKCORE_OPENMP_SUPPORT)
3463 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003464#endif
cristybb503372010-05-27 20:51:26 +00003465 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00003466 {
cristy2b9582a2011-07-04 17:38:56 +00003467 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristyce70c172010-01-07 17:15:30 +00003468 image->colormap[i].red=ClampToQuantum(sigmoidal_map[
cristy3ed852e2009-09-05 21:47:34 +00003469 ScaleQuantumToMap(image->colormap[i].red)]);
cristy2b9582a2011-07-04 17:38:56 +00003470 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristyce70c172010-01-07 17:15:30 +00003471 image->colormap[i].green=ClampToQuantum(sigmoidal_map[
cristy3ed852e2009-09-05 21:47:34 +00003472 ScaleQuantumToMap(image->colormap[i].green)]);
cristy2b9582a2011-07-04 17:38:56 +00003473 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristyce70c172010-01-07 17:15:30 +00003474 image->colormap[i].blue=ClampToQuantum(sigmoidal_map[
cristy3ed852e2009-09-05 21:47:34 +00003475 ScaleQuantumToMap(image->colormap[i].blue)]);
cristy2b9582a2011-07-04 17:38:56 +00003476 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003477 image->colormap[i].alpha=ClampToQuantum(sigmoidal_map[
3478 ScaleQuantumToMap(image->colormap[i].alpha)]);
cristy3ed852e2009-09-05 21:47:34 +00003479 }
3480 }
3481 /*
3482 Sigmoidal-contrast enhance image.
3483 */
3484 status=MagickTrue;
3485 progress=0;
3486 exception=(&image->exception);
3487 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00003488#if defined(MAGICKCORE_OPENMP_SUPPORT)
3489 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003490#endif
cristybb503372010-05-27 20:51:26 +00003491 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003492 {
cristy4c08aed2011-07-01 19:47:50 +00003493 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00003494 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003495
cristy8d4629b2010-08-30 17:59:46 +00003496 register ssize_t
3497 x;
3498
cristy3ed852e2009-09-05 21:47:34 +00003499 if (status == MagickFalse)
3500 continue;
3501 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00003502 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00003503 {
3504 status=MagickFalse;
3505 continue;
3506 }
cristybb503372010-05-27 20:51:26 +00003507 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003508 {
cristy2b9582a2011-07-04 17:38:56 +00003509 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003510 SetPixelRed(image,ClampToQuantum(sigmoidal_map[ScaleQuantumToMap(
3511 GetPixelRed(image,q))]),q);
cristy2b9582a2011-07-04 17:38:56 +00003512 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003513 SetPixelGreen(image,ClampToQuantum(sigmoidal_map[ScaleQuantumToMap(
3514 GetPixelGreen(image,q))]),q);
cristy2b9582a2011-07-04 17:38:56 +00003515 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003516 SetPixelBlue(image,ClampToQuantum(sigmoidal_map[ScaleQuantumToMap(
3517 GetPixelBlue(image,q))]),q);
cristy2b9582a2011-07-04 17:38:56 +00003518 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00003519 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +00003520 SetPixelBlack(image,ClampToQuantum(sigmoidal_map[ScaleQuantumToMap(
3521 GetPixelBlack(image,q))]),q);
cristy2b9582a2011-07-04 17:38:56 +00003522 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003523 SetPixelAlpha(image,ClampToQuantum(sigmoidal_map[ScaleQuantumToMap(
3524 GetPixelAlpha(image,q))]),q);
cristydcfc1ad2011-07-07 16:25:41 +00003525 q+=GetPixelComponents(image);
cristy3ed852e2009-09-05 21:47:34 +00003526 }
3527 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
3528 status=MagickFalse;
3529 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3530 {
3531 MagickBooleanType
3532 proceed;
3533
cristyb5d5f722009-11-04 03:03:49 +00003534#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy9ee60942011-07-06 14:54:38 +00003535 #pragma omp critical (MagickCore_SigmoidalContrastImage)
cristy3ed852e2009-09-05 21:47:34 +00003536#endif
3537 proceed=SetImageProgress(image,SigmoidalContrastImageTag,progress++,
3538 image->rows);
3539 if (proceed == MagickFalse)
3540 status=MagickFalse;
3541 }
3542 }
3543 image_view=DestroyCacheView(image_view);
3544 sigmoidal_map=(MagickRealType *) RelinquishMagickMemory(sigmoidal_map);
3545 return(status);
3546}