blob: bff00dbc2e9931d0700d9945e2f15126027f2427 [file] [log] [blame]
cristy3ed852e2009-09-05 21:47:34 +00001/*
2%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3% %
4% %
5% %
6% EEEEE N N H H AAA N N CCCC EEEEE %
7% E NN N H H A A NN N C E %
8% EEE N N N HHHHH AAAAA N N N C EEE %
9% E N NN H H A A N NN C E %
10% EEEEE N N H H A A N N CCCC EEEEE %
11% %
12% %
13% MagickCore Image Enhancement Methods %
14% %
15% Software Design %
16% John Cristy %
17% July 1992 %
18% %
19% %
cristy7e41fe82010-12-04 23:12:08 +000020% Copyright 1999-2011 ImageMagick Studio LLC, a non-profit organization %
cristy3ed852e2009-09-05 21:47:34 +000021% dedicated to making software imaging solutions freely available. %
22% %
23% You may not use this file except in compliance with the License. You may %
24% obtain a copy of the License at %
25% %
26% http://www.imagemagick.org/script/license.php %
27% %
28% Unless required by applicable law or agreed to in writing, software %
29% distributed under the License is distributed on an "AS IS" BASIS, %
30% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %
31% See the License for the specific language governing permissions and %
32% limitations under the License. %
33% %
34%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
35%
36%
37%
38*/
39
40/*
41 Include declarations.
42*/
cristy4c08aed2011-07-01 19:47:50 +000043#include "MagickCore/studio.h"
44#include "MagickCore/artifact.h"
45#include "MagickCore/cache.h"
46#include "MagickCore/cache-view.h"
47#include "MagickCore/color.h"
48#include "MagickCore/color-private.h"
49#include "MagickCore/colorspace.h"
50#include "MagickCore/composite-private.h"
51#include "MagickCore/enhance.h"
52#include "MagickCore/exception.h"
53#include "MagickCore/exception-private.h"
54#include "MagickCore/fx.h"
55#include "MagickCore/gem.h"
56#include "MagickCore/geometry.h"
57#include "MagickCore/histogram.h"
58#include "MagickCore/image.h"
59#include "MagickCore/image-private.h"
60#include "MagickCore/memory_.h"
61#include "MagickCore/monitor.h"
62#include "MagickCore/monitor-private.h"
63#include "MagickCore/option.h"
64#include "MagickCore/pixel-accessor.h"
65#include "MagickCore/quantum.h"
66#include "MagickCore/quantum-private.h"
67#include "MagickCore/resample.h"
68#include "MagickCore/resample-private.h"
69#include "MagickCore/statistic.h"
70#include "MagickCore/string_.h"
71#include "MagickCore/string-private.h"
72#include "MagickCore/thread-private.h"
73#include "MagickCore/token.h"
74#include "MagickCore/xml-tree.h"
cristy3ed852e2009-09-05 21:47:34 +000075
76/*
77%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
78% %
79% %
80% %
81% A u t o G a m m a I m a g e %
82% %
83% %
84% %
85%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
86%
87% AutoGammaImage() extract the 'mean' from the image and adjust the image
88% to try make set its gamma appropriatally.
89%
cristy308b4e62009-09-21 14:40:44 +000090% The format of the AutoGammaImage method is:
cristy3ed852e2009-09-05 21:47:34 +000091%
92% MagickBooleanType AutoGammaImage(Image *image)
93% MagickBooleanType AutoGammaImageChannel(Image *image,
94% const ChannelType channel)
95%
96% A description of each parameter follows:
97%
98% o image: The image to auto-level
99%
100% o channel: The channels to auto-level. If the special 'SyncChannels'
101% flag is set all given channels is adjusted in the same way using the
102% mean average of those channels.
103%
104*/
105
106MagickExport MagickBooleanType AutoGammaImage(Image *image)
107{
108 return(AutoGammaImageChannel(image,DefaultChannels));
109}
110
111MagickExport MagickBooleanType AutoGammaImageChannel(Image *image,
112 const ChannelType channel)
113{
114 MagickStatusType
115 status;
116
117 double
cristy4c08aed2011-07-01 19:47:50 +0000118 gamma,
119 log_mean,
120 mean,
121 sans;
anthony4efe5972009-09-11 06:46:40 +0000122
cristy4c08aed2011-07-01 19:47:50 +0000123 log_mean=log(0.5);
cristy3ed852e2009-09-05 21:47:34 +0000124
125 if ((channel & SyncChannels) != 0 )
126 {
127 /*
128 Apply gamma correction equally accross all given channels
129 */
cristy2b726bd2010-01-11 01:05:39 +0000130 (void) GetImageChannelMean(image,channel,&mean,&sans,&image->exception);
cristy4c08aed2011-07-01 19:47:50 +0000131 gamma=log(mean*QuantumScale)/log_mean;
132 return(LevelImageChannel(image,channel,0.0,(double)QuantumRange,gamma));
cristy3ed852e2009-09-05 21:47:34 +0000133 }
134
135 /*
cristy4c08aed2011-07-01 19:47:50 +0000136 Auto-gamma each channel separately.
cristy3ed852e2009-09-05 21:47:34 +0000137 */
cristy4c08aed2011-07-01 19:47:50 +0000138 status=MagickTrue;
cristy3ed852e2009-09-05 21:47:34 +0000139 if ((channel & RedChannel) != 0)
140 {
cristy2b726bd2010-01-11 01:05:39 +0000141 (void) GetImageChannelMean(image,RedChannel,&mean,&sans,
142 &image->exception);
cristy4c08aed2011-07-01 19:47:50 +0000143 gamma=log(mean*QuantumScale)/log_mean;
144 status=status && LevelImageChannel(image,RedChannel,0.0,(double)
145 QuantumRange, gamma);
cristy3ed852e2009-09-05 21:47:34 +0000146 }
147 if ((channel & GreenChannel) != 0)
148 {
cristy2b726bd2010-01-11 01:05:39 +0000149 (void) GetImageChannelMean(image,GreenChannel,&mean,&sans,
150 &image->exception);
cristy4c08aed2011-07-01 19:47:50 +0000151 gamma=log(mean*QuantumScale)/log_mean;
152 status=status && LevelImageChannel(image,GreenChannel,0.0,(double)
153 QuantumRange,gamma);
cristy3ed852e2009-09-05 21:47:34 +0000154 }
155 if ((channel & BlueChannel) != 0)
156 {
cristy2b726bd2010-01-11 01:05:39 +0000157 (void) GetImageChannelMean(image,BlueChannel,&mean,&sans,
158 &image->exception);
cristy4c08aed2011-07-01 19:47:50 +0000159 gamma=log(mean*QuantumScale)/log_mean;
160 status=status && LevelImageChannel(image,BlueChannel,0.0,(double)
161 QuantumRange,gamma);
162 }
163 if (((channel & BlackChannel) != 0) &&
164 (image->colorspace == CMYKColorspace))
165 {
166 (void) GetImageChannelMean(image,BlackChannel,&mean,&sans,
167 &image->exception);
168 gamma=log(mean*QuantumScale)/log_mean;
169 status=status && LevelImageChannel(image,BlackChannel,0.0,(double)
170 QuantumRange,gamma);
cristy3ed852e2009-09-05 21:47:34 +0000171 }
172 if (((channel & OpacityChannel) != 0) &&
173 (image->matte == MagickTrue))
174 {
cristy2b726bd2010-01-11 01:05:39 +0000175 (void) GetImageChannelMean(image,OpacityChannel,&mean,&sans,
176 &image->exception);
cristy4c08aed2011-07-01 19:47:50 +0000177 gamma=log(mean*QuantumScale)/log_mean;
178 status=status && LevelImageChannel(image,OpacityChannel,0.0,(double)
179 QuantumRange,gamma);
cristy3ed852e2009-09-05 21:47:34 +0000180 }
181 return(status != 0 ? MagickTrue : MagickFalse);
182}
183
184/*
185%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
186% %
187% %
188% %
189% A u t o L e v e l I m a g e %
190% %
191% %
192% %
193%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
194%
195% AutoLevelImage() adjusts the levels of a particular image channel by
196% scaling the minimum and maximum values to the full quantum range.
197%
198% The format of the LevelImage method is:
199%
200% MagickBooleanType AutoLevelImage(Image *image)
201% MagickBooleanType AutoLevelImageChannel(Image *image,
202% const ChannelType channel)
203%
204% A description of each parameter follows:
205%
206% o image: The image to auto-level
207%
208% o channel: The channels to auto-level. If the special 'SyncChannels'
209% flag is set the min/max/mean value of all given channels is used for
210% all given channels, to all channels in the same way.
211%
212*/
213
214MagickExport MagickBooleanType AutoLevelImage(Image *image)
215{
216 return(AutoLevelImageChannel(image,DefaultChannels));
217}
218
219MagickExport MagickBooleanType AutoLevelImageChannel(Image *image,
220 const ChannelType channel)
221{
222 /*
223 This is simply a convenience function around a Min/Max Histogram Stretch
224 */
225 return MinMaxStretchImage(image, channel, 0.0, 0.0);
226}
227
228/*
229%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
230% %
231% %
232% %
cristya28d6b82010-01-11 20:03:47 +0000233% B r i g h t n e s s C o n t r a s t I m a g e %
234% %
235% %
236% %
237%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
238%
239% Use BrightnessContrastImage() to change the brightness and/or contrast of
240% an image. It converts the brightness and contrast parameters into slope
241% and intercept and calls a polynomical function to apply to the image.
242%
243% The format of the BrightnessContrastImage method is:
244%
245% MagickBooleanType BrightnessContrastImage(Image *image,
246% const double brightness,const double contrast)
247% MagickBooleanType BrightnessContrastImageChannel(Image *image,
248% const ChannelType channel,const double brightness,
249% const double contrast)
250%
251% A description of each parameter follows:
252%
253% o image: the image.
254%
255% o channel: the channel.
256%
257% o brightness: the brightness percent (-100 .. 100).
258%
259% o contrast: the contrast percent (-100 .. 100).
260%
261*/
262
263MagickExport MagickBooleanType BrightnessContrastImage(Image *image,
264 const double brightness,const double contrast)
265{
266 MagickBooleanType
267 status;
268
269 status=BrightnessContrastImageChannel(image,DefaultChannels,brightness,
270 contrast);
271 return(status);
272}
273
274MagickExport MagickBooleanType BrightnessContrastImageChannel(Image *image,
275 const ChannelType channel,const double brightness,const double contrast)
276{
277#define BrightnessContastImageTag "BrightnessContast/Image"
278
279 double
280 alpha,
281 intercept,
282 coefficients[2],
283 slope;
284
285 MagickBooleanType
286 status;
287
288 /*
289 Compute slope and intercept.
290 */
291 assert(image != (Image *) NULL);
292 assert(image->signature == MagickSignature);
293 if (image->debug != MagickFalse)
294 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
295 alpha=contrast;
cristy4205a3c2010-09-12 20:19:59 +0000296 slope=tan((double) (MagickPI*(alpha/100.0+1.0)/4.0));
cristya28d6b82010-01-11 20:03:47 +0000297 if (slope < 0.0)
298 slope=0.0;
299 intercept=brightness/100.0+((100-brightness)/200.0)*(1.0-slope);
300 coefficients[0]=slope;
301 coefficients[1]=intercept;
302 status=FunctionImageChannel(image,channel,PolynomialFunction,2,coefficients,
303 &image->exception);
304 return(status);
305}
306
307/*
308%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
309% %
310% %
311% %
cristy3ed852e2009-09-05 21:47:34 +0000312% C o l o r D e c i s i o n L i s t I m a g e %
313% %
314% %
315% %
316%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
317%
318% ColorDecisionListImage() accepts a lightweight Color Correction Collection
319% (CCC) file which solely contains one or more color corrections and applies
320% the correction to the image. Here is a sample CCC file:
321%
322% <ColorCorrectionCollection xmlns="urn:ASC:CDL:v1.2">
323% <ColorCorrection id="cc03345">
324% <SOPNode>
325% <Slope> 0.9 1.2 0.5 </Slope>
326% <Offset> 0.4 -0.5 0.6 </Offset>
327% <Power> 1.0 0.8 1.5 </Power>
328% </SOPNode>
329% <SATNode>
330% <Saturation> 0.85 </Saturation>
331% </SATNode>
332% </ColorCorrection>
333% </ColorCorrectionCollection>
334%
335% which includes the slop, offset, and power for each of the RGB channels
336% as well as the saturation.
337%
338% The format of the ColorDecisionListImage method is:
339%
340% MagickBooleanType ColorDecisionListImage(Image *image,
341% const char *color_correction_collection)
342%
343% A description of each parameter follows:
344%
345% o image: the image.
346%
347% o color_correction_collection: the color correction collection in XML.
348%
349*/
350MagickExport MagickBooleanType ColorDecisionListImage(Image *image,
351 const char *color_correction_collection)
352{
353#define ColorDecisionListCorrectImageTag "ColorDecisionList/Image"
354
355 typedef struct _Correction
356 {
357 double
358 slope,
359 offset,
360 power;
361 } Correction;
362
363 typedef struct _ColorCorrection
364 {
365 Correction
366 red,
367 green,
368 blue;
369
370 double
371 saturation;
372 } ColorCorrection;
373
cristyc4c8d132010-01-07 01:58:38 +0000374 CacheView
375 *image_view;
376
cristy3ed852e2009-09-05 21:47:34 +0000377 char
378 token[MaxTextExtent];
379
380 ColorCorrection
381 color_correction;
382
383 const char
384 *content,
385 *p;
386
387 ExceptionInfo
388 *exception;
389
cristy3ed852e2009-09-05 21:47:34 +0000390 MagickBooleanType
391 status;
392
cristybb503372010-05-27 20:51:26 +0000393 MagickOffsetType
394 progress;
395
cristy3ed852e2009-09-05 21:47:34 +0000396 PixelPacket
397 *cdl_map;
398
cristybb503372010-05-27 20:51:26 +0000399 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000400 i;
401
cristybb503372010-05-27 20:51:26 +0000402 ssize_t
403 y;
404
cristy3ed852e2009-09-05 21:47:34 +0000405 XMLTreeInfo
406 *cc,
407 *ccc,
408 *sat,
409 *sop;
410
cristy3ed852e2009-09-05 21:47:34 +0000411 /*
412 Allocate and initialize cdl maps.
413 */
414 assert(image != (Image *) NULL);
415 assert(image->signature == MagickSignature);
416 if (image->debug != MagickFalse)
417 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
418 if (color_correction_collection == (const char *) NULL)
419 return(MagickFalse);
420 ccc=NewXMLTree((const char *) color_correction_collection,&image->exception);
421 if (ccc == (XMLTreeInfo *) NULL)
422 return(MagickFalse);
423 cc=GetXMLTreeChild(ccc,"ColorCorrection");
424 if (cc == (XMLTreeInfo *) NULL)
425 {
426 ccc=DestroyXMLTree(ccc);
427 return(MagickFalse);
428 }
429 color_correction.red.slope=1.0;
430 color_correction.red.offset=0.0;
431 color_correction.red.power=1.0;
432 color_correction.green.slope=1.0;
433 color_correction.green.offset=0.0;
434 color_correction.green.power=1.0;
435 color_correction.blue.slope=1.0;
436 color_correction.blue.offset=0.0;
437 color_correction.blue.power=1.0;
438 color_correction.saturation=0.0;
439 sop=GetXMLTreeChild(cc,"SOPNode");
440 if (sop != (XMLTreeInfo *) NULL)
441 {
442 XMLTreeInfo
443 *offset,
444 *power,
445 *slope;
446
447 slope=GetXMLTreeChild(sop,"Slope");
448 if (slope != (XMLTreeInfo *) NULL)
449 {
450 content=GetXMLTreeContent(slope);
451 p=(const char *) content;
452 for (i=0; (*p != '\0') && (i < 3); i++)
453 {
454 GetMagickToken(p,&p,token);
455 if (*token == ',')
456 GetMagickToken(p,&p,token);
457 switch (i)
458 {
cristyc1acd842011-05-19 23:05:47 +0000459 case 0:
460 {
461 color_correction.red.slope=InterpretLocaleValue(token,
462 (char **) NULL);
463 break;
464 }
465 case 1:
466 {
467 color_correction.green.slope=InterpretLocaleValue(token,
468 (char **) NULL);
469 break;
470 }
471 case 2:
472 {
473 color_correction.blue.slope=InterpretLocaleValue(token,
474 (char **) NULL);
475 break;
476 }
cristy3ed852e2009-09-05 21:47:34 +0000477 }
478 }
479 }
480 offset=GetXMLTreeChild(sop,"Offset");
481 if (offset != (XMLTreeInfo *) NULL)
482 {
483 content=GetXMLTreeContent(offset);
484 p=(const char *) content;
485 for (i=0; (*p != '\0') && (i < 3); i++)
486 {
487 GetMagickToken(p,&p,token);
488 if (*token == ',')
489 GetMagickToken(p,&p,token);
490 switch (i)
491 {
cristyc1acd842011-05-19 23:05:47 +0000492 case 0:
493 {
494 color_correction.red.offset=InterpretLocaleValue(token,
495 (char **) NULL);
496 break;
497 }
498 case 1:
499 {
500 color_correction.green.offset=InterpretLocaleValue(token,
501 (char **) NULL);
502 break;
503 }
504 case 2:
505 {
506 color_correction.blue.offset=InterpretLocaleValue(token,
507 (char **) NULL);
508 break;
509 }
cristy3ed852e2009-09-05 21:47:34 +0000510 }
511 }
512 }
513 power=GetXMLTreeChild(sop,"Power");
514 if (power != (XMLTreeInfo *) NULL)
515 {
516 content=GetXMLTreeContent(power);
517 p=(const char *) content;
518 for (i=0; (*p != '\0') && (i < 3); i++)
519 {
520 GetMagickToken(p,&p,token);
521 if (*token == ',')
522 GetMagickToken(p,&p,token);
523 switch (i)
524 {
cristyc1acd842011-05-19 23:05:47 +0000525 case 0:
526 {
527 color_correction.red.power=InterpretLocaleValue(token,
528 (char **) NULL);
529 break;
530 }
531 case 1:
532 {
533 color_correction.green.power=InterpretLocaleValue(token,
534 (char **) NULL);
535 break;
536 }
537 case 2:
538 {
539 color_correction.blue.power=InterpretLocaleValue(token,
540 (char **) NULL);
541 break;
542 }
cristy3ed852e2009-09-05 21:47:34 +0000543 }
544 }
545 }
546 }
547 sat=GetXMLTreeChild(cc,"SATNode");
548 if (sat != (XMLTreeInfo *) NULL)
549 {
550 XMLTreeInfo
551 *saturation;
552
553 saturation=GetXMLTreeChild(sat,"Saturation");
554 if (saturation != (XMLTreeInfo *) NULL)
555 {
556 content=GetXMLTreeContent(saturation);
557 p=(const char *) content;
558 GetMagickToken(p,&p,token);
cristyc1acd842011-05-19 23:05:47 +0000559 color_correction.saturation=InterpretLocaleValue(token,
560 (char **) NULL);
cristy3ed852e2009-09-05 21:47:34 +0000561 }
562 }
563 ccc=DestroyXMLTree(ccc);
564 if (image->debug != MagickFalse)
565 {
566 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
567 " Color Correction Collection:");
568 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000569 " color_correction.red.slope: %g",color_correction.red.slope);
cristy3ed852e2009-09-05 21:47:34 +0000570 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000571 " color_correction.red.offset: %g",color_correction.red.offset);
cristy3ed852e2009-09-05 21:47:34 +0000572 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000573 " color_correction.red.power: %g",color_correction.red.power);
cristy3ed852e2009-09-05 21:47:34 +0000574 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000575 " color_correction.green.slope: %g",color_correction.green.slope);
cristy3ed852e2009-09-05 21:47:34 +0000576 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000577 " color_correction.green.offset: %g",color_correction.green.offset);
cristy3ed852e2009-09-05 21:47:34 +0000578 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000579 " color_correction.green.power: %g",color_correction.green.power);
cristy3ed852e2009-09-05 21:47:34 +0000580 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000581 " color_correction.blue.slope: %g",color_correction.blue.slope);
cristy3ed852e2009-09-05 21:47:34 +0000582 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000583 " color_correction.blue.offset: %g",color_correction.blue.offset);
cristy3ed852e2009-09-05 21:47:34 +0000584 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000585 " color_correction.blue.power: %g",color_correction.blue.power);
cristy3ed852e2009-09-05 21:47:34 +0000586 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000587 " color_correction.saturation: %g",color_correction.saturation);
cristy3ed852e2009-09-05 21:47:34 +0000588 }
589 cdl_map=(PixelPacket *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*cdl_map));
590 if (cdl_map == (PixelPacket *) NULL)
591 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
592 image->filename);
cristyb5d5f722009-11-04 03:03:49 +0000593#if defined(MAGICKCORE_OPENMP_SUPPORT)
594 #pragma omp parallel for schedule(dynamic,4)
cristy3ed852e2009-09-05 21:47:34 +0000595#endif
cristybb503372010-05-27 20:51:26 +0000596 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +0000597 {
cristyce70c172010-01-07 17:15:30 +0000598 cdl_map[i].red=ClampToQuantum((MagickRealType) ScaleMapToQuantum((
cristy3ed852e2009-09-05 21:47:34 +0000599 MagickRealType) (MaxMap*(pow(color_correction.red.slope*i/MaxMap+
600 color_correction.red.offset,color_correction.red.power)))));
cristyce70c172010-01-07 17:15:30 +0000601 cdl_map[i].green=ClampToQuantum((MagickRealType) ScaleMapToQuantum((
cristy3ed852e2009-09-05 21:47:34 +0000602 MagickRealType) (MaxMap*(pow(color_correction.green.slope*i/MaxMap+
603 color_correction.green.offset,color_correction.green.power)))));
cristyce70c172010-01-07 17:15:30 +0000604 cdl_map[i].blue=ClampToQuantum((MagickRealType) ScaleMapToQuantum((
cristy3ed852e2009-09-05 21:47:34 +0000605 MagickRealType) (MaxMap*(pow(color_correction.blue.slope*i/MaxMap+
606 color_correction.blue.offset,color_correction.blue.power)))));
607 }
608 if (image->storage_class == PseudoClass)
609 {
610 /*
611 Apply transfer function to colormap.
612 */
cristyb5d5f722009-11-04 03:03:49 +0000613#if defined(MAGICKCORE_OPENMP_SUPPORT)
614 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000615#endif
cristybb503372010-05-27 20:51:26 +0000616 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +0000617 {
618 double
619 luma;
620
621 luma=0.2126*image->colormap[i].red+0.7152*image->colormap[i].green+
622 0.0722*image->colormap[i].blue;
cristyce70c172010-01-07 17:15:30 +0000623 image->colormap[i].red=ClampToQuantum(luma+color_correction.saturation*
cristy3ed852e2009-09-05 21:47:34 +0000624 cdl_map[ScaleQuantumToMap(image->colormap[i].red)].red-luma);
cristyce70c172010-01-07 17:15:30 +0000625 image->colormap[i].green=ClampToQuantum(luma+
cristy3ed852e2009-09-05 21:47:34 +0000626 color_correction.saturation*cdl_map[ScaleQuantumToMap(
627 image->colormap[i].green)].green-luma);
cristyce70c172010-01-07 17:15:30 +0000628 image->colormap[i].blue=ClampToQuantum(luma+color_correction.saturation*
cristy3ed852e2009-09-05 21:47:34 +0000629 cdl_map[ScaleQuantumToMap(image->colormap[i].blue)].blue-luma);
630 }
631 }
632 /*
633 Apply transfer function to image.
634 */
635 status=MagickTrue;
636 progress=0;
637 exception=(&image->exception);
638 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +0000639#if defined(MAGICKCORE_OPENMP_SUPPORT)
640 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000641#endif
cristybb503372010-05-27 20:51:26 +0000642 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000643 {
644 double
645 luma;
646
cristy4c08aed2011-07-01 19:47:50 +0000647 register Quantum
cristyc47d1f82009-11-26 01:44:43 +0000648 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000649
cristy8d4629b2010-08-30 17:59:46 +0000650 register ssize_t
651 x;
652
cristy3ed852e2009-09-05 21:47:34 +0000653 if (status == MagickFalse)
654 continue;
655 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +0000656 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +0000657 {
658 status=MagickFalse;
659 continue;
660 }
cristybb503372010-05-27 20:51:26 +0000661 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000662 {
cristy4c08aed2011-07-01 19:47:50 +0000663 luma=0.2126*GetPixelRed(image,q)+0.7152*GetPixelGreen(image,q)+0.0722*
664 GetPixelBlue(image,q);
665 SetPixelRed(image,ClampToQuantum(luma+color_correction.saturation*
666 (cdl_map[ScaleQuantumToMap(GetPixelRed(image,q))].red-luma)),q);
667 SetPixelGreen(image,ClampToQuantum(luma+color_correction.saturation*
668 (cdl_map[ScaleQuantumToMap(GetPixelGreen(image,q))].green-luma)),q);
669 SetPixelBlue(image,ClampToQuantum(luma+color_correction.saturation*
670 (cdl_map[ScaleQuantumToMap(GetPixelBlue(image,q))].blue-luma)),q);
671 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +0000672 }
673 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
674 status=MagickFalse;
675 if (image->progress_monitor != (MagickProgressMonitor) NULL)
676 {
677 MagickBooleanType
678 proceed;
679
cristyb5d5f722009-11-04 03:03:49 +0000680#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +0000681 #pragma omp critical (MagickCore_ColorDecisionListImageChannel)
682#endif
683 proceed=SetImageProgress(image,ColorDecisionListCorrectImageTag,
684 progress++,image->rows);
685 if (proceed == MagickFalse)
686 status=MagickFalse;
687 }
688 }
689 image_view=DestroyCacheView(image_view);
690 cdl_map=(PixelPacket *) RelinquishMagickMemory(cdl_map);
691 return(status);
692}
693
694/*
695%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
696% %
697% %
698% %
699% C l u t I m a g e %
700% %
701% %
702% %
703%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
704%
705% ClutImage() replaces each color value in the given image, by using it as an
706% index to lookup a replacement color value in a Color Look UP Table in the
cristycee97112010-05-28 00:44:52 +0000707% form of an image. The values are extracted along a diagonal of the CLUT
cristy3ed852e2009-09-05 21:47:34 +0000708% image so either a horizontal or vertial gradient image can be used.
709%
710% Typically this is used to either re-color a gray-scale image according to a
711% color gradient in the CLUT image, or to perform a freeform histogram
712% (level) adjustment according to the (typically gray-scale) gradient in the
713% CLUT image.
714%
715% When the 'channel' mask includes the matte/alpha transparency channel but
716% one image has no such channel it is assumed that that image is a simple
717% gray-scale image that will effect the alpha channel values, either for
718% gray-scale coloring (with transparent or semi-transparent colors), or
719% a histogram adjustment of existing alpha channel values. If both images
720% have matte channels, direct and normal indexing is applied, which is rarely
721% used.
722%
723% The format of the ClutImage method is:
724%
725% MagickBooleanType ClutImage(Image *image,Image *clut_image)
726% MagickBooleanType ClutImageChannel(Image *image,
727% const ChannelType channel,Image *clut_image)
728%
729% A description of each parameter follows:
730%
731% o image: the image, which is replaced by indexed CLUT values
732%
733% o clut_image: the color lookup table image for replacement color values.
734%
735% o channel: the channel.
736%
737*/
738
739MagickExport MagickBooleanType ClutImage(Image *image,const Image *clut_image)
740{
741 return(ClutImageChannel(image,DefaultChannels,clut_image));
742}
743
744MagickExport MagickBooleanType ClutImageChannel(Image *image,
745 const ChannelType channel,const Image *clut_image)
746{
cristy4c08aed2011-07-01 19:47:50 +0000747#define ClampAlphaPixelComponent(pixel) ClampToQuantum((pixel)->alpha)
748#define ClampBlackPixelComponent(pixel) ClampToQuantum((pixel)->black)
749#define ClampBluePixelComponent(pixel) ClampToQuantum((pixel)->blue)
750#define ClampGreenPixelComponent(pixel) ClampToQuantum((pixel)->green)
751#define ClampRedPixelComponent(pixel) ClampToQuantum((pixel)->red)
cristy3ed852e2009-09-05 21:47:34 +0000752#define ClutImageTag "Clut/Image"
753
cristyfa112112010-01-04 17:48:07 +0000754 CacheView
cristy708333f2011-03-26 01:25:07 +0000755 *clut_view,
cristyfa112112010-01-04 17:48:07 +0000756 *image_view;
757
cristy3ed852e2009-09-05 21:47:34 +0000758 ExceptionInfo
759 *exception;
760
cristy3ed852e2009-09-05 21:47:34 +0000761 MagickBooleanType
762 status;
763
cristybb503372010-05-27 20:51:26 +0000764 MagickOffsetType
765 progress;
766
cristy4c08aed2011-07-01 19:47:50 +0000767 PixelInfo
cristy49f37242011-03-22 18:18:23 +0000768 *clut_map;
769
770 register ssize_t
771 i;
cristy3ed852e2009-09-05 21:47:34 +0000772
cristybb503372010-05-27 20:51:26 +0000773 ssize_t
774 adjust,
775 y;
776
cristy3ed852e2009-09-05 21:47:34 +0000777 assert(image != (Image *) NULL);
778 assert(image->signature == MagickSignature);
779 if (image->debug != MagickFalse)
780 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
781 assert(clut_image != (Image *) NULL);
782 assert(clut_image->signature == MagickSignature);
783 if (SetImageStorageClass(image,DirectClass) == MagickFalse)
784 return(MagickFalse);
cristy4c08aed2011-07-01 19:47:50 +0000785 clut_map=(PixelInfo *) AcquireQuantumMemory(MaxMap+1UL,
cristy49f37242011-03-22 18:18:23 +0000786 sizeof(*clut_map));
cristy4c08aed2011-07-01 19:47:50 +0000787 if (clut_map == (PixelInfo *) NULL)
cristy49f37242011-03-22 18:18:23 +0000788 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
789 image->filename);
cristy3ed852e2009-09-05 21:47:34 +0000790 /*
791 Clut image.
792 */
793 status=MagickTrue;
794 progress=0;
cristybb503372010-05-27 20:51:26 +0000795 adjust=(ssize_t) (clut_image->interpolate == IntegerInterpolatePixel ? 0 : 1);
cristy3ed852e2009-09-05 21:47:34 +0000796 exception=(&image->exception);
cristy708333f2011-03-26 01:25:07 +0000797 clut_view=AcquireCacheView(clut_image);
cristyaf6bc722011-03-25 19:16:14 +0000798#if defined(MAGICKCORE_OPENMP_SUPPORT)
799 #pragma omp parallel for schedule(dynamic,4)
800#endif
cristy49f37242011-03-22 18:18:23 +0000801 for (i=0; i <= (ssize_t) MaxMap; i++)
802 {
cristy4c08aed2011-07-01 19:47:50 +0000803 GetPixelInfo(clut_image,clut_map+i);
804 (void) InterpolatePixelInfo(clut_image,clut_view,
cristy8a7c3e82011-03-26 02:10:53 +0000805 UndefinedInterpolatePixel,QuantumScale*i*(clut_image->columns-adjust),
806 QuantumScale*i*(clut_image->rows-adjust),clut_map+i,exception);
cristy49f37242011-03-22 18:18:23 +0000807 }
cristy708333f2011-03-26 01:25:07 +0000808 clut_view=DestroyCacheView(clut_view);
809 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +0000810#if defined(MAGICKCORE_OPENMP_SUPPORT)
811 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000812#endif
cristybb503372010-05-27 20:51:26 +0000813 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000814 {
cristy4c08aed2011-07-01 19:47:50 +0000815 PixelInfo
cristy3635df22011-03-25 00:16:16 +0000816 pixel;
817
cristy4c08aed2011-07-01 19:47:50 +0000818 register Quantum
cristyc47d1f82009-11-26 01:44:43 +0000819 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000820
cristy8d4629b2010-08-30 17:59:46 +0000821 register ssize_t
822 x;
823
cristy3ed852e2009-09-05 21:47:34 +0000824 if (status == MagickFalse)
825 continue;
826 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +0000827 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +0000828 {
829 status=MagickFalse;
830 continue;
831 }
cristy4c08aed2011-07-01 19:47:50 +0000832 GetPixelInfo(image,&pixel);
cristybb503372010-05-27 20:51:26 +0000833 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000834 {
cristy4c08aed2011-07-01 19:47:50 +0000835 SetPixelInfo(image,q,&pixel);
cristy2253f172011-03-24 23:39:51 +0000836 if ((channel & RedChannel) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000837 SetPixelRed(image,ClampRedPixelComponent(clut_map+
838 ScaleQuantumToMap(GetPixelRed(image,q))),q);
cristy2253f172011-03-24 23:39:51 +0000839 if ((channel & GreenChannel) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000840 SetPixelGreen(image,ClampGreenPixelComponent(clut_map+
841 ScaleQuantumToMap(GetPixelGreen(image,q))),q);
cristy2253f172011-03-24 23:39:51 +0000842 if ((channel & BlueChannel) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000843 SetPixelBlue(image,ClampBluePixelComponent(clut_map+
844 ScaleQuantumToMap(GetPixelBlue(image,q))),q);
845 if (((channel & BlackChannel) != 0) &&
846 (image->colorspace == CMYKColorspace))
847 SetPixelBlack(image,ClampBlackPixelComponent(clut_map+
848 ScaleQuantumToMap(GetPixelBlack(image,q))),q);
cristy3635df22011-03-25 00:16:16 +0000849 if ((channel & OpacityChannel) != 0)
850 {
851 if (clut_image->matte == MagickFalse)
cristy4c08aed2011-07-01 19:47:50 +0000852 SetPixelAlpha(image,GetPixelInfoIntensity(clut_map+
853 ScaleQuantumToMap((Quantum) GetPixelAlpha(image,q))),q);
cristy3635df22011-03-25 00:16:16 +0000854 else
855 if (image->matte == MagickFalse)
cristy4c08aed2011-07-01 19:47:50 +0000856 SetPixelAlpha(image,ClampAlphaPixelComponent(clut_map+
857 ScaleQuantumToMap((Quantum) GetPixelInfoIntensity(&pixel))),q);
cristy3635df22011-03-25 00:16:16 +0000858 else
cristy4c08aed2011-07-01 19:47:50 +0000859 SetPixelAlpha(image,ClampAlphaPixelComponent(clut_map+
860 ScaleQuantumToMap(GetPixelAlpha(image,q))),q);
cristy3635df22011-03-25 00:16:16 +0000861 }
cristy4c08aed2011-07-01 19:47:50 +0000862 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +0000863 }
864 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
865 status=MagickFalse;
866 if (image->progress_monitor != (MagickProgressMonitor) NULL)
867 {
868 MagickBooleanType
869 proceed;
870
cristyb5d5f722009-11-04 03:03:49 +0000871#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +0000872 #pragma omp critical (MagickCore_ClutImageChannel)
873#endif
874 proceed=SetImageProgress(image,ClutImageTag,progress++,image->rows);
875 if (proceed == MagickFalse)
876 status=MagickFalse;
877 }
878 }
879 image_view=DestroyCacheView(image_view);
cristy4c08aed2011-07-01 19:47:50 +0000880 clut_map=(PixelInfo *) RelinquishMagickMemory(clut_map);
cristy3ed852e2009-09-05 21:47:34 +0000881 if ((clut_image->matte != MagickFalse) && ((channel & OpacityChannel) != 0))
882 (void) SetImageAlphaChannel(image,ActivateAlphaChannel);
883 return(status);
884}
885
886/*
887%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
888% %
889% %
890% %
891% C o n t r a s t I m a g e %
892% %
893% %
894% %
895%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
896%
897% ContrastImage() enhances the intensity differences between the lighter and
898% darker elements of the image. Set sharpen to a MagickTrue to increase the
899% image contrast otherwise the contrast is reduced.
900%
901% The format of the ContrastImage method is:
902%
903% MagickBooleanType ContrastImage(Image *image,
904% const MagickBooleanType sharpen)
905%
906% A description of each parameter follows:
907%
908% o image: the image.
909%
910% o sharpen: Increase or decrease image contrast.
911%
912*/
913
914static void Contrast(const int sign,Quantum *red,Quantum *green,Quantum *blue)
915{
916 double
917 brightness,
918 hue,
919 saturation;
920
921 /*
922 Enhance contrast: dark color become darker, light color become lighter.
923 */
924 assert(red != (Quantum *) NULL);
925 assert(green != (Quantum *) NULL);
926 assert(blue != (Quantum *) NULL);
927 hue=0.0;
928 saturation=0.0;
929 brightness=0.0;
930 ConvertRGBToHSB(*red,*green,*blue,&hue,&saturation,&brightness);
cristy31ac0f02011-03-12 02:04:47 +0000931 brightness+=0.5*sign*(0.5*(sin((double) (MagickPI*(brightness-0.5)))+1.0)-
cristy4205a3c2010-09-12 20:19:59 +0000932 brightness);
cristy3ed852e2009-09-05 21:47:34 +0000933 if (brightness > 1.0)
934 brightness=1.0;
935 else
936 if (brightness < 0.0)
937 brightness=0.0;
938 ConvertHSBToRGB(hue,saturation,brightness,red,green,blue);
939}
940
941MagickExport MagickBooleanType ContrastImage(Image *image,
942 const MagickBooleanType sharpen)
943{
944#define ContrastImageTag "Contrast/Image"
945
cristyc4c8d132010-01-07 01:58:38 +0000946 CacheView
947 *image_view;
948
cristy3ed852e2009-09-05 21:47:34 +0000949 ExceptionInfo
950 *exception;
951
952 int
953 sign;
954
cristy3ed852e2009-09-05 21:47:34 +0000955 MagickBooleanType
956 status;
957
cristybb503372010-05-27 20:51:26 +0000958 MagickOffsetType
959 progress;
960
961 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000962 i;
963
cristybb503372010-05-27 20:51:26 +0000964 ssize_t
965 y;
966
cristy3ed852e2009-09-05 21:47:34 +0000967 assert(image != (Image *) NULL);
968 assert(image->signature == MagickSignature);
969 if (image->debug != MagickFalse)
970 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
971 sign=sharpen != MagickFalse ? 1 : -1;
972 if (image->storage_class == PseudoClass)
973 {
974 /*
975 Contrast enhance colormap.
976 */
cristybb503372010-05-27 20:51:26 +0000977 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +0000978 Contrast(sign,&image->colormap[i].red,&image->colormap[i].green,
979 &image->colormap[i].blue);
980 }
981 /*
982 Contrast enhance image.
983 */
984 status=MagickTrue;
985 progress=0;
986 exception=(&image->exception);
987 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +0000988#if defined(MAGICKCORE_OPENMP_SUPPORT)
989 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000990#endif
cristybb503372010-05-27 20:51:26 +0000991 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000992 {
cristy5afeab82011-04-30 01:30:09 +0000993 Quantum
994 blue,
995 green,
996 red;
997
cristy4c08aed2011-07-01 19:47:50 +0000998 register Quantum
cristyc47d1f82009-11-26 01:44:43 +0000999 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001000
cristy8d4629b2010-08-30 17:59:46 +00001001 register ssize_t
1002 x;
1003
cristy3ed852e2009-09-05 21:47:34 +00001004 if (status == MagickFalse)
1005 continue;
1006 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00001007 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001008 {
1009 status=MagickFalse;
1010 continue;
1011 }
cristybb503372010-05-27 20:51:26 +00001012 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001013 {
cristy4c08aed2011-07-01 19:47:50 +00001014 red=GetPixelRed(image,q);
1015 green=GetPixelGreen(image,q);
1016 blue=GetPixelBlue(image,q);
cristy5afeab82011-04-30 01:30:09 +00001017 Contrast(sign,&red,&green,&blue);
cristy4c08aed2011-07-01 19:47:50 +00001018 SetPixelRed(image,red,q);
1019 SetPixelGreen(image,green,q);
1020 SetPixelBlue(image,blue,q);
1021 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00001022 }
1023 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
1024 status=MagickFalse;
1025 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1026 {
1027 MagickBooleanType
1028 proceed;
1029
cristyb5d5f722009-11-04 03:03:49 +00001030#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00001031 #pragma omp critical (MagickCore_ContrastImage)
1032#endif
1033 proceed=SetImageProgress(image,ContrastImageTag,progress++,image->rows);
1034 if (proceed == MagickFalse)
1035 status=MagickFalse;
1036 }
1037 }
1038 image_view=DestroyCacheView(image_view);
1039 return(status);
1040}
1041
1042/*
1043%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1044% %
1045% %
1046% %
1047% C o n t r a s t S t r e t c h I m a g e %
1048% %
1049% %
1050% %
1051%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1052%
1053% The ContrastStretchImage() is a simple image enhancement technique that
1054% attempts to improve the contrast in an image by `stretching' the range of
1055% intensity values it contains to span a desired range of values. It differs
1056% from the more sophisticated histogram equalization in that it can only
1057% apply % a linear scaling function to the image pixel values. As a result
1058% the `enhancement' is less harsh.
1059%
1060% The format of the ContrastStretchImage method is:
1061%
1062% MagickBooleanType ContrastStretchImage(Image *image,
1063% const char *levels)
1064% MagickBooleanType ContrastStretchImageChannel(Image *image,
cristybb503372010-05-27 20:51:26 +00001065% const size_t channel,const double black_point,
cristy3ed852e2009-09-05 21:47:34 +00001066% const double white_point)
1067%
1068% A description of each parameter follows:
1069%
1070% o image: the image.
1071%
1072% o channel: the channel.
1073%
1074% o black_point: the black point.
1075%
1076% o white_point: the white point.
1077%
1078% o levels: Specify the levels where the black and white points have the
1079% range of 0 to number-of-pixels (e.g. 1%, 10x90%, etc.).
1080%
1081*/
1082
1083MagickExport MagickBooleanType ContrastStretchImage(Image *image,
1084 const char *levels)
1085{
1086 double
1087 black_point,
1088 white_point;
1089
1090 GeometryInfo
1091 geometry_info;
1092
1093 MagickBooleanType
1094 status;
1095
1096 MagickStatusType
1097 flags;
1098
1099 /*
1100 Parse levels.
1101 */
1102 if (levels == (char *) NULL)
1103 return(MagickFalse);
1104 flags=ParseGeometry(levels,&geometry_info);
1105 black_point=geometry_info.rho;
1106 white_point=(double) image->columns*image->rows;
1107 if ((flags & SigmaValue) != 0)
1108 white_point=geometry_info.sigma;
1109 if ((flags & PercentValue) != 0)
1110 {
1111 black_point*=(double) QuantumRange/100.0;
1112 white_point*=(double) QuantumRange/100.0;
1113 }
1114 if ((flags & SigmaValue) == 0)
1115 white_point=(double) image->columns*image->rows-black_point;
1116 status=ContrastStretchImageChannel(image,DefaultChannels,black_point,
1117 white_point);
1118 return(status);
1119}
1120
1121MagickExport MagickBooleanType ContrastStretchImageChannel(Image *image,
1122 const ChannelType channel,const double black_point,const double white_point)
1123{
1124#define MaxRange(color) ((MagickRealType) ScaleQuantumToMap((Quantum) (color)))
1125#define ContrastStretchImageTag "ContrastStretch/Image"
1126
cristyc4c8d132010-01-07 01:58:38 +00001127 CacheView
1128 *image_view;
1129
cristy3ed852e2009-09-05 21:47:34 +00001130 double
1131 intensity;
1132
1133 ExceptionInfo
1134 *exception;
1135
cristy3ed852e2009-09-05 21:47:34 +00001136 MagickBooleanType
1137 status;
1138
cristybb503372010-05-27 20:51:26 +00001139 MagickOffsetType
1140 progress;
1141
cristy4c08aed2011-07-01 19:47:50 +00001142 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00001143 black,
1144 *histogram,
1145 *stretch_map,
1146 white;
1147
cristybb503372010-05-27 20:51:26 +00001148 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001149 i;
1150
cristybb503372010-05-27 20:51:26 +00001151 ssize_t
1152 y;
1153
cristy3ed852e2009-09-05 21:47:34 +00001154 /*
1155 Allocate histogram and stretch map.
1156 */
1157 assert(image != (Image *) NULL);
1158 assert(image->signature == MagickSignature);
1159 if (image->debug != MagickFalse)
1160 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
cristy4c08aed2011-07-01 19:47:50 +00001161 histogram=(PixelInfo *) AcquireQuantumMemory(MaxMap+1UL,
cristy3ed852e2009-09-05 21:47:34 +00001162 sizeof(*histogram));
cristy4c08aed2011-07-01 19:47:50 +00001163 stretch_map=(PixelInfo *) AcquireQuantumMemory(MaxMap+1UL,
cristy3ed852e2009-09-05 21:47:34 +00001164 sizeof(*stretch_map));
cristy4c08aed2011-07-01 19:47:50 +00001165 if ((histogram == (PixelInfo *) NULL) ||
1166 (stretch_map == (PixelInfo *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00001167 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
1168 image->filename);
1169 /*
1170 Form histogram.
1171 */
1172 status=MagickTrue;
1173 exception=(&image->exception);
1174 (void) ResetMagickMemory(histogram,0,(MaxMap+1)*sizeof(*histogram));
1175 image_view=AcquireCacheView(image);
cristybb503372010-05-27 20:51:26 +00001176 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001177 {
cristy4c08aed2011-07-01 19:47:50 +00001178 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00001179 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00001180
cristybb503372010-05-27 20:51:26 +00001181 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001182 x;
1183
1184 if (status == MagickFalse)
1185 continue;
1186 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00001187 if (p == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001188 {
1189 status=MagickFalse;
1190 continue;
1191 }
cristy3ed852e2009-09-05 21:47:34 +00001192 if (channel == DefaultChannels)
cristybb503372010-05-27 20:51:26 +00001193 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001194 {
1195 Quantum
1196 intensity;
1197
cristy4c08aed2011-07-01 19:47:50 +00001198 intensity=GetPixelIntensity(image,p);
cristy3ed852e2009-09-05 21:47:34 +00001199 histogram[ScaleQuantumToMap(intensity)].red++;
1200 histogram[ScaleQuantumToMap(intensity)].green++;
1201 histogram[ScaleQuantumToMap(intensity)].blue++;
cristy4c08aed2011-07-01 19:47:50 +00001202 histogram[ScaleQuantumToMap(intensity)].black++;
1203 p+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00001204 }
1205 else
cristybb503372010-05-27 20:51:26 +00001206 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001207 {
1208 if ((channel & RedChannel) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001209 histogram[ScaleQuantumToMap(GetPixelRed(image,p))].red++;
cristy3ed852e2009-09-05 21:47:34 +00001210 if ((channel & GreenChannel) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001211 histogram[ScaleQuantumToMap(GetPixelGreen(image,p))].green++;
cristy3ed852e2009-09-05 21:47:34 +00001212 if ((channel & BlueChannel) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001213 histogram[ScaleQuantumToMap(GetPixelBlue(image,p))].blue++;
1214 if (((channel & BlackChannel) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00001215 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +00001216 histogram[ScaleQuantumToMap(GetPixelBlack(image,p))].black++;
1217 if ((channel & OpacityChannel) != 0)
1218 histogram[ScaleQuantumToMap(GetPixelAlpha(image,p))].alpha++;
1219 p+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00001220 }
1221 }
1222 /*
1223 Find the histogram boundaries by locating the black/white levels.
1224 */
1225 black.red=0.0;
1226 white.red=MaxRange(QuantumRange);
1227 if ((channel & RedChannel) != 0)
1228 {
1229 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00001230 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +00001231 {
1232 intensity+=histogram[i].red;
1233 if (intensity > black_point)
1234 break;
1235 }
1236 black.red=(MagickRealType) i;
1237 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00001238 for (i=(ssize_t) MaxMap; i != 0; i--)
cristy3ed852e2009-09-05 21:47:34 +00001239 {
1240 intensity+=histogram[i].red;
1241 if (intensity > ((double) image->columns*image->rows-white_point))
1242 break;
1243 }
1244 white.red=(MagickRealType) i;
1245 }
1246 black.green=0.0;
1247 white.green=MaxRange(QuantumRange);
1248 if ((channel & GreenChannel) != 0)
1249 {
1250 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00001251 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +00001252 {
1253 intensity+=histogram[i].green;
1254 if (intensity > black_point)
1255 break;
1256 }
1257 black.green=(MagickRealType) i;
1258 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00001259 for (i=(ssize_t) MaxMap; i != 0; i--)
cristy3ed852e2009-09-05 21:47:34 +00001260 {
1261 intensity+=histogram[i].green;
1262 if (intensity > ((double) image->columns*image->rows-white_point))
1263 break;
1264 }
1265 white.green=(MagickRealType) i;
1266 }
1267 black.blue=0.0;
1268 white.blue=MaxRange(QuantumRange);
1269 if ((channel & BlueChannel) != 0)
1270 {
1271 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00001272 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +00001273 {
1274 intensity+=histogram[i].blue;
1275 if (intensity > black_point)
1276 break;
1277 }
1278 black.blue=(MagickRealType) i;
1279 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00001280 for (i=(ssize_t) MaxMap; i != 0; i--)
cristy3ed852e2009-09-05 21:47:34 +00001281 {
1282 intensity+=histogram[i].blue;
1283 if (intensity > ((double) image->columns*image->rows-white_point))
1284 break;
1285 }
1286 white.blue=(MagickRealType) i;
1287 }
cristy4c08aed2011-07-01 19:47:50 +00001288 black.alpha=0.0;
1289 white.alpha=MaxRange(QuantumRange);
cristy3ed852e2009-09-05 21:47:34 +00001290 if ((channel & OpacityChannel) != 0)
1291 {
1292 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00001293 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +00001294 {
cristy4c08aed2011-07-01 19:47:50 +00001295 intensity+=histogram[i].alpha;
cristy3ed852e2009-09-05 21:47:34 +00001296 if (intensity > black_point)
1297 break;
1298 }
cristy4c08aed2011-07-01 19:47:50 +00001299 black.alpha=(MagickRealType) i;
cristy3ed852e2009-09-05 21:47:34 +00001300 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00001301 for (i=(ssize_t) MaxMap; i != 0; i--)
cristy3ed852e2009-09-05 21:47:34 +00001302 {
cristy4c08aed2011-07-01 19:47:50 +00001303 intensity+=histogram[i].alpha;
cristy3ed852e2009-09-05 21:47:34 +00001304 if (intensity > ((double) image->columns*image->rows-white_point))
1305 break;
1306 }
cristy4c08aed2011-07-01 19:47:50 +00001307 white.alpha=(MagickRealType) i;
cristy3ed852e2009-09-05 21:47:34 +00001308 }
cristy4c08aed2011-07-01 19:47:50 +00001309 black.black=0.0;
1310 white.black=MaxRange(QuantumRange);
1311 if (((channel & BlackChannel) != 0) && (image->colorspace == CMYKColorspace))
cristy3ed852e2009-09-05 21:47:34 +00001312 {
1313 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00001314 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +00001315 {
cristy4c08aed2011-07-01 19:47:50 +00001316 intensity+=histogram[i].black;
cristy3ed852e2009-09-05 21:47:34 +00001317 if (intensity > black_point)
1318 break;
1319 }
cristy4c08aed2011-07-01 19:47:50 +00001320 black.black=(MagickRealType) i;
cristy3ed852e2009-09-05 21:47:34 +00001321 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00001322 for (i=(ssize_t) MaxMap; i != 0; i--)
cristy3ed852e2009-09-05 21:47:34 +00001323 {
cristy4c08aed2011-07-01 19:47:50 +00001324 intensity+=histogram[i].black;
cristy3ed852e2009-09-05 21:47:34 +00001325 if (intensity > ((double) image->columns*image->rows-white_point))
1326 break;
1327 }
cristy4c08aed2011-07-01 19:47:50 +00001328 white.black=(MagickRealType) i;
cristy3ed852e2009-09-05 21:47:34 +00001329 }
cristy4c08aed2011-07-01 19:47:50 +00001330 histogram=(PixelInfo *) RelinquishMagickMemory(histogram);
cristy3ed852e2009-09-05 21:47:34 +00001331 /*
1332 Stretch the histogram to create the stretched image mapping.
1333 */
1334 (void) ResetMagickMemory(stretch_map,0,(MaxMap+1)*sizeof(*stretch_map));
cristyb5d5f722009-11-04 03:03:49 +00001335#if defined(MAGICKCORE_OPENMP_SUPPORT)
1336 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001337#endif
cristybb503372010-05-27 20:51:26 +00001338 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +00001339 {
1340 if ((channel & RedChannel) != 0)
1341 {
cristybb503372010-05-27 20:51:26 +00001342 if (i < (ssize_t) black.red)
cristy3ed852e2009-09-05 21:47:34 +00001343 stretch_map[i].red=0.0;
1344 else
cristybb503372010-05-27 20:51:26 +00001345 if (i > (ssize_t) white.red)
cristy3ed852e2009-09-05 21:47:34 +00001346 stretch_map[i].red=(MagickRealType) QuantumRange;
1347 else
1348 if (black.red != white.red)
1349 stretch_map[i].red=(MagickRealType) ScaleMapToQuantum(
1350 (MagickRealType) (MaxMap*(i-black.red)/(white.red-black.red)));
1351 }
1352 if ((channel & GreenChannel) != 0)
1353 {
cristybb503372010-05-27 20:51:26 +00001354 if (i < (ssize_t) black.green)
cristy3ed852e2009-09-05 21:47:34 +00001355 stretch_map[i].green=0.0;
1356 else
cristybb503372010-05-27 20:51:26 +00001357 if (i > (ssize_t) white.green)
cristy3ed852e2009-09-05 21:47:34 +00001358 stretch_map[i].green=(MagickRealType) QuantumRange;
1359 else
1360 if (black.green != white.green)
1361 stretch_map[i].green=(MagickRealType) ScaleMapToQuantum(
1362 (MagickRealType) (MaxMap*(i-black.green)/(white.green-
1363 black.green)));
1364 }
1365 if ((channel & BlueChannel) != 0)
1366 {
cristybb503372010-05-27 20:51:26 +00001367 if (i < (ssize_t) black.blue)
cristy3ed852e2009-09-05 21:47:34 +00001368 stretch_map[i].blue=0.0;
1369 else
cristybb503372010-05-27 20:51:26 +00001370 if (i > (ssize_t) white.blue)
cristy3ed852e2009-09-05 21:47:34 +00001371 stretch_map[i].blue=(MagickRealType) QuantumRange;
1372 else
1373 if (black.blue != white.blue)
1374 stretch_map[i].blue=(MagickRealType) ScaleMapToQuantum(
1375 (MagickRealType) (MaxMap*(i-black.blue)/(white.blue-
1376 black.blue)));
1377 }
1378 if ((channel & OpacityChannel) != 0)
1379 {
cristy4c08aed2011-07-01 19:47:50 +00001380 if (i < (ssize_t) black.alpha)
1381 stretch_map[i].alpha=0.0;
cristy3ed852e2009-09-05 21:47:34 +00001382 else
cristy4c08aed2011-07-01 19:47:50 +00001383 if (i > (ssize_t) white.alpha)
1384 stretch_map[i].alpha=(MagickRealType) QuantumRange;
cristy3ed852e2009-09-05 21:47:34 +00001385 else
cristy4c08aed2011-07-01 19:47:50 +00001386 if (black.alpha != white.alpha)
1387 stretch_map[i].alpha=(MagickRealType) ScaleMapToQuantum(
1388 (MagickRealType) (MaxMap*(i-black.alpha)/(white.alpha-
1389 black.alpha)));
cristy3ed852e2009-09-05 21:47:34 +00001390 }
cristy4c08aed2011-07-01 19:47:50 +00001391 if (((channel & BlackChannel) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00001392 (image->colorspace == CMYKColorspace))
1393 {
cristy4c08aed2011-07-01 19:47:50 +00001394 if (i < (ssize_t) black.black)
1395 stretch_map[i].black=0.0;
cristy3ed852e2009-09-05 21:47:34 +00001396 else
cristy4c08aed2011-07-01 19:47:50 +00001397 if (i > (ssize_t) white.black)
1398 stretch_map[i].black=(MagickRealType) QuantumRange;
cristy3ed852e2009-09-05 21:47:34 +00001399 else
cristy4c08aed2011-07-01 19:47:50 +00001400 if (black.black != white.black)
1401 stretch_map[i].black=(MagickRealType) ScaleMapToQuantum(
1402 (MagickRealType) (MaxMap*(i-black.black)/(white.black-
1403 black.black)));
cristy3ed852e2009-09-05 21:47:34 +00001404 }
1405 }
1406 /*
1407 Stretch the image.
1408 */
cristy4c08aed2011-07-01 19:47:50 +00001409 if (((channel & OpacityChannel) != 0) || (((channel & BlackChannel) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00001410 (image->colorspace == CMYKColorspace)))
1411 image->storage_class=DirectClass;
1412 if (image->storage_class == PseudoClass)
1413 {
1414 /*
1415 Stretch colormap.
1416 */
cristyb5d5f722009-11-04 03:03:49 +00001417#if defined(MAGICKCORE_OPENMP_SUPPORT)
1418 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001419#endif
cristybb503372010-05-27 20:51:26 +00001420 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00001421 {
1422 if ((channel & RedChannel) != 0)
1423 {
1424 if (black.red != white.red)
cristyce70c172010-01-07 17:15:30 +00001425 image->colormap[i].red=ClampToQuantum(stretch_map[
cristy3ed852e2009-09-05 21:47:34 +00001426 ScaleQuantumToMap(image->colormap[i].red)].red);
1427 }
1428 if ((channel & GreenChannel) != 0)
1429 {
1430 if (black.green != white.green)
cristyce70c172010-01-07 17:15:30 +00001431 image->colormap[i].green=ClampToQuantum(stretch_map[
cristy3ed852e2009-09-05 21:47:34 +00001432 ScaleQuantumToMap(image->colormap[i].green)].green);
1433 }
1434 if ((channel & BlueChannel) != 0)
1435 {
1436 if (black.blue != white.blue)
cristyce70c172010-01-07 17:15:30 +00001437 image->colormap[i].blue=ClampToQuantum(stretch_map[
cristy3ed852e2009-09-05 21:47:34 +00001438 ScaleQuantumToMap(image->colormap[i].blue)].blue);
1439 }
1440 if ((channel & OpacityChannel) != 0)
1441 {
cristy4c08aed2011-07-01 19:47:50 +00001442 if (black.alpha != white.alpha)
1443 image->colormap[i].alpha=ClampToQuantum(stretch_map[
1444 ScaleQuantumToMap(image->colormap[i].alpha)].alpha);
cristy3ed852e2009-09-05 21:47:34 +00001445 }
1446 }
1447 }
1448 /*
1449 Stretch image.
1450 */
1451 status=MagickTrue;
1452 progress=0;
cristyb5d5f722009-11-04 03:03:49 +00001453#if defined(MAGICKCORE_OPENMP_SUPPORT)
1454 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001455#endif
cristybb503372010-05-27 20:51:26 +00001456 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001457 {
cristy4c08aed2011-07-01 19:47:50 +00001458 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00001459 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001460
cristy8d4629b2010-08-30 17:59:46 +00001461 register ssize_t
1462 x;
1463
cristy3ed852e2009-09-05 21:47:34 +00001464 if (status == MagickFalse)
1465 continue;
1466 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00001467 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001468 {
1469 status=MagickFalse;
1470 continue;
1471 }
cristybb503372010-05-27 20:51:26 +00001472 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001473 {
1474 if ((channel & RedChannel) != 0)
1475 {
1476 if (black.red != white.red)
cristy4c08aed2011-07-01 19:47:50 +00001477 SetPixelRed(image,ClampToQuantum(stretch_map[ScaleQuantumToMap(
1478 GetPixelRed(image,q))].red),q);
cristy3ed852e2009-09-05 21:47:34 +00001479 }
1480 if ((channel & GreenChannel) != 0)
1481 {
1482 if (black.green != white.green)
cristy4c08aed2011-07-01 19:47:50 +00001483 SetPixelGreen(image,ClampToQuantum(stretch_map[ScaleQuantumToMap(
1484 GetPixelGreen(image,q))].green),q);
cristy3ed852e2009-09-05 21:47:34 +00001485 }
1486 if ((channel & BlueChannel) != 0)
1487 {
1488 if (black.blue != white.blue)
cristy4c08aed2011-07-01 19:47:50 +00001489 SetPixelBlue(image,ClampToQuantum(stretch_map[ScaleQuantumToMap(
1490 GetPixelBlue(image,q))].blue),q);
1491 }
1492 if (((channel & BlackChannel) != 0) &&
1493 (image->colorspace == CMYKColorspace))
1494 {
1495 if (black.black != white.black)
1496 SetPixelBlack(image,ClampToQuantum(stretch_map[ScaleQuantumToMap(
1497 GetPixelBlack(image,q))].black),q);
cristy3ed852e2009-09-05 21:47:34 +00001498 }
1499 if ((channel & OpacityChannel) != 0)
1500 {
cristy4c08aed2011-07-01 19:47:50 +00001501 if (black.alpha != white.alpha)
1502 SetPixelAlpha(image,ClampToQuantum(stretch_map[ScaleQuantumToMap(
1503 GetPixelAlpha(image,q))].alpha),q);
cristy3ed852e2009-09-05 21:47:34 +00001504 }
cristy4c08aed2011-07-01 19:47:50 +00001505 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00001506 }
1507 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
1508 status=MagickFalse;
1509 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1510 {
1511 MagickBooleanType
1512 proceed;
1513
cristyb5d5f722009-11-04 03:03:49 +00001514#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00001515 #pragma omp critical (MagickCore_ContrastStretchImageChannel)
1516#endif
1517 proceed=SetImageProgress(image,ContrastStretchImageTag,progress++,
1518 image->rows);
1519 if (proceed == MagickFalse)
1520 status=MagickFalse;
1521 }
1522 }
1523 image_view=DestroyCacheView(image_view);
cristy4c08aed2011-07-01 19:47:50 +00001524 stretch_map=(PixelInfo *) RelinquishMagickMemory(stretch_map);
cristy3ed852e2009-09-05 21:47:34 +00001525 return(status);
1526}
1527
1528/*
1529%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1530% %
1531% %
1532% %
1533% E n h a n c e I m a g e %
1534% %
1535% %
1536% %
1537%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1538%
1539% EnhanceImage() applies a digital filter that improves the quality of a
1540% noisy image.
1541%
1542% The format of the EnhanceImage method is:
1543%
1544% Image *EnhanceImage(const Image *image,ExceptionInfo *exception)
1545%
1546% A description of each parameter follows:
1547%
1548% o image: the image.
1549%
1550% o exception: return any errors or warnings in this structure.
1551%
1552*/
1553MagickExport Image *EnhanceImage(const Image *image,ExceptionInfo *exception)
1554{
1555#define Enhance(weight) \
cristy4c08aed2011-07-01 19:47:50 +00001556 mean=((MagickRealType) GetPixelRed(image,r)+pixel.red)/2; \
1557 distance=(MagickRealType) GetPixelRed(image,r)-(MagickRealType) pixel.red; \
cristy3ed852e2009-09-05 21:47:34 +00001558 distance_squared=QuantumScale*(2.0*((MagickRealType) QuantumRange+1.0)+ \
1559 mean)*distance*distance; \
cristy4c08aed2011-07-01 19:47:50 +00001560 mean=((MagickRealType) GetPixelGreen(image,r)+pixel.green)/2; \
1561 distance=(MagickRealType) GetPixelGreen(image,r)- \
1562 (MagickRealType) pixel.green; \
cristy3ed852e2009-09-05 21:47:34 +00001563 distance_squared+=4.0*distance*distance; \
cristy4c08aed2011-07-01 19:47:50 +00001564 mean=((MagickRealType) GetPixelBlue(image,r)+pixel.blue)/2; \
1565 distance=(MagickRealType) GetPixelBlue(image,r)- \
1566 (MagickRealType) pixel.blue; \
cristy3ed852e2009-09-05 21:47:34 +00001567 distance_squared+=QuantumScale*(3.0*((MagickRealType) \
1568 QuantumRange+1.0)-1.0-mean)*distance*distance; \
cristy4c08aed2011-07-01 19:47:50 +00001569 mean=((MagickRealType) GetPixelAlpha(image,r)+pixel.alpha)/2; \
1570 distance=(MagickRealType) GetPixelAlpha(image,r)-(MagickRealType) pixel.alpha; \
cristy3ed852e2009-09-05 21:47:34 +00001571 distance_squared+=QuantumScale*(3.0*((MagickRealType) \
1572 QuantumRange+1.0)-1.0-mean)*distance*distance; \
1573 if (distance_squared < ((MagickRealType) QuantumRange*(MagickRealType) \
1574 QuantumRange/25.0f)) \
1575 { \
cristy4c08aed2011-07-01 19:47:50 +00001576 aggregate.red+=(weight)*GetPixelRed(image,r); \
1577 aggregate.green+=(weight)*GetPixelGreen(image,r); \
1578 aggregate.blue+=(weight)*GetPixelBlue(image,r); \
1579 aggregate.alpha+=(weight)*GetPixelAlpha(image,r); \
cristy3ed852e2009-09-05 21:47:34 +00001580 total_weight+=(weight); \
1581 } \
1582 r++;
1583#define EnhanceImageTag "Enhance/Image"
1584
cristyc4c8d132010-01-07 01:58:38 +00001585 CacheView
1586 *enhance_view,
1587 *image_view;
1588
cristy3ed852e2009-09-05 21:47:34 +00001589 Image
1590 *enhance_image;
1591
cristy3ed852e2009-09-05 21:47:34 +00001592 MagickBooleanType
1593 status;
1594
cristybb503372010-05-27 20:51:26 +00001595 MagickOffsetType
1596 progress;
1597
cristy4c08aed2011-07-01 19:47:50 +00001598 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00001599 zero;
1600
cristybb503372010-05-27 20:51:26 +00001601 ssize_t
1602 y;
1603
cristy3ed852e2009-09-05 21:47:34 +00001604 /*
1605 Initialize enhanced image attributes.
1606 */
1607 assert(image != (const Image *) NULL);
1608 assert(image->signature == MagickSignature);
1609 if (image->debug != MagickFalse)
1610 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1611 assert(exception != (ExceptionInfo *) NULL);
1612 assert(exception->signature == MagickSignature);
1613 if ((image->columns < 5) || (image->rows < 5))
1614 return((Image *) NULL);
1615 enhance_image=CloneImage(image,image->columns,image->rows,MagickTrue,
1616 exception);
1617 if (enhance_image == (Image *) NULL)
1618 return((Image *) NULL);
1619 if (SetImageStorageClass(enhance_image,DirectClass) == MagickFalse)
1620 {
1621 InheritException(exception,&enhance_image->exception);
1622 enhance_image=DestroyImage(enhance_image);
1623 return((Image *) NULL);
1624 }
1625 /*
1626 Enhance image.
1627 */
1628 status=MagickTrue;
1629 progress=0;
1630 (void) ResetMagickMemory(&zero,0,sizeof(zero));
1631 image_view=AcquireCacheView(image);
1632 enhance_view=AcquireCacheView(enhance_image);
cristyb5d5f722009-11-04 03:03:49 +00001633#if defined(MAGICKCORE_OPENMP_SUPPORT)
1634 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001635#endif
cristybb503372010-05-27 20:51:26 +00001636 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001637 {
cristy4c08aed2011-07-01 19:47:50 +00001638 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00001639 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00001640
cristy4c08aed2011-07-01 19:47:50 +00001641 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00001642 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001643
cristy8d4629b2010-08-30 17:59:46 +00001644 register ssize_t
1645 x;
1646
cristy3ed852e2009-09-05 21:47:34 +00001647 /*
1648 Read another scan line.
1649 */
1650 if (status == MagickFalse)
1651 continue;
1652 p=GetCacheViewVirtualPixels(image_view,-2,y-2,image->columns+4,5,exception);
1653 q=QueueCacheViewAuthenticPixels(enhance_view,0,y,enhance_image->columns,1,
1654 exception);
cristy4c08aed2011-07-01 19:47:50 +00001655 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00001656 {
1657 status=MagickFalse;
1658 continue;
1659 }
cristybb503372010-05-27 20:51:26 +00001660 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001661 {
cristy4c08aed2011-07-01 19:47:50 +00001662 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00001663 aggregate;
1664
1665 MagickRealType
1666 distance,
1667 distance_squared,
1668 mean,
1669 total_weight;
1670
1671 PixelPacket
1672 pixel;
1673
cristy4c08aed2011-07-01 19:47:50 +00001674 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00001675 *restrict r;
cristy3ed852e2009-09-05 21:47:34 +00001676
1677 /*
1678 Compute weighted average of target pixel color components.
1679 */
1680 aggregate=zero;
1681 total_weight=0.0;
1682 r=p+2*(image->columns+4)+2;
cristy4c08aed2011-07-01 19:47:50 +00001683 GetPixelPacket(image,r,&pixel);
cristy3ed852e2009-09-05 21:47:34 +00001684 r=p;
1685 Enhance(5.0); Enhance(8.0); Enhance(10.0); Enhance(8.0); Enhance(5.0);
1686 r=p+(image->columns+4);
1687 Enhance(8.0); Enhance(20.0); Enhance(40.0); Enhance(20.0); Enhance(8.0);
1688 r=p+2*(image->columns+4);
1689 Enhance(10.0); Enhance(40.0); Enhance(80.0); Enhance(40.0); Enhance(10.0);
1690 r=p+3*(image->columns+4);
1691 Enhance(8.0); Enhance(20.0); Enhance(40.0); Enhance(20.0); Enhance(8.0);
1692 r=p+4*(image->columns+4);
1693 Enhance(5.0); Enhance(8.0); Enhance(10.0); Enhance(8.0); Enhance(5.0);
cristy4c08aed2011-07-01 19:47:50 +00001694 SetPixelRed(enhance_image,(Quantum) ((aggregate.red+
1695 (total_weight/2)-1)/total_weight),q);
1696 SetPixelGreen(enhance_image,(Quantum) ((aggregate.green+
1697 (total_weight/2)-1)/total_weight),q);
1698 SetPixelBlue(enhance_image,(Quantum) ((aggregate.blue+
1699 (total_weight/2)-1)/total_weight),q);
1700 SetPixelAlpha(enhance_image,(Quantum) ((aggregate.alpha+
1701 (total_weight/2)-1)/total_weight),q);
1702 p+=GetPixelChannels(image);
1703 q+=GetPixelChannels(enhance_image);
cristy3ed852e2009-09-05 21:47:34 +00001704 }
1705 if (SyncCacheViewAuthenticPixels(enhance_view,exception) == MagickFalse)
1706 status=MagickFalse;
1707 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1708 {
1709 MagickBooleanType
1710 proceed;
1711
cristyb5d5f722009-11-04 03:03:49 +00001712#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00001713 #pragma omp critical (MagickCore_EnhanceImage)
1714#endif
1715 proceed=SetImageProgress(image,EnhanceImageTag,progress++,image->rows);
1716 if (proceed == MagickFalse)
1717 status=MagickFalse;
1718 }
1719 }
1720 enhance_view=DestroyCacheView(enhance_view);
1721 image_view=DestroyCacheView(image_view);
1722 return(enhance_image);
1723}
1724
1725/*
1726%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1727% %
1728% %
1729% %
1730% E q u a l i z e I m a g e %
1731% %
1732% %
1733% %
1734%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1735%
1736% EqualizeImage() applies a histogram equalization to the image.
1737%
1738% The format of the EqualizeImage method is:
1739%
1740% MagickBooleanType EqualizeImage(Image *image)
1741% MagickBooleanType EqualizeImageChannel(Image *image,
1742% const ChannelType channel)
1743%
1744% A description of each parameter follows:
1745%
1746% o image: the image.
1747%
1748% o channel: the channel.
1749%
1750*/
1751
1752MagickExport MagickBooleanType EqualizeImage(Image *image)
1753{
1754 return(EqualizeImageChannel(image,DefaultChannels));
1755}
1756
1757MagickExport MagickBooleanType EqualizeImageChannel(Image *image,
1758 const ChannelType channel)
1759{
1760#define EqualizeImageTag "Equalize/Image"
1761
cristyc4c8d132010-01-07 01:58:38 +00001762 CacheView
1763 *image_view;
1764
cristy3ed852e2009-09-05 21:47:34 +00001765 ExceptionInfo
1766 *exception;
1767
cristy3ed852e2009-09-05 21:47:34 +00001768 MagickBooleanType
1769 status;
1770
cristybb503372010-05-27 20:51:26 +00001771 MagickOffsetType
1772 progress;
1773
cristy4c08aed2011-07-01 19:47:50 +00001774 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00001775 black,
1776 *equalize_map,
1777 *histogram,
1778 intensity,
1779 *map,
1780 white;
1781
cristybb503372010-05-27 20:51:26 +00001782 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001783 i;
1784
cristybb503372010-05-27 20:51:26 +00001785 ssize_t
1786 y;
1787
cristy3ed852e2009-09-05 21:47:34 +00001788 /*
1789 Allocate and initialize histogram arrays.
1790 */
1791 assert(image != (Image *) NULL);
1792 assert(image->signature == MagickSignature);
1793 if (image->debug != MagickFalse)
1794 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
cristy4c08aed2011-07-01 19:47:50 +00001795 equalize_map=(PixelInfo *) AcquireQuantumMemory(MaxMap+1UL,
cristy3ed852e2009-09-05 21:47:34 +00001796 sizeof(*equalize_map));
cristy4c08aed2011-07-01 19:47:50 +00001797 histogram=(PixelInfo *) AcquireQuantumMemory(MaxMap+1UL,
cristy3ed852e2009-09-05 21:47:34 +00001798 sizeof(*histogram));
cristy4c08aed2011-07-01 19:47:50 +00001799 map=(PixelInfo *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*map));
1800 if ((equalize_map == (PixelInfo *) NULL) ||
1801 (histogram == (PixelInfo *) NULL) ||
1802 (map == (PixelInfo *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00001803 {
cristy4c08aed2011-07-01 19:47:50 +00001804 if (map != (PixelInfo *) NULL)
1805 map=(PixelInfo *) RelinquishMagickMemory(map);
1806 if (histogram != (PixelInfo *) NULL)
1807 histogram=(PixelInfo *) RelinquishMagickMemory(histogram);
1808 if (equalize_map != (PixelInfo *) NULL)
1809 equalize_map=(PixelInfo *) RelinquishMagickMemory(equalize_map);
cristy3ed852e2009-09-05 21:47:34 +00001810 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
1811 image->filename);
1812 }
1813 /*
1814 Form histogram.
1815 */
1816 (void) ResetMagickMemory(histogram,0,(MaxMap+1)*sizeof(*histogram));
1817 exception=(&image->exception);
cristybb503372010-05-27 20:51:26 +00001818 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001819 {
cristy4c08aed2011-07-01 19:47:50 +00001820 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00001821 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00001822
cristybb503372010-05-27 20:51:26 +00001823 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001824 x;
1825
1826 p=GetVirtualPixels(image,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00001827 if (p == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001828 break;
cristybb503372010-05-27 20:51:26 +00001829 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001830 {
1831 if ((channel & RedChannel) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001832 histogram[ScaleQuantumToMap(GetPixelRed(image,p))].red++;
cristy3ed852e2009-09-05 21:47:34 +00001833 if ((channel & GreenChannel) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001834 histogram[ScaleQuantumToMap(GetPixelGreen(image,p))].green++;
cristy3ed852e2009-09-05 21:47:34 +00001835 if ((channel & BlueChannel) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001836 histogram[ScaleQuantumToMap(GetPixelBlue(image,p))].blue++;
1837 if (((channel & BlackChannel) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00001838 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +00001839 histogram[ScaleQuantumToMap(GetPixelBlack(image,p))].black++;
1840 if ((channel & OpacityChannel) != 0)
1841 histogram[ScaleQuantumToMap(GetPixelAlpha(image,p))].alpha++;
1842 p+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00001843 }
1844 }
1845 /*
1846 Integrate the histogram to get the equalization map.
1847 */
1848 (void) ResetMagickMemory(&intensity,0,sizeof(intensity));
cristybb503372010-05-27 20:51:26 +00001849 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +00001850 {
1851 if ((channel & RedChannel) != 0)
1852 intensity.red+=histogram[i].red;
1853 if ((channel & GreenChannel) != 0)
1854 intensity.green+=histogram[i].green;
1855 if ((channel & BlueChannel) != 0)
1856 intensity.blue+=histogram[i].blue;
cristy4c08aed2011-07-01 19:47:50 +00001857 if (((channel & BlackChannel) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00001858 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +00001859 intensity.black+=histogram[i].black;
1860 if ((channel & OpacityChannel) != 0)
1861 intensity.alpha+=histogram[i].alpha;
cristy3ed852e2009-09-05 21:47:34 +00001862 map[i]=intensity;
1863 }
1864 black=map[0];
1865 white=map[(int) MaxMap];
1866 (void) ResetMagickMemory(equalize_map,0,(MaxMap+1)*sizeof(*equalize_map));
cristyb5d5f722009-11-04 03:03:49 +00001867#if defined(MAGICKCORE_OPENMP_SUPPORT)
1868 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001869#endif
cristybb503372010-05-27 20:51:26 +00001870 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +00001871 {
1872 if (((channel & RedChannel) != 0) && (white.red != black.red))
1873 equalize_map[i].red=(MagickRealType) ScaleMapToQuantum((MagickRealType)
1874 ((MaxMap*(map[i].red-black.red))/(white.red-black.red)));
1875 if (((channel & GreenChannel) != 0) && (white.green != black.green))
1876 equalize_map[i].green=(MagickRealType) ScaleMapToQuantum((MagickRealType)
1877 ((MaxMap*(map[i].green-black.green))/(white.green-black.green)));
1878 if (((channel & BlueChannel) != 0) && (white.blue != black.blue))
1879 equalize_map[i].blue=(MagickRealType) ScaleMapToQuantum((MagickRealType)
1880 ((MaxMap*(map[i].blue-black.blue))/(white.blue-black.blue)));
cristy4c08aed2011-07-01 19:47:50 +00001881 if ((((channel & BlackChannel) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00001882 (image->colorspace == CMYKColorspace)) &&
cristy4c08aed2011-07-01 19:47:50 +00001883 (white.black != black.black))
1884 equalize_map[i].black=(MagickRealType) ScaleMapToQuantum((MagickRealType)
1885 ((MaxMap*(map[i].black-black.black))/(white.black-black.black)));
1886 if (((channel & OpacityChannel) != 0) && (white.alpha != black.alpha))
1887 equalize_map[i].alpha=(MagickRealType) ScaleMapToQuantum(
1888 (MagickRealType) ((MaxMap*(map[i].alpha-black.alpha))/
1889 (white.alpha-black.alpha)));
cristy3ed852e2009-09-05 21:47:34 +00001890 }
cristy4c08aed2011-07-01 19:47:50 +00001891 histogram=(PixelInfo *) RelinquishMagickMemory(histogram);
1892 map=(PixelInfo *) RelinquishMagickMemory(map);
cristy3ed852e2009-09-05 21:47:34 +00001893 if (image->storage_class == PseudoClass)
1894 {
1895 /*
1896 Equalize colormap.
1897 */
cristyb5d5f722009-11-04 03:03:49 +00001898#if defined(MAGICKCORE_OPENMP_SUPPORT)
1899 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001900#endif
cristybb503372010-05-27 20:51:26 +00001901 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00001902 {
1903 if (((channel & RedChannel) != 0) && (white.red != black.red))
cristyce70c172010-01-07 17:15:30 +00001904 image->colormap[i].red=ClampToQuantum(equalize_map[
cristy3ed852e2009-09-05 21:47:34 +00001905 ScaleQuantumToMap(image->colormap[i].red)].red);
1906 if (((channel & GreenChannel) != 0) && (white.green != black.green))
cristyce70c172010-01-07 17:15:30 +00001907 image->colormap[i].green=ClampToQuantum(equalize_map[
cristy3ed852e2009-09-05 21:47:34 +00001908 ScaleQuantumToMap(image->colormap[i].green)].green);
1909 if (((channel & BlueChannel) != 0) && (white.blue != black.blue))
cristyce70c172010-01-07 17:15:30 +00001910 image->colormap[i].blue=ClampToQuantum(equalize_map[
cristy3ed852e2009-09-05 21:47:34 +00001911 ScaleQuantumToMap(image->colormap[i].blue)].blue);
1912 if (((channel & OpacityChannel) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +00001913 (white.alpha != black.alpha))
1914 image->colormap[i].alpha=ClampToQuantum(equalize_map[
1915 ScaleQuantumToMap(image->colormap[i].alpha)].alpha);
cristy3ed852e2009-09-05 21:47:34 +00001916 }
1917 }
1918 /*
1919 Equalize image.
1920 */
1921 status=MagickTrue;
1922 progress=0;
1923 exception=(&image->exception);
1924 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00001925#if defined(MAGICKCORE_OPENMP_SUPPORT)
1926 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001927#endif
cristybb503372010-05-27 20:51:26 +00001928 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001929 {
cristy4c08aed2011-07-01 19:47:50 +00001930 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00001931 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001932
cristy8d4629b2010-08-30 17:59:46 +00001933 register ssize_t
1934 x;
1935
cristy3ed852e2009-09-05 21:47:34 +00001936 if (status == MagickFalse)
1937 continue;
1938 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00001939 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001940 {
1941 status=MagickFalse;
1942 continue;
1943 }
cristybb503372010-05-27 20:51:26 +00001944 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001945 {
1946 if (((channel & RedChannel) != 0) && (white.red != black.red))
cristy4c08aed2011-07-01 19:47:50 +00001947 SetPixelRed(image,ClampToQuantum(equalize_map[
1948 ScaleQuantumToMap(GetPixelRed(image,q))].red),q);
cristy3ed852e2009-09-05 21:47:34 +00001949 if (((channel & GreenChannel) != 0) && (white.green != black.green))
cristy4c08aed2011-07-01 19:47:50 +00001950 SetPixelGreen(image,ClampToQuantum(equalize_map[
1951 ScaleQuantumToMap(GetPixelGreen(image,q))].green),q);
cristy3ed852e2009-09-05 21:47:34 +00001952 if (((channel & BlueChannel) != 0) && (white.blue != black.blue))
cristy4c08aed2011-07-01 19:47:50 +00001953 SetPixelBlue(image,ClampToQuantum(equalize_map[
1954 ScaleQuantumToMap(GetPixelBlue(image,q))].blue),q);
1955 if ((((channel & BlackChannel) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00001956 (image->colorspace == CMYKColorspace)) &&
cristy4c08aed2011-07-01 19:47:50 +00001957 (white.black != black.black))
1958 SetPixelBlack(image,ClampToQuantum(equalize_map[
1959 ScaleQuantumToMap(GetPixelBlack(image,q))].black),q);
1960 if (((channel & OpacityChannel) != 0) && (white.alpha != black.alpha))
1961 SetPixelAlpha(image,ClampToQuantum(equalize_map[
1962 ScaleQuantumToMap(GetPixelAlpha(image,q))].alpha),q);
1963 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00001964 }
1965 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
1966 status=MagickFalse;
1967 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1968 {
1969 MagickBooleanType
1970 proceed;
1971
cristyb5d5f722009-11-04 03:03:49 +00001972#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00001973 #pragma omp critical (MagickCore_EqualizeImageChannel)
1974#endif
1975 proceed=SetImageProgress(image,EqualizeImageTag,progress++,image->rows);
1976 if (proceed == MagickFalse)
1977 status=MagickFalse;
1978 }
1979 }
1980 image_view=DestroyCacheView(image_view);
cristy4c08aed2011-07-01 19:47:50 +00001981 equalize_map=(PixelInfo *) RelinquishMagickMemory(equalize_map);
cristy3ed852e2009-09-05 21:47:34 +00001982 return(status);
1983}
1984
1985/*
1986%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1987% %
1988% %
1989% %
1990% G a m m a I m a g e %
1991% %
1992% %
1993% %
1994%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1995%
1996% GammaImage() gamma-corrects a particular image channel. The same
1997% image viewed on different devices will have perceptual differences in the
1998% way the image's intensities are represented on the screen. Specify
1999% individual gamma levels for the red, green, and blue channels, or adjust
2000% all three with the gamma parameter. Values typically range from 0.8 to 2.3.
2001%
2002% You can also reduce the influence of a particular channel with a gamma
2003% value of 0.
2004%
2005% The format of the GammaImage method is:
2006%
cristya6360142011-03-23 23:08:04 +00002007% MagickBooleanType GammaImage(Image *image,const char *level)
cristy3ed852e2009-09-05 21:47:34 +00002008% MagickBooleanType GammaImageChannel(Image *image,
2009% const ChannelType channel,const double gamma)
2010%
2011% A description of each parameter follows:
2012%
2013% o image: the image.
2014%
2015% o channel: the channel.
2016%
cristya6360142011-03-23 23:08:04 +00002017% o level: the image gamma as a string (e.g. 1.6,1.2,1.0).
2018%
cristy3ed852e2009-09-05 21:47:34 +00002019% o gamma: the image gamma.
2020%
2021*/
2022MagickExport MagickBooleanType GammaImage(Image *image,const char *level)
2023{
2024 GeometryInfo
2025 geometry_info;
2026
cristy4c08aed2011-07-01 19:47:50 +00002027 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00002028 gamma;
2029
2030 MagickStatusType
2031 flags,
2032 status;
2033
2034 assert(image != (Image *) NULL);
2035 assert(image->signature == MagickSignature);
2036 if (image->debug != MagickFalse)
2037 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2038 if (level == (char *) NULL)
2039 return(MagickFalse);
2040 flags=ParseGeometry(level,&geometry_info);
2041 gamma.red=geometry_info.rho;
2042 gamma.green=geometry_info.sigma;
2043 if ((flags & SigmaValue) == 0)
2044 gamma.green=gamma.red;
2045 gamma.blue=geometry_info.xi;
2046 if ((flags & XiValue) == 0)
2047 gamma.blue=gamma.red;
2048 if ((gamma.red == 1.0) && (gamma.green == 1.0) && (gamma.blue == 1.0))
2049 return(MagickTrue);
2050 if ((gamma.red == gamma.green) && (gamma.green == gamma.blue))
2051 status=GammaImageChannel(image,(const ChannelType) (RedChannel |
2052 GreenChannel | BlueChannel),(double) gamma.red);
2053 else
2054 {
2055 status=GammaImageChannel(image,RedChannel,(double) gamma.red);
2056 status|=GammaImageChannel(image,GreenChannel,(double) gamma.green);
2057 status|=GammaImageChannel(image,BlueChannel,(double) gamma.blue);
2058 }
2059 return(status != 0 ? MagickTrue : MagickFalse);
2060}
2061
2062MagickExport MagickBooleanType GammaImageChannel(Image *image,
2063 const ChannelType channel,const double gamma)
2064{
2065#define GammaCorrectImageTag "GammaCorrect/Image"
2066
cristyc4c8d132010-01-07 01:58:38 +00002067 CacheView
2068 *image_view;
2069
cristy3ed852e2009-09-05 21:47:34 +00002070 ExceptionInfo
2071 *exception;
2072
cristy3ed852e2009-09-05 21:47:34 +00002073 MagickBooleanType
2074 status;
2075
cristybb503372010-05-27 20:51:26 +00002076 MagickOffsetType
2077 progress;
2078
cristy3ed852e2009-09-05 21:47:34 +00002079 Quantum
2080 *gamma_map;
2081
cristybb503372010-05-27 20:51:26 +00002082 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002083 i;
2084
cristybb503372010-05-27 20:51:26 +00002085 ssize_t
2086 y;
2087
cristy3ed852e2009-09-05 21:47:34 +00002088 /*
2089 Allocate and initialize gamma maps.
2090 */
2091 assert(image != (Image *) NULL);
2092 assert(image->signature == MagickSignature);
2093 if (image->debug != MagickFalse)
2094 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2095 if (gamma == 1.0)
2096 return(MagickTrue);
2097 gamma_map=(Quantum *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*gamma_map));
2098 if (gamma_map == (Quantum *) NULL)
2099 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
2100 image->filename);
2101 (void) ResetMagickMemory(gamma_map,0,(MaxMap+1)*sizeof(*gamma_map));
2102 if (gamma != 0.0)
cristyb5d5f722009-11-04 03:03:49 +00002103#if defined(MAGICKCORE_OPENMP_SUPPORT)
2104 #pragma omp parallel for schedule(dynamic,4)
cristy3ed852e2009-09-05 21:47:34 +00002105#endif
cristybb503372010-05-27 20:51:26 +00002106 for (i=0; i <= (ssize_t) MaxMap; i++)
cristyce70c172010-01-07 17:15:30 +00002107 gamma_map[i]=ClampToQuantum((MagickRealType) ScaleMapToQuantum((
cristy3ed852e2009-09-05 21:47:34 +00002108 MagickRealType) (MaxMap*pow((double) i/MaxMap,1.0/gamma))));
2109 if (image->storage_class == PseudoClass)
2110 {
2111 /*
2112 Gamma-correct colormap.
2113 */
cristyb5d5f722009-11-04 03:03:49 +00002114#if defined(MAGICKCORE_OPENMP_SUPPORT)
2115 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002116#endif
cristybb503372010-05-27 20:51:26 +00002117 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00002118 {
2119 if ((channel & RedChannel) != 0)
2120 image->colormap[i].red=gamma_map[
2121 ScaleQuantumToMap(image->colormap[i].red)];
2122 if ((channel & GreenChannel) != 0)
2123 image->colormap[i].green=gamma_map[
2124 ScaleQuantumToMap(image->colormap[i].green)];
2125 if ((channel & BlueChannel) != 0)
2126 image->colormap[i].blue=gamma_map[
2127 ScaleQuantumToMap(image->colormap[i].blue)];
2128 if ((channel & OpacityChannel) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002129 image->colormap[i].alpha=gamma_map[
2130 ScaleQuantumToMap(image->colormap[i].alpha)];
cristy3ed852e2009-09-05 21:47:34 +00002131 }
2132 }
2133 /*
2134 Gamma-correct image.
2135 */
2136 status=MagickTrue;
2137 progress=0;
2138 exception=(&image->exception);
2139 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00002140#if defined(MAGICKCORE_OPENMP_SUPPORT)
2141 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002142#endif
cristybb503372010-05-27 20:51:26 +00002143 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002144 {
cristy4c08aed2011-07-01 19:47:50 +00002145 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00002146 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002147
cristy8d4629b2010-08-30 17:59:46 +00002148 register ssize_t
2149 x;
2150
cristy3ed852e2009-09-05 21:47:34 +00002151 if (status == MagickFalse)
2152 continue;
2153 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00002154 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00002155 {
2156 status=MagickFalse;
2157 continue;
2158 }
cristybb503372010-05-27 20:51:26 +00002159 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002160 {
cristy6cbd7f52009-10-17 16:06:51 +00002161 if (channel == DefaultChannels)
cristy3ed852e2009-09-05 21:47:34 +00002162 {
cristy4c08aed2011-07-01 19:47:50 +00002163 SetPixelRed(image,gamma_map[
2164 ScaleQuantumToMap(GetPixelRed(image,q))],q);
2165 SetPixelGreen(image,gamma_map[
2166 ScaleQuantumToMap(GetPixelGreen(image,q))],q);
2167 SetPixelBlue(image,gamma_map[ScaleQuantumToMap(
2168 GetPixelBlue(image,q))],q);
cristy6cbd7f52009-10-17 16:06:51 +00002169 }
2170 else
2171 {
2172 if ((channel & RedChannel) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002173 SetPixelRed(image,gamma_map[ScaleQuantumToMap(
2174 GetPixelRed(image,q))],q);
cristy6cbd7f52009-10-17 16:06:51 +00002175 if ((channel & GreenChannel) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002176 SetPixelGreen(image,gamma_map[
2177 ScaleQuantumToMap(GetPixelGreen(image,q))],q);
cristy6cbd7f52009-10-17 16:06:51 +00002178 if ((channel & BlueChannel) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002179 SetPixelBlue(image,gamma_map[
2180 ScaleQuantumToMap(GetPixelBlue(image,q))],q);
cristy6cbd7f52009-10-17 16:06:51 +00002181 if ((channel & OpacityChannel) != 0)
2182 {
2183 if (image->matte == MagickFalse)
cristy4c08aed2011-07-01 19:47:50 +00002184 SetPixelAlpha(image,gamma_map[
2185 ScaleQuantumToMap(GetPixelAlpha(image,q))],q);
cristy6cbd7f52009-10-17 16:06:51 +00002186 else
cristy4c08aed2011-07-01 19:47:50 +00002187 SetPixelAlpha(image,gamma_map[
2188 ScaleQuantumToMap(GetPixelAlpha(image,q))],q);
cristy6cbd7f52009-10-17 16:06:51 +00002189 }
cristy3ed852e2009-09-05 21:47:34 +00002190 }
cristy4c08aed2011-07-01 19:47:50 +00002191 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00002192 }
cristy4c08aed2011-07-01 19:47:50 +00002193 if (((channel & BlackChannel) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00002194 (image->colorspace == CMYKColorspace))
cristybb503372010-05-27 20:51:26 +00002195 for (x=0; x < (ssize_t) image->columns; x++)
cristy4c08aed2011-07-01 19:47:50 +00002196 SetPixelBlack(image,gamma_map[ScaleQuantumToMap(
2197 GetPixelBlack(image,q))],q);
cristy3ed852e2009-09-05 21:47:34 +00002198 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2199 status=MagickFalse;
2200 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2201 {
2202 MagickBooleanType
2203 proceed;
2204
cristyb5d5f722009-11-04 03:03:49 +00002205#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00002206 #pragma omp critical (MagickCore_GammaImageChannel)
2207#endif
2208 proceed=SetImageProgress(image,GammaCorrectImageTag,progress++,
2209 image->rows);
2210 if (proceed == MagickFalse)
2211 status=MagickFalse;
2212 }
2213 }
2214 image_view=DestroyCacheView(image_view);
2215 gamma_map=(Quantum *) RelinquishMagickMemory(gamma_map);
2216 if (image->gamma != 0.0)
2217 image->gamma*=gamma;
2218 return(status);
2219}
2220
2221/*
2222%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2223% %
2224% %
2225% %
2226% H a l d C l u t I m a g e %
2227% %
2228% %
2229% %
2230%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2231%
2232% HaldClutImage() applies a Hald color lookup table to the image. A Hald
2233% color lookup table is a 3-dimensional color cube mapped to 2 dimensions.
2234% Create it with the HALD coder. You can apply any color transformation to
2235% the Hald image and then use this method to apply the transform to the
2236% image.
2237%
2238% The format of the HaldClutImage method is:
2239%
2240% MagickBooleanType HaldClutImage(Image *image,Image *hald_image)
2241% MagickBooleanType HaldClutImageChannel(Image *image,
2242% const ChannelType channel,Image *hald_image)
2243%
2244% A description of each parameter follows:
2245%
2246% o image: the image, which is replaced by indexed CLUT values
2247%
2248% o hald_image: the color lookup table image for replacement color values.
2249%
2250% o channel: the channel.
2251%
2252*/
2253
2254static inline size_t MagickMin(const size_t x,const size_t y)
2255{
2256 if (x < y)
2257 return(x);
2258 return(y);
2259}
2260
2261MagickExport MagickBooleanType HaldClutImage(Image *image,
2262 const Image *hald_image)
2263{
2264 return(HaldClutImageChannel(image,DefaultChannels,hald_image));
2265}
2266
2267MagickExport MagickBooleanType HaldClutImageChannel(Image *image,
2268 const ChannelType channel,const Image *hald_image)
2269{
2270#define HaldClutImageTag "Clut/Image"
2271
2272 typedef struct _HaldInfo
2273 {
2274 MagickRealType
2275 x,
2276 y,
2277 z;
2278 } HaldInfo;
2279
cristyfa112112010-01-04 17:48:07 +00002280 CacheView
cristyd551fbc2011-03-31 18:07:46 +00002281 *hald_view,
cristyfa112112010-01-04 17:48:07 +00002282 *image_view;
2283
cristy3ed852e2009-09-05 21:47:34 +00002284 double
2285 width;
2286
2287 ExceptionInfo
2288 *exception;
2289
cristy3ed852e2009-09-05 21:47:34 +00002290 MagickBooleanType
2291 status;
2292
cristybb503372010-05-27 20:51:26 +00002293 MagickOffsetType
2294 progress;
2295
cristy4c08aed2011-07-01 19:47:50 +00002296 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00002297 zero;
2298
cristy3ed852e2009-09-05 21:47:34 +00002299 size_t
2300 cube_size,
2301 length,
2302 level;
2303
cristybb503372010-05-27 20:51:26 +00002304 ssize_t
2305 y;
2306
cristy3ed852e2009-09-05 21:47:34 +00002307 assert(image != (Image *) NULL);
2308 assert(image->signature == MagickSignature);
2309 if (image->debug != MagickFalse)
2310 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2311 assert(hald_image != (Image *) NULL);
2312 assert(hald_image->signature == MagickSignature);
2313 if (SetImageStorageClass(image,DirectClass) == MagickFalse)
2314 return(MagickFalse);
2315 if (image->matte == MagickFalse)
2316 (void) SetImageAlphaChannel(image,OpaqueAlphaChannel);
2317 /*
2318 Hald clut image.
2319 */
2320 status=MagickTrue;
2321 progress=0;
2322 length=MagickMin(hald_image->columns,hald_image->rows);
2323 for (level=2; (level*level*level) < length; level++) ;
2324 level*=level;
2325 cube_size=level*level;
2326 width=(double) hald_image->columns;
cristy4c08aed2011-07-01 19:47:50 +00002327 GetPixelInfo(hald_image,&zero);
cristy3ed852e2009-09-05 21:47:34 +00002328 exception=(&image->exception);
cristy3ed852e2009-09-05 21:47:34 +00002329 image_view=AcquireCacheView(image);
cristyd551fbc2011-03-31 18:07:46 +00002330 hald_view=AcquireCacheView(hald_image);
cristyb5d5f722009-11-04 03:03:49 +00002331#if defined(MAGICKCORE_OPENMP_SUPPORT)
2332 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002333#endif
cristybb503372010-05-27 20:51:26 +00002334 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002335 {
2336 double
2337 offset;
2338
2339 HaldInfo
2340 point;
2341
cristy4c08aed2011-07-01 19:47:50 +00002342 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00002343 pixel,
2344 pixel1,
2345 pixel2,
2346 pixel3,
2347 pixel4;
2348
cristy4c08aed2011-07-01 19:47:50 +00002349 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00002350 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002351
cristy8d4629b2010-08-30 17:59:46 +00002352 register ssize_t
2353 x;
2354
cristy3ed852e2009-09-05 21:47:34 +00002355 if (status == MagickFalse)
2356 continue;
2357 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00002358 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00002359 {
2360 status=MagickFalse;
2361 continue;
2362 }
cristy3ed852e2009-09-05 21:47:34 +00002363 pixel=zero;
2364 pixel1=zero;
2365 pixel2=zero;
2366 pixel3=zero;
2367 pixel4=zero;
cristybb503372010-05-27 20:51:26 +00002368 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002369 {
cristy4c08aed2011-07-01 19:47:50 +00002370 point.x=QuantumScale*(level-1.0)*GetPixelRed(image,q);
2371 point.y=QuantumScale*(level-1.0)*GetPixelGreen(image,q);
2372 point.z=QuantumScale*(level-1.0)*GetPixelBlue(image,q);
cristy3ed852e2009-09-05 21:47:34 +00002373 offset=point.x+level*floor(point.y)+cube_size*floor(point.z);
2374 point.x-=floor(point.x);
2375 point.y-=floor(point.y);
2376 point.z-=floor(point.z);
cristy4c08aed2011-07-01 19:47:50 +00002377 (void) InterpolatePixelInfo(image,hald_view,
cristy8a7c3e82011-03-26 02:10:53 +00002378 UndefinedInterpolatePixel,fmod(offset,width),floor(offset/width),
2379 &pixel1,exception);
cristy4c08aed2011-07-01 19:47:50 +00002380 (void) InterpolatePixelInfo(image,hald_view,
cristy8a7c3e82011-03-26 02:10:53 +00002381 UndefinedInterpolatePixel,fmod(offset+level,width),floor((offset+level)/
2382 width),&pixel2,exception);
cristy4c08aed2011-07-01 19:47:50 +00002383 CompositePixelInfoAreaBlend(&pixel1,pixel1.alpha,&pixel2,
2384 pixel2.alpha,point.y,&pixel3);
cristy3ed852e2009-09-05 21:47:34 +00002385 offset+=cube_size;
cristy4c08aed2011-07-01 19:47:50 +00002386 (void) InterpolatePixelInfo(image,hald_view,
cristy8a7c3e82011-03-26 02:10:53 +00002387 UndefinedInterpolatePixel,fmod(offset,width),floor(offset/width),
2388 &pixel1,exception);
cristy4c08aed2011-07-01 19:47:50 +00002389 (void) InterpolatePixelInfo(image,hald_view,
cristy8a7c3e82011-03-26 02:10:53 +00002390 UndefinedInterpolatePixel,fmod(offset+level,width),floor((offset+level)/
2391 width),&pixel2,exception);
cristy4c08aed2011-07-01 19:47:50 +00002392 CompositePixelInfoAreaBlend(&pixel1,pixel1.alpha,&pixel2,
2393 pixel2.alpha,point.y,&pixel4);
2394 CompositePixelInfoAreaBlend(&pixel3,pixel3.alpha,&pixel4,
2395 pixel4.alpha,point.z,&pixel);
cristy3ed852e2009-09-05 21:47:34 +00002396 if ((channel & RedChannel) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002397 SetPixelRed(image,
2398 ClampToQuantum(pixel.red),q);
cristy3ed852e2009-09-05 21:47:34 +00002399 if ((channel & GreenChannel) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002400 SetPixelGreen(image,
2401 ClampToQuantum(pixel.green),q);
cristy3ed852e2009-09-05 21:47:34 +00002402 if ((channel & BlueChannel) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002403 SetPixelBlue(image,
2404 ClampToQuantum(pixel.blue),q);
2405 if (((channel & BlackChannel) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00002406 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +00002407 SetPixelBlack(image,
2408 ClampToQuantum(pixel.black),q);
2409 if (((channel & OpacityChannel) != 0) && (image->matte != MagickFalse))
2410 SetPixelAlpha(image,
2411 ClampToQuantum(pixel.alpha),q);
2412 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00002413 }
2414 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2415 status=MagickFalse;
2416 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2417 {
2418 MagickBooleanType
2419 proceed;
2420
cristyb5d5f722009-11-04 03:03:49 +00002421#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00002422 #pragma omp critical (MagickCore_HaldClutImageChannel)
2423#endif
2424 proceed=SetImageProgress(image,HaldClutImageTag,progress++,image->rows);
2425 if (proceed == MagickFalse)
2426 status=MagickFalse;
2427 }
2428 }
cristyd551fbc2011-03-31 18:07:46 +00002429 hald_view=DestroyCacheView(hald_view);
cristy3ed852e2009-09-05 21:47:34 +00002430 image_view=DestroyCacheView(image_view);
cristy3ed852e2009-09-05 21:47:34 +00002431 return(status);
2432}
2433
2434/*
2435%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2436% %
2437% %
2438% %
2439% L e v e l I m a g e %
2440% %
2441% %
2442% %
2443%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2444%
2445% LevelImage() adjusts the levels of a particular image channel by
2446% scaling the colors falling between specified white and black points to
2447% the full available quantum range.
2448%
2449% The parameters provided represent the black, and white points. The black
2450% point specifies the darkest color in the image. Colors darker than the
2451% black point are set to zero. White point specifies the lightest color in
2452% the image. Colors brighter than the white point are set to the maximum
2453% quantum value.
2454%
2455% If a '!' flag is given, map black and white colors to the given levels
2456% rather than mapping those levels to black and white. See
2457% LevelizeImageChannel() and LevelizeImageChannel(), below.
2458%
2459% Gamma specifies a gamma correction to apply to the image.
2460%
2461% The format of the LevelImage method is:
2462%
2463% MagickBooleanType LevelImage(Image *image,const char *levels)
2464%
2465% A description of each parameter follows:
2466%
2467% o image: the image.
2468%
2469% o levels: Specify the levels where the black and white points have the
2470% range of 0-QuantumRange, and gamma has the range 0-10 (e.g. 10x90%+2).
2471% A '!' flag inverts the re-mapping.
2472%
2473*/
2474
2475MagickExport MagickBooleanType LevelImage(Image *image,const char *levels)
2476{
2477 double
2478 black_point,
2479 gamma,
2480 white_point;
2481
2482 GeometryInfo
2483 geometry_info;
2484
2485 MagickBooleanType
2486 status;
2487
2488 MagickStatusType
2489 flags;
2490
2491 /*
2492 Parse levels.
2493 */
2494 if (levels == (char *) NULL)
2495 return(MagickFalse);
2496 flags=ParseGeometry(levels,&geometry_info);
2497 black_point=geometry_info.rho;
2498 white_point=(double) QuantumRange;
2499 if ((flags & SigmaValue) != 0)
2500 white_point=geometry_info.sigma;
2501 gamma=1.0;
2502 if ((flags & XiValue) != 0)
2503 gamma=geometry_info.xi;
2504 if ((flags & PercentValue) != 0)
2505 {
2506 black_point*=(double) image->columns*image->rows/100.0;
2507 white_point*=(double) image->columns*image->rows/100.0;
2508 }
2509 if ((flags & SigmaValue) == 0)
2510 white_point=(double) QuantumRange-black_point;
2511 if ((flags & AspectValue ) == 0)
2512 status=LevelImageChannel(image,DefaultChannels,black_point,white_point,
2513 gamma);
2514 else
cristy308b4e62009-09-21 14:40:44 +00002515 status=LevelizeImage(image,black_point,white_point,gamma);
cristy3ed852e2009-09-05 21:47:34 +00002516 return(status);
2517}
2518
2519/*
2520%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2521% %
2522% %
2523% %
cristy308b4e62009-09-21 14:40:44 +00002524% L e v e l i z e I m a g e %
cristy3ed852e2009-09-05 21:47:34 +00002525% %
2526% %
2527% %
2528%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2529%
cristy308b4e62009-09-21 14:40:44 +00002530% LevelizeImage() applies the normal level operation to the image, spreading
2531% out the values between the black and white points over the entire range of
2532% values. Gamma correction is also applied after the values has been mapped.
cristy3ed852e2009-09-05 21:47:34 +00002533%
2534% It is typically used to improve image contrast, or to provide a controlled
2535% linear threshold for the image. If the black and white points are set to
2536% the minimum and maximum values found in the image, the image can be
2537% normalized. or by swapping black and white values, negate the image.
2538%
cristy308b4e62009-09-21 14:40:44 +00002539% The format of the LevelizeImage method is:
cristy3ed852e2009-09-05 21:47:34 +00002540%
cristy308b4e62009-09-21 14:40:44 +00002541% MagickBooleanType LevelizeImage(Image *image,const double black_point,
2542% const double white_point,const double gamma)
2543% MagickBooleanType LevelizeImageChannel(Image *image,
2544% const ChannelType channel,const double black_point,
2545% const double white_point,const double gamma)
cristy3ed852e2009-09-05 21:47:34 +00002546%
2547% A description of each parameter follows:
2548%
2549% o image: the image.
2550%
2551% o channel: the channel.
2552%
2553% o black_point: The level which is to be mapped to zero (black)
2554%
2555% o white_point: The level which is to be mapped to QuantiumRange (white)
2556%
2557% o gamma: adjust gamma by this factor before mapping values.
2558% use 1.0 for purely linear stretching of image color values
2559%
2560*/
2561MagickExport MagickBooleanType LevelImageChannel(Image *image,
2562 const ChannelType channel,const double black_point,const double white_point,
2563 const double gamma)
2564{
2565#define LevelImageTag "Level/Image"
cristybcfb0432010-05-06 01:45:33 +00002566#define LevelQuantum(x) (ClampToQuantum((MagickRealType) QuantumRange* \
cristyc1f508d2010-04-22 01:21:47 +00002567 pow(scale*((double) (x)-black_point),1.0/gamma)))
cristy3ed852e2009-09-05 21:47:34 +00002568
cristyc4c8d132010-01-07 01:58:38 +00002569 CacheView
2570 *image_view;
2571
cristy3ed852e2009-09-05 21:47:34 +00002572 ExceptionInfo
2573 *exception;
2574
cristy3ed852e2009-09-05 21:47:34 +00002575 MagickBooleanType
2576 status;
2577
cristybb503372010-05-27 20:51:26 +00002578 MagickOffsetType
2579 progress;
2580
anthony7fe39fc2010-04-06 03:19:20 +00002581 register double
2582 scale;
2583
cristy8d4629b2010-08-30 17:59:46 +00002584 register ssize_t
2585 i;
2586
cristybb503372010-05-27 20:51:26 +00002587 ssize_t
2588 y;
2589
cristy3ed852e2009-09-05 21:47:34 +00002590 /*
2591 Allocate and initialize levels map.
2592 */
2593 assert(image != (Image *) NULL);
2594 assert(image->signature == MagickSignature);
2595 if (image->debug != MagickFalse)
2596 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
cristy8d4629b2010-08-30 17:59:46 +00002597 scale=(white_point != black_point) ? 1.0/(white_point-black_point) : 1.0;
cristy3ed852e2009-09-05 21:47:34 +00002598 if (image->storage_class == PseudoClass)
cristyb5d5f722009-11-04 03:03:49 +00002599#if defined(MAGICKCORE_OPENMP_SUPPORT)
2600 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002601#endif
cristybb503372010-05-27 20:51:26 +00002602 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00002603 {
2604 /*
2605 Level colormap.
2606 */
2607 if ((channel & RedChannel) != 0)
cristyc1f508d2010-04-22 01:21:47 +00002608 image->colormap[i].red=LevelQuantum(image->colormap[i].red);
cristy3ed852e2009-09-05 21:47:34 +00002609 if ((channel & GreenChannel) != 0)
cristyc1f508d2010-04-22 01:21:47 +00002610 image->colormap[i].green=LevelQuantum(image->colormap[i].green);
cristy3ed852e2009-09-05 21:47:34 +00002611 if ((channel & BlueChannel) != 0)
cristyc1f508d2010-04-22 01:21:47 +00002612 image->colormap[i].blue=LevelQuantum(image->colormap[i].blue);
cristy3ed852e2009-09-05 21:47:34 +00002613 if ((channel & OpacityChannel) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002614 image->colormap[i].alpha=LevelQuantum(image->colormap[i].alpha);
cristy3ed852e2009-09-05 21:47:34 +00002615 }
2616 /*
2617 Level image.
2618 */
2619 status=MagickTrue;
2620 progress=0;
2621 exception=(&image->exception);
2622 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00002623#if defined(MAGICKCORE_OPENMP_SUPPORT)
2624 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002625#endif
cristybb503372010-05-27 20:51:26 +00002626 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002627 {
cristy4c08aed2011-07-01 19:47:50 +00002628 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00002629 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002630
cristy8d4629b2010-08-30 17:59:46 +00002631 register ssize_t
2632 x;
2633
cristy3ed852e2009-09-05 21:47:34 +00002634 if (status == MagickFalse)
2635 continue;
2636 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00002637 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00002638 {
2639 status=MagickFalse;
2640 continue;
2641 }
cristybb503372010-05-27 20:51:26 +00002642 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002643 {
2644 if ((channel & RedChannel) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002645 SetPixelRed(image,LevelQuantum(
2646 GetPixelRed(image,q)),q);
cristy3ed852e2009-09-05 21:47:34 +00002647 if ((channel & GreenChannel) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002648 SetPixelGreen(image,
2649 LevelQuantum(GetPixelGreen(image,q)),q);
cristy3ed852e2009-09-05 21:47:34 +00002650 if ((channel & BlueChannel) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002651 SetPixelBlue(image,
2652 LevelQuantum(GetPixelBlue(image,q)),q);
cristy3ed852e2009-09-05 21:47:34 +00002653 if (((channel & OpacityChannel) != 0) &&
2654 (image->matte == MagickTrue))
cristy4c08aed2011-07-01 19:47:50 +00002655 SetPixelAlpha(image,
2656 LevelQuantum(GetPixelAlpha(image,q)),q);
2657 if (((channel & BlackChannel) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00002658 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +00002659 SetPixelBlack(image,
2660 LevelQuantum(GetPixelBlack(image,q)),q);
2661 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00002662 }
2663 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2664 status=MagickFalse;
2665 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2666 {
2667 MagickBooleanType
2668 proceed;
2669
cristyb5d5f722009-11-04 03:03:49 +00002670#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00002671 #pragma omp critical (MagickCore_LevelImageChannel)
2672#endif
2673 proceed=SetImageProgress(image,LevelImageTag,progress++,image->rows);
2674 if (proceed == MagickFalse)
2675 status=MagickFalse;
2676 }
2677 }
2678 image_view=DestroyCacheView(image_view);
2679 return(status);
2680}
2681
2682/*
2683%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2684% %
2685% %
2686% %
2687% L e v e l i z e I m a g e C h a n n e l %
2688% %
2689% %
2690% %
2691%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2692%
2693% LevelizeImageChannel() applies the reversed LevelImage() operation to just
2694% the specific channels specified. It compresses the full range of color
2695% values, so that they lie between the given black and white points. Gamma is
2696% applied before the values are mapped.
2697%
2698% LevelizeImageChannel() can be called with by using a +level command line
2699% API option, or using a '!' on a -level or LevelImage() geometry string.
2700%
2701% It can be used for example de-contrast a greyscale image to the exact
2702% levels specified. Or by using specific levels for each channel of an image
2703% you can convert a gray-scale image to any linear color gradient, according
2704% to those levels.
2705%
2706% The format of the LevelizeImageChannel method is:
2707%
2708% MagickBooleanType LevelizeImageChannel(Image *image,
2709% const ChannelType channel,const char *levels)
2710%
2711% A description of each parameter follows:
2712%
2713% o image: the image.
2714%
2715% o channel: the channel.
2716%
2717% o black_point: The level to map zero (black) to.
2718%
2719% o white_point: The level to map QuantiumRange (white) to.
2720%
2721% o gamma: adjust gamma by this factor before mapping values.
2722%
2723*/
cristyd1a2c0f2011-02-09 14:14:50 +00002724
2725MagickExport MagickBooleanType LevelizeImage(Image *image,
2726 const double black_point,const double white_point,const double gamma)
2727{
2728 MagickBooleanType
2729 status;
2730
2731 status=LevelizeImageChannel(image,DefaultChannels,black_point,white_point,
2732 gamma);
2733 return(status);
2734}
2735
cristy3ed852e2009-09-05 21:47:34 +00002736MagickExport MagickBooleanType LevelizeImageChannel(Image *image,
2737 const ChannelType channel,const double black_point,const double white_point,
2738 const double gamma)
2739{
2740#define LevelizeImageTag "Levelize/Image"
cristyce70c172010-01-07 17:15:30 +00002741#define LevelizeValue(x) (ClampToQuantum(((MagickRealType) \
cristy3ed852e2009-09-05 21:47:34 +00002742 pow((double)(QuantumScale*(x)),1.0/gamma))*(white_point-black_point)+ \
2743 black_point))
2744
cristyc4c8d132010-01-07 01:58:38 +00002745 CacheView
2746 *image_view;
2747
cristy3ed852e2009-09-05 21:47:34 +00002748 ExceptionInfo
2749 *exception;
2750
cristy3ed852e2009-09-05 21:47:34 +00002751 MagickBooleanType
2752 status;
2753
cristybb503372010-05-27 20:51:26 +00002754 MagickOffsetType
2755 progress;
2756
2757 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002758 i;
2759
cristybb503372010-05-27 20:51:26 +00002760 ssize_t
2761 y;
2762
cristy3ed852e2009-09-05 21:47:34 +00002763 /*
2764 Allocate and initialize levels map.
2765 */
2766 assert(image != (Image *) NULL);
2767 assert(image->signature == MagickSignature);
2768 if (image->debug != MagickFalse)
2769 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2770 if (image->storage_class == PseudoClass)
cristyb5d5f722009-11-04 03:03:49 +00002771#if defined(MAGICKCORE_OPENMP_SUPPORT)
2772 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002773#endif
cristybb503372010-05-27 20:51:26 +00002774 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00002775 {
2776 /*
2777 Level colormap.
2778 */
2779 if ((channel & RedChannel) != 0)
2780 image->colormap[i].red=LevelizeValue(image->colormap[i].red);
2781 if ((channel & GreenChannel) != 0)
2782 image->colormap[i].green=LevelizeValue(image->colormap[i].green);
2783 if ((channel & BlueChannel) != 0)
2784 image->colormap[i].blue=LevelizeValue(image->colormap[i].blue);
2785 if ((channel & OpacityChannel) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002786 image->colormap[i].alpha=LevelizeValue(image->colormap[i].alpha);
cristy3ed852e2009-09-05 21:47:34 +00002787 }
2788 /*
2789 Level image.
2790 */
2791 status=MagickTrue;
2792 progress=0;
2793 exception=(&image->exception);
2794 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00002795#if defined(MAGICKCORE_OPENMP_SUPPORT)
2796 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002797#endif
cristybb503372010-05-27 20:51:26 +00002798 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002799 {
cristy4c08aed2011-07-01 19:47:50 +00002800 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00002801 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002802
cristy8d4629b2010-08-30 17:59:46 +00002803 register ssize_t
2804 x;
2805
cristy3ed852e2009-09-05 21:47:34 +00002806 if (status == MagickFalse)
2807 continue;
2808 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00002809 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00002810 {
2811 status=MagickFalse;
2812 continue;
2813 }
cristybb503372010-05-27 20:51:26 +00002814 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002815 {
2816 if ((channel & RedChannel) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002817 SetPixelRed(image,LevelizeValue(GetPixelRed(image,q)),q);
cristy3ed852e2009-09-05 21:47:34 +00002818 if ((channel & GreenChannel) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002819 SetPixelGreen(image,LevelizeValue(GetPixelGreen(image,q)),q);
cristy3ed852e2009-09-05 21:47:34 +00002820 if ((channel & BlueChannel) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002821 SetPixelBlue(image,LevelizeValue(GetPixelBlue(image,q)),q);
2822 if (((channel & BlackChannel) != 0) &&
2823 (image->colorspace == CMYKColorspace))
2824 SetPixelBlack(image,LevelizeValue(GetPixelBlack(image,q)),q);
cristy3ed852e2009-09-05 21:47:34 +00002825 if (((channel & OpacityChannel) != 0) &&
2826 (image->matte == MagickTrue))
cristy4c08aed2011-07-01 19:47:50 +00002827 SetPixelAlpha(image,LevelizeValue(GetPixelAlpha(image,q)),q);
2828 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00002829 }
2830 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2831 status=MagickFalse;
2832 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2833 {
2834 MagickBooleanType
2835 proceed;
2836
cristyb5d5f722009-11-04 03:03:49 +00002837#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00002838 #pragma omp critical (MagickCore_LevelizeImageChannel)
2839#endif
2840 proceed=SetImageProgress(image,LevelizeImageTag,progress++,image->rows);
2841 if (proceed == MagickFalse)
2842 status=MagickFalse;
2843 }
2844 }
cristy8d4629b2010-08-30 17:59:46 +00002845 image_view=DestroyCacheView(image_view);
cristy3ed852e2009-09-05 21:47:34 +00002846 return(status);
2847}
2848
2849/*
2850%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2851% %
2852% %
2853% %
2854% L e v e l I m a g e C o l o r s %
2855% %
2856% %
2857% %
2858%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2859%
cristyee0f8d72009-09-19 00:58:29 +00002860% LevelImageColor() maps the given color to "black" and "white" values,
2861% linearly spreading out the colors, and level values on a channel by channel
2862% bases, as per LevelImage(). The given colors allows you to specify
glennrp1e7f7bc2011-03-02 19:25:28 +00002863% different level ranges for each of the color channels separately.
cristy3ed852e2009-09-05 21:47:34 +00002864%
2865% If the boolean 'invert' is set true the image values will modifyed in the
2866% reverse direction. That is any existing "black" and "white" colors in the
2867% image will become the color values given, with all other values compressed
2868% appropriatally. This effectivally maps a greyscale gradient into the given
2869% color gradient.
2870%
cristy308b4e62009-09-21 14:40:44 +00002871% The format of the LevelColorsImageChannel method is:
cristy3ed852e2009-09-05 21:47:34 +00002872%
cristy308b4e62009-09-21 14:40:44 +00002873% MagickBooleanType LevelColorsImage(Image *image,
cristy4c08aed2011-07-01 19:47:50 +00002874% const PixelInfo *black_color,
2875% const PixelInfo *white_color,const MagickBooleanType invert)
cristy308b4e62009-09-21 14:40:44 +00002876% MagickBooleanType LevelColorsImageChannel(Image *image,
cristy4c08aed2011-07-01 19:47:50 +00002877% const ChannelType channel,const PixelInfo *black_color,
2878% const PixelInfo *white_color,const MagickBooleanType invert)
cristy3ed852e2009-09-05 21:47:34 +00002879%
2880% A description of each parameter follows:
2881%
2882% o image: the image.
2883%
2884% o channel: the channel.
2885%
2886% o black_color: The color to map black to/from
2887%
2888% o white_point: The color to map white to/from
2889%
2890% o invert: if true map the colors (levelize), rather than from (level)
2891%
2892*/
cristy308b4e62009-09-21 14:40:44 +00002893
2894MagickExport MagickBooleanType LevelColorsImage(Image *image,
cristy4c08aed2011-07-01 19:47:50 +00002895 const PixelInfo *black_color,const PixelInfo *white_color,
cristy3ed852e2009-09-05 21:47:34 +00002896 const MagickBooleanType invert)
2897{
cristy308b4e62009-09-21 14:40:44 +00002898 MagickBooleanType
2899 status;
cristy3ed852e2009-09-05 21:47:34 +00002900
cristy308b4e62009-09-21 14:40:44 +00002901 status=LevelColorsImageChannel(image,DefaultChannels,black_color,white_color,
2902 invert);
2903 return(status);
2904}
2905
2906MagickExport MagickBooleanType LevelColorsImageChannel(Image *image,
cristy4c08aed2011-07-01 19:47:50 +00002907 const ChannelType channel,const PixelInfo *black_color,
2908 const PixelInfo *white_color,const MagickBooleanType invert)
cristy308b4e62009-09-21 14:40:44 +00002909{
cristy3ed852e2009-09-05 21:47:34 +00002910 MagickStatusType
2911 status;
2912
2913 /*
2914 Allocate and initialize levels map.
2915 */
2916 assert(image != (Image *) NULL);
2917 assert(image->signature == MagickSignature);
2918 if (image->debug != MagickFalse)
2919 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2920 status=MagickFalse;
2921 if (invert == MagickFalse)
2922 {
2923 if ((channel & RedChannel) != 0)
2924 status|=LevelImageChannel(image,RedChannel,
2925 black_color->red,white_color->red,(double) 1.0);
2926 if ((channel & GreenChannel) != 0)
2927 status|=LevelImageChannel(image,GreenChannel,
2928 black_color->green,white_color->green,(double) 1.0);
2929 if ((channel & BlueChannel) != 0)
2930 status|=LevelImageChannel(image,BlueChannel,
2931 black_color->blue,white_color->blue,(double) 1.0);
cristy4c08aed2011-07-01 19:47:50 +00002932 if (((channel & BlackChannel) != 0) &&
2933 (image->colorspace == CMYKColorspace))
2934 status|=LevelImageChannel(image,BlackChannel,
2935 black_color->black,white_color->black,(double) 1.0);
cristy3ed852e2009-09-05 21:47:34 +00002936 if (((channel & OpacityChannel) != 0) &&
2937 (image->matte == MagickTrue))
2938 status|=LevelImageChannel(image,OpacityChannel,
cristy4c08aed2011-07-01 19:47:50 +00002939 black_color->alpha,white_color->alpha,(double) 1.0);
cristy3ed852e2009-09-05 21:47:34 +00002940 }
2941 else
2942 {
2943 if ((channel & RedChannel) != 0)
2944 status|=LevelizeImageChannel(image,RedChannel,
2945 black_color->red,white_color->red,(double) 1.0);
2946 if ((channel & GreenChannel) != 0)
2947 status|=LevelizeImageChannel(image,GreenChannel,
2948 black_color->green,white_color->green,(double) 1.0);
2949 if ((channel & BlueChannel) != 0)
2950 status|=LevelizeImageChannel(image,BlueChannel,
2951 black_color->blue,white_color->blue,(double) 1.0);
cristy4c08aed2011-07-01 19:47:50 +00002952 if (((channel & BlackChannel) != 0) &&
2953 (image->colorspace == CMYKColorspace))
2954 status|=LevelizeImageChannel(image,BlackChannel,
2955 black_color->black,white_color->black,(double) 1.0);
cristy3ed852e2009-09-05 21:47:34 +00002956 if (((channel & OpacityChannel) != 0) &&
2957 (image->matte == MagickTrue))
2958 status|=LevelizeImageChannel(image,OpacityChannel,
cristy4c08aed2011-07-01 19:47:50 +00002959 black_color->alpha,white_color->alpha,(double) 1.0);
cristy3ed852e2009-09-05 21:47:34 +00002960 }
2961 return(status == 0 ? MagickFalse : MagickTrue);
2962}
2963
2964/*
2965%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2966% %
2967% %
2968% %
2969% L i n e a r S t r e t c h I m a g e %
2970% %
2971% %
2972% %
2973%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2974%
2975% The LinearStretchImage() discards any pixels below the black point and
2976% above the white point and levels the remaining pixels.
2977%
2978% The format of the LinearStretchImage method is:
2979%
2980% MagickBooleanType LinearStretchImage(Image *image,
2981% const double black_point,const double white_point)
2982%
2983% A description of each parameter follows:
2984%
2985% o image: the image.
2986%
2987% o black_point: the black point.
2988%
2989% o white_point: the white point.
2990%
2991*/
2992MagickExport MagickBooleanType LinearStretchImage(Image *image,
2993 const double black_point,const double white_point)
2994{
2995#define LinearStretchImageTag "LinearStretch/Image"
2996
2997 ExceptionInfo
2998 *exception;
2999
cristy3ed852e2009-09-05 21:47:34 +00003000 MagickBooleanType
3001 status;
3002
3003 MagickRealType
3004 *histogram,
3005 intensity;
3006
cristy8d4629b2010-08-30 17:59:46 +00003007 ssize_t
3008 black,
3009 white,
3010 y;
3011
cristy3ed852e2009-09-05 21:47:34 +00003012 /*
3013 Allocate histogram and linear map.
3014 */
3015 assert(image != (Image *) NULL);
3016 assert(image->signature == MagickSignature);
3017 histogram=(MagickRealType *) AcquireQuantumMemory(MaxMap+1UL,
3018 sizeof(*histogram));
3019 if (histogram == (MagickRealType *) NULL)
3020 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
3021 image->filename);
3022 /*
3023 Form histogram.
3024 */
3025 (void) ResetMagickMemory(histogram,0,(MaxMap+1)*sizeof(*histogram));
3026 exception=(&image->exception);
cristybb503372010-05-27 20:51:26 +00003027 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003028 {
cristy4c08aed2011-07-01 19:47:50 +00003029 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00003030 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00003031
cristybb503372010-05-27 20:51:26 +00003032 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003033 x;
3034
3035 p=GetVirtualPixels(image,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00003036 if (p == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00003037 break;
cristybb503372010-05-27 20:51:26 +00003038 for (x=(ssize_t) image->columns-1; x >= 0; x--)
cristy3ed852e2009-09-05 21:47:34 +00003039 {
cristy4c08aed2011-07-01 19:47:50 +00003040 histogram[ScaleQuantumToMap(GetPixelIntensity(image,p))]++;
3041 p+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00003042 }
3043 }
3044 /*
3045 Find the histogram boundaries by locating the black and white point levels.
3046 */
cristy3ed852e2009-09-05 21:47:34 +00003047 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00003048 for (black=0; black < (ssize_t) MaxMap; black++)
cristy3ed852e2009-09-05 21:47:34 +00003049 {
3050 intensity+=histogram[black];
3051 if (intensity >= black_point)
3052 break;
3053 }
3054 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00003055 for (white=(ssize_t) MaxMap; white != 0; white--)
cristy3ed852e2009-09-05 21:47:34 +00003056 {
3057 intensity+=histogram[white];
3058 if (intensity >= white_point)
3059 break;
3060 }
3061 histogram=(MagickRealType *) RelinquishMagickMemory(histogram);
3062 status=LevelImageChannel(image,DefaultChannels,(double) black,(double) white,
3063 1.0);
3064 return(status);
3065}
3066
3067/*
3068%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3069% %
3070% %
3071% %
3072% M o d u l a t e I m a g e %
3073% %
3074% %
3075% %
3076%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3077%
3078% ModulateImage() lets you control the brightness, saturation, and hue
3079% of an image. Modulate represents the brightness, saturation, and hue
3080% as one parameter (e.g. 90,150,100). If the image colorspace is HSL, the
3081% modulation is lightness, saturation, and hue. And if the colorspace is
3082% HWB, use blackness, whiteness, and hue.
3083%
3084% The format of the ModulateImage method is:
3085%
3086% MagickBooleanType ModulateImage(Image *image,const char *modulate)
3087%
3088% A description of each parameter follows:
3089%
3090% o image: the image.
3091%
3092% o modulate: Define the percent change in brightness, saturation, and
3093% hue.
3094%
3095*/
3096
3097static void ModulateHSB(const double percent_hue,
3098 const double percent_saturation,const double percent_brightness,
3099 Quantum *red,Quantum *green,Quantum *blue)
3100{
3101 double
3102 brightness,
3103 hue,
3104 saturation;
3105
3106 /*
3107 Increase or decrease color brightness, saturation, or hue.
3108 */
3109 assert(red != (Quantum *) NULL);
3110 assert(green != (Quantum *) NULL);
3111 assert(blue != (Quantum *) NULL);
3112 ConvertRGBToHSB(*red,*green,*blue,&hue,&saturation,&brightness);
3113 hue+=0.5*(0.01*percent_hue-1.0);
3114 while (hue < 0.0)
3115 hue+=1.0;
3116 while (hue > 1.0)
3117 hue-=1.0;
3118 saturation*=0.01*percent_saturation;
3119 brightness*=0.01*percent_brightness;
3120 ConvertHSBToRGB(hue,saturation,brightness,red,green,blue);
3121}
3122
3123static void ModulateHSL(const double percent_hue,
3124 const double percent_saturation,const double percent_lightness,
3125 Quantum *red,Quantum *green,Quantum *blue)
3126{
3127 double
3128 hue,
3129 lightness,
3130 saturation;
3131
3132 /*
3133 Increase or decrease color lightness, saturation, or hue.
3134 */
3135 assert(red != (Quantum *) NULL);
3136 assert(green != (Quantum *) NULL);
3137 assert(blue != (Quantum *) NULL);
3138 ConvertRGBToHSL(*red,*green,*blue,&hue,&saturation,&lightness);
3139 hue+=0.5*(0.01*percent_hue-1.0);
3140 while (hue < 0.0)
3141 hue+=1.0;
3142 while (hue > 1.0)
3143 hue-=1.0;
3144 saturation*=0.01*percent_saturation;
3145 lightness*=0.01*percent_lightness;
3146 ConvertHSLToRGB(hue,saturation,lightness,red,green,blue);
3147}
3148
3149static void ModulateHWB(const double percent_hue,const double percent_whiteness, const double percent_blackness,Quantum *red,Quantum *green,Quantum *blue)
3150{
3151 double
3152 blackness,
3153 hue,
3154 whiteness;
3155
3156 /*
3157 Increase or decrease color blackness, whiteness, or hue.
3158 */
3159 assert(red != (Quantum *) NULL);
3160 assert(green != (Quantum *) NULL);
3161 assert(blue != (Quantum *) NULL);
3162 ConvertRGBToHWB(*red,*green,*blue,&hue,&whiteness,&blackness);
3163 hue+=0.5*(0.01*percent_hue-1.0);
3164 while (hue < 0.0)
3165 hue+=1.0;
3166 while (hue > 1.0)
3167 hue-=1.0;
3168 blackness*=0.01*percent_blackness;
3169 whiteness*=0.01*percent_whiteness;
3170 ConvertHWBToRGB(hue,whiteness,blackness,red,green,blue);
3171}
3172
3173MagickExport MagickBooleanType ModulateImage(Image *image,const char *modulate)
3174{
3175#define ModulateImageTag "Modulate/Image"
3176
cristyc4c8d132010-01-07 01:58:38 +00003177 CacheView
3178 *image_view;
3179
cristy3ed852e2009-09-05 21:47:34 +00003180 ColorspaceType
3181 colorspace;
3182
3183 const char
3184 *artifact;
3185
3186 double
3187 percent_brightness,
3188 percent_hue,
3189 percent_saturation;
3190
3191 ExceptionInfo
3192 *exception;
3193
3194 GeometryInfo
3195 geometry_info;
3196
cristy3ed852e2009-09-05 21:47:34 +00003197 MagickBooleanType
3198 status;
3199
cristybb503372010-05-27 20:51:26 +00003200 MagickOffsetType
3201 progress;
3202
cristy3ed852e2009-09-05 21:47:34 +00003203 MagickStatusType
3204 flags;
3205
cristybb503372010-05-27 20:51:26 +00003206 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003207 i;
3208
cristybb503372010-05-27 20:51:26 +00003209 ssize_t
3210 y;
3211
cristy3ed852e2009-09-05 21:47:34 +00003212 /*
cristy2b726bd2010-01-11 01:05:39 +00003213 Initialize modulate table.
cristy3ed852e2009-09-05 21:47:34 +00003214 */
3215 assert(image != (Image *) NULL);
3216 assert(image->signature == MagickSignature);
3217 if (image->debug != MagickFalse)
3218 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3219 if (modulate == (char *) NULL)
3220 return(MagickFalse);
3221 flags=ParseGeometry(modulate,&geometry_info);
3222 percent_brightness=geometry_info.rho;
3223 percent_saturation=geometry_info.sigma;
3224 if ((flags & SigmaValue) == 0)
3225 percent_saturation=100.0;
3226 percent_hue=geometry_info.xi;
3227 if ((flags & XiValue) == 0)
3228 percent_hue=100.0;
3229 colorspace=UndefinedColorspace;
3230 artifact=GetImageArtifact(image,"modulate:colorspace");
3231 if (artifact != (const char *) NULL)
cristy042ee782011-04-22 18:48:30 +00003232 colorspace=(ColorspaceType) ParseCommandOption(MagickColorspaceOptions,
cristy3ed852e2009-09-05 21:47:34 +00003233 MagickFalse,artifact);
3234 if (image->storage_class == PseudoClass)
3235 {
3236 /*
3237 Modulate colormap.
3238 */
cristyb5d5f722009-11-04 03:03:49 +00003239#if defined(MAGICKCORE_OPENMP_SUPPORT)
3240 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003241#endif
cristybb503372010-05-27 20:51:26 +00003242 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00003243 switch (colorspace)
3244 {
3245 case HSBColorspace:
3246 {
3247 ModulateHSB(percent_hue,percent_saturation,percent_brightness,
3248 &image->colormap[i].red,&image->colormap[i].green,
3249 &image->colormap[i].blue);
3250 break;
3251 }
3252 case HSLColorspace:
3253 default:
3254 {
3255 ModulateHSL(percent_hue,percent_saturation,percent_brightness,
3256 &image->colormap[i].red,&image->colormap[i].green,
3257 &image->colormap[i].blue);
3258 break;
3259 }
3260 case HWBColorspace:
3261 {
3262 ModulateHWB(percent_hue,percent_saturation,percent_brightness,
3263 &image->colormap[i].red,&image->colormap[i].green,
3264 &image->colormap[i].blue);
3265 break;
3266 }
3267 }
3268 }
3269 /*
3270 Modulate image.
3271 */
3272 status=MagickTrue;
3273 progress=0;
3274 exception=(&image->exception);
3275 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00003276#if defined(MAGICKCORE_OPENMP_SUPPORT)
3277 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003278#endif
cristybb503372010-05-27 20:51:26 +00003279 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003280 {
cristy5afeab82011-04-30 01:30:09 +00003281 Quantum
3282 blue,
3283 green,
3284 red;
3285
cristy4c08aed2011-07-01 19:47:50 +00003286 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00003287 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003288
cristy8d4629b2010-08-30 17:59:46 +00003289 register ssize_t
3290 x;
3291
cristy3ed852e2009-09-05 21:47:34 +00003292 if (status == MagickFalse)
3293 continue;
3294 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00003295 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00003296 {
3297 status=MagickFalse;
3298 continue;
3299 }
cristybb503372010-05-27 20:51:26 +00003300 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003301 {
cristy4c08aed2011-07-01 19:47:50 +00003302 red=GetPixelRed(image,q);
3303 green=GetPixelGreen(image,q);
3304 blue=GetPixelBlue(image,q);
cristy3ed852e2009-09-05 21:47:34 +00003305 switch (colorspace)
3306 {
3307 case HSBColorspace:
3308 {
3309 ModulateHSB(percent_hue,percent_saturation,percent_brightness,
cristy5afeab82011-04-30 01:30:09 +00003310 &red,&green,&blue);
cristy3ed852e2009-09-05 21:47:34 +00003311 break;
3312 }
3313 case HSLColorspace:
3314 default:
3315 {
3316 ModulateHSL(percent_hue,percent_saturation,percent_brightness,
cristy5afeab82011-04-30 01:30:09 +00003317 &red,&green,&blue);
cristy3ed852e2009-09-05 21:47:34 +00003318 break;
3319 }
3320 case HWBColorspace:
3321 {
3322 ModulateHWB(percent_hue,percent_saturation,percent_brightness,
cristy5afeab82011-04-30 01:30:09 +00003323 &red,&green,&blue);
cristy3ed852e2009-09-05 21:47:34 +00003324 break;
3325 }
3326 }
cristy4c08aed2011-07-01 19:47:50 +00003327 SetPixelRed(image,red,q);
3328 SetPixelGreen(image,green,q);
3329 SetPixelBlue(image,blue,q);
3330 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00003331 }
3332 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
3333 status=MagickFalse;
3334 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3335 {
3336 MagickBooleanType
3337 proceed;
3338
cristyb5d5f722009-11-04 03:03:49 +00003339#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00003340 #pragma omp critical (MagickCore_ModulateImage)
3341#endif
3342 proceed=SetImageProgress(image,ModulateImageTag,progress++,image->rows);
3343 if (proceed == MagickFalse)
3344 status=MagickFalse;
3345 }
3346 }
3347 image_view=DestroyCacheView(image_view);
3348 return(status);
3349}
3350
3351/*
3352%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3353% %
3354% %
3355% %
3356% N e g a t e I m a g e %
3357% %
3358% %
3359% %
3360%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3361%
3362% NegateImage() negates the colors in the reference image. The grayscale
3363% option means that only grayscale values within the image are negated.
3364%
3365% The format of the NegateImageChannel method is:
3366%
3367% MagickBooleanType NegateImage(Image *image,
3368% const MagickBooleanType grayscale)
3369% MagickBooleanType NegateImageChannel(Image *image,
3370% const ChannelType channel,const MagickBooleanType grayscale)
3371%
3372% A description of each parameter follows:
3373%
3374% o image: the image.
3375%
3376% o channel: the channel.
3377%
3378% o grayscale: If MagickTrue, only negate grayscale pixels within the image.
3379%
3380*/
3381
3382MagickExport MagickBooleanType NegateImage(Image *image,
3383 const MagickBooleanType grayscale)
3384{
3385 MagickBooleanType
3386 status;
3387
3388 status=NegateImageChannel(image,DefaultChannels,grayscale);
3389 return(status);
3390}
3391
3392MagickExport MagickBooleanType NegateImageChannel(Image *image,
3393 const ChannelType channel,const MagickBooleanType grayscale)
3394{
3395#define NegateImageTag "Negate/Image"
3396
cristyc4c8d132010-01-07 01:58:38 +00003397 CacheView
3398 *image_view;
3399
cristy3ed852e2009-09-05 21:47:34 +00003400 ExceptionInfo
3401 *exception;
3402
cristy3ed852e2009-09-05 21:47:34 +00003403 MagickBooleanType
3404 status;
3405
cristybb503372010-05-27 20:51:26 +00003406 MagickOffsetType
3407 progress;
3408
3409 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003410 i;
3411
cristybb503372010-05-27 20:51:26 +00003412 ssize_t
3413 y;
3414
cristy3ed852e2009-09-05 21:47:34 +00003415 assert(image != (Image *) NULL);
3416 assert(image->signature == MagickSignature);
3417 if (image->debug != MagickFalse)
3418 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3419 if (image->storage_class == PseudoClass)
3420 {
3421 /*
3422 Negate colormap.
3423 */
cristyb5d5f722009-11-04 03:03:49 +00003424#if defined(MAGICKCORE_OPENMP_SUPPORT)
3425 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003426#endif
cristybb503372010-05-27 20:51:26 +00003427 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00003428 {
3429 if (grayscale != MagickFalse)
3430 if ((image->colormap[i].red != image->colormap[i].green) ||
3431 (image->colormap[i].green != image->colormap[i].blue))
3432 continue;
3433 if ((channel & RedChannel) != 0)
3434 image->colormap[i].red=(Quantum) QuantumRange-
3435 image->colormap[i].red;
3436 if ((channel & GreenChannel) != 0)
3437 image->colormap[i].green=(Quantum) QuantumRange-
3438 image->colormap[i].green;
3439 if ((channel & BlueChannel) != 0)
3440 image->colormap[i].blue=(Quantum) QuantumRange-
3441 image->colormap[i].blue;
3442 }
3443 }
3444 /*
3445 Negate image.
3446 */
3447 status=MagickTrue;
3448 progress=0;
3449 exception=(&image->exception);
3450 image_view=AcquireCacheView(image);
3451 if (grayscale != MagickFalse)
3452 {
cristyb5d5f722009-11-04 03:03:49 +00003453#if defined(MAGICKCORE_OPENMP_SUPPORT)
3454 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003455#endif
cristybb503372010-05-27 20:51:26 +00003456 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003457 {
3458 MagickBooleanType
3459 sync;
3460
cristy4c08aed2011-07-01 19:47:50 +00003461 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00003462 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003463
cristy8d4629b2010-08-30 17:59:46 +00003464 register ssize_t
3465 x;
3466
cristy3ed852e2009-09-05 21:47:34 +00003467 if (status == MagickFalse)
3468 continue;
3469 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,
3470 exception);
cristy4c08aed2011-07-01 19:47:50 +00003471 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00003472 {
3473 status=MagickFalse;
3474 continue;
3475 }
cristybb503372010-05-27 20:51:26 +00003476 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003477 {
cristy4c08aed2011-07-01 19:47:50 +00003478 if ((GetPixelRed(image,q) != GetPixelGreen(image,q)) ||
3479 (GetPixelGreen(image,q) != GetPixelBlue(image,q)))
cristy3ed852e2009-09-05 21:47:34 +00003480 {
cristy4c08aed2011-07-01 19:47:50 +00003481 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00003482 continue;
3483 }
3484 if ((channel & RedChannel) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003485 SetPixelRed(image,QuantumRange-GetPixelRed(image,q),q);
cristy3ed852e2009-09-05 21:47:34 +00003486 if ((channel & GreenChannel) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003487 SetPixelGreen(image,QuantumRange-GetPixelGreen(image,q),q);
cristy3ed852e2009-09-05 21:47:34 +00003488 if ((channel & BlueChannel) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003489 SetPixelBlue(image,QuantumRange-GetPixelBlue(image,q),q);
3490 if (((channel & BlackChannel) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00003491 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +00003492 SetPixelBlack(image,QuantumRange-GetPixelBlack(image,q),q);
3493 if ((channel & OpacityChannel) != 0)
3494 SetPixelAlpha(image,QuantumRange-GetPixelAlpha(image,q),q);
3495 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00003496 }
3497 sync=SyncCacheViewAuthenticPixels(image_view,exception);
3498 if (sync == MagickFalse)
3499 status=MagickFalse;
3500 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3501 {
3502 MagickBooleanType
3503 proceed;
3504
cristyb5d5f722009-11-04 03:03:49 +00003505#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00003506 #pragma omp critical (MagickCore_NegateImageChannel)
3507#endif
3508 proceed=SetImageProgress(image,NegateImageTag,progress++,
3509 image->rows);
3510 if (proceed == MagickFalse)
3511 status=MagickFalse;
3512 }
3513 }
3514 image_view=DestroyCacheView(image_view);
3515 return(MagickTrue);
3516 }
3517 /*
3518 Negate image.
3519 */
cristyb5d5f722009-11-04 03:03:49 +00003520#if defined(MAGICKCORE_OPENMP_SUPPORT)
3521 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003522#endif
cristybb503372010-05-27 20:51:26 +00003523 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003524 {
cristy4c08aed2011-07-01 19:47:50 +00003525 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00003526 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003527
cristy8d4629b2010-08-30 17:59:46 +00003528 register ssize_t
3529 x;
3530
cristy3ed852e2009-09-05 21:47:34 +00003531 if (status == MagickFalse)
3532 continue;
3533 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00003534 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00003535 {
3536 status=MagickFalse;
3537 continue;
3538 }
cristybb503372010-05-27 20:51:26 +00003539 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003540 {
3541 if ((channel & RedChannel) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003542 SetPixelRed(image,QuantumRange-GetPixelRed(image,q),q);
cristy3ed852e2009-09-05 21:47:34 +00003543 if ((channel & GreenChannel) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003544 SetPixelGreen(image,QuantumRange-GetPixelGreen(image,q),q);
cristy3ed852e2009-09-05 21:47:34 +00003545 if ((channel & BlueChannel) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003546 SetPixelBlue(image,QuantumRange-GetPixelBlue(image,q),q);
3547 if (((channel & BlackChannel) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00003548 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +00003549 SetPixelBlack(image,QuantumRange-GetPixelBlack(image,q),q);
3550 if ((channel & OpacityChannel) != 0)
3551 SetPixelAlpha(image,QuantumRange-GetPixelAlpha(image,q),q);
3552 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00003553 }
3554 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
3555 status=MagickFalse;
3556 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3557 {
3558 MagickBooleanType
3559 proceed;
3560
cristyb5d5f722009-11-04 03:03:49 +00003561#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00003562 #pragma omp critical (MagickCore_NegateImageChannel)
3563#endif
3564 proceed=SetImageProgress(image,NegateImageTag,progress++,image->rows);
3565 if (proceed == MagickFalse)
3566 status=MagickFalse;
3567 }
3568 }
3569 image_view=DestroyCacheView(image_view);
3570 return(status);
3571}
3572
3573/*
3574%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3575% %
3576% %
3577% %
3578% N o r m a l i z e I m a g e %
3579% %
3580% %
3581% %
3582%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3583%
3584% The NormalizeImage() method enhances the contrast of a color image by
3585% mapping the darkest 2 percent of all pixel to black and the brightest
3586% 1 percent to white.
3587%
3588% The format of the NormalizeImage method is:
3589%
3590% MagickBooleanType NormalizeImage(Image *image)
3591% MagickBooleanType NormalizeImageChannel(Image *image,
3592% const ChannelType channel)
3593%
3594% A description of each parameter follows:
3595%
3596% o image: the image.
3597%
3598% o channel: the channel.
3599%
3600*/
3601
3602MagickExport MagickBooleanType NormalizeImage(Image *image)
3603{
3604 MagickBooleanType
3605 status;
3606
3607 status=NormalizeImageChannel(image,DefaultChannels);
3608 return(status);
3609}
3610
3611MagickExport MagickBooleanType NormalizeImageChannel(Image *image,
3612 const ChannelType channel)
3613{
3614 double
3615 black_point,
3616 white_point;
3617
cristy530239c2010-07-25 17:34:26 +00003618 black_point=(double) image->columns*image->rows*0.0015;
3619 white_point=(double) image->columns*image->rows*0.9995;
cristy3ed852e2009-09-05 21:47:34 +00003620 return(ContrastStretchImageChannel(image,channel,black_point,white_point));
3621}
3622
3623/*
3624%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3625% %
3626% %
3627% %
3628% S i g m o i d a l C o n t r a s t I m a g e %
3629% %
3630% %
3631% %
3632%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3633%
3634% SigmoidalContrastImage() adjusts the contrast of an image with a non-linear
3635% sigmoidal contrast algorithm. Increase the contrast of the image using a
3636% sigmoidal transfer function without saturating highlights or shadows.
3637% Contrast indicates how much to increase the contrast (0 is none; 3 is
3638% typical; 20 is pushing it); mid-point indicates where midtones fall in the
3639% resultant image (0 is white; 50% is middle-gray; 100% is black). Set
3640% sharpen to MagickTrue to increase the image contrast otherwise the contrast
3641% is reduced.
3642%
3643% The format of the SigmoidalContrastImage method is:
3644%
3645% MagickBooleanType SigmoidalContrastImage(Image *image,
3646% const MagickBooleanType sharpen,const char *levels)
3647% MagickBooleanType SigmoidalContrastImageChannel(Image *image,
3648% const ChannelType channel,const MagickBooleanType sharpen,
3649% const double contrast,const double midpoint)
3650%
3651% A description of each parameter follows:
3652%
3653% o image: the image.
3654%
3655% o channel: the channel.
3656%
3657% o sharpen: Increase or decrease image contrast.
3658%
cristyfa769582010-09-30 23:30:03 +00003659% o alpha: strength of the contrast, the larger the number the more
3660% 'threshold-like' it becomes.
cristy3ed852e2009-09-05 21:47:34 +00003661%
cristyfa769582010-09-30 23:30:03 +00003662% o beta: midpoint of the function as a color value 0 to QuantumRange.
cristy3ed852e2009-09-05 21:47:34 +00003663%
3664*/
3665
3666MagickExport MagickBooleanType SigmoidalContrastImage(Image *image,
3667 const MagickBooleanType sharpen,const char *levels)
3668{
3669 GeometryInfo
3670 geometry_info;
3671
3672 MagickBooleanType
3673 status;
3674
3675 MagickStatusType
3676 flags;
3677
3678 flags=ParseGeometry(levels,&geometry_info);
3679 if ((flags & SigmaValue) == 0)
3680 geometry_info.sigma=1.0*QuantumRange/2.0;
3681 if ((flags & PercentValue) != 0)
3682 geometry_info.sigma=1.0*QuantumRange*geometry_info.sigma/100.0;
3683 status=SigmoidalContrastImageChannel(image,DefaultChannels,sharpen,
3684 geometry_info.rho,geometry_info.sigma);
3685 return(status);
3686}
3687
3688MagickExport MagickBooleanType SigmoidalContrastImageChannel(Image *image,
3689 const ChannelType channel,const MagickBooleanType sharpen,
3690 const double contrast,const double midpoint)
3691{
3692#define SigmoidalContrastImageTag "SigmoidalContrast/Image"
3693
cristyc4c8d132010-01-07 01:58:38 +00003694 CacheView
3695 *image_view;
3696
cristy3ed852e2009-09-05 21:47:34 +00003697 ExceptionInfo
3698 *exception;
3699
cristy3ed852e2009-09-05 21:47:34 +00003700 MagickBooleanType
3701 status;
3702
cristybb503372010-05-27 20:51:26 +00003703 MagickOffsetType
3704 progress;
3705
cristy3ed852e2009-09-05 21:47:34 +00003706 MagickRealType
3707 *sigmoidal_map;
3708
cristybb503372010-05-27 20:51:26 +00003709 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003710 i;
3711
cristybb503372010-05-27 20:51:26 +00003712 ssize_t
3713 y;
3714
cristy3ed852e2009-09-05 21:47:34 +00003715 /*
3716 Allocate and initialize sigmoidal maps.
3717 */
3718 assert(image != (Image *) NULL);
3719 assert(image->signature == MagickSignature);
3720 if (image->debug != MagickFalse)
3721 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3722 sigmoidal_map=(MagickRealType *) AcquireQuantumMemory(MaxMap+1UL,
3723 sizeof(*sigmoidal_map));
3724 if (sigmoidal_map == (MagickRealType *) NULL)
3725 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
3726 image->filename);
3727 (void) ResetMagickMemory(sigmoidal_map,0,(MaxMap+1)*sizeof(*sigmoidal_map));
cristyb5d5f722009-11-04 03:03:49 +00003728#if defined(MAGICKCORE_OPENMP_SUPPORT)
3729 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003730#endif
cristybb503372010-05-27 20:51:26 +00003731 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +00003732 {
3733 if (sharpen != MagickFalse)
3734 {
3735 sigmoidal_map[i]=(MagickRealType) ScaleMapToQuantum((MagickRealType)
3736 (MaxMap*((1.0/(1.0+exp(contrast*(midpoint/(double) QuantumRange-
3737 (double) i/MaxMap))))-(1.0/(1.0+exp(contrast*(midpoint/
3738 (double) QuantumRange)))))/((1.0/(1.0+exp(contrast*(midpoint/
3739 (double) QuantumRange-1.0))))-(1.0/(1.0+exp(contrast*(midpoint/
3740 (double) QuantumRange)))))+0.5));
3741 continue;
3742 }
3743 sigmoidal_map[i]=(MagickRealType) ScaleMapToQuantum((MagickRealType)
3744 (MaxMap*(QuantumScale*midpoint-log((1.0-(1.0/(1.0+exp(midpoint/
3745 (double) QuantumRange*contrast))+((double) i/MaxMap)*((1.0/
3746 (1.0+exp(contrast*(midpoint/(double) QuantumRange-1.0))))-(1.0/
3747 (1.0+exp(midpoint/(double) QuantumRange*contrast))))))/
3748 (1.0/(1.0+exp(midpoint/(double) QuantumRange*contrast))+
3749 ((double) i/MaxMap)*((1.0/(1.0+exp(contrast*(midpoint/
3750 (double) QuantumRange-1.0))))-(1.0/(1.0+exp(midpoint/
3751 (double) QuantumRange*contrast))))))/contrast)));
3752 }
3753 if (image->storage_class == PseudoClass)
3754 {
3755 /*
3756 Sigmoidal-contrast enhance colormap.
3757 */
cristyb5d5f722009-11-04 03:03:49 +00003758#if defined(MAGICKCORE_OPENMP_SUPPORT)
3759 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003760#endif
cristybb503372010-05-27 20:51:26 +00003761 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00003762 {
3763 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003764 image->colormap[i].red=ClampToQuantum(sigmoidal_map[
cristy3ed852e2009-09-05 21:47:34 +00003765 ScaleQuantumToMap(image->colormap[i].red)]);
3766 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003767 image->colormap[i].green=ClampToQuantum(sigmoidal_map[
cristy3ed852e2009-09-05 21:47:34 +00003768 ScaleQuantumToMap(image->colormap[i].green)]);
3769 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003770 image->colormap[i].blue=ClampToQuantum(sigmoidal_map[
cristy3ed852e2009-09-05 21:47:34 +00003771 ScaleQuantumToMap(image->colormap[i].blue)]);
3772 if ((channel & OpacityChannel) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003773 image->colormap[i].alpha=ClampToQuantum(sigmoidal_map[
3774 ScaleQuantumToMap(image->colormap[i].alpha)]);
cristy3ed852e2009-09-05 21:47:34 +00003775 }
3776 }
3777 /*
3778 Sigmoidal-contrast enhance image.
3779 */
3780 status=MagickTrue;
3781 progress=0;
3782 exception=(&image->exception);
3783 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00003784#if defined(MAGICKCORE_OPENMP_SUPPORT)
3785 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003786#endif
cristybb503372010-05-27 20:51:26 +00003787 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003788 {
cristy4c08aed2011-07-01 19:47:50 +00003789 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00003790 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003791
cristy8d4629b2010-08-30 17:59:46 +00003792 register ssize_t
3793 x;
3794
cristy3ed852e2009-09-05 21:47:34 +00003795 if (status == MagickFalse)
3796 continue;
3797 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00003798 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00003799 {
3800 status=MagickFalse;
3801 continue;
3802 }
cristybb503372010-05-27 20:51:26 +00003803 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003804 {
3805 if ((channel & RedChannel) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003806 SetPixelRed(image,ClampToQuantum(sigmoidal_map[ScaleQuantumToMap(
3807 GetPixelRed(image,q))]),q);
cristy3ed852e2009-09-05 21:47:34 +00003808 if ((channel & GreenChannel) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003809 SetPixelGreen(image,ClampToQuantum(sigmoidal_map[ScaleQuantumToMap(
3810 GetPixelGreen(image,q))]),q);
cristy3ed852e2009-09-05 21:47:34 +00003811 if ((channel & BlueChannel) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003812 SetPixelBlue(image,ClampToQuantum(sigmoidal_map[ScaleQuantumToMap(
3813 GetPixelBlue(image,q))]),q);
3814 if (((channel & BlackChannel) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00003815 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +00003816 SetPixelBlack(image,ClampToQuantum(sigmoidal_map[ScaleQuantumToMap(
3817 GetPixelBlack(image,q))]),q);
3818 if ((channel & OpacityChannel) != 0)
3819 SetPixelAlpha(image,ClampToQuantum(sigmoidal_map[ScaleQuantumToMap(
3820 GetPixelAlpha(image,q))]),q);
3821 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00003822 }
3823 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
3824 status=MagickFalse;
3825 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3826 {
3827 MagickBooleanType
3828 proceed;
3829
cristyb5d5f722009-11-04 03:03:49 +00003830#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00003831 #pragma omp critical (MagickCore_SigmoidalContrastImageChannel)
3832#endif
3833 proceed=SetImageProgress(image,SigmoidalContrastImageTag,progress++,
3834 image->rows);
3835 if (proceed == MagickFalse)
3836 status=MagickFalse;
3837 }
3838 }
3839 image_view=DestroyCacheView(image_view);
3840 sigmoidal_map=(MagickRealType *) RelinquishMagickMemory(sigmoidal_map);
3841 return(status);
3842}