blob: 5c827d5875b8be75fd35f5f7be35af2cc5d04b94 [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% %
cristy16af1cb2009-12-11 21:38:29 +000020% Copyright 1999-2010 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;
cristyca6da3e2010-03-26 01:27:47 +0000293 slope=tan(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
cristybb503372010-05-27 20:51:26 +0000598 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000599 x;
600
601 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +0000602 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000603
604 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
712 zero;
713
714 ResampleFilter
cristyfa112112010-01-04 17:48:07 +0000715 **restrict resample_filter;
cristy3ed852e2009-09-05 21:47:34 +0000716
cristybb503372010-05-27 20:51:26 +0000717 ssize_t
718 adjust,
719 y;
720
cristy3ed852e2009-09-05 21:47:34 +0000721 assert(image != (Image *) NULL);
722 assert(image->signature == MagickSignature);
723 if (image->debug != MagickFalse)
724 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
725 assert(clut_image != (Image *) NULL);
726 assert(clut_image->signature == MagickSignature);
727 if (SetImageStorageClass(image,DirectClass) == MagickFalse)
728 return(MagickFalse);
729 /*
730 Clut image.
731 */
732 status=MagickTrue;
733 progress=0;
734 GetMagickPixelPacket(clut_image,&zero);
cristybb503372010-05-27 20:51:26 +0000735 adjust=(ssize_t) (clut_image->interpolate == IntegerInterpolatePixel ? 0 : 1);
cristy3ed852e2009-09-05 21:47:34 +0000736 exception=(&image->exception);
cristyb2a11ae2010-02-22 00:53:36 +0000737 resample_filter=AcquireResampleFilterThreadSet(clut_image,
738 UndefinedVirtualPixelMethod,MagickTrue,exception);
cristy3ed852e2009-09-05 21:47:34 +0000739 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +0000740#if defined(MAGICKCORE_OPENMP_SUPPORT)
741 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000742#endif
cristybb503372010-05-27 20:51:26 +0000743 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000744 {
745 MagickPixelPacket
746 pixel;
747
748 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +0000749 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +0000750
cristybb503372010-05-27 20:51:26 +0000751 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000752 id,
753 x;
754
755 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +0000756 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000757
758 if (status == MagickFalse)
759 continue;
760 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
761 if (q == (PixelPacket *) NULL)
762 {
763 status=MagickFalse;
764 continue;
765 }
766 indexes=GetCacheViewAuthenticIndexQueue(image_view);
767 pixel=zero;
768 id=GetOpenMPThreadId();
cristybb503372010-05-27 20:51:26 +0000769 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000770 {
771 /*
772 PROGRAMMERS WARNING:
773
774 Apply OpacityChannel BEFORE the color channels. Do not re-order.
775
776 The handling special case 2 (coloring gray-scale), requires access to
777 the unmodified colors of the original image to determine the index
778 value. As such alpha/matte channel handling must be performed BEFORE,
779 any of the color channels are modified.
780
781 */
782 if ((channel & OpacityChannel) != 0)
783 {
784 if (clut_image->matte == MagickFalse)
785 {
786 /*
787 A gray-scale LUT replacement for an image alpha channel.
788 */
789 (void) ResamplePixelColor(resample_filter[id],QuantumScale*
cristy46f08202010-01-10 04:04:21 +0000790 GetAlphaPixelComponent(q)*(clut_image->columns+adjust),
791 QuantumScale*GetAlphaPixelComponent(q)*(clut_image->rows+
cristy3ed852e2009-09-05 21:47:34 +0000792 adjust),&pixel);
793 q->opacity=(Quantum) (QuantumRange-MagickPixelIntensityToQuantum(
794 &pixel));
795 }
796 else
797 if (image->matte == MagickFalse)
798 {
799 /*
800 A greyscale image being colored by a LUT with transparency.
801 */
802 (void) ResamplePixelColor(resample_filter[id],QuantumScale*
803 PixelIntensity(q)*(clut_image->columns-adjust),QuantumScale*
804 PixelIntensity(q)*(clut_image->rows-adjust),&pixel);
cristyce70c172010-01-07 17:15:30 +0000805 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +0000806 }
807 else
808 {
809 /*
810 Direct alpha channel lookup.
811 */
812 (void) ResamplePixelColor(resample_filter[id],QuantumScale*
813 q->opacity*(clut_image->columns-adjust),QuantumScale*
814 q->opacity* (clut_image->rows-adjust),&pixel);
cristyce70c172010-01-07 17:15:30 +0000815 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +0000816 }
817 }
818 if ((channel & RedChannel) != 0)
819 {
820 (void) ResamplePixelColor(resample_filter[id],QuantumScale*q->red*
821 (clut_image->columns-adjust),QuantumScale*q->red*
822 (clut_image->rows-adjust),&pixel);
cristyce70c172010-01-07 17:15:30 +0000823 SetRedPixelComponent(q,ClampRedPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +0000824 }
825 if ((channel & GreenChannel) != 0)
826 {
827 (void) ResamplePixelColor(resample_filter[id],QuantumScale*q->green*
828 (clut_image->columns-adjust),QuantumScale*q->green*
829 (clut_image->rows-adjust),&pixel);
cristyce70c172010-01-07 17:15:30 +0000830 SetGreenPixelComponent(q,ClampGreenPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +0000831 }
832 if ((channel & BlueChannel) != 0)
833 {
834 (void) ResamplePixelColor(resample_filter[id],QuantumScale*q->blue*
835 (clut_image->columns-adjust),QuantumScale*q->blue*
836 (clut_image->rows-adjust),&pixel);
cristyce70c172010-01-07 17:15:30 +0000837 SetBluePixelComponent(q,ClampBluePixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +0000838 }
839 if (((channel & IndexChannel) != 0) &&
840 (image->colorspace == CMYKColorspace))
841 {
842 (void) ResamplePixelColor(resample_filter[id],QuantumScale*indexes[x]*
843 (clut_image->columns-adjust),QuantumScale*indexes[x]*
844 (clut_image->rows-adjust),&pixel);
cristyce70c172010-01-07 17:15:30 +0000845 indexes[x]=ClampToQuantum(pixel.index);
cristy3ed852e2009-09-05 21:47:34 +0000846 }
847 q++;
848 }
849 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
850 status=MagickFalse;
851 if (image->progress_monitor != (MagickProgressMonitor) NULL)
852 {
853 MagickBooleanType
854 proceed;
855
cristyb5d5f722009-11-04 03:03:49 +0000856#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +0000857 #pragma omp critical (MagickCore_ClutImageChannel)
858#endif
859 proceed=SetImageProgress(image,ClutImageTag,progress++,image->rows);
860 if (proceed == MagickFalse)
861 status=MagickFalse;
862 }
863 }
864 image_view=DestroyCacheView(image_view);
865 resample_filter=DestroyResampleFilterThreadSet(resample_filter);
866 /*
867 Enable alpha channel if CLUT image could enable it.
868 */
869 if ((clut_image->matte != MagickFalse) && ((channel & OpacityChannel) != 0))
870 (void) SetImageAlphaChannel(image,ActivateAlphaChannel);
871 return(status);
872}
873
874/*
875%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
876% %
877% %
878% %
879% C o n t r a s t I m a g e %
880% %
881% %
882% %
883%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
884%
885% ContrastImage() enhances the intensity differences between the lighter and
886% darker elements of the image. Set sharpen to a MagickTrue to increase the
887% image contrast otherwise the contrast is reduced.
888%
889% The format of the ContrastImage method is:
890%
891% MagickBooleanType ContrastImage(Image *image,
892% const MagickBooleanType sharpen)
893%
894% A description of each parameter follows:
895%
896% o image: the image.
897%
898% o sharpen: Increase or decrease image contrast.
899%
900*/
901
902static void Contrast(const int sign,Quantum *red,Quantum *green,Quantum *blue)
903{
904 double
905 brightness,
906 hue,
907 saturation;
908
909 /*
910 Enhance contrast: dark color become darker, light color become lighter.
911 */
912 assert(red != (Quantum *) NULL);
913 assert(green != (Quantum *) NULL);
914 assert(blue != (Quantum *) NULL);
915 hue=0.0;
916 saturation=0.0;
917 brightness=0.0;
918 ConvertRGBToHSB(*red,*green,*blue,&hue,&saturation,&brightness);
919 brightness+=0.5*sign*(0.5*(sin(MagickPI*(brightness-0.5))+1.0)-brightness);
920 if (brightness > 1.0)
921 brightness=1.0;
922 else
923 if (brightness < 0.0)
924 brightness=0.0;
925 ConvertHSBToRGB(hue,saturation,brightness,red,green,blue);
926}
927
928MagickExport MagickBooleanType ContrastImage(Image *image,
929 const MagickBooleanType sharpen)
930{
931#define ContrastImageTag "Contrast/Image"
932
cristyc4c8d132010-01-07 01:58:38 +0000933 CacheView
934 *image_view;
935
cristy3ed852e2009-09-05 21:47:34 +0000936 ExceptionInfo
937 *exception;
938
939 int
940 sign;
941
cristy3ed852e2009-09-05 21:47:34 +0000942 MagickBooleanType
943 status;
944
cristybb503372010-05-27 20:51:26 +0000945 MagickOffsetType
946 progress;
947
948 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000949 i;
950
cristybb503372010-05-27 20:51:26 +0000951 ssize_t
952 y;
953
cristy3ed852e2009-09-05 21:47:34 +0000954 assert(image != (Image *) NULL);
955 assert(image->signature == MagickSignature);
956 if (image->debug != MagickFalse)
957 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
958 sign=sharpen != MagickFalse ? 1 : -1;
959 if (image->storage_class == PseudoClass)
960 {
961 /*
962 Contrast enhance colormap.
963 */
cristybb503372010-05-27 20:51:26 +0000964 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +0000965 Contrast(sign,&image->colormap[i].red,&image->colormap[i].green,
966 &image->colormap[i].blue);
967 }
968 /*
969 Contrast enhance image.
970 */
971 status=MagickTrue;
972 progress=0;
973 exception=(&image->exception);
974 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +0000975#if defined(MAGICKCORE_OPENMP_SUPPORT)
976 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000977#endif
cristybb503372010-05-27 20:51:26 +0000978 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000979 {
cristybb503372010-05-27 20:51:26 +0000980 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000981 x;
982
983 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +0000984 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000985
986 if (status == MagickFalse)
987 continue;
988 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
989 if (q == (PixelPacket *) NULL)
990 {
991 status=MagickFalse;
992 continue;
993 }
cristybb503372010-05-27 20:51:26 +0000994 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000995 {
996 Contrast(sign,&q->red,&q->green,&q->blue);
997 q++;
998 }
999 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
1000 status=MagickFalse;
1001 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1002 {
1003 MagickBooleanType
1004 proceed;
1005
cristyb5d5f722009-11-04 03:03:49 +00001006#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00001007 #pragma omp critical (MagickCore_ContrastImage)
1008#endif
1009 proceed=SetImageProgress(image,ContrastImageTag,progress++,image->rows);
1010 if (proceed == MagickFalse)
1011 status=MagickFalse;
1012 }
1013 }
1014 image_view=DestroyCacheView(image_view);
1015 return(status);
1016}
1017
1018/*
1019%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1020% %
1021% %
1022% %
1023% C o n t r a s t S t r e t c h I m a g e %
1024% %
1025% %
1026% %
1027%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1028%
1029% The ContrastStretchImage() is a simple image enhancement technique that
1030% attempts to improve the contrast in an image by `stretching' the range of
1031% intensity values it contains to span a desired range of values. It differs
1032% from the more sophisticated histogram equalization in that it can only
1033% apply % a linear scaling function to the image pixel values. As a result
1034% the `enhancement' is less harsh.
1035%
1036% The format of the ContrastStretchImage method is:
1037%
1038% MagickBooleanType ContrastStretchImage(Image *image,
1039% const char *levels)
1040% MagickBooleanType ContrastStretchImageChannel(Image *image,
cristybb503372010-05-27 20:51:26 +00001041% const size_t channel,const double black_point,
cristy3ed852e2009-09-05 21:47:34 +00001042% const double white_point)
1043%
1044% A description of each parameter follows:
1045%
1046% o image: the image.
1047%
1048% o channel: the channel.
1049%
1050% o black_point: the black point.
1051%
1052% o white_point: the white point.
1053%
1054% o levels: Specify the levels where the black and white points have the
1055% range of 0 to number-of-pixels (e.g. 1%, 10x90%, etc.).
1056%
1057*/
1058
1059MagickExport MagickBooleanType ContrastStretchImage(Image *image,
1060 const char *levels)
1061{
1062 double
1063 black_point,
1064 white_point;
1065
1066 GeometryInfo
1067 geometry_info;
1068
1069 MagickBooleanType
1070 status;
1071
1072 MagickStatusType
1073 flags;
1074
1075 /*
1076 Parse levels.
1077 */
1078 if (levels == (char *) NULL)
1079 return(MagickFalse);
1080 flags=ParseGeometry(levels,&geometry_info);
1081 black_point=geometry_info.rho;
1082 white_point=(double) image->columns*image->rows;
1083 if ((flags & SigmaValue) != 0)
1084 white_point=geometry_info.sigma;
1085 if ((flags & PercentValue) != 0)
1086 {
1087 black_point*=(double) QuantumRange/100.0;
1088 white_point*=(double) QuantumRange/100.0;
1089 }
1090 if ((flags & SigmaValue) == 0)
1091 white_point=(double) image->columns*image->rows-black_point;
1092 status=ContrastStretchImageChannel(image,DefaultChannels,black_point,
1093 white_point);
1094 return(status);
1095}
1096
1097MagickExport MagickBooleanType ContrastStretchImageChannel(Image *image,
1098 const ChannelType channel,const double black_point,const double white_point)
1099{
1100#define MaxRange(color) ((MagickRealType) ScaleQuantumToMap((Quantum) (color)))
1101#define ContrastStretchImageTag "ContrastStretch/Image"
1102
cristyc4c8d132010-01-07 01:58:38 +00001103 CacheView
1104 *image_view;
1105
cristy3ed852e2009-09-05 21:47:34 +00001106 double
1107 intensity;
1108
1109 ExceptionInfo
1110 *exception;
1111
cristy3ed852e2009-09-05 21:47:34 +00001112 MagickBooleanType
1113 status;
1114
cristybb503372010-05-27 20:51:26 +00001115 MagickOffsetType
1116 progress;
1117
cristy3ed852e2009-09-05 21:47:34 +00001118 MagickPixelPacket
1119 black,
1120 *histogram,
1121 *stretch_map,
1122 white;
1123
cristybb503372010-05-27 20:51:26 +00001124 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001125 i;
1126
cristybb503372010-05-27 20:51:26 +00001127 ssize_t
1128 y;
1129
cristy3ed852e2009-09-05 21:47:34 +00001130 /*
1131 Allocate histogram and stretch map.
1132 */
1133 assert(image != (Image *) NULL);
1134 assert(image->signature == MagickSignature);
1135 if (image->debug != MagickFalse)
1136 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1137 histogram=(MagickPixelPacket *) AcquireQuantumMemory(MaxMap+1UL,
1138 sizeof(*histogram));
1139 stretch_map=(MagickPixelPacket *) AcquireQuantumMemory(MaxMap+1UL,
1140 sizeof(*stretch_map));
1141 if ((histogram == (MagickPixelPacket *) NULL) ||
1142 (stretch_map == (MagickPixelPacket *) NULL))
1143 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
1144 image->filename);
1145 /*
1146 Form histogram.
1147 */
1148 status=MagickTrue;
1149 exception=(&image->exception);
1150 (void) ResetMagickMemory(histogram,0,(MaxMap+1)*sizeof(*histogram));
1151 image_view=AcquireCacheView(image);
cristybb503372010-05-27 20:51:26 +00001152 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001153 {
1154 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001155 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00001156
1157 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00001158 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00001159
cristybb503372010-05-27 20:51:26 +00001160 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001161 x;
1162
1163 if (status == MagickFalse)
1164 continue;
1165 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
1166 if (p == (const PixelPacket *) NULL)
1167 {
1168 status=MagickFalse;
1169 continue;
1170 }
1171 indexes=GetCacheViewAuthenticIndexQueue(image_view);
1172 if (channel == DefaultChannels)
cristybb503372010-05-27 20:51:26 +00001173 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001174 {
1175 Quantum
1176 intensity;
1177
1178 intensity=PixelIntensityToQuantum(p);
1179 histogram[ScaleQuantumToMap(intensity)].red++;
1180 histogram[ScaleQuantumToMap(intensity)].green++;
1181 histogram[ScaleQuantumToMap(intensity)].blue++;
1182 histogram[ScaleQuantumToMap(intensity)].index++;
1183 p++;
1184 }
1185 else
cristybb503372010-05-27 20:51:26 +00001186 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001187 {
1188 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001189 histogram[ScaleQuantumToMap(GetRedPixelComponent(p))].red++;
cristy3ed852e2009-09-05 21:47:34 +00001190 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001191 histogram[ScaleQuantumToMap(GetGreenPixelComponent(p))].green++;
cristy3ed852e2009-09-05 21:47:34 +00001192 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001193 histogram[ScaleQuantumToMap(GetBluePixelComponent(p))].blue++;
cristy3ed852e2009-09-05 21:47:34 +00001194 if ((channel & OpacityChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001195 histogram[ScaleQuantumToMap(GetOpacityPixelComponent(p))].opacity++;
cristy3ed852e2009-09-05 21:47:34 +00001196 if (((channel & IndexChannel) != 0) &&
1197 (image->colorspace == CMYKColorspace))
1198 histogram[ScaleQuantumToMap(indexes[x])].index++;
1199 p++;
1200 }
1201 }
1202 /*
1203 Find the histogram boundaries by locating the black/white levels.
1204 */
1205 black.red=0.0;
1206 white.red=MaxRange(QuantumRange);
1207 if ((channel & RedChannel) != 0)
1208 {
1209 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00001210 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +00001211 {
1212 intensity+=histogram[i].red;
1213 if (intensity > black_point)
1214 break;
1215 }
1216 black.red=(MagickRealType) i;
1217 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00001218 for (i=(ssize_t) MaxMap; i != 0; i--)
cristy3ed852e2009-09-05 21:47:34 +00001219 {
1220 intensity+=histogram[i].red;
1221 if (intensity > ((double) image->columns*image->rows-white_point))
1222 break;
1223 }
1224 white.red=(MagickRealType) i;
1225 }
1226 black.green=0.0;
1227 white.green=MaxRange(QuantumRange);
1228 if ((channel & GreenChannel) != 0)
1229 {
1230 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00001231 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +00001232 {
1233 intensity+=histogram[i].green;
1234 if (intensity > black_point)
1235 break;
1236 }
1237 black.green=(MagickRealType) i;
1238 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00001239 for (i=(ssize_t) MaxMap; i != 0; i--)
cristy3ed852e2009-09-05 21:47:34 +00001240 {
1241 intensity+=histogram[i].green;
1242 if (intensity > ((double) image->columns*image->rows-white_point))
1243 break;
1244 }
1245 white.green=(MagickRealType) i;
1246 }
1247 black.blue=0.0;
1248 white.blue=MaxRange(QuantumRange);
1249 if ((channel & BlueChannel) != 0)
1250 {
1251 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00001252 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +00001253 {
1254 intensity+=histogram[i].blue;
1255 if (intensity > black_point)
1256 break;
1257 }
1258 black.blue=(MagickRealType) i;
1259 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00001260 for (i=(ssize_t) MaxMap; i != 0; i--)
cristy3ed852e2009-09-05 21:47:34 +00001261 {
1262 intensity+=histogram[i].blue;
1263 if (intensity > ((double) image->columns*image->rows-white_point))
1264 break;
1265 }
1266 white.blue=(MagickRealType) i;
1267 }
1268 black.opacity=0.0;
1269 white.opacity=MaxRange(QuantumRange);
1270 if ((channel & OpacityChannel) != 0)
1271 {
1272 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00001273 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +00001274 {
1275 intensity+=histogram[i].opacity;
1276 if (intensity > black_point)
1277 break;
1278 }
1279 black.opacity=(MagickRealType) i;
1280 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00001281 for (i=(ssize_t) MaxMap; i != 0; i--)
cristy3ed852e2009-09-05 21:47:34 +00001282 {
1283 intensity+=histogram[i].opacity;
1284 if (intensity > ((double) image->columns*image->rows-white_point))
1285 break;
1286 }
1287 white.opacity=(MagickRealType) i;
1288 }
1289 black.index=0.0;
1290 white.index=MaxRange(QuantumRange);
1291 if (((channel & IndexChannel) != 0) && (image->colorspace == CMYKColorspace))
1292 {
1293 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00001294 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +00001295 {
1296 intensity+=histogram[i].index;
1297 if (intensity > black_point)
1298 break;
1299 }
1300 black.index=(MagickRealType) i;
1301 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00001302 for (i=(ssize_t) MaxMap; i != 0; i--)
cristy3ed852e2009-09-05 21:47:34 +00001303 {
1304 intensity+=histogram[i].index;
1305 if (intensity > ((double) image->columns*image->rows-white_point))
1306 break;
1307 }
1308 white.index=(MagickRealType) i;
1309 }
1310 histogram=(MagickPixelPacket *) RelinquishMagickMemory(histogram);
1311 /*
1312 Stretch the histogram to create the stretched image mapping.
1313 */
1314 (void) ResetMagickMemory(stretch_map,0,(MaxMap+1)*sizeof(*stretch_map));
cristyb5d5f722009-11-04 03:03:49 +00001315#if defined(MAGICKCORE_OPENMP_SUPPORT)
1316 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001317#endif
cristybb503372010-05-27 20:51:26 +00001318 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +00001319 {
1320 if ((channel & RedChannel) != 0)
1321 {
cristybb503372010-05-27 20:51:26 +00001322 if (i < (ssize_t) black.red)
cristy3ed852e2009-09-05 21:47:34 +00001323 stretch_map[i].red=0.0;
1324 else
cristybb503372010-05-27 20:51:26 +00001325 if (i > (ssize_t) white.red)
cristy3ed852e2009-09-05 21:47:34 +00001326 stretch_map[i].red=(MagickRealType) QuantumRange;
1327 else
1328 if (black.red != white.red)
1329 stretch_map[i].red=(MagickRealType) ScaleMapToQuantum(
1330 (MagickRealType) (MaxMap*(i-black.red)/(white.red-black.red)));
1331 }
1332 if ((channel & GreenChannel) != 0)
1333 {
cristybb503372010-05-27 20:51:26 +00001334 if (i < (ssize_t) black.green)
cristy3ed852e2009-09-05 21:47:34 +00001335 stretch_map[i].green=0.0;
1336 else
cristybb503372010-05-27 20:51:26 +00001337 if (i > (ssize_t) white.green)
cristy3ed852e2009-09-05 21:47:34 +00001338 stretch_map[i].green=(MagickRealType) QuantumRange;
1339 else
1340 if (black.green != white.green)
1341 stretch_map[i].green=(MagickRealType) ScaleMapToQuantum(
1342 (MagickRealType) (MaxMap*(i-black.green)/(white.green-
1343 black.green)));
1344 }
1345 if ((channel & BlueChannel) != 0)
1346 {
cristybb503372010-05-27 20:51:26 +00001347 if (i < (ssize_t) black.blue)
cristy3ed852e2009-09-05 21:47:34 +00001348 stretch_map[i].blue=0.0;
1349 else
cristybb503372010-05-27 20:51:26 +00001350 if (i > (ssize_t) white.blue)
cristy3ed852e2009-09-05 21:47:34 +00001351 stretch_map[i].blue=(MagickRealType) QuantumRange;
1352 else
1353 if (black.blue != white.blue)
1354 stretch_map[i].blue=(MagickRealType) ScaleMapToQuantum(
1355 (MagickRealType) (MaxMap*(i-black.blue)/(white.blue-
1356 black.blue)));
1357 }
1358 if ((channel & OpacityChannel) != 0)
1359 {
cristybb503372010-05-27 20:51:26 +00001360 if (i < (ssize_t) black.opacity)
cristy3ed852e2009-09-05 21:47:34 +00001361 stretch_map[i].opacity=0.0;
1362 else
cristybb503372010-05-27 20:51:26 +00001363 if (i > (ssize_t) white.opacity)
cristy3ed852e2009-09-05 21:47:34 +00001364 stretch_map[i].opacity=(MagickRealType) QuantumRange;
1365 else
1366 if (black.opacity != white.opacity)
1367 stretch_map[i].opacity=(MagickRealType) ScaleMapToQuantum(
1368 (MagickRealType) (MaxMap*(i-black.opacity)/(white.opacity-
1369 black.opacity)));
1370 }
1371 if (((channel & IndexChannel) != 0) &&
1372 (image->colorspace == CMYKColorspace))
1373 {
cristybb503372010-05-27 20:51:26 +00001374 if (i < (ssize_t) black.index)
cristy3ed852e2009-09-05 21:47:34 +00001375 stretch_map[i].index=0.0;
1376 else
cristybb503372010-05-27 20:51:26 +00001377 if (i > (ssize_t) white.index)
cristy3ed852e2009-09-05 21:47:34 +00001378 stretch_map[i].index=(MagickRealType) QuantumRange;
1379 else
1380 if (black.index != white.index)
1381 stretch_map[i].index=(MagickRealType) ScaleMapToQuantum(
1382 (MagickRealType) (MaxMap*(i-black.index)/(white.index-
1383 black.index)));
1384 }
1385 }
1386 /*
1387 Stretch the image.
1388 */
1389 if (((channel & OpacityChannel) != 0) || (((channel & IndexChannel) != 0) &&
1390 (image->colorspace == CMYKColorspace)))
1391 image->storage_class=DirectClass;
1392 if (image->storage_class == PseudoClass)
1393 {
1394 /*
1395 Stretch colormap.
1396 */
cristyb5d5f722009-11-04 03:03:49 +00001397#if defined(MAGICKCORE_OPENMP_SUPPORT)
1398 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001399#endif
cristybb503372010-05-27 20:51:26 +00001400 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00001401 {
1402 if ((channel & RedChannel) != 0)
1403 {
1404 if (black.red != white.red)
cristyce70c172010-01-07 17:15:30 +00001405 image->colormap[i].red=ClampToQuantum(stretch_map[
cristy3ed852e2009-09-05 21:47:34 +00001406 ScaleQuantumToMap(image->colormap[i].red)].red);
1407 }
1408 if ((channel & GreenChannel) != 0)
1409 {
1410 if (black.green != white.green)
cristyce70c172010-01-07 17:15:30 +00001411 image->colormap[i].green=ClampToQuantum(stretch_map[
cristy3ed852e2009-09-05 21:47:34 +00001412 ScaleQuantumToMap(image->colormap[i].green)].green);
1413 }
1414 if ((channel & BlueChannel) != 0)
1415 {
1416 if (black.blue != white.blue)
cristyce70c172010-01-07 17:15:30 +00001417 image->colormap[i].blue=ClampToQuantum(stretch_map[
cristy3ed852e2009-09-05 21:47:34 +00001418 ScaleQuantumToMap(image->colormap[i].blue)].blue);
1419 }
1420 if ((channel & OpacityChannel) != 0)
1421 {
1422 if (black.opacity != white.opacity)
cristyce70c172010-01-07 17:15:30 +00001423 image->colormap[i].opacity=ClampToQuantum(stretch_map[
cristy3ed852e2009-09-05 21:47:34 +00001424 ScaleQuantumToMap(image->colormap[i].opacity)].opacity);
1425 }
1426 }
1427 }
1428 /*
1429 Stretch image.
1430 */
1431 status=MagickTrue;
1432 progress=0;
cristyb5d5f722009-11-04 03:03:49 +00001433#if defined(MAGICKCORE_OPENMP_SUPPORT)
1434 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001435#endif
cristybb503372010-05-27 20:51:26 +00001436 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001437 {
1438 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00001439 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00001440
cristybb503372010-05-27 20:51:26 +00001441 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001442 x;
1443
1444 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001445 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001446
1447 if (status == MagickFalse)
1448 continue;
1449 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
1450 if (q == (PixelPacket *) NULL)
1451 {
1452 status=MagickFalse;
1453 continue;
1454 }
1455 indexes=GetCacheViewAuthenticIndexQueue(image_view);
cristybb503372010-05-27 20:51:26 +00001456 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001457 {
1458 if ((channel & RedChannel) != 0)
1459 {
1460 if (black.red != white.red)
cristyce70c172010-01-07 17:15:30 +00001461 q->red=ClampToQuantum(stretch_map[ScaleQuantumToMap(q->red)].red);
cristy3ed852e2009-09-05 21:47:34 +00001462 }
1463 if ((channel & GreenChannel) != 0)
1464 {
1465 if (black.green != white.green)
cristyce70c172010-01-07 17:15:30 +00001466 q->green=ClampToQuantum(stretch_map[ScaleQuantumToMap(
cristy3ed852e2009-09-05 21:47:34 +00001467 q->green)].green);
1468 }
1469 if ((channel & BlueChannel) != 0)
1470 {
1471 if (black.blue != white.blue)
cristyce70c172010-01-07 17:15:30 +00001472 q->blue=ClampToQuantum(stretch_map[ScaleQuantumToMap(
cristy3ed852e2009-09-05 21:47:34 +00001473 q->blue)].blue);
1474 }
1475 if ((channel & OpacityChannel) != 0)
1476 {
1477 if (black.opacity != white.opacity)
cristyce70c172010-01-07 17:15:30 +00001478 q->opacity=ClampToQuantum(stretch_map[ScaleQuantumToMap(
cristy3ed852e2009-09-05 21:47:34 +00001479 q->opacity)].opacity);
1480 }
1481 if (((channel & IndexChannel) != 0) &&
1482 (image->colorspace == CMYKColorspace))
1483 {
1484 if (black.index != white.index)
cristyce70c172010-01-07 17:15:30 +00001485 indexes[x]=(IndexPacket) ClampToQuantum(stretch_map[
cristy3ed852e2009-09-05 21:47:34 +00001486 ScaleQuantumToMap(indexes[x])].index);
1487 }
1488 q++;
1489 }
1490 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
1491 status=MagickFalse;
1492 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1493 {
1494 MagickBooleanType
1495 proceed;
1496
cristyb5d5f722009-11-04 03:03:49 +00001497#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00001498 #pragma omp critical (MagickCore_ContrastStretchImageChannel)
1499#endif
1500 proceed=SetImageProgress(image,ContrastStretchImageTag,progress++,
1501 image->rows);
1502 if (proceed == MagickFalse)
1503 status=MagickFalse;
1504 }
1505 }
1506 image_view=DestroyCacheView(image_view);
1507 stretch_map=(MagickPixelPacket *) RelinquishMagickMemory(stretch_map);
1508 return(status);
1509}
1510
1511/*
1512%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1513% %
1514% %
1515% %
1516% E n h a n c e I m a g e %
1517% %
1518% %
1519% %
1520%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1521%
1522% EnhanceImage() applies a digital filter that improves the quality of a
1523% noisy image.
1524%
1525% The format of the EnhanceImage method is:
1526%
1527% Image *EnhanceImage(const Image *image,ExceptionInfo *exception)
1528%
1529% A description of each parameter follows:
1530%
1531% o image: the image.
1532%
1533% o exception: return any errors or warnings in this structure.
1534%
1535*/
1536MagickExport Image *EnhanceImage(const Image *image,ExceptionInfo *exception)
1537{
1538#define Enhance(weight) \
1539 mean=((MagickRealType) r->red+pixel.red)/2; \
1540 distance=(MagickRealType) r->red-(MagickRealType) pixel.red; \
1541 distance_squared=QuantumScale*(2.0*((MagickRealType) QuantumRange+1.0)+ \
1542 mean)*distance*distance; \
1543 mean=((MagickRealType) r->green+pixel.green)/2; \
1544 distance=(MagickRealType) r->green-(MagickRealType) pixel.green; \
1545 distance_squared+=4.0*distance*distance; \
1546 mean=((MagickRealType) r->blue+pixel.blue)/2; \
1547 distance=(MagickRealType) r->blue-(MagickRealType) pixel.blue; \
1548 distance_squared+=QuantumScale*(3.0*((MagickRealType) \
1549 QuantumRange+1.0)-1.0-mean)*distance*distance; \
1550 mean=((MagickRealType) r->opacity+pixel.opacity)/2; \
1551 distance=(MagickRealType) r->opacity-(MagickRealType) pixel.opacity; \
1552 distance_squared+=QuantumScale*(3.0*((MagickRealType) \
1553 QuantumRange+1.0)-1.0-mean)*distance*distance; \
1554 if (distance_squared < ((MagickRealType) QuantumRange*(MagickRealType) \
1555 QuantumRange/25.0f)) \
1556 { \
1557 aggregate.red+=(weight)*r->red; \
1558 aggregate.green+=(weight)*r->green; \
1559 aggregate.blue+=(weight)*r->blue; \
1560 aggregate.opacity+=(weight)*r->opacity; \
1561 total_weight+=(weight); \
1562 } \
1563 r++;
1564#define EnhanceImageTag "Enhance/Image"
1565
cristyc4c8d132010-01-07 01:58:38 +00001566 CacheView
1567 *enhance_view,
1568 *image_view;
1569
cristy3ed852e2009-09-05 21:47:34 +00001570 Image
1571 *enhance_image;
1572
cristy3ed852e2009-09-05 21:47:34 +00001573 MagickBooleanType
1574 status;
1575
cristybb503372010-05-27 20:51:26 +00001576 MagickOffsetType
1577 progress;
1578
cristy3ed852e2009-09-05 21:47:34 +00001579 MagickPixelPacket
1580 zero;
1581
cristybb503372010-05-27 20:51:26 +00001582 ssize_t
1583 y;
1584
cristy3ed852e2009-09-05 21:47:34 +00001585 /*
1586 Initialize enhanced image attributes.
1587 */
1588 assert(image != (const Image *) NULL);
1589 assert(image->signature == MagickSignature);
1590 if (image->debug != MagickFalse)
1591 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1592 assert(exception != (ExceptionInfo *) NULL);
1593 assert(exception->signature == MagickSignature);
1594 if ((image->columns < 5) || (image->rows < 5))
1595 return((Image *) NULL);
1596 enhance_image=CloneImage(image,image->columns,image->rows,MagickTrue,
1597 exception);
1598 if (enhance_image == (Image *) NULL)
1599 return((Image *) NULL);
1600 if (SetImageStorageClass(enhance_image,DirectClass) == MagickFalse)
1601 {
1602 InheritException(exception,&enhance_image->exception);
1603 enhance_image=DestroyImage(enhance_image);
1604 return((Image *) NULL);
1605 }
1606 /*
1607 Enhance image.
1608 */
1609 status=MagickTrue;
1610 progress=0;
1611 (void) ResetMagickMemory(&zero,0,sizeof(zero));
1612 image_view=AcquireCacheView(image);
1613 enhance_view=AcquireCacheView(enhance_image);
cristyb5d5f722009-11-04 03:03:49 +00001614#if defined(MAGICKCORE_OPENMP_SUPPORT)
1615 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001616#endif
cristybb503372010-05-27 20:51:26 +00001617 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001618 {
1619 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001620 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00001621
cristybb503372010-05-27 20:51:26 +00001622 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001623 x;
1624
1625 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001626 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001627
1628 /*
1629 Read another scan line.
1630 */
1631 if (status == MagickFalse)
1632 continue;
1633 p=GetCacheViewVirtualPixels(image_view,-2,y-2,image->columns+4,5,exception);
1634 q=QueueCacheViewAuthenticPixels(enhance_view,0,y,enhance_image->columns,1,
1635 exception);
1636 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
1637 {
1638 status=MagickFalse;
1639 continue;
1640 }
cristybb503372010-05-27 20:51:26 +00001641 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001642 {
1643 MagickPixelPacket
1644 aggregate;
1645
1646 MagickRealType
1647 distance,
1648 distance_squared,
1649 mean,
1650 total_weight;
1651
1652 PixelPacket
1653 pixel;
1654
1655 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001656 *restrict r;
cristy3ed852e2009-09-05 21:47:34 +00001657
1658 /*
1659 Compute weighted average of target pixel color components.
1660 */
1661 aggregate=zero;
1662 total_weight=0.0;
1663 r=p+2*(image->columns+4)+2;
1664 pixel=(*r);
1665 r=p;
1666 Enhance(5.0); Enhance(8.0); Enhance(10.0); Enhance(8.0); Enhance(5.0);
1667 r=p+(image->columns+4);
1668 Enhance(8.0); Enhance(20.0); Enhance(40.0); Enhance(20.0); Enhance(8.0);
1669 r=p+2*(image->columns+4);
1670 Enhance(10.0); Enhance(40.0); Enhance(80.0); Enhance(40.0); Enhance(10.0);
1671 r=p+3*(image->columns+4);
1672 Enhance(8.0); Enhance(20.0); Enhance(40.0); Enhance(20.0); Enhance(8.0);
1673 r=p+4*(image->columns+4);
1674 Enhance(5.0); Enhance(8.0); Enhance(10.0); Enhance(8.0); Enhance(5.0);
1675 q->red=(Quantum) ((aggregate.red+(total_weight/2)-1)/total_weight);
1676 q->green=(Quantum) ((aggregate.green+(total_weight/2)-1)/total_weight);
1677 q->blue=(Quantum) ((aggregate.blue+(total_weight/2)-1)/total_weight);
1678 q->opacity=(Quantum) ((aggregate.opacity+(total_weight/2)-1)/
1679 total_weight);
1680 p++;
1681 q++;
1682 }
1683 if (SyncCacheViewAuthenticPixels(enhance_view,exception) == MagickFalse)
1684 status=MagickFalse;
1685 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1686 {
1687 MagickBooleanType
1688 proceed;
1689
cristyb5d5f722009-11-04 03:03:49 +00001690#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00001691 #pragma omp critical (MagickCore_EnhanceImage)
1692#endif
1693 proceed=SetImageProgress(image,EnhanceImageTag,progress++,image->rows);
1694 if (proceed == MagickFalse)
1695 status=MagickFalse;
1696 }
1697 }
1698 enhance_view=DestroyCacheView(enhance_view);
1699 image_view=DestroyCacheView(image_view);
1700 return(enhance_image);
1701}
1702
1703/*
1704%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1705% %
1706% %
1707% %
1708% E q u a l i z e I m a g e %
1709% %
1710% %
1711% %
1712%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1713%
1714% EqualizeImage() applies a histogram equalization to the image.
1715%
1716% The format of the EqualizeImage method is:
1717%
1718% MagickBooleanType EqualizeImage(Image *image)
1719% MagickBooleanType EqualizeImageChannel(Image *image,
1720% const ChannelType channel)
1721%
1722% A description of each parameter follows:
1723%
1724% o image: the image.
1725%
1726% o channel: the channel.
1727%
1728*/
1729
1730MagickExport MagickBooleanType EqualizeImage(Image *image)
1731{
1732 return(EqualizeImageChannel(image,DefaultChannels));
1733}
1734
1735MagickExport MagickBooleanType EqualizeImageChannel(Image *image,
1736 const ChannelType channel)
1737{
1738#define EqualizeImageTag "Equalize/Image"
1739
cristyc4c8d132010-01-07 01:58:38 +00001740 CacheView
1741 *image_view;
1742
cristy3ed852e2009-09-05 21:47:34 +00001743 ExceptionInfo
1744 *exception;
1745
cristy3ed852e2009-09-05 21:47:34 +00001746 MagickBooleanType
1747 status;
1748
cristybb503372010-05-27 20:51:26 +00001749 MagickOffsetType
1750 progress;
1751
cristy3ed852e2009-09-05 21:47:34 +00001752 MagickPixelPacket
1753 black,
1754 *equalize_map,
1755 *histogram,
1756 intensity,
1757 *map,
1758 white;
1759
cristybb503372010-05-27 20:51:26 +00001760 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001761 i;
1762
cristybb503372010-05-27 20:51:26 +00001763 ssize_t
1764 y;
1765
cristy3ed852e2009-09-05 21:47:34 +00001766 /*
1767 Allocate and initialize histogram arrays.
1768 */
1769 assert(image != (Image *) NULL);
1770 assert(image->signature == MagickSignature);
1771 if (image->debug != MagickFalse)
1772 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1773 equalize_map=(MagickPixelPacket *) AcquireQuantumMemory(MaxMap+1UL,
1774 sizeof(*equalize_map));
1775 histogram=(MagickPixelPacket *) AcquireQuantumMemory(MaxMap+1UL,
1776 sizeof(*histogram));
1777 map=(MagickPixelPacket *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*map));
1778 if ((equalize_map == (MagickPixelPacket *) NULL) ||
1779 (histogram == (MagickPixelPacket *) NULL) ||
1780 (map == (MagickPixelPacket *) NULL))
1781 {
1782 if (map != (MagickPixelPacket *) NULL)
1783 map=(MagickPixelPacket *) RelinquishMagickMemory(map);
1784 if (histogram != (MagickPixelPacket *) NULL)
1785 histogram=(MagickPixelPacket *) RelinquishMagickMemory(histogram);
1786 if (equalize_map != (MagickPixelPacket *) NULL)
1787 equalize_map=(MagickPixelPacket *) RelinquishMagickMemory(equalize_map);
1788 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
1789 image->filename);
1790 }
1791 /*
1792 Form histogram.
1793 */
1794 (void) ResetMagickMemory(histogram,0,(MaxMap+1)*sizeof(*histogram));
1795 exception=(&image->exception);
cristybb503372010-05-27 20:51:26 +00001796 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001797 {
1798 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00001799 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00001800
1801 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001802 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00001803
cristybb503372010-05-27 20:51:26 +00001804 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001805 x;
1806
1807 p=GetVirtualPixels(image,0,y,image->columns,1,exception);
1808 if (p == (const PixelPacket *) NULL)
1809 break;
1810 indexes=GetVirtualIndexQueue(image);
cristybb503372010-05-27 20:51:26 +00001811 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001812 {
1813 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001814 histogram[ScaleQuantumToMap(GetRedPixelComponent(p))].red++;
cristy3ed852e2009-09-05 21:47:34 +00001815 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001816 histogram[ScaleQuantumToMap(GetGreenPixelComponent(p))].green++;
cristy3ed852e2009-09-05 21:47:34 +00001817 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001818 histogram[ScaleQuantumToMap(GetBluePixelComponent(p))].blue++;
cristy3ed852e2009-09-05 21:47:34 +00001819 if ((channel & OpacityChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001820 histogram[ScaleQuantumToMap(GetOpacityPixelComponent(p))].opacity++;
cristy3ed852e2009-09-05 21:47:34 +00001821 if (((channel & IndexChannel) != 0) &&
1822 (image->colorspace == CMYKColorspace))
1823 histogram[ScaleQuantumToMap(indexes[x])].index++;
1824 p++;
1825 }
1826 }
1827 /*
1828 Integrate the histogram to get the equalization map.
1829 */
1830 (void) ResetMagickMemory(&intensity,0,sizeof(intensity));
cristybb503372010-05-27 20:51:26 +00001831 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +00001832 {
1833 if ((channel & RedChannel) != 0)
1834 intensity.red+=histogram[i].red;
1835 if ((channel & GreenChannel) != 0)
1836 intensity.green+=histogram[i].green;
1837 if ((channel & BlueChannel) != 0)
1838 intensity.blue+=histogram[i].blue;
1839 if ((channel & OpacityChannel) != 0)
1840 intensity.opacity+=histogram[i].opacity;
1841 if (((channel & IndexChannel) != 0) &&
1842 (image->colorspace == CMYKColorspace))
1843 intensity.index+=histogram[i].index;
1844 map[i]=intensity;
1845 }
1846 black=map[0];
1847 white=map[(int) MaxMap];
1848 (void) ResetMagickMemory(equalize_map,0,(MaxMap+1)*sizeof(*equalize_map));
cristyb5d5f722009-11-04 03:03:49 +00001849#if defined(MAGICKCORE_OPENMP_SUPPORT)
1850 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001851#endif
cristybb503372010-05-27 20:51:26 +00001852 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +00001853 {
1854 if (((channel & RedChannel) != 0) && (white.red != black.red))
1855 equalize_map[i].red=(MagickRealType) ScaleMapToQuantum((MagickRealType)
1856 ((MaxMap*(map[i].red-black.red))/(white.red-black.red)));
1857 if (((channel & GreenChannel) != 0) && (white.green != black.green))
1858 equalize_map[i].green=(MagickRealType) ScaleMapToQuantum((MagickRealType)
1859 ((MaxMap*(map[i].green-black.green))/(white.green-black.green)));
1860 if (((channel & BlueChannel) != 0) && (white.blue != black.blue))
1861 equalize_map[i].blue=(MagickRealType) ScaleMapToQuantum((MagickRealType)
1862 ((MaxMap*(map[i].blue-black.blue))/(white.blue-black.blue)));
1863 if (((channel & OpacityChannel) != 0) && (white.opacity != black.opacity))
1864 equalize_map[i].opacity=(MagickRealType) ScaleMapToQuantum(
1865 (MagickRealType) ((MaxMap*(map[i].opacity-black.opacity))/
1866 (white.opacity-black.opacity)));
1867 if ((((channel & IndexChannel) != 0) &&
1868 (image->colorspace == CMYKColorspace)) &&
1869 (white.index != black.index))
1870 equalize_map[i].index=(MagickRealType) ScaleMapToQuantum((MagickRealType)
1871 ((MaxMap*(map[i].index-black.index))/(white.index-black.index)));
1872 }
1873 histogram=(MagickPixelPacket *) RelinquishMagickMemory(histogram);
1874 map=(MagickPixelPacket *) RelinquishMagickMemory(map);
1875 if (image->storage_class == PseudoClass)
1876 {
1877 /*
1878 Equalize colormap.
1879 */
cristyb5d5f722009-11-04 03:03:49 +00001880#if defined(MAGICKCORE_OPENMP_SUPPORT)
1881 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001882#endif
cristybb503372010-05-27 20:51:26 +00001883 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00001884 {
1885 if (((channel & RedChannel) != 0) && (white.red != black.red))
cristyce70c172010-01-07 17:15:30 +00001886 image->colormap[i].red=ClampToQuantum(equalize_map[
cristy3ed852e2009-09-05 21:47:34 +00001887 ScaleQuantumToMap(image->colormap[i].red)].red);
1888 if (((channel & GreenChannel) != 0) && (white.green != black.green))
cristyce70c172010-01-07 17:15:30 +00001889 image->colormap[i].green=ClampToQuantum(equalize_map[
cristy3ed852e2009-09-05 21:47:34 +00001890 ScaleQuantumToMap(image->colormap[i].green)].green);
1891 if (((channel & BlueChannel) != 0) && (white.blue != black.blue))
cristyce70c172010-01-07 17:15:30 +00001892 image->colormap[i].blue=ClampToQuantum(equalize_map[
cristy3ed852e2009-09-05 21:47:34 +00001893 ScaleQuantumToMap(image->colormap[i].blue)].blue);
1894 if (((channel & OpacityChannel) != 0) &&
1895 (white.opacity != black.opacity))
cristyce70c172010-01-07 17:15:30 +00001896 image->colormap[i].opacity=ClampToQuantum(equalize_map[
cristy3ed852e2009-09-05 21:47:34 +00001897 ScaleQuantumToMap(image->colormap[i].opacity)].opacity);
1898 }
1899 }
1900 /*
1901 Equalize image.
1902 */
1903 status=MagickTrue;
1904 progress=0;
1905 exception=(&image->exception);
1906 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00001907#if defined(MAGICKCORE_OPENMP_SUPPORT)
1908 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001909#endif
cristybb503372010-05-27 20:51:26 +00001910 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001911 {
1912 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00001913 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00001914
cristybb503372010-05-27 20:51:26 +00001915 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001916 x;
1917
1918 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001919 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001920
1921 if (status == MagickFalse)
1922 continue;
1923 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
1924 if (q == (PixelPacket *) NULL)
1925 {
1926 status=MagickFalse;
1927 continue;
1928 }
1929 indexes=GetCacheViewAuthenticIndexQueue(image_view);
cristybb503372010-05-27 20:51:26 +00001930 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001931 {
1932 if (((channel & RedChannel) != 0) && (white.red != black.red))
cristyce70c172010-01-07 17:15:30 +00001933 q->red=ClampToQuantum(equalize_map[ScaleQuantumToMap(q->red)].red);
cristy3ed852e2009-09-05 21:47:34 +00001934 if (((channel & GreenChannel) != 0) && (white.green != black.green))
cristyce70c172010-01-07 17:15:30 +00001935 q->green=ClampToQuantum(equalize_map[ScaleQuantumToMap(
cristy3ed852e2009-09-05 21:47:34 +00001936 q->green)].green);
1937 if (((channel & BlueChannel) != 0) && (white.blue != black.blue))
cristyce70c172010-01-07 17:15:30 +00001938 q->blue=ClampToQuantum(equalize_map[ScaleQuantumToMap(q->blue)].blue);
cristy3ed852e2009-09-05 21:47:34 +00001939 if (((channel & OpacityChannel) != 0) && (white.opacity != black.opacity))
cristyce70c172010-01-07 17:15:30 +00001940 q->opacity=ClampToQuantum(equalize_map[ScaleQuantumToMap(
cristy3ed852e2009-09-05 21:47:34 +00001941 q->opacity)].opacity);
1942 if ((((channel & IndexChannel) != 0) &&
1943 (image->colorspace == CMYKColorspace)) &&
1944 (white.index != black.index))
cristyce70c172010-01-07 17:15:30 +00001945 indexes[x]=ClampToQuantum(equalize_map[ScaleQuantumToMap(
cristy3ed852e2009-09-05 21:47:34 +00001946 indexes[x])].index);
1947 q++;
1948 }
1949 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
1950 status=MagickFalse;
1951 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1952 {
1953 MagickBooleanType
1954 proceed;
1955
cristyb5d5f722009-11-04 03:03:49 +00001956#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00001957 #pragma omp critical (MagickCore_EqualizeImageChannel)
1958#endif
1959 proceed=SetImageProgress(image,EqualizeImageTag,progress++,image->rows);
1960 if (proceed == MagickFalse)
1961 status=MagickFalse;
1962 }
1963 }
1964 image_view=DestroyCacheView(image_view);
1965 equalize_map=(MagickPixelPacket *) RelinquishMagickMemory(equalize_map);
1966 return(status);
1967}
1968
1969/*
1970%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1971% %
1972% %
1973% %
1974% G a m m a I m a g e %
1975% %
1976% %
1977% %
1978%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1979%
1980% GammaImage() gamma-corrects a particular image channel. The same
1981% image viewed on different devices will have perceptual differences in the
1982% way the image's intensities are represented on the screen. Specify
1983% individual gamma levels for the red, green, and blue channels, or adjust
1984% all three with the gamma parameter. Values typically range from 0.8 to 2.3.
1985%
1986% You can also reduce the influence of a particular channel with a gamma
1987% value of 0.
1988%
1989% The format of the GammaImage method is:
1990%
1991% MagickBooleanType GammaImage(Image *image,const double gamma)
1992% MagickBooleanType GammaImageChannel(Image *image,
1993% const ChannelType channel,const double gamma)
1994%
1995% A description of each parameter follows:
1996%
1997% o image: the image.
1998%
1999% o channel: the channel.
2000%
2001% o gamma: the image gamma.
2002%
2003*/
2004MagickExport MagickBooleanType GammaImage(Image *image,const char *level)
2005{
2006 GeometryInfo
2007 geometry_info;
2008
2009 MagickPixelPacket
2010 gamma;
2011
2012 MagickStatusType
2013 flags,
2014 status;
2015
2016 assert(image != (Image *) NULL);
2017 assert(image->signature == MagickSignature);
2018 if (image->debug != MagickFalse)
2019 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2020 if (level == (char *) NULL)
2021 return(MagickFalse);
2022 flags=ParseGeometry(level,&geometry_info);
2023 gamma.red=geometry_info.rho;
2024 gamma.green=geometry_info.sigma;
2025 if ((flags & SigmaValue) == 0)
2026 gamma.green=gamma.red;
2027 gamma.blue=geometry_info.xi;
2028 if ((flags & XiValue) == 0)
2029 gamma.blue=gamma.red;
2030 if ((gamma.red == 1.0) && (gamma.green == 1.0) && (gamma.blue == 1.0))
2031 return(MagickTrue);
2032 if ((gamma.red == gamma.green) && (gamma.green == gamma.blue))
2033 status=GammaImageChannel(image,(const ChannelType) (RedChannel |
2034 GreenChannel | BlueChannel),(double) gamma.red);
2035 else
2036 {
2037 status=GammaImageChannel(image,RedChannel,(double) gamma.red);
2038 status|=GammaImageChannel(image,GreenChannel,(double) gamma.green);
2039 status|=GammaImageChannel(image,BlueChannel,(double) gamma.blue);
2040 }
2041 return(status != 0 ? MagickTrue : MagickFalse);
2042}
2043
2044MagickExport MagickBooleanType GammaImageChannel(Image *image,
2045 const ChannelType channel,const double gamma)
2046{
2047#define GammaCorrectImageTag "GammaCorrect/Image"
2048
cristyc4c8d132010-01-07 01:58:38 +00002049 CacheView
2050 *image_view;
2051
cristy3ed852e2009-09-05 21:47:34 +00002052 ExceptionInfo
2053 *exception;
2054
cristy3ed852e2009-09-05 21:47:34 +00002055 MagickBooleanType
2056 status;
2057
cristybb503372010-05-27 20:51:26 +00002058 MagickOffsetType
2059 progress;
2060
cristy3ed852e2009-09-05 21:47:34 +00002061 Quantum
2062 *gamma_map;
2063
cristybb503372010-05-27 20:51:26 +00002064 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002065 i;
2066
cristybb503372010-05-27 20:51:26 +00002067 ssize_t
2068 y;
2069
cristy3ed852e2009-09-05 21:47:34 +00002070 /*
2071 Allocate and initialize gamma maps.
2072 */
2073 assert(image != (Image *) NULL);
2074 assert(image->signature == MagickSignature);
2075 if (image->debug != MagickFalse)
2076 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2077 if (gamma == 1.0)
2078 return(MagickTrue);
2079 gamma_map=(Quantum *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*gamma_map));
2080 if (gamma_map == (Quantum *) NULL)
2081 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
2082 image->filename);
2083 (void) ResetMagickMemory(gamma_map,0,(MaxMap+1)*sizeof(*gamma_map));
2084 if (gamma != 0.0)
cristyb5d5f722009-11-04 03:03:49 +00002085#if defined(MAGICKCORE_OPENMP_SUPPORT)
2086 #pragma omp parallel for schedule(dynamic,4)
cristy3ed852e2009-09-05 21:47:34 +00002087#endif
cristybb503372010-05-27 20:51:26 +00002088 for (i=0; i <= (ssize_t) MaxMap; i++)
cristyce70c172010-01-07 17:15:30 +00002089 gamma_map[i]=ClampToQuantum((MagickRealType) ScaleMapToQuantum((
cristy3ed852e2009-09-05 21:47:34 +00002090 MagickRealType) (MaxMap*pow((double) i/MaxMap,1.0/gamma))));
2091 if (image->storage_class == PseudoClass)
2092 {
2093 /*
2094 Gamma-correct colormap.
2095 */
cristyb5d5f722009-11-04 03:03:49 +00002096#if defined(MAGICKCORE_OPENMP_SUPPORT)
2097 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002098#endif
cristybb503372010-05-27 20:51:26 +00002099 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00002100 {
2101 if ((channel & RedChannel) != 0)
2102 image->colormap[i].red=gamma_map[
2103 ScaleQuantumToMap(image->colormap[i].red)];
2104 if ((channel & GreenChannel) != 0)
2105 image->colormap[i].green=gamma_map[
2106 ScaleQuantumToMap(image->colormap[i].green)];
2107 if ((channel & BlueChannel) != 0)
2108 image->colormap[i].blue=gamma_map[
2109 ScaleQuantumToMap(image->colormap[i].blue)];
2110 if ((channel & OpacityChannel) != 0)
2111 {
2112 if (image->matte == MagickFalse)
2113 image->colormap[i].opacity=gamma_map[
2114 ScaleQuantumToMap(image->colormap[i].opacity)];
2115 else
2116 image->colormap[i].opacity=(Quantum) QuantumRange-
2117 gamma_map[ScaleQuantumToMap((Quantum) (QuantumRange-
2118 image->colormap[i].opacity))];
2119 }
2120 }
2121 }
2122 /*
2123 Gamma-correct image.
2124 */
2125 status=MagickTrue;
2126 progress=0;
2127 exception=(&image->exception);
2128 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00002129#if defined(MAGICKCORE_OPENMP_SUPPORT)
2130 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002131#endif
cristybb503372010-05-27 20:51:26 +00002132 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002133 {
2134 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00002135 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00002136
cristybb503372010-05-27 20:51:26 +00002137 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002138 x;
2139
2140 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00002141 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002142
2143 if (status == MagickFalse)
2144 continue;
2145 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2146 if (q == (PixelPacket *) NULL)
2147 {
2148 status=MagickFalse;
2149 continue;
2150 }
2151 indexes=GetCacheViewAuthenticIndexQueue(image_view);
cristybb503372010-05-27 20:51:26 +00002152 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002153 {
cristy6cbd7f52009-10-17 16:06:51 +00002154 if (channel == DefaultChannels)
cristy3ed852e2009-09-05 21:47:34 +00002155 {
cristy6cbd7f52009-10-17 16:06:51 +00002156 q->red=gamma_map[ScaleQuantumToMap(q->red)];
2157 q->green=gamma_map[ScaleQuantumToMap(q->green)];
2158 q->blue=gamma_map[ScaleQuantumToMap(q->blue)];
2159 }
2160 else
2161 {
2162 if ((channel & RedChannel) != 0)
2163 q->red=gamma_map[ScaleQuantumToMap(q->red)];
2164 if ((channel & GreenChannel) != 0)
2165 q->green=gamma_map[ScaleQuantumToMap(q->green)];
2166 if ((channel & BlueChannel) != 0)
2167 q->blue=gamma_map[ScaleQuantumToMap(q->blue)];
2168 if ((channel & OpacityChannel) != 0)
2169 {
2170 if (image->matte == MagickFalse)
2171 q->opacity=gamma_map[ScaleQuantumToMap(q->opacity)];
2172 else
2173 q->opacity=(Quantum) QuantumRange-gamma_map[
cristy46f08202010-01-10 04:04:21 +00002174 ScaleQuantumToMap((Quantum) GetAlphaPixelComponent(q))];
cristy6cbd7f52009-10-17 16:06:51 +00002175 }
cristy3ed852e2009-09-05 21:47:34 +00002176 }
2177 q++;
2178 }
2179 if (((channel & IndexChannel) != 0) &&
2180 (image->colorspace == CMYKColorspace))
cristybb503372010-05-27 20:51:26 +00002181 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002182 indexes[x]=gamma_map[ScaleQuantumToMap(indexes[x])];
2183 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2184 status=MagickFalse;
2185 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2186 {
2187 MagickBooleanType
2188 proceed;
2189
cristyb5d5f722009-11-04 03:03:49 +00002190#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00002191 #pragma omp critical (MagickCore_GammaImageChannel)
2192#endif
2193 proceed=SetImageProgress(image,GammaCorrectImageTag,progress++,
2194 image->rows);
2195 if (proceed == MagickFalse)
2196 status=MagickFalse;
2197 }
2198 }
2199 image_view=DestroyCacheView(image_view);
2200 gamma_map=(Quantum *) RelinquishMagickMemory(gamma_map);
2201 if (image->gamma != 0.0)
2202 image->gamma*=gamma;
2203 return(status);
2204}
2205
2206/*
2207%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2208% %
2209% %
2210% %
2211% H a l d C l u t I m a g e %
2212% %
2213% %
2214% %
2215%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2216%
2217% HaldClutImage() applies a Hald color lookup table to the image. A Hald
2218% color lookup table is a 3-dimensional color cube mapped to 2 dimensions.
2219% Create it with the HALD coder. You can apply any color transformation to
2220% the Hald image and then use this method to apply the transform to the
2221% image.
2222%
2223% The format of the HaldClutImage method is:
2224%
2225% MagickBooleanType HaldClutImage(Image *image,Image *hald_image)
2226% MagickBooleanType HaldClutImageChannel(Image *image,
2227% const ChannelType channel,Image *hald_image)
2228%
2229% A description of each parameter follows:
2230%
2231% o image: the image, which is replaced by indexed CLUT values
2232%
2233% o hald_image: the color lookup table image for replacement color values.
2234%
2235% o channel: the channel.
2236%
2237*/
2238
2239static inline size_t MagickMin(const size_t x,const size_t y)
2240{
2241 if (x < y)
2242 return(x);
2243 return(y);
2244}
2245
2246MagickExport MagickBooleanType HaldClutImage(Image *image,
2247 const Image *hald_image)
2248{
2249 return(HaldClutImageChannel(image,DefaultChannels,hald_image));
2250}
2251
2252MagickExport MagickBooleanType HaldClutImageChannel(Image *image,
2253 const ChannelType channel,const Image *hald_image)
2254{
2255#define HaldClutImageTag "Clut/Image"
2256
2257 typedef struct _HaldInfo
2258 {
2259 MagickRealType
2260 x,
2261 y,
2262 z;
2263 } HaldInfo;
2264
cristyfa112112010-01-04 17:48:07 +00002265 CacheView
2266 *image_view;
2267
cristy3ed852e2009-09-05 21:47:34 +00002268 double
2269 width;
2270
2271 ExceptionInfo
2272 *exception;
2273
cristy3ed852e2009-09-05 21:47:34 +00002274 MagickBooleanType
2275 status;
2276
cristybb503372010-05-27 20:51:26 +00002277 MagickOffsetType
2278 progress;
2279
cristy3ed852e2009-09-05 21:47:34 +00002280 MagickPixelPacket
2281 zero;
2282
2283 ResampleFilter
cristyfa112112010-01-04 17:48:07 +00002284 **restrict resample_filter;
cristy3ed852e2009-09-05 21:47:34 +00002285
2286 size_t
2287 cube_size,
2288 length,
2289 level;
2290
cristybb503372010-05-27 20:51:26 +00002291 ssize_t
2292 y;
2293
cristy3ed852e2009-09-05 21:47:34 +00002294 assert(image != (Image *) NULL);
2295 assert(image->signature == MagickSignature);
2296 if (image->debug != MagickFalse)
2297 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2298 assert(hald_image != (Image *) NULL);
2299 assert(hald_image->signature == MagickSignature);
2300 if (SetImageStorageClass(image,DirectClass) == MagickFalse)
2301 return(MagickFalse);
2302 if (image->matte == MagickFalse)
2303 (void) SetImageAlphaChannel(image,OpaqueAlphaChannel);
2304 /*
2305 Hald clut image.
2306 */
2307 status=MagickTrue;
2308 progress=0;
2309 length=MagickMin(hald_image->columns,hald_image->rows);
2310 for (level=2; (level*level*level) < length; level++) ;
2311 level*=level;
2312 cube_size=level*level;
2313 width=(double) hald_image->columns;
2314 GetMagickPixelPacket(hald_image,&zero);
2315 exception=(&image->exception);
cristyb2a11ae2010-02-22 00:53:36 +00002316 resample_filter=AcquireResampleFilterThreadSet(hald_image,
2317 UndefinedVirtualPixelMethod,MagickTrue,exception);
cristy3ed852e2009-09-05 21:47:34 +00002318 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00002319#if defined(MAGICKCORE_OPENMP_SUPPORT)
2320 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002321#endif
cristybb503372010-05-27 20:51:26 +00002322 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002323 {
2324 double
2325 offset;
2326
2327 HaldInfo
2328 point;
2329
2330 MagickPixelPacket
2331 pixel,
2332 pixel1,
2333 pixel2,
2334 pixel3,
2335 pixel4;
2336
2337 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00002338 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00002339
cristybb503372010-05-27 20:51:26 +00002340 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002341 id,
2342 x;
2343
2344 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00002345 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002346
2347 if (status == MagickFalse)
2348 continue;
2349 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2350 if (q == (PixelPacket *) NULL)
2351 {
2352 status=MagickFalse;
2353 continue;
2354 }
2355 indexes=GetCacheViewAuthenticIndexQueue(image_view);
2356 pixel=zero;
2357 pixel1=zero;
2358 pixel2=zero;
2359 pixel3=zero;
2360 pixel4=zero;
2361 id=GetOpenMPThreadId();
cristybb503372010-05-27 20:51:26 +00002362 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002363 {
2364 point.x=QuantumScale*(level-1.0)*q->red;
2365 point.y=QuantumScale*(level-1.0)*q->green;
2366 point.z=QuantumScale*(level-1.0)*q->blue;
2367 offset=point.x+level*floor(point.y)+cube_size*floor(point.z);
2368 point.x-=floor(point.x);
2369 point.y-=floor(point.y);
2370 point.z-=floor(point.z);
2371 (void) ResamplePixelColor(resample_filter[id],fmod(offset,width),
2372 floor(offset/width),&pixel1);
2373 (void) ResamplePixelColor(resample_filter[id],fmod(offset+level,width),
2374 floor((offset+level)/width),&pixel2);
2375 MagickPixelCompositeAreaBlend(&pixel1,pixel1.opacity,&pixel2,
2376 pixel2.opacity,point.y,&pixel3);
2377 offset+=cube_size;
2378 (void) ResamplePixelColor(resample_filter[id],fmod(offset,width),
2379 floor(offset/width),&pixel1);
2380 (void) ResamplePixelColor(resample_filter[id],fmod(offset+level,width),
2381 floor((offset+level)/width),&pixel2);
2382 MagickPixelCompositeAreaBlend(&pixel1,pixel1.opacity,&pixel2,
2383 pixel2.opacity,point.y,&pixel4);
2384 MagickPixelCompositeAreaBlend(&pixel3,pixel3.opacity,&pixel4,
2385 pixel4.opacity,point.z,&pixel);
2386 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00002387 SetRedPixelComponent(q,ClampRedPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00002388 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00002389 SetGreenPixelComponent(q,ClampGreenPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00002390 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00002391 SetBluePixelComponent(q,ClampBluePixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00002392 if (((channel & OpacityChannel) != 0) && (image->matte != MagickFalse))
cristyce70c172010-01-07 17:15:30 +00002393 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00002394 if (((channel & IndexChannel) != 0) &&
2395 (image->colorspace == CMYKColorspace))
cristyce70c172010-01-07 17:15:30 +00002396 indexes[x]=ClampToQuantum(pixel.index);
cristy3ed852e2009-09-05 21:47:34 +00002397 q++;
2398 }
2399 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2400 status=MagickFalse;
2401 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2402 {
2403 MagickBooleanType
2404 proceed;
2405
cristyb5d5f722009-11-04 03:03:49 +00002406#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00002407 #pragma omp critical (MagickCore_HaldClutImageChannel)
2408#endif
2409 proceed=SetImageProgress(image,HaldClutImageTag,progress++,image->rows);
2410 if (proceed == MagickFalse)
2411 status=MagickFalse;
2412 }
2413 }
2414 image_view=DestroyCacheView(image_view);
2415 resample_filter=DestroyResampleFilterThreadSet(resample_filter);
2416 return(status);
2417}
2418
2419/*
2420%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2421% %
2422% %
2423% %
2424% L e v e l I m a g e %
2425% %
2426% %
2427% %
2428%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2429%
2430% LevelImage() adjusts the levels of a particular image channel by
2431% scaling the colors falling between specified white and black points to
2432% the full available quantum range.
2433%
2434% The parameters provided represent the black, and white points. The black
2435% point specifies the darkest color in the image. Colors darker than the
2436% black point are set to zero. White point specifies the lightest color in
2437% the image. Colors brighter than the white point are set to the maximum
2438% quantum value.
2439%
2440% If a '!' flag is given, map black and white colors to the given levels
2441% rather than mapping those levels to black and white. See
2442% LevelizeImageChannel() and LevelizeImageChannel(), below.
2443%
2444% Gamma specifies a gamma correction to apply to the image.
2445%
2446% The format of the LevelImage method is:
2447%
2448% MagickBooleanType LevelImage(Image *image,const char *levels)
2449%
2450% A description of each parameter follows:
2451%
2452% o image: the image.
2453%
2454% o levels: Specify the levels where the black and white points have the
2455% range of 0-QuantumRange, and gamma has the range 0-10 (e.g. 10x90%+2).
2456% A '!' flag inverts the re-mapping.
2457%
2458*/
2459
2460MagickExport MagickBooleanType LevelImage(Image *image,const char *levels)
2461{
2462 double
2463 black_point,
2464 gamma,
2465 white_point;
2466
2467 GeometryInfo
2468 geometry_info;
2469
2470 MagickBooleanType
2471 status;
2472
2473 MagickStatusType
2474 flags;
2475
2476 /*
2477 Parse levels.
2478 */
2479 if (levels == (char *) NULL)
2480 return(MagickFalse);
2481 flags=ParseGeometry(levels,&geometry_info);
2482 black_point=geometry_info.rho;
2483 white_point=(double) QuantumRange;
2484 if ((flags & SigmaValue) != 0)
2485 white_point=geometry_info.sigma;
2486 gamma=1.0;
2487 if ((flags & XiValue) != 0)
2488 gamma=geometry_info.xi;
2489 if ((flags & PercentValue) != 0)
2490 {
2491 black_point*=(double) image->columns*image->rows/100.0;
2492 white_point*=(double) image->columns*image->rows/100.0;
2493 }
2494 if ((flags & SigmaValue) == 0)
2495 white_point=(double) QuantumRange-black_point;
2496 if ((flags & AspectValue ) == 0)
2497 status=LevelImageChannel(image,DefaultChannels,black_point,white_point,
2498 gamma);
2499 else
cristy308b4e62009-09-21 14:40:44 +00002500 status=LevelizeImage(image,black_point,white_point,gamma);
cristy3ed852e2009-09-05 21:47:34 +00002501 return(status);
2502}
2503
2504/*
2505%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2506% %
2507% %
2508% %
cristy308b4e62009-09-21 14:40:44 +00002509% L e v e l i z e I m a g e %
cristy3ed852e2009-09-05 21:47:34 +00002510% %
2511% %
2512% %
2513%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2514%
cristy308b4e62009-09-21 14:40:44 +00002515% LevelizeImage() applies the normal level operation to the image, spreading
2516% out the values between the black and white points over the entire range of
2517% values. Gamma correction is also applied after the values has been mapped.
cristy3ed852e2009-09-05 21:47:34 +00002518%
2519% It is typically used to improve image contrast, or to provide a controlled
2520% linear threshold for the image. If the black and white points are set to
2521% the minimum and maximum values found in the image, the image can be
2522% normalized. or by swapping black and white values, negate the image.
2523%
cristy308b4e62009-09-21 14:40:44 +00002524% The format of the LevelizeImage method is:
cristy3ed852e2009-09-05 21:47:34 +00002525%
cristy308b4e62009-09-21 14:40:44 +00002526% MagickBooleanType LevelizeImage(Image *image,const double black_point,
2527% const double white_point,const double gamma)
2528% MagickBooleanType LevelizeImageChannel(Image *image,
2529% const ChannelType channel,const double black_point,
2530% const double white_point,const double gamma)
cristy3ed852e2009-09-05 21:47:34 +00002531%
2532% A description of each parameter follows:
2533%
2534% o image: the image.
2535%
2536% o channel: the channel.
2537%
2538% o black_point: The level which is to be mapped to zero (black)
2539%
2540% o white_point: The level which is to be mapped to QuantiumRange (white)
2541%
2542% o gamma: adjust gamma by this factor before mapping values.
2543% use 1.0 for purely linear stretching of image color values
2544%
2545*/
cristy308b4e62009-09-21 14:40:44 +00002546
2547MagickExport MagickBooleanType LevelizeImage(Image *image,
2548 const double black_point,const double white_point,const double gamma)
2549{
2550 MagickBooleanType
2551 status;
2552
2553 status=LevelizeImageChannel(image,DefaultChannels,black_point,white_point,
2554 gamma);
2555 return(status);
2556}
2557
cristy3ed852e2009-09-05 21:47:34 +00002558MagickExport MagickBooleanType LevelImageChannel(Image *image,
2559 const ChannelType channel,const double black_point,const double white_point,
2560 const double gamma)
2561{
2562#define LevelImageTag "Level/Image"
cristybcfb0432010-05-06 01:45:33 +00002563#define LevelQuantum(x) (ClampToQuantum((MagickRealType) QuantumRange* \
cristyc1f508d2010-04-22 01:21:47 +00002564 pow(scale*((double) (x)-black_point),1.0/gamma)))
cristy3ed852e2009-09-05 21:47:34 +00002565
cristyc4c8d132010-01-07 01:58:38 +00002566 CacheView
2567 *image_view;
2568
cristy3ed852e2009-09-05 21:47:34 +00002569 ExceptionInfo
2570 *exception;
2571
cristy3ed852e2009-09-05 21:47:34 +00002572 MagickBooleanType
2573 status;
2574
cristybb503372010-05-27 20:51:26 +00002575 MagickOffsetType
2576 progress;
2577
2578 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002579 i;
2580
anthony7fe39fc2010-04-06 03:19:20 +00002581 register double
2582 scale;
2583
cristybb503372010-05-27 20:51:26 +00002584 ssize_t
2585 y;
2586
cristy3ed852e2009-09-05 21:47:34 +00002587 /*
2588 Allocate and initialize levels map.
2589 */
2590 assert(image != (Image *) NULL);
2591 assert(image->signature == MagickSignature);
2592 if (image->debug != MagickFalse)
2593 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
anthony7fe39fc2010-04-06 03:19:20 +00002594 scale = (white_point != black_point) ? 1.0/(white_point-black_point) : 1.0;
cristy3ed852e2009-09-05 21:47:34 +00002595 if (image->storage_class == PseudoClass)
cristyb5d5f722009-11-04 03:03:49 +00002596#if defined(MAGICKCORE_OPENMP_SUPPORT)
2597 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002598#endif
cristybb503372010-05-27 20:51:26 +00002599 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00002600 {
2601 /*
2602 Level colormap.
2603 */
2604 if ((channel & RedChannel) != 0)
cristyc1f508d2010-04-22 01:21:47 +00002605 image->colormap[i].red=LevelQuantum(image->colormap[i].red);
cristy3ed852e2009-09-05 21:47:34 +00002606 if ((channel & GreenChannel) != 0)
cristyc1f508d2010-04-22 01:21:47 +00002607 image->colormap[i].green=LevelQuantum(image->colormap[i].green);
cristy3ed852e2009-09-05 21:47:34 +00002608 if ((channel & BlueChannel) != 0)
cristyc1f508d2010-04-22 01:21:47 +00002609 image->colormap[i].blue=LevelQuantum(image->colormap[i].blue);
cristy3ed852e2009-09-05 21:47:34 +00002610 if ((channel & OpacityChannel) != 0)
cristyc1f508d2010-04-22 01:21:47 +00002611 image->colormap[i].opacity=LevelQuantum(image->colormap[i].opacity);
cristy3ed852e2009-09-05 21:47:34 +00002612 }
2613 /*
2614 Level image.
2615 */
2616 status=MagickTrue;
2617 progress=0;
2618 exception=(&image->exception);
2619 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00002620#if defined(MAGICKCORE_OPENMP_SUPPORT)
2621 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002622#endif
cristybb503372010-05-27 20:51:26 +00002623 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002624 {
2625 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00002626 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00002627
cristybb503372010-05-27 20:51:26 +00002628 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002629 x;
2630
2631 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00002632 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002633
2634 if (status == MagickFalse)
2635 continue;
2636 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2637 if (q == (PixelPacket *) NULL)
2638 {
2639 status=MagickFalse;
2640 continue;
2641 }
2642 indexes=GetCacheViewAuthenticIndexQueue(image_view);
cristybb503372010-05-27 20:51:26 +00002643 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002644 {
2645 if ((channel & RedChannel) != 0)
cristyc1f508d2010-04-22 01:21:47 +00002646 q->red=LevelQuantum(q->red);
cristy3ed852e2009-09-05 21:47:34 +00002647 if ((channel & GreenChannel) != 0)
cristyc1f508d2010-04-22 01:21:47 +00002648 q->green=LevelQuantum(q->green);
cristy3ed852e2009-09-05 21:47:34 +00002649 if ((channel & BlueChannel) != 0)
cristyc1f508d2010-04-22 01:21:47 +00002650 q->blue=LevelQuantum(q->blue);
cristy3ed852e2009-09-05 21:47:34 +00002651 if (((channel & OpacityChannel) != 0) &&
2652 (image->matte == MagickTrue))
cristybb503372010-05-27 20:51:26 +00002653 q->opacity=(Quantum) (QuantumRange-LevelQuantum(QuantumRange-
2654 q->opacity));
cristy3ed852e2009-09-05 21:47:34 +00002655 if (((channel & IndexChannel) != 0) &&
2656 (image->colorspace == CMYKColorspace))
cristyc1f508d2010-04-22 01:21:47 +00002657 indexes[x]=LevelQuantum(indexes[x]);
cristy3ed852e2009-09-05 21:47:34 +00002658 q++;
2659 }
2660 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2661 status=MagickFalse;
2662 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2663 {
2664 MagickBooleanType
2665 proceed;
2666
cristyb5d5f722009-11-04 03:03:49 +00002667#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00002668 #pragma omp critical (MagickCore_LevelImageChannel)
2669#endif
2670 proceed=SetImageProgress(image,LevelImageTag,progress++,image->rows);
2671 if (proceed == MagickFalse)
2672 status=MagickFalse;
2673 }
2674 }
2675 image_view=DestroyCacheView(image_view);
2676 return(status);
2677}
2678
2679/*
2680%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2681% %
2682% %
2683% %
2684% L e v e l i z e I m a g e C h a n n e l %
2685% %
2686% %
2687% %
2688%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2689%
2690% LevelizeImageChannel() applies the reversed LevelImage() operation to just
2691% the specific channels specified. It compresses the full range of color
2692% values, so that they lie between the given black and white points. Gamma is
2693% applied before the values are mapped.
2694%
2695% LevelizeImageChannel() can be called with by using a +level command line
2696% API option, or using a '!' on a -level or LevelImage() geometry string.
2697%
2698% It can be used for example de-contrast a greyscale image to the exact
2699% levels specified. Or by using specific levels for each channel of an image
2700% you can convert a gray-scale image to any linear color gradient, according
2701% to those levels.
2702%
2703% The format of the LevelizeImageChannel method is:
2704%
2705% MagickBooleanType LevelizeImageChannel(Image *image,
2706% const ChannelType channel,const char *levels)
2707%
2708% A description of each parameter follows:
2709%
2710% o image: the image.
2711%
2712% o channel: the channel.
2713%
2714% o black_point: The level to map zero (black) to.
2715%
2716% o white_point: The level to map QuantiumRange (white) to.
2717%
2718% o gamma: adjust gamma by this factor before mapping values.
2719%
2720*/
2721MagickExport MagickBooleanType LevelizeImageChannel(Image *image,
2722 const ChannelType channel,const double black_point,const double white_point,
2723 const double gamma)
2724{
2725#define LevelizeImageTag "Levelize/Image"
cristyce70c172010-01-07 17:15:30 +00002726#define LevelizeValue(x) (ClampToQuantum(((MagickRealType) \
cristy3ed852e2009-09-05 21:47:34 +00002727 pow((double)(QuantumScale*(x)),1.0/gamma))*(white_point-black_point)+ \
2728 black_point))
2729
cristyc4c8d132010-01-07 01:58:38 +00002730 CacheView
2731 *image_view;
2732
cristy3ed852e2009-09-05 21:47:34 +00002733 ExceptionInfo
2734 *exception;
2735
cristy3ed852e2009-09-05 21:47:34 +00002736 MagickBooleanType
2737 status;
2738
cristybb503372010-05-27 20:51:26 +00002739 MagickOffsetType
2740 progress;
2741
2742 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002743 i;
2744
cristybb503372010-05-27 20:51:26 +00002745 ssize_t
2746 y;
2747
cristy3ed852e2009-09-05 21:47:34 +00002748 /*
2749 Allocate and initialize levels map.
2750 */
2751 assert(image != (Image *) NULL);
2752 assert(image->signature == MagickSignature);
2753 if (image->debug != MagickFalse)
2754 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2755 if (image->storage_class == PseudoClass)
cristyb5d5f722009-11-04 03:03:49 +00002756#if defined(MAGICKCORE_OPENMP_SUPPORT)
2757 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002758#endif
cristybb503372010-05-27 20:51:26 +00002759 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00002760 {
2761 /*
2762 Level colormap.
2763 */
2764 if ((channel & RedChannel) != 0)
2765 image->colormap[i].red=LevelizeValue(image->colormap[i].red);
2766 if ((channel & GreenChannel) != 0)
2767 image->colormap[i].green=LevelizeValue(image->colormap[i].green);
2768 if ((channel & BlueChannel) != 0)
2769 image->colormap[i].blue=LevelizeValue(image->colormap[i].blue);
2770 if ((channel & OpacityChannel) != 0)
2771 image->colormap[i].opacity=LevelizeValue(image->colormap[i].opacity);
2772 }
2773 /*
2774 Level image.
2775 */
2776 status=MagickTrue;
2777 progress=0;
2778 exception=(&image->exception);
2779 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00002780#if defined(MAGICKCORE_OPENMP_SUPPORT)
2781 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002782#endif
cristybb503372010-05-27 20:51:26 +00002783 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002784 {
2785 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00002786 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00002787
cristybb503372010-05-27 20:51:26 +00002788 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002789 x;
2790
2791 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00002792 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002793
2794 if (status == MagickFalse)
2795 continue;
2796 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2797 if (q == (PixelPacket *) NULL)
2798 {
2799 status=MagickFalse;
2800 continue;
2801 }
2802 indexes=GetCacheViewAuthenticIndexQueue(image_view);
cristybb503372010-05-27 20:51:26 +00002803 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002804 {
2805 if ((channel & RedChannel) != 0)
2806 q->red=LevelizeValue(q->red);
2807 if ((channel & GreenChannel) != 0)
2808 q->green=LevelizeValue(q->green);
2809 if ((channel & BlueChannel) != 0)
2810 q->blue=LevelizeValue(q->blue);
2811 if (((channel & OpacityChannel) != 0) &&
2812 (image->matte == MagickTrue))
2813 q->opacity=LevelizeValue(q->opacity);
2814 if (((channel & IndexChannel) != 0) &&
2815 (image->colorspace == CMYKColorspace))
2816 indexes[x]=LevelizeValue(indexes[x]);
2817 q++;
2818 }
2819 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2820 status=MagickFalse;
2821 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2822 {
2823 MagickBooleanType
2824 proceed;
2825
cristyb5d5f722009-11-04 03:03:49 +00002826#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00002827 #pragma omp critical (MagickCore_LevelizeImageChannel)
2828#endif
2829 proceed=SetImageProgress(image,LevelizeImageTag,progress++,image->rows);
2830 if (proceed == MagickFalse)
2831 status=MagickFalse;
2832 }
2833 }
2834 return(status);
2835}
2836
2837/*
2838%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2839% %
2840% %
2841% %
2842% L e v e l I m a g e C o l o r s %
2843% %
2844% %
2845% %
2846%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2847%
cristyee0f8d72009-09-19 00:58:29 +00002848% LevelImageColor() maps the given color to "black" and "white" values,
2849% linearly spreading out the colors, and level values on a channel by channel
2850% bases, as per LevelImage(). The given colors allows you to specify
cristy3ed852e2009-09-05 21:47:34 +00002851% different level ranges for each of the color channels seperatally.
2852%
2853% If the boolean 'invert' is set true the image values will modifyed in the
2854% reverse direction. That is any existing "black" and "white" colors in the
2855% image will become the color values given, with all other values compressed
2856% appropriatally. This effectivally maps a greyscale gradient into the given
2857% color gradient.
2858%
cristy308b4e62009-09-21 14:40:44 +00002859% The format of the LevelColorsImageChannel method is:
cristy3ed852e2009-09-05 21:47:34 +00002860%
cristy308b4e62009-09-21 14:40:44 +00002861% MagickBooleanType LevelColorsImage(Image *image,
cristyee0f8d72009-09-19 00:58:29 +00002862% const MagickPixelPacket *black_color,
2863% const MagickPixelPacket *white_color,const MagickBooleanType invert)
cristy308b4e62009-09-21 14:40:44 +00002864% MagickBooleanType LevelColorsImageChannel(Image *image,
2865% const ChannelType channel,const MagickPixelPacket *black_color,
2866% const MagickPixelPacket *white_color,const MagickBooleanType invert)
cristy3ed852e2009-09-05 21:47:34 +00002867%
2868% A description of each parameter follows:
2869%
2870% o image: the image.
2871%
2872% o channel: the channel.
2873%
2874% o black_color: The color to map black to/from
2875%
2876% o white_point: The color to map white to/from
2877%
2878% o invert: if true map the colors (levelize), rather than from (level)
2879%
2880*/
cristy308b4e62009-09-21 14:40:44 +00002881
2882MagickExport MagickBooleanType LevelColorsImage(Image *image,
cristy3ed852e2009-09-05 21:47:34 +00002883 const MagickPixelPacket *black_color,const MagickPixelPacket *white_color,
2884 const MagickBooleanType invert)
2885{
cristy308b4e62009-09-21 14:40:44 +00002886 MagickBooleanType
2887 status;
cristy3ed852e2009-09-05 21:47:34 +00002888
cristy308b4e62009-09-21 14:40:44 +00002889 status=LevelColorsImageChannel(image,DefaultChannels,black_color,white_color,
2890 invert);
2891 return(status);
2892}
2893
2894MagickExport MagickBooleanType LevelColorsImageChannel(Image *image,
2895 const ChannelType channel,const MagickPixelPacket *black_color,
2896 const MagickPixelPacket *white_color,const MagickBooleanType invert)
2897{
cristy3ed852e2009-09-05 21:47:34 +00002898 MagickStatusType
2899 status;
2900
2901 /*
2902 Allocate and initialize levels map.
2903 */
2904 assert(image != (Image *) NULL);
2905 assert(image->signature == MagickSignature);
2906 if (image->debug != MagickFalse)
2907 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2908 status=MagickFalse;
2909 if (invert == MagickFalse)
2910 {
2911 if ((channel & RedChannel) != 0)
2912 status|=LevelImageChannel(image,RedChannel,
2913 black_color->red,white_color->red,(double) 1.0);
2914 if ((channel & GreenChannel) != 0)
2915 status|=LevelImageChannel(image,GreenChannel,
2916 black_color->green,white_color->green,(double) 1.0);
2917 if ((channel & BlueChannel) != 0)
2918 status|=LevelImageChannel(image,BlueChannel,
2919 black_color->blue,white_color->blue,(double) 1.0);
2920 if (((channel & OpacityChannel) != 0) &&
2921 (image->matte == MagickTrue))
2922 status|=LevelImageChannel(image,OpacityChannel,
2923 black_color->opacity,white_color->opacity,(double) 1.0);
2924 if (((channel & IndexChannel) != 0) &&
2925 (image->colorspace == CMYKColorspace))
2926 status|=LevelImageChannel(image,IndexChannel,
2927 black_color->index,white_color->index,(double) 1.0);
2928 }
2929 else
2930 {
2931 if ((channel & RedChannel) != 0)
2932 status|=LevelizeImageChannel(image,RedChannel,
2933 black_color->red,white_color->red,(double) 1.0);
2934 if ((channel & GreenChannel) != 0)
2935 status|=LevelizeImageChannel(image,GreenChannel,
2936 black_color->green,white_color->green,(double) 1.0);
2937 if ((channel & BlueChannel) != 0)
2938 status|=LevelizeImageChannel(image,BlueChannel,
2939 black_color->blue,white_color->blue,(double) 1.0);
2940 if (((channel & OpacityChannel) != 0) &&
2941 (image->matte == MagickTrue))
2942 status|=LevelizeImageChannel(image,OpacityChannel,
2943 black_color->opacity,white_color->opacity,(double) 1.0);
2944 if (((channel & IndexChannel) != 0) &&
2945 (image->colorspace == CMYKColorspace))
2946 status|=LevelizeImageChannel(image,IndexChannel,
2947 black_color->index,white_color->index,(double) 1.0);
2948 }
2949 return(status == 0 ? MagickFalse : MagickTrue);
2950}
2951
2952/*
2953%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2954% %
2955% %
2956% %
2957% L i n e a r S t r e t c h I m a g e %
2958% %
2959% %
2960% %
2961%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2962%
2963% The LinearStretchImage() discards any pixels below the black point and
2964% above the white point and levels the remaining pixels.
2965%
2966% The format of the LinearStretchImage method is:
2967%
2968% MagickBooleanType LinearStretchImage(Image *image,
2969% const double black_point,const double white_point)
2970%
2971% A description of each parameter follows:
2972%
2973% o image: the image.
2974%
2975% o black_point: the black point.
2976%
2977% o white_point: the white point.
2978%
2979*/
2980MagickExport MagickBooleanType LinearStretchImage(Image *image,
2981 const double black_point,const double white_point)
2982{
2983#define LinearStretchImageTag "LinearStretch/Image"
2984
2985 ExceptionInfo
2986 *exception;
2987
cristybb503372010-05-27 20:51:26 +00002988 ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002989 black,
2990 white,
2991 y;
2992
2993 MagickBooleanType
2994 status;
2995
2996 MagickRealType
2997 *histogram,
2998 intensity;
2999
3000 MagickSizeType
3001 number_pixels;
3002
3003 /*
3004 Allocate histogram and linear map.
3005 */
3006 assert(image != (Image *) NULL);
3007 assert(image->signature == MagickSignature);
3008 histogram=(MagickRealType *) AcquireQuantumMemory(MaxMap+1UL,
3009 sizeof(*histogram));
3010 if (histogram == (MagickRealType *) NULL)
3011 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
3012 image->filename);
3013 /*
3014 Form histogram.
3015 */
3016 (void) ResetMagickMemory(histogram,0,(MaxMap+1)*sizeof(*histogram));
3017 exception=(&image->exception);
cristybb503372010-05-27 20:51:26 +00003018 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003019 {
3020 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00003021 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00003022
cristybb503372010-05-27 20:51:26 +00003023 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003024 x;
3025
3026 p=GetVirtualPixels(image,0,y,image->columns,1,exception);
3027 if (p == (const PixelPacket *) NULL)
3028 break;
cristybb503372010-05-27 20:51:26 +00003029 for (x=(ssize_t) image->columns-1; x >= 0; x--)
cristy3ed852e2009-09-05 21:47:34 +00003030 {
3031 histogram[ScaleQuantumToMap(PixelIntensityToQuantum(p))]++;
3032 p++;
3033 }
3034 }
3035 /*
3036 Find the histogram boundaries by locating the black and white point levels.
3037 */
3038 number_pixels=(MagickSizeType) image->columns*image->rows;
3039 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00003040 for (black=0; black < (ssize_t) MaxMap; black++)
cristy3ed852e2009-09-05 21:47:34 +00003041 {
3042 intensity+=histogram[black];
3043 if (intensity >= black_point)
3044 break;
3045 }
3046 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00003047 for (white=(ssize_t) MaxMap; white != 0; white--)
cristy3ed852e2009-09-05 21:47:34 +00003048 {
3049 intensity+=histogram[white];
3050 if (intensity >= white_point)
3051 break;
3052 }
3053 histogram=(MagickRealType *) RelinquishMagickMemory(histogram);
3054 status=LevelImageChannel(image,DefaultChannels,(double) black,(double) white,
3055 1.0);
3056 return(status);
3057}
3058
3059/*
3060%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3061% %
3062% %
3063% %
3064% M o d u l a t e I m a g e %
3065% %
3066% %
3067% %
3068%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3069%
3070% ModulateImage() lets you control the brightness, saturation, and hue
3071% of an image. Modulate represents the brightness, saturation, and hue
3072% as one parameter (e.g. 90,150,100). If the image colorspace is HSL, the
3073% modulation is lightness, saturation, and hue. And if the colorspace is
3074% HWB, use blackness, whiteness, and hue.
3075%
3076% The format of the ModulateImage method is:
3077%
3078% MagickBooleanType ModulateImage(Image *image,const char *modulate)
3079%
3080% A description of each parameter follows:
3081%
3082% o image: the image.
3083%
3084% o modulate: Define the percent change in brightness, saturation, and
3085% hue.
3086%
3087*/
3088
3089static void ModulateHSB(const double percent_hue,
3090 const double percent_saturation,const double percent_brightness,
3091 Quantum *red,Quantum *green,Quantum *blue)
3092{
3093 double
3094 brightness,
3095 hue,
3096 saturation;
3097
3098 /*
3099 Increase or decrease color brightness, saturation, or hue.
3100 */
3101 assert(red != (Quantum *) NULL);
3102 assert(green != (Quantum *) NULL);
3103 assert(blue != (Quantum *) NULL);
3104 ConvertRGBToHSB(*red,*green,*blue,&hue,&saturation,&brightness);
3105 hue+=0.5*(0.01*percent_hue-1.0);
3106 while (hue < 0.0)
3107 hue+=1.0;
3108 while (hue > 1.0)
3109 hue-=1.0;
3110 saturation*=0.01*percent_saturation;
3111 brightness*=0.01*percent_brightness;
3112 ConvertHSBToRGB(hue,saturation,brightness,red,green,blue);
3113}
3114
3115static void ModulateHSL(const double percent_hue,
3116 const double percent_saturation,const double percent_lightness,
3117 Quantum *red,Quantum *green,Quantum *blue)
3118{
3119 double
3120 hue,
3121 lightness,
3122 saturation;
3123
3124 /*
3125 Increase or decrease color lightness, saturation, or hue.
3126 */
3127 assert(red != (Quantum *) NULL);
3128 assert(green != (Quantum *) NULL);
3129 assert(blue != (Quantum *) NULL);
3130 ConvertRGBToHSL(*red,*green,*blue,&hue,&saturation,&lightness);
3131 hue+=0.5*(0.01*percent_hue-1.0);
3132 while (hue < 0.0)
3133 hue+=1.0;
3134 while (hue > 1.0)
3135 hue-=1.0;
3136 saturation*=0.01*percent_saturation;
3137 lightness*=0.01*percent_lightness;
3138 ConvertHSLToRGB(hue,saturation,lightness,red,green,blue);
3139}
3140
3141static void ModulateHWB(const double percent_hue,const double percent_whiteness, const double percent_blackness,Quantum *red,Quantum *green,Quantum *blue)
3142{
3143 double
3144 blackness,
3145 hue,
3146 whiteness;
3147
3148 /*
3149 Increase or decrease color blackness, whiteness, or hue.
3150 */
3151 assert(red != (Quantum *) NULL);
3152 assert(green != (Quantum *) NULL);
3153 assert(blue != (Quantum *) NULL);
3154 ConvertRGBToHWB(*red,*green,*blue,&hue,&whiteness,&blackness);
3155 hue+=0.5*(0.01*percent_hue-1.0);
3156 while (hue < 0.0)
3157 hue+=1.0;
3158 while (hue > 1.0)
3159 hue-=1.0;
3160 blackness*=0.01*percent_blackness;
3161 whiteness*=0.01*percent_whiteness;
3162 ConvertHWBToRGB(hue,whiteness,blackness,red,green,blue);
3163}
3164
3165MagickExport MagickBooleanType ModulateImage(Image *image,const char *modulate)
3166{
3167#define ModulateImageTag "Modulate/Image"
3168
cristyc4c8d132010-01-07 01:58:38 +00003169 CacheView
3170 *image_view;
3171
cristy3ed852e2009-09-05 21:47:34 +00003172 ColorspaceType
3173 colorspace;
3174
3175 const char
3176 *artifact;
3177
3178 double
3179 percent_brightness,
3180 percent_hue,
3181 percent_saturation;
3182
3183 ExceptionInfo
3184 *exception;
3185
3186 GeometryInfo
3187 geometry_info;
3188
cristy3ed852e2009-09-05 21:47:34 +00003189 MagickBooleanType
3190 status;
3191
cristybb503372010-05-27 20:51:26 +00003192 MagickOffsetType
3193 progress;
3194
cristy3ed852e2009-09-05 21:47:34 +00003195 MagickStatusType
3196 flags;
3197
cristybb503372010-05-27 20:51:26 +00003198 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003199 i;
3200
cristybb503372010-05-27 20:51:26 +00003201 ssize_t
3202 y;
3203
cristy3ed852e2009-09-05 21:47:34 +00003204 /*
cristy2b726bd2010-01-11 01:05:39 +00003205 Initialize modulate table.
cristy3ed852e2009-09-05 21:47:34 +00003206 */
3207 assert(image != (Image *) NULL);
3208 assert(image->signature == MagickSignature);
3209 if (image->debug != MagickFalse)
3210 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3211 if (modulate == (char *) NULL)
3212 return(MagickFalse);
3213 flags=ParseGeometry(modulate,&geometry_info);
3214 percent_brightness=geometry_info.rho;
3215 percent_saturation=geometry_info.sigma;
3216 if ((flags & SigmaValue) == 0)
3217 percent_saturation=100.0;
3218 percent_hue=geometry_info.xi;
3219 if ((flags & XiValue) == 0)
3220 percent_hue=100.0;
3221 colorspace=UndefinedColorspace;
3222 artifact=GetImageArtifact(image,"modulate:colorspace");
3223 if (artifact != (const char *) NULL)
3224 colorspace=(ColorspaceType) ParseMagickOption(MagickColorspaceOptions,
3225 MagickFalse,artifact);
3226 if (image->storage_class == PseudoClass)
3227 {
3228 /*
3229 Modulate colormap.
3230 */
cristyb5d5f722009-11-04 03:03:49 +00003231#if defined(MAGICKCORE_OPENMP_SUPPORT)
3232 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003233#endif
cristybb503372010-05-27 20:51:26 +00003234 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00003235 switch (colorspace)
3236 {
3237 case HSBColorspace:
3238 {
3239 ModulateHSB(percent_hue,percent_saturation,percent_brightness,
3240 &image->colormap[i].red,&image->colormap[i].green,
3241 &image->colormap[i].blue);
3242 break;
3243 }
3244 case HSLColorspace:
3245 default:
3246 {
3247 ModulateHSL(percent_hue,percent_saturation,percent_brightness,
3248 &image->colormap[i].red,&image->colormap[i].green,
3249 &image->colormap[i].blue);
3250 break;
3251 }
3252 case HWBColorspace:
3253 {
3254 ModulateHWB(percent_hue,percent_saturation,percent_brightness,
3255 &image->colormap[i].red,&image->colormap[i].green,
3256 &image->colormap[i].blue);
3257 break;
3258 }
3259 }
3260 }
3261 /*
3262 Modulate image.
3263 */
3264 status=MagickTrue;
3265 progress=0;
3266 exception=(&image->exception);
3267 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00003268#if defined(MAGICKCORE_OPENMP_SUPPORT)
3269 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003270#endif
cristybb503372010-05-27 20:51:26 +00003271 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003272 {
cristybb503372010-05-27 20:51:26 +00003273 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003274 x;
3275
3276 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00003277 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003278
3279 if (status == MagickFalse)
3280 continue;
3281 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
3282 if (q == (PixelPacket *) NULL)
3283 {
3284 status=MagickFalse;
3285 continue;
3286 }
cristybb503372010-05-27 20:51:26 +00003287 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003288 {
3289 switch (colorspace)
3290 {
3291 case HSBColorspace:
3292 {
3293 ModulateHSB(percent_hue,percent_saturation,percent_brightness,
3294 &q->red,&q->green,&q->blue);
3295 break;
3296 }
3297 case HSLColorspace:
3298 default:
3299 {
3300 ModulateHSL(percent_hue,percent_saturation,percent_brightness,
3301 &q->red,&q->green,&q->blue);
3302 break;
3303 }
3304 case HWBColorspace:
3305 {
3306 ModulateHWB(percent_hue,percent_saturation,percent_brightness,
3307 &q->red,&q->green,&q->blue);
3308 break;
3309 }
3310 }
3311 q++;
3312 }
3313 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
3314 status=MagickFalse;
3315 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3316 {
3317 MagickBooleanType
3318 proceed;
3319
cristyb5d5f722009-11-04 03:03:49 +00003320#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00003321 #pragma omp critical (MagickCore_ModulateImage)
3322#endif
3323 proceed=SetImageProgress(image,ModulateImageTag,progress++,image->rows);
3324 if (proceed == MagickFalse)
3325 status=MagickFalse;
3326 }
3327 }
3328 image_view=DestroyCacheView(image_view);
3329 return(status);
3330}
3331
3332/*
3333%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3334% %
3335% %
3336% %
3337% N e g a t e I m a g e %
3338% %
3339% %
3340% %
3341%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3342%
3343% NegateImage() negates the colors in the reference image. The grayscale
3344% option means that only grayscale values within the image are negated.
3345%
3346% The format of the NegateImageChannel method is:
3347%
3348% MagickBooleanType NegateImage(Image *image,
3349% const MagickBooleanType grayscale)
3350% MagickBooleanType NegateImageChannel(Image *image,
3351% const ChannelType channel,const MagickBooleanType grayscale)
3352%
3353% A description of each parameter follows:
3354%
3355% o image: the image.
3356%
3357% o channel: the channel.
3358%
3359% o grayscale: If MagickTrue, only negate grayscale pixels within the image.
3360%
3361*/
3362
3363MagickExport MagickBooleanType NegateImage(Image *image,
3364 const MagickBooleanType grayscale)
3365{
3366 MagickBooleanType
3367 status;
3368
3369 status=NegateImageChannel(image,DefaultChannels,grayscale);
3370 return(status);
3371}
3372
3373MagickExport MagickBooleanType NegateImageChannel(Image *image,
3374 const ChannelType channel,const MagickBooleanType grayscale)
3375{
3376#define NegateImageTag "Negate/Image"
3377
cristyc4c8d132010-01-07 01:58:38 +00003378 CacheView
3379 *image_view;
3380
cristy3ed852e2009-09-05 21:47:34 +00003381 ExceptionInfo
3382 *exception;
3383
cristy3ed852e2009-09-05 21:47:34 +00003384 MagickBooleanType
3385 status;
3386
cristybb503372010-05-27 20:51:26 +00003387 MagickOffsetType
3388 progress;
3389
3390 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003391 i;
3392
cristybb503372010-05-27 20:51:26 +00003393 ssize_t
3394 y;
3395
cristy3ed852e2009-09-05 21:47:34 +00003396 assert(image != (Image *) NULL);
3397 assert(image->signature == MagickSignature);
3398 if (image->debug != MagickFalse)
3399 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3400 if (image->storage_class == PseudoClass)
3401 {
3402 /*
3403 Negate colormap.
3404 */
cristyb5d5f722009-11-04 03:03:49 +00003405#if defined(MAGICKCORE_OPENMP_SUPPORT)
3406 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003407#endif
cristybb503372010-05-27 20:51:26 +00003408 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00003409 {
3410 if (grayscale != MagickFalse)
3411 if ((image->colormap[i].red != image->colormap[i].green) ||
3412 (image->colormap[i].green != image->colormap[i].blue))
3413 continue;
3414 if ((channel & RedChannel) != 0)
3415 image->colormap[i].red=(Quantum) QuantumRange-
3416 image->colormap[i].red;
3417 if ((channel & GreenChannel) != 0)
3418 image->colormap[i].green=(Quantum) QuantumRange-
3419 image->colormap[i].green;
3420 if ((channel & BlueChannel) != 0)
3421 image->colormap[i].blue=(Quantum) QuantumRange-
3422 image->colormap[i].blue;
3423 }
3424 }
3425 /*
3426 Negate image.
3427 */
3428 status=MagickTrue;
3429 progress=0;
3430 exception=(&image->exception);
3431 image_view=AcquireCacheView(image);
3432 if (grayscale != MagickFalse)
3433 {
cristyb5d5f722009-11-04 03:03:49 +00003434#if defined(MAGICKCORE_OPENMP_SUPPORT)
3435 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003436#endif
cristybb503372010-05-27 20:51:26 +00003437 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003438 {
3439 MagickBooleanType
3440 sync;
3441
3442 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00003443 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00003444
cristybb503372010-05-27 20:51:26 +00003445 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003446 x;
3447
3448 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00003449 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003450
3451 if (status == MagickFalse)
3452 continue;
3453 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,
3454 exception);
3455 if (q == (PixelPacket *) NULL)
3456 {
3457 status=MagickFalse;
3458 continue;
3459 }
3460 indexes=GetCacheViewAuthenticIndexQueue(image_view);
cristybb503372010-05-27 20:51:26 +00003461 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003462 {
3463 if ((q->red != q->green) || (q->green != q->blue))
3464 {
3465 q++;
3466 continue;
3467 }
3468 if ((channel & RedChannel) != 0)
3469 q->red=(Quantum) QuantumRange-q->red;
3470 if ((channel & GreenChannel) != 0)
3471 q->green=(Quantum) QuantumRange-q->green;
3472 if ((channel & BlueChannel) != 0)
3473 q->blue=(Quantum) QuantumRange-q->blue;
3474 if ((channel & OpacityChannel) != 0)
3475 q->opacity=(Quantum) QuantumRange-q->opacity;
3476 if (((channel & IndexChannel) != 0) &&
3477 (image->colorspace == CMYKColorspace))
3478 indexes[x]=(IndexPacket) QuantumRange-indexes[x];
3479 q++;
3480 }
3481 sync=SyncCacheViewAuthenticPixels(image_view,exception);
3482 if (sync == MagickFalse)
3483 status=MagickFalse;
3484 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3485 {
3486 MagickBooleanType
3487 proceed;
3488
cristyb5d5f722009-11-04 03:03:49 +00003489#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00003490 #pragma omp critical (MagickCore_NegateImageChannel)
3491#endif
3492 proceed=SetImageProgress(image,NegateImageTag,progress++,
3493 image->rows);
3494 if (proceed == MagickFalse)
3495 status=MagickFalse;
3496 }
3497 }
3498 image_view=DestroyCacheView(image_view);
3499 return(MagickTrue);
3500 }
3501 /*
3502 Negate image.
3503 */
cristyb5d5f722009-11-04 03:03:49 +00003504#if defined(MAGICKCORE_OPENMP_SUPPORT)
3505 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003506#endif
cristybb503372010-05-27 20:51:26 +00003507 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003508 {
3509 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00003510 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00003511
cristybb503372010-05-27 20:51:26 +00003512 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003513 x;
3514
3515 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00003516 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003517
3518 if (status == MagickFalse)
3519 continue;
3520 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
3521 if (q == (PixelPacket *) NULL)
3522 {
3523 status=MagickFalse;
3524 continue;
3525 }
3526 indexes=GetCacheViewAuthenticIndexQueue(image_view);
cristybb503372010-05-27 20:51:26 +00003527 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003528 {
3529 if ((channel & RedChannel) != 0)
3530 q->red=(Quantum) QuantumRange-q->red;
3531 if ((channel & GreenChannel) != 0)
3532 q->green=(Quantum) QuantumRange-q->green;
3533 if ((channel & BlueChannel) != 0)
3534 q->blue=(Quantum) QuantumRange-q->blue;
3535 if ((channel & OpacityChannel) != 0)
3536 q->opacity=(Quantum) QuantumRange-q->opacity;
3537 if (((channel & IndexChannel) != 0) &&
3538 (image->colorspace == CMYKColorspace))
3539 indexes[x]=(IndexPacket) QuantumRange-indexes[x];
3540 q++;
3541 }
3542 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
3543 status=MagickFalse;
3544 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3545 {
3546 MagickBooleanType
3547 proceed;
3548
cristyb5d5f722009-11-04 03:03:49 +00003549#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00003550 #pragma omp critical (MagickCore_NegateImageChannel)
3551#endif
3552 proceed=SetImageProgress(image,NegateImageTag,progress++,image->rows);
3553 if (proceed == MagickFalse)
3554 status=MagickFalse;
3555 }
3556 }
3557 image_view=DestroyCacheView(image_view);
3558 return(status);
3559}
3560
3561/*
3562%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3563% %
3564% %
3565% %
3566% N o r m a l i z e I m a g e %
3567% %
3568% %
3569% %
3570%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3571%
3572% The NormalizeImage() method enhances the contrast of a color image by
3573% mapping the darkest 2 percent of all pixel to black and the brightest
3574% 1 percent to white.
3575%
3576% The format of the NormalizeImage method is:
3577%
3578% MagickBooleanType NormalizeImage(Image *image)
3579% MagickBooleanType NormalizeImageChannel(Image *image,
3580% const ChannelType channel)
3581%
3582% A description of each parameter follows:
3583%
3584% o image: the image.
3585%
3586% o channel: the channel.
3587%
3588*/
3589
3590MagickExport MagickBooleanType NormalizeImage(Image *image)
3591{
3592 MagickBooleanType
3593 status;
3594
3595 status=NormalizeImageChannel(image,DefaultChannels);
3596 return(status);
3597}
3598
3599MagickExport MagickBooleanType NormalizeImageChannel(Image *image,
3600 const ChannelType channel)
3601{
3602 double
3603 black_point,
3604 white_point;
3605
3606 black_point=(double) image->columns*image->rows*0.02;
3607 white_point=(double) image->columns*image->rows*0.99;
3608 return(ContrastStretchImageChannel(image,channel,black_point,white_point));
3609}
3610
3611/*
3612%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3613% %
3614% %
3615% %
3616% S i g m o i d a l C o n t r a s t I m a g e %
3617% %
3618% %
3619% %
3620%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3621%
3622% SigmoidalContrastImage() adjusts the contrast of an image with a non-linear
3623% sigmoidal contrast algorithm. Increase the contrast of the image using a
3624% sigmoidal transfer function without saturating highlights or shadows.
3625% Contrast indicates how much to increase the contrast (0 is none; 3 is
3626% typical; 20 is pushing it); mid-point indicates where midtones fall in the
3627% resultant image (0 is white; 50% is middle-gray; 100% is black). Set
3628% sharpen to MagickTrue to increase the image contrast otherwise the contrast
3629% is reduced.
3630%
3631% The format of the SigmoidalContrastImage method is:
3632%
3633% MagickBooleanType SigmoidalContrastImage(Image *image,
3634% const MagickBooleanType sharpen,const char *levels)
3635% MagickBooleanType SigmoidalContrastImageChannel(Image *image,
3636% const ChannelType channel,const MagickBooleanType sharpen,
3637% const double contrast,const double midpoint)
3638%
3639% A description of each parameter follows:
3640%
3641% o image: the image.
3642%
3643% o channel: the channel.
3644%
3645% o sharpen: Increase or decrease image contrast.
3646%
3647% o contrast: control the "shoulder" of the contast curve.
3648%
3649% o midpoint: control the "toe" of the contast curve.
3650%
3651*/
3652
3653MagickExport MagickBooleanType SigmoidalContrastImage(Image *image,
3654 const MagickBooleanType sharpen,const char *levels)
3655{
3656 GeometryInfo
3657 geometry_info;
3658
3659 MagickBooleanType
3660 status;
3661
3662 MagickStatusType
3663 flags;
3664
3665 flags=ParseGeometry(levels,&geometry_info);
3666 if ((flags & SigmaValue) == 0)
3667 geometry_info.sigma=1.0*QuantumRange/2.0;
3668 if ((flags & PercentValue) != 0)
3669 geometry_info.sigma=1.0*QuantumRange*geometry_info.sigma/100.0;
3670 status=SigmoidalContrastImageChannel(image,DefaultChannels,sharpen,
3671 geometry_info.rho,geometry_info.sigma);
3672 return(status);
3673}
3674
3675MagickExport MagickBooleanType SigmoidalContrastImageChannel(Image *image,
3676 const ChannelType channel,const MagickBooleanType sharpen,
3677 const double contrast,const double midpoint)
3678{
3679#define SigmoidalContrastImageTag "SigmoidalContrast/Image"
3680
cristyc4c8d132010-01-07 01:58:38 +00003681 CacheView
3682 *image_view;
3683
cristy3ed852e2009-09-05 21:47:34 +00003684 ExceptionInfo
3685 *exception;
3686
cristy3ed852e2009-09-05 21:47:34 +00003687 MagickBooleanType
3688 status;
3689
cristybb503372010-05-27 20:51:26 +00003690 MagickOffsetType
3691 progress;
3692
cristy3ed852e2009-09-05 21:47:34 +00003693 MagickRealType
3694 *sigmoidal_map;
3695
cristybb503372010-05-27 20:51:26 +00003696 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003697 i;
3698
cristybb503372010-05-27 20:51:26 +00003699 ssize_t
3700 y;
3701
cristy3ed852e2009-09-05 21:47:34 +00003702 /*
3703 Allocate and initialize sigmoidal maps.
3704 */
3705 assert(image != (Image *) NULL);
3706 assert(image->signature == MagickSignature);
3707 if (image->debug != MagickFalse)
3708 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3709 sigmoidal_map=(MagickRealType *) AcquireQuantumMemory(MaxMap+1UL,
3710 sizeof(*sigmoidal_map));
3711 if (sigmoidal_map == (MagickRealType *) NULL)
3712 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
3713 image->filename);
3714 (void) ResetMagickMemory(sigmoidal_map,0,(MaxMap+1)*sizeof(*sigmoidal_map));
cristyb5d5f722009-11-04 03:03:49 +00003715#if defined(MAGICKCORE_OPENMP_SUPPORT)
3716 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003717#endif
cristybb503372010-05-27 20:51:26 +00003718 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +00003719 {
3720 if (sharpen != MagickFalse)
3721 {
3722 sigmoidal_map[i]=(MagickRealType) ScaleMapToQuantum((MagickRealType)
3723 (MaxMap*((1.0/(1.0+exp(contrast*(midpoint/(double) QuantumRange-
3724 (double) i/MaxMap))))-(1.0/(1.0+exp(contrast*(midpoint/
3725 (double) QuantumRange)))))/((1.0/(1.0+exp(contrast*(midpoint/
3726 (double) QuantumRange-1.0))))-(1.0/(1.0+exp(contrast*(midpoint/
3727 (double) QuantumRange)))))+0.5));
3728 continue;
3729 }
3730 sigmoidal_map[i]=(MagickRealType) ScaleMapToQuantum((MagickRealType)
3731 (MaxMap*(QuantumScale*midpoint-log((1.0-(1.0/(1.0+exp(midpoint/
3732 (double) QuantumRange*contrast))+((double) i/MaxMap)*((1.0/
3733 (1.0+exp(contrast*(midpoint/(double) QuantumRange-1.0))))-(1.0/
3734 (1.0+exp(midpoint/(double) QuantumRange*contrast))))))/
3735 (1.0/(1.0+exp(midpoint/(double) QuantumRange*contrast))+
3736 ((double) i/MaxMap)*((1.0/(1.0+exp(contrast*(midpoint/
3737 (double) QuantumRange-1.0))))-(1.0/(1.0+exp(midpoint/
3738 (double) QuantumRange*contrast))))))/contrast)));
3739 }
3740 if (image->storage_class == PseudoClass)
3741 {
3742 /*
3743 Sigmoidal-contrast enhance colormap.
3744 */
cristyb5d5f722009-11-04 03:03:49 +00003745#if defined(MAGICKCORE_OPENMP_SUPPORT)
3746 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003747#endif
cristybb503372010-05-27 20:51:26 +00003748 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00003749 {
3750 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003751 image->colormap[i].red=ClampToQuantum(sigmoidal_map[
cristy3ed852e2009-09-05 21:47:34 +00003752 ScaleQuantumToMap(image->colormap[i].red)]);
3753 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003754 image->colormap[i].green=ClampToQuantum(sigmoidal_map[
cristy3ed852e2009-09-05 21:47:34 +00003755 ScaleQuantumToMap(image->colormap[i].green)]);
3756 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003757 image->colormap[i].blue=ClampToQuantum(sigmoidal_map[
cristy3ed852e2009-09-05 21:47:34 +00003758 ScaleQuantumToMap(image->colormap[i].blue)]);
3759 if ((channel & OpacityChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003760 image->colormap[i].opacity=ClampToQuantum(sigmoidal_map[
cristy3ed852e2009-09-05 21:47:34 +00003761 ScaleQuantumToMap(image->colormap[i].opacity)]);
3762 }
3763 }
3764 /*
3765 Sigmoidal-contrast enhance image.
3766 */
3767 status=MagickTrue;
3768 progress=0;
3769 exception=(&image->exception);
3770 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00003771#if defined(MAGICKCORE_OPENMP_SUPPORT)
3772 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003773#endif
cristybb503372010-05-27 20:51:26 +00003774 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003775 {
3776 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00003777 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00003778
cristybb503372010-05-27 20:51:26 +00003779 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003780 x;
3781
3782 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00003783 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003784
3785 if (status == MagickFalse)
3786 continue;
3787 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
3788 if (q == (PixelPacket *) NULL)
3789 {
3790 status=MagickFalse;
3791 continue;
3792 }
3793 indexes=GetCacheViewAuthenticIndexQueue(image_view);
cristybb503372010-05-27 20:51:26 +00003794 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003795 {
3796 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003797 q->red=ClampToQuantum(sigmoidal_map[ScaleQuantumToMap(q->red)]);
cristy3ed852e2009-09-05 21:47:34 +00003798 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003799 q->green=ClampToQuantum(sigmoidal_map[ScaleQuantumToMap(q->green)]);
cristy3ed852e2009-09-05 21:47:34 +00003800 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003801 q->blue=ClampToQuantum(sigmoidal_map[ScaleQuantumToMap(q->blue)]);
cristy3ed852e2009-09-05 21:47:34 +00003802 if ((channel & OpacityChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003803 q->opacity=ClampToQuantum(sigmoidal_map[ScaleQuantumToMap(q->opacity)]);
cristy3ed852e2009-09-05 21:47:34 +00003804 if (((channel & IndexChannel) != 0) &&
3805 (image->colorspace == CMYKColorspace))
cristyce70c172010-01-07 17:15:30 +00003806 indexes[x]=(IndexPacket) ClampToQuantum(sigmoidal_map[
cristy3ed852e2009-09-05 21:47:34 +00003807 ScaleQuantumToMap(indexes[x])]);
3808 q++;
3809 }
3810 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
3811 status=MagickFalse;
3812 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3813 {
3814 MagickBooleanType
3815 proceed;
3816
cristyb5d5f722009-11-04 03:03:49 +00003817#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00003818 #pragma omp critical (MagickCore_SigmoidalContrastImageChannel)
3819#endif
3820 proceed=SetImageProgress(image,SigmoidalContrastImageTag,progress++,
3821 image->rows);
3822 if (proceed == MagickFalse)
3823 status=MagickFalse;
3824 }
3825 }
3826 image_view=DestroyCacheView(image_view);
3827 sigmoidal_map=(MagickRealType *) RelinquishMagickMemory(sigmoidal_map);
3828 return(status);
3829}