blob: d2e35a9e138c2cd2d689441394ade299f6d2d706 [file] [log] [blame]
cristy3ed852e2009-09-05 21:47:34 +00001/*
2%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3% %
4% %
5% %
6% EEEEE N N H H AAA N N CCCC EEEEE %
7% E NN N H H A A NN N C E %
8% EEE N N N HHHHH AAAAA N N N C EEE %
9% E N NN H H A A N NN C E %
10% EEEEE N N H H A A N N CCCC EEEEE %
11% %
12% %
13% MagickCore Image Enhancement Methods %
14% %
15% Software Design %
16% John Cristy %
17% July 1992 %
18% %
19% %
cristy7e41fe82010-12-04 23:12:08 +000020% Copyright 1999-2011 ImageMagick Studio LLC, a non-profit organization %
cristy3ed852e2009-09-05 21:47:34 +000021% dedicated to making software imaging solutions freely available. %
22% %
23% You may not use this file except in compliance with the License. You may %
24% obtain a copy of the License at %
25% %
26% http://www.imagemagick.org/script/license.php %
27% %
28% Unless required by applicable law or agreed to in writing, software %
29% distributed under the License is distributed on an "AS IS" BASIS, %
30% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %
31% See the License for the specific language governing permissions and %
32% limitations under the License. %
33% %
34%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
35%
36%
37%
38*/
39
40/*
41 Include declarations.
42*/
cristy4c08aed2011-07-01 19:47:50 +000043#include "MagickCore/studio.h"
44#include "MagickCore/artifact.h"
45#include "MagickCore/cache.h"
46#include "MagickCore/cache-view.h"
47#include "MagickCore/color.h"
48#include "MagickCore/color-private.h"
49#include "MagickCore/colorspace.h"
50#include "MagickCore/composite-private.h"
51#include "MagickCore/enhance.h"
52#include "MagickCore/exception.h"
53#include "MagickCore/exception-private.h"
54#include "MagickCore/fx.h"
55#include "MagickCore/gem.h"
56#include "MagickCore/geometry.h"
57#include "MagickCore/histogram.h"
58#include "MagickCore/image.h"
59#include "MagickCore/image-private.h"
60#include "MagickCore/memory_.h"
61#include "MagickCore/monitor.h"
62#include "MagickCore/monitor-private.h"
63#include "MagickCore/option.h"
64#include "MagickCore/pixel-accessor.h"
65#include "MagickCore/quantum.h"
66#include "MagickCore/quantum-private.h"
67#include "MagickCore/resample.h"
68#include "MagickCore/resample-private.h"
69#include "MagickCore/statistic.h"
70#include "MagickCore/string_.h"
71#include "MagickCore/string-private.h"
72#include "MagickCore/thread-private.h"
73#include "MagickCore/token.h"
74#include "MagickCore/xml-tree.h"
cristy3ed852e2009-09-05 21:47:34 +000075
76/*
77%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
78% %
79% %
80% %
81% A u t o G a m m a I m a g e %
82% %
83% %
84% %
85%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
86%
87% AutoGammaImage() extract the 'mean' from the image and adjust the image
88% to try make set its gamma appropriatally.
89%
cristy308b4e62009-09-21 14:40:44 +000090% The format of the AutoGammaImage method is:
cristy3ed852e2009-09-05 21:47:34 +000091%
92% MagickBooleanType AutoGammaImage(Image *image)
cristy3ed852e2009-09-05 21:47:34 +000093%
94% A description of each parameter follows:
95%
96% o image: The image to auto-level
97%
cristy3ed852e2009-09-05 21:47:34 +000098*/
cristy3ed852e2009-09-05 21:47:34 +000099MagickExport MagickBooleanType AutoGammaImage(Image *image)
100{
cristy3ed852e2009-09-05 21:47:34 +0000101 MagickStatusType
102 status;
103
104 double
cristy4c08aed2011-07-01 19:47:50 +0000105 gamma,
106 log_mean,
107 mean,
108 sans;
anthony4efe5972009-09-11 06:46:40 +0000109
cristy4c08aed2011-07-01 19:47:50 +0000110 log_mean=log(0.5);
cristyab015852011-07-06 01:03:32 +0000111 if (image->sync != MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +0000112 {
113 /*
114 Apply gamma correction equally accross all given channels
115 */
cristyab015852011-07-06 01:03:32 +0000116 (void) GetImageChannelMean(image,DefaultChannels,&mean,&sans,
117 &image->exception);
cristy4c08aed2011-07-01 19:47:50 +0000118 gamma=log(mean*QuantumScale)/log_mean;
cristyab015852011-07-06 01:03:32 +0000119 return(LevelImageChannel(image,DefaultChannels,0.0,(double) QuantumRange,gamma));
cristy3ed852e2009-09-05 21:47:34 +0000120 }
cristy3ed852e2009-09-05 21:47:34 +0000121 /*
cristy4c08aed2011-07-01 19:47:50 +0000122 Auto-gamma each channel separately.
cristy3ed852e2009-09-05 21:47:34 +0000123 */
cristy4c08aed2011-07-01 19:47:50 +0000124 status=MagickTrue;
cristy2b9582a2011-07-04 17:38:56 +0000125 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +0000126 {
cristy2b726bd2010-01-11 01:05:39 +0000127 (void) GetImageChannelMean(image,RedChannel,&mean,&sans,
128 &image->exception);
cristy4c08aed2011-07-01 19:47:50 +0000129 gamma=log(mean*QuantumScale)/log_mean;
130 status=status && LevelImageChannel(image,RedChannel,0.0,(double)
cristyab015852011-07-06 01:03:32 +0000131 QuantumRange,gamma);
cristy3ed852e2009-09-05 21:47:34 +0000132 }
cristy2b9582a2011-07-04 17:38:56 +0000133 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +0000134 {
cristy2b726bd2010-01-11 01:05:39 +0000135 (void) GetImageChannelMean(image,GreenChannel,&mean,&sans,
136 &image->exception);
cristy4c08aed2011-07-01 19:47:50 +0000137 gamma=log(mean*QuantumScale)/log_mean;
138 status=status && LevelImageChannel(image,GreenChannel,0.0,(double)
139 QuantumRange,gamma);
cristy3ed852e2009-09-05 21:47:34 +0000140 }
cristy2b9582a2011-07-04 17:38:56 +0000141 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +0000142 {
cristy2b726bd2010-01-11 01:05:39 +0000143 (void) GetImageChannelMean(image,BlueChannel,&mean,&sans,
144 &image->exception);
cristy4c08aed2011-07-01 19:47:50 +0000145 gamma=log(mean*QuantumScale)/log_mean;
146 status=status && LevelImageChannel(image,BlueChannel,0.0,(double)
147 QuantumRange,gamma);
148 }
cristy2b9582a2011-07-04 17:38:56 +0000149 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +0000150 (image->colorspace == CMYKColorspace))
151 {
152 (void) GetImageChannelMean(image,BlackChannel,&mean,&sans,
153 &image->exception);
154 gamma=log(mean*QuantumScale)/log_mean;
155 status=status && LevelImageChannel(image,BlackChannel,0.0,(double)
156 QuantumRange,gamma);
cristy3ed852e2009-09-05 21:47:34 +0000157 }
cristy2b9582a2011-07-04 17:38:56 +0000158 if (((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +0000159 (image->matte == MagickTrue))
160 {
cristy2b726bd2010-01-11 01:05:39 +0000161 (void) GetImageChannelMean(image,OpacityChannel,&mean,&sans,
162 &image->exception);
cristy4c08aed2011-07-01 19:47:50 +0000163 gamma=log(mean*QuantumScale)/log_mean;
164 status=status && LevelImageChannel(image,OpacityChannel,0.0,(double)
165 QuantumRange,gamma);
cristy3ed852e2009-09-05 21:47:34 +0000166 }
167 return(status != 0 ? MagickTrue : MagickFalse);
168}
169
170/*
171%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
172% %
173% %
174% %
175% A u t o L e v e l I m a g e %
176% %
177% %
178% %
179%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
180%
181% AutoLevelImage() adjusts the levels of a particular image channel by
182% scaling the minimum and maximum values to the full quantum range.
183%
184% The format of the LevelImage method is:
185%
186% MagickBooleanType AutoLevelImage(Image *image)
cristy3ed852e2009-09-05 21:47:34 +0000187%
188% A description of each parameter follows:
189%
190% o image: The image to auto-level
191%
cristy3ed852e2009-09-05 21:47:34 +0000192*/
cristy3ed852e2009-09-05 21:47:34 +0000193MagickExport MagickBooleanType AutoLevelImage(Image *image)
194{
cristyab015852011-07-06 01:03:32 +0000195 return(MinMaxStretchImage(image,DefaultChannels,0.0,0.0));
cristy3ed852e2009-09-05 21:47:34 +0000196}
197
198/*
199%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
200% %
201% %
202% %
cristya28d6b82010-01-11 20:03:47 +0000203% B r i g h t n e s s C o n t r a s t I m a g e %
204% %
205% %
206% %
207%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
208%
209% Use BrightnessContrastImage() to change the brightness and/or contrast of
210% an image. It converts the brightness and contrast parameters into slope
211% and intercept and calls a polynomical function to apply to the image.
212%
213% The format of the BrightnessContrastImage method is:
214%
215% MagickBooleanType BrightnessContrastImage(Image *image,
216% const double brightness,const double contrast)
217% MagickBooleanType BrightnessContrastImageChannel(Image *image,
218% const ChannelType channel,const double brightness,
219% const double contrast)
220%
221% A description of each parameter follows:
222%
223% o image: the image.
224%
225% o channel: the channel.
226%
227% o brightness: the brightness percent (-100 .. 100).
228%
229% o contrast: the contrast percent (-100 .. 100).
230%
231*/
232
233MagickExport MagickBooleanType BrightnessContrastImage(Image *image,
234 const double brightness,const double contrast)
235{
236 MagickBooleanType
237 status;
238
239 status=BrightnessContrastImageChannel(image,DefaultChannels,brightness,
240 contrast);
241 return(status);
242}
243
244MagickExport MagickBooleanType BrightnessContrastImageChannel(Image *image,
245 const ChannelType channel,const double brightness,const double contrast)
246{
247#define BrightnessContastImageTag "BrightnessContast/Image"
248
249 double
250 alpha,
251 intercept,
252 coefficients[2],
253 slope;
254
255 MagickBooleanType
256 status;
257
258 /*
259 Compute slope and intercept.
260 */
261 assert(image != (Image *) NULL);
262 assert(image->signature == MagickSignature);
263 if (image->debug != MagickFalse)
264 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
265 alpha=contrast;
cristy4205a3c2010-09-12 20:19:59 +0000266 slope=tan((double) (MagickPI*(alpha/100.0+1.0)/4.0));
cristya28d6b82010-01-11 20:03:47 +0000267 if (slope < 0.0)
268 slope=0.0;
269 intercept=brightness/100.0+((100-brightness)/200.0)*(1.0-slope);
270 coefficients[0]=slope;
271 coefficients[1]=intercept;
272 status=FunctionImageChannel(image,channel,PolynomialFunction,2,coefficients,
273 &image->exception);
274 return(status);
275}
276
277/*
278%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
279% %
280% %
281% %
cristy3ed852e2009-09-05 21:47:34 +0000282% C o l o r D e c i s i o n L i s t I m a g e %
283% %
284% %
285% %
286%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
287%
288% ColorDecisionListImage() accepts a lightweight Color Correction Collection
289% (CCC) file which solely contains one or more color corrections and applies
290% the correction to the image. Here is a sample CCC file:
291%
292% <ColorCorrectionCollection xmlns="urn:ASC:CDL:v1.2">
293% <ColorCorrection id="cc03345">
294% <SOPNode>
295% <Slope> 0.9 1.2 0.5 </Slope>
296% <Offset> 0.4 -0.5 0.6 </Offset>
297% <Power> 1.0 0.8 1.5 </Power>
298% </SOPNode>
299% <SATNode>
300% <Saturation> 0.85 </Saturation>
301% </SATNode>
302% </ColorCorrection>
303% </ColorCorrectionCollection>
304%
305% which includes the slop, offset, and power for each of the RGB channels
306% as well as the saturation.
307%
308% The format of the ColorDecisionListImage method is:
309%
310% MagickBooleanType ColorDecisionListImage(Image *image,
311% const char *color_correction_collection)
312%
313% A description of each parameter follows:
314%
315% o image: the image.
316%
317% o color_correction_collection: the color correction collection in XML.
318%
319*/
320MagickExport MagickBooleanType ColorDecisionListImage(Image *image,
321 const char *color_correction_collection)
322{
323#define ColorDecisionListCorrectImageTag "ColorDecisionList/Image"
324
325 typedef struct _Correction
326 {
327 double
328 slope,
329 offset,
330 power;
331 } Correction;
332
333 typedef struct _ColorCorrection
334 {
335 Correction
336 red,
337 green,
338 blue;
339
340 double
341 saturation;
342 } ColorCorrection;
343
cristyc4c8d132010-01-07 01:58:38 +0000344 CacheView
345 *image_view;
346
cristy3ed852e2009-09-05 21:47:34 +0000347 char
348 token[MaxTextExtent];
349
350 ColorCorrection
351 color_correction;
352
353 const char
354 *content,
355 *p;
356
357 ExceptionInfo
358 *exception;
359
cristy3ed852e2009-09-05 21:47:34 +0000360 MagickBooleanType
361 status;
362
cristybb503372010-05-27 20:51:26 +0000363 MagickOffsetType
364 progress;
365
cristy3ed852e2009-09-05 21:47:34 +0000366 PixelPacket
367 *cdl_map;
368
cristybb503372010-05-27 20:51:26 +0000369 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000370 i;
371
cristybb503372010-05-27 20:51:26 +0000372 ssize_t
373 y;
374
cristy3ed852e2009-09-05 21:47:34 +0000375 XMLTreeInfo
376 *cc,
377 *ccc,
378 *sat,
379 *sop;
380
cristy3ed852e2009-09-05 21:47:34 +0000381 /*
382 Allocate and initialize cdl maps.
383 */
384 assert(image != (Image *) NULL);
385 assert(image->signature == MagickSignature);
386 if (image->debug != MagickFalse)
387 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
388 if (color_correction_collection == (const char *) NULL)
389 return(MagickFalse);
390 ccc=NewXMLTree((const char *) color_correction_collection,&image->exception);
391 if (ccc == (XMLTreeInfo *) NULL)
392 return(MagickFalse);
393 cc=GetXMLTreeChild(ccc,"ColorCorrection");
394 if (cc == (XMLTreeInfo *) NULL)
395 {
396 ccc=DestroyXMLTree(ccc);
397 return(MagickFalse);
398 }
399 color_correction.red.slope=1.0;
400 color_correction.red.offset=0.0;
401 color_correction.red.power=1.0;
402 color_correction.green.slope=1.0;
403 color_correction.green.offset=0.0;
404 color_correction.green.power=1.0;
405 color_correction.blue.slope=1.0;
406 color_correction.blue.offset=0.0;
407 color_correction.blue.power=1.0;
408 color_correction.saturation=0.0;
409 sop=GetXMLTreeChild(cc,"SOPNode");
410 if (sop != (XMLTreeInfo *) NULL)
411 {
412 XMLTreeInfo
413 *offset,
414 *power,
415 *slope;
416
417 slope=GetXMLTreeChild(sop,"Slope");
418 if (slope != (XMLTreeInfo *) NULL)
419 {
420 content=GetXMLTreeContent(slope);
421 p=(const char *) content;
422 for (i=0; (*p != '\0') && (i < 3); i++)
423 {
424 GetMagickToken(p,&p,token);
425 if (*token == ',')
426 GetMagickToken(p,&p,token);
427 switch (i)
428 {
cristyc1acd842011-05-19 23:05:47 +0000429 case 0:
430 {
431 color_correction.red.slope=InterpretLocaleValue(token,
432 (char **) NULL);
433 break;
434 }
435 case 1:
436 {
437 color_correction.green.slope=InterpretLocaleValue(token,
438 (char **) NULL);
439 break;
440 }
441 case 2:
442 {
443 color_correction.blue.slope=InterpretLocaleValue(token,
444 (char **) NULL);
445 break;
446 }
cristy3ed852e2009-09-05 21:47:34 +0000447 }
448 }
449 }
450 offset=GetXMLTreeChild(sop,"Offset");
451 if (offset != (XMLTreeInfo *) NULL)
452 {
453 content=GetXMLTreeContent(offset);
454 p=(const char *) content;
455 for (i=0; (*p != '\0') && (i < 3); i++)
456 {
457 GetMagickToken(p,&p,token);
458 if (*token == ',')
459 GetMagickToken(p,&p,token);
460 switch (i)
461 {
cristyc1acd842011-05-19 23:05:47 +0000462 case 0:
463 {
464 color_correction.red.offset=InterpretLocaleValue(token,
465 (char **) NULL);
466 break;
467 }
468 case 1:
469 {
470 color_correction.green.offset=InterpretLocaleValue(token,
471 (char **) NULL);
472 break;
473 }
474 case 2:
475 {
476 color_correction.blue.offset=InterpretLocaleValue(token,
477 (char **) NULL);
478 break;
479 }
cristy3ed852e2009-09-05 21:47:34 +0000480 }
481 }
482 }
483 power=GetXMLTreeChild(sop,"Power");
484 if (power != (XMLTreeInfo *) NULL)
485 {
486 content=GetXMLTreeContent(power);
487 p=(const char *) content;
488 for (i=0; (*p != '\0') && (i < 3); i++)
489 {
490 GetMagickToken(p,&p,token);
491 if (*token == ',')
492 GetMagickToken(p,&p,token);
493 switch (i)
494 {
cristyc1acd842011-05-19 23:05:47 +0000495 case 0:
496 {
497 color_correction.red.power=InterpretLocaleValue(token,
498 (char **) NULL);
499 break;
500 }
501 case 1:
502 {
503 color_correction.green.power=InterpretLocaleValue(token,
504 (char **) NULL);
505 break;
506 }
507 case 2:
508 {
509 color_correction.blue.power=InterpretLocaleValue(token,
510 (char **) NULL);
511 break;
512 }
cristy3ed852e2009-09-05 21:47:34 +0000513 }
514 }
515 }
516 }
517 sat=GetXMLTreeChild(cc,"SATNode");
518 if (sat != (XMLTreeInfo *) NULL)
519 {
520 XMLTreeInfo
521 *saturation;
522
523 saturation=GetXMLTreeChild(sat,"Saturation");
524 if (saturation != (XMLTreeInfo *) NULL)
525 {
526 content=GetXMLTreeContent(saturation);
527 p=(const char *) content;
528 GetMagickToken(p,&p,token);
cristyc1acd842011-05-19 23:05:47 +0000529 color_correction.saturation=InterpretLocaleValue(token,
530 (char **) NULL);
cristy3ed852e2009-09-05 21:47:34 +0000531 }
532 }
533 ccc=DestroyXMLTree(ccc);
534 if (image->debug != MagickFalse)
535 {
536 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
537 " Color Correction Collection:");
538 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000539 " color_correction.red.slope: %g",color_correction.red.slope);
cristy3ed852e2009-09-05 21:47:34 +0000540 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000541 " color_correction.red.offset: %g",color_correction.red.offset);
cristy3ed852e2009-09-05 21:47:34 +0000542 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000543 " color_correction.red.power: %g",color_correction.red.power);
cristy3ed852e2009-09-05 21:47:34 +0000544 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000545 " color_correction.green.slope: %g",color_correction.green.slope);
cristy3ed852e2009-09-05 21:47:34 +0000546 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000547 " color_correction.green.offset: %g",color_correction.green.offset);
cristy3ed852e2009-09-05 21:47:34 +0000548 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000549 " color_correction.green.power: %g",color_correction.green.power);
cristy3ed852e2009-09-05 21:47:34 +0000550 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000551 " color_correction.blue.slope: %g",color_correction.blue.slope);
cristy3ed852e2009-09-05 21:47:34 +0000552 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000553 " color_correction.blue.offset: %g",color_correction.blue.offset);
cristy3ed852e2009-09-05 21:47:34 +0000554 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000555 " color_correction.blue.power: %g",color_correction.blue.power);
cristy3ed852e2009-09-05 21:47:34 +0000556 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000557 " color_correction.saturation: %g",color_correction.saturation);
cristy3ed852e2009-09-05 21:47:34 +0000558 }
559 cdl_map=(PixelPacket *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*cdl_map));
560 if (cdl_map == (PixelPacket *) NULL)
561 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
562 image->filename);
cristyb5d5f722009-11-04 03:03:49 +0000563#if defined(MAGICKCORE_OPENMP_SUPPORT)
564 #pragma omp parallel for schedule(dynamic,4)
cristy3ed852e2009-09-05 21:47:34 +0000565#endif
cristybb503372010-05-27 20:51:26 +0000566 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +0000567 {
cristyce70c172010-01-07 17:15:30 +0000568 cdl_map[i].red=ClampToQuantum((MagickRealType) ScaleMapToQuantum((
cristy3ed852e2009-09-05 21:47:34 +0000569 MagickRealType) (MaxMap*(pow(color_correction.red.slope*i/MaxMap+
570 color_correction.red.offset,color_correction.red.power)))));
cristyce70c172010-01-07 17:15:30 +0000571 cdl_map[i].green=ClampToQuantum((MagickRealType) ScaleMapToQuantum((
cristy3ed852e2009-09-05 21:47:34 +0000572 MagickRealType) (MaxMap*(pow(color_correction.green.slope*i/MaxMap+
573 color_correction.green.offset,color_correction.green.power)))));
cristyce70c172010-01-07 17:15:30 +0000574 cdl_map[i].blue=ClampToQuantum((MagickRealType) ScaleMapToQuantum((
cristy3ed852e2009-09-05 21:47:34 +0000575 MagickRealType) (MaxMap*(pow(color_correction.blue.slope*i/MaxMap+
576 color_correction.blue.offset,color_correction.blue.power)))));
577 }
578 if (image->storage_class == PseudoClass)
579 {
580 /*
581 Apply transfer function to colormap.
582 */
cristyb5d5f722009-11-04 03:03:49 +0000583#if defined(MAGICKCORE_OPENMP_SUPPORT)
584 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000585#endif
cristybb503372010-05-27 20:51:26 +0000586 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +0000587 {
588 double
589 luma;
590
591 luma=0.2126*image->colormap[i].red+0.7152*image->colormap[i].green+
592 0.0722*image->colormap[i].blue;
cristyce70c172010-01-07 17:15:30 +0000593 image->colormap[i].red=ClampToQuantum(luma+color_correction.saturation*
cristy3ed852e2009-09-05 21:47:34 +0000594 cdl_map[ScaleQuantumToMap(image->colormap[i].red)].red-luma);
cristyce70c172010-01-07 17:15:30 +0000595 image->colormap[i].green=ClampToQuantum(luma+
cristy3ed852e2009-09-05 21:47:34 +0000596 color_correction.saturation*cdl_map[ScaleQuantumToMap(
597 image->colormap[i].green)].green-luma);
cristyce70c172010-01-07 17:15:30 +0000598 image->colormap[i].blue=ClampToQuantum(luma+color_correction.saturation*
cristy3ed852e2009-09-05 21:47:34 +0000599 cdl_map[ScaleQuantumToMap(image->colormap[i].blue)].blue-luma);
600 }
601 }
602 /*
603 Apply transfer function to image.
604 */
605 status=MagickTrue;
606 progress=0;
607 exception=(&image->exception);
608 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +0000609#if defined(MAGICKCORE_OPENMP_SUPPORT)
610 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000611#endif
cristybb503372010-05-27 20:51:26 +0000612 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000613 {
614 double
615 luma;
616
cristy4c08aed2011-07-01 19:47:50 +0000617 register Quantum
cristyc47d1f82009-11-26 01:44:43 +0000618 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000619
cristy8d4629b2010-08-30 17:59:46 +0000620 register ssize_t
621 x;
622
cristy3ed852e2009-09-05 21:47:34 +0000623 if (status == MagickFalse)
624 continue;
625 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +0000626 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +0000627 {
628 status=MagickFalse;
629 continue;
630 }
cristybb503372010-05-27 20:51:26 +0000631 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000632 {
cristy4c08aed2011-07-01 19:47:50 +0000633 luma=0.2126*GetPixelRed(image,q)+0.7152*GetPixelGreen(image,q)+0.0722*
634 GetPixelBlue(image,q);
635 SetPixelRed(image,ClampToQuantum(luma+color_correction.saturation*
636 (cdl_map[ScaleQuantumToMap(GetPixelRed(image,q))].red-luma)),q);
637 SetPixelGreen(image,ClampToQuantum(luma+color_correction.saturation*
638 (cdl_map[ScaleQuantumToMap(GetPixelGreen(image,q))].green-luma)),q);
639 SetPixelBlue(image,ClampToQuantum(luma+color_correction.saturation*
640 (cdl_map[ScaleQuantumToMap(GetPixelBlue(image,q))].blue-luma)),q);
641 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +0000642 }
643 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
644 status=MagickFalse;
645 if (image->progress_monitor != (MagickProgressMonitor) NULL)
646 {
647 MagickBooleanType
648 proceed;
649
cristyb5d5f722009-11-04 03:03:49 +0000650#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +0000651 #pragma omp critical (MagickCore_ColorDecisionListImageChannel)
652#endif
653 proceed=SetImageProgress(image,ColorDecisionListCorrectImageTag,
654 progress++,image->rows);
655 if (proceed == MagickFalse)
656 status=MagickFalse;
657 }
658 }
659 image_view=DestroyCacheView(image_view);
660 cdl_map=(PixelPacket *) RelinquishMagickMemory(cdl_map);
661 return(status);
662}
663
664/*
665%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
666% %
667% %
668% %
669% C l u t I m a g e %
670% %
671% %
672% %
673%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
674%
675% ClutImage() replaces each color value in the given image, by using it as an
676% index to lookup a replacement color value in a Color Look UP Table in the
cristycee97112010-05-28 00:44:52 +0000677% form of an image. The values are extracted along a diagonal of the CLUT
cristy3ed852e2009-09-05 21:47:34 +0000678% image so either a horizontal or vertial gradient image can be used.
679%
680% Typically this is used to either re-color a gray-scale image according to a
681% color gradient in the CLUT image, or to perform a freeform histogram
682% (level) adjustment according to the (typically gray-scale) gradient in the
683% CLUT image.
684%
685% When the 'channel' mask includes the matte/alpha transparency channel but
686% one image has no such channel it is assumed that that image is a simple
687% gray-scale image that will effect the alpha channel values, either for
688% gray-scale coloring (with transparent or semi-transparent colors), or
689% a histogram adjustment of existing alpha channel values. If both images
690% have matte channels, direct and normal indexing is applied, which is rarely
691% used.
692%
693% The format of the ClutImage method is:
694%
695% MagickBooleanType ClutImage(Image *image,Image *clut_image)
696% MagickBooleanType ClutImageChannel(Image *image,
697% const ChannelType channel,Image *clut_image)
698%
699% A description of each parameter follows:
700%
701% o image: the image, which is replaced by indexed CLUT values
702%
703% o clut_image: the color lookup table image for replacement color values.
704%
705% o channel: the channel.
706%
707*/
708
709MagickExport MagickBooleanType ClutImage(Image *image,const Image *clut_image)
710{
711 return(ClutImageChannel(image,DefaultChannels,clut_image));
712}
713
714MagickExport MagickBooleanType ClutImageChannel(Image *image,
715 const ChannelType channel,const Image *clut_image)
716{
cristy4c08aed2011-07-01 19:47:50 +0000717#define ClampAlphaPixelComponent(pixel) ClampToQuantum((pixel)->alpha)
718#define ClampBlackPixelComponent(pixel) ClampToQuantum((pixel)->black)
719#define ClampBluePixelComponent(pixel) ClampToQuantum((pixel)->blue)
720#define ClampGreenPixelComponent(pixel) ClampToQuantum((pixel)->green)
721#define ClampRedPixelComponent(pixel) ClampToQuantum((pixel)->red)
cristy3ed852e2009-09-05 21:47:34 +0000722#define ClutImageTag "Clut/Image"
723
cristyfa112112010-01-04 17:48:07 +0000724 CacheView
cristy708333f2011-03-26 01:25:07 +0000725 *clut_view,
cristyfa112112010-01-04 17:48:07 +0000726 *image_view;
727
cristy3ed852e2009-09-05 21:47:34 +0000728 ExceptionInfo
729 *exception;
730
cristy3ed852e2009-09-05 21:47:34 +0000731 MagickBooleanType
732 status;
733
cristybb503372010-05-27 20:51:26 +0000734 MagickOffsetType
735 progress;
736
cristy4c08aed2011-07-01 19:47:50 +0000737 PixelInfo
cristy49f37242011-03-22 18:18:23 +0000738 *clut_map;
739
740 register ssize_t
741 i;
cristy3ed852e2009-09-05 21:47:34 +0000742
cristybb503372010-05-27 20:51:26 +0000743 ssize_t
744 adjust,
745 y;
746
cristy3ed852e2009-09-05 21:47:34 +0000747 assert(image != (Image *) NULL);
748 assert(image->signature == MagickSignature);
749 if (image->debug != MagickFalse)
750 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
751 assert(clut_image != (Image *) NULL);
752 assert(clut_image->signature == MagickSignature);
753 if (SetImageStorageClass(image,DirectClass) == MagickFalse)
754 return(MagickFalse);
cristy4c08aed2011-07-01 19:47:50 +0000755 clut_map=(PixelInfo *) AcquireQuantumMemory(MaxMap+1UL,
cristy49f37242011-03-22 18:18:23 +0000756 sizeof(*clut_map));
cristy4c08aed2011-07-01 19:47:50 +0000757 if (clut_map == (PixelInfo *) NULL)
cristy49f37242011-03-22 18:18:23 +0000758 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
759 image->filename);
cristy3ed852e2009-09-05 21:47:34 +0000760 /*
761 Clut image.
762 */
763 status=MagickTrue;
764 progress=0;
cristybb503372010-05-27 20:51:26 +0000765 adjust=(ssize_t) (clut_image->interpolate == IntegerInterpolatePixel ? 0 : 1);
cristy3ed852e2009-09-05 21:47:34 +0000766 exception=(&image->exception);
cristy708333f2011-03-26 01:25:07 +0000767 clut_view=AcquireCacheView(clut_image);
cristyaf6bc722011-03-25 19:16:14 +0000768#if defined(MAGICKCORE_OPENMP_SUPPORT)
769 #pragma omp parallel for schedule(dynamic,4)
770#endif
cristy49f37242011-03-22 18:18:23 +0000771 for (i=0; i <= (ssize_t) MaxMap; i++)
772 {
cristy4c08aed2011-07-01 19:47:50 +0000773 GetPixelInfo(clut_image,clut_map+i);
774 (void) InterpolatePixelInfo(clut_image,clut_view,
cristy8a7c3e82011-03-26 02:10:53 +0000775 UndefinedInterpolatePixel,QuantumScale*i*(clut_image->columns-adjust),
776 QuantumScale*i*(clut_image->rows-adjust),clut_map+i,exception);
cristy49f37242011-03-22 18:18:23 +0000777 }
cristy708333f2011-03-26 01:25:07 +0000778 clut_view=DestroyCacheView(clut_view);
779 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +0000780#if defined(MAGICKCORE_OPENMP_SUPPORT)
781 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000782#endif
cristybb503372010-05-27 20:51:26 +0000783 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000784 {
cristy4c08aed2011-07-01 19:47:50 +0000785 PixelInfo
cristy3635df22011-03-25 00:16:16 +0000786 pixel;
787
cristy4c08aed2011-07-01 19:47:50 +0000788 register Quantum
cristyc47d1f82009-11-26 01:44:43 +0000789 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000790
cristy8d4629b2010-08-30 17:59:46 +0000791 register ssize_t
792 x;
793
cristy3ed852e2009-09-05 21:47:34 +0000794 if (status == MagickFalse)
795 continue;
796 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +0000797 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +0000798 {
799 status=MagickFalse;
800 continue;
801 }
cristy4c08aed2011-07-01 19:47:50 +0000802 GetPixelInfo(image,&pixel);
cristybb503372010-05-27 20:51:26 +0000803 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000804 {
cristy4c08aed2011-07-01 19:47:50 +0000805 SetPixelInfo(image,q,&pixel);
cristy2b9582a2011-07-04 17:38:56 +0000806 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000807 SetPixelRed(image,ClampRedPixelComponent(clut_map+
808 ScaleQuantumToMap(GetPixelRed(image,q))),q);
cristy2b9582a2011-07-04 17:38:56 +0000809 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000810 SetPixelGreen(image,ClampGreenPixelComponent(clut_map+
811 ScaleQuantumToMap(GetPixelGreen(image,q))),q);
cristy2b9582a2011-07-04 17:38:56 +0000812 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000813 SetPixelBlue(image,ClampBluePixelComponent(clut_map+
814 ScaleQuantumToMap(GetPixelBlue(image,q))),q);
cristy2b9582a2011-07-04 17:38:56 +0000815 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +0000816 (image->colorspace == CMYKColorspace))
817 SetPixelBlack(image,ClampBlackPixelComponent(clut_map+
818 ScaleQuantumToMap(GetPixelBlack(image,q))),q);
cristy2b9582a2011-07-04 17:38:56 +0000819 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy3635df22011-03-25 00:16:16 +0000820 {
821 if (clut_image->matte == MagickFalse)
cristy4c08aed2011-07-01 19:47:50 +0000822 SetPixelAlpha(image,GetPixelInfoIntensity(clut_map+
823 ScaleQuantumToMap((Quantum) GetPixelAlpha(image,q))),q);
cristy3635df22011-03-25 00:16:16 +0000824 else
825 if (image->matte == MagickFalse)
cristy4c08aed2011-07-01 19:47:50 +0000826 SetPixelAlpha(image,ClampAlphaPixelComponent(clut_map+
827 ScaleQuantumToMap((Quantum) GetPixelInfoIntensity(&pixel))),q);
cristy3635df22011-03-25 00:16:16 +0000828 else
cristy4c08aed2011-07-01 19:47:50 +0000829 SetPixelAlpha(image,ClampAlphaPixelComponent(clut_map+
830 ScaleQuantumToMap(GetPixelAlpha(image,q))),q);
cristy3635df22011-03-25 00:16:16 +0000831 }
cristy4c08aed2011-07-01 19:47:50 +0000832 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +0000833 }
834 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
835 status=MagickFalse;
836 if (image->progress_monitor != (MagickProgressMonitor) NULL)
837 {
838 MagickBooleanType
839 proceed;
840
cristyb5d5f722009-11-04 03:03:49 +0000841#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +0000842 #pragma omp critical (MagickCore_ClutImageChannel)
843#endif
844 proceed=SetImageProgress(image,ClutImageTag,progress++,image->rows);
845 if (proceed == MagickFalse)
846 status=MagickFalse;
847 }
848 }
849 image_view=DestroyCacheView(image_view);
cristy4c08aed2011-07-01 19:47:50 +0000850 clut_map=(PixelInfo *) RelinquishMagickMemory(clut_map);
cristy2b9582a2011-07-04 17:38:56 +0000851 if ((clut_image->matte != MagickFalse) &&
852 ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0))
cristy3ed852e2009-09-05 21:47:34 +0000853 (void) SetImageAlphaChannel(image,ActivateAlphaChannel);
854 return(status);
855}
856
857/*
858%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
859% %
860% %
861% %
862% C o n t r a s t I m a g e %
863% %
864% %
865% %
866%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
867%
868% ContrastImage() enhances the intensity differences between the lighter and
869% darker elements of the image. Set sharpen to a MagickTrue to increase the
870% image contrast otherwise the contrast is reduced.
871%
872% The format of the ContrastImage method is:
873%
874% MagickBooleanType ContrastImage(Image *image,
875% const MagickBooleanType sharpen)
876%
877% A description of each parameter follows:
878%
879% o image: the image.
880%
881% o sharpen: Increase or decrease image contrast.
882%
883*/
884
885static void Contrast(const int sign,Quantum *red,Quantum *green,Quantum *blue)
886{
887 double
888 brightness,
889 hue,
890 saturation;
891
892 /*
893 Enhance contrast: dark color become darker, light color become lighter.
894 */
895 assert(red != (Quantum *) NULL);
896 assert(green != (Quantum *) NULL);
897 assert(blue != (Quantum *) NULL);
898 hue=0.0;
899 saturation=0.0;
900 brightness=0.0;
901 ConvertRGBToHSB(*red,*green,*blue,&hue,&saturation,&brightness);
cristy31ac0f02011-03-12 02:04:47 +0000902 brightness+=0.5*sign*(0.5*(sin((double) (MagickPI*(brightness-0.5)))+1.0)-
cristy4205a3c2010-09-12 20:19:59 +0000903 brightness);
cristy3ed852e2009-09-05 21:47:34 +0000904 if (brightness > 1.0)
905 brightness=1.0;
906 else
907 if (brightness < 0.0)
908 brightness=0.0;
909 ConvertHSBToRGB(hue,saturation,brightness,red,green,blue);
910}
911
912MagickExport MagickBooleanType ContrastImage(Image *image,
913 const MagickBooleanType sharpen)
914{
915#define ContrastImageTag "Contrast/Image"
916
cristyc4c8d132010-01-07 01:58:38 +0000917 CacheView
918 *image_view;
919
cristy3ed852e2009-09-05 21:47:34 +0000920 ExceptionInfo
921 *exception;
922
923 int
924 sign;
925
cristy3ed852e2009-09-05 21:47:34 +0000926 MagickBooleanType
927 status;
928
cristybb503372010-05-27 20:51:26 +0000929 MagickOffsetType
930 progress;
931
932 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000933 i;
934
cristybb503372010-05-27 20:51:26 +0000935 ssize_t
936 y;
937
cristy3ed852e2009-09-05 21:47:34 +0000938 assert(image != (Image *) NULL);
939 assert(image->signature == MagickSignature);
940 if (image->debug != MagickFalse)
941 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
942 sign=sharpen != MagickFalse ? 1 : -1;
943 if (image->storage_class == PseudoClass)
944 {
945 /*
946 Contrast enhance colormap.
947 */
cristybb503372010-05-27 20:51:26 +0000948 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +0000949 Contrast(sign,&image->colormap[i].red,&image->colormap[i].green,
950 &image->colormap[i].blue);
951 }
952 /*
953 Contrast enhance image.
954 */
955 status=MagickTrue;
956 progress=0;
957 exception=(&image->exception);
958 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +0000959#if defined(MAGICKCORE_OPENMP_SUPPORT)
960 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000961#endif
cristybb503372010-05-27 20:51:26 +0000962 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000963 {
cristy5afeab82011-04-30 01:30:09 +0000964 Quantum
965 blue,
966 green,
967 red;
968
cristy4c08aed2011-07-01 19:47:50 +0000969 register Quantum
cristyc47d1f82009-11-26 01:44:43 +0000970 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000971
cristy8d4629b2010-08-30 17:59:46 +0000972 register ssize_t
973 x;
974
cristy3ed852e2009-09-05 21:47:34 +0000975 if (status == MagickFalse)
976 continue;
977 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +0000978 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +0000979 {
980 status=MagickFalse;
981 continue;
982 }
cristybb503372010-05-27 20:51:26 +0000983 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000984 {
cristy4c08aed2011-07-01 19:47:50 +0000985 red=GetPixelRed(image,q);
986 green=GetPixelGreen(image,q);
987 blue=GetPixelBlue(image,q);
cristy5afeab82011-04-30 01:30:09 +0000988 Contrast(sign,&red,&green,&blue);
cristy4c08aed2011-07-01 19:47:50 +0000989 SetPixelRed(image,red,q);
990 SetPixelGreen(image,green,q);
991 SetPixelBlue(image,blue,q);
992 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +0000993 }
994 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
995 status=MagickFalse;
996 if (image->progress_monitor != (MagickProgressMonitor) NULL)
997 {
998 MagickBooleanType
999 proceed;
1000
cristyb5d5f722009-11-04 03:03:49 +00001001#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00001002 #pragma omp critical (MagickCore_ContrastImage)
1003#endif
1004 proceed=SetImageProgress(image,ContrastImageTag,progress++,image->rows);
1005 if (proceed == MagickFalse)
1006 status=MagickFalse;
1007 }
1008 }
1009 image_view=DestroyCacheView(image_view);
1010 return(status);
1011}
1012
1013/*
1014%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1015% %
1016% %
1017% %
1018% C o n t r a s t S t r e t c h I m a g e %
1019% %
1020% %
1021% %
1022%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1023%
1024% The ContrastStretchImage() is a simple image enhancement technique that
1025% attempts to improve the contrast in an image by `stretching' the range of
1026% intensity values it contains to span a desired range of values. It differs
1027% from the more sophisticated histogram equalization in that it can only
1028% apply % a linear scaling function to the image pixel values. As a result
1029% the `enhancement' is less harsh.
1030%
1031% The format of the ContrastStretchImage method is:
1032%
1033% MagickBooleanType ContrastStretchImage(Image *image,
1034% const char *levels)
1035% MagickBooleanType ContrastStretchImageChannel(Image *image,
cristybb503372010-05-27 20:51:26 +00001036% const size_t channel,const double black_point,
cristy3ed852e2009-09-05 21:47:34 +00001037% const double white_point)
1038%
1039% A description of each parameter follows:
1040%
1041% o image: the image.
1042%
1043% o channel: the channel.
1044%
1045% o black_point: the black point.
1046%
1047% o white_point: the white point.
1048%
1049% o levels: Specify the levels where the black and white points have the
1050% range of 0 to number-of-pixels (e.g. 1%, 10x90%, etc.).
1051%
1052*/
1053
1054MagickExport MagickBooleanType ContrastStretchImage(Image *image,
1055 const char *levels)
1056{
1057 double
1058 black_point,
1059 white_point;
1060
1061 GeometryInfo
1062 geometry_info;
1063
1064 MagickBooleanType
1065 status;
1066
1067 MagickStatusType
1068 flags;
1069
1070 /*
1071 Parse levels.
1072 */
1073 if (levels == (char *) NULL)
1074 return(MagickFalse);
1075 flags=ParseGeometry(levels,&geometry_info);
1076 black_point=geometry_info.rho;
1077 white_point=(double) image->columns*image->rows;
1078 if ((flags & SigmaValue) != 0)
1079 white_point=geometry_info.sigma;
1080 if ((flags & PercentValue) != 0)
1081 {
1082 black_point*=(double) QuantumRange/100.0;
1083 white_point*=(double) QuantumRange/100.0;
1084 }
1085 if ((flags & SigmaValue) == 0)
1086 white_point=(double) image->columns*image->rows-black_point;
1087 status=ContrastStretchImageChannel(image,DefaultChannels,black_point,
1088 white_point);
1089 return(status);
1090}
1091
1092MagickExport MagickBooleanType ContrastStretchImageChannel(Image *image,
1093 const ChannelType channel,const double black_point,const double white_point)
1094{
1095#define MaxRange(color) ((MagickRealType) ScaleQuantumToMap((Quantum) (color)))
1096#define ContrastStretchImageTag "ContrastStretch/Image"
1097
cristyc4c8d132010-01-07 01:58:38 +00001098 CacheView
1099 *image_view;
1100
cristy3ed852e2009-09-05 21:47:34 +00001101 double
1102 intensity;
1103
1104 ExceptionInfo
1105 *exception;
1106
cristy3ed852e2009-09-05 21:47:34 +00001107 MagickBooleanType
1108 status;
1109
cristybb503372010-05-27 20:51:26 +00001110 MagickOffsetType
1111 progress;
1112
cristy4c08aed2011-07-01 19:47:50 +00001113 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00001114 black,
1115 *histogram,
1116 *stretch_map,
1117 white;
1118
cristybb503372010-05-27 20:51:26 +00001119 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001120 i;
1121
cristybb503372010-05-27 20:51:26 +00001122 ssize_t
1123 y;
1124
cristy3ed852e2009-09-05 21:47:34 +00001125 /*
1126 Allocate histogram and stretch map.
1127 */
1128 assert(image != (Image *) NULL);
1129 assert(image->signature == MagickSignature);
1130 if (image->debug != MagickFalse)
1131 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
cristy4c08aed2011-07-01 19:47:50 +00001132 histogram=(PixelInfo *) AcquireQuantumMemory(MaxMap+1UL,
cristy3ed852e2009-09-05 21:47:34 +00001133 sizeof(*histogram));
cristy4c08aed2011-07-01 19:47:50 +00001134 stretch_map=(PixelInfo *) AcquireQuantumMemory(MaxMap+1UL,
cristy3ed852e2009-09-05 21:47:34 +00001135 sizeof(*stretch_map));
cristy4c08aed2011-07-01 19:47:50 +00001136 if ((histogram == (PixelInfo *) NULL) ||
1137 (stretch_map == (PixelInfo *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00001138 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
1139 image->filename);
1140 /*
1141 Form histogram.
1142 */
1143 status=MagickTrue;
1144 exception=(&image->exception);
1145 (void) ResetMagickMemory(histogram,0,(MaxMap+1)*sizeof(*histogram));
1146 image_view=AcquireCacheView(image);
cristybb503372010-05-27 20:51:26 +00001147 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001148 {
cristy4c08aed2011-07-01 19:47:50 +00001149 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00001150 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00001151
cristybb503372010-05-27 20:51:26 +00001152 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001153 x;
1154
1155 if (status == MagickFalse)
1156 continue;
1157 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00001158 if (p == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001159 {
1160 status=MagickFalse;
1161 continue;
1162 }
cristy3ed852e2009-09-05 21:47:34 +00001163 if (channel == DefaultChannels)
cristybb503372010-05-27 20:51:26 +00001164 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001165 {
1166 Quantum
1167 intensity;
1168
cristy4c08aed2011-07-01 19:47:50 +00001169 intensity=GetPixelIntensity(image,p);
cristy3ed852e2009-09-05 21:47:34 +00001170 histogram[ScaleQuantumToMap(intensity)].red++;
1171 histogram[ScaleQuantumToMap(intensity)].green++;
1172 histogram[ScaleQuantumToMap(intensity)].blue++;
cristy4c08aed2011-07-01 19:47:50 +00001173 histogram[ScaleQuantumToMap(intensity)].black++;
1174 p+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00001175 }
1176 else
cristybb503372010-05-27 20:51:26 +00001177 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001178 {
cristy2b9582a2011-07-04 17:38:56 +00001179 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001180 histogram[ScaleQuantumToMap(GetPixelRed(image,p))].red++;
cristy2b9582a2011-07-04 17:38:56 +00001181 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001182 histogram[ScaleQuantumToMap(GetPixelGreen(image,p))].green++;
cristy2b9582a2011-07-04 17:38:56 +00001183 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001184 histogram[ScaleQuantumToMap(GetPixelBlue(image,p))].blue++;
cristy2b9582a2011-07-04 17:38:56 +00001185 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00001186 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +00001187 histogram[ScaleQuantumToMap(GetPixelBlack(image,p))].black++;
cristy2b9582a2011-07-04 17:38:56 +00001188 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001189 histogram[ScaleQuantumToMap(GetPixelAlpha(image,p))].alpha++;
1190 p+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00001191 }
1192 }
1193 /*
1194 Find the histogram boundaries by locating the black/white levels.
1195 */
1196 black.red=0.0;
1197 white.red=MaxRange(QuantumRange);
cristy2b9582a2011-07-04 17:38:56 +00001198 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001199 {
1200 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00001201 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +00001202 {
1203 intensity+=histogram[i].red;
1204 if (intensity > black_point)
1205 break;
1206 }
1207 black.red=(MagickRealType) i;
1208 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00001209 for (i=(ssize_t) MaxMap; i != 0; i--)
cristy3ed852e2009-09-05 21:47:34 +00001210 {
1211 intensity+=histogram[i].red;
1212 if (intensity > ((double) image->columns*image->rows-white_point))
1213 break;
1214 }
1215 white.red=(MagickRealType) i;
1216 }
1217 black.green=0.0;
1218 white.green=MaxRange(QuantumRange);
cristy2b9582a2011-07-04 17:38:56 +00001219 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001220 {
1221 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00001222 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +00001223 {
1224 intensity+=histogram[i].green;
1225 if (intensity > black_point)
1226 break;
1227 }
1228 black.green=(MagickRealType) i;
1229 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00001230 for (i=(ssize_t) MaxMap; i != 0; i--)
cristy3ed852e2009-09-05 21:47:34 +00001231 {
1232 intensity+=histogram[i].green;
1233 if (intensity > ((double) image->columns*image->rows-white_point))
1234 break;
1235 }
1236 white.green=(MagickRealType) i;
1237 }
1238 black.blue=0.0;
1239 white.blue=MaxRange(QuantumRange);
cristy2b9582a2011-07-04 17:38:56 +00001240 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001241 {
1242 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00001243 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +00001244 {
1245 intensity+=histogram[i].blue;
1246 if (intensity > black_point)
1247 break;
1248 }
1249 black.blue=(MagickRealType) i;
1250 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00001251 for (i=(ssize_t) MaxMap; i != 0; i--)
cristy3ed852e2009-09-05 21:47:34 +00001252 {
1253 intensity+=histogram[i].blue;
1254 if (intensity > ((double) image->columns*image->rows-white_point))
1255 break;
1256 }
1257 white.blue=(MagickRealType) i;
1258 }
cristy4c08aed2011-07-01 19:47:50 +00001259 black.alpha=0.0;
1260 white.alpha=MaxRange(QuantumRange);
cristy2b9582a2011-07-04 17:38:56 +00001261 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001262 {
1263 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00001264 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +00001265 {
cristy4c08aed2011-07-01 19:47:50 +00001266 intensity+=histogram[i].alpha;
cristy3ed852e2009-09-05 21:47:34 +00001267 if (intensity > black_point)
1268 break;
1269 }
cristy4c08aed2011-07-01 19:47:50 +00001270 black.alpha=(MagickRealType) i;
cristy3ed852e2009-09-05 21:47:34 +00001271 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00001272 for (i=(ssize_t) MaxMap; i != 0; i--)
cristy3ed852e2009-09-05 21:47:34 +00001273 {
cristy4c08aed2011-07-01 19:47:50 +00001274 intensity+=histogram[i].alpha;
cristy3ed852e2009-09-05 21:47:34 +00001275 if (intensity > ((double) image->columns*image->rows-white_point))
1276 break;
1277 }
cristy4c08aed2011-07-01 19:47:50 +00001278 white.alpha=(MagickRealType) i;
cristy3ed852e2009-09-05 21:47:34 +00001279 }
cristy4c08aed2011-07-01 19:47:50 +00001280 black.black=0.0;
1281 white.black=MaxRange(QuantumRange);
cristy2b9582a2011-07-04 17:38:56 +00001282 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) && (image->colorspace == CMYKColorspace))
cristy3ed852e2009-09-05 21:47:34 +00001283 {
1284 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00001285 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +00001286 {
cristy4c08aed2011-07-01 19:47:50 +00001287 intensity+=histogram[i].black;
cristy3ed852e2009-09-05 21:47:34 +00001288 if (intensity > black_point)
1289 break;
1290 }
cristy4c08aed2011-07-01 19:47:50 +00001291 black.black=(MagickRealType) i;
cristy3ed852e2009-09-05 21:47:34 +00001292 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00001293 for (i=(ssize_t) MaxMap; i != 0; i--)
cristy3ed852e2009-09-05 21:47:34 +00001294 {
cristy4c08aed2011-07-01 19:47:50 +00001295 intensity+=histogram[i].black;
cristy3ed852e2009-09-05 21:47:34 +00001296 if (intensity > ((double) image->columns*image->rows-white_point))
1297 break;
1298 }
cristy4c08aed2011-07-01 19:47:50 +00001299 white.black=(MagickRealType) i;
cristy3ed852e2009-09-05 21:47:34 +00001300 }
cristy4c08aed2011-07-01 19:47:50 +00001301 histogram=(PixelInfo *) RelinquishMagickMemory(histogram);
cristy3ed852e2009-09-05 21:47:34 +00001302 /*
1303 Stretch the histogram to create the stretched image mapping.
1304 */
1305 (void) ResetMagickMemory(stretch_map,0,(MaxMap+1)*sizeof(*stretch_map));
cristyb5d5f722009-11-04 03:03:49 +00001306#if defined(MAGICKCORE_OPENMP_SUPPORT)
1307 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001308#endif
cristybb503372010-05-27 20:51:26 +00001309 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +00001310 {
cristy2b9582a2011-07-04 17:38:56 +00001311 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001312 {
cristybb503372010-05-27 20:51:26 +00001313 if (i < (ssize_t) black.red)
cristy3ed852e2009-09-05 21:47:34 +00001314 stretch_map[i].red=0.0;
1315 else
cristybb503372010-05-27 20:51:26 +00001316 if (i > (ssize_t) white.red)
cristy3ed852e2009-09-05 21:47:34 +00001317 stretch_map[i].red=(MagickRealType) QuantumRange;
1318 else
1319 if (black.red != white.red)
1320 stretch_map[i].red=(MagickRealType) ScaleMapToQuantum(
1321 (MagickRealType) (MaxMap*(i-black.red)/(white.red-black.red)));
1322 }
cristy2b9582a2011-07-04 17:38:56 +00001323 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001324 {
cristybb503372010-05-27 20:51:26 +00001325 if (i < (ssize_t) black.green)
cristy3ed852e2009-09-05 21:47:34 +00001326 stretch_map[i].green=0.0;
1327 else
cristybb503372010-05-27 20:51:26 +00001328 if (i > (ssize_t) white.green)
cristy3ed852e2009-09-05 21:47:34 +00001329 stretch_map[i].green=(MagickRealType) QuantumRange;
1330 else
1331 if (black.green != white.green)
1332 stretch_map[i].green=(MagickRealType) ScaleMapToQuantum(
1333 (MagickRealType) (MaxMap*(i-black.green)/(white.green-
1334 black.green)));
1335 }
cristy2b9582a2011-07-04 17:38:56 +00001336 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001337 {
cristybb503372010-05-27 20:51:26 +00001338 if (i < (ssize_t) black.blue)
cristy3ed852e2009-09-05 21:47:34 +00001339 stretch_map[i].blue=0.0;
1340 else
cristybb503372010-05-27 20:51:26 +00001341 if (i > (ssize_t) white.blue)
cristy3ed852e2009-09-05 21:47:34 +00001342 stretch_map[i].blue=(MagickRealType) QuantumRange;
1343 else
1344 if (black.blue != white.blue)
1345 stretch_map[i].blue=(MagickRealType) ScaleMapToQuantum(
1346 (MagickRealType) (MaxMap*(i-black.blue)/(white.blue-
1347 black.blue)));
1348 }
cristy2b9582a2011-07-04 17:38:56 +00001349 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001350 {
cristy4c08aed2011-07-01 19:47:50 +00001351 if (i < (ssize_t) black.alpha)
1352 stretch_map[i].alpha=0.0;
cristy3ed852e2009-09-05 21:47:34 +00001353 else
cristy4c08aed2011-07-01 19:47:50 +00001354 if (i > (ssize_t) white.alpha)
1355 stretch_map[i].alpha=(MagickRealType) QuantumRange;
cristy3ed852e2009-09-05 21:47:34 +00001356 else
cristy4c08aed2011-07-01 19:47:50 +00001357 if (black.alpha != white.alpha)
1358 stretch_map[i].alpha=(MagickRealType) ScaleMapToQuantum(
1359 (MagickRealType) (MaxMap*(i-black.alpha)/(white.alpha-
1360 black.alpha)));
cristy3ed852e2009-09-05 21:47:34 +00001361 }
cristy2b9582a2011-07-04 17:38:56 +00001362 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00001363 (image->colorspace == CMYKColorspace))
1364 {
cristy4c08aed2011-07-01 19:47:50 +00001365 if (i < (ssize_t) black.black)
1366 stretch_map[i].black=0.0;
cristy3ed852e2009-09-05 21:47:34 +00001367 else
cristy4c08aed2011-07-01 19:47:50 +00001368 if (i > (ssize_t) white.black)
1369 stretch_map[i].black=(MagickRealType) QuantumRange;
cristy3ed852e2009-09-05 21:47:34 +00001370 else
cristy4c08aed2011-07-01 19:47:50 +00001371 if (black.black != white.black)
1372 stretch_map[i].black=(MagickRealType) ScaleMapToQuantum(
1373 (MagickRealType) (MaxMap*(i-black.black)/(white.black-
1374 black.black)));
cristy3ed852e2009-09-05 21:47:34 +00001375 }
1376 }
1377 /*
1378 Stretch the image.
1379 */
cristy2b9582a2011-07-04 17:38:56 +00001380 if (((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0) || (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00001381 (image->colorspace == CMYKColorspace)))
1382 image->storage_class=DirectClass;
1383 if (image->storage_class == PseudoClass)
1384 {
1385 /*
1386 Stretch colormap.
1387 */
cristyb5d5f722009-11-04 03:03:49 +00001388#if defined(MAGICKCORE_OPENMP_SUPPORT)
1389 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001390#endif
cristybb503372010-05-27 20:51:26 +00001391 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00001392 {
cristy2b9582a2011-07-04 17:38:56 +00001393 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001394 {
1395 if (black.red != white.red)
cristyce70c172010-01-07 17:15:30 +00001396 image->colormap[i].red=ClampToQuantum(stretch_map[
cristy3ed852e2009-09-05 21:47:34 +00001397 ScaleQuantumToMap(image->colormap[i].red)].red);
1398 }
cristy2b9582a2011-07-04 17:38:56 +00001399 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001400 {
1401 if (black.green != white.green)
cristyce70c172010-01-07 17:15:30 +00001402 image->colormap[i].green=ClampToQuantum(stretch_map[
cristy3ed852e2009-09-05 21:47:34 +00001403 ScaleQuantumToMap(image->colormap[i].green)].green);
1404 }
cristy2b9582a2011-07-04 17:38:56 +00001405 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001406 {
1407 if (black.blue != white.blue)
cristyce70c172010-01-07 17:15:30 +00001408 image->colormap[i].blue=ClampToQuantum(stretch_map[
cristy3ed852e2009-09-05 21:47:34 +00001409 ScaleQuantumToMap(image->colormap[i].blue)].blue);
1410 }
cristy2b9582a2011-07-04 17:38:56 +00001411 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001412 {
cristy4c08aed2011-07-01 19:47:50 +00001413 if (black.alpha != white.alpha)
1414 image->colormap[i].alpha=ClampToQuantum(stretch_map[
1415 ScaleQuantumToMap(image->colormap[i].alpha)].alpha);
cristy3ed852e2009-09-05 21:47:34 +00001416 }
1417 }
1418 }
1419 /*
1420 Stretch image.
1421 */
1422 status=MagickTrue;
1423 progress=0;
cristyb5d5f722009-11-04 03:03:49 +00001424#if defined(MAGICKCORE_OPENMP_SUPPORT)
1425 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001426#endif
cristybb503372010-05-27 20:51:26 +00001427 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001428 {
cristy4c08aed2011-07-01 19:47:50 +00001429 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00001430 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001431
cristy8d4629b2010-08-30 17:59:46 +00001432 register ssize_t
1433 x;
1434
cristy3ed852e2009-09-05 21:47:34 +00001435 if (status == MagickFalse)
1436 continue;
1437 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00001438 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001439 {
1440 status=MagickFalse;
1441 continue;
1442 }
cristybb503372010-05-27 20:51:26 +00001443 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001444 {
cristy2b9582a2011-07-04 17:38:56 +00001445 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001446 {
1447 if (black.red != white.red)
cristy4c08aed2011-07-01 19:47:50 +00001448 SetPixelRed(image,ClampToQuantum(stretch_map[ScaleQuantumToMap(
1449 GetPixelRed(image,q))].red),q);
cristy3ed852e2009-09-05 21:47:34 +00001450 }
cristy2b9582a2011-07-04 17:38:56 +00001451 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001452 {
1453 if (black.green != white.green)
cristy4c08aed2011-07-01 19:47:50 +00001454 SetPixelGreen(image,ClampToQuantum(stretch_map[ScaleQuantumToMap(
1455 GetPixelGreen(image,q))].green),q);
cristy3ed852e2009-09-05 21:47:34 +00001456 }
cristy2b9582a2011-07-04 17:38:56 +00001457 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001458 {
1459 if (black.blue != white.blue)
cristy4c08aed2011-07-01 19:47:50 +00001460 SetPixelBlue(image,ClampToQuantum(stretch_map[ScaleQuantumToMap(
1461 GetPixelBlue(image,q))].blue),q);
1462 }
cristy2b9582a2011-07-04 17:38:56 +00001463 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +00001464 (image->colorspace == CMYKColorspace))
1465 {
1466 if (black.black != white.black)
1467 SetPixelBlack(image,ClampToQuantum(stretch_map[ScaleQuantumToMap(
1468 GetPixelBlack(image,q))].black),q);
cristy3ed852e2009-09-05 21:47:34 +00001469 }
cristy2b9582a2011-07-04 17:38:56 +00001470 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001471 {
cristy4c08aed2011-07-01 19:47:50 +00001472 if (black.alpha != white.alpha)
1473 SetPixelAlpha(image,ClampToQuantum(stretch_map[ScaleQuantumToMap(
1474 GetPixelAlpha(image,q))].alpha),q);
cristy3ed852e2009-09-05 21:47:34 +00001475 }
cristy4c08aed2011-07-01 19:47:50 +00001476 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00001477 }
1478 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
1479 status=MagickFalse;
1480 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1481 {
1482 MagickBooleanType
1483 proceed;
1484
cristyb5d5f722009-11-04 03:03:49 +00001485#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00001486 #pragma omp critical (MagickCore_ContrastStretchImageChannel)
1487#endif
1488 proceed=SetImageProgress(image,ContrastStretchImageTag,progress++,
1489 image->rows);
1490 if (proceed == MagickFalse)
1491 status=MagickFalse;
1492 }
1493 }
1494 image_view=DestroyCacheView(image_view);
cristy4c08aed2011-07-01 19:47:50 +00001495 stretch_map=(PixelInfo *) RelinquishMagickMemory(stretch_map);
cristy3ed852e2009-09-05 21:47:34 +00001496 return(status);
1497}
1498
1499/*
1500%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1501% %
1502% %
1503% %
1504% E n h a n c e I m a g e %
1505% %
1506% %
1507% %
1508%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1509%
1510% EnhanceImage() applies a digital filter that improves the quality of a
1511% noisy image.
1512%
1513% The format of the EnhanceImage method is:
1514%
1515% Image *EnhanceImage(const Image *image,ExceptionInfo *exception)
1516%
1517% A description of each parameter follows:
1518%
1519% o image: the image.
1520%
1521% o exception: return any errors or warnings in this structure.
1522%
1523*/
1524MagickExport Image *EnhanceImage(const Image *image,ExceptionInfo *exception)
1525{
1526#define Enhance(weight) \
cristy4c08aed2011-07-01 19:47:50 +00001527 mean=((MagickRealType) GetPixelRed(image,r)+pixel.red)/2; \
1528 distance=(MagickRealType) GetPixelRed(image,r)-(MagickRealType) pixel.red; \
cristy3ed852e2009-09-05 21:47:34 +00001529 distance_squared=QuantumScale*(2.0*((MagickRealType) QuantumRange+1.0)+ \
1530 mean)*distance*distance; \
cristy4c08aed2011-07-01 19:47:50 +00001531 mean=((MagickRealType) GetPixelGreen(image,r)+pixel.green)/2; \
1532 distance=(MagickRealType) GetPixelGreen(image,r)- \
1533 (MagickRealType) pixel.green; \
cristy3ed852e2009-09-05 21:47:34 +00001534 distance_squared+=4.0*distance*distance; \
cristy4c08aed2011-07-01 19:47:50 +00001535 mean=((MagickRealType) GetPixelBlue(image,r)+pixel.blue)/2; \
1536 distance=(MagickRealType) GetPixelBlue(image,r)- \
1537 (MagickRealType) pixel.blue; \
cristy3ed852e2009-09-05 21:47:34 +00001538 distance_squared+=QuantumScale*(3.0*((MagickRealType) \
1539 QuantumRange+1.0)-1.0-mean)*distance*distance; \
cristy4c08aed2011-07-01 19:47:50 +00001540 mean=((MagickRealType) GetPixelAlpha(image,r)+pixel.alpha)/2; \
1541 distance=(MagickRealType) GetPixelAlpha(image,r)-(MagickRealType) pixel.alpha; \
cristy3ed852e2009-09-05 21:47:34 +00001542 distance_squared+=QuantumScale*(3.0*((MagickRealType) \
1543 QuantumRange+1.0)-1.0-mean)*distance*distance; \
1544 if (distance_squared < ((MagickRealType) QuantumRange*(MagickRealType) \
1545 QuantumRange/25.0f)) \
1546 { \
cristy4c08aed2011-07-01 19:47:50 +00001547 aggregate.red+=(weight)*GetPixelRed(image,r); \
1548 aggregate.green+=(weight)*GetPixelGreen(image,r); \
1549 aggregate.blue+=(weight)*GetPixelBlue(image,r); \
1550 aggregate.alpha+=(weight)*GetPixelAlpha(image,r); \
cristy3ed852e2009-09-05 21:47:34 +00001551 total_weight+=(weight); \
1552 } \
1553 r++;
1554#define EnhanceImageTag "Enhance/Image"
1555
cristyc4c8d132010-01-07 01:58:38 +00001556 CacheView
1557 *enhance_view,
1558 *image_view;
1559
cristy3ed852e2009-09-05 21:47:34 +00001560 Image
1561 *enhance_image;
1562
cristy3ed852e2009-09-05 21:47:34 +00001563 MagickBooleanType
1564 status;
1565
cristybb503372010-05-27 20:51:26 +00001566 MagickOffsetType
1567 progress;
1568
cristy4c08aed2011-07-01 19:47:50 +00001569 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00001570 zero;
1571
cristybb503372010-05-27 20:51:26 +00001572 ssize_t
1573 y;
1574
cristy3ed852e2009-09-05 21:47:34 +00001575 /*
1576 Initialize enhanced image attributes.
1577 */
1578 assert(image != (const Image *) NULL);
1579 assert(image->signature == MagickSignature);
1580 if (image->debug != MagickFalse)
1581 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1582 assert(exception != (ExceptionInfo *) NULL);
1583 assert(exception->signature == MagickSignature);
1584 if ((image->columns < 5) || (image->rows < 5))
1585 return((Image *) NULL);
1586 enhance_image=CloneImage(image,image->columns,image->rows,MagickTrue,
1587 exception);
1588 if (enhance_image == (Image *) NULL)
1589 return((Image *) NULL);
1590 if (SetImageStorageClass(enhance_image,DirectClass) == MagickFalse)
1591 {
1592 InheritException(exception,&enhance_image->exception);
1593 enhance_image=DestroyImage(enhance_image);
1594 return((Image *) NULL);
1595 }
1596 /*
1597 Enhance image.
1598 */
1599 status=MagickTrue;
1600 progress=0;
1601 (void) ResetMagickMemory(&zero,0,sizeof(zero));
1602 image_view=AcquireCacheView(image);
1603 enhance_view=AcquireCacheView(enhance_image);
cristyb5d5f722009-11-04 03:03:49 +00001604#if defined(MAGICKCORE_OPENMP_SUPPORT)
1605 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001606#endif
cristybb503372010-05-27 20:51:26 +00001607 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001608 {
cristy4c08aed2011-07-01 19:47:50 +00001609 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00001610 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00001611
cristy4c08aed2011-07-01 19:47:50 +00001612 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00001613 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001614
cristy8d4629b2010-08-30 17:59:46 +00001615 register ssize_t
1616 x;
1617
cristy3ed852e2009-09-05 21:47:34 +00001618 /*
1619 Read another scan line.
1620 */
1621 if (status == MagickFalse)
1622 continue;
1623 p=GetCacheViewVirtualPixels(image_view,-2,y-2,image->columns+4,5,exception);
1624 q=QueueCacheViewAuthenticPixels(enhance_view,0,y,enhance_image->columns,1,
1625 exception);
cristy4c08aed2011-07-01 19:47:50 +00001626 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00001627 {
1628 status=MagickFalse;
1629 continue;
1630 }
cristybb503372010-05-27 20:51:26 +00001631 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001632 {
cristy4c08aed2011-07-01 19:47:50 +00001633 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00001634 aggregate;
1635
1636 MagickRealType
1637 distance,
1638 distance_squared,
1639 mean,
1640 total_weight;
1641
1642 PixelPacket
1643 pixel;
1644
cristy4c08aed2011-07-01 19:47:50 +00001645 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00001646 *restrict r;
cristy3ed852e2009-09-05 21:47:34 +00001647
1648 /*
1649 Compute weighted average of target pixel color components.
1650 */
1651 aggregate=zero;
1652 total_weight=0.0;
1653 r=p+2*(image->columns+4)+2;
cristy4c08aed2011-07-01 19:47:50 +00001654 GetPixelPacket(image,r,&pixel);
cristy3ed852e2009-09-05 21:47:34 +00001655 r=p;
1656 Enhance(5.0); Enhance(8.0); Enhance(10.0); Enhance(8.0); Enhance(5.0);
1657 r=p+(image->columns+4);
1658 Enhance(8.0); Enhance(20.0); Enhance(40.0); Enhance(20.0); Enhance(8.0);
1659 r=p+2*(image->columns+4);
1660 Enhance(10.0); Enhance(40.0); Enhance(80.0); Enhance(40.0); Enhance(10.0);
1661 r=p+3*(image->columns+4);
1662 Enhance(8.0); Enhance(20.0); Enhance(40.0); Enhance(20.0); Enhance(8.0);
1663 r=p+4*(image->columns+4);
1664 Enhance(5.0); Enhance(8.0); Enhance(10.0); Enhance(8.0); Enhance(5.0);
cristy4c08aed2011-07-01 19:47:50 +00001665 SetPixelRed(enhance_image,(Quantum) ((aggregate.red+
1666 (total_weight/2)-1)/total_weight),q);
1667 SetPixelGreen(enhance_image,(Quantum) ((aggregate.green+
1668 (total_weight/2)-1)/total_weight),q);
1669 SetPixelBlue(enhance_image,(Quantum) ((aggregate.blue+
1670 (total_weight/2)-1)/total_weight),q);
1671 SetPixelAlpha(enhance_image,(Quantum) ((aggregate.alpha+
1672 (total_weight/2)-1)/total_weight),q);
1673 p+=GetPixelChannels(image);
1674 q+=GetPixelChannels(enhance_image);
cristy3ed852e2009-09-05 21:47:34 +00001675 }
1676 if (SyncCacheViewAuthenticPixels(enhance_view,exception) == MagickFalse)
1677 status=MagickFalse;
1678 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1679 {
1680 MagickBooleanType
1681 proceed;
1682
cristyb5d5f722009-11-04 03:03:49 +00001683#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00001684 #pragma omp critical (MagickCore_EnhanceImage)
1685#endif
1686 proceed=SetImageProgress(image,EnhanceImageTag,progress++,image->rows);
1687 if (proceed == MagickFalse)
1688 status=MagickFalse;
1689 }
1690 }
1691 enhance_view=DestroyCacheView(enhance_view);
1692 image_view=DestroyCacheView(image_view);
1693 return(enhance_image);
1694}
1695
1696/*
1697%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1698% %
1699% %
1700% %
1701% E q u a l i z e I m a g e %
1702% %
1703% %
1704% %
1705%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1706%
1707% EqualizeImage() applies a histogram equalization to the image.
1708%
1709% The format of the EqualizeImage method is:
1710%
1711% MagickBooleanType EqualizeImage(Image *image)
1712% MagickBooleanType EqualizeImageChannel(Image *image,
1713% const ChannelType channel)
1714%
1715% A description of each parameter follows:
1716%
1717% o image: the image.
1718%
1719% o channel: the channel.
1720%
1721*/
1722
1723MagickExport MagickBooleanType EqualizeImage(Image *image)
1724{
1725 return(EqualizeImageChannel(image,DefaultChannels));
1726}
1727
1728MagickExport MagickBooleanType EqualizeImageChannel(Image *image,
1729 const ChannelType channel)
1730{
1731#define EqualizeImageTag "Equalize/Image"
1732
cristyc4c8d132010-01-07 01:58:38 +00001733 CacheView
1734 *image_view;
1735
cristy3ed852e2009-09-05 21:47:34 +00001736 ExceptionInfo
1737 *exception;
1738
cristy3ed852e2009-09-05 21:47:34 +00001739 MagickBooleanType
1740 status;
1741
cristybb503372010-05-27 20:51:26 +00001742 MagickOffsetType
1743 progress;
1744
cristy4c08aed2011-07-01 19:47:50 +00001745 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00001746 black,
1747 *equalize_map,
1748 *histogram,
1749 intensity,
1750 *map,
1751 white;
1752
cristybb503372010-05-27 20:51:26 +00001753 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001754 i;
1755
cristybb503372010-05-27 20:51:26 +00001756 ssize_t
1757 y;
1758
cristy3ed852e2009-09-05 21:47:34 +00001759 /*
1760 Allocate and initialize histogram arrays.
1761 */
1762 assert(image != (Image *) NULL);
1763 assert(image->signature == MagickSignature);
1764 if (image->debug != MagickFalse)
1765 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
cristy4c08aed2011-07-01 19:47:50 +00001766 equalize_map=(PixelInfo *) AcquireQuantumMemory(MaxMap+1UL,
cristy3ed852e2009-09-05 21:47:34 +00001767 sizeof(*equalize_map));
cristy4c08aed2011-07-01 19:47:50 +00001768 histogram=(PixelInfo *) AcquireQuantumMemory(MaxMap+1UL,
cristy3ed852e2009-09-05 21:47:34 +00001769 sizeof(*histogram));
cristy4c08aed2011-07-01 19:47:50 +00001770 map=(PixelInfo *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*map));
1771 if ((equalize_map == (PixelInfo *) NULL) ||
1772 (histogram == (PixelInfo *) NULL) ||
1773 (map == (PixelInfo *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00001774 {
cristy4c08aed2011-07-01 19:47:50 +00001775 if (map != (PixelInfo *) NULL)
1776 map=(PixelInfo *) RelinquishMagickMemory(map);
1777 if (histogram != (PixelInfo *) NULL)
1778 histogram=(PixelInfo *) RelinquishMagickMemory(histogram);
1779 if (equalize_map != (PixelInfo *) NULL)
1780 equalize_map=(PixelInfo *) RelinquishMagickMemory(equalize_map);
cristy3ed852e2009-09-05 21:47:34 +00001781 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
1782 image->filename);
1783 }
1784 /*
1785 Form histogram.
1786 */
1787 (void) ResetMagickMemory(histogram,0,(MaxMap+1)*sizeof(*histogram));
1788 exception=(&image->exception);
cristybb503372010-05-27 20:51:26 +00001789 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001790 {
cristy4c08aed2011-07-01 19:47:50 +00001791 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00001792 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00001793
cristybb503372010-05-27 20:51:26 +00001794 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001795 x;
1796
1797 p=GetVirtualPixels(image,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00001798 if (p == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001799 break;
cristybb503372010-05-27 20:51:26 +00001800 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001801 {
cristy2b9582a2011-07-04 17:38:56 +00001802 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001803 histogram[ScaleQuantumToMap(GetPixelRed(image,p))].red++;
cristy2b9582a2011-07-04 17:38:56 +00001804 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001805 histogram[ScaleQuantumToMap(GetPixelGreen(image,p))].green++;
cristy2b9582a2011-07-04 17:38:56 +00001806 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001807 histogram[ScaleQuantumToMap(GetPixelBlue(image,p))].blue++;
cristy2b9582a2011-07-04 17:38:56 +00001808 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00001809 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +00001810 histogram[ScaleQuantumToMap(GetPixelBlack(image,p))].black++;
cristy2b9582a2011-07-04 17:38:56 +00001811 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001812 histogram[ScaleQuantumToMap(GetPixelAlpha(image,p))].alpha++;
1813 p+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00001814 }
1815 }
1816 /*
1817 Integrate the histogram to get the equalization map.
1818 */
1819 (void) ResetMagickMemory(&intensity,0,sizeof(intensity));
cristybb503372010-05-27 20:51:26 +00001820 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +00001821 {
cristy2b9582a2011-07-04 17:38:56 +00001822 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001823 intensity.red+=histogram[i].red;
cristy2b9582a2011-07-04 17:38:56 +00001824 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001825 intensity.green+=histogram[i].green;
cristy2b9582a2011-07-04 17:38:56 +00001826 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001827 intensity.blue+=histogram[i].blue;
cristy2b9582a2011-07-04 17:38:56 +00001828 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00001829 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +00001830 intensity.black+=histogram[i].black;
cristy2b9582a2011-07-04 17:38:56 +00001831 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001832 intensity.alpha+=histogram[i].alpha;
cristy3ed852e2009-09-05 21:47:34 +00001833 map[i]=intensity;
1834 }
1835 black=map[0];
1836 white=map[(int) MaxMap];
1837 (void) ResetMagickMemory(equalize_map,0,(MaxMap+1)*sizeof(*equalize_map));
cristyb5d5f722009-11-04 03:03:49 +00001838#if defined(MAGICKCORE_OPENMP_SUPPORT)
1839 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001840#endif
cristybb503372010-05-27 20:51:26 +00001841 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +00001842 {
cristy2b9582a2011-07-04 17:38:56 +00001843 if (((GetPixelRedTraits(image) & ActivePixelTrait) != 0) &&
1844 (white.red != black.red))
cristy3ed852e2009-09-05 21:47:34 +00001845 equalize_map[i].red=(MagickRealType) ScaleMapToQuantum((MagickRealType)
1846 ((MaxMap*(map[i].red-black.red))/(white.red-black.red)));
cristy2b9582a2011-07-04 17:38:56 +00001847 if (((GetPixelGreenTraits(image) & ActivePixelTrait) != 0) &&
1848 (white.green != black.green))
cristy3ed852e2009-09-05 21:47:34 +00001849 equalize_map[i].green=(MagickRealType) ScaleMapToQuantum((MagickRealType)
1850 ((MaxMap*(map[i].green-black.green))/(white.green-black.green)));
cristy2b9582a2011-07-04 17:38:56 +00001851 if (((GetPixelBlueTraits(image) & ActivePixelTrait) != 0) &&
1852 (white.blue != black.blue))
cristy3ed852e2009-09-05 21:47:34 +00001853 equalize_map[i].blue=(MagickRealType) ScaleMapToQuantum((MagickRealType)
1854 ((MaxMap*(map[i].blue-black.blue))/(white.blue-black.blue)));
cristy2b9582a2011-07-04 17:38:56 +00001855 if ((((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00001856 (image->colorspace == CMYKColorspace)) &&
cristy4c08aed2011-07-01 19:47:50 +00001857 (white.black != black.black))
1858 equalize_map[i].black=(MagickRealType) ScaleMapToQuantum((MagickRealType)
1859 ((MaxMap*(map[i].black-black.black))/(white.black-black.black)));
cristy2b9582a2011-07-04 17:38:56 +00001860 if (((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0) &&
1861 (white.alpha != black.alpha))
cristy4c08aed2011-07-01 19:47:50 +00001862 equalize_map[i].alpha=(MagickRealType) ScaleMapToQuantum(
1863 (MagickRealType) ((MaxMap*(map[i].alpha-black.alpha))/
1864 (white.alpha-black.alpha)));
cristy3ed852e2009-09-05 21:47:34 +00001865 }
cristy4c08aed2011-07-01 19:47:50 +00001866 histogram=(PixelInfo *) RelinquishMagickMemory(histogram);
1867 map=(PixelInfo *) RelinquishMagickMemory(map);
cristy3ed852e2009-09-05 21:47:34 +00001868 if (image->storage_class == PseudoClass)
1869 {
1870 /*
1871 Equalize colormap.
1872 */
cristyb5d5f722009-11-04 03:03:49 +00001873#if defined(MAGICKCORE_OPENMP_SUPPORT)
1874 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001875#endif
cristybb503372010-05-27 20:51:26 +00001876 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00001877 {
cristy2b9582a2011-07-04 17:38:56 +00001878 if (((GetPixelRedTraits(image) & ActivePixelTrait) != 0) &&
1879 (white.red != black.red))
cristyce70c172010-01-07 17:15:30 +00001880 image->colormap[i].red=ClampToQuantum(equalize_map[
cristy3ed852e2009-09-05 21:47:34 +00001881 ScaleQuantumToMap(image->colormap[i].red)].red);
cristy2b9582a2011-07-04 17:38:56 +00001882 if (((GetPixelGreenTraits(image) & ActivePixelTrait) != 0) &&
1883 (white.green != black.green))
cristyce70c172010-01-07 17:15:30 +00001884 image->colormap[i].green=ClampToQuantum(equalize_map[
cristy3ed852e2009-09-05 21:47:34 +00001885 ScaleQuantumToMap(image->colormap[i].green)].green);
cristy2b9582a2011-07-04 17:38:56 +00001886 if (((GetPixelBlueTraits(image) & ActivePixelTrait) != 0) &&
1887 (white.blue != black.blue))
cristyce70c172010-01-07 17:15:30 +00001888 image->colormap[i].blue=ClampToQuantum(equalize_map[
cristy3ed852e2009-09-05 21:47:34 +00001889 ScaleQuantumToMap(image->colormap[i].blue)].blue);
cristy2b9582a2011-07-04 17:38:56 +00001890 if (((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +00001891 (white.alpha != black.alpha))
1892 image->colormap[i].alpha=ClampToQuantum(equalize_map[
1893 ScaleQuantumToMap(image->colormap[i].alpha)].alpha);
cristy3ed852e2009-09-05 21:47:34 +00001894 }
1895 }
1896 /*
1897 Equalize image.
1898 */
1899 status=MagickTrue;
1900 progress=0;
1901 exception=(&image->exception);
1902 image_view=AcquireCacheView(image);
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 (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001907 {
cristy4c08aed2011-07-01 19:47:50 +00001908 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00001909 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001910
cristy8d4629b2010-08-30 17:59:46 +00001911 register ssize_t
1912 x;
1913
cristy3ed852e2009-09-05 21:47:34 +00001914 if (status == MagickFalse)
1915 continue;
1916 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00001917 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001918 {
1919 status=MagickFalse;
1920 continue;
1921 }
cristybb503372010-05-27 20:51:26 +00001922 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001923 {
cristy2b9582a2011-07-04 17:38:56 +00001924 if (((GetPixelRedTraits(image) & ActivePixelTrait) != 0) &&
1925 (white.red != black.red))
cristy4c08aed2011-07-01 19:47:50 +00001926 SetPixelRed(image,ClampToQuantum(equalize_map[
1927 ScaleQuantumToMap(GetPixelRed(image,q))].red),q);
cristy2b9582a2011-07-04 17:38:56 +00001928 if (((GetPixelGreenTraits(image) & ActivePixelTrait) != 0) &&
1929 (white.green != black.green))
cristy4c08aed2011-07-01 19:47:50 +00001930 SetPixelGreen(image,ClampToQuantum(equalize_map[
1931 ScaleQuantumToMap(GetPixelGreen(image,q))].green),q);
cristy2b9582a2011-07-04 17:38:56 +00001932 if (((GetPixelBlueTraits(image) & ActivePixelTrait) != 0) &&
1933 (white.blue != black.blue))
cristy4c08aed2011-07-01 19:47:50 +00001934 SetPixelBlue(image,ClampToQuantum(equalize_map[
1935 ScaleQuantumToMap(GetPixelBlue(image,q))].blue),q);
cristy2b9582a2011-07-04 17:38:56 +00001936 if ((((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00001937 (image->colorspace == CMYKColorspace)) &&
cristy4c08aed2011-07-01 19:47:50 +00001938 (white.black != black.black))
1939 SetPixelBlack(image,ClampToQuantum(equalize_map[
1940 ScaleQuantumToMap(GetPixelBlack(image,q))].black),q);
cristy2b9582a2011-07-04 17:38:56 +00001941 if (((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0) &&
1942 (white.alpha != black.alpha))
cristy4c08aed2011-07-01 19:47:50 +00001943 SetPixelAlpha(image,ClampToQuantum(equalize_map[
1944 ScaleQuantumToMap(GetPixelAlpha(image,q))].alpha),q);
1945 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00001946 }
1947 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
1948 status=MagickFalse;
1949 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1950 {
1951 MagickBooleanType
1952 proceed;
1953
cristyb5d5f722009-11-04 03:03:49 +00001954#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00001955 #pragma omp critical (MagickCore_EqualizeImageChannel)
1956#endif
1957 proceed=SetImageProgress(image,EqualizeImageTag,progress++,image->rows);
1958 if (proceed == MagickFalse)
1959 status=MagickFalse;
1960 }
1961 }
1962 image_view=DestroyCacheView(image_view);
cristy4c08aed2011-07-01 19:47:50 +00001963 equalize_map=(PixelInfo *) RelinquishMagickMemory(equalize_map);
cristy3ed852e2009-09-05 21:47:34 +00001964 return(status);
1965}
1966
1967/*
1968%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1969% %
1970% %
1971% %
1972% G a m m a I m a g e %
1973% %
1974% %
1975% %
1976%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1977%
1978% GammaImage() gamma-corrects a particular image channel. The same
1979% image viewed on different devices will have perceptual differences in the
1980% way the image's intensities are represented on the screen. Specify
1981% individual gamma levels for the red, green, and blue channels, or adjust
1982% all three with the gamma parameter. Values typically range from 0.8 to 2.3.
1983%
1984% You can also reduce the influence of a particular channel with a gamma
1985% value of 0.
1986%
1987% The format of the GammaImage method is:
1988%
cristya6360142011-03-23 23:08:04 +00001989% MagickBooleanType GammaImage(Image *image,const char *level)
cristy3ed852e2009-09-05 21:47:34 +00001990% MagickBooleanType GammaImageChannel(Image *image,
1991% const ChannelType channel,const double gamma)
1992%
1993% A description of each parameter follows:
1994%
1995% o image: the image.
1996%
1997% o channel: the channel.
1998%
cristya6360142011-03-23 23:08:04 +00001999% o level: the image gamma as a string (e.g. 1.6,1.2,1.0).
2000%
cristy3ed852e2009-09-05 21:47:34 +00002001% o gamma: the image gamma.
2002%
2003*/
2004MagickExport MagickBooleanType GammaImage(Image *image,const char *level)
2005{
2006 GeometryInfo
2007 geometry_info;
2008
cristy4c08aed2011-07-01 19:47:50 +00002009 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00002010 gamma;
2011
2012 MagickStatusType
2013 flags,
2014 status;
2015
2016 assert(image != (Image *) NULL);
2017 assert(image->signature == MagickSignature);
2018 if (image->debug != MagickFalse)
2019 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2020 if (level == (char *) NULL)
2021 return(MagickFalse);
2022 flags=ParseGeometry(level,&geometry_info);
2023 gamma.red=geometry_info.rho;
2024 gamma.green=geometry_info.sigma;
2025 if ((flags & SigmaValue) == 0)
2026 gamma.green=gamma.red;
2027 gamma.blue=geometry_info.xi;
2028 if ((flags & XiValue) == 0)
2029 gamma.blue=gamma.red;
2030 if ((gamma.red == 1.0) && (gamma.green == 1.0) && (gamma.blue == 1.0))
2031 return(MagickTrue);
2032 if ((gamma.red == gamma.green) && (gamma.green == gamma.blue))
2033 status=GammaImageChannel(image,(const ChannelType) (RedChannel |
2034 GreenChannel | BlueChannel),(double) gamma.red);
2035 else
2036 {
2037 status=GammaImageChannel(image,RedChannel,(double) gamma.red);
2038 status|=GammaImageChannel(image,GreenChannel,(double) gamma.green);
2039 status|=GammaImageChannel(image,BlueChannel,(double) gamma.blue);
2040 }
2041 return(status != 0 ? MagickTrue : MagickFalse);
2042}
2043
2044MagickExport MagickBooleanType GammaImageChannel(Image *image,
2045 const ChannelType channel,const double gamma)
2046{
2047#define GammaCorrectImageTag "GammaCorrect/Image"
2048
cristyc4c8d132010-01-07 01:58:38 +00002049 CacheView
2050 *image_view;
2051
cristy3ed852e2009-09-05 21:47:34 +00002052 ExceptionInfo
2053 *exception;
2054
cristy3ed852e2009-09-05 21:47:34 +00002055 MagickBooleanType
2056 status;
2057
cristybb503372010-05-27 20:51:26 +00002058 MagickOffsetType
2059 progress;
2060
cristy3ed852e2009-09-05 21:47:34 +00002061 Quantum
2062 *gamma_map;
2063
cristybb503372010-05-27 20:51:26 +00002064 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002065 i;
2066
cristybb503372010-05-27 20:51:26 +00002067 ssize_t
2068 y;
2069
cristy3ed852e2009-09-05 21:47:34 +00002070 /*
2071 Allocate and initialize gamma maps.
2072 */
2073 assert(image != (Image *) NULL);
2074 assert(image->signature == MagickSignature);
2075 if (image->debug != MagickFalse)
2076 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2077 if (gamma == 1.0)
2078 return(MagickTrue);
2079 gamma_map=(Quantum *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*gamma_map));
2080 if (gamma_map == (Quantum *) NULL)
2081 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
2082 image->filename);
2083 (void) ResetMagickMemory(gamma_map,0,(MaxMap+1)*sizeof(*gamma_map));
2084 if (gamma != 0.0)
cristyb5d5f722009-11-04 03:03:49 +00002085#if defined(MAGICKCORE_OPENMP_SUPPORT)
2086 #pragma omp parallel for schedule(dynamic,4)
cristy3ed852e2009-09-05 21:47:34 +00002087#endif
cristybb503372010-05-27 20:51:26 +00002088 for (i=0; i <= (ssize_t) MaxMap; i++)
cristyce70c172010-01-07 17:15:30 +00002089 gamma_map[i]=ClampToQuantum((MagickRealType) ScaleMapToQuantum((
cristy3ed852e2009-09-05 21:47:34 +00002090 MagickRealType) (MaxMap*pow((double) i/MaxMap,1.0/gamma))));
2091 if (image->storage_class == PseudoClass)
2092 {
2093 /*
2094 Gamma-correct colormap.
2095 */
cristyb5d5f722009-11-04 03:03:49 +00002096#if defined(MAGICKCORE_OPENMP_SUPPORT)
2097 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002098#endif
cristybb503372010-05-27 20:51:26 +00002099 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00002100 {
cristy2b9582a2011-07-04 17:38:56 +00002101 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00002102 image->colormap[i].red=gamma_map[
2103 ScaleQuantumToMap(image->colormap[i].red)];
cristy2b9582a2011-07-04 17:38:56 +00002104 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00002105 image->colormap[i].green=gamma_map[
2106 ScaleQuantumToMap(image->colormap[i].green)];
cristy2b9582a2011-07-04 17:38:56 +00002107 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00002108 image->colormap[i].blue=gamma_map[
2109 ScaleQuantumToMap(image->colormap[i].blue)];
cristy2b9582a2011-07-04 17:38:56 +00002110 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002111 image->colormap[i].alpha=gamma_map[
2112 ScaleQuantumToMap(image->colormap[i].alpha)];
cristy3ed852e2009-09-05 21:47:34 +00002113 }
2114 }
2115 /*
2116 Gamma-correct image.
2117 */
2118 status=MagickTrue;
2119 progress=0;
2120 exception=(&image->exception);
2121 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00002122#if defined(MAGICKCORE_OPENMP_SUPPORT)
2123 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002124#endif
cristybb503372010-05-27 20:51:26 +00002125 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002126 {
cristy4c08aed2011-07-01 19:47:50 +00002127 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00002128 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002129
cristy8d4629b2010-08-30 17:59:46 +00002130 register ssize_t
2131 x;
2132
cristy3ed852e2009-09-05 21:47:34 +00002133 if (status == MagickFalse)
2134 continue;
2135 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00002136 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00002137 {
2138 status=MagickFalse;
2139 continue;
2140 }
cristybb503372010-05-27 20:51:26 +00002141 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002142 {
cristy6cbd7f52009-10-17 16:06:51 +00002143 if (channel == DefaultChannels)
cristy3ed852e2009-09-05 21:47:34 +00002144 {
cristy4c08aed2011-07-01 19:47:50 +00002145 SetPixelRed(image,gamma_map[
2146 ScaleQuantumToMap(GetPixelRed(image,q))],q);
2147 SetPixelGreen(image,gamma_map[
2148 ScaleQuantumToMap(GetPixelGreen(image,q))],q);
2149 SetPixelBlue(image,gamma_map[ScaleQuantumToMap(
2150 GetPixelBlue(image,q))],q);
cristy6cbd7f52009-10-17 16:06:51 +00002151 }
2152 else
2153 {
cristy2b9582a2011-07-04 17:38:56 +00002154 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002155 SetPixelRed(image,gamma_map[ScaleQuantumToMap(
2156 GetPixelRed(image,q))],q);
cristy2b9582a2011-07-04 17:38:56 +00002157 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002158 SetPixelGreen(image,gamma_map[
2159 ScaleQuantumToMap(GetPixelGreen(image,q))],q);
cristy2b9582a2011-07-04 17:38:56 +00002160 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002161 SetPixelBlue(image,gamma_map[
2162 ScaleQuantumToMap(GetPixelBlue(image,q))],q);
cristy2b9582a2011-07-04 17:38:56 +00002163 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy6cbd7f52009-10-17 16:06:51 +00002164 {
2165 if (image->matte == MagickFalse)
cristy4c08aed2011-07-01 19:47:50 +00002166 SetPixelAlpha(image,gamma_map[
2167 ScaleQuantumToMap(GetPixelAlpha(image,q))],q);
cristy6cbd7f52009-10-17 16:06:51 +00002168 else
cristy4c08aed2011-07-01 19:47:50 +00002169 SetPixelAlpha(image,gamma_map[
2170 ScaleQuantumToMap(GetPixelAlpha(image,q))],q);
cristy6cbd7f52009-10-17 16:06:51 +00002171 }
cristy3ed852e2009-09-05 21:47:34 +00002172 }
cristy4c08aed2011-07-01 19:47:50 +00002173 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00002174 }
cristy2b9582a2011-07-04 17:38:56 +00002175 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00002176 (image->colorspace == CMYKColorspace))
cristybb503372010-05-27 20:51:26 +00002177 for (x=0; x < (ssize_t) image->columns; x++)
cristy4c08aed2011-07-01 19:47:50 +00002178 SetPixelBlack(image,gamma_map[ScaleQuantumToMap(
2179 GetPixelBlack(image,q))],q);
cristy3ed852e2009-09-05 21:47:34 +00002180 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2181 status=MagickFalse;
2182 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2183 {
2184 MagickBooleanType
2185 proceed;
2186
cristyb5d5f722009-11-04 03:03:49 +00002187#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00002188 #pragma omp critical (MagickCore_GammaImageChannel)
2189#endif
2190 proceed=SetImageProgress(image,GammaCorrectImageTag,progress++,
2191 image->rows);
2192 if (proceed == MagickFalse)
2193 status=MagickFalse;
2194 }
2195 }
2196 image_view=DestroyCacheView(image_view);
2197 gamma_map=(Quantum *) RelinquishMagickMemory(gamma_map);
2198 if (image->gamma != 0.0)
2199 image->gamma*=gamma;
2200 return(status);
2201}
2202
2203/*
2204%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2205% %
2206% %
2207% %
2208% H a l d C l u t I m a g e %
2209% %
2210% %
2211% %
2212%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2213%
2214% HaldClutImage() applies a Hald color lookup table to the image. A Hald
2215% color lookup table is a 3-dimensional color cube mapped to 2 dimensions.
2216% Create it with the HALD coder. You can apply any color transformation to
2217% the Hald image and then use this method to apply the transform to the
2218% image.
2219%
2220% The format of the HaldClutImage method is:
2221%
2222% MagickBooleanType HaldClutImage(Image *image,Image *hald_image)
2223% MagickBooleanType HaldClutImageChannel(Image *image,
2224% const ChannelType channel,Image *hald_image)
2225%
2226% A description of each parameter follows:
2227%
2228% o image: the image, which is replaced by indexed CLUT values
2229%
2230% o hald_image: the color lookup table image for replacement color values.
2231%
2232% o channel: the channel.
2233%
2234*/
2235
2236static inline size_t MagickMin(const size_t x,const size_t y)
2237{
2238 if (x < y)
2239 return(x);
2240 return(y);
2241}
2242
2243MagickExport MagickBooleanType HaldClutImage(Image *image,
2244 const Image *hald_image)
2245{
2246 return(HaldClutImageChannel(image,DefaultChannels,hald_image));
2247}
2248
2249MagickExport MagickBooleanType HaldClutImageChannel(Image *image,
2250 const ChannelType channel,const Image *hald_image)
2251{
2252#define HaldClutImageTag "Clut/Image"
2253
2254 typedef struct _HaldInfo
2255 {
2256 MagickRealType
2257 x,
2258 y,
2259 z;
2260 } HaldInfo;
2261
cristyfa112112010-01-04 17:48:07 +00002262 CacheView
cristyd551fbc2011-03-31 18:07:46 +00002263 *hald_view,
cristyfa112112010-01-04 17:48:07 +00002264 *image_view;
2265
cristy3ed852e2009-09-05 21:47:34 +00002266 double
2267 width;
2268
2269 ExceptionInfo
2270 *exception;
2271
cristy3ed852e2009-09-05 21:47:34 +00002272 MagickBooleanType
2273 status;
2274
cristybb503372010-05-27 20:51:26 +00002275 MagickOffsetType
2276 progress;
2277
cristy4c08aed2011-07-01 19:47:50 +00002278 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00002279 zero;
2280
cristy3ed852e2009-09-05 21:47:34 +00002281 size_t
2282 cube_size,
2283 length,
2284 level;
2285
cristybb503372010-05-27 20:51:26 +00002286 ssize_t
2287 y;
2288
cristy3ed852e2009-09-05 21:47:34 +00002289 assert(image != (Image *) NULL);
2290 assert(image->signature == MagickSignature);
2291 if (image->debug != MagickFalse)
2292 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2293 assert(hald_image != (Image *) NULL);
2294 assert(hald_image->signature == MagickSignature);
2295 if (SetImageStorageClass(image,DirectClass) == MagickFalse)
2296 return(MagickFalse);
2297 if (image->matte == MagickFalse)
2298 (void) SetImageAlphaChannel(image,OpaqueAlphaChannel);
2299 /*
2300 Hald clut image.
2301 */
2302 status=MagickTrue;
2303 progress=0;
2304 length=MagickMin(hald_image->columns,hald_image->rows);
2305 for (level=2; (level*level*level) < length; level++) ;
2306 level*=level;
2307 cube_size=level*level;
2308 width=(double) hald_image->columns;
cristy4c08aed2011-07-01 19:47:50 +00002309 GetPixelInfo(hald_image,&zero);
cristy3ed852e2009-09-05 21:47:34 +00002310 exception=(&image->exception);
cristy3ed852e2009-09-05 21:47:34 +00002311 image_view=AcquireCacheView(image);
cristyd551fbc2011-03-31 18:07:46 +00002312 hald_view=AcquireCacheView(hald_image);
cristyb5d5f722009-11-04 03:03:49 +00002313#if defined(MAGICKCORE_OPENMP_SUPPORT)
2314 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002315#endif
cristybb503372010-05-27 20:51:26 +00002316 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002317 {
2318 double
2319 offset;
2320
2321 HaldInfo
2322 point;
2323
cristy4c08aed2011-07-01 19:47:50 +00002324 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00002325 pixel,
2326 pixel1,
2327 pixel2,
2328 pixel3,
2329 pixel4;
2330
cristy4c08aed2011-07-01 19:47:50 +00002331 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00002332 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002333
cristy8d4629b2010-08-30 17:59:46 +00002334 register ssize_t
2335 x;
2336
cristy3ed852e2009-09-05 21:47:34 +00002337 if (status == MagickFalse)
2338 continue;
2339 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00002340 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00002341 {
2342 status=MagickFalse;
2343 continue;
2344 }
cristy3ed852e2009-09-05 21:47:34 +00002345 pixel=zero;
2346 pixel1=zero;
2347 pixel2=zero;
2348 pixel3=zero;
2349 pixel4=zero;
cristybb503372010-05-27 20:51:26 +00002350 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002351 {
cristy4c08aed2011-07-01 19:47:50 +00002352 point.x=QuantumScale*(level-1.0)*GetPixelRed(image,q);
2353 point.y=QuantumScale*(level-1.0)*GetPixelGreen(image,q);
2354 point.z=QuantumScale*(level-1.0)*GetPixelBlue(image,q);
cristy3ed852e2009-09-05 21:47:34 +00002355 offset=point.x+level*floor(point.y)+cube_size*floor(point.z);
2356 point.x-=floor(point.x);
2357 point.y-=floor(point.y);
2358 point.z-=floor(point.z);
cristy4c08aed2011-07-01 19:47:50 +00002359 (void) InterpolatePixelInfo(image,hald_view,
cristy8a7c3e82011-03-26 02:10:53 +00002360 UndefinedInterpolatePixel,fmod(offset,width),floor(offset/width),
2361 &pixel1,exception);
cristy4c08aed2011-07-01 19:47:50 +00002362 (void) InterpolatePixelInfo(image,hald_view,
cristy8a7c3e82011-03-26 02:10:53 +00002363 UndefinedInterpolatePixel,fmod(offset+level,width),floor((offset+level)/
2364 width),&pixel2,exception);
cristy4c08aed2011-07-01 19:47:50 +00002365 CompositePixelInfoAreaBlend(&pixel1,pixel1.alpha,&pixel2,
2366 pixel2.alpha,point.y,&pixel3);
cristy3ed852e2009-09-05 21:47:34 +00002367 offset+=cube_size;
cristy4c08aed2011-07-01 19:47:50 +00002368 (void) InterpolatePixelInfo(image,hald_view,
cristy8a7c3e82011-03-26 02:10:53 +00002369 UndefinedInterpolatePixel,fmod(offset,width),floor(offset/width),
2370 &pixel1,exception);
cristy4c08aed2011-07-01 19:47:50 +00002371 (void) InterpolatePixelInfo(image,hald_view,
cristy8a7c3e82011-03-26 02:10:53 +00002372 UndefinedInterpolatePixel,fmod(offset+level,width),floor((offset+level)/
2373 width),&pixel2,exception);
cristy4c08aed2011-07-01 19:47:50 +00002374 CompositePixelInfoAreaBlend(&pixel1,pixel1.alpha,&pixel2,
2375 pixel2.alpha,point.y,&pixel4);
2376 CompositePixelInfoAreaBlend(&pixel3,pixel3.alpha,&pixel4,
2377 pixel4.alpha,point.z,&pixel);
cristy2b9582a2011-07-04 17:38:56 +00002378 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002379 SetPixelRed(image,
2380 ClampToQuantum(pixel.red),q);
cristy2b9582a2011-07-04 17:38:56 +00002381 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002382 SetPixelGreen(image,
2383 ClampToQuantum(pixel.green),q);
cristy2b9582a2011-07-04 17:38:56 +00002384 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002385 SetPixelBlue(image,
2386 ClampToQuantum(pixel.blue),q);
cristy2b9582a2011-07-04 17:38:56 +00002387 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00002388 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +00002389 SetPixelBlack(image,
2390 ClampToQuantum(pixel.black),q);
cristy2b9582a2011-07-04 17:38:56 +00002391 if (((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0) && (image->matte != MagickFalse))
cristy4c08aed2011-07-01 19:47:50 +00002392 SetPixelAlpha(image,
2393 ClampToQuantum(pixel.alpha),q);
2394 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00002395 }
2396 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2397 status=MagickFalse;
2398 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2399 {
2400 MagickBooleanType
2401 proceed;
2402
cristyb5d5f722009-11-04 03:03:49 +00002403#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00002404 #pragma omp critical (MagickCore_HaldClutImageChannel)
2405#endif
2406 proceed=SetImageProgress(image,HaldClutImageTag,progress++,image->rows);
2407 if (proceed == MagickFalse)
2408 status=MagickFalse;
2409 }
2410 }
cristyd551fbc2011-03-31 18:07:46 +00002411 hald_view=DestroyCacheView(hald_view);
cristy3ed852e2009-09-05 21:47:34 +00002412 image_view=DestroyCacheView(image_view);
cristy3ed852e2009-09-05 21:47:34 +00002413 return(status);
2414}
2415
2416/*
2417%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2418% %
2419% %
2420% %
2421% L e v e l I m a g e %
2422% %
2423% %
2424% %
2425%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2426%
2427% LevelImage() adjusts the levels of a particular image channel by
2428% scaling the colors falling between specified white and black points to
2429% the full available quantum range.
2430%
2431% The parameters provided represent the black, and white points. The black
2432% point specifies the darkest color in the image. Colors darker than the
2433% black point are set to zero. White point specifies the lightest color in
2434% the image. Colors brighter than the white point are set to the maximum
2435% quantum value.
2436%
2437% If a '!' flag is given, map black and white colors to the given levels
2438% rather than mapping those levels to black and white. See
2439% LevelizeImageChannel() and LevelizeImageChannel(), below.
2440%
2441% Gamma specifies a gamma correction to apply to the image.
2442%
2443% The format of the LevelImage method is:
2444%
2445% MagickBooleanType LevelImage(Image *image,const char *levels)
2446%
2447% A description of each parameter follows:
2448%
2449% o image: the image.
2450%
2451% o levels: Specify the levels where the black and white points have the
2452% range of 0-QuantumRange, and gamma has the range 0-10 (e.g. 10x90%+2).
2453% A '!' flag inverts the re-mapping.
2454%
2455*/
2456
2457MagickExport MagickBooleanType LevelImage(Image *image,const char *levels)
2458{
2459 double
2460 black_point,
2461 gamma,
2462 white_point;
2463
2464 GeometryInfo
2465 geometry_info;
2466
2467 MagickBooleanType
2468 status;
2469
2470 MagickStatusType
2471 flags;
2472
2473 /*
2474 Parse levels.
2475 */
2476 if (levels == (char *) NULL)
2477 return(MagickFalse);
2478 flags=ParseGeometry(levels,&geometry_info);
2479 black_point=geometry_info.rho;
2480 white_point=(double) QuantumRange;
2481 if ((flags & SigmaValue) != 0)
2482 white_point=geometry_info.sigma;
2483 gamma=1.0;
2484 if ((flags & XiValue) != 0)
2485 gamma=geometry_info.xi;
2486 if ((flags & PercentValue) != 0)
2487 {
2488 black_point*=(double) image->columns*image->rows/100.0;
2489 white_point*=(double) image->columns*image->rows/100.0;
2490 }
2491 if ((flags & SigmaValue) == 0)
2492 white_point=(double) QuantumRange-black_point;
2493 if ((flags & AspectValue ) == 0)
2494 status=LevelImageChannel(image,DefaultChannels,black_point,white_point,
2495 gamma);
2496 else
cristy308b4e62009-09-21 14:40:44 +00002497 status=LevelizeImage(image,black_point,white_point,gamma);
cristy3ed852e2009-09-05 21:47:34 +00002498 return(status);
2499}
2500
2501/*
2502%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2503% %
2504% %
2505% %
cristy308b4e62009-09-21 14:40:44 +00002506% L e v e l i z e I m a g e %
cristy3ed852e2009-09-05 21:47:34 +00002507% %
2508% %
2509% %
2510%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2511%
cristy308b4e62009-09-21 14:40:44 +00002512% LevelizeImage() applies the normal level operation to the image, spreading
2513% out the values between the black and white points over the entire range of
2514% values. Gamma correction is also applied after the values has been mapped.
cristy3ed852e2009-09-05 21:47:34 +00002515%
2516% It is typically used to improve image contrast, or to provide a controlled
2517% linear threshold for the image. If the black and white points are set to
2518% the minimum and maximum values found in the image, the image can be
2519% normalized. or by swapping black and white values, negate the image.
2520%
cristy308b4e62009-09-21 14:40:44 +00002521% The format of the LevelizeImage method is:
cristy3ed852e2009-09-05 21:47:34 +00002522%
cristy308b4e62009-09-21 14:40:44 +00002523% MagickBooleanType LevelizeImage(Image *image,const double black_point,
2524% const double white_point,const double gamma)
2525% MagickBooleanType LevelizeImageChannel(Image *image,
2526% const ChannelType channel,const double black_point,
2527% const double white_point,const double gamma)
cristy3ed852e2009-09-05 21:47:34 +00002528%
2529% A description of each parameter follows:
2530%
2531% o image: the image.
2532%
2533% o channel: the channel.
2534%
2535% o black_point: The level which is to be mapped to zero (black)
2536%
2537% o white_point: The level which is to be mapped to QuantiumRange (white)
2538%
2539% o gamma: adjust gamma by this factor before mapping values.
2540% use 1.0 for purely linear stretching of image color values
2541%
2542*/
2543MagickExport MagickBooleanType LevelImageChannel(Image *image,
2544 const ChannelType channel,const double black_point,const double white_point,
2545 const double gamma)
2546{
2547#define LevelImageTag "Level/Image"
cristybcfb0432010-05-06 01:45:33 +00002548#define LevelQuantum(x) (ClampToQuantum((MagickRealType) QuantumRange* \
cristyc1f508d2010-04-22 01:21:47 +00002549 pow(scale*((double) (x)-black_point),1.0/gamma)))
cristy3ed852e2009-09-05 21:47:34 +00002550
cristyc4c8d132010-01-07 01:58:38 +00002551 CacheView
2552 *image_view;
2553
cristy3ed852e2009-09-05 21:47:34 +00002554 ExceptionInfo
2555 *exception;
2556
cristy3ed852e2009-09-05 21:47:34 +00002557 MagickBooleanType
2558 status;
2559
cristybb503372010-05-27 20:51:26 +00002560 MagickOffsetType
2561 progress;
2562
anthony7fe39fc2010-04-06 03:19:20 +00002563 register double
2564 scale;
2565
cristy8d4629b2010-08-30 17:59:46 +00002566 register ssize_t
2567 i;
2568
cristybb503372010-05-27 20:51:26 +00002569 ssize_t
2570 y;
2571
cristy3ed852e2009-09-05 21:47:34 +00002572 /*
2573 Allocate and initialize levels map.
2574 */
2575 assert(image != (Image *) NULL);
2576 assert(image->signature == MagickSignature);
2577 if (image->debug != MagickFalse)
2578 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
cristy8d4629b2010-08-30 17:59:46 +00002579 scale=(white_point != black_point) ? 1.0/(white_point-black_point) : 1.0;
cristy3ed852e2009-09-05 21:47:34 +00002580 if (image->storage_class == PseudoClass)
cristyb5d5f722009-11-04 03:03:49 +00002581#if defined(MAGICKCORE_OPENMP_SUPPORT)
2582 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002583#endif
cristybb503372010-05-27 20:51:26 +00002584 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00002585 {
2586 /*
2587 Level colormap.
2588 */
cristy2b9582a2011-07-04 17:38:56 +00002589 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristyc1f508d2010-04-22 01:21:47 +00002590 image->colormap[i].red=LevelQuantum(image->colormap[i].red);
cristy2b9582a2011-07-04 17:38:56 +00002591 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristyc1f508d2010-04-22 01:21:47 +00002592 image->colormap[i].green=LevelQuantum(image->colormap[i].green);
cristy2b9582a2011-07-04 17:38:56 +00002593 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristyc1f508d2010-04-22 01:21:47 +00002594 image->colormap[i].blue=LevelQuantum(image->colormap[i].blue);
cristy2b9582a2011-07-04 17:38:56 +00002595 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002596 image->colormap[i].alpha=LevelQuantum(image->colormap[i].alpha);
cristy3ed852e2009-09-05 21:47:34 +00002597 }
2598 /*
2599 Level image.
2600 */
2601 status=MagickTrue;
2602 progress=0;
2603 exception=(&image->exception);
2604 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00002605#if defined(MAGICKCORE_OPENMP_SUPPORT)
2606 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002607#endif
cristybb503372010-05-27 20:51:26 +00002608 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002609 {
cristy4c08aed2011-07-01 19:47:50 +00002610 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00002611 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002612
cristy8d4629b2010-08-30 17:59:46 +00002613 register ssize_t
2614 x;
2615
cristy3ed852e2009-09-05 21:47:34 +00002616 if (status == MagickFalse)
2617 continue;
2618 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00002619 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00002620 {
2621 status=MagickFalse;
2622 continue;
2623 }
cristybb503372010-05-27 20:51:26 +00002624 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002625 {
cristy2b9582a2011-07-04 17:38:56 +00002626 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002627 SetPixelRed(image,LevelQuantum(
2628 GetPixelRed(image,q)),q);
cristy2b9582a2011-07-04 17:38:56 +00002629 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002630 SetPixelGreen(image,
2631 LevelQuantum(GetPixelGreen(image,q)),q);
cristy2b9582a2011-07-04 17:38:56 +00002632 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002633 SetPixelBlue(image,
2634 LevelQuantum(GetPixelBlue(image,q)),q);
cristy2b9582a2011-07-04 17:38:56 +00002635 if (((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00002636 (image->matte == MagickTrue))
cristy4c08aed2011-07-01 19:47:50 +00002637 SetPixelAlpha(image,
2638 LevelQuantum(GetPixelAlpha(image,q)),q);
cristy2b9582a2011-07-04 17:38:56 +00002639 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00002640 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +00002641 SetPixelBlack(image,
2642 LevelQuantum(GetPixelBlack(image,q)),q);
2643 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00002644 }
2645 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2646 status=MagickFalse;
2647 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2648 {
2649 MagickBooleanType
2650 proceed;
2651
cristyb5d5f722009-11-04 03:03:49 +00002652#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00002653 #pragma omp critical (MagickCore_LevelImageChannel)
2654#endif
2655 proceed=SetImageProgress(image,LevelImageTag,progress++,image->rows);
2656 if (proceed == MagickFalse)
2657 status=MagickFalse;
2658 }
2659 }
2660 image_view=DestroyCacheView(image_view);
2661 return(status);
2662}
2663
2664/*
2665%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2666% %
2667% %
2668% %
2669% L e v e l i z e I m a g e C h a n n e l %
2670% %
2671% %
2672% %
2673%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2674%
2675% LevelizeImageChannel() applies the reversed LevelImage() operation to just
2676% the specific channels specified. It compresses the full range of color
2677% values, so that they lie between the given black and white points. Gamma is
2678% applied before the values are mapped.
2679%
2680% LevelizeImageChannel() can be called with by using a +level command line
2681% API option, or using a '!' on a -level or LevelImage() geometry string.
2682%
2683% It can be used for example de-contrast a greyscale image to the exact
2684% levels specified. Or by using specific levels for each channel of an image
2685% you can convert a gray-scale image to any linear color gradient, according
2686% to those levels.
2687%
2688% The format of the LevelizeImageChannel method is:
2689%
2690% MagickBooleanType LevelizeImageChannel(Image *image,
2691% const ChannelType channel,const char *levels)
2692%
2693% A description of each parameter follows:
2694%
2695% o image: the image.
2696%
2697% o channel: the channel.
2698%
2699% o black_point: The level to map zero (black) to.
2700%
2701% o white_point: The level to map QuantiumRange (white) to.
2702%
2703% o gamma: adjust gamma by this factor before mapping values.
2704%
2705*/
cristyd1a2c0f2011-02-09 14:14:50 +00002706
2707MagickExport MagickBooleanType LevelizeImage(Image *image,
2708 const double black_point,const double white_point,const double gamma)
2709{
2710 MagickBooleanType
2711 status;
2712
2713 status=LevelizeImageChannel(image,DefaultChannels,black_point,white_point,
2714 gamma);
2715 return(status);
2716}
2717
cristy3ed852e2009-09-05 21:47:34 +00002718MagickExport MagickBooleanType LevelizeImageChannel(Image *image,
2719 const ChannelType channel,const double black_point,const double white_point,
2720 const double gamma)
2721{
2722#define LevelizeImageTag "Levelize/Image"
cristyce70c172010-01-07 17:15:30 +00002723#define LevelizeValue(x) (ClampToQuantum(((MagickRealType) \
cristy3ed852e2009-09-05 21:47:34 +00002724 pow((double)(QuantumScale*(x)),1.0/gamma))*(white_point-black_point)+ \
2725 black_point))
2726
cristyc4c8d132010-01-07 01:58:38 +00002727 CacheView
2728 *image_view;
2729
cristy3ed852e2009-09-05 21:47:34 +00002730 ExceptionInfo
2731 *exception;
2732
cristy3ed852e2009-09-05 21:47:34 +00002733 MagickBooleanType
2734 status;
2735
cristybb503372010-05-27 20:51:26 +00002736 MagickOffsetType
2737 progress;
2738
2739 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002740 i;
2741
cristybb503372010-05-27 20:51:26 +00002742 ssize_t
2743 y;
2744
cristy3ed852e2009-09-05 21:47:34 +00002745 /*
2746 Allocate and initialize levels map.
2747 */
2748 assert(image != (Image *) NULL);
2749 assert(image->signature == MagickSignature);
2750 if (image->debug != MagickFalse)
2751 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2752 if (image->storage_class == PseudoClass)
cristyb5d5f722009-11-04 03:03:49 +00002753#if defined(MAGICKCORE_OPENMP_SUPPORT)
2754 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002755#endif
cristybb503372010-05-27 20:51:26 +00002756 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00002757 {
2758 /*
2759 Level colormap.
2760 */
cristy2b9582a2011-07-04 17:38:56 +00002761 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00002762 image->colormap[i].red=LevelizeValue(image->colormap[i].red);
cristy2b9582a2011-07-04 17:38:56 +00002763 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00002764 image->colormap[i].green=LevelizeValue(image->colormap[i].green);
cristy2b9582a2011-07-04 17:38:56 +00002765 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00002766 image->colormap[i].blue=LevelizeValue(image->colormap[i].blue);
cristy2b9582a2011-07-04 17:38:56 +00002767 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002768 image->colormap[i].alpha=LevelizeValue(image->colormap[i].alpha);
cristy3ed852e2009-09-05 21:47:34 +00002769 }
2770 /*
2771 Level image.
2772 */
2773 status=MagickTrue;
2774 progress=0;
2775 exception=(&image->exception);
2776 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00002777#if defined(MAGICKCORE_OPENMP_SUPPORT)
2778 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002779#endif
cristybb503372010-05-27 20:51:26 +00002780 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002781 {
cristy4c08aed2011-07-01 19:47:50 +00002782 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00002783 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002784
cristy8d4629b2010-08-30 17:59:46 +00002785 register ssize_t
2786 x;
2787
cristy3ed852e2009-09-05 21:47:34 +00002788 if (status == MagickFalse)
2789 continue;
2790 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00002791 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00002792 {
2793 status=MagickFalse;
2794 continue;
2795 }
cristybb503372010-05-27 20:51:26 +00002796 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002797 {
cristy2b9582a2011-07-04 17:38:56 +00002798 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002799 SetPixelRed(image,LevelizeValue(GetPixelRed(image,q)),q);
cristy2b9582a2011-07-04 17:38:56 +00002800 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002801 SetPixelGreen(image,LevelizeValue(GetPixelGreen(image,q)),q);
cristy2b9582a2011-07-04 17:38:56 +00002802 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002803 SetPixelBlue(image,LevelizeValue(GetPixelBlue(image,q)),q);
cristy2b9582a2011-07-04 17:38:56 +00002804 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +00002805 (image->colorspace == CMYKColorspace))
2806 SetPixelBlack(image,LevelizeValue(GetPixelBlack(image,q)),q);
cristy2b9582a2011-07-04 17:38:56 +00002807 if (((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00002808 (image->matte == MagickTrue))
cristy4c08aed2011-07-01 19:47:50 +00002809 SetPixelAlpha(image,LevelizeValue(GetPixelAlpha(image,q)),q);
2810 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00002811 }
2812 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2813 status=MagickFalse;
2814 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2815 {
2816 MagickBooleanType
2817 proceed;
2818
cristyb5d5f722009-11-04 03:03:49 +00002819#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00002820 #pragma omp critical (MagickCore_LevelizeImageChannel)
2821#endif
2822 proceed=SetImageProgress(image,LevelizeImageTag,progress++,image->rows);
2823 if (proceed == MagickFalse)
2824 status=MagickFalse;
2825 }
2826 }
cristy8d4629b2010-08-30 17:59:46 +00002827 image_view=DestroyCacheView(image_view);
cristy3ed852e2009-09-05 21:47:34 +00002828 return(status);
2829}
2830
2831/*
2832%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2833% %
2834% %
2835% %
2836% L e v e l I m a g e C o l o r s %
2837% %
2838% %
2839% %
2840%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2841%
cristyee0f8d72009-09-19 00:58:29 +00002842% LevelImageColor() maps the given color to "black" and "white" values,
2843% linearly spreading out the colors, and level values on a channel by channel
2844% bases, as per LevelImage(). The given colors allows you to specify
glennrp1e7f7bc2011-03-02 19:25:28 +00002845% different level ranges for each of the color channels separately.
cristy3ed852e2009-09-05 21:47:34 +00002846%
2847% If the boolean 'invert' is set true the image values will modifyed in the
2848% reverse direction. That is any existing "black" and "white" colors in the
2849% image will become the color values given, with all other values compressed
2850% appropriatally. This effectivally maps a greyscale gradient into the given
2851% color gradient.
2852%
cristy308b4e62009-09-21 14:40:44 +00002853% The format of the LevelColorsImageChannel method is:
cristy3ed852e2009-09-05 21:47:34 +00002854%
cristy308b4e62009-09-21 14:40:44 +00002855% MagickBooleanType LevelColorsImage(Image *image,
cristy4c08aed2011-07-01 19:47:50 +00002856% const PixelInfo *black_color,
2857% const PixelInfo *white_color,const MagickBooleanType invert)
cristy308b4e62009-09-21 14:40:44 +00002858% MagickBooleanType LevelColorsImageChannel(Image *image,
cristy4c08aed2011-07-01 19:47:50 +00002859% const ChannelType channel,const PixelInfo *black_color,
2860% const PixelInfo *white_color,const MagickBooleanType invert)
cristy3ed852e2009-09-05 21:47:34 +00002861%
2862% A description of each parameter follows:
2863%
2864% o image: the image.
2865%
2866% o channel: the channel.
2867%
2868% o black_color: The color to map black to/from
2869%
2870% o white_point: The color to map white to/from
2871%
2872% o invert: if true map the colors (levelize), rather than from (level)
2873%
2874*/
cristy308b4e62009-09-21 14:40:44 +00002875
2876MagickExport MagickBooleanType LevelColorsImage(Image *image,
cristy4c08aed2011-07-01 19:47:50 +00002877 const PixelInfo *black_color,const PixelInfo *white_color,
cristy3ed852e2009-09-05 21:47:34 +00002878 const MagickBooleanType invert)
2879{
cristy308b4e62009-09-21 14:40:44 +00002880 MagickBooleanType
2881 status;
cristy3ed852e2009-09-05 21:47:34 +00002882
cristy308b4e62009-09-21 14:40:44 +00002883 status=LevelColorsImageChannel(image,DefaultChannels,black_color,white_color,
2884 invert);
2885 return(status);
2886}
2887
2888MagickExport 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)
cristy308b4e62009-09-21 14:40:44 +00002891{
cristy3ed852e2009-09-05 21:47:34 +00002892 MagickStatusType
2893 status;
2894
2895 /*
2896 Allocate and initialize levels map.
2897 */
2898 assert(image != (Image *) NULL);
2899 assert(image->signature == MagickSignature);
2900 if (image->debug != MagickFalse)
2901 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2902 status=MagickFalse;
2903 if (invert == MagickFalse)
2904 {
cristy2b9582a2011-07-04 17:38:56 +00002905 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00002906 status|=LevelImageChannel(image,RedChannel,
2907 black_color->red,white_color->red,(double) 1.0);
cristy2b9582a2011-07-04 17:38:56 +00002908 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00002909 status|=LevelImageChannel(image,GreenChannel,
2910 black_color->green,white_color->green,(double) 1.0);
cristy2b9582a2011-07-04 17:38:56 +00002911 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00002912 status|=LevelImageChannel(image,BlueChannel,
2913 black_color->blue,white_color->blue,(double) 1.0);
cristy2b9582a2011-07-04 17:38:56 +00002914 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +00002915 (image->colorspace == CMYKColorspace))
2916 status|=LevelImageChannel(image,BlackChannel,
2917 black_color->black,white_color->black,(double) 1.0);
cristy2b9582a2011-07-04 17:38:56 +00002918 if (((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00002919 (image->matte == MagickTrue))
2920 status|=LevelImageChannel(image,OpacityChannel,
cristy4c08aed2011-07-01 19:47:50 +00002921 black_color->alpha,white_color->alpha,(double) 1.0);
cristy3ed852e2009-09-05 21:47:34 +00002922 }
2923 else
2924 {
cristy2b9582a2011-07-04 17:38:56 +00002925 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00002926 status|=LevelizeImageChannel(image,RedChannel,
2927 black_color->red,white_color->red,(double) 1.0);
cristy2b9582a2011-07-04 17:38:56 +00002928 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00002929 status|=LevelizeImageChannel(image,GreenChannel,
2930 black_color->green,white_color->green,(double) 1.0);
cristy2b9582a2011-07-04 17:38:56 +00002931 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00002932 status|=LevelizeImageChannel(image,BlueChannel,
2933 black_color->blue,white_color->blue,(double) 1.0);
cristy2b9582a2011-07-04 17:38:56 +00002934 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +00002935 (image->colorspace == CMYKColorspace))
2936 status|=LevelizeImageChannel(image,BlackChannel,
2937 black_color->black,white_color->black,(double) 1.0);
cristy2b9582a2011-07-04 17:38:56 +00002938 if (((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00002939 (image->matte == MagickTrue))
2940 status|=LevelizeImageChannel(image,OpacityChannel,
cristy4c08aed2011-07-01 19:47:50 +00002941 black_color->alpha,white_color->alpha,(double) 1.0);
cristy3ed852e2009-09-05 21:47:34 +00002942 }
2943 return(status == 0 ? MagickFalse : MagickTrue);
2944}
2945
2946/*
2947%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2948% %
2949% %
2950% %
2951% L i n e a r S t r e t c h I m a g e %
2952% %
2953% %
2954% %
2955%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2956%
2957% The LinearStretchImage() discards any pixels below the black point and
2958% above the white point and levels the remaining pixels.
2959%
2960% The format of the LinearStretchImage method is:
2961%
2962% MagickBooleanType LinearStretchImage(Image *image,
2963% const double black_point,const double white_point)
2964%
2965% A description of each parameter follows:
2966%
2967% o image: the image.
2968%
2969% o black_point: the black point.
2970%
2971% o white_point: the white point.
2972%
2973*/
2974MagickExport MagickBooleanType LinearStretchImage(Image *image,
2975 const double black_point,const double white_point)
2976{
2977#define LinearStretchImageTag "LinearStretch/Image"
2978
2979 ExceptionInfo
2980 *exception;
2981
cristy3ed852e2009-09-05 21:47:34 +00002982 MagickBooleanType
2983 status;
2984
2985 MagickRealType
2986 *histogram,
2987 intensity;
2988
cristy8d4629b2010-08-30 17:59:46 +00002989 ssize_t
2990 black,
2991 white,
2992 y;
2993
cristy3ed852e2009-09-05 21:47:34 +00002994 /*
2995 Allocate histogram and linear map.
2996 */
2997 assert(image != (Image *) NULL);
2998 assert(image->signature == MagickSignature);
2999 histogram=(MagickRealType *) AcquireQuantumMemory(MaxMap+1UL,
3000 sizeof(*histogram));
3001 if (histogram == (MagickRealType *) NULL)
3002 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
3003 image->filename);
3004 /*
3005 Form histogram.
3006 */
3007 (void) ResetMagickMemory(histogram,0,(MaxMap+1)*sizeof(*histogram));
3008 exception=(&image->exception);
cristybb503372010-05-27 20:51:26 +00003009 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003010 {
cristy4c08aed2011-07-01 19:47:50 +00003011 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00003012 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00003013
cristybb503372010-05-27 20:51:26 +00003014 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003015 x;
3016
3017 p=GetVirtualPixels(image,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00003018 if (p == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00003019 break;
cristybb503372010-05-27 20:51:26 +00003020 for (x=(ssize_t) image->columns-1; x >= 0; x--)
cristy3ed852e2009-09-05 21:47:34 +00003021 {
cristy4c08aed2011-07-01 19:47:50 +00003022 histogram[ScaleQuantumToMap(GetPixelIntensity(image,p))]++;
3023 p+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00003024 }
3025 }
3026 /*
3027 Find the histogram boundaries by locating the black and white point levels.
3028 */
cristy3ed852e2009-09-05 21:47:34 +00003029 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00003030 for (black=0; black < (ssize_t) MaxMap; black++)
cristy3ed852e2009-09-05 21:47:34 +00003031 {
3032 intensity+=histogram[black];
3033 if (intensity >= black_point)
3034 break;
3035 }
3036 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00003037 for (white=(ssize_t) MaxMap; white != 0; white--)
cristy3ed852e2009-09-05 21:47:34 +00003038 {
3039 intensity+=histogram[white];
3040 if (intensity >= white_point)
3041 break;
3042 }
3043 histogram=(MagickRealType *) RelinquishMagickMemory(histogram);
3044 status=LevelImageChannel(image,DefaultChannels,(double) black,(double) white,
3045 1.0);
3046 return(status);
3047}
3048
3049/*
3050%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3051% %
3052% %
3053% %
3054% M o d u l a t e I m a g e %
3055% %
3056% %
3057% %
3058%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3059%
3060% ModulateImage() lets you control the brightness, saturation, and hue
3061% of an image. Modulate represents the brightness, saturation, and hue
3062% as one parameter (e.g. 90,150,100). If the image colorspace is HSL, the
3063% modulation is lightness, saturation, and hue. And if the colorspace is
3064% HWB, use blackness, whiteness, and hue.
3065%
3066% The format of the ModulateImage method is:
3067%
3068% MagickBooleanType ModulateImage(Image *image,const char *modulate)
3069%
3070% A description of each parameter follows:
3071%
3072% o image: the image.
3073%
3074% o modulate: Define the percent change in brightness, saturation, and
3075% hue.
3076%
3077*/
3078
3079static void ModulateHSB(const double percent_hue,
3080 const double percent_saturation,const double percent_brightness,
3081 Quantum *red,Quantum *green,Quantum *blue)
3082{
3083 double
3084 brightness,
3085 hue,
3086 saturation;
3087
3088 /*
3089 Increase or decrease color brightness, saturation, or hue.
3090 */
3091 assert(red != (Quantum *) NULL);
3092 assert(green != (Quantum *) NULL);
3093 assert(blue != (Quantum *) NULL);
3094 ConvertRGBToHSB(*red,*green,*blue,&hue,&saturation,&brightness);
3095 hue+=0.5*(0.01*percent_hue-1.0);
3096 while (hue < 0.0)
3097 hue+=1.0;
3098 while (hue > 1.0)
3099 hue-=1.0;
3100 saturation*=0.01*percent_saturation;
3101 brightness*=0.01*percent_brightness;
3102 ConvertHSBToRGB(hue,saturation,brightness,red,green,blue);
3103}
3104
3105static void ModulateHSL(const double percent_hue,
3106 const double percent_saturation,const double percent_lightness,
3107 Quantum *red,Quantum *green,Quantum *blue)
3108{
3109 double
3110 hue,
3111 lightness,
3112 saturation;
3113
3114 /*
3115 Increase or decrease color lightness, saturation, or hue.
3116 */
3117 assert(red != (Quantum *) NULL);
3118 assert(green != (Quantum *) NULL);
3119 assert(blue != (Quantum *) NULL);
3120 ConvertRGBToHSL(*red,*green,*blue,&hue,&saturation,&lightness);
3121 hue+=0.5*(0.01*percent_hue-1.0);
3122 while (hue < 0.0)
3123 hue+=1.0;
3124 while (hue > 1.0)
3125 hue-=1.0;
3126 saturation*=0.01*percent_saturation;
3127 lightness*=0.01*percent_lightness;
3128 ConvertHSLToRGB(hue,saturation,lightness,red,green,blue);
3129}
3130
3131static void ModulateHWB(const double percent_hue,const double percent_whiteness, const double percent_blackness,Quantum *red,Quantum *green,Quantum *blue)
3132{
3133 double
3134 blackness,
3135 hue,
3136 whiteness;
3137
3138 /*
3139 Increase or decrease color blackness, whiteness, or hue.
3140 */
3141 assert(red != (Quantum *) NULL);
3142 assert(green != (Quantum *) NULL);
3143 assert(blue != (Quantum *) NULL);
3144 ConvertRGBToHWB(*red,*green,*blue,&hue,&whiteness,&blackness);
3145 hue+=0.5*(0.01*percent_hue-1.0);
3146 while (hue < 0.0)
3147 hue+=1.0;
3148 while (hue > 1.0)
3149 hue-=1.0;
3150 blackness*=0.01*percent_blackness;
3151 whiteness*=0.01*percent_whiteness;
3152 ConvertHWBToRGB(hue,whiteness,blackness,red,green,blue);
3153}
3154
3155MagickExport MagickBooleanType ModulateImage(Image *image,const char *modulate)
3156{
3157#define ModulateImageTag "Modulate/Image"
3158
cristyc4c8d132010-01-07 01:58:38 +00003159 CacheView
3160 *image_view;
3161
cristy3ed852e2009-09-05 21:47:34 +00003162 ColorspaceType
3163 colorspace;
3164
3165 const char
3166 *artifact;
3167
3168 double
3169 percent_brightness,
3170 percent_hue,
3171 percent_saturation;
3172
3173 ExceptionInfo
3174 *exception;
3175
3176 GeometryInfo
3177 geometry_info;
3178
cristy3ed852e2009-09-05 21:47:34 +00003179 MagickBooleanType
3180 status;
3181
cristybb503372010-05-27 20:51:26 +00003182 MagickOffsetType
3183 progress;
3184
cristy3ed852e2009-09-05 21:47:34 +00003185 MagickStatusType
3186 flags;
3187
cristybb503372010-05-27 20:51:26 +00003188 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003189 i;
3190
cristybb503372010-05-27 20:51:26 +00003191 ssize_t
3192 y;
3193
cristy3ed852e2009-09-05 21:47:34 +00003194 /*
cristy2b726bd2010-01-11 01:05:39 +00003195 Initialize modulate table.
cristy3ed852e2009-09-05 21:47:34 +00003196 */
3197 assert(image != (Image *) NULL);
3198 assert(image->signature == MagickSignature);
3199 if (image->debug != MagickFalse)
3200 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3201 if (modulate == (char *) NULL)
3202 return(MagickFalse);
3203 flags=ParseGeometry(modulate,&geometry_info);
3204 percent_brightness=geometry_info.rho;
3205 percent_saturation=geometry_info.sigma;
3206 if ((flags & SigmaValue) == 0)
3207 percent_saturation=100.0;
3208 percent_hue=geometry_info.xi;
3209 if ((flags & XiValue) == 0)
3210 percent_hue=100.0;
3211 colorspace=UndefinedColorspace;
3212 artifact=GetImageArtifact(image,"modulate:colorspace");
3213 if (artifact != (const char *) NULL)
cristy042ee782011-04-22 18:48:30 +00003214 colorspace=(ColorspaceType) ParseCommandOption(MagickColorspaceOptions,
cristy3ed852e2009-09-05 21:47:34 +00003215 MagickFalse,artifact);
3216 if (image->storage_class == PseudoClass)
3217 {
3218 /*
3219 Modulate colormap.
3220 */
cristyb5d5f722009-11-04 03:03:49 +00003221#if defined(MAGICKCORE_OPENMP_SUPPORT)
3222 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003223#endif
cristybb503372010-05-27 20:51:26 +00003224 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00003225 switch (colorspace)
3226 {
3227 case HSBColorspace:
3228 {
3229 ModulateHSB(percent_hue,percent_saturation,percent_brightness,
3230 &image->colormap[i].red,&image->colormap[i].green,
3231 &image->colormap[i].blue);
3232 break;
3233 }
3234 case HSLColorspace:
3235 default:
3236 {
3237 ModulateHSL(percent_hue,percent_saturation,percent_brightness,
3238 &image->colormap[i].red,&image->colormap[i].green,
3239 &image->colormap[i].blue);
3240 break;
3241 }
3242 case HWBColorspace:
3243 {
3244 ModulateHWB(percent_hue,percent_saturation,percent_brightness,
3245 &image->colormap[i].red,&image->colormap[i].green,
3246 &image->colormap[i].blue);
3247 break;
3248 }
3249 }
3250 }
3251 /*
3252 Modulate image.
3253 */
3254 status=MagickTrue;
3255 progress=0;
3256 exception=(&image->exception);
3257 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00003258#if defined(MAGICKCORE_OPENMP_SUPPORT)
3259 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003260#endif
cristybb503372010-05-27 20:51:26 +00003261 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003262 {
cristy5afeab82011-04-30 01:30:09 +00003263 Quantum
3264 blue,
3265 green,
3266 red;
3267
cristy4c08aed2011-07-01 19:47:50 +00003268 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00003269 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003270
cristy8d4629b2010-08-30 17:59:46 +00003271 register ssize_t
3272 x;
3273
cristy3ed852e2009-09-05 21:47:34 +00003274 if (status == MagickFalse)
3275 continue;
3276 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00003277 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00003278 {
3279 status=MagickFalse;
3280 continue;
3281 }
cristybb503372010-05-27 20:51:26 +00003282 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003283 {
cristy4c08aed2011-07-01 19:47:50 +00003284 red=GetPixelRed(image,q);
3285 green=GetPixelGreen(image,q);
3286 blue=GetPixelBlue(image,q);
cristy3ed852e2009-09-05 21:47:34 +00003287 switch (colorspace)
3288 {
3289 case HSBColorspace:
3290 {
3291 ModulateHSB(percent_hue,percent_saturation,percent_brightness,
cristy5afeab82011-04-30 01:30:09 +00003292 &red,&green,&blue);
cristy3ed852e2009-09-05 21:47:34 +00003293 break;
3294 }
3295 case HSLColorspace:
3296 default:
3297 {
3298 ModulateHSL(percent_hue,percent_saturation,percent_brightness,
cristy5afeab82011-04-30 01:30:09 +00003299 &red,&green,&blue);
cristy3ed852e2009-09-05 21:47:34 +00003300 break;
3301 }
3302 case HWBColorspace:
3303 {
3304 ModulateHWB(percent_hue,percent_saturation,percent_brightness,
cristy5afeab82011-04-30 01:30:09 +00003305 &red,&green,&blue);
cristy3ed852e2009-09-05 21:47:34 +00003306 break;
3307 }
3308 }
cristy4c08aed2011-07-01 19:47:50 +00003309 SetPixelRed(image,red,q);
3310 SetPixelGreen(image,green,q);
3311 SetPixelBlue(image,blue,q);
3312 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00003313 }
3314 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
3315 status=MagickFalse;
3316 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3317 {
3318 MagickBooleanType
3319 proceed;
3320
cristyb5d5f722009-11-04 03:03:49 +00003321#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00003322 #pragma omp critical (MagickCore_ModulateImage)
3323#endif
3324 proceed=SetImageProgress(image,ModulateImageTag,progress++,image->rows);
3325 if (proceed == MagickFalse)
3326 status=MagickFalse;
3327 }
3328 }
3329 image_view=DestroyCacheView(image_view);
3330 return(status);
3331}
3332
3333/*
3334%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3335% %
3336% %
3337% %
3338% N e g a t e I m a g e %
3339% %
3340% %
3341% %
3342%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3343%
3344% NegateImage() negates the colors in the reference image. The grayscale
3345% option means that only grayscale values within the image are negated.
3346%
3347% The format of the NegateImageChannel method is:
3348%
3349% MagickBooleanType NegateImage(Image *image,
3350% const MagickBooleanType grayscale)
3351% MagickBooleanType NegateImageChannel(Image *image,
3352% const ChannelType channel,const MagickBooleanType grayscale)
3353%
3354% A description of each parameter follows:
3355%
3356% o image: the image.
3357%
3358% o channel: the channel.
3359%
3360% o grayscale: If MagickTrue, only negate grayscale pixels within the image.
3361%
3362*/
3363
3364MagickExport MagickBooleanType NegateImage(Image *image,
3365 const MagickBooleanType grayscale)
3366{
3367 MagickBooleanType
3368 status;
3369
3370 status=NegateImageChannel(image,DefaultChannels,grayscale);
3371 return(status);
3372}
3373
3374MagickExport MagickBooleanType NegateImageChannel(Image *image,
3375 const ChannelType channel,const MagickBooleanType grayscale)
3376{
3377#define NegateImageTag "Negate/Image"
3378
cristyc4c8d132010-01-07 01:58:38 +00003379 CacheView
3380 *image_view;
3381
cristy3ed852e2009-09-05 21:47:34 +00003382 ExceptionInfo
3383 *exception;
3384
cristy3ed852e2009-09-05 21:47:34 +00003385 MagickBooleanType
3386 status;
3387
cristybb503372010-05-27 20:51:26 +00003388 MagickOffsetType
3389 progress;
3390
3391 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003392 i;
3393
cristybb503372010-05-27 20:51:26 +00003394 ssize_t
3395 y;
3396
cristy3ed852e2009-09-05 21:47:34 +00003397 assert(image != (Image *) NULL);
3398 assert(image->signature == MagickSignature);
3399 if (image->debug != MagickFalse)
3400 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3401 if (image->storage_class == PseudoClass)
3402 {
3403 /*
3404 Negate colormap.
3405 */
cristyb5d5f722009-11-04 03:03:49 +00003406#if defined(MAGICKCORE_OPENMP_SUPPORT)
3407 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003408#endif
cristybb503372010-05-27 20:51:26 +00003409 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00003410 {
3411 if (grayscale != MagickFalse)
3412 if ((image->colormap[i].red != image->colormap[i].green) ||
3413 (image->colormap[i].green != image->colormap[i].blue))
3414 continue;
cristy2b9582a2011-07-04 17:38:56 +00003415 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00003416 image->colormap[i].red=(Quantum) QuantumRange-
3417 image->colormap[i].red;
cristy2b9582a2011-07-04 17:38:56 +00003418 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00003419 image->colormap[i].green=(Quantum) QuantumRange-
3420 image->colormap[i].green;
cristy2b9582a2011-07-04 17:38:56 +00003421 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00003422 image->colormap[i].blue=(Quantum) QuantumRange-
3423 image->colormap[i].blue;
3424 }
3425 }
3426 /*
3427 Negate image.
3428 */
3429 status=MagickTrue;
3430 progress=0;
3431 exception=(&image->exception);
3432 image_view=AcquireCacheView(image);
3433 if (grayscale != MagickFalse)
3434 {
cristyb5d5f722009-11-04 03:03:49 +00003435#if defined(MAGICKCORE_OPENMP_SUPPORT)
3436 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003437#endif
cristybb503372010-05-27 20:51:26 +00003438 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003439 {
3440 MagickBooleanType
3441 sync;
3442
cristy4c08aed2011-07-01 19:47:50 +00003443 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00003444 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003445
cristy8d4629b2010-08-30 17:59:46 +00003446 register ssize_t
3447 x;
3448
cristy3ed852e2009-09-05 21:47:34 +00003449 if (status == MagickFalse)
3450 continue;
3451 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,
3452 exception);
cristy4c08aed2011-07-01 19:47:50 +00003453 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00003454 {
3455 status=MagickFalse;
3456 continue;
3457 }
cristybb503372010-05-27 20:51:26 +00003458 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003459 {
cristy4c08aed2011-07-01 19:47:50 +00003460 if ((GetPixelRed(image,q) != GetPixelGreen(image,q)) ||
3461 (GetPixelGreen(image,q) != GetPixelBlue(image,q)))
cristy3ed852e2009-09-05 21:47:34 +00003462 {
cristy4c08aed2011-07-01 19:47:50 +00003463 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00003464 continue;
3465 }
cristy2b9582a2011-07-04 17:38:56 +00003466 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003467 SetPixelRed(image,QuantumRange-GetPixelRed(image,q),q);
cristy2b9582a2011-07-04 17:38:56 +00003468 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003469 SetPixelGreen(image,QuantumRange-GetPixelGreen(image,q),q);
cristy2b9582a2011-07-04 17:38:56 +00003470 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003471 SetPixelBlue(image,QuantumRange-GetPixelBlue(image,q),q);
cristy2b9582a2011-07-04 17:38:56 +00003472 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00003473 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +00003474 SetPixelBlack(image,QuantumRange-GetPixelBlack(image,q),q);
cristy2b9582a2011-07-04 17:38:56 +00003475 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003476 SetPixelAlpha(image,QuantumRange-GetPixelAlpha(image,q),q);
3477 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00003478 }
3479 sync=SyncCacheViewAuthenticPixels(image_view,exception);
3480 if (sync == MagickFalse)
3481 status=MagickFalse;
3482 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3483 {
3484 MagickBooleanType
3485 proceed;
3486
cristyb5d5f722009-11-04 03:03:49 +00003487#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00003488 #pragma omp critical (MagickCore_NegateImageChannel)
3489#endif
3490 proceed=SetImageProgress(image,NegateImageTag,progress++,
3491 image->rows);
3492 if (proceed == MagickFalse)
3493 status=MagickFalse;
3494 }
3495 }
3496 image_view=DestroyCacheView(image_view);
3497 return(MagickTrue);
3498 }
3499 /*
3500 Negate image.
3501 */
cristyb5d5f722009-11-04 03:03:49 +00003502#if defined(MAGICKCORE_OPENMP_SUPPORT)
3503 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003504#endif
cristybb503372010-05-27 20:51:26 +00003505 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003506 {
cristy4c08aed2011-07-01 19:47:50 +00003507 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00003508 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003509
cristy8d4629b2010-08-30 17:59:46 +00003510 register ssize_t
3511 x;
3512
cristy3ed852e2009-09-05 21:47:34 +00003513 if (status == MagickFalse)
3514 continue;
3515 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00003516 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00003517 {
3518 status=MagickFalse;
3519 continue;
3520 }
cristybb503372010-05-27 20:51:26 +00003521 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003522 {
cristy2b9582a2011-07-04 17:38:56 +00003523 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003524 SetPixelRed(image,QuantumRange-GetPixelRed(image,q),q);
cristy2b9582a2011-07-04 17:38:56 +00003525 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003526 SetPixelGreen(image,QuantumRange-GetPixelGreen(image,q),q);
cristy2b9582a2011-07-04 17:38:56 +00003527 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003528 SetPixelBlue(image,QuantumRange-GetPixelBlue(image,q),q);
cristy2b9582a2011-07-04 17:38:56 +00003529 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00003530 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +00003531 SetPixelBlack(image,QuantumRange-GetPixelBlack(image,q),q);
cristy2b9582a2011-07-04 17:38:56 +00003532 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003533 SetPixelAlpha(image,QuantumRange-GetPixelAlpha(image,q),q);
3534 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00003535 }
3536 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
3537 status=MagickFalse;
3538 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3539 {
3540 MagickBooleanType
3541 proceed;
3542
cristyb5d5f722009-11-04 03:03:49 +00003543#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00003544 #pragma omp critical (MagickCore_NegateImageChannel)
3545#endif
3546 proceed=SetImageProgress(image,NegateImageTag,progress++,image->rows);
3547 if (proceed == MagickFalse)
3548 status=MagickFalse;
3549 }
3550 }
3551 image_view=DestroyCacheView(image_view);
3552 return(status);
3553}
3554
3555/*
3556%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3557% %
3558% %
3559% %
3560% N o r m a l i z e I m a g e %
3561% %
3562% %
3563% %
3564%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3565%
3566% The NormalizeImage() method enhances the contrast of a color image by
3567% mapping the darkest 2 percent of all pixel to black and the brightest
3568% 1 percent to white.
3569%
3570% The format of the NormalizeImage method is:
3571%
3572% MagickBooleanType NormalizeImage(Image *image)
3573% MagickBooleanType NormalizeImageChannel(Image *image,
3574% const ChannelType channel)
3575%
3576% A description of each parameter follows:
3577%
3578% o image: the image.
3579%
3580% o channel: the channel.
3581%
3582*/
3583
3584MagickExport MagickBooleanType NormalizeImage(Image *image)
3585{
3586 MagickBooleanType
3587 status;
3588
3589 status=NormalizeImageChannel(image,DefaultChannels);
3590 return(status);
3591}
3592
3593MagickExport MagickBooleanType NormalizeImageChannel(Image *image,
3594 const ChannelType channel)
3595{
3596 double
3597 black_point,
3598 white_point;
3599
cristy530239c2010-07-25 17:34:26 +00003600 black_point=(double) image->columns*image->rows*0.0015;
3601 white_point=(double) image->columns*image->rows*0.9995;
cristy3ed852e2009-09-05 21:47:34 +00003602 return(ContrastStretchImageChannel(image,channel,black_point,white_point));
3603}
3604
3605/*
3606%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3607% %
3608% %
3609% %
3610% S i g m o i d a l C o n t r a s t I m a g e %
3611% %
3612% %
3613% %
3614%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3615%
3616% SigmoidalContrastImage() adjusts the contrast of an image with a non-linear
3617% sigmoidal contrast algorithm. Increase the contrast of the image using a
3618% sigmoidal transfer function without saturating highlights or shadows.
3619% Contrast indicates how much to increase the contrast (0 is none; 3 is
3620% typical; 20 is pushing it); mid-point indicates where midtones fall in the
3621% resultant image (0 is white; 50% is middle-gray; 100% is black). Set
3622% sharpen to MagickTrue to increase the image contrast otherwise the contrast
3623% is reduced.
3624%
3625% The format of the SigmoidalContrastImage method is:
3626%
3627% MagickBooleanType SigmoidalContrastImage(Image *image,
3628% const MagickBooleanType sharpen,const char *levels)
3629% MagickBooleanType SigmoidalContrastImageChannel(Image *image,
3630% const ChannelType channel,const MagickBooleanType sharpen,
3631% const double contrast,const double midpoint)
3632%
3633% A description of each parameter follows:
3634%
3635% o image: the image.
3636%
3637% o channel: the channel.
3638%
3639% o sharpen: Increase or decrease image contrast.
3640%
cristyfa769582010-09-30 23:30:03 +00003641% o alpha: strength of the contrast, the larger the number the more
3642% 'threshold-like' it becomes.
cristy3ed852e2009-09-05 21:47:34 +00003643%
cristyfa769582010-09-30 23:30:03 +00003644% o beta: midpoint of the function as a color value 0 to QuantumRange.
cristy3ed852e2009-09-05 21:47:34 +00003645%
3646*/
3647
3648MagickExport MagickBooleanType SigmoidalContrastImage(Image *image,
3649 const MagickBooleanType sharpen,const char *levels)
3650{
3651 GeometryInfo
3652 geometry_info;
3653
3654 MagickBooleanType
3655 status;
3656
3657 MagickStatusType
3658 flags;
3659
3660 flags=ParseGeometry(levels,&geometry_info);
3661 if ((flags & SigmaValue) == 0)
3662 geometry_info.sigma=1.0*QuantumRange/2.0;
3663 if ((flags & PercentValue) != 0)
3664 geometry_info.sigma=1.0*QuantumRange*geometry_info.sigma/100.0;
3665 status=SigmoidalContrastImageChannel(image,DefaultChannels,sharpen,
3666 geometry_info.rho,geometry_info.sigma);
3667 return(status);
3668}
3669
3670MagickExport MagickBooleanType SigmoidalContrastImageChannel(Image *image,
3671 const ChannelType channel,const MagickBooleanType sharpen,
3672 const double contrast,const double midpoint)
3673{
3674#define SigmoidalContrastImageTag "SigmoidalContrast/Image"
3675
cristyc4c8d132010-01-07 01:58:38 +00003676 CacheView
3677 *image_view;
3678
cristy3ed852e2009-09-05 21:47:34 +00003679 ExceptionInfo
3680 *exception;
3681
cristy3ed852e2009-09-05 21:47:34 +00003682 MagickBooleanType
3683 status;
3684
cristybb503372010-05-27 20:51:26 +00003685 MagickOffsetType
3686 progress;
3687
cristy3ed852e2009-09-05 21:47:34 +00003688 MagickRealType
3689 *sigmoidal_map;
3690
cristybb503372010-05-27 20:51:26 +00003691 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003692 i;
3693
cristybb503372010-05-27 20:51:26 +00003694 ssize_t
3695 y;
3696
cristy3ed852e2009-09-05 21:47:34 +00003697 /*
3698 Allocate and initialize sigmoidal maps.
3699 */
3700 assert(image != (Image *) NULL);
3701 assert(image->signature == MagickSignature);
3702 if (image->debug != MagickFalse)
3703 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3704 sigmoidal_map=(MagickRealType *) AcquireQuantumMemory(MaxMap+1UL,
3705 sizeof(*sigmoidal_map));
3706 if (sigmoidal_map == (MagickRealType *) NULL)
3707 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
3708 image->filename);
3709 (void) ResetMagickMemory(sigmoidal_map,0,(MaxMap+1)*sizeof(*sigmoidal_map));
cristyb5d5f722009-11-04 03:03:49 +00003710#if defined(MAGICKCORE_OPENMP_SUPPORT)
3711 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003712#endif
cristybb503372010-05-27 20:51:26 +00003713 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +00003714 {
3715 if (sharpen != MagickFalse)
3716 {
3717 sigmoidal_map[i]=(MagickRealType) ScaleMapToQuantum((MagickRealType)
3718 (MaxMap*((1.0/(1.0+exp(contrast*(midpoint/(double) QuantumRange-
3719 (double) i/MaxMap))))-(1.0/(1.0+exp(contrast*(midpoint/
3720 (double) QuantumRange)))))/((1.0/(1.0+exp(contrast*(midpoint/
3721 (double) QuantumRange-1.0))))-(1.0/(1.0+exp(contrast*(midpoint/
3722 (double) QuantumRange)))))+0.5));
3723 continue;
3724 }
3725 sigmoidal_map[i]=(MagickRealType) ScaleMapToQuantum((MagickRealType)
3726 (MaxMap*(QuantumScale*midpoint-log((1.0-(1.0/(1.0+exp(midpoint/
3727 (double) QuantumRange*contrast))+((double) i/MaxMap)*((1.0/
3728 (1.0+exp(contrast*(midpoint/(double) QuantumRange-1.0))))-(1.0/
3729 (1.0+exp(midpoint/(double) QuantumRange*contrast))))))/
3730 (1.0/(1.0+exp(midpoint/(double) QuantumRange*contrast))+
3731 ((double) i/MaxMap)*((1.0/(1.0+exp(contrast*(midpoint/
3732 (double) QuantumRange-1.0))))-(1.0/(1.0+exp(midpoint/
3733 (double) QuantumRange*contrast))))))/contrast)));
3734 }
3735 if (image->storage_class == PseudoClass)
3736 {
3737 /*
3738 Sigmoidal-contrast enhance colormap.
3739 */
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) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00003744 {
cristy2b9582a2011-07-04 17:38:56 +00003745 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristyce70c172010-01-07 17:15:30 +00003746 image->colormap[i].red=ClampToQuantum(sigmoidal_map[
cristy3ed852e2009-09-05 21:47:34 +00003747 ScaleQuantumToMap(image->colormap[i].red)]);
cristy2b9582a2011-07-04 17:38:56 +00003748 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristyce70c172010-01-07 17:15:30 +00003749 image->colormap[i].green=ClampToQuantum(sigmoidal_map[
cristy3ed852e2009-09-05 21:47:34 +00003750 ScaleQuantumToMap(image->colormap[i].green)]);
cristy2b9582a2011-07-04 17:38:56 +00003751 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristyce70c172010-01-07 17:15:30 +00003752 image->colormap[i].blue=ClampToQuantum(sigmoidal_map[
cristy3ed852e2009-09-05 21:47:34 +00003753 ScaleQuantumToMap(image->colormap[i].blue)]);
cristy2b9582a2011-07-04 17:38:56 +00003754 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003755 image->colormap[i].alpha=ClampToQuantum(sigmoidal_map[
3756 ScaleQuantumToMap(image->colormap[i].alpha)]);
cristy3ed852e2009-09-05 21:47:34 +00003757 }
3758 }
3759 /*
3760 Sigmoidal-contrast enhance image.
3761 */
3762 status=MagickTrue;
3763 progress=0;
3764 exception=(&image->exception);
3765 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00003766#if defined(MAGICKCORE_OPENMP_SUPPORT)
3767 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003768#endif
cristybb503372010-05-27 20:51:26 +00003769 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003770 {
cristy4c08aed2011-07-01 19:47:50 +00003771 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00003772 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003773
cristy8d4629b2010-08-30 17:59:46 +00003774 register ssize_t
3775 x;
3776
cristy3ed852e2009-09-05 21:47:34 +00003777 if (status == MagickFalse)
3778 continue;
3779 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00003780 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00003781 {
3782 status=MagickFalse;
3783 continue;
3784 }
cristybb503372010-05-27 20:51:26 +00003785 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003786 {
cristy2b9582a2011-07-04 17:38:56 +00003787 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003788 SetPixelRed(image,ClampToQuantum(sigmoidal_map[ScaleQuantumToMap(
3789 GetPixelRed(image,q))]),q);
cristy2b9582a2011-07-04 17:38:56 +00003790 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003791 SetPixelGreen(image,ClampToQuantum(sigmoidal_map[ScaleQuantumToMap(
3792 GetPixelGreen(image,q))]),q);
cristy2b9582a2011-07-04 17:38:56 +00003793 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003794 SetPixelBlue(image,ClampToQuantum(sigmoidal_map[ScaleQuantumToMap(
3795 GetPixelBlue(image,q))]),q);
cristy2b9582a2011-07-04 17:38:56 +00003796 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00003797 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +00003798 SetPixelBlack(image,ClampToQuantum(sigmoidal_map[ScaleQuantumToMap(
3799 GetPixelBlack(image,q))]),q);
cristy2b9582a2011-07-04 17:38:56 +00003800 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003801 SetPixelAlpha(image,ClampToQuantum(sigmoidal_map[ScaleQuantumToMap(
3802 GetPixelAlpha(image,q))]),q);
3803 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00003804 }
3805 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
3806 status=MagickFalse;
3807 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3808 {
3809 MagickBooleanType
3810 proceed;
3811
cristyb5d5f722009-11-04 03:03:49 +00003812#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00003813 #pragma omp critical (MagickCore_SigmoidalContrastImageChannel)
3814#endif
3815 proceed=SetImageProgress(image,SigmoidalContrastImageTag,progress++,
3816 image->rows);
3817 if (proceed == MagickFalse)
3818 status=MagickFalse;
3819 }
3820 }
3821 image_view=DestroyCacheView(image_view);
3822 sigmoidal_map=(MagickRealType *) RelinquishMagickMemory(sigmoidal_map);
3823 return(status);
3824}