blob: b0a91aa6f577d2a1bc703b077922ec285633be6d [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*/
43#include "magick/studio.h"
44#include "magick/artifact.h"
45#include "magick/cache.h"
46#include "magick/cache-view.h"
47#include "magick/color.h"
48#include "magick/color-private.h"
49#include "magick/colorspace.h"
50#include "magick/composite-private.h"
51#include "magick/enhance.h"
52#include "magick/exception.h"
53#include "magick/exception-private.h"
cristya28d6b82010-01-11 20:03:47 +000054#include "magick/fx.h"
cristy3ed852e2009-09-05 21:47:34 +000055#include "magick/gem.h"
56#include "magick/geometry.h"
57#include "magick/histogram.h"
58#include "magick/image.h"
59#include "magick/image-private.h"
60#include "magick/memory_.h"
61#include "magick/monitor.h"
62#include "magick/monitor-private.h"
63#include "magick/option.h"
cristy3635df22011-03-25 00:16:16 +000064#include "magick/pixel-private.h"
cristy3ed852e2009-09-05 21:47:34 +000065#include "magick/quantum.h"
66#include "magick/quantum-private.h"
67#include "magick/resample.h"
68#include "magick/resample-private.h"
69#include "magick/statistic.h"
70#include "magick/string_.h"
cristyf2f27272009-12-17 14:48:46 +000071#include "magick/string-private.h"
cristy3ed852e2009-09-05 21:47:34 +000072#include "magick/thread-private.h"
73#include "magick/token.h"
74#include "magick/xml-tree.h"
75
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
cristy2b726bd2010-01-11 01:05:39 +0000118 mean,sans,gamma,logmean;
anthony4efe5972009-09-11 06:46:40 +0000119
120 logmean=log(0.5);
cristy3ed852e2009-09-05 21:47:34 +0000121
122 if ((channel & SyncChannels) != 0 )
123 {
124 /*
125 Apply gamma correction equally accross all given channels
126 */
cristy2b726bd2010-01-11 01:05:39 +0000127 (void) GetImageChannelMean(image,channel,&mean,&sans,&image->exception);
128 gamma=log(mean*QuantumScale)/logmean;
cristy3ed852e2009-09-05 21:47:34 +0000129 return LevelImageChannel(image, channel,
130 0.0, (double)QuantumRange, gamma);
131 }
132
133 /*
134 auto-gamma each channel separateally
135 */
136 status = MagickTrue;
137 if ((channel & RedChannel) != 0)
138 {
cristy2b726bd2010-01-11 01:05:39 +0000139 (void) GetImageChannelMean(image,RedChannel,&mean,&sans,
140 &image->exception);
141 gamma=log(mean*QuantumScale)/logmean;
cristy3ed852e2009-09-05 21:47:34 +0000142 status = status && LevelImageChannel(image, RedChannel,
143 0.0, (double)QuantumRange, gamma);
144 }
145 if ((channel & GreenChannel) != 0)
146 {
cristy2b726bd2010-01-11 01:05:39 +0000147 (void) GetImageChannelMean(image,GreenChannel,&mean,&sans,
148 &image->exception);
149 gamma=log(mean*QuantumScale)/logmean;
cristy3ed852e2009-09-05 21:47:34 +0000150 status = status && LevelImageChannel(image, GreenChannel,
151 0.0, (double)QuantumRange, gamma);
152 }
153 if ((channel & BlueChannel) != 0)
154 {
cristy2b726bd2010-01-11 01:05:39 +0000155 (void) GetImageChannelMean(image,BlueChannel,&mean,&sans,
156 &image->exception);
157 gamma=log(mean*QuantumScale)/logmean;
cristy3ed852e2009-09-05 21:47:34 +0000158 status = status && LevelImageChannel(image, BlueChannel,
159 0.0, (double)QuantumRange, gamma);
160 }
161 if (((channel & OpacityChannel) != 0) &&
162 (image->matte == MagickTrue))
163 {
cristy2b726bd2010-01-11 01:05:39 +0000164 (void) GetImageChannelMean(image,OpacityChannel,&mean,&sans,
165 &image->exception);
166 gamma=log(mean*QuantumScale)/logmean;
cristy3ed852e2009-09-05 21:47:34 +0000167 status = status && LevelImageChannel(image, OpacityChannel,
168 0.0, (double)QuantumRange, gamma);
169 }
170 if (((channel & IndexChannel) != 0) &&
171 (image->colorspace == CMYKColorspace))
172 {
cristy2b726bd2010-01-11 01:05:39 +0000173 (void) GetImageChannelMean(image,IndexChannel,&mean,&sans,
174 &image->exception);
175 gamma=log(mean*QuantumScale)/logmean;
cristy3ed852e2009-09-05 21:47:34 +0000176 status = status && LevelImageChannel(image, IndexChannel,
177 0.0, (double)QuantumRange, gamma);
178 }
179 return(status != 0 ? MagickTrue : MagickFalse);
180}
181
182/*
183%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
184% %
185% %
186% %
187% A u t o L e v e l I m a g e %
188% %
189% %
190% %
191%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
192%
193% AutoLevelImage() adjusts the levels of a particular image channel by
194% scaling the minimum and maximum values to the full quantum range.
195%
196% The format of the LevelImage method is:
197%
198% MagickBooleanType AutoLevelImage(Image *image)
199% MagickBooleanType AutoLevelImageChannel(Image *image,
200% const ChannelType channel)
201%
202% A description of each parameter follows:
203%
204% o image: The image to auto-level
205%
206% o channel: The channels to auto-level. If the special 'SyncChannels'
207% flag is set the min/max/mean value of all given channels is used for
208% all given channels, to all channels in the same way.
209%
210*/
211
212MagickExport MagickBooleanType AutoLevelImage(Image *image)
213{
214 return(AutoLevelImageChannel(image,DefaultChannels));
215}
216
217MagickExport MagickBooleanType AutoLevelImageChannel(Image *image,
218 const ChannelType channel)
219{
220 /*
221 This is simply a convenience function around a Min/Max Histogram Stretch
222 */
223 return MinMaxStretchImage(image, channel, 0.0, 0.0);
224}
225
226/*
227%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
228% %
229% %
230% %
cristya28d6b82010-01-11 20:03:47 +0000231% B r i g h t n e s s C o n t r a s t I m a g e %
232% %
233% %
234% %
235%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
236%
237% Use BrightnessContrastImage() to change the brightness and/or contrast of
238% an image. It converts the brightness and contrast parameters into slope
239% and intercept and calls a polynomical function to apply to the image.
240%
241% The format of the BrightnessContrastImage method is:
242%
243% MagickBooleanType BrightnessContrastImage(Image *image,
244% const double brightness,const double contrast)
245% MagickBooleanType BrightnessContrastImageChannel(Image *image,
246% const ChannelType channel,const double brightness,
247% const double contrast)
248%
249% A description of each parameter follows:
250%
251% o image: the image.
252%
253% o channel: the channel.
254%
255% o brightness: the brightness percent (-100 .. 100).
256%
257% o contrast: the contrast percent (-100 .. 100).
258%
259*/
260
261MagickExport MagickBooleanType BrightnessContrastImage(Image *image,
262 const double brightness,const double contrast)
263{
264 MagickBooleanType
265 status;
266
267 status=BrightnessContrastImageChannel(image,DefaultChannels,brightness,
268 contrast);
269 return(status);
270}
271
272MagickExport MagickBooleanType BrightnessContrastImageChannel(Image *image,
273 const ChannelType channel,const double brightness,const double contrast)
274{
275#define BrightnessContastImageTag "BrightnessContast/Image"
276
277 double
278 alpha,
279 intercept,
280 coefficients[2],
281 slope;
282
283 MagickBooleanType
284 status;
285
286 /*
287 Compute slope and intercept.
288 */
289 assert(image != (Image *) NULL);
290 assert(image->signature == MagickSignature);
291 if (image->debug != MagickFalse)
292 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
293 alpha=contrast;
cristy4205a3c2010-09-12 20:19:59 +0000294 slope=tan((double) (MagickPI*(alpha/100.0+1.0)/4.0));
cristya28d6b82010-01-11 20:03:47 +0000295 if (slope < 0.0)
296 slope=0.0;
297 intercept=brightness/100.0+((100-brightness)/200.0)*(1.0-slope);
298 coefficients[0]=slope;
299 coefficients[1]=intercept;
300 status=FunctionImageChannel(image,channel,PolynomialFunction,2,coefficients,
301 &image->exception);
302 return(status);
303}
304
305/*
306%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
307% %
308% %
309% %
cristy3ed852e2009-09-05 21:47:34 +0000310% C o l o r D e c i s i o n L i s t I m a g e %
311% %
312% %
313% %
314%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
315%
316% ColorDecisionListImage() accepts a lightweight Color Correction Collection
317% (CCC) file which solely contains one or more color corrections and applies
318% the correction to the image. Here is a sample CCC file:
319%
320% <ColorCorrectionCollection xmlns="urn:ASC:CDL:v1.2">
321% <ColorCorrection id="cc03345">
322% <SOPNode>
323% <Slope> 0.9 1.2 0.5 </Slope>
324% <Offset> 0.4 -0.5 0.6 </Offset>
325% <Power> 1.0 0.8 1.5 </Power>
326% </SOPNode>
327% <SATNode>
328% <Saturation> 0.85 </Saturation>
329% </SATNode>
330% </ColorCorrection>
331% </ColorCorrectionCollection>
332%
333% which includes the slop, offset, and power for each of the RGB channels
334% as well as the saturation.
335%
336% The format of the ColorDecisionListImage method is:
337%
338% MagickBooleanType ColorDecisionListImage(Image *image,
339% const char *color_correction_collection)
340%
341% A description of each parameter follows:
342%
343% o image: the image.
344%
345% o color_correction_collection: the color correction collection in XML.
346%
347*/
348MagickExport MagickBooleanType ColorDecisionListImage(Image *image,
349 const char *color_correction_collection)
350{
351#define ColorDecisionListCorrectImageTag "ColorDecisionList/Image"
352
353 typedef struct _Correction
354 {
355 double
356 slope,
357 offset,
358 power;
359 } Correction;
360
361 typedef struct _ColorCorrection
362 {
363 Correction
364 red,
365 green,
366 blue;
367
368 double
369 saturation;
370 } ColorCorrection;
371
cristyc4c8d132010-01-07 01:58:38 +0000372 CacheView
373 *image_view;
374
cristy3ed852e2009-09-05 21:47:34 +0000375 char
376 token[MaxTextExtent];
377
378 ColorCorrection
379 color_correction;
380
381 const char
382 *content,
383 *p;
384
385 ExceptionInfo
386 *exception;
387
cristy3ed852e2009-09-05 21:47:34 +0000388 MagickBooleanType
389 status;
390
cristybb503372010-05-27 20:51:26 +0000391 MagickOffsetType
392 progress;
393
cristy3ed852e2009-09-05 21:47:34 +0000394 PixelPacket
395 *cdl_map;
396
cristybb503372010-05-27 20:51:26 +0000397 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000398 i;
399
cristybb503372010-05-27 20:51:26 +0000400 ssize_t
401 y;
402
cristy3ed852e2009-09-05 21:47:34 +0000403 XMLTreeInfo
404 *cc,
405 *ccc,
406 *sat,
407 *sop;
408
cristy3ed852e2009-09-05 21:47:34 +0000409 /*
410 Allocate and initialize cdl maps.
411 */
412 assert(image != (Image *) NULL);
413 assert(image->signature == MagickSignature);
414 if (image->debug != MagickFalse)
415 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
416 if (color_correction_collection == (const char *) NULL)
417 return(MagickFalse);
418 ccc=NewXMLTree((const char *) color_correction_collection,&image->exception);
419 if (ccc == (XMLTreeInfo *) NULL)
420 return(MagickFalse);
421 cc=GetXMLTreeChild(ccc,"ColorCorrection");
422 if (cc == (XMLTreeInfo *) NULL)
423 {
424 ccc=DestroyXMLTree(ccc);
425 return(MagickFalse);
426 }
427 color_correction.red.slope=1.0;
428 color_correction.red.offset=0.0;
429 color_correction.red.power=1.0;
430 color_correction.green.slope=1.0;
431 color_correction.green.offset=0.0;
432 color_correction.green.power=1.0;
433 color_correction.blue.slope=1.0;
434 color_correction.blue.offset=0.0;
435 color_correction.blue.power=1.0;
436 color_correction.saturation=0.0;
437 sop=GetXMLTreeChild(cc,"SOPNode");
438 if (sop != (XMLTreeInfo *) NULL)
439 {
440 XMLTreeInfo
441 *offset,
442 *power,
443 *slope;
444
445 slope=GetXMLTreeChild(sop,"Slope");
446 if (slope != (XMLTreeInfo *) NULL)
447 {
448 content=GetXMLTreeContent(slope);
449 p=(const char *) content;
450 for (i=0; (*p != '\0') && (i < 3); i++)
451 {
452 GetMagickToken(p,&p,token);
453 if (*token == ',')
454 GetMagickToken(p,&p,token);
455 switch (i)
456 {
cristyf2f27272009-12-17 14:48:46 +0000457 case 0: color_correction.red.slope=StringToDouble(token); break;
458 case 1: color_correction.green.slope=StringToDouble(token); break;
459 case 2: color_correction.blue.slope=StringToDouble(token); break;
cristy3ed852e2009-09-05 21:47:34 +0000460 }
461 }
462 }
463 offset=GetXMLTreeChild(sop,"Offset");
464 if (offset != (XMLTreeInfo *) NULL)
465 {
466 content=GetXMLTreeContent(offset);
467 p=(const char *) content;
468 for (i=0; (*p != '\0') && (i < 3); i++)
469 {
470 GetMagickToken(p,&p,token);
471 if (*token == ',')
472 GetMagickToken(p,&p,token);
473 switch (i)
474 {
cristyf2f27272009-12-17 14:48:46 +0000475 case 0: color_correction.red.offset=StringToDouble(token); break;
476 case 1: color_correction.green.offset=StringToDouble(token); break;
477 case 2: color_correction.blue.offset=StringToDouble(token); break;
cristy3ed852e2009-09-05 21:47:34 +0000478 }
479 }
480 }
481 power=GetXMLTreeChild(sop,"Power");
482 if (power != (XMLTreeInfo *) NULL)
483 {
484 content=GetXMLTreeContent(power);
485 p=(const char *) content;
486 for (i=0; (*p != '\0') && (i < 3); i++)
487 {
488 GetMagickToken(p,&p,token);
489 if (*token == ',')
490 GetMagickToken(p,&p,token);
491 switch (i)
492 {
cristyf2f27272009-12-17 14:48:46 +0000493 case 0: color_correction.red.power=StringToDouble(token); break;
494 case 1: color_correction.green.power=StringToDouble(token); break;
495 case 2: color_correction.blue.power=StringToDouble(token); break;
cristy3ed852e2009-09-05 21:47:34 +0000496 }
497 }
498 }
499 }
500 sat=GetXMLTreeChild(cc,"SATNode");
501 if (sat != (XMLTreeInfo *) NULL)
502 {
503 XMLTreeInfo
504 *saturation;
505
506 saturation=GetXMLTreeChild(sat,"Saturation");
507 if (saturation != (XMLTreeInfo *) NULL)
508 {
509 content=GetXMLTreeContent(saturation);
510 p=(const char *) content;
511 GetMagickToken(p,&p,token);
cristyf2f27272009-12-17 14:48:46 +0000512 color_correction.saturation=StringToDouble(token);
cristy3ed852e2009-09-05 21:47:34 +0000513 }
514 }
515 ccc=DestroyXMLTree(ccc);
516 if (image->debug != MagickFalse)
517 {
518 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
519 " Color Correction Collection:");
520 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000521 " color_correction.red.slope: %g",color_correction.red.slope);
cristy3ed852e2009-09-05 21:47:34 +0000522 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000523 " color_correction.red.offset: %g",color_correction.red.offset);
cristy3ed852e2009-09-05 21:47:34 +0000524 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000525 " color_correction.red.power: %g",color_correction.red.power);
cristy3ed852e2009-09-05 21:47:34 +0000526 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000527 " color_correction.green.slope: %g",color_correction.green.slope);
cristy3ed852e2009-09-05 21:47:34 +0000528 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000529 " color_correction.green.offset: %g",color_correction.green.offset);
cristy3ed852e2009-09-05 21:47:34 +0000530 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000531 " color_correction.green.power: %g",color_correction.green.power);
cristy3ed852e2009-09-05 21:47:34 +0000532 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000533 " color_correction.blue.slope: %g",color_correction.blue.slope);
cristy3ed852e2009-09-05 21:47:34 +0000534 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000535 " color_correction.blue.offset: %g",color_correction.blue.offset);
cristy3ed852e2009-09-05 21:47:34 +0000536 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000537 " color_correction.blue.power: %g",color_correction.blue.power);
cristy3ed852e2009-09-05 21:47:34 +0000538 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000539 " color_correction.saturation: %g",color_correction.saturation);
cristy3ed852e2009-09-05 21:47:34 +0000540 }
541 cdl_map=(PixelPacket *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*cdl_map));
542 if (cdl_map == (PixelPacket *) NULL)
543 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
544 image->filename);
cristyb5d5f722009-11-04 03:03:49 +0000545#if defined(MAGICKCORE_OPENMP_SUPPORT)
546 #pragma omp parallel for schedule(dynamic,4)
cristy3ed852e2009-09-05 21:47:34 +0000547#endif
cristybb503372010-05-27 20:51:26 +0000548 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +0000549 {
cristyce70c172010-01-07 17:15:30 +0000550 cdl_map[i].red=ClampToQuantum((MagickRealType) ScaleMapToQuantum((
cristy3ed852e2009-09-05 21:47:34 +0000551 MagickRealType) (MaxMap*(pow(color_correction.red.slope*i/MaxMap+
552 color_correction.red.offset,color_correction.red.power)))));
cristyce70c172010-01-07 17:15:30 +0000553 cdl_map[i].green=ClampToQuantum((MagickRealType) ScaleMapToQuantum((
cristy3ed852e2009-09-05 21:47:34 +0000554 MagickRealType) (MaxMap*(pow(color_correction.green.slope*i/MaxMap+
555 color_correction.green.offset,color_correction.green.power)))));
cristyce70c172010-01-07 17:15:30 +0000556 cdl_map[i].blue=ClampToQuantum((MagickRealType) ScaleMapToQuantum((
cristy3ed852e2009-09-05 21:47:34 +0000557 MagickRealType) (MaxMap*(pow(color_correction.blue.slope*i/MaxMap+
558 color_correction.blue.offset,color_correction.blue.power)))));
559 }
560 if (image->storage_class == PseudoClass)
561 {
562 /*
563 Apply transfer function to colormap.
564 */
cristyb5d5f722009-11-04 03:03:49 +0000565#if defined(MAGICKCORE_OPENMP_SUPPORT)
566 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000567#endif
cristybb503372010-05-27 20:51:26 +0000568 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +0000569 {
570 double
571 luma;
572
573 luma=0.2126*image->colormap[i].red+0.7152*image->colormap[i].green+
574 0.0722*image->colormap[i].blue;
cristyce70c172010-01-07 17:15:30 +0000575 image->colormap[i].red=ClampToQuantum(luma+color_correction.saturation*
cristy3ed852e2009-09-05 21:47:34 +0000576 cdl_map[ScaleQuantumToMap(image->colormap[i].red)].red-luma);
cristyce70c172010-01-07 17:15:30 +0000577 image->colormap[i].green=ClampToQuantum(luma+
cristy3ed852e2009-09-05 21:47:34 +0000578 color_correction.saturation*cdl_map[ScaleQuantumToMap(
579 image->colormap[i].green)].green-luma);
cristyce70c172010-01-07 17:15:30 +0000580 image->colormap[i].blue=ClampToQuantum(luma+color_correction.saturation*
cristy3ed852e2009-09-05 21:47:34 +0000581 cdl_map[ScaleQuantumToMap(image->colormap[i].blue)].blue-luma);
582 }
583 }
584 /*
585 Apply transfer function to image.
586 */
587 status=MagickTrue;
588 progress=0;
589 exception=(&image->exception);
590 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +0000591#if defined(MAGICKCORE_OPENMP_SUPPORT)
592 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000593#endif
cristybb503372010-05-27 20:51:26 +0000594 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000595 {
596 double
597 luma;
598
cristy3ed852e2009-09-05 21:47:34 +0000599 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +0000600 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000601
cristy8d4629b2010-08-30 17:59:46 +0000602 register ssize_t
603 x;
604
cristy3ed852e2009-09-05 21:47:34 +0000605 if (status == MagickFalse)
606 continue;
607 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
608 if (q == (PixelPacket *) NULL)
609 {
610 status=MagickFalse;
611 continue;
612 }
cristybb503372010-05-27 20:51:26 +0000613 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000614 {
615 luma=0.2126*q->red+0.7152*q->green+0.0722*q->blue;
cristyce70c172010-01-07 17:15:30 +0000616 q->red=ClampToQuantum(luma+color_correction.saturation*
cristy3ed852e2009-09-05 21:47:34 +0000617 (cdl_map[ScaleQuantumToMap(q->red)].red-luma));
cristyce70c172010-01-07 17:15:30 +0000618 q->green=ClampToQuantum(luma+color_correction.saturation*
cristy3ed852e2009-09-05 21:47:34 +0000619 (cdl_map[ScaleQuantumToMap(q->green)].green-luma));
cristyce70c172010-01-07 17:15:30 +0000620 q->blue=ClampToQuantum(luma+color_correction.saturation*
cristy3ed852e2009-09-05 21:47:34 +0000621 (cdl_map[ScaleQuantumToMap(q->blue)].blue-luma));
622 q++;
623 }
624 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
625 status=MagickFalse;
626 if (image->progress_monitor != (MagickProgressMonitor) NULL)
627 {
628 MagickBooleanType
629 proceed;
630
cristyb5d5f722009-11-04 03:03:49 +0000631#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +0000632 #pragma omp critical (MagickCore_ColorDecisionListImageChannel)
633#endif
634 proceed=SetImageProgress(image,ColorDecisionListCorrectImageTag,
635 progress++,image->rows);
636 if (proceed == MagickFalse)
637 status=MagickFalse;
638 }
639 }
640 image_view=DestroyCacheView(image_view);
641 cdl_map=(PixelPacket *) RelinquishMagickMemory(cdl_map);
642 return(status);
643}
644
645/*
646%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
647% %
648% %
649% %
650% C l u t I m a g e %
651% %
652% %
653% %
654%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
655%
656% ClutImage() replaces each color value in the given image, by using it as an
657% index to lookup a replacement color value in a Color Look UP Table in the
cristycee97112010-05-28 00:44:52 +0000658% form of an image. The values are extracted along a diagonal of the CLUT
cristy3ed852e2009-09-05 21:47:34 +0000659% image so either a horizontal or vertial gradient image can be used.
660%
661% Typically this is used to either re-color a gray-scale image according to a
662% color gradient in the CLUT image, or to perform a freeform histogram
663% (level) adjustment according to the (typically gray-scale) gradient in the
664% CLUT image.
665%
666% When the 'channel' mask includes the matte/alpha transparency channel but
667% one image has no such channel it is assumed that that image is a simple
668% gray-scale image that will effect the alpha channel values, either for
669% gray-scale coloring (with transparent or semi-transparent colors), or
670% a histogram adjustment of existing alpha channel values. If both images
671% have matte channels, direct and normal indexing is applied, which is rarely
672% used.
673%
674% The format of the ClutImage method is:
675%
676% MagickBooleanType ClutImage(Image *image,Image *clut_image)
677% MagickBooleanType ClutImageChannel(Image *image,
678% const ChannelType channel,Image *clut_image)
679%
680% A description of each parameter follows:
681%
682% o image: the image, which is replaced by indexed CLUT values
683%
684% o clut_image: the color lookup table image for replacement color values.
685%
686% o channel: the channel.
687%
688*/
689
690MagickExport MagickBooleanType ClutImage(Image *image,const Image *clut_image)
691{
692 return(ClutImageChannel(image,DefaultChannels,clut_image));
693}
694
695MagickExport MagickBooleanType ClutImageChannel(Image *image,
696 const ChannelType channel,const Image *clut_image)
697{
698#define ClutImageTag "Clut/Image"
699
cristyfa112112010-01-04 17:48:07 +0000700 CacheView
701 *image_view;
702
cristy3ed852e2009-09-05 21:47:34 +0000703 ExceptionInfo
704 *exception;
705
cristy3ed852e2009-09-05 21:47:34 +0000706 MagickBooleanType
707 status;
708
cristybb503372010-05-27 20:51:26 +0000709 MagickOffsetType
710 progress;
711
cristy3ed852e2009-09-05 21:47:34 +0000712 MagickPixelPacket
cristy49f37242011-03-22 18:18:23 +0000713 *clut_map;
714
715 register ssize_t
716 i;
cristy3ed852e2009-09-05 21:47:34 +0000717
cristybb503372010-05-27 20:51:26 +0000718 ssize_t
719 adjust,
720 y;
721
cristy3ed852e2009-09-05 21:47:34 +0000722 assert(image != (Image *) NULL);
723 assert(image->signature == MagickSignature);
724 if (image->debug != MagickFalse)
725 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
726 assert(clut_image != (Image *) NULL);
727 assert(clut_image->signature == MagickSignature);
728 if (SetImageStorageClass(image,DirectClass) == MagickFalse)
729 return(MagickFalse);
cristy49f37242011-03-22 18:18:23 +0000730 clut_map=(MagickPixelPacket *) AcquireQuantumMemory(MaxMap+1UL,
731 sizeof(*clut_map));
732 if (clut_map == (MagickPixelPacket *) NULL)
733 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
734 image->filename);
cristy3ed852e2009-09-05 21:47:34 +0000735 /*
736 Clut image.
737 */
738 status=MagickTrue;
739 progress=0;
cristybb503372010-05-27 20:51:26 +0000740 adjust=(ssize_t) (clut_image->interpolate == IntegerInterpolatePixel ? 0 : 1);
cristy3ed852e2009-09-05 21:47:34 +0000741 exception=(&image->exception);
cristyd76c51e2011-03-26 00:21:26 +0000742 image_view=AcquireCacheView(image);
cristyaf6bc722011-03-25 19:16:14 +0000743#if defined(MAGICKCORE_OPENMP_SUPPORT)
744 #pragma omp parallel for schedule(dynamic,4)
745#endif
cristy49f37242011-03-22 18:18:23 +0000746 for (i=0; i <= (ssize_t) MaxMap; i++)
747 {
748 GetMagickPixelPacket(clut_image,clut_map+i);
cristyd76c51e2011-03-26 00:21:26 +0000749 (void) InterpolatePixelPacket(image,image_view,image->interpolate,
750 QuantumScale*i*(clut_image->columns-adjust),QuantumScale*i*
751 (clut_image->rows-adjust),clut_map+i,exception);
cristy49f37242011-03-22 18:18:23 +0000752 }
cristyb5d5f722009-11-04 03:03:49 +0000753#if defined(MAGICKCORE_OPENMP_SUPPORT)
754 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000755#endif
cristybb503372010-05-27 20:51:26 +0000756 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000757 {
cristy3635df22011-03-25 00:16:16 +0000758 MagickPixelPacket
759 pixel;
760
cristy3ed852e2009-09-05 21:47:34 +0000761 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +0000762 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +0000763
cristy3ed852e2009-09-05 21:47:34 +0000764 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +0000765 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000766
cristy8d4629b2010-08-30 17:59:46 +0000767 register ssize_t
768 x;
769
cristy3ed852e2009-09-05 21:47:34 +0000770 if (status == MagickFalse)
771 continue;
772 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
773 if (q == (PixelPacket *) NULL)
774 {
775 status=MagickFalse;
776 continue;
777 }
778 indexes=GetCacheViewAuthenticIndexQueue(image_view);
cristy3635df22011-03-25 00:16:16 +0000779 GetMagickPixelPacket(image,&pixel);
cristybb503372010-05-27 20:51:26 +0000780 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000781 {
cristy3635df22011-03-25 00:16:16 +0000782 SetMagickPixelPacket(image,q,indexes+x,&pixel);
cristy2253f172011-03-24 23:39:51 +0000783 if ((channel & RedChannel) != 0)
784 SetRedPixelComponent(q,ClampRedPixelComponent(clut_map+
785 ScaleQuantumToMap(q->red)));
786 if ((channel & GreenChannel) != 0)
787 SetGreenPixelComponent(q,ClampGreenPixelComponent(clut_map+
788 ScaleQuantumToMap(q->green)));
789 if ((channel & BlueChannel) != 0)
790 SetBluePixelComponent(q,ClampBluePixelComponent(clut_map+
791 ScaleQuantumToMap(q->blue)));
cristy3635df22011-03-25 00:16:16 +0000792 if ((channel & OpacityChannel) != 0)
793 {
794 if (clut_image->matte == MagickFalse)
795 q->opacity=(Quantum) (QuantumRange-MagickPixelIntensityToQuantum(
cristyd76c51e2011-03-26 00:21:26 +0000796 clut_map+ScaleQuantumToMap((Quantum) GetAlphaPixelComponent(q))));
cristy3635df22011-03-25 00:16:16 +0000797 else
798 if (image->matte == MagickFalse)
799 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(clut_map+
cristyd76c51e2011-03-26 00:21:26 +0000800 ScaleQuantumToMap((Quantum) MagickPixelIntensity(&pixel))));
cristy3635df22011-03-25 00:16:16 +0000801 else
802 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(
803 clut_map+ScaleQuantumToMap(q->opacity)));
804 }
cristy3ed852e2009-09-05 21:47:34 +0000805 if (((channel & IndexChannel) != 0) &&
806 (image->colorspace == CMYKColorspace))
cristy49f37242011-03-22 18:18:23 +0000807 indexes[x]=ClampToQuantum((clut_map+(ssize_t) indexes[x])->index);
cristy3ed852e2009-09-05 21:47:34 +0000808 q++;
809 }
810 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
811 status=MagickFalse;
812 if (image->progress_monitor != (MagickProgressMonitor) NULL)
813 {
814 MagickBooleanType
815 proceed;
816
cristyb5d5f722009-11-04 03:03:49 +0000817#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +0000818 #pragma omp critical (MagickCore_ClutImageChannel)
819#endif
820 proceed=SetImageProgress(image,ClutImageTag,progress++,image->rows);
821 if (proceed == MagickFalse)
822 status=MagickFalse;
823 }
824 }
825 image_view=DestroyCacheView(image_view);
cristy49f37242011-03-22 18:18:23 +0000826 clut_map=(MagickPixelPacket *) RelinquishMagickMemory(clut_map);
cristy3ed852e2009-09-05 21:47:34 +0000827 if ((clut_image->matte != MagickFalse) && ((channel & OpacityChannel) != 0))
828 (void) SetImageAlphaChannel(image,ActivateAlphaChannel);
829 return(status);
830}
831
832/*
833%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
834% %
835% %
836% %
837% C o n t r a s t I m a g e %
838% %
839% %
840% %
841%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
842%
843% ContrastImage() enhances the intensity differences between the lighter and
844% darker elements of the image. Set sharpen to a MagickTrue to increase the
845% image contrast otherwise the contrast is reduced.
846%
847% The format of the ContrastImage method is:
848%
849% MagickBooleanType ContrastImage(Image *image,
850% const MagickBooleanType sharpen)
851%
852% A description of each parameter follows:
853%
854% o image: the image.
855%
856% o sharpen: Increase or decrease image contrast.
857%
858*/
859
860static void Contrast(const int sign,Quantum *red,Quantum *green,Quantum *blue)
861{
862 double
863 brightness,
864 hue,
865 saturation;
866
867 /*
868 Enhance contrast: dark color become darker, light color become lighter.
869 */
870 assert(red != (Quantum *) NULL);
871 assert(green != (Quantum *) NULL);
872 assert(blue != (Quantum *) NULL);
873 hue=0.0;
874 saturation=0.0;
875 brightness=0.0;
876 ConvertRGBToHSB(*red,*green,*blue,&hue,&saturation,&brightness);
cristy31ac0f02011-03-12 02:04:47 +0000877 brightness+=0.5*sign*(0.5*(sin((double) (MagickPI*(brightness-0.5)))+1.0)-
cristy4205a3c2010-09-12 20:19:59 +0000878 brightness);
cristy3ed852e2009-09-05 21:47:34 +0000879 if (brightness > 1.0)
880 brightness=1.0;
881 else
882 if (brightness < 0.0)
883 brightness=0.0;
884 ConvertHSBToRGB(hue,saturation,brightness,red,green,blue);
885}
886
887MagickExport MagickBooleanType ContrastImage(Image *image,
888 const MagickBooleanType sharpen)
889{
890#define ContrastImageTag "Contrast/Image"
891
cristyc4c8d132010-01-07 01:58:38 +0000892 CacheView
893 *image_view;
894
cristy3ed852e2009-09-05 21:47:34 +0000895 ExceptionInfo
896 *exception;
897
898 int
899 sign;
900
cristy3ed852e2009-09-05 21:47:34 +0000901 MagickBooleanType
902 status;
903
cristybb503372010-05-27 20:51:26 +0000904 MagickOffsetType
905 progress;
906
907 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000908 i;
909
cristybb503372010-05-27 20:51:26 +0000910 ssize_t
911 y;
912
cristy3ed852e2009-09-05 21:47:34 +0000913 assert(image != (Image *) NULL);
914 assert(image->signature == MagickSignature);
915 if (image->debug != MagickFalse)
916 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
917 sign=sharpen != MagickFalse ? 1 : -1;
918 if (image->storage_class == PseudoClass)
919 {
920 /*
921 Contrast enhance colormap.
922 */
cristybb503372010-05-27 20:51:26 +0000923 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +0000924 Contrast(sign,&image->colormap[i].red,&image->colormap[i].green,
925 &image->colormap[i].blue);
926 }
927 /*
928 Contrast enhance image.
929 */
930 status=MagickTrue;
931 progress=0;
932 exception=(&image->exception);
933 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +0000934#if defined(MAGICKCORE_OPENMP_SUPPORT)
935 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000936#endif
cristybb503372010-05-27 20:51:26 +0000937 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000938 {
cristy3ed852e2009-09-05 21:47:34 +0000939 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +0000940 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000941
cristy8d4629b2010-08-30 17:59:46 +0000942 register ssize_t
943 x;
944
cristy3ed852e2009-09-05 21:47:34 +0000945 if (status == MagickFalse)
946 continue;
947 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
948 if (q == (PixelPacket *) NULL)
949 {
950 status=MagickFalse;
951 continue;
952 }
cristybb503372010-05-27 20:51:26 +0000953 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000954 {
955 Contrast(sign,&q->red,&q->green,&q->blue);
956 q++;
957 }
958 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
959 status=MagickFalse;
960 if (image->progress_monitor != (MagickProgressMonitor) NULL)
961 {
962 MagickBooleanType
963 proceed;
964
cristyb5d5f722009-11-04 03:03:49 +0000965#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +0000966 #pragma omp critical (MagickCore_ContrastImage)
967#endif
968 proceed=SetImageProgress(image,ContrastImageTag,progress++,image->rows);
969 if (proceed == MagickFalse)
970 status=MagickFalse;
971 }
972 }
973 image_view=DestroyCacheView(image_view);
974 return(status);
975}
976
977/*
978%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
979% %
980% %
981% %
982% C o n t r a s t S t r e t c h I m a g e %
983% %
984% %
985% %
986%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
987%
988% The ContrastStretchImage() is a simple image enhancement technique that
989% attempts to improve the contrast in an image by `stretching' the range of
990% intensity values it contains to span a desired range of values. It differs
991% from the more sophisticated histogram equalization in that it can only
992% apply % a linear scaling function to the image pixel values. As a result
993% the `enhancement' is less harsh.
994%
995% The format of the ContrastStretchImage method is:
996%
997% MagickBooleanType ContrastStretchImage(Image *image,
998% const char *levels)
999% MagickBooleanType ContrastStretchImageChannel(Image *image,
cristybb503372010-05-27 20:51:26 +00001000% const size_t channel,const double black_point,
cristy3ed852e2009-09-05 21:47:34 +00001001% const double white_point)
1002%
1003% A description of each parameter follows:
1004%
1005% o image: the image.
1006%
1007% o channel: the channel.
1008%
1009% o black_point: the black point.
1010%
1011% o white_point: the white point.
1012%
1013% o levels: Specify the levels where the black and white points have the
1014% range of 0 to number-of-pixels (e.g. 1%, 10x90%, etc.).
1015%
1016*/
1017
1018MagickExport MagickBooleanType ContrastStretchImage(Image *image,
1019 const char *levels)
1020{
1021 double
1022 black_point,
1023 white_point;
1024
1025 GeometryInfo
1026 geometry_info;
1027
1028 MagickBooleanType
1029 status;
1030
1031 MagickStatusType
1032 flags;
1033
1034 /*
1035 Parse levels.
1036 */
1037 if (levels == (char *) NULL)
1038 return(MagickFalse);
1039 flags=ParseGeometry(levels,&geometry_info);
1040 black_point=geometry_info.rho;
1041 white_point=(double) image->columns*image->rows;
1042 if ((flags & SigmaValue) != 0)
1043 white_point=geometry_info.sigma;
1044 if ((flags & PercentValue) != 0)
1045 {
1046 black_point*=(double) QuantumRange/100.0;
1047 white_point*=(double) QuantumRange/100.0;
1048 }
1049 if ((flags & SigmaValue) == 0)
1050 white_point=(double) image->columns*image->rows-black_point;
1051 status=ContrastStretchImageChannel(image,DefaultChannels,black_point,
1052 white_point);
1053 return(status);
1054}
1055
1056MagickExport MagickBooleanType ContrastStretchImageChannel(Image *image,
1057 const ChannelType channel,const double black_point,const double white_point)
1058{
1059#define MaxRange(color) ((MagickRealType) ScaleQuantumToMap((Quantum) (color)))
1060#define ContrastStretchImageTag "ContrastStretch/Image"
1061
cristyc4c8d132010-01-07 01:58:38 +00001062 CacheView
1063 *image_view;
1064
cristy3ed852e2009-09-05 21:47:34 +00001065 double
1066 intensity;
1067
1068 ExceptionInfo
1069 *exception;
1070
cristy3ed852e2009-09-05 21:47:34 +00001071 MagickBooleanType
1072 status;
1073
cristybb503372010-05-27 20:51:26 +00001074 MagickOffsetType
1075 progress;
1076
cristy3ed852e2009-09-05 21:47:34 +00001077 MagickPixelPacket
1078 black,
1079 *histogram,
1080 *stretch_map,
1081 white;
1082
cristybb503372010-05-27 20:51:26 +00001083 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001084 i;
1085
cristybb503372010-05-27 20:51:26 +00001086 ssize_t
1087 y;
1088
cristy3ed852e2009-09-05 21:47:34 +00001089 /*
1090 Allocate histogram and stretch map.
1091 */
1092 assert(image != (Image *) NULL);
1093 assert(image->signature == MagickSignature);
1094 if (image->debug != MagickFalse)
1095 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1096 histogram=(MagickPixelPacket *) AcquireQuantumMemory(MaxMap+1UL,
1097 sizeof(*histogram));
1098 stretch_map=(MagickPixelPacket *) AcquireQuantumMemory(MaxMap+1UL,
1099 sizeof(*stretch_map));
1100 if ((histogram == (MagickPixelPacket *) NULL) ||
1101 (stretch_map == (MagickPixelPacket *) NULL))
1102 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
1103 image->filename);
1104 /*
1105 Form histogram.
1106 */
1107 status=MagickTrue;
1108 exception=(&image->exception);
1109 (void) ResetMagickMemory(histogram,0,(MaxMap+1)*sizeof(*histogram));
1110 image_view=AcquireCacheView(image);
cristybb503372010-05-27 20:51:26 +00001111 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001112 {
1113 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001114 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00001115
1116 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00001117 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00001118
cristybb503372010-05-27 20:51:26 +00001119 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001120 x;
1121
1122 if (status == MagickFalse)
1123 continue;
1124 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
1125 if (p == (const PixelPacket *) NULL)
1126 {
1127 status=MagickFalse;
1128 continue;
1129 }
1130 indexes=GetCacheViewAuthenticIndexQueue(image_view);
1131 if (channel == DefaultChannels)
cristybb503372010-05-27 20:51:26 +00001132 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001133 {
1134 Quantum
1135 intensity;
1136
1137 intensity=PixelIntensityToQuantum(p);
1138 histogram[ScaleQuantumToMap(intensity)].red++;
1139 histogram[ScaleQuantumToMap(intensity)].green++;
1140 histogram[ScaleQuantumToMap(intensity)].blue++;
1141 histogram[ScaleQuantumToMap(intensity)].index++;
1142 p++;
1143 }
1144 else
cristybb503372010-05-27 20:51:26 +00001145 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001146 {
1147 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001148 histogram[ScaleQuantumToMap(GetRedPixelComponent(p))].red++;
cristy3ed852e2009-09-05 21:47:34 +00001149 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001150 histogram[ScaleQuantumToMap(GetGreenPixelComponent(p))].green++;
cristy3ed852e2009-09-05 21:47:34 +00001151 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001152 histogram[ScaleQuantumToMap(GetBluePixelComponent(p))].blue++;
cristy3ed852e2009-09-05 21:47:34 +00001153 if ((channel & OpacityChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001154 histogram[ScaleQuantumToMap(GetOpacityPixelComponent(p))].opacity++;
cristy3ed852e2009-09-05 21:47:34 +00001155 if (((channel & IndexChannel) != 0) &&
1156 (image->colorspace == CMYKColorspace))
1157 histogram[ScaleQuantumToMap(indexes[x])].index++;
1158 p++;
1159 }
1160 }
1161 /*
1162 Find the histogram boundaries by locating the black/white levels.
1163 */
1164 black.red=0.0;
1165 white.red=MaxRange(QuantumRange);
1166 if ((channel & RedChannel) != 0)
1167 {
1168 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00001169 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +00001170 {
1171 intensity+=histogram[i].red;
1172 if (intensity > black_point)
1173 break;
1174 }
1175 black.red=(MagickRealType) i;
1176 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00001177 for (i=(ssize_t) MaxMap; i != 0; i--)
cristy3ed852e2009-09-05 21:47:34 +00001178 {
1179 intensity+=histogram[i].red;
1180 if (intensity > ((double) image->columns*image->rows-white_point))
1181 break;
1182 }
1183 white.red=(MagickRealType) i;
1184 }
1185 black.green=0.0;
1186 white.green=MaxRange(QuantumRange);
1187 if ((channel & GreenChannel) != 0)
1188 {
1189 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00001190 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +00001191 {
1192 intensity+=histogram[i].green;
1193 if (intensity > black_point)
1194 break;
1195 }
1196 black.green=(MagickRealType) i;
1197 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00001198 for (i=(ssize_t) MaxMap; i != 0; i--)
cristy3ed852e2009-09-05 21:47:34 +00001199 {
1200 intensity+=histogram[i].green;
1201 if (intensity > ((double) image->columns*image->rows-white_point))
1202 break;
1203 }
1204 white.green=(MagickRealType) i;
1205 }
1206 black.blue=0.0;
1207 white.blue=MaxRange(QuantumRange);
1208 if ((channel & BlueChannel) != 0)
1209 {
1210 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00001211 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +00001212 {
1213 intensity+=histogram[i].blue;
1214 if (intensity > black_point)
1215 break;
1216 }
1217 black.blue=(MagickRealType) i;
1218 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00001219 for (i=(ssize_t) MaxMap; i != 0; i--)
cristy3ed852e2009-09-05 21:47:34 +00001220 {
1221 intensity+=histogram[i].blue;
1222 if (intensity > ((double) image->columns*image->rows-white_point))
1223 break;
1224 }
1225 white.blue=(MagickRealType) i;
1226 }
1227 black.opacity=0.0;
1228 white.opacity=MaxRange(QuantumRange);
1229 if ((channel & OpacityChannel) != 0)
1230 {
1231 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00001232 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +00001233 {
1234 intensity+=histogram[i].opacity;
1235 if (intensity > black_point)
1236 break;
1237 }
1238 black.opacity=(MagickRealType) i;
1239 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00001240 for (i=(ssize_t) MaxMap; i != 0; i--)
cristy3ed852e2009-09-05 21:47:34 +00001241 {
1242 intensity+=histogram[i].opacity;
1243 if (intensity > ((double) image->columns*image->rows-white_point))
1244 break;
1245 }
1246 white.opacity=(MagickRealType) i;
1247 }
1248 black.index=0.0;
1249 white.index=MaxRange(QuantumRange);
1250 if (((channel & IndexChannel) != 0) && (image->colorspace == CMYKColorspace))
1251 {
1252 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00001253 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +00001254 {
1255 intensity+=histogram[i].index;
1256 if (intensity > black_point)
1257 break;
1258 }
1259 black.index=(MagickRealType) i;
1260 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00001261 for (i=(ssize_t) MaxMap; i != 0; i--)
cristy3ed852e2009-09-05 21:47:34 +00001262 {
1263 intensity+=histogram[i].index;
1264 if (intensity > ((double) image->columns*image->rows-white_point))
1265 break;
1266 }
1267 white.index=(MagickRealType) i;
1268 }
1269 histogram=(MagickPixelPacket *) RelinquishMagickMemory(histogram);
1270 /*
1271 Stretch the histogram to create the stretched image mapping.
1272 */
1273 (void) ResetMagickMemory(stretch_map,0,(MaxMap+1)*sizeof(*stretch_map));
cristyb5d5f722009-11-04 03:03:49 +00001274#if defined(MAGICKCORE_OPENMP_SUPPORT)
1275 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001276#endif
cristybb503372010-05-27 20:51:26 +00001277 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +00001278 {
1279 if ((channel & RedChannel) != 0)
1280 {
cristybb503372010-05-27 20:51:26 +00001281 if (i < (ssize_t) black.red)
cristy3ed852e2009-09-05 21:47:34 +00001282 stretch_map[i].red=0.0;
1283 else
cristybb503372010-05-27 20:51:26 +00001284 if (i > (ssize_t) white.red)
cristy3ed852e2009-09-05 21:47:34 +00001285 stretch_map[i].red=(MagickRealType) QuantumRange;
1286 else
1287 if (black.red != white.red)
1288 stretch_map[i].red=(MagickRealType) ScaleMapToQuantum(
1289 (MagickRealType) (MaxMap*(i-black.red)/(white.red-black.red)));
1290 }
1291 if ((channel & GreenChannel) != 0)
1292 {
cristybb503372010-05-27 20:51:26 +00001293 if (i < (ssize_t) black.green)
cristy3ed852e2009-09-05 21:47:34 +00001294 stretch_map[i].green=0.0;
1295 else
cristybb503372010-05-27 20:51:26 +00001296 if (i > (ssize_t) white.green)
cristy3ed852e2009-09-05 21:47:34 +00001297 stretch_map[i].green=(MagickRealType) QuantumRange;
1298 else
1299 if (black.green != white.green)
1300 stretch_map[i].green=(MagickRealType) ScaleMapToQuantum(
1301 (MagickRealType) (MaxMap*(i-black.green)/(white.green-
1302 black.green)));
1303 }
1304 if ((channel & BlueChannel) != 0)
1305 {
cristybb503372010-05-27 20:51:26 +00001306 if (i < (ssize_t) black.blue)
cristy3ed852e2009-09-05 21:47:34 +00001307 stretch_map[i].blue=0.0;
1308 else
cristybb503372010-05-27 20:51:26 +00001309 if (i > (ssize_t) white.blue)
cristy3ed852e2009-09-05 21:47:34 +00001310 stretch_map[i].blue=(MagickRealType) QuantumRange;
1311 else
1312 if (black.blue != white.blue)
1313 stretch_map[i].blue=(MagickRealType) ScaleMapToQuantum(
1314 (MagickRealType) (MaxMap*(i-black.blue)/(white.blue-
1315 black.blue)));
1316 }
1317 if ((channel & OpacityChannel) != 0)
1318 {
cristybb503372010-05-27 20:51:26 +00001319 if (i < (ssize_t) black.opacity)
cristy3ed852e2009-09-05 21:47:34 +00001320 stretch_map[i].opacity=0.0;
1321 else
cristybb503372010-05-27 20:51:26 +00001322 if (i > (ssize_t) white.opacity)
cristy3ed852e2009-09-05 21:47:34 +00001323 stretch_map[i].opacity=(MagickRealType) QuantumRange;
1324 else
1325 if (black.opacity != white.opacity)
1326 stretch_map[i].opacity=(MagickRealType) ScaleMapToQuantum(
1327 (MagickRealType) (MaxMap*(i-black.opacity)/(white.opacity-
1328 black.opacity)));
1329 }
1330 if (((channel & IndexChannel) != 0) &&
1331 (image->colorspace == CMYKColorspace))
1332 {
cristybb503372010-05-27 20:51:26 +00001333 if (i < (ssize_t) black.index)
cristy3ed852e2009-09-05 21:47:34 +00001334 stretch_map[i].index=0.0;
1335 else
cristybb503372010-05-27 20:51:26 +00001336 if (i > (ssize_t) white.index)
cristy3ed852e2009-09-05 21:47:34 +00001337 stretch_map[i].index=(MagickRealType) QuantumRange;
1338 else
1339 if (black.index != white.index)
1340 stretch_map[i].index=(MagickRealType) ScaleMapToQuantum(
1341 (MagickRealType) (MaxMap*(i-black.index)/(white.index-
1342 black.index)));
1343 }
1344 }
1345 /*
1346 Stretch the image.
1347 */
1348 if (((channel & OpacityChannel) != 0) || (((channel & IndexChannel) != 0) &&
1349 (image->colorspace == CMYKColorspace)))
1350 image->storage_class=DirectClass;
1351 if (image->storage_class == PseudoClass)
1352 {
1353 /*
1354 Stretch colormap.
1355 */
cristyb5d5f722009-11-04 03:03:49 +00001356#if defined(MAGICKCORE_OPENMP_SUPPORT)
1357 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001358#endif
cristybb503372010-05-27 20:51:26 +00001359 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00001360 {
1361 if ((channel & RedChannel) != 0)
1362 {
1363 if (black.red != white.red)
cristyce70c172010-01-07 17:15:30 +00001364 image->colormap[i].red=ClampToQuantum(stretch_map[
cristy3ed852e2009-09-05 21:47:34 +00001365 ScaleQuantumToMap(image->colormap[i].red)].red);
1366 }
1367 if ((channel & GreenChannel) != 0)
1368 {
1369 if (black.green != white.green)
cristyce70c172010-01-07 17:15:30 +00001370 image->colormap[i].green=ClampToQuantum(stretch_map[
cristy3ed852e2009-09-05 21:47:34 +00001371 ScaleQuantumToMap(image->colormap[i].green)].green);
1372 }
1373 if ((channel & BlueChannel) != 0)
1374 {
1375 if (black.blue != white.blue)
cristyce70c172010-01-07 17:15:30 +00001376 image->colormap[i].blue=ClampToQuantum(stretch_map[
cristy3ed852e2009-09-05 21:47:34 +00001377 ScaleQuantumToMap(image->colormap[i].blue)].blue);
1378 }
1379 if ((channel & OpacityChannel) != 0)
1380 {
1381 if (black.opacity != white.opacity)
cristyce70c172010-01-07 17:15:30 +00001382 image->colormap[i].opacity=ClampToQuantum(stretch_map[
cristy3ed852e2009-09-05 21:47:34 +00001383 ScaleQuantumToMap(image->colormap[i].opacity)].opacity);
1384 }
1385 }
1386 }
1387 /*
1388 Stretch image.
1389 */
1390 status=MagickTrue;
1391 progress=0;
cristyb5d5f722009-11-04 03:03:49 +00001392#if defined(MAGICKCORE_OPENMP_SUPPORT)
1393 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001394#endif
cristybb503372010-05-27 20:51:26 +00001395 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001396 {
1397 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00001398 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00001399
cristy3ed852e2009-09-05 21:47:34 +00001400 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001401 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001402
cristy8d4629b2010-08-30 17:59:46 +00001403 register ssize_t
1404 x;
1405
cristy3ed852e2009-09-05 21:47:34 +00001406 if (status == MagickFalse)
1407 continue;
1408 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
1409 if (q == (PixelPacket *) NULL)
1410 {
1411 status=MagickFalse;
1412 continue;
1413 }
1414 indexes=GetCacheViewAuthenticIndexQueue(image_view);
cristybb503372010-05-27 20:51:26 +00001415 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001416 {
1417 if ((channel & RedChannel) != 0)
1418 {
1419 if (black.red != white.red)
cristyce70c172010-01-07 17:15:30 +00001420 q->red=ClampToQuantum(stretch_map[ScaleQuantumToMap(q->red)].red);
cristy3ed852e2009-09-05 21:47:34 +00001421 }
1422 if ((channel & GreenChannel) != 0)
1423 {
1424 if (black.green != white.green)
cristyce70c172010-01-07 17:15:30 +00001425 q->green=ClampToQuantum(stretch_map[ScaleQuantumToMap(
cristy3ed852e2009-09-05 21:47:34 +00001426 q->green)].green);
1427 }
1428 if ((channel & BlueChannel) != 0)
1429 {
1430 if (black.blue != white.blue)
cristyce70c172010-01-07 17:15:30 +00001431 q->blue=ClampToQuantum(stretch_map[ScaleQuantumToMap(
cristy3ed852e2009-09-05 21:47:34 +00001432 q->blue)].blue);
1433 }
1434 if ((channel & OpacityChannel) != 0)
1435 {
1436 if (black.opacity != white.opacity)
cristyce70c172010-01-07 17:15:30 +00001437 q->opacity=ClampToQuantum(stretch_map[ScaleQuantumToMap(
cristy3ed852e2009-09-05 21:47:34 +00001438 q->opacity)].opacity);
1439 }
1440 if (((channel & IndexChannel) != 0) &&
1441 (image->colorspace == CMYKColorspace))
1442 {
1443 if (black.index != white.index)
cristyce70c172010-01-07 17:15:30 +00001444 indexes[x]=(IndexPacket) ClampToQuantum(stretch_map[
cristy3ed852e2009-09-05 21:47:34 +00001445 ScaleQuantumToMap(indexes[x])].index);
1446 }
1447 q++;
1448 }
1449 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
1450 status=MagickFalse;
1451 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1452 {
1453 MagickBooleanType
1454 proceed;
1455
cristyb5d5f722009-11-04 03:03:49 +00001456#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00001457 #pragma omp critical (MagickCore_ContrastStretchImageChannel)
1458#endif
1459 proceed=SetImageProgress(image,ContrastStretchImageTag,progress++,
1460 image->rows);
1461 if (proceed == MagickFalse)
1462 status=MagickFalse;
1463 }
1464 }
1465 image_view=DestroyCacheView(image_view);
1466 stretch_map=(MagickPixelPacket *) RelinquishMagickMemory(stretch_map);
1467 return(status);
1468}
1469
1470/*
1471%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1472% %
1473% %
1474% %
1475% E n h a n c e I m a g e %
1476% %
1477% %
1478% %
1479%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1480%
1481% EnhanceImage() applies a digital filter that improves the quality of a
1482% noisy image.
1483%
1484% The format of the EnhanceImage method is:
1485%
1486% Image *EnhanceImage(const Image *image,ExceptionInfo *exception)
1487%
1488% A description of each parameter follows:
1489%
1490% o image: the image.
1491%
1492% o exception: return any errors or warnings in this structure.
1493%
1494*/
1495MagickExport Image *EnhanceImage(const Image *image,ExceptionInfo *exception)
1496{
1497#define Enhance(weight) \
1498 mean=((MagickRealType) r->red+pixel.red)/2; \
1499 distance=(MagickRealType) r->red-(MagickRealType) pixel.red; \
1500 distance_squared=QuantumScale*(2.0*((MagickRealType) QuantumRange+1.0)+ \
1501 mean)*distance*distance; \
1502 mean=((MagickRealType) r->green+pixel.green)/2; \
1503 distance=(MagickRealType) r->green-(MagickRealType) pixel.green; \
1504 distance_squared+=4.0*distance*distance; \
1505 mean=((MagickRealType) r->blue+pixel.blue)/2; \
1506 distance=(MagickRealType) r->blue-(MagickRealType) pixel.blue; \
1507 distance_squared+=QuantumScale*(3.0*((MagickRealType) \
1508 QuantumRange+1.0)-1.0-mean)*distance*distance; \
1509 mean=((MagickRealType) r->opacity+pixel.opacity)/2; \
1510 distance=(MagickRealType) r->opacity-(MagickRealType) pixel.opacity; \
1511 distance_squared+=QuantumScale*(3.0*((MagickRealType) \
1512 QuantumRange+1.0)-1.0-mean)*distance*distance; \
1513 if (distance_squared < ((MagickRealType) QuantumRange*(MagickRealType) \
1514 QuantumRange/25.0f)) \
1515 { \
1516 aggregate.red+=(weight)*r->red; \
1517 aggregate.green+=(weight)*r->green; \
1518 aggregate.blue+=(weight)*r->blue; \
1519 aggregate.opacity+=(weight)*r->opacity; \
1520 total_weight+=(weight); \
1521 } \
1522 r++;
1523#define EnhanceImageTag "Enhance/Image"
1524
cristyc4c8d132010-01-07 01:58:38 +00001525 CacheView
1526 *enhance_view,
1527 *image_view;
1528
cristy3ed852e2009-09-05 21:47:34 +00001529 Image
1530 *enhance_image;
1531
cristy3ed852e2009-09-05 21:47:34 +00001532 MagickBooleanType
1533 status;
1534
cristybb503372010-05-27 20:51:26 +00001535 MagickOffsetType
1536 progress;
1537
cristy3ed852e2009-09-05 21:47:34 +00001538 MagickPixelPacket
1539 zero;
1540
cristybb503372010-05-27 20:51:26 +00001541 ssize_t
1542 y;
1543
cristy3ed852e2009-09-05 21:47:34 +00001544 /*
1545 Initialize enhanced image attributes.
1546 */
1547 assert(image != (const Image *) NULL);
1548 assert(image->signature == MagickSignature);
1549 if (image->debug != MagickFalse)
1550 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1551 assert(exception != (ExceptionInfo *) NULL);
1552 assert(exception->signature == MagickSignature);
1553 if ((image->columns < 5) || (image->rows < 5))
1554 return((Image *) NULL);
1555 enhance_image=CloneImage(image,image->columns,image->rows,MagickTrue,
1556 exception);
1557 if (enhance_image == (Image *) NULL)
1558 return((Image *) NULL);
1559 if (SetImageStorageClass(enhance_image,DirectClass) == MagickFalse)
1560 {
1561 InheritException(exception,&enhance_image->exception);
1562 enhance_image=DestroyImage(enhance_image);
1563 return((Image *) NULL);
1564 }
1565 /*
1566 Enhance image.
1567 */
1568 status=MagickTrue;
1569 progress=0;
1570 (void) ResetMagickMemory(&zero,0,sizeof(zero));
1571 image_view=AcquireCacheView(image);
1572 enhance_view=AcquireCacheView(enhance_image);
cristyb5d5f722009-11-04 03:03:49 +00001573#if defined(MAGICKCORE_OPENMP_SUPPORT)
1574 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001575#endif
cristybb503372010-05-27 20:51:26 +00001576 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001577 {
1578 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001579 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00001580
cristy3ed852e2009-09-05 21:47:34 +00001581 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001582 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001583
cristy8d4629b2010-08-30 17:59:46 +00001584 register ssize_t
1585 x;
1586
cristy3ed852e2009-09-05 21:47:34 +00001587 /*
1588 Read another scan line.
1589 */
1590 if (status == MagickFalse)
1591 continue;
1592 p=GetCacheViewVirtualPixels(image_view,-2,y-2,image->columns+4,5,exception);
1593 q=QueueCacheViewAuthenticPixels(enhance_view,0,y,enhance_image->columns,1,
1594 exception);
1595 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
1596 {
1597 status=MagickFalse;
1598 continue;
1599 }
cristybb503372010-05-27 20:51:26 +00001600 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001601 {
1602 MagickPixelPacket
1603 aggregate;
1604
1605 MagickRealType
1606 distance,
1607 distance_squared,
1608 mean,
1609 total_weight;
1610
1611 PixelPacket
1612 pixel;
1613
1614 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001615 *restrict r;
cristy3ed852e2009-09-05 21:47:34 +00001616
1617 /*
1618 Compute weighted average of target pixel color components.
1619 */
1620 aggregate=zero;
1621 total_weight=0.0;
1622 r=p+2*(image->columns+4)+2;
1623 pixel=(*r);
1624 r=p;
1625 Enhance(5.0); Enhance(8.0); Enhance(10.0); Enhance(8.0); Enhance(5.0);
1626 r=p+(image->columns+4);
1627 Enhance(8.0); Enhance(20.0); Enhance(40.0); Enhance(20.0); Enhance(8.0);
1628 r=p+2*(image->columns+4);
1629 Enhance(10.0); Enhance(40.0); Enhance(80.0); Enhance(40.0); Enhance(10.0);
1630 r=p+3*(image->columns+4);
1631 Enhance(8.0); Enhance(20.0); Enhance(40.0); Enhance(20.0); Enhance(8.0);
1632 r=p+4*(image->columns+4);
1633 Enhance(5.0); Enhance(8.0); Enhance(10.0); Enhance(8.0); Enhance(5.0);
1634 q->red=(Quantum) ((aggregate.red+(total_weight/2)-1)/total_weight);
1635 q->green=(Quantum) ((aggregate.green+(total_weight/2)-1)/total_weight);
1636 q->blue=(Quantum) ((aggregate.blue+(total_weight/2)-1)/total_weight);
1637 q->opacity=(Quantum) ((aggregate.opacity+(total_weight/2)-1)/
1638 total_weight);
1639 p++;
1640 q++;
1641 }
1642 if (SyncCacheViewAuthenticPixels(enhance_view,exception) == MagickFalse)
1643 status=MagickFalse;
1644 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1645 {
1646 MagickBooleanType
1647 proceed;
1648
cristyb5d5f722009-11-04 03:03:49 +00001649#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00001650 #pragma omp critical (MagickCore_EnhanceImage)
1651#endif
1652 proceed=SetImageProgress(image,EnhanceImageTag,progress++,image->rows);
1653 if (proceed == MagickFalse)
1654 status=MagickFalse;
1655 }
1656 }
1657 enhance_view=DestroyCacheView(enhance_view);
1658 image_view=DestroyCacheView(image_view);
1659 return(enhance_image);
1660}
1661
1662/*
1663%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1664% %
1665% %
1666% %
1667% E q u a l i z e I m a g e %
1668% %
1669% %
1670% %
1671%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1672%
1673% EqualizeImage() applies a histogram equalization to the image.
1674%
1675% The format of the EqualizeImage method is:
1676%
1677% MagickBooleanType EqualizeImage(Image *image)
1678% MagickBooleanType EqualizeImageChannel(Image *image,
1679% const ChannelType channel)
1680%
1681% A description of each parameter follows:
1682%
1683% o image: the image.
1684%
1685% o channel: the channel.
1686%
1687*/
1688
1689MagickExport MagickBooleanType EqualizeImage(Image *image)
1690{
1691 return(EqualizeImageChannel(image,DefaultChannels));
1692}
1693
1694MagickExport MagickBooleanType EqualizeImageChannel(Image *image,
1695 const ChannelType channel)
1696{
1697#define EqualizeImageTag "Equalize/Image"
1698
cristyc4c8d132010-01-07 01:58:38 +00001699 CacheView
1700 *image_view;
1701
cristy3ed852e2009-09-05 21:47:34 +00001702 ExceptionInfo
1703 *exception;
1704
cristy3ed852e2009-09-05 21:47:34 +00001705 MagickBooleanType
1706 status;
1707
cristybb503372010-05-27 20:51:26 +00001708 MagickOffsetType
1709 progress;
1710
cristy3ed852e2009-09-05 21:47:34 +00001711 MagickPixelPacket
1712 black,
1713 *equalize_map,
1714 *histogram,
1715 intensity,
1716 *map,
1717 white;
1718
cristybb503372010-05-27 20:51:26 +00001719 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001720 i;
1721
cristybb503372010-05-27 20:51:26 +00001722 ssize_t
1723 y;
1724
cristy3ed852e2009-09-05 21:47:34 +00001725 /*
1726 Allocate and initialize histogram arrays.
1727 */
1728 assert(image != (Image *) NULL);
1729 assert(image->signature == MagickSignature);
1730 if (image->debug != MagickFalse)
1731 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1732 equalize_map=(MagickPixelPacket *) AcquireQuantumMemory(MaxMap+1UL,
1733 sizeof(*equalize_map));
1734 histogram=(MagickPixelPacket *) AcquireQuantumMemory(MaxMap+1UL,
1735 sizeof(*histogram));
1736 map=(MagickPixelPacket *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*map));
1737 if ((equalize_map == (MagickPixelPacket *) NULL) ||
1738 (histogram == (MagickPixelPacket *) NULL) ||
1739 (map == (MagickPixelPacket *) NULL))
1740 {
1741 if (map != (MagickPixelPacket *) NULL)
1742 map=(MagickPixelPacket *) RelinquishMagickMemory(map);
1743 if (histogram != (MagickPixelPacket *) NULL)
1744 histogram=(MagickPixelPacket *) RelinquishMagickMemory(histogram);
1745 if (equalize_map != (MagickPixelPacket *) NULL)
1746 equalize_map=(MagickPixelPacket *) RelinquishMagickMemory(equalize_map);
1747 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
1748 image->filename);
1749 }
1750 /*
1751 Form histogram.
1752 */
1753 (void) ResetMagickMemory(histogram,0,(MaxMap+1)*sizeof(*histogram));
1754 exception=(&image->exception);
cristybb503372010-05-27 20:51:26 +00001755 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001756 {
1757 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00001758 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00001759
1760 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001761 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00001762
cristybb503372010-05-27 20:51:26 +00001763 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001764 x;
1765
1766 p=GetVirtualPixels(image,0,y,image->columns,1,exception);
1767 if (p == (const PixelPacket *) NULL)
1768 break;
1769 indexes=GetVirtualIndexQueue(image);
cristybb503372010-05-27 20:51:26 +00001770 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001771 {
1772 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001773 histogram[ScaleQuantumToMap(GetRedPixelComponent(p))].red++;
cristy3ed852e2009-09-05 21:47:34 +00001774 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001775 histogram[ScaleQuantumToMap(GetGreenPixelComponent(p))].green++;
cristy3ed852e2009-09-05 21:47:34 +00001776 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001777 histogram[ScaleQuantumToMap(GetBluePixelComponent(p))].blue++;
cristy3ed852e2009-09-05 21:47:34 +00001778 if ((channel & OpacityChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001779 histogram[ScaleQuantumToMap(GetOpacityPixelComponent(p))].opacity++;
cristy3ed852e2009-09-05 21:47:34 +00001780 if (((channel & IndexChannel) != 0) &&
1781 (image->colorspace == CMYKColorspace))
1782 histogram[ScaleQuantumToMap(indexes[x])].index++;
1783 p++;
1784 }
1785 }
1786 /*
1787 Integrate the histogram to get the equalization map.
1788 */
1789 (void) ResetMagickMemory(&intensity,0,sizeof(intensity));
cristybb503372010-05-27 20:51:26 +00001790 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +00001791 {
1792 if ((channel & RedChannel) != 0)
1793 intensity.red+=histogram[i].red;
1794 if ((channel & GreenChannel) != 0)
1795 intensity.green+=histogram[i].green;
1796 if ((channel & BlueChannel) != 0)
1797 intensity.blue+=histogram[i].blue;
1798 if ((channel & OpacityChannel) != 0)
1799 intensity.opacity+=histogram[i].opacity;
1800 if (((channel & IndexChannel) != 0) &&
1801 (image->colorspace == CMYKColorspace))
1802 intensity.index+=histogram[i].index;
1803 map[i]=intensity;
1804 }
1805 black=map[0];
1806 white=map[(int) MaxMap];
1807 (void) ResetMagickMemory(equalize_map,0,(MaxMap+1)*sizeof(*equalize_map));
cristyb5d5f722009-11-04 03:03:49 +00001808#if defined(MAGICKCORE_OPENMP_SUPPORT)
1809 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001810#endif
cristybb503372010-05-27 20:51:26 +00001811 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +00001812 {
1813 if (((channel & RedChannel) != 0) && (white.red != black.red))
1814 equalize_map[i].red=(MagickRealType) ScaleMapToQuantum((MagickRealType)
1815 ((MaxMap*(map[i].red-black.red))/(white.red-black.red)));
1816 if (((channel & GreenChannel) != 0) && (white.green != black.green))
1817 equalize_map[i].green=(MagickRealType) ScaleMapToQuantum((MagickRealType)
1818 ((MaxMap*(map[i].green-black.green))/(white.green-black.green)));
1819 if (((channel & BlueChannel) != 0) && (white.blue != black.blue))
1820 equalize_map[i].blue=(MagickRealType) ScaleMapToQuantum((MagickRealType)
1821 ((MaxMap*(map[i].blue-black.blue))/(white.blue-black.blue)));
1822 if (((channel & OpacityChannel) != 0) && (white.opacity != black.opacity))
1823 equalize_map[i].opacity=(MagickRealType) ScaleMapToQuantum(
1824 (MagickRealType) ((MaxMap*(map[i].opacity-black.opacity))/
1825 (white.opacity-black.opacity)));
1826 if ((((channel & IndexChannel) != 0) &&
1827 (image->colorspace == CMYKColorspace)) &&
1828 (white.index != black.index))
1829 equalize_map[i].index=(MagickRealType) ScaleMapToQuantum((MagickRealType)
1830 ((MaxMap*(map[i].index-black.index))/(white.index-black.index)));
1831 }
1832 histogram=(MagickPixelPacket *) RelinquishMagickMemory(histogram);
1833 map=(MagickPixelPacket *) RelinquishMagickMemory(map);
1834 if (image->storage_class == PseudoClass)
1835 {
1836 /*
1837 Equalize colormap.
1838 */
cristyb5d5f722009-11-04 03:03:49 +00001839#if defined(MAGICKCORE_OPENMP_SUPPORT)
1840 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001841#endif
cristybb503372010-05-27 20:51:26 +00001842 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00001843 {
1844 if (((channel & RedChannel) != 0) && (white.red != black.red))
cristyce70c172010-01-07 17:15:30 +00001845 image->colormap[i].red=ClampToQuantum(equalize_map[
cristy3ed852e2009-09-05 21:47:34 +00001846 ScaleQuantumToMap(image->colormap[i].red)].red);
1847 if (((channel & GreenChannel) != 0) && (white.green != black.green))
cristyce70c172010-01-07 17:15:30 +00001848 image->colormap[i].green=ClampToQuantum(equalize_map[
cristy3ed852e2009-09-05 21:47:34 +00001849 ScaleQuantumToMap(image->colormap[i].green)].green);
1850 if (((channel & BlueChannel) != 0) && (white.blue != black.blue))
cristyce70c172010-01-07 17:15:30 +00001851 image->colormap[i].blue=ClampToQuantum(equalize_map[
cristy3ed852e2009-09-05 21:47:34 +00001852 ScaleQuantumToMap(image->colormap[i].blue)].blue);
1853 if (((channel & OpacityChannel) != 0) &&
1854 (white.opacity != black.opacity))
cristyce70c172010-01-07 17:15:30 +00001855 image->colormap[i].opacity=ClampToQuantum(equalize_map[
cristy3ed852e2009-09-05 21:47:34 +00001856 ScaleQuantumToMap(image->colormap[i].opacity)].opacity);
1857 }
1858 }
1859 /*
1860 Equalize image.
1861 */
1862 status=MagickTrue;
1863 progress=0;
1864 exception=(&image->exception);
1865 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00001866#if defined(MAGICKCORE_OPENMP_SUPPORT)
1867 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001868#endif
cristybb503372010-05-27 20:51:26 +00001869 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001870 {
1871 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00001872 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00001873
cristy3ed852e2009-09-05 21:47:34 +00001874 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001875 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001876
cristy8d4629b2010-08-30 17:59:46 +00001877 register ssize_t
1878 x;
1879
cristy3ed852e2009-09-05 21:47:34 +00001880 if (status == MagickFalse)
1881 continue;
1882 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
1883 if (q == (PixelPacket *) NULL)
1884 {
1885 status=MagickFalse;
1886 continue;
1887 }
1888 indexes=GetCacheViewAuthenticIndexQueue(image_view);
cristybb503372010-05-27 20:51:26 +00001889 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001890 {
1891 if (((channel & RedChannel) != 0) && (white.red != black.red))
cristyce70c172010-01-07 17:15:30 +00001892 q->red=ClampToQuantum(equalize_map[ScaleQuantumToMap(q->red)].red);
cristy3ed852e2009-09-05 21:47:34 +00001893 if (((channel & GreenChannel) != 0) && (white.green != black.green))
cristyce70c172010-01-07 17:15:30 +00001894 q->green=ClampToQuantum(equalize_map[ScaleQuantumToMap(
cristy3ed852e2009-09-05 21:47:34 +00001895 q->green)].green);
1896 if (((channel & BlueChannel) != 0) && (white.blue != black.blue))
cristyce70c172010-01-07 17:15:30 +00001897 q->blue=ClampToQuantum(equalize_map[ScaleQuantumToMap(q->blue)].blue);
cristy3ed852e2009-09-05 21:47:34 +00001898 if (((channel & OpacityChannel) != 0) && (white.opacity != black.opacity))
cristyce70c172010-01-07 17:15:30 +00001899 q->opacity=ClampToQuantum(equalize_map[ScaleQuantumToMap(
cristy3ed852e2009-09-05 21:47:34 +00001900 q->opacity)].opacity);
1901 if ((((channel & IndexChannel) != 0) &&
1902 (image->colorspace == CMYKColorspace)) &&
1903 (white.index != black.index))
cristyce70c172010-01-07 17:15:30 +00001904 indexes[x]=ClampToQuantum(equalize_map[ScaleQuantumToMap(
cristy3ed852e2009-09-05 21:47:34 +00001905 indexes[x])].index);
1906 q++;
1907 }
1908 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
1909 status=MagickFalse;
1910 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1911 {
1912 MagickBooleanType
1913 proceed;
1914
cristyb5d5f722009-11-04 03:03:49 +00001915#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00001916 #pragma omp critical (MagickCore_EqualizeImageChannel)
1917#endif
1918 proceed=SetImageProgress(image,EqualizeImageTag,progress++,image->rows);
1919 if (proceed == MagickFalse)
1920 status=MagickFalse;
1921 }
1922 }
1923 image_view=DestroyCacheView(image_view);
1924 equalize_map=(MagickPixelPacket *) RelinquishMagickMemory(equalize_map);
1925 return(status);
1926}
1927
1928/*
1929%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1930% %
1931% %
1932% %
1933% G a m m a I m a g e %
1934% %
1935% %
1936% %
1937%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1938%
1939% GammaImage() gamma-corrects a particular image channel. The same
1940% image viewed on different devices will have perceptual differences in the
1941% way the image's intensities are represented on the screen. Specify
1942% individual gamma levels for the red, green, and blue channels, or adjust
1943% all three with the gamma parameter. Values typically range from 0.8 to 2.3.
1944%
1945% You can also reduce the influence of a particular channel with a gamma
1946% value of 0.
1947%
1948% The format of the GammaImage method is:
1949%
cristya6360142011-03-23 23:08:04 +00001950% MagickBooleanType GammaImage(Image *image,const char *level)
cristy3ed852e2009-09-05 21:47:34 +00001951% MagickBooleanType GammaImageChannel(Image *image,
1952% const ChannelType channel,const double gamma)
1953%
1954% A description of each parameter follows:
1955%
1956% o image: the image.
1957%
1958% o channel: the channel.
1959%
cristya6360142011-03-23 23:08:04 +00001960% o level: the image gamma as a string (e.g. 1.6,1.2,1.0).
1961%
cristy3ed852e2009-09-05 21:47:34 +00001962% o gamma: the image gamma.
1963%
1964*/
1965MagickExport MagickBooleanType GammaImage(Image *image,const char *level)
1966{
1967 GeometryInfo
1968 geometry_info;
1969
1970 MagickPixelPacket
1971 gamma;
1972
1973 MagickStatusType
1974 flags,
1975 status;
1976
1977 assert(image != (Image *) NULL);
1978 assert(image->signature == MagickSignature);
1979 if (image->debug != MagickFalse)
1980 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1981 if (level == (char *) NULL)
1982 return(MagickFalse);
1983 flags=ParseGeometry(level,&geometry_info);
1984 gamma.red=geometry_info.rho;
1985 gamma.green=geometry_info.sigma;
1986 if ((flags & SigmaValue) == 0)
1987 gamma.green=gamma.red;
1988 gamma.blue=geometry_info.xi;
1989 if ((flags & XiValue) == 0)
1990 gamma.blue=gamma.red;
1991 if ((gamma.red == 1.0) && (gamma.green == 1.0) && (gamma.blue == 1.0))
1992 return(MagickTrue);
1993 if ((gamma.red == gamma.green) && (gamma.green == gamma.blue))
1994 status=GammaImageChannel(image,(const ChannelType) (RedChannel |
1995 GreenChannel | BlueChannel),(double) gamma.red);
1996 else
1997 {
1998 status=GammaImageChannel(image,RedChannel,(double) gamma.red);
1999 status|=GammaImageChannel(image,GreenChannel,(double) gamma.green);
2000 status|=GammaImageChannel(image,BlueChannel,(double) gamma.blue);
2001 }
2002 return(status != 0 ? MagickTrue : MagickFalse);
2003}
2004
2005MagickExport MagickBooleanType GammaImageChannel(Image *image,
2006 const ChannelType channel,const double gamma)
2007{
2008#define GammaCorrectImageTag "GammaCorrect/Image"
2009
cristyc4c8d132010-01-07 01:58:38 +00002010 CacheView
2011 *image_view;
2012
cristy3ed852e2009-09-05 21:47:34 +00002013 ExceptionInfo
2014 *exception;
2015
cristy3ed852e2009-09-05 21:47:34 +00002016 MagickBooleanType
2017 status;
2018
cristybb503372010-05-27 20:51:26 +00002019 MagickOffsetType
2020 progress;
2021
cristy3ed852e2009-09-05 21:47:34 +00002022 Quantum
2023 *gamma_map;
2024
cristybb503372010-05-27 20:51:26 +00002025 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002026 i;
2027
cristybb503372010-05-27 20:51:26 +00002028 ssize_t
2029 y;
2030
cristy3ed852e2009-09-05 21:47:34 +00002031 /*
2032 Allocate and initialize gamma maps.
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 (gamma == 1.0)
2039 return(MagickTrue);
2040 gamma_map=(Quantum *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*gamma_map));
2041 if (gamma_map == (Quantum *) NULL)
2042 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
2043 image->filename);
2044 (void) ResetMagickMemory(gamma_map,0,(MaxMap+1)*sizeof(*gamma_map));
2045 if (gamma != 0.0)
cristyb5d5f722009-11-04 03:03:49 +00002046#if defined(MAGICKCORE_OPENMP_SUPPORT)
2047 #pragma omp parallel for schedule(dynamic,4)
cristy3ed852e2009-09-05 21:47:34 +00002048#endif
cristybb503372010-05-27 20:51:26 +00002049 for (i=0; i <= (ssize_t) MaxMap; i++)
cristyce70c172010-01-07 17:15:30 +00002050 gamma_map[i]=ClampToQuantum((MagickRealType) ScaleMapToQuantum((
cristy3ed852e2009-09-05 21:47:34 +00002051 MagickRealType) (MaxMap*pow((double) i/MaxMap,1.0/gamma))));
2052 if (image->storage_class == PseudoClass)
2053 {
2054 /*
2055 Gamma-correct colormap.
2056 */
cristyb5d5f722009-11-04 03:03:49 +00002057#if defined(MAGICKCORE_OPENMP_SUPPORT)
2058 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002059#endif
cristybb503372010-05-27 20:51:26 +00002060 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00002061 {
2062 if ((channel & RedChannel) != 0)
2063 image->colormap[i].red=gamma_map[
2064 ScaleQuantumToMap(image->colormap[i].red)];
2065 if ((channel & GreenChannel) != 0)
2066 image->colormap[i].green=gamma_map[
2067 ScaleQuantumToMap(image->colormap[i].green)];
2068 if ((channel & BlueChannel) != 0)
2069 image->colormap[i].blue=gamma_map[
2070 ScaleQuantumToMap(image->colormap[i].blue)];
2071 if ((channel & OpacityChannel) != 0)
2072 {
2073 if (image->matte == MagickFalse)
2074 image->colormap[i].opacity=gamma_map[
2075 ScaleQuantumToMap(image->colormap[i].opacity)];
2076 else
2077 image->colormap[i].opacity=(Quantum) QuantumRange-
2078 gamma_map[ScaleQuantumToMap((Quantum) (QuantumRange-
2079 image->colormap[i].opacity))];
2080 }
2081 }
2082 }
2083 /*
2084 Gamma-correct image.
2085 */
2086 status=MagickTrue;
2087 progress=0;
2088 exception=(&image->exception);
2089 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00002090#if defined(MAGICKCORE_OPENMP_SUPPORT)
2091 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002092#endif
cristybb503372010-05-27 20:51:26 +00002093 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002094 {
2095 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00002096 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00002097
cristy3ed852e2009-09-05 21:47:34 +00002098 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00002099 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002100
cristy8d4629b2010-08-30 17:59:46 +00002101 register ssize_t
2102 x;
2103
cristy3ed852e2009-09-05 21:47:34 +00002104 if (status == MagickFalse)
2105 continue;
2106 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2107 if (q == (PixelPacket *) NULL)
2108 {
2109 status=MagickFalse;
2110 continue;
2111 }
2112 indexes=GetCacheViewAuthenticIndexQueue(image_view);
cristybb503372010-05-27 20:51:26 +00002113 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002114 {
cristy6cbd7f52009-10-17 16:06:51 +00002115 if (channel == DefaultChannels)
cristy3ed852e2009-09-05 21:47:34 +00002116 {
cristy6cbd7f52009-10-17 16:06:51 +00002117 q->red=gamma_map[ScaleQuantumToMap(q->red)];
2118 q->green=gamma_map[ScaleQuantumToMap(q->green)];
2119 q->blue=gamma_map[ScaleQuantumToMap(q->blue)];
2120 }
2121 else
2122 {
2123 if ((channel & RedChannel) != 0)
2124 q->red=gamma_map[ScaleQuantumToMap(q->red)];
2125 if ((channel & GreenChannel) != 0)
2126 q->green=gamma_map[ScaleQuantumToMap(q->green)];
2127 if ((channel & BlueChannel) != 0)
2128 q->blue=gamma_map[ScaleQuantumToMap(q->blue)];
2129 if ((channel & OpacityChannel) != 0)
2130 {
2131 if (image->matte == MagickFalse)
2132 q->opacity=gamma_map[ScaleQuantumToMap(q->opacity)];
2133 else
2134 q->opacity=(Quantum) QuantumRange-gamma_map[
cristy46f08202010-01-10 04:04:21 +00002135 ScaleQuantumToMap((Quantum) GetAlphaPixelComponent(q))];
cristy6cbd7f52009-10-17 16:06:51 +00002136 }
cristy3ed852e2009-09-05 21:47:34 +00002137 }
2138 q++;
2139 }
2140 if (((channel & IndexChannel) != 0) &&
2141 (image->colorspace == CMYKColorspace))
cristybb503372010-05-27 20:51:26 +00002142 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002143 indexes[x]=gamma_map[ScaleQuantumToMap(indexes[x])];
2144 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2145 status=MagickFalse;
2146 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2147 {
2148 MagickBooleanType
2149 proceed;
2150
cristyb5d5f722009-11-04 03:03:49 +00002151#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00002152 #pragma omp critical (MagickCore_GammaImageChannel)
2153#endif
2154 proceed=SetImageProgress(image,GammaCorrectImageTag,progress++,
2155 image->rows);
2156 if (proceed == MagickFalse)
2157 status=MagickFalse;
2158 }
2159 }
2160 image_view=DestroyCacheView(image_view);
2161 gamma_map=(Quantum *) RelinquishMagickMemory(gamma_map);
2162 if (image->gamma != 0.0)
2163 image->gamma*=gamma;
2164 return(status);
2165}
2166
2167/*
2168%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2169% %
2170% %
2171% %
2172% H a l d C l u t I m a g e %
2173% %
2174% %
2175% %
2176%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2177%
2178% HaldClutImage() applies a Hald color lookup table to the image. A Hald
2179% color lookup table is a 3-dimensional color cube mapped to 2 dimensions.
2180% Create it with the HALD coder. You can apply any color transformation to
2181% the Hald image and then use this method to apply the transform to the
2182% image.
2183%
2184% The format of the HaldClutImage method is:
2185%
2186% MagickBooleanType HaldClutImage(Image *image,Image *hald_image)
2187% MagickBooleanType HaldClutImageChannel(Image *image,
2188% const ChannelType channel,Image *hald_image)
2189%
2190% A description of each parameter follows:
2191%
2192% o image: the image, which is replaced by indexed CLUT values
2193%
2194% o hald_image: the color lookup table image for replacement color values.
2195%
2196% o channel: the channel.
2197%
2198*/
2199
2200static inline size_t MagickMin(const size_t x,const size_t y)
2201{
2202 if (x < y)
2203 return(x);
2204 return(y);
2205}
2206
2207MagickExport MagickBooleanType HaldClutImage(Image *image,
2208 const Image *hald_image)
2209{
2210 return(HaldClutImageChannel(image,DefaultChannels,hald_image));
2211}
2212
2213MagickExport MagickBooleanType HaldClutImageChannel(Image *image,
2214 const ChannelType channel,const Image *hald_image)
2215{
2216#define HaldClutImageTag "Clut/Image"
2217
2218 typedef struct _HaldInfo
2219 {
2220 MagickRealType
2221 x,
2222 y,
2223 z;
2224 } HaldInfo;
2225
cristyfa112112010-01-04 17:48:07 +00002226 CacheView
2227 *image_view;
2228
cristy3ed852e2009-09-05 21:47:34 +00002229 double
2230 width;
2231
2232 ExceptionInfo
2233 *exception;
2234
cristy3ed852e2009-09-05 21:47:34 +00002235 MagickBooleanType
2236 status;
2237
cristybb503372010-05-27 20:51:26 +00002238 MagickOffsetType
2239 progress;
2240
cristy3ed852e2009-09-05 21:47:34 +00002241 MagickPixelPacket
2242 zero;
2243
cristy3ed852e2009-09-05 21:47:34 +00002244 size_t
2245 cube_size,
2246 length,
2247 level;
2248
cristybb503372010-05-27 20:51:26 +00002249 ssize_t
2250 y;
2251
cristy3ed852e2009-09-05 21:47:34 +00002252 assert(image != (Image *) NULL);
2253 assert(image->signature == MagickSignature);
2254 if (image->debug != MagickFalse)
2255 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2256 assert(hald_image != (Image *) NULL);
2257 assert(hald_image->signature == MagickSignature);
2258 if (SetImageStorageClass(image,DirectClass) == MagickFalse)
2259 return(MagickFalse);
2260 if (image->matte == MagickFalse)
2261 (void) SetImageAlphaChannel(image,OpaqueAlphaChannel);
2262 /*
2263 Hald clut image.
2264 */
2265 status=MagickTrue;
2266 progress=0;
2267 length=MagickMin(hald_image->columns,hald_image->rows);
2268 for (level=2; (level*level*level) < length; level++) ;
2269 level*=level;
2270 cube_size=level*level;
2271 width=(double) hald_image->columns;
2272 GetMagickPixelPacket(hald_image,&zero);
2273 exception=(&image->exception);
cristy3ed852e2009-09-05 21:47:34 +00002274 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00002275#if defined(MAGICKCORE_OPENMP_SUPPORT)
2276 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002277#endif
cristybb503372010-05-27 20:51:26 +00002278 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002279 {
2280 double
2281 offset;
2282
2283 HaldInfo
2284 point;
2285
2286 MagickPixelPacket
2287 pixel,
2288 pixel1,
2289 pixel2,
2290 pixel3,
2291 pixel4;
2292
2293 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00002294 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00002295
cristy3ed852e2009-09-05 21:47:34 +00002296 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00002297 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002298
cristy8d4629b2010-08-30 17:59:46 +00002299 register ssize_t
2300 x;
2301
cristy3ed852e2009-09-05 21:47:34 +00002302 if (status == MagickFalse)
2303 continue;
2304 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2305 if (q == (PixelPacket *) NULL)
2306 {
2307 status=MagickFalse;
2308 continue;
2309 }
2310 indexes=GetCacheViewAuthenticIndexQueue(image_view);
2311 pixel=zero;
2312 pixel1=zero;
2313 pixel2=zero;
2314 pixel3=zero;
2315 pixel4=zero;
cristybb503372010-05-27 20:51:26 +00002316 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002317 {
2318 point.x=QuantumScale*(level-1.0)*q->red;
2319 point.y=QuantumScale*(level-1.0)*q->green;
2320 point.z=QuantumScale*(level-1.0)*q->blue;
2321 offset=point.x+level*floor(point.y)+cube_size*floor(point.z);
2322 point.x-=floor(point.x);
2323 point.y-=floor(point.y);
2324 point.z-=floor(point.z);
cristyd76c51e2011-03-26 00:21:26 +00002325 (void) InterpolatePixelPacket(image,image_view,image->interpolate,
2326 fmod(offset,width),floor(offset/width),&pixel1,exception);
2327 (void) InterpolatePixelPacket(image,image_view,image->interpolate,
2328 fmod(offset+level,width),floor((offset+level)/width),&pixel2,exception);
cristy3ed852e2009-09-05 21:47:34 +00002329 MagickPixelCompositeAreaBlend(&pixel1,pixel1.opacity,&pixel2,
2330 pixel2.opacity,point.y,&pixel3);
2331 offset+=cube_size;
cristyd76c51e2011-03-26 00:21:26 +00002332 (void) InterpolatePixelPacket(image,image_view,image->interpolate,
2333 fmod(offset,width),floor(offset/width),&pixel1,exception);
2334 (void) InterpolatePixelPacket(image,image_view,image->interpolate,
2335 fmod(offset+level,width),floor((offset+level)/width),&pixel2,exception);
cristy3ed852e2009-09-05 21:47:34 +00002336 MagickPixelCompositeAreaBlend(&pixel1,pixel1.opacity,&pixel2,
2337 pixel2.opacity,point.y,&pixel4);
2338 MagickPixelCompositeAreaBlend(&pixel3,pixel3.opacity,&pixel4,
2339 pixel4.opacity,point.z,&pixel);
2340 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00002341 SetRedPixelComponent(q,ClampRedPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00002342 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00002343 SetGreenPixelComponent(q,ClampGreenPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00002344 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00002345 SetBluePixelComponent(q,ClampBluePixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00002346 if (((channel & OpacityChannel) != 0) && (image->matte != MagickFalse))
cristyce70c172010-01-07 17:15:30 +00002347 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00002348 if (((channel & IndexChannel) != 0) &&
2349 (image->colorspace == CMYKColorspace))
cristyce70c172010-01-07 17:15:30 +00002350 indexes[x]=ClampToQuantum(pixel.index);
cristy3ed852e2009-09-05 21:47:34 +00002351 q++;
2352 }
2353 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2354 status=MagickFalse;
2355 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2356 {
2357 MagickBooleanType
2358 proceed;
2359
cristyb5d5f722009-11-04 03:03:49 +00002360#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00002361 #pragma omp critical (MagickCore_HaldClutImageChannel)
2362#endif
2363 proceed=SetImageProgress(image,HaldClutImageTag,progress++,image->rows);
2364 if (proceed == MagickFalse)
2365 status=MagickFalse;
2366 }
2367 }
2368 image_view=DestroyCacheView(image_view);
cristy3ed852e2009-09-05 21:47:34 +00002369 return(status);
2370}
2371
2372/*
2373%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2374% %
2375% %
2376% %
2377% L e v e l I m a g e %
2378% %
2379% %
2380% %
2381%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2382%
2383% LevelImage() adjusts the levels of a particular image channel by
2384% scaling the colors falling between specified white and black points to
2385% the full available quantum range.
2386%
2387% The parameters provided represent the black, and white points. The black
2388% point specifies the darkest color in the image. Colors darker than the
2389% black point are set to zero. White point specifies the lightest color in
2390% the image. Colors brighter than the white point are set to the maximum
2391% quantum value.
2392%
2393% If a '!' flag is given, map black and white colors to the given levels
2394% rather than mapping those levels to black and white. See
2395% LevelizeImageChannel() and LevelizeImageChannel(), below.
2396%
2397% Gamma specifies a gamma correction to apply to the image.
2398%
2399% The format of the LevelImage method is:
2400%
2401% MagickBooleanType LevelImage(Image *image,const char *levels)
2402%
2403% A description of each parameter follows:
2404%
2405% o image: the image.
2406%
2407% o levels: Specify the levels where the black and white points have the
2408% range of 0-QuantumRange, and gamma has the range 0-10 (e.g. 10x90%+2).
2409% A '!' flag inverts the re-mapping.
2410%
2411*/
2412
2413MagickExport MagickBooleanType LevelImage(Image *image,const char *levels)
2414{
2415 double
2416 black_point,
2417 gamma,
2418 white_point;
2419
2420 GeometryInfo
2421 geometry_info;
2422
2423 MagickBooleanType
2424 status;
2425
2426 MagickStatusType
2427 flags;
2428
2429 /*
2430 Parse levels.
2431 */
2432 if (levels == (char *) NULL)
2433 return(MagickFalse);
2434 flags=ParseGeometry(levels,&geometry_info);
2435 black_point=geometry_info.rho;
2436 white_point=(double) QuantumRange;
2437 if ((flags & SigmaValue) != 0)
2438 white_point=geometry_info.sigma;
2439 gamma=1.0;
2440 if ((flags & XiValue) != 0)
2441 gamma=geometry_info.xi;
2442 if ((flags & PercentValue) != 0)
2443 {
2444 black_point*=(double) image->columns*image->rows/100.0;
2445 white_point*=(double) image->columns*image->rows/100.0;
2446 }
2447 if ((flags & SigmaValue) == 0)
2448 white_point=(double) QuantumRange-black_point;
2449 if ((flags & AspectValue ) == 0)
2450 status=LevelImageChannel(image,DefaultChannels,black_point,white_point,
2451 gamma);
2452 else
cristy308b4e62009-09-21 14:40:44 +00002453 status=LevelizeImage(image,black_point,white_point,gamma);
cristy3ed852e2009-09-05 21:47:34 +00002454 return(status);
2455}
2456
2457/*
2458%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2459% %
2460% %
2461% %
cristy308b4e62009-09-21 14:40:44 +00002462% L e v e l i z e I m a g e %
cristy3ed852e2009-09-05 21:47:34 +00002463% %
2464% %
2465% %
2466%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2467%
cristy308b4e62009-09-21 14:40:44 +00002468% LevelizeImage() applies the normal level operation to the image, spreading
2469% out the values between the black and white points over the entire range of
2470% values. Gamma correction is also applied after the values has been mapped.
cristy3ed852e2009-09-05 21:47:34 +00002471%
2472% It is typically used to improve image contrast, or to provide a controlled
2473% linear threshold for the image. If the black and white points are set to
2474% the minimum and maximum values found in the image, the image can be
2475% normalized. or by swapping black and white values, negate the image.
2476%
cristy308b4e62009-09-21 14:40:44 +00002477% The format of the LevelizeImage method is:
cristy3ed852e2009-09-05 21:47:34 +00002478%
cristy308b4e62009-09-21 14:40:44 +00002479% MagickBooleanType LevelizeImage(Image *image,const double black_point,
2480% const double white_point,const double gamma)
2481% MagickBooleanType LevelizeImageChannel(Image *image,
2482% const ChannelType channel,const double black_point,
2483% const double white_point,const double gamma)
cristy3ed852e2009-09-05 21:47:34 +00002484%
2485% A description of each parameter follows:
2486%
2487% o image: the image.
2488%
2489% o channel: the channel.
2490%
2491% o black_point: The level which is to be mapped to zero (black)
2492%
2493% o white_point: The level which is to be mapped to QuantiumRange (white)
2494%
2495% o gamma: adjust gamma by this factor before mapping values.
2496% use 1.0 for purely linear stretching of image color values
2497%
2498*/
2499MagickExport MagickBooleanType LevelImageChannel(Image *image,
2500 const ChannelType channel,const double black_point,const double white_point,
2501 const double gamma)
2502{
2503#define LevelImageTag "Level/Image"
cristybcfb0432010-05-06 01:45:33 +00002504#define LevelQuantum(x) (ClampToQuantum((MagickRealType) QuantumRange* \
cristyc1f508d2010-04-22 01:21:47 +00002505 pow(scale*((double) (x)-black_point),1.0/gamma)))
cristy3ed852e2009-09-05 21:47:34 +00002506
cristyc4c8d132010-01-07 01:58:38 +00002507 CacheView
2508 *image_view;
2509
cristy3ed852e2009-09-05 21:47:34 +00002510 ExceptionInfo
2511 *exception;
2512
cristy3ed852e2009-09-05 21:47:34 +00002513 MagickBooleanType
2514 status;
2515
cristybb503372010-05-27 20:51:26 +00002516 MagickOffsetType
2517 progress;
2518
anthony7fe39fc2010-04-06 03:19:20 +00002519 register double
2520 scale;
2521
cristy8d4629b2010-08-30 17:59:46 +00002522 register ssize_t
2523 i;
2524
cristybb503372010-05-27 20:51:26 +00002525 ssize_t
2526 y;
2527
cristy3ed852e2009-09-05 21:47:34 +00002528 /*
2529 Allocate and initialize levels map.
2530 */
2531 assert(image != (Image *) NULL);
2532 assert(image->signature == MagickSignature);
2533 if (image->debug != MagickFalse)
2534 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
cristy8d4629b2010-08-30 17:59:46 +00002535 scale=(white_point != black_point) ? 1.0/(white_point-black_point) : 1.0;
cristy3ed852e2009-09-05 21:47:34 +00002536 if (image->storage_class == PseudoClass)
cristyb5d5f722009-11-04 03:03:49 +00002537#if defined(MAGICKCORE_OPENMP_SUPPORT)
2538 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002539#endif
cristybb503372010-05-27 20:51:26 +00002540 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00002541 {
2542 /*
2543 Level colormap.
2544 */
2545 if ((channel & RedChannel) != 0)
cristyc1f508d2010-04-22 01:21:47 +00002546 image->colormap[i].red=LevelQuantum(image->colormap[i].red);
cristy3ed852e2009-09-05 21:47:34 +00002547 if ((channel & GreenChannel) != 0)
cristyc1f508d2010-04-22 01:21:47 +00002548 image->colormap[i].green=LevelQuantum(image->colormap[i].green);
cristy3ed852e2009-09-05 21:47:34 +00002549 if ((channel & BlueChannel) != 0)
cristyc1f508d2010-04-22 01:21:47 +00002550 image->colormap[i].blue=LevelQuantum(image->colormap[i].blue);
cristy3ed852e2009-09-05 21:47:34 +00002551 if ((channel & OpacityChannel) != 0)
cristyc1f508d2010-04-22 01:21:47 +00002552 image->colormap[i].opacity=LevelQuantum(image->colormap[i].opacity);
cristy3ed852e2009-09-05 21:47:34 +00002553 }
2554 /*
2555 Level image.
2556 */
2557 status=MagickTrue;
2558 progress=0;
2559 exception=(&image->exception);
2560 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00002561#if defined(MAGICKCORE_OPENMP_SUPPORT)
2562 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002563#endif
cristybb503372010-05-27 20:51:26 +00002564 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002565 {
2566 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00002567 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00002568
cristy3ed852e2009-09-05 21:47:34 +00002569 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00002570 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002571
cristy8d4629b2010-08-30 17:59:46 +00002572 register ssize_t
2573 x;
2574
cristy3ed852e2009-09-05 21:47:34 +00002575 if (status == MagickFalse)
2576 continue;
2577 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2578 if (q == (PixelPacket *) NULL)
2579 {
2580 status=MagickFalse;
2581 continue;
2582 }
2583 indexes=GetCacheViewAuthenticIndexQueue(image_view);
cristybb503372010-05-27 20:51:26 +00002584 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002585 {
2586 if ((channel & RedChannel) != 0)
cristyc1f508d2010-04-22 01:21:47 +00002587 q->red=LevelQuantum(q->red);
cristy3ed852e2009-09-05 21:47:34 +00002588 if ((channel & GreenChannel) != 0)
cristyc1f508d2010-04-22 01:21:47 +00002589 q->green=LevelQuantum(q->green);
cristy3ed852e2009-09-05 21:47:34 +00002590 if ((channel & BlueChannel) != 0)
cristyc1f508d2010-04-22 01:21:47 +00002591 q->blue=LevelQuantum(q->blue);
cristy3ed852e2009-09-05 21:47:34 +00002592 if (((channel & OpacityChannel) != 0) &&
2593 (image->matte == MagickTrue))
cristybb503372010-05-27 20:51:26 +00002594 q->opacity=(Quantum) (QuantumRange-LevelQuantum(QuantumRange-
2595 q->opacity));
cristy3ed852e2009-09-05 21:47:34 +00002596 if (((channel & IndexChannel) != 0) &&
2597 (image->colorspace == CMYKColorspace))
cristyc1f508d2010-04-22 01:21:47 +00002598 indexes[x]=LevelQuantum(indexes[x]);
cristy3ed852e2009-09-05 21:47:34 +00002599 q++;
2600 }
2601 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2602 status=MagickFalse;
2603 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2604 {
2605 MagickBooleanType
2606 proceed;
2607
cristyb5d5f722009-11-04 03:03:49 +00002608#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00002609 #pragma omp critical (MagickCore_LevelImageChannel)
2610#endif
2611 proceed=SetImageProgress(image,LevelImageTag,progress++,image->rows);
2612 if (proceed == MagickFalse)
2613 status=MagickFalse;
2614 }
2615 }
2616 image_view=DestroyCacheView(image_view);
2617 return(status);
2618}
2619
2620/*
2621%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2622% %
2623% %
2624% %
2625% L e v e l i z e I m a g e C h a n n e l %
2626% %
2627% %
2628% %
2629%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2630%
2631% LevelizeImageChannel() applies the reversed LevelImage() operation to just
2632% the specific channels specified. It compresses the full range of color
2633% values, so that they lie between the given black and white points. Gamma is
2634% applied before the values are mapped.
2635%
2636% LevelizeImageChannel() can be called with by using a +level command line
2637% API option, or using a '!' on a -level or LevelImage() geometry string.
2638%
2639% It can be used for example de-contrast a greyscale image to the exact
2640% levels specified. Or by using specific levels for each channel of an image
2641% you can convert a gray-scale image to any linear color gradient, according
2642% to those levels.
2643%
2644% The format of the LevelizeImageChannel method is:
2645%
2646% MagickBooleanType LevelizeImageChannel(Image *image,
2647% const ChannelType channel,const char *levels)
2648%
2649% A description of each parameter follows:
2650%
2651% o image: the image.
2652%
2653% o channel: the channel.
2654%
2655% o black_point: The level to map zero (black) to.
2656%
2657% o white_point: The level to map QuantiumRange (white) to.
2658%
2659% o gamma: adjust gamma by this factor before mapping values.
2660%
2661*/
cristyd1a2c0f2011-02-09 14:14:50 +00002662
2663MagickExport MagickBooleanType LevelizeImage(Image *image,
2664 const double black_point,const double white_point,const double gamma)
2665{
2666 MagickBooleanType
2667 status;
2668
2669 status=LevelizeImageChannel(image,DefaultChannels,black_point,white_point,
2670 gamma);
2671 return(status);
2672}
2673
cristy3ed852e2009-09-05 21:47:34 +00002674MagickExport MagickBooleanType LevelizeImageChannel(Image *image,
2675 const ChannelType channel,const double black_point,const double white_point,
2676 const double gamma)
2677{
2678#define LevelizeImageTag "Levelize/Image"
cristyce70c172010-01-07 17:15:30 +00002679#define LevelizeValue(x) (ClampToQuantum(((MagickRealType) \
cristy3ed852e2009-09-05 21:47:34 +00002680 pow((double)(QuantumScale*(x)),1.0/gamma))*(white_point-black_point)+ \
2681 black_point))
2682
cristyc4c8d132010-01-07 01:58:38 +00002683 CacheView
2684 *image_view;
2685
cristy3ed852e2009-09-05 21:47:34 +00002686 ExceptionInfo
2687 *exception;
2688
cristy3ed852e2009-09-05 21:47:34 +00002689 MagickBooleanType
2690 status;
2691
cristybb503372010-05-27 20:51:26 +00002692 MagickOffsetType
2693 progress;
2694
2695 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002696 i;
2697
cristybb503372010-05-27 20:51:26 +00002698 ssize_t
2699 y;
2700
cristy3ed852e2009-09-05 21:47:34 +00002701 /*
2702 Allocate and initialize levels map.
2703 */
2704 assert(image != (Image *) NULL);
2705 assert(image->signature == MagickSignature);
2706 if (image->debug != MagickFalse)
2707 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2708 if (image->storage_class == PseudoClass)
cristyb5d5f722009-11-04 03:03:49 +00002709#if defined(MAGICKCORE_OPENMP_SUPPORT)
2710 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002711#endif
cristybb503372010-05-27 20:51:26 +00002712 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00002713 {
2714 /*
2715 Level colormap.
2716 */
2717 if ((channel & RedChannel) != 0)
2718 image->colormap[i].red=LevelizeValue(image->colormap[i].red);
2719 if ((channel & GreenChannel) != 0)
2720 image->colormap[i].green=LevelizeValue(image->colormap[i].green);
2721 if ((channel & BlueChannel) != 0)
2722 image->colormap[i].blue=LevelizeValue(image->colormap[i].blue);
2723 if ((channel & OpacityChannel) != 0)
2724 image->colormap[i].opacity=LevelizeValue(image->colormap[i].opacity);
2725 }
2726 /*
2727 Level image.
2728 */
2729 status=MagickTrue;
2730 progress=0;
2731 exception=(&image->exception);
2732 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00002733#if defined(MAGICKCORE_OPENMP_SUPPORT)
2734 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002735#endif
cristybb503372010-05-27 20:51:26 +00002736 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002737 {
2738 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00002739 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00002740
cristy3ed852e2009-09-05 21:47:34 +00002741 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00002742 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002743
cristy8d4629b2010-08-30 17:59:46 +00002744 register ssize_t
2745 x;
2746
cristy3ed852e2009-09-05 21:47:34 +00002747 if (status == MagickFalse)
2748 continue;
2749 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2750 if (q == (PixelPacket *) NULL)
2751 {
2752 status=MagickFalse;
2753 continue;
2754 }
2755 indexes=GetCacheViewAuthenticIndexQueue(image_view);
cristybb503372010-05-27 20:51:26 +00002756 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002757 {
2758 if ((channel & RedChannel) != 0)
2759 q->red=LevelizeValue(q->red);
2760 if ((channel & GreenChannel) != 0)
2761 q->green=LevelizeValue(q->green);
2762 if ((channel & BlueChannel) != 0)
2763 q->blue=LevelizeValue(q->blue);
2764 if (((channel & OpacityChannel) != 0) &&
2765 (image->matte == MagickTrue))
2766 q->opacity=LevelizeValue(q->opacity);
2767 if (((channel & IndexChannel) != 0) &&
2768 (image->colorspace == CMYKColorspace))
2769 indexes[x]=LevelizeValue(indexes[x]);
2770 q++;
2771 }
2772 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2773 status=MagickFalse;
2774 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2775 {
2776 MagickBooleanType
2777 proceed;
2778
cristyb5d5f722009-11-04 03:03:49 +00002779#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00002780 #pragma omp critical (MagickCore_LevelizeImageChannel)
2781#endif
2782 proceed=SetImageProgress(image,LevelizeImageTag,progress++,image->rows);
2783 if (proceed == MagickFalse)
2784 status=MagickFalse;
2785 }
2786 }
cristy8d4629b2010-08-30 17:59:46 +00002787 image_view=DestroyCacheView(image_view);
cristy3ed852e2009-09-05 21:47:34 +00002788 return(status);
2789}
2790
2791/*
2792%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2793% %
2794% %
2795% %
2796% L e v e l I m a g e C o l o r s %
2797% %
2798% %
2799% %
2800%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2801%
cristyee0f8d72009-09-19 00:58:29 +00002802% LevelImageColor() maps the given color to "black" and "white" values,
2803% linearly spreading out the colors, and level values on a channel by channel
2804% bases, as per LevelImage(). The given colors allows you to specify
glennrp1e7f7bc2011-03-02 19:25:28 +00002805% different level ranges for each of the color channels separately.
cristy3ed852e2009-09-05 21:47:34 +00002806%
2807% If the boolean 'invert' is set true the image values will modifyed in the
2808% reverse direction. That is any existing "black" and "white" colors in the
2809% image will become the color values given, with all other values compressed
2810% appropriatally. This effectivally maps a greyscale gradient into the given
2811% color gradient.
2812%
cristy308b4e62009-09-21 14:40:44 +00002813% The format of the LevelColorsImageChannel method is:
cristy3ed852e2009-09-05 21:47:34 +00002814%
cristy308b4e62009-09-21 14:40:44 +00002815% MagickBooleanType LevelColorsImage(Image *image,
cristyee0f8d72009-09-19 00:58:29 +00002816% const MagickPixelPacket *black_color,
2817% const MagickPixelPacket *white_color,const MagickBooleanType invert)
cristy308b4e62009-09-21 14:40:44 +00002818% MagickBooleanType LevelColorsImageChannel(Image *image,
2819% const ChannelType channel,const MagickPixelPacket *black_color,
2820% const MagickPixelPacket *white_color,const MagickBooleanType invert)
cristy3ed852e2009-09-05 21:47:34 +00002821%
2822% A description of each parameter follows:
2823%
2824% o image: the image.
2825%
2826% o channel: the channel.
2827%
2828% o black_color: The color to map black to/from
2829%
2830% o white_point: The color to map white to/from
2831%
2832% o invert: if true map the colors (levelize), rather than from (level)
2833%
2834*/
cristy308b4e62009-09-21 14:40:44 +00002835
2836MagickExport MagickBooleanType LevelColorsImage(Image *image,
cristy3ed852e2009-09-05 21:47:34 +00002837 const MagickPixelPacket *black_color,const MagickPixelPacket *white_color,
2838 const MagickBooleanType invert)
2839{
cristy308b4e62009-09-21 14:40:44 +00002840 MagickBooleanType
2841 status;
cristy3ed852e2009-09-05 21:47:34 +00002842
cristy308b4e62009-09-21 14:40:44 +00002843 status=LevelColorsImageChannel(image,DefaultChannels,black_color,white_color,
2844 invert);
2845 return(status);
2846}
2847
2848MagickExport MagickBooleanType LevelColorsImageChannel(Image *image,
2849 const ChannelType channel,const MagickPixelPacket *black_color,
2850 const MagickPixelPacket *white_color,const MagickBooleanType invert)
2851{
cristy3ed852e2009-09-05 21:47:34 +00002852 MagickStatusType
2853 status;
2854
2855 /*
2856 Allocate and initialize levels map.
2857 */
2858 assert(image != (Image *) NULL);
2859 assert(image->signature == MagickSignature);
2860 if (image->debug != MagickFalse)
2861 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2862 status=MagickFalse;
2863 if (invert == MagickFalse)
2864 {
2865 if ((channel & RedChannel) != 0)
2866 status|=LevelImageChannel(image,RedChannel,
2867 black_color->red,white_color->red,(double) 1.0);
2868 if ((channel & GreenChannel) != 0)
2869 status|=LevelImageChannel(image,GreenChannel,
2870 black_color->green,white_color->green,(double) 1.0);
2871 if ((channel & BlueChannel) != 0)
2872 status|=LevelImageChannel(image,BlueChannel,
2873 black_color->blue,white_color->blue,(double) 1.0);
2874 if (((channel & OpacityChannel) != 0) &&
2875 (image->matte == MagickTrue))
2876 status|=LevelImageChannel(image,OpacityChannel,
2877 black_color->opacity,white_color->opacity,(double) 1.0);
2878 if (((channel & IndexChannel) != 0) &&
2879 (image->colorspace == CMYKColorspace))
2880 status|=LevelImageChannel(image,IndexChannel,
2881 black_color->index,white_color->index,(double) 1.0);
2882 }
2883 else
2884 {
2885 if ((channel & RedChannel) != 0)
2886 status|=LevelizeImageChannel(image,RedChannel,
2887 black_color->red,white_color->red,(double) 1.0);
2888 if ((channel & GreenChannel) != 0)
2889 status|=LevelizeImageChannel(image,GreenChannel,
2890 black_color->green,white_color->green,(double) 1.0);
2891 if ((channel & BlueChannel) != 0)
2892 status|=LevelizeImageChannel(image,BlueChannel,
2893 black_color->blue,white_color->blue,(double) 1.0);
2894 if (((channel & OpacityChannel) != 0) &&
2895 (image->matte == MagickTrue))
2896 status|=LevelizeImageChannel(image,OpacityChannel,
2897 black_color->opacity,white_color->opacity,(double) 1.0);
2898 if (((channel & IndexChannel) != 0) &&
2899 (image->colorspace == CMYKColorspace))
2900 status|=LevelizeImageChannel(image,IndexChannel,
2901 black_color->index,white_color->index,(double) 1.0);
2902 }
2903 return(status == 0 ? MagickFalse : MagickTrue);
2904}
2905
2906/*
2907%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2908% %
2909% %
2910% %
2911% L i n e a r S t r e t c h I m a g e %
2912% %
2913% %
2914% %
2915%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2916%
2917% The LinearStretchImage() discards any pixels below the black point and
2918% above the white point and levels the remaining pixels.
2919%
2920% The format of the LinearStretchImage method is:
2921%
2922% MagickBooleanType LinearStretchImage(Image *image,
2923% const double black_point,const double white_point)
2924%
2925% A description of each parameter follows:
2926%
2927% o image: the image.
2928%
2929% o black_point: the black point.
2930%
2931% o white_point: the white point.
2932%
2933*/
2934MagickExport MagickBooleanType LinearStretchImage(Image *image,
2935 const double black_point,const double white_point)
2936{
2937#define LinearStretchImageTag "LinearStretch/Image"
2938
2939 ExceptionInfo
2940 *exception;
2941
cristy3ed852e2009-09-05 21:47:34 +00002942 MagickBooleanType
2943 status;
2944
2945 MagickRealType
2946 *histogram,
2947 intensity;
2948
cristy8d4629b2010-08-30 17:59:46 +00002949 ssize_t
2950 black,
2951 white,
2952 y;
2953
cristy3ed852e2009-09-05 21:47:34 +00002954 /*
2955 Allocate histogram and linear map.
2956 */
2957 assert(image != (Image *) NULL);
2958 assert(image->signature == MagickSignature);
2959 histogram=(MagickRealType *) AcquireQuantumMemory(MaxMap+1UL,
2960 sizeof(*histogram));
2961 if (histogram == (MagickRealType *) NULL)
2962 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
2963 image->filename);
2964 /*
2965 Form histogram.
2966 */
2967 (void) ResetMagickMemory(histogram,0,(MaxMap+1)*sizeof(*histogram));
2968 exception=(&image->exception);
cristybb503372010-05-27 20:51:26 +00002969 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002970 {
2971 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00002972 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00002973
cristybb503372010-05-27 20:51:26 +00002974 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002975 x;
2976
2977 p=GetVirtualPixels(image,0,y,image->columns,1,exception);
2978 if (p == (const PixelPacket *) NULL)
2979 break;
cristybb503372010-05-27 20:51:26 +00002980 for (x=(ssize_t) image->columns-1; x >= 0; x--)
cristy3ed852e2009-09-05 21:47:34 +00002981 {
2982 histogram[ScaleQuantumToMap(PixelIntensityToQuantum(p))]++;
2983 p++;
2984 }
2985 }
2986 /*
2987 Find the histogram boundaries by locating the black and white point levels.
2988 */
cristy3ed852e2009-09-05 21:47:34 +00002989 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00002990 for (black=0; black < (ssize_t) MaxMap; black++)
cristy3ed852e2009-09-05 21:47:34 +00002991 {
2992 intensity+=histogram[black];
2993 if (intensity >= black_point)
2994 break;
2995 }
2996 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00002997 for (white=(ssize_t) MaxMap; white != 0; white--)
cristy3ed852e2009-09-05 21:47:34 +00002998 {
2999 intensity+=histogram[white];
3000 if (intensity >= white_point)
3001 break;
3002 }
3003 histogram=(MagickRealType *) RelinquishMagickMemory(histogram);
3004 status=LevelImageChannel(image,DefaultChannels,(double) black,(double) white,
3005 1.0);
3006 return(status);
3007}
3008
3009/*
3010%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3011% %
3012% %
3013% %
3014% M o d u l a t e I m a g e %
3015% %
3016% %
3017% %
3018%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3019%
3020% ModulateImage() lets you control the brightness, saturation, and hue
3021% of an image. Modulate represents the brightness, saturation, and hue
3022% as one parameter (e.g. 90,150,100). If the image colorspace is HSL, the
3023% modulation is lightness, saturation, and hue. And if the colorspace is
3024% HWB, use blackness, whiteness, and hue.
3025%
3026% The format of the ModulateImage method is:
3027%
3028% MagickBooleanType ModulateImage(Image *image,const char *modulate)
3029%
3030% A description of each parameter follows:
3031%
3032% o image: the image.
3033%
3034% o modulate: Define the percent change in brightness, saturation, and
3035% hue.
3036%
3037*/
3038
3039static void ModulateHSB(const double percent_hue,
3040 const double percent_saturation,const double percent_brightness,
3041 Quantum *red,Quantum *green,Quantum *blue)
3042{
3043 double
3044 brightness,
3045 hue,
3046 saturation;
3047
3048 /*
3049 Increase or decrease color brightness, saturation, or hue.
3050 */
3051 assert(red != (Quantum *) NULL);
3052 assert(green != (Quantum *) NULL);
3053 assert(blue != (Quantum *) NULL);
3054 ConvertRGBToHSB(*red,*green,*blue,&hue,&saturation,&brightness);
3055 hue+=0.5*(0.01*percent_hue-1.0);
3056 while (hue < 0.0)
3057 hue+=1.0;
3058 while (hue > 1.0)
3059 hue-=1.0;
3060 saturation*=0.01*percent_saturation;
3061 brightness*=0.01*percent_brightness;
3062 ConvertHSBToRGB(hue,saturation,brightness,red,green,blue);
3063}
3064
3065static void ModulateHSL(const double percent_hue,
3066 const double percent_saturation,const double percent_lightness,
3067 Quantum *red,Quantum *green,Quantum *blue)
3068{
3069 double
3070 hue,
3071 lightness,
3072 saturation;
3073
3074 /*
3075 Increase or decrease color lightness, saturation, or hue.
3076 */
3077 assert(red != (Quantum *) NULL);
3078 assert(green != (Quantum *) NULL);
3079 assert(blue != (Quantum *) NULL);
3080 ConvertRGBToHSL(*red,*green,*blue,&hue,&saturation,&lightness);
3081 hue+=0.5*(0.01*percent_hue-1.0);
3082 while (hue < 0.0)
3083 hue+=1.0;
3084 while (hue > 1.0)
3085 hue-=1.0;
3086 saturation*=0.01*percent_saturation;
3087 lightness*=0.01*percent_lightness;
3088 ConvertHSLToRGB(hue,saturation,lightness,red,green,blue);
3089}
3090
3091static void ModulateHWB(const double percent_hue,const double percent_whiteness, const double percent_blackness,Quantum *red,Quantum *green,Quantum *blue)
3092{
3093 double
3094 blackness,
3095 hue,
3096 whiteness;
3097
3098 /*
3099 Increase or decrease color blackness, whiteness, or hue.
3100 */
3101 assert(red != (Quantum *) NULL);
3102 assert(green != (Quantum *) NULL);
3103 assert(blue != (Quantum *) NULL);
3104 ConvertRGBToHWB(*red,*green,*blue,&hue,&whiteness,&blackness);
3105 hue+=0.5*(0.01*percent_hue-1.0);
3106 while (hue < 0.0)
3107 hue+=1.0;
3108 while (hue > 1.0)
3109 hue-=1.0;
3110 blackness*=0.01*percent_blackness;
3111 whiteness*=0.01*percent_whiteness;
3112 ConvertHWBToRGB(hue,whiteness,blackness,red,green,blue);
3113}
3114
3115MagickExport MagickBooleanType ModulateImage(Image *image,const char *modulate)
3116{
3117#define ModulateImageTag "Modulate/Image"
3118
cristyc4c8d132010-01-07 01:58:38 +00003119 CacheView
3120 *image_view;
3121
cristy3ed852e2009-09-05 21:47:34 +00003122 ColorspaceType
3123 colorspace;
3124
3125 const char
3126 *artifact;
3127
3128 double
3129 percent_brightness,
3130 percent_hue,
3131 percent_saturation;
3132
3133 ExceptionInfo
3134 *exception;
3135
3136 GeometryInfo
3137 geometry_info;
3138
cristy3ed852e2009-09-05 21:47:34 +00003139 MagickBooleanType
3140 status;
3141
cristybb503372010-05-27 20:51:26 +00003142 MagickOffsetType
3143 progress;
3144
cristy3ed852e2009-09-05 21:47:34 +00003145 MagickStatusType
3146 flags;
3147
cristybb503372010-05-27 20:51:26 +00003148 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003149 i;
3150
cristybb503372010-05-27 20:51:26 +00003151 ssize_t
3152 y;
3153
cristy3ed852e2009-09-05 21:47:34 +00003154 /*
cristy2b726bd2010-01-11 01:05:39 +00003155 Initialize modulate table.
cristy3ed852e2009-09-05 21:47:34 +00003156 */
3157 assert(image != (Image *) NULL);
3158 assert(image->signature == MagickSignature);
3159 if (image->debug != MagickFalse)
3160 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3161 if (modulate == (char *) NULL)
3162 return(MagickFalse);
3163 flags=ParseGeometry(modulate,&geometry_info);
3164 percent_brightness=geometry_info.rho;
3165 percent_saturation=geometry_info.sigma;
3166 if ((flags & SigmaValue) == 0)
3167 percent_saturation=100.0;
3168 percent_hue=geometry_info.xi;
3169 if ((flags & XiValue) == 0)
3170 percent_hue=100.0;
3171 colorspace=UndefinedColorspace;
3172 artifact=GetImageArtifact(image,"modulate:colorspace");
3173 if (artifact != (const char *) NULL)
3174 colorspace=(ColorspaceType) ParseMagickOption(MagickColorspaceOptions,
3175 MagickFalse,artifact);
3176 if (image->storage_class == PseudoClass)
3177 {
3178 /*
3179 Modulate colormap.
3180 */
cristyb5d5f722009-11-04 03:03:49 +00003181#if defined(MAGICKCORE_OPENMP_SUPPORT)
3182 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003183#endif
cristybb503372010-05-27 20:51:26 +00003184 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00003185 switch (colorspace)
3186 {
3187 case HSBColorspace:
3188 {
3189 ModulateHSB(percent_hue,percent_saturation,percent_brightness,
3190 &image->colormap[i].red,&image->colormap[i].green,
3191 &image->colormap[i].blue);
3192 break;
3193 }
3194 case HSLColorspace:
3195 default:
3196 {
3197 ModulateHSL(percent_hue,percent_saturation,percent_brightness,
3198 &image->colormap[i].red,&image->colormap[i].green,
3199 &image->colormap[i].blue);
3200 break;
3201 }
3202 case HWBColorspace:
3203 {
3204 ModulateHWB(percent_hue,percent_saturation,percent_brightness,
3205 &image->colormap[i].red,&image->colormap[i].green,
3206 &image->colormap[i].blue);
3207 break;
3208 }
3209 }
3210 }
3211 /*
3212 Modulate image.
3213 */
3214 status=MagickTrue;
3215 progress=0;
3216 exception=(&image->exception);
3217 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00003218#if defined(MAGICKCORE_OPENMP_SUPPORT)
3219 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003220#endif
cristybb503372010-05-27 20:51:26 +00003221 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003222 {
cristy3ed852e2009-09-05 21:47:34 +00003223 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00003224 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003225
cristy8d4629b2010-08-30 17:59:46 +00003226 register ssize_t
3227 x;
3228
cristy3ed852e2009-09-05 21:47:34 +00003229 if (status == MagickFalse)
3230 continue;
3231 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
3232 if (q == (PixelPacket *) NULL)
3233 {
3234 status=MagickFalse;
3235 continue;
3236 }
cristybb503372010-05-27 20:51:26 +00003237 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003238 {
3239 switch (colorspace)
3240 {
3241 case HSBColorspace:
3242 {
3243 ModulateHSB(percent_hue,percent_saturation,percent_brightness,
3244 &q->red,&q->green,&q->blue);
3245 break;
3246 }
3247 case HSLColorspace:
3248 default:
3249 {
3250 ModulateHSL(percent_hue,percent_saturation,percent_brightness,
3251 &q->red,&q->green,&q->blue);
3252 break;
3253 }
3254 case HWBColorspace:
3255 {
3256 ModulateHWB(percent_hue,percent_saturation,percent_brightness,
3257 &q->red,&q->green,&q->blue);
3258 break;
3259 }
3260 }
3261 q++;
3262 }
3263 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
3264 status=MagickFalse;
3265 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3266 {
3267 MagickBooleanType
3268 proceed;
3269
cristyb5d5f722009-11-04 03:03:49 +00003270#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00003271 #pragma omp critical (MagickCore_ModulateImage)
3272#endif
3273 proceed=SetImageProgress(image,ModulateImageTag,progress++,image->rows);
3274 if (proceed == MagickFalse)
3275 status=MagickFalse;
3276 }
3277 }
3278 image_view=DestroyCacheView(image_view);
3279 return(status);
3280}
3281
3282/*
3283%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3284% %
3285% %
3286% %
3287% N e g a t e I m a g e %
3288% %
3289% %
3290% %
3291%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3292%
3293% NegateImage() negates the colors in the reference image. The grayscale
3294% option means that only grayscale values within the image are negated.
3295%
3296% The format of the NegateImageChannel method is:
3297%
3298% MagickBooleanType NegateImage(Image *image,
3299% const MagickBooleanType grayscale)
3300% MagickBooleanType NegateImageChannel(Image *image,
3301% const ChannelType channel,const MagickBooleanType grayscale)
3302%
3303% A description of each parameter follows:
3304%
3305% o image: the image.
3306%
3307% o channel: the channel.
3308%
3309% o grayscale: If MagickTrue, only negate grayscale pixels within the image.
3310%
3311*/
3312
3313MagickExport MagickBooleanType NegateImage(Image *image,
3314 const MagickBooleanType grayscale)
3315{
3316 MagickBooleanType
3317 status;
3318
3319 status=NegateImageChannel(image,DefaultChannels,grayscale);
3320 return(status);
3321}
3322
3323MagickExport MagickBooleanType NegateImageChannel(Image *image,
3324 const ChannelType channel,const MagickBooleanType grayscale)
3325{
3326#define NegateImageTag "Negate/Image"
3327
cristyc4c8d132010-01-07 01:58:38 +00003328 CacheView
3329 *image_view;
3330
cristy3ed852e2009-09-05 21:47:34 +00003331 ExceptionInfo
3332 *exception;
3333
cristy3ed852e2009-09-05 21:47:34 +00003334 MagickBooleanType
3335 status;
3336
cristybb503372010-05-27 20:51:26 +00003337 MagickOffsetType
3338 progress;
3339
3340 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003341 i;
3342
cristybb503372010-05-27 20:51:26 +00003343 ssize_t
3344 y;
3345
cristy3ed852e2009-09-05 21:47:34 +00003346 assert(image != (Image *) NULL);
3347 assert(image->signature == MagickSignature);
3348 if (image->debug != MagickFalse)
3349 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3350 if (image->storage_class == PseudoClass)
3351 {
3352 /*
3353 Negate colormap.
3354 */
cristyb5d5f722009-11-04 03:03:49 +00003355#if defined(MAGICKCORE_OPENMP_SUPPORT)
3356 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003357#endif
cristybb503372010-05-27 20:51:26 +00003358 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00003359 {
3360 if (grayscale != MagickFalse)
3361 if ((image->colormap[i].red != image->colormap[i].green) ||
3362 (image->colormap[i].green != image->colormap[i].blue))
3363 continue;
3364 if ((channel & RedChannel) != 0)
3365 image->colormap[i].red=(Quantum) QuantumRange-
3366 image->colormap[i].red;
3367 if ((channel & GreenChannel) != 0)
3368 image->colormap[i].green=(Quantum) QuantumRange-
3369 image->colormap[i].green;
3370 if ((channel & BlueChannel) != 0)
3371 image->colormap[i].blue=(Quantum) QuantumRange-
3372 image->colormap[i].blue;
3373 }
3374 }
3375 /*
3376 Negate image.
3377 */
3378 status=MagickTrue;
3379 progress=0;
3380 exception=(&image->exception);
3381 image_view=AcquireCacheView(image);
3382 if (grayscale != MagickFalse)
3383 {
cristyb5d5f722009-11-04 03:03:49 +00003384#if defined(MAGICKCORE_OPENMP_SUPPORT)
3385 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003386#endif
cristybb503372010-05-27 20:51:26 +00003387 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003388 {
3389 MagickBooleanType
3390 sync;
3391
3392 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00003393 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00003394
cristy3ed852e2009-09-05 21:47:34 +00003395 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00003396 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003397
cristy8d4629b2010-08-30 17:59:46 +00003398 register ssize_t
3399 x;
3400
cristy3ed852e2009-09-05 21:47:34 +00003401 if (status == MagickFalse)
3402 continue;
3403 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,
3404 exception);
3405 if (q == (PixelPacket *) NULL)
3406 {
3407 status=MagickFalse;
3408 continue;
3409 }
3410 indexes=GetCacheViewAuthenticIndexQueue(image_view);
cristybb503372010-05-27 20:51:26 +00003411 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003412 {
3413 if ((q->red != q->green) || (q->green != q->blue))
3414 {
3415 q++;
3416 continue;
3417 }
3418 if ((channel & RedChannel) != 0)
3419 q->red=(Quantum) QuantumRange-q->red;
3420 if ((channel & GreenChannel) != 0)
3421 q->green=(Quantum) QuantumRange-q->green;
3422 if ((channel & BlueChannel) != 0)
3423 q->blue=(Quantum) QuantumRange-q->blue;
3424 if ((channel & OpacityChannel) != 0)
3425 q->opacity=(Quantum) QuantumRange-q->opacity;
3426 if (((channel & IndexChannel) != 0) &&
3427 (image->colorspace == CMYKColorspace))
3428 indexes[x]=(IndexPacket) QuantumRange-indexes[x];
3429 q++;
3430 }
3431 sync=SyncCacheViewAuthenticPixels(image_view,exception);
3432 if (sync == MagickFalse)
3433 status=MagickFalse;
3434 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3435 {
3436 MagickBooleanType
3437 proceed;
3438
cristyb5d5f722009-11-04 03:03:49 +00003439#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00003440 #pragma omp critical (MagickCore_NegateImageChannel)
3441#endif
3442 proceed=SetImageProgress(image,NegateImageTag,progress++,
3443 image->rows);
3444 if (proceed == MagickFalse)
3445 status=MagickFalse;
3446 }
3447 }
3448 image_view=DestroyCacheView(image_view);
3449 return(MagickTrue);
3450 }
3451 /*
3452 Negate image.
3453 */
cristyb5d5f722009-11-04 03:03:49 +00003454#if defined(MAGICKCORE_OPENMP_SUPPORT)
3455 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003456#endif
cristybb503372010-05-27 20:51:26 +00003457 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003458 {
3459 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00003460 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00003461
cristy3ed852e2009-09-05 21:47:34 +00003462 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00003463 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003464
cristy8d4629b2010-08-30 17:59:46 +00003465 register ssize_t
3466 x;
3467
cristy3ed852e2009-09-05 21:47:34 +00003468 if (status == MagickFalse)
3469 continue;
3470 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
3471 if (q == (PixelPacket *) NULL)
3472 {
3473 status=MagickFalse;
3474 continue;
3475 }
3476 indexes=GetCacheViewAuthenticIndexQueue(image_view);
cristybb503372010-05-27 20:51:26 +00003477 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003478 {
3479 if ((channel & RedChannel) != 0)
3480 q->red=(Quantum) QuantumRange-q->red;
3481 if ((channel & GreenChannel) != 0)
3482 q->green=(Quantum) QuantumRange-q->green;
3483 if ((channel & BlueChannel) != 0)
3484 q->blue=(Quantum) QuantumRange-q->blue;
3485 if ((channel & OpacityChannel) != 0)
3486 q->opacity=(Quantum) QuantumRange-q->opacity;
3487 if (((channel & IndexChannel) != 0) &&
3488 (image->colorspace == CMYKColorspace))
3489 indexes[x]=(IndexPacket) QuantumRange-indexes[x];
3490 q++;
3491 }
3492 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
3493 status=MagickFalse;
3494 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3495 {
3496 MagickBooleanType
3497 proceed;
3498
cristyb5d5f722009-11-04 03:03:49 +00003499#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00003500 #pragma omp critical (MagickCore_NegateImageChannel)
3501#endif
3502 proceed=SetImageProgress(image,NegateImageTag,progress++,image->rows);
3503 if (proceed == MagickFalse)
3504 status=MagickFalse;
3505 }
3506 }
3507 image_view=DestroyCacheView(image_view);
3508 return(status);
3509}
3510
3511/*
3512%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3513% %
3514% %
3515% %
3516% N o r m a l i z e I m a g e %
3517% %
3518% %
3519% %
3520%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3521%
3522% The NormalizeImage() method enhances the contrast of a color image by
3523% mapping the darkest 2 percent of all pixel to black and the brightest
3524% 1 percent to white.
3525%
3526% The format of the NormalizeImage method is:
3527%
3528% MagickBooleanType NormalizeImage(Image *image)
3529% MagickBooleanType NormalizeImageChannel(Image *image,
3530% const ChannelType channel)
3531%
3532% A description of each parameter follows:
3533%
3534% o image: the image.
3535%
3536% o channel: the channel.
3537%
3538*/
3539
3540MagickExport MagickBooleanType NormalizeImage(Image *image)
3541{
3542 MagickBooleanType
3543 status;
3544
3545 status=NormalizeImageChannel(image,DefaultChannels);
3546 return(status);
3547}
3548
3549MagickExport MagickBooleanType NormalizeImageChannel(Image *image,
3550 const ChannelType channel)
3551{
3552 double
3553 black_point,
3554 white_point;
3555
cristy530239c2010-07-25 17:34:26 +00003556 black_point=(double) image->columns*image->rows*0.0015;
3557 white_point=(double) image->columns*image->rows*0.9995;
cristy3ed852e2009-09-05 21:47:34 +00003558 return(ContrastStretchImageChannel(image,channel,black_point,white_point));
3559}
3560
3561/*
3562%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3563% %
3564% %
3565% %
3566% S i g m o i d a l C o n t r a s t I m a g e %
3567% %
3568% %
3569% %
3570%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3571%
3572% SigmoidalContrastImage() adjusts the contrast of an image with a non-linear
3573% sigmoidal contrast algorithm. Increase the contrast of the image using a
3574% sigmoidal transfer function without saturating highlights or shadows.
3575% Contrast indicates how much to increase the contrast (0 is none; 3 is
3576% typical; 20 is pushing it); mid-point indicates where midtones fall in the
3577% resultant image (0 is white; 50% is middle-gray; 100% is black). Set
3578% sharpen to MagickTrue to increase the image contrast otherwise the contrast
3579% is reduced.
3580%
3581% The format of the SigmoidalContrastImage method is:
3582%
3583% MagickBooleanType SigmoidalContrastImage(Image *image,
3584% const MagickBooleanType sharpen,const char *levels)
3585% MagickBooleanType SigmoidalContrastImageChannel(Image *image,
3586% const ChannelType channel,const MagickBooleanType sharpen,
3587% const double contrast,const double midpoint)
3588%
3589% A description of each parameter follows:
3590%
3591% o image: the image.
3592%
3593% o channel: the channel.
3594%
3595% o sharpen: Increase or decrease image contrast.
3596%
cristyfa769582010-09-30 23:30:03 +00003597% o alpha: strength of the contrast, the larger the number the more
3598% 'threshold-like' it becomes.
cristy3ed852e2009-09-05 21:47:34 +00003599%
cristyfa769582010-09-30 23:30:03 +00003600% o beta: midpoint of the function as a color value 0 to QuantumRange.
cristy3ed852e2009-09-05 21:47:34 +00003601%
3602*/
3603
3604MagickExport MagickBooleanType SigmoidalContrastImage(Image *image,
3605 const MagickBooleanType sharpen,const char *levels)
3606{
3607 GeometryInfo
3608 geometry_info;
3609
3610 MagickBooleanType
3611 status;
3612
3613 MagickStatusType
3614 flags;
3615
3616 flags=ParseGeometry(levels,&geometry_info);
3617 if ((flags & SigmaValue) == 0)
3618 geometry_info.sigma=1.0*QuantumRange/2.0;
3619 if ((flags & PercentValue) != 0)
3620 geometry_info.sigma=1.0*QuantumRange*geometry_info.sigma/100.0;
3621 status=SigmoidalContrastImageChannel(image,DefaultChannels,sharpen,
3622 geometry_info.rho,geometry_info.sigma);
3623 return(status);
3624}
3625
3626MagickExport MagickBooleanType SigmoidalContrastImageChannel(Image *image,
3627 const ChannelType channel,const MagickBooleanType sharpen,
3628 const double contrast,const double midpoint)
3629{
3630#define SigmoidalContrastImageTag "SigmoidalContrast/Image"
3631
cristyc4c8d132010-01-07 01:58:38 +00003632 CacheView
3633 *image_view;
3634
cristy3ed852e2009-09-05 21:47:34 +00003635 ExceptionInfo
3636 *exception;
3637
cristy3ed852e2009-09-05 21:47:34 +00003638 MagickBooleanType
3639 status;
3640
cristybb503372010-05-27 20:51:26 +00003641 MagickOffsetType
3642 progress;
3643
cristy3ed852e2009-09-05 21:47:34 +00003644 MagickRealType
3645 *sigmoidal_map;
3646
cristybb503372010-05-27 20:51:26 +00003647 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003648 i;
3649
cristybb503372010-05-27 20:51:26 +00003650 ssize_t
3651 y;
3652
cristy3ed852e2009-09-05 21:47:34 +00003653 /*
3654 Allocate and initialize sigmoidal maps.
3655 */
3656 assert(image != (Image *) NULL);
3657 assert(image->signature == MagickSignature);
3658 if (image->debug != MagickFalse)
3659 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3660 sigmoidal_map=(MagickRealType *) AcquireQuantumMemory(MaxMap+1UL,
3661 sizeof(*sigmoidal_map));
3662 if (sigmoidal_map == (MagickRealType *) NULL)
3663 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
3664 image->filename);
3665 (void) ResetMagickMemory(sigmoidal_map,0,(MaxMap+1)*sizeof(*sigmoidal_map));
cristyb5d5f722009-11-04 03:03:49 +00003666#if defined(MAGICKCORE_OPENMP_SUPPORT)
3667 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003668#endif
cristybb503372010-05-27 20:51:26 +00003669 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +00003670 {
3671 if (sharpen != MagickFalse)
3672 {
3673 sigmoidal_map[i]=(MagickRealType) ScaleMapToQuantum((MagickRealType)
3674 (MaxMap*((1.0/(1.0+exp(contrast*(midpoint/(double) QuantumRange-
3675 (double) i/MaxMap))))-(1.0/(1.0+exp(contrast*(midpoint/
3676 (double) QuantumRange)))))/((1.0/(1.0+exp(contrast*(midpoint/
3677 (double) QuantumRange-1.0))))-(1.0/(1.0+exp(contrast*(midpoint/
3678 (double) QuantumRange)))))+0.5));
3679 continue;
3680 }
3681 sigmoidal_map[i]=(MagickRealType) ScaleMapToQuantum((MagickRealType)
3682 (MaxMap*(QuantumScale*midpoint-log((1.0-(1.0/(1.0+exp(midpoint/
3683 (double) QuantumRange*contrast))+((double) i/MaxMap)*((1.0/
3684 (1.0+exp(contrast*(midpoint/(double) QuantumRange-1.0))))-(1.0/
3685 (1.0+exp(midpoint/(double) QuantumRange*contrast))))))/
3686 (1.0/(1.0+exp(midpoint/(double) QuantumRange*contrast))+
3687 ((double) i/MaxMap)*((1.0/(1.0+exp(contrast*(midpoint/
3688 (double) QuantumRange-1.0))))-(1.0/(1.0+exp(midpoint/
3689 (double) QuantumRange*contrast))))))/contrast)));
3690 }
3691 if (image->storage_class == PseudoClass)
3692 {
3693 /*
3694 Sigmoidal-contrast enhance colormap.
3695 */
cristyb5d5f722009-11-04 03:03:49 +00003696#if defined(MAGICKCORE_OPENMP_SUPPORT)
3697 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003698#endif
cristybb503372010-05-27 20:51:26 +00003699 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00003700 {
3701 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003702 image->colormap[i].red=ClampToQuantum(sigmoidal_map[
cristy3ed852e2009-09-05 21:47:34 +00003703 ScaleQuantumToMap(image->colormap[i].red)]);
3704 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003705 image->colormap[i].green=ClampToQuantum(sigmoidal_map[
cristy3ed852e2009-09-05 21:47:34 +00003706 ScaleQuantumToMap(image->colormap[i].green)]);
3707 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003708 image->colormap[i].blue=ClampToQuantum(sigmoidal_map[
cristy3ed852e2009-09-05 21:47:34 +00003709 ScaleQuantumToMap(image->colormap[i].blue)]);
3710 if ((channel & OpacityChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003711 image->colormap[i].opacity=ClampToQuantum(sigmoidal_map[
cristy3ed852e2009-09-05 21:47:34 +00003712 ScaleQuantumToMap(image->colormap[i].opacity)]);
3713 }
3714 }
3715 /*
3716 Sigmoidal-contrast enhance image.
3717 */
3718 status=MagickTrue;
3719 progress=0;
3720 exception=(&image->exception);
3721 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00003722#if defined(MAGICKCORE_OPENMP_SUPPORT)
3723 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003724#endif
cristybb503372010-05-27 20:51:26 +00003725 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003726 {
3727 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00003728 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00003729
cristy3ed852e2009-09-05 21:47:34 +00003730 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00003731 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003732
cristy8d4629b2010-08-30 17:59:46 +00003733 register ssize_t
3734 x;
3735
cristy3ed852e2009-09-05 21:47:34 +00003736 if (status == MagickFalse)
3737 continue;
3738 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
3739 if (q == (PixelPacket *) NULL)
3740 {
3741 status=MagickFalse;
3742 continue;
3743 }
3744 indexes=GetCacheViewAuthenticIndexQueue(image_view);
cristybb503372010-05-27 20:51:26 +00003745 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003746 {
3747 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003748 q->red=ClampToQuantum(sigmoidal_map[ScaleQuantumToMap(q->red)]);
cristy3ed852e2009-09-05 21:47:34 +00003749 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003750 q->green=ClampToQuantum(sigmoidal_map[ScaleQuantumToMap(q->green)]);
cristy3ed852e2009-09-05 21:47:34 +00003751 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003752 q->blue=ClampToQuantum(sigmoidal_map[ScaleQuantumToMap(q->blue)]);
cristy3ed852e2009-09-05 21:47:34 +00003753 if ((channel & OpacityChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003754 q->opacity=ClampToQuantum(sigmoidal_map[ScaleQuantumToMap(q->opacity)]);
cristy3ed852e2009-09-05 21:47:34 +00003755 if (((channel & IndexChannel) != 0) &&
3756 (image->colorspace == CMYKColorspace))
cristyce70c172010-01-07 17:15:30 +00003757 indexes[x]=(IndexPacket) ClampToQuantum(sigmoidal_map[
cristy3ed852e2009-09-05 21:47:34 +00003758 ScaleQuantumToMap(indexes[x])]);
3759 q++;
3760 }
3761 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
3762 status=MagickFalse;
3763 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3764 {
3765 MagickBooleanType
3766 proceed;
3767
cristyb5d5f722009-11-04 03:03:49 +00003768#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00003769 #pragma omp critical (MagickCore_SigmoidalContrastImageChannel)
3770#endif
3771 proceed=SetImageProgress(image,SigmoidalContrastImageTag,progress++,
3772 image->rows);
3773 if (proceed == MagickFalse)
3774 status=MagickFalse;
3775 }
3776 }
3777 image_view=DestroyCacheView(image_view);
3778 sigmoidal_map=(MagickRealType *) RelinquishMagickMemory(sigmoidal_map);
3779 return(status);
3780}