blob: 400f1c6aad566f980a41dda3730351e60ef4056b [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"
64#include "magick/quantum.h"
65#include "magick/quantum-private.h"
66#include "magick/resample.h"
67#include "magick/resample-private.h"
68#include "magick/statistic.h"
69#include "magick/string_.h"
cristyf2f27272009-12-17 14:48:46 +000070#include "magick/string-private.h"
cristy3ed852e2009-09-05 21:47:34 +000071#include "magick/thread-private.h"
72#include "magick/token.h"
73#include "magick/xml-tree.h"
74
75/*
76%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
77% %
78% %
79% %
80% A u t o G a m m a I m a g e %
81% %
82% %
83% %
84%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
85%
86% AutoGammaImage() extract the 'mean' from the image and adjust the image
87% to try make set its gamma appropriatally.
88%
cristy308b4e62009-09-21 14:40:44 +000089% The format of the AutoGammaImage method is:
cristy3ed852e2009-09-05 21:47:34 +000090%
91% MagickBooleanType AutoGammaImage(Image *image)
92% MagickBooleanType AutoGammaImageChannel(Image *image,
93% const ChannelType channel)
94%
95% A description of each parameter follows:
96%
97% o image: The image to auto-level
98%
99% o channel: The channels to auto-level. If the special 'SyncChannels'
100% flag is set all given channels is adjusted in the same way using the
101% mean average of those channels.
102%
103*/
104
105MagickExport MagickBooleanType AutoGammaImage(Image *image)
106{
107 return(AutoGammaImageChannel(image,DefaultChannels));
108}
109
110MagickExport MagickBooleanType AutoGammaImageChannel(Image *image,
111 const ChannelType channel)
112{
113 MagickStatusType
114 status;
115
116 double
cristy2b726bd2010-01-11 01:05:39 +0000117 mean,sans,gamma,logmean;
anthony4efe5972009-09-11 06:46:40 +0000118
119 logmean=log(0.5);
cristy3ed852e2009-09-05 21:47:34 +0000120
121 if ((channel & SyncChannels) != 0 )
122 {
123 /*
124 Apply gamma correction equally accross all given channels
125 */
cristy2b726bd2010-01-11 01:05:39 +0000126 (void) GetImageChannelMean(image,channel,&mean,&sans,&image->exception);
127 gamma=log(mean*QuantumScale)/logmean;
cristy3ed852e2009-09-05 21:47:34 +0000128 return LevelImageChannel(image, channel,
129 0.0, (double)QuantumRange, gamma);
130 }
131
132 /*
133 auto-gamma each channel separateally
134 */
135 status = MagickTrue;
136 if ((channel & RedChannel) != 0)
137 {
cristy2b726bd2010-01-11 01:05:39 +0000138 (void) GetImageChannelMean(image,RedChannel,&mean,&sans,
139 &image->exception);
140 gamma=log(mean*QuantumScale)/logmean;
cristy3ed852e2009-09-05 21:47:34 +0000141 status = status && LevelImageChannel(image, RedChannel,
142 0.0, (double)QuantumRange, gamma);
143 }
144 if ((channel & GreenChannel) != 0)
145 {
cristy2b726bd2010-01-11 01:05:39 +0000146 (void) GetImageChannelMean(image,GreenChannel,&mean,&sans,
147 &image->exception);
148 gamma=log(mean*QuantumScale)/logmean;
cristy3ed852e2009-09-05 21:47:34 +0000149 status = status && LevelImageChannel(image, GreenChannel,
150 0.0, (double)QuantumRange, gamma);
151 }
152 if ((channel & BlueChannel) != 0)
153 {
cristy2b726bd2010-01-11 01:05:39 +0000154 (void) GetImageChannelMean(image,BlueChannel,&mean,&sans,
155 &image->exception);
156 gamma=log(mean*QuantumScale)/logmean;
cristy3ed852e2009-09-05 21:47:34 +0000157 status = status && LevelImageChannel(image, BlueChannel,
158 0.0, (double)QuantumRange, gamma);
159 }
160 if (((channel & OpacityChannel) != 0) &&
161 (image->matte == MagickTrue))
162 {
cristy2b726bd2010-01-11 01:05:39 +0000163 (void) GetImageChannelMean(image,OpacityChannel,&mean,&sans,
164 &image->exception);
165 gamma=log(mean*QuantumScale)/logmean;
cristy3ed852e2009-09-05 21:47:34 +0000166 status = status && LevelImageChannel(image, OpacityChannel,
167 0.0, (double)QuantumRange, gamma);
168 }
169 if (((channel & IndexChannel) != 0) &&
170 (image->colorspace == CMYKColorspace))
171 {
cristy2b726bd2010-01-11 01:05:39 +0000172 (void) GetImageChannelMean(image,IndexChannel,&mean,&sans,
173 &image->exception);
174 gamma=log(mean*QuantumScale)/logmean;
cristy3ed852e2009-09-05 21:47:34 +0000175 status = status && LevelImageChannel(image, IndexChannel,
176 0.0, (double)QuantumRange, gamma);
177 }
178 return(status != 0 ? MagickTrue : MagickFalse);
179}
180
181/*
182%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
183% %
184% %
185% %
186% A u t o L e v e l I m a g e %
187% %
188% %
189% %
190%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
191%
192% AutoLevelImage() adjusts the levels of a particular image channel by
193% scaling the minimum and maximum values to the full quantum range.
194%
195% The format of the LevelImage method is:
196%
197% MagickBooleanType AutoLevelImage(Image *image)
198% MagickBooleanType AutoLevelImageChannel(Image *image,
199% const ChannelType channel)
200%
201% A description of each parameter follows:
202%
203% o image: The image to auto-level
204%
205% o channel: The channels to auto-level. If the special 'SyncChannels'
206% flag is set the min/max/mean value of all given channels is used for
207% all given channels, to all channels in the same way.
208%
209*/
210
211MagickExport MagickBooleanType AutoLevelImage(Image *image)
212{
213 return(AutoLevelImageChannel(image,DefaultChannels));
214}
215
216MagickExport MagickBooleanType AutoLevelImageChannel(Image *image,
217 const ChannelType channel)
218{
219 /*
220 This is simply a convenience function around a Min/Max Histogram Stretch
221 */
222 return MinMaxStretchImage(image, channel, 0.0, 0.0);
223}
224
225/*
226%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
227% %
228% %
229% %
cristya28d6b82010-01-11 20:03:47 +0000230% B r i g h t n e s s C o n t r a s t I m a g e %
231% %
232% %
233% %
234%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
235%
236% Use BrightnessContrastImage() to change the brightness and/or contrast of
237% an image. It converts the brightness and contrast parameters into slope
238% and intercept and calls a polynomical function to apply to the image.
239%
240% The format of the BrightnessContrastImage method is:
241%
242% MagickBooleanType BrightnessContrastImage(Image *image,
243% const double brightness,const double contrast)
244% MagickBooleanType BrightnessContrastImageChannel(Image *image,
245% const ChannelType channel,const double brightness,
246% const double contrast)
247%
248% A description of each parameter follows:
249%
250% o image: the image.
251%
252% o channel: the channel.
253%
254% o brightness: the brightness percent (-100 .. 100).
255%
256% o contrast: the contrast percent (-100 .. 100).
257%
258*/
259
260MagickExport MagickBooleanType BrightnessContrastImage(Image *image,
261 const double brightness,const double contrast)
262{
263 MagickBooleanType
264 status;
265
266 status=BrightnessContrastImageChannel(image,DefaultChannels,brightness,
267 contrast);
268 return(status);
269}
270
271MagickExport MagickBooleanType BrightnessContrastImageChannel(Image *image,
272 const ChannelType channel,const double brightness,const double contrast)
273{
274#define BrightnessContastImageTag "BrightnessContast/Image"
275
276 double
277 alpha,
278 intercept,
279 coefficients[2],
280 slope;
281
282 MagickBooleanType
283 status;
284
285 /*
286 Compute slope and intercept.
287 */
288 assert(image != (Image *) NULL);
289 assert(image->signature == MagickSignature);
290 if (image->debug != MagickFalse)
291 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
292 alpha=contrast;
cristy4205a3c2010-09-12 20:19:59 +0000293 slope=tan((double) (MagickPI*(alpha/100.0+1.0)/4.0));
cristya28d6b82010-01-11 20:03:47 +0000294 if (slope < 0.0)
295 slope=0.0;
296 intercept=brightness/100.0+((100-brightness)/200.0)*(1.0-slope);
297 coefficients[0]=slope;
298 coefficients[1]=intercept;
299 status=FunctionImageChannel(image,channel,PolynomialFunction,2,coefficients,
300 &image->exception);
301 return(status);
302}
303
304/*
305%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
306% %
307% %
308% %
cristy3ed852e2009-09-05 21:47:34 +0000309% C o l o r D e c i s i o n L i s t I m a g e %
310% %
311% %
312% %
313%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
314%
315% ColorDecisionListImage() accepts a lightweight Color Correction Collection
316% (CCC) file which solely contains one or more color corrections and applies
317% the correction to the image. Here is a sample CCC file:
318%
319% <ColorCorrectionCollection xmlns="urn:ASC:CDL:v1.2">
320% <ColorCorrection id="cc03345">
321% <SOPNode>
322% <Slope> 0.9 1.2 0.5 </Slope>
323% <Offset> 0.4 -0.5 0.6 </Offset>
324% <Power> 1.0 0.8 1.5 </Power>
325% </SOPNode>
326% <SATNode>
327% <Saturation> 0.85 </Saturation>
328% </SATNode>
329% </ColorCorrection>
330% </ColorCorrectionCollection>
331%
332% which includes the slop, offset, and power for each of the RGB channels
333% as well as the saturation.
334%
335% The format of the ColorDecisionListImage method is:
336%
337% MagickBooleanType ColorDecisionListImage(Image *image,
338% const char *color_correction_collection)
339%
340% A description of each parameter follows:
341%
342% o image: the image.
343%
344% o color_correction_collection: the color correction collection in XML.
345%
346*/
347MagickExport MagickBooleanType ColorDecisionListImage(Image *image,
348 const char *color_correction_collection)
349{
350#define ColorDecisionListCorrectImageTag "ColorDecisionList/Image"
351
352 typedef struct _Correction
353 {
354 double
355 slope,
356 offset,
357 power;
358 } Correction;
359
360 typedef struct _ColorCorrection
361 {
362 Correction
363 red,
364 green,
365 blue;
366
367 double
368 saturation;
369 } ColorCorrection;
370
cristyc4c8d132010-01-07 01:58:38 +0000371 CacheView
372 *image_view;
373
cristy3ed852e2009-09-05 21:47:34 +0000374 char
375 token[MaxTextExtent];
376
377 ColorCorrection
378 color_correction;
379
380 const char
381 *content,
382 *p;
383
384 ExceptionInfo
385 *exception;
386
cristy3ed852e2009-09-05 21:47:34 +0000387 MagickBooleanType
388 status;
389
cristybb503372010-05-27 20:51:26 +0000390 MagickOffsetType
391 progress;
392
cristy3ed852e2009-09-05 21:47:34 +0000393 PixelPacket
394 *cdl_map;
395
cristybb503372010-05-27 20:51:26 +0000396 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000397 i;
398
cristybb503372010-05-27 20:51:26 +0000399 ssize_t
400 y;
401
cristy3ed852e2009-09-05 21:47:34 +0000402 XMLTreeInfo
403 *cc,
404 *ccc,
405 *sat,
406 *sop;
407
cristy3ed852e2009-09-05 21:47:34 +0000408 /*
409 Allocate and initialize cdl maps.
410 */
411 assert(image != (Image *) NULL);
412 assert(image->signature == MagickSignature);
413 if (image->debug != MagickFalse)
414 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
415 if (color_correction_collection == (const char *) NULL)
416 return(MagickFalse);
417 ccc=NewXMLTree((const char *) color_correction_collection,&image->exception);
418 if (ccc == (XMLTreeInfo *) NULL)
419 return(MagickFalse);
420 cc=GetXMLTreeChild(ccc,"ColorCorrection");
421 if (cc == (XMLTreeInfo *) NULL)
422 {
423 ccc=DestroyXMLTree(ccc);
424 return(MagickFalse);
425 }
426 color_correction.red.slope=1.0;
427 color_correction.red.offset=0.0;
428 color_correction.red.power=1.0;
429 color_correction.green.slope=1.0;
430 color_correction.green.offset=0.0;
431 color_correction.green.power=1.0;
432 color_correction.blue.slope=1.0;
433 color_correction.blue.offset=0.0;
434 color_correction.blue.power=1.0;
435 color_correction.saturation=0.0;
436 sop=GetXMLTreeChild(cc,"SOPNode");
437 if (sop != (XMLTreeInfo *) NULL)
438 {
439 XMLTreeInfo
440 *offset,
441 *power,
442 *slope;
443
444 slope=GetXMLTreeChild(sop,"Slope");
445 if (slope != (XMLTreeInfo *) NULL)
446 {
447 content=GetXMLTreeContent(slope);
448 p=(const char *) content;
449 for (i=0; (*p != '\0') && (i < 3); i++)
450 {
451 GetMagickToken(p,&p,token);
452 if (*token == ',')
453 GetMagickToken(p,&p,token);
454 switch (i)
455 {
cristyf2f27272009-12-17 14:48:46 +0000456 case 0: color_correction.red.slope=StringToDouble(token); break;
457 case 1: color_correction.green.slope=StringToDouble(token); break;
458 case 2: color_correction.blue.slope=StringToDouble(token); break;
cristy3ed852e2009-09-05 21:47:34 +0000459 }
460 }
461 }
462 offset=GetXMLTreeChild(sop,"Offset");
463 if (offset != (XMLTreeInfo *) NULL)
464 {
465 content=GetXMLTreeContent(offset);
466 p=(const char *) content;
467 for (i=0; (*p != '\0') && (i < 3); i++)
468 {
469 GetMagickToken(p,&p,token);
470 if (*token == ',')
471 GetMagickToken(p,&p,token);
472 switch (i)
473 {
cristyf2f27272009-12-17 14:48:46 +0000474 case 0: color_correction.red.offset=StringToDouble(token); break;
475 case 1: color_correction.green.offset=StringToDouble(token); break;
476 case 2: color_correction.blue.offset=StringToDouble(token); break;
cristy3ed852e2009-09-05 21:47:34 +0000477 }
478 }
479 }
480 power=GetXMLTreeChild(sop,"Power");
481 if (power != (XMLTreeInfo *) NULL)
482 {
483 content=GetXMLTreeContent(power);
484 p=(const char *) content;
485 for (i=0; (*p != '\0') && (i < 3); i++)
486 {
487 GetMagickToken(p,&p,token);
488 if (*token == ',')
489 GetMagickToken(p,&p,token);
490 switch (i)
491 {
cristyf2f27272009-12-17 14:48:46 +0000492 case 0: color_correction.red.power=StringToDouble(token); break;
493 case 1: color_correction.green.power=StringToDouble(token); break;
494 case 2: color_correction.blue.power=StringToDouble(token); break;
cristy3ed852e2009-09-05 21:47:34 +0000495 }
496 }
497 }
498 }
499 sat=GetXMLTreeChild(cc,"SATNode");
500 if (sat != (XMLTreeInfo *) NULL)
501 {
502 XMLTreeInfo
503 *saturation;
504
505 saturation=GetXMLTreeChild(sat,"Saturation");
506 if (saturation != (XMLTreeInfo *) NULL)
507 {
508 content=GetXMLTreeContent(saturation);
509 p=(const char *) content;
510 GetMagickToken(p,&p,token);
cristyf2f27272009-12-17 14:48:46 +0000511 color_correction.saturation=StringToDouble(token);
cristy3ed852e2009-09-05 21:47:34 +0000512 }
513 }
514 ccc=DestroyXMLTree(ccc);
515 if (image->debug != MagickFalse)
516 {
517 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
518 " Color Correction Collection:");
519 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000520 " color_correction.red.slope: %g",color_correction.red.slope);
cristy3ed852e2009-09-05 21:47:34 +0000521 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000522 " color_correction.red.offset: %g",color_correction.red.offset);
cristy3ed852e2009-09-05 21:47:34 +0000523 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000524 " color_correction.red.power: %g",color_correction.red.power);
cristy3ed852e2009-09-05 21:47:34 +0000525 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000526 " color_correction.green.slope: %g",color_correction.green.slope);
cristy3ed852e2009-09-05 21:47:34 +0000527 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000528 " color_correction.green.offset: %g",color_correction.green.offset);
cristy3ed852e2009-09-05 21:47:34 +0000529 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000530 " color_correction.green.power: %g",color_correction.green.power);
cristy3ed852e2009-09-05 21:47:34 +0000531 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000532 " color_correction.blue.slope: %g",color_correction.blue.slope);
cristy3ed852e2009-09-05 21:47:34 +0000533 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000534 " color_correction.blue.offset: %g",color_correction.blue.offset);
cristy3ed852e2009-09-05 21:47:34 +0000535 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000536 " color_correction.blue.power: %g",color_correction.blue.power);
cristy3ed852e2009-09-05 21:47:34 +0000537 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000538 " color_correction.saturation: %g",color_correction.saturation);
cristy3ed852e2009-09-05 21:47:34 +0000539 }
540 cdl_map=(PixelPacket *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*cdl_map));
541 if (cdl_map == (PixelPacket *) NULL)
542 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
543 image->filename);
cristyb5d5f722009-11-04 03:03:49 +0000544#if defined(MAGICKCORE_OPENMP_SUPPORT)
545 #pragma omp parallel for schedule(dynamic,4)
cristy3ed852e2009-09-05 21:47:34 +0000546#endif
cristybb503372010-05-27 20:51:26 +0000547 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +0000548 {
cristyce70c172010-01-07 17:15:30 +0000549 cdl_map[i].red=ClampToQuantum((MagickRealType) ScaleMapToQuantum((
cristy3ed852e2009-09-05 21:47:34 +0000550 MagickRealType) (MaxMap*(pow(color_correction.red.slope*i/MaxMap+
551 color_correction.red.offset,color_correction.red.power)))));
cristyce70c172010-01-07 17:15:30 +0000552 cdl_map[i].green=ClampToQuantum((MagickRealType) ScaleMapToQuantum((
cristy3ed852e2009-09-05 21:47:34 +0000553 MagickRealType) (MaxMap*(pow(color_correction.green.slope*i/MaxMap+
554 color_correction.green.offset,color_correction.green.power)))));
cristyce70c172010-01-07 17:15:30 +0000555 cdl_map[i].blue=ClampToQuantum((MagickRealType) ScaleMapToQuantum((
cristy3ed852e2009-09-05 21:47:34 +0000556 MagickRealType) (MaxMap*(pow(color_correction.blue.slope*i/MaxMap+
557 color_correction.blue.offset,color_correction.blue.power)))));
558 }
559 if (image->storage_class == PseudoClass)
560 {
561 /*
562 Apply transfer function to colormap.
563 */
cristyb5d5f722009-11-04 03:03:49 +0000564#if defined(MAGICKCORE_OPENMP_SUPPORT)
565 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000566#endif
cristybb503372010-05-27 20:51:26 +0000567 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +0000568 {
569 double
570 luma;
571
572 luma=0.2126*image->colormap[i].red+0.7152*image->colormap[i].green+
573 0.0722*image->colormap[i].blue;
cristyce70c172010-01-07 17:15:30 +0000574 image->colormap[i].red=ClampToQuantum(luma+color_correction.saturation*
cristy3ed852e2009-09-05 21:47:34 +0000575 cdl_map[ScaleQuantumToMap(image->colormap[i].red)].red-luma);
cristyce70c172010-01-07 17:15:30 +0000576 image->colormap[i].green=ClampToQuantum(luma+
cristy3ed852e2009-09-05 21:47:34 +0000577 color_correction.saturation*cdl_map[ScaleQuantumToMap(
578 image->colormap[i].green)].green-luma);
cristyce70c172010-01-07 17:15:30 +0000579 image->colormap[i].blue=ClampToQuantum(luma+color_correction.saturation*
cristy3ed852e2009-09-05 21:47:34 +0000580 cdl_map[ScaleQuantumToMap(image->colormap[i].blue)].blue-luma);
581 }
582 }
583 /*
584 Apply transfer function to image.
585 */
586 status=MagickTrue;
587 progress=0;
588 exception=(&image->exception);
589 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +0000590#if defined(MAGICKCORE_OPENMP_SUPPORT)
591 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000592#endif
cristybb503372010-05-27 20:51:26 +0000593 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000594 {
595 double
596 luma;
597
cristy3ed852e2009-09-05 21:47:34 +0000598 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +0000599 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000600
cristy8d4629b2010-08-30 17:59:46 +0000601 register ssize_t
602 x;
603
cristy3ed852e2009-09-05 21:47:34 +0000604 if (status == MagickFalse)
605 continue;
606 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
607 if (q == (PixelPacket *) NULL)
608 {
609 status=MagickFalse;
610 continue;
611 }
cristybb503372010-05-27 20:51:26 +0000612 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000613 {
614 luma=0.2126*q->red+0.7152*q->green+0.0722*q->blue;
cristyce70c172010-01-07 17:15:30 +0000615 q->red=ClampToQuantum(luma+color_correction.saturation*
cristy3ed852e2009-09-05 21:47:34 +0000616 (cdl_map[ScaleQuantumToMap(q->red)].red-luma));
cristyce70c172010-01-07 17:15:30 +0000617 q->green=ClampToQuantum(luma+color_correction.saturation*
cristy3ed852e2009-09-05 21:47:34 +0000618 (cdl_map[ScaleQuantumToMap(q->green)].green-luma));
cristyce70c172010-01-07 17:15:30 +0000619 q->blue=ClampToQuantum(luma+color_correction.saturation*
cristy3ed852e2009-09-05 21:47:34 +0000620 (cdl_map[ScaleQuantumToMap(q->blue)].blue-luma));
621 q++;
622 }
623 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
624 status=MagickFalse;
625 if (image->progress_monitor != (MagickProgressMonitor) NULL)
626 {
627 MagickBooleanType
628 proceed;
629
cristyb5d5f722009-11-04 03:03:49 +0000630#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +0000631 #pragma omp critical (MagickCore_ColorDecisionListImageChannel)
632#endif
633 proceed=SetImageProgress(image,ColorDecisionListCorrectImageTag,
634 progress++,image->rows);
635 if (proceed == MagickFalse)
636 status=MagickFalse;
637 }
638 }
639 image_view=DestroyCacheView(image_view);
640 cdl_map=(PixelPacket *) RelinquishMagickMemory(cdl_map);
641 return(status);
642}
643
644/*
645%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
646% %
647% %
648% %
649% C l u t I m a g e %
650% %
651% %
652% %
653%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
654%
655% ClutImage() replaces each color value in the given image, by using it as an
656% index to lookup a replacement color value in a Color Look UP Table in the
cristycee97112010-05-28 00:44:52 +0000657% form of an image. The values are extracted along a diagonal of the CLUT
cristy3ed852e2009-09-05 21:47:34 +0000658% image so either a horizontal or vertial gradient image can be used.
659%
660% Typically this is used to either re-color a gray-scale image according to a
661% color gradient in the CLUT image, or to perform a freeform histogram
662% (level) adjustment according to the (typically gray-scale) gradient in the
663% CLUT image.
664%
665% When the 'channel' mask includes the matte/alpha transparency channel but
666% one image has no such channel it is assumed that that image is a simple
667% gray-scale image that will effect the alpha channel values, either for
668% gray-scale coloring (with transparent or semi-transparent colors), or
669% a histogram adjustment of existing alpha channel values. If both images
670% have matte channels, direct and normal indexing is applied, which is rarely
671% used.
672%
673% The format of the ClutImage method is:
674%
675% MagickBooleanType ClutImage(Image *image,Image *clut_image)
676% MagickBooleanType ClutImageChannel(Image *image,
677% const ChannelType channel,Image *clut_image)
678%
679% A description of each parameter follows:
680%
681% o image: the image, which is replaced by indexed CLUT values
682%
683% o clut_image: the color lookup table image for replacement color values.
684%
685% o channel: the channel.
686%
687*/
688
689MagickExport MagickBooleanType ClutImage(Image *image,const Image *clut_image)
690{
691 return(ClutImageChannel(image,DefaultChannels,clut_image));
692}
693
694MagickExport MagickBooleanType ClutImageChannel(Image *image,
695 const ChannelType channel,const Image *clut_image)
696{
697#define ClutImageTag "Clut/Image"
698
cristyfa112112010-01-04 17:48:07 +0000699 CacheView
700 *image_view;
701
cristy3ed852e2009-09-05 21:47:34 +0000702 ExceptionInfo
703 *exception;
704
cristy3ed852e2009-09-05 21:47:34 +0000705 MagickBooleanType
706 status;
707
cristybb503372010-05-27 20:51:26 +0000708 MagickOffsetType
709 progress;
710
cristy3ed852e2009-09-05 21:47:34 +0000711 MagickPixelPacket
cristy49f37242011-03-22 18:18:23 +0000712 *clut_map;
713
714 register ssize_t
715 i;
cristy3ed852e2009-09-05 21:47:34 +0000716
717 ResampleFilter
cristyfa112112010-01-04 17:48:07 +0000718 **restrict resample_filter;
cristy3ed852e2009-09-05 21:47:34 +0000719
cristybb503372010-05-27 20:51:26 +0000720 ssize_t
721 adjust,
722 y;
723
cristy3ed852e2009-09-05 21:47:34 +0000724 assert(image != (Image *) NULL);
725 assert(image->signature == MagickSignature);
726 if (image->debug != MagickFalse)
727 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
728 assert(clut_image != (Image *) NULL);
729 assert(clut_image->signature == MagickSignature);
730 if (SetImageStorageClass(image,DirectClass) == MagickFalse)
731 return(MagickFalse);
cristy49f37242011-03-22 18:18:23 +0000732 clut_map=(MagickPixelPacket *) AcquireQuantumMemory(MaxMap+1UL,
733 sizeof(*clut_map));
734 if (clut_map == (MagickPixelPacket *) NULL)
735 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
736 image->filename);
cristy3ed852e2009-09-05 21:47:34 +0000737 /*
738 Clut image.
739 */
740 status=MagickTrue;
741 progress=0;
cristybb503372010-05-27 20:51:26 +0000742 adjust=(ssize_t) (clut_image->interpolate == IntegerInterpolatePixel ? 0 : 1);
cristy3ed852e2009-09-05 21:47:34 +0000743 exception=(&image->exception);
cristyb2a11ae2010-02-22 00:53:36 +0000744 resample_filter=AcquireResampleFilterThreadSet(clut_image,
745 UndefinedVirtualPixelMethod,MagickTrue,exception);
cristy49f37242011-03-22 18:18:23 +0000746 for (i=0; i <= (ssize_t) MaxMap; i++)
747 {
748 GetMagickPixelPacket(clut_image,clut_map+i);
749 (void) ResamplePixelColor(resample_filter[0],QuantumScale*i*
750 (clut_image->columns-adjust),QuantumScale*i*(clut_image->rows-adjust),
751 clut_map+i);
752 }
cristy3ed852e2009-09-05 21:47:34 +0000753 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +0000754#if defined(MAGICKCORE_OPENMP_SUPPORT)
755 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000756#endif
cristybb503372010-05-27 20:51:26 +0000757 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000758 {
cristy3ed852e2009-09-05 21:47:34 +0000759 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +0000760 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +0000761
cristy3ed852e2009-09-05 21:47:34 +0000762 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +0000763 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000764
cristy8d4629b2010-08-30 17:59:46 +0000765 register ssize_t
766 x;
767
cristy3ed852e2009-09-05 21:47:34 +0000768 if (status == MagickFalse)
769 continue;
770 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
771 if (q == (PixelPacket *) NULL)
772 {
773 status=MagickFalse;
774 continue;
775 }
776 indexes=GetCacheViewAuthenticIndexQueue(image_view);
cristybb503372010-05-27 20:51:26 +0000777 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000778 {
cristy3ed852e2009-09-05 21:47:34 +0000779 if ((channel & OpacityChannel) != 0)
780 {
781 if (clut_image->matte == MagickFalse)
cristy49f37242011-03-22 18:18:23 +0000782 q->opacity=(Quantum) (QuantumRange-MagickPixelIntensityToQuantum(
783 clut_map+ScaleQuantumToMap(GetAlphaPixelComponent(q))));
cristy3ed852e2009-09-05 21:47:34 +0000784 else
785 if (image->matte == MagickFalse)
cristy49f37242011-03-22 18:18:23 +0000786 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(clut_map+
787 ScaleQuantumToMap(PixelIntensity(q))));
cristy3ed852e2009-09-05 21:47:34 +0000788 else
cristy49f37242011-03-22 18:18:23 +0000789 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(
790 clut_map+ScaleQuantumToMap(q->opacity)));
cristy3ed852e2009-09-05 21:47:34 +0000791 }
cristy2253f172011-03-24 23:39:51 +0000792 if ((channel & RedChannel) != 0)
793 SetRedPixelComponent(q,ClampRedPixelComponent(clut_map+
794 ScaleQuantumToMap(q->red)));
795 if ((channel & GreenChannel) != 0)
796 SetGreenPixelComponent(q,ClampGreenPixelComponent(clut_map+
797 ScaleQuantumToMap(q->green)));
798 if ((channel & BlueChannel) != 0)
799 SetBluePixelComponent(q,ClampBluePixelComponent(clut_map+
800 ScaleQuantumToMap(q->blue)));
cristy3ed852e2009-09-05 21:47:34 +0000801 if (((channel & IndexChannel) != 0) &&
802 (image->colorspace == CMYKColorspace))
cristy49f37242011-03-22 18:18:23 +0000803 indexes[x]=ClampToQuantum((clut_map+(ssize_t) indexes[x])->index);
cristy3ed852e2009-09-05 21:47:34 +0000804 q++;
805 }
806 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
807 status=MagickFalse;
808 if (image->progress_monitor != (MagickProgressMonitor) NULL)
809 {
810 MagickBooleanType
811 proceed;
812
cristyb5d5f722009-11-04 03:03:49 +0000813#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +0000814 #pragma omp critical (MagickCore_ClutImageChannel)
815#endif
816 proceed=SetImageProgress(image,ClutImageTag,progress++,image->rows);
817 if (proceed == MagickFalse)
818 status=MagickFalse;
819 }
820 }
821 image_view=DestroyCacheView(image_view);
822 resample_filter=DestroyResampleFilterThreadSet(resample_filter);
cristy49f37242011-03-22 18:18:23 +0000823 clut_map=(MagickPixelPacket *) RelinquishMagickMemory(clut_map);
cristy3ed852e2009-09-05 21:47:34 +0000824 if ((clut_image->matte != MagickFalse) && ((channel & OpacityChannel) != 0))
825 (void) SetImageAlphaChannel(image,ActivateAlphaChannel);
826 return(status);
827}
828
829/*
830%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
831% %
832% %
833% %
834% C o n t r a s t I m a g e %
835% %
836% %
837% %
838%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
839%
840% ContrastImage() enhances the intensity differences between the lighter and
841% darker elements of the image. Set sharpen to a MagickTrue to increase the
842% image contrast otherwise the contrast is reduced.
843%
844% The format of the ContrastImage method is:
845%
846% MagickBooleanType ContrastImage(Image *image,
847% const MagickBooleanType sharpen)
848%
849% A description of each parameter follows:
850%
851% o image: the image.
852%
853% o sharpen: Increase or decrease image contrast.
854%
855*/
856
857static void Contrast(const int sign,Quantum *red,Quantum *green,Quantum *blue)
858{
859 double
860 brightness,
861 hue,
862 saturation;
863
864 /*
865 Enhance contrast: dark color become darker, light color become lighter.
866 */
867 assert(red != (Quantum *) NULL);
868 assert(green != (Quantum *) NULL);
869 assert(blue != (Quantum *) NULL);
870 hue=0.0;
871 saturation=0.0;
872 brightness=0.0;
873 ConvertRGBToHSB(*red,*green,*blue,&hue,&saturation,&brightness);
cristy31ac0f02011-03-12 02:04:47 +0000874 brightness+=0.5*sign*(0.5*(sin((double) (MagickPI*(brightness-0.5)))+1.0)-
cristy4205a3c2010-09-12 20:19:59 +0000875 brightness);
cristy3ed852e2009-09-05 21:47:34 +0000876 if (brightness > 1.0)
877 brightness=1.0;
878 else
879 if (brightness < 0.0)
880 brightness=0.0;
881 ConvertHSBToRGB(hue,saturation,brightness,red,green,blue);
882}
883
884MagickExport MagickBooleanType ContrastImage(Image *image,
885 const MagickBooleanType sharpen)
886{
887#define ContrastImageTag "Contrast/Image"
888
cristyc4c8d132010-01-07 01:58:38 +0000889 CacheView
890 *image_view;
891
cristy3ed852e2009-09-05 21:47:34 +0000892 ExceptionInfo
893 *exception;
894
895 int
896 sign;
897
cristy3ed852e2009-09-05 21:47:34 +0000898 MagickBooleanType
899 status;
900
cristybb503372010-05-27 20:51:26 +0000901 MagickOffsetType
902 progress;
903
904 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000905 i;
906
cristybb503372010-05-27 20:51:26 +0000907 ssize_t
908 y;
909
cristy3ed852e2009-09-05 21:47:34 +0000910 assert(image != (Image *) NULL);
911 assert(image->signature == MagickSignature);
912 if (image->debug != MagickFalse)
913 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
914 sign=sharpen != MagickFalse ? 1 : -1;
915 if (image->storage_class == PseudoClass)
916 {
917 /*
918 Contrast enhance colormap.
919 */
cristybb503372010-05-27 20:51:26 +0000920 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +0000921 Contrast(sign,&image->colormap[i].red,&image->colormap[i].green,
922 &image->colormap[i].blue);
923 }
924 /*
925 Contrast enhance image.
926 */
927 status=MagickTrue;
928 progress=0;
929 exception=(&image->exception);
930 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +0000931#if defined(MAGICKCORE_OPENMP_SUPPORT)
932 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000933#endif
cristybb503372010-05-27 20:51:26 +0000934 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000935 {
cristy3ed852e2009-09-05 21:47:34 +0000936 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +0000937 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000938
cristy8d4629b2010-08-30 17:59:46 +0000939 register ssize_t
940 x;
941
cristy3ed852e2009-09-05 21:47:34 +0000942 if (status == MagickFalse)
943 continue;
944 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
945 if (q == (PixelPacket *) NULL)
946 {
947 status=MagickFalse;
948 continue;
949 }
cristybb503372010-05-27 20:51:26 +0000950 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000951 {
952 Contrast(sign,&q->red,&q->green,&q->blue);
953 q++;
954 }
955 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
956 status=MagickFalse;
957 if (image->progress_monitor != (MagickProgressMonitor) NULL)
958 {
959 MagickBooleanType
960 proceed;
961
cristyb5d5f722009-11-04 03:03:49 +0000962#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +0000963 #pragma omp critical (MagickCore_ContrastImage)
964#endif
965 proceed=SetImageProgress(image,ContrastImageTag,progress++,image->rows);
966 if (proceed == MagickFalse)
967 status=MagickFalse;
968 }
969 }
970 image_view=DestroyCacheView(image_view);
971 return(status);
972}
973
974/*
975%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
976% %
977% %
978% %
979% C o n t r a s t S t r e t c h I m a g e %
980% %
981% %
982% %
983%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
984%
985% The ContrastStretchImage() is a simple image enhancement technique that
986% attempts to improve the contrast in an image by `stretching' the range of
987% intensity values it contains to span a desired range of values. It differs
988% from the more sophisticated histogram equalization in that it can only
989% apply % a linear scaling function to the image pixel values. As a result
990% the `enhancement' is less harsh.
991%
992% The format of the ContrastStretchImage method is:
993%
994% MagickBooleanType ContrastStretchImage(Image *image,
995% const char *levels)
996% MagickBooleanType ContrastStretchImageChannel(Image *image,
cristybb503372010-05-27 20:51:26 +0000997% const size_t channel,const double black_point,
cristy3ed852e2009-09-05 21:47:34 +0000998% const double white_point)
999%
1000% A description of each parameter follows:
1001%
1002% o image: the image.
1003%
1004% o channel: the channel.
1005%
1006% o black_point: the black point.
1007%
1008% o white_point: the white point.
1009%
1010% o levels: Specify the levels where the black and white points have the
1011% range of 0 to number-of-pixels (e.g. 1%, 10x90%, etc.).
1012%
1013*/
1014
1015MagickExport MagickBooleanType ContrastStretchImage(Image *image,
1016 const char *levels)
1017{
1018 double
1019 black_point,
1020 white_point;
1021
1022 GeometryInfo
1023 geometry_info;
1024
1025 MagickBooleanType
1026 status;
1027
1028 MagickStatusType
1029 flags;
1030
1031 /*
1032 Parse levels.
1033 */
1034 if (levels == (char *) NULL)
1035 return(MagickFalse);
1036 flags=ParseGeometry(levels,&geometry_info);
1037 black_point=geometry_info.rho;
1038 white_point=(double) image->columns*image->rows;
1039 if ((flags & SigmaValue) != 0)
1040 white_point=geometry_info.sigma;
1041 if ((flags & PercentValue) != 0)
1042 {
1043 black_point*=(double) QuantumRange/100.0;
1044 white_point*=(double) QuantumRange/100.0;
1045 }
1046 if ((flags & SigmaValue) == 0)
1047 white_point=(double) image->columns*image->rows-black_point;
1048 status=ContrastStretchImageChannel(image,DefaultChannels,black_point,
1049 white_point);
1050 return(status);
1051}
1052
1053MagickExport MagickBooleanType ContrastStretchImageChannel(Image *image,
1054 const ChannelType channel,const double black_point,const double white_point)
1055{
1056#define MaxRange(color) ((MagickRealType) ScaleQuantumToMap((Quantum) (color)))
1057#define ContrastStretchImageTag "ContrastStretch/Image"
1058
cristyc4c8d132010-01-07 01:58:38 +00001059 CacheView
1060 *image_view;
1061
cristy3ed852e2009-09-05 21:47:34 +00001062 double
1063 intensity;
1064
1065 ExceptionInfo
1066 *exception;
1067
cristy3ed852e2009-09-05 21:47:34 +00001068 MagickBooleanType
1069 status;
1070
cristybb503372010-05-27 20:51:26 +00001071 MagickOffsetType
1072 progress;
1073
cristy3ed852e2009-09-05 21:47:34 +00001074 MagickPixelPacket
1075 black,
1076 *histogram,
1077 *stretch_map,
1078 white;
1079
cristybb503372010-05-27 20:51:26 +00001080 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001081 i;
1082
cristybb503372010-05-27 20:51:26 +00001083 ssize_t
1084 y;
1085
cristy3ed852e2009-09-05 21:47:34 +00001086 /*
1087 Allocate histogram and stretch map.
1088 */
1089 assert(image != (Image *) NULL);
1090 assert(image->signature == MagickSignature);
1091 if (image->debug != MagickFalse)
1092 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1093 histogram=(MagickPixelPacket *) AcquireQuantumMemory(MaxMap+1UL,
1094 sizeof(*histogram));
1095 stretch_map=(MagickPixelPacket *) AcquireQuantumMemory(MaxMap+1UL,
1096 sizeof(*stretch_map));
1097 if ((histogram == (MagickPixelPacket *) NULL) ||
1098 (stretch_map == (MagickPixelPacket *) NULL))
1099 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
1100 image->filename);
1101 /*
1102 Form histogram.
1103 */
1104 status=MagickTrue;
1105 exception=(&image->exception);
1106 (void) ResetMagickMemory(histogram,0,(MaxMap+1)*sizeof(*histogram));
1107 image_view=AcquireCacheView(image);
cristybb503372010-05-27 20:51:26 +00001108 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001109 {
1110 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001111 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00001112
1113 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00001114 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00001115
cristybb503372010-05-27 20:51:26 +00001116 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001117 x;
1118
1119 if (status == MagickFalse)
1120 continue;
1121 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
1122 if (p == (const PixelPacket *) NULL)
1123 {
1124 status=MagickFalse;
1125 continue;
1126 }
1127 indexes=GetCacheViewAuthenticIndexQueue(image_view);
1128 if (channel == DefaultChannels)
cristybb503372010-05-27 20:51:26 +00001129 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001130 {
1131 Quantum
1132 intensity;
1133
1134 intensity=PixelIntensityToQuantum(p);
1135 histogram[ScaleQuantumToMap(intensity)].red++;
1136 histogram[ScaleQuantumToMap(intensity)].green++;
1137 histogram[ScaleQuantumToMap(intensity)].blue++;
1138 histogram[ScaleQuantumToMap(intensity)].index++;
1139 p++;
1140 }
1141 else
cristybb503372010-05-27 20:51:26 +00001142 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001143 {
1144 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001145 histogram[ScaleQuantumToMap(GetRedPixelComponent(p))].red++;
cristy3ed852e2009-09-05 21:47:34 +00001146 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001147 histogram[ScaleQuantumToMap(GetGreenPixelComponent(p))].green++;
cristy3ed852e2009-09-05 21:47:34 +00001148 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001149 histogram[ScaleQuantumToMap(GetBluePixelComponent(p))].blue++;
cristy3ed852e2009-09-05 21:47:34 +00001150 if ((channel & OpacityChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001151 histogram[ScaleQuantumToMap(GetOpacityPixelComponent(p))].opacity++;
cristy3ed852e2009-09-05 21:47:34 +00001152 if (((channel & IndexChannel) != 0) &&
1153 (image->colorspace == CMYKColorspace))
1154 histogram[ScaleQuantumToMap(indexes[x])].index++;
1155 p++;
1156 }
1157 }
1158 /*
1159 Find the histogram boundaries by locating the black/white levels.
1160 */
1161 black.red=0.0;
1162 white.red=MaxRange(QuantumRange);
1163 if ((channel & RedChannel) != 0)
1164 {
1165 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00001166 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +00001167 {
1168 intensity+=histogram[i].red;
1169 if (intensity > black_point)
1170 break;
1171 }
1172 black.red=(MagickRealType) i;
1173 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00001174 for (i=(ssize_t) MaxMap; i != 0; i--)
cristy3ed852e2009-09-05 21:47:34 +00001175 {
1176 intensity+=histogram[i].red;
1177 if (intensity > ((double) image->columns*image->rows-white_point))
1178 break;
1179 }
1180 white.red=(MagickRealType) i;
1181 }
1182 black.green=0.0;
1183 white.green=MaxRange(QuantumRange);
1184 if ((channel & GreenChannel) != 0)
1185 {
1186 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00001187 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +00001188 {
1189 intensity+=histogram[i].green;
1190 if (intensity > black_point)
1191 break;
1192 }
1193 black.green=(MagickRealType) i;
1194 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00001195 for (i=(ssize_t) MaxMap; i != 0; i--)
cristy3ed852e2009-09-05 21:47:34 +00001196 {
1197 intensity+=histogram[i].green;
1198 if (intensity > ((double) image->columns*image->rows-white_point))
1199 break;
1200 }
1201 white.green=(MagickRealType) i;
1202 }
1203 black.blue=0.0;
1204 white.blue=MaxRange(QuantumRange);
1205 if ((channel & BlueChannel) != 0)
1206 {
1207 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00001208 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +00001209 {
1210 intensity+=histogram[i].blue;
1211 if (intensity > black_point)
1212 break;
1213 }
1214 black.blue=(MagickRealType) i;
1215 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00001216 for (i=(ssize_t) MaxMap; i != 0; i--)
cristy3ed852e2009-09-05 21:47:34 +00001217 {
1218 intensity+=histogram[i].blue;
1219 if (intensity > ((double) image->columns*image->rows-white_point))
1220 break;
1221 }
1222 white.blue=(MagickRealType) i;
1223 }
1224 black.opacity=0.0;
1225 white.opacity=MaxRange(QuantumRange);
1226 if ((channel & OpacityChannel) != 0)
1227 {
1228 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00001229 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +00001230 {
1231 intensity+=histogram[i].opacity;
1232 if (intensity > black_point)
1233 break;
1234 }
1235 black.opacity=(MagickRealType) i;
1236 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00001237 for (i=(ssize_t) MaxMap; i != 0; i--)
cristy3ed852e2009-09-05 21:47:34 +00001238 {
1239 intensity+=histogram[i].opacity;
1240 if (intensity > ((double) image->columns*image->rows-white_point))
1241 break;
1242 }
1243 white.opacity=(MagickRealType) i;
1244 }
1245 black.index=0.0;
1246 white.index=MaxRange(QuantumRange);
1247 if (((channel & IndexChannel) != 0) && (image->colorspace == CMYKColorspace))
1248 {
1249 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00001250 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +00001251 {
1252 intensity+=histogram[i].index;
1253 if (intensity > black_point)
1254 break;
1255 }
1256 black.index=(MagickRealType) i;
1257 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00001258 for (i=(ssize_t) MaxMap; i != 0; i--)
cristy3ed852e2009-09-05 21:47:34 +00001259 {
1260 intensity+=histogram[i].index;
1261 if (intensity > ((double) image->columns*image->rows-white_point))
1262 break;
1263 }
1264 white.index=(MagickRealType) i;
1265 }
1266 histogram=(MagickPixelPacket *) RelinquishMagickMemory(histogram);
1267 /*
1268 Stretch the histogram to create the stretched image mapping.
1269 */
1270 (void) ResetMagickMemory(stretch_map,0,(MaxMap+1)*sizeof(*stretch_map));
cristyb5d5f722009-11-04 03:03:49 +00001271#if defined(MAGICKCORE_OPENMP_SUPPORT)
1272 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001273#endif
cristybb503372010-05-27 20:51:26 +00001274 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +00001275 {
1276 if ((channel & RedChannel) != 0)
1277 {
cristybb503372010-05-27 20:51:26 +00001278 if (i < (ssize_t) black.red)
cristy3ed852e2009-09-05 21:47:34 +00001279 stretch_map[i].red=0.0;
1280 else
cristybb503372010-05-27 20:51:26 +00001281 if (i > (ssize_t) white.red)
cristy3ed852e2009-09-05 21:47:34 +00001282 stretch_map[i].red=(MagickRealType) QuantumRange;
1283 else
1284 if (black.red != white.red)
1285 stretch_map[i].red=(MagickRealType) ScaleMapToQuantum(
1286 (MagickRealType) (MaxMap*(i-black.red)/(white.red-black.red)));
1287 }
1288 if ((channel & GreenChannel) != 0)
1289 {
cristybb503372010-05-27 20:51:26 +00001290 if (i < (ssize_t) black.green)
cristy3ed852e2009-09-05 21:47:34 +00001291 stretch_map[i].green=0.0;
1292 else
cristybb503372010-05-27 20:51:26 +00001293 if (i > (ssize_t) white.green)
cristy3ed852e2009-09-05 21:47:34 +00001294 stretch_map[i].green=(MagickRealType) QuantumRange;
1295 else
1296 if (black.green != white.green)
1297 stretch_map[i].green=(MagickRealType) ScaleMapToQuantum(
1298 (MagickRealType) (MaxMap*(i-black.green)/(white.green-
1299 black.green)));
1300 }
1301 if ((channel & BlueChannel) != 0)
1302 {
cristybb503372010-05-27 20:51:26 +00001303 if (i < (ssize_t) black.blue)
cristy3ed852e2009-09-05 21:47:34 +00001304 stretch_map[i].blue=0.0;
1305 else
cristybb503372010-05-27 20:51:26 +00001306 if (i > (ssize_t) white.blue)
cristy3ed852e2009-09-05 21:47:34 +00001307 stretch_map[i].blue=(MagickRealType) QuantumRange;
1308 else
1309 if (black.blue != white.blue)
1310 stretch_map[i].blue=(MagickRealType) ScaleMapToQuantum(
1311 (MagickRealType) (MaxMap*(i-black.blue)/(white.blue-
1312 black.blue)));
1313 }
1314 if ((channel & OpacityChannel) != 0)
1315 {
cristybb503372010-05-27 20:51:26 +00001316 if (i < (ssize_t) black.opacity)
cristy3ed852e2009-09-05 21:47:34 +00001317 stretch_map[i].opacity=0.0;
1318 else
cristybb503372010-05-27 20:51:26 +00001319 if (i > (ssize_t) white.opacity)
cristy3ed852e2009-09-05 21:47:34 +00001320 stretch_map[i].opacity=(MagickRealType) QuantumRange;
1321 else
1322 if (black.opacity != white.opacity)
1323 stretch_map[i].opacity=(MagickRealType) ScaleMapToQuantum(
1324 (MagickRealType) (MaxMap*(i-black.opacity)/(white.opacity-
1325 black.opacity)));
1326 }
1327 if (((channel & IndexChannel) != 0) &&
1328 (image->colorspace == CMYKColorspace))
1329 {
cristybb503372010-05-27 20:51:26 +00001330 if (i < (ssize_t) black.index)
cristy3ed852e2009-09-05 21:47:34 +00001331 stretch_map[i].index=0.0;
1332 else
cristybb503372010-05-27 20:51:26 +00001333 if (i > (ssize_t) white.index)
cristy3ed852e2009-09-05 21:47:34 +00001334 stretch_map[i].index=(MagickRealType) QuantumRange;
1335 else
1336 if (black.index != white.index)
1337 stretch_map[i].index=(MagickRealType) ScaleMapToQuantum(
1338 (MagickRealType) (MaxMap*(i-black.index)/(white.index-
1339 black.index)));
1340 }
1341 }
1342 /*
1343 Stretch the image.
1344 */
1345 if (((channel & OpacityChannel) != 0) || (((channel & IndexChannel) != 0) &&
1346 (image->colorspace == CMYKColorspace)))
1347 image->storage_class=DirectClass;
1348 if (image->storage_class == PseudoClass)
1349 {
1350 /*
1351 Stretch colormap.
1352 */
cristyb5d5f722009-11-04 03:03:49 +00001353#if defined(MAGICKCORE_OPENMP_SUPPORT)
1354 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001355#endif
cristybb503372010-05-27 20:51:26 +00001356 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00001357 {
1358 if ((channel & RedChannel) != 0)
1359 {
1360 if (black.red != white.red)
cristyce70c172010-01-07 17:15:30 +00001361 image->colormap[i].red=ClampToQuantum(stretch_map[
cristy3ed852e2009-09-05 21:47:34 +00001362 ScaleQuantumToMap(image->colormap[i].red)].red);
1363 }
1364 if ((channel & GreenChannel) != 0)
1365 {
1366 if (black.green != white.green)
cristyce70c172010-01-07 17:15:30 +00001367 image->colormap[i].green=ClampToQuantum(stretch_map[
cristy3ed852e2009-09-05 21:47:34 +00001368 ScaleQuantumToMap(image->colormap[i].green)].green);
1369 }
1370 if ((channel & BlueChannel) != 0)
1371 {
1372 if (black.blue != white.blue)
cristyce70c172010-01-07 17:15:30 +00001373 image->colormap[i].blue=ClampToQuantum(stretch_map[
cristy3ed852e2009-09-05 21:47:34 +00001374 ScaleQuantumToMap(image->colormap[i].blue)].blue);
1375 }
1376 if ((channel & OpacityChannel) != 0)
1377 {
1378 if (black.opacity != white.opacity)
cristyce70c172010-01-07 17:15:30 +00001379 image->colormap[i].opacity=ClampToQuantum(stretch_map[
cristy3ed852e2009-09-05 21:47:34 +00001380 ScaleQuantumToMap(image->colormap[i].opacity)].opacity);
1381 }
1382 }
1383 }
1384 /*
1385 Stretch image.
1386 */
1387 status=MagickTrue;
1388 progress=0;
cristyb5d5f722009-11-04 03:03:49 +00001389#if defined(MAGICKCORE_OPENMP_SUPPORT)
1390 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001391#endif
cristybb503372010-05-27 20:51:26 +00001392 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001393 {
1394 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00001395 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00001396
cristy3ed852e2009-09-05 21:47:34 +00001397 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001398 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001399
cristy8d4629b2010-08-30 17:59:46 +00001400 register ssize_t
1401 x;
1402
cristy3ed852e2009-09-05 21:47:34 +00001403 if (status == MagickFalse)
1404 continue;
1405 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
1406 if (q == (PixelPacket *) NULL)
1407 {
1408 status=MagickFalse;
1409 continue;
1410 }
1411 indexes=GetCacheViewAuthenticIndexQueue(image_view);
cristybb503372010-05-27 20:51:26 +00001412 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001413 {
1414 if ((channel & RedChannel) != 0)
1415 {
1416 if (black.red != white.red)
cristyce70c172010-01-07 17:15:30 +00001417 q->red=ClampToQuantum(stretch_map[ScaleQuantumToMap(q->red)].red);
cristy3ed852e2009-09-05 21:47:34 +00001418 }
1419 if ((channel & GreenChannel) != 0)
1420 {
1421 if (black.green != white.green)
cristyce70c172010-01-07 17:15:30 +00001422 q->green=ClampToQuantum(stretch_map[ScaleQuantumToMap(
cristy3ed852e2009-09-05 21:47:34 +00001423 q->green)].green);
1424 }
1425 if ((channel & BlueChannel) != 0)
1426 {
1427 if (black.blue != white.blue)
cristyce70c172010-01-07 17:15:30 +00001428 q->blue=ClampToQuantum(stretch_map[ScaleQuantumToMap(
cristy3ed852e2009-09-05 21:47:34 +00001429 q->blue)].blue);
1430 }
1431 if ((channel & OpacityChannel) != 0)
1432 {
1433 if (black.opacity != white.opacity)
cristyce70c172010-01-07 17:15:30 +00001434 q->opacity=ClampToQuantum(stretch_map[ScaleQuantumToMap(
cristy3ed852e2009-09-05 21:47:34 +00001435 q->opacity)].opacity);
1436 }
1437 if (((channel & IndexChannel) != 0) &&
1438 (image->colorspace == CMYKColorspace))
1439 {
1440 if (black.index != white.index)
cristyce70c172010-01-07 17:15:30 +00001441 indexes[x]=(IndexPacket) ClampToQuantum(stretch_map[
cristy3ed852e2009-09-05 21:47:34 +00001442 ScaleQuantumToMap(indexes[x])].index);
1443 }
1444 q++;
1445 }
1446 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
1447 status=MagickFalse;
1448 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1449 {
1450 MagickBooleanType
1451 proceed;
1452
cristyb5d5f722009-11-04 03:03:49 +00001453#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00001454 #pragma omp critical (MagickCore_ContrastStretchImageChannel)
1455#endif
1456 proceed=SetImageProgress(image,ContrastStretchImageTag,progress++,
1457 image->rows);
1458 if (proceed == MagickFalse)
1459 status=MagickFalse;
1460 }
1461 }
1462 image_view=DestroyCacheView(image_view);
1463 stretch_map=(MagickPixelPacket *) RelinquishMagickMemory(stretch_map);
1464 return(status);
1465}
1466
1467/*
1468%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1469% %
1470% %
1471% %
1472% E n h a n c e I m a g e %
1473% %
1474% %
1475% %
1476%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1477%
1478% EnhanceImage() applies a digital filter that improves the quality of a
1479% noisy image.
1480%
1481% The format of the EnhanceImage method is:
1482%
1483% Image *EnhanceImage(const Image *image,ExceptionInfo *exception)
1484%
1485% A description of each parameter follows:
1486%
1487% o image: the image.
1488%
1489% o exception: return any errors or warnings in this structure.
1490%
1491*/
1492MagickExport Image *EnhanceImage(const Image *image,ExceptionInfo *exception)
1493{
1494#define Enhance(weight) \
1495 mean=((MagickRealType) r->red+pixel.red)/2; \
1496 distance=(MagickRealType) r->red-(MagickRealType) pixel.red; \
1497 distance_squared=QuantumScale*(2.0*((MagickRealType) QuantumRange+1.0)+ \
1498 mean)*distance*distance; \
1499 mean=((MagickRealType) r->green+pixel.green)/2; \
1500 distance=(MagickRealType) r->green-(MagickRealType) pixel.green; \
1501 distance_squared+=4.0*distance*distance; \
1502 mean=((MagickRealType) r->blue+pixel.blue)/2; \
1503 distance=(MagickRealType) r->blue-(MagickRealType) pixel.blue; \
1504 distance_squared+=QuantumScale*(3.0*((MagickRealType) \
1505 QuantumRange+1.0)-1.0-mean)*distance*distance; \
1506 mean=((MagickRealType) r->opacity+pixel.opacity)/2; \
1507 distance=(MagickRealType) r->opacity-(MagickRealType) pixel.opacity; \
1508 distance_squared+=QuantumScale*(3.0*((MagickRealType) \
1509 QuantumRange+1.0)-1.0-mean)*distance*distance; \
1510 if (distance_squared < ((MagickRealType) QuantumRange*(MagickRealType) \
1511 QuantumRange/25.0f)) \
1512 { \
1513 aggregate.red+=(weight)*r->red; \
1514 aggregate.green+=(weight)*r->green; \
1515 aggregate.blue+=(weight)*r->blue; \
1516 aggregate.opacity+=(weight)*r->opacity; \
1517 total_weight+=(weight); \
1518 } \
1519 r++;
1520#define EnhanceImageTag "Enhance/Image"
1521
cristyc4c8d132010-01-07 01:58:38 +00001522 CacheView
1523 *enhance_view,
1524 *image_view;
1525
cristy3ed852e2009-09-05 21:47:34 +00001526 Image
1527 *enhance_image;
1528
cristy3ed852e2009-09-05 21:47:34 +00001529 MagickBooleanType
1530 status;
1531
cristybb503372010-05-27 20:51:26 +00001532 MagickOffsetType
1533 progress;
1534
cristy3ed852e2009-09-05 21:47:34 +00001535 MagickPixelPacket
1536 zero;
1537
cristybb503372010-05-27 20:51:26 +00001538 ssize_t
1539 y;
1540
cristy3ed852e2009-09-05 21:47:34 +00001541 /*
1542 Initialize enhanced image attributes.
1543 */
1544 assert(image != (const Image *) NULL);
1545 assert(image->signature == MagickSignature);
1546 if (image->debug != MagickFalse)
1547 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1548 assert(exception != (ExceptionInfo *) NULL);
1549 assert(exception->signature == MagickSignature);
1550 if ((image->columns < 5) || (image->rows < 5))
1551 return((Image *) NULL);
1552 enhance_image=CloneImage(image,image->columns,image->rows,MagickTrue,
1553 exception);
1554 if (enhance_image == (Image *) NULL)
1555 return((Image *) NULL);
1556 if (SetImageStorageClass(enhance_image,DirectClass) == MagickFalse)
1557 {
1558 InheritException(exception,&enhance_image->exception);
1559 enhance_image=DestroyImage(enhance_image);
1560 return((Image *) NULL);
1561 }
1562 /*
1563 Enhance image.
1564 */
1565 status=MagickTrue;
1566 progress=0;
1567 (void) ResetMagickMemory(&zero,0,sizeof(zero));
1568 image_view=AcquireCacheView(image);
1569 enhance_view=AcquireCacheView(enhance_image);
cristyb5d5f722009-11-04 03:03:49 +00001570#if defined(MAGICKCORE_OPENMP_SUPPORT)
1571 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001572#endif
cristybb503372010-05-27 20:51:26 +00001573 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001574 {
1575 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001576 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00001577
cristy3ed852e2009-09-05 21:47:34 +00001578 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001579 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001580
cristy8d4629b2010-08-30 17:59:46 +00001581 register ssize_t
1582 x;
1583
cristy3ed852e2009-09-05 21:47:34 +00001584 /*
1585 Read another scan line.
1586 */
1587 if (status == MagickFalse)
1588 continue;
1589 p=GetCacheViewVirtualPixels(image_view,-2,y-2,image->columns+4,5,exception);
1590 q=QueueCacheViewAuthenticPixels(enhance_view,0,y,enhance_image->columns,1,
1591 exception);
1592 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
1593 {
1594 status=MagickFalse;
1595 continue;
1596 }
cristybb503372010-05-27 20:51:26 +00001597 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001598 {
1599 MagickPixelPacket
1600 aggregate;
1601
1602 MagickRealType
1603 distance,
1604 distance_squared,
1605 mean,
1606 total_weight;
1607
1608 PixelPacket
1609 pixel;
1610
1611 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001612 *restrict r;
cristy3ed852e2009-09-05 21:47:34 +00001613
1614 /*
1615 Compute weighted average of target pixel color components.
1616 */
1617 aggregate=zero;
1618 total_weight=0.0;
1619 r=p+2*(image->columns+4)+2;
1620 pixel=(*r);
1621 r=p;
1622 Enhance(5.0); Enhance(8.0); Enhance(10.0); Enhance(8.0); Enhance(5.0);
1623 r=p+(image->columns+4);
1624 Enhance(8.0); Enhance(20.0); Enhance(40.0); Enhance(20.0); Enhance(8.0);
1625 r=p+2*(image->columns+4);
1626 Enhance(10.0); Enhance(40.0); Enhance(80.0); Enhance(40.0); Enhance(10.0);
1627 r=p+3*(image->columns+4);
1628 Enhance(8.0); Enhance(20.0); Enhance(40.0); Enhance(20.0); Enhance(8.0);
1629 r=p+4*(image->columns+4);
1630 Enhance(5.0); Enhance(8.0); Enhance(10.0); Enhance(8.0); Enhance(5.0);
1631 q->red=(Quantum) ((aggregate.red+(total_weight/2)-1)/total_weight);
1632 q->green=(Quantum) ((aggregate.green+(total_weight/2)-1)/total_weight);
1633 q->blue=(Quantum) ((aggregate.blue+(total_weight/2)-1)/total_weight);
1634 q->opacity=(Quantum) ((aggregate.opacity+(total_weight/2)-1)/
1635 total_weight);
1636 p++;
1637 q++;
1638 }
1639 if (SyncCacheViewAuthenticPixels(enhance_view,exception) == MagickFalse)
1640 status=MagickFalse;
1641 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1642 {
1643 MagickBooleanType
1644 proceed;
1645
cristyb5d5f722009-11-04 03:03:49 +00001646#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00001647 #pragma omp critical (MagickCore_EnhanceImage)
1648#endif
1649 proceed=SetImageProgress(image,EnhanceImageTag,progress++,image->rows);
1650 if (proceed == MagickFalse)
1651 status=MagickFalse;
1652 }
1653 }
1654 enhance_view=DestroyCacheView(enhance_view);
1655 image_view=DestroyCacheView(image_view);
1656 return(enhance_image);
1657}
1658
1659/*
1660%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1661% %
1662% %
1663% %
1664% E q u a l i z e I m a g e %
1665% %
1666% %
1667% %
1668%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1669%
1670% EqualizeImage() applies a histogram equalization to the image.
1671%
1672% The format of the EqualizeImage method is:
1673%
1674% MagickBooleanType EqualizeImage(Image *image)
1675% MagickBooleanType EqualizeImageChannel(Image *image,
1676% const ChannelType channel)
1677%
1678% A description of each parameter follows:
1679%
1680% o image: the image.
1681%
1682% o channel: the channel.
1683%
1684*/
1685
1686MagickExport MagickBooleanType EqualizeImage(Image *image)
1687{
1688 return(EqualizeImageChannel(image,DefaultChannels));
1689}
1690
1691MagickExport MagickBooleanType EqualizeImageChannel(Image *image,
1692 const ChannelType channel)
1693{
1694#define EqualizeImageTag "Equalize/Image"
1695
cristyc4c8d132010-01-07 01:58:38 +00001696 CacheView
1697 *image_view;
1698
cristy3ed852e2009-09-05 21:47:34 +00001699 ExceptionInfo
1700 *exception;
1701
cristy3ed852e2009-09-05 21:47:34 +00001702 MagickBooleanType
1703 status;
1704
cristybb503372010-05-27 20:51:26 +00001705 MagickOffsetType
1706 progress;
1707
cristy3ed852e2009-09-05 21:47:34 +00001708 MagickPixelPacket
1709 black,
1710 *equalize_map,
1711 *histogram,
1712 intensity,
1713 *map,
1714 white;
1715
cristybb503372010-05-27 20:51:26 +00001716 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001717 i;
1718
cristybb503372010-05-27 20:51:26 +00001719 ssize_t
1720 y;
1721
cristy3ed852e2009-09-05 21:47:34 +00001722 /*
1723 Allocate and initialize histogram arrays.
1724 */
1725 assert(image != (Image *) NULL);
1726 assert(image->signature == MagickSignature);
1727 if (image->debug != MagickFalse)
1728 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1729 equalize_map=(MagickPixelPacket *) AcquireQuantumMemory(MaxMap+1UL,
1730 sizeof(*equalize_map));
1731 histogram=(MagickPixelPacket *) AcquireQuantumMemory(MaxMap+1UL,
1732 sizeof(*histogram));
1733 map=(MagickPixelPacket *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*map));
1734 if ((equalize_map == (MagickPixelPacket *) NULL) ||
1735 (histogram == (MagickPixelPacket *) NULL) ||
1736 (map == (MagickPixelPacket *) NULL))
1737 {
1738 if (map != (MagickPixelPacket *) NULL)
1739 map=(MagickPixelPacket *) RelinquishMagickMemory(map);
1740 if (histogram != (MagickPixelPacket *) NULL)
1741 histogram=(MagickPixelPacket *) RelinquishMagickMemory(histogram);
1742 if (equalize_map != (MagickPixelPacket *) NULL)
1743 equalize_map=(MagickPixelPacket *) RelinquishMagickMemory(equalize_map);
1744 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
1745 image->filename);
1746 }
1747 /*
1748 Form histogram.
1749 */
1750 (void) ResetMagickMemory(histogram,0,(MaxMap+1)*sizeof(*histogram));
1751 exception=(&image->exception);
cristybb503372010-05-27 20:51:26 +00001752 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001753 {
1754 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00001755 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00001756
1757 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001758 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00001759
cristybb503372010-05-27 20:51:26 +00001760 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001761 x;
1762
1763 p=GetVirtualPixels(image,0,y,image->columns,1,exception);
1764 if (p == (const PixelPacket *) NULL)
1765 break;
1766 indexes=GetVirtualIndexQueue(image);
cristybb503372010-05-27 20:51:26 +00001767 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001768 {
1769 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001770 histogram[ScaleQuantumToMap(GetRedPixelComponent(p))].red++;
cristy3ed852e2009-09-05 21:47:34 +00001771 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001772 histogram[ScaleQuantumToMap(GetGreenPixelComponent(p))].green++;
cristy3ed852e2009-09-05 21:47:34 +00001773 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001774 histogram[ScaleQuantumToMap(GetBluePixelComponent(p))].blue++;
cristy3ed852e2009-09-05 21:47:34 +00001775 if ((channel & OpacityChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001776 histogram[ScaleQuantumToMap(GetOpacityPixelComponent(p))].opacity++;
cristy3ed852e2009-09-05 21:47:34 +00001777 if (((channel & IndexChannel) != 0) &&
1778 (image->colorspace == CMYKColorspace))
1779 histogram[ScaleQuantumToMap(indexes[x])].index++;
1780 p++;
1781 }
1782 }
1783 /*
1784 Integrate the histogram to get the equalization map.
1785 */
1786 (void) ResetMagickMemory(&intensity,0,sizeof(intensity));
cristybb503372010-05-27 20:51:26 +00001787 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +00001788 {
1789 if ((channel & RedChannel) != 0)
1790 intensity.red+=histogram[i].red;
1791 if ((channel & GreenChannel) != 0)
1792 intensity.green+=histogram[i].green;
1793 if ((channel & BlueChannel) != 0)
1794 intensity.blue+=histogram[i].blue;
1795 if ((channel & OpacityChannel) != 0)
1796 intensity.opacity+=histogram[i].opacity;
1797 if (((channel & IndexChannel) != 0) &&
1798 (image->colorspace == CMYKColorspace))
1799 intensity.index+=histogram[i].index;
1800 map[i]=intensity;
1801 }
1802 black=map[0];
1803 white=map[(int) MaxMap];
1804 (void) ResetMagickMemory(equalize_map,0,(MaxMap+1)*sizeof(*equalize_map));
cristyb5d5f722009-11-04 03:03:49 +00001805#if defined(MAGICKCORE_OPENMP_SUPPORT)
1806 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001807#endif
cristybb503372010-05-27 20:51:26 +00001808 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +00001809 {
1810 if (((channel & RedChannel) != 0) && (white.red != black.red))
1811 equalize_map[i].red=(MagickRealType) ScaleMapToQuantum((MagickRealType)
1812 ((MaxMap*(map[i].red-black.red))/(white.red-black.red)));
1813 if (((channel & GreenChannel) != 0) && (white.green != black.green))
1814 equalize_map[i].green=(MagickRealType) ScaleMapToQuantum((MagickRealType)
1815 ((MaxMap*(map[i].green-black.green))/(white.green-black.green)));
1816 if (((channel & BlueChannel) != 0) && (white.blue != black.blue))
1817 equalize_map[i].blue=(MagickRealType) ScaleMapToQuantum((MagickRealType)
1818 ((MaxMap*(map[i].blue-black.blue))/(white.blue-black.blue)));
1819 if (((channel & OpacityChannel) != 0) && (white.opacity != black.opacity))
1820 equalize_map[i].opacity=(MagickRealType) ScaleMapToQuantum(
1821 (MagickRealType) ((MaxMap*(map[i].opacity-black.opacity))/
1822 (white.opacity-black.opacity)));
1823 if ((((channel & IndexChannel) != 0) &&
1824 (image->colorspace == CMYKColorspace)) &&
1825 (white.index != black.index))
1826 equalize_map[i].index=(MagickRealType) ScaleMapToQuantum((MagickRealType)
1827 ((MaxMap*(map[i].index-black.index))/(white.index-black.index)));
1828 }
1829 histogram=(MagickPixelPacket *) RelinquishMagickMemory(histogram);
1830 map=(MagickPixelPacket *) RelinquishMagickMemory(map);
1831 if (image->storage_class == PseudoClass)
1832 {
1833 /*
1834 Equalize colormap.
1835 */
cristyb5d5f722009-11-04 03:03:49 +00001836#if defined(MAGICKCORE_OPENMP_SUPPORT)
1837 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001838#endif
cristybb503372010-05-27 20:51:26 +00001839 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00001840 {
1841 if (((channel & RedChannel) != 0) && (white.red != black.red))
cristyce70c172010-01-07 17:15:30 +00001842 image->colormap[i].red=ClampToQuantum(equalize_map[
cristy3ed852e2009-09-05 21:47:34 +00001843 ScaleQuantumToMap(image->colormap[i].red)].red);
1844 if (((channel & GreenChannel) != 0) && (white.green != black.green))
cristyce70c172010-01-07 17:15:30 +00001845 image->colormap[i].green=ClampToQuantum(equalize_map[
cristy3ed852e2009-09-05 21:47:34 +00001846 ScaleQuantumToMap(image->colormap[i].green)].green);
1847 if (((channel & BlueChannel) != 0) && (white.blue != black.blue))
cristyce70c172010-01-07 17:15:30 +00001848 image->colormap[i].blue=ClampToQuantum(equalize_map[
cristy3ed852e2009-09-05 21:47:34 +00001849 ScaleQuantumToMap(image->colormap[i].blue)].blue);
1850 if (((channel & OpacityChannel) != 0) &&
1851 (white.opacity != black.opacity))
cristyce70c172010-01-07 17:15:30 +00001852 image->colormap[i].opacity=ClampToQuantum(equalize_map[
cristy3ed852e2009-09-05 21:47:34 +00001853 ScaleQuantumToMap(image->colormap[i].opacity)].opacity);
1854 }
1855 }
1856 /*
1857 Equalize image.
1858 */
1859 status=MagickTrue;
1860 progress=0;
1861 exception=(&image->exception);
1862 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00001863#if defined(MAGICKCORE_OPENMP_SUPPORT)
1864 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001865#endif
cristybb503372010-05-27 20:51:26 +00001866 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001867 {
1868 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00001869 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00001870
cristy3ed852e2009-09-05 21:47:34 +00001871 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001872 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001873
cristy8d4629b2010-08-30 17:59:46 +00001874 register ssize_t
1875 x;
1876
cristy3ed852e2009-09-05 21:47:34 +00001877 if (status == MagickFalse)
1878 continue;
1879 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
1880 if (q == (PixelPacket *) NULL)
1881 {
1882 status=MagickFalse;
1883 continue;
1884 }
1885 indexes=GetCacheViewAuthenticIndexQueue(image_view);
cristybb503372010-05-27 20:51:26 +00001886 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001887 {
1888 if (((channel & RedChannel) != 0) && (white.red != black.red))
cristyce70c172010-01-07 17:15:30 +00001889 q->red=ClampToQuantum(equalize_map[ScaleQuantumToMap(q->red)].red);
cristy3ed852e2009-09-05 21:47:34 +00001890 if (((channel & GreenChannel) != 0) && (white.green != black.green))
cristyce70c172010-01-07 17:15:30 +00001891 q->green=ClampToQuantum(equalize_map[ScaleQuantumToMap(
cristy3ed852e2009-09-05 21:47:34 +00001892 q->green)].green);
1893 if (((channel & BlueChannel) != 0) && (white.blue != black.blue))
cristyce70c172010-01-07 17:15:30 +00001894 q->blue=ClampToQuantum(equalize_map[ScaleQuantumToMap(q->blue)].blue);
cristy3ed852e2009-09-05 21:47:34 +00001895 if (((channel & OpacityChannel) != 0) && (white.opacity != black.opacity))
cristyce70c172010-01-07 17:15:30 +00001896 q->opacity=ClampToQuantum(equalize_map[ScaleQuantumToMap(
cristy3ed852e2009-09-05 21:47:34 +00001897 q->opacity)].opacity);
1898 if ((((channel & IndexChannel) != 0) &&
1899 (image->colorspace == CMYKColorspace)) &&
1900 (white.index != black.index))
cristyce70c172010-01-07 17:15:30 +00001901 indexes[x]=ClampToQuantum(equalize_map[ScaleQuantumToMap(
cristy3ed852e2009-09-05 21:47:34 +00001902 indexes[x])].index);
1903 q++;
1904 }
1905 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
1906 status=MagickFalse;
1907 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1908 {
1909 MagickBooleanType
1910 proceed;
1911
cristyb5d5f722009-11-04 03:03:49 +00001912#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00001913 #pragma omp critical (MagickCore_EqualizeImageChannel)
1914#endif
1915 proceed=SetImageProgress(image,EqualizeImageTag,progress++,image->rows);
1916 if (proceed == MagickFalse)
1917 status=MagickFalse;
1918 }
1919 }
1920 image_view=DestroyCacheView(image_view);
1921 equalize_map=(MagickPixelPacket *) RelinquishMagickMemory(equalize_map);
1922 return(status);
1923}
1924
1925/*
1926%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1927% %
1928% %
1929% %
1930% G a m m a I m a g e %
1931% %
1932% %
1933% %
1934%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1935%
1936% GammaImage() gamma-corrects a particular image channel. The same
1937% image viewed on different devices will have perceptual differences in the
1938% way the image's intensities are represented on the screen. Specify
1939% individual gamma levels for the red, green, and blue channels, or adjust
1940% all three with the gamma parameter. Values typically range from 0.8 to 2.3.
1941%
1942% You can also reduce the influence of a particular channel with a gamma
1943% value of 0.
1944%
1945% The format of the GammaImage method is:
1946%
cristya6360142011-03-23 23:08:04 +00001947% MagickBooleanType GammaImage(Image *image,const char *level)
cristy3ed852e2009-09-05 21:47:34 +00001948% MagickBooleanType GammaImageChannel(Image *image,
1949% const ChannelType channel,const double gamma)
1950%
1951% A description of each parameter follows:
1952%
1953% o image: the image.
1954%
1955% o channel: the channel.
1956%
cristya6360142011-03-23 23:08:04 +00001957% o level: the image gamma as a string (e.g. 1.6,1.2,1.0).
1958%
cristy3ed852e2009-09-05 21:47:34 +00001959% o gamma: the image gamma.
1960%
1961*/
1962MagickExport MagickBooleanType GammaImage(Image *image,const char *level)
1963{
1964 GeometryInfo
1965 geometry_info;
1966
1967 MagickPixelPacket
1968 gamma;
1969
1970 MagickStatusType
1971 flags,
1972 status;
1973
1974 assert(image != (Image *) NULL);
1975 assert(image->signature == MagickSignature);
1976 if (image->debug != MagickFalse)
1977 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1978 if (level == (char *) NULL)
1979 return(MagickFalse);
1980 flags=ParseGeometry(level,&geometry_info);
1981 gamma.red=geometry_info.rho;
1982 gamma.green=geometry_info.sigma;
1983 if ((flags & SigmaValue) == 0)
1984 gamma.green=gamma.red;
1985 gamma.blue=geometry_info.xi;
1986 if ((flags & XiValue) == 0)
1987 gamma.blue=gamma.red;
1988 if ((gamma.red == 1.0) && (gamma.green == 1.0) && (gamma.blue == 1.0))
1989 return(MagickTrue);
1990 if ((gamma.red == gamma.green) && (gamma.green == gamma.blue))
1991 status=GammaImageChannel(image,(const ChannelType) (RedChannel |
1992 GreenChannel | BlueChannel),(double) gamma.red);
1993 else
1994 {
1995 status=GammaImageChannel(image,RedChannel,(double) gamma.red);
1996 status|=GammaImageChannel(image,GreenChannel,(double) gamma.green);
1997 status|=GammaImageChannel(image,BlueChannel,(double) gamma.blue);
1998 }
1999 return(status != 0 ? MagickTrue : MagickFalse);
2000}
2001
2002MagickExport MagickBooleanType GammaImageChannel(Image *image,
2003 const ChannelType channel,const double gamma)
2004{
2005#define GammaCorrectImageTag "GammaCorrect/Image"
2006
cristyc4c8d132010-01-07 01:58:38 +00002007 CacheView
2008 *image_view;
2009
cristy3ed852e2009-09-05 21:47:34 +00002010 ExceptionInfo
2011 *exception;
2012
cristy3ed852e2009-09-05 21:47:34 +00002013 MagickBooleanType
2014 status;
2015
cristybb503372010-05-27 20:51:26 +00002016 MagickOffsetType
2017 progress;
2018
cristy3ed852e2009-09-05 21:47:34 +00002019 Quantum
2020 *gamma_map;
2021
cristybb503372010-05-27 20:51:26 +00002022 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002023 i;
2024
cristybb503372010-05-27 20:51:26 +00002025 ssize_t
2026 y;
2027
cristy3ed852e2009-09-05 21:47:34 +00002028 /*
2029 Allocate and initialize gamma maps.
2030 */
2031 assert(image != (Image *) NULL);
2032 assert(image->signature == MagickSignature);
2033 if (image->debug != MagickFalse)
2034 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2035 if (gamma == 1.0)
2036 return(MagickTrue);
2037 gamma_map=(Quantum *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*gamma_map));
2038 if (gamma_map == (Quantum *) NULL)
2039 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
2040 image->filename);
2041 (void) ResetMagickMemory(gamma_map,0,(MaxMap+1)*sizeof(*gamma_map));
2042 if (gamma != 0.0)
cristyb5d5f722009-11-04 03:03:49 +00002043#if defined(MAGICKCORE_OPENMP_SUPPORT)
2044 #pragma omp parallel for schedule(dynamic,4)
cristy3ed852e2009-09-05 21:47:34 +00002045#endif
cristybb503372010-05-27 20:51:26 +00002046 for (i=0; i <= (ssize_t) MaxMap; i++)
cristyce70c172010-01-07 17:15:30 +00002047 gamma_map[i]=ClampToQuantum((MagickRealType) ScaleMapToQuantum((
cristy3ed852e2009-09-05 21:47:34 +00002048 MagickRealType) (MaxMap*pow((double) i/MaxMap,1.0/gamma))));
2049 if (image->storage_class == PseudoClass)
2050 {
2051 /*
2052 Gamma-correct colormap.
2053 */
cristyb5d5f722009-11-04 03:03:49 +00002054#if defined(MAGICKCORE_OPENMP_SUPPORT)
2055 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002056#endif
cristybb503372010-05-27 20:51:26 +00002057 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00002058 {
2059 if ((channel & RedChannel) != 0)
2060 image->colormap[i].red=gamma_map[
2061 ScaleQuantumToMap(image->colormap[i].red)];
2062 if ((channel & GreenChannel) != 0)
2063 image->colormap[i].green=gamma_map[
2064 ScaleQuantumToMap(image->colormap[i].green)];
2065 if ((channel & BlueChannel) != 0)
2066 image->colormap[i].blue=gamma_map[
2067 ScaleQuantumToMap(image->colormap[i].blue)];
2068 if ((channel & OpacityChannel) != 0)
2069 {
2070 if (image->matte == MagickFalse)
2071 image->colormap[i].opacity=gamma_map[
2072 ScaleQuantumToMap(image->colormap[i].opacity)];
2073 else
2074 image->colormap[i].opacity=(Quantum) QuantumRange-
2075 gamma_map[ScaleQuantumToMap((Quantum) (QuantumRange-
2076 image->colormap[i].opacity))];
2077 }
2078 }
2079 }
2080 /*
2081 Gamma-correct image.
2082 */
2083 status=MagickTrue;
2084 progress=0;
2085 exception=(&image->exception);
2086 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00002087#if defined(MAGICKCORE_OPENMP_SUPPORT)
2088 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002089#endif
cristybb503372010-05-27 20:51:26 +00002090 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002091 {
2092 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00002093 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00002094
cristy3ed852e2009-09-05 21:47:34 +00002095 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00002096 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002097
cristy8d4629b2010-08-30 17:59:46 +00002098 register ssize_t
2099 x;
2100
cristy3ed852e2009-09-05 21:47:34 +00002101 if (status == MagickFalse)
2102 continue;
2103 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2104 if (q == (PixelPacket *) NULL)
2105 {
2106 status=MagickFalse;
2107 continue;
2108 }
2109 indexes=GetCacheViewAuthenticIndexQueue(image_view);
cristybb503372010-05-27 20:51:26 +00002110 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002111 {
cristy6cbd7f52009-10-17 16:06:51 +00002112 if (channel == DefaultChannels)
cristy3ed852e2009-09-05 21:47:34 +00002113 {
cristy6cbd7f52009-10-17 16:06:51 +00002114 q->red=gamma_map[ScaleQuantumToMap(q->red)];
2115 q->green=gamma_map[ScaleQuantumToMap(q->green)];
2116 q->blue=gamma_map[ScaleQuantumToMap(q->blue)];
2117 }
2118 else
2119 {
2120 if ((channel & RedChannel) != 0)
2121 q->red=gamma_map[ScaleQuantumToMap(q->red)];
2122 if ((channel & GreenChannel) != 0)
2123 q->green=gamma_map[ScaleQuantumToMap(q->green)];
2124 if ((channel & BlueChannel) != 0)
2125 q->blue=gamma_map[ScaleQuantumToMap(q->blue)];
2126 if ((channel & OpacityChannel) != 0)
2127 {
2128 if (image->matte == MagickFalse)
2129 q->opacity=gamma_map[ScaleQuantumToMap(q->opacity)];
2130 else
2131 q->opacity=(Quantum) QuantumRange-gamma_map[
cristy46f08202010-01-10 04:04:21 +00002132 ScaleQuantumToMap((Quantum) GetAlphaPixelComponent(q))];
cristy6cbd7f52009-10-17 16:06:51 +00002133 }
cristy3ed852e2009-09-05 21:47:34 +00002134 }
2135 q++;
2136 }
2137 if (((channel & IndexChannel) != 0) &&
2138 (image->colorspace == CMYKColorspace))
cristybb503372010-05-27 20:51:26 +00002139 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002140 indexes[x]=gamma_map[ScaleQuantumToMap(indexes[x])];
2141 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2142 status=MagickFalse;
2143 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2144 {
2145 MagickBooleanType
2146 proceed;
2147
cristyb5d5f722009-11-04 03:03:49 +00002148#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00002149 #pragma omp critical (MagickCore_GammaImageChannel)
2150#endif
2151 proceed=SetImageProgress(image,GammaCorrectImageTag,progress++,
2152 image->rows);
2153 if (proceed == MagickFalse)
2154 status=MagickFalse;
2155 }
2156 }
2157 image_view=DestroyCacheView(image_view);
2158 gamma_map=(Quantum *) RelinquishMagickMemory(gamma_map);
2159 if (image->gamma != 0.0)
2160 image->gamma*=gamma;
2161 return(status);
2162}
2163
2164/*
2165%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2166% %
2167% %
2168% %
2169% H a l d C l u t I m a g e %
2170% %
2171% %
2172% %
2173%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2174%
2175% HaldClutImage() applies a Hald color lookup table to the image. A Hald
2176% color lookup table is a 3-dimensional color cube mapped to 2 dimensions.
2177% Create it with the HALD coder. You can apply any color transformation to
2178% the Hald image and then use this method to apply the transform to the
2179% image.
2180%
2181% The format of the HaldClutImage method is:
2182%
2183% MagickBooleanType HaldClutImage(Image *image,Image *hald_image)
2184% MagickBooleanType HaldClutImageChannel(Image *image,
2185% const ChannelType channel,Image *hald_image)
2186%
2187% A description of each parameter follows:
2188%
2189% o image: the image, which is replaced by indexed CLUT values
2190%
2191% o hald_image: the color lookup table image for replacement color values.
2192%
2193% o channel: the channel.
2194%
2195*/
2196
2197static inline size_t MagickMin(const size_t x,const size_t y)
2198{
2199 if (x < y)
2200 return(x);
2201 return(y);
2202}
2203
2204MagickExport MagickBooleanType HaldClutImage(Image *image,
2205 const Image *hald_image)
2206{
2207 return(HaldClutImageChannel(image,DefaultChannels,hald_image));
2208}
2209
2210MagickExport MagickBooleanType HaldClutImageChannel(Image *image,
2211 const ChannelType channel,const Image *hald_image)
2212{
2213#define HaldClutImageTag "Clut/Image"
2214
2215 typedef struct _HaldInfo
2216 {
2217 MagickRealType
2218 x,
2219 y,
2220 z;
2221 } HaldInfo;
2222
cristyfa112112010-01-04 17:48:07 +00002223 CacheView
2224 *image_view;
2225
cristy3ed852e2009-09-05 21:47:34 +00002226 double
2227 width;
2228
2229 ExceptionInfo
2230 *exception;
2231
cristy3ed852e2009-09-05 21:47:34 +00002232 MagickBooleanType
2233 status;
2234
cristybb503372010-05-27 20:51:26 +00002235 MagickOffsetType
2236 progress;
2237
cristy3ed852e2009-09-05 21:47:34 +00002238 MagickPixelPacket
2239 zero;
2240
2241 ResampleFilter
cristyfa112112010-01-04 17:48:07 +00002242 **restrict resample_filter;
cristy3ed852e2009-09-05 21:47:34 +00002243
2244 size_t
2245 cube_size,
2246 length,
2247 level;
2248
cristybb503372010-05-27 20:51:26 +00002249 ssize_t
2250 y;
2251
cristy3ed852e2009-09-05 21:47:34 +00002252 assert(image != (Image *) NULL);
2253 assert(image->signature == MagickSignature);
2254 if (image->debug != MagickFalse)
2255 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2256 assert(hald_image != (Image *) NULL);
2257 assert(hald_image->signature == MagickSignature);
2258 if (SetImageStorageClass(image,DirectClass) == MagickFalse)
2259 return(MagickFalse);
2260 if (image->matte == MagickFalse)
2261 (void) SetImageAlphaChannel(image,OpaqueAlphaChannel);
2262 /*
2263 Hald clut image.
2264 */
2265 status=MagickTrue;
2266 progress=0;
2267 length=MagickMin(hald_image->columns,hald_image->rows);
2268 for (level=2; (level*level*level) < length; level++) ;
2269 level*=level;
2270 cube_size=level*level;
2271 width=(double) hald_image->columns;
2272 GetMagickPixelPacket(hald_image,&zero);
2273 exception=(&image->exception);
cristyb2a11ae2010-02-22 00:53:36 +00002274 resample_filter=AcquireResampleFilterThreadSet(hald_image,
2275 UndefinedVirtualPixelMethod,MagickTrue,exception);
cristy3ed852e2009-09-05 21:47:34 +00002276 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00002277#if defined(MAGICKCORE_OPENMP_SUPPORT)
2278 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002279#endif
cristybb503372010-05-27 20:51:26 +00002280 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002281 {
cristy5c9e6f22010-09-17 17:31:01 +00002282 const int
2283 id = GetOpenMPThreadId();
2284
cristy3ed852e2009-09-05 21:47:34 +00002285 double
2286 offset;
2287
2288 HaldInfo
2289 point;
2290
2291 MagickPixelPacket
2292 pixel,
2293 pixel1,
2294 pixel2,
2295 pixel3,
2296 pixel4;
2297
2298 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00002299 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00002300
cristy3ed852e2009-09-05 21:47:34 +00002301 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00002302 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002303
cristy8d4629b2010-08-30 17:59:46 +00002304 register ssize_t
2305 x;
2306
cristy3ed852e2009-09-05 21:47:34 +00002307 if (status == MagickFalse)
2308 continue;
2309 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2310 if (q == (PixelPacket *) NULL)
2311 {
2312 status=MagickFalse;
2313 continue;
2314 }
2315 indexes=GetCacheViewAuthenticIndexQueue(image_view);
2316 pixel=zero;
2317 pixel1=zero;
2318 pixel2=zero;
2319 pixel3=zero;
2320 pixel4=zero;
cristybb503372010-05-27 20:51:26 +00002321 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002322 {
2323 point.x=QuantumScale*(level-1.0)*q->red;
2324 point.y=QuantumScale*(level-1.0)*q->green;
2325 point.z=QuantumScale*(level-1.0)*q->blue;
2326 offset=point.x+level*floor(point.y)+cube_size*floor(point.z);
2327 point.x-=floor(point.x);
2328 point.y-=floor(point.y);
2329 point.z-=floor(point.z);
2330 (void) ResamplePixelColor(resample_filter[id],fmod(offset,width),
2331 floor(offset/width),&pixel1);
2332 (void) ResamplePixelColor(resample_filter[id],fmod(offset+level,width),
2333 floor((offset+level)/width),&pixel2);
2334 MagickPixelCompositeAreaBlend(&pixel1,pixel1.opacity,&pixel2,
2335 pixel2.opacity,point.y,&pixel3);
2336 offset+=cube_size;
2337 (void) ResamplePixelColor(resample_filter[id],fmod(offset,width),
2338 floor(offset/width),&pixel1);
2339 (void) ResamplePixelColor(resample_filter[id],fmod(offset+level,width),
2340 floor((offset+level)/width),&pixel2);
2341 MagickPixelCompositeAreaBlend(&pixel1,pixel1.opacity,&pixel2,
2342 pixel2.opacity,point.y,&pixel4);
2343 MagickPixelCompositeAreaBlend(&pixel3,pixel3.opacity,&pixel4,
2344 pixel4.opacity,point.z,&pixel);
2345 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00002346 SetRedPixelComponent(q,ClampRedPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00002347 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00002348 SetGreenPixelComponent(q,ClampGreenPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00002349 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00002350 SetBluePixelComponent(q,ClampBluePixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00002351 if (((channel & OpacityChannel) != 0) && (image->matte != MagickFalse))
cristyce70c172010-01-07 17:15:30 +00002352 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00002353 if (((channel & IndexChannel) != 0) &&
2354 (image->colorspace == CMYKColorspace))
cristyce70c172010-01-07 17:15:30 +00002355 indexes[x]=ClampToQuantum(pixel.index);
cristy3ed852e2009-09-05 21:47:34 +00002356 q++;
2357 }
2358 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2359 status=MagickFalse;
2360 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2361 {
2362 MagickBooleanType
2363 proceed;
2364
cristyb5d5f722009-11-04 03:03:49 +00002365#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00002366 #pragma omp critical (MagickCore_HaldClutImageChannel)
2367#endif
2368 proceed=SetImageProgress(image,HaldClutImageTag,progress++,image->rows);
2369 if (proceed == MagickFalse)
2370 status=MagickFalse;
2371 }
2372 }
2373 image_view=DestroyCacheView(image_view);
2374 resample_filter=DestroyResampleFilterThreadSet(resample_filter);
2375 return(status);
2376}
2377
2378/*
2379%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2380% %
2381% %
2382% %
2383% L e v e l I m a g e %
2384% %
2385% %
2386% %
2387%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2388%
2389% LevelImage() adjusts the levels of a particular image channel by
2390% scaling the colors falling between specified white and black points to
2391% the full available quantum range.
2392%
2393% The parameters provided represent the black, and white points. The black
2394% point specifies the darkest color in the image. Colors darker than the
2395% black point are set to zero. White point specifies the lightest color in
2396% the image. Colors brighter than the white point are set to the maximum
2397% quantum value.
2398%
2399% If a '!' flag is given, map black and white colors to the given levels
2400% rather than mapping those levels to black and white. See
2401% LevelizeImageChannel() and LevelizeImageChannel(), below.
2402%
2403% Gamma specifies a gamma correction to apply to the image.
2404%
2405% The format of the LevelImage method is:
2406%
2407% MagickBooleanType LevelImage(Image *image,const char *levels)
2408%
2409% A description of each parameter follows:
2410%
2411% o image: the image.
2412%
2413% o levels: Specify the levels where the black and white points have the
2414% range of 0-QuantumRange, and gamma has the range 0-10 (e.g. 10x90%+2).
2415% A '!' flag inverts the re-mapping.
2416%
2417*/
2418
2419MagickExport MagickBooleanType LevelImage(Image *image,const char *levels)
2420{
2421 double
2422 black_point,
2423 gamma,
2424 white_point;
2425
2426 GeometryInfo
2427 geometry_info;
2428
2429 MagickBooleanType
2430 status;
2431
2432 MagickStatusType
2433 flags;
2434
2435 /*
2436 Parse levels.
2437 */
2438 if (levels == (char *) NULL)
2439 return(MagickFalse);
2440 flags=ParseGeometry(levels,&geometry_info);
2441 black_point=geometry_info.rho;
2442 white_point=(double) QuantumRange;
2443 if ((flags & SigmaValue) != 0)
2444 white_point=geometry_info.sigma;
2445 gamma=1.0;
2446 if ((flags & XiValue) != 0)
2447 gamma=geometry_info.xi;
2448 if ((flags & PercentValue) != 0)
2449 {
2450 black_point*=(double) image->columns*image->rows/100.0;
2451 white_point*=(double) image->columns*image->rows/100.0;
2452 }
2453 if ((flags & SigmaValue) == 0)
2454 white_point=(double) QuantumRange-black_point;
2455 if ((flags & AspectValue ) == 0)
2456 status=LevelImageChannel(image,DefaultChannels,black_point,white_point,
2457 gamma);
2458 else
cristy308b4e62009-09-21 14:40:44 +00002459 status=LevelizeImage(image,black_point,white_point,gamma);
cristy3ed852e2009-09-05 21:47:34 +00002460 return(status);
2461}
2462
2463/*
2464%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2465% %
2466% %
2467% %
cristy308b4e62009-09-21 14:40:44 +00002468% L e v e l i z e I m a g e %
cristy3ed852e2009-09-05 21:47:34 +00002469% %
2470% %
2471% %
2472%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2473%
cristy308b4e62009-09-21 14:40:44 +00002474% LevelizeImage() applies the normal level operation to the image, spreading
2475% out the values between the black and white points over the entire range of
2476% values. Gamma correction is also applied after the values has been mapped.
cristy3ed852e2009-09-05 21:47:34 +00002477%
2478% It is typically used to improve image contrast, or to provide a controlled
2479% linear threshold for the image. If the black and white points are set to
2480% the minimum and maximum values found in the image, the image can be
2481% normalized. or by swapping black and white values, negate the image.
2482%
cristy308b4e62009-09-21 14:40:44 +00002483% The format of the LevelizeImage method is:
cristy3ed852e2009-09-05 21:47:34 +00002484%
cristy308b4e62009-09-21 14:40:44 +00002485% MagickBooleanType LevelizeImage(Image *image,const double black_point,
2486% const double white_point,const double gamma)
2487% MagickBooleanType LevelizeImageChannel(Image *image,
2488% const ChannelType channel,const double black_point,
2489% const double white_point,const double gamma)
cristy3ed852e2009-09-05 21:47:34 +00002490%
2491% A description of each parameter follows:
2492%
2493% o image: the image.
2494%
2495% o channel: the channel.
2496%
2497% o black_point: The level which is to be mapped to zero (black)
2498%
2499% o white_point: The level which is to be mapped to QuantiumRange (white)
2500%
2501% o gamma: adjust gamma by this factor before mapping values.
2502% use 1.0 for purely linear stretching of image color values
2503%
2504*/
2505MagickExport MagickBooleanType LevelImageChannel(Image *image,
2506 const ChannelType channel,const double black_point,const double white_point,
2507 const double gamma)
2508{
2509#define LevelImageTag "Level/Image"
cristybcfb0432010-05-06 01:45:33 +00002510#define LevelQuantum(x) (ClampToQuantum((MagickRealType) QuantumRange* \
cristyc1f508d2010-04-22 01:21:47 +00002511 pow(scale*((double) (x)-black_point),1.0/gamma)))
cristy3ed852e2009-09-05 21:47:34 +00002512
cristyc4c8d132010-01-07 01:58:38 +00002513 CacheView
2514 *image_view;
2515
cristy3ed852e2009-09-05 21:47:34 +00002516 ExceptionInfo
2517 *exception;
2518
cristy3ed852e2009-09-05 21:47:34 +00002519 MagickBooleanType
2520 status;
2521
cristybb503372010-05-27 20:51:26 +00002522 MagickOffsetType
2523 progress;
2524
anthony7fe39fc2010-04-06 03:19:20 +00002525 register double
2526 scale;
2527
cristy8d4629b2010-08-30 17:59:46 +00002528 register ssize_t
2529 i;
2530
cristybb503372010-05-27 20:51:26 +00002531 ssize_t
2532 y;
2533
cristy3ed852e2009-09-05 21:47:34 +00002534 /*
2535 Allocate and initialize levels map.
2536 */
2537 assert(image != (Image *) NULL);
2538 assert(image->signature == MagickSignature);
2539 if (image->debug != MagickFalse)
2540 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
cristy8d4629b2010-08-30 17:59:46 +00002541 scale=(white_point != black_point) ? 1.0/(white_point-black_point) : 1.0;
cristy3ed852e2009-09-05 21:47:34 +00002542 if (image->storage_class == PseudoClass)
cristyb5d5f722009-11-04 03:03:49 +00002543#if defined(MAGICKCORE_OPENMP_SUPPORT)
2544 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002545#endif
cristybb503372010-05-27 20:51:26 +00002546 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00002547 {
2548 /*
2549 Level colormap.
2550 */
2551 if ((channel & RedChannel) != 0)
cristyc1f508d2010-04-22 01:21:47 +00002552 image->colormap[i].red=LevelQuantum(image->colormap[i].red);
cristy3ed852e2009-09-05 21:47:34 +00002553 if ((channel & GreenChannel) != 0)
cristyc1f508d2010-04-22 01:21:47 +00002554 image->colormap[i].green=LevelQuantum(image->colormap[i].green);
cristy3ed852e2009-09-05 21:47:34 +00002555 if ((channel & BlueChannel) != 0)
cristyc1f508d2010-04-22 01:21:47 +00002556 image->colormap[i].blue=LevelQuantum(image->colormap[i].blue);
cristy3ed852e2009-09-05 21:47:34 +00002557 if ((channel & OpacityChannel) != 0)
cristyc1f508d2010-04-22 01:21:47 +00002558 image->colormap[i].opacity=LevelQuantum(image->colormap[i].opacity);
cristy3ed852e2009-09-05 21:47:34 +00002559 }
2560 /*
2561 Level image.
2562 */
2563 status=MagickTrue;
2564 progress=0;
2565 exception=(&image->exception);
2566 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00002567#if defined(MAGICKCORE_OPENMP_SUPPORT)
2568 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002569#endif
cristybb503372010-05-27 20:51:26 +00002570 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002571 {
2572 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00002573 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00002574
cristy3ed852e2009-09-05 21:47:34 +00002575 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00002576 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002577
cristy8d4629b2010-08-30 17:59:46 +00002578 register ssize_t
2579 x;
2580
cristy3ed852e2009-09-05 21:47:34 +00002581 if (status == MagickFalse)
2582 continue;
2583 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2584 if (q == (PixelPacket *) NULL)
2585 {
2586 status=MagickFalse;
2587 continue;
2588 }
2589 indexes=GetCacheViewAuthenticIndexQueue(image_view);
cristybb503372010-05-27 20:51:26 +00002590 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002591 {
2592 if ((channel & RedChannel) != 0)
cristyc1f508d2010-04-22 01:21:47 +00002593 q->red=LevelQuantum(q->red);
cristy3ed852e2009-09-05 21:47:34 +00002594 if ((channel & GreenChannel) != 0)
cristyc1f508d2010-04-22 01:21:47 +00002595 q->green=LevelQuantum(q->green);
cristy3ed852e2009-09-05 21:47:34 +00002596 if ((channel & BlueChannel) != 0)
cristyc1f508d2010-04-22 01:21:47 +00002597 q->blue=LevelQuantum(q->blue);
cristy3ed852e2009-09-05 21:47:34 +00002598 if (((channel & OpacityChannel) != 0) &&
2599 (image->matte == MagickTrue))
cristybb503372010-05-27 20:51:26 +00002600 q->opacity=(Quantum) (QuantumRange-LevelQuantum(QuantumRange-
2601 q->opacity));
cristy3ed852e2009-09-05 21:47:34 +00002602 if (((channel & IndexChannel) != 0) &&
2603 (image->colorspace == CMYKColorspace))
cristyc1f508d2010-04-22 01:21:47 +00002604 indexes[x]=LevelQuantum(indexes[x]);
cristy3ed852e2009-09-05 21:47:34 +00002605 q++;
2606 }
2607 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2608 status=MagickFalse;
2609 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2610 {
2611 MagickBooleanType
2612 proceed;
2613
cristyb5d5f722009-11-04 03:03:49 +00002614#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00002615 #pragma omp critical (MagickCore_LevelImageChannel)
2616#endif
2617 proceed=SetImageProgress(image,LevelImageTag,progress++,image->rows);
2618 if (proceed == MagickFalse)
2619 status=MagickFalse;
2620 }
2621 }
2622 image_view=DestroyCacheView(image_view);
2623 return(status);
2624}
2625
2626/*
2627%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2628% %
2629% %
2630% %
2631% L e v e l i z e I m a g e C h a n n e l %
2632% %
2633% %
2634% %
2635%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2636%
2637% LevelizeImageChannel() applies the reversed LevelImage() operation to just
2638% the specific channels specified. It compresses the full range of color
2639% values, so that they lie between the given black and white points. Gamma is
2640% applied before the values are mapped.
2641%
2642% LevelizeImageChannel() can be called with by using a +level command line
2643% API option, or using a '!' on a -level or LevelImage() geometry string.
2644%
2645% It can be used for example de-contrast a greyscale image to the exact
2646% levels specified. Or by using specific levels for each channel of an image
2647% you can convert a gray-scale image to any linear color gradient, according
2648% to those levels.
2649%
2650% The format of the LevelizeImageChannel method is:
2651%
2652% MagickBooleanType LevelizeImageChannel(Image *image,
2653% const ChannelType channel,const char *levels)
2654%
2655% A description of each parameter follows:
2656%
2657% o image: the image.
2658%
2659% o channel: the channel.
2660%
2661% o black_point: The level to map zero (black) to.
2662%
2663% o white_point: The level to map QuantiumRange (white) to.
2664%
2665% o gamma: adjust gamma by this factor before mapping values.
2666%
2667*/
cristyd1a2c0f2011-02-09 14:14:50 +00002668
2669MagickExport MagickBooleanType LevelizeImage(Image *image,
2670 const double black_point,const double white_point,const double gamma)
2671{
2672 MagickBooleanType
2673 status;
2674
2675 status=LevelizeImageChannel(image,DefaultChannels,black_point,white_point,
2676 gamma);
2677 return(status);
2678}
2679
cristy3ed852e2009-09-05 21:47:34 +00002680MagickExport MagickBooleanType LevelizeImageChannel(Image *image,
2681 const ChannelType channel,const double black_point,const double white_point,
2682 const double gamma)
2683{
2684#define LevelizeImageTag "Levelize/Image"
cristyce70c172010-01-07 17:15:30 +00002685#define LevelizeValue(x) (ClampToQuantum(((MagickRealType) \
cristy3ed852e2009-09-05 21:47:34 +00002686 pow((double)(QuantumScale*(x)),1.0/gamma))*(white_point-black_point)+ \
2687 black_point))
2688
cristyc4c8d132010-01-07 01:58:38 +00002689 CacheView
2690 *image_view;
2691
cristy3ed852e2009-09-05 21:47:34 +00002692 ExceptionInfo
2693 *exception;
2694
cristy3ed852e2009-09-05 21:47:34 +00002695 MagickBooleanType
2696 status;
2697
cristybb503372010-05-27 20:51:26 +00002698 MagickOffsetType
2699 progress;
2700
2701 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002702 i;
2703
cristybb503372010-05-27 20:51:26 +00002704 ssize_t
2705 y;
2706
cristy3ed852e2009-09-05 21:47:34 +00002707 /*
2708 Allocate and initialize levels map.
2709 */
2710 assert(image != (Image *) NULL);
2711 assert(image->signature == MagickSignature);
2712 if (image->debug != MagickFalse)
2713 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2714 if (image->storage_class == PseudoClass)
cristyb5d5f722009-11-04 03:03:49 +00002715#if defined(MAGICKCORE_OPENMP_SUPPORT)
2716 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002717#endif
cristybb503372010-05-27 20:51:26 +00002718 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00002719 {
2720 /*
2721 Level colormap.
2722 */
2723 if ((channel & RedChannel) != 0)
2724 image->colormap[i].red=LevelizeValue(image->colormap[i].red);
2725 if ((channel & GreenChannel) != 0)
2726 image->colormap[i].green=LevelizeValue(image->colormap[i].green);
2727 if ((channel & BlueChannel) != 0)
2728 image->colormap[i].blue=LevelizeValue(image->colormap[i].blue);
2729 if ((channel & OpacityChannel) != 0)
2730 image->colormap[i].opacity=LevelizeValue(image->colormap[i].opacity);
2731 }
2732 /*
2733 Level image.
2734 */
2735 status=MagickTrue;
2736 progress=0;
2737 exception=(&image->exception);
2738 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00002739#if defined(MAGICKCORE_OPENMP_SUPPORT)
2740 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002741#endif
cristybb503372010-05-27 20:51:26 +00002742 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002743 {
2744 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00002745 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00002746
cristy3ed852e2009-09-05 21:47:34 +00002747 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00002748 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002749
cristy8d4629b2010-08-30 17:59:46 +00002750 register ssize_t
2751 x;
2752
cristy3ed852e2009-09-05 21:47:34 +00002753 if (status == MagickFalse)
2754 continue;
2755 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2756 if (q == (PixelPacket *) NULL)
2757 {
2758 status=MagickFalse;
2759 continue;
2760 }
2761 indexes=GetCacheViewAuthenticIndexQueue(image_view);
cristybb503372010-05-27 20:51:26 +00002762 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002763 {
2764 if ((channel & RedChannel) != 0)
2765 q->red=LevelizeValue(q->red);
2766 if ((channel & GreenChannel) != 0)
2767 q->green=LevelizeValue(q->green);
2768 if ((channel & BlueChannel) != 0)
2769 q->blue=LevelizeValue(q->blue);
2770 if (((channel & OpacityChannel) != 0) &&
2771 (image->matte == MagickTrue))
2772 q->opacity=LevelizeValue(q->opacity);
2773 if (((channel & IndexChannel) != 0) &&
2774 (image->colorspace == CMYKColorspace))
2775 indexes[x]=LevelizeValue(indexes[x]);
2776 q++;
2777 }
2778 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2779 status=MagickFalse;
2780 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2781 {
2782 MagickBooleanType
2783 proceed;
2784
cristyb5d5f722009-11-04 03:03:49 +00002785#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00002786 #pragma omp critical (MagickCore_LevelizeImageChannel)
2787#endif
2788 proceed=SetImageProgress(image,LevelizeImageTag,progress++,image->rows);
2789 if (proceed == MagickFalse)
2790 status=MagickFalse;
2791 }
2792 }
cristy8d4629b2010-08-30 17:59:46 +00002793 image_view=DestroyCacheView(image_view);
cristy3ed852e2009-09-05 21:47:34 +00002794 return(status);
2795}
2796
2797/*
2798%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2799% %
2800% %
2801% %
2802% L e v e l I m a g e C o l o r s %
2803% %
2804% %
2805% %
2806%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2807%
cristyee0f8d72009-09-19 00:58:29 +00002808% LevelImageColor() maps the given color to "black" and "white" values,
2809% linearly spreading out the colors, and level values on a channel by channel
2810% bases, as per LevelImage(). The given colors allows you to specify
glennrp1e7f7bc2011-03-02 19:25:28 +00002811% different level ranges for each of the color channels separately.
cristy3ed852e2009-09-05 21:47:34 +00002812%
2813% If the boolean 'invert' is set true the image values will modifyed in the
2814% reverse direction. That is any existing "black" and "white" colors in the
2815% image will become the color values given, with all other values compressed
2816% appropriatally. This effectivally maps a greyscale gradient into the given
2817% color gradient.
2818%
cristy308b4e62009-09-21 14:40:44 +00002819% The format of the LevelColorsImageChannel method is:
cristy3ed852e2009-09-05 21:47:34 +00002820%
cristy308b4e62009-09-21 14:40:44 +00002821% MagickBooleanType LevelColorsImage(Image *image,
cristyee0f8d72009-09-19 00:58:29 +00002822% const MagickPixelPacket *black_color,
2823% const MagickPixelPacket *white_color,const MagickBooleanType invert)
cristy308b4e62009-09-21 14:40:44 +00002824% MagickBooleanType LevelColorsImageChannel(Image *image,
2825% const ChannelType channel,const MagickPixelPacket *black_color,
2826% const MagickPixelPacket *white_color,const MagickBooleanType invert)
cristy3ed852e2009-09-05 21:47:34 +00002827%
2828% A description of each parameter follows:
2829%
2830% o image: the image.
2831%
2832% o channel: the channel.
2833%
2834% o black_color: The color to map black to/from
2835%
2836% o white_point: The color to map white to/from
2837%
2838% o invert: if true map the colors (levelize), rather than from (level)
2839%
2840*/
cristy308b4e62009-09-21 14:40:44 +00002841
2842MagickExport MagickBooleanType LevelColorsImage(Image *image,
cristy3ed852e2009-09-05 21:47:34 +00002843 const MagickPixelPacket *black_color,const MagickPixelPacket *white_color,
2844 const MagickBooleanType invert)
2845{
cristy308b4e62009-09-21 14:40:44 +00002846 MagickBooleanType
2847 status;
cristy3ed852e2009-09-05 21:47:34 +00002848
cristy308b4e62009-09-21 14:40:44 +00002849 status=LevelColorsImageChannel(image,DefaultChannels,black_color,white_color,
2850 invert);
2851 return(status);
2852}
2853
2854MagickExport MagickBooleanType LevelColorsImageChannel(Image *image,
2855 const ChannelType channel,const MagickPixelPacket *black_color,
2856 const MagickPixelPacket *white_color,const MagickBooleanType invert)
2857{
cristy3ed852e2009-09-05 21:47:34 +00002858 MagickStatusType
2859 status;
2860
2861 /*
2862 Allocate and initialize levels map.
2863 */
2864 assert(image != (Image *) NULL);
2865 assert(image->signature == MagickSignature);
2866 if (image->debug != MagickFalse)
2867 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2868 status=MagickFalse;
2869 if (invert == MagickFalse)
2870 {
2871 if ((channel & RedChannel) != 0)
2872 status|=LevelImageChannel(image,RedChannel,
2873 black_color->red,white_color->red,(double) 1.0);
2874 if ((channel & GreenChannel) != 0)
2875 status|=LevelImageChannel(image,GreenChannel,
2876 black_color->green,white_color->green,(double) 1.0);
2877 if ((channel & BlueChannel) != 0)
2878 status|=LevelImageChannel(image,BlueChannel,
2879 black_color->blue,white_color->blue,(double) 1.0);
2880 if (((channel & OpacityChannel) != 0) &&
2881 (image->matte == MagickTrue))
2882 status|=LevelImageChannel(image,OpacityChannel,
2883 black_color->opacity,white_color->opacity,(double) 1.0);
2884 if (((channel & IndexChannel) != 0) &&
2885 (image->colorspace == CMYKColorspace))
2886 status|=LevelImageChannel(image,IndexChannel,
2887 black_color->index,white_color->index,(double) 1.0);
2888 }
2889 else
2890 {
2891 if ((channel & RedChannel) != 0)
2892 status|=LevelizeImageChannel(image,RedChannel,
2893 black_color->red,white_color->red,(double) 1.0);
2894 if ((channel & GreenChannel) != 0)
2895 status|=LevelizeImageChannel(image,GreenChannel,
2896 black_color->green,white_color->green,(double) 1.0);
2897 if ((channel & BlueChannel) != 0)
2898 status|=LevelizeImageChannel(image,BlueChannel,
2899 black_color->blue,white_color->blue,(double) 1.0);
2900 if (((channel & OpacityChannel) != 0) &&
2901 (image->matte == MagickTrue))
2902 status|=LevelizeImageChannel(image,OpacityChannel,
2903 black_color->opacity,white_color->opacity,(double) 1.0);
2904 if (((channel & IndexChannel) != 0) &&
2905 (image->colorspace == CMYKColorspace))
2906 status|=LevelizeImageChannel(image,IndexChannel,
2907 black_color->index,white_color->index,(double) 1.0);
2908 }
2909 return(status == 0 ? MagickFalse : MagickTrue);
2910}
2911
2912/*
2913%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2914% %
2915% %
2916% %
2917% L i n e a r S t r e t c h I m a g e %
2918% %
2919% %
2920% %
2921%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2922%
2923% The LinearStretchImage() discards any pixels below the black point and
2924% above the white point and levels the remaining pixels.
2925%
2926% The format of the LinearStretchImage method is:
2927%
2928% MagickBooleanType LinearStretchImage(Image *image,
2929% const double black_point,const double white_point)
2930%
2931% A description of each parameter follows:
2932%
2933% o image: the image.
2934%
2935% o black_point: the black point.
2936%
2937% o white_point: the white point.
2938%
2939*/
2940MagickExport MagickBooleanType LinearStretchImage(Image *image,
2941 const double black_point,const double white_point)
2942{
2943#define LinearStretchImageTag "LinearStretch/Image"
2944
2945 ExceptionInfo
2946 *exception;
2947
cristy3ed852e2009-09-05 21:47:34 +00002948 MagickBooleanType
2949 status;
2950
2951 MagickRealType
2952 *histogram,
2953 intensity;
2954
cristy8d4629b2010-08-30 17:59:46 +00002955 ssize_t
2956 black,
2957 white,
2958 y;
2959
cristy3ed852e2009-09-05 21:47:34 +00002960 /*
2961 Allocate histogram and linear map.
2962 */
2963 assert(image != (Image *) NULL);
2964 assert(image->signature == MagickSignature);
2965 histogram=(MagickRealType *) AcquireQuantumMemory(MaxMap+1UL,
2966 sizeof(*histogram));
2967 if (histogram == (MagickRealType *) NULL)
2968 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
2969 image->filename);
2970 /*
2971 Form histogram.
2972 */
2973 (void) ResetMagickMemory(histogram,0,(MaxMap+1)*sizeof(*histogram));
2974 exception=(&image->exception);
cristybb503372010-05-27 20:51:26 +00002975 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002976 {
2977 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00002978 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00002979
cristybb503372010-05-27 20:51:26 +00002980 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002981 x;
2982
2983 p=GetVirtualPixels(image,0,y,image->columns,1,exception);
2984 if (p == (const PixelPacket *) NULL)
2985 break;
cristybb503372010-05-27 20:51:26 +00002986 for (x=(ssize_t) image->columns-1; x >= 0; x--)
cristy3ed852e2009-09-05 21:47:34 +00002987 {
2988 histogram[ScaleQuantumToMap(PixelIntensityToQuantum(p))]++;
2989 p++;
2990 }
2991 }
2992 /*
2993 Find the histogram boundaries by locating the black and white point levels.
2994 */
cristy3ed852e2009-09-05 21:47:34 +00002995 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00002996 for (black=0; black < (ssize_t) MaxMap; black++)
cristy3ed852e2009-09-05 21:47:34 +00002997 {
2998 intensity+=histogram[black];
2999 if (intensity >= black_point)
3000 break;
3001 }
3002 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00003003 for (white=(ssize_t) MaxMap; white != 0; white--)
cristy3ed852e2009-09-05 21:47:34 +00003004 {
3005 intensity+=histogram[white];
3006 if (intensity >= white_point)
3007 break;
3008 }
3009 histogram=(MagickRealType *) RelinquishMagickMemory(histogram);
3010 status=LevelImageChannel(image,DefaultChannels,(double) black,(double) white,
3011 1.0);
3012 return(status);
3013}
3014
3015/*
3016%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3017% %
3018% %
3019% %
3020% M o d u l a t e I m a g e %
3021% %
3022% %
3023% %
3024%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3025%
3026% ModulateImage() lets you control the brightness, saturation, and hue
3027% of an image. Modulate represents the brightness, saturation, and hue
3028% as one parameter (e.g. 90,150,100). If the image colorspace is HSL, the
3029% modulation is lightness, saturation, and hue. And if the colorspace is
3030% HWB, use blackness, whiteness, and hue.
3031%
3032% The format of the ModulateImage method is:
3033%
3034% MagickBooleanType ModulateImage(Image *image,const char *modulate)
3035%
3036% A description of each parameter follows:
3037%
3038% o image: the image.
3039%
3040% o modulate: Define the percent change in brightness, saturation, and
3041% hue.
3042%
3043*/
3044
3045static void ModulateHSB(const double percent_hue,
3046 const double percent_saturation,const double percent_brightness,
3047 Quantum *red,Quantum *green,Quantum *blue)
3048{
3049 double
3050 brightness,
3051 hue,
3052 saturation;
3053
3054 /*
3055 Increase or decrease color brightness, saturation, or hue.
3056 */
3057 assert(red != (Quantum *) NULL);
3058 assert(green != (Quantum *) NULL);
3059 assert(blue != (Quantum *) NULL);
3060 ConvertRGBToHSB(*red,*green,*blue,&hue,&saturation,&brightness);
3061 hue+=0.5*(0.01*percent_hue-1.0);
3062 while (hue < 0.0)
3063 hue+=1.0;
3064 while (hue > 1.0)
3065 hue-=1.0;
3066 saturation*=0.01*percent_saturation;
3067 brightness*=0.01*percent_brightness;
3068 ConvertHSBToRGB(hue,saturation,brightness,red,green,blue);
3069}
3070
3071static void ModulateHSL(const double percent_hue,
3072 const double percent_saturation,const double percent_lightness,
3073 Quantum *red,Quantum *green,Quantum *blue)
3074{
3075 double
3076 hue,
3077 lightness,
3078 saturation;
3079
3080 /*
3081 Increase or decrease color lightness, saturation, or hue.
3082 */
3083 assert(red != (Quantum *) NULL);
3084 assert(green != (Quantum *) NULL);
3085 assert(blue != (Quantum *) NULL);
3086 ConvertRGBToHSL(*red,*green,*blue,&hue,&saturation,&lightness);
3087 hue+=0.5*(0.01*percent_hue-1.0);
3088 while (hue < 0.0)
3089 hue+=1.0;
3090 while (hue > 1.0)
3091 hue-=1.0;
3092 saturation*=0.01*percent_saturation;
3093 lightness*=0.01*percent_lightness;
3094 ConvertHSLToRGB(hue,saturation,lightness,red,green,blue);
3095}
3096
3097static void ModulateHWB(const double percent_hue,const double percent_whiteness, const double percent_blackness,Quantum *red,Quantum *green,Quantum *blue)
3098{
3099 double
3100 blackness,
3101 hue,
3102 whiteness;
3103
3104 /*
3105 Increase or decrease color blackness, whiteness, or hue.
3106 */
3107 assert(red != (Quantum *) NULL);
3108 assert(green != (Quantum *) NULL);
3109 assert(blue != (Quantum *) NULL);
3110 ConvertRGBToHWB(*red,*green,*blue,&hue,&whiteness,&blackness);
3111 hue+=0.5*(0.01*percent_hue-1.0);
3112 while (hue < 0.0)
3113 hue+=1.0;
3114 while (hue > 1.0)
3115 hue-=1.0;
3116 blackness*=0.01*percent_blackness;
3117 whiteness*=0.01*percent_whiteness;
3118 ConvertHWBToRGB(hue,whiteness,blackness,red,green,blue);
3119}
3120
3121MagickExport MagickBooleanType ModulateImage(Image *image,const char *modulate)
3122{
3123#define ModulateImageTag "Modulate/Image"
3124
cristyc4c8d132010-01-07 01:58:38 +00003125 CacheView
3126 *image_view;
3127
cristy3ed852e2009-09-05 21:47:34 +00003128 ColorspaceType
3129 colorspace;
3130
3131 const char
3132 *artifact;
3133
3134 double
3135 percent_brightness,
3136 percent_hue,
3137 percent_saturation;
3138
3139 ExceptionInfo
3140 *exception;
3141
3142 GeometryInfo
3143 geometry_info;
3144
cristy3ed852e2009-09-05 21:47:34 +00003145 MagickBooleanType
3146 status;
3147
cristybb503372010-05-27 20:51:26 +00003148 MagickOffsetType
3149 progress;
3150
cristy3ed852e2009-09-05 21:47:34 +00003151 MagickStatusType
3152 flags;
3153
cristybb503372010-05-27 20:51:26 +00003154 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003155 i;
3156
cristybb503372010-05-27 20:51:26 +00003157 ssize_t
3158 y;
3159
cristy3ed852e2009-09-05 21:47:34 +00003160 /*
cristy2b726bd2010-01-11 01:05:39 +00003161 Initialize modulate table.
cristy3ed852e2009-09-05 21:47:34 +00003162 */
3163 assert(image != (Image *) NULL);
3164 assert(image->signature == MagickSignature);
3165 if (image->debug != MagickFalse)
3166 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3167 if (modulate == (char *) NULL)
3168 return(MagickFalse);
3169 flags=ParseGeometry(modulate,&geometry_info);
3170 percent_brightness=geometry_info.rho;
3171 percent_saturation=geometry_info.sigma;
3172 if ((flags & SigmaValue) == 0)
3173 percent_saturation=100.0;
3174 percent_hue=geometry_info.xi;
3175 if ((flags & XiValue) == 0)
3176 percent_hue=100.0;
3177 colorspace=UndefinedColorspace;
3178 artifact=GetImageArtifact(image,"modulate:colorspace");
3179 if (artifact != (const char *) NULL)
3180 colorspace=(ColorspaceType) ParseMagickOption(MagickColorspaceOptions,
3181 MagickFalse,artifact);
3182 if (image->storage_class == PseudoClass)
3183 {
3184 /*
3185 Modulate colormap.
3186 */
cristyb5d5f722009-11-04 03:03:49 +00003187#if defined(MAGICKCORE_OPENMP_SUPPORT)
3188 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003189#endif
cristybb503372010-05-27 20:51:26 +00003190 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00003191 switch (colorspace)
3192 {
3193 case HSBColorspace:
3194 {
3195 ModulateHSB(percent_hue,percent_saturation,percent_brightness,
3196 &image->colormap[i].red,&image->colormap[i].green,
3197 &image->colormap[i].blue);
3198 break;
3199 }
3200 case HSLColorspace:
3201 default:
3202 {
3203 ModulateHSL(percent_hue,percent_saturation,percent_brightness,
3204 &image->colormap[i].red,&image->colormap[i].green,
3205 &image->colormap[i].blue);
3206 break;
3207 }
3208 case HWBColorspace:
3209 {
3210 ModulateHWB(percent_hue,percent_saturation,percent_brightness,
3211 &image->colormap[i].red,&image->colormap[i].green,
3212 &image->colormap[i].blue);
3213 break;
3214 }
3215 }
3216 }
3217 /*
3218 Modulate image.
3219 */
3220 status=MagickTrue;
3221 progress=0;
3222 exception=(&image->exception);
3223 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00003224#if defined(MAGICKCORE_OPENMP_SUPPORT)
3225 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003226#endif
cristybb503372010-05-27 20:51:26 +00003227 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003228 {
cristy3ed852e2009-09-05 21:47:34 +00003229 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00003230 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003231
cristy8d4629b2010-08-30 17:59:46 +00003232 register ssize_t
3233 x;
3234
cristy3ed852e2009-09-05 21:47:34 +00003235 if (status == MagickFalse)
3236 continue;
3237 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
3238 if (q == (PixelPacket *) NULL)
3239 {
3240 status=MagickFalse;
3241 continue;
3242 }
cristybb503372010-05-27 20:51:26 +00003243 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003244 {
3245 switch (colorspace)
3246 {
3247 case HSBColorspace:
3248 {
3249 ModulateHSB(percent_hue,percent_saturation,percent_brightness,
3250 &q->red,&q->green,&q->blue);
3251 break;
3252 }
3253 case HSLColorspace:
3254 default:
3255 {
3256 ModulateHSL(percent_hue,percent_saturation,percent_brightness,
3257 &q->red,&q->green,&q->blue);
3258 break;
3259 }
3260 case HWBColorspace:
3261 {
3262 ModulateHWB(percent_hue,percent_saturation,percent_brightness,
3263 &q->red,&q->green,&q->blue);
3264 break;
3265 }
3266 }
3267 q++;
3268 }
3269 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
3270 status=MagickFalse;
3271 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3272 {
3273 MagickBooleanType
3274 proceed;
3275
cristyb5d5f722009-11-04 03:03:49 +00003276#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00003277 #pragma omp critical (MagickCore_ModulateImage)
3278#endif
3279 proceed=SetImageProgress(image,ModulateImageTag,progress++,image->rows);
3280 if (proceed == MagickFalse)
3281 status=MagickFalse;
3282 }
3283 }
3284 image_view=DestroyCacheView(image_view);
3285 return(status);
3286}
3287
3288/*
3289%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3290% %
3291% %
3292% %
3293% N e g a t e I m a g e %
3294% %
3295% %
3296% %
3297%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3298%
3299% NegateImage() negates the colors in the reference image. The grayscale
3300% option means that only grayscale values within the image are negated.
3301%
3302% The format of the NegateImageChannel method is:
3303%
3304% MagickBooleanType NegateImage(Image *image,
3305% const MagickBooleanType grayscale)
3306% MagickBooleanType NegateImageChannel(Image *image,
3307% const ChannelType channel,const MagickBooleanType grayscale)
3308%
3309% A description of each parameter follows:
3310%
3311% o image: the image.
3312%
3313% o channel: the channel.
3314%
3315% o grayscale: If MagickTrue, only negate grayscale pixels within the image.
3316%
3317*/
3318
3319MagickExport MagickBooleanType NegateImage(Image *image,
3320 const MagickBooleanType grayscale)
3321{
3322 MagickBooleanType
3323 status;
3324
3325 status=NegateImageChannel(image,DefaultChannels,grayscale);
3326 return(status);
3327}
3328
3329MagickExport MagickBooleanType NegateImageChannel(Image *image,
3330 const ChannelType channel,const MagickBooleanType grayscale)
3331{
3332#define NegateImageTag "Negate/Image"
3333
cristyc4c8d132010-01-07 01:58:38 +00003334 CacheView
3335 *image_view;
3336
cristy3ed852e2009-09-05 21:47:34 +00003337 ExceptionInfo
3338 *exception;
3339
cristy3ed852e2009-09-05 21:47:34 +00003340 MagickBooleanType
3341 status;
3342
cristybb503372010-05-27 20:51:26 +00003343 MagickOffsetType
3344 progress;
3345
3346 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003347 i;
3348
cristybb503372010-05-27 20:51:26 +00003349 ssize_t
3350 y;
3351
cristy3ed852e2009-09-05 21:47:34 +00003352 assert(image != (Image *) NULL);
3353 assert(image->signature == MagickSignature);
3354 if (image->debug != MagickFalse)
3355 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3356 if (image->storage_class == PseudoClass)
3357 {
3358 /*
3359 Negate colormap.
3360 */
cristyb5d5f722009-11-04 03:03:49 +00003361#if defined(MAGICKCORE_OPENMP_SUPPORT)
3362 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003363#endif
cristybb503372010-05-27 20:51:26 +00003364 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00003365 {
3366 if (grayscale != MagickFalse)
3367 if ((image->colormap[i].red != image->colormap[i].green) ||
3368 (image->colormap[i].green != image->colormap[i].blue))
3369 continue;
3370 if ((channel & RedChannel) != 0)
3371 image->colormap[i].red=(Quantum) QuantumRange-
3372 image->colormap[i].red;
3373 if ((channel & GreenChannel) != 0)
3374 image->colormap[i].green=(Quantum) QuantumRange-
3375 image->colormap[i].green;
3376 if ((channel & BlueChannel) != 0)
3377 image->colormap[i].blue=(Quantum) QuantumRange-
3378 image->colormap[i].blue;
3379 }
3380 }
3381 /*
3382 Negate image.
3383 */
3384 status=MagickTrue;
3385 progress=0;
3386 exception=(&image->exception);
3387 image_view=AcquireCacheView(image);
3388 if (grayscale != MagickFalse)
3389 {
cristyb5d5f722009-11-04 03:03:49 +00003390#if defined(MAGICKCORE_OPENMP_SUPPORT)
3391 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003392#endif
cristybb503372010-05-27 20:51:26 +00003393 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003394 {
3395 MagickBooleanType
3396 sync;
3397
3398 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00003399 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00003400
cristy3ed852e2009-09-05 21:47:34 +00003401 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00003402 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003403
cristy8d4629b2010-08-30 17:59:46 +00003404 register ssize_t
3405 x;
3406
cristy3ed852e2009-09-05 21:47:34 +00003407 if (status == MagickFalse)
3408 continue;
3409 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,
3410 exception);
3411 if (q == (PixelPacket *) NULL)
3412 {
3413 status=MagickFalse;
3414 continue;
3415 }
3416 indexes=GetCacheViewAuthenticIndexQueue(image_view);
cristybb503372010-05-27 20:51:26 +00003417 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003418 {
3419 if ((q->red != q->green) || (q->green != q->blue))
3420 {
3421 q++;
3422 continue;
3423 }
3424 if ((channel & RedChannel) != 0)
3425 q->red=(Quantum) QuantumRange-q->red;
3426 if ((channel & GreenChannel) != 0)
3427 q->green=(Quantum) QuantumRange-q->green;
3428 if ((channel & BlueChannel) != 0)
3429 q->blue=(Quantum) QuantumRange-q->blue;
3430 if ((channel & OpacityChannel) != 0)
3431 q->opacity=(Quantum) QuantumRange-q->opacity;
3432 if (((channel & IndexChannel) != 0) &&
3433 (image->colorspace == CMYKColorspace))
3434 indexes[x]=(IndexPacket) QuantumRange-indexes[x];
3435 q++;
3436 }
3437 sync=SyncCacheViewAuthenticPixels(image_view,exception);
3438 if (sync == MagickFalse)
3439 status=MagickFalse;
3440 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3441 {
3442 MagickBooleanType
3443 proceed;
3444
cristyb5d5f722009-11-04 03:03:49 +00003445#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00003446 #pragma omp critical (MagickCore_NegateImageChannel)
3447#endif
3448 proceed=SetImageProgress(image,NegateImageTag,progress++,
3449 image->rows);
3450 if (proceed == MagickFalse)
3451 status=MagickFalse;
3452 }
3453 }
3454 image_view=DestroyCacheView(image_view);
3455 return(MagickTrue);
3456 }
3457 /*
3458 Negate image.
3459 */
cristyb5d5f722009-11-04 03:03:49 +00003460#if defined(MAGICKCORE_OPENMP_SUPPORT)
3461 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003462#endif
cristybb503372010-05-27 20:51:26 +00003463 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003464 {
3465 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00003466 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00003467
cristy3ed852e2009-09-05 21:47:34 +00003468 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00003469 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003470
cristy8d4629b2010-08-30 17:59:46 +00003471 register ssize_t
3472 x;
3473
cristy3ed852e2009-09-05 21:47:34 +00003474 if (status == MagickFalse)
3475 continue;
3476 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
3477 if (q == (PixelPacket *) NULL)
3478 {
3479 status=MagickFalse;
3480 continue;
3481 }
3482 indexes=GetCacheViewAuthenticIndexQueue(image_view);
cristybb503372010-05-27 20:51:26 +00003483 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003484 {
3485 if ((channel & RedChannel) != 0)
3486 q->red=(Quantum) QuantumRange-q->red;
3487 if ((channel & GreenChannel) != 0)
3488 q->green=(Quantum) QuantumRange-q->green;
3489 if ((channel & BlueChannel) != 0)
3490 q->blue=(Quantum) QuantumRange-q->blue;
3491 if ((channel & OpacityChannel) != 0)
3492 q->opacity=(Quantum) QuantumRange-q->opacity;
3493 if (((channel & IndexChannel) != 0) &&
3494 (image->colorspace == CMYKColorspace))
3495 indexes[x]=(IndexPacket) QuantumRange-indexes[x];
3496 q++;
3497 }
3498 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
3499 status=MagickFalse;
3500 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3501 {
3502 MagickBooleanType
3503 proceed;
3504
cristyb5d5f722009-11-04 03:03:49 +00003505#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00003506 #pragma omp critical (MagickCore_NegateImageChannel)
3507#endif
3508 proceed=SetImageProgress(image,NegateImageTag,progress++,image->rows);
3509 if (proceed == MagickFalse)
3510 status=MagickFalse;
3511 }
3512 }
3513 image_view=DestroyCacheView(image_view);
3514 return(status);
3515}
3516
3517/*
3518%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3519% %
3520% %
3521% %
3522% N o r m a l i z e I m a g e %
3523% %
3524% %
3525% %
3526%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3527%
3528% The NormalizeImage() method enhances the contrast of a color image by
3529% mapping the darkest 2 percent of all pixel to black and the brightest
3530% 1 percent to white.
3531%
3532% The format of the NormalizeImage method is:
3533%
3534% MagickBooleanType NormalizeImage(Image *image)
3535% MagickBooleanType NormalizeImageChannel(Image *image,
3536% const ChannelType channel)
3537%
3538% A description of each parameter follows:
3539%
3540% o image: the image.
3541%
3542% o channel: the channel.
3543%
3544*/
3545
3546MagickExport MagickBooleanType NormalizeImage(Image *image)
3547{
3548 MagickBooleanType
3549 status;
3550
3551 status=NormalizeImageChannel(image,DefaultChannels);
3552 return(status);
3553}
3554
3555MagickExport MagickBooleanType NormalizeImageChannel(Image *image,
3556 const ChannelType channel)
3557{
3558 double
3559 black_point,
3560 white_point;
3561
cristy530239c2010-07-25 17:34:26 +00003562 black_point=(double) image->columns*image->rows*0.0015;
3563 white_point=(double) image->columns*image->rows*0.9995;
cristy3ed852e2009-09-05 21:47:34 +00003564 return(ContrastStretchImageChannel(image,channel,black_point,white_point));
3565}
3566
3567/*
3568%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3569% %
3570% %
3571% %
3572% S i g m o i d a l C o n t r a s t I m a g e %
3573% %
3574% %
3575% %
3576%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3577%
3578% SigmoidalContrastImage() adjusts the contrast of an image with a non-linear
3579% sigmoidal contrast algorithm. Increase the contrast of the image using a
3580% sigmoidal transfer function without saturating highlights or shadows.
3581% Contrast indicates how much to increase the contrast (0 is none; 3 is
3582% typical; 20 is pushing it); mid-point indicates where midtones fall in the
3583% resultant image (0 is white; 50% is middle-gray; 100% is black). Set
3584% sharpen to MagickTrue to increase the image contrast otherwise the contrast
3585% is reduced.
3586%
3587% The format of the SigmoidalContrastImage method is:
3588%
3589% MagickBooleanType SigmoidalContrastImage(Image *image,
3590% const MagickBooleanType sharpen,const char *levels)
3591% MagickBooleanType SigmoidalContrastImageChannel(Image *image,
3592% const ChannelType channel,const MagickBooleanType sharpen,
3593% const double contrast,const double midpoint)
3594%
3595% A description of each parameter follows:
3596%
3597% o image: the image.
3598%
3599% o channel: the channel.
3600%
3601% o sharpen: Increase or decrease image contrast.
3602%
cristyfa769582010-09-30 23:30:03 +00003603% o alpha: strength of the contrast, the larger the number the more
3604% 'threshold-like' it becomes.
cristy3ed852e2009-09-05 21:47:34 +00003605%
cristyfa769582010-09-30 23:30:03 +00003606% o beta: midpoint of the function as a color value 0 to QuantumRange.
cristy3ed852e2009-09-05 21:47:34 +00003607%
3608*/
3609
3610MagickExport MagickBooleanType SigmoidalContrastImage(Image *image,
3611 const MagickBooleanType sharpen,const char *levels)
3612{
3613 GeometryInfo
3614 geometry_info;
3615
3616 MagickBooleanType
3617 status;
3618
3619 MagickStatusType
3620 flags;
3621
3622 flags=ParseGeometry(levels,&geometry_info);
3623 if ((flags & SigmaValue) == 0)
3624 geometry_info.sigma=1.0*QuantumRange/2.0;
3625 if ((flags & PercentValue) != 0)
3626 geometry_info.sigma=1.0*QuantumRange*geometry_info.sigma/100.0;
3627 status=SigmoidalContrastImageChannel(image,DefaultChannels,sharpen,
3628 geometry_info.rho,geometry_info.sigma);
3629 return(status);
3630}
3631
3632MagickExport MagickBooleanType SigmoidalContrastImageChannel(Image *image,
3633 const ChannelType channel,const MagickBooleanType sharpen,
3634 const double contrast,const double midpoint)
3635{
3636#define SigmoidalContrastImageTag "SigmoidalContrast/Image"
3637
cristyc4c8d132010-01-07 01:58:38 +00003638 CacheView
3639 *image_view;
3640
cristy3ed852e2009-09-05 21:47:34 +00003641 ExceptionInfo
3642 *exception;
3643
cristy3ed852e2009-09-05 21:47:34 +00003644 MagickBooleanType
3645 status;
3646
cristybb503372010-05-27 20:51:26 +00003647 MagickOffsetType
3648 progress;
3649
cristy3ed852e2009-09-05 21:47:34 +00003650 MagickRealType
3651 *sigmoidal_map;
3652
cristybb503372010-05-27 20:51:26 +00003653 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003654 i;
3655
cristybb503372010-05-27 20:51:26 +00003656 ssize_t
3657 y;
3658
cristy3ed852e2009-09-05 21:47:34 +00003659 /*
3660 Allocate and initialize sigmoidal maps.
3661 */
3662 assert(image != (Image *) NULL);
3663 assert(image->signature == MagickSignature);
3664 if (image->debug != MagickFalse)
3665 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3666 sigmoidal_map=(MagickRealType *) AcquireQuantumMemory(MaxMap+1UL,
3667 sizeof(*sigmoidal_map));
3668 if (sigmoidal_map == (MagickRealType *) NULL)
3669 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
3670 image->filename);
3671 (void) ResetMagickMemory(sigmoidal_map,0,(MaxMap+1)*sizeof(*sigmoidal_map));
cristyb5d5f722009-11-04 03:03:49 +00003672#if defined(MAGICKCORE_OPENMP_SUPPORT)
3673 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003674#endif
cristybb503372010-05-27 20:51:26 +00003675 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +00003676 {
3677 if (sharpen != MagickFalse)
3678 {
3679 sigmoidal_map[i]=(MagickRealType) ScaleMapToQuantum((MagickRealType)
3680 (MaxMap*((1.0/(1.0+exp(contrast*(midpoint/(double) QuantumRange-
3681 (double) i/MaxMap))))-(1.0/(1.0+exp(contrast*(midpoint/
3682 (double) QuantumRange)))))/((1.0/(1.0+exp(contrast*(midpoint/
3683 (double) QuantumRange-1.0))))-(1.0/(1.0+exp(contrast*(midpoint/
3684 (double) QuantumRange)))))+0.5));
3685 continue;
3686 }
3687 sigmoidal_map[i]=(MagickRealType) ScaleMapToQuantum((MagickRealType)
3688 (MaxMap*(QuantumScale*midpoint-log((1.0-(1.0/(1.0+exp(midpoint/
3689 (double) QuantumRange*contrast))+((double) i/MaxMap)*((1.0/
3690 (1.0+exp(contrast*(midpoint/(double) QuantumRange-1.0))))-(1.0/
3691 (1.0+exp(midpoint/(double) QuantumRange*contrast))))))/
3692 (1.0/(1.0+exp(midpoint/(double) QuantumRange*contrast))+
3693 ((double) i/MaxMap)*((1.0/(1.0+exp(contrast*(midpoint/
3694 (double) QuantumRange-1.0))))-(1.0/(1.0+exp(midpoint/
3695 (double) QuantumRange*contrast))))))/contrast)));
3696 }
3697 if (image->storage_class == PseudoClass)
3698 {
3699 /*
3700 Sigmoidal-contrast enhance colormap.
3701 */
cristyb5d5f722009-11-04 03:03:49 +00003702#if defined(MAGICKCORE_OPENMP_SUPPORT)
3703 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003704#endif
cristybb503372010-05-27 20:51:26 +00003705 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00003706 {
3707 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003708 image->colormap[i].red=ClampToQuantum(sigmoidal_map[
cristy3ed852e2009-09-05 21:47:34 +00003709 ScaleQuantumToMap(image->colormap[i].red)]);
3710 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003711 image->colormap[i].green=ClampToQuantum(sigmoidal_map[
cristy3ed852e2009-09-05 21:47:34 +00003712 ScaleQuantumToMap(image->colormap[i].green)]);
3713 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003714 image->colormap[i].blue=ClampToQuantum(sigmoidal_map[
cristy3ed852e2009-09-05 21:47:34 +00003715 ScaleQuantumToMap(image->colormap[i].blue)]);
3716 if ((channel & OpacityChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003717 image->colormap[i].opacity=ClampToQuantum(sigmoidal_map[
cristy3ed852e2009-09-05 21:47:34 +00003718 ScaleQuantumToMap(image->colormap[i].opacity)]);
3719 }
3720 }
3721 /*
3722 Sigmoidal-contrast enhance image.
3723 */
3724 status=MagickTrue;
3725 progress=0;
3726 exception=(&image->exception);
3727 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00003728#if defined(MAGICKCORE_OPENMP_SUPPORT)
3729 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003730#endif
cristybb503372010-05-27 20:51:26 +00003731 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003732 {
3733 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00003734 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00003735
cristy3ed852e2009-09-05 21:47:34 +00003736 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00003737 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003738
cristy8d4629b2010-08-30 17:59:46 +00003739 register ssize_t
3740 x;
3741
cristy3ed852e2009-09-05 21:47:34 +00003742 if (status == MagickFalse)
3743 continue;
3744 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
3745 if (q == (PixelPacket *) NULL)
3746 {
3747 status=MagickFalse;
3748 continue;
3749 }
3750 indexes=GetCacheViewAuthenticIndexQueue(image_view);
cristybb503372010-05-27 20:51:26 +00003751 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003752 {
3753 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003754 q->red=ClampToQuantum(sigmoidal_map[ScaleQuantumToMap(q->red)]);
cristy3ed852e2009-09-05 21:47:34 +00003755 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003756 q->green=ClampToQuantum(sigmoidal_map[ScaleQuantumToMap(q->green)]);
cristy3ed852e2009-09-05 21:47:34 +00003757 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003758 q->blue=ClampToQuantum(sigmoidal_map[ScaleQuantumToMap(q->blue)]);
cristy3ed852e2009-09-05 21:47:34 +00003759 if ((channel & OpacityChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003760 q->opacity=ClampToQuantum(sigmoidal_map[ScaleQuantumToMap(q->opacity)]);
cristy3ed852e2009-09-05 21:47:34 +00003761 if (((channel & IndexChannel) != 0) &&
3762 (image->colorspace == CMYKColorspace))
cristyce70c172010-01-07 17:15:30 +00003763 indexes[x]=(IndexPacket) ClampToQuantum(sigmoidal_map[
cristy3ed852e2009-09-05 21:47:34 +00003764 ScaleQuantumToMap(indexes[x])]);
3765 q++;
3766 }
3767 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
3768 status=MagickFalse;
3769 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3770 {
3771 MagickBooleanType
3772 proceed;
3773
cristyb5d5f722009-11-04 03:03:49 +00003774#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00003775 #pragma omp critical (MagickCore_SigmoidalContrastImageChannel)
3776#endif
3777 proceed=SetImageProgress(image,SigmoidalContrastImageTag,progress++,
3778 image->rows);
3779 if (proceed == MagickFalse)
3780 status=MagickFalse;
3781 }
3782 }
3783 image_view=DestroyCacheView(image_view);
3784 sigmoidal_map=(MagickRealType *) RelinquishMagickMemory(sigmoidal_map);
3785 return(status);
3786}