blob: 065600054a22ac55262d600b63b1a750cd035f1e [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
718 ResampleFilter
cristyfa112112010-01-04 17:48:07 +0000719 **restrict resample_filter;
cristy3ed852e2009-09-05 21:47:34 +0000720
cristybb503372010-05-27 20:51:26 +0000721 ssize_t
722 adjust,
723 y;
724
cristy3ed852e2009-09-05 21:47:34 +0000725 assert(image != (Image *) NULL);
726 assert(image->signature == MagickSignature);
727 if (image->debug != MagickFalse)
728 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
729 assert(clut_image != (Image *) NULL);
730 assert(clut_image->signature == MagickSignature);
731 if (SetImageStorageClass(image,DirectClass) == MagickFalse)
732 return(MagickFalse);
cristy49f37242011-03-22 18:18:23 +0000733 clut_map=(MagickPixelPacket *) AcquireQuantumMemory(MaxMap+1UL,
734 sizeof(*clut_map));
735 if (clut_map == (MagickPixelPacket *) NULL)
736 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
737 image->filename);
cristy3ed852e2009-09-05 21:47:34 +0000738 /*
739 Clut image.
740 */
741 status=MagickTrue;
742 progress=0;
cristybb503372010-05-27 20:51:26 +0000743 adjust=(ssize_t) (clut_image->interpolate == IntegerInterpolatePixel ? 0 : 1);
cristy3ed852e2009-09-05 21:47:34 +0000744 exception=(&image->exception);
cristyb2a11ae2010-02-22 00:53:36 +0000745 resample_filter=AcquireResampleFilterThreadSet(clut_image,
746 UndefinedVirtualPixelMethod,MagickTrue,exception);
cristy49f37242011-03-22 18:18:23 +0000747 for (i=0; i <= (ssize_t) MaxMap; i++)
748 {
749 GetMagickPixelPacket(clut_image,clut_map+i);
750 (void) ResamplePixelColor(resample_filter[0],QuantumScale*i*
751 (clut_image->columns-adjust),QuantumScale*i*(clut_image->rows-adjust),
752 clut_map+i);
753 }
cristy3ed852e2009-09-05 21:47:34 +0000754 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +0000755#if defined(MAGICKCORE_OPENMP_SUPPORT)
756 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000757#endif
cristybb503372010-05-27 20:51:26 +0000758 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000759 {
cristy3635df22011-03-25 00:16:16 +0000760 MagickPixelPacket
761 pixel;
762
cristy3ed852e2009-09-05 21:47:34 +0000763 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +0000764 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +0000765
cristy3ed852e2009-09-05 21:47:34 +0000766 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +0000767 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000768
cristy8d4629b2010-08-30 17:59:46 +0000769 register ssize_t
770 x;
771
cristy3ed852e2009-09-05 21:47:34 +0000772 if (status == MagickFalse)
773 continue;
774 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
775 if (q == (PixelPacket *) NULL)
776 {
777 status=MagickFalse;
778 continue;
779 }
780 indexes=GetCacheViewAuthenticIndexQueue(image_view);
cristy3635df22011-03-25 00:16:16 +0000781 GetMagickPixelPacket(image,&pixel);
cristybb503372010-05-27 20:51:26 +0000782 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000783 {
cristy3635df22011-03-25 00:16:16 +0000784 SetMagickPixelPacket(image,q,indexes+x,&pixel);
cristy2253f172011-03-24 23:39:51 +0000785 if ((channel & RedChannel) != 0)
786 SetRedPixelComponent(q,ClampRedPixelComponent(clut_map+
787 ScaleQuantumToMap(q->red)));
788 if ((channel & GreenChannel) != 0)
789 SetGreenPixelComponent(q,ClampGreenPixelComponent(clut_map+
790 ScaleQuantumToMap(q->green)));
791 if ((channel & BlueChannel) != 0)
792 SetBluePixelComponent(q,ClampBluePixelComponent(clut_map+
793 ScaleQuantumToMap(q->blue)));
cristy3635df22011-03-25 00:16:16 +0000794 if ((channel & OpacityChannel) != 0)
795 {
796 if (clut_image->matte == MagickFalse)
797 q->opacity=(Quantum) (QuantumRange-MagickPixelIntensityToQuantum(
798 clut_map+ScaleQuantumToMap(GetAlphaPixelComponent(q))));
799 else
800 if (image->matte == MagickFalse)
801 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(clut_map+
802 ScaleQuantumToMap(MagickPixelIntensity(&pixel))));
803 else
804 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(
805 clut_map+ScaleQuantumToMap(q->opacity)));
806 }
cristy3ed852e2009-09-05 21:47:34 +0000807 if (((channel & IndexChannel) != 0) &&
808 (image->colorspace == CMYKColorspace))
cristy49f37242011-03-22 18:18:23 +0000809 indexes[x]=ClampToQuantum((clut_map+(ssize_t) indexes[x])->index);
cristy3ed852e2009-09-05 21:47:34 +0000810 q++;
811 }
812 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
813 status=MagickFalse;
814 if (image->progress_monitor != (MagickProgressMonitor) NULL)
815 {
816 MagickBooleanType
817 proceed;
818
cristyb5d5f722009-11-04 03:03:49 +0000819#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +0000820 #pragma omp critical (MagickCore_ClutImageChannel)
821#endif
822 proceed=SetImageProgress(image,ClutImageTag,progress++,image->rows);
823 if (proceed == MagickFalse)
824 status=MagickFalse;
825 }
826 }
827 image_view=DestroyCacheView(image_view);
828 resample_filter=DestroyResampleFilterThreadSet(resample_filter);
cristy49f37242011-03-22 18:18:23 +0000829 clut_map=(MagickPixelPacket *) RelinquishMagickMemory(clut_map);
cristy3ed852e2009-09-05 21:47:34 +0000830 if ((clut_image->matte != MagickFalse) && ((channel & OpacityChannel) != 0))
831 (void) SetImageAlphaChannel(image,ActivateAlphaChannel);
832 return(status);
833}
834
835/*
836%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
837% %
838% %
839% %
840% C o n t r a s t I m a g e %
841% %
842% %
843% %
844%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
845%
846% ContrastImage() enhances the intensity differences between the lighter and
847% darker elements of the image. Set sharpen to a MagickTrue to increase the
848% image contrast otherwise the contrast is reduced.
849%
850% The format of the ContrastImage method is:
851%
852% MagickBooleanType ContrastImage(Image *image,
853% const MagickBooleanType sharpen)
854%
855% A description of each parameter follows:
856%
857% o image: the image.
858%
859% o sharpen: Increase or decrease image contrast.
860%
861*/
862
863static void Contrast(const int sign,Quantum *red,Quantum *green,Quantum *blue)
864{
865 double
866 brightness,
867 hue,
868 saturation;
869
870 /*
871 Enhance contrast: dark color become darker, light color become lighter.
872 */
873 assert(red != (Quantum *) NULL);
874 assert(green != (Quantum *) NULL);
875 assert(blue != (Quantum *) NULL);
876 hue=0.0;
877 saturation=0.0;
878 brightness=0.0;
879 ConvertRGBToHSB(*red,*green,*blue,&hue,&saturation,&brightness);
cristy31ac0f02011-03-12 02:04:47 +0000880 brightness+=0.5*sign*(0.5*(sin((double) (MagickPI*(brightness-0.5)))+1.0)-
cristy4205a3c2010-09-12 20:19:59 +0000881 brightness);
cristy3ed852e2009-09-05 21:47:34 +0000882 if (brightness > 1.0)
883 brightness=1.0;
884 else
885 if (brightness < 0.0)
886 brightness=0.0;
887 ConvertHSBToRGB(hue,saturation,brightness,red,green,blue);
888}
889
890MagickExport MagickBooleanType ContrastImage(Image *image,
891 const MagickBooleanType sharpen)
892{
893#define ContrastImageTag "Contrast/Image"
894
cristyc4c8d132010-01-07 01:58:38 +0000895 CacheView
896 *image_view;
897
cristy3ed852e2009-09-05 21:47:34 +0000898 ExceptionInfo
899 *exception;
900
901 int
902 sign;
903
cristy3ed852e2009-09-05 21:47:34 +0000904 MagickBooleanType
905 status;
906
cristybb503372010-05-27 20:51:26 +0000907 MagickOffsetType
908 progress;
909
910 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000911 i;
912
cristybb503372010-05-27 20:51:26 +0000913 ssize_t
914 y;
915
cristy3ed852e2009-09-05 21:47:34 +0000916 assert(image != (Image *) NULL);
917 assert(image->signature == MagickSignature);
918 if (image->debug != MagickFalse)
919 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
920 sign=sharpen != MagickFalse ? 1 : -1;
921 if (image->storage_class == PseudoClass)
922 {
923 /*
924 Contrast enhance colormap.
925 */
cristybb503372010-05-27 20:51:26 +0000926 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +0000927 Contrast(sign,&image->colormap[i].red,&image->colormap[i].green,
928 &image->colormap[i].blue);
929 }
930 /*
931 Contrast enhance image.
932 */
933 status=MagickTrue;
934 progress=0;
935 exception=(&image->exception);
936 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +0000937#if defined(MAGICKCORE_OPENMP_SUPPORT)
938 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000939#endif
cristybb503372010-05-27 20:51:26 +0000940 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000941 {
cristy3ed852e2009-09-05 21:47:34 +0000942 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +0000943 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000944
cristy8d4629b2010-08-30 17:59:46 +0000945 register ssize_t
946 x;
947
cristy3ed852e2009-09-05 21:47:34 +0000948 if (status == MagickFalse)
949 continue;
950 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
951 if (q == (PixelPacket *) NULL)
952 {
953 status=MagickFalse;
954 continue;
955 }
cristybb503372010-05-27 20:51:26 +0000956 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000957 {
958 Contrast(sign,&q->red,&q->green,&q->blue);
959 q++;
960 }
961 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
962 status=MagickFalse;
963 if (image->progress_monitor != (MagickProgressMonitor) NULL)
964 {
965 MagickBooleanType
966 proceed;
967
cristyb5d5f722009-11-04 03:03:49 +0000968#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +0000969 #pragma omp critical (MagickCore_ContrastImage)
970#endif
971 proceed=SetImageProgress(image,ContrastImageTag,progress++,image->rows);
972 if (proceed == MagickFalse)
973 status=MagickFalse;
974 }
975 }
976 image_view=DestroyCacheView(image_view);
977 return(status);
978}
979
980/*
981%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
982% %
983% %
984% %
985% C o n t r a s t S t r e t c h I m a g e %
986% %
987% %
988% %
989%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
990%
991% The ContrastStretchImage() is a simple image enhancement technique that
992% attempts to improve the contrast in an image by `stretching' the range of
993% intensity values it contains to span a desired range of values. It differs
994% from the more sophisticated histogram equalization in that it can only
995% apply % a linear scaling function to the image pixel values. As a result
996% the `enhancement' is less harsh.
997%
998% The format of the ContrastStretchImage method is:
999%
1000% MagickBooleanType ContrastStretchImage(Image *image,
1001% const char *levels)
1002% MagickBooleanType ContrastStretchImageChannel(Image *image,
cristybb503372010-05-27 20:51:26 +00001003% const size_t channel,const double black_point,
cristy3ed852e2009-09-05 21:47:34 +00001004% const double white_point)
1005%
1006% A description of each parameter follows:
1007%
1008% o image: the image.
1009%
1010% o channel: the channel.
1011%
1012% o black_point: the black point.
1013%
1014% o white_point: the white point.
1015%
1016% o levels: Specify the levels where the black and white points have the
1017% range of 0 to number-of-pixels (e.g. 1%, 10x90%, etc.).
1018%
1019*/
1020
1021MagickExport MagickBooleanType ContrastStretchImage(Image *image,
1022 const char *levels)
1023{
1024 double
1025 black_point,
1026 white_point;
1027
1028 GeometryInfo
1029 geometry_info;
1030
1031 MagickBooleanType
1032 status;
1033
1034 MagickStatusType
1035 flags;
1036
1037 /*
1038 Parse levels.
1039 */
1040 if (levels == (char *) NULL)
1041 return(MagickFalse);
1042 flags=ParseGeometry(levels,&geometry_info);
1043 black_point=geometry_info.rho;
1044 white_point=(double) image->columns*image->rows;
1045 if ((flags & SigmaValue) != 0)
1046 white_point=geometry_info.sigma;
1047 if ((flags & PercentValue) != 0)
1048 {
1049 black_point*=(double) QuantumRange/100.0;
1050 white_point*=(double) QuantumRange/100.0;
1051 }
1052 if ((flags & SigmaValue) == 0)
1053 white_point=(double) image->columns*image->rows-black_point;
1054 status=ContrastStretchImageChannel(image,DefaultChannels,black_point,
1055 white_point);
1056 return(status);
1057}
1058
1059MagickExport MagickBooleanType ContrastStretchImageChannel(Image *image,
1060 const ChannelType channel,const double black_point,const double white_point)
1061{
1062#define MaxRange(color) ((MagickRealType) ScaleQuantumToMap((Quantum) (color)))
1063#define ContrastStretchImageTag "ContrastStretch/Image"
1064
cristyc4c8d132010-01-07 01:58:38 +00001065 CacheView
1066 *image_view;
1067
cristy3ed852e2009-09-05 21:47:34 +00001068 double
1069 intensity;
1070
1071 ExceptionInfo
1072 *exception;
1073
cristy3ed852e2009-09-05 21:47:34 +00001074 MagickBooleanType
1075 status;
1076
cristybb503372010-05-27 20:51:26 +00001077 MagickOffsetType
1078 progress;
1079
cristy3ed852e2009-09-05 21:47:34 +00001080 MagickPixelPacket
1081 black,
1082 *histogram,
1083 *stretch_map,
1084 white;
1085
cristybb503372010-05-27 20:51:26 +00001086 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001087 i;
1088
cristybb503372010-05-27 20:51:26 +00001089 ssize_t
1090 y;
1091
cristy3ed852e2009-09-05 21:47:34 +00001092 /*
1093 Allocate histogram and stretch map.
1094 */
1095 assert(image != (Image *) NULL);
1096 assert(image->signature == MagickSignature);
1097 if (image->debug != MagickFalse)
1098 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1099 histogram=(MagickPixelPacket *) AcquireQuantumMemory(MaxMap+1UL,
1100 sizeof(*histogram));
1101 stretch_map=(MagickPixelPacket *) AcquireQuantumMemory(MaxMap+1UL,
1102 sizeof(*stretch_map));
1103 if ((histogram == (MagickPixelPacket *) NULL) ||
1104 (stretch_map == (MagickPixelPacket *) NULL))
1105 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
1106 image->filename);
1107 /*
1108 Form histogram.
1109 */
1110 status=MagickTrue;
1111 exception=(&image->exception);
1112 (void) ResetMagickMemory(histogram,0,(MaxMap+1)*sizeof(*histogram));
1113 image_view=AcquireCacheView(image);
cristybb503372010-05-27 20:51:26 +00001114 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001115 {
1116 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001117 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00001118
1119 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00001120 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00001121
cristybb503372010-05-27 20:51:26 +00001122 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001123 x;
1124
1125 if (status == MagickFalse)
1126 continue;
1127 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
1128 if (p == (const PixelPacket *) NULL)
1129 {
1130 status=MagickFalse;
1131 continue;
1132 }
1133 indexes=GetCacheViewAuthenticIndexQueue(image_view);
1134 if (channel == DefaultChannels)
cristybb503372010-05-27 20:51:26 +00001135 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001136 {
1137 Quantum
1138 intensity;
1139
1140 intensity=PixelIntensityToQuantum(p);
1141 histogram[ScaleQuantumToMap(intensity)].red++;
1142 histogram[ScaleQuantumToMap(intensity)].green++;
1143 histogram[ScaleQuantumToMap(intensity)].blue++;
1144 histogram[ScaleQuantumToMap(intensity)].index++;
1145 p++;
1146 }
1147 else
cristybb503372010-05-27 20:51:26 +00001148 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001149 {
1150 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001151 histogram[ScaleQuantumToMap(GetRedPixelComponent(p))].red++;
cristy3ed852e2009-09-05 21:47:34 +00001152 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001153 histogram[ScaleQuantumToMap(GetGreenPixelComponent(p))].green++;
cristy3ed852e2009-09-05 21:47:34 +00001154 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001155 histogram[ScaleQuantumToMap(GetBluePixelComponent(p))].blue++;
cristy3ed852e2009-09-05 21:47:34 +00001156 if ((channel & OpacityChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001157 histogram[ScaleQuantumToMap(GetOpacityPixelComponent(p))].opacity++;
cristy3ed852e2009-09-05 21:47:34 +00001158 if (((channel & IndexChannel) != 0) &&
1159 (image->colorspace == CMYKColorspace))
1160 histogram[ScaleQuantumToMap(indexes[x])].index++;
1161 p++;
1162 }
1163 }
1164 /*
1165 Find the histogram boundaries by locating the black/white levels.
1166 */
1167 black.red=0.0;
1168 white.red=MaxRange(QuantumRange);
1169 if ((channel & RedChannel) != 0)
1170 {
1171 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00001172 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +00001173 {
1174 intensity+=histogram[i].red;
1175 if (intensity > black_point)
1176 break;
1177 }
1178 black.red=(MagickRealType) i;
1179 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00001180 for (i=(ssize_t) MaxMap; i != 0; i--)
cristy3ed852e2009-09-05 21:47:34 +00001181 {
1182 intensity+=histogram[i].red;
1183 if (intensity > ((double) image->columns*image->rows-white_point))
1184 break;
1185 }
1186 white.red=(MagickRealType) i;
1187 }
1188 black.green=0.0;
1189 white.green=MaxRange(QuantumRange);
1190 if ((channel & GreenChannel) != 0)
1191 {
1192 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00001193 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +00001194 {
1195 intensity+=histogram[i].green;
1196 if (intensity > black_point)
1197 break;
1198 }
1199 black.green=(MagickRealType) i;
1200 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00001201 for (i=(ssize_t) MaxMap; i != 0; i--)
cristy3ed852e2009-09-05 21:47:34 +00001202 {
1203 intensity+=histogram[i].green;
1204 if (intensity > ((double) image->columns*image->rows-white_point))
1205 break;
1206 }
1207 white.green=(MagickRealType) i;
1208 }
1209 black.blue=0.0;
1210 white.blue=MaxRange(QuantumRange);
1211 if ((channel & BlueChannel) != 0)
1212 {
1213 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00001214 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +00001215 {
1216 intensity+=histogram[i].blue;
1217 if (intensity > black_point)
1218 break;
1219 }
1220 black.blue=(MagickRealType) i;
1221 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00001222 for (i=(ssize_t) MaxMap; i != 0; i--)
cristy3ed852e2009-09-05 21:47:34 +00001223 {
1224 intensity+=histogram[i].blue;
1225 if (intensity > ((double) image->columns*image->rows-white_point))
1226 break;
1227 }
1228 white.blue=(MagickRealType) i;
1229 }
1230 black.opacity=0.0;
1231 white.opacity=MaxRange(QuantumRange);
1232 if ((channel & OpacityChannel) != 0)
1233 {
1234 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00001235 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +00001236 {
1237 intensity+=histogram[i].opacity;
1238 if (intensity > black_point)
1239 break;
1240 }
1241 black.opacity=(MagickRealType) i;
1242 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00001243 for (i=(ssize_t) MaxMap; i != 0; i--)
cristy3ed852e2009-09-05 21:47:34 +00001244 {
1245 intensity+=histogram[i].opacity;
1246 if (intensity > ((double) image->columns*image->rows-white_point))
1247 break;
1248 }
1249 white.opacity=(MagickRealType) i;
1250 }
1251 black.index=0.0;
1252 white.index=MaxRange(QuantumRange);
1253 if (((channel & IndexChannel) != 0) && (image->colorspace == CMYKColorspace))
1254 {
1255 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00001256 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +00001257 {
1258 intensity+=histogram[i].index;
1259 if (intensity > black_point)
1260 break;
1261 }
1262 black.index=(MagickRealType) i;
1263 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00001264 for (i=(ssize_t) MaxMap; i != 0; i--)
cristy3ed852e2009-09-05 21:47:34 +00001265 {
1266 intensity+=histogram[i].index;
1267 if (intensity > ((double) image->columns*image->rows-white_point))
1268 break;
1269 }
1270 white.index=(MagickRealType) i;
1271 }
1272 histogram=(MagickPixelPacket *) RelinquishMagickMemory(histogram);
1273 /*
1274 Stretch the histogram to create the stretched image mapping.
1275 */
1276 (void) ResetMagickMemory(stretch_map,0,(MaxMap+1)*sizeof(*stretch_map));
cristyb5d5f722009-11-04 03:03:49 +00001277#if defined(MAGICKCORE_OPENMP_SUPPORT)
1278 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001279#endif
cristybb503372010-05-27 20:51:26 +00001280 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +00001281 {
1282 if ((channel & RedChannel) != 0)
1283 {
cristybb503372010-05-27 20:51:26 +00001284 if (i < (ssize_t) black.red)
cristy3ed852e2009-09-05 21:47:34 +00001285 stretch_map[i].red=0.0;
1286 else
cristybb503372010-05-27 20:51:26 +00001287 if (i > (ssize_t) white.red)
cristy3ed852e2009-09-05 21:47:34 +00001288 stretch_map[i].red=(MagickRealType) QuantumRange;
1289 else
1290 if (black.red != white.red)
1291 stretch_map[i].red=(MagickRealType) ScaleMapToQuantum(
1292 (MagickRealType) (MaxMap*(i-black.red)/(white.red-black.red)));
1293 }
1294 if ((channel & GreenChannel) != 0)
1295 {
cristybb503372010-05-27 20:51:26 +00001296 if (i < (ssize_t) black.green)
cristy3ed852e2009-09-05 21:47:34 +00001297 stretch_map[i].green=0.0;
1298 else
cristybb503372010-05-27 20:51:26 +00001299 if (i > (ssize_t) white.green)
cristy3ed852e2009-09-05 21:47:34 +00001300 stretch_map[i].green=(MagickRealType) QuantumRange;
1301 else
1302 if (black.green != white.green)
1303 stretch_map[i].green=(MagickRealType) ScaleMapToQuantum(
1304 (MagickRealType) (MaxMap*(i-black.green)/(white.green-
1305 black.green)));
1306 }
1307 if ((channel & BlueChannel) != 0)
1308 {
cristybb503372010-05-27 20:51:26 +00001309 if (i < (ssize_t) black.blue)
cristy3ed852e2009-09-05 21:47:34 +00001310 stretch_map[i].blue=0.0;
1311 else
cristybb503372010-05-27 20:51:26 +00001312 if (i > (ssize_t) white.blue)
cristy3ed852e2009-09-05 21:47:34 +00001313 stretch_map[i].blue=(MagickRealType) QuantumRange;
1314 else
1315 if (black.blue != white.blue)
1316 stretch_map[i].blue=(MagickRealType) ScaleMapToQuantum(
1317 (MagickRealType) (MaxMap*(i-black.blue)/(white.blue-
1318 black.blue)));
1319 }
1320 if ((channel & OpacityChannel) != 0)
1321 {
cristybb503372010-05-27 20:51:26 +00001322 if (i < (ssize_t) black.opacity)
cristy3ed852e2009-09-05 21:47:34 +00001323 stretch_map[i].opacity=0.0;
1324 else
cristybb503372010-05-27 20:51:26 +00001325 if (i > (ssize_t) white.opacity)
cristy3ed852e2009-09-05 21:47:34 +00001326 stretch_map[i].opacity=(MagickRealType) QuantumRange;
1327 else
1328 if (black.opacity != white.opacity)
1329 stretch_map[i].opacity=(MagickRealType) ScaleMapToQuantum(
1330 (MagickRealType) (MaxMap*(i-black.opacity)/(white.opacity-
1331 black.opacity)));
1332 }
1333 if (((channel & IndexChannel) != 0) &&
1334 (image->colorspace == CMYKColorspace))
1335 {
cristybb503372010-05-27 20:51:26 +00001336 if (i < (ssize_t) black.index)
cristy3ed852e2009-09-05 21:47:34 +00001337 stretch_map[i].index=0.0;
1338 else
cristybb503372010-05-27 20:51:26 +00001339 if (i > (ssize_t) white.index)
cristy3ed852e2009-09-05 21:47:34 +00001340 stretch_map[i].index=(MagickRealType) QuantumRange;
1341 else
1342 if (black.index != white.index)
1343 stretch_map[i].index=(MagickRealType) ScaleMapToQuantum(
1344 (MagickRealType) (MaxMap*(i-black.index)/(white.index-
1345 black.index)));
1346 }
1347 }
1348 /*
1349 Stretch the image.
1350 */
1351 if (((channel & OpacityChannel) != 0) || (((channel & IndexChannel) != 0) &&
1352 (image->colorspace == CMYKColorspace)))
1353 image->storage_class=DirectClass;
1354 if (image->storage_class == PseudoClass)
1355 {
1356 /*
1357 Stretch colormap.
1358 */
cristyb5d5f722009-11-04 03:03:49 +00001359#if defined(MAGICKCORE_OPENMP_SUPPORT)
1360 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001361#endif
cristybb503372010-05-27 20:51:26 +00001362 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00001363 {
1364 if ((channel & RedChannel) != 0)
1365 {
1366 if (black.red != white.red)
cristyce70c172010-01-07 17:15:30 +00001367 image->colormap[i].red=ClampToQuantum(stretch_map[
cristy3ed852e2009-09-05 21:47:34 +00001368 ScaleQuantumToMap(image->colormap[i].red)].red);
1369 }
1370 if ((channel & GreenChannel) != 0)
1371 {
1372 if (black.green != white.green)
cristyce70c172010-01-07 17:15:30 +00001373 image->colormap[i].green=ClampToQuantum(stretch_map[
cristy3ed852e2009-09-05 21:47:34 +00001374 ScaleQuantumToMap(image->colormap[i].green)].green);
1375 }
1376 if ((channel & BlueChannel) != 0)
1377 {
1378 if (black.blue != white.blue)
cristyce70c172010-01-07 17:15:30 +00001379 image->colormap[i].blue=ClampToQuantum(stretch_map[
cristy3ed852e2009-09-05 21:47:34 +00001380 ScaleQuantumToMap(image->colormap[i].blue)].blue);
1381 }
1382 if ((channel & OpacityChannel) != 0)
1383 {
1384 if (black.opacity != white.opacity)
cristyce70c172010-01-07 17:15:30 +00001385 image->colormap[i].opacity=ClampToQuantum(stretch_map[
cristy3ed852e2009-09-05 21:47:34 +00001386 ScaleQuantumToMap(image->colormap[i].opacity)].opacity);
1387 }
1388 }
1389 }
1390 /*
1391 Stretch image.
1392 */
1393 status=MagickTrue;
1394 progress=0;
cristyb5d5f722009-11-04 03:03:49 +00001395#if defined(MAGICKCORE_OPENMP_SUPPORT)
1396 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001397#endif
cristybb503372010-05-27 20:51:26 +00001398 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001399 {
1400 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00001401 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00001402
cristy3ed852e2009-09-05 21:47:34 +00001403 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001404 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001405
cristy8d4629b2010-08-30 17:59:46 +00001406 register ssize_t
1407 x;
1408
cristy3ed852e2009-09-05 21:47:34 +00001409 if (status == MagickFalse)
1410 continue;
1411 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
1412 if (q == (PixelPacket *) NULL)
1413 {
1414 status=MagickFalse;
1415 continue;
1416 }
1417 indexes=GetCacheViewAuthenticIndexQueue(image_view);
cristybb503372010-05-27 20:51:26 +00001418 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001419 {
1420 if ((channel & RedChannel) != 0)
1421 {
1422 if (black.red != white.red)
cristyce70c172010-01-07 17:15:30 +00001423 q->red=ClampToQuantum(stretch_map[ScaleQuantumToMap(q->red)].red);
cristy3ed852e2009-09-05 21:47:34 +00001424 }
1425 if ((channel & GreenChannel) != 0)
1426 {
1427 if (black.green != white.green)
cristyce70c172010-01-07 17:15:30 +00001428 q->green=ClampToQuantum(stretch_map[ScaleQuantumToMap(
cristy3ed852e2009-09-05 21:47:34 +00001429 q->green)].green);
1430 }
1431 if ((channel & BlueChannel) != 0)
1432 {
1433 if (black.blue != white.blue)
cristyce70c172010-01-07 17:15:30 +00001434 q->blue=ClampToQuantum(stretch_map[ScaleQuantumToMap(
cristy3ed852e2009-09-05 21:47:34 +00001435 q->blue)].blue);
1436 }
1437 if ((channel & OpacityChannel) != 0)
1438 {
1439 if (black.opacity != white.opacity)
cristyce70c172010-01-07 17:15:30 +00001440 q->opacity=ClampToQuantum(stretch_map[ScaleQuantumToMap(
cristy3ed852e2009-09-05 21:47:34 +00001441 q->opacity)].opacity);
1442 }
1443 if (((channel & IndexChannel) != 0) &&
1444 (image->colorspace == CMYKColorspace))
1445 {
1446 if (black.index != white.index)
cristyce70c172010-01-07 17:15:30 +00001447 indexes[x]=(IndexPacket) ClampToQuantum(stretch_map[
cristy3ed852e2009-09-05 21:47:34 +00001448 ScaleQuantumToMap(indexes[x])].index);
1449 }
1450 q++;
1451 }
1452 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
1453 status=MagickFalse;
1454 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1455 {
1456 MagickBooleanType
1457 proceed;
1458
cristyb5d5f722009-11-04 03:03:49 +00001459#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00001460 #pragma omp critical (MagickCore_ContrastStretchImageChannel)
1461#endif
1462 proceed=SetImageProgress(image,ContrastStretchImageTag,progress++,
1463 image->rows);
1464 if (proceed == MagickFalse)
1465 status=MagickFalse;
1466 }
1467 }
1468 image_view=DestroyCacheView(image_view);
1469 stretch_map=(MagickPixelPacket *) RelinquishMagickMemory(stretch_map);
1470 return(status);
1471}
1472
1473/*
1474%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1475% %
1476% %
1477% %
1478% E n h a n c e I m a g e %
1479% %
1480% %
1481% %
1482%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1483%
1484% EnhanceImage() applies a digital filter that improves the quality of a
1485% noisy image.
1486%
1487% The format of the EnhanceImage method is:
1488%
1489% Image *EnhanceImage(const Image *image,ExceptionInfo *exception)
1490%
1491% A description of each parameter follows:
1492%
1493% o image: the image.
1494%
1495% o exception: return any errors or warnings in this structure.
1496%
1497*/
1498MagickExport Image *EnhanceImage(const Image *image,ExceptionInfo *exception)
1499{
1500#define Enhance(weight) \
1501 mean=((MagickRealType) r->red+pixel.red)/2; \
1502 distance=(MagickRealType) r->red-(MagickRealType) pixel.red; \
1503 distance_squared=QuantumScale*(2.0*((MagickRealType) QuantumRange+1.0)+ \
1504 mean)*distance*distance; \
1505 mean=((MagickRealType) r->green+pixel.green)/2; \
1506 distance=(MagickRealType) r->green-(MagickRealType) pixel.green; \
1507 distance_squared+=4.0*distance*distance; \
1508 mean=((MagickRealType) r->blue+pixel.blue)/2; \
1509 distance=(MagickRealType) r->blue-(MagickRealType) pixel.blue; \
1510 distance_squared+=QuantumScale*(3.0*((MagickRealType) \
1511 QuantumRange+1.0)-1.0-mean)*distance*distance; \
1512 mean=((MagickRealType) r->opacity+pixel.opacity)/2; \
1513 distance=(MagickRealType) r->opacity-(MagickRealType) pixel.opacity; \
1514 distance_squared+=QuantumScale*(3.0*((MagickRealType) \
1515 QuantumRange+1.0)-1.0-mean)*distance*distance; \
1516 if (distance_squared < ((MagickRealType) QuantumRange*(MagickRealType) \
1517 QuantumRange/25.0f)) \
1518 { \
1519 aggregate.red+=(weight)*r->red; \
1520 aggregate.green+=(weight)*r->green; \
1521 aggregate.blue+=(weight)*r->blue; \
1522 aggregate.opacity+=(weight)*r->opacity; \
1523 total_weight+=(weight); \
1524 } \
1525 r++;
1526#define EnhanceImageTag "Enhance/Image"
1527
cristyc4c8d132010-01-07 01:58:38 +00001528 CacheView
1529 *enhance_view,
1530 *image_view;
1531
cristy3ed852e2009-09-05 21:47:34 +00001532 Image
1533 *enhance_image;
1534
cristy3ed852e2009-09-05 21:47:34 +00001535 MagickBooleanType
1536 status;
1537
cristybb503372010-05-27 20:51:26 +00001538 MagickOffsetType
1539 progress;
1540
cristy3ed852e2009-09-05 21:47:34 +00001541 MagickPixelPacket
1542 zero;
1543
cristybb503372010-05-27 20:51:26 +00001544 ssize_t
1545 y;
1546
cristy3ed852e2009-09-05 21:47:34 +00001547 /*
1548 Initialize enhanced image attributes.
1549 */
1550 assert(image != (const Image *) NULL);
1551 assert(image->signature == MagickSignature);
1552 if (image->debug != MagickFalse)
1553 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1554 assert(exception != (ExceptionInfo *) NULL);
1555 assert(exception->signature == MagickSignature);
1556 if ((image->columns < 5) || (image->rows < 5))
1557 return((Image *) NULL);
1558 enhance_image=CloneImage(image,image->columns,image->rows,MagickTrue,
1559 exception);
1560 if (enhance_image == (Image *) NULL)
1561 return((Image *) NULL);
1562 if (SetImageStorageClass(enhance_image,DirectClass) == MagickFalse)
1563 {
1564 InheritException(exception,&enhance_image->exception);
1565 enhance_image=DestroyImage(enhance_image);
1566 return((Image *) NULL);
1567 }
1568 /*
1569 Enhance image.
1570 */
1571 status=MagickTrue;
1572 progress=0;
1573 (void) ResetMagickMemory(&zero,0,sizeof(zero));
1574 image_view=AcquireCacheView(image);
1575 enhance_view=AcquireCacheView(enhance_image);
cristyb5d5f722009-11-04 03:03:49 +00001576#if defined(MAGICKCORE_OPENMP_SUPPORT)
1577 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001578#endif
cristybb503372010-05-27 20:51:26 +00001579 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001580 {
1581 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001582 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00001583
cristy3ed852e2009-09-05 21:47:34 +00001584 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001585 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001586
cristy8d4629b2010-08-30 17:59:46 +00001587 register ssize_t
1588 x;
1589
cristy3ed852e2009-09-05 21:47:34 +00001590 /*
1591 Read another scan line.
1592 */
1593 if (status == MagickFalse)
1594 continue;
1595 p=GetCacheViewVirtualPixels(image_view,-2,y-2,image->columns+4,5,exception);
1596 q=QueueCacheViewAuthenticPixels(enhance_view,0,y,enhance_image->columns,1,
1597 exception);
1598 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
1599 {
1600 status=MagickFalse;
1601 continue;
1602 }
cristybb503372010-05-27 20:51:26 +00001603 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001604 {
1605 MagickPixelPacket
1606 aggregate;
1607
1608 MagickRealType
1609 distance,
1610 distance_squared,
1611 mean,
1612 total_weight;
1613
1614 PixelPacket
1615 pixel;
1616
1617 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001618 *restrict r;
cristy3ed852e2009-09-05 21:47:34 +00001619
1620 /*
1621 Compute weighted average of target pixel color components.
1622 */
1623 aggregate=zero;
1624 total_weight=0.0;
1625 r=p+2*(image->columns+4)+2;
1626 pixel=(*r);
1627 r=p;
1628 Enhance(5.0); Enhance(8.0); Enhance(10.0); Enhance(8.0); Enhance(5.0);
1629 r=p+(image->columns+4);
1630 Enhance(8.0); Enhance(20.0); Enhance(40.0); Enhance(20.0); Enhance(8.0);
1631 r=p+2*(image->columns+4);
1632 Enhance(10.0); Enhance(40.0); Enhance(80.0); Enhance(40.0); Enhance(10.0);
1633 r=p+3*(image->columns+4);
1634 Enhance(8.0); Enhance(20.0); Enhance(40.0); Enhance(20.0); Enhance(8.0);
1635 r=p+4*(image->columns+4);
1636 Enhance(5.0); Enhance(8.0); Enhance(10.0); Enhance(8.0); Enhance(5.0);
1637 q->red=(Quantum) ((aggregate.red+(total_weight/2)-1)/total_weight);
1638 q->green=(Quantum) ((aggregate.green+(total_weight/2)-1)/total_weight);
1639 q->blue=(Quantum) ((aggregate.blue+(total_weight/2)-1)/total_weight);
1640 q->opacity=(Quantum) ((aggregate.opacity+(total_weight/2)-1)/
1641 total_weight);
1642 p++;
1643 q++;
1644 }
1645 if (SyncCacheViewAuthenticPixels(enhance_view,exception) == MagickFalse)
1646 status=MagickFalse;
1647 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1648 {
1649 MagickBooleanType
1650 proceed;
1651
cristyb5d5f722009-11-04 03:03:49 +00001652#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00001653 #pragma omp critical (MagickCore_EnhanceImage)
1654#endif
1655 proceed=SetImageProgress(image,EnhanceImageTag,progress++,image->rows);
1656 if (proceed == MagickFalse)
1657 status=MagickFalse;
1658 }
1659 }
1660 enhance_view=DestroyCacheView(enhance_view);
1661 image_view=DestroyCacheView(image_view);
1662 return(enhance_image);
1663}
1664
1665/*
1666%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1667% %
1668% %
1669% %
1670% E q u a l i z e I m a g e %
1671% %
1672% %
1673% %
1674%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1675%
1676% EqualizeImage() applies a histogram equalization to the image.
1677%
1678% The format of the EqualizeImage method is:
1679%
1680% MagickBooleanType EqualizeImage(Image *image)
1681% MagickBooleanType EqualizeImageChannel(Image *image,
1682% const ChannelType channel)
1683%
1684% A description of each parameter follows:
1685%
1686% o image: the image.
1687%
1688% o channel: the channel.
1689%
1690*/
1691
1692MagickExport MagickBooleanType EqualizeImage(Image *image)
1693{
1694 return(EqualizeImageChannel(image,DefaultChannels));
1695}
1696
1697MagickExport MagickBooleanType EqualizeImageChannel(Image *image,
1698 const ChannelType channel)
1699{
1700#define EqualizeImageTag "Equalize/Image"
1701
cristyc4c8d132010-01-07 01:58:38 +00001702 CacheView
1703 *image_view;
1704
cristy3ed852e2009-09-05 21:47:34 +00001705 ExceptionInfo
1706 *exception;
1707
cristy3ed852e2009-09-05 21:47:34 +00001708 MagickBooleanType
1709 status;
1710
cristybb503372010-05-27 20:51:26 +00001711 MagickOffsetType
1712 progress;
1713
cristy3ed852e2009-09-05 21:47:34 +00001714 MagickPixelPacket
1715 black,
1716 *equalize_map,
1717 *histogram,
1718 intensity,
1719 *map,
1720 white;
1721
cristybb503372010-05-27 20:51:26 +00001722 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001723 i;
1724
cristybb503372010-05-27 20:51:26 +00001725 ssize_t
1726 y;
1727
cristy3ed852e2009-09-05 21:47:34 +00001728 /*
1729 Allocate and initialize histogram arrays.
1730 */
1731 assert(image != (Image *) NULL);
1732 assert(image->signature == MagickSignature);
1733 if (image->debug != MagickFalse)
1734 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1735 equalize_map=(MagickPixelPacket *) AcquireQuantumMemory(MaxMap+1UL,
1736 sizeof(*equalize_map));
1737 histogram=(MagickPixelPacket *) AcquireQuantumMemory(MaxMap+1UL,
1738 sizeof(*histogram));
1739 map=(MagickPixelPacket *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*map));
1740 if ((equalize_map == (MagickPixelPacket *) NULL) ||
1741 (histogram == (MagickPixelPacket *) NULL) ||
1742 (map == (MagickPixelPacket *) NULL))
1743 {
1744 if (map != (MagickPixelPacket *) NULL)
1745 map=(MagickPixelPacket *) RelinquishMagickMemory(map);
1746 if (histogram != (MagickPixelPacket *) NULL)
1747 histogram=(MagickPixelPacket *) RelinquishMagickMemory(histogram);
1748 if (equalize_map != (MagickPixelPacket *) NULL)
1749 equalize_map=(MagickPixelPacket *) RelinquishMagickMemory(equalize_map);
1750 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
1751 image->filename);
1752 }
1753 /*
1754 Form histogram.
1755 */
1756 (void) ResetMagickMemory(histogram,0,(MaxMap+1)*sizeof(*histogram));
1757 exception=(&image->exception);
cristybb503372010-05-27 20:51:26 +00001758 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001759 {
1760 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00001761 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00001762
1763 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001764 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00001765
cristybb503372010-05-27 20:51:26 +00001766 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001767 x;
1768
1769 p=GetVirtualPixels(image,0,y,image->columns,1,exception);
1770 if (p == (const PixelPacket *) NULL)
1771 break;
1772 indexes=GetVirtualIndexQueue(image);
cristybb503372010-05-27 20:51:26 +00001773 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001774 {
1775 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001776 histogram[ScaleQuantumToMap(GetRedPixelComponent(p))].red++;
cristy3ed852e2009-09-05 21:47:34 +00001777 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001778 histogram[ScaleQuantumToMap(GetGreenPixelComponent(p))].green++;
cristy3ed852e2009-09-05 21:47:34 +00001779 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001780 histogram[ScaleQuantumToMap(GetBluePixelComponent(p))].blue++;
cristy3ed852e2009-09-05 21:47:34 +00001781 if ((channel & OpacityChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001782 histogram[ScaleQuantumToMap(GetOpacityPixelComponent(p))].opacity++;
cristy3ed852e2009-09-05 21:47:34 +00001783 if (((channel & IndexChannel) != 0) &&
1784 (image->colorspace == CMYKColorspace))
1785 histogram[ScaleQuantumToMap(indexes[x])].index++;
1786 p++;
1787 }
1788 }
1789 /*
1790 Integrate the histogram to get the equalization map.
1791 */
1792 (void) ResetMagickMemory(&intensity,0,sizeof(intensity));
cristybb503372010-05-27 20:51:26 +00001793 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +00001794 {
1795 if ((channel & RedChannel) != 0)
1796 intensity.red+=histogram[i].red;
1797 if ((channel & GreenChannel) != 0)
1798 intensity.green+=histogram[i].green;
1799 if ((channel & BlueChannel) != 0)
1800 intensity.blue+=histogram[i].blue;
1801 if ((channel & OpacityChannel) != 0)
1802 intensity.opacity+=histogram[i].opacity;
1803 if (((channel & IndexChannel) != 0) &&
1804 (image->colorspace == CMYKColorspace))
1805 intensity.index+=histogram[i].index;
1806 map[i]=intensity;
1807 }
1808 black=map[0];
1809 white=map[(int) MaxMap];
1810 (void) ResetMagickMemory(equalize_map,0,(MaxMap+1)*sizeof(*equalize_map));
cristyb5d5f722009-11-04 03:03:49 +00001811#if defined(MAGICKCORE_OPENMP_SUPPORT)
1812 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001813#endif
cristybb503372010-05-27 20:51:26 +00001814 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +00001815 {
1816 if (((channel & RedChannel) != 0) && (white.red != black.red))
1817 equalize_map[i].red=(MagickRealType) ScaleMapToQuantum((MagickRealType)
1818 ((MaxMap*(map[i].red-black.red))/(white.red-black.red)));
1819 if (((channel & GreenChannel) != 0) && (white.green != black.green))
1820 equalize_map[i].green=(MagickRealType) ScaleMapToQuantum((MagickRealType)
1821 ((MaxMap*(map[i].green-black.green))/(white.green-black.green)));
1822 if (((channel & BlueChannel) != 0) && (white.blue != black.blue))
1823 equalize_map[i].blue=(MagickRealType) ScaleMapToQuantum((MagickRealType)
1824 ((MaxMap*(map[i].blue-black.blue))/(white.blue-black.blue)));
1825 if (((channel & OpacityChannel) != 0) && (white.opacity != black.opacity))
1826 equalize_map[i].opacity=(MagickRealType) ScaleMapToQuantum(
1827 (MagickRealType) ((MaxMap*(map[i].opacity-black.opacity))/
1828 (white.opacity-black.opacity)));
1829 if ((((channel & IndexChannel) != 0) &&
1830 (image->colorspace == CMYKColorspace)) &&
1831 (white.index != black.index))
1832 equalize_map[i].index=(MagickRealType) ScaleMapToQuantum((MagickRealType)
1833 ((MaxMap*(map[i].index-black.index))/(white.index-black.index)));
1834 }
1835 histogram=(MagickPixelPacket *) RelinquishMagickMemory(histogram);
1836 map=(MagickPixelPacket *) RelinquishMagickMemory(map);
1837 if (image->storage_class == PseudoClass)
1838 {
1839 /*
1840 Equalize colormap.
1841 */
cristyb5d5f722009-11-04 03:03:49 +00001842#if defined(MAGICKCORE_OPENMP_SUPPORT)
1843 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001844#endif
cristybb503372010-05-27 20:51:26 +00001845 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00001846 {
1847 if (((channel & RedChannel) != 0) && (white.red != black.red))
cristyce70c172010-01-07 17:15:30 +00001848 image->colormap[i].red=ClampToQuantum(equalize_map[
cristy3ed852e2009-09-05 21:47:34 +00001849 ScaleQuantumToMap(image->colormap[i].red)].red);
1850 if (((channel & GreenChannel) != 0) && (white.green != black.green))
cristyce70c172010-01-07 17:15:30 +00001851 image->colormap[i].green=ClampToQuantum(equalize_map[
cristy3ed852e2009-09-05 21:47:34 +00001852 ScaleQuantumToMap(image->colormap[i].green)].green);
1853 if (((channel & BlueChannel) != 0) && (white.blue != black.blue))
cristyce70c172010-01-07 17:15:30 +00001854 image->colormap[i].blue=ClampToQuantum(equalize_map[
cristy3ed852e2009-09-05 21:47:34 +00001855 ScaleQuantumToMap(image->colormap[i].blue)].blue);
1856 if (((channel & OpacityChannel) != 0) &&
1857 (white.opacity != black.opacity))
cristyce70c172010-01-07 17:15:30 +00001858 image->colormap[i].opacity=ClampToQuantum(equalize_map[
cristy3ed852e2009-09-05 21:47:34 +00001859 ScaleQuantumToMap(image->colormap[i].opacity)].opacity);
1860 }
1861 }
1862 /*
1863 Equalize image.
1864 */
1865 status=MagickTrue;
1866 progress=0;
1867 exception=(&image->exception);
1868 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00001869#if defined(MAGICKCORE_OPENMP_SUPPORT)
1870 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001871#endif
cristybb503372010-05-27 20:51:26 +00001872 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001873 {
1874 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00001875 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00001876
cristy3ed852e2009-09-05 21:47:34 +00001877 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001878 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001879
cristy8d4629b2010-08-30 17:59:46 +00001880 register ssize_t
1881 x;
1882
cristy3ed852e2009-09-05 21:47:34 +00001883 if (status == MagickFalse)
1884 continue;
1885 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
1886 if (q == (PixelPacket *) NULL)
1887 {
1888 status=MagickFalse;
1889 continue;
1890 }
1891 indexes=GetCacheViewAuthenticIndexQueue(image_view);
cristybb503372010-05-27 20:51:26 +00001892 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001893 {
1894 if (((channel & RedChannel) != 0) && (white.red != black.red))
cristyce70c172010-01-07 17:15:30 +00001895 q->red=ClampToQuantum(equalize_map[ScaleQuantumToMap(q->red)].red);
cristy3ed852e2009-09-05 21:47:34 +00001896 if (((channel & GreenChannel) != 0) && (white.green != black.green))
cristyce70c172010-01-07 17:15:30 +00001897 q->green=ClampToQuantum(equalize_map[ScaleQuantumToMap(
cristy3ed852e2009-09-05 21:47:34 +00001898 q->green)].green);
1899 if (((channel & BlueChannel) != 0) && (white.blue != black.blue))
cristyce70c172010-01-07 17:15:30 +00001900 q->blue=ClampToQuantum(equalize_map[ScaleQuantumToMap(q->blue)].blue);
cristy3ed852e2009-09-05 21:47:34 +00001901 if (((channel & OpacityChannel) != 0) && (white.opacity != black.opacity))
cristyce70c172010-01-07 17:15:30 +00001902 q->opacity=ClampToQuantum(equalize_map[ScaleQuantumToMap(
cristy3ed852e2009-09-05 21:47:34 +00001903 q->opacity)].opacity);
1904 if ((((channel & IndexChannel) != 0) &&
1905 (image->colorspace == CMYKColorspace)) &&
1906 (white.index != black.index))
cristyce70c172010-01-07 17:15:30 +00001907 indexes[x]=ClampToQuantum(equalize_map[ScaleQuantumToMap(
cristy3ed852e2009-09-05 21:47:34 +00001908 indexes[x])].index);
1909 q++;
1910 }
1911 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
1912 status=MagickFalse;
1913 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1914 {
1915 MagickBooleanType
1916 proceed;
1917
cristyb5d5f722009-11-04 03:03:49 +00001918#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00001919 #pragma omp critical (MagickCore_EqualizeImageChannel)
1920#endif
1921 proceed=SetImageProgress(image,EqualizeImageTag,progress++,image->rows);
1922 if (proceed == MagickFalse)
1923 status=MagickFalse;
1924 }
1925 }
1926 image_view=DestroyCacheView(image_view);
1927 equalize_map=(MagickPixelPacket *) RelinquishMagickMemory(equalize_map);
1928 return(status);
1929}
1930
1931/*
1932%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1933% %
1934% %
1935% %
1936% G a m m a I m a g e %
1937% %
1938% %
1939% %
1940%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1941%
1942% GammaImage() gamma-corrects a particular image channel. The same
1943% image viewed on different devices will have perceptual differences in the
1944% way the image's intensities are represented on the screen. Specify
1945% individual gamma levels for the red, green, and blue channels, or adjust
1946% all three with the gamma parameter. Values typically range from 0.8 to 2.3.
1947%
1948% You can also reduce the influence of a particular channel with a gamma
1949% value of 0.
1950%
1951% The format of the GammaImage method is:
1952%
cristya6360142011-03-23 23:08:04 +00001953% MagickBooleanType GammaImage(Image *image,const char *level)
cristy3ed852e2009-09-05 21:47:34 +00001954% MagickBooleanType GammaImageChannel(Image *image,
1955% const ChannelType channel,const double gamma)
1956%
1957% A description of each parameter follows:
1958%
1959% o image: the image.
1960%
1961% o channel: the channel.
1962%
cristya6360142011-03-23 23:08:04 +00001963% o level: the image gamma as a string (e.g. 1.6,1.2,1.0).
1964%
cristy3ed852e2009-09-05 21:47:34 +00001965% o gamma: the image gamma.
1966%
1967*/
1968MagickExport MagickBooleanType GammaImage(Image *image,const char *level)
1969{
1970 GeometryInfo
1971 geometry_info;
1972
1973 MagickPixelPacket
1974 gamma;
1975
1976 MagickStatusType
1977 flags,
1978 status;
1979
1980 assert(image != (Image *) NULL);
1981 assert(image->signature == MagickSignature);
1982 if (image->debug != MagickFalse)
1983 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1984 if (level == (char *) NULL)
1985 return(MagickFalse);
1986 flags=ParseGeometry(level,&geometry_info);
1987 gamma.red=geometry_info.rho;
1988 gamma.green=geometry_info.sigma;
1989 if ((flags & SigmaValue) == 0)
1990 gamma.green=gamma.red;
1991 gamma.blue=geometry_info.xi;
1992 if ((flags & XiValue) == 0)
1993 gamma.blue=gamma.red;
1994 if ((gamma.red == 1.0) && (gamma.green == 1.0) && (gamma.blue == 1.0))
1995 return(MagickTrue);
1996 if ((gamma.red == gamma.green) && (gamma.green == gamma.blue))
1997 status=GammaImageChannel(image,(const ChannelType) (RedChannel |
1998 GreenChannel | BlueChannel),(double) gamma.red);
1999 else
2000 {
2001 status=GammaImageChannel(image,RedChannel,(double) gamma.red);
2002 status|=GammaImageChannel(image,GreenChannel,(double) gamma.green);
2003 status|=GammaImageChannel(image,BlueChannel,(double) gamma.blue);
2004 }
2005 return(status != 0 ? MagickTrue : MagickFalse);
2006}
2007
2008MagickExport MagickBooleanType GammaImageChannel(Image *image,
2009 const ChannelType channel,const double gamma)
2010{
2011#define GammaCorrectImageTag "GammaCorrect/Image"
2012
cristyc4c8d132010-01-07 01:58:38 +00002013 CacheView
2014 *image_view;
2015
cristy3ed852e2009-09-05 21:47:34 +00002016 ExceptionInfo
2017 *exception;
2018
cristy3ed852e2009-09-05 21:47:34 +00002019 MagickBooleanType
2020 status;
2021
cristybb503372010-05-27 20:51:26 +00002022 MagickOffsetType
2023 progress;
2024
cristy3ed852e2009-09-05 21:47:34 +00002025 Quantum
2026 *gamma_map;
2027
cristybb503372010-05-27 20:51:26 +00002028 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002029 i;
2030
cristybb503372010-05-27 20:51:26 +00002031 ssize_t
2032 y;
2033
cristy3ed852e2009-09-05 21:47:34 +00002034 /*
2035 Allocate and initialize gamma maps.
2036 */
2037 assert(image != (Image *) NULL);
2038 assert(image->signature == MagickSignature);
2039 if (image->debug != MagickFalse)
2040 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2041 if (gamma == 1.0)
2042 return(MagickTrue);
2043 gamma_map=(Quantum *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*gamma_map));
2044 if (gamma_map == (Quantum *) NULL)
2045 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
2046 image->filename);
2047 (void) ResetMagickMemory(gamma_map,0,(MaxMap+1)*sizeof(*gamma_map));
2048 if (gamma != 0.0)
cristyb5d5f722009-11-04 03:03:49 +00002049#if defined(MAGICKCORE_OPENMP_SUPPORT)
2050 #pragma omp parallel for schedule(dynamic,4)
cristy3ed852e2009-09-05 21:47:34 +00002051#endif
cristybb503372010-05-27 20:51:26 +00002052 for (i=0; i <= (ssize_t) MaxMap; i++)
cristyce70c172010-01-07 17:15:30 +00002053 gamma_map[i]=ClampToQuantum((MagickRealType) ScaleMapToQuantum((
cristy3ed852e2009-09-05 21:47:34 +00002054 MagickRealType) (MaxMap*pow((double) i/MaxMap,1.0/gamma))));
2055 if (image->storage_class == PseudoClass)
2056 {
2057 /*
2058 Gamma-correct colormap.
2059 */
cristyb5d5f722009-11-04 03:03:49 +00002060#if defined(MAGICKCORE_OPENMP_SUPPORT)
2061 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002062#endif
cristybb503372010-05-27 20:51:26 +00002063 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00002064 {
2065 if ((channel & RedChannel) != 0)
2066 image->colormap[i].red=gamma_map[
2067 ScaleQuantumToMap(image->colormap[i].red)];
2068 if ((channel & GreenChannel) != 0)
2069 image->colormap[i].green=gamma_map[
2070 ScaleQuantumToMap(image->colormap[i].green)];
2071 if ((channel & BlueChannel) != 0)
2072 image->colormap[i].blue=gamma_map[
2073 ScaleQuantumToMap(image->colormap[i].blue)];
2074 if ((channel & OpacityChannel) != 0)
2075 {
2076 if (image->matte == MagickFalse)
2077 image->colormap[i].opacity=gamma_map[
2078 ScaleQuantumToMap(image->colormap[i].opacity)];
2079 else
2080 image->colormap[i].opacity=(Quantum) QuantumRange-
2081 gamma_map[ScaleQuantumToMap((Quantum) (QuantumRange-
2082 image->colormap[i].opacity))];
2083 }
2084 }
2085 }
2086 /*
2087 Gamma-correct image.
2088 */
2089 status=MagickTrue;
2090 progress=0;
2091 exception=(&image->exception);
2092 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00002093#if defined(MAGICKCORE_OPENMP_SUPPORT)
2094 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002095#endif
cristybb503372010-05-27 20:51:26 +00002096 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002097 {
2098 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00002099 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00002100
cristy3ed852e2009-09-05 21:47:34 +00002101 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00002102 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002103
cristy8d4629b2010-08-30 17:59:46 +00002104 register ssize_t
2105 x;
2106
cristy3ed852e2009-09-05 21:47:34 +00002107 if (status == MagickFalse)
2108 continue;
2109 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2110 if (q == (PixelPacket *) NULL)
2111 {
2112 status=MagickFalse;
2113 continue;
2114 }
2115 indexes=GetCacheViewAuthenticIndexQueue(image_view);
cristybb503372010-05-27 20:51:26 +00002116 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002117 {
cristy6cbd7f52009-10-17 16:06:51 +00002118 if (channel == DefaultChannels)
cristy3ed852e2009-09-05 21:47:34 +00002119 {
cristy6cbd7f52009-10-17 16:06:51 +00002120 q->red=gamma_map[ScaleQuantumToMap(q->red)];
2121 q->green=gamma_map[ScaleQuantumToMap(q->green)];
2122 q->blue=gamma_map[ScaleQuantumToMap(q->blue)];
2123 }
2124 else
2125 {
2126 if ((channel & RedChannel) != 0)
2127 q->red=gamma_map[ScaleQuantumToMap(q->red)];
2128 if ((channel & GreenChannel) != 0)
2129 q->green=gamma_map[ScaleQuantumToMap(q->green)];
2130 if ((channel & BlueChannel) != 0)
2131 q->blue=gamma_map[ScaleQuantumToMap(q->blue)];
2132 if ((channel & OpacityChannel) != 0)
2133 {
2134 if (image->matte == MagickFalse)
2135 q->opacity=gamma_map[ScaleQuantumToMap(q->opacity)];
2136 else
2137 q->opacity=(Quantum) QuantumRange-gamma_map[
cristy46f08202010-01-10 04:04:21 +00002138 ScaleQuantumToMap((Quantum) GetAlphaPixelComponent(q))];
cristy6cbd7f52009-10-17 16:06:51 +00002139 }
cristy3ed852e2009-09-05 21:47:34 +00002140 }
2141 q++;
2142 }
2143 if (((channel & IndexChannel) != 0) &&
2144 (image->colorspace == CMYKColorspace))
cristybb503372010-05-27 20:51:26 +00002145 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002146 indexes[x]=gamma_map[ScaleQuantumToMap(indexes[x])];
2147 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2148 status=MagickFalse;
2149 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2150 {
2151 MagickBooleanType
2152 proceed;
2153
cristyb5d5f722009-11-04 03:03:49 +00002154#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00002155 #pragma omp critical (MagickCore_GammaImageChannel)
2156#endif
2157 proceed=SetImageProgress(image,GammaCorrectImageTag,progress++,
2158 image->rows);
2159 if (proceed == MagickFalse)
2160 status=MagickFalse;
2161 }
2162 }
2163 image_view=DestroyCacheView(image_view);
2164 gamma_map=(Quantum *) RelinquishMagickMemory(gamma_map);
2165 if (image->gamma != 0.0)
2166 image->gamma*=gamma;
2167 return(status);
2168}
2169
2170/*
2171%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2172% %
2173% %
2174% %
2175% H a l d C l u t I m a g e %
2176% %
2177% %
2178% %
2179%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2180%
2181% HaldClutImage() applies a Hald color lookup table to the image. A Hald
2182% color lookup table is a 3-dimensional color cube mapped to 2 dimensions.
2183% Create it with the HALD coder. You can apply any color transformation to
2184% the Hald image and then use this method to apply the transform to the
2185% image.
2186%
2187% The format of the HaldClutImage method is:
2188%
2189% MagickBooleanType HaldClutImage(Image *image,Image *hald_image)
2190% MagickBooleanType HaldClutImageChannel(Image *image,
2191% const ChannelType channel,Image *hald_image)
2192%
2193% A description of each parameter follows:
2194%
2195% o image: the image, which is replaced by indexed CLUT values
2196%
2197% o hald_image: the color lookup table image for replacement color values.
2198%
2199% o channel: the channel.
2200%
2201*/
2202
2203static inline size_t MagickMin(const size_t x,const size_t y)
2204{
2205 if (x < y)
2206 return(x);
2207 return(y);
2208}
2209
2210MagickExport MagickBooleanType HaldClutImage(Image *image,
2211 const Image *hald_image)
2212{
2213 return(HaldClutImageChannel(image,DefaultChannels,hald_image));
2214}
2215
2216MagickExport MagickBooleanType HaldClutImageChannel(Image *image,
2217 const ChannelType channel,const Image *hald_image)
2218{
2219#define HaldClutImageTag "Clut/Image"
2220
2221 typedef struct _HaldInfo
2222 {
2223 MagickRealType
2224 x,
2225 y,
2226 z;
2227 } HaldInfo;
2228
cristyfa112112010-01-04 17:48:07 +00002229 CacheView
2230 *image_view;
2231
cristy3ed852e2009-09-05 21:47:34 +00002232 double
2233 width;
2234
2235 ExceptionInfo
2236 *exception;
2237
cristy3ed852e2009-09-05 21:47:34 +00002238 MagickBooleanType
2239 status;
2240
cristybb503372010-05-27 20:51:26 +00002241 MagickOffsetType
2242 progress;
2243
cristy3ed852e2009-09-05 21:47:34 +00002244 MagickPixelPacket
2245 zero;
2246
2247 ResampleFilter
cristyfa112112010-01-04 17:48:07 +00002248 **restrict resample_filter;
cristy3ed852e2009-09-05 21:47:34 +00002249
2250 size_t
2251 cube_size,
2252 length,
2253 level;
2254
cristybb503372010-05-27 20:51:26 +00002255 ssize_t
2256 y;
2257
cristy3ed852e2009-09-05 21:47:34 +00002258 assert(image != (Image *) NULL);
2259 assert(image->signature == MagickSignature);
2260 if (image->debug != MagickFalse)
2261 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2262 assert(hald_image != (Image *) NULL);
2263 assert(hald_image->signature == MagickSignature);
2264 if (SetImageStorageClass(image,DirectClass) == MagickFalse)
2265 return(MagickFalse);
2266 if (image->matte == MagickFalse)
2267 (void) SetImageAlphaChannel(image,OpaqueAlphaChannel);
2268 /*
2269 Hald clut image.
2270 */
2271 status=MagickTrue;
2272 progress=0;
2273 length=MagickMin(hald_image->columns,hald_image->rows);
2274 for (level=2; (level*level*level) < length; level++) ;
2275 level*=level;
2276 cube_size=level*level;
2277 width=(double) hald_image->columns;
2278 GetMagickPixelPacket(hald_image,&zero);
2279 exception=(&image->exception);
cristyb2a11ae2010-02-22 00:53:36 +00002280 resample_filter=AcquireResampleFilterThreadSet(hald_image,
2281 UndefinedVirtualPixelMethod,MagickTrue,exception);
cristy3ed852e2009-09-05 21:47:34 +00002282 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00002283#if defined(MAGICKCORE_OPENMP_SUPPORT)
2284 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002285#endif
cristybb503372010-05-27 20:51:26 +00002286 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002287 {
cristy5c9e6f22010-09-17 17:31:01 +00002288 const int
2289 id = GetOpenMPThreadId();
2290
cristy3ed852e2009-09-05 21:47:34 +00002291 double
2292 offset;
2293
2294 HaldInfo
2295 point;
2296
2297 MagickPixelPacket
2298 pixel,
2299 pixel1,
2300 pixel2,
2301 pixel3,
2302 pixel4;
2303
2304 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00002305 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00002306
cristy3ed852e2009-09-05 21:47:34 +00002307 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00002308 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002309
cristy8d4629b2010-08-30 17:59:46 +00002310 register ssize_t
2311 x;
2312
cristy3ed852e2009-09-05 21:47:34 +00002313 if (status == MagickFalse)
2314 continue;
2315 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2316 if (q == (PixelPacket *) NULL)
2317 {
2318 status=MagickFalse;
2319 continue;
2320 }
2321 indexes=GetCacheViewAuthenticIndexQueue(image_view);
2322 pixel=zero;
2323 pixel1=zero;
2324 pixel2=zero;
2325 pixel3=zero;
2326 pixel4=zero;
cristybb503372010-05-27 20:51:26 +00002327 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002328 {
2329 point.x=QuantumScale*(level-1.0)*q->red;
2330 point.y=QuantumScale*(level-1.0)*q->green;
2331 point.z=QuantumScale*(level-1.0)*q->blue;
2332 offset=point.x+level*floor(point.y)+cube_size*floor(point.z);
2333 point.x-=floor(point.x);
2334 point.y-=floor(point.y);
2335 point.z-=floor(point.z);
2336 (void) ResamplePixelColor(resample_filter[id],fmod(offset,width),
2337 floor(offset/width),&pixel1);
2338 (void) ResamplePixelColor(resample_filter[id],fmod(offset+level,width),
2339 floor((offset+level)/width),&pixel2);
2340 MagickPixelCompositeAreaBlend(&pixel1,pixel1.opacity,&pixel2,
2341 pixel2.opacity,point.y,&pixel3);
2342 offset+=cube_size;
2343 (void) ResamplePixelColor(resample_filter[id],fmod(offset,width),
2344 floor(offset/width),&pixel1);
2345 (void) ResamplePixelColor(resample_filter[id],fmod(offset+level,width),
2346 floor((offset+level)/width),&pixel2);
2347 MagickPixelCompositeAreaBlend(&pixel1,pixel1.opacity,&pixel2,
2348 pixel2.opacity,point.y,&pixel4);
2349 MagickPixelCompositeAreaBlend(&pixel3,pixel3.opacity,&pixel4,
2350 pixel4.opacity,point.z,&pixel);
2351 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00002352 SetRedPixelComponent(q,ClampRedPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00002353 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00002354 SetGreenPixelComponent(q,ClampGreenPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00002355 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00002356 SetBluePixelComponent(q,ClampBluePixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00002357 if (((channel & OpacityChannel) != 0) && (image->matte != MagickFalse))
cristyce70c172010-01-07 17:15:30 +00002358 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00002359 if (((channel & IndexChannel) != 0) &&
2360 (image->colorspace == CMYKColorspace))
cristyce70c172010-01-07 17:15:30 +00002361 indexes[x]=ClampToQuantum(pixel.index);
cristy3ed852e2009-09-05 21:47:34 +00002362 q++;
2363 }
2364 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2365 status=MagickFalse;
2366 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2367 {
2368 MagickBooleanType
2369 proceed;
2370
cristyb5d5f722009-11-04 03:03:49 +00002371#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00002372 #pragma omp critical (MagickCore_HaldClutImageChannel)
2373#endif
2374 proceed=SetImageProgress(image,HaldClutImageTag,progress++,image->rows);
2375 if (proceed == MagickFalse)
2376 status=MagickFalse;
2377 }
2378 }
2379 image_view=DestroyCacheView(image_view);
2380 resample_filter=DestroyResampleFilterThreadSet(resample_filter);
2381 return(status);
2382}
2383
2384/*
2385%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2386% %
2387% %
2388% %
2389% L e v e l I m a g e %
2390% %
2391% %
2392% %
2393%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2394%
2395% LevelImage() adjusts the levels of a particular image channel by
2396% scaling the colors falling between specified white and black points to
2397% the full available quantum range.
2398%
2399% The parameters provided represent the black, and white points. The black
2400% point specifies the darkest color in the image. Colors darker than the
2401% black point are set to zero. White point specifies the lightest color in
2402% the image. Colors brighter than the white point are set to the maximum
2403% quantum value.
2404%
2405% If a '!' flag is given, map black and white colors to the given levels
2406% rather than mapping those levels to black and white. See
2407% LevelizeImageChannel() and LevelizeImageChannel(), below.
2408%
2409% Gamma specifies a gamma correction to apply to the image.
2410%
2411% The format of the LevelImage method is:
2412%
2413% MagickBooleanType LevelImage(Image *image,const char *levels)
2414%
2415% A description of each parameter follows:
2416%
2417% o image: the image.
2418%
2419% o levels: Specify the levels where the black and white points have the
2420% range of 0-QuantumRange, and gamma has the range 0-10 (e.g. 10x90%+2).
2421% A '!' flag inverts the re-mapping.
2422%
2423*/
2424
2425MagickExport MagickBooleanType LevelImage(Image *image,const char *levels)
2426{
2427 double
2428 black_point,
2429 gamma,
2430 white_point;
2431
2432 GeometryInfo
2433 geometry_info;
2434
2435 MagickBooleanType
2436 status;
2437
2438 MagickStatusType
2439 flags;
2440
2441 /*
2442 Parse levels.
2443 */
2444 if (levels == (char *) NULL)
2445 return(MagickFalse);
2446 flags=ParseGeometry(levels,&geometry_info);
2447 black_point=geometry_info.rho;
2448 white_point=(double) QuantumRange;
2449 if ((flags & SigmaValue) != 0)
2450 white_point=geometry_info.sigma;
2451 gamma=1.0;
2452 if ((flags & XiValue) != 0)
2453 gamma=geometry_info.xi;
2454 if ((flags & PercentValue) != 0)
2455 {
2456 black_point*=(double) image->columns*image->rows/100.0;
2457 white_point*=(double) image->columns*image->rows/100.0;
2458 }
2459 if ((flags & SigmaValue) == 0)
2460 white_point=(double) QuantumRange-black_point;
2461 if ((flags & AspectValue ) == 0)
2462 status=LevelImageChannel(image,DefaultChannels,black_point,white_point,
2463 gamma);
2464 else
cristy308b4e62009-09-21 14:40:44 +00002465 status=LevelizeImage(image,black_point,white_point,gamma);
cristy3ed852e2009-09-05 21:47:34 +00002466 return(status);
2467}
2468
2469/*
2470%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2471% %
2472% %
2473% %
cristy308b4e62009-09-21 14:40:44 +00002474% L e v e l i z e I m a g e %
cristy3ed852e2009-09-05 21:47:34 +00002475% %
2476% %
2477% %
2478%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2479%
cristy308b4e62009-09-21 14:40:44 +00002480% LevelizeImage() applies the normal level operation to the image, spreading
2481% out the values between the black and white points over the entire range of
2482% values. Gamma correction is also applied after the values has been mapped.
cristy3ed852e2009-09-05 21:47:34 +00002483%
2484% It is typically used to improve image contrast, or to provide a controlled
2485% linear threshold for the image. If the black and white points are set to
2486% the minimum and maximum values found in the image, the image can be
2487% normalized. or by swapping black and white values, negate the image.
2488%
cristy308b4e62009-09-21 14:40:44 +00002489% The format of the LevelizeImage method is:
cristy3ed852e2009-09-05 21:47:34 +00002490%
cristy308b4e62009-09-21 14:40:44 +00002491% MagickBooleanType LevelizeImage(Image *image,const double black_point,
2492% const double white_point,const double gamma)
2493% MagickBooleanType LevelizeImageChannel(Image *image,
2494% const ChannelType channel,const double black_point,
2495% const double white_point,const double gamma)
cristy3ed852e2009-09-05 21:47:34 +00002496%
2497% A description of each parameter follows:
2498%
2499% o image: the image.
2500%
2501% o channel: the channel.
2502%
2503% o black_point: The level which is to be mapped to zero (black)
2504%
2505% o white_point: The level which is to be mapped to QuantiumRange (white)
2506%
2507% o gamma: adjust gamma by this factor before mapping values.
2508% use 1.0 for purely linear stretching of image color values
2509%
2510*/
2511MagickExport MagickBooleanType LevelImageChannel(Image *image,
2512 const ChannelType channel,const double black_point,const double white_point,
2513 const double gamma)
2514{
2515#define LevelImageTag "Level/Image"
cristybcfb0432010-05-06 01:45:33 +00002516#define LevelQuantum(x) (ClampToQuantum((MagickRealType) QuantumRange* \
cristyc1f508d2010-04-22 01:21:47 +00002517 pow(scale*((double) (x)-black_point),1.0/gamma)))
cristy3ed852e2009-09-05 21:47:34 +00002518
cristyc4c8d132010-01-07 01:58:38 +00002519 CacheView
2520 *image_view;
2521
cristy3ed852e2009-09-05 21:47:34 +00002522 ExceptionInfo
2523 *exception;
2524
cristy3ed852e2009-09-05 21:47:34 +00002525 MagickBooleanType
2526 status;
2527
cristybb503372010-05-27 20:51:26 +00002528 MagickOffsetType
2529 progress;
2530
anthony7fe39fc2010-04-06 03:19:20 +00002531 register double
2532 scale;
2533
cristy8d4629b2010-08-30 17:59:46 +00002534 register ssize_t
2535 i;
2536
cristybb503372010-05-27 20:51:26 +00002537 ssize_t
2538 y;
2539
cristy3ed852e2009-09-05 21:47:34 +00002540 /*
2541 Allocate and initialize levels map.
2542 */
2543 assert(image != (Image *) NULL);
2544 assert(image->signature == MagickSignature);
2545 if (image->debug != MagickFalse)
2546 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
cristy8d4629b2010-08-30 17:59:46 +00002547 scale=(white_point != black_point) ? 1.0/(white_point-black_point) : 1.0;
cristy3ed852e2009-09-05 21:47:34 +00002548 if (image->storage_class == PseudoClass)
cristyb5d5f722009-11-04 03:03:49 +00002549#if defined(MAGICKCORE_OPENMP_SUPPORT)
2550 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002551#endif
cristybb503372010-05-27 20:51:26 +00002552 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00002553 {
2554 /*
2555 Level colormap.
2556 */
2557 if ((channel & RedChannel) != 0)
cristyc1f508d2010-04-22 01:21:47 +00002558 image->colormap[i].red=LevelQuantum(image->colormap[i].red);
cristy3ed852e2009-09-05 21:47:34 +00002559 if ((channel & GreenChannel) != 0)
cristyc1f508d2010-04-22 01:21:47 +00002560 image->colormap[i].green=LevelQuantum(image->colormap[i].green);
cristy3ed852e2009-09-05 21:47:34 +00002561 if ((channel & BlueChannel) != 0)
cristyc1f508d2010-04-22 01:21:47 +00002562 image->colormap[i].blue=LevelQuantum(image->colormap[i].blue);
cristy3ed852e2009-09-05 21:47:34 +00002563 if ((channel & OpacityChannel) != 0)
cristyc1f508d2010-04-22 01:21:47 +00002564 image->colormap[i].opacity=LevelQuantum(image->colormap[i].opacity);
cristy3ed852e2009-09-05 21:47:34 +00002565 }
2566 /*
2567 Level image.
2568 */
2569 status=MagickTrue;
2570 progress=0;
2571 exception=(&image->exception);
2572 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00002573#if defined(MAGICKCORE_OPENMP_SUPPORT)
2574 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002575#endif
cristybb503372010-05-27 20:51:26 +00002576 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002577 {
2578 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00002579 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00002580
cristy3ed852e2009-09-05 21:47:34 +00002581 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00002582 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002583
cristy8d4629b2010-08-30 17:59:46 +00002584 register ssize_t
2585 x;
2586
cristy3ed852e2009-09-05 21:47:34 +00002587 if (status == MagickFalse)
2588 continue;
2589 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2590 if (q == (PixelPacket *) NULL)
2591 {
2592 status=MagickFalse;
2593 continue;
2594 }
2595 indexes=GetCacheViewAuthenticIndexQueue(image_view);
cristybb503372010-05-27 20:51:26 +00002596 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002597 {
2598 if ((channel & RedChannel) != 0)
cristyc1f508d2010-04-22 01:21:47 +00002599 q->red=LevelQuantum(q->red);
cristy3ed852e2009-09-05 21:47:34 +00002600 if ((channel & GreenChannel) != 0)
cristyc1f508d2010-04-22 01:21:47 +00002601 q->green=LevelQuantum(q->green);
cristy3ed852e2009-09-05 21:47:34 +00002602 if ((channel & BlueChannel) != 0)
cristyc1f508d2010-04-22 01:21:47 +00002603 q->blue=LevelQuantum(q->blue);
cristy3ed852e2009-09-05 21:47:34 +00002604 if (((channel & OpacityChannel) != 0) &&
2605 (image->matte == MagickTrue))
cristybb503372010-05-27 20:51:26 +00002606 q->opacity=(Quantum) (QuantumRange-LevelQuantum(QuantumRange-
2607 q->opacity));
cristy3ed852e2009-09-05 21:47:34 +00002608 if (((channel & IndexChannel) != 0) &&
2609 (image->colorspace == CMYKColorspace))
cristyc1f508d2010-04-22 01:21:47 +00002610 indexes[x]=LevelQuantum(indexes[x]);
cristy3ed852e2009-09-05 21:47:34 +00002611 q++;
2612 }
2613 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2614 status=MagickFalse;
2615 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2616 {
2617 MagickBooleanType
2618 proceed;
2619
cristyb5d5f722009-11-04 03:03:49 +00002620#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00002621 #pragma omp critical (MagickCore_LevelImageChannel)
2622#endif
2623 proceed=SetImageProgress(image,LevelImageTag,progress++,image->rows);
2624 if (proceed == MagickFalse)
2625 status=MagickFalse;
2626 }
2627 }
2628 image_view=DestroyCacheView(image_view);
2629 return(status);
2630}
2631
2632/*
2633%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2634% %
2635% %
2636% %
2637% L e v e l i z e I m a g e C h a n n e l %
2638% %
2639% %
2640% %
2641%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2642%
2643% LevelizeImageChannel() applies the reversed LevelImage() operation to just
2644% the specific channels specified. It compresses the full range of color
2645% values, so that they lie between the given black and white points. Gamma is
2646% applied before the values are mapped.
2647%
2648% LevelizeImageChannel() can be called with by using a +level command line
2649% API option, or using a '!' on a -level or LevelImage() geometry string.
2650%
2651% It can be used for example de-contrast a greyscale image to the exact
2652% levels specified. Or by using specific levels for each channel of an image
2653% you can convert a gray-scale image to any linear color gradient, according
2654% to those levels.
2655%
2656% The format of the LevelizeImageChannel method is:
2657%
2658% MagickBooleanType LevelizeImageChannel(Image *image,
2659% const ChannelType channel,const char *levels)
2660%
2661% A description of each parameter follows:
2662%
2663% o image: the image.
2664%
2665% o channel: the channel.
2666%
2667% o black_point: The level to map zero (black) to.
2668%
2669% o white_point: The level to map QuantiumRange (white) to.
2670%
2671% o gamma: adjust gamma by this factor before mapping values.
2672%
2673*/
cristyd1a2c0f2011-02-09 14:14:50 +00002674
2675MagickExport MagickBooleanType LevelizeImage(Image *image,
2676 const double black_point,const double white_point,const double gamma)
2677{
2678 MagickBooleanType
2679 status;
2680
2681 status=LevelizeImageChannel(image,DefaultChannels,black_point,white_point,
2682 gamma);
2683 return(status);
2684}
2685
cristy3ed852e2009-09-05 21:47:34 +00002686MagickExport MagickBooleanType LevelizeImageChannel(Image *image,
2687 const ChannelType channel,const double black_point,const double white_point,
2688 const double gamma)
2689{
2690#define LevelizeImageTag "Levelize/Image"
cristyce70c172010-01-07 17:15:30 +00002691#define LevelizeValue(x) (ClampToQuantum(((MagickRealType) \
cristy3ed852e2009-09-05 21:47:34 +00002692 pow((double)(QuantumScale*(x)),1.0/gamma))*(white_point-black_point)+ \
2693 black_point))
2694
cristyc4c8d132010-01-07 01:58:38 +00002695 CacheView
2696 *image_view;
2697
cristy3ed852e2009-09-05 21:47:34 +00002698 ExceptionInfo
2699 *exception;
2700
cristy3ed852e2009-09-05 21:47:34 +00002701 MagickBooleanType
2702 status;
2703
cristybb503372010-05-27 20:51:26 +00002704 MagickOffsetType
2705 progress;
2706
2707 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002708 i;
2709
cristybb503372010-05-27 20:51:26 +00002710 ssize_t
2711 y;
2712
cristy3ed852e2009-09-05 21:47:34 +00002713 /*
2714 Allocate and initialize levels map.
2715 */
2716 assert(image != (Image *) NULL);
2717 assert(image->signature == MagickSignature);
2718 if (image->debug != MagickFalse)
2719 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2720 if (image->storage_class == PseudoClass)
cristyb5d5f722009-11-04 03:03:49 +00002721#if defined(MAGICKCORE_OPENMP_SUPPORT)
2722 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002723#endif
cristybb503372010-05-27 20:51:26 +00002724 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00002725 {
2726 /*
2727 Level colormap.
2728 */
2729 if ((channel & RedChannel) != 0)
2730 image->colormap[i].red=LevelizeValue(image->colormap[i].red);
2731 if ((channel & GreenChannel) != 0)
2732 image->colormap[i].green=LevelizeValue(image->colormap[i].green);
2733 if ((channel & BlueChannel) != 0)
2734 image->colormap[i].blue=LevelizeValue(image->colormap[i].blue);
2735 if ((channel & OpacityChannel) != 0)
2736 image->colormap[i].opacity=LevelizeValue(image->colormap[i].opacity);
2737 }
2738 /*
2739 Level image.
2740 */
2741 status=MagickTrue;
2742 progress=0;
2743 exception=(&image->exception);
2744 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00002745#if defined(MAGICKCORE_OPENMP_SUPPORT)
2746 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002747#endif
cristybb503372010-05-27 20:51:26 +00002748 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002749 {
2750 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00002751 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00002752
cristy3ed852e2009-09-05 21:47:34 +00002753 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00002754 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002755
cristy8d4629b2010-08-30 17:59:46 +00002756 register ssize_t
2757 x;
2758
cristy3ed852e2009-09-05 21:47:34 +00002759 if (status == MagickFalse)
2760 continue;
2761 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2762 if (q == (PixelPacket *) NULL)
2763 {
2764 status=MagickFalse;
2765 continue;
2766 }
2767 indexes=GetCacheViewAuthenticIndexQueue(image_view);
cristybb503372010-05-27 20:51:26 +00002768 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002769 {
2770 if ((channel & RedChannel) != 0)
2771 q->red=LevelizeValue(q->red);
2772 if ((channel & GreenChannel) != 0)
2773 q->green=LevelizeValue(q->green);
2774 if ((channel & BlueChannel) != 0)
2775 q->blue=LevelizeValue(q->blue);
2776 if (((channel & OpacityChannel) != 0) &&
2777 (image->matte == MagickTrue))
2778 q->opacity=LevelizeValue(q->opacity);
2779 if (((channel & IndexChannel) != 0) &&
2780 (image->colorspace == CMYKColorspace))
2781 indexes[x]=LevelizeValue(indexes[x]);
2782 q++;
2783 }
2784 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2785 status=MagickFalse;
2786 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2787 {
2788 MagickBooleanType
2789 proceed;
2790
cristyb5d5f722009-11-04 03:03:49 +00002791#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00002792 #pragma omp critical (MagickCore_LevelizeImageChannel)
2793#endif
2794 proceed=SetImageProgress(image,LevelizeImageTag,progress++,image->rows);
2795 if (proceed == MagickFalse)
2796 status=MagickFalse;
2797 }
2798 }
cristy8d4629b2010-08-30 17:59:46 +00002799 image_view=DestroyCacheView(image_view);
cristy3ed852e2009-09-05 21:47:34 +00002800 return(status);
2801}
2802
2803/*
2804%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2805% %
2806% %
2807% %
2808% L e v e l I m a g e C o l o r s %
2809% %
2810% %
2811% %
2812%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2813%
cristyee0f8d72009-09-19 00:58:29 +00002814% LevelImageColor() maps the given color to "black" and "white" values,
2815% linearly spreading out the colors, and level values on a channel by channel
2816% bases, as per LevelImage(). The given colors allows you to specify
glennrp1e7f7bc2011-03-02 19:25:28 +00002817% different level ranges for each of the color channels separately.
cristy3ed852e2009-09-05 21:47:34 +00002818%
2819% If the boolean 'invert' is set true the image values will modifyed in the
2820% reverse direction. That is any existing "black" and "white" colors in the
2821% image will become the color values given, with all other values compressed
2822% appropriatally. This effectivally maps a greyscale gradient into the given
2823% color gradient.
2824%
cristy308b4e62009-09-21 14:40:44 +00002825% The format of the LevelColorsImageChannel method is:
cristy3ed852e2009-09-05 21:47:34 +00002826%
cristy308b4e62009-09-21 14:40:44 +00002827% MagickBooleanType LevelColorsImage(Image *image,
cristyee0f8d72009-09-19 00:58:29 +00002828% const MagickPixelPacket *black_color,
2829% const MagickPixelPacket *white_color,const MagickBooleanType invert)
cristy308b4e62009-09-21 14:40:44 +00002830% MagickBooleanType LevelColorsImageChannel(Image *image,
2831% const ChannelType channel,const MagickPixelPacket *black_color,
2832% const MagickPixelPacket *white_color,const MagickBooleanType invert)
cristy3ed852e2009-09-05 21:47:34 +00002833%
2834% A description of each parameter follows:
2835%
2836% o image: the image.
2837%
2838% o channel: the channel.
2839%
2840% o black_color: The color to map black to/from
2841%
2842% o white_point: The color to map white to/from
2843%
2844% o invert: if true map the colors (levelize), rather than from (level)
2845%
2846*/
cristy308b4e62009-09-21 14:40:44 +00002847
2848MagickExport MagickBooleanType LevelColorsImage(Image *image,
cristy3ed852e2009-09-05 21:47:34 +00002849 const MagickPixelPacket *black_color,const MagickPixelPacket *white_color,
2850 const MagickBooleanType invert)
2851{
cristy308b4e62009-09-21 14:40:44 +00002852 MagickBooleanType
2853 status;
cristy3ed852e2009-09-05 21:47:34 +00002854
cristy308b4e62009-09-21 14:40:44 +00002855 status=LevelColorsImageChannel(image,DefaultChannels,black_color,white_color,
2856 invert);
2857 return(status);
2858}
2859
2860MagickExport MagickBooleanType LevelColorsImageChannel(Image *image,
2861 const ChannelType channel,const MagickPixelPacket *black_color,
2862 const MagickPixelPacket *white_color,const MagickBooleanType invert)
2863{
cristy3ed852e2009-09-05 21:47:34 +00002864 MagickStatusType
2865 status;
2866
2867 /*
2868 Allocate and initialize levels map.
2869 */
2870 assert(image != (Image *) NULL);
2871 assert(image->signature == MagickSignature);
2872 if (image->debug != MagickFalse)
2873 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2874 status=MagickFalse;
2875 if (invert == MagickFalse)
2876 {
2877 if ((channel & RedChannel) != 0)
2878 status|=LevelImageChannel(image,RedChannel,
2879 black_color->red,white_color->red,(double) 1.0);
2880 if ((channel & GreenChannel) != 0)
2881 status|=LevelImageChannel(image,GreenChannel,
2882 black_color->green,white_color->green,(double) 1.0);
2883 if ((channel & BlueChannel) != 0)
2884 status|=LevelImageChannel(image,BlueChannel,
2885 black_color->blue,white_color->blue,(double) 1.0);
2886 if (((channel & OpacityChannel) != 0) &&
2887 (image->matte == MagickTrue))
2888 status|=LevelImageChannel(image,OpacityChannel,
2889 black_color->opacity,white_color->opacity,(double) 1.0);
2890 if (((channel & IndexChannel) != 0) &&
2891 (image->colorspace == CMYKColorspace))
2892 status|=LevelImageChannel(image,IndexChannel,
2893 black_color->index,white_color->index,(double) 1.0);
2894 }
2895 else
2896 {
2897 if ((channel & RedChannel) != 0)
2898 status|=LevelizeImageChannel(image,RedChannel,
2899 black_color->red,white_color->red,(double) 1.0);
2900 if ((channel & GreenChannel) != 0)
2901 status|=LevelizeImageChannel(image,GreenChannel,
2902 black_color->green,white_color->green,(double) 1.0);
2903 if ((channel & BlueChannel) != 0)
2904 status|=LevelizeImageChannel(image,BlueChannel,
2905 black_color->blue,white_color->blue,(double) 1.0);
2906 if (((channel & OpacityChannel) != 0) &&
2907 (image->matte == MagickTrue))
2908 status|=LevelizeImageChannel(image,OpacityChannel,
2909 black_color->opacity,white_color->opacity,(double) 1.0);
2910 if (((channel & IndexChannel) != 0) &&
2911 (image->colorspace == CMYKColorspace))
2912 status|=LevelizeImageChannel(image,IndexChannel,
2913 black_color->index,white_color->index,(double) 1.0);
2914 }
2915 return(status == 0 ? MagickFalse : MagickTrue);
2916}
2917
2918/*
2919%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2920% %
2921% %
2922% %
2923% L i n e a r S t r e t c h I m a g e %
2924% %
2925% %
2926% %
2927%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2928%
2929% The LinearStretchImage() discards any pixels below the black point and
2930% above the white point and levels the remaining pixels.
2931%
2932% The format of the LinearStretchImage method is:
2933%
2934% MagickBooleanType LinearStretchImage(Image *image,
2935% const double black_point,const double white_point)
2936%
2937% A description of each parameter follows:
2938%
2939% o image: the image.
2940%
2941% o black_point: the black point.
2942%
2943% o white_point: the white point.
2944%
2945*/
2946MagickExport MagickBooleanType LinearStretchImage(Image *image,
2947 const double black_point,const double white_point)
2948{
2949#define LinearStretchImageTag "LinearStretch/Image"
2950
2951 ExceptionInfo
2952 *exception;
2953
cristy3ed852e2009-09-05 21:47:34 +00002954 MagickBooleanType
2955 status;
2956
2957 MagickRealType
2958 *histogram,
2959 intensity;
2960
cristy8d4629b2010-08-30 17:59:46 +00002961 ssize_t
2962 black,
2963 white,
2964 y;
2965
cristy3ed852e2009-09-05 21:47:34 +00002966 /*
2967 Allocate histogram and linear map.
2968 */
2969 assert(image != (Image *) NULL);
2970 assert(image->signature == MagickSignature);
2971 histogram=(MagickRealType *) AcquireQuantumMemory(MaxMap+1UL,
2972 sizeof(*histogram));
2973 if (histogram == (MagickRealType *) NULL)
2974 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
2975 image->filename);
2976 /*
2977 Form histogram.
2978 */
2979 (void) ResetMagickMemory(histogram,0,(MaxMap+1)*sizeof(*histogram));
2980 exception=(&image->exception);
cristybb503372010-05-27 20:51:26 +00002981 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002982 {
2983 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00002984 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00002985
cristybb503372010-05-27 20:51:26 +00002986 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002987 x;
2988
2989 p=GetVirtualPixels(image,0,y,image->columns,1,exception);
2990 if (p == (const PixelPacket *) NULL)
2991 break;
cristybb503372010-05-27 20:51:26 +00002992 for (x=(ssize_t) image->columns-1; x >= 0; x--)
cristy3ed852e2009-09-05 21:47:34 +00002993 {
2994 histogram[ScaleQuantumToMap(PixelIntensityToQuantum(p))]++;
2995 p++;
2996 }
2997 }
2998 /*
2999 Find the histogram boundaries by locating the black and white point levels.
3000 */
cristy3ed852e2009-09-05 21:47:34 +00003001 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00003002 for (black=0; black < (ssize_t) MaxMap; black++)
cristy3ed852e2009-09-05 21:47:34 +00003003 {
3004 intensity+=histogram[black];
3005 if (intensity >= black_point)
3006 break;
3007 }
3008 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00003009 for (white=(ssize_t) MaxMap; white != 0; white--)
cristy3ed852e2009-09-05 21:47:34 +00003010 {
3011 intensity+=histogram[white];
3012 if (intensity >= white_point)
3013 break;
3014 }
3015 histogram=(MagickRealType *) RelinquishMagickMemory(histogram);
3016 status=LevelImageChannel(image,DefaultChannels,(double) black,(double) white,
3017 1.0);
3018 return(status);
3019}
3020
3021/*
3022%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3023% %
3024% %
3025% %
3026% M o d u l a t e I m a g e %
3027% %
3028% %
3029% %
3030%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3031%
3032% ModulateImage() lets you control the brightness, saturation, and hue
3033% of an image. Modulate represents the brightness, saturation, and hue
3034% as one parameter (e.g. 90,150,100). If the image colorspace is HSL, the
3035% modulation is lightness, saturation, and hue. And if the colorspace is
3036% HWB, use blackness, whiteness, and hue.
3037%
3038% The format of the ModulateImage method is:
3039%
3040% MagickBooleanType ModulateImage(Image *image,const char *modulate)
3041%
3042% A description of each parameter follows:
3043%
3044% o image: the image.
3045%
3046% o modulate: Define the percent change in brightness, saturation, and
3047% hue.
3048%
3049*/
3050
3051static void ModulateHSB(const double percent_hue,
3052 const double percent_saturation,const double percent_brightness,
3053 Quantum *red,Quantum *green,Quantum *blue)
3054{
3055 double
3056 brightness,
3057 hue,
3058 saturation;
3059
3060 /*
3061 Increase or decrease color brightness, saturation, or hue.
3062 */
3063 assert(red != (Quantum *) NULL);
3064 assert(green != (Quantum *) NULL);
3065 assert(blue != (Quantum *) NULL);
3066 ConvertRGBToHSB(*red,*green,*blue,&hue,&saturation,&brightness);
3067 hue+=0.5*(0.01*percent_hue-1.0);
3068 while (hue < 0.0)
3069 hue+=1.0;
3070 while (hue > 1.0)
3071 hue-=1.0;
3072 saturation*=0.01*percent_saturation;
3073 brightness*=0.01*percent_brightness;
3074 ConvertHSBToRGB(hue,saturation,brightness,red,green,blue);
3075}
3076
3077static void ModulateHSL(const double percent_hue,
3078 const double percent_saturation,const double percent_lightness,
3079 Quantum *red,Quantum *green,Quantum *blue)
3080{
3081 double
3082 hue,
3083 lightness,
3084 saturation;
3085
3086 /*
3087 Increase or decrease color lightness, saturation, or hue.
3088 */
3089 assert(red != (Quantum *) NULL);
3090 assert(green != (Quantum *) NULL);
3091 assert(blue != (Quantum *) NULL);
3092 ConvertRGBToHSL(*red,*green,*blue,&hue,&saturation,&lightness);
3093 hue+=0.5*(0.01*percent_hue-1.0);
3094 while (hue < 0.0)
3095 hue+=1.0;
3096 while (hue > 1.0)
3097 hue-=1.0;
3098 saturation*=0.01*percent_saturation;
3099 lightness*=0.01*percent_lightness;
3100 ConvertHSLToRGB(hue,saturation,lightness,red,green,blue);
3101}
3102
3103static void ModulateHWB(const double percent_hue,const double percent_whiteness, const double percent_blackness,Quantum *red,Quantum *green,Quantum *blue)
3104{
3105 double
3106 blackness,
3107 hue,
3108 whiteness;
3109
3110 /*
3111 Increase or decrease color blackness, whiteness, or hue.
3112 */
3113 assert(red != (Quantum *) NULL);
3114 assert(green != (Quantum *) NULL);
3115 assert(blue != (Quantum *) NULL);
3116 ConvertRGBToHWB(*red,*green,*blue,&hue,&whiteness,&blackness);
3117 hue+=0.5*(0.01*percent_hue-1.0);
3118 while (hue < 0.0)
3119 hue+=1.0;
3120 while (hue > 1.0)
3121 hue-=1.0;
3122 blackness*=0.01*percent_blackness;
3123 whiteness*=0.01*percent_whiteness;
3124 ConvertHWBToRGB(hue,whiteness,blackness,red,green,blue);
3125}
3126
3127MagickExport MagickBooleanType ModulateImage(Image *image,const char *modulate)
3128{
3129#define ModulateImageTag "Modulate/Image"
3130
cristyc4c8d132010-01-07 01:58:38 +00003131 CacheView
3132 *image_view;
3133
cristy3ed852e2009-09-05 21:47:34 +00003134 ColorspaceType
3135 colorspace;
3136
3137 const char
3138 *artifact;
3139
3140 double
3141 percent_brightness,
3142 percent_hue,
3143 percent_saturation;
3144
3145 ExceptionInfo
3146 *exception;
3147
3148 GeometryInfo
3149 geometry_info;
3150
cristy3ed852e2009-09-05 21:47:34 +00003151 MagickBooleanType
3152 status;
3153
cristybb503372010-05-27 20:51:26 +00003154 MagickOffsetType
3155 progress;
3156
cristy3ed852e2009-09-05 21:47:34 +00003157 MagickStatusType
3158 flags;
3159
cristybb503372010-05-27 20:51:26 +00003160 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003161 i;
3162
cristybb503372010-05-27 20:51:26 +00003163 ssize_t
3164 y;
3165
cristy3ed852e2009-09-05 21:47:34 +00003166 /*
cristy2b726bd2010-01-11 01:05:39 +00003167 Initialize modulate table.
cristy3ed852e2009-09-05 21:47:34 +00003168 */
3169 assert(image != (Image *) NULL);
3170 assert(image->signature == MagickSignature);
3171 if (image->debug != MagickFalse)
3172 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3173 if (modulate == (char *) NULL)
3174 return(MagickFalse);
3175 flags=ParseGeometry(modulate,&geometry_info);
3176 percent_brightness=geometry_info.rho;
3177 percent_saturation=geometry_info.sigma;
3178 if ((flags & SigmaValue) == 0)
3179 percent_saturation=100.0;
3180 percent_hue=geometry_info.xi;
3181 if ((flags & XiValue) == 0)
3182 percent_hue=100.0;
3183 colorspace=UndefinedColorspace;
3184 artifact=GetImageArtifact(image,"modulate:colorspace");
3185 if (artifact != (const char *) NULL)
3186 colorspace=(ColorspaceType) ParseMagickOption(MagickColorspaceOptions,
3187 MagickFalse,artifact);
3188 if (image->storage_class == PseudoClass)
3189 {
3190 /*
3191 Modulate colormap.
3192 */
cristyb5d5f722009-11-04 03:03:49 +00003193#if defined(MAGICKCORE_OPENMP_SUPPORT)
3194 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003195#endif
cristybb503372010-05-27 20:51:26 +00003196 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00003197 switch (colorspace)
3198 {
3199 case HSBColorspace:
3200 {
3201 ModulateHSB(percent_hue,percent_saturation,percent_brightness,
3202 &image->colormap[i].red,&image->colormap[i].green,
3203 &image->colormap[i].blue);
3204 break;
3205 }
3206 case HSLColorspace:
3207 default:
3208 {
3209 ModulateHSL(percent_hue,percent_saturation,percent_brightness,
3210 &image->colormap[i].red,&image->colormap[i].green,
3211 &image->colormap[i].blue);
3212 break;
3213 }
3214 case HWBColorspace:
3215 {
3216 ModulateHWB(percent_hue,percent_saturation,percent_brightness,
3217 &image->colormap[i].red,&image->colormap[i].green,
3218 &image->colormap[i].blue);
3219 break;
3220 }
3221 }
3222 }
3223 /*
3224 Modulate image.
3225 */
3226 status=MagickTrue;
3227 progress=0;
3228 exception=(&image->exception);
3229 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00003230#if defined(MAGICKCORE_OPENMP_SUPPORT)
3231 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003232#endif
cristybb503372010-05-27 20:51:26 +00003233 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003234 {
cristy3ed852e2009-09-05 21:47:34 +00003235 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00003236 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003237
cristy8d4629b2010-08-30 17:59:46 +00003238 register ssize_t
3239 x;
3240
cristy3ed852e2009-09-05 21:47:34 +00003241 if (status == MagickFalse)
3242 continue;
3243 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
3244 if (q == (PixelPacket *) NULL)
3245 {
3246 status=MagickFalse;
3247 continue;
3248 }
cristybb503372010-05-27 20:51:26 +00003249 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003250 {
3251 switch (colorspace)
3252 {
3253 case HSBColorspace:
3254 {
3255 ModulateHSB(percent_hue,percent_saturation,percent_brightness,
3256 &q->red,&q->green,&q->blue);
3257 break;
3258 }
3259 case HSLColorspace:
3260 default:
3261 {
3262 ModulateHSL(percent_hue,percent_saturation,percent_brightness,
3263 &q->red,&q->green,&q->blue);
3264 break;
3265 }
3266 case HWBColorspace:
3267 {
3268 ModulateHWB(percent_hue,percent_saturation,percent_brightness,
3269 &q->red,&q->green,&q->blue);
3270 break;
3271 }
3272 }
3273 q++;
3274 }
3275 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
3276 status=MagickFalse;
3277 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3278 {
3279 MagickBooleanType
3280 proceed;
3281
cristyb5d5f722009-11-04 03:03:49 +00003282#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00003283 #pragma omp critical (MagickCore_ModulateImage)
3284#endif
3285 proceed=SetImageProgress(image,ModulateImageTag,progress++,image->rows);
3286 if (proceed == MagickFalse)
3287 status=MagickFalse;
3288 }
3289 }
3290 image_view=DestroyCacheView(image_view);
3291 return(status);
3292}
3293
3294/*
3295%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3296% %
3297% %
3298% %
3299% N e g a t e I m a g e %
3300% %
3301% %
3302% %
3303%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3304%
3305% NegateImage() negates the colors in the reference image. The grayscale
3306% option means that only grayscale values within the image are negated.
3307%
3308% The format of the NegateImageChannel method is:
3309%
3310% MagickBooleanType NegateImage(Image *image,
3311% const MagickBooleanType grayscale)
3312% MagickBooleanType NegateImageChannel(Image *image,
3313% const ChannelType channel,const MagickBooleanType grayscale)
3314%
3315% A description of each parameter follows:
3316%
3317% o image: the image.
3318%
3319% o channel: the channel.
3320%
3321% o grayscale: If MagickTrue, only negate grayscale pixels within the image.
3322%
3323*/
3324
3325MagickExport MagickBooleanType NegateImage(Image *image,
3326 const MagickBooleanType grayscale)
3327{
3328 MagickBooleanType
3329 status;
3330
3331 status=NegateImageChannel(image,DefaultChannels,grayscale);
3332 return(status);
3333}
3334
3335MagickExport MagickBooleanType NegateImageChannel(Image *image,
3336 const ChannelType channel,const MagickBooleanType grayscale)
3337{
3338#define NegateImageTag "Negate/Image"
3339
cristyc4c8d132010-01-07 01:58:38 +00003340 CacheView
3341 *image_view;
3342
cristy3ed852e2009-09-05 21:47:34 +00003343 ExceptionInfo
3344 *exception;
3345
cristy3ed852e2009-09-05 21:47:34 +00003346 MagickBooleanType
3347 status;
3348
cristybb503372010-05-27 20:51:26 +00003349 MagickOffsetType
3350 progress;
3351
3352 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003353 i;
3354
cristybb503372010-05-27 20:51:26 +00003355 ssize_t
3356 y;
3357
cristy3ed852e2009-09-05 21:47:34 +00003358 assert(image != (Image *) NULL);
3359 assert(image->signature == MagickSignature);
3360 if (image->debug != MagickFalse)
3361 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3362 if (image->storage_class == PseudoClass)
3363 {
3364 /*
3365 Negate colormap.
3366 */
cristyb5d5f722009-11-04 03:03:49 +00003367#if defined(MAGICKCORE_OPENMP_SUPPORT)
3368 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003369#endif
cristybb503372010-05-27 20:51:26 +00003370 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00003371 {
3372 if (grayscale != MagickFalse)
3373 if ((image->colormap[i].red != image->colormap[i].green) ||
3374 (image->colormap[i].green != image->colormap[i].blue))
3375 continue;
3376 if ((channel & RedChannel) != 0)
3377 image->colormap[i].red=(Quantum) QuantumRange-
3378 image->colormap[i].red;
3379 if ((channel & GreenChannel) != 0)
3380 image->colormap[i].green=(Quantum) QuantumRange-
3381 image->colormap[i].green;
3382 if ((channel & BlueChannel) != 0)
3383 image->colormap[i].blue=(Quantum) QuantumRange-
3384 image->colormap[i].blue;
3385 }
3386 }
3387 /*
3388 Negate image.
3389 */
3390 status=MagickTrue;
3391 progress=0;
3392 exception=(&image->exception);
3393 image_view=AcquireCacheView(image);
3394 if (grayscale != MagickFalse)
3395 {
cristyb5d5f722009-11-04 03:03:49 +00003396#if defined(MAGICKCORE_OPENMP_SUPPORT)
3397 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003398#endif
cristybb503372010-05-27 20:51:26 +00003399 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003400 {
3401 MagickBooleanType
3402 sync;
3403
3404 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00003405 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00003406
cristy3ed852e2009-09-05 21:47:34 +00003407 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00003408 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003409
cristy8d4629b2010-08-30 17:59:46 +00003410 register ssize_t
3411 x;
3412
cristy3ed852e2009-09-05 21:47:34 +00003413 if (status == MagickFalse)
3414 continue;
3415 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,
3416 exception);
3417 if (q == (PixelPacket *) NULL)
3418 {
3419 status=MagickFalse;
3420 continue;
3421 }
3422 indexes=GetCacheViewAuthenticIndexQueue(image_view);
cristybb503372010-05-27 20:51:26 +00003423 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003424 {
3425 if ((q->red != q->green) || (q->green != q->blue))
3426 {
3427 q++;
3428 continue;
3429 }
3430 if ((channel & RedChannel) != 0)
3431 q->red=(Quantum) QuantumRange-q->red;
3432 if ((channel & GreenChannel) != 0)
3433 q->green=(Quantum) QuantumRange-q->green;
3434 if ((channel & BlueChannel) != 0)
3435 q->blue=(Quantum) QuantumRange-q->blue;
3436 if ((channel & OpacityChannel) != 0)
3437 q->opacity=(Quantum) QuantumRange-q->opacity;
3438 if (((channel & IndexChannel) != 0) &&
3439 (image->colorspace == CMYKColorspace))
3440 indexes[x]=(IndexPacket) QuantumRange-indexes[x];
3441 q++;
3442 }
3443 sync=SyncCacheViewAuthenticPixels(image_view,exception);
3444 if (sync == MagickFalse)
3445 status=MagickFalse;
3446 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3447 {
3448 MagickBooleanType
3449 proceed;
3450
cristyb5d5f722009-11-04 03:03:49 +00003451#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00003452 #pragma omp critical (MagickCore_NegateImageChannel)
3453#endif
3454 proceed=SetImageProgress(image,NegateImageTag,progress++,
3455 image->rows);
3456 if (proceed == MagickFalse)
3457 status=MagickFalse;
3458 }
3459 }
3460 image_view=DestroyCacheView(image_view);
3461 return(MagickTrue);
3462 }
3463 /*
3464 Negate image.
3465 */
cristyb5d5f722009-11-04 03:03:49 +00003466#if defined(MAGICKCORE_OPENMP_SUPPORT)
3467 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003468#endif
cristybb503372010-05-27 20:51:26 +00003469 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003470 {
3471 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00003472 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00003473
cristy3ed852e2009-09-05 21:47:34 +00003474 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00003475 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003476
cristy8d4629b2010-08-30 17:59:46 +00003477 register ssize_t
3478 x;
3479
cristy3ed852e2009-09-05 21:47:34 +00003480 if (status == MagickFalse)
3481 continue;
3482 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
3483 if (q == (PixelPacket *) NULL)
3484 {
3485 status=MagickFalse;
3486 continue;
3487 }
3488 indexes=GetCacheViewAuthenticIndexQueue(image_view);
cristybb503372010-05-27 20:51:26 +00003489 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003490 {
3491 if ((channel & RedChannel) != 0)
3492 q->red=(Quantum) QuantumRange-q->red;
3493 if ((channel & GreenChannel) != 0)
3494 q->green=(Quantum) QuantumRange-q->green;
3495 if ((channel & BlueChannel) != 0)
3496 q->blue=(Quantum) QuantumRange-q->blue;
3497 if ((channel & OpacityChannel) != 0)
3498 q->opacity=(Quantum) QuantumRange-q->opacity;
3499 if (((channel & IndexChannel) != 0) &&
3500 (image->colorspace == CMYKColorspace))
3501 indexes[x]=(IndexPacket) QuantumRange-indexes[x];
3502 q++;
3503 }
3504 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
3505 status=MagickFalse;
3506 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3507 {
3508 MagickBooleanType
3509 proceed;
3510
cristyb5d5f722009-11-04 03:03:49 +00003511#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00003512 #pragma omp critical (MagickCore_NegateImageChannel)
3513#endif
3514 proceed=SetImageProgress(image,NegateImageTag,progress++,image->rows);
3515 if (proceed == MagickFalse)
3516 status=MagickFalse;
3517 }
3518 }
3519 image_view=DestroyCacheView(image_view);
3520 return(status);
3521}
3522
3523/*
3524%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3525% %
3526% %
3527% %
3528% N o r m a l i z e I m a g e %
3529% %
3530% %
3531% %
3532%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3533%
3534% The NormalizeImage() method enhances the contrast of a color image by
3535% mapping the darkest 2 percent of all pixel to black and the brightest
3536% 1 percent to white.
3537%
3538% The format of the NormalizeImage method is:
3539%
3540% MagickBooleanType NormalizeImage(Image *image)
3541% MagickBooleanType NormalizeImageChannel(Image *image,
3542% const ChannelType channel)
3543%
3544% A description of each parameter follows:
3545%
3546% o image: the image.
3547%
3548% o channel: the channel.
3549%
3550*/
3551
3552MagickExport MagickBooleanType NormalizeImage(Image *image)
3553{
3554 MagickBooleanType
3555 status;
3556
3557 status=NormalizeImageChannel(image,DefaultChannels);
3558 return(status);
3559}
3560
3561MagickExport MagickBooleanType NormalizeImageChannel(Image *image,
3562 const ChannelType channel)
3563{
3564 double
3565 black_point,
3566 white_point;
3567
cristy530239c2010-07-25 17:34:26 +00003568 black_point=(double) image->columns*image->rows*0.0015;
3569 white_point=(double) image->columns*image->rows*0.9995;
cristy3ed852e2009-09-05 21:47:34 +00003570 return(ContrastStretchImageChannel(image,channel,black_point,white_point));
3571}
3572
3573/*
3574%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3575% %
3576% %
3577% %
3578% S i g m o i d a l C o n t r a s t I m a g e %
3579% %
3580% %
3581% %
3582%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3583%
3584% SigmoidalContrastImage() adjusts the contrast of an image with a non-linear
3585% sigmoidal contrast algorithm. Increase the contrast of the image using a
3586% sigmoidal transfer function without saturating highlights or shadows.
3587% Contrast indicates how much to increase the contrast (0 is none; 3 is
3588% typical; 20 is pushing it); mid-point indicates where midtones fall in the
3589% resultant image (0 is white; 50% is middle-gray; 100% is black). Set
3590% sharpen to MagickTrue to increase the image contrast otherwise the contrast
3591% is reduced.
3592%
3593% The format of the SigmoidalContrastImage method is:
3594%
3595% MagickBooleanType SigmoidalContrastImage(Image *image,
3596% const MagickBooleanType sharpen,const char *levels)
3597% MagickBooleanType SigmoidalContrastImageChannel(Image *image,
3598% const ChannelType channel,const MagickBooleanType sharpen,
3599% const double contrast,const double midpoint)
3600%
3601% A description of each parameter follows:
3602%
3603% o image: the image.
3604%
3605% o channel: the channel.
3606%
3607% o sharpen: Increase or decrease image contrast.
3608%
cristyfa769582010-09-30 23:30:03 +00003609% o alpha: strength of the contrast, the larger the number the more
3610% 'threshold-like' it becomes.
cristy3ed852e2009-09-05 21:47:34 +00003611%
cristyfa769582010-09-30 23:30:03 +00003612% o beta: midpoint of the function as a color value 0 to QuantumRange.
cristy3ed852e2009-09-05 21:47:34 +00003613%
3614*/
3615
3616MagickExport MagickBooleanType SigmoidalContrastImage(Image *image,
3617 const MagickBooleanType sharpen,const char *levels)
3618{
3619 GeometryInfo
3620 geometry_info;
3621
3622 MagickBooleanType
3623 status;
3624
3625 MagickStatusType
3626 flags;
3627
3628 flags=ParseGeometry(levels,&geometry_info);
3629 if ((flags & SigmaValue) == 0)
3630 geometry_info.sigma=1.0*QuantumRange/2.0;
3631 if ((flags & PercentValue) != 0)
3632 geometry_info.sigma=1.0*QuantumRange*geometry_info.sigma/100.0;
3633 status=SigmoidalContrastImageChannel(image,DefaultChannels,sharpen,
3634 geometry_info.rho,geometry_info.sigma);
3635 return(status);
3636}
3637
3638MagickExport MagickBooleanType SigmoidalContrastImageChannel(Image *image,
3639 const ChannelType channel,const MagickBooleanType sharpen,
3640 const double contrast,const double midpoint)
3641{
3642#define SigmoidalContrastImageTag "SigmoidalContrast/Image"
3643
cristyc4c8d132010-01-07 01:58:38 +00003644 CacheView
3645 *image_view;
3646
cristy3ed852e2009-09-05 21:47:34 +00003647 ExceptionInfo
3648 *exception;
3649
cristy3ed852e2009-09-05 21:47:34 +00003650 MagickBooleanType
3651 status;
3652
cristybb503372010-05-27 20:51:26 +00003653 MagickOffsetType
3654 progress;
3655
cristy3ed852e2009-09-05 21:47:34 +00003656 MagickRealType
3657 *sigmoidal_map;
3658
cristybb503372010-05-27 20:51:26 +00003659 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003660 i;
3661
cristybb503372010-05-27 20:51:26 +00003662 ssize_t
3663 y;
3664
cristy3ed852e2009-09-05 21:47:34 +00003665 /*
3666 Allocate and initialize sigmoidal maps.
3667 */
3668 assert(image != (Image *) NULL);
3669 assert(image->signature == MagickSignature);
3670 if (image->debug != MagickFalse)
3671 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3672 sigmoidal_map=(MagickRealType *) AcquireQuantumMemory(MaxMap+1UL,
3673 sizeof(*sigmoidal_map));
3674 if (sigmoidal_map == (MagickRealType *) NULL)
3675 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
3676 image->filename);
3677 (void) ResetMagickMemory(sigmoidal_map,0,(MaxMap+1)*sizeof(*sigmoidal_map));
cristyb5d5f722009-11-04 03:03:49 +00003678#if defined(MAGICKCORE_OPENMP_SUPPORT)
3679 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003680#endif
cristybb503372010-05-27 20:51:26 +00003681 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +00003682 {
3683 if (sharpen != MagickFalse)
3684 {
3685 sigmoidal_map[i]=(MagickRealType) ScaleMapToQuantum((MagickRealType)
3686 (MaxMap*((1.0/(1.0+exp(contrast*(midpoint/(double) QuantumRange-
3687 (double) i/MaxMap))))-(1.0/(1.0+exp(contrast*(midpoint/
3688 (double) QuantumRange)))))/((1.0/(1.0+exp(contrast*(midpoint/
3689 (double) QuantumRange-1.0))))-(1.0/(1.0+exp(contrast*(midpoint/
3690 (double) QuantumRange)))))+0.5));
3691 continue;
3692 }
3693 sigmoidal_map[i]=(MagickRealType) ScaleMapToQuantum((MagickRealType)
3694 (MaxMap*(QuantumScale*midpoint-log((1.0-(1.0/(1.0+exp(midpoint/
3695 (double) QuantumRange*contrast))+((double) i/MaxMap)*((1.0/
3696 (1.0+exp(contrast*(midpoint/(double) QuantumRange-1.0))))-(1.0/
3697 (1.0+exp(midpoint/(double) QuantumRange*contrast))))))/
3698 (1.0/(1.0+exp(midpoint/(double) QuantumRange*contrast))+
3699 ((double) i/MaxMap)*((1.0/(1.0+exp(contrast*(midpoint/
3700 (double) QuantumRange-1.0))))-(1.0/(1.0+exp(midpoint/
3701 (double) QuantumRange*contrast))))))/contrast)));
3702 }
3703 if (image->storage_class == PseudoClass)
3704 {
3705 /*
3706 Sigmoidal-contrast enhance colormap.
3707 */
cristyb5d5f722009-11-04 03:03:49 +00003708#if defined(MAGICKCORE_OPENMP_SUPPORT)
3709 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003710#endif
cristybb503372010-05-27 20:51:26 +00003711 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00003712 {
3713 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003714 image->colormap[i].red=ClampToQuantum(sigmoidal_map[
cristy3ed852e2009-09-05 21:47:34 +00003715 ScaleQuantumToMap(image->colormap[i].red)]);
3716 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003717 image->colormap[i].green=ClampToQuantum(sigmoidal_map[
cristy3ed852e2009-09-05 21:47:34 +00003718 ScaleQuantumToMap(image->colormap[i].green)]);
3719 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003720 image->colormap[i].blue=ClampToQuantum(sigmoidal_map[
cristy3ed852e2009-09-05 21:47:34 +00003721 ScaleQuantumToMap(image->colormap[i].blue)]);
3722 if ((channel & OpacityChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003723 image->colormap[i].opacity=ClampToQuantum(sigmoidal_map[
cristy3ed852e2009-09-05 21:47:34 +00003724 ScaleQuantumToMap(image->colormap[i].opacity)]);
3725 }
3726 }
3727 /*
3728 Sigmoidal-contrast enhance image.
3729 */
3730 status=MagickTrue;
3731 progress=0;
3732 exception=(&image->exception);
3733 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00003734#if defined(MAGICKCORE_OPENMP_SUPPORT)
3735 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003736#endif
cristybb503372010-05-27 20:51:26 +00003737 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003738 {
3739 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00003740 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00003741
cristy3ed852e2009-09-05 21:47:34 +00003742 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00003743 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003744
cristy8d4629b2010-08-30 17:59:46 +00003745 register ssize_t
3746 x;
3747
cristy3ed852e2009-09-05 21:47:34 +00003748 if (status == MagickFalse)
3749 continue;
3750 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
3751 if (q == (PixelPacket *) NULL)
3752 {
3753 status=MagickFalse;
3754 continue;
3755 }
3756 indexes=GetCacheViewAuthenticIndexQueue(image_view);
cristybb503372010-05-27 20:51:26 +00003757 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003758 {
3759 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003760 q->red=ClampToQuantum(sigmoidal_map[ScaleQuantumToMap(q->red)]);
cristy3ed852e2009-09-05 21:47:34 +00003761 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003762 q->green=ClampToQuantum(sigmoidal_map[ScaleQuantumToMap(q->green)]);
cristy3ed852e2009-09-05 21:47:34 +00003763 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003764 q->blue=ClampToQuantum(sigmoidal_map[ScaleQuantumToMap(q->blue)]);
cristy3ed852e2009-09-05 21:47:34 +00003765 if ((channel & OpacityChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003766 q->opacity=ClampToQuantum(sigmoidal_map[ScaleQuantumToMap(q->opacity)]);
cristy3ed852e2009-09-05 21:47:34 +00003767 if (((channel & IndexChannel) != 0) &&
3768 (image->colorspace == CMYKColorspace))
cristyce70c172010-01-07 17:15:30 +00003769 indexes[x]=(IndexPacket) ClampToQuantum(sigmoidal_map[
cristy3ed852e2009-09-05 21:47:34 +00003770 ScaleQuantumToMap(indexes[x])]);
3771 q++;
3772 }
3773 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
3774 status=MagickFalse;
3775 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3776 {
3777 MagickBooleanType
3778 proceed;
3779
cristyb5d5f722009-11-04 03:03:49 +00003780#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00003781 #pragma omp critical (MagickCore_SigmoidalContrastImageChannel)
3782#endif
3783 proceed=SetImageProgress(image,SigmoidalContrastImageTag,progress++,
3784 image->rows);
3785 if (proceed == MagickFalse)
3786 status=MagickFalse;
3787 }
3788 }
3789 image_view=DestroyCacheView(image_view);
3790 sigmoidal_map=(MagickRealType *) RelinquishMagickMemory(sigmoidal_map);
3791 return(status);
3792}