blob: b3166c36d2e04efd09d83ed8b8c543bd0c653628 [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)
93% MagickBooleanType AutoGammaImageChannel(Image *image,
94% const ChannelType channel)
95%
96% A description of each parameter follows:
97%
98% o image: The image to auto-level
99%
100% o channel: The channels to auto-level. If the special 'SyncChannels'
101% flag is set all given channels is adjusted in the same way using the
102% mean average of those channels.
103%
104*/
105
106MagickExport MagickBooleanType AutoGammaImage(Image *image)
107{
108 return(AutoGammaImageChannel(image,DefaultChannels));
109}
110
111MagickExport MagickBooleanType AutoGammaImageChannel(Image *image,
112 const ChannelType channel)
113{
114 MagickStatusType
115 status;
116
117 double
cristy4c08aed2011-07-01 19:47:50 +0000118 gamma,
119 log_mean,
120 mean,
121 sans;
anthony4efe5972009-09-11 06:46:40 +0000122
cristy4c08aed2011-07-01 19:47:50 +0000123 log_mean=log(0.5);
cristy3ed852e2009-09-05 21:47:34 +0000124
125 if ((channel & SyncChannels) != 0 )
126 {
127 /*
128 Apply gamma correction equally accross all given channels
129 */
cristy2b726bd2010-01-11 01:05:39 +0000130 (void) GetImageChannelMean(image,channel,&mean,&sans,&image->exception);
cristy4c08aed2011-07-01 19:47:50 +0000131 gamma=log(mean*QuantumScale)/log_mean;
132 return(LevelImageChannel(image,channel,0.0,(double)QuantumRange,gamma));
cristy3ed852e2009-09-05 21:47:34 +0000133 }
134
135 /*
cristy4c08aed2011-07-01 19:47:50 +0000136 Auto-gamma each channel separately.
cristy3ed852e2009-09-05 21:47:34 +0000137 */
cristy4c08aed2011-07-01 19:47:50 +0000138 status=MagickTrue;
cristy2b9582a2011-07-04 17:38:56 +0000139 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +0000140 {
cristy2b726bd2010-01-11 01:05:39 +0000141 (void) GetImageChannelMean(image,RedChannel,&mean,&sans,
142 &image->exception);
cristy4c08aed2011-07-01 19:47:50 +0000143 gamma=log(mean*QuantumScale)/log_mean;
144 status=status && LevelImageChannel(image,RedChannel,0.0,(double)
145 QuantumRange, gamma);
cristy3ed852e2009-09-05 21:47:34 +0000146 }
cristy2b9582a2011-07-04 17:38:56 +0000147 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +0000148 {
cristy2b726bd2010-01-11 01:05:39 +0000149 (void) GetImageChannelMean(image,GreenChannel,&mean,&sans,
150 &image->exception);
cristy4c08aed2011-07-01 19:47:50 +0000151 gamma=log(mean*QuantumScale)/log_mean;
152 status=status && LevelImageChannel(image,GreenChannel,0.0,(double)
153 QuantumRange,gamma);
cristy3ed852e2009-09-05 21:47:34 +0000154 }
cristy2b9582a2011-07-04 17:38:56 +0000155 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +0000156 {
cristy2b726bd2010-01-11 01:05:39 +0000157 (void) GetImageChannelMean(image,BlueChannel,&mean,&sans,
158 &image->exception);
cristy4c08aed2011-07-01 19:47:50 +0000159 gamma=log(mean*QuantumScale)/log_mean;
160 status=status && LevelImageChannel(image,BlueChannel,0.0,(double)
161 QuantumRange,gamma);
162 }
cristy2b9582a2011-07-04 17:38:56 +0000163 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +0000164 (image->colorspace == CMYKColorspace))
165 {
166 (void) GetImageChannelMean(image,BlackChannel,&mean,&sans,
167 &image->exception);
168 gamma=log(mean*QuantumScale)/log_mean;
169 status=status && LevelImageChannel(image,BlackChannel,0.0,(double)
170 QuantumRange,gamma);
cristy3ed852e2009-09-05 21:47:34 +0000171 }
cristy2b9582a2011-07-04 17:38:56 +0000172 if (((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +0000173 (image->matte == MagickTrue))
174 {
cristy2b726bd2010-01-11 01:05:39 +0000175 (void) GetImageChannelMean(image,OpacityChannel,&mean,&sans,
176 &image->exception);
cristy4c08aed2011-07-01 19:47:50 +0000177 gamma=log(mean*QuantumScale)/log_mean;
178 status=status && LevelImageChannel(image,OpacityChannel,0.0,(double)
179 QuantumRange,gamma);
cristy3ed852e2009-09-05 21:47:34 +0000180 }
181 return(status != 0 ? MagickTrue : MagickFalse);
182}
183
184/*
185%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
186% %
187% %
188% %
189% A u t o L e v e l I m a g e %
190% %
191% %
192% %
193%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
194%
195% AutoLevelImage() adjusts the levels of a particular image channel by
196% scaling the minimum and maximum values to the full quantum range.
197%
198% The format of the LevelImage method is:
199%
200% MagickBooleanType AutoLevelImage(Image *image)
201% MagickBooleanType AutoLevelImageChannel(Image *image,
202% const ChannelType channel)
203%
204% A description of each parameter follows:
205%
206% o image: The image to auto-level
207%
208% o channel: The channels to auto-level. If the special 'SyncChannels'
209% flag is set the min/max/mean value of all given channels is used for
210% all given channels, to all channels in the same way.
211%
212*/
213
214MagickExport MagickBooleanType AutoLevelImage(Image *image)
215{
216 return(AutoLevelImageChannel(image,DefaultChannels));
217}
218
219MagickExport MagickBooleanType AutoLevelImageChannel(Image *image,
220 const ChannelType channel)
221{
222 /*
223 This is simply a convenience function around a Min/Max Histogram Stretch
224 */
225 return MinMaxStretchImage(image, channel, 0.0, 0.0);
226}
227
228/*
229%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
230% %
231% %
232% %
cristya28d6b82010-01-11 20:03:47 +0000233% B r i g h t n e s s C o n t r a s t I m a g e %
234% %
235% %
236% %
237%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
238%
239% Use BrightnessContrastImage() to change the brightness and/or contrast of
240% an image. It converts the brightness and contrast parameters into slope
241% and intercept and calls a polynomical function to apply to the image.
242%
243% The format of the BrightnessContrastImage method is:
244%
245% MagickBooleanType BrightnessContrastImage(Image *image,
246% const double brightness,const double contrast)
247% MagickBooleanType BrightnessContrastImageChannel(Image *image,
248% const ChannelType channel,const double brightness,
249% const double contrast)
250%
251% A description of each parameter follows:
252%
253% o image: the image.
254%
255% o channel: the channel.
256%
257% o brightness: the brightness percent (-100 .. 100).
258%
259% o contrast: the contrast percent (-100 .. 100).
260%
261*/
262
263MagickExport MagickBooleanType BrightnessContrastImage(Image *image,
264 const double brightness,const double contrast)
265{
266 MagickBooleanType
267 status;
268
269 status=BrightnessContrastImageChannel(image,DefaultChannels,brightness,
270 contrast);
271 return(status);
272}
273
274MagickExport MagickBooleanType BrightnessContrastImageChannel(Image *image,
275 const ChannelType channel,const double brightness,const double contrast)
276{
277#define BrightnessContastImageTag "BrightnessContast/Image"
278
279 double
280 alpha,
281 intercept,
282 coefficients[2],
283 slope;
284
285 MagickBooleanType
286 status;
287
288 /*
289 Compute slope and intercept.
290 */
291 assert(image != (Image *) NULL);
292 assert(image->signature == MagickSignature);
293 if (image->debug != MagickFalse)
294 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
295 alpha=contrast;
cristy4205a3c2010-09-12 20:19:59 +0000296 slope=tan((double) (MagickPI*(alpha/100.0+1.0)/4.0));
cristya28d6b82010-01-11 20:03:47 +0000297 if (slope < 0.0)
298 slope=0.0;
299 intercept=brightness/100.0+((100-brightness)/200.0)*(1.0-slope);
300 coefficients[0]=slope;
301 coefficients[1]=intercept;
302 status=FunctionImageChannel(image,channel,PolynomialFunction,2,coefficients,
303 &image->exception);
304 return(status);
305}
306
307/*
308%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
309% %
310% %
311% %
cristy3ed852e2009-09-05 21:47:34 +0000312% C o l o r D e c i s i o n L i s t I m a g e %
313% %
314% %
315% %
316%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
317%
318% ColorDecisionListImage() accepts a lightweight Color Correction Collection
319% (CCC) file which solely contains one or more color corrections and applies
320% the correction to the image. Here is a sample CCC file:
321%
322% <ColorCorrectionCollection xmlns="urn:ASC:CDL:v1.2">
323% <ColorCorrection id="cc03345">
324% <SOPNode>
325% <Slope> 0.9 1.2 0.5 </Slope>
326% <Offset> 0.4 -0.5 0.6 </Offset>
327% <Power> 1.0 0.8 1.5 </Power>
328% </SOPNode>
329% <SATNode>
330% <Saturation> 0.85 </Saturation>
331% </SATNode>
332% </ColorCorrection>
333% </ColorCorrectionCollection>
334%
335% which includes the slop, offset, and power for each of the RGB channels
336% as well as the saturation.
337%
338% The format of the ColorDecisionListImage method is:
339%
340% MagickBooleanType ColorDecisionListImage(Image *image,
341% const char *color_correction_collection)
342%
343% A description of each parameter follows:
344%
345% o image: the image.
346%
347% o color_correction_collection: the color correction collection in XML.
348%
349*/
350MagickExport MagickBooleanType ColorDecisionListImage(Image *image,
351 const char *color_correction_collection)
352{
353#define ColorDecisionListCorrectImageTag "ColorDecisionList/Image"
354
355 typedef struct _Correction
356 {
357 double
358 slope,
359 offset,
360 power;
361 } Correction;
362
363 typedef struct _ColorCorrection
364 {
365 Correction
366 red,
367 green,
368 blue;
369
370 double
371 saturation;
372 } ColorCorrection;
373
cristyc4c8d132010-01-07 01:58:38 +0000374 CacheView
375 *image_view;
376
cristy3ed852e2009-09-05 21:47:34 +0000377 char
378 token[MaxTextExtent];
379
380 ColorCorrection
381 color_correction;
382
383 const char
384 *content,
385 *p;
386
387 ExceptionInfo
388 *exception;
389
cristy3ed852e2009-09-05 21:47:34 +0000390 MagickBooleanType
391 status;
392
cristybb503372010-05-27 20:51:26 +0000393 MagickOffsetType
394 progress;
395
cristy3ed852e2009-09-05 21:47:34 +0000396 PixelPacket
397 *cdl_map;
398
cristybb503372010-05-27 20:51:26 +0000399 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000400 i;
401
cristybb503372010-05-27 20:51:26 +0000402 ssize_t
403 y;
404
cristy3ed852e2009-09-05 21:47:34 +0000405 XMLTreeInfo
406 *cc,
407 *ccc,
408 *sat,
409 *sop;
410
cristy3ed852e2009-09-05 21:47:34 +0000411 /*
412 Allocate and initialize cdl maps.
413 */
414 assert(image != (Image *) NULL);
415 assert(image->signature == MagickSignature);
416 if (image->debug != MagickFalse)
417 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
418 if (color_correction_collection == (const char *) NULL)
419 return(MagickFalse);
420 ccc=NewXMLTree((const char *) color_correction_collection,&image->exception);
421 if (ccc == (XMLTreeInfo *) NULL)
422 return(MagickFalse);
423 cc=GetXMLTreeChild(ccc,"ColorCorrection");
424 if (cc == (XMLTreeInfo *) NULL)
425 {
426 ccc=DestroyXMLTree(ccc);
427 return(MagickFalse);
428 }
429 color_correction.red.slope=1.0;
430 color_correction.red.offset=0.0;
431 color_correction.red.power=1.0;
432 color_correction.green.slope=1.0;
433 color_correction.green.offset=0.0;
434 color_correction.green.power=1.0;
435 color_correction.blue.slope=1.0;
436 color_correction.blue.offset=0.0;
437 color_correction.blue.power=1.0;
438 color_correction.saturation=0.0;
439 sop=GetXMLTreeChild(cc,"SOPNode");
440 if (sop != (XMLTreeInfo *) NULL)
441 {
442 XMLTreeInfo
443 *offset,
444 *power,
445 *slope;
446
447 slope=GetXMLTreeChild(sop,"Slope");
448 if (slope != (XMLTreeInfo *) NULL)
449 {
450 content=GetXMLTreeContent(slope);
451 p=(const char *) content;
452 for (i=0; (*p != '\0') && (i < 3); i++)
453 {
454 GetMagickToken(p,&p,token);
455 if (*token == ',')
456 GetMagickToken(p,&p,token);
457 switch (i)
458 {
cristyc1acd842011-05-19 23:05:47 +0000459 case 0:
460 {
461 color_correction.red.slope=InterpretLocaleValue(token,
462 (char **) NULL);
463 break;
464 }
465 case 1:
466 {
467 color_correction.green.slope=InterpretLocaleValue(token,
468 (char **) NULL);
469 break;
470 }
471 case 2:
472 {
473 color_correction.blue.slope=InterpretLocaleValue(token,
474 (char **) NULL);
475 break;
476 }
cristy3ed852e2009-09-05 21:47:34 +0000477 }
478 }
479 }
480 offset=GetXMLTreeChild(sop,"Offset");
481 if (offset != (XMLTreeInfo *) NULL)
482 {
483 content=GetXMLTreeContent(offset);
484 p=(const char *) content;
485 for (i=0; (*p != '\0') && (i < 3); i++)
486 {
487 GetMagickToken(p,&p,token);
488 if (*token == ',')
489 GetMagickToken(p,&p,token);
490 switch (i)
491 {
cristyc1acd842011-05-19 23:05:47 +0000492 case 0:
493 {
494 color_correction.red.offset=InterpretLocaleValue(token,
495 (char **) NULL);
496 break;
497 }
498 case 1:
499 {
500 color_correction.green.offset=InterpretLocaleValue(token,
501 (char **) NULL);
502 break;
503 }
504 case 2:
505 {
506 color_correction.blue.offset=InterpretLocaleValue(token,
507 (char **) NULL);
508 break;
509 }
cristy3ed852e2009-09-05 21:47:34 +0000510 }
511 }
512 }
513 power=GetXMLTreeChild(sop,"Power");
514 if (power != (XMLTreeInfo *) NULL)
515 {
516 content=GetXMLTreeContent(power);
517 p=(const char *) content;
518 for (i=0; (*p != '\0') && (i < 3); i++)
519 {
520 GetMagickToken(p,&p,token);
521 if (*token == ',')
522 GetMagickToken(p,&p,token);
523 switch (i)
524 {
cristyc1acd842011-05-19 23:05:47 +0000525 case 0:
526 {
527 color_correction.red.power=InterpretLocaleValue(token,
528 (char **) NULL);
529 break;
530 }
531 case 1:
532 {
533 color_correction.green.power=InterpretLocaleValue(token,
534 (char **) NULL);
535 break;
536 }
537 case 2:
538 {
539 color_correction.blue.power=InterpretLocaleValue(token,
540 (char **) NULL);
541 break;
542 }
cristy3ed852e2009-09-05 21:47:34 +0000543 }
544 }
545 }
546 }
547 sat=GetXMLTreeChild(cc,"SATNode");
548 if (sat != (XMLTreeInfo *) NULL)
549 {
550 XMLTreeInfo
551 *saturation;
552
553 saturation=GetXMLTreeChild(sat,"Saturation");
554 if (saturation != (XMLTreeInfo *) NULL)
555 {
556 content=GetXMLTreeContent(saturation);
557 p=(const char *) content;
558 GetMagickToken(p,&p,token);
cristyc1acd842011-05-19 23:05:47 +0000559 color_correction.saturation=InterpretLocaleValue(token,
560 (char **) NULL);
cristy3ed852e2009-09-05 21:47:34 +0000561 }
562 }
563 ccc=DestroyXMLTree(ccc);
564 if (image->debug != MagickFalse)
565 {
566 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
567 " Color Correction Collection:");
568 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000569 " color_correction.red.slope: %g",color_correction.red.slope);
cristy3ed852e2009-09-05 21:47:34 +0000570 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000571 " color_correction.red.offset: %g",color_correction.red.offset);
cristy3ed852e2009-09-05 21:47:34 +0000572 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000573 " color_correction.red.power: %g",color_correction.red.power);
cristy3ed852e2009-09-05 21:47:34 +0000574 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000575 " color_correction.green.slope: %g",color_correction.green.slope);
cristy3ed852e2009-09-05 21:47:34 +0000576 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000577 " color_correction.green.offset: %g",color_correction.green.offset);
cristy3ed852e2009-09-05 21:47:34 +0000578 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000579 " color_correction.green.power: %g",color_correction.green.power);
cristy3ed852e2009-09-05 21:47:34 +0000580 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000581 " color_correction.blue.slope: %g",color_correction.blue.slope);
cristy3ed852e2009-09-05 21:47:34 +0000582 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000583 " color_correction.blue.offset: %g",color_correction.blue.offset);
cristy3ed852e2009-09-05 21:47:34 +0000584 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000585 " color_correction.blue.power: %g",color_correction.blue.power);
cristy3ed852e2009-09-05 21:47:34 +0000586 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000587 " color_correction.saturation: %g",color_correction.saturation);
cristy3ed852e2009-09-05 21:47:34 +0000588 }
589 cdl_map=(PixelPacket *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*cdl_map));
590 if (cdl_map == (PixelPacket *) NULL)
591 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
592 image->filename);
cristyb5d5f722009-11-04 03:03:49 +0000593#if defined(MAGICKCORE_OPENMP_SUPPORT)
594 #pragma omp parallel for schedule(dynamic,4)
cristy3ed852e2009-09-05 21:47:34 +0000595#endif
cristybb503372010-05-27 20:51:26 +0000596 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +0000597 {
cristyce70c172010-01-07 17:15:30 +0000598 cdl_map[i].red=ClampToQuantum((MagickRealType) ScaleMapToQuantum((
cristy3ed852e2009-09-05 21:47:34 +0000599 MagickRealType) (MaxMap*(pow(color_correction.red.slope*i/MaxMap+
600 color_correction.red.offset,color_correction.red.power)))));
cristyce70c172010-01-07 17:15:30 +0000601 cdl_map[i].green=ClampToQuantum((MagickRealType) ScaleMapToQuantum((
cristy3ed852e2009-09-05 21:47:34 +0000602 MagickRealType) (MaxMap*(pow(color_correction.green.slope*i/MaxMap+
603 color_correction.green.offset,color_correction.green.power)))));
cristyce70c172010-01-07 17:15:30 +0000604 cdl_map[i].blue=ClampToQuantum((MagickRealType) ScaleMapToQuantum((
cristy3ed852e2009-09-05 21:47:34 +0000605 MagickRealType) (MaxMap*(pow(color_correction.blue.slope*i/MaxMap+
606 color_correction.blue.offset,color_correction.blue.power)))));
607 }
608 if (image->storage_class == PseudoClass)
609 {
610 /*
611 Apply transfer function to colormap.
612 */
cristyb5d5f722009-11-04 03:03:49 +0000613#if defined(MAGICKCORE_OPENMP_SUPPORT)
614 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000615#endif
cristybb503372010-05-27 20:51:26 +0000616 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +0000617 {
618 double
619 luma;
620
621 luma=0.2126*image->colormap[i].red+0.7152*image->colormap[i].green+
622 0.0722*image->colormap[i].blue;
cristyce70c172010-01-07 17:15:30 +0000623 image->colormap[i].red=ClampToQuantum(luma+color_correction.saturation*
cristy3ed852e2009-09-05 21:47:34 +0000624 cdl_map[ScaleQuantumToMap(image->colormap[i].red)].red-luma);
cristyce70c172010-01-07 17:15:30 +0000625 image->colormap[i].green=ClampToQuantum(luma+
cristy3ed852e2009-09-05 21:47:34 +0000626 color_correction.saturation*cdl_map[ScaleQuantumToMap(
627 image->colormap[i].green)].green-luma);
cristyce70c172010-01-07 17:15:30 +0000628 image->colormap[i].blue=ClampToQuantum(luma+color_correction.saturation*
cristy3ed852e2009-09-05 21:47:34 +0000629 cdl_map[ScaleQuantumToMap(image->colormap[i].blue)].blue-luma);
630 }
631 }
632 /*
633 Apply transfer function to image.
634 */
635 status=MagickTrue;
636 progress=0;
637 exception=(&image->exception);
638 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +0000639#if defined(MAGICKCORE_OPENMP_SUPPORT)
640 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000641#endif
cristybb503372010-05-27 20:51:26 +0000642 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000643 {
644 double
645 luma;
646
cristy4c08aed2011-07-01 19:47:50 +0000647 register Quantum
cristyc47d1f82009-11-26 01:44:43 +0000648 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000649
cristy8d4629b2010-08-30 17:59:46 +0000650 register ssize_t
651 x;
652
cristy3ed852e2009-09-05 21:47:34 +0000653 if (status == MagickFalse)
654 continue;
655 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +0000656 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +0000657 {
658 status=MagickFalse;
659 continue;
660 }
cristybb503372010-05-27 20:51:26 +0000661 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000662 {
cristy4c08aed2011-07-01 19:47:50 +0000663 luma=0.2126*GetPixelRed(image,q)+0.7152*GetPixelGreen(image,q)+0.0722*
664 GetPixelBlue(image,q);
665 SetPixelRed(image,ClampToQuantum(luma+color_correction.saturation*
666 (cdl_map[ScaleQuantumToMap(GetPixelRed(image,q))].red-luma)),q);
667 SetPixelGreen(image,ClampToQuantum(luma+color_correction.saturation*
668 (cdl_map[ScaleQuantumToMap(GetPixelGreen(image,q))].green-luma)),q);
669 SetPixelBlue(image,ClampToQuantum(luma+color_correction.saturation*
670 (cdl_map[ScaleQuantumToMap(GetPixelBlue(image,q))].blue-luma)),q);
671 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +0000672 }
673 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
674 status=MagickFalse;
675 if (image->progress_monitor != (MagickProgressMonitor) NULL)
676 {
677 MagickBooleanType
678 proceed;
679
cristyb5d5f722009-11-04 03:03:49 +0000680#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +0000681 #pragma omp critical (MagickCore_ColorDecisionListImageChannel)
682#endif
683 proceed=SetImageProgress(image,ColorDecisionListCorrectImageTag,
684 progress++,image->rows);
685 if (proceed == MagickFalse)
686 status=MagickFalse;
687 }
688 }
689 image_view=DestroyCacheView(image_view);
690 cdl_map=(PixelPacket *) RelinquishMagickMemory(cdl_map);
691 return(status);
692}
693
694/*
695%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
696% %
697% %
698% %
699% C l u t I m a g e %
700% %
701% %
702% %
703%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
704%
705% ClutImage() replaces each color value in the given image, by using it as an
706% index to lookup a replacement color value in a Color Look UP Table in the
cristycee97112010-05-28 00:44:52 +0000707% form of an image. The values are extracted along a diagonal of the CLUT
cristy3ed852e2009-09-05 21:47:34 +0000708% image so either a horizontal or vertial gradient image can be used.
709%
710% Typically this is used to either re-color a gray-scale image according to a
711% color gradient in the CLUT image, or to perform a freeform histogram
712% (level) adjustment according to the (typically gray-scale) gradient in the
713% CLUT image.
714%
715% When the 'channel' mask includes the matte/alpha transparency channel but
716% one image has no such channel it is assumed that that image is a simple
717% gray-scale image that will effect the alpha channel values, either for
718% gray-scale coloring (with transparent or semi-transparent colors), or
719% a histogram adjustment of existing alpha channel values. If both images
720% have matte channels, direct and normal indexing is applied, which is rarely
721% used.
722%
723% The format of the ClutImage method is:
724%
725% MagickBooleanType ClutImage(Image *image,Image *clut_image)
726% MagickBooleanType ClutImageChannel(Image *image,
727% const ChannelType channel,Image *clut_image)
728%
729% A description of each parameter follows:
730%
731% o image: the image, which is replaced by indexed CLUT values
732%
733% o clut_image: the color lookup table image for replacement color values.
734%
735% o channel: the channel.
736%
737*/
738
739MagickExport MagickBooleanType ClutImage(Image *image,const Image *clut_image)
740{
741 return(ClutImageChannel(image,DefaultChannels,clut_image));
742}
743
744MagickExport MagickBooleanType ClutImageChannel(Image *image,
745 const ChannelType channel,const Image *clut_image)
746{
cristy4c08aed2011-07-01 19:47:50 +0000747#define ClampAlphaPixelComponent(pixel) ClampToQuantum((pixel)->alpha)
748#define ClampBlackPixelComponent(pixel) ClampToQuantum((pixel)->black)
749#define ClampBluePixelComponent(pixel) ClampToQuantum((pixel)->blue)
750#define ClampGreenPixelComponent(pixel) ClampToQuantum((pixel)->green)
751#define ClampRedPixelComponent(pixel) ClampToQuantum((pixel)->red)
cristy3ed852e2009-09-05 21:47:34 +0000752#define ClutImageTag "Clut/Image"
753
cristyfa112112010-01-04 17:48:07 +0000754 CacheView
cristy708333f2011-03-26 01:25:07 +0000755 *clut_view,
cristyfa112112010-01-04 17:48:07 +0000756 *image_view;
757
cristy3ed852e2009-09-05 21:47:34 +0000758 ExceptionInfo
759 *exception;
760
cristy3ed852e2009-09-05 21:47:34 +0000761 MagickBooleanType
762 status;
763
cristybb503372010-05-27 20:51:26 +0000764 MagickOffsetType
765 progress;
766
cristy4c08aed2011-07-01 19:47:50 +0000767 PixelInfo
cristy49f37242011-03-22 18:18:23 +0000768 *clut_map;
769
770 register ssize_t
771 i;
cristy3ed852e2009-09-05 21:47:34 +0000772
cristybb503372010-05-27 20:51:26 +0000773 ssize_t
774 adjust,
775 y;
776
cristy3ed852e2009-09-05 21:47:34 +0000777 assert(image != (Image *) NULL);
778 assert(image->signature == MagickSignature);
779 if (image->debug != MagickFalse)
780 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
781 assert(clut_image != (Image *) NULL);
782 assert(clut_image->signature == MagickSignature);
783 if (SetImageStorageClass(image,DirectClass) == MagickFalse)
784 return(MagickFalse);
cristy4c08aed2011-07-01 19:47:50 +0000785 clut_map=(PixelInfo *) AcquireQuantumMemory(MaxMap+1UL,
cristy49f37242011-03-22 18:18:23 +0000786 sizeof(*clut_map));
cristy4c08aed2011-07-01 19:47:50 +0000787 if (clut_map == (PixelInfo *) NULL)
cristy49f37242011-03-22 18:18:23 +0000788 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
789 image->filename);
cristy3ed852e2009-09-05 21:47:34 +0000790 /*
791 Clut image.
792 */
793 status=MagickTrue;
794 progress=0;
cristybb503372010-05-27 20:51:26 +0000795 adjust=(ssize_t) (clut_image->interpolate == IntegerInterpolatePixel ? 0 : 1);
cristy3ed852e2009-09-05 21:47:34 +0000796 exception=(&image->exception);
cristy708333f2011-03-26 01:25:07 +0000797 clut_view=AcquireCacheView(clut_image);
cristyaf6bc722011-03-25 19:16:14 +0000798#if defined(MAGICKCORE_OPENMP_SUPPORT)
799 #pragma omp parallel for schedule(dynamic,4)
800#endif
cristy49f37242011-03-22 18:18:23 +0000801 for (i=0; i <= (ssize_t) MaxMap; i++)
802 {
cristy4c08aed2011-07-01 19:47:50 +0000803 GetPixelInfo(clut_image,clut_map+i);
804 (void) InterpolatePixelInfo(clut_image,clut_view,
cristy8a7c3e82011-03-26 02:10:53 +0000805 UndefinedInterpolatePixel,QuantumScale*i*(clut_image->columns-adjust),
806 QuantumScale*i*(clut_image->rows-adjust),clut_map+i,exception);
cristy49f37242011-03-22 18:18:23 +0000807 }
cristy708333f2011-03-26 01:25:07 +0000808 clut_view=DestroyCacheView(clut_view);
809 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +0000810#if defined(MAGICKCORE_OPENMP_SUPPORT)
811 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000812#endif
cristybb503372010-05-27 20:51:26 +0000813 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000814 {
cristy4c08aed2011-07-01 19:47:50 +0000815 PixelInfo
cristy3635df22011-03-25 00:16:16 +0000816 pixel;
817
cristy4c08aed2011-07-01 19:47:50 +0000818 register Quantum
cristyc47d1f82009-11-26 01:44:43 +0000819 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000820
cristy8d4629b2010-08-30 17:59:46 +0000821 register ssize_t
822 x;
823
cristy3ed852e2009-09-05 21:47:34 +0000824 if (status == MagickFalse)
825 continue;
826 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +0000827 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +0000828 {
829 status=MagickFalse;
830 continue;
831 }
cristy4c08aed2011-07-01 19:47:50 +0000832 GetPixelInfo(image,&pixel);
cristybb503372010-05-27 20:51:26 +0000833 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000834 {
cristy4c08aed2011-07-01 19:47:50 +0000835 SetPixelInfo(image,q,&pixel);
cristy2b9582a2011-07-04 17:38:56 +0000836 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000837 SetPixelRed(image,ClampRedPixelComponent(clut_map+
838 ScaleQuantumToMap(GetPixelRed(image,q))),q);
cristy2b9582a2011-07-04 17:38:56 +0000839 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000840 SetPixelGreen(image,ClampGreenPixelComponent(clut_map+
841 ScaleQuantumToMap(GetPixelGreen(image,q))),q);
cristy2b9582a2011-07-04 17:38:56 +0000842 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000843 SetPixelBlue(image,ClampBluePixelComponent(clut_map+
844 ScaleQuantumToMap(GetPixelBlue(image,q))),q);
cristy2b9582a2011-07-04 17:38:56 +0000845 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +0000846 (image->colorspace == CMYKColorspace))
847 SetPixelBlack(image,ClampBlackPixelComponent(clut_map+
848 ScaleQuantumToMap(GetPixelBlack(image,q))),q);
cristy2b9582a2011-07-04 17:38:56 +0000849 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy3635df22011-03-25 00:16:16 +0000850 {
851 if (clut_image->matte == MagickFalse)
cristy4c08aed2011-07-01 19:47:50 +0000852 SetPixelAlpha(image,GetPixelInfoIntensity(clut_map+
853 ScaleQuantumToMap((Quantum) GetPixelAlpha(image,q))),q);
cristy3635df22011-03-25 00:16:16 +0000854 else
855 if (image->matte == MagickFalse)
cristy4c08aed2011-07-01 19:47:50 +0000856 SetPixelAlpha(image,ClampAlphaPixelComponent(clut_map+
857 ScaleQuantumToMap((Quantum) GetPixelInfoIntensity(&pixel))),q);
cristy3635df22011-03-25 00:16:16 +0000858 else
cristy4c08aed2011-07-01 19:47:50 +0000859 SetPixelAlpha(image,ClampAlphaPixelComponent(clut_map+
860 ScaleQuantumToMap(GetPixelAlpha(image,q))),q);
cristy3635df22011-03-25 00:16:16 +0000861 }
cristy4c08aed2011-07-01 19:47:50 +0000862 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +0000863 }
864 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
865 status=MagickFalse;
866 if (image->progress_monitor != (MagickProgressMonitor) NULL)
867 {
868 MagickBooleanType
869 proceed;
870
cristyb5d5f722009-11-04 03:03:49 +0000871#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +0000872 #pragma omp critical (MagickCore_ClutImageChannel)
873#endif
874 proceed=SetImageProgress(image,ClutImageTag,progress++,image->rows);
875 if (proceed == MagickFalse)
876 status=MagickFalse;
877 }
878 }
879 image_view=DestroyCacheView(image_view);
cristy4c08aed2011-07-01 19:47:50 +0000880 clut_map=(PixelInfo *) RelinquishMagickMemory(clut_map);
cristy2b9582a2011-07-04 17:38:56 +0000881 if ((clut_image->matte != MagickFalse) &&
882 ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0))
cristy3ed852e2009-09-05 21:47:34 +0000883 (void) SetImageAlphaChannel(image,ActivateAlphaChannel);
884 return(status);
885}
886
887/*
888%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
889% %
890% %
891% %
892% C o n t r a s t I m a g e %
893% %
894% %
895% %
896%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
897%
898% ContrastImage() enhances the intensity differences between the lighter and
899% darker elements of the image. Set sharpen to a MagickTrue to increase the
900% image contrast otherwise the contrast is reduced.
901%
902% The format of the ContrastImage method is:
903%
904% MagickBooleanType ContrastImage(Image *image,
905% const MagickBooleanType sharpen)
906%
907% A description of each parameter follows:
908%
909% o image: the image.
910%
911% o sharpen: Increase or decrease image contrast.
912%
913*/
914
915static void Contrast(const int sign,Quantum *red,Quantum *green,Quantum *blue)
916{
917 double
918 brightness,
919 hue,
920 saturation;
921
922 /*
923 Enhance contrast: dark color become darker, light color become lighter.
924 */
925 assert(red != (Quantum *) NULL);
926 assert(green != (Quantum *) NULL);
927 assert(blue != (Quantum *) NULL);
928 hue=0.0;
929 saturation=0.0;
930 brightness=0.0;
931 ConvertRGBToHSB(*red,*green,*blue,&hue,&saturation,&brightness);
cristy31ac0f02011-03-12 02:04:47 +0000932 brightness+=0.5*sign*(0.5*(sin((double) (MagickPI*(brightness-0.5)))+1.0)-
cristy4205a3c2010-09-12 20:19:59 +0000933 brightness);
cristy3ed852e2009-09-05 21:47:34 +0000934 if (brightness > 1.0)
935 brightness=1.0;
936 else
937 if (brightness < 0.0)
938 brightness=0.0;
939 ConvertHSBToRGB(hue,saturation,brightness,red,green,blue);
940}
941
942MagickExport MagickBooleanType ContrastImage(Image *image,
943 const MagickBooleanType sharpen)
944{
945#define ContrastImageTag "Contrast/Image"
946
cristyc4c8d132010-01-07 01:58:38 +0000947 CacheView
948 *image_view;
949
cristy3ed852e2009-09-05 21:47:34 +0000950 ExceptionInfo
951 *exception;
952
953 int
954 sign;
955
cristy3ed852e2009-09-05 21:47:34 +0000956 MagickBooleanType
957 status;
958
cristybb503372010-05-27 20:51:26 +0000959 MagickOffsetType
960 progress;
961
962 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000963 i;
964
cristybb503372010-05-27 20:51:26 +0000965 ssize_t
966 y;
967
cristy3ed852e2009-09-05 21:47:34 +0000968 assert(image != (Image *) NULL);
969 assert(image->signature == MagickSignature);
970 if (image->debug != MagickFalse)
971 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
972 sign=sharpen != MagickFalse ? 1 : -1;
973 if (image->storage_class == PseudoClass)
974 {
975 /*
976 Contrast enhance colormap.
977 */
cristybb503372010-05-27 20:51:26 +0000978 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +0000979 Contrast(sign,&image->colormap[i].red,&image->colormap[i].green,
980 &image->colormap[i].blue);
981 }
982 /*
983 Contrast enhance image.
984 */
985 status=MagickTrue;
986 progress=0;
987 exception=(&image->exception);
988 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +0000989#if defined(MAGICKCORE_OPENMP_SUPPORT)
990 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000991#endif
cristybb503372010-05-27 20:51:26 +0000992 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000993 {
cristy5afeab82011-04-30 01:30:09 +0000994 Quantum
995 blue,
996 green,
997 red;
998
cristy4c08aed2011-07-01 19:47:50 +0000999 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00001000 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001001
cristy8d4629b2010-08-30 17:59:46 +00001002 register ssize_t
1003 x;
1004
cristy3ed852e2009-09-05 21:47:34 +00001005 if (status == MagickFalse)
1006 continue;
1007 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00001008 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001009 {
1010 status=MagickFalse;
1011 continue;
1012 }
cristybb503372010-05-27 20:51:26 +00001013 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001014 {
cristy4c08aed2011-07-01 19:47:50 +00001015 red=GetPixelRed(image,q);
1016 green=GetPixelGreen(image,q);
1017 blue=GetPixelBlue(image,q);
cristy5afeab82011-04-30 01:30:09 +00001018 Contrast(sign,&red,&green,&blue);
cristy4c08aed2011-07-01 19:47:50 +00001019 SetPixelRed(image,red,q);
1020 SetPixelGreen(image,green,q);
1021 SetPixelBlue(image,blue,q);
1022 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00001023 }
1024 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
1025 status=MagickFalse;
1026 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1027 {
1028 MagickBooleanType
1029 proceed;
1030
cristyb5d5f722009-11-04 03:03:49 +00001031#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00001032 #pragma omp critical (MagickCore_ContrastImage)
1033#endif
1034 proceed=SetImageProgress(image,ContrastImageTag,progress++,image->rows);
1035 if (proceed == MagickFalse)
1036 status=MagickFalse;
1037 }
1038 }
1039 image_view=DestroyCacheView(image_view);
1040 return(status);
1041}
1042
1043/*
1044%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1045% %
1046% %
1047% %
1048% C o n t r a s t S t r e t c h I m a g e %
1049% %
1050% %
1051% %
1052%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1053%
1054% The ContrastStretchImage() is a simple image enhancement technique that
1055% attempts to improve the contrast in an image by `stretching' the range of
1056% intensity values it contains to span a desired range of values. It differs
1057% from the more sophisticated histogram equalization in that it can only
1058% apply % a linear scaling function to the image pixel values. As a result
1059% the `enhancement' is less harsh.
1060%
1061% The format of the ContrastStretchImage method is:
1062%
1063% MagickBooleanType ContrastStretchImage(Image *image,
1064% const char *levels)
1065% MagickBooleanType ContrastStretchImageChannel(Image *image,
cristybb503372010-05-27 20:51:26 +00001066% const size_t channel,const double black_point,
cristy3ed852e2009-09-05 21:47:34 +00001067% const double white_point)
1068%
1069% A description of each parameter follows:
1070%
1071% o image: the image.
1072%
1073% o channel: the channel.
1074%
1075% o black_point: the black point.
1076%
1077% o white_point: the white point.
1078%
1079% o levels: Specify the levels where the black and white points have the
1080% range of 0 to number-of-pixels (e.g. 1%, 10x90%, etc.).
1081%
1082*/
1083
1084MagickExport MagickBooleanType ContrastStretchImage(Image *image,
1085 const char *levels)
1086{
1087 double
1088 black_point,
1089 white_point;
1090
1091 GeometryInfo
1092 geometry_info;
1093
1094 MagickBooleanType
1095 status;
1096
1097 MagickStatusType
1098 flags;
1099
1100 /*
1101 Parse levels.
1102 */
1103 if (levels == (char *) NULL)
1104 return(MagickFalse);
1105 flags=ParseGeometry(levels,&geometry_info);
1106 black_point=geometry_info.rho;
1107 white_point=(double) image->columns*image->rows;
1108 if ((flags & SigmaValue) != 0)
1109 white_point=geometry_info.sigma;
1110 if ((flags & PercentValue) != 0)
1111 {
1112 black_point*=(double) QuantumRange/100.0;
1113 white_point*=(double) QuantumRange/100.0;
1114 }
1115 if ((flags & SigmaValue) == 0)
1116 white_point=(double) image->columns*image->rows-black_point;
1117 status=ContrastStretchImageChannel(image,DefaultChannels,black_point,
1118 white_point);
1119 return(status);
1120}
1121
1122MagickExport MagickBooleanType ContrastStretchImageChannel(Image *image,
1123 const ChannelType channel,const double black_point,const double white_point)
1124{
1125#define MaxRange(color) ((MagickRealType) ScaleQuantumToMap((Quantum) (color)))
1126#define ContrastStretchImageTag "ContrastStretch/Image"
1127
cristyc4c8d132010-01-07 01:58:38 +00001128 CacheView
1129 *image_view;
1130
cristy3ed852e2009-09-05 21:47:34 +00001131 double
1132 intensity;
1133
1134 ExceptionInfo
1135 *exception;
1136
cristy3ed852e2009-09-05 21:47:34 +00001137 MagickBooleanType
1138 status;
1139
cristybb503372010-05-27 20:51:26 +00001140 MagickOffsetType
1141 progress;
1142
cristy4c08aed2011-07-01 19:47:50 +00001143 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00001144 black,
1145 *histogram,
1146 *stretch_map,
1147 white;
1148
cristybb503372010-05-27 20:51:26 +00001149 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001150 i;
1151
cristybb503372010-05-27 20:51:26 +00001152 ssize_t
1153 y;
1154
cristy3ed852e2009-09-05 21:47:34 +00001155 /*
1156 Allocate histogram and stretch map.
1157 */
1158 assert(image != (Image *) NULL);
1159 assert(image->signature == MagickSignature);
1160 if (image->debug != MagickFalse)
1161 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
cristy4c08aed2011-07-01 19:47:50 +00001162 histogram=(PixelInfo *) AcquireQuantumMemory(MaxMap+1UL,
cristy3ed852e2009-09-05 21:47:34 +00001163 sizeof(*histogram));
cristy4c08aed2011-07-01 19:47:50 +00001164 stretch_map=(PixelInfo *) AcquireQuantumMemory(MaxMap+1UL,
cristy3ed852e2009-09-05 21:47:34 +00001165 sizeof(*stretch_map));
cristy4c08aed2011-07-01 19:47:50 +00001166 if ((histogram == (PixelInfo *) NULL) ||
1167 (stretch_map == (PixelInfo *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00001168 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
1169 image->filename);
1170 /*
1171 Form histogram.
1172 */
1173 status=MagickTrue;
1174 exception=(&image->exception);
1175 (void) ResetMagickMemory(histogram,0,(MaxMap+1)*sizeof(*histogram));
1176 image_view=AcquireCacheView(image);
cristybb503372010-05-27 20:51:26 +00001177 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001178 {
cristy4c08aed2011-07-01 19:47:50 +00001179 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00001180 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00001181
cristybb503372010-05-27 20:51:26 +00001182 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001183 x;
1184
1185 if (status == MagickFalse)
1186 continue;
1187 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00001188 if (p == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001189 {
1190 status=MagickFalse;
1191 continue;
1192 }
cristy3ed852e2009-09-05 21:47:34 +00001193 if (channel == DefaultChannels)
cristybb503372010-05-27 20:51:26 +00001194 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001195 {
1196 Quantum
1197 intensity;
1198
cristy4c08aed2011-07-01 19:47:50 +00001199 intensity=GetPixelIntensity(image,p);
cristy3ed852e2009-09-05 21:47:34 +00001200 histogram[ScaleQuantumToMap(intensity)].red++;
1201 histogram[ScaleQuantumToMap(intensity)].green++;
1202 histogram[ScaleQuantumToMap(intensity)].blue++;
cristy4c08aed2011-07-01 19:47:50 +00001203 histogram[ScaleQuantumToMap(intensity)].black++;
1204 p+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00001205 }
1206 else
cristybb503372010-05-27 20:51:26 +00001207 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001208 {
cristy2b9582a2011-07-04 17:38:56 +00001209 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001210 histogram[ScaleQuantumToMap(GetPixelRed(image,p))].red++;
cristy2b9582a2011-07-04 17:38:56 +00001211 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001212 histogram[ScaleQuantumToMap(GetPixelGreen(image,p))].green++;
cristy2b9582a2011-07-04 17:38:56 +00001213 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001214 histogram[ScaleQuantumToMap(GetPixelBlue(image,p))].blue++;
cristy2b9582a2011-07-04 17:38:56 +00001215 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00001216 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +00001217 histogram[ScaleQuantumToMap(GetPixelBlack(image,p))].black++;
cristy2b9582a2011-07-04 17:38:56 +00001218 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001219 histogram[ScaleQuantumToMap(GetPixelAlpha(image,p))].alpha++;
1220 p+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00001221 }
1222 }
1223 /*
1224 Find the histogram boundaries by locating the black/white levels.
1225 */
1226 black.red=0.0;
1227 white.red=MaxRange(QuantumRange);
cristy2b9582a2011-07-04 17:38:56 +00001228 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001229 {
1230 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00001231 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +00001232 {
1233 intensity+=histogram[i].red;
1234 if (intensity > black_point)
1235 break;
1236 }
1237 black.red=(MagickRealType) i;
1238 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00001239 for (i=(ssize_t) MaxMap; i != 0; i--)
cristy3ed852e2009-09-05 21:47:34 +00001240 {
1241 intensity+=histogram[i].red;
1242 if (intensity > ((double) image->columns*image->rows-white_point))
1243 break;
1244 }
1245 white.red=(MagickRealType) i;
1246 }
1247 black.green=0.0;
1248 white.green=MaxRange(QuantumRange);
cristy2b9582a2011-07-04 17:38:56 +00001249 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001250 {
1251 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00001252 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +00001253 {
1254 intensity+=histogram[i].green;
1255 if (intensity > black_point)
1256 break;
1257 }
1258 black.green=(MagickRealType) i;
1259 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00001260 for (i=(ssize_t) MaxMap; i != 0; i--)
cristy3ed852e2009-09-05 21:47:34 +00001261 {
1262 intensity+=histogram[i].green;
1263 if (intensity > ((double) image->columns*image->rows-white_point))
1264 break;
1265 }
1266 white.green=(MagickRealType) i;
1267 }
1268 black.blue=0.0;
1269 white.blue=MaxRange(QuantumRange);
cristy2b9582a2011-07-04 17:38:56 +00001270 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001271 {
1272 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00001273 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +00001274 {
1275 intensity+=histogram[i].blue;
1276 if (intensity > black_point)
1277 break;
1278 }
1279 black.blue=(MagickRealType) i;
1280 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00001281 for (i=(ssize_t) MaxMap; i != 0; i--)
cristy3ed852e2009-09-05 21:47:34 +00001282 {
1283 intensity+=histogram[i].blue;
1284 if (intensity > ((double) image->columns*image->rows-white_point))
1285 break;
1286 }
1287 white.blue=(MagickRealType) i;
1288 }
cristy4c08aed2011-07-01 19:47:50 +00001289 black.alpha=0.0;
1290 white.alpha=MaxRange(QuantumRange);
cristy2b9582a2011-07-04 17:38:56 +00001291 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001292 {
1293 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00001294 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +00001295 {
cristy4c08aed2011-07-01 19:47:50 +00001296 intensity+=histogram[i].alpha;
cristy3ed852e2009-09-05 21:47:34 +00001297 if (intensity > black_point)
1298 break;
1299 }
cristy4c08aed2011-07-01 19:47:50 +00001300 black.alpha=(MagickRealType) i;
cristy3ed852e2009-09-05 21:47:34 +00001301 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00001302 for (i=(ssize_t) MaxMap; i != 0; i--)
cristy3ed852e2009-09-05 21:47:34 +00001303 {
cristy4c08aed2011-07-01 19:47:50 +00001304 intensity+=histogram[i].alpha;
cristy3ed852e2009-09-05 21:47:34 +00001305 if (intensity > ((double) image->columns*image->rows-white_point))
1306 break;
1307 }
cristy4c08aed2011-07-01 19:47:50 +00001308 white.alpha=(MagickRealType) i;
cristy3ed852e2009-09-05 21:47:34 +00001309 }
cristy4c08aed2011-07-01 19:47:50 +00001310 black.black=0.0;
1311 white.black=MaxRange(QuantumRange);
cristy2b9582a2011-07-04 17:38:56 +00001312 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) && (image->colorspace == CMYKColorspace))
cristy3ed852e2009-09-05 21:47:34 +00001313 {
1314 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00001315 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +00001316 {
cristy4c08aed2011-07-01 19:47:50 +00001317 intensity+=histogram[i].black;
cristy3ed852e2009-09-05 21:47:34 +00001318 if (intensity > black_point)
1319 break;
1320 }
cristy4c08aed2011-07-01 19:47:50 +00001321 black.black=(MagickRealType) i;
cristy3ed852e2009-09-05 21:47:34 +00001322 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00001323 for (i=(ssize_t) MaxMap; i != 0; i--)
cristy3ed852e2009-09-05 21:47:34 +00001324 {
cristy4c08aed2011-07-01 19:47:50 +00001325 intensity+=histogram[i].black;
cristy3ed852e2009-09-05 21:47:34 +00001326 if (intensity > ((double) image->columns*image->rows-white_point))
1327 break;
1328 }
cristy4c08aed2011-07-01 19:47:50 +00001329 white.black=(MagickRealType) i;
cristy3ed852e2009-09-05 21:47:34 +00001330 }
cristy4c08aed2011-07-01 19:47:50 +00001331 histogram=(PixelInfo *) RelinquishMagickMemory(histogram);
cristy3ed852e2009-09-05 21:47:34 +00001332 /*
1333 Stretch the histogram to create the stretched image mapping.
1334 */
1335 (void) ResetMagickMemory(stretch_map,0,(MaxMap+1)*sizeof(*stretch_map));
cristyb5d5f722009-11-04 03:03:49 +00001336#if defined(MAGICKCORE_OPENMP_SUPPORT)
1337 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001338#endif
cristybb503372010-05-27 20:51:26 +00001339 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +00001340 {
cristy2b9582a2011-07-04 17:38:56 +00001341 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001342 {
cristybb503372010-05-27 20:51:26 +00001343 if (i < (ssize_t) black.red)
cristy3ed852e2009-09-05 21:47:34 +00001344 stretch_map[i].red=0.0;
1345 else
cristybb503372010-05-27 20:51:26 +00001346 if (i > (ssize_t) white.red)
cristy3ed852e2009-09-05 21:47:34 +00001347 stretch_map[i].red=(MagickRealType) QuantumRange;
1348 else
1349 if (black.red != white.red)
1350 stretch_map[i].red=(MagickRealType) ScaleMapToQuantum(
1351 (MagickRealType) (MaxMap*(i-black.red)/(white.red-black.red)));
1352 }
cristy2b9582a2011-07-04 17:38:56 +00001353 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001354 {
cristybb503372010-05-27 20:51:26 +00001355 if (i < (ssize_t) black.green)
cristy3ed852e2009-09-05 21:47:34 +00001356 stretch_map[i].green=0.0;
1357 else
cristybb503372010-05-27 20:51:26 +00001358 if (i > (ssize_t) white.green)
cristy3ed852e2009-09-05 21:47:34 +00001359 stretch_map[i].green=(MagickRealType) QuantumRange;
1360 else
1361 if (black.green != white.green)
1362 stretch_map[i].green=(MagickRealType) ScaleMapToQuantum(
1363 (MagickRealType) (MaxMap*(i-black.green)/(white.green-
1364 black.green)));
1365 }
cristy2b9582a2011-07-04 17:38:56 +00001366 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001367 {
cristybb503372010-05-27 20:51:26 +00001368 if (i < (ssize_t) black.blue)
cristy3ed852e2009-09-05 21:47:34 +00001369 stretch_map[i].blue=0.0;
1370 else
cristybb503372010-05-27 20:51:26 +00001371 if (i > (ssize_t) white.blue)
cristy3ed852e2009-09-05 21:47:34 +00001372 stretch_map[i].blue=(MagickRealType) QuantumRange;
1373 else
1374 if (black.blue != white.blue)
1375 stretch_map[i].blue=(MagickRealType) ScaleMapToQuantum(
1376 (MagickRealType) (MaxMap*(i-black.blue)/(white.blue-
1377 black.blue)));
1378 }
cristy2b9582a2011-07-04 17:38:56 +00001379 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001380 {
cristy4c08aed2011-07-01 19:47:50 +00001381 if (i < (ssize_t) black.alpha)
1382 stretch_map[i].alpha=0.0;
cristy3ed852e2009-09-05 21:47:34 +00001383 else
cristy4c08aed2011-07-01 19:47:50 +00001384 if (i > (ssize_t) white.alpha)
1385 stretch_map[i].alpha=(MagickRealType) QuantumRange;
cristy3ed852e2009-09-05 21:47:34 +00001386 else
cristy4c08aed2011-07-01 19:47:50 +00001387 if (black.alpha != white.alpha)
1388 stretch_map[i].alpha=(MagickRealType) ScaleMapToQuantum(
1389 (MagickRealType) (MaxMap*(i-black.alpha)/(white.alpha-
1390 black.alpha)));
cristy3ed852e2009-09-05 21:47:34 +00001391 }
cristy2b9582a2011-07-04 17:38:56 +00001392 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00001393 (image->colorspace == CMYKColorspace))
1394 {
cristy4c08aed2011-07-01 19:47:50 +00001395 if (i < (ssize_t) black.black)
1396 stretch_map[i].black=0.0;
cristy3ed852e2009-09-05 21:47:34 +00001397 else
cristy4c08aed2011-07-01 19:47:50 +00001398 if (i > (ssize_t) white.black)
1399 stretch_map[i].black=(MagickRealType) QuantumRange;
cristy3ed852e2009-09-05 21:47:34 +00001400 else
cristy4c08aed2011-07-01 19:47:50 +00001401 if (black.black != white.black)
1402 stretch_map[i].black=(MagickRealType) ScaleMapToQuantum(
1403 (MagickRealType) (MaxMap*(i-black.black)/(white.black-
1404 black.black)));
cristy3ed852e2009-09-05 21:47:34 +00001405 }
1406 }
1407 /*
1408 Stretch the image.
1409 */
cristy2b9582a2011-07-04 17:38:56 +00001410 if (((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0) || (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00001411 (image->colorspace == CMYKColorspace)))
1412 image->storage_class=DirectClass;
1413 if (image->storage_class == PseudoClass)
1414 {
1415 /*
1416 Stretch colormap.
1417 */
cristyb5d5f722009-11-04 03:03:49 +00001418#if defined(MAGICKCORE_OPENMP_SUPPORT)
1419 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001420#endif
cristybb503372010-05-27 20:51:26 +00001421 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00001422 {
cristy2b9582a2011-07-04 17:38:56 +00001423 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001424 {
1425 if (black.red != white.red)
cristyce70c172010-01-07 17:15:30 +00001426 image->colormap[i].red=ClampToQuantum(stretch_map[
cristy3ed852e2009-09-05 21:47:34 +00001427 ScaleQuantumToMap(image->colormap[i].red)].red);
1428 }
cristy2b9582a2011-07-04 17:38:56 +00001429 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001430 {
1431 if (black.green != white.green)
cristyce70c172010-01-07 17:15:30 +00001432 image->colormap[i].green=ClampToQuantum(stretch_map[
cristy3ed852e2009-09-05 21:47:34 +00001433 ScaleQuantumToMap(image->colormap[i].green)].green);
1434 }
cristy2b9582a2011-07-04 17:38:56 +00001435 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001436 {
1437 if (black.blue != white.blue)
cristyce70c172010-01-07 17:15:30 +00001438 image->colormap[i].blue=ClampToQuantum(stretch_map[
cristy3ed852e2009-09-05 21:47:34 +00001439 ScaleQuantumToMap(image->colormap[i].blue)].blue);
1440 }
cristy2b9582a2011-07-04 17:38:56 +00001441 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001442 {
cristy4c08aed2011-07-01 19:47:50 +00001443 if (black.alpha != white.alpha)
1444 image->colormap[i].alpha=ClampToQuantum(stretch_map[
1445 ScaleQuantumToMap(image->colormap[i].alpha)].alpha);
cristy3ed852e2009-09-05 21:47:34 +00001446 }
1447 }
1448 }
1449 /*
1450 Stretch image.
1451 */
1452 status=MagickTrue;
1453 progress=0;
cristyb5d5f722009-11-04 03:03:49 +00001454#if defined(MAGICKCORE_OPENMP_SUPPORT)
1455 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001456#endif
cristybb503372010-05-27 20:51:26 +00001457 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001458 {
cristy4c08aed2011-07-01 19:47:50 +00001459 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00001460 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001461
cristy8d4629b2010-08-30 17:59:46 +00001462 register ssize_t
1463 x;
1464
cristy3ed852e2009-09-05 21:47:34 +00001465 if (status == MagickFalse)
1466 continue;
1467 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00001468 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001469 {
1470 status=MagickFalse;
1471 continue;
1472 }
cristybb503372010-05-27 20:51:26 +00001473 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001474 {
cristy2b9582a2011-07-04 17:38:56 +00001475 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001476 {
1477 if (black.red != white.red)
cristy4c08aed2011-07-01 19:47:50 +00001478 SetPixelRed(image,ClampToQuantum(stretch_map[ScaleQuantumToMap(
1479 GetPixelRed(image,q))].red),q);
cristy3ed852e2009-09-05 21:47:34 +00001480 }
cristy2b9582a2011-07-04 17:38:56 +00001481 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001482 {
1483 if (black.green != white.green)
cristy4c08aed2011-07-01 19:47:50 +00001484 SetPixelGreen(image,ClampToQuantum(stretch_map[ScaleQuantumToMap(
1485 GetPixelGreen(image,q))].green),q);
cristy3ed852e2009-09-05 21:47:34 +00001486 }
cristy2b9582a2011-07-04 17:38:56 +00001487 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001488 {
1489 if (black.blue != white.blue)
cristy4c08aed2011-07-01 19:47:50 +00001490 SetPixelBlue(image,ClampToQuantum(stretch_map[ScaleQuantumToMap(
1491 GetPixelBlue(image,q))].blue),q);
1492 }
cristy2b9582a2011-07-04 17:38:56 +00001493 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +00001494 (image->colorspace == CMYKColorspace))
1495 {
1496 if (black.black != white.black)
1497 SetPixelBlack(image,ClampToQuantum(stretch_map[ScaleQuantumToMap(
1498 GetPixelBlack(image,q))].black),q);
cristy3ed852e2009-09-05 21:47:34 +00001499 }
cristy2b9582a2011-07-04 17:38:56 +00001500 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001501 {
cristy4c08aed2011-07-01 19:47:50 +00001502 if (black.alpha != white.alpha)
1503 SetPixelAlpha(image,ClampToQuantum(stretch_map[ScaleQuantumToMap(
1504 GetPixelAlpha(image,q))].alpha),q);
cristy3ed852e2009-09-05 21:47:34 +00001505 }
cristy4c08aed2011-07-01 19:47:50 +00001506 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00001507 }
1508 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
1509 status=MagickFalse;
1510 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1511 {
1512 MagickBooleanType
1513 proceed;
1514
cristyb5d5f722009-11-04 03:03:49 +00001515#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00001516 #pragma omp critical (MagickCore_ContrastStretchImageChannel)
1517#endif
1518 proceed=SetImageProgress(image,ContrastStretchImageTag,progress++,
1519 image->rows);
1520 if (proceed == MagickFalse)
1521 status=MagickFalse;
1522 }
1523 }
1524 image_view=DestroyCacheView(image_view);
cristy4c08aed2011-07-01 19:47:50 +00001525 stretch_map=(PixelInfo *) RelinquishMagickMemory(stretch_map);
cristy3ed852e2009-09-05 21:47:34 +00001526 return(status);
1527}
1528
1529/*
1530%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1531% %
1532% %
1533% %
1534% E n h a n c e I m a g e %
1535% %
1536% %
1537% %
1538%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1539%
1540% EnhanceImage() applies a digital filter that improves the quality of a
1541% noisy image.
1542%
1543% The format of the EnhanceImage method is:
1544%
1545% Image *EnhanceImage(const Image *image,ExceptionInfo *exception)
1546%
1547% A description of each parameter follows:
1548%
1549% o image: the image.
1550%
1551% o exception: return any errors or warnings in this structure.
1552%
1553*/
1554MagickExport Image *EnhanceImage(const Image *image,ExceptionInfo *exception)
1555{
1556#define Enhance(weight) \
cristy4c08aed2011-07-01 19:47:50 +00001557 mean=((MagickRealType) GetPixelRed(image,r)+pixel.red)/2; \
1558 distance=(MagickRealType) GetPixelRed(image,r)-(MagickRealType) pixel.red; \
cristy3ed852e2009-09-05 21:47:34 +00001559 distance_squared=QuantumScale*(2.0*((MagickRealType) QuantumRange+1.0)+ \
1560 mean)*distance*distance; \
cristy4c08aed2011-07-01 19:47:50 +00001561 mean=((MagickRealType) GetPixelGreen(image,r)+pixel.green)/2; \
1562 distance=(MagickRealType) GetPixelGreen(image,r)- \
1563 (MagickRealType) pixel.green; \
cristy3ed852e2009-09-05 21:47:34 +00001564 distance_squared+=4.0*distance*distance; \
cristy4c08aed2011-07-01 19:47:50 +00001565 mean=((MagickRealType) GetPixelBlue(image,r)+pixel.blue)/2; \
1566 distance=(MagickRealType) GetPixelBlue(image,r)- \
1567 (MagickRealType) pixel.blue; \
cristy3ed852e2009-09-05 21:47:34 +00001568 distance_squared+=QuantumScale*(3.0*((MagickRealType) \
1569 QuantumRange+1.0)-1.0-mean)*distance*distance; \
cristy4c08aed2011-07-01 19:47:50 +00001570 mean=((MagickRealType) GetPixelAlpha(image,r)+pixel.alpha)/2; \
1571 distance=(MagickRealType) GetPixelAlpha(image,r)-(MagickRealType) pixel.alpha; \
cristy3ed852e2009-09-05 21:47:34 +00001572 distance_squared+=QuantumScale*(3.0*((MagickRealType) \
1573 QuantumRange+1.0)-1.0-mean)*distance*distance; \
1574 if (distance_squared < ((MagickRealType) QuantumRange*(MagickRealType) \
1575 QuantumRange/25.0f)) \
1576 { \
cristy4c08aed2011-07-01 19:47:50 +00001577 aggregate.red+=(weight)*GetPixelRed(image,r); \
1578 aggregate.green+=(weight)*GetPixelGreen(image,r); \
1579 aggregate.blue+=(weight)*GetPixelBlue(image,r); \
1580 aggregate.alpha+=(weight)*GetPixelAlpha(image,r); \
cristy3ed852e2009-09-05 21:47:34 +00001581 total_weight+=(weight); \
1582 } \
1583 r++;
1584#define EnhanceImageTag "Enhance/Image"
1585
cristyc4c8d132010-01-07 01:58:38 +00001586 CacheView
1587 *enhance_view,
1588 *image_view;
1589
cristy3ed852e2009-09-05 21:47:34 +00001590 Image
1591 *enhance_image;
1592
cristy3ed852e2009-09-05 21:47:34 +00001593 MagickBooleanType
1594 status;
1595
cristybb503372010-05-27 20:51:26 +00001596 MagickOffsetType
1597 progress;
1598
cristy4c08aed2011-07-01 19:47:50 +00001599 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00001600 zero;
1601
cristybb503372010-05-27 20:51:26 +00001602 ssize_t
1603 y;
1604
cristy3ed852e2009-09-05 21:47:34 +00001605 /*
1606 Initialize enhanced image attributes.
1607 */
1608 assert(image != (const Image *) NULL);
1609 assert(image->signature == MagickSignature);
1610 if (image->debug != MagickFalse)
1611 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1612 assert(exception != (ExceptionInfo *) NULL);
1613 assert(exception->signature == MagickSignature);
1614 if ((image->columns < 5) || (image->rows < 5))
1615 return((Image *) NULL);
1616 enhance_image=CloneImage(image,image->columns,image->rows,MagickTrue,
1617 exception);
1618 if (enhance_image == (Image *) NULL)
1619 return((Image *) NULL);
1620 if (SetImageStorageClass(enhance_image,DirectClass) == MagickFalse)
1621 {
1622 InheritException(exception,&enhance_image->exception);
1623 enhance_image=DestroyImage(enhance_image);
1624 return((Image *) NULL);
1625 }
1626 /*
1627 Enhance image.
1628 */
1629 status=MagickTrue;
1630 progress=0;
1631 (void) ResetMagickMemory(&zero,0,sizeof(zero));
1632 image_view=AcquireCacheView(image);
1633 enhance_view=AcquireCacheView(enhance_image);
cristyb5d5f722009-11-04 03:03:49 +00001634#if defined(MAGICKCORE_OPENMP_SUPPORT)
1635 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001636#endif
cristybb503372010-05-27 20:51:26 +00001637 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001638 {
cristy4c08aed2011-07-01 19:47:50 +00001639 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00001640 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00001641
cristy4c08aed2011-07-01 19:47:50 +00001642 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00001643 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001644
cristy8d4629b2010-08-30 17:59:46 +00001645 register ssize_t
1646 x;
1647
cristy3ed852e2009-09-05 21:47:34 +00001648 /*
1649 Read another scan line.
1650 */
1651 if (status == MagickFalse)
1652 continue;
1653 p=GetCacheViewVirtualPixels(image_view,-2,y-2,image->columns+4,5,exception);
1654 q=QueueCacheViewAuthenticPixels(enhance_view,0,y,enhance_image->columns,1,
1655 exception);
cristy4c08aed2011-07-01 19:47:50 +00001656 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00001657 {
1658 status=MagickFalse;
1659 continue;
1660 }
cristybb503372010-05-27 20:51:26 +00001661 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001662 {
cristy4c08aed2011-07-01 19:47:50 +00001663 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00001664 aggregate;
1665
1666 MagickRealType
1667 distance,
1668 distance_squared,
1669 mean,
1670 total_weight;
1671
1672 PixelPacket
1673 pixel;
1674
cristy4c08aed2011-07-01 19:47:50 +00001675 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00001676 *restrict r;
cristy3ed852e2009-09-05 21:47:34 +00001677
1678 /*
1679 Compute weighted average of target pixel color components.
1680 */
1681 aggregate=zero;
1682 total_weight=0.0;
1683 r=p+2*(image->columns+4)+2;
cristy4c08aed2011-07-01 19:47:50 +00001684 GetPixelPacket(image,r,&pixel);
cristy3ed852e2009-09-05 21:47:34 +00001685 r=p;
1686 Enhance(5.0); Enhance(8.0); Enhance(10.0); Enhance(8.0); Enhance(5.0);
1687 r=p+(image->columns+4);
1688 Enhance(8.0); Enhance(20.0); Enhance(40.0); Enhance(20.0); Enhance(8.0);
1689 r=p+2*(image->columns+4);
1690 Enhance(10.0); Enhance(40.0); Enhance(80.0); Enhance(40.0); Enhance(10.0);
1691 r=p+3*(image->columns+4);
1692 Enhance(8.0); Enhance(20.0); Enhance(40.0); Enhance(20.0); Enhance(8.0);
1693 r=p+4*(image->columns+4);
1694 Enhance(5.0); Enhance(8.0); Enhance(10.0); Enhance(8.0); Enhance(5.0);
cristy4c08aed2011-07-01 19:47:50 +00001695 SetPixelRed(enhance_image,(Quantum) ((aggregate.red+
1696 (total_weight/2)-1)/total_weight),q);
1697 SetPixelGreen(enhance_image,(Quantum) ((aggregate.green+
1698 (total_weight/2)-1)/total_weight),q);
1699 SetPixelBlue(enhance_image,(Quantum) ((aggregate.blue+
1700 (total_weight/2)-1)/total_weight),q);
1701 SetPixelAlpha(enhance_image,(Quantum) ((aggregate.alpha+
1702 (total_weight/2)-1)/total_weight),q);
1703 p+=GetPixelChannels(image);
1704 q+=GetPixelChannels(enhance_image);
cristy3ed852e2009-09-05 21:47:34 +00001705 }
1706 if (SyncCacheViewAuthenticPixels(enhance_view,exception) == MagickFalse)
1707 status=MagickFalse;
1708 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1709 {
1710 MagickBooleanType
1711 proceed;
1712
cristyb5d5f722009-11-04 03:03:49 +00001713#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00001714 #pragma omp critical (MagickCore_EnhanceImage)
1715#endif
1716 proceed=SetImageProgress(image,EnhanceImageTag,progress++,image->rows);
1717 if (proceed == MagickFalse)
1718 status=MagickFalse;
1719 }
1720 }
1721 enhance_view=DestroyCacheView(enhance_view);
1722 image_view=DestroyCacheView(image_view);
1723 return(enhance_image);
1724}
1725
1726/*
1727%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1728% %
1729% %
1730% %
1731% E q u a l i z e I m a g e %
1732% %
1733% %
1734% %
1735%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1736%
1737% EqualizeImage() applies a histogram equalization to the image.
1738%
1739% The format of the EqualizeImage method is:
1740%
1741% MagickBooleanType EqualizeImage(Image *image)
1742% MagickBooleanType EqualizeImageChannel(Image *image,
1743% const ChannelType channel)
1744%
1745% A description of each parameter follows:
1746%
1747% o image: the image.
1748%
1749% o channel: the channel.
1750%
1751*/
1752
1753MagickExport MagickBooleanType EqualizeImage(Image *image)
1754{
1755 return(EqualizeImageChannel(image,DefaultChannels));
1756}
1757
1758MagickExport MagickBooleanType EqualizeImageChannel(Image *image,
1759 const ChannelType channel)
1760{
1761#define EqualizeImageTag "Equalize/Image"
1762
cristyc4c8d132010-01-07 01:58:38 +00001763 CacheView
1764 *image_view;
1765
cristy3ed852e2009-09-05 21:47:34 +00001766 ExceptionInfo
1767 *exception;
1768
cristy3ed852e2009-09-05 21:47:34 +00001769 MagickBooleanType
1770 status;
1771
cristybb503372010-05-27 20:51:26 +00001772 MagickOffsetType
1773 progress;
1774
cristy4c08aed2011-07-01 19:47:50 +00001775 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00001776 black,
1777 *equalize_map,
1778 *histogram,
1779 intensity,
1780 *map,
1781 white;
1782
cristybb503372010-05-27 20:51:26 +00001783 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001784 i;
1785
cristybb503372010-05-27 20:51:26 +00001786 ssize_t
1787 y;
1788
cristy3ed852e2009-09-05 21:47:34 +00001789 /*
1790 Allocate and initialize histogram arrays.
1791 */
1792 assert(image != (Image *) NULL);
1793 assert(image->signature == MagickSignature);
1794 if (image->debug != MagickFalse)
1795 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
cristy4c08aed2011-07-01 19:47:50 +00001796 equalize_map=(PixelInfo *) AcquireQuantumMemory(MaxMap+1UL,
cristy3ed852e2009-09-05 21:47:34 +00001797 sizeof(*equalize_map));
cristy4c08aed2011-07-01 19:47:50 +00001798 histogram=(PixelInfo *) AcquireQuantumMemory(MaxMap+1UL,
cristy3ed852e2009-09-05 21:47:34 +00001799 sizeof(*histogram));
cristy4c08aed2011-07-01 19:47:50 +00001800 map=(PixelInfo *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*map));
1801 if ((equalize_map == (PixelInfo *) NULL) ||
1802 (histogram == (PixelInfo *) NULL) ||
1803 (map == (PixelInfo *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00001804 {
cristy4c08aed2011-07-01 19:47:50 +00001805 if (map != (PixelInfo *) NULL)
1806 map=(PixelInfo *) RelinquishMagickMemory(map);
1807 if (histogram != (PixelInfo *) NULL)
1808 histogram=(PixelInfo *) RelinquishMagickMemory(histogram);
1809 if (equalize_map != (PixelInfo *) NULL)
1810 equalize_map=(PixelInfo *) RelinquishMagickMemory(equalize_map);
cristy3ed852e2009-09-05 21:47:34 +00001811 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
1812 image->filename);
1813 }
1814 /*
1815 Form histogram.
1816 */
1817 (void) ResetMagickMemory(histogram,0,(MaxMap+1)*sizeof(*histogram));
1818 exception=(&image->exception);
cristybb503372010-05-27 20:51:26 +00001819 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001820 {
cristy4c08aed2011-07-01 19:47:50 +00001821 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00001822 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00001823
cristybb503372010-05-27 20:51:26 +00001824 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001825 x;
1826
1827 p=GetVirtualPixels(image,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00001828 if (p == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001829 break;
cristybb503372010-05-27 20:51:26 +00001830 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001831 {
cristy2b9582a2011-07-04 17:38:56 +00001832 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001833 histogram[ScaleQuantumToMap(GetPixelRed(image,p))].red++;
cristy2b9582a2011-07-04 17:38:56 +00001834 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001835 histogram[ScaleQuantumToMap(GetPixelGreen(image,p))].green++;
cristy2b9582a2011-07-04 17:38:56 +00001836 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001837 histogram[ScaleQuantumToMap(GetPixelBlue(image,p))].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 histogram[ScaleQuantumToMap(GetPixelBlack(image,p))].black++;
cristy2b9582a2011-07-04 17:38:56 +00001841 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001842 histogram[ScaleQuantumToMap(GetPixelAlpha(image,p))].alpha++;
1843 p+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00001844 }
1845 }
1846 /*
1847 Integrate the histogram to get the equalization map.
1848 */
1849 (void) ResetMagickMemory(&intensity,0,sizeof(intensity));
cristybb503372010-05-27 20:51:26 +00001850 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +00001851 {
cristy2b9582a2011-07-04 17:38:56 +00001852 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001853 intensity.red+=histogram[i].red;
cristy2b9582a2011-07-04 17:38:56 +00001854 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001855 intensity.green+=histogram[i].green;
cristy2b9582a2011-07-04 17:38:56 +00001856 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001857 intensity.blue+=histogram[i].blue;
cristy2b9582a2011-07-04 17:38:56 +00001858 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00001859 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +00001860 intensity.black+=histogram[i].black;
cristy2b9582a2011-07-04 17:38:56 +00001861 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001862 intensity.alpha+=histogram[i].alpha;
cristy3ed852e2009-09-05 21:47:34 +00001863 map[i]=intensity;
1864 }
1865 black=map[0];
1866 white=map[(int) MaxMap];
1867 (void) ResetMagickMemory(equalize_map,0,(MaxMap+1)*sizeof(*equalize_map));
cristyb5d5f722009-11-04 03:03:49 +00001868#if defined(MAGICKCORE_OPENMP_SUPPORT)
1869 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001870#endif
cristybb503372010-05-27 20:51:26 +00001871 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +00001872 {
cristy2b9582a2011-07-04 17:38:56 +00001873 if (((GetPixelRedTraits(image) & ActivePixelTrait) != 0) &&
1874 (white.red != black.red))
cristy3ed852e2009-09-05 21:47:34 +00001875 equalize_map[i].red=(MagickRealType) ScaleMapToQuantum((MagickRealType)
1876 ((MaxMap*(map[i].red-black.red))/(white.red-black.red)));
cristy2b9582a2011-07-04 17:38:56 +00001877 if (((GetPixelGreenTraits(image) & ActivePixelTrait) != 0) &&
1878 (white.green != black.green))
cristy3ed852e2009-09-05 21:47:34 +00001879 equalize_map[i].green=(MagickRealType) ScaleMapToQuantum((MagickRealType)
1880 ((MaxMap*(map[i].green-black.green))/(white.green-black.green)));
cristy2b9582a2011-07-04 17:38:56 +00001881 if (((GetPixelBlueTraits(image) & ActivePixelTrait) != 0) &&
1882 (white.blue != black.blue))
cristy3ed852e2009-09-05 21:47:34 +00001883 equalize_map[i].blue=(MagickRealType) ScaleMapToQuantum((MagickRealType)
1884 ((MaxMap*(map[i].blue-black.blue))/(white.blue-black.blue)));
cristy2b9582a2011-07-04 17:38:56 +00001885 if ((((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00001886 (image->colorspace == CMYKColorspace)) &&
cristy4c08aed2011-07-01 19:47:50 +00001887 (white.black != black.black))
1888 equalize_map[i].black=(MagickRealType) ScaleMapToQuantum((MagickRealType)
1889 ((MaxMap*(map[i].black-black.black))/(white.black-black.black)));
cristy2b9582a2011-07-04 17:38:56 +00001890 if (((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0) &&
1891 (white.alpha != black.alpha))
cristy4c08aed2011-07-01 19:47:50 +00001892 equalize_map[i].alpha=(MagickRealType) ScaleMapToQuantum(
1893 (MagickRealType) ((MaxMap*(map[i].alpha-black.alpha))/
1894 (white.alpha-black.alpha)));
cristy3ed852e2009-09-05 21:47:34 +00001895 }
cristy4c08aed2011-07-01 19:47:50 +00001896 histogram=(PixelInfo *) RelinquishMagickMemory(histogram);
1897 map=(PixelInfo *) RelinquishMagickMemory(map);
cristy3ed852e2009-09-05 21:47:34 +00001898 if (image->storage_class == PseudoClass)
1899 {
1900 /*
1901 Equalize colormap.
1902 */
cristyb5d5f722009-11-04 03:03:49 +00001903#if defined(MAGICKCORE_OPENMP_SUPPORT)
1904 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001905#endif
cristybb503372010-05-27 20:51:26 +00001906 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00001907 {
cristy2b9582a2011-07-04 17:38:56 +00001908 if (((GetPixelRedTraits(image) & ActivePixelTrait) != 0) &&
1909 (white.red != black.red))
cristyce70c172010-01-07 17:15:30 +00001910 image->colormap[i].red=ClampToQuantum(equalize_map[
cristy3ed852e2009-09-05 21:47:34 +00001911 ScaleQuantumToMap(image->colormap[i].red)].red);
cristy2b9582a2011-07-04 17:38:56 +00001912 if (((GetPixelGreenTraits(image) & ActivePixelTrait) != 0) &&
1913 (white.green != black.green))
cristyce70c172010-01-07 17:15:30 +00001914 image->colormap[i].green=ClampToQuantum(equalize_map[
cristy3ed852e2009-09-05 21:47:34 +00001915 ScaleQuantumToMap(image->colormap[i].green)].green);
cristy2b9582a2011-07-04 17:38:56 +00001916 if (((GetPixelBlueTraits(image) & ActivePixelTrait) != 0) &&
1917 (white.blue != black.blue))
cristyce70c172010-01-07 17:15:30 +00001918 image->colormap[i].blue=ClampToQuantum(equalize_map[
cristy3ed852e2009-09-05 21:47:34 +00001919 ScaleQuantumToMap(image->colormap[i].blue)].blue);
cristy2b9582a2011-07-04 17:38:56 +00001920 if (((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +00001921 (white.alpha != black.alpha))
1922 image->colormap[i].alpha=ClampToQuantum(equalize_map[
1923 ScaleQuantumToMap(image->colormap[i].alpha)].alpha);
cristy3ed852e2009-09-05 21:47:34 +00001924 }
1925 }
1926 /*
1927 Equalize image.
1928 */
1929 status=MagickTrue;
1930 progress=0;
1931 exception=(&image->exception);
1932 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00001933#if defined(MAGICKCORE_OPENMP_SUPPORT)
1934 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001935#endif
cristybb503372010-05-27 20:51:26 +00001936 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001937 {
cristy4c08aed2011-07-01 19:47:50 +00001938 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00001939 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001940
cristy8d4629b2010-08-30 17:59:46 +00001941 register ssize_t
1942 x;
1943
cristy3ed852e2009-09-05 21:47:34 +00001944 if (status == MagickFalse)
1945 continue;
1946 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00001947 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001948 {
1949 status=MagickFalse;
1950 continue;
1951 }
cristybb503372010-05-27 20:51:26 +00001952 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001953 {
cristy2b9582a2011-07-04 17:38:56 +00001954 if (((GetPixelRedTraits(image) & ActivePixelTrait) != 0) &&
1955 (white.red != black.red))
cristy4c08aed2011-07-01 19:47:50 +00001956 SetPixelRed(image,ClampToQuantum(equalize_map[
1957 ScaleQuantumToMap(GetPixelRed(image,q))].red),q);
cristy2b9582a2011-07-04 17:38:56 +00001958 if (((GetPixelGreenTraits(image) & ActivePixelTrait) != 0) &&
1959 (white.green != black.green))
cristy4c08aed2011-07-01 19:47:50 +00001960 SetPixelGreen(image,ClampToQuantum(equalize_map[
1961 ScaleQuantumToMap(GetPixelGreen(image,q))].green),q);
cristy2b9582a2011-07-04 17:38:56 +00001962 if (((GetPixelBlueTraits(image) & ActivePixelTrait) != 0) &&
1963 (white.blue != black.blue))
cristy4c08aed2011-07-01 19:47:50 +00001964 SetPixelBlue(image,ClampToQuantum(equalize_map[
1965 ScaleQuantumToMap(GetPixelBlue(image,q))].blue),q);
cristy2b9582a2011-07-04 17:38:56 +00001966 if ((((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00001967 (image->colorspace == CMYKColorspace)) &&
cristy4c08aed2011-07-01 19:47:50 +00001968 (white.black != black.black))
1969 SetPixelBlack(image,ClampToQuantum(equalize_map[
1970 ScaleQuantumToMap(GetPixelBlack(image,q))].black),q);
cristy2b9582a2011-07-04 17:38:56 +00001971 if (((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0) &&
1972 (white.alpha != black.alpha))
cristy4c08aed2011-07-01 19:47:50 +00001973 SetPixelAlpha(image,ClampToQuantum(equalize_map[
1974 ScaleQuantumToMap(GetPixelAlpha(image,q))].alpha),q);
1975 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00001976 }
1977 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
1978 status=MagickFalse;
1979 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1980 {
1981 MagickBooleanType
1982 proceed;
1983
cristyb5d5f722009-11-04 03:03:49 +00001984#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00001985 #pragma omp critical (MagickCore_EqualizeImageChannel)
1986#endif
1987 proceed=SetImageProgress(image,EqualizeImageTag,progress++,image->rows);
1988 if (proceed == MagickFalse)
1989 status=MagickFalse;
1990 }
1991 }
1992 image_view=DestroyCacheView(image_view);
cristy4c08aed2011-07-01 19:47:50 +00001993 equalize_map=(PixelInfo *) RelinquishMagickMemory(equalize_map);
cristy3ed852e2009-09-05 21:47:34 +00001994 return(status);
1995}
1996
1997/*
1998%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1999% %
2000% %
2001% %
2002% G a m m a I m a g e %
2003% %
2004% %
2005% %
2006%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2007%
2008% GammaImage() gamma-corrects a particular image channel. The same
2009% image viewed on different devices will have perceptual differences in the
2010% way the image's intensities are represented on the screen. Specify
2011% individual gamma levels for the red, green, and blue channels, or adjust
2012% all three with the gamma parameter. Values typically range from 0.8 to 2.3.
2013%
2014% You can also reduce the influence of a particular channel with a gamma
2015% value of 0.
2016%
2017% The format of the GammaImage method is:
2018%
cristya6360142011-03-23 23:08:04 +00002019% MagickBooleanType GammaImage(Image *image,const char *level)
cristy3ed852e2009-09-05 21:47:34 +00002020% MagickBooleanType GammaImageChannel(Image *image,
2021% const ChannelType channel,const double gamma)
2022%
2023% A description of each parameter follows:
2024%
2025% o image: the image.
2026%
2027% o channel: the channel.
2028%
cristya6360142011-03-23 23:08:04 +00002029% o level: the image gamma as a string (e.g. 1.6,1.2,1.0).
2030%
cristy3ed852e2009-09-05 21:47:34 +00002031% o gamma: the image gamma.
2032%
2033*/
2034MagickExport MagickBooleanType GammaImage(Image *image,const char *level)
2035{
2036 GeometryInfo
2037 geometry_info;
2038
cristy4c08aed2011-07-01 19:47:50 +00002039 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00002040 gamma;
2041
2042 MagickStatusType
2043 flags,
2044 status;
2045
2046 assert(image != (Image *) NULL);
2047 assert(image->signature == MagickSignature);
2048 if (image->debug != MagickFalse)
2049 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2050 if (level == (char *) NULL)
2051 return(MagickFalse);
2052 flags=ParseGeometry(level,&geometry_info);
2053 gamma.red=geometry_info.rho;
2054 gamma.green=geometry_info.sigma;
2055 if ((flags & SigmaValue) == 0)
2056 gamma.green=gamma.red;
2057 gamma.blue=geometry_info.xi;
2058 if ((flags & XiValue) == 0)
2059 gamma.blue=gamma.red;
2060 if ((gamma.red == 1.0) && (gamma.green == 1.0) && (gamma.blue == 1.0))
2061 return(MagickTrue);
2062 if ((gamma.red == gamma.green) && (gamma.green == gamma.blue))
2063 status=GammaImageChannel(image,(const ChannelType) (RedChannel |
2064 GreenChannel | BlueChannel),(double) gamma.red);
2065 else
2066 {
2067 status=GammaImageChannel(image,RedChannel,(double) gamma.red);
2068 status|=GammaImageChannel(image,GreenChannel,(double) gamma.green);
2069 status|=GammaImageChannel(image,BlueChannel,(double) gamma.blue);
2070 }
2071 return(status != 0 ? MagickTrue : MagickFalse);
2072}
2073
2074MagickExport MagickBooleanType GammaImageChannel(Image *image,
2075 const ChannelType channel,const double gamma)
2076{
2077#define GammaCorrectImageTag "GammaCorrect/Image"
2078
cristyc4c8d132010-01-07 01:58:38 +00002079 CacheView
2080 *image_view;
2081
cristy3ed852e2009-09-05 21:47:34 +00002082 ExceptionInfo
2083 *exception;
2084
cristy3ed852e2009-09-05 21:47:34 +00002085 MagickBooleanType
2086 status;
2087
cristybb503372010-05-27 20:51:26 +00002088 MagickOffsetType
2089 progress;
2090
cristy3ed852e2009-09-05 21:47:34 +00002091 Quantum
2092 *gamma_map;
2093
cristybb503372010-05-27 20:51:26 +00002094 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002095 i;
2096
cristybb503372010-05-27 20:51:26 +00002097 ssize_t
2098 y;
2099
cristy3ed852e2009-09-05 21:47:34 +00002100 /*
2101 Allocate and initialize gamma maps.
2102 */
2103 assert(image != (Image *) NULL);
2104 assert(image->signature == MagickSignature);
2105 if (image->debug != MagickFalse)
2106 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2107 if (gamma == 1.0)
2108 return(MagickTrue);
2109 gamma_map=(Quantum *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*gamma_map));
2110 if (gamma_map == (Quantum *) NULL)
2111 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
2112 image->filename);
2113 (void) ResetMagickMemory(gamma_map,0,(MaxMap+1)*sizeof(*gamma_map));
2114 if (gamma != 0.0)
cristyb5d5f722009-11-04 03:03:49 +00002115#if defined(MAGICKCORE_OPENMP_SUPPORT)
2116 #pragma omp parallel for schedule(dynamic,4)
cristy3ed852e2009-09-05 21:47:34 +00002117#endif
cristybb503372010-05-27 20:51:26 +00002118 for (i=0; i <= (ssize_t) MaxMap; i++)
cristyce70c172010-01-07 17:15:30 +00002119 gamma_map[i]=ClampToQuantum((MagickRealType) ScaleMapToQuantum((
cristy3ed852e2009-09-05 21:47:34 +00002120 MagickRealType) (MaxMap*pow((double) i/MaxMap,1.0/gamma))));
2121 if (image->storage_class == PseudoClass)
2122 {
2123 /*
2124 Gamma-correct colormap.
2125 */
cristyb5d5f722009-11-04 03:03:49 +00002126#if defined(MAGICKCORE_OPENMP_SUPPORT)
2127 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002128#endif
cristybb503372010-05-27 20:51:26 +00002129 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00002130 {
cristy2b9582a2011-07-04 17:38:56 +00002131 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00002132 image->colormap[i].red=gamma_map[
2133 ScaleQuantumToMap(image->colormap[i].red)];
cristy2b9582a2011-07-04 17:38:56 +00002134 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00002135 image->colormap[i].green=gamma_map[
2136 ScaleQuantumToMap(image->colormap[i].green)];
cristy2b9582a2011-07-04 17:38:56 +00002137 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00002138 image->colormap[i].blue=gamma_map[
2139 ScaleQuantumToMap(image->colormap[i].blue)];
cristy2b9582a2011-07-04 17:38:56 +00002140 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002141 image->colormap[i].alpha=gamma_map[
2142 ScaleQuantumToMap(image->colormap[i].alpha)];
cristy3ed852e2009-09-05 21:47:34 +00002143 }
2144 }
2145 /*
2146 Gamma-correct image.
2147 */
2148 status=MagickTrue;
2149 progress=0;
2150 exception=(&image->exception);
2151 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00002152#if defined(MAGICKCORE_OPENMP_SUPPORT)
2153 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002154#endif
cristybb503372010-05-27 20:51:26 +00002155 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002156 {
cristy4c08aed2011-07-01 19:47:50 +00002157 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00002158 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002159
cristy8d4629b2010-08-30 17:59:46 +00002160 register ssize_t
2161 x;
2162
cristy3ed852e2009-09-05 21:47:34 +00002163 if (status == MagickFalse)
2164 continue;
2165 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00002166 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00002167 {
2168 status=MagickFalse;
2169 continue;
2170 }
cristybb503372010-05-27 20:51:26 +00002171 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002172 {
cristy6cbd7f52009-10-17 16:06:51 +00002173 if (channel == DefaultChannels)
cristy3ed852e2009-09-05 21:47:34 +00002174 {
cristy4c08aed2011-07-01 19:47:50 +00002175 SetPixelRed(image,gamma_map[
2176 ScaleQuantumToMap(GetPixelRed(image,q))],q);
2177 SetPixelGreen(image,gamma_map[
2178 ScaleQuantumToMap(GetPixelGreen(image,q))],q);
2179 SetPixelBlue(image,gamma_map[ScaleQuantumToMap(
2180 GetPixelBlue(image,q))],q);
cristy6cbd7f52009-10-17 16:06:51 +00002181 }
2182 else
2183 {
cristy2b9582a2011-07-04 17:38:56 +00002184 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002185 SetPixelRed(image,gamma_map[ScaleQuantumToMap(
2186 GetPixelRed(image,q))],q);
cristy2b9582a2011-07-04 17:38:56 +00002187 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002188 SetPixelGreen(image,gamma_map[
2189 ScaleQuantumToMap(GetPixelGreen(image,q))],q);
cristy2b9582a2011-07-04 17:38:56 +00002190 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002191 SetPixelBlue(image,gamma_map[
2192 ScaleQuantumToMap(GetPixelBlue(image,q))],q);
cristy2b9582a2011-07-04 17:38:56 +00002193 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy6cbd7f52009-10-17 16:06:51 +00002194 {
2195 if (image->matte == MagickFalse)
cristy4c08aed2011-07-01 19:47:50 +00002196 SetPixelAlpha(image,gamma_map[
2197 ScaleQuantumToMap(GetPixelAlpha(image,q))],q);
cristy6cbd7f52009-10-17 16:06:51 +00002198 else
cristy4c08aed2011-07-01 19:47:50 +00002199 SetPixelAlpha(image,gamma_map[
2200 ScaleQuantumToMap(GetPixelAlpha(image,q))],q);
cristy6cbd7f52009-10-17 16:06:51 +00002201 }
cristy3ed852e2009-09-05 21:47:34 +00002202 }
cristy4c08aed2011-07-01 19:47:50 +00002203 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00002204 }
cristy2b9582a2011-07-04 17:38:56 +00002205 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00002206 (image->colorspace == CMYKColorspace))
cristybb503372010-05-27 20:51:26 +00002207 for (x=0; x < (ssize_t) image->columns; x++)
cristy4c08aed2011-07-01 19:47:50 +00002208 SetPixelBlack(image,gamma_map[ScaleQuantumToMap(
2209 GetPixelBlack(image,q))],q);
cristy3ed852e2009-09-05 21:47:34 +00002210 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2211 status=MagickFalse;
2212 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2213 {
2214 MagickBooleanType
2215 proceed;
2216
cristyb5d5f722009-11-04 03:03:49 +00002217#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00002218 #pragma omp critical (MagickCore_GammaImageChannel)
2219#endif
2220 proceed=SetImageProgress(image,GammaCorrectImageTag,progress++,
2221 image->rows);
2222 if (proceed == MagickFalse)
2223 status=MagickFalse;
2224 }
2225 }
2226 image_view=DestroyCacheView(image_view);
2227 gamma_map=(Quantum *) RelinquishMagickMemory(gamma_map);
2228 if (image->gamma != 0.0)
2229 image->gamma*=gamma;
2230 return(status);
2231}
2232
2233/*
2234%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2235% %
2236% %
2237% %
2238% H a l d C l u t I m a g e %
2239% %
2240% %
2241% %
2242%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2243%
2244% HaldClutImage() applies a Hald color lookup table to the image. A Hald
2245% color lookup table is a 3-dimensional color cube mapped to 2 dimensions.
2246% Create it with the HALD coder. You can apply any color transformation to
2247% the Hald image and then use this method to apply the transform to the
2248% image.
2249%
2250% The format of the HaldClutImage method is:
2251%
2252% MagickBooleanType HaldClutImage(Image *image,Image *hald_image)
2253% MagickBooleanType HaldClutImageChannel(Image *image,
2254% const ChannelType channel,Image *hald_image)
2255%
2256% A description of each parameter follows:
2257%
2258% o image: the image, which is replaced by indexed CLUT values
2259%
2260% o hald_image: the color lookup table image for replacement color values.
2261%
2262% o channel: the channel.
2263%
2264*/
2265
2266static inline size_t MagickMin(const size_t x,const size_t y)
2267{
2268 if (x < y)
2269 return(x);
2270 return(y);
2271}
2272
2273MagickExport MagickBooleanType HaldClutImage(Image *image,
2274 const Image *hald_image)
2275{
2276 return(HaldClutImageChannel(image,DefaultChannels,hald_image));
2277}
2278
2279MagickExport MagickBooleanType HaldClutImageChannel(Image *image,
2280 const ChannelType channel,const Image *hald_image)
2281{
2282#define HaldClutImageTag "Clut/Image"
2283
2284 typedef struct _HaldInfo
2285 {
2286 MagickRealType
2287 x,
2288 y,
2289 z;
2290 } HaldInfo;
2291
cristyfa112112010-01-04 17:48:07 +00002292 CacheView
cristyd551fbc2011-03-31 18:07:46 +00002293 *hald_view,
cristyfa112112010-01-04 17:48:07 +00002294 *image_view;
2295
cristy3ed852e2009-09-05 21:47:34 +00002296 double
2297 width;
2298
2299 ExceptionInfo
2300 *exception;
2301
cristy3ed852e2009-09-05 21:47:34 +00002302 MagickBooleanType
2303 status;
2304
cristybb503372010-05-27 20:51:26 +00002305 MagickOffsetType
2306 progress;
2307
cristy4c08aed2011-07-01 19:47:50 +00002308 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00002309 zero;
2310
cristy3ed852e2009-09-05 21:47:34 +00002311 size_t
2312 cube_size,
2313 length,
2314 level;
2315
cristybb503372010-05-27 20:51:26 +00002316 ssize_t
2317 y;
2318
cristy3ed852e2009-09-05 21:47:34 +00002319 assert(image != (Image *) NULL);
2320 assert(image->signature == MagickSignature);
2321 if (image->debug != MagickFalse)
2322 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2323 assert(hald_image != (Image *) NULL);
2324 assert(hald_image->signature == MagickSignature);
2325 if (SetImageStorageClass(image,DirectClass) == MagickFalse)
2326 return(MagickFalse);
2327 if (image->matte == MagickFalse)
2328 (void) SetImageAlphaChannel(image,OpaqueAlphaChannel);
2329 /*
2330 Hald clut image.
2331 */
2332 status=MagickTrue;
2333 progress=0;
2334 length=MagickMin(hald_image->columns,hald_image->rows);
2335 for (level=2; (level*level*level) < length; level++) ;
2336 level*=level;
2337 cube_size=level*level;
2338 width=(double) hald_image->columns;
cristy4c08aed2011-07-01 19:47:50 +00002339 GetPixelInfo(hald_image,&zero);
cristy3ed852e2009-09-05 21:47:34 +00002340 exception=(&image->exception);
cristy3ed852e2009-09-05 21:47:34 +00002341 image_view=AcquireCacheView(image);
cristyd551fbc2011-03-31 18:07:46 +00002342 hald_view=AcquireCacheView(hald_image);
cristyb5d5f722009-11-04 03:03:49 +00002343#if defined(MAGICKCORE_OPENMP_SUPPORT)
2344 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002345#endif
cristybb503372010-05-27 20:51:26 +00002346 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002347 {
2348 double
2349 offset;
2350
2351 HaldInfo
2352 point;
2353
cristy4c08aed2011-07-01 19:47:50 +00002354 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00002355 pixel,
2356 pixel1,
2357 pixel2,
2358 pixel3,
2359 pixel4;
2360
cristy4c08aed2011-07-01 19:47:50 +00002361 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00002362 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002363
cristy8d4629b2010-08-30 17:59:46 +00002364 register ssize_t
2365 x;
2366
cristy3ed852e2009-09-05 21:47:34 +00002367 if (status == MagickFalse)
2368 continue;
2369 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00002370 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00002371 {
2372 status=MagickFalse;
2373 continue;
2374 }
cristy3ed852e2009-09-05 21:47:34 +00002375 pixel=zero;
2376 pixel1=zero;
2377 pixel2=zero;
2378 pixel3=zero;
2379 pixel4=zero;
cristybb503372010-05-27 20:51:26 +00002380 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002381 {
cristy4c08aed2011-07-01 19:47:50 +00002382 point.x=QuantumScale*(level-1.0)*GetPixelRed(image,q);
2383 point.y=QuantumScale*(level-1.0)*GetPixelGreen(image,q);
2384 point.z=QuantumScale*(level-1.0)*GetPixelBlue(image,q);
cristy3ed852e2009-09-05 21:47:34 +00002385 offset=point.x+level*floor(point.y)+cube_size*floor(point.z);
2386 point.x-=floor(point.x);
2387 point.y-=floor(point.y);
2388 point.z-=floor(point.z);
cristy4c08aed2011-07-01 19:47:50 +00002389 (void) InterpolatePixelInfo(image,hald_view,
cristy8a7c3e82011-03-26 02:10:53 +00002390 UndefinedInterpolatePixel,fmod(offset,width),floor(offset/width),
2391 &pixel1,exception);
cristy4c08aed2011-07-01 19:47:50 +00002392 (void) InterpolatePixelInfo(image,hald_view,
cristy8a7c3e82011-03-26 02:10:53 +00002393 UndefinedInterpolatePixel,fmod(offset+level,width),floor((offset+level)/
2394 width),&pixel2,exception);
cristy4c08aed2011-07-01 19:47:50 +00002395 CompositePixelInfoAreaBlend(&pixel1,pixel1.alpha,&pixel2,
2396 pixel2.alpha,point.y,&pixel3);
cristy3ed852e2009-09-05 21:47:34 +00002397 offset+=cube_size;
cristy4c08aed2011-07-01 19:47:50 +00002398 (void) InterpolatePixelInfo(image,hald_view,
cristy8a7c3e82011-03-26 02:10:53 +00002399 UndefinedInterpolatePixel,fmod(offset,width),floor(offset/width),
2400 &pixel1,exception);
cristy4c08aed2011-07-01 19:47:50 +00002401 (void) InterpolatePixelInfo(image,hald_view,
cristy8a7c3e82011-03-26 02:10:53 +00002402 UndefinedInterpolatePixel,fmod(offset+level,width),floor((offset+level)/
2403 width),&pixel2,exception);
cristy4c08aed2011-07-01 19:47:50 +00002404 CompositePixelInfoAreaBlend(&pixel1,pixel1.alpha,&pixel2,
2405 pixel2.alpha,point.y,&pixel4);
2406 CompositePixelInfoAreaBlend(&pixel3,pixel3.alpha,&pixel4,
2407 pixel4.alpha,point.z,&pixel);
cristy2b9582a2011-07-04 17:38:56 +00002408 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002409 SetPixelRed(image,
2410 ClampToQuantum(pixel.red),q);
cristy2b9582a2011-07-04 17:38:56 +00002411 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002412 SetPixelGreen(image,
2413 ClampToQuantum(pixel.green),q);
cristy2b9582a2011-07-04 17:38:56 +00002414 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002415 SetPixelBlue(image,
2416 ClampToQuantum(pixel.blue),q);
cristy2b9582a2011-07-04 17:38:56 +00002417 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00002418 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +00002419 SetPixelBlack(image,
2420 ClampToQuantum(pixel.black),q);
cristy2b9582a2011-07-04 17:38:56 +00002421 if (((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0) && (image->matte != MagickFalse))
cristy4c08aed2011-07-01 19:47:50 +00002422 SetPixelAlpha(image,
2423 ClampToQuantum(pixel.alpha),q);
2424 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00002425 }
2426 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2427 status=MagickFalse;
2428 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2429 {
2430 MagickBooleanType
2431 proceed;
2432
cristyb5d5f722009-11-04 03:03:49 +00002433#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00002434 #pragma omp critical (MagickCore_HaldClutImageChannel)
2435#endif
2436 proceed=SetImageProgress(image,HaldClutImageTag,progress++,image->rows);
2437 if (proceed == MagickFalse)
2438 status=MagickFalse;
2439 }
2440 }
cristyd551fbc2011-03-31 18:07:46 +00002441 hald_view=DestroyCacheView(hald_view);
cristy3ed852e2009-09-05 21:47:34 +00002442 image_view=DestroyCacheView(image_view);
cristy3ed852e2009-09-05 21:47:34 +00002443 return(status);
2444}
2445
2446/*
2447%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2448% %
2449% %
2450% %
2451% L e v e l I m a g e %
2452% %
2453% %
2454% %
2455%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2456%
2457% LevelImage() adjusts the levels of a particular image channel by
2458% scaling the colors falling between specified white and black points to
2459% the full available quantum range.
2460%
2461% The parameters provided represent the black, and white points. The black
2462% point specifies the darkest color in the image. Colors darker than the
2463% black point are set to zero. White point specifies the lightest color in
2464% the image. Colors brighter than the white point are set to the maximum
2465% quantum value.
2466%
2467% If a '!' flag is given, map black and white colors to the given levels
2468% rather than mapping those levels to black and white. See
2469% LevelizeImageChannel() and LevelizeImageChannel(), below.
2470%
2471% Gamma specifies a gamma correction to apply to the image.
2472%
2473% The format of the LevelImage method is:
2474%
2475% MagickBooleanType LevelImage(Image *image,const char *levels)
2476%
2477% A description of each parameter follows:
2478%
2479% o image: the image.
2480%
2481% o levels: Specify the levels where the black and white points have the
2482% range of 0-QuantumRange, and gamma has the range 0-10 (e.g. 10x90%+2).
2483% A '!' flag inverts the re-mapping.
2484%
2485*/
2486
2487MagickExport MagickBooleanType LevelImage(Image *image,const char *levels)
2488{
2489 double
2490 black_point,
2491 gamma,
2492 white_point;
2493
2494 GeometryInfo
2495 geometry_info;
2496
2497 MagickBooleanType
2498 status;
2499
2500 MagickStatusType
2501 flags;
2502
2503 /*
2504 Parse levels.
2505 */
2506 if (levels == (char *) NULL)
2507 return(MagickFalse);
2508 flags=ParseGeometry(levels,&geometry_info);
2509 black_point=geometry_info.rho;
2510 white_point=(double) QuantumRange;
2511 if ((flags & SigmaValue) != 0)
2512 white_point=geometry_info.sigma;
2513 gamma=1.0;
2514 if ((flags & XiValue) != 0)
2515 gamma=geometry_info.xi;
2516 if ((flags & PercentValue) != 0)
2517 {
2518 black_point*=(double) image->columns*image->rows/100.0;
2519 white_point*=(double) image->columns*image->rows/100.0;
2520 }
2521 if ((flags & SigmaValue) == 0)
2522 white_point=(double) QuantumRange-black_point;
2523 if ((flags & AspectValue ) == 0)
2524 status=LevelImageChannel(image,DefaultChannels,black_point,white_point,
2525 gamma);
2526 else
cristy308b4e62009-09-21 14:40:44 +00002527 status=LevelizeImage(image,black_point,white_point,gamma);
cristy3ed852e2009-09-05 21:47:34 +00002528 return(status);
2529}
2530
2531/*
2532%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2533% %
2534% %
2535% %
cristy308b4e62009-09-21 14:40:44 +00002536% L e v e l i z e I m a g e %
cristy3ed852e2009-09-05 21:47:34 +00002537% %
2538% %
2539% %
2540%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2541%
cristy308b4e62009-09-21 14:40:44 +00002542% LevelizeImage() applies the normal level operation to the image, spreading
2543% out the values between the black and white points over the entire range of
2544% values. Gamma correction is also applied after the values has been mapped.
cristy3ed852e2009-09-05 21:47:34 +00002545%
2546% It is typically used to improve image contrast, or to provide a controlled
2547% linear threshold for the image. If the black and white points are set to
2548% the minimum and maximum values found in the image, the image can be
2549% normalized. or by swapping black and white values, negate the image.
2550%
cristy308b4e62009-09-21 14:40:44 +00002551% The format of the LevelizeImage method is:
cristy3ed852e2009-09-05 21:47:34 +00002552%
cristy308b4e62009-09-21 14:40:44 +00002553% MagickBooleanType LevelizeImage(Image *image,const double black_point,
2554% const double white_point,const double gamma)
2555% MagickBooleanType LevelizeImageChannel(Image *image,
2556% const ChannelType channel,const double black_point,
2557% const double white_point,const double gamma)
cristy3ed852e2009-09-05 21:47:34 +00002558%
2559% A description of each parameter follows:
2560%
2561% o image: the image.
2562%
2563% o channel: the channel.
2564%
2565% o black_point: The level which is to be mapped to zero (black)
2566%
2567% o white_point: The level which is to be mapped to QuantiumRange (white)
2568%
2569% o gamma: adjust gamma by this factor before mapping values.
2570% use 1.0 for purely linear stretching of image color values
2571%
2572*/
2573MagickExport MagickBooleanType LevelImageChannel(Image *image,
2574 const ChannelType channel,const double black_point,const double white_point,
2575 const double gamma)
2576{
2577#define LevelImageTag "Level/Image"
cristybcfb0432010-05-06 01:45:33 +00002578#define LevelQuantum(x) (ClampToQuantum((MagickRealType) QuantumRange* \
cristyc1f508d2010-04-22 01:21:47 +00002579 pow(scale*((double) (x)-black_point),1.0/gamma)))
cristy3ed852e2009-09-05 21:47:34 +00002580
cristyc4c8d132010-01-07 01:58:38 +00002581 CacheView
2582 *image_view;
2583
cristy3ed852e2009-09-05 21:47:34 +00002584 ExceptionInfo
2585 *exception;
2586
cristy3ed852e2009-09-05 21:47:34 +00002587 MagickBooleanType
2588 status;
2589
cristybb503372010-05-27 20:51:26 +00002590 MagickOffsetType
2591 progress;
2592
anthony7fe39fc2010-04-06 03:19:20 +00002593 register double
2594 scale;
2595
cristy8d4629b2010-08-30 17:59:46 +00002596 register ssize_t
2597 i;
2598
cristybb503372010-05-27 20:51:26 +00002599 ssize_t
2600 y;
2601
cristy3ed852e2009-09-05 21:47:34 +00002602 /*
2603 Allocate and initialize levels map.
2604 */
2605 assert(image != (Image *) NULL);
2606 assert(image->signature == MagickSignature);
2607 if (image->debug != MagickFalse)
2608 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
cristy8d4629b2010-08-30 17:59:46 +00002609 scale=(white_point != black_point) ? 1.0/(white_point-black_point) : 1.0;
cristy3ed852e2009-09-05 21:47:34 +00002610 if (image->storage_class == PseudoClass)
cristyb5d5f722009-11-04 03:03:49 +00002611#if defined(MAGICKCORE_OPENMP_SUPPORT)
2612 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002613#endif
cristybb503372010-05-27 20:51:26 +00002614 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00002615 {
2616 /*
2617 Level colormap.
2618 */
cristy2b9582a2011-07-04 17:38:56 +00002619 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristyc1f508d2010-04-22 01:21:47 +00002620 image->colormap[i].red=LevelQuantum(image->colormap[i].red);
cristy2b9582a2011-07-04 17:38:56 +00002621 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristyc1f508d2010-04-22 01:21:47 +00002622 image->colormap[i].green=LevelQuantum(image->colormap[i].green);
cristy2b9582a2011-07-04 17:38:56 +00002623 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristyc1f508d2010-04-22 01:21:47 +00002624 image->colormap[i].blue=LevelQuantum(image->colormap[i].blue);
cristy2b9582a2011-07-04 17:38:56 +00002625 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002626 image->colormap[i].alpha=LevelQuantum(image->colormap[i].alpha);
cristy3ed852e2009-09-05 21:47:34 +00002627 }
2628 /*
2629 Level image.
2630 */
2631 status=MagickTrue;
2632 progress=0;
2633 exception=(&image->exception);
2634 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00002635#if defined(MAGICKCORE_OPENMP_SUPPORT)
2636 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002637#endif
cristybb503372010-05-27 20:51:26 +00002638 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002639 {
cristy4c08aed2011-07-01 19:47:50 +00002640 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00002641 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002642
cristy8d4629b2010-08-30 17:59:46 +00002643 register ssize_t
2644 x;
2645
cristy3ed852e2009-09-05 21:47:34 +00002646 if (status == MagickFalse)
2647 continue;
2648 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00002649 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00002650 {
2651 status=MagickFalse;
2652 continue;
2653 }
cristybb503372010-05-27 20:51:26 +00002654 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002655 {
cristy2b9582a2011-07-04 17:38:56 +00002656 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002657 SetPixelRed(image,LevelQuantum(
2658 GetPixelRed(image,q)),q);
cristy2b9582a2011-07-04 17:38:56 +00002659 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002660 SetPixelGreen(image,
2661 LevelQuantum(GetPixelGreen(image,q)),q);
cristy2b9582a2011-07-04 17:38:56 +00002662 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002663 SetPixelBlue(image,
2664 LevelQuantum(GetPixelBlue(image,q)),q);
cristy2b9582a2011-07-04 17:38:56 +00002665 if (((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00002666 (image->matte == MagickTrue))
cristy4c08aed2011-07-01 19:47:50 +00002667 SetPixelAlpha(image,
2668 LevelQuantum(GetPixelAlpha(image,q)),q);
cristy2b9582a2011-07-04 17:38:56 +00002669 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00002670 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +00002671 SetPixelBlack(image,
2672 LevelQuantum(GetPixelBlack(image,q)),q);
2673 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00002674 }
2675 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2676 status=MagickFalse;
2677 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2678 {
2679 MagickBooleanType
2680 proceed;
2681
cristyb5d5f722009-11-04 03:03:49 +00002682#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00002683 #pragma omp critical (MagickCore_LevelImageChannel)
2684#endif
2685 proceed=SetImageProgress(image,LevelImageTag,progress++,image->rows);
2686 if (proceed == MagickFalse)
2687 status=MagickFalse;
2688 }
2689 }
2690 image_view=DestroyCacheView(image_view);
2691 return(status);
2692}
2693
2694/*
2695%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2696% %
2697% %
2698% %
2699% L e v e l i z e I m a g e C h a n n e l %
2700% %
2701% %
2702% %
2703%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2704%
2705% LevelizeImageChannel() applies the reversed LevelImage() operation to just
2706% the specific channels specified. It compresses the full range of color
2707% values, so that they lie between the given black and white points. Gamma is
2708% applied before the values are mapped.
2709%
2710% LevelizeImageChannel() can be called with by using a +level command line
2711% API option, or using a '!' on a -level or LevelImage() geometry string.
2712%
2713% It can be used for example de-contrast a greyscale image to the exact
2714% levels specified. Or by using specific levels for each channel of an image
2715% you can convert a gray-scale image to any linear color gradient, according
2716% to those levels.
2717%
2718% The format of the LevelizeImageChannel method is:
2719%
2720% MagickBooleanType LevelizeImageChannel(Image *image,
2721% const ChannelType channel,const char *levels)
2722%
2723% A description of each parameter follows:
2724%
2725% o image: the image.
2726%
2727% o channel: the channel.
2728%
2729% o black_point: The level to map zero (black) to.
2730%
2731% o white_point: The level to map QuantiumRange (white) to.
2732%
2733% o gamma: adjust gamma by this factor before mapping values.
2734%
2735*/
cristyd1a2c0f2011-02-09 14:14:50 +00002736
2737MagickExport MagickBooleanType LevelizeImage(Image *image,
2738 const double black_point,const double white_point,const double gamma)
2739{
2740 MagickBooleanType
2741 status;
2742
2743 status=LevelizeImageChannel(image,DefaultChannels,black_point,white_point,
2744 gamma);
2745 return(status);
2746}
2747
cristy3ed852e2009-09-05 21:47:34 +00002748MagickExport MagickBooleanType LevelizeImageChannel(Image *image,
2749 const ChannelType channel,const double black_point,const double white_point,
2750 const double gamma)
2751{
2752#define LevelizeImageTag "Levelize/Image"
cristyce70c172010-01-07 17:15:30 +00002753#define LevelizeValue(x) (ClampToQuantum(((MagickRealType) \
cristy3ed852e2009-09-05 21:47:34 +00002754 pow((double)(QuantumScale*(x)),1.0/gamma))*(white_point-black_point)+ \
2755 black_point))
2756
cristyc4c8d132010-01-07 01:58:38 +00002757 CacheView
2758 *image_view;
2759
cristy3ed852e2009-09-05 21:47:34 +00002760 ExceptionInfo
2761 *exception;
2762
cristy3ed852e2009-09-05 21:47:34 +00002763 MagickBooleanType
2764 status;
2765
cristybb503372010-05-27 20:51:26 +00002766 MagickOffsetType
2767 progress;
2768
2769 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002770 i;
2771
cristybb503372010-05-27 20:51:26 +00002772 ssize_t
2773 y;
2774
cristy3ed852e2009-09-05 21:47:34 +00002775 /*
2776 Allocate and initialize levels map.
2777 */
2778 assert(image != (Image *) NULL);
2779 assert(image->signature == MagickSignature);
2780 if (image->debug != MagickFalse)
2781 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2782 if (image->storage_class == PseudoClass)
cristyb5d5f722009-11-04 03:03:49 +00002783#if defined(MAGICKCORE_OPENMP_SUPPORT)
2784 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002785#endif
cristybb503372010-05-27 20:51:26 +00002786 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00002787 {
2788 /*
2789 Level colormap.
2790 */
cristy2b9582a2011-07-04 17:38:56 +00002791 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00002792 image->colormap[i].red=LevelizeValue(image->colormap[i].red);
cristy2b9582a2011-07-04 17:38:56 +00002793 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00002794 image->colormap[i].green=LevelizeValue(image->colormap[i].green);
cristy2b9582a2011-07-04 17:38:56 +00002795 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00002796 image->colormap[i].blue=LevelizeValue(image->colormap[i].blue);
cristy2b9582a2011-07-04 17:38:56 +00002797 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002798 image->colormap[i].alpha=LevelizeValue(image->colormap[i].alpha);
cristy3ed852e2009-09-05 21:47:34 +00002799 }
2800 /*
2801 Level image.
2802 */
2803 status=MagickTrue;
2804 progress=0;
2805 exception=(&image->exception);
2806 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00002807#if defined(MAGICKCORE_OPENMP_SUPPORT)
2808 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002809#endif
cristybb503372010-05-27 20:51:26 +00002810 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002811 {
cristy4c08aed2011-07-01 19:47:50 +00002812 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00002813 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002814
cristy8d4629b2010-08-30 17:59:46 +00002815 register ssize_t
2816 x;
2817
cristy3ed852e2009-09-05 21:47:34 +00002818 if (status == MagickFalse)
2819 continue;
2820 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00002821 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00002822 {
2823 status=MagickFalse;
2824 continue;
2825 }
cristybb503372010-05-27 20:51:26 +00002826 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002827 {
cristy2b9582a2011-07-04 17:38:56 +00002828 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002829 SetPixelRed(image,LevelizeValue(GetPixelRed(image,q)),q);
cristy2b9582a2011-07-04 17:38:56 +00002830 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002831 SetPixelGreen(image,LevelizeValue(GetPixelGreen(image,q)),q);
cristy2b9582a2011-07-04 17:38:56 +00002832 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002833 SetPixelBlue(image,LevelizeValue(GetPixelBlue(image,q)),q);
cristy2b9582a2011-07-04 17:38:56 +00002834 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +00002835 (image->colorspace == CMYKColorspace))
2836 SetPixelBlack(image,LevelizeValue(GetPixelBlack(image,q)),q);
cristy2b9582a2011-07-04 17:38:56 +00002837 if (((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00002838 (image->matte == MagickTrue))
cristy4c08aed2011-07-01 19:47:50 +00002839 SetPixelAlpha(image,LevelizeValue(GetPixelAlpha(image,q)),q);
2840 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00002841 }
2842 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2843 status=MagickFalse;
2844 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2845 {
2846 MagickBooleanType
2847 proceed;
2848
cristyb5d5f722009-11-04 03:03:49 +00002849#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00002850 #pragma omp critical (MagickCore_LevelizeImageChannel)
2851#endif
2852 proceed=SetImageProgress(image,LevelizeImageTag,progress++,image->rows);
2853 if (proceed == MagickFalse)
2854 status=MagickFalse;
2855 }
2856 }
cristy8d4629b2010-08-30 17:59:46 +00002857 image_view=DestroyCacheView(image_view);
cristy3ed852e2009-09-05 21:47:34 +00002858 return(status);
2859}
2860
2861/*
2862%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2863% %
2864% %
2865% %
2866% L e v e l I m a g e C o l o r s %
2867% %
2868% %
2869% %
2870%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2871%
cristyee0f8d72009-09-19 00:58:29 +00002872% LevelImageColor() maps the given color to "black" and "white" values,
2873% linearly spreading out the colors, and level values on a channel by channel
2874% bases, as per LevelImage(). The given colors allows you to specify
glennrp1e7f7bc2011-03-02 19:25:28 +00002875% different level ranges for each of the color channels separately.
cristy3ed852e2009-09-05 21:47:34 +00002876%
2877% If the boolean 'invert' is set true the image values will modifyed in the
2878% reverse direction. That is any existing "black" and "white" colors in the
2879% image will become the color values given, with all other values compressed
2880% appropriatally. This effectivally maps a greyscale gradient into the given
2881% color gradient.
2882%
cristy308b4e62009-09-21 14:40:44 +00002883% The format of the LevelColorsImageChannel method is:
cristy3ed852e2009-09-05 21:47:34 +00002884%
cristy308b4e62009-09-21 14:40:44 +00002885% MagickBooleanType LevelColorsImage(Image *image,
cristy4c08aed2011-07-01 19:47:50 +00002886% const PixelInfo *black_color,
2887% const PixelInfo *white_color,const MagickBooleanType invert)
cristy308b4e62009-09-21 14:40:44 +00002888% MagickBooleanType LevelColorsImageChannel(Image *image,
cristy4c08aed2011-07-01 19:47:50 +00002889% const ChannelType channel,const PixelInfo *black_color,
2890% const PixelInfo *white_color,const MagickBooleanType invert)
cristy3ed852e2009-09-05 21:47:34 +00002891%
2892% A description of each parameter follows:
2893%
2894% o image: the image.
2895%
2896% o channel: the channel.
2897%
2898% o black_color: The color to map black to/from
2899%
2900% o white_point: The color to map white to/from
2901%
2902% o invert: if true map the colors (levelize), rather than from (level)
2903%
2904*/
cristy308b4e62009-09-21 14:40:44 +00002905
2906MagickExport MagickBooleanType LevelColorsImage(Image *image,
cristy4c08aed2011-07-01 19:47:50 +00002907 const PixelInfo *black_color,const PixelInfo *white_color,
cristy3ed852e2009-09-05 21:47:34 +00002908 const MagickBooleanType invert)
2909{
cristy308b4e62009-09-21 14:40:44 +00002910 MagickBooleanType
2911 status;
cristy3ed852e2009-09-05 21:47:34 +00002912
cristy308b4e62009-09-21 14:40:44 +00002913 status=LevelColorsImageChannel(image,DefaultChannels,black_color,white_color,
2914 invert);
2915 return(status);
2916}
2917
2918MagickExport MagickBooleanType LevelColorsImageChannel(Image *image,
cristy4c08aed2011-07-01 19:47:50 +00002919 const ChannelType channel,const PixelInfo *black_color,
2920 const PixelInfo *white_color,const MagickBooleanType invert)
cristy308b4e62009-09-21 14:40:44 +00002921{
cristy3ed852e2009-09-05 21:47:34 +00002922 MagickStatusType
2923 status;
2924
2925 /*
2926 Allocate and initialize levels map.
2927 */
2928 assert(image != (Image *) NULL);
2929 assert(image->signature == MagickSignature);
2930 if (image->debug != MagickFalse)
2931 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2932 status=MagickFalse;
2933 if (invert == MagickFalse)
2934 {
cristy2b9582a2011-07-04 17:38:56 +00002935 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00002936 status|=LevelImageChannel(image,RedChannel,
2937 black_color->red,white_color->red,(double) 1.0);
cristy2b9582a2011-07-04 17:38:56 +00002938 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00002939 status|=LevelImageChannel(image,GreenChannel,
2940 black_color->green,white_color->green,(double) 1.0);
cristy2b9582a2011-07-04 17:38:56 +00002941 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00002942 status|=LevelImageChannel(image,BlueChannel,
2943 black_color->blue,white_color->blue,(double) 1.0);
cristy2b9582a2011-07-04 17:38:56 +00002944 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +00002945 (image->colorspace == CMYKColorspace))
2946 status|=LevelImageChannel(image,BlackChannel,
2947 black_color->black,white_color->black,(double) 1.0);
cristy2b9582a2011-07-04 17:38:56 +00002948 if (((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00002949 (image->matte == MagickTrue))
2950 status|=LevelImageChannel(image,OpacityChannel,
cristy4c08aed2011-07-01 19:47:50 +00002951 black_color->alpha,white_color->alpha,(double) 1.0);
cristy3ed852e2009-09-05 21:47:34 +00002952 }
2953 else
2954 {
cristy2b9582a2011-07-04 17:38:56 +00002955 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00002956 status|=LevelizeImageChannel(image,RedChannel,
2957 black_color->red,white_color->red,(double) 1.0);
cristy2b9582a2011-07-04 17:38:56 +00002958 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00002959 status|=LevelizeImageChannel(image,GreenChannel,
2960 black_color->green,white_color->green,(double) 1.0);
cristy2b9582a2011-07-04 17:38:56 +00002961 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00002962 status|=LevelizeImageChannel(image,BlueChannel,
2963 black_color->blue,white_color->blue,(double) 1.0);
cristy2b9582a2011-07-04 17:38:56 +00002964 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +00002965 (image->colorspace == CMYKColorspace))
2966 status|=LevelizeImageChannel(image,BlackChannel,
2967 black_color->black,white_color->black,(double) 1.0);
cristy2b9582a2011-07-04 17:38:56 +00002968 if (((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00002969 (image->matte == MagickTrue))
2970 status|=LevelizeImageChannel(image,OpacityChannel,
cristy4c08aed2011-07-01 19:47:50 +00002971 black_color->alpha,white_color->alpha,(double) 1.0);
cristy3ed852e2009-09-05 21:47:34 +00002972 }
2973 return(status == 0 ? MagickFalse : MagickTrue);
2974}
2975
2976/*
2977%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2978% %
2979% %
2980% %
2981% L i n e a r S t r e t c h I m a g e %
2982% %
2983% %
2984% %
2985%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2986%
2987% The LinearStretchImage() discards any pixels below the black point and
2988% above the white point and levels the remaining pixels.
2989%
2990% The format of the LinearStretchImage method is:
2991%
2992% MagickBooleanType LinearStretchImage(Image *image,
2993% const double black_point,const double white_point)
2994%
2995% A description of each parameter follows:
2996%
2997% o image: the image.
2998%
2999% o black_point: the black point.
3000%
3001% o white_point: the white point.
3002%
3003*/
3004MagickExport MagickBooleanType LinearStretchImage(Image *image,
3005 const double black_point,const double white_point)
3006{
3007#define LinearStretchImageTag "LinearStretch/Image"
3008
3009 ExceptionInfo
3010 *exception;
3011
cristy3ed852e2009-09-05 21:47:34 +00003012 MagickBooleanType
3013 status;
3014
3015 MagickRealType
3016 *histogram,
3017 intensity;
3018
cristy8d4629b2010-08-30 17:59:46 +00003019 ssize_t
3020 black,
3021 white,
3022 y;
3023
cristy3ed852e2009-09-05 21:47:34 +00003024 /*
3025 Allocate histogram and linear map.
3026 */
3027 assert(image != (Image *) NULL);
3028 assert(image->signature == MagickSignature);
3029 histogram=(MagickRealType *) AcquireQuantumMemory(MaxMap+1UL,
3030 sizeof(*histogram));
3031 if (histogram == (MagickRealType *) NULL)
3032 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
3033 image->filename);
3034 /*
3035 Form histogram.
3036 */
3037 (void) ResetMagickMemory(histogram,0,(MaxMap+1)*sizeof(*histogram));
3038 exception=(&image->exception);
cristybb503372010-05-27 20:51:26 +00003039 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003040 {
cristy4c08aed2011-07-01 19:47:50 +00003041 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00003042 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00003043
cristybb503372010-05-27 20:51:26 +00003044 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003045 x;
3046
3047 p=GetVirtualPixels(image,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00003048 if (p == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00003049 break;
cristybb503372010-05-27 20:51:26 +00003050 for (x=(ssize_t) image->columns-1; x >= 0; x--)
cristy3ed852e2009-09-05 21:47:34 +00003051 {
cristy4c08aed2011-07-01 19:47:50 +00003052 histogram[ScaleQuantumToMap(GetPixelIntensity(image,p))]++;
3053 p+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00003054 }
3055 }
3056 /*
3057 Find the histogram boundaries by locating the black and white point levels.
3058 */
cristy3ed852e2009-09-05 21:47:34 +00003059 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00003060 for (black=0; black < (ssize_t) MaxMap; black++)
cristy3ed852e2009-09-05 21:47:34 +00003061 {
3062 intensity+=histogram[black];
3063 if (intensity >= black_point)
3064 break;
3065 }
3066 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00003067 for (white=(ssize_t) MaxMap; white != 0; white--)
cristy3ed852e2009-09-05 21:47:34 +00003068 {
3069 intensity+=histogram[white];
3070 if (intensity >= white_point)
3071 break;
3072 }
3073 histogram=(MagickRealType *) RelinquishMagickMemory(histogram);
3074 status=LevelImageChannel(image,DefaultChannels,(double) black,(double) white,
3075 1.0);
3076 return(status);
3077}
3078
3079/*
3080%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3081% %
3082% %
3083% %
3084% M o d u l a t e I m a g e %
3085% %
3086% %
3087% %
3088%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3089%
3090% ModulateImage() lets you control the brightness, saturation, and hue
3091% of an image. Modulate represents the brightness, saturation, and hue
3092% as one parameter (e.g. 90,150,100). If the image colorspace is HSL, the
3093% modulation is lightness, saturation, and hue. And if the colorspace is
3094% HWB, use blackness, whiteness, and hue.
3095%
3096% The format of the ModulateImage method is:
3097%
3098% MagickBooleanType ModulateImage(Image *image,const char *modulate)
3099%
3100% A description of each parameter follows:
3101%
3102% o image: the image.
3103%
3104% o modulate: Define the percent change in brightness, saturation, and
3105% hue.
3106%
3107*/
3108
3109static void ModulateHSB(const double percent_hue,
3110 const double percent_saturation,const double percent_brightness,
3111 Quantum *red,Quantum *green,Quantum *blue)
3112{
3113 double
3114 brightness,
3115 hue,
3116 saturation;
3117
3118 /*
3119 Increase or decrease color brightness, saturation, or hue.
3120 */
3121 assert(red != (Quantum *) NULL);
3122 assert(green != (Quantum *) NULL);
3123 assert(blue != (Quantum *) NULL);
3124 ConvertRGBToHSB(*red,*green,*blue,&hue,&saturation,&brightness);
3125 hue+=0.5*(0.01*percent_hue-1.0);
3126 while (hue < 0.0)
3127 hue+=1.0;
3128 while (hue > 1.0)
3129 hue-=1.0;
3130 saturation*=0.01*percent_saturation;
3131 brightness*=0.01*percent_brightness;
3132 ConvertHSBToRGB(hue,saturation,brightness,red,green,blue);
3133}
3134
3135static void ModulateHSL(const double percent_hue,
3136 const double percent_saturation,const double percent_lightness,
3137 Quantum *red,Quantum *green,Quantum *blue)
3138{
3139 double
3140 hue,
3141 lightness,
3142 saturation;
3143
3144 /*
3145 Increase or decrease color lightness, saturation, or hue.
3146 */
3147 assert(red != (Quantum *) NULL);
3148 assert(green != (Quantum *) NULL);
3149 assert(blue != (Quantum *) NULL);
3150 ConvertRGBToHSL(*red,*green,*blue,&hue,&saturation,&lightness);
3151 hue+=0.5*(0.01*percent_hue-1.0);
3152 while (hue < 0.0)
3153 hue+=1.0;
3154 while (hue > 1.0)
3155 hue-=1.0;
3156 saturation*=0.01*percent_saturation;
3157 lightness*=0.01*percent_lightness;
3158 ConvertHSLToRGB(hue,saturation,lightness,red,green,blue);
3159}
3160
3161static void ModulateHWB(const double percent_hue,const double percent_whiteness, const double percent_blackness,Quantum *red,Quantum *green,Quantum *blue)
3162{
3163 double
3164 blackness,
3165 hue,
3166 whiteness;
3167
3168 /*
3169 Increase or decrease color blackness, whiteness, or hue.
3170 */
3171 assert(red != (Quantum *) NULL);
3172 assert(green != (Quantum *) NULL);
3173 assert(blue != (Quantum *) NULL);
3174 ConvertRGBToHWB(*red,*green,*blue,&hue,&whiteness,&blackness);
3175 hue+=0.5*(0.01*percent_hue-1.0);
3176 while (hue < 0.0)
3177 hue+=1.0;
3178 while (hue > 1.0)
3179 hue-=1.0;
3180 blackness*=0.01*percent_blackness;
3181 whiteness*=0.01*percent_whiteness;
3182 ConvertHWBToRGB(hue,whiteness,blackness,red,green,blue);
3183}
3184
3185MagickExport MagickBooleanType ModulateImage(Image *image,const char *modulate)
3186{
3187#define ModulateImageTag "Modulate/Image"
3188
cristyc4c8d132010-01-07 01:58:38 +00003189 CacheView
3190 *image_view;
3191
cristy3ed852e2009-09-05 21:47:34 +00003192 ColorspaceType
3193 colorspace;
3194
3195 const char
3196 *artifact;
3197
3198 double
3199 percent_brightness,
3200 percent_hue,
3201 percent_saturation;
3202
3203 ExceptionInfo
3204 *exception;
3205
3206 GeometryInfo
3207 geometry_info;
3208
cristy3ed852e2009-09-05 21:47:34 +00003209 MagickBooleanType
3210 status;
3211
cristybb503372010-05-27 20:51:26 +00003212 MagickOffsetType
3213 progress;
3214
cristy3ed852e2009-09-05 21:47:34 +00003215 MagickStatusType
3216 flags;
3217
cristybb503372010-05-27 20:51:26 +00003218 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003219 i;
3220
cristybb503372010-05-27 20:51:26 +00003221 ssize_t
3222 y;
3223
cristy3ed852e2009-09-05 21:47:34 +00003224 /*
cristy2b726bd2010-01-11 01:05:39 +00003225 Initialize modulate table.
cristy3ed852e2009-09-05 21:47:34 +00003226 */
3227 assert(image != (Image *) NULL);
3228 assert(image->signature == MagickSignature);
3229 if (image->debug != MagickFalse)
3230 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3231 if (modulate == (char *) NULL)
3232 return(MagickFalse);
3233 flags=ParseGeometry(modulate,&geometry_info);
3234 percent_brightness=geometry_info.rho;
3235 percent_saturation=geometry_info.sigma;
3236 if ((flags & SigmaValue) == 0)
3237 percent_saturation=100.0;
3238 percent_hue=geometry_info.xi;
3239 if ((flags & XiValue) == 0)
3240 percent_hue=100.0;
3241 colorspace=UndefinedColorspace;
3242 artifact=GetImageArtifact(image,"modulate:colorspace");
3243 if (artifact != (const char *) NULL)
cristy042ee782011-04-22 18:48:30 +00003244 colorspace=(ColorspaceType) ParseCommandOption(MagickColorspaceOptions,
cristy3ed852e2009-09-05 21:47:34 +00003245 MagickFalse,artifact);
3246 if (image->storage_class == PseudoClass)
3247 {
3248 /*
3249 Modulate colormap.
3250 */
cristyb5d5f722009-11-04 03:03:49 +00003251#if defined(MAGICKCORE_OPENMP_SUPPORT)
3252 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003253#endif
cristybb503372010-05-27 20:51:26 +00003254 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00003255 switch (colorspace)
3256 {
3257 case HSBColorspace:
3258 {
3259 ModulateHSB(percent_hue,percent_saturation,percent_brightness,
3260 &image->colormap[i].red,&image->colormap[i].green,
3261 &image->colormap[i].blue);
3262 break;
3263 }
3264 case HSLColorspace:
3265 default:
3266 {
3267 ModulateHSL(percent_hue,percent_saturation,percent_brightness,
3268 &image->colormap[i].red,&image->colormap[i].green,
3269 &image->colormap[i].blue);
3270 break;
3271 }
3272 case HWBColorspace:
3273 {
3274 ModulateHWB(percent_hue,percent_saturation,percent_brightness,
3275 &image->colormap[i].red,&image->colormap[i].green,
3276 &image->colormap[i].blue);
3277 break;
3278 }
3279 }
3280 }
3281 /*
3282 Modulate image.
3283 */
3284 status=MagickTrue;
3285 progress=0;
3286 exception=(&image->exception);
3287 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00003288#if defined(MAGICKCORE_OPENMP_SUPPORT)
3289 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003290#endif
cristybb503372010-05-27 20:51:26 +00003291 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003292 {
cristy5afeab82011-04-30 01:30:09 +00003293 Quantum
3294 blue,
3295 green,
3296 red;
3297
cristy4c08aed2011-07-01 19:47:50 +00003298 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00003299 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003300
cristy8d4629b2010-08-30 17:59:46 +00003301 register ssize_t
3302 x;
3303
cristy3ed852e2009-09-05 21:47:34 +00003304 if (status == MagickFalse)
3305 continue;
3306 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00003307 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00003308 {
3309 status=MagickFalse;
3310 continue;
3311 }
cristybb503372010-05-27 20:51:26 +00003312 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003313 {
cristy4c08aed2011-07-01 19:47:50 +00003314 red=GetPixelRed(image,q);
3315 green=GetPixelGreen(image,q);
3316 blue=GetPixelBlue(image,q);
cristy3ed852e2009-09-05 21:47:34 +00003317 switch (colorspace)
3318 {
3319 case HSBColorspace:
3320 {
3321 ModulateHSB(percent_hue,percent_saturation,percent_brightness,
cristy5afeab82011-04-30 01:30:09 +00003322 &red,&green,&blue);
cristy3ed852e2009-09-05 21:47:34 +00003323 break;
3324 }
3325 case HSLColorspace:
3326 default:
3327 {
3328 ModulateHSL(percent_hue,percent_saturation,percent_brightness,
cristy5afeab82011-04-30 01:30:09 +00003329 &red,&green,&blue);
cristy3ed852e2009-09-05 21:47:34 +00003330 break;
3331 }
3332 case HWBColorspace:
3333 {
3334 ModulateHWB(percent_hue,percent_saturation,percent_brightness,
cristy5afeab82011-04-30 01:30:09 +00003335 &red,&green,&blue);
cristy3ed852e2009-09-05 21:47:34 +00003336 break;
3337 }
3338 }
cristy4c08aed2011-07-01 19:47:50 +00003339 SetPixelRed(image,red,q);
3340 SetPixelGreen(image,green,q);
3341 SetPixelBlue(image,blue,q);
3342 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00003343 }
3344 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
3345 status=MagickFalse;
3346 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3347 {
3348 MagickBooleanType
3349 proceed;
3350
cristyb5d5f722009-11-04 03:03:49 +00003351#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00003352 #pragma omp critical (MagickCore_ModulateImage)
3353#endif
3354 proceed=SetImageProgress(image,ModulateImageTag,progress++,image->rows);
3355 if (proceed == MagickFalse)
3356 status=MagickFalse;
3357 }
3358 }
3359 image_view=DestroyCacheView(image_view);
3360 return(status);
3361}
3362
3363/*
3364%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3365% %
3366% %
3367% %
3368% N e g a t e I m a g e %
3369% %
3370% %
3371% %
3372%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3373%
3374% NegateImage() negates the colors in the reference image. The grayscale
3375% option means that only grayscale values within the image are negated.
3376%
3377% The format of the NegateImageChannel method is:
3378%
3379% MagickBooleanType NegateImage(Image *image,
3380% const MagickBooleanType grayscale)
3381% MagickBooleanType NegateImageChannel(Image *image,
3382% const ChannelType channel,const MagickBooleanType grayscale)
3383%
3384% A description of each parameter follows:
3385%
3386% o image: the image.
3387%
3388% o channel: the channel.
3389%
3390% o grayscale: If MagickTrue, only negate grayscale pixels within the image.
3391%
3392*/
3393
3394MagickExport MagickBooleanType NegateImage(Image *image,
3395 const MagickBooleanType grayscale)
3396{
3397 MagickBooleanType
3398 status;
3399
3400 status=NegateImageChannel(image,DefaultChannels,grayscale);
3401 return(status);
3402}
3403
3404MagickExport MagickBooleanType NegateImageChannel(Image *image,
3405 const ChannelType channel,const MagickBooleanType grayscale)
3406{
3407#define NegateImageTag "Negate/Image"
3408
cristyc4c8d132010-01-07 01:58:38 +00003409 CacheView
3410 *image_view;
3411
cristy3ed852e2009-09-05 21:47:34 +00003412 ExceptionInfo
3413 *exception;
3414
cristy3ed852e2009-09-05 21:47:34 +00003415 MagickBooleanType
3416 status;
3417
cristybb503372010-05-27 20:51:26 +00003418 MagickOffsetType
3419 progress;
3420
3421 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003422 i;
3423
cristybb503372010-05-27 20:51:26 +00003424 ssize_t
3425 y;
3426
cristy3ed852e2009-09-05 21:47:34 +00003427 assert(image != (Image *) NULL);
3428 assert(image->signature == MagickSignature);
3429 if (image->debug != MagickFalse)
3430 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3431 if (image->storage_class == PseudoClass)
3432 {
3433 /*
3434 Negate colormap.
3435 */
cristyb5d5f722009-11-04 03:03:49 +00003436#if defined(MAGICKCORE_OPENMP_SUPPORT)
3437 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003438#endif
cristybb503372010-05-27 20:51:26 +00003439 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00003440 {
3441 if (grayscale != MagickFalse)
3442 if ((image->colormap[i].red != image->colormap[i].green) ||
3443 (image->colormap[i].green != image->colormap[i].blue))
3444 continue;
cristy2b9582a2011-07-04 17:38:56 +00003445 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00003446 image->colormap[i].red=(Quantum) QuantumRange-
3447 image->colormap[i].red;
cristy2b9582a2011-07-04 17:38:56 +00003448 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00003449 image->colormap[i].green=(Quantum) QuantumRange-
3450 image->colormap[i].green;
cristy2b9582a2011-07-04 17:38:56 +00003451 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00003452 image->colormap[i].blue=(Quantum) QuantumRange-
3453 image->colormap[i].blue;
3454 }
3455 }
3456 /*
3457 Negate image.
3458 */
3459 status=MagickTrue;
3460 progress=0;
3461 exception=(&image->exception);
3462 image_view=AcquireCacheView(image);
3463 if (grayscale != MagickFalse)
3464 {
cristyb5d5f722009-11-04 03:03:49 +00003465#if defined(MAGICKCORE_OPENMP_SUPPORT)
3466 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003467#endif
cristybb503372010-05-27 20:51:26 +00003468 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003469 {
3470 MagickBooleanType
3471 sync;
3472
cristy4c08aed2011-07-01 19:47:50 +00003473 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00003474 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003475
cristy8d4629b2010-08-30 17:59:46 +00003476 register ssize_t
3477 x;
3478
cristy3ed852e2009-09-05 21:47:34 +00003479 if (status == MagickFalse)
3480 continue;
3481 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,
3482 exception);
cristy4c08aed2011-07-01 19:47:50 +00003483 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00003484 {
3485 status=MagickFalse;
3486 continue;
3487 }
cristybb503372010-05-27 20:51:26 +00003488 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003489 {
cristy4c08aed2011-07-01 19:47:50 +00003490 if ((GetPixelRed(image,q) != GetPixelGreen(image,q)) ||
3491 (GetPixelGreen(image,q) != GetPixelBlue(image,q)))
cristy3ed852e2009-09-05 21:47:34 +00003492 {
cristy4c08aed2011-07-01 19:47:50 +00003493 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00003494 continue;
3495 }
cristy2b9582a2011-07-04 17:38:56 +00003496 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003497 SetPixelRed(image,QuantumRange-GetPixelRed(image,q),q);
cristy2b9582a2011-07-04 17:38:56 +00003498 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003499 SetPixelGreen(image,QuantumRange-GetPixelGreen(image,q),q);
cristy2b9582a2011-07-04 17:38:56 +00003500 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003501 SetPixelBlue(image,QuantumRange-GetPixelBlue(image,q),q);
cristy2b9582a2011-07-04 17:38:56 +00003502 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00003503 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +00003504 SetPixelBlack(image,QuantumRange-GetPixelBlack(image,q),q);
cristy2b9582a2011-07-04 17:38:56 +00003505 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003506 SetPixelAlpha(image,QuantumRange-GetPixelAlpha(image,q),q);
3507 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00003508 }
3509 sync=SyncCacheViewAuthenticPixels(image_view,exception);
3510 if (sync == MagickFalse)
3511 status=MagickFalse;
3512 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3513 {
3514 MagickBooleanType
3515 proceed;
3516
cristyb5d5f722009-11-04 03:03:49 +00003517#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00003518 #pragma omp critical (MagickCore_NegateImageChannel)
3519#endif
3520 proceed=SetImageProgress(image,NegateImageTag,progress++,
3521 image->rows);
3522 if (proceed == MagickFalse)
3523 status=MagickFalse;
3524 }
3525 }
3526 image_view=DestroyCacheView(image_view);
3527 return(MagickTrue);
3528 }
3529 /*
3530 Negate image.
3531 */
cristyb5d5f722009-11-04 03:03:49 +00003532#if defined(MAGICKCORE_OPENMP_SUPPORT)
3533 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003534#endif
cristybb503372010-05-27 20:51:26 +00003535 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003536 {
cristy4c08aed2011-07-01 19:47:50 +00003537 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00003538 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003539
cristy8d4629b2010-08-30 17:59:46 +00003540 register ssize_t
3541 x;
3542
cristy3ed852e2009-09-05 21:47:34 +00003543 if (status == MagickFalse)
3544 continue;
3545 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00003546 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00003547 {
3548 status=MagickFalse;
3549 continue;
3550 }
cristybb503372010-05-27 20:51:26 +00003551 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003552 {
cristy2b9582a2011-07-04 17:38:56 +00003553 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003554 SetPixelRed(image,QuantumRange-GetPixelRed(image,q),q);
cristy2b9582a2011-07-04 17:38:56 +00003555 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003556 SetPixelGreen(image,QuantumRange-GetPixelGreen(image,q),q);
cristy2b9582a2011-07-04 17:38:56 +00003557 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003558 SetPixelBlue(image,QuantumRange-GetPixelBlue(image,q),q);
cristy2b9582a2011-07-04 17:38:56 +00003559 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00003560 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +00003561 SetPixelBlack(image,QuantumRange-GetPixelBlack(image,q),q);
cristy2b9582a2011-07-04 17:38:56 +00003562 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003563 SetPixelAlpha(image,QuantumRange-GetPixelAlpha(image,q),q);
3564 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00003565 }
3566 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
3567 status=MagickFalse;
3568 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3569 {
3570 MagickBooleanType
3571 proceed;
3572
cristyb5d5f722009-11-04 03:03:49 +00003573#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00003574 #pragma omp critical (MagickCore_NegateImageChannel)
3575#endif
3576 proceed=SetImageProgress(image,NegateImageTag,progress++,image->rows);
3577 if (proceed == MagickFalse)
3578 status=MagickFalse;
3579 }
3580 }
3581 image_view=DestroyCacheView(image_view);
3582 return(status);
3583}
3584
3585/*
3586%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3587% %
3588% %
3589% %
3590% N o r m a l i z e I m a g e %
3591% %
3592% %
3593% %
3594%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3595%
3596% The NormalizeImage() method enhances the contrast of a color image by
3597% mapping the darkest 2 percent of all pixel to black and the brightest
3598% 1 percent to white.
3599%
3600% The format of the NormalizeImage method is:
3601%
3602% MagickBooleanType NormalizeImage(Image *image)
3603% MagickBooleanType NormalizeImageChannel(Image *image,
3604% const ChannelType channel)
3605%
3606% A description of each parameter follows:
3607%
3608% o image: the image.
3609%
3610% o channel: the channel.
3611%
3612*/
3613
3614MagickExport MagickBooleanType NormalizeImage(Image *image)
3615{
3616 MagickBooleanType
3617 status;
3618
3619 status=NormalizeImageChannel(image,DefaultChannels);
3620 return(status);
3621}
3622
3623MagickExport MagickBooleanType NormalizeImageChannel(Image *image,
3624 const ChannelType channel)
3625{
3626 double
3627 black_point,
3628 white_point;
3629
cristy530239c2010-07-25 17:34:26 +00003630 black_point=(double) image->columns*image->rows*0.0015;
3631 white_point=(double) image->columns*image->rows*0.9995;
cristy3ed852e2009-09-05 21:47:34 +00003632 return(ContrastStretchImageChannel(image,channel,black_point,white_point));
3633}
3634
3635/*
3636%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3637% %
3638% %
3639% %
3640% S i g m o i d a l C o n t r a s t I m a g e %
3641% %
3642% %
3643% %
3644%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3645%
3646% SigmoidalContrastImage() adjusts the contrast of an image with a non-linear
3647% sigmoidal contrast algorithm. Increase the contrast of the image using a
3648% sigmoidal transfer function without saturating highlights or shadows.
3649% Contrast indicates how much to increase the contrast (0 is none; 3 is
3650% typical; 20 is pushing it); mid-point indicates where midtones fall in the
3651% resultant image (0 is white; 50% is middle-gray; 100% is black). Set
3652% sharpen to MagickTrue to increase the image contrast otherwise the contrast
3653% is reduced.
3654%
3655% The format of the SigmoidalContrastImage method is:
3656%
3657% MagickBooleanType SigmoidalContrastImage(Image *image,
3658% const MagickBooleanType sharpen,const char *levels)
3659% MagickBooleanType SigmoidalContrastImageChannel(Image *image,
3660% const ChannelType channel,const MagickBooleanType sharpen,
3661% const double contrast,const double midpoint)
3662%
3663% A description of each parameter follows:
3664%
3665% o image: the image.
3666%
3667% o channel: the channel.
3668%
3669% o sharpen: Increase or decrease image contrast.
3670%
cristyfa769582010-09-30 23:30:03 +00003671% o alpha: strength of the contrast, the larger the number the more
3672% 'threshold-like' it becomes.
cristy3ed852e2009-09-05 21:47:34 +00003673%
cristyfa769582010-09-30 23:30:03 +00003674% o beta: midpoint of the function as a color value 0 to QuantumRange.
cristy3ed852e2009-09-05 21:47:34 +00003675%
3676*/
3677
3678MagickExport MagickBooleanType SigmoidalContrastImage(Image *image,
3679 const MagickBooleanType sharpen,const char *levels)
3680{
3681 GeometryInfo
3682 geometry_info;
3683
3684 MagickBooleanType
3685 status;
3686
3687 MagickStatusType
3688 flags;
3689
3690 flags=ParseGeometry(levels,&geometry_info);
3691 if ((flags & SigmaValue) == 0)
3692 geometry_info.sigma=1.0*QuantumRange/2.0;
3693 if ((flags & PercentValue) != 0)
3694 geometry_info.sigma=1.0*QuantumRange*geometry_info.sigma/100.0;
3695 status=SigmoidalContrastImageChannel(image,DefaultChannels,sharpen,
3696 geometry_info.rho,geometry_info.sigma);
3697 return(status);
3698}
3699
3700MagickExport MagickBooleanType SigmoidalContrastImageChannel(Image *image,
3701 const ChannelType channel,const MagickBooleanType sharpen,
3702 const double contrast,const double midpoint)
3703{
3704#define SigmoidalContrastImageTag "SigmoidalContrast/Image"
3705
cristyc4c8d132010-01-07 01:58:38 +00003706 CacheView
3707 *image_view;
3708
cristy3ed852e2009-09-05 21:47:34 +00003709 ExceptionInfo
3710 *exception;
3711
cristy3ed852e2009-09-05 21:47:34 +00003712 MagickBooleanType
3713 status;
3714
cristybb503372010-05-27 20:51:26 +00003715 MagickOffsetType
3716 progress;
3717
cristy3ed852e2009-09-05 21:47:34 +00003718 MagickRealType
3719 *sigmoidal_map;
3720
cristybb503372010-05-27 20:51:26 +00003721 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003722 i;
3723
cristybb503372010-05-27 20:51:26 +00003724 ssize_t
3725 y;
3726
cristy3ed852e2009-09-05 21:47:34 +00003727 /*
3728 Allocate and initialize sigmoidal maps.
3729 */
3730 assert(image != (Image *) NULL);
3731 assert(image->signature == MagickSignature);
3732 if (image->debug != MagickFalse)
3733 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3734 sigmoidal_map=(MagickRealType *) AcquireQuantumMemory(MaxMap+1UL,
3735 sizeof(*sigmoidal_map));
3736 if (sigmoidal_map == (MagickRealType *) NULL)
3737 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
3738 image->filename);
3739 (void) ResetMagickMemory(sigmoidal_map,0,(MaxMap+1)*sizeof(*sigmoidal_map));
cristyb5d5f722009-11-04 03:03:49 +00003740#if defined(MAGICKCORE_OPENMP_SUPPORT)
3741 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003742#endif
cristybb503372010-05-27 20:51:26 +00003743 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +00003744 {
3745 if (sharpen != MagickFalse)
3746 {
3747 sigmoidal_map[i]=(MagickRealType) ScaleMapToQuantum((MagickRealType)
3748 (MaxMap*((1.0/(1.0+exp(contrast*(midpoint/(double) QuantumRange-
3749 (double) i/MaxMap))))-(1.0/(1.0+exp(contrast*(midpoint/
3750 (double) QuantumRange)))))/((1.0/(1.0+exp(contrast*(midpoint/
3751 (double) QuantumRange-1.0))))-(1.0/(1.0+exp(contrast*(midpoint/
3752 (double) QuantumRange)))))+0.5));
3753 continue;
3754 }
3755 sigmoidal_map[i]=(MagickRealType) ScaleMapToQuantum((MagickRealType)
3756 (MaxMap*(QuantumScale*midpoint-log((1.0-(1.0/(1.0+exp(midpoint/
3757 (double) QuantumRange*contrast))+((double) i/MaxMap)*((1.0/
3758 (1.0+exp(contrast*(midpoint/(double) QuantumRange-1.0))))-(1.0/
3759 (1.0+exp(midpoint/(double) QuantumRange*contrast))))))/
3760 (1.0/(1.0+exp(midpoint/(double) QuantumRange*contrast))+
3761 ((double) i/MaxMap)*((1.0/(1.0+exp(contrast*(midpoint/
3762 (double) QuantumRange-1.0))))-(1.0/(1.0+exp(midpoint/
3763 (double) QuantumRange*contrast))))))/contrast)));
3764 }
3765 if (image->storage_class == PseudoClass)
3766 {
3767 /*
3768 Sigmoidal-contrast enhance colormap.
3769 */
cristyb5d5f722009-11-04 03:03:49 +00003770#if defined(MAGICKCORE_OPENMP_SUPPORT)
3771 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003772#endif
cristybb503372010-05-27 20:51:26 +00003773 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00003774 {
cristy2b9582a2011-07-04 17:38:56 +00003775 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristyce70c172010-01-07 17:15:30 +00003776 image->colormap[i].red=ClampToQuantum(sigmoidal_map[
cristy3ed852e2009-09-05 21:47:34 +00003777 ScaleQuantumToMap(image->colormap[i].red)]);
cristy2b9582a2011-07-04 17:38:56 +00003778 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristyce70c172010-01-07 17:15:30 +00003779 image->colormap[i].green=ClampToQuantum(sigmoidal_map[
cristy3ed852e2009-09-05 21:47:34 +00003780 ScaleQuantumToMap(image->colormap[i].green)]);
cristy2b9582a2011-07-04 17:38:56 +00003781 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristyce70c172010-01-07 17:15:30 +00003782 image->colormap[i].blue=ClampToQuantum(sigmoidal_map[
cristy3ed852e2009-09-05 21:47:34 +00003783 ScaleQuantumToMap(image->colormap[i].blue)]);
cristy2b9582a2011-07-04 17:38:56 +00003784 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003785 image->colormap[i].alpha=ClampToQuantum(sigmoidal_map[
3786 ScaleQuantumToMap(image->colormap[i].alpha)]);
cristy3ed852e2009-09-05 21:47:34 +00003787 }
3788 }
3789 /*
3790 Sigmoidal-contrast enhance image.
3791 */
3792 status=MagickTrue;
3793 progress=0;
3794 exception=(&image->exception);
3795 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00003796#if defined(MAGICKCORE_OPENMP_SUPPORT)
3797 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003798#endif
cristybb503372010-05-27 20:51:26 +00003799 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003800 {
cristy4c08aed2011-07-01 19:47:50 +00003801 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00003802 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003803
cristy8d4629b2010-08-30 17:59:46 +00003804 register ssize_t
3805 x;
3806
cristy3ed852e2009-09-05 21:47:34 +00003807 if (status == MagickFalse)
3808 continue;
3809 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00003810 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00003811 {
3812 status=MagickFalse;
3813 continue;
3814 }
cristybb503372010-05-27 20:51:26 +00003815 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003816 {
cristy2b9582a2011-07-04 17:38:56 +00003817 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003818 SetPixelRed(image,ClampToQuantum(sigmoidal_map[ScaleQuantumToMap(
3819 GetPixelRed(image,q))]),q);
cristy2b9582a2011-07-04 17:38:56 +00003820 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003821 SetPixelGreen(image,ClampToQuantum(sigmoidal_map[ScaleQuantumToMap(
3822 GetPixelGreen(image,q))]),q);
cristy2b9582a2011-07-04 17:38:56 +00003823 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003824 SetPixelBlue(image,ClampToQuantum(sigmoidal_map[ScaleQuantumToMap(
3825 GetPixelBlue(image,q))]),q);
cristy2b9582a2011-07-04 17:38:56 +00003826 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00003827 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +00003828 SetPixelBlack(image,ClampToQuantum(sigmoidal_map[ScaleQuantumToMap(
3829 GetPixelBlack(image,q))]),q);
cristy2b9582a2011-07-04 17:38:56 +00003830 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003831 SetPixelAlpha(image,ClampToQuantum(sigmoidal_map[ScaleQuantumToMap(
3832 GetPixelAlpha(image,q))]),q);
3833 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00003834 }
3835 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
3836 status=MagickFalse;
3837 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3838 {
3839 MagickBooleanType
3840 proceed;
3841
cristyb5d5f722009-11-04 03:03:49 +00003842#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00003843 #pragma omp critical (MagickCore_SigmoidalContrastImageChannel)
3844#endif
3845 proceed=SetImageProgress(image,SigmoidalContrastImageTag,progress++,
3846 image->rows);
3847 if (proceed == MagickFalse)
3848 status=MagickFalse;
3849 }
3850 }
3851 image_view=DestroyCacheView(image_view);
3852 sigmoidal_map=(MagickRealType *) RelinquishMagickMemory(sigmoidal_map);
3853 return(status);
3854}