blob: 12f80e2234300494524ee2a668f4dadb894893a5 [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);
cristyaf6bc722011-03-25 19:16:14 +0000747#if defined(MAGICKCORE_OPENMP_SUPPORT)
748 #pragma omp parallel for schedule(dynamic,4)
749#endif
cristy49f37242011-03-22 18:18:23 +0000750 for (i=0; i <= (ssize_t) MaxMap; i++)
751 {
cristyaf6bc722011-03-25 19:16:14 +0000752 const int
753 id = GetOpenMPThreadId();
754
cristy49f37242011-03-22 18:18:23 +0000755 GetMagickPixelPacket(clut_image,clut_map+i);
cristyaf6bc722011-03-25 19:16:14 +0000756 (void) ResamplePixelColor(resample_filter[id],QuantumScale*i*
cristy49f37242011-03-22 18:18:23 +0000757 (clut_image->columns-adjust),QuantumScale*i*(clut_image->rows-adjust),
758 clut_map+i);
759 }
cristyaf6bc722011-03-25 19:16:14 +0000760 resample_filter=DestroyResampleFilterThreadSet(resample_filter);
cristy3ed852e2009-09-05 21:47:34 +0000761 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +0000762#if defined(MAGICKCORE_OPENMP_SUPPORT)
763 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000764#endif
cristybb503372010-05-27 20:51:26 +0000765 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000766 {
cristy3635df22011-03-25 00:16:16 +0000767 MagickPixelPacket
768 pixel;
769
cristy3ed852e2009-09-05 21:47:34 +0000770 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +0000771 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +0000772
cristy3ed852e2009-09-05 21:47:34 +0000773 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +0000774 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000775
cristy8d4629b2010-08-30 17:59:46 +0000776 register ssize_t
777 x;
778
cristy3ed852e2009-09-05 21:47:34 +0000779 if (status == MagickFalse)
780 continue;
781 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
782 if (q == (PixelPacket *) NULL)
783 {
784 status=MagickFalse;
785 continue;
786 }
787 indexes=GetCacheViewAuthenticIndexQueue(image_view);
cristy3635df22011-03-25 00:16:16 +0000788 GetMagickPixelPacket(image,&pixel);
cristybb503372010-05-27 20:51:26 +0000789 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000790 {
cristy3635df22011-03-25 00:16:16 +0000791 SetMagickPixelPacket(image,q,indexes+x,&pixel);
cristy2253f172011-03-24 23:39:51 +0000792 if ((channel & RedChannel) != 0)
793 SetRedPixelComponent(q,ClampRedPixelComponent(clut_map+
794 ScaleQuantumToMap(q->red)));
795 if ((channel & GreenChannel) != 0)
796 SetGreenPixelComponent(q,ClampGreenPixelComponent(clut_map+
797 ScaleQuantumToMap(q->green)));
798 if ((channel & BlueChannel) != 0)
799 SetBluePixelComponent(q,ClampBluePixelComponent(clut_map+
800 ScaleQuantumToMap(q->blue)));
cristy3635df22011-03-25 00:16:16 +0000801 if ((channel & OpacityChannel) != 0)
802 {
803 if (clut_image->matte == MagickFalse)
804 q->opacity=(Quantum) (QuantumRange-MagickPixelIntensityToQuantum(
805 clut_map+ScaleQuantumToMap(GetAlphaPixelComponent(q))));
806 else
807 if (image->matte == MagickFalse)
808 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(clut_map+
809 ScaleQuantumToMap(MagickPixelIntensity(&pixel))));
810 else
811 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(
812 clut_map+ScaleQuantumToMap(q->opacity)));
813 }
cristy3ed852e2009-09-05 21:47:34 +0000814 if (((channel & IndexChannel) != 0) &&
815 (image->colorspace == CMYKColorspace))
cristy49f37242011-03-22 18:18:23 +0000816 indexes[x]=ClampToQuantum((clut_map+(ssize_t) indexes[x])->index);
cristy3ed852e2009-09-05 21:47:34 +0000817 q++;
818 }
819 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
820 status=MagickFalse;
821 if (image->progress_monitor != (MagickProgressMonitor) NULL)
822 {
823 MagickBooleanType
824 proceed;
825
cristyb5d5f722009-11-04 03:03:49 +0000826#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +0000827 #pragma omp critical (MagickCore_ClutImageChannel)
828#endif
829 proceed=SetImageProgress(image,ClutImageTag,progress++,image->rows);
830 if (proceed == MagickFalse)
831 status=MagickFalse;
832 }
833 }
834 image_view=DestroyCacheView(image_view);
cristy49f37242011-03-22 18:18:23 +0000835 clut_map=(MagickPixelPacket *) RelinquishMagickMemory(clut_map);
cristy3ed852e2009-09-05 21:47:34 +0000836 if ((clut_image->matte != MagickFalse) && ((channel & OpacityChannel) != 0))
837 (void) SetImageAlphaChannel(image,ActivateAlphaChannel);
838 return(status);
839}
840
841/*
842%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
843% %
844% %
845% %
846% C o n t r a s t I m a g e %
847% %
848% %
849% %
850%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
851%
852% ContrastImage() enhances the intensity differences between the lighter and
853% darker elements of the image. Set sharpen to a MagickTrue to increase the
854% image contrast otherwise the contrast is reduced.
855%
856% The format of the ContrastImage method is:
857%
858% MagickBooleanType ContrastImage(Image *image,
859% const MagickBooleanType sharpen)
860%
861% A description of each parameter follows:
862%
863% o image: the image.
864%
865% o sharpen: Increase or decrease image contrast.
866%
867*/
868
869static void Contrast(const int sign,Quantum *red,Quantum *green,Quantum *blue)
870{
871 double
872 brightness,
873 hue,
874 saturation;
875
876 /*
877 Enhance contrast: dark color become darker, light color become lighter.
878 */
879 assert(red != (Quantum *) NULL);
880 assert(green != (Quantum *) NULL);
881 assert(blue != (Quantum *) NULL);
882 hue=0.0;
883 saturation=0.0;
884 brightness=0.0;
885 ConvertRGBToHSB(*red,*green,*blue,&hue,&saturation,&brightness);
cristy31ac0f02011-03-12 02:04:47 +0000886 brightness+=0.5*sign*(0.5*(sin((double) (MagickPI*(brightness-0.5)))+1.0)-
cristy4205a3c2010-09-12 20:19:59 +0000887 brightness);
cristy3ed852e2009-09-05 21:47:34 +0000888 if (brightness > 1.0)
889 brightness=1.0;
890 else
891 if (brightness < 0.0)
892 brightness=0.0;
893 ConvertHSBToRGB(hue,saturation,brightness,red,green,blue);
894}
895
896MagickExport MagickBooleanType ContrastImage(Image *image,
897 const MagickBooleanType sharpen)
898{
899#define ContrastImageTag "Contrast/Image"
900
cristyc4c8d132010-01-07 01:58:38 +0000901 CacheView
902 *image_view;
903
cristy3ed852e2009-09-05 21:47:34 +0000904 ExceptionInfo
905 *exception;
906
907 int
908 sign;
909
cristy3ed852e2009-09-05 21:47:34 +0000910 MagickBooleanType
911 status;
912
cristybb503372010-05-27 20:51:26 +0000913 MagickOffsetType
914 progress;
915
916 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000917 i;
918
cristybb503372010-05-27 20:51:26 +0000919 ssize_t
920 y;
921
cristy3ed852e2009-09-05 21:47:34 +0000922 assert(image != (Image *) NULL);
923 assert(image->signature == MagickSignature);
924 if (image->debug != MagickFalse)
925 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
926 sign=sharpen != MagickFalse ? 1 : -1;
927 if (image->storage_class == PseudoClass)
928 {
929 /*
930 Contrast enhance colormap.
931 */
cristybb503372010-05-27 20:51:26 +0000932 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +0000933 Contrast(sign,&image->colormap[i].red,&image->colormap[i].green,
934 &image->colormap[i].blue);
935 }
936 /*
937 Contrast enhance image.
938 */
939 status=MagickTrue;
940 progress=0;
941 exception=(&image->exception);
942 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +0000943#if defined(MAGICKCORE_OPENMP_SUPPORT)
944 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000945#endif
cristybb503372010-05-27 20:51:26 +0000946 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000947 {
cristy3ed852e2009-09-05 21:47:34 +0000948 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +0000949 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000950
cristy8d4629b2010-08-30 17:59:46 +0000951 register ssize_t
952 x;
953
cristy3ed852e2009-09-05 21:47:34 +0000954 if (status == MagickFalse)
955 continue;
956 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
957 if (q == (PixelPacket *) NULL)
958 {
959 status=MagickFalse;
960 continue;
961 }
cristybb503372010-05-27 20:51:26 +0000962 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000963 {
964 Contrast(sign,&q->red,&q->green,&q->blue);
965 q++;
966 }
967 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
968 status=MagickFalse;
969 if (image->progress_monitor != (MagickProgressMonitor) NULL)
970 {
971 MagickBooleanType
972 proceed;
973
cristyb5d5f722009-11-04 03:03:49 +0000974#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +0000975 #pragma omp critical (MagickCore_ContrastImage)
976#endif
977 proceed=SetImageProgress(image,ContrastImageTag,progress++,image->rows);
978 if (proceed == MagickFalse)
979 status=MagickFalse;
980 }
981 }
982 image_view=DestroyCacheView(image_view);
983 return(status);
984}
985
986/*
987%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
988% %
989% %
990% %
991% C o n t r a s t S t r e t c h I m a g e %
992% %
993% %
994% %
995%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
996%
997% The ContrastStretchImage() is a simple image enhancement technique that
998% attempts to improve the contrast in an image by `stretching' the range of
999% intensity values it contains to span a desired range of values. It differs
1000% from the more sophisticated histogram equalization in that it can only
1001% apply % a linear scaling function to the image pixel values. As a result
1002% the `enhancement' is less harsh.
1003%
1004% The format of the ContrastStretchImage method is:
1005%
1006% MagickBooleanType ContrastStretchImage(Image *image,
1007% const char *levels)
1008% MagickBooleanType ContrastStretchImageChannel(Image *image,
cristybb503372010-05-27 20:51:26 +00001009% const size_t channel,const double black_point,
cristy3ed852e2009-09-05 21:47:34 +00001010% const double white_point)
1011%
1012% A description of each parameter follows:
1013%
1014% o image: the image.
1015%
1016% o channel: the channel.
1017%
1018% o black_point: the black point.
1019%
1020% o white_point: the white point.
1021%
1022% o levels: Specify the levels where the black and white points have the
1023% range of 0 to number-of-pixels (e.g. 1%, 10x90%, etc.).
1024%
1025*/
1026
1027MagickExport MagickBooleanType ContrastStretchImage(Image *image,
1028 const char *levels)
1029{
1030 double
1031 black_point,
1032 white_point;
1033
1034 GeometryInfo
1035 geometry_info;
1036
1037 MagickBooleanType
1038 status;
1039
1040 MagickStatusType
1041 flags;
1042
1043 /*
1044 Parse levels.
1045 */
1046 if (levels == (char *) NULL)
1047 return(MagickFalse);
1048 flags=ParseGeometry(levels,&geometry_info);
1049 black_point=geometry_info.rho;
1050 white_point=(double) image->columns*image->rows;
1051 if ((flags & SigmaValue) != 0)
1052 white_point=geometry_info.sigma;
1053 if ((flags & PercentValue) != 0)
1054 {
1055 black_point*=(double) QuantumRange/100.0;
1056 white_point*=(double) QuantumRange/100.0;
1057 }
1058 if ((flags & SigmaValue) == 0)
1059 white_point=(double) image->columns*image->rows-black_point;
1060 status=ContrastStretchImageChannel(image,DefaultChannels,black_point,
1061 white_point);
1062 return(status);
1063}
1064
1065MagickExport MagickBooleanType ContrastStretchImageChannel(Image *image,
1066 const ChannelType channel,const double black_point,const double white_point)
1067{
1068#define MaxRange(color) ((MagickRealType) ScaleQuantumToMap((Quantum) (color)))
1069#define ContrastStretchImageTag "ContrastStretch/Image"
1070
cristyc4c8d132010-01-07 01:58:38 +00001071 CacheView
1072 *image_view;
1073
cristy3ed852e2009-09-05 21:47:34 +00001074 double
1075 intensity;
1076
1077 ExceptionInfo
1078 *exception;
1079
cristy3ed852e2009-09-05 21:47:34 +00001080 MagickBooleanType
1081 status;
1082
cristybb503372010-05-27 20:51:26 +00001083 MagickOffsetType
1084 progress;
1085
cristy3ed852e2009-09-05 21:47:34 +00001086 MagickPixelPacket
1087 black,
1088 *histogram,
1089 *stretch_map,
1090 white;
1091
cristybb503372010-05-27 20:51:26 +00001092 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001093 i;
1094
cristybb503372010-05-27 20:51:26 +00001095 ssize_t
1096 y;
1097
cristy3ed852e2009-09-05 21:47:34 +00001098 /*
1099 Allocate histogram and stretch map.
1100 */
1101 assert(image != (Image *) NULL);
1102 assert(image->signature == MagickSignature);
1103 if (image->debug != MagickFalse)
1104 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1105 histogram=(MagickPixelPacket *) AcquireQuantumMemory(MaxMap+1UL,
1106 sizeof(*histogram));
1107 stretch_map=(MagickPixelPacket *) AcquireQuantumMemory(MaxMap+1UL,
1108 sizeof(*stretch_map));
1109 if ((histogram == (MagickPixelPacket *) NULL) ||
1110 (stretch_map == (MagickPixelPacket *) NULL))
1111 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
1112 image->filename);
1113 /*
1114 Form histogram.
1115 */
1116 status=MagickTrue;
1117 exception=(&image->exception);
1118 (void) ResetMagickMemory(histogram,0,(MaxMap+1)*sizeof(*histogram));
1119 image_view=AcquireCacheView(image);
cristybb503372010-05-27 20:51:26 +00001120 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001121 {
1122 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001123 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00001124
1125 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00001126 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00001127
cristybb503372010-05-27 20:51:26 +00001128 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001129 x;
1130
1131 if (status == MagickFalse)
1132 continue;
1133 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
1134 if (p == (const PixelPacket *) NULL)
1135 {
1136 status=MagickFalse;
1137 continue;
1138 }
1139 indexes=GetCacheViewAuthenticIndexQueue(image_view);
1140 if (channel == DefaultChannels)
cristybb503372010-05-27 20:51:26 +00001141 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001142 {
1143 Quantum
1144 intensity;
1145
1146 intensity=PixelIntensityToQuantum(p);
1147 histogram[ScaleQuantumToMap(intensity)].red++;
1148 histogram[ScaleQuantumToMap(intensity)].green++;
1149 histogram[ScaleQuantumToMap(intensity)].blue++;
1150 histogram[ScaleQuantumToMap(intensity)].index++;
1151 p++;
1152 }
1153 else
cristybb503372010-05-27 20:51:26 +00001154 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001155 {
1156 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001157 histogram[ScaleQuantumToMap(GetRedPixelComponent(p))].red++;
cristy3ed852e2009-09-05 21:47:34 +00001158 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001159 histogram[ScaleQuantumToMap(GetGreenPixelComponent(p))].green++;
cristy3ed852e2009-09-05 21:47:34 +00001160 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001161 histogram[ScaleQuantumToMap(GetBluePixelComponent(p))].blue++;
cristy3ed852e2009-09-05 21:47:34 +00001162 if ((channel & OpacityChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001163 histogram[ScaleQuantumToMap(GetOpacityPixelComponent(p))].opacity++;
cristy3ed852e2009-09-05 21:47:34 +00001164 if (((channel & IndexChannel) != 0) &&
1165 (image->colorspace == CMYKColorspace))
1166 histogram[ScaleQuantumToMap(indexes[x])].index++;
1167 p++;
1168 }
1169 }
1170 /*
1171 Find the histogram boundaries by locating the black/white levels.
1172 */
1173 black.red=0.0;
1174 white.red=MaxRange(QuantumRange);
1175 if ((channel & RedChannel) != 0)
1176 {
1177 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00001178 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +00001179 {
1180 intensity+=histogram[i].red;
1181 if (intensity > black_point)
1182 break;
1183 }
1184 black.red=(MagickRealType) i;
1185 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00001186 for (i=(ssize_t) MaxMap; i != 0; i--)
cristy3ed852e2009-09-05 21:47:34 +00001187 {
1188 intensity+=histogram[i].red;
1189 if (intensity > ((double) image->columns*image->rows-white_point))
1190 break;
1191 }
1192 white.red=(MagickRealType) i;
1193 }
1194 black.green=0.0;
1195 white.green=MaxRange(QuantumRange);
1196 if ((channel & GreenChannel) != 0)
1197 {
1198 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00001199 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +00001200 {
1201 intensity+=histogram[i].green;
1202 if (intensity > black_point)
1203 break;
1204 }
1205 black.green=(MagickRealType) i;
1206 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00001207 for (i=(ssize_t) MaxMap; i != 0; i--)
cristy3ed852e2009-09-05 21:47:34 +00001208 {
1209 intensity+=histogram[i].green;
1210 if (intensity > ((double) image->columns*image->rows-white_point))
1211 break;
1212 }
1213 white.green=(MagickRealType) i;
1214 }
1215 black.blue=0.0;
1216 white.blue=MaxRange(QuantumRange);
1217 if ((channel & BlueChannel) != 0)
1218 {
1219 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00001220 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +00001221 {
1222 intensity+=histogram[i].blue;
1223 if (intensity > black_point)
1224 break;
1225 }
1226 black.blue=(MagickRealType) i;
1227 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00001228 for (i=(ssize_t) MaxMap; i != 0; i--)
cristy3ed852e2009-09-05 21:47:34 +00001229 {
1230 intensity+=histogram[i].blue;
1231 if (intensity > ((double) image->columns*image->rows-white_point))
1232 break;
1233 }
1234 white.blue=(MagickRealType) i;
1235 }
1236 black.opacity=0.0;
1237 white.opacity=MaxRange(QuantumRange);
1238 if ((channel & OpacityChannel) != 0)
1239 {
1240 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00001241 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +00001242 {
1243 intensity+=histogram[i].opacity;
1244 if (intensity > black_point)
1245 break;
1246 }
1247 black.opacity=(MagickRealType) i;
1248 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00001249 for (i=(ssize_t) MaxMap; i != 0; i--)
cristy3ed852e2009-09-05 21:47:34 +00001250 {
1251 intensity+=histogram[i].opacity;
1252 if (intensity > ((double) image->columns*image->rows-white_point))
1253 break;
1254 }
1255 white.opacity=(MagickRealType) i;
1256 }
1257 black.index=0.0;
1258 white.index=MaxRange(QuantumRange);
1259 if (((channel & IndexChannel) != 0) && (image->colorspace == CMYKColorspace))
1260 {
1261 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00001262 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +00001263 {
1264 intensity+=histogram[i].index;
1265 if (intensity > black_point)
1266 break;
1267 }
1268 black.index=(MagickRealType) i;
1269 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00001270 for (i=(ssize_t) MaxMap; i != 0; i--)
cristy3ed852e2009-09-05 21:47:34 +00001271 {
1272 intensity+=histogram[i].index;
1273 if (intensity > ((double) image->columns*image->rows-white_point))
1274 break;
1275 }
1276 white.index=(MagickRealType) i;
1277 }
1278 histogram=(MagickPixelPacket *) RelinquishMagickMemory(histogram);
1279 /*
1280 Stretch the histogram to create the stretched image mapping.
1281 */
1282 (void) ResetMagickMemory(stretch_map,0,(MaxMap+1)*sizeof(*stretch_map));
cristyb5d5f722009-11-04 03:03:49 +00001283#if defined(MAGICKCORE_OPENMP_SUPPORT)
1284 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001285#endif
cristybb503372010-05-27 20:51:26 +00001286 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +00001287 {
1288 if ((channel & RedChannel) != 0)
1289 {
cristybb503372010-05-27 20:51:26 +00001290 if (i < (ssize_t) black.red)
cristy3ed852e2009-09-05 21:47:34 +00001291 stretch_map[i].red=0.0;
1292 else
cristybb503372010-05-27 20:51:26 +00001293 if (i > (ssize_t) white.red)
cristy3ed852e2009-09-05 21:47:34 +00001294 stretch_map[i].red=(MagickRealType) QuantumRange;
1295 else
1296 if (black.red != white.red)
1297 stretch_map[i].red=(MagickRealType) ScaleMapToQuantum(
1298 (MagickRealType) (MaxMap*(i-black.red)/(white.red-black.red)));
1299 }
1300 if ((channel & GreenChannel) != 0)
1301 {
cristybb503372010-05-27 20:51:26 +00001302 if (i < (ssize_t) black.green)
cristy3ed852e2009-09-05 21:47:34 +00001303 stretch_map[i].green=0.0;
1304 else
cristybb503372010-05-27 20:51:26 +00001305 if (i > (ssize_t) white.green)
cristy3ed852e2009-09-05 21:47:34 +00001306 stretch_map[i].green=(MagickRealType) QuantumRange;
1307 else
1308 if (black.green != white.green)
1309 stretch_map[i].green=(MagickRealType) ScaleMapToQuantum(
1310 (MagickRealType) (MaxMap*(i-black.green)/(white.green-
1311 black.green)));
1312 }
1313 if ((channel & BlueChannel) != 0)
1314 {
cristybb503372010-05-27 20:51:26 +00001315 if (i < (ssize_t) black.blue)
cristy3ed852e2009-09-05 21:47:34 +00001316 stretch_map[i].blue=0.0;
1317 else
cristybb503372010-05-27 20:51:26 +00001318 if (i > (ssize_t) white.blue)
cristy3ed852e2009-09-05 21:47:34 +00001319 stretch_map[i].blue=(MagickRealType) QuantumRange;
1320 else
1321 if (black.blue != white.blue)
1322 stretch_map[i].blue=(MagickRealType) ScaleMapToQuantum(
1323 (MagickRealType) (MaxMap*(i-black.blue)/(white.blue-
1324 black.blue)));
1325 }
1326 if ((channel & OpacityChannel) != 0)
1327 {
cristybb503372010-05-27 20:51:26 +00001328 if (i < (ssize_t) black.opacity)
cristy3ed852e2009-09-05 21:47:34 +00001329 stretch_map[i].opacity=0.0;
1330 else
cristybb503372010-05-27 20:51:26 +00001331 if (i > (ssize_t) white.opacity)
cristy3ed852e2009-09-05 21:47:34 +00001332 stretch_map[i].opacity=(MagickRealType) QuantumRange;
1333 else
1334 if (black.opacity != white.opacity)
1335 stretch_map[i].opacity=(MagickRealType) ScaleMapToQuantum(
1336 (MagickRealType) (MaxMap*(i-black.opacity)/(white.opacity-
1337 black.opacity)));
1338 }
1339 if (((channel & IndexChannel) != 0) &&
1340 (image->colorspace == CMYKColorspace))
1341 {
cristybb503372010-05-27 20:51:26 +00001342 if (i < (ssize_t) black.index)
cristy3ed852e2009-09-05 21:47:34 +00001343 stretch_map[i].index=0.0;
1344 else
cristybb503372010-05-27 20:51:26 +00001345 if (i > (ssize_t) white.index)
cristy3ed852e2009-09-05 21:47:34 +00001346 stretch_map[i].index=(MagickRealType) QuantumRange;
1347 else
1348 if (black.index != white.index)
1349 stretch_map[i].index=(MagickRealType) ScaleMapToQuantum(
1350 (MagickRealType) (MaxMap*(i-black.index)/(white.index-
1351 black.index)));
1352 }
1353 }
1354 /*
1355 Stretch the image.
1356 */
1357 if (((channel & OpacityChannel) != 0) || (((channel & IndexChannel) != 0) &&
1358 (image->colorspace == CMYKColorspace)))
1359 image->storage_class=DirectClass;
1360 if (image->storage_class == PseudoClass)
1361 {
1362 /*
1363 Stretch colormap.
1364 */
cristyb5d5f722009-11-04 03:03:49 +00001365#if defined(MAGICKCORE_OPENMP_SUPPORT)
1366 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001367#endif
cristybb503372010-05-27 20:51:26 +00001368 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00001369 {
1370 if ((channel & RedChannel) != 0)
1371 {
1372 if (black.red != white.red)
cristyce70c172010-01-07 17:15:30 +00001373 image->colormap[i].red=ClampToQuantum(stretch_map[
cristy3ed852e2009-09-05 21:47:34 +00001374 ScaleQuantumToMap(image->colormap[i].red)].red);
1375 }
1376 if ((channel & GreenChannel) != 0)
1377 {
1378 if (black.green != white.green)
cristyce70c172010-01-07 17:15:30 +00001379 image->colormap[i].green=ClampToQuantum(stretch_map[
cristy3ed852e2009-09-05 21:47:34 +00001380 ScaleQuantumToMap(image->colormap[i].green)].green);
1381 }
1382 if ((channel & BlueChannel) != 0)
1383 {
1384 if (black.blue != white.blue)
cristyce70c172010-01-07 17:15:30 +00001385 image->colormap[i].blue=ClampToQuantum(stretch_map[
cristy3ed852e2009-09-05 21:47:34 +00001386 ScaleQuantumToMap(image->colormap[i].blue)].blue);
1387 }
1388 if ((channel & OpacityChannel) != 0)
1389 {
1390 if (black.opacity != white.opacity)
cristyce70c172010-01-07 17:15:30 +00001391 image->colormap[i].opacity=ClampToQuantum(stretch_map[
cristy3ed852e2009-09-05 21:47:34 +00001392 ScaleQuantumToMap(image->colormap[i].opacity)].opacity);
1393 }
1394 }
1395 }
1396 /*
1397 Stretch image.
1398 */
1399 status=MagickTrue;
1400 progress=0;
cristyb5d5f722009-11-04 03:03:49 +00001401#if defined(MAGICKCORE_OPENMP_SUPPORT)
1402 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001403#endif
cristybb503372010-05-27 20:51:26 +00001404 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001405 {
1406 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00001407 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00001408
cristy3ed852e2009-09-05 21:47:34 +00001409 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001410 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001411
cristy8d4629b2010-08-30 17:59:46 +00001412 register ssize_t
1413 x;
1414
cristy3ed852e2009-09-05 21:47:34 +00001415 if (status == MagickFalse)
1416 continue;
1417 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
1418 if (q == (PixelPacket *) NULL)
1419 {
1420 status=MagickFalse;
1421 continue;
1422 }
1423 indexes=GetCacheViewAuthenticIndexQueue(image_view);
cristybb503372010-05-27 20:51:26 +00001424 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001425 {
1426 if ((channel & RedChannel) != 0)
1427 {
1428 if (black.red != white.red)
cristyce70c172010-01-07 17:15:30 +00001429 q->red=ClampToQuantum(stretch_map[ScaleQuantumToMap(q->red)].red);
cristy3ed852e2009-09-05 21:47:34 +00001430 }
1431 if ((channel & GreenChannel) != 0)
1432 {
1433 if (black.green != white.green)
cristyce70c172010-01-07 17:15:30 +00001434 q->green=ClampToQuantum(stretch_map[ScaleQuantumToMap(
cristy3ed852e2009-09-05 21:47:34 +00001435 q->green)].green);
1436 }
1437 if ((channel & BlueChannel) != 0)
1438 {
1439 if (black.blue != white.blue)
cristyce70c172010-01-07 17:15:30 +00001440 q->blue=ClampToQuantum(stretch_map[ScaleQuantumToMap(
cristy3ed852e2009-09-05 21:47:34 +00001441 q->blue)].blue);
1442 }
1443 if ((channel & OpacityChannel) != 0)
1444 {
1445 if (black.opacity != white.opacity)
cristyce70c172010-01-07 17:15:30 +00001446 q->opacity=ClampToQuantum(stretch_map[ScaleQuantumToMap(
cristy3ed852e2009-09-05 21:47:34 +00001447 q->opacity)].opacity);
1448 }
1449 if (((channel & IndexChannel) != 0) &&
1450 (image->colorspace == CMYKColorspace))
1451 {
1452 if (black.index != white.index)
cristyce70c172010-01-07 17:15:30 +00001453 indexes[x]=(IndexPacket) ClampToQuantum(stretch_map[
cristy3ed852e2009-09-05 21:47:34 +00001454 ScaleQuantumToMap(indexes[x])].index);
1455 }
1456 q++;
1457 }
1458 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
1459 status=MagickFalse;
1460 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1461 {
1462 MagickBooleanType
1463 proceed;
1464
cristyb5d5f722009-11-04 03:03:49 +00001465#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00001466 #pragma omp critical (MagickCore_ContrastStretchImageChannel)
1467#endif
1468 proceed=SetImageProgress(image,ContrastStretchImageTag,progress++,
1469 image->rows);
1470 if (proceed == MagickFalse)
1471 status=MagickFalse;
1472 }
1473 }
1474 image_view=DestroyCacheView(image_view);
1475 stretch_map=(MagickPixelPacket *) RelinquishMagickMemory(stretch_map);
1476 return(status);
1477}
1478
1479/*
1480%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1481% %
1482% %
1483% %
1484% E n h a n c e I m a g e %
1485% %
1486% %
1487% %
1488%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1489%
1490% EnhanceImage() applies a digital filter that improves the quality of a
1491% noisy image.
1492%
1493% The format of the EnhanceImage method is:
1494%
1495% Image *EnhanceImage(const Image *image,ExceptionInfo *exception)
1496%
1497% A description of each parameter follows:
1498%
1499% o image: the image.
1500%
1501% o exception: return any errors or warnings in this structure.
1502%
1503*/
1504MagickExport Image *EnhanceImage(const Image *image,ExceptionInfo *exception)
1505{
1506#define Enhance(weight) \
1507 mean=((MagickRealType) r->red+pixel.red)/2; \
1508 distance=(MagickRealType) r->red-(MagickRealType) pixel.red; \
1509 distance_squared=QuantumScale*(2.0*((MagickRealType) QuantumRange+1.0)+ \
1510 mean)*distance*distance; \
1511 mean=((MagickRealType) r->green+pixel.green)/2; \
1512 distance=(MagickRealType) r->green-(MagickRealType) pixel.green; \
1513 distance_squared+=4.0*distance*distance; \
1514 mean=((MagickRealType) r->blue+pixel.blue)/2; \
1515 distance=(MagickRealType) r->blue-(MagickRealType) pixel.blue; \
1516 distance_squared+=QuantumScale*(3.0*((MagickRealType) \
1517 QuantumRange+1.0)-1.0-mean)*distance*distance; \
1518 mean=((MagickRealType) r->opacity+pixel.opacity)/2; \
1519 distance=(MagickRealType) r->opacity-(MagickRealType) pixel.opacity; \
1520 distance_squared+=QuantumScale*(3.0*((MagickRealType) \
1521 QuantumRange+1.0)-1.0-mean)*distance*distance; \
1522 if (distance_squared < ((MagickRealType) QuantumRange*(MagickRealType) \
1523 QuantumRange/25.0f)) \
1524 { \
1525 aggregate.red+=(weight)*r->red; \
1526 aggregate.green+=(weight)*r->green; \
1527 aggregate.blue+=(weight)*r->blue; \
1528 aggregate.opacity+=(weight)*r->opacity; \
1529 total_weight+=(weight); \
1530 } \
1531 r++;
1532#define EnhanceImageTag "Enhance/Image"
1533
cristyc4c8d132010-01-07 01:58:38 +00001534 CacheView
1535 *enhance_view,
1536 *image_view;
1537
cristy3ed852e2009-09-05 21:47:34 +00001538 Image
1539 *enhance_image;
1540
cristy3ed852e2009-09-05 21:47:34 +00001541 MagickBooleanType
1542 status;
1543
cristybb503372010-05-27 20:51:26 +00001544 MagickOffsetType
1545 progress;
1546
cristy3ed852e2009-09-05 21:47:34 +00001547 MagickPixelPacket
1548 zero;
1549
cristybb503372010-05-27 20:51:26 +00001550 ssize_t
1551 y;
1552
cristy3ed852e2009-09-05 21:47:34 +00001553 /*
1554 Initialize enhanced image attributes.
1555 */
1556 assert(image != (const Image *) NULL);
1557 assert(image->signature == MagickSignature);
1558 if (image->debug != MagickFalse)
1559 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1560 assert(exception != (ExceptionInfo *) NULL);
1561 assert(exception->signature == MagickSignature);
1562 if ((image->columns < 5) || (image->rows < 5))
1563 return((Image *) NULL);
1564 enhance_image=CloneImage(image,image->columns,image->rows,MagickTrue,
1565 exception);
1566 if (enhance_image == (Image *) NULL)
1567 return((Image *) NULL);
1568 if (SetImageStorageClass(enhance_image,DirectClass) == MagickFalse)
1569 {
1570 InheritException(exception,&enhance_image->exception);
1571 enhance_image=DestroyImage(enhance_image);
1572 return((Image *) NULL);
1573 }
1574 /*
1575 Enhance image.
1576 */
1577 status=MagickTrue;
1578 progress=0;
1579 (void) ResetMagickMemory(&zero,0,sizeof(zero));
1580 image_view=AcquireCacheView(image);
1581 enhance_view=AcquireCacheView(enhance_image);
cristyb5d5f722009-11-04 03:03:49 +00001582#if defined(MAGICKCORE_OPENMP_SUPPORT)
1583 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001584#endif
cristybb503372010-05-27 20:51:26 +00001585 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001586 {
1587 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001588 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00001589
cristy3ed852e2009-09-05 21:47:34 +00001590 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001591 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001592
cristy8d4629b2010-08-30 17:59:46 +00001593 register ssize_t
1594 x;
1595
cristy3ed852e2009-09-05 21:47:34 +00001596 /*
1597 Read another scan line.
1598 */
1599 if (status == MagickFalse)
1600 continue;
1601 p=GetCacheViewVirtualPixels(image_view,-2,y-2,image->columns+4,5,exception);
1602 q=QueueCacheViewAuthenticPixels(enhance_view,0,y,enhance_image->columns,1,
1603 exception);
1604 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
1605 {
1606 status=MagickFalse;
1607 continue;
1608 }
cristybb503372010-05-27 20:51:26 +00001609 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001610 {
1611 MagickPixelPacket
1612 aggregate;
1613
1614 MagickRealType
1615 distance,
1616 distance_squared,
1617 mean,
1618 total_weight;
1619
1620 PixelPacket
1621 pixel;
1622
1623 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001624 *restrict r;
cristy3ed852e2009-09-05 21:47:34 +00001625
1626 /*
1627 Compute weighted average of target pixel color components.
1628 */
1629 aggregate=zero;
1630 total_weight=0.0;
1631 r=p+2*(image->columns+4)+2;
1632 pixel=(*r);
1633 r=p;
1634 Enhance(5.0); Enhance(8.0); Enhance(10.0); Enhance(8.0); Enhance(5.0);
1635 r=p+(image->columns+4);
1636 Enhance(8.0); Enhance(20.0); Enhance(40.0); Enhance(20.0); Enhance(8.0);
1637 r=p+2*(image->columns+4);
1638 Enhance(10.0); Enhance(40.0); Enhance(80.0); Enhance(40.0); Enhance(10.0);
1639 r=p+3*(image->columns+4);
1640 Enhance(8.0); Enhance(20.0); Enhance(40.0); Enhance(20.0); Enhance(8.0);
1641 r=p+4*(image->columns+4);
1642 Enhance(5.0); Enhance(8.0); Enhance(10.0); Enhance(8.0); Enhance(5.0);
1643 q->red=(Quantum) ((aggregate.red+(total_weight/2)-1)/total_weight);
1644 q->green=(Quantum) ((aggregate.green+(total_weight/2)-1)/total_weight);
1645 q->blue=(Quantum) ((aggregate.blue+(total_weight/2)-1)/total_weight);
1646 q->opacity=(Quantum) ((aggregate.opacity+(total_weight/2)-1)/
1647 total_weight);
1648 p++;
1649 q++;
1650 }
1651 if (SyncCacheViewAuthenticPixels(enhance_view,exception) == MagickFalse)
1652 status=MagickFalse;
1653 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1654 {
1655 MagickBooleanType
1656 proceed;
1657
cristyb5d5f722009-11-04 03:03:49 +00001658#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00001659 #pragma omp critical (MagickCore_EnhanceImage)
1660#endif
1661 proceed=SetImageProgress(image,EnhanceImageTag,progress++,image->rows);
1662 if (proceed == MagickFalse)
1663 status=MagickFalse;
1664 }
1665 }
1666 enhance_view=DestroyCacheView(enhance_view);
1667 image_view=DestroyCacheView(image_view);
1668 return(enhance_image);
1669}
1670
1671/*
1672%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1673% %
1674% %
1675% %
1676% E q u a l i z e I m a g e %
1677% %
1678% %
1679% %
1680%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1681%
1682% EqualizeImage() applies a histogram equalization to the image.
1683%
1684% The format of the EqualizeImage method is:
1685%
1686% MagickBooleanType EqualizeImage(Image *image)
1687% MagickBooleanType EqualizeImageChannel(Image *image,
1688% const ChannelType channel)
1689%
1690% A description of each parameter follows:
1691%
1692% o image: the image.
1693%
1694% o channel: the channel.
1695%
1696*/
1697
1698MagickExport MagickBooleanType EqualizeImage(Image *image)
1699{
1700 return(EqualizeImageChannel(image,DefaultChannels));
1701}
1702
1703MagickExport MagickBooleanType EqualizeImageChannel(Image *image,
1704 const ChannelType channel)
1705{
1706#define EqualizeImageTag "Equalize/Image"
1707
cristyc4c8d132010-01-07 01:58:38 +00001708 CacheView
1709 *image_view;
1710
cristy3ed852e2009-09-05 21:47:34 +00001711 ExceptionInfo
1712 *exception;
1713
cristy3ed852e2009-09-05 21:47:34 +00001714 MagickBooleanType
1715 status;
1716
cristybb503372010-05-27 20:51:26 +00001717 MagickOffsetType
1718 progress;
1719
cristy3ed852e2009-09-05 21:47:34 +00001720 MagickPixelPacket
1721 black,
1722 *equalize_map,
1723 *histogram,
1724 intensity,
1725 *map,
1726 white;
1727
cristybb503372010-05-27 20:51:26 +00001728 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001729 i;
1730
cristybb503372010-05-27 20:51:26 +00001731 ssize_t
1732 y;
1733
cristy3ed852e2009-09-05 21:47:34 +00001734 /*
1735 Allocate and initialize histogram arrays.
1736 */
1737 assert(image != (Image *) NULL);
1738 assert(image->signature == MagickSignature);
1739 if (image->debug != MagickFalse)
1740 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1741 equalize_map=(MagickPixelPacket *) AcquireQuantumMemory(MaxMap+1UL,
1742 sizeof(*equalize_map));
1743 histogram=(MagickPixelPacket *) AcquireQuantumMemory(MaxMap+1UL,
1744 sizeof(*histogram));
1745 map=(MagickPixelPacket *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*map));
1746 if ((equalize_map == (MagickPixelPacket *) NULL) ||
1747 (histogram == (MagickPixelPacket *) NULL) ||
1748 (map == (MagickPixelPacket *) NULL))
1749 {
1750 if (map != (MagickPixelPacket *) NULL)
1751 map=(MagickPixelPacket *) RelinquishMagickMemory(map);
1752 if (histogram != (MagickPixelPacket *) NULL)
1753 histogram=(MagickPixelPacket *) RelinquishMagickMemory(histogram);
1754 if (equalize_map != (MagickPixelPacket *) NULL)
1755 equalize_map=(MagickPixelPacket *) RelinquishMagickMemory(equalize_map);
1756 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
1757 image->filename);
1758 }
1759 /*
1760 Form histogram.
1761 */
1762 (void) ResetMagickMemory(histogram,0,(MaxMap+1)*sizeof(*histogram));
1763 exception=(&image->exception);
cristybb503372010-05-27 20:51:26 +00001764 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001765 {
1766 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00001767 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00001768
1769 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001770 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00001771
cristybb503372010-05-27 20:51:26 +00001772 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001773 x;
1774
1775 p=GetVirtualPixels(image,0,y,image->columns,1,exception);
1776 if (p == (const PixelPacket *) NULL)
1777 break;
1778 indexes=GetVirtualIndexQueue(image);
cristybb503372010-05-27 20:51:26 +00001779 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001780 {
1781 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001782 histogram[ScaleQuantumToMap(GetRedPixelComponent(p))].red++;
cristy3ed852e2009-09-05 21:47:34 +00001783 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001784 histogram[ScaleQuantumToMap(GetGreenPixelComponent(p))].green++;
cristy3ed852e2009-09-05 21:47:34 +00001785 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001786 histogram[ScaleQuantumToMap(GetBluePixelComponent(p))].blue++;
cristy3ed852e2009-09-05 21:47:34 +00001787 if ((channel & OpacityChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001788 histogram[ScaleQuantumToMap(GetOpacityPixelComponent(p))].opacity++;
cristy3ed852e2009-09-05 21:47:34 +00001789 if (((channel & IndexChannel) != 0) &&
1790 (image->colorspace == CMYKColorspace))
1791 histogram[ScaleQuantumToMap(indexes[x])].index++;
1792 p++;
1793 }
1794 }
1795 /*
1796 Integrate the histogram to get the equalization map.
1797 */
1798 (void) ResetMagickMemory(&intensity,0,sizeof(intensity));
cristybb503372010-05-27 20:51:26 +00001799 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +00001800 {
1801 if ((channel & RedChannel) != 0)
1802 intensity.red+=histogram[i].red;
1803 if ((channel & GreenChannel) != 0)
1804 intensity.green+=histogram[i].green;
1805 if ((channel & BlueChannel) != 0)
1806 intensity.blue+=histogram[i].blue;
1807 if ((channel & OpacityChannel) != 0)
1808 intensity.opacity+=histogram[i].opacity;
1809 if (((channel & IndexChannel) != 0) &&
1810 (image->colorspace == CMYKColorspace))
1811 intensity.index+=histogram[i].index;
1812 map[i]=intensity;
1813 }
1814 black=map[0];
1815 white=map[(int) MaxMap];
1816 (void) ResetMagickMemory(equalize_map,0,(MaxMap+1)*sizeof(*equalize_map));
cristyb5d5f722009-11-04 03:03:49 +00001817#if defined(MAGICKCORE_OPENMP_SUPPORT)
1818 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001819#endif
cristybb503372010-05-27 20:51:26 +00001820 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +00001821 {
1822 if (((channel & RedChannel) != 0) && (white.red != black.red))
1823 equalize_map[i].red=(MagickRealType) ScaleMapToQuantum((MagickRealType)
1824 ((MaxMap*(map[i].red-black.red))/(white.red-black.red)));
1825 if (((channel & GreenChannel) != 0) && (white.green != black.green))
1826 equalize_map[i].green=(MagickRealType) ScaleMapToQuantum((MagickRealType)
1827 ((MaxMap*(map[i].green-black.green))/(white.green-black.green)));
1828 if (((channel & BlueChannel) != 0) && (white.blue != black.blue))
1829 equalize_map[i].blue=(MagickRealType) ScaleMapToQuantum((MagickRealType)
1830 ((MaxMap*(map[i].blue-black.blue))/(white.blue-black.blue)));
1831 if (((channel & OpacityChannel) != 0) && (white.opacity != black.opacity))
1832 equalize_map[i].opacity=(MagickRealType) ScaleMapToQuantum(
1833 (MagickRealType) ((MaxMap*(map[i].opacity-black.opacity))/
1834 (white.opacity-black.opacity)));
1835 if ((((channel & IndexChannel) != 0) &&
1836 (image->colorspace == CMYKColorspace)) &&
1837 (white.index != black.index))
1838 equalize_map[i].index=(MagickRealType) ScaleMapToQuantum((MagickRealType)
1839 ((MaxMap*(map[i].index-black.index))/(white.index-black.index)));
1840 }
1841 histogram=(MagickPixelPacket *) RelinquishMagickMemory(histogram);
1842 map=(MagickPixelPacket *) RelinquishMagickMemory(map);
1843 if (image->storage_class == PseudoClass)
1844 {
1845 /*
1846 Equalize colormap.
1847 */
cristyb5d5f722009-11-04 03:03:49 +00001848#if defined(MAGICKCORE_OPENMP_SUPPORT)
1849 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001850#endif
cristybb503372010-05-27 20:51:26 +00001851 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00001852 {
1853 if (((channel & RedChannel) != 0) && (white.red != black.red))
cristyce70c172010-01-07 17:15:30 +00001854 image->colormap[i].red=ClampToQuantum(equalize_map[
cristy3ed852e2009-09-05 21:47:34 +00001855 ScaleQuantumToMap(image->colormap[i].red)].red);
1856 if (((channel & GreenChannel) != 0) && (white.green != black.green))
cristyce70c172010-01-07 17:15:30 +00001857 image->colormap[i].green=ClampToQuantum(equalize_map[
cristy3ed852e2009-09-05 21:47:34 +00001858 ScaleQuantumToMap(image->colormap[i].green)].green);
1859 if (((channel & BlueChannel) != 0) && (white.blue != black.blue))
cristyce70c172010-01-07 17:15:30 +00001860 image->colormap[i].blue=ClampToQuantum(equalize_map[
cristy3ed852e2009-09-05 21:47:34 +00001861 ScaleQuantumToMap(image->colormap[i].blue)].blue);
1862 if (((channel & OpacityChannel) != 0) &&
1863 (white.opacity != black.opacity))
cristyce70c172010-01-07 17:15:30 +00001864 image->colormap[i].opacity=ClampToQuantum(equalize_map[
cristy3ed852e2009-09-05 21:47:34 +00001865 ScaleQuantumToMap(image->colormap[i].opacity)].opacity);
1866 }
1867 }
1868 /*
1869 Equalize image.
1870 */
1871 status=MagickTrue;
1872 progress=0;
1873 exception=(&image->exception);
1874 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00001875#if defined(MAGICKCORE_OPENMP_SUPPORT)
1876 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001877#endif
cristybb503372010-05-27 20:51:26 +00001878 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001879 {
1880 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00001881 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00001882
cristy3ed852e2009-09-05 21:47:34 +00001883 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001884 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001885
cristy8d4629b2010-08-30 17:59:46 +00001886 register ssize_t
1887 x;
1888
cristy3ed852e2009-09-05 21:47:34 +00001889 if (status == MagickFalse)
1890 continue;
1891 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
1892 if (q == (PixelPacket *) NULL)
1893 {
1894 status=MagickFalse;
1895 continue;
1896 }
1897 indexes=GetCacheViewAuthenticIndexQueue(image_view);
cristybb503372010-05-27 20:51:26 +00001898 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001899 {
1900 if (((channel & RedChannel) != 0) && (white.red != black.red))
cristyce70c172010-01-07 17:15:30 +00001901 q->red=ClampToQuantum(equalize_map[ScaleQuantumToMap(q->red)].red);
cristy3ed852e2009-09-05 21:47:34 +00001902 if (((channel & GreenChannel) != 0) && (white.green != black.green))
cristyce70c172010-01-07 17:15:30 +00001903 q->green=ClampToQuantum(equalize_map[ScaleQuantumToMap(
cristy3ed852e2009-09-05 21:47:34 +00001904 q->green)].green);
1905 if (((channel & BlueChannel) != 0) && (white.blue != black.blue))
cristyce70c172010-01-07 17:15:30 +00001906 q->blue=ClampToQuantum(equalize_map[ScaleQuantumToMap(q->blue)].blue);
cristy3ed852e2009-09-05 21:47:34 +00001907 if (((channel & OpacityChannel) != 0) && (white.opacity != black.opacity))
cristyce70c172010-01-07 17:15:30 +00001908 q->opacity=ClampToQuantum(equalize_map[ScaleQuantumToMap(
cristy3ed852e2009-09-05 21:47:34 +00001909 q->opacity)].opacity);
1910 if ((((channel & IndexChannel) != 0) &&
1911 (image->colorspace == CMYKColorspace)) &&
1912 (white.index != black.index))
cristyce70c172010-01-07 17:15:30 +00001913 indexes[x]=ClampToQuantum(equalize_map[ScaleQuantumToMap(
cristy3ed852e2009-09-05 21:47:34 +00001914 indexes[x])].index);
1915 q++;
1916 }
1917 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
1918 status=MagickFalse;
1919 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1920 {
1921 MagickBooleanType
1922 proceed;
1923
cristyb5d5f722009-11-04 03:03:49 +00001924#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00001925 #pragma omp critical (MagickCore_EqualizeImageChannel)
1926#endif
1927 proceed=SetImageProgress(image,EqualizeImageTag,progress++,image->rows);
1928 if (proceed == MagickFalse)
1929 status=MagickFalse;
1930 }
1931 }
1932 image_view=DestroyCacheView(image_view);
1933 equalize_map=(MagickPixelPacket *) RelinquishMagickMemory(equalize_map);
1934 return(status);
1935}
1936
1937/*
1938%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1939% %
1940% %
1941% %
1942% G a m m a I m a g e %
1943% %
1944% %
1945% %
1946%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1947%
1948% GammaImage() gamma-corrects a particular image channel. The same
1949% image viewed on different devices will have perceptual differences in the
1950% way the image's intensities are represented on the screen. Specify
1951% individual gamma levels for the red, green, and blue channels, or adjust
1952% all three with the gamma parameter. Values typically range from 0.8 to 2.3.
1953%
1954% You can also reduce the influence of a particular channel with a gamma
1955% value of 0.
1956%
1957% The format of the GammaImage method is:
1958%
cristya6360142011-03-23 23:08:04 +00001959% MagickBooleanType GammaImage(Image *image,const char *level)
cristy3ed852e2009-09-05 21:47:34 +00001960% MagickBooleanType GammaImageChannel(Image *image,
1961% const ChannelType channel,const double gamma)
1962%
1963% A description of each parameter follows:
1964%
1965% o image: the image.
1966%
1967% o channel: the channel.
1968%
cristya6360142011-03-23 23:08:04 +00001969% o level: the image gamma as a string (e.g. 1.6,1.2,1.0).
1970%
cristy3ed852e2009-09-05 21:47:34 +00001971% o gamma: the image gamma.
1972%
1973*/
1974MagickExport MagickBooleanType GammaImage(Image *image,const char *level)
1975{
1976 GeometryInfo
1977 geometry_info;
1978
1979 MagickPixelPacket
1980 gamma;
1981
1982 MagickStatusType
1983 flags,
1984 status;
1985
1986 assert(image != (Image *) NULL);
1987 assert(image->signature == MagickSignature);
1988 if (image->debug != MagickFalse)
1989 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1990 if (level == (char *) NULL)
1991 return(MagickFalse);
1992 flags=ParseGeometry(level,&geometry_info);
1993 gamma.red=geometry_info.rho;
1994 gamma.green=geometry_info.sigma;
1995 if ((flags & SigmaValue) == 0)
1996 gamma.green=gamma.red;
1997 gamma.blue=geometry_info.xi;
1998 if ((flags & XiValue) == 0)
1999 gamma.blue=gamma.red;
2000 if ((gamma.red == 1.0) && (gamma.green == 1.0) && (gamma.blue == 1.0))
2001 return(MagickTrue);
2002 if ((gamma.red == gamma.green) && (gamma.green == gamma.blue))
2003 status=GammaImageChannel(image,(const ChannelType) (RedChannel |
2004 GreenChannel | BlueChannel),(double) gamma.red);
2005 else
2006 {
2007 status=GammaImageChannel(image,RedChannel,(double) gamma.red);
2008 status|=GammaImageChannel(image,GreenChannel,(double) gamma.green);
2009 status|=GammaImageChannel(image,BlueChannel,(double) gamma.blue);
2010 }
2011 return(status != 0 ? MagickTrue : MagickFalse);
2012}
2013
2014MagickExport MagickBooleanType GammaImageChannel(Image *image,
2015 const ChannelType channel,const double gamma)
2016{
2017#define GammaCorrectImageTag "GammaCorrect/Image"
2018
cristyc4c8d132010-01-07 01:58:38 +00002019 CacheView
2020 *image_view;
2021
cristy3ed852e2009-09-05 21:47:34 +00002022 ExceptionInfo
2023 *exception;
2024
cristy3ed852e2009-09-05 21:47:34 +00002025 MagickBooleanType
2026 status;
2027
cristybb503372010-05-27 20:51:26 +00002028 MagickOffsetType
2029 progress;
2030
cristy3ed852e2009-09-05 21:47:34 +00002031 Quantum
2032 *gamma_map;
2033
cristybb503372010-05-27 20:51:26 +00002034 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002035 i;
2036
cristybb503372010-05-27 20:51:26 +00002037 ssize_t
2038 y;
2039
cristy3ed852e2009-09-05 21:47:34 +00002040 /*
2041 Allocate and initialize gamma maps.
2042 */
2043 assert(image != (Image *) NULL);
2044 assert(image->signature == MagickSignature);
2045 if (image->debug != MagickFalse)
2046 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2047 if (gamma == 1.0)
2048 return(MagickTrue);
2049 gamma_map=(Quantum *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*gamma_map));
2050 if (gamma_map == (Quantum *) NULL)
2051 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
2052 image->filename);
2053 (void) ResetMagickMemory(gamma_map,0,(MaxMap+1)*sizeof(*gamma_map));
2054 if (gamma != 0.0)
cristyb5d5f722009-11-04 03:03:49 +00002055#if defined(MAGICKCORE_OPENMP_SUPPORT)
2056 #pragma omp parallel for schedule(dynamic,4)
cristy3ed852e2009-09-05 21:47:34 +00002057#endif
cristybb503372010-05-27 20:51:26 +00002058 for (i=0; i <= (ssize_t) MaxMap; i++)
cristyce70c172010-01-07 17:15:30 +00002059 gamma_map[i]=ClampToQuantum((MagickRealType) ScaleMapToQuantum((
cristy3ed852e2009-09-05 21:47:34 +00002060 MagickRealType) (MaxMap*pow((double) i/MaxMap,1.0/gamma))));
2061 if (image->storage_class == PseudoClass)
2062 {
2063 /*
2064 Gamma-correct colormap.
2065 */
cristyb5d5f722009-11-04 03:03:49 +00002066#if defined(MAGICKCORE_OPENMP_SUPPORT)
2067 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002068#endif
cristybb503372010-05-27 20:51:26 +00002069 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00002070 {
2071 if ((channel & RedChannel) != 0)
2072 image->colormap[i].red=gamma_map[
2073 ScaleQuantumToMap(image->colormap[i].red)];
2074 if ((channel & GreenChannel) != 0)
2075 image->colormap[i].green=gamma_map[
2076 ScaleQuantumToMap(image->colormap[i].green)];
2077 if ((channel & BlueChannel) != 0)
2078 image->colormap[i].blue=gamma_map[
2079 ScaleQuantumToMap(image->colormap[i].blue)];
2080 if ((channel & OpacityChannel) != 0)
2081 {
2082 if (image->matte == MagickFalse)
2083 image->colormap[i].opacity=gamma_map[
2084 ScaleQuantumToMap(image->colormap[i].opacity)];
2085 else
2086 image->colormap[i].opacity=(Quantum) QuantumRange-
2087 gamma_map[ScaleQuantumToMap((Quantum) (QuantumRange-
2088 image->colormap[i].opacity))];
2089 }
2090 }
2091 }
2092 /*
2093 Gamma-correct image.
2094 */
2095 status=MagickTrue;
2096 progress=0;
2097 exception=(&image->exception);
2098 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00002099#if defined(MAGICKCORE_OPENMP_SUPPORT)
2100 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002101#endif
cristybb503372010-05-27 20:51:26 +00002102 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002103 {
2104 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00002105 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00002106
cristy3ed852e2009-09-05 21:47:34 +00002107 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00002108 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002109
cristy8d4629b2010-08-30 17:59:46 +00002110 register ssize_t
2111 x;
2112
cristy3ed852e2009-09-05 21:47:34 +00002113 if (status == MagickFalse)
2114 continue;
2115 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2116 if (q == (PixelPacket *) NULL)
2117 {
2118 status=MagickFalse;
2119 continue;
2120 }
2121 indexes=GetCacheViewAuthenticIndexQueue(image_view);
cristybb503372010-05-27 20:51:26 +00002122 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002123 {
cristy6cbd7f52009-10-17 16:06:51 +00002124 if (channel == DefaultChannels)
cristy3ed852e2009-09-05 21:47:34 +00002125 {
cristy6cbd7f52009-10-17 16:06:51 +00002126 q->red=gamma_map[ScaleQuantumToMap(q->red)];
2127 q->green=gamma_map[ScaleQuantumToMap(q->green)];
2128 q->blue=gamma_map[ScaleQuantumToMap(q->blue)];
2129 }
2130 else
2131 {
2132 if ((channel & RedChannel) != 0)
2133 q->red=gamma_map[ScaleQuantumToMap(q->red)];
2134 if ((channel & GreenChannel) != 0)
2135 q->green=gamma_map[ScaleQuantumToMap(q->green)];
2136 if ((channel & BlueChannel) != 0)
2137 q->blue=gamma_map[ScaleQuantumToMap(q->blue)];
2138 if ((channel & OpacityChannel) != 0)
2139 {
2140 if (image->matte == MagickFalse)
2141 q->opacity=gamma_map[ScaleQuantumToMap(q->opacity)];
2142 else
2143 q->opacity=(Quantum) QuantumRange-gamma_map[
cristy46f08202010-01-10 04:04:21 +00002144 ScaleQuantumToMap((Quantum) GetAlphaPixelComponent(q))];
cristy6cbd7f52009-10-17 16:06:51 +00002145 }
cristy3ed852e2009-09-05 21:47:34 +00002146 }
2147 q++;
2148 }
2149 if (((channel & IndexChannel) != 0) &&
2150 (image->colorspace == CMYKColorspace))
cristybb503372010-05-27 20:51:26 +00002151 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002152 indexes[x]=gamma_map[ScaleQuantumToMap(indexes[x])];
2153 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2154 status=MagickFalse;
2155 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2156 {
2157 MagickBooleanType
2158 proceed;
2159
cristyb5d5f722009-11-04 03:03:49 +00002160#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00002161 #pragma omp critical (MagickCore_GammaImageChannel)
2162#endif
2163 proceed=SetImageProgress(image,GammaCorrectImageTag,progress++,
2164 image->rows);
2165 if (proceed == MagickFalse)
2166 status=MagickFalse;
2167 }
2168 }
2169 image_view=DestroyCacheView(image_view);
2170 gamma_map=(Quantum *) RelinquishMagickMemory(gamma_map);
2171 if (image->gamma != 0.0)
2172 image->gamma*=gamma;
2173 return(status);
2174}
2175
2176/*
2177%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2178% %
2179% %
2180% %
2181% H a l d C l u t I m a g e %
2182% %
2183% %
2184% %
2185%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2186%
2187% HaldClutImage() applies a Hald color lookup table to the image. A Hald
2188% color lookup table is a 3-dimensional color cube mapped to 2 dimensions.
2189% Create it with the HALD coder. You can apply any color transformation to
2190% the Hald image and then use this method to apply the transform to the
2191% image.
2192%
2193% The format of the HaldClutImage method is:
2194%
2195% MagickBooleanType HaldClutImage(Image *image,Image *hald_image)
2196% MagickBooleanType HaldClutImageChannel(Image *image,
2197% const ChannelType channel,Image *hald_image)
2198%
2199% A description of each parameter follows:
2200%
2201% o image: the image, which is replaced by indexed CLUT values
2202%
2203% o hald_image: the color lookup table image for replacement color values.
2204%
2205% o channel: the channel.
2206%
2207*/
2208
2209static inline size_t MagickMin(const size_t x,const size_t y)
2210{
2211 if (x < y)
2212 return(x);
2213 return(y);
2214}
2215
2216MagickExport MagickBooleanType HaldClutImage(Image *image,
2217 const Image *hald_image)
2218{
2219 return(HaldClutImageChannel(image,DefaultChannels,hald_image));
2220}
2221
2222MagickExport MagickBooleanType HaldClutImageChannel(Image *image,
2223 const ChannelType channel,const Image *hald_image)
2224{
2225#define HaldClutImageTag "Clut/Image"
2226
2227 typedef struct _HaldInfo
2228 {
2229 MagickRealType
2230 x,
2231 y,
2232 z;
2233 } HaldInfo;
2234
cristyfa112112010-01-04 17:48:07 +00002235 CacheView
2236 *image_view;
2237
cristy3ed852e2009-09-05 21:47:34 +00002238 double
2239 width;
2240
2241 ExceptionInfo
2242 *exception;
2243
cristy3ed852e2009-09-05 21:47:34 +00002244 MagickBooleanType
2245 status;
2246
cristybb503372010-05-27 20:51:26 +00002247 MagickOffsetType
2248 progress;
2249
cristy3ed852e2009-09-05 21:47:34 +00002250 MagickPixelPacket
2251 zero;
2252
2253 ResampleFilter
cristyfa112112010-01-04 17:48:07 +00002254 **restrict resample_filter;
cristy3ed852e2009-09-05 21:47:34 +00002255
2256 size_t
2257 cube_size,
2258 length,
2259 level;
2260
cristybb503372010-05-27 20:51:26 +00002261 ssize_t
2262 y;
2263
cristy3ed852e2009-09-05 21:47:34 +00002264 assert(image != (Image *) NULL);
2265 assert(image->signature == MagickSignature);
2266 if (image->debug != MagickFalse)
2267 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2268 assert(hald_image != (Image *) NULL);
2269 assert(hald_image->signature == MagickSignature);
2270 if (SetImageStorageClass(image,DirectClass) == MagickFalse)
2271 return(MagickFalse);
2272 if (image->matte == MagickFalse)
2273 (void) SetImageAlphaChannel(image,OpaqueAlphaChannel);
2274 /*
2275 Hald clut image.
2276 */
2277 status=MagickTrue;
2278 progress=0;
2279 length=MagickMin(hald_image->columns,hald_image->rows);
2280 for (level=2; (level*level*level) < length; level++) ;
2281 level*=level;
2282 cube_size=level*level;
2283 width=(double) hald_image->columns;
2284 GetMagickPixelPacket(hald_image,&zero);
2285 exception=(&image->exception);
cristyb2a11ae2010-02-22 00:53:36 +00002286 resample_filter=AcquireResampleFilterThreadSet(hald_image,
2287 UndefinedVirtualPixelMethod,MagickTrue,exception);
cristy3ed852e2009-09-05 21:47:34 +00002288 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00002289#if defined(MAGICKCORE_OPENMP_SUPPORT)
2290 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002291#endif
cristybb503372010-05-27 20:51:26 +00002292 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002293 {
cristy5c9e6f22010-09-17 17:31:01 +00002294 const int
2295 id = GetOpenMPThreadId();
2296
cristy3ed852e2009-09-05 21:47:34 +00002297 double
2298 offset;
2299
2300 HaldInfo
2301 point;
2302
2303 MagickPixelPacket
2304 pixel,
2305 pixel1,
2306 pixel2,
2307 pixel3,
2308 pixel4;
2309
2310 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00002311 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00002312
cristy3ed852e2009-09-05 21:47:34 +00002313 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00002314 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002315
cristy8d4629b2010-08-30 17:59:46 +00002316 register ssize_t
2317 x;
2318
cristy3ed852e2009-09-05 21:47:34 +00002319 if (status == MagickFalse)
2320 continue;
2321 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2322 if (q == (PixelPacket *) NULL)
2323 {
2324 status=MagickFalse;
2325 continue;
2326 }
2327 indexes=GetCacheViewAuthenticIndexQueue(image_view);
2328 pixel=zero;
2329 pixel1=zero;
2330 pixel2=zero;
2331 pixel3=zero;
2332 pixel4=zero;
cristybb503372010-05-27 20:51:26 +00002333 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002334 {
2335 point.x=QuantumScale*(level-1.0)*q->red;
2336 point.y=QuantumScale*(level-1.0)*q->green;
2337 point.z=QuantumScale*(level-1.0)*q->blue;
2338 offset=point.x+level*floor(point.y)+cube_size*floor(point.z);
2339 point.x-=floor(point.x);
2340 point.y-=floor(point.y);
2341 point.z-=floor(point.z);
2342 (void) ResamplePixelColor(resample_filter[id],fmod(offset,width),
2343 floor(offset/width),&pixel1);
2344 (void) ResamplePixelColor(resample_filter[id],fmod(offset+level,width),
2345 floor((offset+level)/width),&pixel2);
2346 MagickPixelCompositeAreaBlend(&pixel1,pixel1.opacity,&pixel2,
2347 pixel2.opacity,point.y,&pixel3);
2348 offset+=cube_size;
2349 (void) ResamplePixelColor(resample_filter[id],fmod(offset,width),
2350 floor(offset/width),&pixel1);
2351 (void) ResamplePixelColor(resample_filter[id],fmod(offset+level,width),
2352 floor((offset+level)/width),&pixel2);
2353 MagickPixelCompositeAreaBlend(&pixel1,pixel1.opacity,&pixel2,
2354 pixel2.opacity,point.y,&pixel4);
2355 MagickPixelCompositeAreaBlend(&pixel3,pixel3.opacity,&pixel4,
2356 pixel4.opacity,point.z,&pixel);
2357 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00002358 SetRedPixelComponent(q,ClampRedPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00002359 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00002360 SetGreenPixelComponent(q,ClampGreenPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00002361 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00002362 SetBluePixelComponent(q,ClampBluePixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00002363 if (((channel & OpacityChannel) != 0) && (image->matte != MagickFalse))
cristyce70c172010-01-07 17:15:30 +00002364 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00002365 if (((channel & IndexChannel) != 0) &&
2366 (image->colorspace == CMYKColorspace))
cristyce70c172010-01-07 17:15:30 +00002367 indexes[x]=ClampToQuantum(pixel.index);
cristy3ed852e2009-09-05 21:47:34 +00002368 q++;
2369 }
2370 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2371 status=MagickFalse;
2372 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2373 {
2374 MagickBooleanType
2375 proceed;
2376
cristyb5d5f722009-11-04 03:03:49 +00002377#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00002378 #pragma omp critical (MagickCore_HaldClutImageChannel)
2379#endif
2380 proceed=SetImageProgress(image,HaldClutImageTag,progress++,image->rows);
2381 if (proceed == MagickFalse)
2382 status=MagickFalse;
2383 }
2384 }
2385 image_view=DestroyCacheView(image_view);
2386 resample_filter=DestroyResampleFilterThreadSet(resample_filter);
2387 return(status);
2388}
2389
2390/*
2391%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2392% %
2393% %
2394% %
2395% L e v e l I m a g e %
2396% %
2397% %
2398% %
2399%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2400%
2401% LevelImage() adjusts the levels of a particular image channel by
2402% scaling the colors falling between specified white and black points to
2403% the full available quantum range.
2404%
2405% The parameters provided represent the black, and white points. The black
2406% point specifies the darkest color in the image. Colors darker than the
2407% black point are set to zero. White point specifies the lightest color in
2408% the image. Colors brighter than the white point are set to the maximum
2409% quantum value.
2410%
2411% If a '!' flag is given, map black and white colors to the given levels
2412% rather than mapping those levels to black and white. See
2413% LevelizeImageChannel() and LevelizeImageChannel(), below.
2414%
2415% Gamma specifies a gamma correction to apply to the image.
2416%
2417% The format of the LevelImage method is:
2418%
2419% MagickBooleanType LevelImage(Image *image,const char *levels)
2420%
2421% A description of each parameter follows:
2422%
2423% o image: the image.
2424%
2425% o levels: Specify the levels where the black and white points have the
2426% range of 0-QuantumRange, and gamma has the range 0-10 (e.g. 10x90%+2).
2427% A '!' flag inverts the re-mapping.
2428%
2429*/
2430
2431MagickExport MagickBooleanType LevelImage(Image *image,const char *levels)
2432{
2433 double
2434 black_point,
2435 gamma,
2436 white_point;
2437
2438 GeometryInfo
2439 geometry_info;
2440
2441 MagickBooleanType
2442 status;
2443
2444 MagickStatusType
2445 flags;
2446
2447 /*
2448 Parse levels.
2449 */
2450 if (levels == (char *) NULL)
2451 return(MagickFalse);
2452 flags=ParseGeometry(levels,&geometry_info);
2453 black_point=geometry_info.rho;
2454 white_point=(double) QuantumRange;
2455 if ((flags & SigmaValue) != 0)
2456 white_point=geometry_info.sigma;
2457 gamma=1.0;
2458 if ((flags & XiValue) != 0)
2459 gamma=geometry_info.xi;
2460 if ((flags & PercentValue) != 0)
2461 {
2462 black_point*=(double) image->columns*image->rows/100.0;
2463 white_point*=(double) image->columns*image->rows/100.0;
2464 }
2465 if ((flags & SigmaValue) == 0)
2466 white_point=(double) QuantumRange-black_point;
2467 if ((flags & AspectValue ) == 0)
2468 status=LevelImageChannel(image,DefaultChannels,black_point,white_point,
2469 gamma);
2470 else
cristy308b4e62009-09-21 14:40:44 +00002471 status=LevelizeImage(image,black_point,white_point,gamma);
cristy3ed852e2009-09-05 21:47:34 +00002472 return(status);
2473}
2474
2475/*
2476%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2477% %
2478% %
2479% %
cristy308b4e62009-09-21 14:40:44 +00002480% L e v e l i z e I m a g e %
cristy3ed852e2009-09-05 21:47:34 +00002481% %
2482% %
2483% %
2484%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2485%
cristy308b4e62009-09-21 14:40:44 +00002486% LevelizeImage() applies the normal level operation to the image, spreading
2487% out the values between the black and white points over the entire range of
2488% values. Gamma correction is also applied after the values has been mapped.
cristy3ed852e2009-09-05 21:47:34 +00002489%
2490% It is typically used to improve image contrast, or to provide a controlled
2491% linear threshold for the image. If the black and white points are set to
2492% the minimum and maximum values found in the image, the image can be
2493% normalized. or by swapping black and white values, negate the image.
2494%
cristy308b4e62009-09-21 14:40:44 +00002495% The format of the LevelizeImage method is:
cristy3ed852e2009-09-05 21:47:34 +00002496%
cristy308b4e62009-09-21 14:40:44 +00002497% MagickBooleanType LevelizeImage(Image *image,const double black_point,
2498% const double white_point,const double gamma)
2499% MagickBooleanType LevelizeImageChannel(Image *image,
2500% const ChannelType channel,const double black_point,
2501% const double white_point,const double gamma)
cristy3ed852e2009-09-05 21:47:34 +00002502%
2503% A description of each parameter follows:
2504%
2505% o image: the image.
2506%
2507% o channel: the channel.
2508%
2509% o black_point: The level which is to be mapped to zero (black)
2510%
2511% o white_point: The level which is to be mapped to QuantiumRange (white)
2512%
2513% o gamma: adjust gamma by this factor before mapping values.
2514% use 1.0 for purely linear stretching of image color values
2515%
2516*/
2517MagickExport MagickBooleanType LevelImageChannel(Image *image,
2518 const ChannelType channel,const double black_point,const double white_point,
2519 const double gamma)
2520{
2521#define LevelImageTag "Level/Image"
cristybcfb0432010-05-06 01:45:33 +00002522#define LevelQuantum(x) (ClampToQuantum((MagickRealType) QuantumRange* \
cristyc1f508d2010-04-22 01:21:47 +00002523 pow(scale*((double) (x)-black_point),1.0/gamma)))
cristy3ed852e2009-09-05 21:47:34 +00002524
cristyc4c8d132010-01-07 01:58:38 +00002525 CacheView
2526 *image_view;
2527
cristy3ed852e2009-09-05 21:47:34 +00002528 ExceptionInfo
2529 *exception;
2530
cristy3ed852e2009-09-05 21:47:34 +00002531 MagickBooleanType
2532 status;
2533
cristybb503372010-05-27 20:51:26 +00002534 MagickOffsetType
2535 progress;
2536
anthony7fe39fc2010-04-06 03:19:20 +00002537 register double
2538 scale;
2539
cristy8d4629b2010-08-30 17:59:46 +00002540 register ssize_t
2541 i;
2542
cristybb503372010-05-27 20:51:26 +00002543 ssize_t
2544 y;
2545
cristy3ed852e2009-09-05 21:47:34 +00002546 /*
2547 Allocate and initialize levels map.
2548 */
2549 assert(image != (Image *) NULL);
2550 assert(image->signature == MagickSignature);
2551 if (image->debug != MagickFalse)
2552 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
cristy8d4629b2010-08-30 17:59:46 +00002553 scale=(white_point != black_point) ? 1.0/(white_point-black_point) : 1.0;
cristy3ed852e2009-09-05 21:47:34 +00002554 if (image->storage_class == PseudoClass)
cristyb5d5f722009-11-04 03:03:49 +00002555#if defined(MAGICKCORE_OPENMP_SUPPORT)
2556 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002557#endif
cristybb503372010-05-27 20:51:26 +00002558 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00002559 {
2560 /*
2561 Level colormap.
2562 */
2563 if ((channel & RedChannel) != 0)
cristyc1f508d2010-04-22 01:21:47 +00002564 image->colormap[i].red=LevelQuantum(image->colormap[i].red);
cristy3ed852e2009-09-05 21:47:34 +00002565 if ((channel & GreenChannel) != 0)
cristyc1f508d2010-04-22 01:21:47 +00002566 image->colormap[i].green=LevelQuantum(image->colormap[i].green);
cristy3ed852e2009-09-05 21:47:34 +00002567 if ((channel & BlueChannel) != 0)
cristyc1f508d2010-04-22 01:21:47 +00002568 image->colormap[i].blue=LevelQuantum(image->colormap[i].blue);
cristy3ed852e2009-09-05 21:47:34 +00002569 if ((channel & OpacityChannel) != 0)
cristyc1f508d2010-04-22 01:21:47 +00002570 image->colormap[i].opacity=LevelQuantum(image->colormap[i].opacity);
cristy3ed852e2009-09-05 21:47:34 +00002571 }
2572 /*
2573 Level image.
2574 */
2575 status=MagickTrue;
2576 progress=0;
2577 exception=(&image->exception);
2578 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00002579#if defined(MAGICKCORE_OPENMP_SUPPORT)
2580 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002581#endif
cristybb503372010-05-27 20:51:26 +00002582 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002583 {
2584 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00002585 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00002586
cristy3ed852e2009-09-05 21:47:34 +00002587 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00002588 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002589
cristy8d4629b2010-08-30 17:59:46 +00002590 register ssize_t
2591 x;
2592
cristy3ed852e2009-09-05 21:47:34 +00002593 if (status == MagickFalse)
2594 continue;
2595 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2596 if (q == (PixelPacket *) NULL)
2597 {
2598 status=MagickFalse;
2599 continue;
2600 }
2601 indexes=GetCacheViewAuthenticIndexQueue(image_view);
cristybb503372010-05-27 20:51:26 +00002602 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002603 {
2604 if ((channel & RedChannel) != 0)
cristyc1f508d2010-04-22 01:21:47 +00002605 q->red=LevelQuantum(q->red);
cristy3ed852e2009-09-05 21:47:34 +00002606 if ((channel & GreenChannel) != 0)
cristyc1f508d2010-04-22 01:21:47 +00002607 q->green=LevelQuantum(q->green);
cristy3ed852e2009-09-05 21:47:34 +00002608 if ((channel & BlueChannel) != 0)
cristyc1f508d2010-04-22 01:21:47 +00002609 q->blue=LevelQuantum(q->blue);
cristy3ed852e2009-09-05 21:47:34 +00002610 if (((channel & OpacityChannel) != 0) &&
2611 (image->matte == MagickTrue))
cristybb503372010-05-27 20:51:26 +00002612 q->opacity=(Quantum) (QuantumRange-LevelQuantum(QuantumRange-
2613 q->opacity));
cristy3ed852e2009-09-05 21:47:34 +00002614 if (((channel & IndexChannel) != 0) &&
2615 (image->colorspace == CMYKColorspace))
cristyc1f508d2010-04-22 01:21:47 +00002616 indexes[x]=LevelQuantum(indexes[x]);
cristy3ed852e2009-09-05 21:47:34 +00002617 q++;
2618 }
2619 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2620 status=MagickFalse;
2621 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2622 {
2623 MagickBooleanType
2624 proceed;
2625
cristyb5d5f722009-11-04 03:03:49 +00002626#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00002627 #pragma omp critical (MagickCore_LevelImageChannel)
2628#endif
2629 proceed=SetImageProgress(image,LevelImageTag,progress++,image->rows);
2630 if (proceed == MagickFalse)
2631 status=MagickFalse;
2632 }
2633 }
2634 image_view=DestroyCacheView(image_view);
2635 return(status);
2636}
2637
2638/*
2639%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2640% %
2641% %
2642% %
2643% L e v e l i z e I m a g e C h a n n e l %
2644% %
2645% %
2646% %
2647%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2648%
2649% LevelizeImageChannel() applies the reversed LevelImage() operation to just
2650% the specific channels specified. It compresses the full range of color
2651% values, so that they lie between the given black and white points. Gamma is
2652% applied before the values are mapped.
2653%
2654% LevelizeImageChannel() can be called with by using a +level command line
2655% API option, or using a '!' on a -level or LevelImage() geometry string.
2656%
2657% It can be used for example de-contrast a greyscale image to the exact
2658% levels specified. Or by using specific levels for each channel of an image
2659% you can convert a gray-scale image to any linear color gradient, according
2660% to those levels.
2661%
2662% The format of the LevelizeImageChannel method is:
2663%
2664% MagickBooleanType LevelizeImageChannel(Image *image,
2665% const ChannelType channel,const char *levels)
2666%
2667% A description of each parameter follows:
2668%
2669% o image: the image.
2670%
2671% o channel: the channel.
2672%
2673% o black_point: The level to map zero (black) to.
2674%
2675% o white_point: The level to map QuantiumRange (white) to.
2676%
2677% o gamma: adjust gamma by this factor before mapping values.
2678%
2679*/
cristyd1a2c0f2011-02-09 14:14:50 +00002680
2681MagickExport MagickBooleanType LevelizeImage(Image *image,
2682 const double black_point,const double white_point,const double gamma)
2683{
2684 MagickBooleanType
2685 status;
2686
2687 status=LevelizeImageChannel(image,DefaultChannels,black_point,white_point,
2688 gamma);
2689 return(status);
2690}
2691
cristy3ed852e2009-09-05 21:47:34 +00002692MagickExport MagickBooleanType LevelizeImageChannel(Image *image,
2693 const ChannelType channel,const double black_point,const double white_point,
2694 const double gamma)
2695{
2696#define LevelizeImageTag "Levelize/Image"
cristyce70c172010-01-07 17:15:30 +00002697#define LevelizeValue(x) (ClampToQuantum(((MagickRealType) \
cristy3ed852e2009-09-05 21:47:34 +00002698 pow((double)(QuantumScale*(x)),1.0/gamma))*(white_point-black_point)+ \
2699 black_point))
2700
cristyc4c8d132010-01-07 01:58:38 +00002701 CacheView
2702 *image_view;
2703
cristy3ed852e2009-09-05 21:47:34 +00002704 ExceptionInfo
2705 *exception;
2706
cristy3ed852e2009-09-05 21:47:34 +00002707 MagickBooleanType
2708 status;
2709
cristybb503372010-05-27 20:51:26 +00002710 MagickOffsetType
2711 progress;
2712
2713 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002714 i;
2715
cristybb503372010-05-27 20:51:26 +00002716 ssize_t
2717 y;
2718
cristy3ed852e2009-09-05 21:47:34 +00002719 /*
2720 Allocate and initialize levels map.
2721 */
2722 assert(image != (Image *) NULL);
2723 assert(image->signature == MagickSignature);
2724 if (image->debug != MagickFalse)
2725 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2726 if (image->storage_class == PseudoClass)
cristyb5d5f722009-11-04 03:03:49 +00002727#if defined(MAGICKCORE_OPENMP_SUPPORT)
2728 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002729#endif
cristybb503372010-05-27 20:51:26 +00002730 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00002731 {
2732 /*
2733 Level colormap.
2734 */
2735 if ((channel & RedChannel) != 0)
2736 image->colormap[i].red=LevelizeValue(image->colormap[i].red);
2737 if ((channel & GreenChannel) != 0)
2738 image->colormap[i].green=LevelizeValue(image->colormap[i].green);
2739 if ((channel & BlueChannel) != 0)
2740 image->colormap[i].blue=LevelizeValue(image->colormap[i].blue);
2741 if ((channel & OpacityChannel) != 0)
2742 image->colormap[i].opacity=LevelizeValue(image->colormap[i].opacity);
2743 }
2744 /*
2745 Level image.
2746 */
2747 status=MagickTrue;
2748 progress=0;
2749 exception=(&image->exception);
2750 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00002751#if defined(MAGICKCORE_OPENMP_SUPPORT)
2752 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002753#endif
cristybb503372010-05-27 20:51:26 +00002754 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002755 {
2756 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00002757 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00002758
cristy3ed852e2009-09-05 21:47:34 +00002759 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00002760 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002761
cristy8d4629b2010-08-30 17:59:46 +00002762 register ssize_t
2763 x;
2764
cristy3ed852e2009-09-05 21:47:34 +00002765 if (status == MagickFalse)
2766 continue;
2767 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2768 if (q == (PixelPacket *) NULL)
2769 {
2770 status=MagickFalse;
2771 continue;
2772 }
2773 indexes=GetCacheViewAuthenticIndexQueue(image_view);
cristybb503372010-05-27 20:51:26 +00002774 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002775 {
2776 if ((channel & RedChannel) != 0)
2777 q->red=LevelizeValue(q->red);
2778 if ((channel & GreenChannel) != 0)
2779 q->green=LevelizeValue(q->green);
2780 if ((channel & BlueChannel) != 0)
2781 q->blue=LevelizeValue(q->blue);
2782 if (((channel & OpacityChannel) != 0) &&
2783 (image->matte == MagickTrue))
2784 q->opacity=LevelizeValue(q->opacity);
2785 if (((channel & IndexChannel) != 0) &&
2786 (image->colorspace == CMYKColorspace))
2787 indexes[x]=LevelizeValue(indexes[x]);
2788 q++;
2789 }
2790 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2791 status=MagickFalse;
2792 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2793 {
2794 MagickBooleanType
2795 proceed;
2796
cristyb5d5f722009-11-04 03:03:49 +00002797#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00002798 #pragma omp critical (MagickCore_LevelizeImageChannel)
2799#endif
2800 proceed=SetImageProgress(image,LevelizeImageTag,progress++,image->rows);
2801 if (proceed == MagickFalse)
2802 status=MagickFalse;
2803 }
2804 }
cristy8d4629b2010-08-30 17:59:46 +00002805 image_view=DestroyCacheView(image_view);
cristy3ed852e2009-09-05 21:47:34 +00002806 return(status);
2807}
2808
2809/*
2810%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2811% %
2812% %
2813% %
2814% L e v e l I m a g e C o l o r s %
2815% %
2816% %
2817% %
2818%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2819%
cristyee0f8d72009-09-19 00:58:29 +00002820% LevelImageColor() maps the given color to "black" and "white" values,
2821% linearly spreading out the colors, and level values on a channel by channel
2822% bases, as per LevelImage(). The given colors allows you to specify
glennrp1e7f7bc2011-03-02 19:25:28 +00002823% different level ranges for each of the color channels separately.
cristy3ed852e2009-09-05 21:47:34 +00002824%
2825% If the boolean 'invert' is set true the image values will modifyed in the
2826% reverse direction. That is any existing "black" and "white" colors in the
2827% image will become the color values given, with all other values compressed
2828% appropriatally. This effectivally maps a greyscale gradient into the given
2829% color gradient.
2830%
cristy308b4e62009-09-21 14:40:44 +00002831% The format of the LevelColorsImageChannel method is:
cristy3ed852e2009-09-05 21:47:34 +00002832%
cristy308b4e62009-09-21 14:40:44 +00002833% MagickBooleanType LevelColorsImage(Image *image,
cristyee0f8d72009-09-19 00:58:29 +00002834% const MagickPixelPacket *black_color,
2835% const MagickPixelPacket *white_color,const MagickBooleanType invert)
cristy308b4e62009-09-21 14:40:44 +00002836% MagickBooleanType LevelColorsImageChannel(Image *image,
2837% const ChannelType channel,const MagickPixelPacket *black_color,
2838% const MagickPixelPacket *white_color,const MagickBooleanType invert)
cristy3ed852e2009-09-05 21:47:34 +00002839%
2840% A description of each parameter follows:
2841%
2842% o image: the image.
2843%
2844% o channel: the channel.
2845%
2846% o black_color: The color to map black to/from
2847%
2848% o white_point: The color to map white to/from
2849%
2850% o invert: if true map the colors (levelize), rather than from (level)
2851%
2852*/
cristy308b4e62009-09-21 14:40:44 +00002853
2854MagickExport MagickBooleanType LevelColorsImage(Image *image,
cristy3ed852e2009-09-05 21:47:34 +00002855 const MagickPixelPacket *black_color,const MagickPixelPacket *white_color,
2856 const MagickBooleanType invert)
2857{
cristy308b4e62009-09-21 14:40:44 +00002858 MagickBooleanType
2859 status;
cristy3ed852e2009-09-05 21:47:34 +00002860
cristy308b4e62009-09-21 14:40:44 +00002861 status=LevelColorsImageChannel(image,DefaultChannels,black_color,white_color,
2862 invert);
2863 return(status);
2864}
2865
2866MagickExport MagickBooleanType LevelColorsImageChannel(Image *image,
2867 const ChannelType channel,const MagickPixelPacket *black_color,
2868 const MagickPixelPacket *white_color,const MagickBooleanType invert)
2869{
cristy3ed852e2009-09-05 21:47:34 +00002870 MagickStatusType
2871 status;
2872
2873 /*
2874 Allocate and initialize levels map.
2875 */
2876 assert(image != (Image *) NULL);
2877 assert(image->signature == MagickSignature);
2878 if (image->debug != MagickFalse)
2879 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2880 status=MagickFalse;
2881 if (invert == MagickFalse)
2882 {
2883 if ((channel & RedChannel) != 0)
2884 status|=LevelImageChannel(image,RedChannel,
2885 black_color->red,white_color->red,(double) 1.0);
2886 if ((channel & GreenChannel) != 0)
2887 status|=LevelImageChannel(image,GreenChannel,
2888 black_color->green,white_color->green,(double) 1.0);
2889 if ((channel & BlueChannel) != 0)
2890 status|=LevelImageChannel(image,BlueChannel,
2891 black_color->blue,white_color->blue,(double) 1.0);
2892 if (((channel & OpacityChannel) != 0) &&
2893 (image->matte == MagickTrue))
2894 status|=LevelImageChannel(image,OpacityChannel,
2895 black_color->opacity,white_color->opacity,(double) 1.0);
2896 if (((channel & IndexChannel) != 0) &&
2897 (image->colorspace == CMYKColorspace))
2898 status|=LevelImageChannel(image,IndexChannel,
2899 black_color->index,white_color->index,(double) 1.0);
2900 }
2901 else
2902 {
2903 if ((channel & RedChannel) != 0)
2904 status|=LevelizeImageChannel(image,RedChannel,
2905 black_color->red,white_color->red,(double) 1.0);
2906 if ((channel & GreenChannel) != 0)
2907 status|=LevelizeImageChannel(image,GreenChannel,
2908 black_color->green,white_color->green,(double) 1.0);
2909 if ((channel & BlueChannel) != 0)
2910 status|=LevelizeImageChannel(image,BlueChannel,
2911 black_color->blue,white_color->blue,(double) 1.0);
2912 if (((channel & OpacityChannel) != 0) &&
2913 (image->matte == MagickTrue))
2914 status|=LevelizeImageChannel(image,OpacityChannel,
2915 black_color->opacity,white_color->opacity,(double) 1.0);
2916 if (((channel & IndexChannel) != 0) &&
2917 (image->colorspace == CMYKColorspace))
2918 status|=LevelizeImageChannel(image,IndexChannel,
2919 black_color->index,white_color->index,(double) 1.0);
2920 }
2921 return(status == 0 ? MagickFalse : MagickTrue);
2922}
2923
2924/*
2925%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2926% %
2927% %
2928% %
2929% L i n e a r S t r e t c h I m a g e %
2930% %
2931% %
2932% %
2933%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2934%
2935% The LinearStretchImage() discards any pixels below the black point and
2936% above the white point and levels the remaining pixels.
2937%
2938% The format of the LinearStretchImage method is:
2939%
2940% MagickBooleanType LinearStretchImage(Image *image,
2941% const double black_point,const double white_point)
2942%
2943% A description of each parameter follows:
2944%
2945% o image: the image.
2946%
2947% o black_point: the black point.
2948%
2949% o white_point: the white point.
2950%
2951*/
2952MagickExport MagickBooleanType LinearStretchImage(Image *image,
2953 const double black_point,const double white_point)
2954{
2955#define LinearStretchImageTag "LinearStretch/Image"
2956
2957 ExceptionInfo
2958 *exception;
2959
cristy3ed852e2009-09-05 21:47:34 +00002960 MagickBooleanType
2961 status;
2962
2963 MagickRealType
2964 *histogram,
2965 intensity;
2966
cristy8d4629b2010-08-30 17:59:46 +00002967 ssize_t
2968 black,
2969 white,
2970 y;
2971
cristy3ed852e2009-09-05 21:47:34 +00002972 /*
2973 Allocate histogram and linear map.
2974 */
2975 assert(image != (Image *) NULL);
2976 assert(image->signature == MagickSignature);
2977 histogram=(MagickRealType *) AcquireQuantumMemory(MaxMap+1UL,
2978 sizeof(*histogram));
2979 if (histogram == (MagickRealType *) NULL)
2980 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
2981 image->filename);
2982 /*
2983 Form histogram.
2984 */
2985 (void) ResetMagickMemory(histogram,0,(MaxMap+1)*sizeof(*histogram));
2986 exception=(&image->exception);
cristybb503372010-05-27 20:51:26 +00002987 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002988 {
2989 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00002990 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00002991
cristybb503372010-05-27 20:51:26 +00002992 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002993 x;
2994
2995 p=GetVirtualPixels(image,0,y,image->columns,1,exception);
2996 if (p == (const PixelPacket *) NULL)
2997 break;
cristybb503372010-05-27 20:51:26 +00002998 for (x=(ssize_t) image->columns-1; x >= 0; x--)
cristy3ed852e2009-09-05 21:47:34 +00002999 {
3000 histogram[ScaleQuantumToMap(PixelIntensityToQuantum(p))]++;
3001 p++;
3002 }
3003 }
3004 /*
3005 Find the histogram boundaries by locating the black and white point levels.
3006 */
cristy3ed852e2009-09-05 21:47:34 +00003007 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00003008 for (black=0; black < (ssize_t) MaxMap; black++)
cristy3ed852e2009-09-05 21:47:34 +00003009 {
3010 intensity+=histogram[black];
3011 if (intensity >= black_point)
3012 break;
3013 }
3014 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00003015 for (white=(ssize_t) MaxMap; white != 0; white--)
cristy3ed852e2009-09-05 21:47:34 +00003016 {
3017 intensity+=histogram[white];
3018 if (intensity >= white_point)
3019 break;
3020 }
3021 histogram=(MagickRealType *) RelinquishMagickMemory(histogram);
3022 status=LevelImageChannel(image,DefaultChannels,(double) black,(double) white,
3023 1.0);
3024 return(status);
3025}
3026
3027/*
3028%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3029% %
3030% %
3031% %
3032% M o d u l a t e I m a g e %
3033% %
3034% %
3035% %
3036%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3037%
3038% ModulateImage() lets you control the brightness, saturation, and hue
3039% of an image. Modulate represents the brightness, saturation, and hue
3040% as one parameter (e.g. 90,150,100). If the image colorspace is HSL, the
3041% modulation is lightness, saturation, and hue. And if the colorspace is
3042% HWB, use blackness, whiteness, and hue.
3043%
3044% The format of the ModulateImage method is:
3045%
3046% MagickBooleanType ModulateImage(Image *image,const char *modulate)
3047%
3048% A description of each parameter follows:
3049%
3050% o image: the image.
3051%
3052% o modulate: Define the percent change in brightness, saturation, and
3053% hue.
3054%
3055*/
3056
3057static void ModulateHSB(const double percent_hue,
3058 const double percent_saturation,const double percent_brightness,
3059 Quantum *red,Quantum *green,Quantum *blue)
3060{
3061 double
3062 brightness,
3063 hue,
3064 saturation;
3065
3066 /*
3067 Increase or decrease color brightness, saturation, or hue.
3068 */
3069 assert(red != (Quantum *) NULL);
3070 assert(green != (Quantum *) NULL);
3071 assert(blue != (Quantum *) NULL);
3072 ConvertRGBToHSB(*red,*green,*blue,&hue,&saturation,&brightness);
3073 hue+=0.5*(0.01*percent_hue-1.0);
3074 while (hue < 0.0)
3075 hue+=1.0;
3076 while (hue > 1.0)
3077 hue-=1.0;
3078 saturation*=0.01*percent_saturation;
3079 brightness*=0.01*percent_brightness;
3080 ConvertHSBToRGB(hue,saturation,brightness,red,green,blue);
3081}
3082
3083static void ModulateHSL(const double percent_hue,
3084 const double percent_saturation,const double percent_lightness,
3085 Quantum *red,Quantum *green,Quantum *blue)
3086{
3087 double
3088 hue,
3089 lightness,
3090 saturation;
3091
3092 /*
3093 Increase or decrease color lightness, saturation, or hue.
3094 */
3095 assert(red != (Quantum *) NULL);
3096 assert(green != (Quantum *) NULL);
3097 assert(blue != (Quantum *) NULL);
3098 ConvertRGBToHSL(*red,*green,*blue,&hue,&saturation,&lightness);
3099 hue+=0.5*(0.01*percent_hue-1.0);
3100 while (hue < 0.0)
3101 hue+=1.0;
3102 while (hue > 1.0)
3103 hue-=1.0;
3104 saturation*=0.01*percent_saturation;
3105 lightness*=0.01*percent_lightness;
3106 ConvertHSLToRGB(hue,saturation,lightness,red,green,blue);
3107}
3108
3109static void ModulateHWB(const double percent_hue,const double percent_whiteness, const double percent_blackness,Quantum *red,Quantum *green,Quantum *blue)
3110{
3111 double
3112 blackness,
3113 hue,
3114 whiteness;
3115
3116 /*
3117 Increase or decrease color blackness, whiteness, or hue.
3118 */
3119 assert(red != (Quantum *) NULL);
3120 assert(green != (Quantum *) NULL);
3121 assert(blue != (Quantum *) NULL);
3122 ConvertRGBToHWB(*red,*green,*blue,&hue,&whiteness,&blackness);
3123 hue+=0.5*(0.01*percent_hue-1.0);
3124 while (hue < 0.0)
3125 hue+=1.0;
3126 while (hue > 1.0)
3127 hue-=1.0;
3128 blackness*=0.01*percent_blackness;
3129 whiteness*=0.01*percent_whiteness;
3130 ConvertHWBToRGB(hue,whiteness,blackness,red,green,blue);
3131}
3132
3133MagickExport MagickBooleanType ModulateImage(Image *image,const char *modulate)
3134{
3135#define ModulateImageTag "Modulate/Image"
3136
cristyc4c8d132010-01-07 01:58:38 +00003137 CacheView
3138 *image_view;
3139
cristy3ed852e2009-09-05 21:47:34 +00003140 ColorspaceType
3141 colorspace;
3142
3143 const char
3144 *artifact;
3145
3146 double
3147 percent_brightness,
3148 percent_hue,
3149 percent_saturation;
3150
3151 ExceptionInfo
3152 *exception;
3153
3154 GeometryInfo
3155 geometry_info;
3156
cristy3ed852e2009-09-05 21:47:34 +00003157 MagickBooleanType
3158 status;
3159
cristybb503372010-05-27 20:51:26 +00003160 MagickOffsetType
3161 progress;
3162
cristy3ed852e2009-09-05 21:47:34 +00003163 MagickStatusType
3164 flags;
3165
cristybb503372010-05-27 20:51:26 +00003166 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003167 i;
3168
cristybb503372010-05-27 20:51:26 +00003169 ssize_t
3170 y;
3171
cristy3ed852e2009-09-05 21:47:34 +00003172 /*
cristy2b726bd2010-01-11 01:05:39 +00003173 Initialize modulate table.
cristy3ed852e2009-09-05 21:47:34 +00003174 */
3175 assert(image != (Image *) NULL);
3176 assert(image->signature == MagickSignature);
3177 if (image->debug != MagickFalse)
3178 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3179 if (modulate == (char *) NULL)
3180 return(MagickFalse);
3181 flags=ParseGeometry(modulate,&geometry_info);
3182 percent_brightness=geometry_info.rho;
3183 percent_saturation=geometry_info.sigma;
3184 if ((flags & SigmaValue) == 0)
3185 percent_saturation=100.0;
3186 percent_hue=geometry_info.xi;
3187 if ((flags & XiValue) == 0)
3188 percent_hue=100.0;
3189 colorspace=UndefinedColorspace;
3190 artifact=GetImageArtifact(image,"modulate:colorspace");
3191 if (artifact != (const char *) NULL)
3192 colorspace=(ColorspaceType) ParseMagickOption(MagickColorspaceOptions,
3193 MagickFalse,artifact);
3194 if (image->storage_class == PseudoClass)
3195 {
3196 /*
3197 Modulate colormap.
3198 */
cristyb5d5f722009-11-04 03:03:49 +00003199#if defined(MAGICKCORE_OPENMP_SUPPORT)
3200 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003201#endif
cristybb503372010-05-27 20:51:26 +00003202 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00003203 switch (colorspace)
3204 {
3205 case HSBColorspace:
3206 {
3207 ModulateHSB(percent_hue,percent_saturation,percent_brightness,
3208 &image->colormap[i].red,&image->colormap[i].green,
3209 &image->colormap[i].blue);
3210 break;
3211 }
3212 case HSLColorspace:
3213 default:
3214 {
3215 ModulateHSL(percent_hue,percent_saturation,percent_brightness,
3216 &image->colormap[i].red,&image->colormap[i].green,
3217 &image->colormap[i].blue);
3218 break;
3219 }
3220 case HWBColorspace:
3221 {
3222 ModulateHWB(percent_hue,percent_saturation,percent_brightness,
3223 &image->colormap[i].red,&image->colormap[i].green,
3224 &image->colormap[i].blue);
3225 break;
3226 }
3227 }
3228 }
3229 /*
3230 Modulate image.
3231 */
3232 status=MagickTrue;
3233 progress=0;
3234 exception=(&image->exception);
3235 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00003236#if defined(MAGICKCORE_OPENMP_SUPPORT)
3237 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003238#endif
cristybb503372010-05-27 20:51:26 +00003239 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003240 {
cristy3ed852e2009-09-05 21:47:34 +00003241 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00003242 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003243
cristy8d4629b2010-08-30 17:59:46 +00003244 register ssize_t
3245 x;
3246
cristy3ed852e2009-09-05 21:47:34 +00003247 if (status == MagickFalse)
3248 continue;
3249 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
3250 if (q == (PixelPacket *) NULL)
3251 {
3252 status=MagickFalse;
3253 continue;
3254 }
cristybb503372010-05-27 20:51:26 +00003255 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003256 {
3257 switch (colorspace)
3258 {
3259 case HSBColorspace:
3260 {
3261 ModulateHSB(percent_hue,percent_saturation,percent_brightness,
3262 &q->red,&q->green,&q->blue);
3263 break;
3264 }
3265 case HSLColorspace:
3266 default:
3267 {
3268 ModulateHSL(percent_hue,percent_saturation,percent_brightness,
3269 &q->red,&q->green,&q->blue);
3270 break;
3271 }
3272 case HWBColorspace:
3273 {
3274 ModulateHWB(percent_hue,percent_saturation,percent_brightness,
3275 &q->red,&q->green,&q->blue);
3276 break;
3277 }
3278 }
3279 q++;
3280 }
3281 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
3282 status=MagickFalse;
3283 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3284 {
3285 MagickBooleanType
3286 proceed;
3287
cristyb5d5f722009-11-04 03:03:49 +00003288#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00003289 #pragma omp critical (MagickCore_ModulateImage)
3290#endif
3291 proceed=SetImageProgress(image,ModulateImageTag,progress++,image->rows);
3292 if (proceed == MagickFalse)
3293 status=MagickFalse;
3294 }
3295 }
3296 image_view=DestroyCacheView(image_view);
3297 return(status);
3298}
3299
3300/*
3301%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3302% %
3303% %
3304% %
3305% N e g a t e I m a g e %
3306% %
3307% %
3308% %
3309%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3310%
3311% NegateImage() negates the colors in the reference image. The grayscale
3312% option means that only grayscale values within the image are negated.
3313%
3314% The format of the NegateImageChannel method is:
3315%
3316% MagickBooleanType NegateImage(Image *image,
3317% const MagickBooleanType grayscale)
3318% MagickBooleanType NegateImageChannel(Image *image,
3319% const ChannelType channel,const MagickBooleanType grayscale)
3320%
3321% A description of each parameter follows:
3322%
3323% o image: the image.
3324%
3325% o channel: the channel.
3326%
3327% o grayscale: If MagickTrue, only negate grayscale pixels within the image.
3328%
3329*/
3330
3331MagickExport MagickBooleanType NegateImage(Image *image,
3332 const MagickBooleanType grayscale)
3333{
3334 MagickBooleanType
3335 status;
3336
3337 status=NegateImageChannel(image,DefaultChannels,grayscale);
3338 return(status);
3339}
3340
3341MagickExport MagickBooleanType NegateImageChannel(Image *image,
3342 const ChannelType channel,const MagickBooleanType grayscale)
3343{
3344#define NegateImageTag "Negate/Image"
3345
cristyc4c8d132010-01-07 01:58:38 +00003346 CacheView
3347 *image_view;
3348
cristy3ed852e2009-09-05 21:47:34 +00003349 ExceptionInfo
3350 *exception;
3351
cristy3ed852e2009-09-05 21:47:34 +00003352 MagickBooleanType
3353 status;
3354
cristybb503372010-05-27 20:51:26 +00003355 MagickOffsetType
3356 progress;
3357
3358 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003359 i;
3360
cristybb503372010-05-27 20:51:26 +00003361 ssize_t
3362 y;
3363
cristy3ed852e2009-09-05 21:47:34 +00003364 assert(image != (Image *) NULL);
3365 assert(image->signature == MagickSignature);
3366 if (image->debug != MagickFalse)
3367 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3368 if (image->storage_class == PseudoClass)
3369 {
3370 /*
3371 Negate colormap.
3372 */
cristyb5d5f722009-11-04 03:03:49 +00003373#if defined(MAGICKCORE_OPENMP_SUPPORT)
3374 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003375#endif
cristybb503372010-05-27 20:51:26 +00003376 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00003377 {
3378 if (grayscale != MagickFalse)
3379 if ((image->colormap[i].red != image->colormap[i].green) ||
3380 (image->colormap[i].green != image->colormap[i].blue))
3381 continue;
3382 if ((channel & RedChannel) != 0)
3383 image->colormap[i].red=(Quantum) QuantumRange-
3384 image->colormap[i].red;
3385 if ((channel & GreenChannel) != 0)
3386 image->colormap[i].green=(Quantum) QuantumRange-
3387 image->colormap[i].green;
3388 if ((channel & BlueChannel) != 0)
3389 image->colormap[i].blue=(Quantum) QuantumRange-
3390 image->colormap[i].blue;
3391 }
3392 }
3393 /*
3394 Negate image.
3395 */
3396 status=MagickTrue;
3397 progress=0;
3398 exception=(&image->exception);
3399 image_view=AcquireCacheView(image);
3400 if (grayscale != MagickFalse)
3401 {
cristyb5d5f722009-11-04 03:03:49 +00003402#if defined(MAGICKCORE_OPENMP_SUPPORT)
3403 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003404#endif
cristybb503372010-05-27 20:51:26 +00003405 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003406 {
3407 MagickBooleanType
3408 sync;
3409
3410 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00003411 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00003412
cristy3ed852e2009-09-05 21:47:34 +00003413 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00003414 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003415
cristy8d4629b2010-08-30 17:59:46 +00003416 register ssize_t
3417 x;
3418
cristy3ed852e2009-09-05 21:47:34 +00003419 if (status == MagickFalse)
3420 continue;
3421 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,
3422 exception);
3423 if (q == (PixelPacket *) NULL)
3424 {
3425 status=MagickFalse;
3426 continue;
3427 }
3428 indexes=GetCacheViewAuthenticIndexQueue(image_view);
cristybb503372010-05-27 20:51:26 +00003429 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003430 {
3431 if ((q->red != q->green) || (q->green != q->blue))
3432 {
3433 q++;
3434 continue;
3435 }
3436 if ((channel & RedChannel) != 0)
3437 q->red=(Quantum) QuantumRange-q->red;
3438 if ((channel & GreenChannel) != 0)
3439 q->green=(Quantum) QuantumRange-q->green;
3440 if ((channel & BlueChannel) != 0)
3441 q->blue=(Quantum) QuantumRange-q->blue;
3442 if ((channel & OpacityChannel) != 0)
3443 q->opacity=(Quantum) QuantumRange-q->opacity;
3444 if (((channel & IndexChannel) != 0) &&
3445 (image->colorspace == CMYKColorspace))
3446 indexes[x]=(IndexPacket) QuantumRange-indexes[x];
3447 q++;
3448 }
3449 sync=SyncCacheViewAuthenticPixels(image_view,exception);
3450 if (sync == MagickFalse)
3451 status=MagickFalse;
3452 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3453 {
3454 MagickBooleanType
3455 proceed;
3456
cristyb5d5f722009-11-04 03:03:49 +00003457#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00003458 #pragma omp critical (MagickCore_NegateImageChannel)
3459#endif
3460 proceed=SetImageProgress(image,NegateImageTag,progress++,
3461 image->rows);
3462 if (proceed == MagickFalse)
3463 status=MagickFalse;
3464 }
3465 }
3466 image_view=DestroyCacheView(image_view);
3467 return(MagickTrue);
3468 }
3469 /*
3470 Negate image.
3471 */
cristyb5d5f722009-11-04 03:03:49 +00003472#if defined(MAGICKCORE_OPENMP_SUPPORT)
3473 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003474#endif
cristybb503372010-05-27 20:51:26 +00003475 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003476 {
3477 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00003478 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00003479
cristy3ed852e2009-09-05 21:47:34 +00003480 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00003481 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003482
cristy8d4629b2010-08-30 17:59:46 +00003483 register ssize_t
3484 x;
3485
cristy3ed852e2009-09-05 21:47:34 +00003486 if (status == MagickFalse)
3487 continue;
3488 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
3489 if (q == (PixelPacket *) NULL)
3490 {
3491 status=MagickFalse;
3492 continue;
3493 }
3494 indexes=GetCacheViewAuthenticIndexQueue(image_view);
cristybb503372010-05-27 20:51:26 +00003495 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003496 {
3497 if ((channel & RedChannel) != 0)
3498 q->red=(Quantum) QuantumRange-q->red;
3499 if ((channel & GreenChannel) != 0)
3500 q->green=(Quantum) QuantumRange-q->green;
3501 if ((channel & BlueChannel) != 0)
3502 q->blue=(Quantum) QuantumRange-q->blue;
3503 if ((channel & OpacityChannel) != 0)
3504 q->opacity=(Quantum) QuantumRange-q->opacity;
3505 if (((channel & IndexChannel) != 0) &&
3506 (image->colorspace == CMYKColorspace))
3507 indexes[x]=(IndexPacket) QuantumRange-indexes[x];
3508 q++;
3509 }
3510 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
3511 status=MagickFalse;
3512 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3513 {
3514 MagickBooleanType
3515 proceed;
3516
cristyb5d5f722009-11-04 03:03:49 +00003517#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00003518 #pragma omp critical (MagickCore_NegateImageChannel)
3519#endif
3520 proceed=SetImageProgress(image,NegateImageTag,progress++,image->rows);
3521 if (proceed == MagickFalse)
3522 status=MagickFalse;
3523 }
3524 }
3525 image_view=DestroyCacheView(image_view);
3526 return(status);
3527}
3528
3529/*
3530%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3531% %
3532% %
3533% %
3534% N o r m a l i z e I m a g e %
3535% %
3536% %
3537% %
3538%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3539%
3540% The NormalizeImage() method enhances the contrast of a color image by
3541% mapping the darkest 2 percent of all pixel to black and the brightest
3542% 1 percent to white.
3543%
3544% The format of the NormalizeImage method is:
3545%
3546% MagickBooleanType NormalizeImage(Image *image)
3547% MagickBooleanType NormalizeImageChannel(Image *image,
3548% const ChannelType channel)
3549%
3550% A description of each parameter follows:
3551%
3552% o image: the image.
3553%
3554% o channel: the channel.
3555%
3556*/
3557
3558MagickExport MagickBooleanType NormalizeImage(Image *image)
3559{
3560 MagickBooleanType
3561 status;
3562
3563 status=NormalizeImageChannel(image,DefaultChannels);
3564 return(status);
3565}
3566
3567MagickExport MagickBooleanType NormalizeImageChannel(Image *image,
3568 const ChannelType channel)
3569{
3570 double
3571 black_point,
3572 white_point;
3573
cristy530239c2010-07-25 17:34:26 +00003574 black_point=(double) image->columns*image->rows*0.0015;
3575 white_point=(double) image->columns*image->rows*0.9995;
cristy3ed852e2009-09-05 21:47:34 +00003576 return(ContrastStretchImageChannel(image,channel,black_point,white_point));
3577}
3578
3579/*
3580%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3581% %
3582% %
3583% %
3584% S i g m o i d a l C o n t r a s t I m a g e %
3585% %
3586% %
3587% %
3588%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3589%
3590% SigmoidalContrastImage() adjusts the contrast of an image with a non-linear
3591% sigmoidal contrast algorithm. Increase the contrast of the image using a
3592% sigmoidal transfer function without saturating highlights or shadows.
3593% Contrast indicates how much to increase the contrast (0 is none; 3 is
3594% typical; 20 is pushing it); mid-point indicates where midtones fall in the
3595% resultant image (0 is white; 50% is middle-gray; 100% is black). Set
3596% sharpen to MagickTrue to increase the image contrast otherwise the contrast
3597% is reduced.
3598%
3599% The format of the SigmoidalContrastImage method is:
3600%
3601% MagickBooleanType SigmoidalContrastImage(Image *image,
3602% const MagickBooleanType sharpen,const char *levels)
3603% MagickBooleanType SigmoidalContrastImageChannel(Image *image,
3604% const ChannelType channel,const MagickBooleanType sharpen,
3605% const double contrast,const double midpoint)
3606%
3607% A description of each parameter follows:
3608%
3609% o image: the image.
3610%
3611% o channel: the channel.
3612%
3613% o sharpen: Increase or decrease image contrast.
3614%
cristyfa769582010-09-30 23:30:03 +00003615% o alpha: strength of the contrast, the larger the number the more
3616% 'threshold-like' it becomes.
cristy3ed852e2009-09-05 21:47:34 +00003617%
cristyfa769582010-09-30 23:30:03 +00003618% o beta: midpoint of the function as a color value 0 to QuantumRange.
cristy3ed852e2009-09-05 21:47:34 +00003619%
3620*/
3621
3622MagickExport MagickBooleanType SigmoidalContrastImage(Image *image,
3623 const MagickBooleanType sharpen,const char *levels)
3624{
3625 GeometryInfo
3626 geometry_info;
3627
3628 MagickBooleanType
3629 status;
3630
3631 MagickStatusType
3632 flags;
3633
3634 flags=ParseGeometry(levels,&geometry_info);
3635 if ((flags & SigmaValue) == 0)
3636 geometry_info.sigma=1.0*QuantumRange/2.0;
3637 if ((flags & PercentValue) != 0)
3638 geometry_info.sigma=1.0*QuantumRange*geometry_info.sigma/100.0;
3639 status=SigmoidalContrastImageChannel(image,DefaultChannels,sharpen,
3640 geometry_info.rho,geometry_info.sigma);
3641 return(status);
3642}
3643
3644MagickExport MagickBooleanType SigmoidalContrastImageChannel(Image *image,
3645 const ChannelType channel,const MagickBooleanType sharpen,
3646 const double contrast,const double midpoint)
3647{
3648#define SigmoidalContrastImageTag "SigmoidalContrast/Image"
3649
cristyc4c8d132010-01-07 01:58:38 +00003650 CacheView
3651 *image_view;
3652
cristy3ed852e2009-09-05 21:47:34 +00003653 ExceptionInfo
3654 *exception;
3655
cristy3ed852e2009-09-05 21:47:34 +00003656 MagickBooleanType
3657 status;
3658
cristybb503372010-05-27 20:51:26 +00003659 MagickOffsetType
3660 progress;
3661
cristy3ed852e2009-09-05 21:47:34 +00003662 MagickRealType
3663 *sigmoidal_map;
3664
cristybb503372010-05-27 20:51:26 +00003665 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003666 i;
3667
cristybb503372010-05-27 20:51:26 +00003668 ssize_t
3669 y;
3670
cristy3ed852e2009-09-05 21:47:34 +00003671 /*
3672 Allocate and initialize sigmoidal maps.
3673 */
3674 assert(image != (Image *) NULL);
3675 assert(image->signature == MagickSignature);
3676 if (image->debug != MagickFalse)
3677 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3678 sigmoidal_map=(MagickRealType *) AcquireQuantumMemory(MaxMap+1UL,
3679 sizeof(*sigmoidal_map));
3680 if (sigmoidal_map == (MagickRealType *) NULL)
3681 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
3682 image->filename);
3683 (void) ResetMagickMemory(sigmoidal_map,0,(MaxMap+1)*sizeof(*sigmoidal_map));
cristyb5d5f722009-11-04 03:03:49 +00003684#if defined(MAGICKCORE_OPENMP_SUPPORT)
3685 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003686#endif
cristybb503372010-05-27 20:51:26 +00003687 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +00003688 {
3689 if (sharpen != MagickFalse)
3690 {
3691 sigmoidal_map[i]=(MagickRealType) ScaleMapToQuantum((MagickRealType)
3692 (MaxMap*((1.0/(1.0+exp(contrast*(midpoint/(double) QuantumRange-
3693 (double) i/MaxMap))))-(1.0/(1.0+exp(contrast*(midpoint/
3694 (double) QuantumRange)))))/((1.0/(1.0+exp(contrast*(midpoint/
3695 (double) QuantumRange-1.0))))-(1.0/(1.0+exp(contrast*(midpoint/
3696 (double) QuantumRange)))))+0.5));
3697 continue;
3698 }
3699 sigmoidal_map[i]=(MagickRealType) ScaleMapToQuantum((MagickRealType)
3700 (MaxMap*(QuantumScale*midpoint-log((1.0-(1.0/(1.0+exp(midpoint/
3701 (double) QuantumRange*contrast))+((double) i/MaxMap)*((1.0/
3702 (1.0+exp(contrast*(midpoint/(double) QuantumRange-1.0))))-(1.0/
3703 (1.0+exp(midpoint/(double) QuantumRange*contrast))))))/
3704 (1.0/(1.0+exp(midpoint/(double) QuantumRange*contrast))+
3705 ((double) i/MaxMap)*((1.0/(1.0+exp(contrast*(midpoint/
3706 (double) QuantumRange-1.0))))-(1.0/(1.0+exp(midpoint/
3707 (double) QuantumRange*contrast))))))/contrast)));
3708 }
3709 if (image->storage_class == PseudoClass)
3710 {
3711 /*
3712 Sigmoidal-contrast enhance colormap.
3713 */
cristyb5d5f722009-11-04 03:03:49 +00003714#if defined(MAGICKCORE_OPENMP_SUPPORT)
3715 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003716#endif
cristybb503372010-05-27 20:51:26 +00003717 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00003718 {
3719 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003720 image->colormap[i].red=ClampToQuantum(sigmoidal_map[
cristy3ed852e2009-09-05 21:47:34 +00003721 ScaleQuantumToMap(image->colormap[i].red)]);
3722 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003723 image->colormap[i].green=ClampToQuantum(sigmoidal_map[
cristy3ed852e2009-09-05 21:47:34 +00003724 ScaleQuantumToMap(image->colormap[i].green)]);
3725 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003726 image->colormap[i].blue=ClampToQuantum(sigmoidal_map[
cristy3ed852e2009-09-05 21:47:34 +00003727 ScaleQuantumToMap(image->colormap[i].blue)]);
3728 if ((channel & OpacityChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003729 image->colormap[i].opacity=ClampToQuantum(sigmoidal_map[
cristy3ed852e2009-09-05 21:47:34 +00003730 ScaleQuantumToMap(image->colormap[i].opacity)]);
3731 }
3732 }
3733 /*
3734 Sigmoidal-contrast enhance image.
3735 */
3736 status=MagickTrue;
3737 progress=0;
3738 exception=(&image->exception);
3739 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00003740#if defined(MAGICKCORE_OPENMP_SUPPORT)
3741 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003742#endif
cristybb503372010-05-27 20:51:26 +00003743 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003744 {
3745 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00003746 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00003747
cristy3ed852e2009-09-05 21:47:34 +00003748 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00003749 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003750
cristy8d4629b2010-08-30 17:59:46 +00003751 register ssize_t
3752 x;
3753
cristy3ed852e2009-09-05 21:47:34 +00003754 if (status == MagickFalse)
3755 continue;
3756 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
3757 if (q == (PixelPacket *) NULL)
3758 {
3759 status=MagickFalse;
3760 continue;
3761 }
3762 indexes=GetCacheViewAuthenticIndexQueue(image_view);
cristybb503372010-05-27 20:51:26 +00003763 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003764 {
3765 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003766 q->red=ClampToQuantum(sigmoidal_map[ScaleQuantumToMap(q->red)]);
cristy3ed852e2009-09-05 21:47:34 +00003767 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003768 q->green=ClampToQuantum(sigmoidal_map[ScaleQuantumToMap(q->green)]);
cristy3ed852e2009-09-05 21:47:34 +00003769 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003770 q->blue=ClampToQuantum(sigmoidal_map[ScaleQuantumToMap(q->blue)]);
cristy3ed852e2009-09-05 21:47:34 +00003771 if ((channel & OpacityChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003772 q->opacity=ClampToQuantum(sigmoidal_map[ScaleQuantumToMap(q->opacity)]);
cristy3ed852e2009-09-05 21:47:34 +00003773 if (((channel & IndexChannel) != 0) &&
3774 (image->colorspace == CMYKColorspace))
cristyce70c172010-01-07 17:15:30 +00003775 indexes[x]=(IndexPacket) ClampToQuantum(sigmoidal_map[
cristy3ed852e2009-09-05 21:47:34 +00003776 ScaleQuantumToMap(indexes[x])]);
3777 q++;
3778 }
3779 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
3780 status=MagickFalse;
3781 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3782 {
3783 MagickBooleanType
3784 proceed;
3785
cristyb5d5f722009-11-04 03:03:49 +00003786#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00003787 #pragma omp critical (MagickCore_SigmoidalContrastImageChannel)
3788#endif
3789 proceed=SetImageProgress(image,SigmoidalContrastImageTag,progress++,
3790 image->rows);
3791 if (proceed == MagickFalse)
3792 status=MagickFalse;
3793 }
3794 }
3795 image_view=DestroyCacheView(image_view);
3796 sigmoidal_map=(MagickRealType *) RelinquishMagickMemory(sigmoidal_map);
3797 return(status);
3798}