blob: 5581e1e93c35e9b8ccf9a44d72aad9e1d8410474 [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;
cristy4205a3c2010-09-12 20:19:59 +0000293 slope=tan((double) (MagickPI*(alpha/100.0+1.0)/4.0));
cristya28d6b82010-01-11 20:03:47 +0000294 if (slope < 0.0)
295 slope=0.0;
296 intercept=brightness/100.0+((100-brightness)/200.0)*(1.0-slope);
297 coefficients[0]=slope;
298 coefficients[1]=intercept;
299 status=FunctionImageChannel(image,channel,PolynomialFunction,2,coefficients,
300 &image->exception);
301 return(status);
302}
303
304/*
305%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
306% %
307% %
308% %
cristy3ed852e2009-09-05 21:47:34 +0000309% C o l o r D e c i s i o n L i s t I m a g e %
310% %
311% %
312% %
313%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
314%
315% ColorDecisionListImage() accepts a lightweight Color Correction Collection
316% (CCC) file which solely contains one or more color corrections and applies
317% the correction to the image. Here is a sample CCC file:
318%
319% <ColorCorrectionCollection xmlns="urn:ASC:CDL:v1.2">
320% <ColorCorrection id="cc03345">
321% <SOPNode>
322% <Slope> 0.9 1.2 0.5 </Slope>
323% <Offset> 0.4 -0.5 0.6 </Offset>
324% <Power> 1.0 0.8 1.5 </Power>
325% </SOPNode>
326% <SATNode>
327% <Saturation> 0.85 </Saturation>
328% </SATNode>
329% </ColorCorrection>
330% </ColorCorrectionCollection>
331%
332% which includes the slop, offset, and power for each of the RGB channels
333% as well as the saturation.
334%
335% The format of the ColorDecisionListImage method is:
336%
337% MagickBooleanType ColorDecisionListImage(Image *image,
338% const char *color_correction_collection)
339%
340% A description of each parameter follows:
341%
342% o image: the image.
343%
344% o color_correction_collection: the color correction collection in XML.
345%
346*/
347MagickExport MagickBooleanType ColorDecisionListImage(Image *image,
348 const char *color_correction_collection)
349{
350#define ColorDecisionListCorrectImageTag "ColorDecisionList/Image"
351
352 typedef struct _Correction
353 {
354 double
355 slope,
356 offset,
357 power;
358 } Correction;
359
360 typedef struct _ColorCorrection
361 {
362 Correction
363 red,
364 green,
365 blue;
366
367 double
368 saturation;
369 } ColorCorrection;
370
cristyc4c8d132010-01-07 01:58:38 +0000371 CacheView
372 *image_view;
373
cristy3ed852e2009-09-05 21:47:34 +0000374 char
375 token[MaxTextExtent];
376
377 ColorCorrection
378 color_correction;
379
380 const char
381 *content,
382 *p;
383
384 ExceptionInfo
385 *exception;
386
cristy3ed852e2009-09-05 21:47:34 +0000387 MagickBooleanType
388 status;
389
cristybb503372010-05-27 20:51:26 +0000390 MagickOffsetType
391 progress;
392
cristy3ed852e2009-09-05 21:47:34 +0000393 PixelPacket
394 *cdl_map;
395
cristybb503372010-05-27 20:51:26 +0000396 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000397 i;
398
cristybb503372010-05-27 20:51:26 +0000399 ssize_t
400 y;
401
cristy3ed852e2009-09-05 21:47:34 +0000402 XMLTreeInfo
403 *cc,
404 *ccc,
405 *sat,
406 *sop;
407
cristy3ed852e2009-09-05 21:47:34 +0000408 /*
409 Allocate and initialize cdl maps.
410 */
411 assert(image != (Image *) NULL);
412 assert(image->signature == MagickSignature);
413 if (image->debug != MagickFalse)
414 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
415 if (color_correction_collection == (const char *) NULL)
416 return(MagickFalse);
417 ccc=NewXMLTree((const char *) color_correction_collection,&image->exception);
418 if (ccc == (XMLTreeInfo *) NULL)
419 return(MagickFalse);
420 cc=GetXMLTreeChild(ccc,"ColorCorrection");
421 if (cc == (XMLTreeInfo *) NULL)
422 {
423 ccc=DestroyXMLTree(ccc);
424 return(MagickFalse);
425 }
426 color_correction.red.slope=1.0;
427 color_correction.red.offset=0.0;
428 color_correction.red.power=1.0;
429 color_correction.green.slope=1.0;
430 color_correction.green.offset=0.0;
431 color_correction.green.power=1.0;
432 color_correction.blue.slope=1.0;
433 color_correction.blue.offset=0.0;
434 color_correction.blue.power=1.0;
435 color_correction.saturation=0.0;
436 sop=GetXMLTreeChild(cc,"SOPNode");
437 if (sop != (XMLTreeInfo *) NULL)
438 {
439 XMLTreeInfo
440 *offset,
441 *power,
442 *slope;
443
444 slope=GetXMLTreeChild(sop,"Slope");
445 if (slope != (XMLTreeInfo *) NULL)
446 {
447 content=GetXMLTreeContent(slope);
448 p=(const char *) content;
449 for (i=0; (*p != '\0') && (i < 3); i++)
450 {
451 GetMagickToken(p,&p,token);
452 if (*token == ',')
453 GetMagickToken(p,&p,token);
454 switch (i)
455 {
cristyf2f27272009-12-17 14:48:46 +0000456 case 0: color_correction.red.slope=StringToDouble(token); break;
457 case 1: color_correction.green.slope=StringToDouble(token); break;
458 case 2: color_correction.blue.slope=StringToDouble(token); break;
cristy3ed852e2009-09-05 21:47:34 +0000459 }
460 }
461 }
462 offset=GetXMLTreeChild(sop,"Offset");
463 if (offset != (XMLTreeInfo *) NULL)
464 {
465 content=GetXMLTreeContent(offset);
466 p=(const char *) content;
467 for (i=0; (*p != '\0') && (i < 3); i++)
468 {
469 GetMagickToken(p,&p,token);
470 if (*token == ',')
471 GetMagickToken(p,&p,token);
472 switch (i)
473 {
cristyf2f27272009-12-17 14:48:46 +0000474 case 0: color_correction.red.offset=StringToDouble(token); break;
475 case 1: color_correction.green.offset=StringToDouble(token); break;
476 case 2: color_correction.blue.offset=StringToDouble(token); break;
cristy3ed852e2009-09-05 21:47:34 +0000477 }
478 }
479 }
480 power=GetXMLTreeChild(sop,"Power");
481 if (power != (XMLTreeInfo *) NULL)
482 {
483 content=GetXMLTreeContent(power);
484 p=(const char *) content;
485 for (i=0; (*p != '\0') && (i < 3); i++)
486 {
487 GetMagickToken(p,&p,token);
488 if (*token == ',')
489 GetMagickToken(p,&p,token);
490 switch (i)
491 {
cristyf2f27272009-12-17 14:48:46 +0000492 case 0: color_correction.red.power=StringToDouble(token); break;
493 case 1: color_correction.green.power=StringToDouble(token); break;
494 case 2: color_correction.blue.power=StringToDouble(token); break;
cristy3ed852e2009-09-05 21:47:34 +0000495 }
496 }
497 }
498 }
499 sat=GetXMLTreeChild(cc,"SATNode");
500 if (sat != (XMLTreeInfo *) NULL)
501 {
502 XMLTreeInfo
503 *saturation;
504
505 saturation=GetXMLTreeChild(sat,"Saturation");
506 if (saturation != (XMLTreeInfo *) NULL)
507 {
508 content=GetXMLTreeContent(saturation);
509 p=(const char *) content;
510 GetMagickToken(p,&p,token);
cristyf2f27272009-12-17 14:48:46 +0000511 color_correction.saturation=StringToDouble(token);
cristy3ed852e2009-09-05 21:47:34 +0000512 }
513 }
514 ccc=DestroyXMLTree(ccc);
515 if (image->debug != MagickFalse)
516 {
517 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
518 " Color Correction Collection:");
519 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000520 " color_correction.red.slope: %g",color_correction.red.slope);
cristy3ed852e2009-09-05 21:47:34 +0000521 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000522 " color_correction.red.offset: %g",color_correction.red.offset);
cristy3ed852e2009-09-05 21:47:34 +0000523 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000524 " color_correction.red.power: %g",color_correction.red.power);
cristy3ed852e2009-09-05 21:47:34 +0000525 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000526 " color_correction.green.slope: %g",color_correction.green.slope);
cristy3ed852e2009-09-05 21:47:34 +0000527 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000528 " color_correction.green.offset: %g",color_correction.green.offset);
cristy3ed852e2009-09-05 21:47:34 +0000529 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000530 " color_correction.green.power: %g",color_correction.green.power);
cristy3ed852e2009-09-05 21:47:34 +0000531 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000532 " color_correction.blue.slope: %g",color_correction.blue.slope);
cristy3ed852e2009-09-05 21:47:34 +0000533 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000534 " color_correction.blue.offset: %g",color_correction.blue.offset);
cristy3ed852e2009-09-05 21:47:34 +0000535 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000536 " color_correction.blue.power: %g",color_correction.blue.power);
cristy3ed852e2009-09-05 21:47:34 +0000537 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000538 " color_correction.saturation: %g",color_correction.saturation);
cristy3ed852e2009-09-05 21:47:34 +0000539 }
540 cdl_map=(PixelPacket *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*cdl_map));
541 if (cdl_map == (PixelPacket *) NULL)
542 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
543 image->filename);
cristyb5d5f722009-11-04 03:03:49 +0000544#if defined(MAGICKCORE_OPENMP_SUPPORT)
545 #pragma omp parallel for schedule(dynamic,4)
cristy3ed852e2009-09-05 21:47:34 +0000546#endif
cristybb503372010-05-27 20:51:26 +0000547 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +0000548 {
cristyce70c172010-01-07 17:15:30 +0000549 cdl_map[i].red=ClampToQuantum((MagickRealType) ScaleMapToQuantum((
cristy3ed852e2009-09-05 21:47:34 +0000550 MagickRealType) (MaxMap*(pow(color_correction.red.slope*i/MaxMap+
551 color_correction.red.offset,color_correction.red.power)))));
cristyce70c172010-01-07 17:15:30 +0000552 cdl_map[i].green=ClampToQuantum((MagickRealType) ScaleMapToQuantum((
cristy3ed852e2009-09-05 21:47:34 +0000553 MagickRealType) (MaxMap*(pow(color_correction.green.slope*i/MaxMap+
554 color_correction.green.offset,color_correction.green.power)))));
cristyce70c172010-01-07 17:15:30 +0000555 cdl_map[i].blue=ClampToQuantum((MagickRealType) ScaleMapToQuantum((
cristy3ed852e2009-09-05 21:47:34 +0000556 MagickRealType) (MaxMap*(pow(color_correction.blue.slope*i/MaxMap+
557 color_correction.blue.offset,color_correction.blue.power)))));
558 }
559 if (image->storage_class == PseudoClass)
560 {
561 /*
562 Apply transfer function to colormap.
563 */
cristyb5d5f722009-11-04 03:03:49 +0000564#if defined(MAGICKCORE_OPENMP_SUPPORT)
565 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000566#endif
cristybb503372010-05-27 20:51:26 +0000567 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +0000568 {
569 double
570 luma;
571
572 luma=0.2126*image->colormap[i].red+0.7152*image->colormap[i].green+
573 0.0722*image->colormap[i].blue;
cristyce70c172010-01-07 17:15:30 +0000574 image->colormap[i].red=ClampToQuantum(luma+color_correction.saturation*
cristy3ed852e2009-09-05 21:47:34 +0000575 cdl_map[ScaleQuantumToMap(image->colormap[i].red)].red-luma);
cristyce70c172010-01-07 17:15:30 +0000576 image->colormap[i].green=ClampToQuantum(luma+
cristy3ed852e2009-09-05 21:47:34 +0000577 color_correction.saturation*cdl_map[ScaleQuantumToMap(
578 image->colormap[i].green)].green-luma);
cristyce70c172010-01-07 17:15:30 +0000579 image->colormap[i].blue=ClampToQuantum(luma+color_correction.saturation*
cristy3ed852e2009-09-05 21:47:34 +0000580 cdl_map[ScaleQuantumToMap(image->colormap[i].blue)].blue-luma);
581 }
582 }
583 /*
584 Apply transfer function to image.
585 */
586 status=MagickTrue;
587 progress=0;
588 exception=(&image->exception);
589 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +0000590#if defined(MAGICKCORE_OPENMP_SUPPORT)
591 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000592#endif
cristybb503372010-05-27 20:51:26 +0000593 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000594 {
595 double
596 luma;
597
cristy3ed852e2009-09-05 21:47:34 +0000598 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +0000599 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000600
cristy8d4629b2010-08-30 17:59:46 +0000601 register ssize_t
602 x;
603
cristy3ed852e2009-09-05 21:47:34 +0000604 if (status == MagickFalse)
605 continue;
606 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
607 if (q == (PixelPacket *) NULL)
608 {
609 status=MagickFalse;
610 continue;
611 }
cristybb503372010-05-27 20:51:26 +0000612 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000613 {
614 luma=0.2126*q->red+0.7152*q->green+0.0722*q->blue;
cristyce70c172010-01-07 17:15:30 +0000615 q->red=ClampToQuantum(luma+color_correction.saturation*
cristy3ed852e2009-09-05 21:47:34 +0000616 (cdl_map[ScaleQuantumToMap(q->red)].red-luma));
cristyce70c172010-01-07 17:15:30 +0000617 q->green=ClampToQuantum(luma+color_correction.saturation*
cristy3ed852e2009-09-05 21:47:34 +0000618 (cdl_map[ScaleQuantumToMap(q->green)].green-luma));
cristyce70c172010-01-07 17:15:30 +0000619 q->blue=ClampToQuantum(luma+color_correction.saturation*
cristy3ed852e2009-09-05 21:47:34 +0000620 (cdl_map[ScaleQuantumToMap(q->blue)].blue-luma));
621 q++;
622 }
623 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
624 status=MagickFalse;
625 if (image->progress_monitor != (MagickProgressMonitor) NULL)
626 {
627 MagickBooleanType
628 proceed;
629
cristyb5d5f722009-11-04 03:03:49 +0000630#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +0000631 #pragma omp critical (MagickCore_ColorDecisionListImageChannel)
632#endif
633 proceed=SetImageProgress(image,ColorDecisionListCorrectImageTag,
634 progress++,image->rows);
635 if (proceed == MagickFalse)
636 status=MagickFalse;
637 }
638 }
639 image_view=DestroyCacheView(image_view);
640 cdl_map=(PixelPacket *) RelinquishMagickMemory(cdl_map);
641 return(status);
642}
643
644/*
645%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
646% %
647% %
648% %
649% C l u t I m a g e %
650% %
651% %
652% %
653%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
654%
655% ClutImage() replaces each color value in the given image, by using it as an
656% index to lookup a replacement color value in a Color Look UP Table in the
cristycee97112010-05-28 00:44:52 +0000657% form of an image. The values are extracted along a diagonal of the CLUT
cristy3ed852e2009-09-05 21:47:34 +0000658% image so either a horizontal or vertial gradient image can be used.
659%
660% Typically this is used to either re-color a gray-scale image according to a
661% color gradient in the CLUT image, or to perform a freeform histogram
662% (level) adjustment according to the (typically gray-scale) gradient in the
663% CLUT image.
664%
665% When the 'channel' mask includes the matte/alpha transparency channel but
666% one image has no such channel it is assumed that that image is a simple
667% gray-scale image that will effect the alpha channel values, either for
668% gray-scale coloring (with transparent or semi-transparent colors), or
669% a histogram adjustment of existing alpha channel values. If both images
670% have matte channels, direct and normal indexing is applied, which is rarely
671% used.
672%
673% The format of the ClutImage method is:
674%
675% MagickBooleanType ClutImage(Image *image,Image *clut_image)
676% MagickBooleanType ClutImageChannel(Image *image,
677% const ChannelType channel,Image *clut_image)
678%
679% A description of each parameter follows:
680%
681% o image: the image, which is replaced by indexed CLUT values
682%
683% o clut_image: the color lookup table image for replacement color values.
684%
685% o channel: the channel.
686%
687*/
688
689MagickExport MagickBooleanType ClutImage(Image *image,const Image *clut_image)
690{
691 return(ClutImageChannel(image,DefaultChannels,clut_image));
692}
693
694MagickExport MagickBooleanType ClutImageChannel(Image *image,
695 const ChannelType channel,const Image *clut_image)
696{
697#define ClutImageTag "Clut/Image"
698
cristyfa112112010-01-04 17:48:07 +0000699 CacheView
700 *image_view;
701
cristy3ed852e2009-09-05 21:47:34 +0000702 ExceptionInfo
703 *exception;
704
cristy3ed852e2009-09-05 21:47:34 +0000705 MagickBooleanType
706 status;
707
cristybb503372010-05-27 20:51:26 +0000708 MagickOffsetType
709 progress;
710
cristy3ed852e2009-09-05 21:47:34 +0000711 MagickPixelPacket
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 {
cristy6ebe97c2010-07-03 01:17:28 +0000745 int
746 id;
747
cristy3ed852e2009-09-05 21:47:34 +0000748 MagickPixelPacket
749 pixel;
750
751 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +0000752 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +0000753
cristy3ed852e2009-09-05 21:47:34 +0000754 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +0000755 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000756
cristy8d4629b2010-08-30 17:59:46 +0000757 register ssize_t
758 x;
759
cristy3ed852e2009-09-05 21:47:34 +0000760 if (status == MagickFalse)
761 continue;
762 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
763 if (q == (PixelPacket *) NULL)
764 {
765 status=MagickFalse;
766 continue;
767 }
768 indexes=GetCacheViewAuthenticIndexQueue(image_view);
769 pixel=zero;
770 id=GetOpenMPThreadId();
cristybb503372010-05-27 20:51:26 +0000771 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000772 {
773 /*
774 PROGRAMMERS WARNING:
775
776 Apply OpacityChannel BEFORE the color channels. Do not re-order.
777
778 The handling special case 2 (coloring gray-scale), requires access to
779 the unmodified colors of the original image to determine the index
780 value. As such alpha/matte channel handling must be performed BEFORE,
781 any of the color channels are modified.
782
783 */
784 if ((channel & OpacityChannel) != 0)
785 {
786 if (clut_image->matte == MagickFalse)
787 {
788 /*
789 A gray-scale LUT replacement for an image alpha channel.
790 */
791 (void) ResamplePixelColor(resample_filter[id],QuantumScale*
cristy46f08202010-01-10 04:04:21 +0000792 GetAlphaPixelComponent(q)*(clut_image->columns+adjust),
793 QuantumScale*GetAlphaPixelComponent(q)*(clut_image->rows+
cristy3ed852e2009-09-05 21:47:34 +0000794 adjust),&pixel);
795 q->opacity=(Quantum) (QuantumRange-MagickPixelIntensityToQuantum(
796 &pixel));
797 }
798 else
799 if (image->matte == MagickFalse)
800 {
801 /*
802 A greyscale image being colored by a LUT with transparency.
803 */
804 (void) ResamplePixelColor(resample_filter[id],QuantumScale*
805 PixelIntensity(q)*(clut_image->columns-adjust),QuantumScale*
806 PixelIntensity(q)*(clut_image->rows-adjust),&pixel);
cristyce70c172010-01-07 17:15:30 +0000807 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +0000808 }
809 else
810 {
811 /*
812 Direct alpha channel lookup.
813 */
814 (void) ResamplePixelColor(resample_filter[id],QuantumScale*
815 q->opacity*(clut_image->columns-adjust),QuantumScale*
816 q->opacity* (clut_image->rows-adjust),&pixel);
cristyce70c172010-01-07 17:15:30 +0000817 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +0000818 }
819 }
820 if ((channel & RedChannel) != 0)
821 {
822 (void) ResamplePixelColor(resample_filter[id],QuantumScale*q->red*
823 (clut_image->columns-adjust),QuantumScale*q->red*
824 (clut_image->rows-adjust),&pixel);
cristyce70c172010-01-07 17:15:30 +0000825 SetRedPixelComponent(q,ClampRedPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +0000826 }
827 if ((channel & GreenChannel) != 0)
828 {
829 (void) ResamplePixelColor(resample_filter[id],QuantumScale*q->green*
830 (clut_image->columns-adjust),QuantumScale*q->green*
831 (clut_image->rows-adjust),&pixel);
cristyce70c172010-01-07 17:15:30 +0000832 SetGreenPixelComponent(q,ClampGreenPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +0000833 }
834 if ((channel & BlueChannel) != 0)
835 {
836 (void) ResamplePixelColor(resample_filter[id],QuantumScale*q->blue*
837 (clut_image->columns-adjust),QuantumScale*q->blue*
838 (clut_image->rows-adjust),&pixel);
cristyce70c172010-01-07 17:15:30 +0000839 SetBluePixelComponent(q,ClampBluePixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +0000840 }
841 if (((channel & IndexChannel) != 0) &&
842 (image->colorspace == CMYKColorspace))
843 {
844 (void) ResamplePixelColor(resample_filter[id],QuantumScale*indexes[x]*
845 (clut_image->columns-adjust),QuantumScale*indexes[x]*
846 (clut_image->rows-adjust),&pixel);
cristyce70c172010-01-07 17:15:30 +0000847 indexes[x]=ClampToQuantum(pixel.index);
cristy3ed852e2009-09-05 21:47:34 +0000848 }
849 q++;
850 }
851 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
852 status=MagickFalse;
853 if (image->progress_monitor != (MagickProgressMonitor) NULL)
854 {
855 MagickBooleanType
856 proceed;
857
cristyb5d5f722009-11-04 03:03:49 +0000858#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +0000859 #pragma omp critical (MagickCore_ClutImageChannel)
860#endif
861 proceed=SetImageProgress(image,ClutImageTag,progress++,image->rows);
862 if (proceed == MagickFalse)
863 status=MagickFalse;
864 }
865 }
866 image_view=DestroyCacheView(image_view);
867 resample_filter=DestroyResampleFilterThreadSet(resample_filter);
868 /*
869 Enable alpha channel if CLUT image could enable it.
870 */
871 if ((clut_image->matte != MagickFalse) && ((channel & OpacityChannel) != 0))
872 (void) SetImageAlphaChannel(image,ActivateAlphaChannel);
873 return(status);
874}
875
876/*
877%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
878% %
879% %
880% %
881% C o n t r a s t I m a g e %
882% %
883% %
884% %
885%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
886%
887% ContrastImage() enhances the intensity differences between the lighter and
888% darker elements of the image. Set sharpen to a MagickTrue to increase the
889% image contrast otherwise the contrast is reduced.
890%
891% The format of the ContrastImage method is:
892%
893% MagickBooleanType ContrastImage(Image *image,
894% const MagickBooleanType sharpen)
895%
896% A description of each parameter follows:
897%
898% o image: the image.
899%
900% o sharpen: Increase or decrease image contrast.
901%
902*/
903
904static void Contrast(const int sign,Quantum *red,Quantum *green,Quantum *blue)
905{
906 double
907 brightness,
908 hue,
909 saturation;
910
911 /*
912 Enhance contrast: dark color become darker, light color become lighter.
913 */
914 assert(red != (Quantum *) NULL);
915 assert(green != (Quantum *) NULL);
916 assert(blue != (Quantum *) NULL);
917 hue=0.0;
918 saturation=0.0;
919 brightness=0.0;
920 ConvertRGBToHSB(*red,*green,*blue,&hue,&saturation,&brightness);
cristy4205a3c2010-09-12 20:19:59 +0000921 brightness+=0.5*sign*(0.5*(sin((double) (MagickPI*(brightness-0.5))+1.0))-
922 brightness);
cristy3ed852e2009-09-05 21:47:34 +0000923 if (brightness > 1.0)
924 brightness=1.0;
925 else
926 if (brightness < 0.0)
927 brightness=0.0;
928 ConvertHSBToRGB(hue,saturation,brightness,red,green,blue);
929}
930
931MagickExport MagickBooleanType ContrastImage(Image *image,
932 const MagickBooleanType sharpen)
933{
934#define ContrastImageTag "Contrast/Image"
935
cristyc4c8d132010-01-07 01:58:38 +0000936 CacheView
937 *image_view;
938
cristy3ed852e2009-09-05 21:47:34 +0000939 ExceptionInfo
940 *exception;
941
942 int
943 sign;
944
cristy3ed852e2009-09-05 21:47:34 +0000945 MagickBooleanType
946 status;
947
cristybb503372010-05-27 20:51:26 +0000948 MagickOffsetType
949 progress;
950
951 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000952 i;
953
cristybb503372010-05-27 20:51:26 +0000954 ssize_t
955 y;
956
cristy3ed852e2009-09-05 21:47:34 +0000957 assert(image != (Image *) NULL);
958 assert(image->signature == MagickSignature);
959 if (image->debug != MagickFalse)
960 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
961 sign=sharpen != MagickFalse ? 1 : -1;
962 if (image->storage_class == PseudoClass)
963 {
964 /*
965 Contrast enhance colormap.
966 */
cristybb503372010-05-27 20:51:26 +0000967 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +0000968 Contrast(sign,&image->colormap[i].red,&image->colormap[i].green,
969 &image->colormap[i].blue);
970 }
971 /*
972 Contrast enhance image.
973 */
974 status=MagickTrue;
975 progress=0;
976 exception=(&image->exception);
977 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +0000978#if defined(MAGICKCORE_OPENMP_SUPPORT)
979 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000980#endif
cristybb503372010-05-27 20:51:26 +0000981 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000982 {
cristy3ed852e2009-09-05 21:47:34 +0000983 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +0000984 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000985
cristy8d4629b2010-08-30 17:59:46 +0000986 register ssize_t
987 x;
988
cristy3ed852e2009-09-05 21:47:34 +0000989 if (status == MagickFalse)
990 continue;
991 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
992 if (q == (PixelPacket *) NULL)
993 {
994 status=MagickFalse;
995 continue;
996 }
cristybb503372010-05-27 20:51:26 +0000997 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000998 {
999 Contrast(sign,&q->red,&q->green,&q->blue);
1000 q++;
1001 }
1002 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
1003 status=MagickFalse;
1004 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1005 {
1006 MagickBooleanType
1007 proceed;
1008
cristyb5d5f722009-11-04 03:03:49 +00001009#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00001010 #pragma omp critical (MagickCore_ContrastImage)
1011#endif
1012 proceed=SetImageProgress(image,ContrastImageTag,progress++,image->rows);
1013 if (proceed == MagickFalse)
1014 status=MagickFalse;
1015 }
1016 }
1017 image_view=DestroyCacheView(image_view);
1018 return(status);
1019}
1020
1021/*
1022%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1023% %
1024% %
1025% %
1026% C o n t r a s t S t r e t c h I m a g e %
1027% %
1028% %
1029% %
1030%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1031%
1032% The ContrastStretchImage() is a simple image enhancement technique that
1033% attempts to improve the contrast in an image by `stretching' the range of
1034% intensity values it contains to span a desired range of values. It differs
1035% from the more sophisticated histogram equalization in that it can only
1036% apply % a linear scaling function to the image pixel values. As a result
1037% the `enhancement' is less harsh.
1038%
1039% The format of the ContrastStretchImage method is:
1040%
1041% MagickBooleanType ContrastStretchImage(Image *image,
1042% const char *levels)
1043% MagickBooleanType ContrastStretchImageChannel(Image *image,
cristybb503372010-05-27 20:51:26 +00001044% const size_t channel,const double black_point,
cristy3ed852e2009-09-05 21:47:34 +00001045% const double white_point)
1046%
1047% A description of each parameter follows:
1048%
1049% o image: the image.
1050%
1051% o channel: the channel.
1052%
1053% o black_point: the black point.
1054%
1055% o white_point: the white point.
1056%
1057% o levels: Specify the levels where the black and white points have the
1058% range of 0 to number-of-pixels (e.g. 1%, 10x90%, etc.).
1059%
1060*/
1061
1062MagickExport MagickBooleanType ContrastStretchImage(Image *image,
1063 const char *levels)
1064{
1065 double
1066 black_point,
1067 white_point;
1068
1069 GeometryInfo
1070 geometry_info;
1071
1072 MagickBooleanType
1073 status;
1074
1075 MagickStatusType
1076 flags;
1077
1078 /*
1079 Parse levels.
1080 */
1081 if (levels == (char *) NULL)
1082 return(MagickFalse);
1083 flags=ParseGeometry(levels,&geometry_info);
1084 black_point=geometry_info.rho;
1085 white_point=(double) image->columns*image->rows;
1086 if ((flags & SigmaValue) != 0)
1087 white_point=geometry_info.sigma;
1088 if ((flags & PercentValue) != 0)
1089 {
1090 black_point*=(double) QuantumRange/100.0;
1091 white_point*=(double) QuantumRange/100.0;
1092 }
1093 if ((flags & SigmaValue) == 0)
1094 white_point=(double) image->columns*image->rows-black_point;
1095 status=ContrastStretchImageChannel(image,DefaultChannels,black_point,
1096 white_point);
1097 return(status);
1098}
1099
1100MagickExport MagickBooleanType ContrastStretchImageChannel(Image *image,
1101 const ChannelType channel,const double black_point,const double white_point)
1102{
1103#define MaxRange(color) ((MagickRealType) ScaleQuantumToMap((Quantum) (color)))
1104#define ContrastStretchImageTag "ContrastStretch/Image"
1105
cristyc4c8d132010-01-07 01:58:38 +00001106 CacheView
1107 *image_view;
1108
cristy3ed852e2009-09-05 21:47:34 +00001109 double
1110 intensity;
1111
1112 ExceptionInfo
1113 *exception;
1114
cristy3ed852e2009-09-05 21:47:34 +00001115 MagickBooleanType
1116 status;
1117
cristybb503372010-05-27 20:51:26 +00001118 MagickOffsetType
1119 progress;
1120
cristy3ed852e2009-09-05 21:47:34 +00001121 MagickPixelPacket
1122 black,
1123 *histogram,
1124 *stretch_map,
1125 white;
1126
cristybb503372010-05-27 20:51:26 +00001127 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001128 i;
1129
cristybb503372010-05-27 20:51:26 +00001130 ssize_t
1131 y;
1132
cristy3ed852e2009-09-05 21:47:34 +00001133 /*
1134 Allocate histogram and stretch map.
1135 */
1136 assert(image != (Image *) NULL);
1137 assert(image->signature == MagickSignature);
1138 if (image->debug != MagickFalse)
1139 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1140 histogram=(MagickPixelPacket *) AcquireQuantumMemory(MaxMap+1UL,
1141 sizeof(*histogram));
1142 stretch_map=(MagickPixelPacket *) AcquireQuantumMemory(MaxMap+1UL,
1143 sizeof(*stretch_map));
1144 if ((histogram == (MagickPixelPacket *) NULL) ||
1145 (stretch_map == (MagickPixelPacket *) NULL))
1146 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
1147 image->filename);
1148 /*
1149 Form histogram.
1150 */
1151 status=MagickTrue;
1152 exception=(&image->exception);
1153 (void) ResetMagickMemory(histogram,0,(MaxMap+1)*sizeof(*histogram));
1154 image_view=AcquireCacheView(image);
cristybb503372010-05-27 20:51:26 +00001155 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001156 {
1157 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001158 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00001159
1160 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00001161 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00001162
cristybb503372010-05-27 20:51:26 +00001163 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001164 x;
1165
1166 if (status == MagickFalse)
1167 continue;
1168 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
1169 if (p == (const PixelPacket *) NULL)
1170 {
1171 status=MagickFalse;
1172 continue;
1173 }
1174 indexes=GetCacheViewAuthenticIndexQueue(image_view);
1175 if (channel == DefaultChannels)
cristybb503372010-05-27 20:51:26 +00001176 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001177 {
1178 Quantum
1179 intensity;
1180
1181 intensity=PixelIntensityToQuantum(p);
1182 histogram[ScaleQuantumToMap(intensity)].red++;
1183 histogram[ScaleQuantumToMap(intensity)].green++;
1184 histogram[ScaleQuantumToMap(intensity)].blue++;
1185 histogram[ScaleQuantumToMap(intensity)].index++;
1186 p++;
1187 }
1188 else
cristybb503372010-05-27 20:51:26 +00001189 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001190 {
1191 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001192 histogram[ScaleQuantumToMap(GetRedPixelComponent(p))].red++;
cristy3ed852e2009-09-05 21:47:34 +00001193 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001194 histogram[ScaleQuantumToMap(GetGreenPixelComponent(p))].green++;
cristy3ed852e2009-09-05 21:47:34 +00001195 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001196 histogram[ScaleQuantumToMap(GetBluePixelComponent(p))].blue++;
cristy3ed852e2009-09-05 21:47:34 +00001197 if ((channel & OpacityChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001198 histogram[ScaleQuantumToMap(GetOpacityPixelComponent(p))].opacity++;
cristy3ed852e2009-09-05 21:47:34 +00001199 if (((channel & IndexChannel) != 0) &&
1200 (image->colorspace == CMYKColorspace))
1201 histogram[ScaleQuantumToMap(indexes[x])].index++;
1202 p++;
1203 }
1204 }
1205 /*
1206 Find the histogram boundaries by locating the black/white levels.
1207 */
1208 black.red=0.0;
1209 white.red=MaxRange(QuantumRange);
1210 if ((channel & RedChannel) != 0)
1211 {
1212 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00001213 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +00001214 {
1215 intensity+=histogram[i].red;
1216 if (intensity > black_point)
1217 break;
1218 }
1219 black.red=(MagickRealType) i;
1220 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00001221 for (i=(ssize_t) MaxMap; i != 0; i--)
cristy3ed852e2009-09-05 21:47:34 +00001222 {
1223 intensity+=histogram[i].red;
1224 if (intensity > ((double) image->columns*image->rows-white_point))
1225 break;
1226 }
1227 white.red=(MagickRealType) i;
1228 }
1229 black.green=0.0;
1230 white.green=MaxRange(QuantumRange);
1231 if ((channel & GreenChannel) != 0)
1232 {
1233 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00001234 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +00001235 {
1236 intensity+=histogram[i].green;
1237 if (intensity > black_point)
1238 break;
1239 }
1240 black.green=(MagickRealType) i;
1241 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00001242 for (i=(ssize_t) MaxMap; i != 0; i--)
cristy3ed852e2009-09-05 21:47:34 +00001243 {
1244 intensity+=histogram[i].green;
1245 if (intensity > ((double) image->columns*image->rows-white_point))
1246 break;
1247 }
1248 white.green=(MagickRealType) i;
1249 }
1250 black.blue=0.0;
1251 white.blue=MaxRange(QuantumRange);
1252 if ((channel & BlueChannel) != 0)
1253 {
1254 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00001255 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +00001256 {
1257 intensity+=histogram[i].blue;
1258 if (intensity > black_point)
1259 break;
1260 }
1261 black.blue=(MagickRealType) i;
1262 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00001263 for (i=(ssize_t) MaxMap; i != 0; i--)
cristy3ed852e2009-09-05 21:47:34 +00001264 {
1265 intensity+=histogram[i].blue;
1266 if (intensity > ((double) image->columns*image->rows-white_point))
1267 break;
1268 }
1269 white.blue=(MagickRealType) i;
1270 }
1271 black.opacity=0.0;
1272 white.opacity=MaxRange(QuantumRange);
1273 if ((channel & OpacityChannel) != 0)
1274 {
1275 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00001276 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +00001277 {
1278 intensity+=histogram[i].opacity;
1279 if (intensity > black_point)
1280 break;
1281 }
1282 black.opacity=(MagickRealType) i;
1283 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00001284 for (i=(ssize_t) MaxMap; i != 0; i--)
cristy3ed852e2009-09-05 21:47:34 +00001285 {
1286 intensity+=histogram[i].opacity;
1287 if (intensity > ((double) image->columns*image->rows-white_point))
1288 break;
1289 }
1290 white.opacity=(MagickRealType) i;
1291 }
1292 black.index=0.0;
1293 white.index=MaxRange(QuantumRange);
1294 if (((channel & IndexChannel) != 0) && (image->colorspace == CMYKColorspace))
1295 {
1296 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00001297 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +00001298 {
1299 intensity+=histogram[i].index;
1300 if (intensity > black_point)
1301 break;
1302 }
1303 black.index=(MagickRealType) i;
1304 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00001305 for (i=(ssize_t) MaxMap; i != 0; i--)
cristy3ed852e2009-09-05 21:47:34 +00001306 {
1307 intensity+=histogram[i].index;
1308 if (intensity > ((double) image->columns*image->rows-white_point))
1309 break;
1310 }
1311 white.index=(MagickRealType) i;
1312 }
1313 histogram=(MagickPixelPacket *) RelinquishMagickMemory(histogram);
1314 /*
1315 Stretch the histogram to create the stretched image mapping.
1316 */
1317 (void) ResetMagickMemory(stretch_map,0,(MaxMap+1)*sizeof(*stretch_map));
cristyb5d5f722009-11-04 03:03:49 +00001318#if defined(MAGICKCORE_OPENMP_SUPPORT)
1319 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001320#endif
cristybb503372010-05-27 20:51:26 +00001321 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +00001322 {
1323 if ((channel & RedChannel) != 0)
1324 {
cristybb503372010-05-27 20:51:26 +00001325 if (i < (ssize_t) black.red)
cristy3ed852e2009-09-05 21:47:34 +00001326 stretch_map[i].red=0.0;
1327 else
cristybb503372010-05-27 20:51:26 +00001328 if (i > (ssize_t) white.red)
cristy3ed852e2009-09-05 21:47:34 +00001329 stretch_map[i].red=(MagickRealType) QuantumRange;
1330 else
1331 if (black.red != white.red)
1332 stretch_map[i].red=(MagickRealType) ScaleMapToQuantum(
1333 (MagickRealType) (MaxMap*(i-black.red)/(white.red-black.red)));
1334 }
1335 if ((channel & GreenChannel) != 0)
1336 {
cristybb503372010-05-27 20:51:26 +00001337 if (i < (ssize_t) black.green)
cristy3ed852e2009-09-05 21:47:34 +00001338 stretch_map[i].green=0.0;
1339 else
cristybb503372010-05-27 20:51:26 +00001340 if (i > (ssize_t) white.green)
cristy3ed852e2009-09-05 21:47:34 +00001341 stretch_map[i].green=(MagickRealType) QuantumRange;
1342 else
1343 if (black.green != white.green)
1344 stretch_map[i].green=(MagickRealType) ScaleMapToQuantum(
1345 (MagickRealType) (MaxMap*(i-black.green)/(white.green-
1346 black.green)));
1347 }
1348 if ((channel & BlueChannel) != 0)
1349 {
cristybb503372010-05-27 20:51:26 +00001350 if (i < (ssize_t) black.blue)
cristy3ed852e2009-09-05 21:47:34 +00001351 stretch_map[i].blue=0.0;
1352 else
cristybb503372010-05-27 20:51:26 +00001353 if (i > (ssize_t) white.blue)
cristy3ed852e2009-09-05 21:47:34 +00001354 stretch_map[i].blue=(MagickRealType) QuantumRange;
1355 else
1356 if (black.blue != white.blue)
1357 stretch_map[i].blue=(MagickRealType) ScaleMapToQuantum(
1358 (MagickRealType) (MaxMap*(i-black.blue)/(white.blue-
1359 black.blue)));
1360 }
1361 if ((channel & OpacityChannel) != 0)
1362 {
cristybb503372010-05-27 20:51:26 +00001363 if (i < (ssize_t) black.opacity)
cristy3ed852e2009-09-05 21:47:34 +00001364 stretch_map[i].opacity=0.0;
1365 else
cristybb503372010-05-27 20:51:26 +00001366 if (i > (ssize_t) white.opacity)
cristy3ed852e2009-09-05 21:47:34 +00001367 stretch_map[i].opacity=(MagickRealType) QuantumRange;
1368 else
1369 if (black.opacity != white.opacity)
1370 stretch_map[i].opacity=(MagickRealType) ScaleMapToQuantum(
1371 (MagickRealType) (MaxMap*(i-black.opacity)/(white.opacity-
1372 black.opacity)));
1373 }
1374 if (((channel & IndexChannel) != 0) &&
1375 (image->colorspace == CMYKColorspace))
1376 {
cristybb503372010-05-27 20:51:26 +00001377 if (i < (ssize_t) black.index)
cristy3ed852e2009-09-05 21:47:34 +00001378 stretch_map[i].index=0.0;
1379 else
cristybb503372010-05-27 20:51:26 +00001380 if (i > (ssize_t) white.index)
cristy3ed852e2009-09-05 21:47:34 +00001381 stretch_map[i].index=(MagickRealType) QuantumRange;
1382 else
1383 if (black.index != white.index)
1384 stretch_map[i].index=(MagickRealType) ScaleMapToQuantum(
1385 (MagickRealType) (MaxMap*(i-black.index)/(white.index-
1386 black.index)));
1387 }
1388 }
1389 /*
1390 Stretch the image.
1391 */
1392 if (((channel & OpacityChannel) != 0) || (((channel & IndexChannel) != 0) &&
1393 (image->colorspace == CMYKColorspace)))
1394 image->storage_class=DirectClass;
1395 if (image->storage_class == PseudoClass)
1396 {
1397 /*
1398 Stretch colormap.
1399 */
cristyb5d5f722009-11-04 03:03:49 +00001400#if defined(MAGICKCORE_OPENMP_SUPPORT)
1401 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001402#endif
cristybb503372010-05-27 20:51:26 +00001403 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00001404 {
1405 if ((channel & RedChannel) != 0)
1406 {
1407 if (black.red != white.red)
cristyce70c172010-01-07 17:15:30 +00001408 image->colormap[i].red=ClampToQuantum(stretch_map[
cristy3ed852e2009-09-05 21:47:34 +00001409 ScaleQuantumToMap(image->colormap[i].red)].red);
1410 }
1411 if ((channel & GreenChannel) != 0)
1412 {
1413 if (black.green != white.green)
cristyce70c172010-01-07 17:15:30 +00001414 image->colormap[i].green=ClampToQuantum(stretch_map[
cristy3ed852e2009-09-05 21:47:34 +00001415 ScaleQuantumToMap(image->colormap[i].green)].green);
1416 }
1417 if ((channel & BlueChannel) != 0)
1418 {
1419 if (black.blue != white.blue)
cristyce70c172010-01-07 17:15:30 +00001420 image->colormap[i].blue=ClampToQuantum(stretch_map[
cristy3ed852e2009-09-05 21:47:34 +00001421 ScaleQuantumToMap(image->colormap[i].blue)].blue);
1422 }
1423 if ((channel & OpacityChannel) != 0)
1424 {
1425 if (black.opacity != white.opacity)
cristyce70c172010-01-07 17:15:30 +00001426 image->colormap[i].opacity=ClampToQuantum(stretch_map[
cristy3ed852e2009-09-05 21:47:34 +00001427 ScaleQuantumToMap(image->colormap[i].opacity)].opacity);
1428 }
1429 }
1430 }
1431 /*
1432 Stretch image.
1433 */
1434 status=MagickTrue;
1435 progress=0;
cristyb5d5f722009-11-04 03:03:49 +00001436#if defined(MAGICKCORE_OPENMP_SUPPORT)
1437 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001438#endif
cristybb503372010-05-27 20:51:26 +00001439 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001440 {
1441 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00001442 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00001443
cristy3ed852e2009-09-05 21:47:34 +00001444 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001445 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001446
cristy8d4629b2010-08-30 17:59:46 +00001447 register ssize_t
1448 x;
1449
cristy3ed852e2009-09-05 21:47:34 +00001450 if (status == MagickFalse)
1451 continue;
1452 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
1453 if (q == (PixelPacket *) NULL)
1454 {
1455 status=MagickFalse;
1456 continue;
1457 }
1458 indexes=GetCacheViewAuthenticIndexQueue(image_view);
cristybb503372010-05-27 20:51:26 +00001459 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001460 {
1461 if ((channel & RedChannel) != 0)
1462 {
1463 if (black.red != white.red)
cristyce70c172010-01-07 17:15:30 +00001464 q->red=ClampToQuantum(stretch_map[ScaleQuantumToMap(q->red)].red);
cristy3ed852e2009-09-05 21:47:34 +00001465 }
1466 if ((channel & GreenChannel) != 0)
1467 {
1468 if (black.green != white.green)
cristyce70c172010-01-07 17:15:30 +00001469 q->green=ClampToQuantum(stretch_map[ScaleQuantumToMap(
cristy3ed852e2009-09-05 21:47:34 +00001470 q->green)].green);
1471 }
1472 if ((channel & BlueChannel) != 0)
1473 {
1474 if (black.blue != white.blue)
cristyce70c172010-01-07 17:15:30 +00001475 q->blue=ClampToQuantum(stretch_map[ScaleQuantumToMap(
cristy3ed852e2009-09-05 21:47:34 +00001476 q->blue)].blue);
1477 }
1478 if ((channel & OpacityChannel) != 0)
1479 {
1480 if (black.opacity != white.opacity)
cristyce70c172010-01-07 17:15:30 +00001481 q->opacity=ClampToQuantum(stretch_map[ScaleQuantumToMap(
cristy3ed852e2009-09-05 21:47:34 +00001482 q->opacity)].opacity);
1483 }
1484 if (((channel & IndexChannel) != 0) &&
1485 (image->colorspace == CMYKColorspace))
1486 {
1487 if (black.index != white.index)
cristyce70c172010-01-07 17:15:30 +00001488 indexes[x]=(IndexPacket) ClampToQuantum(stretch_map[
cristy3ed852e2009-09-05 21:47:34 +00001489 ScaleQuantumToMap(indexes[x])].index);
1490 }
1491 q++;
1492 }
1493 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
1494 status=MagickFalse;
1495 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1496 {
1497 MagickBooleanType
1498 proceed;
1499
cristyb5d5f722009-11-04 03:03:49 +00001500#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00001501 #pragma omp critical (MagickCore_ContrastStretchImageChannel)
1502#endif
1503 proceed=SetImageProgress(image,ContrastStretchImageTag,progress++,
1504 image->rows);
1505 if (proceed == MagickFalse)
1506 status=MagickFalse;
1507 }
1508 }
1509 image_view=DestroyCacheView(image_view);
1510 stretch_map=(MagickPixelPacket *) RelinquishMagickMemory(stretch_map);
1511 return(status);
1512}
1513
1514/*
1515%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1516% %
1517% %
1518% %
1519% E n h a n c e I m a g e %
1520% %
1521% %
1522% %
1523%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1524%
1525% EnhanceImage() applies a digital filter that improves the quality of a
1526% noisy image.
1527%
1528% The format of the EnhanceImage method is:
1529%
1530% Image *EnhanceImage(const Image *image,ExceptionInfo *exception)
1531%
1532% A description of each parameter follows:
1533%
1534% o image: the image.
1535%
1536% o exception: return any errors or warnings in this structure.
1537%
1538*/
1539MagickExport Image *EnhanceImage(const Image *image,ExceptionInfo *exception)
1540{
1541#define Enhance(weight) \
1542 mean=((MagickRealType) r->red+pixel.red)/2; \
1543 distance=(MagickRealType) r->red-(MagickRealType) pixel.red; \
1544 distance_squared=QuantumScale*(2.0*((MagickRealType) QuantumRange+1.0)+ \
1545 mean)*distance*distance; \
1546 mean=((MagickRealType) r->green+pixel.green)/2; \
1547 distance=(MagickRealType) r->green-(MagickRealType) pixel.green; \
1548 distance_squared+=4.0*distance*distance; \
1549 mean=((MagickRealType) r->blue+pixel.blue)/2; \
1550 distance=(MagickRealType) r->blue-(MagickRealType) pixel.blue; \
1551 distance_squared+=QuantumScale*(3.0*((MagickRealType) \
1552 QuantumRange+1.0)-1.0-mean)*distance*distance; \
1553 mean=((MagickRealType) r->opacity+pixel.opacity)/2; \
1554 distance=(MagickRealType) r->opacity-(MagickRealType) pixel.opacity; \
1555 distance_squared+=QuantumScale*(3.0*((MagickRealType) \
1556 QuantumRange+1.0)-1.0-mean)*distance*distance; \
1557 if (distance_squared < ((MagickRealType) QuantumRange*(MagickRealType) \
1558 QuantumRange/25.0f)) \
1559 { \
1560 aggregate.red+=(weight)*r->red; \
1561 aggregate.green+=(weight)*r->green; \
1562 aggregate.blue+=(weight)*r->blue; \
1563 aggregate.opacity+=(weight)*r->opacity; \
1564 total_weight+=(weight); \
1565 } \
1566 r++;
1567#define EnhanceImageTag "Enhance/Image"
1568
cristyc4c8d132010-01-07 01:58:38 +00001569 CacheView
1570 *enhance_view,
1571 *image_view;
1572
cristy3ed852e2009-09-05 21:47:34 +00001573 Image
1574 *enhance_image;
1575
cristy3ed852e2009-09-05 21:47:34 +00001576 MagickBooleanType
1577 status;
1578
cristybb503372010-05-27 20:51:26 +00001579 MagickOffsetType
1580 progress;
1581
cristy3ed852e2009-09-05 21:47:34 +00001582 MagickPixelPacket
1583 zero;
1584
cristybb503372010-05-27 20:51:26 +00001585 ssize_t
1586 y;
1587
cristy3ed852e2009-09-05 21:47:34 +00001588 /*
1589 Initialize enhanced image attributes.
1590 */
1591 assert(image != (const Image *) NULL);
1592 assert(image->signature == MagickSignature);
1593 if (image->debug != MagickFalse)
1594 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1595 assert(exception != (ExceptionInfo *) NULL);
1596 assert(exception->signature == MagickSignature);
1597 if ((image->columns < 5) || (image->rows < 5))
1598 return((Image *) NULL);
1599 enhance_image=CloneImage(image,image->columns,image->rows,MagickTrue,
1600 exception);
1601 if (enhance_image == (Image *) NULL)
1602 return((Image *) NULL);
1603 if (SetImageStorageClass(enhance_image,DirectClass) == MagickFalse)
1604 {
1605 InheritException(exception,&enhance_image->exception);
1606 enhance_image=DestroyImage(enhance_image);
1607 return((Image *) NULL);
1608 }
1609 /*
1610 Enhance image.
1611 */
1612 status=MagickTrue;
1613 progress=0;
1614 (void) ResetMagickMemory(&zero,0,sizeof(zero));
1615 image_view=AcquireCacheView(image);
1616 enhance_view=AcquireCacheView(enhance_image);
cristyb5d5f722009-11-04 03:03:49 +00001617#if defined(MAGICKCORE_OPENMP_SUPPORT)
1618 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001619#endif
cristybb503372010-05-27 20:51:26 +00001620 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001621 {
1622 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001623 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00001624
cristy3ed852e2009-09-05 21:47:34 +00001625 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001626 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001627
cristy8d4629b2010-08-30 17:59:46 +00001628 register ssize_t
1629 x;
1630
cristy3ed852e2009-09-05 21:47:34 +00001631 /*
1632 Read another scan line.
1633 */
1634 if (status == MagickFalse)
1635 continue;
1636 p=GetCacheViewVirtualPixels(image_view,-2,y-2,image->columns+4,5,exception);
1637 q=QueueCacheViewAuthenticPixels(enhance_view,0,y,enhance_image->columns,1,
1638 exception);
1639 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
1640 {
1641 status=MagickFalse;
1642 continue;
1643 }
cristybb503372010-05-27 20:51:26 +00001644 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001645 {
1646 MagickPixelPacket
1647 aggregate;
1648
1649 MagickRealType
1650 distance,
1651 distance_squared,
1652 mean,
1653 total_weight;
1654
1655 PixelPacket
1656 pixel;
1657
1658 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001659 *restrict r;
cristy3ed852e2009-09-05 21:47:34 +00001660
1661 /*
1662 Compute weighted average of target pixel color components.
1663 */
1664 aggregate=zero;
1665 total_weight=0.0;
1666 r=p+2*(image->columns+4)+2;
1667 pixel=(*r);
1668 r=p;
1669 Enhance(5.0); Enhance(8.0); Enhance(10.0); Enhance(8.0); Enhance(5.0);
1670 r=p+(image->columns+4);
1671 Enhance(8.0); Enhance(20.0); Enhance(40.0); Enhance(20.0); Enhance(8.0);
1672 r=p+2*(image->columns+4);
1673 Enhance(10.0); Enhance(40.0); Enhance(80.0); Enhance(40.0); Enhance(10.0);
1674 r=p+3*(image->columns+4);
1675 Enhance(8.0); Enhance(20.0); Enhance(40.0); Enhance(20.0); Enhance(8.0);
1676 r=p+4*(image->columns+4);
1677 Enhance(5.0); Enhance(8.0); Enhance(10.0); Enhance(8.0); Enhance(5.0);
1678 q->red=(Quantum) ((aggregate.red+(total_weight/2)-1)/total_weight);
1679 q->green=(Quantum) ((aggregate.green+(total_weight/2)-1)/total_weight);
1680 q->blue=(Quantum) ((aggregate.blue+(total_weight/2)-1)/total_weight);
1681 q->opacity=(Quantum) ((aggregate.opacity+(total_weight/2)-1)/
1682 total_weight);
1683 p++;
1684 q++;
1685 }
1686 if (SyncCacheViewAuthenticPixels(enhance_view,exception) == MagickFalse)
1687 status=MagickFalse;
1688 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1689 {
1690 MagickBooleanType
1691 proceed;
1692
cristyb5d5f722009-11-04 03:03:49 +00001693#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00001694 #pragma omp critical (MagickCore_EnhanceImage)
1695#endif
1696 proceed=SetImageProgress(image,EnhanceImageTag,progress++,image->rows);
1697 if (proceed == MagickFalse)
1698 status=MagickFalse;
1699 }
1700 }
1701 enhance_view=DestroyCacheView(enhance_view);
1702 image_view=DestroyCacheView(image_view);
1703 return(enhance_image);
1704}
1705
1706/*
1707%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1708% %
1709% %
1710% %
1711% E q u a l i z e I m a g e %
1712% %
1713% %
1714% %
1715%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1716%
1717% EqualizeImage() applies a histogram equalization to the image.
1718%
1719% The format of the EqualizeImage method is:
1720%
1721% MagickBooleanType EqualizeImage(Image *image)
1722% MagickBooleanType EqualizeImageChannel(Image *image,
1723% const ChannelType channel)
1724%
1725% A description of each parameter follows:
1726%
1727% o image: the image.
1728%
1729% o channel: the channel.
1730%
1731*/
1732
1733MagickExport MagickBooleanType EqualizeImage(Image *image)
1734{
1735 return(EqualizeImageChannel(image,DefaultChannels));
1736}
1737
1738MagickExport MagickBooleanType EqualizeImageChannel(Image *image,
1739 const ChannelType channel)
1740{
1741#define EqualizeImageTag "Equalize/Image"
1742
cristyc4c8d132010-01-07 01:58:38 +00001743 CacheView
1744 *image_view;
1745
cristy3ed852e2009-09-05 21:47:34 +00001746 ExceptionInfo
1747 *exception;
1748
cristy3ed852e2009-09-05 21:47:34 +00001749 MagickBooleanType
1750 status;
1751
cristybb503372010-05-27 20:51:26 +00001752 MagickOffsetType
1753 progress;
1754
cristy3ed852e2009-09-05 21:47:34 +00001755 MagickPixelPacket
1756 black,
1757 *equalize_map,
1758 *histogram,
1759 intensity,
1760 *map,
1761 white;
1762
cristybb503372010-05-27 20:51:26 +00001763 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001764 i;
1765
cristybb503372010-05-27 20:51:26 +00001766 ssize_t
1767 y;
1768
cristy3ed852e2009-09-05 21:47:34 +00001769 /*
1770 Allocate and initialize histogram arrays.
1771 */
1772 assert(image != (Image *) NULL);
1773 assert(image->signature == MagickSignature);
1774 if (image->debug != MagickFalse)
1775 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1776 equalize_map=(MagickPixelPacket *) AcquireQuantumMemory(MaxMap+1UL,
1777 sizeof(*equalize_map));
1778 histogram=(MagickPixelPacket *) AcquireQuantumMemory(MaxMap+1UL,
1779 sizeof(*histogram));
1780 map=(MagickPixelPacket *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*map));
1781 if ((equalize_map == (MagickPixelPacket *) NULL) ||
1782 (histogram == (MagickPixelPacket *) NULL) ||
1783 (map == (MagickPixelPacket *) NULL))
1784 {
1785 if (map != (MagickPixelPacket *) NULL)
1786 map=(MagickPixelPacket *) RelinquishMagickMemory(map);
1787 if (histogram != (MagickPixelPacket *) NULL)
1788 histogram=(MagickPixelPacket *) RelinquishMagickMemory(histogram);
1789 if (equalize_map != (MagickPixelPacket *) NULL)
1790 equalize_map=(MagickPixelPacket *) RelinquishMagickMemory(equalize_map);
1791 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
1792 image->filename);
1793 }
1794 /*
1795 Form histogram.
1796 */
1797 (void) ResetMagickMemory(histogram,0,(MaxMap+1)*sizeof(*histogram));
1798 exception=(&image->exception);
cristybb503372010-05-27 20:51:26 +00001799 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001800 {
1801 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00001802 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00001803
1804 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001805 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00001806
cristybb503372010-05-27 20:51:26 +00001807 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001808 x;
1809
1810 p=GetVirtualPixels(image,0,y,image->columns,1,exception);
1811 if (p == (const PixelPacket *) NULL)
1812 break;
1813 indexes=GetVirtualIndexQueue(image);
cristybb503372010-05-27 20:51:26 +00001814 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001815 {
1816 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001817 histogram[ScaleQuantumToMap(GetRedPixelComponent(p))].red++;
cristy3ed852e2009-09-05 21:47:34 +00001818 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001819 histogram[ScaleQuantumToMap(GetGreenPixelComponent(p))].green++;
cristy3ed852e2009-09-05 21:47:34 +00001820 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001821 histogram[ScaleQuantumToMap(GetBluePixelComponent(p))].blue++;
cristy3ed852e2009-09-05 21:47:34 +00001822 if ((channel & OpacityChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001823 histogram[ScaleQuantumToMap(GetOpacityPixelComponent(p))].opacity++;
cristy3ed852e2009-09-05 21:47:34 +00001824 if (((channel & IndexChannel) != 0) &&
1825 (image->colorspace == CMYKColorspace))
1826 histogram[ScaleQuantumToMap(indexes[x])].index++;
1827 p++;
1828 }
1829 }
1830 /*
1831 Integrate the histogram to get the equalization map.
1832 */
1833 (void) ResetMagickMemory(&intensity,0,sizeof(intensity));
cristybb503372010-05-27 20:51:26 +00001834 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +00001835 {
1836 if ((channel & RedChannel) != 0)
1837 intensity.red+=histogram[i].red;
1838 if ((channel & GreenChannel) != 0)
1839 intensity.green+=histogram[i].green;
1840 if ((channel & BlueChannel) != 0)
1841 intensity.blue+=histogram[i].blue;
1842 if ((channel & OpacityChannel) != 0)
1843 intensity.opacity+=histogram[i].opacity;
1844 if (((channel & IndexChannel) != 0) &&
1845 (image->colorspace == CMYKColorspace))
1846 intensity.index+=histogram[i].index;
1847 map[i]=intensity;
1848 }
1849 black=map[0];
1850 white=map[(int) MaxMap];
1851 (void) ResetMagickMemory(equalize_map,0,(MaxMap+1)*sizeof(*equalize_map));
cristyb5d5f722009-11-04 03:03:49 +00001852#if defined(MAGICKCORE_OPENMP_SUPPORT)
1853 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001854#endif
cristybb503372010-05-27 20:51:26 +00001855 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +00001856 {
1857 if (((channel & RedChannel) != 0) && (white.red != black.red))
1858 equalize_map[i].red=(MagickRealType) ScaleMapToQuantum((MagickRealType)
1859 ((MaxMap*(map[i].red-black.red))/(white.red-black.red)));
1860 if (((channel & GreenChannel) != 0) && (white.green != black.green))
1861 equalize_map[i].green=(MagickRealType) ScaleMapToQuantum((MagickRealType)
1862 ((MaxMap*(map[i].green-black.green))/(white.green-black.green)));
1863 if (((channel & BlueChannel) != 0) && (white.blue != black.blue))
1864 equalize_map[i].blue=(MagickRealType) ScaleMapToQuantum((MagickRealType)
1865 ((MaxMap*(map[i].blue-black.blue))/(white.blue-black.blue)));
1866 if (((channel & OpacityChannel) != 0) && (white.opacity != black.opacity))
1867 equalize_map[i].opacity=(MagickRealType) ScaleMapToQuantum(
1868 (MagickRealType) ((MaxMap*(map[i].opacity-black.opacity))/
1869 (white.opacity-black.opacity)));
1870 if ((((channel & IndexChannel) != 0) &&
1871 (image->colorspace == CMYKColorspace)) &&
1872 (white.index != black.index))
1873 equalize_map[i].index=(MagickRealType) ScaleMapToQuantum((MagickRealType)
1874 ((MaxMap*(map[i].index-black.index))/(white.index-black.index)));
1875 }
1876 histogram=(MagickPixelPacket *) RelinquishMagickMemory(histogram);
1877 map=(MagickPixelPacket *) RelinquishMagickMemory(map);
1878 if (image->storage_class == PseudoClass)
1879 {
1880 /*
1881 Equalize colormap.
1882 */
cristyb5d5f722009-11-04 03:03:49 +00001883#if defined(MAGICKCORE_OPENMP_SUPPORT)
1884 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001885#endif
cristybb503372010-05-27 20:51:26 +00001886 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00001887 {
1888 if (((channel & RedChannel) != 0) && (white.red != black.red))
cristyce70c172010-01-07 17:15:30 +00001889 image->colormap[i].red=ClampToQuantum(equalize_map[
cristy3ed852e2009-09-05 21:47:34 +00001890 ScaleQuantumToMap(image->colormap[i].red)].red);
1891 if (((channel & GreenChannel) != 0) && (white.green != black.green))
cristyce70c172010-01-07 17:15:30 +00001892 image->colormap[i].green=ClampToQuantum(equalize_map[
cristy3ed852e2009-09-05 21:47:34 +00001893 ScaleQuantumToMap(image->colormap[i].green)].green);
1894 if (((channel & BlueChannel) != 0) && (white.blue != black.blue))
cristyce70c172010-01-07 17:15:30 +00001895 image->colormap[i].blue=ClampToQuantum(equalize_map[
cristy3ed852e2009-09-05 21:47:34 +00001896 ScaleQuantumToMap(image->colormap[i].blue)].blue);
1897 if (((channel & OpacityChannel) != 0) &&
1898 (white.opacity != black.opacity))
cristyce70c172010-01-07 17:15:30 +00001899 image->colormap[i].opacity=ClampToQuantum(equalize_map[
cristy3ed852e2009-09-05 21:47:34 +00001900 ScaleQuantumToMap(image->colormap[i].opacity)].opacity);
1901 }
1902 }
1903 /*
1904 Equalize image.
1905 */
1906 status=MagickTrue;
1907 progress=0;
1908 exception=(&image->exception);
1909 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00001910#if defined(MAGICKCORE_OPENMP_SUPPORT)
1911 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001912#endif
cristybb503372010-05-27 20:51:26 +00001913 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001914 {
1915 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00001916 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00001917
cristy3ed852e2009-09-05 21:47:34 +00001918 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001919 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001920
cristy8d4629b2010-08-30 17:59:46 +00001921 register ssize_t
1922 x;
1923
cristy3ed852e2009-09-05 21:47:34 +00001924 if (status == MagickFalse)
1925 continue;
1926 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
1927 if (q == (PixelPacket *) NULL)
1928 {
1929 status=MagickFalse;
1930 continue;
1931 }
1932 indexes=GetCacheViewAuthenticIndexQueue(image_view);
cristybb503372010-05-27 20:51:26 +00001933 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001934 {
1935 if (((channel & RedChannel) != 0) && (white.red != black.red))
cristyce70c172010-01-07 17:15:30 +00001936 q->red=ClampToQuantum(equalize_map[ScaleQuantumToMap(q->red)].red);
cristy3ed852e2009-09-05 21:47:34 +00001937 if (((channel & GreenChannel) != 0) && (white.green != black.green))
cristyce70c172010-01-07 17:15:30 +00001938 q->green=ClampToQuantum(equalize_map[ScaleQuantumToMap(
cristy3ed852e2009-09-05 21:47:34 +00001939 q->green)].green);
1940 if (((channel & BlueChannel) != 0) && (white.blue != black.blue))
cristyce70c172010-01-07 17:15:30 +00001941 q->blue=ClampToQuantum(equalize_map[ScaleQuantumToMap(q->blue)].blue);
cristy3ed852e2009-09-05 21:47:34 +00001942 if (((channel & OpacityChannel) != 0) && (white.opacity != black.opacity))
cristyce70c172010-01-07 17:15:30 +00001943 q->opacity=ClampToQuantum(equalize_map[ScaleQuantumToMap(
cristy3ed852e2009-09-05 21:47:34 +00001944 q->opacity)].opacity);
1945 if ((((channel & IndexChannel) != 0) &&
1946 (image->colorspace == CMYKColorspace)) &&
1947 (white.index != black.index))
cristyce70c172010-01-07 17:15:30 +00001948 indexes[x]=ClampToQuantum(equalize_map[ScaleQuantumToMap(
cristy3ed852e2009-09-05 21:47:34 +00001949 indexes[x])].index);
1950 q++;
1951 }
1952 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
1953 status=MagickFalse;
1954 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1955 {
1956 MagickBooleanType
1957 proceed;
1958
cristyb5d5f722009-11-04 03:03:49 +00001959#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00001960 #pragma omp critical (MagickCore_EqualizeImageChannel)
1961#endif
1962 proceed=SetImageProgress(image,EqualizeImageTag,progress++,image->rows);
1963 if (proceed == MagickFalse)
1964 status=MagickFalse;
1965 }
1966 }
1967 image_view=DestroyCacheView(image_view);
1968 equalize_map=(MagickPixelPacket *) RelinquishMagickMemory(equalize_map);
1969 return(status);
1970}
1971
1972/*
1973%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1974% %
1975% %
1976% %
1977% G a m m a I m a g e %
1978% %
1979% %
1980% %
1981%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1982%
1983% GammaImage() gamma-corrects a particular image channel. The same
1984% image viewed on different devices will have perceptual differences in the
1985% way the image's intensities are represented on the screen. Specify
1986% individual gamma levels for the red, green, and blue channels, or adjust
1987% all three with the gamma parameter. Values typically range from 0.8 to 2.3.
1988%
1989% You can also reduce the influence of a particular channel with a gamma
1990% value of 0.
1991%
1992% The format of the GammaImage method is:
1993%
1994% MagickBooleanType GammaImage(Image *image,const double gamma)
1995% MagickBooleanType GammaImageChannel(Image *image,
1996% const ChannelType channel,const double gamma)
1997%
1998% A description of each parameter follows:
1999%
2000% o image: the image.
2001%
2002% o channel: the channel.
2003%
2004% o gamma: the image gamma.
2005%
2006*/
2007MagickExport MagickBooleanType GammaImage(Image *image,const char *level)
2008{
2009 GeometryInfo
2010 geometry_info;
2011
2012 MagickPixelPacket
2013 gamma;
2014
2015 MagickStatusType
2016 flags,
2017 status;
2018
2019 assert(image != (Image *) NULL);
2020 assert(image->signature == MagickSignature);
2021 if (image->debug != MagickFalse)
2022 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2023 if (level == (char *) NULL)
2024 return(MagickFalse);
2025 flags=ParseGeometry(level,&geometry_info);
2026 gamma.red=geometry_info.rho;
2027 gamma.green=geometry_info.sigma;
2028 if ((flags & SigmaValue) == 0)
2029 gamma.green=gamma.red;
2030 gamma.blue=geometry_info.xi;
2031 if ((flags & XiValue) == 0)
2032 gamma.blue=gamma.red;
2033 if ((gamma.red == 1.0) && (gamma.green == 1.0) && (gamma.blue == 1.0))
2034 return(MagickTrue);
2035 if ((gamma.red == gamma.green) && (gamma.green == gamma.blue))
2036 status=GammaImageChannel(image,(const ChannelType) (RedChannel |
2037 GreenChannel | BlueChannel),(double) gamma.red);
2038 else
2039 {
2040 status=GammaImageChannel(image,RedChannel,(double) gamma.red);
2041 status|=GammaImageChannel(image,GreenChannel,(double) gamma.green);
2042 status|=GammaImageChannel(image,BlueChannel,(double) gamma.blue);
2043 }
2044 return(status != 0 ? MagickTrue : MagickFalse);
2045}
2046
2047MagickExport MagickBooleanType GammaImageChannel(Image *image,
2048 const ChannelType channel,const double gamma)
2049{
2050#define GammaCorrectImageTag "GammaCorrect/Image"
2051
cristyc4c8d132010-01-07 01:58:38 +00002052 CacheView
2053 *image_view;
2054
cristy3ed852e2009-09-05 21:47:34 +00002055 ExceptionInfo
2056 *exception;
2057
cristy3ed852e2009-09-05 21:47:34 +00002058 MagickBooleanType
2059 status;
2060
cristybb503372010-05-27 20:51:26 +00002061 MagickOffsetType
2062 progress;
2063
cristy3ed852e2009-09-05 21:47:34 +00002064 Quantum
2065 *gamma_map;
2066
cristybb503372010-05-27 20:51:26 +00002067 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002068 i;
2069
cristybb503372010-05-27 20:51:26 +00002070 ssize_t
2071 y;
2072
cristy3ed852e2009-09-05 21:47:34 +00002073 /*
2074 Allocate and initialize gamma maps.
2075 */
2076 assert(image != (Image *) NULL);
2077 assert(image->signature == MagickSignature);
2078 if (image->debug != MagickFalse)
2079 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2080 if (gamma == 1.0)
2081 return(MagickTrue);
2082 gamma_map=(Quantum *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*gamma_map));
2083 if (gamma_map == (Quantum *) NULL)
2084 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
2085 image->filename);
2086 (void) ResetMagickMemory(gamma_map,0,(MaxMap+1)*sizeof(*gamma_map));
2087 if (gamma != 0.0)
cristyb5d5f722009-11-04 03:03:49 +00002088#if defined(MAGICKCORE_OPENMP_SUPPORT)
2089 #pragma omp parallel for schedule(dynamic,4)
cristy3ed852e2009-09-05 21:47:34 +00002090#endif
cristybb503372010-05-27 20:51:26 +00002091 for (i=0; i <= (ssize_t) MaxMap; i++)
cristyce70c172010-01-07 17:15:30 +00002092 gamma_map[i]=ClampToQuantum((MagickRealType) ScaleMapToQuantum((
cristy3ed852e2009-09-05 21:47:34 +00002093 MagickRealType) (MaxMap*pow((double) i/MaxMap,1.0/gamma))));
2094 if (image->storage_class == PseudoClass)
2095 {
2096 /*
2097 Gamma-correct colormap.
2098 */
cristyb5d5f722009-11-04 03:03:49 +00002099#if defined(MAGICKCORE_OPENMP_SUPPORT)
2100 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002101#endif
cristybb503372010-05-27 20:51:26 +00002102 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00002103 {
2104 if ((channel & RedChannel) != 0)
2105 image->colormap[i].red=gamma_map[
2106 ScaleQuantumToMap(image->colormap[i].red)];
2107 if ((channel & GreenChannel) != 0)
2108 image->colormap[i].green=gamma_map[
2109 ScaleQuantumToMap(image->colormap[i].green)];
2110 if ((channel & BlueChannel) != 0)
2111 image->colormap[i].blue=gamma_map[
2112 ScaleQuantumToMap(image->colormap[i].blue)];
2113 if ((channel & OpacityChannel) != 0)
2114 {
2115 if (image->matte == MagickFalse)
2116 image->colormap[i].opacity=gamma_map[
2117 ScaleQuantumToMap(image->colormap[i].opacity)];
2118 else
2119 image->colormap[i].opacity=(Quantum) QuantumRange-
2120 gamma_map[ScaleQuantumToMap((Quantum) (QuantumRange-
2121 image->colormap[i].opacity))];
2122 }
2123 }
2124 }
2125 /*
2126 Gamma-correct image.
2127 */
2128 status=MagickTrue;
2129 progress=0;
2130 exception=(&image->exception);
2131 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00002132#if defined(MAGICKCORE_OPENMP_SUPPORT)
2133 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002134#endif
cristybb503372010-05-27 20:51:26 +00002135 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002136 {
2137 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00002138 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00002139
cristy3ed852e2009-09-05 21:47:34 +00002140 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00002141 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002142
cristy8d4629b2010-08-30 17:59:46 +00002143 register ssize_t
2144 x;
2145
cristy3ed852e2009-09-05 21:47:34 +00002146 if (status == MagickFalse)
2147 continue;
2148 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2149 if (q == (PixelPacket *) NULL)
2150 {
2151 status=MagickFalse;
2152 continue;
2153 }
2154 indexes=GetCacheViewAuthenticIndexQueue(image_view);
cristybb503372010-05-27 20:51:26 +00002155 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002156 {
cristy6cbd7f52009-10-17 16:06:51 +00002157 if (channel == DefaultChannels)
cristy3ed852e2009-09-05 21:47:34 +00002158 {
cristy6cbd7f52009-10-17 16:06:51 +00002159 q->red=gamma_map[ScaleQuantumToMap(q->red)];
2160 q->green=gamma_map[ScaleQuantumToMap(q->green)];
2161 q->blue=gamma_map[ScaleQuantumToMap(q->blue)];
2162 }
2163 else
2164 {
2165 if ((channel & RedChannel) != 0)
2166 q->red=gamma_map[ScaleQuantumToMap(q->red)];
2167 if ((channel & GreenChannel) != 0)
2168 q->green=gamma_map[ScaleQuantumToMap(q->green)];
2169 if ((channel & BlueChannel) != 0)
2170 q->blue=gamma_map[ScaleQuantumToMap(q->blue)];
2171 if ((channel & OpacityChannel) != 0)
2172 {
2173 if (image->matte == MagickFalse)
2174 q->opacity=gamma_map[ScaleQuantumToMap(q->opacity)];
2175 else
2176 q->opacity=(Quantum) QuantumRange-gamma_map[
cristy46f08202010-01-10 04:04:21 +00002177 ScaleQuantumToMap((Quantum) GetAlphaPixelComponent(q))];
cristy6cbd7f52009-10-17 16:06:51 +00002178 }
cristy3ed852e2009-09-05 21:47:34 +00002179 }
2180 q++;
2181 }
2182 if (((channel & IndexChannel) != 0) &&
2183 (image->colorspace == CMYKColorspace))
cristybb503372010-05-27 20:51:26 +00002184 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002185 indexes[x]=gamma_map[ScaleQuantumToMap(indexes[x])];
2186 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2187 status=MagickFalse;
2188 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2189 {
2190 MagickBooleanType
2191 proceed;
2192
cristyb5d5f722009-11-04 03:03:49 +00002193#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00002194 #pragma omp critical (MagickCore_GammaImageChannel)
2195#endif
2196 proceed=SetImageProgress(image,GammaCorrectImageTag,progress++,
2197 image->rows);
2198 if (proceed == MagickFalse)
2199 status=MagickFalse;
2200 }
2201 }
2202 image_view=DestroyCacheView(image_view);
2203 gamma_map=(Quantum *) RelinquishMagickMemory(gamma_map);
2204 if (image->gamma != 0.0)
2205 image->gamma*=gamma;
2206 return(status);
2207}
2208
2209/*
2210%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2211% %
2212% %
2213% %
2214% H a l d C l u t I m a g e %
2215% %
2216% %
2217% %
2218%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2219%
2220% HaldClutImage() applies a Hald color lookup table to the image. A Hald
2221% color lookup table is a 3-dimensional color cube mapped to 2 dimensions.
2222% Create it with the HALD coder. You can apply any color transformation to
2223% the Hald image and then use this method to apply the transform to the
2224% image.
2225%
2226% The format of the HaldClutImage method is:
2227%
2228% MagickBooleanType HaldClutImage(Image *image,Image *hald_image)
2229% MagickBooleanType HaldClutImageChannel(Image *image,
2230% const ChannelType channel,Image *hald_image)
2231%
2232% A description of each parameter follows:
2233%
2234% o image: the image, which is replaced by indexed CLUT values
2235%
2236% o hald_image: the color lookup table image for replacement color values.
2237%
2238% o channel: the channel.
2239%
2240*/
2241
2242static inline size_t MagickMin(const size_t x,const size_t y)
2243{
2244 if (x < y)
2245 return(x);
2246 return(y);
2247}
2248
2249MagickExport MagickBooleanType HaldClutImage(Image *image,
2250 const Image *hald_image)
2251{
2252 return(HaldClutImageChannel(image,DefaultChannels,hald_image));
2253}
2254
2255MagickExport MagickBooleanType HaldClutImageChannel(Image *image,
2256 const ChannelType channel,const Image *hald_image)
2257{
2258#define HaldClutImageTag "Clut/Image"
2259
2260 typedef struct _HaldInfo
2261 {
2262 MagickRealType
2263 x,
2264 y,
2265 z;
2266 } HaldInfo;
2267
cristyfa112112010-01-04 17:48:07 +00002268 CacheView
2269 *image_view;
2270
cristy3ed852e2009-09-05 21:47:34 +00002271 double
2272 width;
2273
2274 ExceptionInfo
2275 *exception;
2276
cristy3ed852e2009-09-05 21:47:34 +00002277 MagickBooleanType
2278 status;
2279
cristybb503372010-05-27 20:51:26 +00002280 MagickOffsetType
2281 progress;
2282
cristy3ed852e2009-09-05 21:47:34 +00002283 MagickPixelPacket
2284 zero;
2285
2286 ResampleFilter
cristyfa112112010-01-04 17:48:07 +00002287 **restrict resample_filter;
cristy3ed852e2009-09-05 21:47:34 +00002288
2289 size_t
2290 cube_size,
2291 length,
2292 level;
2293
cristybb503372010-05-27 20:51:26 +00002294 ssize_t
2295 y;
2296
cristy3ed852e2009-09-05 21:47:34 +00002297 assert(image != (Image *) NULL);
2298 assert(image->signature == MagickSignature);
2299 if (image->debug != MagickFalse)
2300 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2301 assert(hald_image != (Image *) NULL);
2302 assert(hald_image->signature == MagickSignature);
2303 if (SetImageStorageClass(image,DirectClass) == MagickFalse)
2304 return(MagickFalse);
2305 if (image->matte == MagickFalse)
2306 (void) SetImageAlphaChannel(image,OpaqueAlphaChannel);
2307 /*
2308 Hald clut image.
2309 */
2310 status=MagickTrue;
2311 progress=0;
2312 length=MagickMin(hald_image->columns,hald_image->rows);
2313 for (level=2; (level*level*level) < length; level++) ;
2314 level*=level;
2315 cube_size=level*level;
2316 width=(double) hald_image->columns;
2317 GetMagickPixelPacket(hald_image,&zero);
2318 exception=(&image->exception);
cristyb2a11ae2010-02-22 00:53:36 +00002319 resample_filter=AcquireResampleFilterThreadSet(hald_image,
2320 UndefinedVirtualPixelMethod,MagickTrue,exception);
cristy3ed852e2009-09-05 21:47:34 +00002321 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00002322#if defined(MAGICKCORE_OPENMP_SUPPORT)
2323 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002324#endif
cristybb503372010-05-27 20:51:26 +00002325 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002326 {
2327 double
2328 offset;
2329
2330 HaldInfo
2331 point;
2332
cristy6ebe97c2010-07-03 01:17:28 +00002333 int
2334 id;
2335
cristy3ed852e2009-09-05 21:47:34 +00002336 MagickPixelPacket
2337 pixel,
2338 pixel1,
2339 pixel2,
2340 pixel3,
2341 pixel4;
2342
2343 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00002344 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00002345
cristy3ed852e2009-09-05 21:47:34 +00002346 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00002347 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002348
cristy8d4629b2010-08-30 17:59:46 +00002349 register ssize_t
2350 x;
2351
cristy3ed852e2009-09-05 21:47:34 +00002352 if (status == MagickFalse)
2353 continue;
2354 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2355 if (q == (PixelPacket *) NULL)
2356 {
2357 status=MagickFalse;
2358 continue;
2359 }
2360 indexes=GetCacheViewAuthenticIndexQueue(image_view);
2361 pixel=zero;
2362 pixel1=zero;
2363 pixel2=zero;
2364 pixel3=zero;
2365 pixel4=zero;
2366 id=GetOpenMPThreadId();
cristybb503372010-05-27 20:51:26 +00002367 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002368 {
2369 point.x=QuantumScale*(level-1.0)*q->red;
2370 point.y=QuantumScale*(level-1.0)*q->green;
2371 point.z=QuantumScale*(level-1.0)*q->blue;
2372 offset=point.x+level*floor(point.y)+cube_size*floor(point.z);
2373 point.x-=floor(point.x);
2374 point.y-=floor(point.y);
2375 point.z-=floor(point.z);
2376 (void) ResamplePixelColor(resample_filter[id],fmod(offset,width),
2377 floor(offset/width),&pixel1);
2378 (void) ResamplePixelColor(resample_filter[id],fmod(offset+level,width),
2379 floor((offset+level)/width),&pixel2);
2380 MagickPixelCompositeAreaBlend(&pixel1,pixel1.opacity,&pixel2,
2381 pixel2.opacity,point.y,&pixel3);
2382 offset+=cube_size;
2383 (void) ResamplePixelColor(resample_filter[id],fmod(offset,width),
2384 floor(offset/width),&pixel1);
2385 (void) ResamplePixelColor(resample_filter[id],fmod(offset+level,width),
2386 floor((offset+level)/width),&pixel2);
2387 MagickPixelCompositeAreaBlend(&pixel1,pixel1.opacity,&pixel2,
2388 pixel2.opacity,point.y,&pixel4);
2389 MagickPixelCompositeAreaBlend(&pixel3,pixel3.opacity,&pixel4,
2390 pixel4.opacity,point.z,&pixel);
2391 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00002392 SetRedPixelComponent(q,ClampRedPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00002393 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00002394 SetGreenPixelComponent(q,ClampGreenPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00002395 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00002396 SetBluePixelComponent(q,ClampBluePixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00002397 if (((channel & OpacityChannel) != 0) && (image->matte != MagickFalse))
cristyce70c172010-01-07 17:15:30 +00002398 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00002399 if (((channel & IndexChannel) != 0) &&
2400 (image->colorspace == CMYKColorspace))
cristyce70c172010-01-07 17:15:30 +00002401 indexes[x]=ClampToQuantum(pixel.index);
cristy3ed852e2009-09-05 21:47:34 +00002402 q++;
2403 }
2404 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2405 status=MagickFalse;
2406 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2407 {
2408 MagickBooleanType
2409 proceed;
2410
cristyb5d5f722009-11-04 03:03:49 +00002411#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00002412 #pragma omp critical (MagickCore_HaldClutImageChannel)
2413#endif
2414 proceed=SetImageProgress(image,HaldClutImageTag,progress++,image->rows);
2415 if (proceed == MagickFalse)
2416 status=MagickFalse;
2417 }
2418 }
2419 image_view=DestroyCacheView(image_view);
2420 resample_filter=DestroyResampleFilterThreadSet(resample_filter);
2421 return(status);
2422}
2423
2424/*
2425%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2426% %
2427% %
2428% %
2429% L e v e l I m a g e %
2430% %
2431% %
2432% %
2433%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2434%
2435% LevelImage() adjusts the levels of a particular image channel by
2436% scaling the colors falling between specified white and black points to
2437% the full available quantum range.
2438%
2439% The parameters provided represent the black, and white points. The black
2440% point specifies the darkest color in the image. Colors darker than the
2441% black point are set to zero. White point specifies the lightest color in
2442% the image. Colors brighter than the white point are set to the maximum
2443% quantum value.
2444%
2445% If a '!' flag is given, map black and white colors to the given levels
2446% rather than mapping those levels to black and white. See
2447% LevelizeImageChannel() and LevelizeImageChannel(), below.
2448%
2449% Gamma specifies a gamma correction to apply to the image.
2450%
2451% The format of the LevelImage method is:
2452%
2453% MagickBooleanType LevelImage(Image *image,const char *levels)
2454%
2455% A description of each parameter follows:
2456%
2457% o image: the image.
2458%
2459% o levels: Specify the levels where the black and white points have the
2460% range of 0-QuantumRange, and gamma has the range 0-10 (e.g. 10x90%+2).
2461% A '!' flag inverts the re-mapping.
2462%
2463*/
2464
2465MagickExport MagickBooleanType LevelImage(Image *image,const char *levels)
2466{
2467 double
2468 black_point,
2469 gamma,
2470 white_point;
2471
2472 GeometryInfo
2473 geometry_info;
2474
2475 MagickBooleanType
2476 status;
2477
2478 MagickStatusType
2479 flags;
2480
2481 /*
2482 Parse levels.
2483 */
2484 if (levels == (char *) NULL)
2485 return(MagickFalse);
2486 flags=ParseGeometry(levels,&geometry_info);
2487 black_point=geometry_info.rho;
2488 white_point=(double) QuantumRange;
2489 if ((flags & SigmaValue) != 0)
2490 white_point=geometry_info.sigma;
2491 gamma=1.0;
2492 if ((flags & XiValue) != 0)
2493 gamma=geometry_info.xi;
2494 if ((flags & PercentValue) != 0)
2495 {
2496 black_point*=(double) image->columns*image->rows/100.0;
2497 white_point*=(double) image->columns*image->rows/100.0;
2498 }
2499 if ((flags & SigmaValue) == 0)
2500 white_point=(double) QuantumRange-black_point;
2501 if ((flags & AspectValue ) == 0)
2502 status=LevelImageChannel(image,DefaultChannels,black_point,white_point,
2503 gamma);
2504 else
cristy308b4e62009-09-21 14:40:44 +00002505 status=LevelizeImage(image,black_point,white_point,gamma);
cristy3ed852e2009-09-05 21:47:34 +00002506 return(status);
2507}
2508
2509/*
2510%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2511% %
2512% %
2513% %
cristy308b4e62009-09-21 14:40:44 +00002514% L e v e l i z e I m a g e %
cristy3ed852e2009-09-05 21:47:34 +00002515% %
2516% %
2517% %
2518%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2519%
cristy308b4e62009-09-21 14:40:44 +00002520% LevelizeImage() applies the normal level operation to the image, spreading
2521% out the values between the black and white points over the entire range of
2522% values. Gamma correction is also applied after the values has been mapped.
cristy3ed852e2009-09-05 21:47:34 +00002523%
2524% It is typically used to improve image contrast, or to provide a controlled
2525% linear threshold for the image. If the black and white points are set to
2526% the minimum and maximum values found in the image, the image can be
2527% normalized. or by swapping black and white values, negate the image.
2528%
cristy308b4e62009-09-21 14:40:44 +00002529% The format of the LevelizeImage method is:
cristy3ed852e2009-09-05 21:47:34 +00002530%
cristy308b4e62009-09-21 14:40:44 +00002531% MagickBooleanType LevelizeImage(Image *image,const double black_point,
2532% const double white_point,const double gamma)
2533% MagickBooleanType LevelizeImageChannel(Image *image,
2534% const ChannelType channel,const double black_point,
2535% const double white_point,const double gamma)
cristy3ed852e2009-09-05 21:47:34 +00002536%
2537% A description of each parameter follows:
2538%
2539% o image: the image.
2540%
2541% o channel: the channel.
2542%
2543% o black_point: The level which is to be mapped to zero (black)
2544%
2545% o white_point: The level which is to be mapped to QuantiumRange (white)
2546%
2547% o gamma: adjust gamma by this factor before mapping values.
2548% use 1.0 for purely linear stretching of image color values
2549%
2550*/
cristy308b4e62009-09-21 14:40:44 +00002551
2552MagickExport MagickBooleanType LevelizeImage(Image *image,
2553 const double black_point,const double white_point,const double gamma)
2554{
2555 MagickBooleanType
2556 status;
2557
2558 status=LevelizeImageChannel(image,DefaultChannels,black_point,white_point,
2559 gamma);
2560 return(status);
2561}
2562
cristy3ed852e2009-09-05 21:47:34 +00002563MagickExport MagickBooleanType LevelImageChannel(Image *image,
2564 const ChannelType channel,const double black_point,const double white_point,
2565 const double gamma)
2566{
2567#define LevelImageTag "Level/Image"
cristybcfb0432010-05-06 01:45:33 +00002568#define LevelQuantum(x) (ClampToQuantum((MagickRealType) QuantumRange* \
cristyc1f508d2010-04-22 01:21:47 +00002569 pow(scale*((double) (x)-black_point),1.0/gamma)))
cristy3ed852e2009-09-05 21:47:34 +00002570
cristyc4c8d132010-01-07 01:58:38 +00002571 CacheView
2572 *image_view;
2573
cristy3ed852e2009-09-05 21:47:34 +00002574 ExceptionInfo
2575 *exception;
2576
cristy3ed852e2009-09-05 21:47:34 +00002577 MagickBooleanType
2578 status;
2579
cristybb503372010-05-27 20:51:26 +00002580 MagickOffsetType
2581 progress;
2582
anthony7fe39fc2010-04-06 03:19:20 +00002583 register double
2584 scale;
2585
cristy8d4629b2010-08-30 17:59:46 +00002586 register ssize_t
2587 i;
2588
cristybb503372010-05-27 20:51:26 +00002589 ssize_t
2590 y;
2591
cristy3ed852e2009-09-05 21:47:34 +00002592 /*
2593 Allocate and initialize levels map.
2594 */
2595 assert(image != (Image *) NULL);
2596 assert(image->signature == MagickSignature);
2597 if (image->debug != MagickFalse)
2598 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
cristy8d4629b2010-08-30 17:59:46 +00002599 scale=(white_point != black_point) ? 1.0/(white_point-black_point) : 1.0;
cristy3ed852e2009-09-05 21:47:34 +00002600 if (image->storage_class == PseudoClass)
cristyb5d5f722009-11-04 03:03:49 +00002601#if defined(MAGICKCORE_OPENMP_SUPPORT)
2602 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002603#endif
cristybb503372010-05-27 20:51:26 +00002604 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00002605 {
2606 /*
2607 Level colormap.
2608 */
2609 if ((channel & RedChannel) != 0)
cristyc1f508d2010-04-22 01:21:47 +00002610 image->colormap[i].red=LevelQuantum(image->colormap[i].red);
cristy3ed852e2009-09-05 21:47:34 +00002611 if ((channel & GreenChannel) != 0)
cristyc1f508d2010-04-22 01:21:47 +00002612 image->colormap[i].green=LevelQuantum(image->colormap[i].green);
cristy3ed852e2009-09-05 21:47:34 +00002613 if ((channel & BlueChannel) != 0)
cristyc1f508d2010-04-22 01:21:47 +00002614 image->colormap[i].blue=LevelQuantum(image->colormap[i].blue);
cristy3ed852e2009-09-05 21:47:34 +00002615 if ((channel & OpacityChannel) != 0)
cristyc1f508d2010-04-22 01:21:47 +00002616 image->colormap[i].opacity=LevelQuantum(image->colormap[i].opacity);
cristy3ed852e2009-09-05 21:47:34 +00002617 }
2618 /*
2619 Level image.
2620 */
2621 status=MagickTrue;
2622 progress=0;
2623 exception=(&image->exception);
2624 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00002625#if defined(MAGICKCORE_OPENMP_SUPPORT)
2626 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002627#endif
cristybb503372010-05-27 20:51:26 +00002628 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002629 {
2630 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00002631 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00002632
cristy3ed852e2009-09-05 21:47:34 +00002633 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00002634 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002635
cristy8d4629b2010-08-30 17:59:46 +00002636 register ssize_t
2637 x;
2638
cristy3ed852e2009-09-05 21:47:34 +00002639 if (status == MagickFalse)
2640 continue;
2641 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2642 if (q == (PixelPacket *) NULL)
2643 {
2644 status=MagickFalse;
2645 continue;
2646 }
2647 indexes=GetCacheViewAuthenticIndexQueue(image_view);
cristybb503372010-05-27 20:51:26 +00002648 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002649 {
2650 if ((channel & RedChannel) != 0)
cristyc1f508d2010-04-22 01:21:47 +00002651 q->red=LevelQuantum(q->red);
cristy3ed852e2009-09-05 21:47:34 +00002652 if ((channel & GreenChannel) != 0)
cristyc1f508d2010-04-22 01:21:47 +00002653 q->green=LevelQuantum(q->green);
cristy3ed852e2009-09-05 21:47:34 +00002654 if ((channel & BlueChannel) != 0)
cristyc1f508d2010-04-22 01:21:47 +00002655 q->blue=LevelQuantum(q->blue);
cristy3ed852e2009-09-05 21:47:34 +00002656 if (((channel & OpacityChannel) != 0) &&
2657 (image->matte == MagickTrue))
cristybb503372010-05-27 20:51:26 +00002658 q->opacity=(Quantum) (QuantumRange-LevelQuantum(QuantumRange-
2659 q->opacity));
cristy3ed852e2009-09-05 21:47:34 +00002660 if (((channel & IndexChannel) != 0) &&
2661 (image->colorspace == CMYKColorspace))
cristyc1f508d2010-04-22 01:21:47 +00002662 indexes[x]=LevelQuantum(indexes[x]);
cristy3ed852e2009-09-05 21:47:34 +00002663 q++;
2664 }
2665 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2666 status=MagickFalse;
2667 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2668 {
2669 MagickBooleanType
2670 proceed;
2671
cristyb5d5f722009-11-04 03:03:49 +00002672#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00002673 #pragma omp critical (MagickCore_LevelImageChannel)
2674#endif
2675 proceed=SetImageProgress(image,LevelImageTag,progress++,image->rows);
2676 if (proceed == MagickFalse)
2677 status=MagickFalse;
2678 }
2679 }
2680 image_view=DestroyCacheView(image_view);
2681 return(status);
2682}
2683
2684/*
2685%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2686% %
2687% %
2688% %
2689% L e v e l i z e I m a g e C h a n n e l %
2690% %
2691% %
2692% %
2693%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2694%
2695% LevelizeImageChannel() applies the reversed LevelImage() operation to just
2696% the specific channels specified. It compresses the full range of color
2697% values, so that they lie between the given black and white points. Gamma is
2698% applied before the values are mapped.
2699%
2700% LevelizeImageChannel() can be called with by using a +level command line
2701% API option, or using a '!' on a -level or LevelImage() geometry string.
2702%
2703% It can be used for example de-contrast a greyscale image to the exact
2704% levels specified. Or by using specific levels for each channel of an image
2705% you can convert a gray-scale image to any linear color gradient, according
2706% to those levels.
2707%
2708% The format of the LevelizeImageChannel method is:
2709%
2710% MagickBooleanType LevelizeImageChannel(Image *image,
2711% const ChannelType channel,const char *levels)
2712%
2713% A description of each parameter follows:
2714%
2715% o image: the image.
2716%
2717% o channel: the channel.
2718%
2719% o black_point: The level to map zero (black) to.
2720%
2721% o white_point: The level to map QuantiumRange (white) to.
2722%
2723% o gamma: adjust gamma by this factor before mapping values.
2724%
2725*/
2726MagickExport MagickBooleanType LevelizeImageChannel(Image *image,
2727 const ChannelType channel,const double black_point,const double white_point,
2728 const double gamma)
2729{
2730#define LevelizeImageTag "Levelize/Image"
cristyce70c172010-01-07 17:15:30 +00002731#define LevelizeValue(x) (ClampToQuantum(((MagickRealType) \
cristy3ed852e2009-09-05 21:47:34 +00002732 pow((double)(QuantumScale*(x)),1.0/gamma))*(white_point-black_point)+ \
2733 black_point))
2734
cristyc4c8d132010-01-07 01:58:38 +00002735 CacheView
2736 *image_view;
2737
cristy3ed852e2009-09-05 21:47:34 +00002738 ExceptionInfo
2739 *exception;
2740
cristy3ed852e2009-09-05 21:47:34 +00002741 MagickBooleanType
2742 status;
2743
cristybb503372010-05-27 20:51:26 +00002744 MagickOffsetType
2745 progress;
2746
2747 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002748 i;
2749
cristybb503372010-05-27 20:51:26 +00002750 ssize_t
2751 y;
2752
cristy3ed852e2009-09-05 21:47:34 +00002753 /*
2754 Allocate and initialize levels map.
2755 */
2756 assert(image != (Image *) NULL);
2757 assert(image->signature == MagickSignature);
2758 if (image->debug != MagickFalse)
2759 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2760 if (image->storage_class == PseudoClass)
cristyb5d5f722009-11-04 03:03:49 +00002761#if defined(MAGICKCORE_OPENMP_SUPPORT)
2762 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002763#endif
cristybb503372010-05-27 20:51:26 +00002764 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00002765 {
2766 /*
2767 Level colormap.
2768 */
2769 if ((channel & RedChannel) != 0)
2770 image->colormap[i].red=LevelizeValue(image->colormap[i].red);
2771 if ((channel & GreenChannel) != 0)
2772 image->colormap[i].green=LevelizeValue(image->colormap[i].green);
2773 if ((channel & BlueChannel) != 0)
2774 image->colormap[i].blue=LevelizeValue(image->colormap[i].blue);
2775 if ((channel & OpacityChannel) != 0)
2776 image->colormap[i].opacity=LevelizeValue(image->colormap[i].opacity);
2777 }
2778 /*
2779 Level image.
2780 */
2781 status=MagickTrue;
2782 progress=0;
2783 exception=(&image->exception);
2784 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00002785#if defined(MAGICKCORE_OPENMP_SUPPORT)
2786 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002787#endif
cristybb503372010-05-27 20:51:26 +00002788 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002789 {
2790 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00002791 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00002792
cristy3ed852e2009-09-05 21:47:34 +00002793 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00002794 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002795
cristy8d4629b2010-08-30 17:59:46 +00002796 register ssize_t
2797 x;
2798
cristy3ed852e2009-09-05 21:47:34 +00002799 if (status == MagickFalse)
2800 continue;
2801 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2802 if (q == (PixelPacket *) NULL)
2803 {
2804 status=MagickFalse;
2805 continue;
2806 }
2807 indexes=GetCacheViewAuthenticIndexQueue(image_view);
cristybb503372010-05-27 20:51:26 +00002808 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002809 {
2810 if ((channel & RedChannel) != 0)
2811 q->red=LevelizeValue(q->red);
2812 if ((channel & GreenChannel) != 0)
2813 q->green=LevelizeValue(q->green);
2814 if ((channel & BlueChannel) != 0)
2815 q->blue=LevelizeValue(q->blue);
2816 if (((channel & OpacityChannel) != 0) &&
2817 (image->matte == MagickTrue))
2818 q->opacity=LevelizeValue(q->opacity);
2819 if (((channel & IndexChannel) != 0) &&
2820 (image->colorspace == CMYKColorspace))
2821 indexes[x]=LevelizeValue(indexes[x]);
2822 q++;
2823 }
2824 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2825 status=MagickFalse;
2826 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2827 {
2828 MagickBooleanType
2829 proceed;
2830
cristyb5d5f722009-11-04 03:03:49 +00002831#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00002832 #pragma omp critical (MagickCore_LevelizeImageChannel)
2833#endif
2834 proceed=SetImageProgress(image,LevelizeImageTag,progress++,image->rows);
2835 if (proceed == MagickFalse)
2836 status=MagickFalse;
2837 }
2838 }
cristy8d4629b2010-08-30 17:59:46 +00002839 image_view=DestroyCacheView(image_view);
cristy3ed852e2009-09-05 21:47:34 +00002840 return(status);
2841}
2842
2843/*
2844%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2845% %
2846% %
2847% %
2848% L e v e l I m a g e C o l o r s %
2849% %
2850% %
2851% %
2852%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2853%
cristyee0f8d72009-09-19 00:58:29 +00002854% LevelImageColor() maps the given color to "black" and "white" values,
2855% linearly spreading out the colors, and level values on a channel by channel
2856% bases, as per LevelImage(). The given colors allows you to specify
cristy3ed852e2009-09-05 21:47:34 +00002857% different level ranges for each of the color channels seperatally.
2858%
2859% If the boolean 'invert' is set true the image values will modifyed in the
2860% reverse direction. That is any existing "black" and "white" colors in the
2861% image will become the color values given, with all other values compressed
2862% appropriatally. This effectivally maps a greyscale gradient into the given
2863% color gradient.
2864%
cristy308b4e62009-09-21 14:40:44 +00002865% The format of the LevelColorsImageChannel method is:
cristy3ed852e2009-09-05 21:47:34 +00002866%
cristy308b4e62009-09-21 14:40:44 +00002867% MagickBooleanType LevelColorsImage(Image *image,
cristyee0f8d72009-09-19 00:58:29 +00002868% const MagickPixelPacket *black_color,
2869% const MagickPixelPacket *white_color,const MagickBooleanType invert)
cristy308b4e62009-09-21 14:40:44 +00002870% MagickBooleanType LevelColorsImageChannel(Image *image,
2871% const ChannelType channel,const MagickPixelPacket *black_color,
2872% const MagickPixelPacket *white_color,const MagickBooleanType invert)
cristy3ed852e2009-09-05 21:47:34 +00002873%
2874% A description of each parameter follows:
2875%
2876% o image: the image.
2877%
2878% o channel: the channel.
2879%
2880% o black_color: The color to map black to/from
2881%
2882% o white_point: The color to map white to/from
2883%
2884% o invert: if true map the colors (levelize), rather than from (level)
2885%
2886*/
cristy308b4e62009-09-21 14:40:44 +00002887
2888MagickExport MagickBooleanType LevelColorsImage(Image *image,
cristy3ed852e2009-09-05 21:47:34 +00002889 const MagickPixelPacket *black_color,const MagickPixelPacket *white_color,
2890 const MagickBooleanType invert)
2891{
cristy308b4e62009-09-21 14:40:44 +00002892 MagickBooleanType
2893 status;
cristy3ed852e2009-09-05 21:47:34 +00002894
cristy308b4e62009-09-21 14:40:44 +00002895 status=LevelColorsImageChannel(image,DefaultChannels,black_color,white_color,
2896 invert);
2897 return(status);
2898}
2899
2900MagickExport MagickBooleanType LevelColorsImageChannel(Image *image,
2901 const ChannelType channel,const MagickPixelPacket *black_color,
2902 const MagickPixelPacket *white_color,const MagickBooleanType invert)
2903{
cristy3ed852e2009-09-05 21:47:34 +00002904 MagickStatusType
2905 status;
2906
2907 /*
2908 Allocate and initialize levels map.
2909 */
2910 assert(image != (Image *) NULL);
2911 assert(image->signature == MagickSignature);
2912 if (image->debug != MagickFalse)
2913 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2914 status=MagickFalse;
2915 if (invert == MagickFalse)
2916 {
2917 if ((channel & RedChannel) != 0)
2918 status|=LevelImageChannel(image,RedChannel,
2919 black_color->red,white_color->red,(double) 1.0);
2920 if ((channel & GreenChannel) != 0)
2921 status|=LevelImageChannel(image,GreenChannel,
2922 black_color->green,white_color->green,(double) 1.0);
2923 if ((channel & BlueChannel) != 0)
2924 status|=LevelImageChannel(image,BlueChannel,
2925 black_color->blue,white_color->blue,(double) 1.0);
2926 if (((channel & OpacityChannel) != 0) &&
2927 (image->matte == MagickTrue))
2928 status|=LevelImageChannel(image,OpacityChannel,
2929 black_color->opacity,white_color->opacity,(double) 1.0);
2930 if (((channel & IndexChannel) != 0) &&
2931 (image->colorspace == CMYKColorspace))
2932 status|=LevelImageChannel(image,IndexChannel,
2933 black_color->index,white_color->index,(double) 1.0);
2934 }
2935 else
2936 {
2937 if ((channel & RedChannel) != 0)
2938 status|=LevelizeImageChannel(image,RedChannel,
2939 black_color->red,white_color->red,(double) 1.0);
2940 if ((channel & GreenChannel) != 0)
2941 status|=LevelizeImageChannel(image,GreenChannel,
2942 black_color->green,white_color->green,(double) 1.0);
2943 if ((channel & BlueChannel) != 0)
2944 status|=LevelizeImageChannel(image,BlueChannel,
2945 black_color->blue,white_color->blue,(double) 1.0);
2946 if (((channel & OpacityChannel) != 0) &&
2947 (image->matte == MagickTrue))
2948 status|=LevelizeImageChannel(image,OpacityChannel,
2949 black_color->opacity,white_color->opacity,(double) 1.0);
2950 if (((channel & IndexChannel) != 0) &&
2951 (image->colorspace == CMYKColorspace))
2952 status|=LevelizeImageChannel(image,IndexChannel,
2953 black_color->index,white_color->index,(double) 1.0);
2954 }
2955 return(status == 0 ? MagickFalse : MagickTrue);
2956}
2957
2958/*
2959%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2960% %
2961% %
2962% %
2963% L i n e a r S t r e t c h I m a g e %
2964% %
2965% %
2966% %
2967%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2968%
2969% The LinearStretchImage() discards any pixels below the black point and
2970% above the white point and levels the remaining pixels.
2971%
2972% The format of the LinearStretchImage method is:
2973%
2974% MagickBooleanType LinearStretchImage(Image *image,
2975% const double black_point,const double white_point)
2976%
2977% A description of each parameter follows:
2978%
2979% o image: the image.
2980%
2981% o black_point: the black point.
2982%
2983% o white_point: the white point.
2984%
2985*/
2986MagickExport MagickBooleanType LinearStretchImage(Image *image,
2987 const double black_point,const double white_point)
2988{
2989#define LinearStretchImageTag "LinearStretch/Image"
2990
2991 ExceptionInfo
2992 *exception;
2993
cristy3ed852e2009-09-05 21:47:34 +00002994 MagickBooleanType
2995 status;
2996
2997 MagickRealType
2998 *histogram,
2999 intensity;
3000
3001 MagickSizeType
3002 number_pixels;
3003
cristy8d4629b2010-08-30 17:59:46 +00003004 ssize_t
3005 black,
3006 white,
3007 y;
3008
cristy3ed852e2009-09-05 21:47:34 +00003009 /*
3010 Allocate histogram and linear map.
3011 */
3012 assert(image != (Image *) NULL);
3013 assert(image->signature == MagickSignature);
3014 histogram=(MagickRealType *) AcquireQuantumMemory(MaxMap+1UL,
3015 sizeof(*histogram));
3016 if (histogram == (MagickRealType *) NULL)
3017 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
3018 image->filename);
3019 /*
3020 Form histogram.
3021 */
3022 (void) ResetMagickMemory(histogram,0,(MaxMap+1)*sizeof(*histogram));
3023 exception=(&image->exception);
cristybb503372010-05-27 20:51:26 +00003024 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003025 {
3026 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00003027 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00003028
cristybb503372010-05-27 20:51:26 +00003029 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003030 x;
3031
3032 p=GetVirtualPixels(image,0,y,image->columns,1,exception);
3033 if (p == (const PixelPacket *) NULL)
3034 break;
cristybb503372010-05-27 20:51:26 +00003035 for (x=(ssize_t) image->columns-1; x >= 0; x--)
cristy3ed852e2009-09-05 21:47:34 +00003036 {
3037 histogram[ScaleQuantumToMap(PixelIntensityToQuantum(p))]++;
3038 p++;
3039 }
3040 }
3041 /*
3042 Find the histogram boundaries by locating the black and white point levels.
3043 */
3044 number_pixels=(MagickSizeType) image->columns*image->rows;
3045 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00003046 for (black=0; black < (ssize_t) MaxMap; black++)
cristy3ed852e2009-09-05 21:47:34 +00003047 {
3048 intensity+=histogram[black];
3049 if (intensity >= black_point)
3050 break;
3051 }
3052 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00003053 for (white=(ssize_t) MaxMap; white != 0; white--)
cristy3ed852e2009-09-05 21:47:34 +00003054 {
3055 intensity+=histogram[white];
3056 if (intensity >= white_point)
3057 break;
3058 }
3059 histogram=(MagickRealType *) RelinquishMagickMemory(histogram);
3060 status=LevelImageChannel(image,DefaultChannels,(double) black,(double) white,
3061 1.0);
3062 return(status);
3063}
3064
3065/*
3066%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3067% %
3068% %
3069% %
3070% M o d u l a t e I m a g e %
3071% %
3072% %
3073% %
3074%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3075%
3076% ModulateImage() lets you control the brightness, saturation, and hue
3077% of an image. Modulate represents the brightness, saturation, and hue
3078% as one parameter (e.g. 90,150,100). If the image colorspace is HSL, the
3079% modulation is lightness, saturation, and hue. And if the colorspace is
3080% HWB, use blackness, whiteness, and hue.
3081%
3082% The format of the ModulateImage method is:
3083%
3084% MagickBooleanType ModulateImage(Image *image,const char *modulate)
3085%
3086% A description of each parameter follows:
3087%
3088% o image: the image.
3089%
3090% o modulate: Define the percent change in brightness, saturation, and
3091% hue.
3092%
3093*/
3094
3095static void ModulateHSB(const double percent_hue,
3096 const double percent_saturation,const double percent_brightness,
3097 Quantum *red,Quantum *green,Quantum *blue)
3098{
3099 double
3100 brightness,
3101 hue,
3102 saturation;
3103
3104 /*
3105 Increase or decrease color brightness, saturation, or hue.
3106 */
3107 assert(red != (Quantum *) NULL);
3108 assert(green != (Quantum *) NULL);
3109 assert(blue != (Quantum *) NULL);
3110 ConvertRGBToHSB(*red,*green,*blue,&hue,&saturation,&brightness);
3111 hue+=0.5*(0.01*percent_hue-1.0);
3112 while (hue < 0.0)
3113 hue+=1.0;
3114 while (hue > 1.0)
3115 hue-=1.0;
3116 saturation*=0.01*percent_saturation;
3117 brightness*=0.01*percent_brightness;
3118 ConvertHSBToRGB(hue,saturation,brightness,red,green,blue);
3119}
3120
3121static void ModulateHSL(const double percent_hue,
3122 const double percent_saturation,const double percent_lightness,
3123 Quantum *red,Quantum *green,Quantum *blue)
3124{
3125 double
3126 hue,
3127 lightness,
3128 saturation;
3129
3130 /*
3131 Increase or decrease color lightness, saturation, or hue.
3132 */
3133 assert(red != (Quantum *) NULL);
3134 assert(green != (Quantum *) NULL);
3135 assert(blue != (Quantum *) NULL);
3136 ConvertRGBToHSL(*red,*green,*blue,&hue,&saturation,&lightness);
3137 hue+=0.5*(0.01*percent_hue-1.0);
3138 while (hue < 0.0)
3139 hue+=1.0;
3140 while (hue > 1.0)
3141 hue-=1.0;
3142 saturation*=0.01*percent_saturation;
3143 lightness*=0.01*percent_lightness;
3144 ConvertHSLToRGB(hue,saturation,lightness,red,green,blue);
3145}
3146
3147static void ModulateHWB(const double percent_hue,const double percent_whiteness, const double percent_blackness,Quantum *red,Quantum *green,Quantum *blue)
3148{
3149 double
3150 blackness,
3151 hue,
3152 whiteness;
3153
3154 /*
3155 Increase or decrease color blackness, whiteness, or hue.
3156 */
3157 assert(red != (Quantum *) NULL);
3158 assert(green != (Quantum *) NULL);
3159 assert(blue != (Quantum *) NULL);
3160 ConvertRGBToHWB(*red,*green,*blue,&hue,&whiteness,&blackness);
3161 hue+=0.5*(0.01*percent_hue-1.0);
3162 while (hue < 0.0)
3163 hue+=1.0;
3164 while (hue > 1.0)
3165 hue-=1.0;
3166 blackness*=0.01*percent_blackness;
3167 whiteness*=0.01*percent_whiteness;
3168 ConvertHWBToRGB(hue,whiteness,blackness,red,green,blue);
3169}
3170
3171MagickExport MagickBooleanType ModulateImage(Image *image,const char *modulate)
3172{
3173#define ModulateImageTag "Modulate/Image"
3174
cristyc4c8d132010-01-07 01:58:38 +00003175 CacheView
3176 *image_view;
3177
cristy3ed852e2009-09-05 21:47:34 +00003178 ColorspaceType
3179 colorspace;
3180
3181 const char
3182 *artifact;
3183
3184 double
3185 percent_brightness,
3186 percent_hue,
3187 percent_saturation;
3188
3189 ExceptionInfo
3190 *exception;
3191
3192 GeometryInfo
3193 geometry_info;
3194
cristy3ed852e2009-09-05 21:47:34 +00003195 MagickBooleanType
3196 status;
3197
cristybb503372010-05-27 20:51:26 +00003198 MagickOffsetType
3199 progress;
3200
cristy3ed852e2009-09-05 21:47:34 +00003201 MagickStatusType
3202 flags;
3203
cristybb503372010-05-27 20:51:26 +00003204 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003205 i;
3206
cristybb503372010-05-27 20:51:26 +00003207 ssize_t
3208 y;
3209
cristy3ed852e2009-09-05 21:47:34 +00003210 /*
cristy2b726bd2010-01-11 01:05:39 +00003211 Initialize modulate table.
cristy3ed852e2009-09-05 21:47:34 +00003212 */
3213 assert(image != (Image *) NULL);
3214 assert(image->signature == MagickSignature);
3215 if (image->debug != MagickFalse)
3216 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3217 if (modulate == (char *) NULL)
3218 return(MagickFalse);
3219 flags=ParseGeometry(modulate,&geometry_info);
3220 percent_brightness=geometry_info.rho;
3221 percent_saturation=geometry_info.sigma;
3222 if ((flags & SigmaValue) == 0)
3223 percent_saturation=100.0;
3224 percent_hue=geometry_info.xi;
3225 if ((flags & XiValue) == 0)
3226 percent_hue=100.0;
3227 colorspace=UndefinedColorspace;
3228 artifact=GetImageArtifact(image,"modulate:colorspace");
3229 if (artifact != (const char *) NULL)
3230 colorspace=(ColorspaceType) ParseMagickOption(MagickColorspaceOptions,
3231 MagickFalse,artifact);
3232 if (image->storage_class == PseudoClass)
3233 {
3234 /*
3235 Modulate colormap.
3236 */
cristyb5d5f722009-11-04 03:03:49 +00003237#if defined(MAGICKCORE_OPENMP_SUPPORT)
3238 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003239#endif
cristybb503372010-05-27 20:51:26 +00003240 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00003241 switch (colorspace)
3242 {
3243 case HSBColorspace:
3244 {
3245 ModulateHSB(percent_hue,percent_saturation,percent_brightness,
3246 &image->colormap[i].red,&image->colormap[i].green,
3247 &image->colormap[i].blue);
3248 break;
3249 }
3250 case HSLColorspace:
3251 default:
3252 {
3253 ModulateHSL(percent_hue,percent_saturation,percent_brightness,
3254 &image->colormap[i].red,&image->colormap[i].green,
3255 &image->colormap[i].blue);
3256 break;
3257 }
3258 case HWBColorspace:
3259 {
3260 ModulateHWB(percent_hue,percent_saturation,percent_brightness,
3261 &image->colormap[i].red,&image->colormap[i].green,
3262 &image->colormap[i].blue);
3263 break;
3264 }
3265 }
3266 }
3267 /*
3268 Modulate image.
3269 */
3270 status=MagickTrue;
3271 progress=0;
3272 exception=(&image->exception);
3273 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00003274#if defined(MAGICKCORE_OPENMP_SUPPORT)
3275 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003276#endif
cristybb503372010-05-27 20:51:26 +00003277 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003278 {
cristy3ed852e2009-09-05 21:47:34 +00003279 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00003280 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003281
cristy8d4629b2010-08-30 17:59:46 +00003282 register ssize_t
3283 x;
3284
cristy3ed852e2009-09-05 21:47:34 +00003285 if (status == MagickFalse)
3286 continue;
3287 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
3288 if (q == (PixelPacket *) NULL)
3289 {
3290 status=MagickFalse;
3291 continue;
3292 }
cristybb503372010-05-27 20:51:26 +00003293 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003294 {
3295 switch (colorspace)
3296 {
3297 case HSBColorspace:
3298 {
3299 ModulateHSB(percent_hue,percent_saturation,percent_brightness,
3300 &q->red,&q->green,&q->blue);
3301 break;
3302 }
3303 case HSLColorspace:
3304 default:
3305 {
3306 ModulateHSL(percent_hue,percent_saturation,percent_brightness,
3307 &q->red,&q->green,&q->blue);
3308 break;
3309 }
3310 case HWBColorspace:
3311 {
3312 ModulateHWB(percent_hue,percent_saturation,percent_brightness,
3313 &q->red,&q->green,&q->blue);
3314 break;
3315 }
3316 }
3317 q++;
3318 }
3319 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
3320 status=MagickFalse;
3321 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3322 {
3323 MagickBooleanType
3324 proceed;
3325
cristyb5d5f722009-11-04 03:03:49 +00003326#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00003327 #pragma omp critical (MagickCore_ModulateImage)
3328#endif
3329 proceed=SetImageProgress(image,ModulateImageTag,progress++,image->rows);
3330 if (proceed == MagickFalse)
3331 status=MagickFalse;
3332 }
3333 }
3334 image_view=DestroyCacheView(image_view);
3335 return(status);
3336}
3337
3338/*
3339%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3340% %
3341% %
3342% %
3343% N e g a t e I m a g e %
3344% %
3345% %
3346% %
3347%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3348%
3349% NegateImage() negates the colors in the reference image. The grayscale
3350% option means that only grayscale values within the image are negated.
3351%
3352% The format of the NegateImageChannel method is:
3353%
3354% MagickBooleanType NegateImage(Image *image,
3355% const MagickBooleanType grayscale)
3356% MagickBooleanType NegateImageChannel(Image *image,
3357% const ChannelType channel,const MagickBooleanType grayscale)
3358%
3359% A description of each parameter follows:
3360%
3361% o image: the image.
3362%
3363% o channel: the channel.
3364%
3365% o grayscale: If MagickTrue, only negate grayscale pixels within the image.
3366%
3367*/
3368
3369MagickExport MagickBooleanType NegateImage(Image *image,
3370 const MagickBooleanType grayscale)
3371{
3372 MagickBooleanType
3373 status;
3374
3375 status=NegateImageChannel(image,DefaultChannels,grayscale);
3376 return(status);
3377}
3378
3379MagickExport MagickBooleanType NegateImageChannel(Image *image,
3380 const ChannelType channel,const MagickBooleanType grayscale)
3381{
3382#define NegateImageTag "Negate/Image"
3383
cristyc4c8d132010-01-07 01:58:38 +00003384 CacheView
3385 *image_view;
3386
cristy3ed852e2009-09-05 21:47:34 +00003387 ExceptionInfo
3388 *exception;
3389
cristy3ed852e2009-09-05 21:47:34 +00003390 MagickBooleanType
3391 status;
3392
cristybb503372010-05-27 20:51:26 +00003393 MagickOffsetType
3394 progress;
3395
3396 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003397 i;
3398
cristybb503372010-05-27 20:51:26 +00003399 ssize_t
3400 y;
3401
cristy3ed852e2009-09-05 21:47:34 +00003402 assert(image != (Image *) NULL);
3403 assert(image->signature == MagickSignature);
3404 if (image->debug != MagickFalse)
3405 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3406 if (image->storage_class == PseudoClass)
3407 {
3408 /*
3409 Negate colormap.
3410 */
cristyb5d5f722009-11-04 03:03:49 +00003411#if defined(MAGICKCORE_OPENMP_SUPPORT)
3412 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003413#endif
cristybb503372010-05-27 20:51:26 +00003414 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00003415 {
3416 if (grayscale != MagickFalse)
3417 if ((image->colormap[i].red != image->colormap[i].green) ||
3418 (image->colormap[i].green != image->colormap[i].blue))
3419 continue;
3420 if ((channel & RedChannel) != 0)
3421 image->colormap[i].red=(Quantum) QuantumRange-
3422 image->colormap[i].red;
3423 if ((channel & GreenChannel) != 0)
3424 image->colormap[i].green=(Quantum) QuantumRange-
3425 image->colormap[i].green;
3426 if ((channel & BlueChannel) != 0)
3427 image->colormap[i].blue=(Quantum) QuantumRange-
3428 image->colormap[i].blue;
3429 }
3430 }
3431 /*
3432 Negate image.
3433 */
3434 status=MagickTrue;
3435 progress=0;
3436 exception=(&image->exception);
3437 image_view=AcquireCacheView(image);
3438 if (grayscale != MagickFalse)
3439 {
cristyb5d5f722009-11-04 03:03:49 +00003440#if defined(MAGICKCORE_OPENMP_SUPPORT)
3441 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003442#endif
cristybb503372010-05-27 20:51:26 +00003443 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003444 {
3445 MagickBooleanType
3446 sync;
3447
3448 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00003449 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00003450
cristy3ed852e2009-09-05 21:47:34 +00003451 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00003452 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003453
cristy8d4629b2010-08-30 17:59:46 +00003454 register ssize_t
3455 x;
3456
cristy3ed852e2009-09-05 21:47:34 +00003457 if (status == MagickFalse)
3458 continue;
3459 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,
3460 exception);
3461 if (q == (PixelPacket *) NULL)
3462 {
3463 status=MagickFalse;
3464 continue;
3465 }
3466 indexes=GetCacheViewAuthenticIndexQueue(image_view);
cristybb503372010-05-27 20:51:26 +00003467 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003468 {
3469 if ((q->red != q->green) || (q->green != q->blue))
3470 {
3471 q++;
3472 continue;
3473 }
3474 if ((channel & RedChannel) != 0)
3475 q->red=(Quantum) QuantumRange-q->red;
3476 if ((channel & GreenChannel) != 0)
3477 q->green=(Quantum) QuantumRange-q->green;
3478 if ((channel & BlueChannel) != 0)
3479 q->blue=(Quantum) QuantumRange-q->blue;
3480 if ((channel & OpacityChannel) != 0)
3481 q->opacity=(Quantum) QuantumRange-q->opacity;
3482 if (((channel & IndexChannel) != 0) &&
3483 (image->colorspace == CMYKColorspace))
3484 indexes[x]=(IndexPacket) QuantumRange-indexes[x];
3485 q++;
3486 }
3487 sync=SyncCacheViewAuthenticPixels(image_view,exception);
3488 if (sync == MagickFalse)
3489 status=MagickFalse;
3490 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3491 {
3492 MagickBooleanType
3493 proceed;
3494
cristyb5d5f722009-11-04 03:03:49 +00003495#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00003496 #pragma omp critical (MagickCore_NegateImageChannel)
3497#endif
3498 proceed=SetImageProgress(image,NegateImageTag,progress++,
3499 image->rows);
3500 if (proceed == MagickFalse)
3501 status=MagickFalse;
3502 }
3503 }
3504 image_view=DestroyCacheView(image_view);
3505 return(MagickTrue);
3506 }
3507 /*
3508 Negate image.
3509 */
cristyb5d5f722009-11-04 03:03:49 +00003510#if defined(MAGICKCORE_OPENMP_SUPPORT)
3511 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003512#endif
cristybb503372010-05-27 20:51:26 +00003513 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003514 {
3515 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00003516 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00003517
cristy3ed852e2009-09-05 21:47:34 +00003518 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00003519 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003520
cristy8d4629b2010-08-30 17:59:46 +00003521 register ssize_t
3522 x;
3523
cristy3ed852e2009-09-05 21:47:34 +00003524 if (status == MagickFalse)
3525 continue;
3526 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
3527 if (q == (PixelPacket *) NULL)
3528 {
3529 status=MagickFalse;
3530 continue;
3531 }
3532 indexes=GetCacheViewAuthenticIndexQueue(image_view);
cristybb503372010-05-27 20:51:26 +00003533 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003534 {
3535 if ((channel & RedChannel) != 0)
3536 q->red=(Quantum) QuantumRange-q->red;
3537 if ((channel & GreenChannel) != 0)
3538 q->green=(Quantum) QuantumRange-q->green;
3539 if ((channel & BlueChannel) != 0)
3540 q->blue=(Quantum) QuantumRange-q->blue;
3541 if ((channel & OpacityChannel) != 0)
3542 q->opacity=(Quantum) QuantumRange-q->opacity;
3543 if (((channel & IndexChannel) != 0) &&
3544 (image->colorspace == CMYKColorspace))
3545 indexes[x]=(IndexPacket) QuantumRange-indexes[x];
3546 q++;
3547 }
3548 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
3549 status=MagickFalse;
3550 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3551 {
3552 MagickBooleanType
3553 proceed;
3554
cristyb5d5f722009-11-04 03:03:49 +00003555#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00003556 #pragma omp critical (MagickCore_NegateImageChannel)
3557#endif
3558 proceed=SetImageProgress(image,NegateImageTag,progress++,image->rows);
3559 if (proceed == MagickFalse)
3560 status=MagickFalse;
3561 }
3562 }
3563 image_view=DestroyCacheView(image_view);
3564 return(status);
3565}
3566
3567/*
3568%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3569% %
3570% %
3571% %
3572% N o r m a l i z e I m a g e %
3573% %
3574% %
3575% %
3576%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3577%
3578% The NormalizeImage() method enhances the contrast of a color image by
3579% mapping the darkest 2 percent of all pixel to black and the brightest
3580% 1 percent to white.
3581%
3582% The format of the NormalizeImage method is:
3583%
3584% MagickBooleanType NormalizeImage(Image *image)
3585% MagickBooleanType NormalizeImageChannel(Image *image,
3586% const ChannelType channel)
3587%
3588% A description of each parameter follows:
3589%
3590% o image: the image.
3591%
3592% o channel: the channel.
3593%
3594*/
3595
3596MagickExport MagickBooleanType NormalizeImage(Image *image)
3597{
3598 MagickBooleanType
3599 status;
3600
3601 status=NormalizeImageChannel(image,DefaultChannels);
3602 return(status);
3603}
3604
3605MagickExport MagickBooleanType NormalizeImageChannel(Image *image,
3606 const ChannelType channel)
3607{
3608 double
3609 black_point,
3610 white_point;
3611
cristy530239c2010-07-25 17:34:26 +00003612 black_point=(double) image->columns*image->rows*0.0015;
3613 white_point=(double) image->columns*image->rows*0.9995;
cristy3ed852e2009-09-05 21:47:34 +00003614 return(ContrastStretchImageChannel(image,channel,black_point,white_point));
3615}
3616
3617/*
3618%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3619% %
3620% %
3621% %
3622% S i g m o i d a l C o n t r a s t I m a g e %
3623% %
3624% %
3625% %
3626%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3627%
3628% SigmoidalContrastImage() adjusts the contrast of an image with a non-linear
3629% sigmoidal contrast algorithm. Increase the contrast of the image using a
3630% sigmoidal transfer function without saturating highlights or shadows.
3631% Contrast indicates how much to increase the contrast (0 is none; 3 is
3632% typical; 20 is pushing it); mid-point indicates where midtones fall in the
3633% resultant image (0 is white; 50% is middle-gray; 100% is black). Set
3634% sharpen to MagickTrue to increase the image contrast otherwise the contrast
3635% is reduced.
3636%
3637% The format of the SigmoidalContrastImage method is:
3638%
3639% MagickBooleanType SigmoidalContrastImage(Image *image,
3640% const MagickBooleanType sharpen,const char *levels)
3641% MagickBooleanType SigmoidalContrastImageChannel(Image *image,
3642% const ChannelType channel,const MagickBooleanType sharpen,
3643% const double contrast,const double midpoint)
3644%
3645% A description of each parameter follows:
3646%
3647% o image: the image.
3648%
3649% o channel: the channel.
3650%
3651% o sharpen: Increase or decrease image contrast.
3652%
3653% o contrast: control the "shoulder" of the contast curve.
3654%
3655% o midpoint: control the "toe" of the contast curve.
3656%
3657*/
3658
3659MagickExport MagickBooleanType SigmoidalContrastImage(Image *image,
3660 const MagickBooleanType sharpen,const char *levels)
3661{
3662 GeometryInfo
3663 geometry_info;
3664
3665 MagickBooleanType
3666 status;
3667
3668 MagickStatusType
3669 flags;
3670
3671 flags=ParseGeometry(levels,&geometry_info);
3672 if ((flags & SigmaValue) == 0)
3673 geometry_info.sigma=1.0*QuantumRange/2.0;
3674 if ((flags & PercentValue) != 0)
3675 geometry_info.sigma=1.0*QuantumRange*geometry_info.sigma/100.0;
3676 status=SigmoidalContrastImageChannel(image,DefaultChannels,sharpen,
3677 geometry_info.rho,geometry_info.sigma);
3678 return(status);
3679}
3680
3681MagickExport MagickBooleanType SigmoidalContrastImageChannel(Image *image,
3682 const ChannelType channel,const MagickBooleanType sharpen,
3683 const double contrast,const double midpoint)
3684{
3685#define SigmoidalContrastImageTag "SigmoidalContrast/Image"
3686
cristyc4c8d132010-01-07 01:58:38 +00003687 CacheView
3688 *image_view;
3689
cristy3ed852e2009-09-05 21:47:34 +00003690 ExceptionInfo
3691 *exception;
3692
cristy3ed852e2009-09-05 21:47:34 +00003693 MagickBooleanType
3694 status;
3695
cristybb503372010-05-27 20:51:26 +00003696 MagickOffsetType
3697 progress;
3698
cristy3ed852e2009-09-05 21:47:34 +00003699 MagickRealType
3700 *sigmoidal_map;
3701
cristybb503372010-05-27 20:51:26 +00003702 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003703 i;
3704
cristybb503372010-05-27 20:51:26 +00003705 ssize_t
3706 y;
3707
cristy3ed852e2009-09-05 21:47:34 +00003708 /*
3709 Allocate and initialize sigmoidal maps.
3710 */
3711 assert(image != (Image *) NULL);
3712 assert(image->signature == MagickSignature);
3713 if (image->debug != MagickFalse)
3714 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3715 sigmoidal_map=(MagickRealType *) AcquireQuantumMemory(MaxMap+1UL,
3716 sizeof(*sigmoidal_map));
3717 if (sigmoidal_map == (MagickRealType *) NULL)
3718 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
3719 image->filename);
3720 (void) ResetMagickMemory(sigmoidal_map,0,(MaxMap+1)*sizeof(*sigmoidal_map));
cristyb5d5f722009-11-04 03:03:49 +00003721#if defined(MAGICKCORE_OPENMP_SUPPORT)
3722 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003723#endif
cristybb503372010-05-27 20:51:26 +00003724 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +00003725 {
3726 if (sharpen != MagickFalse)
3727 {
3728 sigmoidal_map[i]=(MagickRealType) ScaleMapToQuantum((MagickRealType)
3729 (MaxMap*((1.0/(1.0+exp(contrast*(midpoint/(double) QuantumRange-
3730 (double) i/MaxMap))))-(1.0/(1.0+exp(contrast*(midpoint/
3731 (double) QuantumRange)))))/((1.0/(1.0+exp(contrast*(midpoint/
3732 (double) QuantumRange-1.0))))-(1.0/(1.0+exp(contrast*(midpoint/
3733 (double) QuantumRange)))))+0.5));
3734 continue;
3735 }
3736 sigmoidal_map[i]=(MagickRealType) ScaleMapToQuantum((MagickRealType)
3737 (MaxMap*(QuantumScale*midpoint-log((1.0-(1.0/(1.0+exp(midpoint/
3738 (double) QuantumRange*contrast))+((double) i/MaxMap)*((1.0/
3739 (1.0+exp(contrast*(midpoint/(double) QuantumRange-1.0))))-(1.0/
3740 (1.0+exp(midpoint/(double) QuantumRange*contrast))))))/
3741 (1.0/(1.0+exp(midpoint/(double) QuantumRange*contrast))+
3742 ((double) i/MaxMap)*((1.0/(1.0+exp(contrast*(midpoint/
3743 (double) QuantumRange-1.0))))-(1.0/(1.0+exp(midpoint/
3744 (double) QuantumRange*contrast))))))/contrast)));
3745 }
3746 if (image->storage_class == PseudoClass)
3747 {
3748 /*
3749 Sigmoidal-contrast enhance colormap.
3750 */
cristyb5d5f722009-11-04 03:03:49 +00003751#if defined(MAGICKCORE_OPENMP_SUPPORT)
3752 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003753#endif
cristybb503372010-05-27 20:51:26 +00003754 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00003755 {
3756 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003757 image->colormap[i].red=ClampToQuantum(sigmoidal_map[
cristy3ed852e2009-09-05 21:47:34 +00003758 ScaleQuantumToMap(image->colormap[i].red)]);
3759 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003760 image->colormap[i].green=ClampToQuantum(sigmoidal_map[
cristy3ed852e2009-09-05 21:47:34 +00003761 ScaleQuantumToMap(image->colormap[i].green)]);
3762 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003763 image->colormap[i].blue=ClampToQuantum(sigmoidal_map[
cristy3ed852e2009-09-05 21:47:34 +00003764 ScaleQuantumToMap(image->colormap[i].blue)]);
3765 if ((channel & OpacityChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003766 image->colormap[i].opacity=ClampToQuantum(sigmoidal_map[
cristy3ed852e2009-09-05 21:47:34 +00003767 ScaleQuantumToMap(image->colormap[i].opacity)]);
3768 }
3769 }
3770 /*
3771 Sigmoidal-contrast enhance image.
3772 */
3773 status=MagickTrue;
3774 progress=0;
3775 exception=(&image->exception);
3776 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00003777#if defined(MAGICKCORE_OPENMP_SUPPORT)
3778 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003779#endif
cristybb503372010-05-27 20:51:26 +00003780 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003781 {
3782 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00003783 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00003784
cristy3ed852e2009-09-05 21:47:34 +00003785 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00003786 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003787
cristy8d4629b2010-08-30 17:59:46 +00003788 register ssize_t
3789 x;
3790
cristy3ed852e2009-09-05 21:47:34 +00003791 if (status == MagickFalse)
3792 continue;
3793 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
3794 if (q == (PixelPacket *) NULL)
3795 {
3796 status=MagickFalse;
3797 continue;
3798 }
3799 indexes=GetCacheViewAuthenticIndexQueue(image_view);
cristybb503372010-05-27 20:51:26 +00003800 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003801 {
3802 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003803 q->red=ClampToQuantum(sigmoidal_map[ScaleQuantumToMap(q->red)]);
cristy3ed852e2009-09-05 21:47:34 +00003804 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003805 q->green=ClampToQuantum(sigmoidal_map[ScaleQuantumToMap(q->green)]);
cristy3ed852e2009-09-05 21:47:34 +00003806 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003807 q->blue=ClampToQuantum(sigmoidal_map[ScaleQuantumToMap(q->blue)]);
cristy3ed852e2009-09-05 21:47:34 +00003808 if ((channel & OpacityChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003809 q->opacity=ClampToQuantum(sigmoidal_map[ScaleQuantumToMap(q->opacity)]);
cristy3ed852e2009-09-05 21:47:34 +00003810 if (((channel & IndexChannel) != 0) &&
3811 (image->colorspace == CMYKColorspace))
cristyce70c172010-01-07 17:15:30 +00003812 indexes[x]=(IndexPacket) ClampToQuantum(sigmoidal_map[
cristy3ed852e2009-09-05 21:47:34 +00003813 ScaleQuantumToMap(indexes[x])]);
3814 q++;
3815 }
3816 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
3817 status=MagickFalse;
3818 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3819 {
3820 MagickBooleanType
3821 proceed;
3822
cristyb5d5f722009-11-04 03:03:49 +00003823#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00003824 #pragma omp critical (MagickCore_SigmoidalContrastImageChannel)
3825#endif
3826 proceed=SetImageProgress(image,SigmoidalContrastImageTag,progress++,
3827 image->rows);
3828 if (proceed == MagickFalse)
3829 status=MagickFalse;
3830 }
3831 }
3832 image_view=DestroyCacheView(image_view);
3833 sigmoidal_map=(MagickRealType *) RelinquishMagickMemory(sigmoidal_map);
3834 return(status);
3835}