blob: 86f2a7159c0ffc6fde1623d00965e6fb5beff1b7 [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;
cristyab015852011-07-06 01:03:32 +0000119 return(LevelImageChannel(image,DefaultChannels,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;
130 status=status && LevelImageChannel(image,RedChannel,0.0,(double)
cristyab015852011-07-06 01:03:32 +0000131 QuantumRange,gamma);
cristy3ed852e2009-09-05 21:47:34 +0000132 }
cristy2b9582a2011-07-04 17:38:56 +0000133 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +0000134 {
cristy2b726bd2010-01-11 01:05:39 +0000135 (void) GetImageChannelMean(image,GreenChannel,&mean,&sans,
136 &image->exception);
cristy4c08aed2011-07-01 19:47:50 +0000137 gamma=log(mean*QuantumScale)/log_mean;
138 status=status && LevelImageChannel(image,GreenChannel,0.0,(double)
139 QuantumRange,gamma);
cristy3ed852e2009-09-05 21:47:34 +0000140 }
cristy2b9582a2011-07-04 17:38:56 +0000141 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +0000142 {
cristy2b726bd2010-01-11 01:05:39 +0000143 (void) GetImageChannelMean(image,BlueChannel,&mean,&sans,
144 &image->exception);
cristy4c08aed2011-07-01 19:47:50 +0000145 gamma=log(mean*QuantumScale)/log_mean;
146 status=status && LevelImageChannel(image,BlueChannel,0.0,(double)
147 QuantumRange,gamma);
148 }
cristy2b9582a2011-07-04 17:38:56 +0000149 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +0000150 (image->colorspace == CMYKColorspace))
151 {
152 (void) GetImageChannelMean(image,BlackChannel,&mean,&sans,
153 &image->exception);
154 gamma=log(mean*QuantumScale)/log_mean;
155 status=status && LevelImageChannel(image,BlackChannel,0.0,(double)
156 QuantumRange,gamma);
cristy3ed852e2009-09-05 21:47:34 +0000157 }
cristy2b9582a2011-07-04 17:38:56 +0000158 if (((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +0000159 (image->matte == MagickTrue))
160 {
cristy2b726bd2010-01-11 01:05:39 +0000161 (void) GetImageChannelMean(image,OpacityChannel,&mean,&sans,
162 &image->exception);
cristy4c08aed2011-07-01 19:47:50 +0000163 gamma=log(mean*QuantumScale)/log_mean;
164 status=status && LevelImageChannel(image,OpacityChannel,0.0,(double)
165 QuantumRange,gamma);
cristy3ed852e2009-09-05 21:47:34 +0000166 }
167 return(status != 0 ? MagickTrue : MagickFalse);
168}
169
170/*
171%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
172% %
173% %
174% %
175% A u t o L e v e l I m a g e %
176% %
177% %
178% %
179%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
180%
181% AutoLevelImage() adjusts the levels of a particular image channel by
182% scaling the minimum and maximum values to the full quantum range.
183%
184% The format of the LevelImage method is:
185%
186% MagickBooleanType AutoLevelImage(Image *image)
cristy3ed852e2009-09-05 21:47:34 +0000187%
188% A description of each parameter follows:
189%
190% o image: The image to auto-level
191%
cristy3ed852e2009-09-05 21:47:34 +0000192*/
cristy3ed852e2009-09-05 21:47:34 +0000193MagickExport MagickBooleanType AutoLevelImage(Image *image)
194{
cristyab015852011-07-06 01:03:32 +0000195 return(MinMaxStretchImage(image,DefaultChannels,0.0,0.0));
cristy3ed852e2009-09-05 21:47:34 +0000196}
197
198/*
199%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
200% %
201% %
202% %
cristya28d6b82010-01-11 20:03:47 +0000203% B r i g h t n e s s C o n t r a s t I m a g e %
204% %
205% %
206% %
207%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
208%
209% Use BrightnessContrastImage() to change the brightness and/or contrast of
210% an image. It converts the brightness and contrast parameters into slope
211% and intercept and calls a polynomical function to apply to the image.
212%
213% The format of the BrightnessContrastImage method is:
214%
215% MagickBooleanType BrightnessContrastImage(Image *image,
216% const double brightness,const double contrast)
cristya28d6b82010-01-11 20:03:47 +0000217%
218% A description of each parameter follows:
219%
220% o image: the image.
221%
cristya28d6b82010-01-11 20:03:47 +0000222% o brightness: the brightness percent (-100 .. 100).
223%
224% o contrast: the contrast percent (-100 .. 100).
225%
226*/
cristya28d6b82010-01-11 20:03:47 +0000227MagickExport MagickBooleanType BrightnessContrastImage(Image *image,
228 const double brightness,const double contrast)
229{
cristya28d6b82010-01-11 20:03:47 +0000230#define BrightnessContastImageTag "BrightnessContast/Image"
231
232 double
233 alpha,
234 intercept,
235 coefficients[2],
236 slope;
237
238 MagickBooleanType
239 status;
240
241 /*
242 Compute slope and intercept.
243 */
244 assert(image != (Image *) NULL);
245 assert(image->signature == MagickSignature);
246 if (image->debug != MagickFalse)
247 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
248 alpha=contrast;
cristy4205a3c2010-09-12 20:19:59 +0000249 slope=tan((double) (MagickPI*(alpha/100.0+1.0)/4.0));
cristya28d6b82010-01-11 20:03:47 +0000250 if (slope < 0.0)
251 slope=0.0;
252 intercept=brightness/100.0+((100-brightness)/200.0)*(1.0-slope);
253 coefficients[0]=slope;
254 coefficients[1]=intercept;
cristy9ee60942011-07-06 14:54:38 +0000255 status=FunctionImageChannel(image,DefaultChannels,PolynomialFunction,2,
256 coefficients,&image->exception);
cristya28d6b82010-01-11 20:03:47 +0000257 return(status);
258}
259
260/*
261%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
262% %
263% %
264% %
cristy3ed852e2009-09-05 21:47:34 +0000265% C o l o r D e c i s i o n L i s t I m a g e %
266% %
267% %
268% %
269%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
270%
271% ColorDecisionListImage() accepts a lightweight Color Correction Collection
272% (CCC) file which solely contains one or more color corrections and applies
273% the correction to the image. Here is a sample CCC file:
274%
275% <ColorCorrectionCollection xmlns="urn:ASC:CDL:v1.2">
276% <ColorCorrection id="cc03345">
277% <SOPNode>
278% <Slope> 0.9 1.2 0.5 </Slope>
279% <Offset> 0.4 -0.5 0.6 </Offset>
280% <Power> 1.0 0.8 1.5 </Power>
281% </SOPNode>
282% <SATNode>
283% <Saturation> 0.85 </Saturation>
284% </SATNode>
285% </ColorCorrection>
286% </ColorCorrectionCollection>
287%
288% which includes the slop, offset, and power for each of the RGB channels
289% as well as the saturation.
290%
291% The format of the ColorDecisionListImage method is:
292%
293% MagickBooleanType ColorDecisionListImage(Image *image,
294% const char *color_correction_collection)
295%
296% A description of each parameter follows:
297%
298% o image: the image.
299%
300% o color_correction_collection: the color correction collection in XML.
301%
302*/
303MagickExport MagickBooleanType ColorDecisionListImage(Image *image,
304 const char *color_correction_collection)
305{
306#define ColorDecisionListCorrectImageTag "ColorDecisionList/Image"
307
308 typedef struct _Correction
309 {
310 double
311 slope,
312 offset,
313 power;
314 } Correction;
315
316 typedef struct _ColorCorrection
317 {
318 Correction
319 red,
320 green,
321 blue;
322
323 double
324 saturation;
325 } ColorCorrection;
326
cristyc4c8d132010-01-07 01:58:38 +0000327 CacheView
328 *image_view;
329
cristy3ed852e2009-09-05 21:47:34 +0000330 char
331 token[MaxTextExtent];
332
333 ColorCorrection
334 color_correction;
335
336 const char
337 *content,
338 *p;
339
340 ExceptionInfo
341 *exception;
342
cristy3ed852e2009-09-05 21:47:34 +0000343 MagickBooleanType
344 status;
345
cristybb503372010-05-27 20:51:26 +0000346 MagickOffsetType
347 progress;
348
cristy3ed852e2009-09-05 21:47:34 +0000349 PixelPacket
350 *cdl_map;
351
cristybb503372010-05-27 20:51:26 +0000352 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000353 i;
354
cristybb503372010-05-27 20:51:26 +0000355 ssize_t
356 y;
357
cristy3ed852e2009-09-05 21:47:34 +0000358 XMLTreeInfo
359 *cc,
360 *ccc,
361 *sat,
362 *sop;
363
cristy3ed852e2009-09-05 21:47:34 +0000364 /*
365 Allocate and initialize cdl maps.
366 */
367 assert(image != (Image *) NULL);
368 assert(image->signature == MagickSignature);
369 if (image->debug != MagickFalse)
370 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
371 if (color_correction_collection == (const char *) NULL)
372 return(MagickFalse);
373 ccc=NewXMLTree((const char *) color_correction_collection,&image->exception);
374 if (ccc == (XMLTreeInfo *) NULL)
375 return(MagickFalse);
376 cc=GetXMLTreeChild(ccc,"ColorCorrection");
377 if (cc == (XMLTreeInfo *) NULL)
378 {
379 ccc=DestroyXMLTree(ccc);
380 return(MagickFalse);
381 }
382 color_correction.red.slope=1.0;
383 color_correction.red.offset=0.0;
384 color_correction.red.power=1.0;
385 color_correction.green.slope=1.0;
386 color_correction.green.offset=0.0;
387 color_correction.green.power=1.0;
388 color_correction.blue.slope=1.0;
389 color_correction.blue.offset=0.0;
390 color_correction.blue.power=1.0;
391 color_correction.saturation=0.0;
392 sop=GetXMLTreeChild(cc,"SOPNode");
393 if (sop != (XMLTreeInfo *) NULL)
394 {
395 XMLTreeInfo
396 *offset,
397 *power,
398 *slope;
399
400 slope=GetXMLTreeChild(sop,"Slope");
401 if (slope != (XMLTreeInfo *) NULL)
402 {
403 content=GetXMLTreeContent(slope);
404 p=(const char *) content;
405 for (i=0; (*p != '\0') && (i < 3); i++)
406 {
407 GetMagickToken(p,&p,token);
408 if (*token == ',')
409 GetMagickToken(p,&p,token);
410 switch (i)
411 {
cristyc1acd842011-05-19 23:05:47 +0000412 case 0:
413 {
414 color_correction.red.slope=InterpretLocaleValue(token,
415 (char **) NULL);
416 break;
417 }
418 case 1:
419 {
420 color_correction.green.slope=InterpretLocaleValue(token,
421 (char **) NULL);
422 break;
423 }
424 case 2:
425 {
426 color_correction.blue.slope=InterpretLocaleValue(token,
427 (char **) NULL);
428 break;
429 }
cristy3ed852e2009-09-05 21:47:34 +0000430 }
431 }
432 }
433 offset=GetXMLTreeChild(sop,"Offset");
434 if (offset != (XMLTreeInfo *) NULL)
435 {
436 content=GetXMLTreeContent(offset);
437 p=(const char *) content;
438 for (i=0; (*p != '\0') && (i < 3); i++)
439 {
440 GetMagickToken(p,&p,token);
441 if (*token == ',')
442 GetMagickToken(p,&p,token);
443 switch (i)
444 {
cristyc1acd842011-05-19 23:05:47 +0000445 case 0:
446 {
447 color_correction.red.offset=InterpretLocaleValue(token,
448 (char **) NULL);
449 break;
450 }
451 case 1:
452 {
453 color_correction.green.offset=InterpretLocaleValue(token,
454 (char **) NULL);
455 break;
456 }
457 case 2:
458 {
459 color_correction.blue.offset=InterpretLocaleValue(token,
460 (char **) NULL);
461 break;
462 }
cristy3ed852e2009-09-05 21:47:34 +0000463 }
464 }
465 }
466 power=GetXMLTreeChild(sop,"Power");
467 if (power != (XMLTreeInfo *) NULL)
468 {
469 content=GetXMLTreeContent(power);
470 p=(const char *) content;
471 for (i=0; (*p != '\0') && (i < 3); i++)
472 {
473 GetMagickToken(p,&p,token);
474 if (*token == ',')
475 GetMagickToken(p,&p,token);
476 switch (i)
477 {
cristyc1acd842011-05-19 23:05:47 +0000478 case 0:
479 {
480 color_correction.red.power=InterpretLocaleValue(token,
481 (char **) NULL);
482 break;
483 }
484 case 1:
485 {
486 color_correction.green.power=InterpretLocaleValue(token,
487 (char **) NULL);
488 break;
489 }
490 case 2:
491 {
492 color_correction.blue.power=InterpretLocaleValue(token,
493 (char **) NULL);
494 break;
495 }
cristy3ed852e2009-09-05 21:47:34 +0000496 }
497 }
498 }
499 }
500 sat=GetXMLTreeChild(cc,"SATNode");
501 if (sat != (XMLTreeInfo *) NULL)
502 {
503 XMLTreeInfo
504 *saturation;
505
506 saturation=GetXMLTreeChild(sat,"Saturation");
507 if (saturation != (XMLTreeInfo *) NULL)
508 {
509 content=GetXMLTreeContent(saturation);
510 p=(const char *) content;
511 GetMagickToken(p,&p,token);
cristyc1acd842011-05-19 23:05:47 +0000512 color_correction.saturation=InterpretLocaleValue(token,
513 (char **) NULL);
cristy3ed852e2009-09-05 21:47:34 +0000514 }
515 }
516 ccc=DestroyXMLTree(ccc);
517 if (image->debug != MagickFalse)
518 {
519 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
520 " Color Correction Collection:");
521 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000522 " color_correction.red.slope: %g",color_correction.red.slope);
cristy3ed852e2009-09-05 21:47:34 +0000523 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000524 " color_correction.red.offset: %g",color_correction.red.offset);
cristy3ed852e2009-09-05 21:47:34 +0000525 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000526 " color_correction.red.power: %g",color_correction.red.power);
cristy3ed852e2009-09-05 21:47:34 +0000527 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000528 " color_correction.green.slope: %g",color_correction.green.slope);
cristy3ed852e2009-09-05 21:47:34 +0000529 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000530 " color_correction.green.offset: %g",color_correction.green.offset);
cristy3ed852e2009-09-05 21:47:34 +0000531 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000532 " color_correction.green.power: %g",color_correction.green.power);
cristy3ed852e2009-09-05 21:47:34 +0000533 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000534 " color_correction.blue.slope: %g",color_correction.blue.slope);
cristy3ed852e2009-09-05 21:47:34 +0000535 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000536 " color_correction.blue.offset: %g",color_correction.blue.offset);
cristy3ed852e2009-09-05 21:47:34 +0000537 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000538 " color_correction.blue.power: %g",color_correction.blue.power);
cristy3ed852e2009-09-05 21:47:34 +0000539 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000540 " color_correction.saturation: %g",color_correction.saturation);
cristy3ed852e2009-09-05 21:47:34 +0000541 }
542 cdl_map=(PixelPacket *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*cdl_map));
543 if (cdl_map == (PixelPacket *) NULL)
544 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
545 image->filename);
cristyb5d5f722009-11-04 03:03:49 +0000546#if defined(MAGICKCORE_OPENMP_SUPPORT)
547 #pragma omp parallel for schedule(dynamic,4)
cristy3ed852e2009-09-05 21:47:34 +0000548#endif
cristybb503372010-05-27 20:51:26 +0000549 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +0000550 {
cristyce70c172010-01-07 17:15:30 +0000551 cdl_map[i].red=ClampToQuantum((MagickRealType) ScaleMapToQuantum((
cristy3ed852e2009-09-05 21:47:34 +0000552 MagickRealType) (MaxMap*(pow(color_correction.red.slope*i/MaxMap+
553 color_correction.red.offset,color_correction.red.power)))));
cristyce70c172010-01-07 17:15:30 +0000554 cdl_map[i].green=ClampToQuantum((MagickRealType) ScaleMapToQuantum((
cristy3ed852e2009-09-05 21:47:34 +0000555 MagickRealType) (MaxMap*(pow(color_correction.green.slope*i/MaxMap+
556 color_correction.green.offset,color_correction.green.power)))));
cristyce70c172010-01-07 17:15:30 +0000557 cdl_map[i].blue=ClampToQuantum((MagickRealType) ScaleMapToQuantum((
cristy3ed852e2009-09-05 21:47:34 +0000558 MagickRealType) (MaxMap*(pow(color_correction.blue.slope*i/MaxMap+
559 color_correction.blue.offset,color_correction.blue.power)))));
560 }
561 if (image->storage_class == PseudoClass)
562 {
563 /*
564 Apply transfer function to colormap.
565 */
cristyb5d5f722009-11-04 03:03:49 +0000566#if defined(MAGICKCORE_OPENMP_SUPPORT)
567 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000568#endif
cristybb503372010-05-27 20:51:26 +0000569 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +0000570 {
571 double
572 luma;
573
574 luma=0.2126*image->colormap[i].red+0.7152*image->colormap[i].green+
575 0.0722*image->colormap[i].blue;
cristyce70c172010-01-07 17:15:30 +0000576 image->colormap[i].red=ClampToQuantum(luma+color_correction.saturation*
cristy3ed852e2009-09-05 21:47:34 +0000577 cdl_map[ScaleQuantumToMap(image->colormap[i].red)].red-luma);
cristyce70c172010-01-07 17:15:30 +0000578 image->colormap[i].green=ClampToQuantum(luma+
cristy3ed852e2009-09-05 21:47:34 +0000579 color_correction.saturation*cdl_map[ScaleQuantumToMap(
580 image->colormap[i].green)].green-luma);
cristyce70c172010-01-07 17:15:30 +0000581 image->colormap[i].blue=ClampToQuantum(luma+color_correction.saturation*
cristy3ed852e2009-09-05 21:47:34 +0000582 cdl_map[ScaleQuantumToMap(image->colormap[i].blue)].blue-luma);
583 }
584 }
585 /*
586 Apply transfer function to image.
587 */
588 status=MagickTrue;
589 progress=0;
590 exception=(&image->exception);
591 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +0000592#if defined(MAGICKCORE_OPENMP_SUPPORT)
593 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000594#endif
cristybb503372010-05-27 20:51:26 +0000595 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000596 {
597 double
598 luma;
599
cristy4c08aed2011-07-01 19:47:50 +0000600 register Quantum
cristyc47d1f82009-11-26 01:44:43 +0000601 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000602
cristy8d4629b2010-08-30 17:59:46 +0000603 register ssize_t
604 x;
605
cristy3ed852e2009-09-05 21:47:34 +0000606 if (status == MagickFalse)
607 continue;
608 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +0000609 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +0000610 {
611 status=MagickFalse;
612 continue;
613 }
cristybb503372010-05-27 20:51:26 +0000614 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000615 {
cristy4c08aed2011-07-01 19:47:50 +0000616 luma=0.2126*GetPixelRed(image,q)+0.7152*GetPixelGreen(image,q)+0.0722*
617 GetPixelBlue(image,q);
618 SetPixelRed(image,ClampToQuantum(luma+color_correction.saturation*
619 (cdl_map[ScaleQuantumToMap(GetPixelRed(image,q))].red-luma)),q);
620 SetPixelGreen(image,ClampToQuantum(luma+color_correction.saturation*
621 (cdl_map[ScaleQuantumToMap(GetPixelGreen(image,q))].green-luma)),q);
622 SetPixelBlue(image,ClampToQuantum(luma+color_correction.saturation*
623 (cdl_map[ScaleQuantumToMap(GetPixelBlue(image,q))].blue-luma)),q);
624 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +0000625 }
626 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
627 status=MagickFalse;
628 if (image->progress_monitor != (MagickProgressMonitor) NULL)
629 {
630 MagickBooleanType
631 proceed;
632
cristyb5d5f722009-11-04 03:03:49 +0000633#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +0000634 #pragma omp critical (MagickCore_ColorDecisionListImageChannel)
635#endif
636 proceed=SetImageProgress(image,ColorDecisionListCorrectImageTag,
637 progress++,image->rows);
638 if (proceed == MagickFalse)
639 status=MagickFalse;
640 }
641 }
642 image_view=DestroyCacheView(image_view);
643 cdl_map=(PixelPacket *) RelinquishMagickMemory(cdl_map);
644 return(status);
645}
646
647/*
648%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
649% %
650% %
651% %
652% C l u t I m a g e %
653% %
654% %
655% %
656%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
657%
658% ClutImage() replaces each color value in the given image, by using it as an
659% index to lookup a replacement color value in a Color Look UP Table in the
cristycee97112010-05-28 00:44:52 +0000660% form of an image. The values are extracted along a diagonal of the CLUT
cristy3ed852e2009-09-05 21:47:34 +0000661% image so either a horizontal or vertial gradient image can be used.
662%
663% Typically this is used to either re-color a gray-scale image according to a
664% color gradient in the CLUT image, or to perform a freeform histogram
665% (level) adjustment according to the (typically gray-scale) gradient in the
666% CLUT image.
667%
668% When the 'channel' mask includes the matte/alpha transparency channel but
669% one image has no such channel it is assumed that that image is a simple
670% gray-scale image that will effect the alpha channel values, either for
671% gray-scale coloring (with transparent or semi-transparent colors), or
672% a histogram adjustment of existing alpha channel values. If both images
673% have matte channels, direct and normal indexing is applied, which is rarely
674% used.
675%
676% The format of the ClutImage method is:
677%
678% MagickBooleanType ClutImage(Image *image,Image *clut_image)
679% MagickBooleanType ClutImageChannel(Image *image,
680% const ChannelType channel,Image *clut_image)
681%
682% A description of each parameter follows:
683%
684% o image: the image, which is replaced by indexed CLUT values
685%
686% o clut_image: the color lookup table image for replacement color values.
687%
688% o channel: the channel.
689%
690*/
691
692MagickExport MagickBooleanType ClutImage(Image *image,const Image *clut_image)
693{
694 return(ClutImageChannel(image,DefaultChannels,clut_image));
695}
696
697MagickExport MagickBooleanType ClutImageChannel(Image *image,
698 const ChannelType channel,const Image *clut_image)
699{
cristy4c08aed2011-07-01 19:47:50 +0000700#define ClampAlphaPixelComponent(pixel) ClampToQuantum((pixel)->alpha)
701#define ClampBlackPixelComponent(pixel) ClampToQuantum((pixel)->black)
702#define ClampBluePixelComponent(pixel) ClampToQuantum((pixel)->blue)
703#define ClampGreenPixelComponent(pixel) ClampToQuantum((pixel)->green)
704#define ClampRedPixelComponent(pixel) ClampToQuantum((pixel)->red)
cristy3ed852e2009-09-05 21:47:34 +0000705#define ClutImageTag "Clut/Image"
706
cristyfa112112010-01-04 17:48:07 +0000707 CacheView
cristy708333f2011-03-26 01:25:07 +0000708 *clut_view,
cristyfa112112010-01-04 17:48:07 +0000709 *image_view;
710
cristy3ed852e2009-09-05 21:47:34 +0000711 ExceptionInfo
712 *exception;
713
cristy3ed852e2009-09-05 21:47:34 +0000714 MagickBooleanType
715 status;
716
cristybb503372010-05-27 20:51:26 +0000717 MagickOffsetType
718 progress;
719
cristy4c08aed2011-07-01 19:47:50 +0000720 PixelInfo
cristy49f37242011-03-22 18:18:23 +0000721 *clut_map;
722
723 register ssize_t
724 i;
cristy3ed852e2009-09-05 21:47:34 +0000725
cristybb503372010-05-27 20:51:26 +0000726 ssize_t
727 adjust,
728 y;
729
cristy3ed852e2009-09-05 21:47:34 +0000730 assert(image != (Image *) NULL);
731 assert(image->signature == MagickSignature);
732 if (image->debug != MagickFalse)
733 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
734 assert(clut_image != (Image *) NULL);
735 assert(clut_image->signature == MagickSignature);
736 if (SetImageStorageClass(image,DirectClass) == MagickFalse)
737 return(MagickFalse);
cristy4c08aed2011-07-01 19:47:50 +0000738 clut_map=(PixelInfo *) AcquireQuantumMemory(MaxMap+1UL,
cristy49f37242011-03-22 18:18:23 +0000739 sizeof(*clut_map));
cristy4c08aed2011-07-01 19:47:50 +0000740 if (clut_map == (PixelInfo *) NULL)
cristy49f37242011-03-22 18:18:23 +0000741 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
742 image->filename);
cristy3ed852e2009-09-05 21:47:34 +0000743 /*
744 Clut image.
745 */
746 status=MagickTrue;
747 progress=0;
cristybb503372010-05-27 20:51:26 +0000748 adjust=(ssize_t) (clut_image->interpolate == IntegerInterpolatePixel ? 0 : 1);
cristy3ed852e2009-09-05 21:47:34 +0000749 exception=(&image->exception);
cristy708333f2011-03-26 01:25:07 +0000750 clut_view=AcquireCacheView(clut_image);
cristyaf6bc722011-03-25 19:16:14 +0000751#if defined(MAGICKCORE_OPENMP_SUPPORT)
752 #pragma omp parallel for schedule(dynamic,4)
753#endif
cristy49f37242011-03-22 18:18:23 +0000754 for (i=0; i <= (ssize_t) MaxMap; i++)
755 {
cristy4c08aed2011-07-01 19:47:50 +0000756 GetPixelInfo(clut_image,clut_map+i);
757 (void) InterpolatePixelInfo(clut_image,clut_view,
cristy8a7c3e82011-03-26 02:10:53 +0000758 UndefinedInterpolatePixel,QuantumScale*i*(clut_image->columns-adjust),
759 QuantumScale*i*(clut_image->rows-adjust),clut_map+i,exception);
cristy49f37242011-03-22 18:18:23 +0000760 }
cristy708333f2011-03-26 01:25:07 +0000761 clut_view=DestroyCacheView(clut_view);
762 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +0000763#if defined(MAGICKCORE_OPENMP_SUPPORT)
764 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000765#endif
cristybb503372010-05-27 20:51:26 +0000766 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000767 {
cristy4c08aed2011-07-01 19:47:50 +0000768 PixelInfo
cristy3635df22011-03-25 00:16:16 +0000769 pixel;
770
cristy4c08aed2011-07-01 19:47:50 +0000771 register Quantum
cristyc47d1f82009-11-26 01:44:43 +0000772 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000773
cristy8d4629b2010-08-30 17:59:46 +0000774 register ssize_t
775 x;
776
cristy3ed852e2009-09-05 21:47:34 +0000777 if (status == MagickFalse)
778 continue;
779 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +0000780 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +0000781 {
782 status=MagickFalse;
783 continue;
784 }
cristy4c08aed2011-07-01 19:47:50 +0000785 GetPixelInfo(image,&pixel);
cristybb503372010-05-27 20:51:26 +0000786 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000787 {
cristy4c08aed2011-07-01 19:47:50 +0000788 SetPixelInfo(image,q,&pixel);
cristy2b9582a2011-07-04 17:38:56 +0000789 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000790 SetPixelRed(image,ClampRedPixelComponent(clut_map+
791 ScaleQuantumToMap(GetPixelRed(image,q))),q);
cristy2b9582a2011-07-04 17:38:56 +0000792 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000793 SetPixelGreen(image,ClampGreenPixelComponent(clut_map+
794 ScaleQuantumToMap(GetPixelGreen(image,q))),q);
cristy2b9582a2011-07-04 17:38:56 +0000795 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000796 SetPixelBlue(image,ClampBluePixelComponent(clut_map+
797 ScaleQuantumToMap(GetPixelBlue(image,q))),q);
cristy2b9582a2011-07-04 17:38:56 +0000798 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +0000799 (image->colorspace == CMYKColorspace))
800 SetPixelBlack(image,ClampBlackPixelComponent(clut_map+
801 ScaleQuantumToMap(GetPixelBlack(image,q))),q);
cristy2b9582a2011-07-04 17:38:56 +0000802 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy3635df22011-03-25 00:16:16 +0000803 {
804 if (clut_image->matte == MagickFalse)
cristy4c08aed2011-07-01 19:47:50 +0000805 SetPixelAlpha(image,GetPixelInfoIntensity(clut_map+
806 ScaleQuantumToMap((Quantum) GetPixelAlpha(image,q))),q);
cristy3635df22011-03-25 00:16:16 +0000807 else
808 if (image->matte == MagickFalse)
cristy4c08aed2011-07-01 19:47:50 +0000809 SetPixelAlpha(image,ClampAlphaPixelComponent(clut_map+
810 ScaleQuantumToMap((Quantum) GetPixelInfoIntensity(&pixel))),q);
cristy3635df22011-03-25 00:16:16 +0000811 else
cristy4c08aed2011-07-01 19:47:50 +0000812 SetPixelAlpha(image,ClampAlphaPixelComponent(clut_map+
813 ScaleQuantumToMap(GetPixelAlpha(image,q))),q);
cristy3635df22011-03-25 00:16:16 +0000814 }
cristy4c08aed2011-07-01 19:47:50 +0000815 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +0000816 }
817 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
818 status=MagickFalse;
819 if (image->progress_monitor != (MagickProgressMonitor) NULL)
820 {
821 MagickBooleanType
822 proceed;
823
cristyb5d5f722009-11-04 03:03:49 +0000824#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +0000825 #pragma omp critical (MagickCore_ClutImageChannel)
826#endif
827 proceed=SetImageProgress(image,ClutImageTag,progress++,image->rows);
828 if (proceed == MagickFalse)
829 status=MagickFalse;
830 }
831 }
832 image_view=DestroyCacheView(image_view);
cristy4c08aed2011-07-01 19:47:50 +0000833 clut_map=(PixelInfo *) RelinquishMagickMemory(clut_map);
cristy2b9582a2011-07-04 17:38:56 +0000834 if ((clut_image->matte != MagickFalse) &&
835 ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0))
cristy3ed852e2009-09-05 21:47:34 +0000836 (void) SetImageAlphaChannel(image,ActivateAlphaChannel);
837 return(status);
838}
839
840/*
841%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
842% %
843% %
844% %
845% C o n t r a s t I m a g e %
846% %
847% %
848% %
849%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
850%
851% ContrastImage() enhances the intensity differences between the lighter and
852% darker elements of the image. Set sharpen to a MagickTrue to increase the
853% image contrast otherwise the contrast is reduced.
854%
855% The format of the ContrastImage method is:
856%
857% MagickBooleanType ContrastImage(Image *image,
858% const MagickBooleanType sharpen)
859%
860% A description of each parameter follows:
861%
862% o image: the image.
863%
864% o sharpen: Increase or decrease image contrast.
865%
866*/
867
868static void Contrast(const int sign,Quantum *red,Quantum *green,Quantum *blue)
869{
870 double
871 brightness,
872 hue,
873 saturation;
874
875 /*
876 Enhance contrast: dark color become darker, light color become lighter.
877 */
878 assert(red != (Quantum *) NULL);
879 assert(green != (Quantum *) NULL);
880 assert(blue != (Quantum *) NULL);
881 hue=0.0;
882 saturation=0.0;
883 brightness=0.0;
884 ConvertRGBToHSB(*red,*green,*blue,&hue,&saturation,&brightness);
cristy31ac0f02011-03-12 02:04:47 +0000885 brightness+=0.5*sign*(0.5*(sin((double) (MagickPI*(brightness-0.5)))+1.0)-
cristy4205a3c2010-09-12 20:19:59 +0000886 brightness);
cristy3ed852e2009-09-05 21:47:34 +0000887 if (brightness > 1.0)
888 brightness=1.0;
889 else
890 if (brightness < 0.0)
891 brightness=0.0;
892 ConvertHSBToRGB(hue,saturation,brightness,red,green,blue);
893}
894
895MagickExport MagickBooleanType ContrastImage(Image *image,
896 const MagickBooleanType sharpen)
897{
898#define ContrastImageTag "Contrast/Image"
899
cristyc4c8d132010-01-07 01:58:38 +0000900 CacheView
901 *image_view;
902
cristy3ed852e2009-09-05 21:47:34 +0000903 ExceptionInfo
904 *exception;
905
906 int
907 sign;
908
cristy3ed852e2009-09-05 21:47:34 +0000909 MagickBooleanType
910 status;
911
cristybb503372010-05-27 20:51:26 +0000912 MagickOffsetType
913 progress;
914
915 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000916 i;
917
cristybb503372010-05-27 20:51:26 +0000918 ssize_t
919 y;
920
cristy3ed852e2009-09-05 21:47:34 +0000921 assert(image != (Image *) NULL);
922 assert(image->signature == MagickSignature);
923 if (image->debug != MagickFalse)
924 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
925 sign=sharpen != MagickFalse ? 1 : -1;
926 if (image->storage_class == PseudoClass)
927 {
928 /*
929 Contrast enhance colormap.
930 */
cristybb503372010-05-27 20:51:26 +0000931 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +0000932 Contrast(sign,&image->colormap[i].red,&image->colormap[i].green,
933 &image->colormap[i].blue);
934 }
935 /*
936 Contrast enhance image.
937 */
938 status=MagickTrue;
939 progress=0;
940 exception=(&image->exception);
941 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +0000942#if defined(MAGICKCORE_OPENMP_SUPPORT)
943 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000944#endif
cristybb503372010-05-27 20:51:26 +0000945 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000946 {
cristy5afeab82011-04-30 01:30:09 +0000947 Quantum
948 blue,
949 green,
950 red;
951
cristy4c08aed2011-07-01 19:47:50 +0000952 register Quantum
cristyc47d1f82009-11-26 01:44:43 +0000953 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000954
cristy8d4629b2010-08-30 17:59:46 +0000955 register ssize_t
956 x;
957
cristy3ed852e2009-09-05 21:47:34 +0000958 if (status == MagickFalse)
959 continue;
960 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +0000961 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +0000962 {
963 status=MagickFalse;
964 continue;
965 }
cristybb503372010-05-27 20:51:26 +0000966 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000967 {
cristy4c08aed2011-07-01 19:47:50 +0000968 red=GetPixelRed(image,q);
969 green=GetPixelGreen(image,q);
970 blue=GetPixelBlue(image,q);
cristy5afeab82011-04-30 01:30:09 +0000971 Contrast(sign,&red,&green,&blue);
cristy4c08aed2011-07-01 19:47:50 +0000972 SetPixelRed(image,red,q);
973 SetPixelGreen(image,green,q);
974 SetPixelBlue(image,blue,q);
975 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +0000976 }
977 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
978 status=MagickFalse;
979 if (image->progress_monitor != (MagickProgressMonitor) NULL)
980 {
981 MagickBooleanType
982 proceed;
983
cristyb5d5f722009-11-04 03:03:49 +0000984#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +0000985 #pragma omp critical (MagickCore_ContrastImage)
986#endif
987 proceed=SetImageProgress(image,ContrastImageTag,progress++,image->rows);
988 if (proceed == MagickFalse)
989 status=MagickFalse;
990 }
991 }
992 image_view=DestroyCacheView(image_view);
993 return(status);
994}
995
996/*
997%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
998% %
999% %
1000% %
1001% C o n t r a s t S t r e t c h I m a g e %
1002% %
1003% %
1004% %
1005%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1006%
1007% The ContrastStretchImage() is a simple image enhancement technique that
1008% attempts to improve the contrast in an image by `stretching' the range of
1009% intensity values it contains to span a desired range of values. It differs
1010% from the more sophisticated histogram equalization in that it can only
1011% apply % a linear scaling function to the image pixel values. As a result
1012% the `enhancement' is less harsh.
1013%
1014% The format of the ContrastStretchImage method is:
1015%
1016% MagickBooleanType ContrastStretchImage(Image *image,
1017% const char *levels)
1018% MagickBooleanType ContrastStretchImageChannel(Image *image,
cristybb503372010-05-27 20:51:26 +00001019% const size_t channel,const double black_point,
cristy3ed852e2009-09-05 21:47:34 +00001020% const double white_point)
1021%
1022% A description of each parameter follows:
1023%
1024% o image: the image.
1025%
1026% o channel: the channel.
1027%
1028% o black_point: the black point.
1029%
1030% o white_point: the white point.
1031%
1032% o levels: Specify the levels where the black and white points have the
1033% range of 0 to number-of-pixels (e.g. 1%, 10x90%, etc.).
1034%
1035*/
1036
1037MagickExport MagickBooleanType ContrastStretchImage(Image *image,
1038 const char *levels)
1039{
1040 double
1041 black_point,
1042 white_point;
1043
1044 GeometryInfo
1045 geometry_info;
1046
1047 MagickBooleanType
1048 status;
1049
1050 MagickStatusType
1051 flags;
1052
1053 /*
1054 Parse levels.
1055 */
1056 if (levels == (char *) NULL)
1057 return(MagickFalse);
1058 flags=ParseGeometry(levels,&geometry_info);
1059 black_point=geometry_info.rho;
1060 white_point=(double) image->columns*image->rows;
1061 if ((flags & SigmaValue) != 0)
1062 white_point=geometry_info.sigma;
1063 if ((flags & PercentValue) != 0)
1064 {
1065 black_point*=(double) QuantumRange/100.0;
1066 white_point*=(double) QuantumRange/100.0;
1067 }
1068 if ((flags & SigmaValue) == 0)
1069 white_point=(double) image->columns*image->rows-black_point;
1070 status=ContrastStretchImageChannel(image,DefaultChannels,black_point,
1071 white_point);
1072 return(status);
1073}
1074
1075MagickExport MagickBooleanType ContrastStretchImageChannel(Image *image,
1076 const ChannelType channel,const double black_point,const double white_point)
1077{
1078#define MaxRange(color) ((MagickRealType) ScaleQuantumToMap((Quantum) (color)))
1079#define ContrastStretchImageTag "ContrastStretch/Image"
1080
cristyc4c8d132010-01-07 01:58:38 +00001081 CacheView
1082 *image_view;
1083
cristy3ed852e2009-09-05 21:47:34 +00001084 double
1085 intensity;
1086
1087 ExceptionInfo
1088 *exception;
1089
cristy3ed852e2009-09-05 21:47:34 +00001090 MagickBooleanType
1091 status;
1092
cristybb503372010-05-27 20:51:26 +00001093 MagickOffsetType
1094 progress;
1095
cristy4c08aed2011-07-01 19:47:50 +00001096 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00001097 black,
1098 *histogram,
1099 *stretch_map,
1100 white;
1101
cristybb503372010-05-27 20:51:26 +00001102 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001103 i;
1104
cristybb503372010-05-27 20:51:26 +00001105 ssize_t
1106 y;
1107
cristy3ed852e2009-09-05 21:47:34 +00001108 /*
1109 Allocate histogram and stretch map.
1110 */
1111 assert(image != (Image *) NULL);
1112 assert(image->signature == MagickSignature);
1113 if (image->debug != MagickFalse)
1114 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
cristy4c08aed2011-07-01 19:47:50 +00001115 histogram=(PixelInfo *) AcquireQuantumMemory(MaxMap+1UL,
cristy3ed852e2009-09-05 21:47:34 +00001116 sizeof(*histogram));
cristy4c08aed2011-07-01 19:47:50 +00001117 stretch_map=(PixelInfo *) AcquireQuantumMemory(MaxMap+1UL,
cristy3ed852e2009-09-05 21:47:34 +00001118 sizeof(*stretch_map));
cristy4c08aed2011-07-01 19:47:50 +00001119 if ((histogram == (PixelInfo *) NULL) ||
1120 (stretch_map == (PixelInfo *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00001121 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
1122 image->filename);
1123 /*
1124 Form histogram.
1125 */
1126 status=MagickTrue;
1127 exception=(&image->exception);
1128 (void) ResetMagickMemory(histogram,0,(MaxMap+1)*sizeof(*histogram));
1129 image_view=AcquireCacheView(image);
cristybb503372010-05-27 20:51:26 +00001130 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001131 {
cristy4c08aed2011-07-01 19:47:50 +00001132 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00001133 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00001134
cristybb503372010-05-27 20:51:26 +00001135 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001136 x;
1137
1138 if (status == MagickFalse)
1139 continue;
1140 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00001141 if (p == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001142 {
1143 status=MagickFalse;
1144 continue;
1145 }
cristy3ed852e2009-09-05 21:47:34 +00001146 if (channel == DefaultChannels)
cristybb503372010-05-27 20:51:26 +00001147 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001148 {
1149 Quantum
1150 intensity;
1151
cristy4c08aed2011-07-01 19:47:50 +00001152 intensity=GetPixelIntensity(image,p);
cristy3ed852e2009-09-05 21:47:34 +00001153 histogram[ScaleQuantumToMap(intensity)].red++;
1154 histogram[ScaleQuantumToMap(intensity)].green++;
1155 histogram[ScaleQuantumToMap(intensity)].blue++;
cristy4c08aed2011-07-01 19:47:50 +00001156 histogram[ScaleQuantumToMap(intensity)].black++;
1157 p+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00001158 }
1159 else
cristybb503372010-05-27 20:51:26 +00001160 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001161 {
cristy2b9582a2011-07-04 17:38:56 +00001162 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001163 histogram[ScaleQuantumToMap(GetPixelRed(image,p))].red++;
cristy2b9582a2011-07-04 17:38:56 +00001164 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001165 histogram[ScaleQuantumToMap(GetPixelGreen(image,p))].green++;
cristy2b9582a2011-07-04 17:38:56 +00001166 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001167 histogram[ScaleQuantumToMap(GetPixelBlue(image,p))].blue++;
cristy2b9582a2011-07-04 17:38:56 +00001168 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00001169 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +00001170 histogram[ScaleQuantumToMap(GetPixelBlack(image,p))].black++;
cristy2b9582a2011-07-04 17:38:56 +00001171 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001172 histogram[ScaleQuantumToMap(GetPixelAlpha(image,p))].alpha++;
1173 p+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00001174 }
1175 }
1176 /*
1177 Find the histogram boundaries by locating the black/white levels.
1178 */
1179 black.red=0.0;
1180 white.red=MaxRange(QuantumRange);
cristy2b9582a2011-07-04 17:38:56 +00001181 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001182 {
1183 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00001184 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +00001185 {
1186 intensity+=histogram[i].red;
1187 if (intensity > black_point)
1188 break;
1189 }
1190 black.red=(MagickRealType) i;
1191 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00001192 for (i=(ssize_t) MaxMap; i != 0; i--)
cristy3ed852e2009-09-05 21:47:34 +00001193 {
1194 intensity+=histogram[i].red;
1195 if (intensity > ((double) image->columns*image->rows-white_point))
1196 break;
1197 }
1198 white.red=(MagickRealType) i;
1199 }
1200 black.green=0.0;
1201 white.green=MaxRange(QuantumRange);
cristy2b9582a2011-07-04 17:38:56 +00001202 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001203 {
1204 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00001205 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +00001206 {
1207 intensity+=histogram[i].green;
1208 if (intensity > black_point)
1209 break;
1210 }
1211 black.green=(MagickRealType) i;
1212 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00001213 for (i=(ssize_t) MaxMap; i != 0; i--)
cristy3ed852e2009-09-05 21:47:34 +00001214 {
1215 intensity+=histogram[i].green;
1216 if (intensity > ((double) image->columns*image->rows-white_point))
1217 break;
1218 }
1219 white.green=(MagickRealType) i;
1220 }
1221 black.blue=0.0;
1222 white.blue=MaxRange(QuantumRange);
cristy2b9582a2011-07-04 17:38:56 +00001223 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001224 {
1225 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00001226 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +00001227 {
1228 intensity+=histogram[i].blue;
1229 if (intensity > black_point)
1230 break;
1231 }
1232 black.blue=(MagickRealType) i;
1233 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00001234 for (i=(ssize_t) MaxMap; i != 0; i--)
cristy3ed852e2009-09-05 21:47:34 +00001235 {
1236 intensity+=histogram[i].blue;
1237 if (intensity > ((double) image->columns*image->rows-white_point))
1238 break;
1239 }
1240 white.blue=(MagickRealType) i;
1241 }
cristy4c08aed2011-07-01 19:47:50 +00001242 black.alpha=0.0;
1243 white.alpha=MaxRange(QuantumRange);
cristy2b9582a2011-07-04 17:38:56 +00001244 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001245 {
1246 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00001247 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +00001248 {
cristy4c08aed2011-07-01 19:47:50 +00001249 intensity+=histogram[i].alpha;
cristy3ed852e2009-09-05 21:47:34 +00001250 if (intensity > black_point)
1251 break;
1252 }
cristy4c08aed2011-07-01 19:47:50 +00001253 black.alpha=(MagickRealType) i;
cristy3ed852e2009-09-05 21:47:34 +00001254 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00001255 for (i=(ssize_t) MaxMap; i != 0; i--)
cristy3ed852e2009-09-05 21:47:34 +00001256 {
cristy4c08aed2011-07-01 19:47:50 +00001257 intensity+=histogram[i].alpha;
cristy3ed852e2009-09-05 21:47:34 +00001258 if (intensity > ((double) image->columns*image->rows-white_point))
1259 break;
1260 }
cristy4c08aed2011-07-01 19:47:50 +00001261 white.alpha=(MagickRealType) i;
cristy3ed852e2009-09-05 21:47:34 +00001262 }
cristy4c08aed2011-07-01 19:47:50 +00001263 black.black=0.0;
1264 white.black=MaxRange(QuantumRange);
cristy2b9582a2011-07-04 17:38:56 +00001265 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) && (image->colorspace == CMYKColorspace))
cristy3ed852e2009-09-05 21:47:34 +00001266 {
1267 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00001268 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +00001269 {
cristy4c08aed2011-07-01 19:47:50 +00001270 intensity+=histogram[i].black;
cristy3ed852e2009-09-05 21:47:34 +00001271 if (intensity > black_point)
1272 break;
1273 }
cristy4c08aed2011-07-01 19:47:50 +00001274 black.black=(MagickRealType) i;
cristy3ed852e2009-09-05 21:47:34 +00001275 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00001276 for (i=(ssize_t) MaxMap; i != 0; i--)
cristy3ed852e2009-09-05 21:47:34 +00001277 {
cristy4c08aed2011-07-01 19:47:50 +00001278 intensity+=histogram[i].black;
cristy3ed852e2009-09-05 21:47:34 +00001279 if (intensity > ((double) image->columns*image->rows-white_point))
1280 break;
1281 }
cristy4c08aed2011-07-01 19:47:50 +00001282 white.black=(MagickRealType) i;
cristy3ed852e2009-09-05 21:47:34 +00001283 }
cristy4c08aed2011-07-01 19:47:50 +00001284 histogram=(PixelInfo *) RelinquishMagickMemory(histogram);
cristy3ed852e2009-09-05 21:47:34 +00001285 /*
1286 Stretch the histogram to create the stretched image mapping.
1287 */
1288 (void) ResetMagickMemory(stretch_map,0,(MaxMap+1)*sizeof(*stretch_map));
cristyb5d5f722009-11-04 03:03:49 +00001289#if defined(MAGICKCORE_OPENMP_SUPPORT)
1290 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001291#endif
cristybb503372010-05-27 20:51:26 +00001292 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +00001293 {
cristy2b9582a2011-07-04 17:38:56 +00001294 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001295 {
cristybb503372010-05-27 20:51:26 +00001296 if (i < (ssize_t) black.red)
cristy3ed852e2009-09-05 21:47:34 +00001297 stretch_map[i].red=0.0;
1298 else
cristybb503372010-05-27 20:51:26 +00001299 if (i > (ssize_t) white.red)
cristy3ed852e2009-09-05 21:47:34 +00001300 stretch_map[i].red=(MagickRealType) QuantumRange;
1301 else
1302 if (black.red != white.red)
1303 stretch_map[i].red=(MagickRealType) ScaleMapToQuantum(
1304 (MagickRealType) (MaxMap*(i-black.red)/(white.red-black.red)));
1305 }
cristy2b9582a2011-07-04 17:38:56 +00001306 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001307 {
cristybb503372010-05-27 20:51:26 +00001308 if (i < (ssize_t) black.green)
cristy3ed852e2009-09-05 21:47:34 +00001309 stretch_map[i].green=0.0;
1310 else
cristybb503372010-05-27 20:51:26 +00001311 if (i > (ssize_t) white.green)
cristy3ed852e2009-09-05 21:47:34 +00001312 stretch_map[i].green=(MagickRealType) QuantumRange;
1313 else
1314 if (black.green != white.green)
1315 stretch_map[i].green=(MagickRealType) ScaleMapToQuantum(
1316 (MagickRealType) (MaxMap*(i-black.green)/(white.green-
1317 black.green)));
1318 }
cristy2b9582a2011-07-04 17:38:56 +00001319 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001320 {
cristybb503372010-05-27 20:51:26 +00001321 if (i < (ssize_t) black.blue)
cristy3ed852e2009-09-05 21:47:34 +00001322 stretch_map[i].blue=0.0;
1323 else
cristybb503372010-05-27 20:51:26 +00001324 if (i > (ssize_t) white.blue)
cristy3ed852e2009-09-05 21:47:34 +00001325 stretch_map[i].blue=(MagickRealType) QuantumRange;
1326 else
1327 if (black.blue != white.blue)
1328 stretch_map[i].blue=(MagickRealType) ScaleMapToQuantum(
1329 (MagickRealType) (MaxMap*(i-black.blue)/(white.blue-
1330 black.blue)));
1331 }
cristy2b9582a2011-07-04 17:38:56 +00001332 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001333 {
cristy4c08aed2011-07-01 19:47:50 +00001334 if (i < (ssize_t) black.alpha)
1335 stretch_map[i].alpha=0.0;
cristy3ed852e2009-09-05 21:47:34 +00001336 else
cristy4c08aed2011-07-01 19:47:50 +00001337 if (i > (ssize_t) white.alpha)
1338 stretch_map[i].alpha=(MagickRealType) QuantumRange;
cristy3ed852e2009-09-05 21:47:34 +00001339 else
cristy4c08aed2011-07-01 19:47:50 +00001340 if (black.alpha != white.alpha)
1341 stretch_map[i].alpha=(MagickRealType) ScaleMapToQuantum(
1342 (MagickRealType) (MaxMap*(i-black.alpha)/(white.alpha-
1343 black.alpha)));
cristy3ed852e2009-09-05 21:47:34 +00001344 }
cristy2b9582a2011-07-04 17:38:56 +00001345 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00001346 (image->colorspace == CMYKColorspace))
1347 {
cristy4c08aed2011-07-01 19:47:50 +00001348 if (i < (ssize_t) black.black)
1349 stretch_map[i].black=0.0;
cristy3ed852e2009-09-05 21:47:34 +00001350 else
cristy4c08aed2011-07-01 19:47:50 +00001351 if (i > (ssize_t) white.black)
1352 stretch_map[i].black=(MagickRealType) QuantumRange;
cristy3ed852e2009-09-05 21:47:34 +00001353 else
cristy4c08aed2011-07-01 19:47:50 +00001354 if (black.black != white.black)
1355 stretch_map[i].black=(MagickRealType) ScaleMapToQuantum(
1356 (MagickRealType) (MaxMap*(i-black.black)/(white.black-
1357 black.black)));
cristy3ed852e2009-09-05 21:47:34 +00001358 }
1359 }
1360 /*
1361 Stretch the image.
1362 */
cristy2b9582a2011-07-04 17:38:56 +00001363 if (((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0) || (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00001364 (image->colorspace == CMYKColorspace)))
1365 image->storage_class=DirectClass;
1366 if (image->storage_class == PseudoClass)
1367 {
1368 /*
1369 Stretch colormap.
1370 */
cristyb5d5f722009-11-04 03:03:49 +00001371#if defined(MAGICKCORE_OPENMP_SUPPORT)
1372 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001373#endif
cristybb503372010-05-27 20:51:26 +00001374 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00001375 {
cristy2b9582a2011-07-04 17:38:56 +00001376 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001377 {
1378 if (black.red != white.red)
cristyce70c172010-01-07 17:15:30 +00001379 image->colormap[i].red=ClampToQuantum(stretch_map[
cristy3ed852e2009-09-05 21:47:34 +00001380 ScaleQuantumToMap(image->colormap[i].red)].red);
1381 }
cristy2b9582a2011-07-04 17:38:56 +00001382 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001383 {
1384 if (black.green != white.green)
cristyce70c172010-01-07 17:15:30 +00001385 image->colormap[i].green=ClampToQuantum(stretch_map[
cristy3ed852e2009-09-05 21:47:34 +00001386 ScaleQuantumToMap(image->colormap[i].green)].green);
1387 }
cristy2b9582a2011-07-04 17:38:56 +00001388 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001389 {
1390 if (black.blue != white.blue)
cristyce70c172010-01-07 17:15:30 +00001391 image->colormap[i].blue=ClampToQuantum(stretch_map[
cristy3ed852e2009-09-05 21:47:34 +00001392 ScaleQuantumToMap(image->colormap[i].blue)].blue);
1393 }
cristy2b9582a2011-07-04 17:38:56 +00001394 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001395 {
cristy4c08aed2011-07-01 19:47:50 +00001396 if (black.alpha != white.alpha)
1397 image->colormap[i].alpha=ClampToQuantum(stretch_map[
1398 ScaleQuantumToMap(image->colormap[i].alpha)].alpha);
cristy3ed852e2009-09-05 21:47:34 +00001399 }
1400 }
1401 }
1402 /*
1403 Stretch image.
1404 */
1405 status=MagickTrue;
1406 progress=0;
cristyb5d5f722009-11-04 03:03:49 +00001407#if defined(MAGICKCORE_OPENMP_SUPPORT)
1408 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001409#endif
cristybb503372010-05-27 20:51:26 +00001410 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001411 {
cristy4c08aed2011-07-01 19:47:50 +00001412 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00001413 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001414
cristy8d4629b2010-08-30 17:59:46 +00001415 register ssize_t
1416 x;
1417
cristy3ed852e2009-09-05 21:47:34 +00001418 if (status == MagickFalse)
1419 continue;
1420 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00001421 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001422 {
1423 status=MagickFalse;
1424 continue;
1425 }
cristybb503372010-05-27 20:51:26 +00001426 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001427 {
cristy2b9582a2011-07-04 17:38:56 +00001428 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001429 {
1430 if (black.red != white.red)
cristy4c08aed2011-07-01 19:47:50 +00001431 SetPixelRed(image,ClampToQuantum(stretch_map[ScaleQuantumToMap(
1432 GetPixelRed(image,q))].red),q);
cristy3ed852e2009-09-05 21:47:34 +00001433 }
cristy2b9582a2011-07-04 17:38:56 +00001434 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001435 {
1436 if (black.green != white.green)
cristy4c08aed2011-07-01 19:47:50 +00001437 SetPixelGreen(image,ClampToQuantum(stretch_map[ScaleQuantumToMap(
1438 GetPixelGreen(image,q))].green),q);
cristy3ed852e2009-09-05 21:47:34 +00001439 }
cristy2b9582a2011-07-04 17:38:56 +00001440 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001441 {
1442 if (black.blue != white.blue)
cristy4c08aed2011-07-01 19:47:50 +00001443 SetPixelBlue(image,ClampToQuantum(stretch_map[ScaleQuantumToMap(
1444 GetPixelBlue(image,q))].blue),q);
1445 }
cristy2b9582a2011-07-04 17:38:56 +00001446 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +00001447 (image->colorspace == CMYKColorspace))
1448 {
1449 if (black.black != white.black)
1450 SetPixelBlack(image,ClampToQuantum(stretch_map[ScaleQuantumToMap(
1451 GetPixelBlack(image,q))].black),q);
cristy3ed852e2009-09-05 21:47:34 +00001452 }
cristy2b9582a2011-07-04 17:38:56 +00001453 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001454 {
cristy4c08aed2011-07-01 19:47:50 +00001455 if (black.alpha != white.alpha)
1456 SetPixelAlpha(image,ClampToQuantum(stretch_map[ScaleQuantumToMap(
1457 GetPixelAlpha(image,q))].alpha),q);
cristy3ed852e2009-09-05 21:47:34 +00001458 }
cristy4c08aed2011-07-01 19:47:50 +00001459 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00001460 }
1461 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
1462 status=MagickFalse;
1463 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1464 {
1465 MagickBooleanType
1466 proceed;
1467
cristyb5d5f722009-11-04 03:03:49 +00001468#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00001469 #pragma omp critical (MagickCore_ContrastStretchImageChannel)
1470#endif
1471 proceed=SetImageProgress(image,ContrastStretchImageTag,progress++,
1472 image->rows);
1473 if (proceed == MagickFalse)
1474 status=MagickFalse;
1475 }
1476 }
1477 image_view=DestroyCacheView(image_view);
cristy4c08aed2011-07-01 19:47:50 +00001478 stretch_map=(PixelInfo *) RelinquishMagickMemory(stretch_map);
cristy3ed852e2009-09-05 21:47:34 +00001479 return(status);
1480}
1481
1482/*
1483%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1484% %
1485% %
1486% %
1487% E n h a n c e I m a g e %
1488% %
1489% %
1490% %
1491%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1492%
1493% EnhanceImage() applies a digital filter that improves the quality of a
1494% noisy image.
1495%
1496% The format of the EnhanceImage method is:
1497%
1498% Image *EnhanceImage(const Image *image,ExceptionInfo *exception)
1499%
1500% A description of each parameter follows:
1501%
1502% o image: the image.
1503%
1504% o exception: return any errors or warnings in this structure.
1505%
1506*/
1507MagickExport Image *EnhanceImage(const Image *image,ExceptionInfo *exception)
1508{
1509#define Enhance(weight) \
cristy4c08aed2011-07-01 19:47:50 +00001510 mean=((MagickRealType) GetPixelRed(image,r)+pixel.red)/2; \
1511 distance=(MagickRealType) GetPixelRed(image,r)-(MagickRealType) pixel.red; \
cristy3ed852e2009-09-05 21:47:34 +00001512 distance_squared=QuantumScale*(2.0*((MagickRealType) QuantumRange+1.0)+ \
1513 mean)*distance*distance; \
cristy4c08aed2011-07-01 19:47:50 +00001514 mean=((MagickRealType) GetPixelGreen(image,r)+pixel.green)/2; \
1515 distance=(MagickRealType) GetPixelGreen(image,r)- \
1516 (MagickRealType) pixel.green; \
cristy3ed852e2009-09-05 21:47:34 +00001517 distance_squared+=4.0*distance*distance; \
cristy4c08aed2011-07-01 19:47:50 +00001518 mean=((MagickRealType) GetPixelBlue(image,r)+pixel.blue)/2; \
1519 distance=(MagickRealType) GetPixelBlue(image,r)- \
1520 (MagickRealType) pixel.blue; \
cristy3ed852e2009-09-05 21:47:34 +00001521 distance_squared+=QuantumScale*(3.0*((MagickRealType) \
1522 QuantumRange+1.0)-1.0-mean)*distance*distance; \
cristy4c08aed2011-07-01 19:47:50 +00001523 mean=((MagickRealType) GetPixelAlpha(image,r)+pixel.alpha)/2; \
1524 distance=(MagickRealType) GetPixelAlpha(image,r)-(MagickRealType) pixel.alpha; \
cristy3ed852e2009-09-05 21:47:34 +00001525 distance_squared+=QuantumScale*(3.0*((MagickRealType) \
1526 QuantumRange+1.0)-1.0-mean)*distance*distance; \
1527 if (distance_squared < ((MagickRealType) QuantumRange*(MagickRealType) \
1528 QuantumRange/25.0f)) \
1529 { \
cristy4c08aed2011-07-01 19:47:50 +00001530 aggregate.red+=(weight)*GetPixelRed(image,r); \
1531 aggregate.green+=(weight)*GetPixelGreen(image,r); \
1532 aggregate.blue+=(weight)*GetPixelBlue(image,r); \
1533 aggregate.alpha+=(weight)*GetPixelAlpha(image,r); \
cristy3ed852e2009-09-05 21:47:34 +00001534 total_weight+=(weight); \
1535 } \
1536 r++;
1537#define EnhanceImageTag "Enhance/Image"
1538
cristyc4c8d132010-01-07 01:58:38 +00001539 CacheView
1540 *enhance_view,
1541 *image_view;
1542
cristy3ed852e2009-09-05 21:47:34 +00001543 Image
1544 *enhance_image;
1545
cristy3ed852e2009-09-05 21:47:34 +00001546 MagickBooleanType
1547 status;
1548
cristybb503372010-05-27 20:51:26 +00001549 MagickOffsetType
1550 progress;
1551
cristy4c08aed2011-07-01 19:47:50 +00001552 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00001553 zero;
1554
cristybb503372010-05-27 20:51:26 +00001555 ssize_t
1556 y;
1557
cristy3ed852e2009-09-05 21:47:34 +00001558 /*
1559 Initialize enhanced image attributes.
1560 */
1561 assert(image != (const Image *) NULL);
1562 assert(image->signature == MagickSignature);
1563 if (image->debug != MagickFalse)
1564 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1565 assert(exception != (ExceptionInfo *) NULL);
1566 assert(exception->signature == MagickSignature);
1567 if ((image->columns < 5) || (image->rows < 5))
1568 return((Image *) NULL);
1569 enhance_image=CloneImage(image,image->columns,image->rows,MagickTrue,
1570 exception);
1571 if (enhance_image == (Image *) NULL)
1572 return((Image *) NULL);
1573 if (SetImageStorageClass(enhance_image,DirectClass) == MagickFalse)
1574 {
1575 InheritException(exception,&enhance_image->exception);
1576 enhance_image=DestroyImage(enhance_image);
1577 return((Image *) NULL);
1578 }
1579 /*
1580 Enhance image.
1581 */
1582 status=MagickTrue;
1583 progress=0;
1584 (void) ResetMagickMemory(&zero,0,sizeof(zero));
1585 image_view=AcquireCacheView(image);
1586 enhance_view=AcquireCacheView(enhance_image);
cristyb5d5f722009-11-04 03:03:49 +00001587#if defined(MAGICKCORE_OPENMP_SUPPORT)
1588 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001589#endif
cristybb503372010-05-27 20:51:26 +00001590 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001591 {
cristy4c08aed2011-07-01 19:47:50 +00001592 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00001593 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00001594
cristy4c08aed2011-07-01 19:47:50 +00001595 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00001596 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001597
cristy8d4629b2010-08-30 17:59:46 +00001598 register ssize_t
1599 x;
1600
cristy3ed852e2009-09-05 21:47:34 +00001601 /*
1602 Read another scan line.
1603 */
1604 if (status == MagickFalse)
1605 continue;
1606 p=GetCacheViewVirtualPixels(image_view,-2,y-2,image->columns+4,5,exception);
1607 q=QueueCacheViewAuthenticPixels(enhance_view,0,y,enhance_image->columns,1,
1608 exception);
cristy4c08aed2011-07-01 19:47:50 +00001609 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00001610 {
1611 status=MagickFalse;
1612 continue;
1613 }
cristybb503372010-05-27 20:51:26 +00001614 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001615 {
cristy4c08aed2011-07-01 19:47:50 +00001616 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00001617 aggregate;
1618
1619 MagickRealType
1620 distance,
1621 distance_squared,
1622 mean,
1623 total_weight;
1624
1625 PixelPacket
1626 pixel;
1627
cristy4c08aed2011-07-01 19:47:50 +00001628 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00001629 *restrict r;
cristy3ed852e2009-09-05 21:47:34 +00001630
1631 /*
1632 Compute weighted average of target pixel color components.
1633 */
1634 aggregate=zero;
1635 total_weight=0.0;
1636 r=p+2*(image->columns+4)+2;
cristy4c08aed2011-07-01 19:47:50 +00001637 GetPixelPacket(image,r,&pixel);
cristy3ed852e2009-09-05 21:47:34 +00001638 r=p;
1639 Enhance(5.0); Enhance(8.0); Enhance(10.0); Enhance(8.0); Enhance(5.0);
1640 r=p+(image->columns+4);
1641 Enhance(8.0); Enhance(20.0); Enhance(40.0); Enhance(20.0); Enhance(8.0);
1642 r=p+2*(image->columns+4);
1643 Enhance(10.0); Enhance(40.0); Enhance(80.0); Enhance(40.0); Enhance(10.0);
1644 r=p+3*(image->columns+4);
1645 Enhance(8.0); Enhance(20.0); Enhance(40.0); Enhance(20.0); Enhance(8.0);
1646 r=p+4*(image->columns+4);
1647 Enhance(5.0); Enhance(8.0); Enhance(10.0); Enhance(8.0); Enhance(5.0);
cristy4c08aed2011-07-01 19:47:50 +00001648 SetPixelRed(enhance_image,(Quantum) ((aggregate.red+
1649 (total_weight/2)-1)/total_weight),q);
1650 SetPixelGreen(enhance_image,(Quantum) ((aggregate.green+
1651 (total_weight/2)-1)/total_weight),q);
1652 SetPixelBlue(enhance_image,(Quantum) ((aggregate.blue+
1653 (total_weight/2)-1)/total_weight),q);
1654 SetPixelAlpha(enhance_image,(Quantum) ((aggregate.alpha+
1655 (total_weight/2)-1)/total_weight),q);
1656 p+=GetPixelChannels(image);
1657 q+=GetPixelChannels(enhance_image);
cristy3ed852e2009-09-05 21:47:34 +00001658 }
1659 if (SyncCacheViewAuthenticPixels(enhance_view,exception) == MagickFalse)
1660 status=MagickFalse;
1661 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1662 {
1663 MagickBooleanType
1664 proceed;
1665
cristyb5d5f722009-11-04 03:03:49 +00001666#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00001667 #pragma omp critical (MagickCore_EnhanceImage)
1668#endif
1669 proceed=SetImageProgress(image,EnhanceImageTag,progress++,image->rows);
1670 if (proceed == MagickFalse)
1671 status=MagickFalse;
1672 }
1673 }
1674 enhance_view=DestroyCacheView(enhance_view);
1675 image_view=DestroyCacheView(image_view);
1676 return(enhance_image);
1677}
1678
1679/*
1680%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1681% %
1682% %
1683% %
1684% E q u a l i z e I m a g e %
1685% %
1686% %
1687% %
1688%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1689%
1690% EqualizeImage() applies a histogram equalization to the image.
1691%
1692% The format of the EqualizeImage method is:
1693%
1694% MagickBooleanType EqualizeImage(Image *image)
1695% MagickBooleanType EqualizeImageChannel(Image *image,
1696% const ChannelType channel)
1697%
1698% A description of each parameter follows:
1699%
1700% o image: the image.
1701%
1702% o channel: the channel.
1703%
1704*/
1705
1706MagickExport MagickBooleanType EqualizeImage(Image *image)
1707{
1708 return(EqualizeImageChannel(image,DefaultChannels));
1709}
1710
1711MagickExport MagickBooleanType EqualizeImageChannel(Image *image,
1712 const ChannelType channel)
1713{
1714#define EqualizeImageTag "Equalize/Image"
1715
cristyc4c8d132010-01-07 01:58:38 +00001716 CacheView
1717 *image_view;
1718
cristy3ed852e2009-09-05 21:47:34 +00001719 ExceptionInfo
1720 *exception;
1721
cristy3ed852e2009-09-05 21:47:34 +00001722 MagickBooleanType
1723 status;
1724
cristybb503372010-05-27 20:51:26 +00001725 MagickOffsetType
1726 progress;
1727
cristy4c08aed2011-07-01 19:47:50 +00001728 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00001729 black,
1730 *equalize_map,
1731 *histogram,
1732 intensity,
1733 *map,
1734 white;
1735
cristybb503372010-05-27 20:51:26 +00001736 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001737 i;
1738
cristybb503372010-05-27 20:51:26 +00001739 ssize_t
1740 y;
1741
cristy3ed852e2009-09-05 21:47:34 +00001742 /*
1743 Allocate and initialize histogram arrays.
1744 */
1745 assert(image != (Image *) NULL);
1746 assert(image->signature == MagickSignature);
1747 if (image->debug != MagickFalse)
1748 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
cristy4c08aed2011-07-01 19:47:50 +00001749 equalize_map=(PixelInfo *) AcquireQuantumMemory(MaxMap+1UL,
cristy3ed852e2009-09-05 21:47:34 +00001750 sizeof(*equalize_map));
cristy4c08aed2011-07-01 19:47:50 +00001751 histogram=(PixelInfo *) AcquireQuantumMemory(MaxMap+1UL,
cristy3ed852e2009-09-05 21:47:34 +00001752 sizeof(*histogram));
cristy4c08aed2011-07-01 19:47:50 +00001753 map=(PixelInfo *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*map));
1754 if ((equalize_map == (PixelInfo *) NULL) ||
1755 (histogram == (PixelInfo *) NULL) ||
1756 (map == (PixelInfo *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00001757 {
cristy4c08aed2011-07-01 19:47:50 +00001758 if (map != (PixelInfo *) NULL)
1759 map=(PixelInfo *) RelinquishMagickMemory(map);
1760 if (histogram != (PixelInfo *) NULL)
1761 histogram=(PixelInfo *) RelinquishMagickMemory(histogram);
1762 if (equalize_map != (PixelInfo *) NULL)
1763 equalize_map=(PixelInfo *) RelinquishMagickMemory(equalize_map);
cristy3ed852e2009-09-05 21:47:34 +00001764 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
1765 image->filename);
1766 }
1767 /*
1768 Form histogram.
1769 */
1770 (void) ResetMagickMemory(histogram,0,(MaxMap+1)*sizeof(*histogram));
1771 exception=(&image->exception);
cristybb503372010-05-27 20:51:26 +00001772 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001773 {
cristy4c08aed2011-07-01 19:47:50 +00001774 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00001775 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00001776
cristybb503372010-05-27 20:51:26 +00001777 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001778 x;
1779
1780 p=GetVirtualPixels(image,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00001781 if (p == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001782 break;
cristybb503372010-05-27 20:51:26 +00001783 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001784 {
cristy2b9582a2011-07-04 17:38:56 +00001785 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001786 histogram[ScaleQuantumToMap(GetPixelRed(image,p))].red++;
cristy2b9582a2011-07-04 17:38:56 +00001787 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001788 histogram[ScaleQuantumToMap(GetPixelGreen(image,p))].green++;
cristy2b9582a2011-07-04 17:38:56 +00001789 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001790 histogram[ScaleQuantumToMap(GetPixelBlue(image,p))].blue++;
cristy2b9582a2011-07-04 17:38:56 +00001791 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00001792 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +00001793 histogram[ScaleQuantumToMap(GetPixelBlack(image,p))].black++;
cristy2b9582a2011-07-04 17:38:56 +00001794 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001795 histogram[ScaleQuantumToMap(GetPixelAlpha(image,p))].alpha++;
1796 p+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00001797 }
1798 }
1799 /*
1800 Integrate the histogram to get the equalization map.
1801 */
1802 (void) ResetMagickMemory(&intensity,0,sizeof(intensity));
cristybb503372010-05-27 20:51:26 +00001803 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +00001804 {
cristy2b9582a2011-07-04 17:38:56 +00001805 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001806 intensity.red+=histogram[i].red;
cristy2b9582a2011-07-04 17:38:56 +00001807 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001808 intensity.green+=histogram[i].green;
cristy2b9582a2011-07-04 17:38:56 +00001809 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001810 intensity.blue+=histogram[i].blue;
cristy2b9582a2011-07-04 17:38:56 +00001811 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00001812 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +00001813 intensity.black+=histogram[i].black;
cristy2b9582a2011-07-04 17:38:56 +00001814 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001815 intensity.alpha+=histogram[i].alpha;
cristy3ed852e2009-09-05 21:47:34 +00001816 map[i]=intensity;
1817 }
1818 black=map[0];
1819 white=map[(int) MaxMap];
1820 (void) ResetMagickMemory(equalize_map,0,(MaxMap+1)*sizeof(*equalize_map));
cristyb5d5f722009-11-04 03:03:49 +00001821#if defined(MAGICKCORE_OPENMP_SUPPORT)
1822 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001823#endif
cristybb503372010-05-27 20:51:26 +00001824 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +00001825 {
cristy2b9582a2011-07-04 17:38:56 +00001826 if (((GetPixelRedTraits(image) & ActivePixelTrait) != 0) &&
1827 (white.red != black.red))
cristy3ed852e2009-09-05 21:47:34 +00001828 equalize_map[i].red=(MagickRealType) ScaleMapToQuantum((MagickRealType)
1829 ((MaxMap*(map[i].red-black.red))/(white.red-black.red)));
cristy2b9582a2011-07-04 17:38:56 +00001830 if (((GetPixelGreenTraits(image) & ActivePixelTrait) != 0) &&
1831 (white.green != black.green))
cristy3ed852e2009-09-05 21:47:34 +00001832 equalize_map[i].green=(MagickRealType) ScaleMapToQuantum((MagickRealType)
1833 ((MaxMap*(map[i].green-black.green))/(white.green-black.green)));
cristy2b9582a2011-07-04 17:38:56 +00001834 if (((GetPixelBlueTraits(image) & ActivePixelTrait) != 0) &&
1835 (white.blue != black.blue))
cristy3ed852e2009-09-05 21:47:34 +00001836 equalize_map[i].blue=(MagickRealType) ScaleMapToQuantum((MagickRealType)
1837 ((MaxMap*(map[i].blue-black.blue))/(white.blue-black.blue)));
cristy2b9582a2011-07-04 17:38:56 +00001838 if ((((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00001839 (image->colorspace == CMYKColorspace)) &&
cristy4c08aed2011-07-01 19:47:50 +00001840 (white.black != black.black))
1841 equalize_map[i].black=(MagickRealType) ScaleMapToQuantum((MagickRealType)
1842 ((MaxMap*(map[i].black-black.black))/(white.black-black.black)));
cristy2b9582a2011-07-04 17:38:56 +00001843 if (((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0) &&
1844 (white.alpha != black.alpha))
cristy4c08aed2011-07-01 19:47:50 +00001845 equalize_map[i].alpha=(MagickRealType) ScaleMapToQuantum(
1846 (MagickRealType) ((MaxMap*(map[i].alpha-black.alpha))/
1847 (white.alpha-black.alpha)));
cristy3ed852e2009-09-05 21:47:34 +00001848 }
cristy4c08aed2011-07-01 19:47:50 +00001849 histogram=(PixelInfo *) RelinquishMagickMemory(histogram);
1850 map=(PixelInfo *) RelinquishMagickMemory(map);
cristy3ed852e2009-09-05 21:47:34 +00001851 if (image->storage_class == PseudoClass)
1852 {
1853 /*
1854 Equalize colormap.
1855 */
cristyb5d5f722009-11-04 03:03:49 +00001856#if defined(MAGICKCORE_OPENMP_SUPPORT)
1857 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001858#endif
cristybb503372010-05-27 20:51:26 +00001859 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00001860 {
cristy2b9582a2011-07-04 17:38:56 +00001861 if (((GetPixelRedTraits(image) & ActivePixelTrait) != 0) &&
1862 (white.red != black.red))
cristyce70c172010-01-07 17:15:30 +00001863 image->colormap[i].red=ClampToQuantum(equalize_map[
cristy3ed852e2009-09-05 21:47:34 +00001864 ScaleQuantumToMap(image->colormap[i].red)].red);
cristy2b9582a2011-07-04 17:38:56 +00001865 if (((GetPixelGreenTraits(image) & ActivePixelTrait) != 0) &&
1866 (white.green != black.green))
cristyce70c172010-01-07 17:15:30 +00001867 image->colormap[i].green=ClampToQuantum(equalize_map[
cristy3ed852e2009-09-05 21:47:34 +00001868 ScaleQuantumToMap(image->colormap[i].green)].green);
cristy2b9582a2011-07-04 17:38:56 +00001869 if (((GetPixelBlueTraits(image) & ActivePixelTrait) != 0) &&
1870 (white.blue != black.blue))
cristyce70c172010-01-07 17:15:30 +00001871 image->colormap[i].blue=ClampToQuantum(equalize_map[
cristy3ed852e2009-09-05 21:47:34 +00001872 ScaleQuantumToMap(image->colormap[i].blue)].blue);
cristy2b9582a2011-07-04 17:38:56 +00001873 if (((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +00001874 (white.alpha != black.alpha))
1875 image->colormap[i].alpha=ClampToQuantum(equalize_map[
1876 ScaleQuantumToMap(image->colormap[i].alpha)].alpha);
cristy3ed852e2009-09-05 21:47:34 +00001877 }
1878 }
1879 /*
1880 Equalize image.
1881 */
1882 status=MagickTrue;
1883 progress=0;
1884 exception=(&image->exception);
1885 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00001886#if defined(MAGICKCORE_OPENMP_SUPPORT)
1887 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001888#endif
cristybb503372010-05-27 20:51:26 +00001889 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001890 {
cristy4c08aed2011-07-01 19:47:50 +00001891 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00001892 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001893
cristy8d4629b2010-08-30 17:59:46 +00001894 register ssize_t
1895 x;
1896
cristy3ed852e2009-09-05 21:47:34 +00001897 if (status == MagickFalse)
1898 continue;
1899 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00001900 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001901 {
1902 status=MagickFalse;
1903 continue;
1904 }
cristybb503372010-05-27 20:51:26 +00001905 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001906 {
cristy2b9582a2011-07-04 17:38:56 +00001907 if (((GetPixelRedTraits(image) & ActivePixelTrait) != 0) &&
1908 (white.red != black.red))
cristy4c08aed2011-07-01 19:47:50 +00001909 SetPixelRed(image,ClampToQuantum(equalize_map[
1910 ScaleQuantumToMap(GetPixelRed(image,q))].red),q);
cristy2b9582a2011-07-04 17:38:56 +00001911 if (((GetPixelGreenTraits(image) & ActivePixelTrait) != 0) &&
1912 (white.green != black.green))
cristy4c08aed2011-07-01 19:47:50 +00001913 SetPixelGreen(image,ClampToQuantum(equalize_map[
1914 ScaleQuantumToMap(GetPixelGreen(image,q))].green),q);
cristy2b9582a2011-07-04 17:38:56 +00001915 if (((GetPixelBlueTraits(image) & ActivePixelTrait) != 0) &&
1916 (white.blue != black.blue))
cristy4c08aed2011-07-01 19:47:50 +00001917 SetPixelBlue(image,ClampToQuantum(equalize_map[
1918 ScaleQuantumToMap(GetPixelBlue(image,q))].blue),q);
cristy2b9582a2011-07-04 17:38:56 +00001919 if ((((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00001920 (image->colorspace == CMYKColorspace)) &&
cristy4c08aed2011-07-01 19:47:50 +00001921 (white.black != black.black))
1922 SetPixelBlack(image,ClampToQuantum(equalize_map[
1923 ScaleQuantumToMap(GetPixelBlack(image,q))].black),q);
cristy2b9582a2011-07-04 17:38:56 +00001924 if (((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0) &&
1925 (white.alpha != black.alpha))
cristy4c08aed2011-07-01 19:47:50 +00001926 SetPixelAlpha(image,ClampToQuantum(equalize_map[
1927 ScaleQuantumToMap(GetPixelAlpha(image,q))].alpha),q);
1928 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00001929 }
1930 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
1931 status=MagickFalse;
1932 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1933 {
1934 MagickBooleanType
1935 proceed;
1936
cristyb5d5f722009-11-04 03:03:49 +00001937#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00001938 #pragma omp critical (MagickCore_EqualizeImageChannel)
1939#endif
1940 proceed=SetImageProgress(image,EqualizeImageTag,progress++,image->rows);
1941 if (proceed == MagickFalse)
1942 status=MagickFalse;
1943 }
1944 }
1945 image_view=DestroyCacheView(image_view);
cristy4c08aed2011-07-01 19:47:50 +00001946 equalize_map=(PixelInfo *) RelinquishMagickMemory(equalize_map);
cristy3ed852e2009-09-05 21:47:34 +00001947 return(status);
1948}
1949
1950/*
1951%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1952% %
1953% %
1954% %
1955% G a m m a I m a g e %
1956% %
1957% %
1958% %
1959%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1960%
1961% GammaImage() gamma-corrects a particular image channel. The same
1962% image viewed on different devices will have perceptual differences in the
1963% way the image's intensities are represented on the screen. Specify
1964% individual gamma levels for the red, green, and blue channels, or adjust
1965% all three with the gamma parameter. Values typically range from 0.8 to 2.3.
1966%
1967% You can also reduce the influence of a particular channel with a gamma
1968% value of 0.
1969%
1970% The format of the GammaImage method is:
1971%
cristya6360142011-03-23 23:08:04 +00001972% MagickBooleanType GammaImage(Image *image,const char *level)
cristy3ed852e2009-09-05 21:47:34 +00001973% MagickBooleanType GammaImageChannel(Image *image,
1974% const ChannelType channel,const double gamma)
1975%
1976% A description of each parameter follows:
1977%
1978% o image: the image.
1979%
1980% o channel: the channel.
1981%
cristya6360142011-03-23 23:08:04 +00001982% o level: the image gamma as a string (e.g. 1.6,1.2,1.0).
1983%
cristy3ed852e2009-09-05 21:47:34 +00001984% o gamma: the image gamma.
1985%
1986*/
1987MagickExport MagickBooleanType GammaImage(Image *image,const char *level)
1988{
1989 GeometryInfo
1990 geometry_info;
1991
cristy4c08aed2011-07-01 19:47:50 +00001992 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00001993 gamma;
1994
1995 MagickStatusType
1996 flags,
1997 status;
1998
1999 assert(image != (Image *) NULL);
2000 assert(image->signature == MagickSignature);
2001 if (image->debug != MagickFalse)
2002 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2003 if (level == (char *) NULL)
2004 return(MagickFalse);
2005 flags=ParseGeometry(level,&geometry_info);
2006 gamma.red=geometry_info.rho;
2007 gamma.green=geometry_info.sigma;
2008 if ((flags & SigmaValue) == 0)
2009 gamma.green=gamma.red;
2010 gamma.blue=geometry_info.xi;
2011 if ((flags & XiValue) == 0)
2012 gamma.blue=gamma.red;
2013 if ((gamma.red == 1.0) && (gamma.green == 1.0) && (gamma.blue == 1.0))
2014 return(MagickTrue);
2015 if ((gamma.red == gamma.green) && (gamma.green == gamma.blue))
2016 status=GammaImageChannel(image,(const ChannelType) (RedChannel |
2017 GreenChannel | BlueChannel),(double) gamma.red);
2018 else
2019 {
2020 status=GammaImageChannel(image,RedChannel,(double) gamma.red);
2021 status|=GammaImageChannel(image,GreenChannel,(double) gamma.green);
2022 status|=GammaImageChannel(image,BlueChannel,(double) gamma.blue);
2023 }
2024 return(status != 0 ? MagickTrue : MagickFalse);
2025}
2026
2027MagickExport MagickBooleanType GammaImageChannel(Image *image,
2028 const ChannelType channel,const double gamma)
2029{
2030#define GammaCorrectImageTag "GammaCorrect/Image"
2031
cristyc4c8d132010-01-07 01:58:38 +00002032 CacheView
2033 *image_view;
2034
cristy3ed852e2009-09-05 21:47:34 +00002035 ExceptionInfo
2036 *exception;
2037
cristy3ed852e2009-09-05 21:47:34 +00002038 MagickBooleanType
2039 status;
2040
cristybb503372010-05-27 20:51:26 +00002041 MagickOffsetType
2042 progress;
2043
cristy3ed852e2009-09-05 21:47:34 +00002044 Quantum
2045 *gamma_map;
2046
cristybb503372010-05-27 20:51:26 +00002047 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002048 i;
2049
cristybb503372010-05-27 20:51:26 +00002050 ssize_t
2051 y;
2052
cristy3ed852e2009-09-05 21:47:34 +00002053 /*
2054 Allocate and initialize gamma maps.
2055 */
2056 assert(image != (Image *) NULL);
2057 assert(image->signature == MagickSignature);
2058 if (image->debug != MagickFalse)
2059 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2060 if (gamma == 1.0)
2061 return(MagickTrue);
2062 gamma_map=(Quantum *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*gamma_map));
2063 if (gamma_map == (Quantum *) NULL)
2064 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
2065 image->filename);
2066 (void) ResetMagickMemory(gamma_map,0,(MaxMap+1)*sizeof(*gamma_map));
2067 if (gamma != 0.0)
cristyb5d5f722009-11-04 03:03:49 +00002068#if defined(MAGICKCORE_OPENMP_SUPPORT)
2069 #pragma omp parallel for schedule(dynamic,4)
cristy3ed852e2009-09-05 21:47:34 +00002070#endif
cristybb503372010-05-27 20:51:26 +00002071 for (i=0; i <= (ssize_t) MaxMap; i++)
cristyce70c172010-01-07 17:15:30 +00002072 gamma_map[i]=ClampToQuantum((MagickRealType) ScaleMapToQuantum((
cristy3ed852e2009-09-05 21:47:34 +00002073 MagickRealType) (MaxMap*pow((double) i/MaxMap,1.0/gamma))));
2074 if (image->storage_class == PseudoClass)
2075 {
2076 /*
2077 Gamma-correct colormap.
2078 */
cristyb5d5f722009-11-04 03:03:49 +00002079#if defined(MAGICKCORE_OPENMP_SUPPORT)
2080 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002081#endif
cristybb503372010-05-27 20:51:26 +00002082 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00002083 {
cristy2b9582a2011-07-04 17:38:56 +00002084 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00002085 image->colormap[i].red=gamma_map[
2086 ScaleQuantumToMap(image->colormap[i].red)];
cristy2b9582a2011-07-04 17:38:56 +00002087 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00002088 image->colormap[i].green=gamma_map[
2089 ScaleQuantumToMap(image->colormap[i].green)];
cristy2b9582a2011-07-04 17:38:56 +00002090 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00002091 image->colormap[i].blue=gamma_map[
2092 ScaleQuantumToMap(image->colormap[i].blue)];
cristy2b9582a2011-07-04 17:38:56 +00002093 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002094 image->colormap[i].alpha=gamma_map[
2095 ScaleQuantumToMap(image->colormap[i].alpha)];
cristy3ed852e2009-09-05 21:47:34 +00002096 }
2097 }
2098 /*
2099 Gamma-correct image.
2100 */
2101 status=MagickTrue;
2102 progress=0;
2103 exception=(&image->exception);
2104 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00002105#if defined(MAGICKCORE_OPENMP_SUPPORT)
2106 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002107#endif
cristybb503372010-05-27 20:51:26 +00002108 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002109 {
cristy4c08aed2011-07-01 19:47:50 +00002110 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00002111 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002112
cristy8d4629b2010-08-30 17:59:46 +00002113 register ssize_t
2114 x;
2115
cristy3ed852e2009-09-05 21:47:34 +00002116 if (status == MagickFalse)
2117 continue;
2118 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00002119 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00002120 {
2121 status=MagickFalse;
2122 continue;
2123 }
cristybb503372010-05-27 20:51:26 +00002124 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002125 {
cristy6cbd7f52009-10-17 16:06:51 +00002126 if (channel == DefaultChannels)
cristy3ed852e2009-09-05 21:47:34 +00002127 {
cristy4c08aed2011-07-01 19:47:50 +00002128 SetPixelRed(image,gamma_map[
2129 ScaleQuantumToMap(GetPixelRed(image,q))],q);
2130 SetPixelGreen(image,gamma_map[
2131 ScaleQuantumToMap(GetPixelGreen(image,q))],q);
2132 SetPixelBlue(image,gamma_map[ScaleQuantumToMap(
2133 GetPixelBlue(image,q))],q);
cristy6cbd7f52009-10-17 16:06:51 +00002134 }
2135 else
2136 {
cristy2b9582a2011-07-04 17:38:56 +00002137 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002138 SetPixelRed(image,gamma_map[ScaleQuantumToMap(
2139 GetPixelRed(image,q))],q);
cristy2b9582a2011-07-04 17:38:56 +00002140 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002141 SetPixelGreen(image,gamma_map[
2142 ScaleQuantumToMap(GetPixelGreen(image,q))],q);
cristy2b9582a2011-07-04 17:38:56 +00002143 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002144 SetPixelBlue(image,gamma_map[
2145 ScaleQuantumToMap(GetPixelBlue(image,q))],q);
cristy2b9582a2011-07-04 17:38:56 +00002146 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy6cbd7f52009-10-17 16:06:51 +00002147 {
2148 if (image->matte == MagickFalse)
cristy4c08aed2011-07-01 19:47:50 +00002149 SetPixelAlpha(image,gamma_map[
2150 ScaleQuantumToMap(GetPixelAlpha(image,q))],q);
cristy6cbd7f52009-10-17 16:06:51 +00002151 else
cristy4c08aed2011-07-01 19:47:50 +00002152 SetPixelAlpha(image,gamma_map[
2153 ScaleQuantumToMap(GetPixelAlpha(image,q))],q);
cristy6cbd7f52009-10-17 16:06:51 +00002154 }
cristy3ed852e2009-09-05 21:47:34 +00002155 }
cristy4c08aed2011-07-01 19:47:50 +00002156 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00002157 }
cristy2b9582a2011-07-04 17:38:56 +00002158 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00002159 (image->colorspace == CMYKColorspace))
cristybb503372010-05-27 20:51:26 +00002160 for (x=0; x < (ssize_t) image->columns; x++)
cristy4c08aed2011-07-01 19:47:50 +00002161 SetPixelBlack(image,gamma_map[ScaleQuantumToMap(
2162 GetPixelBlack(image,q))],q);
cristy3ed852e2009-09-05 21:47:34 +00002163 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2164 status=MagickFalse;
2165 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2166 {
2167 MagickBooleanType
2168 proceed;
2169
cristyb5d5f722009-11-04 03:03:49 +00002170#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00002171 #pragma omp critical (MagickCore_GammaImageChannel)
2172#endif
2173 proceed=SetImageProgress(image,GammaCorrectImageTag,progress++,
2174 image->rows);
2175 if (proceed == MagickFalse)
2176 status=MagickFalse;
2177 }
2178 }
2179 image_view=DestroyCacheView(image_view);
2180 gamma_map=(Quantum *) RelinquishMagickMemory(gamma_map);
2181 if (image->gamma != 0.0)
2182 image->gamma*=gamma;
2183 return(status);
2184}
2185
2186/*
2187%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2188% %
2189% %
2190% %
2191% H a l d C l u t I m a g e %
2192% %
2193% %
2194% %
2195%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2196%
2197% HaldClutImage() applies a Hald color lookup table to the image. A Hald
2198% color lookup table is a 3-dimensional color cube mapped to 2 dimensions.
2199% Create it with the HALD coder. You can apply any color transformation to
2200% the Hald image and then use this method to apply the transform to the
2201% image.
2202%
2203% The format of the HaldClutImage method is:
2204%
2205% MagickBooleanType HaldClutImage(Image *image,Image *hald_image)
2206% MagickBooleanType HaldClutImageChannel(Image *image,
2207% const ChannelType channel,Image *hald_image)
2208%
2209% A description of each parameter follows:
2210%
2211% o image: the image, which is replaced by indexed CLUT values
2212%
2213% o hald_image: the color lookup table image for replacement color values.
2214%
2215% o channel: the channel.
2216%
2217*/
2218
2219static inline size_t MagickMin(const size_t x,const size_t y)
2220{
2221 if (x < y)
2222 return(x);
2223 return(y);
2224}
2225
2226MagickExport MagickBooleanType HaldClutImage(Image *image,
2227 const Image *hald_image)
2228{
2229 return(HaldClutImageChannel(image,DefaultChannels,hald_image));
2230}
2231
2232MagickExport MagickBooleanType HaldClutImageChannel(Image *image,
2233 const ChannelType channel,const Image *hald_image)
2234{
2235#define HaldClutImageTag "Clut/Image"
2236
2237 typedef struct _HaldInfo
2238 {
2239 MagickRealType
2240 x,
2241 y,
2242 z;
2243 } HaldInfo;
2244
cristyfa112112010-01-04 17:48:07 +00002245 CacheView
cristyd551fbc2011-03-31 18:07:46 +00002246 *hald_view,
cristyfa112112010-01-04 17:48:07 +00002247 *image_view;
2248
cristy3ed852e2009-09-05 21:47:34 +00002249 double
2250 width;
2251
2252 ExceptionInfo
2253 *exception;
2254
cristy3ed852e2009-09-05 21:47:34 +00002255 MagickBooleanType
2256 status;
2257
cristybb503372010-05-27 20:51:26 +00002258 MagickOffsetType
2259 progress;
2260
cristy4c08aed2011-07-01 19:47:50 +00002261 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00002262 zero;
2263
cristy3ed852e2009-09-05 21:47:34 +00002264 size_t
2265 cube_size,
2266 length,
2267 level;
2268
cristybb503372010-05-27 20:51:26 +00002269 ssize_t
2270 y;
2271
cristy3ed852e2009-09-05 21:47:34 +00002272 assert(image != (Image *) NULL);
2273 assert(image->signature == MagickSignature);
2274 if (image->debug != MagickFalse)
2275 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2276 assert(hald_image != (Image *) NULL);
2277 assert(hald_image->signature == MagickSignature);
2278 if (SetImageStorageClass(image,DirectClass) == MagickFalse)
2279 return(MagickFalse);
2280 if (image->matte == MagickFalse)
2281 (void) SetImageAlphaChannel(image,OpaqueAlphaChannel);
2282 /*
2283 Hald clut image.
2284 */
2285 status=MagickTrue;
2286 progress=0;
2287 length=MagickMin(hald_image->columns,hald_image->rows);
2288 for (level=2; (level*level*level) < length; level++) ;
2289 level*=level;
2290 cube_size=level*level;
2291 width=(double) hald_image->columns;
cristy4c08aed2011-07-01 19:47:50 +00002292 GetPixelInfo(hald_image,&zero);
cristy3ed852e2009-09-05 21:47:34 +00002293 exception=(&image->exception);
cristy3ed852e2009-09-05 21:47:34 +00002294 image_view=AcquireCacheView(image);
cristyd551fbc2011-03-31 18:07:46 +00002295 hald_view=AcquireCacheView(hald_image);
cristyb5d5f722009-11-04 03:03:49 +00002296#if defined(MAGICKCORE_OPENMP_SUPPORT)
2297 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002298#endif
cristybb503372010-05-27 20:51:26 +00002299 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002300 {
2301 double
2302 offset;
2303
2304 HaldInfo
2305 point;
2306
cristy4c08aed2011-07-01 19:47:50 +00002307 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00002308 pixel,
2309 pixel1,
2310 pixel2,
2311 pixel3,
2312 pixel4;
2313
cristy4c08aed2011-07-01 19:47:50 +00002314 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00002315 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002316
cristy8d4629b2010-08-30 17:59:46 +00002317 register ssize_t
2318 x;
2319
cristy3ed852e2009-09-05 21:47:34 +00002320 if (status == MagickFalse)
2321 continue;
2322 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00002323 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00002324 {
2325 status=MagickFalse;
2326 continue;
2327 }
cristy3ed852e2009-09-05 21:47:34 +00002328 pixel=zero;
2329 pixel1=zero;
2330 pixel2=zero;
2331 pixel3=zero;
2332 pixel4=zero;
cristybb503372010-05-27 20:51:26 +00002333 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002334 {
cristy4c08aed2011-07-01 19:47:50 +00002335 point.x=QuantumScale*(level-1.0)*GetPixelRed(image,q);
2336 point.y=QuantumScale*(level-1.0)*GetPixelGreen(image,q);
2337 point.z=QuantumScale*(level-1.0)*GetPixelBlue(image,q);
cristy3ed852e2009-09-05 21:47:34 +00002338 offset=point.x+level*floor(point.y)+cube_size*floor(point.z);
2339 point.x-=floor(point.x);
2340 point.y-=floor(point.y);
2341 point.z-=floor(point.z);
cristy4c08aed2011-07-01 19:47:50 +00002342 (void) InterpolatePixelInfo(image,hald_view,
cristy8a7c3e82011-03-26 02:10:53 +00002343 UndefinedInterpolatePixel,fmod(offset,width),floor(offset/width),
2344 &pixel1,exception);
cristy4c08aed2011-07-01 19:47:50 +00002345 (void) InterpolatePixelInfo(image,hald_view,
cristy8a7c3e82011-03-26 02:10:53 +00002346 UndefinedInterpolatePixel,fmod(offset+level,width),floor((offset+level)/
2347 width),&pixel2,exception);
cristy4c08aed2011-07-01 19:47:50 +00002348 CompositePixelInfoAreaBlend(&pixel1,pixel1.alpha,&pixel2,
2349 pixel2.alpha,point.y,&pixel3);
cristy3ed852e2009-09-05 21:47:34 +00002350 offset+=cube_size;
cristy4c08aed2011-07-01 19:47:50 +00002351 (void) InterpolatePixelInfo(image,hald_view,
cristy8a7c3e82011-03-26 02:10:53 +00002352 UndefinedInterpolatePixel,fmod(offset,width),floor(offset/width),
2353 &pixel1,exception);
cristy4c08aed2011-07-01 19:47:50 +00002354 (void) InterpolatePixelInfo(image,hald_view,
cristy8a7c3e82011-03-26 02:10:53 +00002355 UndefinedInterpolatePixel,fmod(offset+level,width),floor((offset+level)/
2356 width),&pixel2,exception);
cristy4c08aed2011-07-01 19:47:50 +00002357 CompositePixelInfoAreaBlend(&pixel1,pixel1.alpha,&pixel2,
2358 pixel2.alpha,point.y,&pixel4);
2359 CompositePixelInfoAreaBlend(&pixel3,pixel3.alpha,&pixel4,
2360 pixel4.alpha,point.z,&pixel);
cristy2b9582a2011-07-04 17:38:56 +00002361 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002362 SetPixelRed(image,
2363 ClampToQuantum(pixel.red),q);
cristy2b9582a2011-07-04 17:38:56 +00002364 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002365 SetPixelGreen(image,
2366 ClampToQuantum(pixel.green),q);
cristy2b9582a2011-07-04 17:38:56 +00002367 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002368 SetPixelBlue(image,
2369 ClampToQuantum(pixel.blue),q);
cristy2b9582a2011-07-04 17:38:56 +00002370 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00002371 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +00002372 SetPixelBlack(image,
2373 ClampToQuantum(pixel.black),q);
cristy2b9582a2011-07-04 17:38:56 +00002374 if (((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0) && (image->matte != MagickFalse))
cristy4c08aed2011-07-01 19:47:50 +00002375 SetPixelAlpha(image,
2376 ClampToQuantum(pixel.alpha),q);
2377 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00002378 }
2379 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2380 status=MagickFalse;
2381 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2382 {
2383 MagickBooleanType
2384 proceed;
2385
cristyb5d5f722009-11-04 03:03:49 +00002386#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00002387 #pragma omp critical (MagickCore_HaldClutImageChannel)
2388#endif
2389 proceed=SetImageProgress(image,HaldClutImageTag,progress++,image->rows);
2390 if (proceed == MagickFalse)
2391 status=MagickFalse;
2392 }
2393 }
cristyd551fbc2011-03-31 18:07:46 +00002394 hald_view=DestroyCacheView(hald_view);
cristy3ed852e2009-09-05 21:47:34 +00002395 image_view=DestroyCacheView(image_view);
cristy3ed852e2009-09-05 21:47:34 +00002396 return(status);
2397}
2398
2399/*
2400%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2401% %
2402% %
2403% %
2404% L e v e l I m a g e %
2405% %
2406% %
2407% %
2408%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2409%
2410% LevelImage() adjusts the levels of a particular image channel by
2411% scaling the colors falling between specified white and black points to
2412% the full available quantum range.
2413%
2414% The parameters provided represent the black, and white points. The black
2415% point specifies the darkest color in the image. Colors darker than the
2416% black point are set to zero. White point specifies the lightest color in
2417% the image. Colors brighter than the white point are set to the maximum
2418% quantum value.
2419%
2420% If a '!' flag is given, map black and white colors to the given levels
2421% rather than mapping those levels to black and white. See
2422% LevelizeImageChannel() and LevelizeImageChannel(), below.
2423%
2424% Gamma specifies a gamma correction to apply to the image.
2425%
2426% The format of the LevelImage method is:
2427%
2428% MagickBooleanType LevelImage(Image *image,const char *levels)
2429%
2430% A description of each parameter follows:
2431%
2432% o image: the image.
2433%
2434% o levels: Specify the levels where the black and white points have the
2435% range of 0-QuantumRange, and gamma has the range 0-10 (e.g. 10x90%+2).
2436% A '!' flag inverts the re-mapping.
2437%
2438*/
2439
2440MagickExport MagickBooleanType LevelImage(Image *image,const char *levels)
2441{
2442 double
2443 black_point,
2444 gamma,
2445 white_point;
2446
2447 GeometryInfo
2448 geometry_info;
2449
2450 MagickBooleanType
2451 status;
2452
2453 MagickStatusType
2454 flags;
2455
2456 /*
2457 Parse levels.
2458 */
2459 if (levels == (char *) NULL)
2460 return(MagickFalse);
2461 flags=ParseGeometry(levels,&geometry_info);
2462 black_point=geometry_info.rho;
2463 white_point=(double) QuantumRange;
2464 if ((flags & SigmaValue) != 0)
2465 white_point=geometry_info.sigma;
2466 gamma=1.0;
2467 if ((flags & XiValue) != 0)
2468 gamma=geometry_info.xi;
2469 if ((flags & PercentValue) != 0)
2470 {
2471 black_point*=(double) image->columns*image->rows/100.0;
2472 white_point*=(double) image->columns*image->rows/100.0;
2473 }
2474 if ((flags & SigmaValue) == 0)
2475 white_point=(double) QuantumRange-black_point;
2476 if ((flags & AspectValue ) == 0)
2477 status=LevelImageChannel(image,DefaultChannels,black_point,white_point,
2478 gamma);
2479 else
cristy308b4e62009-09-21 14:40:44 +00002480 status=LevelizeImage(image,black_point,white_point,gamma);
cristy3ed852e2009-09-05 21:47:34 +00002481 return(status);
2482}
2483
2484/*
2485%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2486% %
2487% %
2488% %
cristy308b4e62009-09-21 14:40:44 +00002489% L e v e l i z e I m a g e %
cristy3ed852e2009-09-05 21:47:34 +00002490% %
2491% %
2492% %
2493%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2494%
cristy308b4e62009-09-21 14:40:44 +00002495% LevelizeImage() applies the normal level operation to the image, spreading
2496% out the values between the black and white points over the entire range of
2497% values. Gamma correction is also applied after the values has been mapped.
cristy3ed852e2009-09-05 21:47:34 +00002498%
2499% It is typically used to improve image contrast, or to provide a controlled
2500% linear threshold for the image. If the black and white points are set to
2501% the minimum and maximum values found in the image, the image can be
2502% normalized. or by swapping black and white values, negate the image.
2503%
cristy308b4e62009-09-21 14:40:44 +00002504% The format of the LevelizeImage method is:
cristy3ed852e2009-09-05 21:47:34 +00002505%
cristy308b4e62009-09-21 14:40:44 +00002506% MagickBooleanType LevelizeImage(Image *image,const double black_point,
2507% const double white_point,const double gamma)
2508% MagickBooleanType LevelizeImageChannel(Image *image,
2509% const ChannelType channel,const double black_point,
2510% const double white_point,const double gamma)
cristy3ed852e2009-09-05 21:47:34 +00002511%
2512% A description of each parameter follows:
2513%
2514% o image: the image.
2515%
2516% o channel: the channel.
2517%
2518% o black_point: The level which is to be mapped to zero (black)
2519%
2520% o white_point: The level which is to be mapped to QuantiumRange (white)
2521%
2522% o gamma: adjust gamma by this factor before mapping values.
2523% use 1.0 for purely linear stretching of image color values
2524%
2525*/
2526MagickExport MagickBooleanType LevelImageChannel(Image *image,
2527 const ChannelType channel,const double black_point,const double white_point,
2528 const double gamma)
2529{
2530#define LevelImageTag "Level/Image"
cristybcfb0432010-05-06 01:45:33 +00002531#define LevelQuantum(x) (ClampToQuantum((MagickRealType) QuantumRange* \
cristyc1f508d2010-04-22 01:21:47 +00002532 pow(scale*((double) (x)-black_point),1.0/gamma)))
cristy3ed852e2009-09-05 21:47:34 +00002533
cristyc4c8d132010-01-07 01:58:38 +00002534 CacheView
2535 *image_view;
2536
cristy3ed852e2009-09-05 21:47:34 +00002537 ExceptionInfo
2538 *exception;
2539
cristy3ed852e2009-09-05 21:47:34 +00002540 MagickBooleanType
2541 status;
2542
cristybb503372010-05-27 20:51:26 +00002543 MagickOffsetType
2544 progress;
2545
anthony7fe39fc2010-04-06 03:19:20 +00002546 register double
2547 scale;
2548
cristy8d4629b2010-08-30 17:59:46 +00002549 register ssize_t
2550 i;
2551
cristybb503372010-05-27 20:51:26 +00002552 ssize_t
2553 y;
2554
cristy3ed852e2009-09-05 21:47:34 +00002555 /*
2556 Allocate and initialize levels map.
2557 */
2558 assert(image != (Image *) NULL);
2559 assert(image->signature == MagickSignature);
2560 if (image->debug != MagickFalse)
2561 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
cristy8d4629b2010-08-30 17:59:46 +00002562 scale=(white_point != black_point) ? 1.0/(white_point-black_point) : 1.0;
cristy3ed852e2009-09-05 21:47:34 +00002563 if (image->storage_class == PseudoClass)
cristyb5d5f722009-11-04 03:03:49 +00002564#if defined(MAGICKCORE_OPENMP_SUPPORT)
2565 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002566#endif
cristybb503372010-05-27 20:51:26 +00002567 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00002568 {
2569 /*
2570 Level colormap.
2571 */
cristy2b9582a2011-07-04 17:38:56 +00002572 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristyc1f508d2010-04-22 01:21:47 +00002573 image->colormap[i].red=LevelQuantum(image->colormap[i].red);
cristy2b9582a2011-07-04 17:38:56 +00002574 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristyc1f508d2010-04-22 01:21:47 +00002575 image->colormap[i].green=LevelQuantum(image->colormap[i].green);
cristy2b9582a2011-07-04 17:38:56 +00002576 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristyc1f508d2010-04-22 01:21:47 +00002577 image->colormap[i].blue=LevelQuantum(image->colormap[i].blue);
cristy2b9582a2011-07-04 17:38:56 +00002578 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002579 image->colormap[i].alpha=LevelQuantum(image->colormap[i].alpha);
cristy3ed852e2009-09-05 21:47:34 +00002580 }
2581 /*
2582 Level image.
2583 */
2584 status=MagickTrue;
2585 progress=0;
2586 exception=(&image->exception);
2587 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00002588#if defined(MAGICKCORE_OPENMP_SUPPORT)
2589 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002590#endif
cristybb503372010-05-27 20:51:26 +00002591 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002592 {
cristy4c08aed2011-07-01 19:47:50 +00002593 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00002594 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002595
cristy8d4629b2010-08-30 17:59:46 +00002596 register ssize_t
2597 x;
2598
cristy3ed852e2009-09-05 21:47:34 +00002599 if (status == MagickFalse)
2600 continue;
2601 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00002602 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00002603 {
2604 status=MagickFalse;
2605 continue;
2606 }
cristybb503372010-05-27 20:51:26 +00002607 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002608 {
cristy2b9582a2011-07-04 17:38:56 +00002609 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002610 SetPixelRed(image,LevelQuantum(
2611 GetPixelRed(image,q)),q);
cristy2b9582a2011-07-04 17:38:56 +00002612 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002613 SetPixelGreen(image,
2614 LevelQuantum(GetPixelGreen(image,q)),q);
cristy2b9582a2011-07-04 17:38:56 +00002615 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002616 SetPixelBlue(image,
2617 LevelQuantum(GetPixelBlue(image,q)),q);
cristy2b9582a2011-07-04 17:38:56 +00002618 if (((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00002619 (image->matte == MagickTrue))
cristy4c08aed2011-07-01 19:47:50 +00002620 SetPixelAlpha(image,
2621 LevelQuantum(GetPixelAlpha(image,q)),q);
cristy2b9582a2011-07-04 17:38:56 +00002622 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00002623 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +00002624 SetPixelBlack(image,
2625 LevelQuantum(GetPixelBlack(image,q)),q);
2626 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00002627 }
2628 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2629 status=MagickFalse;
2630 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2631 {
2632 MagickBooleanType
2633 proceed;
2634
cristyb5d5f722009-11-04 03:03:49 +00002635#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00002636 #pragma omp critical (MagickCore_LevelImageChannel)
2637#endif
2638 proceed=SetImageProgress(image,LevelImageTag,progress++,image->rows);
2639 if (proceed == MagickFalse)
2640 status=MagickFalse;
2641 }
2642 }
2643 image_view=DestroyCacheView(image_view);
2644 return(status);
2645}
2646
2647/*
2648%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2649% %
2650% %
2651% %
2652% L e v e l i z e I m a g e C h a n n e l %
2653% %
2654% %
2655% %
2656%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2657%
2658% LevelizeImageChannel() applies the reversed LevelImage() operation to just
2659% the specific channels specified. It compresses the full range of color
2660% values, so that they lie between the given black and white points. Gamma is
2661% applied before the values are mapped.
2662%
2663% LevelizeImageChannel() can be called with by using a +level command line
2664% API option, or using a '!' on a -level or LevelImage() geometry string.
2665%
2666% It can be used for example de-contrast a greyscale image to the exact
2667% levels specified. Or by using specific levels for each channel of an image
2668% you can convert a gray-scale image to any linear color gradient, according
2669% to those levels.
2670%
2671% The format of the LevelizeImageChannel method is:
2672%
2673% MagickBooleanType LevelizeImageChannel(Image *image,
2674% const ChannelType channel,const char *levels)
2675%
2676% A description of each parameter follows:
2677%
2678% o image: the image.
2679%
2680% o channel: the channel.
2681%
2682% o black_point: The level to map zero (black) to.
2683%
2684% o white_point: The level to map QuantiumRange (white) to.
2685%
2686% o gamma: adjust gamma by this factor before mapping values.
2687%
2688*/
cristyd1a2c0f2011-02-09 14:14:50 +00002689
2690MagickExport MagickBooleanType LevelizeImage(Image *image,
2691 const double black_point,const double white_point,const double gamma)
2692{
2693 MagickBooleanType
2694 status;
2695
2696 status=LevelizeImageChannel(image,DefaultChannels,black_point,white_point,
2697 gamma);
2698 return(status);
2699}
2700
cristy3ed852e2009-09-05 21:47:34 +00002701MagickExport MagickBooleanType LevelizeImageChannel(Image *image,
2702 const ChannelType channel,const double black_point,const double white_point,
2703 const double gamma)
2704{
2705#define LevelizeImageTag "Levelize/Image"
cristyce70c172010-01-07 17:15:30 +00002706#define LevelizeValue(x) (ClampToQuantum(((MagickRealType) \
cristy3ed852e2009-09-05 21:47:34 +00002707 pow((double)(QuantumScale*(x)),1.0/gamma))*(white_point-black_point)+ \
2708 black_point))
2709
cristyc4c8d132010-01-07 01:58:38 +00002710 CacheView
2711 *image_view;
2712
cristy3ed852e2009-09-05 21:47:34 +00002713 ExceptionInfo
2714 *exception;
2715
cristy3ed852e2009-09-05 21:47:34 +00002716 MagickBooleanType
2717 status;
2718
cristybb503372010-05-27 20:51:26 +00002719 MagickOffsetType
2720 progress;
2721
2722 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002723 i;
2724
cristybb503372010-05-27 20:51:26 +00002725 ssize_t
2726 y;
2727
cristy3ed852e2009-09-05 21:47:34 +00002728 /*
2729 Allocate and initialize levels map.
2730 */
2731 assert(image != (Image *) NULL);
2732 assert(image->signature == MagickSignature);
2733 if (image->debug != MagickFalse)
2734 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2735 if (image->storage_class == PseudoClass)
cristyb5d5f722009-11-04 03:03:49 +00002736#if defined(MAGICKCORE_OPENMP_SUPPORT)
2737 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002738#endif
cristybb503372010-05-27 20:51:26 +00002739 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00002740 {
2741 /*
2742 Level colormap.
2743 */
cristy2b9582a2011-07-04 17:38:56 +00002744 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00002745 image->colormap[i].red=LevelizeValue(image->colormap[i].red);
cristy2b9582a2011-07-04 17:38:56 +00002746 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00002747 image->colormap[i].green=LevelizeValue(image->colormap[i].green);
cristy2b9582a2011-07-04 17:38:56 +00002748 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00002749 image->colormap[i].blue=LevelizeValue(image->colormap[i].blue);
cristy2b9582a2011-07-04 17:38:56 +00002750 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002751 image->colormap[i].alpha=LevelizeValue(image->colormap[i].alpha);
cristy3ed852e2009-09-05 21:47:34 +00002752 }
2753 /*
2754 Level image.
2755 */
2756 status=MagickTrue;
2757 progress=0;
2758 exception=(&image->exception);
2759 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00002760#if defined(MAGICKCORE_OPENMP_SUPPORT)
2761 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002762#endif
cristybb503372010-05-27 20:51:26 +00002763 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002764 {
cristy4c08aed2011-07-01 19:47:50 +00002765 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00002766 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002767
cristy8d4629b2010-08-30 17:59:46 +00002768 register ssize_t
2769 x;
2770
cristy3ed852e2009-09-05 21:47:34 +00002771 if (status == MagickFalse)
2772 continue;
2773 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00002774 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00002775 {
2776 status=MagickFalse;
2777 continue;
2778 }
cristybb503372010-05-27 20:51:26 +00002779 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002780 {
cristy2b9582a2011-07-04 17:38:56 +00002781 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002782 SetPixelRed(image,LevelizeValue(GetPixelRed(image,q)),q);
cristy2b9582a2011-07-04 17:38:56 +00002783 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002784 SetPixelGreen(image,LevelizeValue(GetPixelGreen(image,q)),q);
cristy2b9582a2011-07-04 17:38:56 +00002785 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002786 SetPixelBlue(image,LevelizeValue(GetPixelBlue(image,q)),q);
cristy2b9582a2011-07-04 17:38:56 +00002787 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +00002788 (image->colorspace == CMYKColorspace))
2789 SetPixelBlack(image,LevelizeValue(GetPixelBlack(image,q)),q);
cristy2b9582a2011-07-04 17:38:56 +00002790 if (((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00002791 (image->matte == MagickTrue))
cristy4c08aed2011-07-01 19:47:50 +00002792 SetPixelAlpha(image,LevelizeValue(GetPixelAlpha(image,q)),q);
2793 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00002794 }
2795 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2796 status=MagickFalse;
2797 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2798 {
2799 MagickBooleanType
2800 proceed;
2801
cristyb5d5f722009-11-04 03:03:49 +00002802#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00002803 #pragma omp critical (MagickCore_LevelizeImageChannel)
2804#endif
2805 proceed=SetImageProgress(image,LevelizeImageTag,progress++,image->rows);
2806 if (proceed == MagickFalse)
2807 status=MagickFalse;
2808 }
2809 }
cristy8d4629b2010-08-30 17:59:46 +00002810 image_view=DestroyCacheView(image_view);
cristy3ed852e2009-09-05 21:47:34 +00002811 return(status);
2812}
2813
2814/*
2815%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2816% %
2817% %
2818% %
2819% L e v e l I m a g e C o l o r s %
2820% %
2821% %
2822% %
2823%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2824%
cristyee0f8d72009-09-19 00:58:29 +00002825% LevelImageColor() maps the given color to "black" and "white" values,
2826% linearly spreading out the colors, and level values on a channel by channel
2827% bases, as per LevelImage(). The given colors allows you to specify
glennrp1e7f7bc2011-03-02 19:25:28 +00002828% different level ranges for each of the color channels separately.
cristy3ed852e2009-09-05 21:47:34 +00002829%
2830% If the boolean 'invert' is set true the image values will modifyed in the
2831% reverse direction. That is any existing "black" and "white" colors in the
2832% image will become the color values given, with all other values compressed
2833% appropriatally. This effectivally maps a greyscale gradient into the given
2834% color gradient.
2835%
cristy308b4e62009-09-21 14:40:44 +00002836% The format of the LevelColorsImageChannel method is:
cristy3ed852e2009-09-05 21:47:34 +00002837%
cristy308b4e62009-09-21 14:40:44 +00002838% MagickBooleanType LevelColorsImage(Image *image,
cristy4c08aed2011-07-01 19:47:50 +00002839% const PixelInfo *black_color,
2840% const PixelInfo *white_color,const MagickBooleanType invert)
cristy308b4e62009-09-21 14:40:44 +00002841% MagickBooleanType LevelColorsImageChannel(Image *image,
cristy4c08aed2011-07-01 19:47:50 +00002842% const ChannelType channel,const PixelInfo *black_color,
2843% const PixelInfo *white_color,const MagickBooleanType invert)
cristy3ed852e2009-09-05 21:47:34 +00002844%
2845% A description of each parameter follows:
2846%
2847% o image: the image.
2848%
2849% o channel: the channel.
2850%
2851% o black_color: The color to map black to/from
2852%
2853% o white_point: The color to map white to/from
2854%
2855% o invert: if true map the colors (levelize), rather than from (level)
2856%
2857*/
cristy308b4e62009-09-21 14:40:44 +00002858
2859MagickExport MagickBooleanType LevelColorsImage(Image *image,
cristy4c08aed2011-07-01 19:47:50 +00002860 const PixelInfo *black_color,const PixelInfo *white_color,
cristy3ed852e2009-09-05 21:47:34 +00002861 const MagickBooleanType invert)
2862{
cristy308b4e62009-09-21 14:40:44 +00002863 MagickBooleanType
2864 status;
cristy3ed852e2009-09-05 21:47:34 +00002865
cristy308b4e62009-09-21 14:40:44 +00002866 status=LevelColorsImageChannel(image,DefaultChannels,black_color,white_color,
2867 invert);
2868 return(status);
2869}
2870
2871MagickExport MagickBooleanType LevelColorsImageChannel(Image *image,
cristy4c08aed2011-07-01 19:47:50 +00002872 const ChannelType channel,const PixelInfo *black_color,
2873 const PixelInfo *white_color,const MagickBooleanType invert)
cristy308b4e62009-09-21 14:40:44 +00002874{
cristy3ed852e2009-09-05 21:47:34 +00002875 MagickStatusType
2876 status;
2877
2878 /*
2879 Allocate and initialize levels map.
2880 */
2881 assert(image != (Image *) NULL);
2882 assert(image->signature == MagickSignature);
2883 if (image->debug != MagickFalse)
2884 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2885 status=MagickFalse;
2886 if (invert == MagickFalse)
2887 {
cristy2b9582a2011-07-04 17:38:56 +00002888 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00002889 status|=LevelImageChannel(image,RedChannel,
2890 black_color->red,white_color->red,(double) 1.0);
cristy2b9582a2011-07-04 17:38:56 +00002891 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00002892 status|=LevelImageChannel(image,GreenChannel,
2893 black_color->green,white_color->green,(double) 1.0);
cristy2b9582a2011-07-04 17:38:56 +00002894 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00002895 status|=LevelImageChannel(image,BlueChannel,
2896 black_color->blue,white_color->blue,(double) 1.0);
cristy2b9582a2011-07-04 17:38:56 +00002897 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +00002898 (image->colorspace == CMYKColorspace))
2899 status|=LevelImageChannel(image,BlackChannel,
2900 black_color->black,white_color->black,(double) 1.0);
cristy2b9582a2011-07-04 17:38:56 +00002901 if (((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00002902 (image->matte == MagickTrue))
2903 status|=LevelImageChannel(image,OpacityChannel,
cristy4c08aed2011-07-01 19:47:50 +00002904 black_color->alpha,white_color->alpha,(double) 1.0);
cristy3ed852e2009-09-05 21:47:34 +00002905 }
2906 else
2907 {
cristy2b9582a2011-07-04 17:38:56 +00002908 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00002909 status|=LevelizeImageChannel(image,RedChannel,
2910 black_color->red,white_color->red,(double) 1.0);
cristy2b9582a2011-07-04 17:38:56 +00002911 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00002912 status|=LevelizeImageChannel(image,GreenChannel,
2913 black_color->green,white_color->green,(double) 1.0);
cristy2b9582a2011-07-04 17:38:56 +00002914 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00002915 status|=LevelizeImageChannel(image,BlueChannel,
2916 black_color->blue,white_color->blue,(double) 1.0);
cristy2b9582a2011-07-04 17:38:56 +00002917 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +00002918 (image->colorspace == CMYKColorspace))
2919 status|=LevelizeImageChannel(image,BlackChannel,
2920 black_color->black,white_color->black,(double) 1.0);
cristy2b9582a2011-07-04 17:38:56 +00002921 if (((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00002922 (image->matte == MagickTrue))
2923 status|=LevelizeImageChannel(image,OpacityChannel,
cristy4c08aed2011-07-01 19:47:50 +00002924 black_color->alpha,white_color->alpha,(double) 1.0);
cristy3ed852e2009-09-05 21:47:34 +00002925 }
2926 return(status == 0 ? MagickFalse : MagickTrue);
2927}
2928
2929/*
2930%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2931% %
2932% %
2933% %
2934% L i n e a r S t r e t c h I m a g e %
2935% %
2936% %
2937% %
2938%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2939%
2940% The LinearStretchImage() discards any pixels below the black point and
2941% above the white point and levels the remaining pixels.
2942%
2943% The format of the LinearStretchImage method is:
2944%
2945% MagickBooleanType LinearStretchImage(Image *image,
2946% const double black_point,const double white_point)
2947%
2948% A description of each parameter follows:
2949%
2950% o image: the image.
2951%
2952% o black_point: the black point.
2953%
2954% o white_point: the white point.
2955%
2956*/
2957MagickExport MagickBooleanType LinearStretchImage(Image *image,
2958 const double black_point,const double white_point)
2959{
2960#define LinearStretchImageTag "LinearStretch/Image"
2961
2962 ExceptionInfo
2963 *exception;
2964
cristy3ed852e2009-09-05 21:47:34 +00002965 MagickBooleanType
2966 status;
2967
2968 MagickRealType
2969 *histogram,
2970 intensity;
2971
cristy8d4629b2010-08-30 17:59:46 +00002972 ssize_t
2973 black,
2974 white,
2975 y;
2976
cristy3ed852e2009-09-05 21:47:34 +00002977 /*
2978 Allocate histogram and linear map.
2979 */
2980 assert(image != (Image *) NULL);
2981 assert(image->signature == MagickSignature);
2982 histogram=(MagickRealType *) AcquireQuantumMemory(MaxMap+1UL,
2983 sizeof(*histogram));
2984 if (histogram == (MagickRealType *) NULL)
2985 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
2986 image->filename);
2987 /*
2988 Form histogram.
2989 */
2990 (void) ResetMagickMemory(histogram,0,(MaxMap+1)*sizeof(*histogram));
2991 exception=(&image->exception);
cristybb503372010-05-27 20:51:26 +00002992 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002993 {
cristy4c08aed2011-07-01 19:47:50 +00002994 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00002995 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00002996
cristybb503372010-05-27 20:51:26 +00002997 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002998 x;
2999
3000 p=GetVirtualPixels(image,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00003001 if (p == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00003002 break;
cristybb503372010-05-27 20:51:26 +00003003 for (x=(ssize_t) image->columns-1; x >= 0; x--)
cristy3ed852e2009-09-05 21:47:34 +00003004 {
cristy4c08aed2011-07-01 19:47:50 +00003005 histogram[ScaleQuantumToMap(GetPixelIntensity(image,p))]++;
3006 p+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00003007 }
3008 }
3009 /*
3010 Find the histogram boundaries by locating the black and white point levels.
3011 */
cristy3ed852e2009-09-05 21:47:34 +00003012 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00003013 for (black=0; black < (ssize_t) MaxMap; black++)
cristy3ed852e2009-09-05 21:47:34 +00003014 {
3015 intensity+=histogram[black];
3016 if (intensity >= black_point)
3017 break;
3018 }
3019 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00003020 for (white=(ssize_t) MaxMap; white != 0; white--)
cristy3ed852e2009-09-05 21:47:34 +00003021 {
3022 intensity+=histogram[white];
3023 if (intensity >= white_point)
3024 break;
3025 }
3026 histogram=(MagickRealType *) RelinquishMagickMemory(histogram);
3027 status=LevelImageChannel(image,DefaultChannels,(double) black,(double) white,
3028 1.0);
3029 return(status);
3030}
3031
3032/*
3033%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3034% %
3035% %
3036% %
3037% M o d u l a t e I m a g e %
3038% %
3039% %
3040% %
3041%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3042%
3043% ModulateImage() lets you control the brightness, saturation, and hue
3044% of an image. Modulate represents the brightness, saturation, and hue
3045% as one parameter (e.g. 90,150,100). If the image colorspace is HSL, the
3046% modulation is lightness, saturation, and hue. And if the colorspace is
3047% HWB, use blackness, whiteness, and hue.
3048%
3049% The format of the ModulateImage method is:
3050%
3051% MagickBooleanType ModulateImage(Image *image,const char *modulate)
3052%
3053% A description of each parameter follows:
3054%
3055% o image: the image.
3056%
3057% o modulate: Define the percent change in brightness, saturation, and
3058% hue.
3059%
3060*/
3061
3062static void ModulateHSB(const double percent_hue,
3063 const double percent_saturation,const double percent_brightness,
3064 Quantum *red,Quantum *green,Quantum *blue)
3065{
3066 double
3067 brightness,
3068 hue,
3069 saturation;
3070
3071 /*
3072 Increase or decrease color brightness, saturation, or hue.
3073 */
3074 assert(red != (Quantum *) NULL);
3075 assert(green != (Quantum *) NULL);
3076 assert(blue != (Quantum *) NULL);
3077 ConvertRGBToHSB(*red,*green,*blue,&hue,&saturation,&brightness);
3078 hue+=0.5*(0.01*percent_hue-1.0);
3079 while (hue < 0.0)
3080 hue+=1.0;
3081 while (hue > 1.0)
3082 hue-=1.0;
3083 saturation*=0.01*percent_saturation;
3084 brightness*=0.01*percent_brightness;
3085 ConvertHSBToRGB(hue,saturation,brightness,red,green,blue);
3086}
3087
3088static void ModulateHSL(const double percent_hue,
3089 const double percent_saturation,const double percent_lightness,
3090 Quantum *red,Quantum *green,Quantum *blue)
3091{
3092 double
3093 hue,
3094 lightness,
3095 saturation;
3096
3097 /*
3098 Increase or decrease color lightness, saturation, or hue.
3099 */
3100 assert(red != (Quantum *) NULL);
3101 assert(green != (Quantum *) NULL);
3102 assert(blue != (Quantum *) NULL);
3103 ConvertRGBToHSL(*red,*green,*blue,&hue,&saturation,&lightness);
3104 hue+=0.5*(0.01*percent_hue-1.0);
3105 while (hue < 0.0)
3106 hue+=1.0;
3107 while (hue > 1.0)
3108 hue-=1.0;
3109 saturation*=0.01*percent_saturation;
3110 lightness*=0.01*percent_lightness;
3111 ConvertHSLToRGB(hue,saturation,lightness,red,green,blue);
3112}
3113
3114static void ModulateHWB(const double percent_hue,const double percent_whiteness, const double percent_blackness,Quantum *red,Quantum *green,Quantum *blue)
3115{
3116 double
3117 blackness,
3118 hue,
3119 whiteness;
3120
3121 /*
3122 Increase or decrease color blackness, whiteness, or hue.
3123 */
3124 assert(red != (Quantum *) NULL);
3125 assert(green != (Quantum *) NULL);
3126 assert(blue != (Quantum *) NULL);
3127 ConvertRGBToHWB(*red,*green,*blue,&hue,&whiteness,&blackness);
3128 hue+=0.5*(0.01*percent_hue-1.0);
3129 while (hue < 0.0)
3130 hue+=1.0;
3131 while (hue > 1.0)
3132 hue-=1.0;
3133 blackness*=0.01*percent_blackness;
3134 whiteness*=0.01*percent_whiteness;
3135 ConvertHWBToRGB(hue,whiteness,blackness,red,green,blue);
3136}
3137
3138MagickExport MagickBooleanType ModulateImage(Image *image,const char *modulate)
3139{
3140#define ModulateImageTag "Modulate/Image"
3141
cristyc4c8d132010-01-07 01:58:38 +00003142 CacheView
3143 *image_view;
3144
cristy3ed852e2009-09-05 21:47:34 +00003145 ColorspaceType
3146 colorspace;
3147
3148 const char
3149 *artifact;
3150
3151 double
3152 percent_brightness,
3153 percent_hue,
3154 percent_saturation;
3155
3156 ExceptionInfo
3157 *exception;
3158
3159 GeometryInfo
3160 geometry_info;
3161
cristy3ed852e2009-09-05 21:47:34 +00003162 MagickBooleanType
3163 status;
3164
cristybb503372010-05-27 20:51:26 +00003165 MagickOffsetType
3166 progress;
3167
cristy3ed852e2009-09-05 21:47:34 +00003168 MagickStatusType
3169 flags;
3170
cristybb503372010-05-27 20:51:26 +00003171 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003172 i;
3173
cristybb503372010-05-27 20:51:26 +00003174 ssize_t
3175 y;
3176
cristy3ed852e2009-09-05 21:47:34 +00003177 /*
cristy2b726bd2010-01-11 01:05:39 +00003178 Initialize modulate table.
cristy3ed852e2009-09-05 21:47:34 +00003179 */
3180 assert(image != (Image *) NULL);
3181 assert(image->signature == MagickSignature);
3182 if (image->debug != MagickFalse)
3183 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3184 if (modulate == (char *) NULL)
3185 return(MagickFalse);
3186 flags=ParseGeometry(modulate,&geometry_info);
3187 percent_brightness=geometry_info.rho;
3188 percent_saturation=geometry_info.sigma;
3189 if ((flags & SigmaValue) == 0)
3190 percent_saturation=100.0;
3191 percent_hue=geometry_info.xi;
3192 if ((flags & XiValue) == 0)
3193 percent_hue=100.0;
3194 colorspace=UndefinedColorspace;
3195 artifact=GetImageArtifact(image,"modulate:colorspace");
3196 if (artifact != (const char *) NULL)
cristy042ee782011-04-22 18:48:30 +00003197 colorspace=(ColorspaceType) ParseCommandOption(MagickColorspaceOptions,
cristy3ed852e2009-09-05 21:47:34 +00003198 MagickFalse,artifact);
3199 if (image->storage_class == PseudoClass)
3200 {
3201 /*
3202 Modulate colormap.
3203 */
cristyb5d5f722009-11-04 03:03:49 +00003204#if defined(MAGICKCORE_OPENMP_SUPPORT)
3205 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003206#endif
cristybb503372010-05-27 20:51:26 +00003207 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00003208 switch (colorspace)
3209 {
3210 case HSBColorspace:
3211 {
3212 ModulateHSB(percent_hue,percent_saturation,percent_brightness,
3213 &image->colormap[i].red,&image->colormap[i].green,
3214 &image->colormap[i].blue);
3215 break;
3216 }
3217 case HSLColorspace:
3218 default:
3219 {
3220 ModulateHSL(percent_hue,percent_saturation,percent_brightness,
3221 &image->colormap[i].red,&image->colormap[i].green,
3222 &image->colormap[i].blue);
3223 break;
3224 }
3225 case HWBColorspace:
3226 {
3227 ModulateHWB(percent_hue,percent_saturation,percent_brightness,
3228 &image->colormap[i].red,&image->colormap[i].green,
3229 &image->colormap[i].blue);
3230 break;
3231 }
3232 }
3233 }
3234 /*
3235 Modulate image.
3236 */
3237 status=MagickTrue;
3238 progress=0;
3239 exception=(&image->exception);
3240 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00003241#if defined(MAGICKCORE_OPENMP_SUPPORT)
3242 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003243#endif
cristybb503372010-05-27 20:51:26 +00003244 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003245 {
cristy5afeab82011-04-30 01:30:09 +00003246 Quantum
3247 blue,
3248 green,
3249 red;
3250
cristy4c08aed2011-07-01 19:47:50 +00003251 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00003252 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003253
cristy8d4629b2010-08-30 17:59:46 +00003254 register ssize_t
3255 x;
3256
cristy3ed852e2009-09-05 21:47:34 +00003257 if (status == MagickFalse)
3258 continue;
3259 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00003260 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00003261 {
3262 status=MagickFalse;
3263 continue;
3264 }
cristybb503372010-05-27 20:51:26 +00003265 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003266 {
cristy4c08aed2011-07-01 19:47:50 +00003267 red=GetPixelRed(image,q);
3268 green=GetPixelGreen(image,q);
3269 blue=GetPixelBlue(image,q);
cristy3ed852e2009-09-05 21:47:34 +00003270 switch (colorspace)
3271 {
3272 case HSBColorspace:
3273 {
3274 ModulateHSB(percent_hue,percent_saturation,percent_brightness,
cristy5afeab82011-04-30 01:30:09 +00003275 &red,&green,&blue);
cristy3ed852e2009-09-05 21:47:34 +00003276 break;
3277 }
3278 case HSLColorspace:
3279 default:
3280 {
3281 ModulateHSL(percent_hue,percent_saturation,percent_brightness,
cristy5afeab82011-04-30 01:30:09 +00003282 &red,&green,&blue);
cristy3ed852e2009-09-05 21:47:34 +00003283 break;
3284 }
3285 case HWBColorspace:
3286 {
3287 ModulateHWB(percent_hue,percent_saturation,percent_brightness,
cristy5afeab82011-04-30 01:30:09 +00003288 &red,&green,&blue);
cristy3ed852e2009-09-05 21:47:34 +00003289 break;
3290 }
3291 }
cristy4c08aed2011-07-01 19:47:50 +00003292 SetPixelRed(image,red,q);
3293 SetPixelGreen(image,green,q);
3294 SetPixelBlue(image,blue,q);
3295 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00003296 }
3297 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
3298 status=MagickFalse;
3299 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3300 {
3301 MagickBooleanType
3302 proceed;
3303
cristyb5d5f722009-11-04 03:03:49 +00003304#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00003305 #pragma omp critical (MagickCore_ModulateImage)
3306#endif
3307 proceed=SetImageProgress(image,ModulateImageTag,progress++,image->rows);
3308 if (proceed == MagickFalse)
3309 status=MagickFalse;
3310 }
3311 }
3312 image_view=DestroyCacheView(image_view);
3313 return(status);
3314}
3315
3316/*
3317%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3318% %
3319% %
3320% %
3321% N e g a t e I m a g e %
3322% %
3323% %
3324% %
3325%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3326%
3327% NegateImage() negates the colors in the reference image. The grayscale
3328% option means that only grayscale values within the image are negated.
3329%
3330% The format of the NegateImageChannel method is:
3331%
3332% MagickBooleanType NegateImage(Image *image,
3333% const MagickBooleanType grayscale)
3334% MagickBooleanType NegateImageChannel(Image *image,
3335% const ChannelType channel,const MagickBooleanType grayscale)
3336%
3337% A description of each parameter follows:
3338%
3339% o image: the image.
3340%
3341% o channel: the channel.
3342%
3343% o grayscale: If MagickTrue, only negate grayscale pixels within the image.
3344%
3345*/
3346
3347MagickExport MagickBooleanType NegateImage(Image *image,
3348 const MagickBooleanType grayscale)
3349{
3350 MagickBooleanType
3351 status;
3352
3353 status=NegateImageChannel(image,DefaultChannels,grayscale);
3354 return(status);
3355}
3356
3357MagickExport MagickBooleanType NegateImageChannel(Image *image,
3358 const ChannelType channel,const MagickBooleanType grayscale)
3359{
3360#define NegateImageTag "Negate/Image"
3361
cristyc4c8d132010-01-07 01:58:38 +00003362 CacheView
3363 *image_view;
3364
cristy3ed852e2009-09-05 21:47:34 +00003365 ExceptionInfo
3366 *exception;
3367
cristy3ed852e2009-09-05 21:47:34 +00003368 MagickBooleanType
3369 status;
3370
cristybb503372010-05-27 20:51:26 +00003371 MagickOffsetType
3372 progress;
3373
3374 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003375 i;
3376
cristybb503372010-05-27 20:51:26 +00003377 ssize_t
3378 y;
3379
cristy3ed852e2009-09-05 21:47:34 +00003380 assert(image != (Image *) NULL);
3381 assert(image->signature == MagickSignature);
3382 if (image->debug != MagickFalse)
3383 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3384 if (image->storage_class == PseudoClass)
3385 {
3386 /*
3387 Negate colormap.
3388 */
cristyb5d5f722009-11-04 03:03:49 +00003389#if defined(MAGICKCORE_OPENMP_SUPPORT)
3390 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003391#endif
cristybb503372010-05-27 20:51:26 +00003392 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00003393 {
3394 if (grayscale != MagickFalse)
3395 if ((image->colormap[i].red != image->colormap[i].green) ||
3396 (image->colormap[i].green != image->colormap[i].blue))
3397 continue;
cristy2b9582a2011-07-04 17:38:56 +00003398 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00003399 image->colormap[i].red=(Quantum) QuantumRange-
3400 image->colormap[i].red;
cristy2b9582a2011-07-04 17:38:56 +00003401 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00003402 image->colormap[i].green=(Quantum) QuantumRange-
3403 image->colormap[i].green;
cristy2b9582a2011-07-04 17:38:56 +00003404 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00003405 image->colormap[i].blue=(Quantum) QuantumRange-
3406 image->colormap[i].blue;
3407 }
3408 }
3409 /*
3410 Negate image.
3411 */
3412 status=MagickTrue;
3413 progress=0;
3414 exception=(&image->exception);
3415 image_view=AcquireCacheView(image);
3416 if (grayscale != MagickFalse)
3417 {
cristyb5d5f722009-11-04 03:03:49 +00003418#if defined(MAGICKCORE_OPENMP_SUPPORT)
3419 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003420#endif
cristybb503372010-05-27 20:51:26 +00003421 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003422 {
3423 MagickBooleanType
3424 sync;
3425
cristy4c08aed2011-07-01 19:47:50 +00003426 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00003427 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003428
cristy8d4629b2010-08-30 17:59:46 +00003429 register ssize_t
3430 x;
3431
cristy3ed852e2009-09-05 21:47:34 +00003432 if (status == MagickFalse)
3433 continue;
3434 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,
3435 exception);
cristy4c08aed2011-07-01 19:47:50 +00003436 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00003437 {
3438 status=MagickFalse;
3439 continue;
3440 }
cristybb503372010-05-27 20:51:26 +00003441 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003442 {
cristy4c08aed2011-07-01 19:47:50 +00003443 if ((GetPixelRed(image,q) != GetPixelGreen(image,q)) ||
3444 (GetPixelGreen(image,q) != GetPixelBlue(image,q)))
cristy3ed852e2009-09-05 21:47:34 +00003445 {
cristy4c08aed2011-07-01 19:47:50 +00003446 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00003447 continue;
3448 }
cristy2b9582a2011-07-04 17:38:56 +00003449 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003450 SetPixelRed(image,QuantumRange-GetPixelRed(image,q),q);
cristy2b9582a2011-07-04 17:38:56 +00003451 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003452 SetPixelGreen(image,QuantumRange-GetPixelGreen(image,q),q);
cristy2b9582a2011-07-04 17:38:56 +00003453 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003454 SetPixelBlue(image,QuantumRange-GetPixelBlue(image,q),q);
cristy2b9582a2011-07-04 17:38:56 +00003455 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00003456 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +00003457 SetPixelBlack(image,QuantumRange-GetPixelBlack(image,q),q);
cristy2b9582a2011-07-04 17:38:56 +00003458 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003459 SetPixelAlpha(image,QuantumRange-GetPixelAlpha(image,q),q);
3460 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00003461 }
3462 sync=SyncCacheViewAuthenticPixels(image_view,exception);
3463 if (sync == MagickFalse)
3464 status=MagickFalse;
3465 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3466 {
3467 MagickBooleanType
3468 proceed;
3469
cristyb5d5f722009-11-04 03:03:49 +00003470#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00003471 #pragma omp critical (MagickCore_NegateImageChannel)
3472#endif
3473 proceed=SetImageProgress(image,NegateImageTag,progress++,
3474 image->rows);
3475 if (proceed == MagickFalse)
3476 status=MagickFalse;
3477 }
3478 }
3479 image_view=DestroyCacheView(image_view);
3480 return(MagickTrue);
3481 }
3482 /*
3483 Negate image.
3484 */
cristyb5d5f722009-11-04 03:03:49 +00003485#if defined(MAGICKCORE_OPENMP_SUPPORT)
3486 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003487#endif
cristybb503372010-05-27 20:51:26 +00003488 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003489 {
cristy4c08aed2011-07-01 19:47:50 +00003490 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00003491 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003492
cristy8d4629b2010-08-30 17:59:46 +00003493 register ssize_t
3494 x;
3495
cristy3ed852e2009-09-05 21:47:34 +00003496 if (status == MagickFalse)
3497 continue;
3498 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00003499 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00003500 {
3501 status=MagickFalse;
3502 continue;
3503 }
cristybb503372010-05-27 20:51:26 +00003504 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003505 {
cristy2b9582a2011-07-04 17:38:56 +00003506 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003507 SetPixelRed(image,QuantumRange-GetPixelRed(image,q),q);
cristy2b9582a2011-07-04 17:38:56 +00003508 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003509 SetPixelGreen(image,QuantumRange-GetPixelGreen(image,q),q);
cristy2b9582a2011-07-04 17:38:56 +00003510 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003511 SetPixelBlue(image,QuantumRange-GetPixelBlue(image,q),q);
cristy2b9582a2011-07-04 17:38:56 +00003512 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00003513 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +00003514 SetPixelBlack(image,QuantumRange-GetPixelBlack(image,q),q);
cristy2b9582a2011-07-04 17:38:56 +00003515 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003516 SetPixelAlpha(image,QuantumRange-GetPixelAlpha(image,q),q);
3517 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00003518 }
3519 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
3520 status=MagickFalse;
3521 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3522 {
3523 MagickBooleanType
3524 proceed;
3525
cristyb5d5f722009-11-04 03:03:49 +00003526#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00003527 #pragma omp critical (MagickCore_NegateImageChannel)
3528#endif
3529 proceed=SetImageProgress(image,NegateImageTag,progress++,image->rows);
3530 if (proceed == MagickFalse)
3531 status=MagickFalse;
3532 }
3533 }
3534 image_view=DestroyCacheView(image_view);
3535 return(status);
3536}
3537
3538/*
3539%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3540% %
3541% %
3542% %
3543% N o r m a l i z e I m a g e %
3544% %
3545% %
3546% %
3547%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3548%
3549% The NormalizeImage() method enhances the contrast of a color image by
3550% mapping the darkest 2 percent of all pixel to black and the brightest
3551% 1 percent to white.
3552%
3553% The format of the NormalizeImage method is:
3554%
3555% MagickBooleanType NormalizeImage(Image *image)
3556% MagickBooleanType NormalizeImageChannel(Image *image,
3557% const ChannelType channel)
3558%
3559% A description of each parameter follows:
3560%
3561% o image: the image.
3562%
3563% o channel: the channel.
3564%
3565*/
3566
3567MagickExport MagickBooleanType NormalizeImage(Image *image)
3568{
3569 MagickBooleanType
3570 status;
3571
3572 status=NormalizeImageChannel(image,DefaultChannels);
3573 return(status);
3574}
3575
3576MagickExport MagickBooleanType NormalizeImageChannel(Image *image,
3577 const ChannelType channel)
3578{
3579 double
3580 black_point,
3581 white_point;
3582
cristy530239c2010-07-25 17:34:26 +00003583 black_point=(double) image->columns*image->rows*0.0015;
3584 white_point=(double) image->columns*image->rows*0.9995;
cristy3ed852e2009-09-05 21:47:34 +00003585 return(ContrastStretchImageChannel(image,channel,black_point,white_point));
3586}
3587
3588/*
3589%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3590% %
3591% %
3592% %
3593% S i g m o i d a l C o n t r a s t I m a g e %
3594% %
3595% %
3596% %
3597%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3598%
3599% SigmoidalContrastImage() adjusts the contrast of an image with a non-linear
3600% sigmoidal contrast algorithm. Increase the contrast of the image using a
3601% sigmoidal transfer function without saturating highlights or shadows.
3602% Contrast indicates how much to increase the contrast (0 is none; 3 is
3603% typical; 20 is pushing it); mid-point indicates where midtones fall in the
3604% resultant image (0 is white; 50% is middle-gray; 100% is black). Set
3605% sharpen to MagickTrue to increase the image contrast otherwise the contrast
3606% is reduced.
3607%
3608% The format of the SigmoidalContrastImage method is:
3609%
3610% MagickBooleanType SigmoidalContrastImage(Image *image,
3611% const MagickBooleanType sharpen,const char *levels)
cristy3ed852e2009-09-05 21:47:34 +00003612%
3613% A description of each parameter follows:
3614%
3615% o image: the image.
3616%
cristy3ed852e2009-09-05 21:47:34 +00003617% o sharpen: Increase or decrease image contrast.
3618%
cristyfa769582010-09-30 23:30:03 +00003619% o alpha: strength of the contrast, the larger the number the more
3620% 'threshold-like' it becomes.
cristy3ed852e2009-09-05 21:47:34 +00003621%
cristyfa769582010-09-30 23:30:03 +00003622% o beta: midpoint of the function as a color value 0 to QuantumRange.
cristy3ed852e2009-09-05 21:47:34 +00003623%
3624*/
cristy3ed852e2009-09-05 21:47:34 +00003625MagickExport MagickBooleanType SigmoidalContrastImage(Image *image,
cristy9ee60942011-07-06 14:54:38 +00003626 const MagickBooleanType sharpen,const double contrast,const double midpoint)
cristy3ed852e2009-09-05 21:47:34 +00003627{
3628#define SigmoidalContrastImageTag "SigmoidalContrast/Image"
3629
cristyc4c8d132010-01-07 01:58:38 +00003630 CacheView
3631 *image_view;
3632
cristy3ed852e2009-09-05 21:47:34 +00003633 ExceptionInfo
3634 *exception;
3635
cristy3ed852e2009-09-05 21:47:34 +00003636 MagickBooleanType
3637 status;
3638
cristybb503372010-05-27 20:51:26 +00003639 MagickOffsetType
3640 progress;
3641
cristy3ed852e2009-09-05 21:47:34 +00003642 MagickRealType
3643 *sigmoidal_map;
3644
cristybb503372010-05-27 20:51:26 +00003645 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003646 i;
3647
cristybb503372010-05-27 20:51:26 +00003648 ssize_t
3649 y;
3650
cristy3ed852e2009-09-05 21:47:34 +00003651 /*
3652 Allocate and initialize sigmoidal maps.
3653 */
3654 assert(image != (Image *) NULL);
3655 assert(image->signature == MagickSignature);
3656 if (image->debug != MagickFalse)
3657 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3658 sigmoidal_map=(MagickRealType *) AcquireQuantumMemory(MaxMap+1UL,
3659 sizeof(*sigmoidal_map));
3660 if (sigmoidal_map == (MagickRealType *) NULL)
3661 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
3662 image->filename);
3663 (void) ResetMagickMemory(sigmoidal_map,0,(MaxMap+1)*sizeof(*sigmoidal_map));
cristyb5d5f722009-11-04 03:03:49 +00003664#if defined(MAGICKCORE_OPENMP_SUPPORT)
3665 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003666#endif
cristybb503372010-05-27 20:51:26 +00003667 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +00003668 {
3669 if (sharpen != MagickFalse)
3670 {
3671 sigmoidal_map[i]=(MagickRealType) ScaleMapToQuantum((MagickRealType)
3672 (MaxMap*((1.0/(1.0+exp(contrast*(midpoint/(double) QuantumRange-
3673 (double) i/MaxMap))))-(1.0/(1.0+exp(contrast*(midpoint/
3674 (double) QuantumRange)))))/((1.0/(1.0+exp(contrast*(midpoint/
3675 (double) QuantumRange-1.0))))-(1.0/(1.0+exp(contrast*(midpoint/
3676 (double) QuantumRange)))))+0.5));
3677 continue;
3678 }
3679 sigmoidal_map[i]=(MagickRealType) ScaleMapToQuantum((MagickRealType)
3680 (MaxMap*(QuantumScale*midpoint-log((1.0-(1.0/(1.0+exp(midpoint/
3681 (double) QuantumRange*contrast))+((double) i/MaxMap)*((1.0/
3682 (1.0+exp(contrast*(midpoint/(double) QuantumRange-1.0))))-(1.0/
3683 (1.0+exp(midpoint/(double) QuantumRange*contrast))))))/
3684 (1.0/(1.0+exp(midpoint/(double) QuantumRange*contrast))+
3685 ((double) i/MaxMap)*((1.0/(1.0+exp(contrast*(midpoint/
3686 (double) QuantumRange-1.0))))-(1.0/(1.0+exp(midpoint/
3687 (double) QuantumRange*contrast))))))/contrast)));
3688 }
3689 if (image->storage_class == PseudoClass)
3690 {
3691 /*
3692 Sigmoidal-contrast enhance colormap.
3693 */
cristyb5d5f722009-11-04 03:03:49 +00003694#if defined(MAGICKCORE_OPENMP_SUPPORT)
3695 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003696#endif
cristybb503372010-05-27 20:51:26 +00003697 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00003698 {
cristy2b9582a2011-07-04 17:38:56 +00003699 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristyce70c172010-01-07 17:15:30 +00003700 image->colormap[i].red=ClampToQuantum(sigmoidal_map[
cristy3ed852e2009-09-05 21:47:34 +00003701 ScaleQuantumToMap(image->colormap[i].red)]);
cristy2b9582a2011-07-04 17:38:56 +00003702 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristyce70c172010-01-07 17:15:30 +00003703 image->colormap[i].green=ClampToQuantum(sigmoidal_map[
cristy3ed852e2009-09-05 21:47:34 +00003704 ScaleQuantumToMap(image->colormap[i].green)]);
cristy2b9582a2011-07-04 17:38:56 +00003705 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristyce70c172010-01-07 17:15:30 +00003706 image->colormap[i].blue=ClampToQuantum(sigmoidal_map[
cristy3ed852e2009-09-05 21:47:34 +00003707 ScaleQuantumToMap(image->colormap[i].blue)]);
cristy2b9582a2011-07-04 17:38:56 +00003708 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003709 image->colormap[i].alpha=ClampToQuantum(sigmoidal_map[
3710 ScaleQuantumToMap(image->colormap[i].alpha)]);
cristy3ed852e2009-09-05 21:47:34 +00003711 }
3712 }
3713 /*
3714 Sigmoidal-contrast enhance image.
3715 */
3716 status=MagickTrue;
3717 progress=0;
3718 exception=(&image->exception);
3719 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00003720#if defined(MAGICKCORE_OPENMP_SUPPORT)
3721 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003722#endif
cristybb503372010-05-27 20:51:26 +00003723 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003724 {
cristy4c08aed2011-07-01 19:47:50 +00003725 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00003726 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003727
cristy8d4629b2010-08-30 17:59:46 +00003728 register ssize_t
3729 x;
3730
cristy3ed852e2009-09-05 21:47:34 +00003731 if (status == MagickFalse)
3732 continue;
3733 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00003734 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00003735 {
3736 status=MagickFalse;
3737 continue;
3738 }
cristybb503372010-05-27 20:51:26 +00003739 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003740 {
cristy2b9582a2011-07-04 17:38:56 +00003741 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003742 SetPixelRed(image,ClampToQuantum(sigmoidal_map[ScaleQuantumToMap(
3743 GetPixelRed(image,q))]),q);
cristy2b9582a2011-07-04 17:38:56 +00003744 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003745 SetPixelGreen(image,ClampToQuantum(sigmoidal_map[ScaleQuantumToMap(
3746 GetPixelGreen(image,q))]),q);
cristy2b9582a2011-07-04 17:38:56 +00003747 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003748 SetPixelBlue(image,ClampToQuantum(sigmoidal_map[ScaleQuantumToMap(
3749 GetPixelBlue(image,q))]),q);
cristy2b9582a2011-07-04 17:38:56 +00003750 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00003751 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +00003752 SetPixelBlack(image,ClampToQuantum(sigmoidal_map[ScaleQuantumToMap(
3753 GetPixelBlack(image,q))]),q);
cristy2b9582a2011-07-04 17:38:56 +00003754 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003755 SetPixelAlpha(image,ClampToQuantum(sigmoidal_map[ScaleQuantumToMap(
3756 GetPixelAlpha(image,q))]),q);
3757 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00003758 }
3759 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
3760 status=MagickFalse;
3761 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3762 {
3763 MagickBooleanType
3764 proceed;
3765
cristyb5d5f722009-11-04 03:03:49 +00003766#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy9ee60942011-07-06 14:54:38 +00003767 #pragma omp critical (MagickCore_SigmoidalContrastImage)
cristy3ed852e2009-09-05 21:47:34 +00003768#endif
3769 proceed=SetImageProgress(image,SigmoidalContrastImageTag,progress++,
3770 image->rows);
3771 if (proceed == MagickFalse)
3772 status=MagickFalse;
3773 }
3774 }
3775 image_view=DestroyCacheView(image_view);
3776 sigmoidal_map=(MagickRealType *) RelinquishMagickMemory(sigmoidal_map);
3777 return(status);
3778}