blob: d22c8866f2283dc7921f1b15e5f8ba6abc93fc3c [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
cristy708333f2011-03-26 01:25:07 +0000701 *clut_view,
cristyfa112112010-01-04 17:48:07 +0000702 *image_view;
703
cristy3ed852e2009-09-05 21:47:34 +0000704 ExceptionInfo
705 *exception;
706
cristy3ed852e2009-09-05 21:47:34 +0000707 MagickBooleanType
708 status;
709
cristybb503372010-05-27 20:51:26 +0000710 MagickOffsetType
711 progress;
712
cristy3ed852e2009-09-05 21:47:34 +0000713 MagickPixelPacket
cristy49f37242011-03-22 18:18:23 +0000714 *clut_map;
715
716 register ssize_t
717 i;
cristy3ed852e2009-09-05 21:47:34 +0000718
cristybb503372010-05-27 20:51:26 +0000719 ssize_t
720 adjust,
721 y;
722
cristy3ed852e2009-09-05 21:47:34 +0000723 assert(image != (Image *) NULL);
724 assert(image->signature == MagickSignature);
725 if (image->debug != MagickFalse)
726 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
727 assert(clut_image != (Image *) NULL);
728 assert(clut_image->signature == MagickSignature);
729 if (SetImageStorageClass(image,DirectClass) == MagickFalse)
730 return(MagickFalse);
cristy49f37242011-03-22 18:18:23 +0000731 clut_map=(MagickPixelPacket *) AcquireQuantumMemory(MaxMap+1UL,
732 sizeof(*clut_map));
733 if (clut_map == (MagickPixelPacket *) NULL)
734 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
735 image->filename);
cristy3ed852e2009-09-05 21:47:34 +0000736 /*
737 Clut image.
738 */
739 status=MagickTrue;
740 progress=0;
cristybb503372010-05-27 20:51:26 +0000741 adjust=(ssize_t) (clut_image->interpolate == IntegerInterpolatePixel ? 0 : 1);
cristy3ed852e2009-09-05 21:47:34 +0000742 exception=(&image->exception);
cristy708333f2011-03-26 01:25:07 +0000743 clut_view=AcquireCacheView(clut_image);
cristyaf6bc722011-03-25 19:16:14 +0000744#if defined(MAGICKCORE_OPENMP_SUPPORT)
745 #pragma omp parallel for schedule(dynamic,4)
746#endif
cristy49f37242011-03-22 18:18:23 +0000747 for (i=0; i <= (ssize_t) MaxMap; i++)
748 {
749 GetMagickPixelPacket(clut_image,clut_map+i);
cristy708333f2011-03-26 01:25:07 +0000750 (void) InterpolatePixelPacket(clut_image,clut_view,clut_image->interpolate,
cristyd76c51e2011-03-26 00:21:26 +0000751 QuantumScale*i*(clut_image->columns-adjust),QuantumScale*i*
752 (clut_image->rows-adjust),clut_map+i,exception);
cristy49f37242011-03-22 18:18:23 +0000753 }
cristy708333f2011-03-26 01:25:07 +0000754 clut_view=DestroyCacheView(clut_view);
755 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +0000756#if defined(MAGICKCORE_OPENMP_SUPPORT)
757 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000758#endif
cristybb503372010-05-27 20:51:26 +0000759 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000760 {
cristy3635df22011-03-25 00:16:16 +0000761 MagickPixelPacket
762 pixel;
763
cristy3ed852e2009-09-05 21:47:34 +0000764 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +0000765 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +0000766
cristy3ed852e2009-09-05 21:47:34 +0000767 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +0000768 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000769
cristy8d4629b2010-08-30 17:59:46 +0000770 register ssize_t
771 x;
772
cristy3ed852e2009-09-05 21:47:34 +0000773 if (status == MagickFalse)
774 continue;
775 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
776 if (q == (PixelPacket *) NULL)
777 {
778 status=MagickFalse;
779 continue;
780 }
781 indexes=GetCacheViewAuthenticIndexQueue(image_view);
cristy3635df22011-03-25 00:16:16 +0000782 GetMagickPixelPacket(image,&pixel);
cristybb503372010-05-27 20:51:26 +0000783 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000784 {
cristy3635df22011-03-25 00:16:16 +0000785 SetMagickPixelPacket(image,q,indexes+x,&pixel);
cristy2253f172011-03-24 23:39:51 +0000786 if ((channel & RedChannel) != 0)
787 SetRedPixelComponent(q,ClampRedPixelComponent(clut_map+
788 ScaleQuantumToMap(q->red)));
789 if ((channel & GreenChannel) != 0)
790 SetGreenPixelComponent(q,ClampGreenPixelComponent(clut_map+
791 ScaleQuantumToMap(q->green)));
792 if ((channel & BlueChannel) != 0)
793 SetBluePixelComponent(q,ClampBluePixelComponent(clut_map+
794 ScaleQuantumToMap(q->blue)));
cristy3635df22011-03-25 00:16:16 +0000795 if ((channel & OpacityChannel) != 0)
796 {
797 if (clut_image->matte == MagickFalse)
798 q->opacity=(Quantum) (QuantumRange-MagickPixelIntensityToQuantum(
cristyd76c51e2011-03-26 00:21:26 +0000799 clut_map+ScaleQuantumToMap((Quantum) GetAlphaPixelComponent(q))));
cristy3635df22011-03-25 00:16:16 +0000800 else
801 if (image->matte == MagickFalse)
802 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(clut_map+
cristyd76c51e2011-03-26 00:21:26 +0000803 ScaleQuantumToMap((Quantum) MagickPixelIntensity(&pixel))));
cristy3635df22011-03-25 00:16:16 +0000804 else
805 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(
806 clut_map+ScaleQuantumToMap(q->opacity)));
807 }
cristy3ed852e2009-09-05 21:47:34 +0000808 if (((channel & IndexChannel) != 0) &&
809 (image->colorspace == CMYKColorspace))
cristy49f37242011-03-22 18:18:23 +0000810 indexes[x]=ClampToQuantum((clut_map+(ssize_t) indexes[x])->index);
cristy3ed852e2009-09-05 21:47:34 +0000811 q++;
812 }
813 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
814 status=MagickFalse;
815 if (image->progress_monitor != (MagickProgressMonitor) NULL)
816 {
817 MagickBooleanType
818 proceed;
819
cristyb5d5f722009-11-04 03:03:49 +0000820#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +0000821 #pragma omp critical (MagickCore_ClutImageChannel)
822#endif
823 proceed=SetImageProgress(image,ClutImageTag,progress++,image->rows);
824 if (proceed == MagickFalse)
825 status=MagickFalse;
826 }
827 }
828 image_view=DestroyCacheView(image_view);
cristy49f37242011-03-22 18:18:23 +0000829 clut_map=(MagickPixelPacket *) RelinquishMagickMemory(clut_map);
cristy3ed852e2009-09-05 21:47:34 +0000830 if ((clut_image->matte != MagickFalse) && ((channel & OpacityChannel) != 0))
831 (void) SetImageAlphaChannel(image,ActivateAlphaChannel);
832 return(status);
833}
834
835/*
836%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
837% %
838% %
839% %
840% C o n t r a s t I m a g e %
841% %
842% %
843% %
844%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
845%
846% ContrastImage() enhances the intensity differences between the lighter and
847% darker elements of the image. Set sharpen to a MagickTrue to increase the
848% image contrast otherwise the contrast is reduced.
849%
850% The format of the ContrastImage method is:
851%
852% MagickBooleanType ContrastImage(Image *image,
853% const MagickBooleanType sharpen)
854%
855% A description of each parameter follows:
856%
857% o image: the image.
858%
859% o sharpen: Increase or decrease image contrast.
860%
861*/
862
863static void Contrast(const int sign,Quantum *red,Quantum *green,Quantum *blue)
864{
865 double
866 brightness,
867 hue,
868 saturation;
869
870 /*
871 Enhance contrast: dark color become darker, light color become lighter.
872 */
873 assert(red != (Quantum *) NULL);
874 assert(green != (Quantum *) NULL);
875 assert(blue != (Quantum *) NULL);
876 hue=0.0;
877 saturation=0.0;
878 brightness=0.0;
879 ConvertRGBToHSB(*red,*green,*blue,&hue,&saturation,&brightness);
cristy31ac0f02011-03-12 02:04:47 +0000880 brightness+=0.5*sign*(0.5*(sin((double) (MagickPI*(brightness-0.5)))+1.0)-
cristy4205a3c2010-09-12 20:19:59 +0000881 brightness);
cristy3ed852e2009-09-05 21:47:34 +0000882 if (brightness > 1.0)
883 brightness=1.0;
884 else
885 if (brightness < 0.0)
886 brightness=0.0;
887 ConvertHSBToRGB(hue,saturation,brightness,red,green,blue);
888}
889
890MagickExport MagickBooleanType ContrastImage(Image *image,
891 const MagickBooleanType sharpen)
892{
893#define ContrastImageTag "Contrast/Image"
894
cristyc4c8d132010-01-07 01:58:38 +0000895 CacheView
896 *image_view;
897
cristy3ed852e2009-09-05 21:47:34 +0000898 ExceptionInfo
899 *exception;
900
901 int
902 sign;
903
cristy3ed852e2009-09-05 21:47:34 +0000904 MagickBooleanType
905 status;
906
cristybb503372010-05-27 20:51:26 +0000907 MagickOffsetType
908 progress;
909
910 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000911 i;
912
cristybb503372010-05-27 20:51:26 +0000913 ssize_t
914 y;
915
cristy3ed852e2009-09-05 21:47:34 +0000916 assert(image != (Image *) NULL);
917 assert(image->signature == MagickSignature);
918 if (image->debug != MagickFalse)
919 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
920 sign=sharpen != MagickFalse ? 1 : -1;
921 if (image->storage_class == PseudoClass)
922 {
923 /*
924 Contrast enhance colormap.
925 */
cristybb503372010-05-27 20:51:26 +0000926 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +0000927 Contrast(sign,&image->colormap[i].red,&image->colormap[i].green,
928 &image->colormap[i].blue);
929 }
930 /*
931 Contrast enhance image.
932 */
933 status=MagickTrue;
934 progress=0;
935 exception=(&image->exception);
936 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +0000937#if defined(MAGICKCORE_OPENMP_SUPPORT)
938 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000939#endif
cristybb503372010-05-27 20:51:26 +0000940 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000941 {
cristy3ed852e2009-09-05 21:47:34 +0000942 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +0000943 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000944
cristy8d4629b2010-08-30 17:59:46 +0000945 register ssize_t
946 x;
947
cristy3ed852e2009-09-05 21:47:34 +0000948 if (status == MagickFalse)
949 continue;
950 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
951 if (q == (PixelPacket *) NULL)
952 {
953 status=MagickFalse;
954 continue;
955 }
cristybb503372010-05-27 20:51:26 +0000956 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000957 {
958 Contrast(sign,&q->red,&q->green,&q->blue);
959 q++;
960 }
961 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
962 status=MagickFalse;
963 if (image->progress_monitor != (MagickProgressMonitor) NULL)
964 {
965 MagickBooleanType
966 proceed;
967
cristyb5d5f722009-11-04 03:03:49 +0000968#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +0000969 #pragma omp critical (MagickCore_ContrastImage)
970#endif
971 proceed=SetImageProgress(image,ContrastImageTag,progress++,image->rows);
972 if (proceed == MagickFalse)
973 status=MagickFalse;
974 }
975 }
976 image_view=DestroyCacheView(image_view);
977 return(status);
978}
979
980/*
981%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
982% %
983% %
984% %
985% C o n t r a s t S t r e t c h I m a g e %
986% %
987% %
988% %
989%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
990%
991% The ContrastStretchImage() is a simple image enhancement technique that
992% attempts to improve the contrast in an image by `stretching' the range of
993% intensity values it contains to span a desired range of values. It differs
994% from the more sophisticated histogram equalization in that it can only
995% apply % a linear scaling function to the image pixel values. As a result
996% the `enhancement' is less harsh.
997%
998% The format of the ContrastStretchImage method is:
999%
1000% MagickBooleanType ContrastStretchImage(Image *image,
1001% const char *levels)
1002% MagickBooleanType ContrastStretchImageChannel(Image *image,
cristybb503372010-05-27 20:51:26 +00001003% const size_t channel,const double black_point,
cristy3ed852e2009-09-05 21:47:34 +00001004% const double white_point)
1005%
1006% A description of each parameter follows:
1007%
1008% o image: the image.
1009%
1010% o channel: the channel.
1011%
1012% o black_point: the black point.
1013%
1014% o white_point: the white point.
1015%
1016% o levels: Specify the levels where the black and white points have the
1017% range of 0 to number-of-pixels (e.g. 1%, 10x90%, etc.).
1018%
1019*/
1020
1021MagickExport MagickBooleanType ContrastStretchImage(Image *image,
1022 const char *levels)
1023{
1024 double
1025 black_point,
1026 white_point;
1027
1028 GeometryInfo
1029 geometry_info;
1030
1031 MagickBooleanType
1032 status;
1033
1034 MagickStatusType
1035 flags;
1036
1037 /*
1038 Parse levels.
1039 */
1040 if (levels == (char *) NULL)
1041 return(MagickFalse);
1042 flags=ParseGeometry(levels,&geometry_info);
1043 black_point=geometry_info.rho;
1044 white_point=(double) image->columns*image->rows;
1045 if ((flags & SigmaValue) != 0)
1046 white_point=geometry_info.sigma;
1047 if ((flags & PercentValue) != 0)
1048 {
1049 black_point*=(double) QuantumRange/100.0;
1050 white_point*=(double) QuantumRange/100.0;
1051 }
1052 if ((flags & SigmaValue) == 0)
1053 white_point=(double) image->columns*image->rows-black_point;
1054 status=ContrastStretchImageChannel(image,DefaultChannels,black_point,
1055 white_point);
1056 return(status);
1057}
1058
1059MagickExport MagickBooleanType ContrastStretchImageChannel(Image *image,
1060 const ChannelType channel,const double black_point,const double white_point)
1061{
1062#define MaxRange(color) ((MagickRealType) ScaleQuantumToMap((Quantum) (color)))
1063#define ContrastStretchImageTag "ContrastStretch/Image"
1064
cristyc4c8d132010-01-07 01:58:38 +00001065 CacheView
1066 *image_view;
1067
cristy3ed852e2009-09-05 21:47:34 +00001068 double
1069 intensity;
1070
1071 ExceptionInfo
1072 *exception;
1073
cristy3ed852e2009-09-05 21:47:34 +00001074 MagickBooleanType
1075 status;
1076
cristybb503372010-05-27 20:51:26 +00001077 MagickOffsetType
1078 progress;
1079
cristy3ed852e2009-09-05 21:47:34 +00001080 MagickPixelPacket
1081 black,
1082 *histogram,
1083 *stretch_map,
1084 white;
1085
cristybb503372010-05-27 20:51:26 +00001086 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001087 i;
1088
cristybb503372010-05-27 20:51:26 +00001089 ssize_t
1090 y;
1091
cristy3ed852e2009-09-05 21:47:34 +00001092 /*
1093 Allocate histogram and stretch map.
1094 */
1095 assert(image != (Image *) NULL);
1096 assert(image->signature == MagickSignature);
1097 if (image->debug != MagickFalse)
1098 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1099 histogram=(MagickPixelPacket *) AcquireQuantumMemory(MaxMap+1UL,
1100 sizeof(*histogram));
1101 stretch_map=(MagickPixelPacket *) AcquireQuantumMemory(MaxMap+1UL,
1102 sizeof(*stretch_map));
1103 if ((histogram == (MagickPixelPacket *) NULL) ||
1104 (stretch_map == (MagickPixelPacket *) NULL))
1105 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
1106 image->filename);
1107 /*
1108 Form histogram.
1109 */
1110 status=MagickTrue;
1111 exception=(&image->exception);
1112 (void) ResetMagickMemory(histogram,0,(MaxMap+1)*sizeof(*histogram));
1113 image_view=AcquireCacheView(image);
cristybb503372010-05-27 20:51:26 +00001114 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001115 {
1116 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001117 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00001118
1119 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00001120 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00001121
cristybb503372010-05-27 20:51:26 +00001122 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001123 x;
1124
1125 if (status == MagickFalse)
1126 continue;
1127 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
1128 if (p == (const PixelPacket *) NULL)
1129 {
1130 status=MagickFalse;
1131 continue;
1132 }
1133 indexes=GetCacheViewAuthenticIndexQueue(image_view);
1134 if (channel == DefaultChannels)
cristybb503372010-05-27 20:51:26 +00001135 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001136 {
1137 Quantum
1138 intensity;
1139
1140 intensity=PixelIntensityToQuantum(p);
1141 histogram[ScaleQuantumToMap(intensity)].red++;
1142 histogram[ScaleQuantumToMap(intensity)].green++;
1143 histogram[ScaleQuantumToMap(intensity)].blue++;
1144 histogram[ScaleQuantumToMap(intensity)].index++;
1145 p++;
1146 }
1147 else
cristybb503372010-05-27 20:51:26 +00001148 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001149 {
1150 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001151 histogram[ScaleQuantumToMap(GetRedPixelComponent(p))].red++;
cristy3ed852e2009-09-05 21:47:34 +00001152 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001153 histogram[ScaleQuantumToMap(GetGreenPixelComponent(p))].green++;
cristy3ed852e2009-09-05 21:47:34 +00001154 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001155 histogram[ScaleQuantumToMap(GetBluePixelComponent(p))].blue++;
cristy3ed852e2009-09-05 21:47:34 +00001156 if ((channel & OpacityChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001157 histogram[ScaleQuantumToMap(GetOpacityPixelComponent(p))].opacity++;
cristy3ed852e2009-09-05 21:47:34 +00001158 if (((channel & IndexChannel) != 0) &&
1159 (image->colorspace == CMYKColorspace))
1160 histogram[ScaleQuantumToMap(indexes[x])].index++;
1161 p++;
1162 }
1163 }
1164 /*
1165 Find the histogram boundaries by locating the black/white levels.
1166 */
1167 black.red=0.0;
1168 white.red=MaxRange(QuantumRange);
1169 if ((channel & RedChannel) != 0)
1170 {
1171 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00001172 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +00001173 {
1174 intensity+=histogram[i].red;
1175 if (intensity > black_point)
1176 break;
1177 }
1178 black.red=(MagickRealType) i;
1179 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00001180 for (i=(ssize_t) MaxMap; i != 0; i--)
cristy3ed852e2009-09-05 21:47:34 +00001181 {
1182 intensity+=histogram[i].red;
1183 if (intensity > ((double) image->columns*image->rows-white_point))
1184 break;
1185 }
1186 white.red=(MagickRealType) i;
1187 }
1188 black.green=0.0;
1189 white.green=MaxRange(QuantumRange);
1190 if ((channel & GreenChannel) != 0)
1191 {
1192 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00001193 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +00001194 {
1195 intensity+=histogram[i].green;
1196 if (intensity > black_point)
1197 break;
1198 }
1199 black.green=(MagickRealType) i;
1200 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00001201 for (i=(ssize_t) MaxMap; i != 0; i--)
cristy3ed852e2009-09-05 21:47:34 +00001202 {
1203 intensity+=histogram[i].green;
1204 if (intensity > ((double) image->columns*image->rows-white_point))
1205 break;
1206 }
1207 white.green=(MagickRealType) i;
1208 }
1209 black.blue=0.0;
1210 white.blue=MaxRange(QuantumRange);
1211 if ((channel & BlueChannel) != 0)
1212 {
1213 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00001214 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +00001215 {
1216 intensity+=histogram[i].blue;
1217 if (intensity > black_point)
1218 break;
1219 }
1220 black.blue=(MagickRealType) i;
1221 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00001222 for (i=(ssize_t) MaxMap; i != 0; i--)
cristy3ed852e2009-09-05 21:47:34 +00001223 {
1224 intensity+=histogram[i].blue;
1225 if (intensity > ((double) image->columns*image->rows-white_point))
1226 break;
1227 }
1228 white.blue=(MagickRealType) i;
1229 }
1230 black.opacity=0.0;
1231 white.opacity=MaxRange(QuantumRange);
1232 if ((channel & OpacityChannel) != 0)
1233 {
1234 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00001235 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +00001236 {
1237 intensity+=histogram[i].opacity;
1238 if (intensity > black_point)
1239 break;
1240 }
1241 black.opacity=(MagickRealType) i;
1242 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00001243 for (i=(ssize_t) MaxMap; i != 0; i--)
cristy3ed852e2009-09-05 21:47:34 +00001244 {
1245 intensity+=histogram[i].opacity;
1246 if (intensity > ((double) image->columns*image->rows-white_point))
1247 break;
1248 }
1249 white.opacity=(MagickRealType) i;
1250 }
1251 black.index=0.0;
1252 white.index=MaxRange(QuantumRange);
1253 if (((channel & IndexChannel) != 0) && (image->colorspace == CMYKColorspace))
1254 {
1255 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00001256 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +00001257 {
1258 intensity+=histogram[i].index;
1259 if (intensity > black_point)
1260 break;
1261 }
1262 black.index=(MagickRealType) i;
1263 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00001264 for (i=(ssize_t) MaxMap; i != 0; i--)
cristy3ed852e2009-09-05 21:47:34 +00001265 {
1266 intensity+=histogram[i].index;
1267 if (intensity > ((double) image->columns*image->rows-white_point))
1268 break;
1269 }
1270 white.index=(MagickRealType) i;
1271 }
1272 histogram=(MagickPixelPacket *) RelinquishMagickMemory(histogram);
1273 /*
1274 Stretch the histogram to create the stretched image mapping.
1275 */
1276 (void) ResetMagickMemory(stretch_map,0,(MaxMap+1)*sizeof(*stretch_map));
cristyb5d5f722009-11-04 03:03:49 +00001277#if defined(MAGICKCORE_OPENMP_SUPPORT)
1278 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001279#endif
cristybb503372010-05-27 20:51:26 +00001280 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +00001281 {
1282 if ((channel & RedChannel) != 0)
1283 {
cristybb503372010-05-27 20:51:26 +00001284 if (i < (ssize_t) black.red)
cristy3ed852e2009-09-05 21:47:34 +00001285 stretch_map[i].red=0.0;
1286 else
cristybb503372010-05-27 20:51:26 +00001287 if (i > (ssize_t) white.red)
cristy3ed852e2009-09-05 21:47:34 +00001288 stretch_map[i].red=(MagickRealType) QuantumRange;
1289 else
1290 if (black.red != white.red)
1291 stretch_map[i].red=(MagickRealType) ScaleMapToQuantum(
1292 (MagickRealType) (MaxMap*(i-black.red)/(white.red-black.red)));
1293 }
1294 if ((channel & GreenChannel) != 0)
1295 {
cristybb503372010-05-27 20:51:26 +00001296 if (i < (ssize_t) black.green)
cristy3ed852e2009-09-05 21:47:34 +00001297 stretch_map[i].green=0.0;
1298 else
cristybb503372010-05-27 20:51:26 +00001299 if (i > (ssize_t) white.green)
cristy3ed852e2009-09-05 21:47:34 +00001300 stretch_map[i].green=(MagickRealType) QuantumRange;
1301 else
1302 if (black.green != white.green)
1303 stretch_map[i].green=(MagickRealType) ScaleMapToQuantum(
1304 (MagickRealType) (MaxMap*(i-black.green)/(white.green-
1305 black.green)));
1306 }
1307 if ((channel & BlueChannel) != 0)
1308 {
cristybb503372010-05-27 20:51:26 +00001309 if (i < (ssize_t) black.blue)
cristy3ed852e2009-09-05 21:47:34 +00001310 stretch_map[i].blue=0.0;
1311 else
cristybb503372010-05-27 20:51:26 +00001312 if (i > (ssize_t) white.blue)
cristy3ed852e2009-09-05 21:47:34 +00001313 stretch_map[i].blue=(MagickRealType) QuantumRange;
1314 else
1315 if (black.blue != white.blue)
1316 stretch_map[i].blue=(MagickRealType) ScaleMapToQuantum(
1317 (MagickRealType) (MaxMap*(i-black.blue)/(white.blue-
1318 black.blue)));
1319 }
1320 if ((channel & OpacityChannel) != 0)
1321 {
cristybb503372010-05-27 20:51:26 +00001322 if (i < (ssize_t) black.opacity)
cristy3ed852e2009-09-05 21:47:34 +00001323 stretch_map[i].opacity=0.0;
1324 else
cristybb503372010-05-27 20:51:26 +00001325 if (i > (ssize_t) white.opacity)
cristy3ed852e2009-09-05 21:47:34 +00001326 stretch_map[i].opacity=(MagickRealType) QuantumRange;
1327 else
1328 if (black.opacity != white.opacity)
1329 stretch_map[i].opacity=(MagickRealType) ScaleMapToQuantum(
1330 (MagickRealType) (MaxMap*(i-black.opacity)/(white.opacity-
1331 black.opacity)));
1332 }
1333 if (((channel & IndexChannel) != 0) &&
1334 (image->colorspace == CMYKColorspace))
1335 {
cristybb503372010-05-27 20:51:26 +00001336 if (i < (ssize_t) black.index)
cristy3ed852e2009-09-05 21:47:34 +00001337 stretch_map[i].index=0.0;
1338 else
cristybb503372010-05-27 20:51:26 +00001339 if (i > (ssize_t) white.index)
cristy3ed852e2009-09-05 21:47:34 +00001340 stretch_map[i].index=(MagickRealType) QuantumRange;
1341 else
1342 if (black.index != white.index)
1343 stretch_map[i].index=(MagickRealType) ScaleMapToQuantum(
1344 (MagickRealType) (MaxMap*(i-black.index)/(white.index-
1345 black.index)));
1346 }
1347 }
1348 /*
1349 Stretch the image.
1350 */
1351 if (((channel & OpacityChannel) != 0) || (((channel & IndexChannel) != 0) &&
1352 (image->colorspace == CMYKColorspace)))
1353 image->storage_class=DirectClass;
1354 if (image->storage_class == PseudoClass)
1355 {
1356 /*
1357 Stretch colormap.
1358 */
cristyb5d5f722009-11-04 03:03:49 +00001359#if defined(MAGICKCORE_OPENMP_SUPPORT)
1360 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001361#endif
cristybb503372010-05-27 20:51:26 +00001362 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00001363 {
1364 if ((channel & RedChannel) != 0)
1365 {
1366 if (black.red != white.red)
cristyce70c172010-01-07 17:15:30 +00001367 image->colormap[i].red=ClampToQuantum(stretch_map[
cristy3ed852e2009-09-05 21:47:34 +00001368 ScaleQuantumToMap(image->colormap[i].red)].red);
1369 }
1370 if ((channel & GreenChannel) != 0)
1371 {
1372 if (black.green != white.green)
cristyce70c172010-01-07 17:15:30 +00001373 image->colormap[i].green=ClampToQuantum(stretch_map[
cristy3ed852e2009-09-05 21:47:34 +00001374 ScaleQuantumToMap(image->colormap[i].green)].green);
1375 }
1376 if ((channel & BlueChannel) != 0)
1377 {
1378 if (black.blue != white.blue)
cristyce70c172010-01-07 17:15:30 +00001379 image->colormap[i].blue=ClampToQuantum(stretch_map[
cristy3ed852e2009-09-05 21:47:34 +00001380 ScaleQuantumToMap(image->colormap[i].blue)].blue);
1381 }
1382 if ((channel & OpacityChannel) != 0)
1383 {
1384 if (black.opacity != white.opacity)
cristyce70c172010-01-07 17:15:30 +00001385 image->colormap[i].opacity=ClampToQuantum(stretch_map[
cristy3ed852e2009-09-05 21:47:34 +00001386 ScaleQuantumToMap(image->colormap[i].opacity)].opacity);
1387 }
1388 }
1389 }
1390 /*
1391 Stretch image.
1392 */
1393 status=MagickTrue;
1394 progress=0;
cristyb5d5f722009-11-04 03:03:49 +00001395#if defined(MAGICKCORE_OPENMP_SUPPORT)
1396 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001397#endif
cristybb503372010-05-27 20:51:26 +00001398 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001399 {
1400 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00001401 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00001402
cristy3ed852e2009-09-05 21:47:34 +00001403 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001404 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001405
cristy8d4629b2010-08-30 17:59:46 +00001406 register ssize_t
1407 x;
1408
cristy3ed852e2009-09-05 21:47:34 +00001409 if (status == MagickFalse)
1410 continue;
1411 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
1412 if (q == (PixelPacket *) NULL)
1413 {
1414 status=MagickFalse;
1415 continue;
1416 }
1417 indexes=GetCacheViewAuthenticIndexQueue(image_view);
cristybb503372010-05-27 20:51:26 +00001418 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001419 {
1420 if ((channel & RedChannel) != 0)
1421 {
1422 if (black.red != white.red)
cristyce70c172010-01-07 17:15:30 +00001423 q->red=ClampToQuantum(stretch_map[ScaleQuantumToMap(q->red)].red);
cristy3ed852e2009-09-05 21:47:34 +00001424 }
1425 if ((channel & GreenChannel) != 0)
1426 {
1427 if (black.green != white.green)
cristyce70c172010-01-07 17:15:30 +00001428 q->green=ClampToQuantum(stretch_map[ScaleQuantumToMap(
cristy3ed852e2009-09-05 21:47:34 +00001429 q->green)].green);
1430 }
1431 if ((channel & BlueChannel) != 0)
1432 {
1433 if (black.blue != white.blue)
cristyce70c172010-01-07 17:15:30 +00001434 q->blue=ClampToQuantum(stretch_map[ScaleQuantumToMap(
cristy3ed852e2009-09-05 21:47:34 +00001435 q->blue)].blue);
1436 }
1437 if ((channel & OpacityChannel) != 0)
1438 {
1439 if (black.opacity != white.opacity)
cristyce70c172010-01-07 17:15:30 +00001440 q->opacity=ClampToQuantum(stretch_map[ScaleQuantumToMap(
cristy3ed852e2009-09-05 21:47:34 +00001441 q->opacity)].opacity);
1442 }
1443 if (((channel & IndexChannel) != 0) &&
1444 (image->colorspace == CMYKColorspace))
1445 {
1446 if (black.index != white.index)
cristyce70c172010-01-07 17:15:30 +00001447 indexes[x]=(IndexPacket) ClampToQuantum(stretch_map[
cristy3ed852e2009-09-05 21:47:34 +00001448 ScaleQuantumToMap(indexes[x])].index);
1449 }
1450 q++;
1451 }
1452 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
1453 status=MagickFalse;
1454 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1455 {
1456 MagickBooleanType
1457 proceed;
1458
cristyb5d5f722009-11-04 03:03:49 +00001459#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00001460 #pragma omp critical (MagickCore_ContrastStretchImageChannel)
1461#endif
1462 proceed=SetImageProgress(image,ContrastStretchImageTag,progress++,
1463 image->rows);
1464 if (proceed == MagickFalse)
1465 status=MagickFalse;
1466 }
1467 }
1468 image_view=DestroyCacheView(image_view);
1469 stretch_map=(MagickPixelPacket *) RelinquishMagickMemory(stretch_map);
1470 return(status);
1471}
1472
1473/*
1474%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1475% %
1476% %
1477% %
1478% E n h a n c e I m a g e %
1479% %
1480% %
1481% %
1482%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1483%
1484% EnhanceImage() applies a digital filter that improves the quality of a
1485% noisy image.
1486%
1487% The format of the EnhanceImage method is:
1488%
1489% Image *EnhanceImage(const Image *image,ExceptionInfo *exception)
1490%
1491% A description of each parameter follows:
1492%
1493% o image: the image.
1494%
1495% o exception: return any errors or warnings in this structure.
1496%
1497*/
1498MagickExport Image *EnhanceImage(const Image *image,ExceptionInfo *exception)
1499{
1500#define Enhance(weight) \
1501 mean=((MagickRealType) r->red+pixel.red)/2; \
1502 distance=(MagickRealType) r->red-(MagickRealType) pixel.red; \
1503 distance_squared=QuantumScale*(2.0*((MagickRealType) QuantumRange+1.0)+ \
1504 mean)*distance*distance; \
1505 mean=((MagickRealType) r->green+pixel.green)/2; \
1506 distance=(MagickRealType) r->green-(MagickRealType) pixel.green; \
1507 distance_squared+=4.0*distance*distance; \
1508 mean=((MagickRealType) r->blue+pixel.blue)/2; \
1509 distance=(MagickRealType) r->blue-(MagickRealType) pixel.blue; \
1510 distance_squared+=QuantumScale*(3.0*((MagickRealType) \
1511 QuantumRange+1.0)-1.0-mean)*distance*distance; \
1512 mean=((MagickRealType) r->opacity+pixel.opacity)/2; \
1513 distance=(MagickRealType) r->opacity-(MagickRealType) pixel.opacity; \
1514 distance_squared+=QuantumScale*(3.0*((MagickRealType) \
1515 QuantumRange+1.0)-1.0-mean)*distance*distance; \
1516 if (distance_squared < ((MagickRealType) QuantumRange*(MagickRealType) \
1517 QuantumRange/25.0f)) \
1518 { \
1519 aggregate.red+=(weight)*r->red; \
1520 aggregate.green+=(weight)*r->green; \
1521 aggregate.blue+=(weight)*r->blue; \
1522 aggregate.opacity+=(weight)*r->opacity; \
1523 total_weight+=(weight); \
1524 } \
1525 r++;
1526#define EnhanceImageTag "Enhance/Image"
1527
cristyc4c8d132010-01-07 01:58:38 +00001528 CacheView
1529 *enhance_view,
1530 *image_view;
1531
cristy3ed852e2009-09-05 21:47:34 +00001532 Image
1533 *enhance_image;
1534
cristy3ed852e2009-09-05 21:47:34 +00001535 MagickBooleanType
1536 status;
1537
cristybb503372010-05-27 20:51:26 +00001538 MagickOffsetType
1539 progress;
1540
cristy3ed852e2009-09-05 21:47:34 +00001541 MagickPixelPacket
1542 zero;
1543
cristybb503372010-05-27 20:51:26 +00001544 ssize_t
1545 y;
1546
cristy3ed852e2009-09-05 21:47:34 +00001547 /*
1548 Initialize enhanced image attributes.
1549 */
1550 assert(image != (const Image *) NULL);
1551 assert(image->signature == MagickSignature);
1552 if (image->debug != MagickFalse)
1553 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1554 assert(exception != (ExceptionInfo *) NULL);
1555 assert(exception->signature == MagickSignature);
1556 if ((image->columns < 5) || (image->rows < 5))
1557 return((Image *) NULL);
1558 enhance_image=CloneImage(image,image->columns,image->rows,MagickTrue,
1559 exception);
1560 if (enhance_image == (Image *) NULL)
1561 return((Image *) NULL);
1562 if (SetImageStorageClass(enhance_image,DirectClass) == MagickFalse)
1563 {
1564 InheritException(exception,&enhance_image->exception);
1565 enhance_image=DestroyImage(enhance_image);
1566 return((Image *) NULL);
1567 }
1568 /*
1569 Enhance image.
1570 */
1571 status=MagickTrue;
1572 progress=0;
1573 (void) ResetMagickMemory(&zero,0,sizeof(zero));
1574 image_view=AcquireCacheView(image);
1575 enhance_view=AcquireCacheView(enhance_image);
cristyb5d5f722009-11-04 03:03:49 +00001576#if defined(MAGICKCORE_OPENMP_SUPPORT)
1577 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001578#endif
cristybb503372010-05-27 20:51:26 +00001579 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001580 {
1581 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001582 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00001583
cristy3ed852e2009-09-05 21:47:34 +00001584 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001585 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001586
cristy8d4629b2010-08-30 17:59:46 +00001587 register ssize_t
1588 x;
1589
cristy3ed852e2009-09-05 21:47:34 +00001590 /*
1591 Read another scan line.
1592 */
1593 if (status == MagickFalse)
1594 continue;
1595 p=GetCacheViewVirtualPixels(image_view,-2,y-2,image->columns+4,5,exception);
1596 q=QueueCacheViewAuthenticPixels(enhance_view,0,y,enhance_image->columns,1,
1597 exception);
1598 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
1599 {
1600 status=MagickFalse;
1601 continue;
1602 }
cristybb503372010-05-27 20:51:26 +00001603 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001604 {
1605 MagickPixelPacket
1606 aggregate;
1607
1608 MagickRealType
1609 distance,
1610 distance_squared,
1611 mean,
1612 total_weight;
1613
1614 PixelPacket
1615 pixel;
1616
1617 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001618 *restrict r;
cristy3ed852e2009-09-05 21:47:34 +00001619
1620 /*
1621 Compute weighted average of target pixel color components.
1622 */
1623 aggregate=zero;
1624 total_weight=0.0;
1625 r=p+2*(image->columns+4)+2;
1626 pixel=(*r);
1627 r=p;
1628 Enhance(5.0); Enhance(8.0); Enhance(10.0); Enhance(8.0); Enhance(5.0);
1629 r=p+(image->columns+4);
1630 Enhance(8.0); Enhance(20.0); Enhance(40.0); Enhance(20.0); Enhance(8.0);
1631 r=p+2*(image->columns+4);
1632 Enhance(10.0); Enhance(40.0); Enhance(80.0); Enhance(40.0); Enhance(10.0);
1633 r=p+3*(image->columns+4);
1634 Enhance(8.0); Enhance(20.0); Enhance(40.0); Enhance(20.0); Enhance(8.0);
1635 r=p+4*(image->columns+4);
1636 Enhance(5.0); Enhance(8.0); Enhance(10.0); Enhance(8.0); Enhance(5.0);
1637 q->red=(Quantum) ((aggregate.red+(total_weight/2)-1)/total_weight);
1638 q->green=(Quantum) ((aggregate.green+(total_weight/2)-1)/total_weight);
1639 q->blue=(Quantum) ((aggregate.blue+(total_weight/2)-1)/total_weight);
1640 q->opacity=(Quantum) ((aggregate.opacity+(total_weight/2)-1)/
1641 total_weight);
1642 p++;
1643 q++;
1644 }
1645 if (SyncCacheViewAuthenticPixels(enhance_view,exception) == MagickFalse)
1646 status=MagickFalse;
1647 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1648 {
1649 MagickBooleanType
1650 proceed;
1651
cristyb5d5f722009-11-04 03:03:49 +00001652#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00001653 #pragma omp critical (MagickCore_EnhanceImage)
1654#endif
1655 proceed=SetImageProgress(image,EnhanceImageTag,progress++,image->rows);
1656 if (proceed == MagickFalse)
1657 status=MagickFalse;
1658 }
1659 }
1660 enhance_view=DestroyCacheView(enhance_view);
1661 image_view=DestroyCacheView(image_view);
1662 return(enhance_image);
1663}
1664
1665/*
1666%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1667% %
1668% %
1669% %
1670% E q u a l i z e I m a g e %
1671% %
1672% %
1673% %
1674%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1675%
1676% EqualizeImage() applies a histogram equalization to the image.
1677%
1678% The format of the EqualizeImage method is:
1679%
1680% MagickBooleanType EqualizeImage(Image *image)
1681% MagickBooleanType EqualizeImageChannel(Image *image,
1682% const ChannelType channel)
1683%
1684% A description of each parameter follows:
1685%
1686% o image: the image.
1687%
1688% o channel: the channel.
1689%
1690*/
1691
1692MagickExport MagickBooleanType EqualizeImage(Image *image)
1693{
1694 return(EqualizeImageChannel(image,DefaultChannels));
1695}
1696
1697MagickExport MagickBooleanType EqualizeImageChannel(Image *image,
1698 const ChannelType channel)
1699{
1700#define EqualizeImageTag "Equalize/Image"
1701
cristyc4c8d132010-01-07 01:58:38 +00001702 CacheView
1703 *image_view;
1704
cristy3ed852e2009-09-05 21:47:34 +00001705 ExceptionInfo
1706 *exception;
1707
cristy3ed852e2009-09-05 21:47:34 +00001708 MagickBooleanType
1709 status;
1710
cristybb503372010-05-27 20:51:26 +00001711 MagickOffsetType
1712 progress;
1713
cristy3ed852e2009-09-05 21:47:34 +00001714 MagickPixelPacket
1715 black,
1716 *equalize_map,
1717 *histogram,
1718 intensity,
1719 *map,
1720 white;
1721
cristybb503372010-05-27 20:51:26 +00001722 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001723 i;
1724
cristybb503372010-05-27 20:51:26 +00001725 ssize_t
1726 y;
1727
cristy3ed852e2009-09-05 21:47:34 +00001728 /*
1729 Allocate and initialize histogram arrays.
1730 */
1731 assert(image != (Image *) NULL);
1732 assert(image->signature == MagickSignature);
1733 if (image->debug != MagickFalse)
1734 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1735 equalize_map=(MagickPixelPacket *) AcquireQuantumMemory(MaxMap+1UL,
1736 sizeof(*equalize_map));
1737 histogram=(MagickPixelPacket *) AcquireQuantumMemory(MaxMap+1UL,
1738 sizeof(*histogram));
1739 map=(MagickPixelPacket *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*map));
1740 if ((equalize_map == (MagickPixelPacket *) NULL) ||
1741 (histogram == (MagickPixelPacket *) NULL) ||
1742 (map == (MagickPixelPacket *) NULL))
1743 {
1744 if (map != (MagickPixelPacket *) NULL)
1745 map=(MagickPixelPacket *) RelinquishMagickMemory(map);
1746 if (histogram != (MagickPixelPacket *) NULL)
1747 histogram=(MagickPixelPacket *) RelinquishMagickMemory(histogram);
1748 if (equalize_map != (MagickPixelPacket *) NULL)
1749 equalize_map=(MagickPixelPacket *) RelinquishMagickMemory(equalize_map);
1750 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
1751 image->filename);
1752 }
1753 /*
1754 Form histogram.
1755 */
1756 (void) ResetMagickMemory(histogram,0,(MaxMap+1)*sizeof(*histogram));
1757 exception=(&image->exception);
cristybb503372010-05-27 20:51:26 +00001758 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001759 {
1760 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00001761 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00001762
1763 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001764 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00001765
cristybb503372010-05-27 20:51:26 +00001766 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001767 x;
1768
1769 p=GetVirtualPixels(image,0,y,image->columns,1,exception);
1770 if (p == (const PixelPacket *) NULL)
1771 break;
1772 indexes=GetVirtualIndexQueue(image);
cristybb503372010-05-27 20:51:26 +00001773 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001774 {
1775 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001776 histogram[ScaleQuantumToMap(GetRedPixelComponent(p))].red++;
cristy3ed852e2009-09-05 21:47:34 +00001777 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001778 histogram[ScaleQuantumToMap(GetGreenPixelComponent(p))].green++;
cristy3ed852e2009-09-05 21:47:34 +00001779 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001780 histogram[ScaleQuantumToMap(GetBluePixelComponent(p))].blue++;
cristy3ed852e2009-09-05 21:47:34 +00001781 if ((channel & OpacityChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001782 histogram[ScaleQuantumToMap(GetOpacityPixelComponent(p))].opacity++;
cristy3ed852e2009-09-05 21:47:34 +00001783 if (((channel & IndexChannel) != 0) &&
1784 (image->colorspace == CMYKColorspace))
1785 histogram[ScaleQuantumToMap(indexes[x])].index++;
1786 p++;
1787 }
1788 }
1789 /*
1790 Integrate the histogram to get the equalization map.
1791 */
1792 (void) ResetMagickMemory(&intensity,0,sizeof(intensity));
cristybb503372010-05-27 20:51:26 +00001793 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +00001794 {
1795 if ((channel & RedChannel) != 0)
1796 intensity.red+=histogram[i].red;
1797 if ((channel & GreenChannel) != 0)
1798 intensity.green+=histogram[i].green;
1799 if ((channel & BlueChannel) != 0)
1800 intensity.blue+=histogram[i].blue;
1801 if ((channel & OpacityChannel) != 0)
1802 intensity.opacity+=histogram[i].opacity;
1803 if (((channel & IndexChannel) != 0) &&
1804 (image->colorspace == CMYKColorspace))
1805 intensity.index+=histogram[i].index;
1806 map[i]=intensity;
1807 }
1808 black=map[0];
1809 white=map[(int) MaxMap];
1810 (void) ResetMagickMemory(equalize_map,0,(MaxMap+1)*sizeof(*equalize_map));
cristyb5d5f722009-11-04 03:03:49 +00001811#if defined(MAGICKCORE_OPENMP_SUPPORT)
1812 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001813#endif
cristybb503372010-05-27 20:51:26 +00001814 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +00001815 {
1816 if (((channel & RedChannel) != 0) && (white.red != black.red))
1817 equalize_map[i].red=(MagickRealType) ScaleMapToQuantum((MagickRealType)
1818 ((MaxMap*(map[i].red-black.red))/(white.red-black.red)));
1819 if (((channel & GreenChannel) != 0) && (white.green != black.green))
1820 equalize_map[i].green=(MagickRealType) ScaleMapToQuantum((MagickRealType)
1821 ((MaxMap*(map[i].green-black.green))/(white.green-black.green)));
1822 if (((channel & BlueChannel) != 0) && (white.blue != black.blue))
1823 equalize_map[i].blue=(MagickRealType) ScaleMapToQuantum((MagickRealType)
1824 ((MaxMap*(map[i].blue-black.blue))/(white.blue-black.blue)));
1825 if (((channel & OpacityChannel) != 0) && (white.opacity != black.opacity))
1826 equalize_map[i].opacity=(MagickRealType) ScaleMapToQuantum(
1827 (MagickRealType) ((MaxMap*(map[i].opacity-black.opacity))/
1828 (white.opacity-black.opacity)));
1829 if ((((channel & IndexChannel) != 0) &&
1830 (image->colorspace == CMYKColorspace)) &&
1831 (white.index != black.index))
1832 equalize_map[i].index=(MagickRealType) ScaleMapToQuantum((MagickRealType)
1833 ((MaxMap*(map[i].index-black.index))/(white.index-black.index)));
1834 }
1835 histogram=(MagickPixelPacket *) RelinquishMagickMemory(histogram);
1836 map=(MagickPixelPacket *) RelinquishMagickMemory(map);
1837 if (image->storage_class == PseudoClass)
1838 {
1839 /*
1840 Equalize colormap.
1841 */
cristyb5d5f722009-11-04 03:03:49 +00001842#if defined(MAGICKCORE_OPENMP_SUPPORT)
1843 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001844#endif
cristybb503372010-05-27 20:51:26 +00001845 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00001846 {
1847 if (((channel & RedChannel) != 0) && (white.red != black.red))
cristyce70c172010-01-07 17:15:30 +00001848 image->colormap[i].red=ClampToQuantum(equalize_map[
cristy3ed852e2009-09-05 21:47:34 +00001849 ScaleQuantumToMap(image->colormap[i].red)].red);
1850 if (((channel & GreenChannel) != 0) && (white.green != black.green))
cristyce70c172010-01-07 17:15:30 +00001851 image->colormap[i].green=ClampToQuantum(equalize_map[
cristy3ed852e2009-09-05 21:47:34 +00001852 ScaleQuantumToMap(image->colormap[i].green)].green);
1853 if (((channel & BlueChannel) != 0) && (white.blue != black.blue))
cristyce70c172010-01-07 17:15:30 +00001854 image->colormap[i].blue=ClampToQuantum(equalize_map[
cristy3ed852e2009-09-05 21:47:34 +00001855 ScaleQuantumToMap(image->colormap[i].blue)].blue);
1856 if (((channel & OpacityChannel) != 0) &&
1857 (white.opacity != black.opacity))
cristyce70c172010-01-07 17:15:30 +00001858 image->colormap[i].opacity=ClampToQuantum(equalize_map[
cristy3ed852e2009-09-05 21:47:34 +00001859 ScaleQuantumToMap(image->colormap[i].opacity)].opacity);
1860 }
1861 }
1862 /*
1863 Equalize image.
1864 */
1865 status=MagickTrue;
1866 progress=0;
1867 exception=(&image->exception);
1868 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00001869#if defined(MAGICKCORE_OPENMP_SUPPORT)
1870 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001871#endif
cristybb503372010-05-27 20:51:26 +00001872 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001873 {
1874 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00001875 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00001876
cristy3ed852e2009-09-05 21:47:34 +00001877 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001878 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001879
cristy8d4629b2010-08-30 17:59:46 +00001880 register ssize_t
1881 x;
1882
cristy3ed852e2009-09-05 21:47:34 +00001883 if (status == MagickFalse)
1884 continue;
1885 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
1886 if (q == (PixelPacket *) NULL)
1887 {
1888 status=MagickFalse;
1889 continue;
1890 }
1891 indexes=GetCacheViewAuthenticIndexQueue(image_view);
cristybb503372010-05-27 20:51:26 +00001892 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001893 {
1894 if (((channel & RedChannel) != 0) && (white.red != black.red))
cristyce70c172010-01-07 17:15:30 +00001895 q->red=ClampToQuantum(equalize_map[ScaleQuantumToMap(q->red)].red);
cristy3ed852e2009-09-05 21:47:34 +00001896 if (((channel & GreenChannel) != 0) && (white.green != black.green))
cristyce70c172010-01-07 17:15:30 +00001897 q->green=ClampToQuantum(equalize_map[ScaleQuantumToMap(
cristy3ed852e2009-09-05 21:47:34 +00001898 q->green)].green);
1899 if (((channel & BlueChannel) != 0) && (white.blue != black.blue))
cristyce70c172010-01-07 17:15:30 +00001900 q->blue=ClampToQuantum(equalize_map[ScaleQuantumToMap(q->blue)].blue);
cristy3ed852e2009-09-05 21:47:34 +00001901 if (((channel & OpacityChannel) != 0) && (white.opacity != black.opacity))
cristyce70c172010-01-07 17:15:30 +00001902 q->opacity=ClampToQuantum(equalize_map[ScaleQuantumToMap(
cristy3ed852e2009-09-05 21:47:34 +00001903 q->opacity)].opacity);
1904 if ((((channel & IndexChannel) != 0) &&
1905 (image->colorspace == CMYKColorspace)) &&
1906 (white.index != black.index))
cristyce70c172010-01-07 17:15:30 +00001907 indexes[x]=ClampToQuantum(equalize_map[ScaleQuantumToMap(
cristy3ed852e2009-09-05 21:47:34 +00001908 indexes[x])].index);
1909 q++;
1910 }
1911 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
1912 status=MagickFalse;
1913 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1914 {
1915 MagickBooleanType
1916 proceed;
1917
cristyb5d5f722009-11-04 03:03:49 +00001918#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00001919 #pragma omp critical (MagickCore_EqualizeImageChannel)
1920#endif
1921 proceed=SetImageProgress(image,EqualizeImageTag,progress++,image->rows);
1922 if (proceed == MagickFalse)
1923 status=MagickFalse;
1924 }
1925 }
1926 image_view=DestroyCacheView(image_view);
1927 equalize_map=(MagickPixelPacket *) RelinquishMagickMemory(equalize_map);
1928 return(status);
1929}
1930
1931/*
1932%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1933% %
1934% %
1935% %
1936% G a m m a I m a g e %
1937% %
1938% %
1939% %
1940%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1941%
1942% GammaImage() gamma-corrects a particular image channel. The same
1943% image viewed on different devices will have perceptual differences in the
1944% way the image's intensities are represented on the screen. Specify
1945% individual gamma levels for the red, green, and blue channels, or adjust
1946% all three with the gamma parameter. Values typically range from 0.8 to 2.3.
1947%
1948% You can also reduce the influence of a particular channel with a gamma
1949% value of 0.
1950%
1951% The format of the GammaImage method is:
1952%
cristya6360142011-03-23 23:08:04 +00001953% MagickBooleanType GammaImage(Image *image,const char *level)
cristy3ed852e2009-09-05 21:47:34 +00001954% MagickBooleanType GammaImageChannel(Image *image,
1955% const ChannelType channel,const double gamma)
1956%
1957% A description of each parameter follows:
1958%
1959% o image: the image.
1960%
1961% o channel: the channel.
1962%
cristya6360142011-03-23 23:08:04 +00001963% o level: the image gamma as a string (e.g. 1.6,1.2,1.0).
1964%
cristy3ed852e2009-09-05 21:47:34 +00001965% o gamma: the image gamma.
1966%
1967*/
1968MagickExport MagickBooleanType GammaImage(Image *image,const char *level)
1969{
1970 GeometryInfo
1971 geometry_info;
1972
1973 MagickPixelPacket
1974 gamma;
1975
1976 MagickStatusType
1977 flags,
1978 status;
1979
1980 assert(image != (Image *) NULL);
1981 assert(image->signature == MagickSignature);
1982 if (image->debug != MagickFalse)
1983 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1984 if (level == (char *) NULL)
1985 return(MagickFalse);
1986 flags=ParseGeometry(level,&geometry_info);
1987 gamma.red=geometry_info.rho;
1988 gamma.green=geometry_info.sigma;
1989 if ((flags & SigmaValue) == 0)
1990 gamma.green=gamma.red;
1991 gamma.blue=geometry_info.xi;
1992 if ((flags & XiValue) == 0)
1993 gamma.blue=gamma.red;
1994 if ((gamma.red == 1.0) && (gamma.green == 1.0) && (gamma.blue == 1.0))
1995 return(MagickTrue);
1996 if ((gamma.red == gamma.green) && (gamma.green == gamma.blue))
1997 status=GammaImageChannel(image,(const ChannelType) (RedChannel |
1998 GreenChannel | BlueChannel),(double) gamma.red);
1999 else
2000 {
2001 status=GammaImageChannel(image,RedChannel,(double) gamma.red);
2002 status|=GammaImageChannel(image,GreenChannel,(double) gamma.green);
2003 status|=GammaImageChannel(image,BlueChannel,(double) gamma.blue);
2004 }
2005 return(status != 0 ? MagickTrue : MagickFalse);
2006}
2007
2008MagickExport MagickBooleanType GammaImageChannel(Image *image,
2009 const ChannelType channel,const double gamma)
2010{
2011#define GammaCorrectImageTag "GammaCorrect/Image"
2012
cristyc4c8d132010-01-07 01:58:38 +00002013 CacheView
2014 *image_view;
2015
cristy3ed852e2009-09-05 21:47:34 +00002016 ExceptionInfo
2017 *exception;
2018
cristy3ed852e2009-09-05 21:47:34 +00002019 MagickBooleanType
2020 status;
2021
cristybb503372010-05-27 20:51:26 +00002022 MagickOffsetType
2023 progress;
2024
cristy3ed852e2009-09-05 21:47:34 +00002025 Quantum
2026 *gamma_map;
2027
cristybb503372010-05-27 20:51:26 +00002028 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002029 i;
2030
cristybb503372010-05-27 20:51:26 +00002031 ssize_t
2032 y;
2033
cristy3ed852e2009-09-05 21:47:34 +00002034 /*
2035 Allocate and initialize gamma maps.
2036 */
2037 assert(image != (Image *) NULL);
2038 assert(image->signature == MagickSignature);
2039 if (image->debug != MagickFalse)
2040 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2041 if (gamma == 1.0)
2042 return(MagickTrue);
2043 gamma_map=(Quantum *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*gamma_map));
2044 if (gamma_map == (Quantum *) NULL)
2045 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
2046 image->filename);
2047 (void) ResetMagickMemory(gamma_map,0,(MaxMap+1)*sizeof(*gamma_map));
2048 if (gamma != 0.0)
cristyb5d5f722009-11-04 03:03:49 +00002049#if defined(MAGICKCORE_OPENMP_SUPPORT)
2050 #pragma omp parallel for schedule(dynamic,4)
cristy3ed852e2009-09-05 21:47:34 +00002051#endif
cristybb503372010-05-27 20:51:26 +00002052 for (i=0; i <= (ssize_t) MaxMap; i++)
cristyce70c172010-01-07 17:15:30 +00002053 gamma_map[i]=ClampToQuantum((MagickRealType) ScaleMapToQuantum((
cristy3ed852e2009-09-05 21:47:34 +00002054 MagickRealType) (MaxMap*pow((double) i/MaxMap,1.0/gamma))));
2055 if (image->storage_class == PseudoClass)
2056 {
2057 /*
2058 Gamma-correct colormap.
2059 */
cristyb5d5f722009-11-04 03:03:49 +00002060#if defined(MAGICKCORE_OPENMP_SUPPORT)
2061 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002062#endif
cristybb503372010-05-27 20:51:26 +00002063 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00002064 {
2065 if ((channel & RedChannel) != 0)
2066 image->colormap[i].red=gamma_map[
2067 ScaleQuantumToMap(image->colormap[i].red)];
2068 if ((channel & GreenChannel) != 0)
2069 image->colormap[i].green=gamma_map[
2070 ScaleQuantumToMap(image->colormap[i].green)];
2071 if ((channel & BlueChannel) != 0)
2072 image->colormap[i].blue=gamma_map[
2073 ScaleQuantumToMap(image->colormap[i].blue)];
2074 if ((channel & OpacityChannel) != 0)
2075 {
2076 if (image->matte == MagickFalse)
2077 image->colormap[i].opacity=gamma_map[
2078 ScaleQuantumToMap(image->colormap[i].opacity)];
2079 else
2080 image->colormap[i].opacity=(Quantum) QuantumRange-
2081 gamma_map[ScaleQuantumToMap((Quantum) (QuantumRange-
2082 image->colormap[i].opacity))];
2083 }
2084 }
2085 }
2086 /*
2087 Gamma-correct image.
2088 */
2089 status=MagickTrue;
2090 progress=0;
2091 exception=(&image->exception);
2092 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00002093#if defined(MAGICKCORE_OPENMP_SUPPORT)
2094 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002095#endif
cristybb503372010-05-27 20:51:26 +00002096 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002097 {
2098 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00002099 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00002100
cristy3ed852e2009-09-05 21:47:34 +00002101 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00002102 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002103
cristy8d4629b2010-08-30 17:59:46 +00002104 register ssize_t
2105 x;
2106
cristy3ed852e2009-09-05 21:47:34 +00002107 if (status == MagickFalse)
2108 continue;
2109 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2110 if (q == (PixelPacket *) NULL)
2111 {
2112 status=MagickFalse;
2113 continue;
2114 }
2115 indexes=GetCacheViewAuthenticIndexQueue(image_view);
cristybb503372010-05-27 20:51:26 +00002116 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002117 {
cristy6cbd7f52009-10-17 16:06:51 +00002118 if (channel == DefaultChannels)
cristy3ed852e2009-09-05 21:47:34 +00002119 {
cristy6cbd7f52009-10-17 16:06:51 +00002120 q->red=gamma_map[ScaleQuantumToMap(q->red)];
2121 q->green=gamma_map[ScaleQuantumToMap(q->green)];
2122 q->blue=gamma_map[ScaleQuantumToMap(q->blue)];
2123 }
2124 else
2125 {
2126 if ((channel & RedChannel) != 0)
2127 q->red=gamma_map[ScaleQuantumToMap(q->red)];
2128 if ((channel & GreenChannel) != 0)
2129 q->green=gamma_map[ScaleQuantumToMap(q->green)];
2130 if ((channel & BlueChannel) != 0)
2131 q->blue=gamma_map[ScaleQuantumToMap(q->blue)];
2132 if ((channel & OpacityChannel) != 0)
2133 {
2134 if (image->matte == MagickFalse)
2135 q->opacity=gamma_map[ScaleQuantumToMap(q->opacity)];
2136 else
2137 q->opacity=(Quantum) QuantumRange-gamma_map[
cristy46f08202010-01-10 04:04:21 +00002138 ScaleQuantumToMap((Quantum) GetAlphaPixelComponent(q))];
cristy6cbd7f52009-10-17 16:06:51 +00002139 }
cristy3ed852e2009-09-05 21:47:34 +00002140 }
2141 q++;
2142 }
2143 if (((channel & IndexChannel) != 0) &&
2144 (image->colorspace == CMYKColorspace))
cristybb503372010-05-27 20:51:26 +00002145 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002146 indexes[x]=gamma_map[ScaleQuantumToMap(indexes[x])];
2147 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2148 status=MagickFalse;
2149 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2150 {
2151 MagickBooleanType
2152 proceed;
2153
cristyb5d5f722009-11-04 03:03:49 +00002154#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00002155 #pragma omp critical (MagickCore_GammaImageChannel)
2156#endif
2157 proceed=SetImageProgress(image,GammaCorrectImageTag,progress++,
2158 image->rows);
2159 if (proceed == MagickFalse)
2160 status=MagickFalse;
2161 }
2162 }
2163 image_view=DestroyCacheView(image_view);
2164 gamma_map=(Quantum *) RelinquishMagickMemory(gamma_map);
2165 if (image->gamma != 0.0)
2166 image->gamma*=gamma;
2167 return(status);
2168}
2169
2170/*
2171%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2172% %
2173% %
2174% %
2175% H a l d C l u t I m a g e %
2176% %
2177% %
2178% %
2179%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2180%
2181% HaldClutImage() applies a Hald color lookup table to the image. A Hald
2182% color lookup table is a 3-dimensional color cube mapped to 2 dimensions.
2183% Create it with the HALD coder. You can apply any color transformation to
2184% the Hald image and then use this method to apply the transform to the
2185% image.
2186%
2187% The format of the HaldClutImage method is:
2188%
2189% MagickBooleanType HaldClutImage(Image *image,Image *hald_image)
2190% MagickBooleanType HaldClutImageChannel(Image *image,
2191% const ChannelType channel,Image *hald_image)
2192%
2193% A description of each parameter follows:
2194%
2195% o image: the image, which is replaced by indexed CLUT values
2196%
2197% o hald_image: the color lookup table image for replacement color values.
2198%
2199% o channel: the channel.
2200%
2201*/
2202
2203static inline size_t MagickMin(const size_t x,const size_t y)
2204{
2205 if (x < y)
2206 return(x);
2207 return(y);
2208}
2209
2210MagickExport MagickBooleanType HaldClutImage(Image *image,
2211 const Image *hald_image)
2212{
2213 return(HaldClutImageChannel(image,DefaultChannels,hald_image));
2214}
2215
2216MagickExport MagickBooleanType HaldClutImageChannel(Image *image,
2217 const ChannelType channel,const Image *hald_image)
2218{
2219#define HaldClutImageTag "Clut/Image"
2220
2221 typedef struct _HaldInfo
2222 {
2223 MagickRealType
2224 x,
2225 y,
2226 z;
2227 } HaldInfo;
2228
cristyfa112112010-01-04 17:48:07 +00002229 CacheView
2230 *image_view;
2231
cristy3ed852e2009-09-05 21:47:34 +00002232 double
2233 width;
2234
2235 ExceptionInfo
2236 *exception;
2237
cristy3ed852e2009-09-05 21:47:34 +00002238 MagickBooleanType
2239 status;
2240
cristybb503372010-05-27 20:51:26 +00002241 MagickOffsetType
2242 progress;
2243
cristy3ed852e2009-09-05 21:47:34 +00002244 MagickPixelPacket
2245 zero;
2246
cristy3ed852e2009-09-05 21:47:34 +00002247 size_t
2248 cube_size,
2249 length,
2250 level;
2251
cristybb503372010-05-27 20:51:26 +00002252 ssize_t
2253 y;
2254
cristy3ed852e2009-09-05 21:47:34 +00002255 assert(image != (Image *) NULL);
2256 assert(image->signature == MagickSignature);
2257 if (image->debug != MagickFalse)
2258 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2259 assert(hald_image != (Image *) NULL);
2260 assert(hald_image->signature == MagickSignature);
2261 if (SetImageStorageClass(image,DirectClass) == MagickFalse)
2262 return(MagickFalse);
2263 if (image->matte == MagickFalse)
2264 (void) SetImageAlphaChannel(image,OpaqueAlphaChannel);
2265 /*
2266 Hald clut image.
2267 */
2268 status=MagickTrue;
2269 progress=0;
2270 length=MagickMin(hald_image->columns,hald_image->rows);
2271 for (level=2; (level*level*level) < length; level++) ;
2272 level*=level;
2273 cube_size=level*level;
2274 width=(double) hald_image->columns;
2275 GetMagickPixelPacket(hald_image,&zero);
2276 exception=(&image->exception);
cristy3ed852e2009-09-05 21:47:34 +00002277 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00002278#if defined(MAGICKCORE_OPENMP_SUPPORT)
2279 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002280#endif
cristybb503372010-05-27 20:51:26 +00002281 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002282 {
2283 double
2284 offset;
2285
2286 HaldInfo
2287 point;
2288
2289 MagickPixelPacket
2290 pixel,
2291 pixel1,
2292 pixel2,
2293 pixel3,
2294 pixel4;
2295
2296 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00002297 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00002298
cristy3ed852e2009-09-05 21:47:34 +00002299 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00002300 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002301
cristy8d4629b2010-08-30 17:59:46 +00002302 register ssize_t
2303 x;
2304
cristy3ed852e2009-09-05 21:47:34 +00002305 if (status == MagickFalse)
2306 continue;
2307 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2308 if (q == (PixelPacket *) NULL)
2309 {
2310 status=MagickFalse;
2311 continue;
2312 }
2313 indexes=GetCacheViewAuthenticIndexQueue(image_view);
2314 pixel=zero;
2315 pixel1=zero;
2316 pixel2=zero;
2317 pixel3=zero;
2318 pixel4=zero;
cristybb503372010-05-27 20:51:26 +00002319 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002320 {
2321 point.x=QuantumScale*(level-1.0)*q->red;
2322 point.y=QuantumScale*(level-1.0)*q->green;
2323 point.z=QuantumScale*(level-1.0)*q->blue;
2324 offset=point.x+level*floor(point.y)+cube_size*floor(point.z);
2325 point.x-=floor(point.x);
2326 point.y-=floor(point.y);
2327 point.z-=floor(point.z);
cristyd76c51e2011-03-26 00:21:26 +00002328 (void) InterpolatePixelPacket(image,image_view,image->interpolate,
2329 fmod(offset,width),floor(offset/width),&pixel1,exception);
2330 (void) InterpolatePixelPacket(image,image_view,image->interpolate,
2331 fmod(offset+level,width),floor((offset+level)/width),&pixel2,exception);
cristy3ed852e2009-09-05 21:47:34 +00002332 MagickPixelCompositeAreaBlend(&pixel1,pixel1.opacity,&pixel2,
2333 pixel2.opacity,point.y,&pixel3);
2334 offset+=cube_size;
cristyd76c51e2011-03-26 00:21:26 +00002335 (void) InterpolatePixelPacket(image,image_view,image->interpolate,
2336 fmod(offset,width),floor(offset/width),&pixel1,exception);
2337 (void) InterpolatePixelPacket(image,image_view,image->interpolate,
2338 fmod(offset+level,width),floor((offset+level)/width),&pixel2,exception);
cristy3ed852e2009-09-05 21:47:34 +00002339 MagickPixelCompositeAreaBlend(&pixel1,pixel1.opacity,&pixel2,
2340 pixel2.opacity,point.y,&pixel4);
2341 MagickPixelCompositeAreaBlend(&pixel3,pixel3.opacity,&pixel4,
2342 pixel4.opacity,point.z,&pixel);
2343 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00002344 SetRedPixelComponent(q,ClampRedPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00002345 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00002346 SetGreenPixelComponent(q,ClampGreenPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00002347 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00002348 SetBluePixelComponent(q,ClampBluePixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00002349 if (((channel & OpacityChannel) != 0) && (image->matte != MagickFalse))
cristyce70c172010-01-07 17:15:30 +00002350 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00002351 if (((channel & IndexChannel) != 0) &&
2352 (image->colorspace == CMYKColorspace))
cristyce70c172010-01-07 17:15:30 +00002353 indexes[x]=ClampToQuantum(pixel.index);
cristy3ed852e2009-09-05 21:47:34 +00002354 q++;
2355 }
2356 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2357 status=MagickFalse;
2358 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2359 {
2360 MagickBooleanType
2361 proceed;
2362
cristyb5d5f722009-11-04 03:03:49 +00002363#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00002364 #pragma omp critical (MagickCore_HaldClutImageChannel)
2365#endif
2366 proceed=SetImageProgress(image,HaldClutImageTag,progress++,image->rows);
2367 if (proceed == MagickFalse)
2368 status=MagickFalse;
2369 }
2370 }
2371 image_view=DestroyCacheView(image_view);
cristy3ed852e2009-09-05 21:47:34 +00002372 return(status);
2373}
2374
2375/*
2376%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2377% %
2378% %
2379% %
2380% L e v e l I m a g e %
2381% %
2382% %
2383% %
2384%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2385%
2386% LevelImage() adjusts the levels of a particular image channel by
2387% scaling the colors falling between specified white and black points to
2388% the full available quantum range.
2389%
2390% The parameters provided represent the black, and white points. The black
2391% point specifies the darkest color in the image. Colors darker than the
2392% black point are set to zero. White point specifies the lightest color in
2393% the image. Colors brighter than the white point are set to the maximum
2394% quantum value.
2395%
2396% If a '!' flag is given, map black and white colors to the given levels
2397% rather than mapping those levels to black and white. See
2398% LevelizeImageChannel() and LevelizeImageChannel(), below.
2399%
2400% Gamma specifies a gamma correction to apply to the image.
2401%
2402% The format of the LevelImage method is:
2403%
2404% MagickBooleanType LevelImage(Image *image,const char *levels)
2405%
2406% A description of each parameter follows:
2407%
2408% o image: the image.
2409%
2410% o levels: Specify the levels where the black and white points have the
2411% range of 0-QuantumRange, and gamma has the range 0-10 (e.g. 10x90%+2).
2412% A '!' flag inverts the re-mapping.
2413%
2414*/
2415
2416MagickExport MagickBooleanType LevelImage(Image *image,const char *levels)
2417{
2418 double
2419 black_point,
2420 gamma,
2421 white_point;
2422
2423 GeometryInfo
2424 geometry_info;
2425
2426 MagickBooleanType
2427 status;
2428
2429 MagickStatusType
2430 flags;
2431
2432 /*
2433 Parse levels.
2434 */
2435 if (levels == (char *) NULL)
2436 return(MagickFalse);
2437 flags=ParseGeometry(levels,&geometry_info);
2438 black_point=geometry_info.rho;
2439 white_point=(double) QuantumRange;
2440 if ((flags & SigmaValue) != 0)
2441 white_point=geometry_info.sigma;
2442 gamma=1.0;
2443 if ((flags & XiValue) != 0)
2444 gamma=geometry_info.xi;
2445 if ((flags & PercentValue) != 0)
2446 {
2447 black_point*=(double) image->columns*image->rows/100.0;
2448 white_point*=(double) image->columns*image->rows/100.0;
2449 }
2450 if ((flags & SigmaValue) == 0)
2451 white_point=(double) QuantumRange-black_point;
2452 if ((flags & AspectValue ) == 0)
2453 status=LevelImageChannel(image,DefaultChannels,black_point,white_point,
2454 gamma);
2455 else
cristy308b4e62009-09-21 14:40:44 +00002456 status=LevelizeImage(image,black_point,white_point,gamma);
cristy3ed852e2009-09-05 21:47:34 +00002457 return(status);
2458}
2459
2460/*
2461%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2462% %
2463% %
2464% %
cristy308b4e62009-09-21 14:40:44 +00002465% L e v e l i z e I m a g e %
cristy3ed852e2009-09-05 21:47:34 +00002466% %
2467% %
2468% %
2469%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2470%
cristy308b4e62009-09-21 14:40:44 +00002471% LevelizeImage() applies the normal level operation to the image, spreading
2472% out the values between the black and white points over the entire range of
2473% values. Gamma correction is also applied after the values has been mapped.
cristy3ed852e2009-09-05 21:47:34 +00002474%
2475% It is typically used to improve image contrast, or to provide a controlled
2476% linear threshold for the image. If the black and white points are set to
2477% the minimum and maximum values found in the image, the image can be
2478% normalized. or by swapping black and white values, negate the image.
2479%
cristy308b4e62009-09-21 14:40:44 +00002480% The format of the LevelizeImage method is:
cristy3ed852e2009-09-05 21:47:34 +00002481%
cristy308b4e62009-09-21 14:40:44 +00002482% MagickBooleanType LevelizeImage(Image *image,const double black_point,
2483% const double white_point,const double gamma)
2484% MagickBooleanType LevelizeImageChannel(Image *image,
2485% const ChannelType channel,const double black_point,
2486% const double white_point,const double gamma)
cristy3ed852e2009-09-05 21:47:34 +00002487%
2488% A description of each parameter follows:
2489%
2490% o image: the image.
2491%
2492% o channel: the channel.
2493%
2494% o black_point: The level which is to be mapped to zero (black)
2495%
2496% o white_point: The level which is to be mapped to QuantiumRange (white)
2497%
2498% o gamma: adjust gamma by this factor before mapping values.
2499% use 1.0 for purely linear stretching of image color values
2500%
2501*/
2502MagickExport MagickBooleanType LevelImageChannel(Image *image,
2503 const ChannelType channel,const double black_point,const double white_point,
2504 const double gamma)
2505{
2506#define LevelImageTag "Level/Image"
cristybcfb0432010-05-06 01:45:33 +00002507#define LevelQuantum(x) (ClampToQuantum((MagickRealType) QuantumRange* \
cristyc1f508d2010-04-22 01:21:47 +00002508 pow(scale*((double) (x)-black_point),1.0/gamma)))
cristy3ed852e2009-09-05 21:47:34 +00002509
cristyc4c8d132010-01-07 01:58:38 +00002510 CacheView
2511 *image_view;
2512
cristy3ed852e2009-09-05 21:47:34 +00002513 ExceptionInfo
2514 *exception;
2515
cristy3ed852e2009-09-05 21:47:34 +00002516 MagickBooleanType
2517 status;
2518
cristybb503372010-05-27 20:51:26 +00002519 MagickOffsetType
2520 progress;
2521
anthony7fe39fc2010-04-06 03:19:20 +00002522 register double
2523 scale;
2524
cristy8d4629b2010-08-30 17:59:46 +00002525 register ssize_t
2526 i;
2527
cristybb503372010-05-27 20:51:26 +00002528 ssize_t
2529 y;
2530
cristy3ed852e2009-09-05 21:47:34 +00002531 /*
2532 Allocate and initialize levels map.
2533 */
2534 assert(image != (Image *) NULL);
2535 assert(image->signature == MagickSignature);
2536 if (image->debug != MagickFalse)
2537 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
cristy8d4629b2010-08-30 17:59:46 +00002538 scale=(white_point != black_point) ? 1.0/(white_point-black_point) : 1.0;
cristy3ed852e2009-09-05 21:47:34 +00002539 if (image->storage_class == PseudoClass)
cristyb5d5f722009-11-04 03:03:49 +00002540#if defined(MAGICKCORE_OPENMP_SUPPORT)
2541 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002542#endif
cristybb503372010-05-27 20:51:26 +00002543 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00002544 {
2545 /*
2546 Level colormap.
2547 */
2548 if ((channel & RedChannel) != 0)
cristyc1f508d2010-04-22 01:21:47 +00002549 image->colormap[i].red=LevelQuantum(image->colormap[i].red);
cristy3ed852e2009-09-05 21:47:34 +00002550 if ((channel & GreenChannel) != 0)
cristyc1f508d2010-04-22 01:21:47 +00002551 image->colormap[i].green=LevelQuantum(image->colormap[i].green);
cristy3ed852e2009-09-05 21:47:34 +00002552 if ((channel & BlueChannel) != 0)
cristyc1f508d2010-04-22 01:21:47 +00002553 image->colormap[i].blue=LevelQuantum(image->colormap[i].blue);
cristy3ed852e2009-09-05 21:47:34 +00002554 if ((channel & OpacityChannel) != 0)
cristyc1f508d2010-04-22 01:21:47 +00002555 image->colormap[i].opacity=LevelQuantum(image->colormap[i].opacity);
cristy3ed852e2009-09-05 21:47:34 +00002556 }
2557 /*
2558 Level image.
2559 */
2560 status=MagickTrue;
2561 progress=0;
2562 exception=(&image->exception);
2563 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00002564#if defined(MAGICKCORE_OPENMP_SUPPORT)
2565 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002566#endif
cristybb503372010-05-27 20:51:26 +00002567 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002568 {
2569 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00002570 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00002571
cristy3ed852e2009-09-05 21:47:34 +00002572 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00002573 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002574
cristy8d4629b2010-08-30 17:59:46 +00002575 register ssize_t
2576 x;
2577
cristy3ed852e2009-09-05 21:47:34 +00002578 if (status == MagickFalse)
2579 continue;
2580 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2581 if (q == (PixelPacket *) NULL)
2582 {
2583 status=MagickFalse;
2584 continue;
2585 }
2586 indexes=GetCacheViewAuthenticIndexQueue(image_view);
cristybb503372010-05-27 20:51:26 +00002587 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002588 {
2589 if ((channel & RedChannel) != 0)
cristyc1f508d2010-04-22 01:21:47 +00002590 q->red=LevelQuantum(q->red);
cristy3ed852e2009-09-05 21:47:34 +00002591 if ((channel & GreenChannel) != 0)
cristyc1f508d2010-04-22 01:21:47 +00002592 q->green=LevelQuantum(q->green);
cristy3ed852e2009-09-05 21:47:34 +00002593 if ((channel & BlueChannel) != 0)
cristyc1f508d2010-04-22 01:21:47 +00002594 q->blue=LevelQuantum(q->blue);
cristy3ed852e2009-09-05 21:47:34 +00002595 if (((channel & OpacityChannel) != 0) &&
2596 (image->matte == MagickTrue))
cristybb503372010-05-27 20:51:26 +00002597 q->opacity=(Quantum) (QuantumRange-LevelQuantum(QuantumRange-
2598 q->opacity));
cristy3ed852e2009-09-05 21:47:34 +00002599 if (((channel & IndexChannel) != 0) &&
2600 (image->colorspace == CMYKColorspace))
cristyc1f508d2010-04-22 01:21:47 +00002601 indexes[x]=LevelQuantum(indexes[x]);
cristy3ed852e2009-09-05 21:47:34 +00002602 q++;
2603 }
2604 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2605 status=MagickFalse;
2606 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2607 {
2608 MagickBooleanType
2609 proceed;
2610
cristyb5d5f722009-11-04 03:03:49 +00002611#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00002612 #pragma omp critical (MagickCore_LevelImageChannel)
2613#endif
2614 proceed=SetImageProgress(image,LevelImageTag,progress++,image->rows);
2615 if (proceed == MagickFalse)
2616 status=MagickFalse;
2617 }
2618 }
2619 image_view=DestroyCacheView(image_view);
2620 return(status);
2621}
2622
2623/*
2624%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2625% %
2626% %
2627% %
2628% L e v e l i z e I m a g e C h a n n e l %
2629% %
2630% %
2631% %
2632%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2633%
2634% LevelizeImageChannel() applies the reversed LevelImage() operation to just
2635% the specific channels specified. It compresses the full range of color
2636% values, so that they lie between the given black and white points. Gamma is
2637% applied before the values are mapped.
2638%
2639% LevelizeImageChannel() can be called with by using a +level command line
2640% API option, or using a '!' on a -level or LevelImage() geometry string.
2641%
2642% It can be used for example de-contrast a greyscale image to the exact
2643% levels specified. Or by using specific levels for each channel of an image
2644% you can convert a gray-scale image to any linear color gradient, according
2645% to those levels.
2646%
2647% The format of the LevelizeImageChannel method is:
2648%
2649% MagickBooleanType LevelizeImageChannel(Image *image,
2650% const ChannelType channel,const char *levels)
2651%
2652% A description of each parameter follows:
2653%
2654% o image: the image.
2655%
2656% o channel: the channel.
2657%
2658% o black_point: The level to map zero (black) to.
2659%
2660% o white_point: The level to map QuantiumRange (white) to.
2661%
2662% o gamma: adjust gamma by this factor before mapping values.
2663%
2664*/
cristyd1a2c0f2011-02-09 14:14:50 +00002665
2666MagickExport MagickBooleanType LevelizeImage(Image *image,
2667 const double black_point,const double white_point,const double gamma)
2668{
2669 MagickBooleanType
2670 status;
2671
2672 status=LevelizeImageChannel(image,DefaultChannels,black_point,white_point,
2673 gamma);
2674 return(status);
2675}
2676
cristy3ed852e2009-09-05 21:47:34 +00002677MagickExport MagickBooleanType LevelizeImageChannel(Image *image,
2678 const ChannelType channel,const double black_point,const double white_point,
2679 const double gamma)
2680{
2681#define LevelizeImageTag "Levelize/Image"
cristyce70c172010-01-07 17:15:30 +00002682#define LevelizeValue(x) (ClampToQuantum(((MagickRealType) \
cristy3ed852e2009-09-05 21:47:34 +00002683 pow((double)(QuantumScale*(x)),1.0/gamma))*(white_point-black_point)+ \
2684 black_point))
2685
cristyc4c8d132010-01-07 01:58:38 +00002686 CacheView
2687 *image_view;
2688
cristy3ed852e2009-09-05 21:47:34 +00002689 ExceptionInfo
2690 *exception;
2691
cristy3ed852e2009-09-05 21:47:34 +00002692 MagickBooleanType
2693 status;
2694
cristybb503372010-05-27 20:51:26 +00002695 MagickOffsetType
2696 progress;
2697
2698 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002699 i;
2700
cristybb503372010-05-27 20:51:26 +00002701 ssize_t
2702 y;
2703
cristy3ed852e2009-09-05 21:47:34 +00002704 /*
2705 Allocate and initialize levels map.
2706 */
2707 assert(image != (Image *) NULL);
2708 assert(image->signature == MagickSignature);
2709 if (image->debug != MagickFalse)
2710 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2711 if (image->storage_class == PseudoClass)
cristyb5d5f722009-11-04 03:03:49 +00002712#if defined(MAGICKCORE_OPENMP_SUPPORT)
2713 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002714#endif
cristybb503372010-05-27 20:51:26 +00002715 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00002716 {
2717 /*
2718 Level colormap.
2719 */
2720 if ((channel & RedChannel) != 0)
2721 image->colormap[i].red=LevelizeValue(image->colormap[i].red);
2722 if ((channel & GreenChannel) != 0)
2723 image->colormap[i].green=LevelizeValue(image->colormap[i].green);
2724 if ((channel & BlueChannel) != 0)
2725 image->colormap[i].blue=LevelizeValue(image->colormap[i].blue);
2726 if ((channel & OpacityChannel) != 0)
2727 image->colormap[i].opacity=LevelizeValue(image->colormap[i].opacity);
2728 }
2729 /*
2730 Level image.
2731 */
2732 status=MagickTrue;
2733 progress=0;
2734 exception=(&image->exception);
2735 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00002736#if defined(MAGICKCORE_OPENMP_SUPPORT)
2737 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002738#endif
cristybb503372010-05-27 20:51:26 +00002739 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002740 {
2741 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00002742 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00002743
cristy3ed852e2009-09-05 21:47:34 +00002744 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00002745 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002746
cristy8d4629b2010-08-30 17:59:46 +00002747 register ssize_t
2748 x;
2749
cristy3ed852e2009-09-05 21:47:34 +00002750 if (status == MagickFalse)
2751 continue;
2752 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2753 if (q == (PixelPacket *) NULL)
2754 {
2755 status=MagickFalse;
2756 continue;
2757 }
2758 indexes=GetCacheViewAuthenticIndexQueue(image_view);
cristybb503372010-05-27 20:51:26 +00002759 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002760 {
2761 if ((channel & RedChannel) != 0)
2762 q->red=LevelizeValue(q->red);
2763 if ((channel & GreenChannel) != 0)
2764 q->green=LevelizeValue(q->green);
2765 if ((channel & BlueChannel) != 0)
2766 q->blue=LevelizeValue(q->blue);
2767 if (((channel & OpacityChannel) != 0) &&
2768 (image->matte == MagickTrue))
2769 q->opacity=LevelizeValue(q->opacity);
2770 if (((channel & IndexChannel) != 0) &&
2771 (image->colorspace == CMYKColorspace))
2772 indexes[x]=LevelizeValue(indexes[x]);
2773 q++;
2774 }
2775 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2776 status=MagickFalse;
2777 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2778 {
2779 MagickBooleanType
2780 proceed;
2781
cristyb5d5f722009-11-04 03:03:49 +00002782#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00002783 #pragma omp critical (MagickCore_LevelizeImageChannel)
2784#endif
2785 proceed=SetImageProgress(image,LevelizeImageTag,progress++,image->rows);
2786 if (proceed == MagickFalse)
2787 status=MagickFalse;
2788 }
2789 }
cristy8d4629b2010-08-30 17:59:46 +00002790 image_view=DestroyCacheView(image_view);
cristy3ed852e2009-09-05 21:47:34 +00002791 return(status);
2792}
2793
2794/*
2795%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2796% %
2797% %
2798% %
2799% L e v e l I m a g e C o l o r s %
2800% %
2801% %
2802% %
2803%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2804%
cristyee0f8d72009-09-19 00:58:29 +00002805% LevelImageColor() maps the given color to "black" and "white" values,
2806% linearly spreading out the colors, and level values on a channel by channel
2807% bases, as per LevelImage(). The given colors allows you to specify
glennrp1e7f7bc2011-03-02 19:25:28 +00002808% different level ranges for each of the color channels separately.
cristy3ed852e2009-09-05 21:47:34 +00002809%
2810% If the boolean 'invert' is set true the image values will modifyed in the
2811% reverse direction. That is any existing "black" and "white" colors in the
2812% image will become the color values given, with all other values compressed
2813% appropriatally. This effectivally maps a greyscale gradient into the given
2814% color gradient.
2815%
cristy308b4e62009-09-21 14:40:44 +00002816% The format of the LevelColorsImageChannel method is:
cristy3ed852e2009-09-05 21:47:34 +00002817%
cristy308b4e62009-09-21 14:40:44 +00002818% MagickBooleanType LevelColorsImage(Image *image,
cristyee0f8d72009-09-19 00:58:29 +00002819% const MagickPixelPacket *black_color,
2820% const MagickPixelPacket *white_color,const MagickBooleanType invert)
cristy308b4e62009-09-21 14:40:44 +00002821% MagickBooleanType LevelColorsImageChannel(Image *image,
2822% const ChannelType channel,const MagickPixelPacket *black_color,
2823% const MagickPixelPacket *white_color,const MagickBooleanType invert)
cristy3ed852e2009-09-05 21:47:34 +00002824%
2825% A description of each parameter follows:
2826%
2827% o image: the image.
2828%
2829% o channel: the channel.
2830%
2831% o black_color: The color to map black to/from
2832%
2833% o white_point: The color to map white to/from
2834%
2835% o invert: if true map the colors (levelize), rather than from (level)
2836%
2837*/
cristy308b4e62009-09-21 14:40:44 +00002838
2839MagickExport MagickBooleanType LevelColorsImage(Image *image,
cristy3ed852e2009-09-05 21:47:34 +00002840 const MagickPixelPacket *black_color,const MagickPixelPacket *white_color,
2841 const MagickBooleanType invert)
2842{
cristy308b4e62009-09-21 14:40:44 +00002843 MagickBooleanType
2844 status;
cristy3ed852e2009-09-05 21:47:34 +00002845
cristy308b4e62009-09-21 14:40:44 +00002846 status=LevelColorsImageChannel(image,DefaultChannels,black_color,white_color,
2847 invert);
2848 return(status);
2849}
2850
2851MagickExport MagickBooleanType LevelColorsImageChannel(Image *image,
2852 const ChannelType channel,const MagickPixelPacket *black_color,
2853 const MagickPixelPacket *white_color,const MagickBooleanType invert)
2854{
cristy3ed852e2009-09-05 21:47:34 +00002855 MagickStatusType
2856 status;
2857
2858 /*
2859 Allocate and initialize levels map.
2860 */
2861 assert(image != (Image *) NULL);
2862 assert(image->signature == MagickSignature);
2863 if (image->debug != MagickFalse)
2864 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2865 status=MagickFalse;
2866 if (invert == MagickFalse)
2867 {
2868 if ((channel & RedChannel) != 0)
2869 status|=LevelImageChannel(image,RedChannel,
2870 black_color->red,white_color->red,(double) 1.0);
2871 if ((channel & GreenChannel) != 0)
2872 status|=LevelImageChannel(image,GreenChannel,
2873 black_color->green,white_color->green,(double) 1.0);
2874 if ((channel & BlueChannel) != 0)
2875 status|=LevelImageChannel(image,BlueChannel,
2876 black_color->blue,white_color->blue,(double) 1.0);
2877 if (((channel & OpacityChannel) != 0) &&
2878 (image->matte == MagickTrue))
2879 status|=LevelImageChannel(image,OpacityChannel,
2880 black_color->opacity,white_color->opacity,(double) 1.0);
2881 if (((channel & IndexChannel) != 0) &&
2882 (image->colorspace == CMYKColorspace))
2883 status|=LevelImageChannel(image,IndexChannel,
2884 black_color->index,white_color->index,(double) 1.0);
2885 }
2886 else
2887 {
2888 if ((channel & RedChannel) != 0)
2889 status|=LevelizeImageChannel(image,RedChannel,
2890 black_color->red,white_color->red,(double) 1.0);
2891 if ((channel & GreenChannel) != 0)
2892 status|=LevelizeImageChannel(image,GreenChannel,
2893 black_color->green,white_color->green,(double) 1.0);
2894 if ((channel & BlueChannel) != 0)
2895 status|=LevelizeImageChannel(image,BlueChannel,
2896 black_color->blue,white_color->blue,(double) 1.0);
2897 if (((channel & OpacityChannel) != 0) &&
2898 (image->matte == MagickTrue))
2899 status|=LevelizeImageChannel(image,OpacityChannel,
2900 black_color->opacity,white_color->opacity,(double) 1.0);
2901 if (((channel & IndexChannel) != 0) &&
2902 (image->colorspace == CMYKColorspace))
2903 status|=LevelizeImageChannel(image,IndexChannel,
2904 black_color->index,white_color->index,(double) 1.0);
2905 }
2906 return(status == 0 ? MagickFalse : MagickTrue);
2907}
2908
2909/*
2910%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2911% %
2912% %
2913% %
2914% L i n e a r S t r e t c h I m a g e %
2915% %
2916% %
2917% %
2918%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2919%
2920% The LinearStretchImage() discards any pixels below the black point and
2921% above the white point and levels the remaining pixels.
2922%
2923% The format of the LinearStretchImage method is:
2924%
2925% MagickBooleanType LinearStretchImage(Image *image,
2926% const double black_point,const double white_point)
2927%
2928% A description of each parameter follows:
2929%
2930% o image: the image.
2931%
2932% o black_point: the black point.
2933%
2934% o white_point: the white point.
2935%
2936*/
2937MagickExport MagickBooleanType LinearStretchImage(Image *image,
2938 const double black_point,const double white_point)
2939{
2940#define LinearStretchImageTag "LinearStretch/Image"
2941
2942 ExceptionInfo
2943 *exception;
2944
cristy3ed852e2009-09-05 21:47:34 +00002945 MagickBooleanType
2946 status;
2947
2948 MagickRealType
2949 *histogram,
2950 intensity;
2951
cristy8d4629b2010-08-30 17:59:46 +00002952 ssize_t
2953 black,
2954 white,
2955 y;
2956
cristy3ed852e2009-09-05 21:47:34 +00002957 /*
2958 Allocate histogram and linear map.
2959 */
2960 assert(image != (Image *) NULL);
2961 assert(image->signature == MagickSignature);
2962 histogram=(MagickRealType *) AcquireQuantumMemory(MaxMap+1UL,
2963 sizeof(*histogram));
2964 if (histogram == (MagickRealType *) NULL)
2965 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
2966 image->filename);
2967 /*
2968 Form histogram.
2969 */
2970 (void) ResetMagickMemory(histogram,0,(MaxMap+1)*sizeof(*histogram));
2971 exception=(&image->exception);
cristybb503372010-05-27 20:51:26 +00002972 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002973 {
2974 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00002975 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00002976
cristybb503372010-05-27 20:51:26 +00002977 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002978 x;
2979
2980 p=GetVirtualPixels(image,0,y,image->columns,1,exception);
2981 if (p == (const PixelPacket *) NULL)
2982 break;
cristybb503372010-05-27 20:51:26 +00002983 for (x=(ssize_t) image->columns-1; x >= 0; x--)
cristy3ed852e2009-09-05 21:47:34 +00002984 {
2985 histogram[ScaleQuantumToMap(PixelIntensityToQuantum(p))]++;
2986 p++;
2987 }
2988 }
2989 /*
2990 Find the histogram boundaries by locating the black and white point levels.
2991 */
cristy3ed852e2009-09-05 21:47:34 +00002992 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00002993 for (black=0; black < (ssize_t) MaxMap; black++)
cristy3ed852e2009-09-05 21:47:34 +00002994 {
2995 intensity+=histogram[black];
2996 if (intensity >= black_point)
2997 break;
2998 }
2999 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00003000 for (white=(ssize_t) MaxMap; white != 0; white--)
cristy3ed852e2009-09-05 21:47:34 +00003001 {
3002 intensity+=histogram[white];
3003 if (intensity >= white_point)
3004 break;
3005 }
3006 histogram=(MagickRealType *) RelinquishMagickMemory(histogram);
3007 status=LevelImageChannel(image,DefaultChannels,(double) black,(double) white,
3008 1.0);
3009 return(status);
3010}
3011
3012/*
3013%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3014% %
3015% %
3016% %
3017% M o d u l a t e I m a g e %
3018% %
3019% %
3020% %
3021%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3022%
3023% ModulateImage() lets you control the brightness, saturation, and hue
3024% of an image. Modulate represents the brightness, saturation, and hue
3025% as one parameter (e.g. 90,150,100). If the image colorspace is HSL, the
3026% modulation is lightness, saturation, and hue. And if the colorspace is
3027% HWB, use blackness, whiteness, and hue.
3028%
3029% The format of the ModulateImage method is:
3030%
3031% MagickBooleanType ModulateImage(Image *image,const char *modulate)
3032%
3033% A description of each parameter follows:
3034%
3035% o image: the image.
3036%
3037% o modulate: Define the percent change in brightness, saturation, and
3038% hue.
3039%
3040*/
3041
3042static void ModulateHSB(const double percent_hue,
3043 const double percent_saturation,const double percent_brightness,
3044 Quantum *red,Quantum *green,Quantum *blue)
3045{
3046 double
3047 brightness,
3048 hue,
3049 saturation;
3050
3051 /*
3052 Increase or decrease color brightness, saturation, or hue.
3053 */
3054 assert(red != (Quantum *) NULL);
3055 assert(green != (Quantum *) NULL);
3056 assert(blue != (Quantum *) NULL);
3057 ConvertRGBToHSB(*red,*green,*blue,&hue,&saturation,&brightness);
3058 hue+=0.5*(0.01*percent_hue-1.0);
3059 while (hue < 0.0)
3060 hue+=1.0;
3061 while (hue > 1.0)
3062 hue-=1.0;
3063 saturation*=0.01*percent_saturation;
3064 brightness*=0.01*percent_brightness;
3065 ConvertHSBToRGB(hue,saturation,brightness,red,green,blue);
3066}
3067
3068static void ModulateHSL(const double percent_hue,
3069 const double percent_saturation,const double percent_lightness,
3070 Quantum *red,Quantum *green,Quantum *blue)
3071{
3072 double
3073 hue,
3074 lightness,
3075 saturation;
3076
3077 /*
3078 Increase or decrease color lightness, saturation, or hue.
3079 */
3080 assert(red != (Quantum *) NULL);
3081 assert(green != (Quantum *) NULL);
3082 assert(blue != (Quantum *) NULL);
3083 ConvertRGBToHSL(*red,*green,*blue,&hue,&saturation,&lightness);
3084 hue+=0.5*(0.01*percent_hue-1.0);
3085 while (hue < 0.0)
3086 hue+=1.0;
3087 while (hue > 1.0)
3088 hue-=1.0;
3089 saturation*=0.01*percent_saturation;
3090 lightness*=0.01*percent_lightness;
3091 ConvertHSLToRGB(hue,saturation,lightness,red,green,blue);
3092}
3093
3094static void ModulateHWB(const double percent_hue,const double percent_whiteness, const double percent_blackness,Quantum *red,Quantum *green,Quantum *blue)
3095{
3096 double
3097 blackness,
3098 hue,
3099 whiteness;
3100
3101 /*
3102 Increase or decrease color blackness, whiteness, or hue.
3103 */
3104 assert(red != (Quantum *) NULL);
3105 assert(green != (Quantum *) NULL);
3106 assert(blue != (Quantum *) NULL);
3107 ConvertRGBToHWB(*red,*green,*blue,&hue,&whiteness,&blackness);
3108 hue+=0.5*(0.01*percent_hue-1.0);
3109 while (hue < 0.0)
3110 hue+=1.0;
3111 while (hue > 1.0)
3112 hue-=1.0;
3113 blackness*=0.01*percent_blackness;
3114 whiteness*=0.01*percent_whiteness;
3115 ConvertHWBToRGB(hue,whiteness,blackness,red,green,blue);
3116}
3117
3118MagickExport MagickBooleanType ModulateImage(Image *image,const char *modulate)
3119{
3120#define ModulateImageTag "Modulate/Image"
3121
cristyc4c8d132010-01-07 01:58:38 +00003122 CacheView
3123 *image_view;
3124
cristy3ed852e2009-09-05 21:47:34 +00003125 ColorspaceType
3126 colorspace;
3127
3128 const char
3129 *artifact;
3130
3131 double
3132 percent_brightness,
3133 percent_hue,
3134 percent_saturation;
3135
3136 ExceptionInfo
3137 *exception;
3138
3139 GeometryInfo
3140 geometry_info;
3141
cristy3ed852e2009-09-05 21:47:34 +00003142 MagickBooleanType
3143 status;
3144
cristybb503372010-05-27 20:51:26 +00003145 MagickOffsetType
3146 progress;
3147
cristy3ed852e2009-09-05 21:47:34 +00003148 MagickStatusType
3149 flags;
3150
cristybb503372010-05-27 20:51:26 +00003151 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003152 i;
3153
cristybb503372010-05-27 20:51:26 +00003154 ssize_t
3155 y;
3156
cristy3ed852e2009-09-05 21:47:34 +00003157 /*
cristy2b726bd2010-01-11 01:05:39 +00003158 Initialize modulate table.
cristy3ed852e2009-09-05 21:47:34 +00003159 */
3160 assert(image != (Image *) NULL);
3161 assert(image->signature == MagickSignature);
3162 if (image->debug != MagickFalse)
3163 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3164 if (modulate == (char *) NULL)
3165 return(MagickFalse);
3166 flags=ParseGeometry(modulate,&geometry_info);
3167 percent_brightness=geometry_info.rho;
3168 percent_saturation=geometry_info.sigma;
3169 if ((flags & SigmaValue) == 0)
3170 percent_saturation=100.0;
3171 percent_hue=geometry_info.xi;
3172 if ((flags & XiValue) == 0)
3173 percent_hue=100.0;
3174 colorspace=UndefinedColorspace;
3175 artifact=GetImageArtifact(image,"modulate:colorspace");
3176 if (artifact != (const char *) NULL)
3177 colorspace=(ColorspaceType) ParseMagickOption(MagickColorspaceOptions,
3178 MagickFalse,artifact);
3179 if (image->storage_class == PseudoClass)
3180 {
3181 /*
3182 Modulate colormap.
3183 */
cristyb5d5f722009-11-04 03:03:49 +00003184#if defined(MAGICKCORE_OPENMP_SUPPORT)
3185 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003186#endif
cristybb503372010-05-27 20:51:26 +00003187 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00003188 switch (colorspace)
3189 {
3190 case HSBColorspace:
3191 {
3192 ModulateHSB(percent_hue,percent_saturation,percent_brightness,
3193 &image->colormap[i].red,&image->colormap[i].green,
3194 &image->colormap[i].blue);
3195 break;
3196 }
3197 case HSLColorspace:
3198 default:
3199 {
3200 ModulateHSL(percent_hue,percent_saturation,percent_brightness,
3201 &image->colormap[i].red,&image->colormap[i].green,
3202 &image->colormap[i].blue);
3203 break;
3204 }
3205 case HWBColorspace:
3206 {
3207 ModulateHWB(percent_hue,percent_saturation,percent_brightness,
3208 &image->colormap[i].red,&image->colormap[i].green,
3209 &image->colormap[i].blue);
3210 break;
3211 }
3212 }
3213 }
3214 /*
3215 Modulate image.
3216 */
3217 status=MagickTrue;
3218 progress=0;
3219 exception=(&image->exception);
3220 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00003221#if defined(MAGICKCORE_OPENMP_SUPPORT)
3222 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003223#endif
cristybb503372010-05-27 20:51:26 +00003224 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003225 {
cristy3ed852e2009-09-05 21:47:34 +00003226 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00003227 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003228
cristy8d4629b2010-08-30 17:59:46 +00003229 register ssize_t
3230 x;
3231
cristy3ed852e2009-09-05 21:47:34 +00003232 if (status == MagickFalse)
3233 continue;
3234 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
3235 if (q == (PixelPacket *) NULL)
3236 {
3237 status=MagickFalse;
3238 continue;
3239 }
cristybb503372010-05-27 20:51:26 +00003240 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003241 {
3242 switch (colorspace)
3243 {
3244 case HSBColorspace:
3245 {
3246 ModulateHSB(percent_hue,percent_saturation,percent_brightness,
3247 &q->red,&q->green,&q->blue);
3248 break;
3249 }
3250 case HSLColorspace:
3251 default:
3252 {
3253 ModulateHSL(percent_hue,percent_saturation,percent_brightness,
3254 &q->red,&q->green,&q->blue);
3255 break;
3256 }
3257 case HWBColorspace:
3258 {
3259 ModulateHWB(percent_hue,percent_saturation,percent_brightness,
3260 &q->red,&q->green,&q->blue);
3261 break;
3262 }
3263 }
3264 q++;
3265 }
3266 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
3267 status=MagickFalse;
3268 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3269 {
3270 MagickBooleanType
3271 proceed;
3272
cristyb5d5f722009-11-04 03:03:49 +00003273#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00003274 #pragma omp critical (MagickCore_ModulateImage)
3275#endif
3276 proceed=SetImageProgress(image,ModulateImageTag,progress++,image->rows);
3277 if (proceed == MagickFalse)
3278 status=MagickFalse;
3279 }
3280 }
3281 image_view=DestroyCacheView(image_view);
3282 return(status);
3283}
3284
3285/*
3286%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3287% %
3288% %
3289% %
3290% N e g a t e I m a g e %
3291% %
3292% %
3293% %
3294%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3295%
3296% NegateImage() negates the colors in the reference image. The grayscale
3297% option means that only grayscale values within the image are negated.
3298%
3299% The format of the NegateImageChannel method is:
3300%
3301% MagickBooleanType NegateImage(Image *image,
3302% const MagickBooleanType grayscale)
3303% MagickBooleanType NegateImageChannel(Image *image,
3304% const ChannelType channel,const MagickBooleanType grayscale)
3305%
3306% A description of each parameter follows:
3307%
3308% o image: the image.
3309%
3310% o channel: the channel.
3311%
3312% o grayscale: If MagickTrue, only negate grayscale pixels within the image.
3313%
3314*/
3315
3316MagickExport MagickBooleanType NegateImage(Image *image,
3317 const MagickBooleanType grayscale)
3318{
3319 MagickBooleanType
3320 status;
3321
3322 status=NegateImageChannel(image,DefaultChannels,grayscale);
3323 return(status);
3324}
3325
3326MagickExport MagickBooleanType NegateImageChannel(Image *image,
3327 const ChannelType channel,const MagickBooleanType grayscale)
3328{
3329#define NegateImageTag "Negate/Image"
3330
cristyc4c8d132010-01-07 01:58:38 +00003331 CacheView
3332 *image_view;
3333
cristy3ed852e2009-09-05 21:47:34 +00003334 ExceptionInfo
3335 *exception;
3336
cristy3ed852e2009-09-05 21:47:34 +00003337 MagickBooleanType
3338 status;
3339
cristybb503372010-05-27 20:51:26 +00003340 MagickOffsetType
3341 progress;
3342
3343 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003344 i;
3345
cristybb503372010-05-27 20:51:26 +00003346 ssize_t
3347 y;
3348
cristy3ed852e2009-09-05 21:47:34 +00003349 assert(image != (Image *) NULL);
3350 assert(image->signature == MagickSignature);
3351 if (image->debug != MagickFalse)
3352 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3353 if (image->storage_class == PseudoClass)
3354 {
3355 /*
3356 Negate colormap.
3357 */
cristyb5d5f722009-11-04 03:03:49 +00003358#if defined(MAGICKCORE_OPENMP_SUPPORT)
3359 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003360#endif
cristybb503372010-05-27 20:51:26 +00003361 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00003362 {
3363 if (grayscale != MagickFalse)
3364 if ((image->colormap[i].red != image->colormap[i].green) ||
3365 (image->colormap[i].green != image->colormap[i].blue))
3366 continue;
3367 if ((channel & RedChannel) != 0)
3368 image->colormap[i].red=(Quantum) QuantumRange-
3369 image->colormap[i].red;
3370 if ((channel & GreenChannel) != 0)
3371 image->colormap[i].green=(Quantum) QuantumRange-
3372 image->colormap[i].green;
3373 if ((channel & BlueChannel) != 0)
3374 image->colormap[i].blue=(Quantum) QuantumRange-
3375 image->colormap[i].blue;
3376 }
3377 }
3378 /*
3379 Negate image.
3380 */
3381 status=MagickTrue;
3382 progress=0;
3383 exception=(&image->exception);
3384 image_view=AcquireCacheView(image);
3385 if (grayscale != MagickFalse)
3386 {
cristyb5d5f722009-11-04 03:03:49 +00003387#if defined(MAGICKCORE_OPENMP_SUPPORT)
3388 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003389#endif
cristybb503372010-05-27 20:51:26 +00003390 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003391 {
3392 MagickBooleanType
3393 sync;
3394
3395 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00003396 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00003397
cristy3ed852e2009-09-05 21:47:34 +00003398 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00003399 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003400
cristy8d4629b2010-08-30 17:59:46 +00003401 register ssize_t
3402 x;
3403
cristy3ed852e2009-09-05 21:47:34 +00003404 if (status == MagickFalse)
3405 continue;
3406 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,
3407 exception);
3408 if (q == (PixelPacket *) NULL)
3409 {
3410 status=MagickFalse;
3411 continue;
3412 }
3413 indexes=GetCacheViewAuthenticIndexQueue(image_view);
cristybb503372010-05-27 20:51:26 +00003414 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003415 {
3416 if ((q->red != q->green) || (q->green != q->blue))
3417 {
3418 q++;
3419 continue;
3420 }
3421 if ((channel & RedChannel) != 0)
3422 q->red=(Quantum) QuantumRange-q->red;
3423 if ((channel & GreenChannel) != 0)
3424 q->green=(Quantum) QuantumRange-q->green;
3425 if ((channel & BlueChannel) != 0)
3426 q->blue=(Quantum) QuantumRange-q->blue;
3427 if ((channel & OpacityChannel) != 0)
3428 q->opacity=(Quantum) QuantumRange-q->opacity;
3429 if (((channel & IndexChannel) != 0) &&
3430 (image->colorspace == CMYKColorspace))
3431 indexes[x]=(IndexPacket) QuantumRange-indexes[x];
3432 q++;
3433 }
3434 sync=SyncCacheViewAuthenticPixels(image_view,exception);
3435 if (sync == MagickFalse)
3436 status=MagickFalse;
3437 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3438 {
3439 MagickBooleanType
3440 proceed;
3441
cristyb5d5f722009-11-04 03:03:49 +00003442#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00003443 #pragma omp critical (MagickCore_NegateImageChannel)
3444#endif
3445 proceed=SetImageProgress(image,NegateImageTag,progress++,
3446 image->rows);
3447 if (proceed == MagickFalse)
3448 status=MagickFalse;
3449 }
3450 }
3451 image_view=DestroyCacheView(image_view);
3452 return(MagickTrue);
3453 }
3454 /*
3455 Negate image.
3456 */
cristyb5d5f722009-11-04 03:03:49 +00003457#if defined(MAGICKCORE_OPENMP_SUPPORT)
3458 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003459#endif
cristybb503372010-05-27 20:51:26 +00003460 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003461 {
3462 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00003463 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00003464
cristy3ed852e2009-09-05 21:47:34 +00003465 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00003466 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003467
cristy8d4629b2010-08-30 17:59:46 +00003468 register ssize_t
3469 x;
3470
cristy3ed852e2009-09-05 21:47:34 +00003471 if (status == MagickFalse)
3472 continue;
3473 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
3474 if (q == (PixelPacket *) NULL)
3475 {
3476 status=MagickFalse;
3477 continue;
3478 }
3479 indexes=GetCacheViewAuthenticIndexQueue(image_view);
cristybb503372010-05-27 20:51:26 +00003480 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003481 {
3482 if ((channel & RedChannel) != 0)
3483 q->red=(Quantum) QuantumRange-q->red;
3484 if ((channel & GreenChannel) != 0)
3485 q->green=(Quantum) QuantumRange-q->green;
3486 if ((channel & BlueChannel) != 0)
3487 q->blue=(Quantum) QuantumRange-q->blue;
3488 if ((channel & OpacityChannel) != 0)
3489 q->opacity=(Quantum) QuantumRange-q->opacity;
3490 if (((channel & IndexChannel) != 0) &&
3491 (image->colorspace == CMYKColorspace))
3492 indexes[x]=(IndexPacket) QuantumRange-indexes[x];
3493 q++;
3494 }
3495 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
3496 status=MagickFalse;
3497 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3498 {
3499 MagickBooleanType
3500 proceed;
3501
cristyb5d5f722009-11-04 03:03:49 +00003502#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00003503 #pragma omp critical (MagickCore_NegateImageChannel)
3504#endif
3505 proceed=SetImageProgress(image,NegateImageTag,progress++,image->rows);
3506 if (proceed == MagickFalse)
3507 status=MagickFalse;
3508 }
3509 }
3510 image_view=DestroyCacheView(image_view);
3511 return(status);
3512}
3513
3514/*
3515%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3516% %
3517% %
3518% %
3519% N o r m a l i z e I m a g e %
3520% %
3521% %
3522% %
3523%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3524%
3525% The NormalizeImage() method enhances the contrast of a color image by
3526% mapping the darkest 2 percent of all pixel to black and the brightest
3527% 1 percent to white.
3528%
3529% The format of the NormalizeImage method is:
3530%
3531% MagickBooleanType NormalizeImage(Image *image)
3532% MagickBooleanType NormalizeImageChannel(Image *image,
3533% const ChannelType channel)
3534%
3535% A description of each parameter follows:
3536%
3537% o image: the image.
3538%
3539% o channel: the channel.
3540%
3541*/
3542
3543MagickExport MagickBooleanType NormalizeImage(Image *image)
3544{
3545 MagickBooleanType
3546 status;
3547
3548 status=NormalizeImageChannel(image,DefaultChannels);
3549 return(status);
3550}
3551
3552MagickExport MagickBooleanType NormalizeImageChannel(Image *image,
3553 const ChannelType channel)
3554{
3555 double
3556 black_point,
3557 white_point;
3558
cristy530239c2010-07-25 17:34:26 +00003559 black_point=(double) image->columns*image->rows*0.0015;
3560 white_point=(double) image->columns*image->rows*0.9995;
cristy3ed852e2009-09-05 21:47:34 +00003561 return(ContrastStretchImageChannel(image,channel,black_point,white_point));
3562}
3563
3564/*
3565%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3566% %
3567% %
3568% %
3569% S i g m o i d a l C o n t r a s t I m a g e %
3570% %
3571% %
3572% %
3573%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3574%
3575% SigmoidalContrastImage() adjusts the contrast of an image with a non-linear
3576% sigmoidal contrast algorithm. Increase the contrast of the image using a
3577% sigmoidal transfer function without saturating highlights or shadows.
3578% Contrast indicates how much to increase the contrast (0 is none; 3 is
3579% typical; 20 is pushing it); mid-point indicates where midtones fall in the
3580% resultant image (0 is white; 50% is middle-gray; 100% is black). Set
3581% sharpen to MagickTrue to increase the image contrast otherwise the contrast
3582% is reduced.
3583%
3584% The format of the SigmoidalContrastImage method is:
3585%
3586% MagickBooleanType SigmoidalContrastImage(Image *image,
3587% const MagickBooleanType sharpen,const char *levels)
3588% MagickBooleanType SigmoidalContrastImageChannel(Image *image,
3589% const ChannelType channel,const MagickBooleanType sharpen,
3590% const double contrast,const double midpoint)
3591%
3592% A description of each parameter follows:
3593%
3594% o image: the image.
3595%
3596% o channel: the channel.
3597%
3598% o sharpen: Increase or decrease image contrast.
3599%
cristyfa769582010-09-30 23:30:03 +00003600% o alpha: strength of the contrast, the larger the number the more
3601% 'threshold-like' it becomes.
cristy3ed852e2009-09-05 21:47:34 +00003602%
cristyfa769582010-09-30 23:30:03 +00003603% o beta: midpoint of the function as a color value 0 to QuantumRange.
cristy3ed852e2009-09-05 21:47:34 +00003604%
3605*/
3606
3607MagickExport MagickBooleanType SigmoidalContrastImage(Image *image,
3608 const MagickBooleanType sharpen,const char *levels)
3609{
3610 GeometryInfo
3611 geometry_info;
3612
3613 MagickBooleanType
3614 status;
3615
3616 MagickStatusType
3617 flags;
3618
3619 flags=ParseGeometry(levels,&geometry_info);
3620 if ((flags & SigmaValue) == 0)
3621 geometry_info.sigma=1.0*QuantumRange/2.0;
3622 if ((flags & PercentValue) != 0)
3623 geometry_info.sigma=1.0*QuantumRange*geometry_info.sigma/100.0;
3624 status=SigmoidalContrastImageChannel(image,DefaultChannels,sharpen,
3625 geometry_info.rho,geometry_info.sigma);
3626 return(status);
3627}
3628
3629MagickExport MagickBooleanType SigmoidalContrastImageChannel(Image *image,
3630 const ChannelType channel,const MagickBooleanType sharpen,
3631 const double contrast,const double midpoint)
3632{
3633#define SigmoidalContrastImageTag "SigmoidalContrast/Image"
3634
cristyc4c8d132010-01-07 01:58:38 +00003635 CacheView
3636 *image_view;
3637
cristy3ed852e2009-09-05 21:47:34 +00003638 ExceptionInfo
3639 *exception;
3640
cristy3ed852e2009-09-05 21:47:34 +00003641 MagickBooleanType
3642 status;
3643
cristybb503372010-05-27 20:51:26 +00003644 MagickOffsetType
3645 progress;
3646
cristy3ed852e2009-09-05 21:47:34 +00003647 MagickRealType
3648 *sigmoidal_map;
3649
cristybb503372010-05-27 20:51:26 +00003650 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003651 i;
3652
cristybb503372010-05-27 20:51:26 +00003653 ssize_t
3654 y;
3655
cristy3ed852e2009-09-05 21:47:34 +00003656 /*
3657 Allocate and initialize sigmoidal maps.
3658 */
3659 assert(image != (Image *) NULL);
3660 assert(image->signature == MagickSignature);
3661 if (image->debug != MagickFalse)
3662 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3663 sigmoidal_map=(MagickRealType *) AcquireQuantumMemory(MaxMap+1UL,
3664 sizeof(*sigmoidal_map));
3665 if (sigmoidal_map == (MagickRealType *) NULL)
3666 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
3667 image->filename);
3668 (void) ResetMagickMemory(sigmoidal_map,0,(MaxMap+1)*sizeof(*sigmoidal_map));
cristyb5d5f722009-11-04 03:03:49 +00003669#if defined(MAGICKCORE_OPENMP_SUPPORT)
3670 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003671#endif
cristybb503372010-05-27 20:51:26 +00003672 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +00003673 {
3674 if (sharpen != MagickFalse)
3675 {
3676 sigmoidal_map[i]=(MagickRealType) ScaleMapToQuantum((MagickRealType)
3677 (MaxMap*((1.0/(1.0+exp(contrast*(midpoint/(double) QuantumRange-
3678 (double) i/MaxMap))))-(1.0/(1.0+exp(contrast*(midpoint/
3679 (double) QuantumRange)))))/((1.0/(1.0+exp(contrast*(midpoint/
3680 (double) QuantumRange-1.0))))-(1.0/(1.0+exp(contrast*(midpoint/
3681 (double) QuantumRange)))))+0.5));
3682 continue;
3683 }
3684 sigmoidal_map[i]=(MagickRealType) ScaleMapToQuantum((MagickRealType)
3685 (MaxMap*(QuantumScale*midpoint-log((1.0-(1.0/(1.0+exp(midpoint/
3686 (double) QuantumRange*contrast))+((double) i/MaxMap)*((1.0/
3687 (1.0+exp(contrast*(midpoint/(double) QuantumRange-1.0))))-(1.0/
3688 (1.0+exp(midpoint/(double) QuantumRange*contrast))))))/
3689 (1.0/(1.0+exp(midpoint/(double) QuantumRange*contrast))+
3690 ((double) i/MaxMap)*((1.0/(1.0+exp(contrast*(midpoint/
3691 (double) QuantumRange-1.0))))-(1.0/(1.0+exp(midpoint/
3692 (double) QuantumRange*contrast))))))/contrast)));
3693 }
3694 if (image->storage_class == PseudoClass)
3695 {
3696 /*
3697 Sigmoidal-contrast enhance colormap.
3698 */
cristyb5d5f722009-11-04 03:03:49 +00003699#if defined(MAGICKCORE_OPENMP_SUPPORT)
3700 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003701#endif
cristybb503372010-05-27 20:51:26 +00003702 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00003703 {
3704 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003705 image->colormap[i].red=ClampToQuantum(sigmoidal_map[
cristy3ed852e2009-09-05 21:47:34 +00003706 ScaleQuantumToMap(image->colormap[i].red)]);
3707 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003708 image->colormap[i].green=ClampToQuantum(sigmoidal_map[
cristy3ed852e2009-09-05 21:47:34 +00003709 ScaleQuantumToMap(image->colormap[i].green)]);
3710 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003711 image->colormap[i].blue=ClampToQuantum(sigmoidal_map[
cristy3ed852e2009-09-05 21:47:34 +00003712 ScaleQuantumToMap(image->colormap[i].blue)]);
3713 if ((channel & OpacityChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003714 image->colormap[i].opacity=ClampToQuantum(sigmoidal_map[
cristy3ed852e2009-09-05 21:47:34 +00003715 ScaleQuantumToMap(image->colormap[i].opacity)]);
3716 }
3717 }
3718 /*
3719 Sigmoidal-contrast enhance image.
3720 */
3721 status=MagickTrue;
3722 progress=0;
3723 exception=(&image->exception);
3724 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00003725#if defined(MAGICKCORE_OPENMP_SUPPORT)
3726 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003727#endif
cristybb503372010-05-27 20:51:26 +00003728 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003729 {
3730 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00003731 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00003732
cristy3ed852e2009-09-05 21:47:34 +00003733 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00003734 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003735
cristy8d4629b2010-08-30 17:59:46 +00003736 register ssize_t
3737 x;
3738
cristy3ed852e2009-09-05 21:47:34 +00003739 if (status == MagickFalse)
3740 continue;
3741 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
3742 if (q == (PixelPacket *) NULL)
3743 {
3744 status=MagickFalse;
3745 continue;
3746 }
3747 indexes=GetCacheViewAuthenticIndexQueue(image_view);
cristybb503372010-05-27 20:51:26 +00003748 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003749 {
3750 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003751 q->red=ClampToQuantum(sigmoidal_map[ScaleQuantumToMap(q->red)]);
cristy3ed852e2009-09-05 21:47:34 +00003752 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003753 q->green=ClampToQuantum(sigmoidal_map[ScaleQuantumToMap(q->green)]);
cristy3ed852e2009-09-05 21:47:34 +00003754 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003755 q->blue=ClampToQuantum(sigmoidal_map[ScaleQuantumToMap(q->blue)]);
cristy3ed852e2009-09-05 21:47:34 +00003756 if ((channel & OpacityChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003757 q->opacity=ClampToQuantum(sigmoidal_map[ScaleQuantumToMap(q->opacity)]);
cristy3ed852e2009-09-05 21:47:34 +00003758 if (((channel & IndexChannel) != 0) &&
3759 (image->colorspace == CMYKColorspace))
cristyce70c172010-01-07 17:15:30 +00003760 indexes[x]=(IndexPacket) ClampToQuantum(sigmoidal_map[
cristy3ed852e2009-09-05 21:47:34 +00003761 ScaleQuantumToMap(indexes[x])]);
3762 q++;
3763 }
3764 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
3765 status=MagickFalse;
3766 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3767 {
3768 MagickBooleanType
3769 proceed;
3770
cristyb5d5f722009-11-04 03:03:49 +00003771#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00003772 #pragma omp critical (MagickCore_SigmoidalContrastImageChannel)
3773#endif
3774 proceed=SetImageProgress(image,SigmoidalContrastImageTag,progress++,
3775 image->rows);
3776 if (proceed == MagickFalse)
3777 status=MagickFalse;
3778 }
3779 }
3780 image_view=DestroyCacheView(image_view);
3781 sigmoidal_map=(MagickRealType *) RelinquishMagickMemory(sigmoidal_map);
3782 return(status);
3783}