blob: 9bc7236c2e025086904bc3946f23066fc5bae9be [file] [log] [blame]
cristy3ed852e2009-09-05 21:47:34 +00001/*
2%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3% %
4% %
5% %
6% EEEEE N N H H AAA N N CCCC EEEEE %
7% E NN N H H A A NN N C E %
8% EEE N N N HHHHH AAAAA N N N C EEE %
9% E N NN H H A A N NN C E %
10% EEEEE N N H H A A N N CCCC EEEEE %
11% %
12% %
13% MagickCore Image Enhancement Methods %
14% %
15% Software Design %
16% John Cristy %
17% July 1992 %
18% %
19% %
cristy7e41fe82010-12-04 23:12:08 +000020% Copyright 1999-2011 ImageMagick Studio LLC, a non-profit organization %
cristy3ed852e2009-09-05 21:47:34 +000021% dedicated to making software imaging solutions freely available. %
22% %
23% You may not use this file except in compliance with the License. You may %
24% obtain a copy of the License at %
25% %
26% http://www.imagemagick.org/script/license.php %
27% %
28% Unless required by applicable law or agreed to in writing, software %
29% distributed under the License is distributed on an "AS IS" BASIS, %
30% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %
31% See the License for the specific language governing permissions and %
32% limitations under the License. %
33% %
34%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
35%
36%
37%
38*/
39
40/*
41 Include declarations.
42*/
43#include "magick/studio.h"
44#include "magick/artifact.h"
45#include "magick/cache.h"
46#include "magick/cache-view.h"
47#include "magick/color.h"
48#include "magick/color-private.h"
49#include "magick/colorspace.h"
50#include "magick/composite-private.h"
51#include "magick/enhance.h"
52#include "magick/exception.h"
53#include "magick/exception-private.h"
cristya28d6b82010-01-11 20:03:47 +000054#include "magick/fx.h"
cristy3ed852e2009-09-05 21:47:34 +000055#include "magick/gem.h"
56#include "magick/geometry.h"
57#include "magick/histogram.h"
58#include "magick/image.h"
59#include "magick/image-private.h"
60#include "magick/memory_.h"
61#include "magick/monitor.h"
62#include "magick/monitor-private.h"
63#include "magick/option.h"
64#include "magick/quantum.h"
65#include "magick/quantum-private.h"
66#include "magick/resample.h"
67#include "magick/resample-private.h"
68#include "magick/statistic.h"
69#include "magick/string_.h"
cristyf2f27272009-12-17 14:48:46 +000070#include "magick/string-private.h"
cristy3ed852e2009-09-05 21:47:34 +000071#include "magick/thread-private.h"
72#include "magick/token.h"
73#include "magick/xml-tree.h"
74
75/*
76%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
77% %
78% %
79% %
80% A u t o G a m m a I m a g e %
81% %
82% %
83% %
84%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
85%
86% AutoGammaImage() extract the 'mean' from the image and adjust the image
87% to try make set its gamma appropriatally.
88%
cristy308b4e62009-09-21 14:40:44 +000089% The format of the AutoGammaImage method is:
cristy3ed852e2009-09-05 21:47:34 +000090%
91% MagickBooleanType AutoGammaImage(Image *image)
92% MagickBooleanType AutoGammaImageChannel(Image *image,
93% const ChannelType channel)
94%
95% A description of each parameter follows:
96%
97% o image: The image to auto-level
98%
99% o channel: The channels to auto-level. If the special 'SyncChannels'
100% flag is set all given channels is adjusted in the same way using the
101% mean average of those channels.
102%
103*/
104
105MagickExport MagickBooleanType AutoGammaImage(Image *image)
106{
107 return(AutoGammaImageChannel(image,DefaultChannels));
108}
109
110MagickExport MagickBooleanType AutoGammaImageChannel(Image *image,
111 const ChannelType channel)
112{
113 MagickStatusType
114 status;
115
116 double
cristy2b726bd2010-01-11 01:05:39 +0000117 mean,sans,gamma,logmean;
anthony4efe5972009-09-11 06:46:40 +0000118
119 logmean=log(0.5);
cristy3ed852e2009-09-05 21:47:34 +0000120
121 if ((channel & SyncChannels) != 0 )
122 {
123 /*
124 Apply gamma correction equally accross all given channels
125 */
cristy2b726bd2010-01-11 01:05:39 +0000126 (void) GetImageChannelMean(image,channel,&mean,&sans,&image->exception);
127 gamma=log(mean*QuantumScale)/logmean;
cristy3ed852e2009-09-05 21:47:34 +0000128 return LevelImageChannel(image, channel,
129 0.0, (double)QuantumRange, gamma);
130 }
131
132 /*
133 auto-gamma each channel separateally
134 */
135 status = MagickTrue;
136 if ((channel & RedChannel) != 0)
137 {
cristy2b726bd2010-01-11 01:05:39 +0000138 (void) GetImageChannelMean(image,RedChannel,&mean,&sans,
139 &image->exception);
140 gamma=log(mean*QuantumScale)/logmean;
cristy3ed852e2009-09-05 21:47:34 +0000141 status = status && LevelImageChannel(image, RedChannel,
142 0.0, (double)QuantumRange, gamma);
143 }
144 if ((channel & GreenChannel) != 0)
145 {
cristy2b726bd2010-01-11 01:05:39 +0000146 (void) GetImageChannelMean(image,GreenChannel,&mean,&sans,
147 &image->exception);
148 gamma=log(mean*QuantumScale)/logmean;
cristy3ed852e2009-09-05 21:47:34 +0000149 status = status && LevelImageChannel(image, GreenChannel,
150 0.0, (double)QuantumRange, gamma);
151 }
152 if ((channel & BlueChannel) != 0)
153 {
cristy2b726bd2010-01-11 01:05:39 +0000154 (void) GetImageChannelMean(image,BlueChannel,&mean,&sans,
155 &image->exception);
156 gamma=log(mean*QuantumScale)/logmean;
cristy3ed852e2009-09-05 21:47:34 +0000157 status = status && LevelImageChannel(image, BlueChannel,
158 0.0, (double)QuantumRange, gamma);
159 }
160 if (((channel & OpacityChannel) != 0) &&
161 (image->matte == MagickTrue))
162 {
cristy2b726bd2010-01-11 01:05:39 +0000163 (void) GetImageChannelMean(image,OpacityChannel,&mean,&sans,
164 &image->exception);
165 gamma=log(mean*QuantumScale)/logmean;
cristy3ed852e2009-09-05 21:47:34 +0000166 status = status && LevelImageChannel(image, OpacityChannel,
167 0.0, (double)QuantumRange, gamma);
168 }
169 if (((channel & IndexChannel) != 0) &&
170 (image->colorspace == CMYKColorspace))
171 {
cristy2b726bd2010-01-11 01:05:39 +0000172 (void) GetImageChannelMean(image,IndexChannel,&mean,&sans,
173 &image->exception);
174 gamma=log(mean*QuantumScale)/logmean;
cristy3ed852e2009-09-05 21:47:34 +0000175 status = status && LevelImageChannel(image, IndexChannel,
176 0.0, (double)QuantumRange, gamma);
177 }
178 return(status != 0 ? MagickTrue : MagickFalse);
179}
180
181/*
182%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
183% %
184% %
185% %
186% A u t o L e v e l I m a g e %
187% %
188% %
189% %
190%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
191%
192% AutoLevelImage() adjusts the levels of a particular image channel by
193% scaling the minimum and maximum values to the full quantum range.
194%
195% The format of the LevelImage method is:
196%
197% MagickBooleanType AutoLevelImage(Image *image)
198% MagickBooleanType AutoLevelImageChannel(Image *image,
199% const ChannelType channel)
200%
201% A description of each parameter follows:
202%
203% o image: The image to auto-level
204%
205% o channel: The channels to auto-level. If the special 'SyncChannels'
206% flag is set the min/max/mean value of all given channels is used for
207% all given channels, to all channels in the same way.
208%
209*/
210
211MagickExport MagickBooleanType AutoLevelImage(Image *image)
212{
213 return(AutoLevelImageChannel(image,DefaultChannels));
214}
215
216MagickExport MagickBooleanType AutoLevelImageChannel(Image *image,
217 const ChannelType channel)
218{
219 /*
220 This is simply a convenience function around a Min/Max Histogram Stretch
221 */
222 return MinMaxStretchImage(image, channel, 0.0, 0.0);
223}
224
225/*
226%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
227% %
228% %
229% %
cristya28d6b82010-01-11 20:03:47 +0000230% B r i g h t n e s s C o n t r a s t I m a g e %
231% %
232% %
233% %
234%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
235%
236% Use BrightnessContrastImage() to change the brightness and/or contrast of
237% an image. It converts the brightness and contrast parameters into slope
238% and intercept and calls a polynomical function to apply to the image.
239%
240% The format of the BrightnessContrastImage method is:
241%
242% MagickBooleanType BrightnessContrastImage(Image *image,
243% const double brightness,const double contrast)
244% MagickBooleanType BrightnessContrastImageChannel(Image *image,
245% const ChannelType channel,const double brightness,
246% const double contrast)
247%
248% A description of each parameter follows:
249%
250% o image: the image.
251%
252% o channel: the channel.
253%
254% o brightness: the brightness percent (-100 .. 100).
255%
256% o contrast: the contrast percent (-100 .. 100).
257%
258*/
259
260MagickExport MagickBooleanType BrightnessContrastImage(Image *image,
261 const double brightness,const double contrast)
262{
263 MagickBooleanType
264 status;
265
266 status=BrightnessContrastImageChannel(image,DefaultChannels,brightness,
267 contrast);
268 return(status);
269}
270
271MagickExport MagickBooleanType BrightnessContrastImageChannel(Image *image,
272 const ChannelType channel,const double brightness,const double contrast)
273{
274#define BrightnessContastImageTag "BrightnessContast/Image"
275
276 double
277 alpha,
278 intercept,
279 coefficients[2],
280 slope;
281
282 MagickBooleanType
283 status;
284
285 /*
286 Compute slope and intercept.
287 */
288 assert(image != (Image *) NULL);
289 assert(image->signature == MagickSignature);
290 if (image->debug != MagickFalse)
291 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
292 alpha=contrast;
cristy4205a3c2010-09-12 20:19:59 +0000293 slope=tan((double) (MagickPI*(alpha/100.0+1.0)/4.0));
cristya28d6b82010-01-11 20:03:47 +0000294 if (slope < 0.0)
295 slope=0.0;
296 intercept=brightness/100.0+((100-brightness)/200.0)*(1.0-slope);
297 coefficients[0]=slope;
298 coefficients[1]=intercept;
299 status=FunctionImageChannel(image,channel,PolynomialFunction,2,coefficients,
300 &image->exception);
301 return(status);
302}
303
304/*
305%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
306% %
307% %
308% %
cristy3ed852e2009-09-05 21:47:34 +0000309% C o l o r D e c i s i o n L i s t I m a g e %
310% %
311% %
312% %
313%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
314%
315% ColorDecisionListImage() accepts a lightweight Color Correction Collection
316% (CCC) file which solely contains one or more color corrections and applies
317% the correction to the image. Here is a sample CCC file:
318%
319% <ColorCorrectionCollection xmlns="urn:ASC:CDL:v1.2">
320% <ColorCorrection id="cc03345">
321% <SOPNode>
322% <Slope> 0.9 1.2 0.5 </Slope>
323% <Offset> 0.4 -0.5 0.6 </Offset>
324% <Power> 1.0 0.8 1.5 </Power>
325% </SOPNode>
326% <SATNode>
327% <Saturation> 0.85 </Saturation>
328% </SATNode>
329% </ColorCorrection>
330% </ColorCorrectionCollection>
331%
332% which includes the slop, offset, and power for each of the RGB channels
333% as well as the saturation.
334%
335% The format of the ColorDecisionListImage method is:
336%
337% MagickBooleanType ColorDecisionListImage(Image *image,
338% const char *color_correction_collection)
339%
340% A description of each parameter follows:
341%
342% o image: the image.
343%
344% o color_correction_collection: the color correction collection in XML.
345%
346*/
347MagickExport MagickBooleanType ColorDecisionListImage(Image *image,
348 const char *color_correction_collection)
349{
350#define ColorDecisionListCorrectImageTag "ColorDecisionList/Image"
351
352 typedef struct _Correction
353 {
354 double
355 slope,
356 offset,
357 power;
358 } Correction;
359
360 typedef struct _ColorCorrection
361 {
362 Correction
363 red,
364 green,
365 blue;
366
367 double
368 saturation;
369 } ColorCorrection;
370
cristyc4c8d132010-01-07 01:58:38 +0000371 CacheView
372 *image_view;
373
cristy3ed852e2009-09-05 21:47:34 +0000374 char
375 token[MaxTextExtent];
376
377 ColorCorrection
378 color_correction;
379
380 const char
381 *content,
382 *p;
383
384 ExceptionInfo
385 *exception;
386
cristy3ed852e2009-09-05 21:47:34 +0000387 MagickBooleanType
388 status;
389
cristybb503372010-05-27 20:51:26 +0000390 MagickOffsetType
391 progress;
392
cristy3ed852e2009-09-05 21:47:34 +0000393 PixelPacket
394 *cdl_map;
395
cristybb503372010-05-27 20:51:26 +0000396 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000397 i;
398
cristybb503372010-05-27 20:51:26 +0000399 ssize_t
400 y;
401
cristy3ed852e2009-09-05 21:47:34 +0000402 XMLTreeInfo
403 *cc,
404 *ccc,
405 *sat,
406 *sop;
407
cristy3ed852e2009-09-05 21:47:34 +0000408 /*
409 Allocate and initialize cdl maps.
410 */
411 assert(image != (Image *) NULL);
412 assert(image->signature == MagickSignature);
413 if (image->debug != MagickFalse)
414 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
415 if (color_correction_collection == (const char *) NULL)
416 return(MagickFalse);
417 ccc=NewXMLTree((const char *) color_correction_collection,&image->exception);
418 if (ccc == (XMLTreeInfo *) NULL)
419 return(MagickFalse);
420 cc=GetXMLTreeChild(ccc,"ColorCorrection");
421 if (cc == (XMLTreeInfo *) NULL)
422 {
423 ccc=DestroyXMLTree(ccc);
424 return(MagickFalse);
425 }
426 color_correction.red.slope=1.0;
427 color_correction.red.offset=0.0;
428 color_correction.red.power=1.0;
429 color_correction.green.slope=1.0;
430 color_correction.green.offset=0.0;
431 color_correction.green.power=1.0;
432 color_correction.blue.slope=1.0;
433 color_correction.blue.offset=0.0;
434 color_correction.blue.power=1.0;
435 color_correction.saturation=0.0;
436 sop=GetXMLTreeChild(cc,"SOPNode");
437 if (sop != (XMLTreeInfo *) NULL)
438 {
439 XMLTreeInfo
440 *offset,
441 *power,
442 *slope;
443
444 slope=GetXMLTreeChild(sop,"Slope");
445 if (slope != (XMLTreeInfo *) NULL)
446 {
447 content=GetXMLTreeContent(slope);
448 p=(const char *) content;
449 for (i=0; (*p != '\0') && (i < 3); i++)
450 {
451 GetMagickToken(p,&p,token);
452 if (*token == ',')
453 GetMagickToken(p,&p,token);
454 switch (i)
455 {
cristyf2f27272009-12-17 14:48:46 +0000456 case 0: color_correction.red.slope=StringToDouble(token); break;
457 case 1: color_correction.green.slope=StringToDouble(token); break;
458 case 2: color_correction.blue.slope=StringToDouble(token); break;
cristy3ed852e2009-09-05 21:47:34 +0000459 }
460 }
461 }
462 offset=GetXMLTreeChild(sop,"Offset");
463 if (offset != (XMLTreeInfo *) NULL)
464 {
465 content=GetXMLTreeContent(offset);
466 p=(const char *) content;
467 for (i=0; (*p != '\0') && (i < 3); i++)
468 {
469 GetMagickToken(p,&p,token);
470 if (*token == ',')
471 GetMagickToken(p,&p,token);
472 switch (i)
473 {
cristyf2f27272009-12-17 14:48:46 +0000474 case 0: color_correction.red.offset=StringToDouble(token); break;
475 case 1: color_correction.green.offset=StringToDouble(token); break;
476 case 2: color_correction.blue.offset=StringToDouble(token); break;
cristy3ed852e2009-09-05 21:47:34 +0000477 }
478 }
479 }
480 power=GetXMLTreeChild(sop,"Power");
481 if (power != (XMLTreeInfo *) NULL)
482 {
483 content=GetXMLTreeContent(power);
484 p=(const char *) content;
485 for (i=0; (*p != '\0') && (i < 3); i++)
486 {
487 GetMagickToken(p,&p,token);
488 if (*token == ',')
489 GetMagickToken(p,&p,token);
490 switch (i)
491 {
cristyf2f27272009-12-17 14:48:46 +0000492 case 0: color_correction.red.power=StringToDouble(token); break;
493 case 1: color_correction.green.power=StringToDouble(token); break;
494 case 2: color_correction.blue.power=StringToDouble(token); break;
cristy3ed852e2009-09-05 21:47:34 +0000495 }
496 }
497 }
498 }
499 sat=GetXMLTreeChild(cc,"SATNode");
500 if (sat != (XMLTreeInfo *) NULL)
501 {
502 XMLTreeInfo
503 *saturation;
504
505 saturation=GetXMLTreeChild(sat,"Saturation");
506 if (saturation != (XMLTreeInfo *) NULL)
507 {
508 content=GetXMLTreeContent(saturation);
509 p=(const char *) content;
510 GetMagickToken(p,&p,token);
cristyf2f27272009-12-17 14:48:46 +0000511 color_correction.saturation=StringToDouble(token);
cristy3ed852e2009-09-05 21:47:34 +0000512 }
513 }
514 ccc=DestroyXMLTree(ccc);
515 if (image->debug != MagickFalse)
516 {
517 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
518 " Color Correction Collection:");
519 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000520 " color_correction.red.slope: %g",color_correction.red.slope);
cristy3ed852e2009-09-05 21:47:34 +0000521 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000522 " color_correction.red.offset: %g",color_correction.red.offset);
cristy3ed852e2009-09-05 21:47:34 +0000523 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000524 " color_correction.red.power: %g",color_correction.red.power);
cristy3ed852e2009-09-05 21:47:34 +0000525 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000526 " color_correction.green.slope: %g",color_correction.green.slope);
cristy3ed852e2009-09-05 21:47:34 +0000527 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000528 " color_correction.green.offset: %g",color_correction.green.offset);
cristy3ed852e2009-09-05 21:47:34 +0000529 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000530 " color_correction.green.power: %g",color_correction.green.power);
cristy3ed852e2009-09-05 21:47:34 +0000531 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000532 " color_correction.blue.slope: %g",color_correction.blue.slope);
cristy3ed852e2009-09-05 21:47:34 +0000533 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000534 " color_correction.blue.offset: %g",color_correction.blue.offset);
cristy3ed852e2009-09-05 21:47:34 +0000535 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000536 " color_correction.blue.power: %g",color_correction.blue.power);
cristy3ed852e2009-09-05 21:47:34 +0000537 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000538 " color_correction.saturation: %g",color_correction.saturation);
cristy3ed852e2009-09-05 21:47:34 +0000539 }
540 cdl_map=(PixelPacket *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*cdl_map));
541 if (cdl_map == (PixelPacket *) NULL)
542 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
543 image->filename);
cristyb5d5f722009-11-04 03:03:49 +0000544#if defined(MAGICKCORE_OPENMP_SUPPORT)
545 #pragma omp parallel for schedule(dynamic,4)
cristy3ed852e2009-09-05 21:47:34 +0000546#endif
cristybb503372010-05-27 20:51:26 +0000547 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +0000548 {
cristyce70c172010-01-07 17:15:30 +0000549 cdl_map[i].red=ClampToQuantum((MagickRealType) ScaleMapToQuantum((
cristy3ed852e2009-09-05 21:47:34 +0000550 MagickRealType) (MaxMap*(pow(color_correction.red.slope*i/MaxMap+
551 color_correction.red.offset,color_correction.red.power)))));
cristyce70c172010-01-07 17:15:30 +0000552 cdl_map[i].green=ClampToQuantum((MagickRealType) ScaleMapToQuantum((
cristy3ed852e2009-09-05 21:47:34 +0000553 MagickRealType) (MaxMap*(pow(color_correction.green.slope*i/MaxMap+
554 color_correction.green.offset,color_correction.green.power)))));
cristyce70c172010-01-07 17:15:30 +0000555 cdl_map[i].blue=ClampToQuantum((MagickRealType) ScaleMapToQuantum((
cristy3ed852e2009-09-05 21:47:34 +0000556 MagickRealType) (MaxMap*(pow(color_correction.blue.slope*i/MaxMap+
557 color_correction.blue.offset,color_correction.blue.power)))));
558 }
559 if (image->storage_class == PseudoClass)
560 {
561 /*
562 Apply transfer function to colormap.
563 */
cristyb5d5f722009-11-04 03:03:49 +0000564#if defined(MAGICKCORE_OPENMP_SUPPORT)
565 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000566#endif
cristybb503372010-05-27 20:51:26 +0000567 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +0000568 {
569 double
570 luma;
571
572 luma=0.2126*image->colormap[i].red+0.7152*image->colormap[i].green+
573 0.0722*image->colormap[i].blue;
cristyce70c172010-01-07 17:15:30 +0000574 image->colormap[i].red=ClampToQuantum(luma+color_correction.saturation*
cristy3ed852e2009-09-05 21:47:34 +0000575 cdl_map[ScaleQuantumToMap(image->colormap[i].red)].red-luma);
cristyce70c172010-01-07 17:15:30 +0000576 image->colormap[i].green=ClampToQuantum(luma+
cristy3ed852e2009-09-05 21:47:34 +0000577 color_correction.saturation*cdl_map[ScaleQuantumToMap(
578 image->colormap[i].green)].green-luma);
cristyce70c172010-01-07 17:15:30 +0000579 image->colormap[i].blue=ClampToQuantum(luma+color_correction.saturation*
cristy3ed852e2009-09-05 21:47:34 +0000580 cdl_map[ScaleQuantumToMap(image->colormap[i].blue)].blue-luma);
581 }
582 }
583 /*
584 Apply transfer function to image.
585 */
586 status=MagickTrue;
587 progress=0;
588 exception=(&image->exception);
589 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +0000590#if defined(MAGICKCORE_OPENMP_SUPPORT)
591 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000592#endif
cristybb503372010-05-27 20:51:26 +0000593 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000594 {
595 double
596 luma;
597
cristy3ed852e2009-09-05 21:47:34 +0000598 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +0000599 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000600
cristy8d4629b2010-08-30 17:59:46 +0000601 register ssize_t
602 x;
603
cristy3ed852e2009-09-05 21:47:34 +0000604 if (status == MagickFalse)
605 continue;
606 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
607 if (q == (PixelPacket *) NULL)
608 {
609 status=MagickFalse;
610 continue;
611 }
cristybb503372010-05-27 20:51:26 +0000612 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000613 {
614 luma=0.2126*q->red+0.7152*q->green+0.0722*q->blue;
cristyce70c172010-01-07 17:15:30 +0000615 q->red=ClampToQuantum(luma+color_correction.saturation*
cristy3ed852e2009-09-05 21:47:34 +0000616 (cdl_map[ScaleQuantumToMap(q->red)].red-luma));
cristyce70c172010-01-07 17:15:30 +0000617 q->green=ClampToQuantum(luma+color_correction.saturation*
cristy3ed852e2009-09-05 21:47:34 +0000618 (cdl_map[ScaleQuantumToMap(q->green)].green-luma));
cristyce70c172010-01-07 17:15:30 +0000619 q->blue=ClampToQuantum(luma+color_correction.saturation*
cristy3ed852e2009-09-05 21:47:34 +0000620 (cdl_map[ScaleQuantumToMap(q->blue)].blue-luma));
621 q++;
622 }
623 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
624 status=MagickFalse;
625 if (image->progress_monitor != (MagickProgressMonitor) NULL)
626 {
627 MagickBooleanType
628 proceed;
629
cristyb5d5f722009-11-04 03:03:49 +0000630#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +0000631 #pragma omp critical (MagickCore_ColorDecisionListImageChannel)
632#endif
633 proceed=SetImageProgress(image,ColorDecisionListCorrectImageTag,
634 progress++,image->rows);
635 if (proceed == MagickFalse)
636 status=MagickFalse;
637 }
638 }
639 image_view=DestroyCacheView(image_view);
640 cdl_map=(PixelPacket *) RelinquishMagickMemory(cdl_map);
641 return(status);
642}
643
644/*
645%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
646% %
647% %
648% %
649% C l u t I m a g e %
650% %
651% %
652% %
653%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
654%
655% ClutImage() replaces each color value in the given image, by using it as an
656% index to lookup a replacement color value in a Color Look UP Table in the
cristycee97112010-05-28 00:44:52 +0000657% form of an image. The values are extracted along a diagonal of the CLUT
cristy3ed852e2009-09-05 21:47:34 +0000658% image so either a horizontal or vertial gradient image can be used.
659%
660% Typically this is used to either re-color a gray-scale image according to a
661% color gradient in the CLUT image, or to perform a freeform histogram
662% (level) adjustment according to the (typically gray-scale) gradient in the
663% CLUT image.
664%
665% When the 'channel' mask includes the matte/alpha transparency channel but
666% one image has no such channel it is assumed that that image is a simple
667% gray-scale image that will effect the alpha channel values, either for
668% gray-scale coloring (with transparent or semi-transparent colors), or
669% a histogram adjustment of existing alpha channel values. If both images
670% have matte channels, direct and normal indexing is applied, which is rarely
671% used.
672%
673% The format of the ClutImage method is:
674%
675% MagickBooleanType ClutImage(Image *image,Image *clut_image)
676% MagickBooleanType ClutImageChannel(Image *image,
677% const ChannelType channel,Image *clut_image)
678%
679% A description of each parameter follows:
680%
681% o image: the image, which is replaced by indexed CLUT values
682%
683% o clut_image: the color lookup table image for replacement color values.
684%
685% o channel: the channel.
686%
687*/
688
689MagickExport MagickBooleanType ClutImage(Image *image,const Image *clut_image)
690{
691 return(ClutImageChannel(image,DefaultChannels,clut_image));
692}
693
694MagickExport MagickBooleanType ClutImageChannel(Image *image,
695 const ChannelType channel,const Image *clut_image)
696{
697#define ClutImageTag "Clut/Image"
698
cristyfa112112010-01-04 17:48:07 +0000699 CacheView
700 *image_view;
701
cristy3ed852e2009-09-05 21:47:34 +0000702 ExceptionInfo
703 *exception;
704
cristy3ed852e2009-09-05 21:47:34 +0000705 MagickBooleanType
706 status;
707
cristybb503372010-05-27 20:51:26 +0000708 MagickOffsetType
709 progress;
710
cristy3ed852e2009-09-05 21:47:34 +0000711 MagickPixelPacket
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 {
cristy5c9e6f22010-09-17 17:31:01 +0000745 const int
746 id = GetOpenMPThreadId();
cristy6ebe97c2010-07-03 01:17:28 +0000747
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;
cristybb503372010-05-27 20:51:26 +0000770 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000771 {
772 /*
773 PROGRAMMERS WARNING:
774
775 Apply OpacityChannel BEFORE the color channels. Do not re-order.
776
777 The handling special case 2 (coloring gray-scale), requires access to
778 the unmodified colors of the original image to determine the index
779 value. As such alpha/matte channel handling must be performed BEFORE,
780 any of the color channels are modified.
781
782 */
783 if ((channel & OpacityChannel) != 0)
784 {
785 if (clut_image->matte == MagickFalse)
786 {
787 /*
788 A gray-scale LUT replacement for an image alpha channel.
789 */
790 (void) ResamplePixelColor(resample_filter[id],QuantumScale*
cristy46f08202010-01-10 04:04:21 +0000791 GetAlphaPixelComponent(q)*(clut_image->columns+adjust),
792 QuantumScale*GetAlphaPixelComponent(q)*(clut_image->rows+
cristy3ed852e2009-09-05 21:47:34 +0000793 adjust),&pixel);
794 q->opacity=(Quantum) (QuantumRange-MagickPixelIntensityToQuantum(
795 &pixel));
796 }
797 else
798 if (image->matte == MagickFalse)
799 {
800 /*
801 A greyscale image being colored by a LUT with transparency.
802 */
803 (void) ResamplePixelColor(resample_filter[id],QuantumScale*
804 PixelIntensity(q)*(clut_image->columns-adjust),QuantumScale*
805 PixelIntensity(q)*(clut_image->rows-adjust),&pixel);
cristyce70c172010-01-07 17:15:30 +0000806 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +0000807 }
808 else
809 {
810 /*
811 Direct alpha channel lookup.
812 */
813 (void) ResamplePixelColor(resample_filter[id],QuantumScale*
814 q->opacity*(clut_image->columns-adjust),QuantumScale*
cristyd1764462011-03-22 16:16:25 +0000815 q->opacity*(clut_image->rows-adjust),&pixel);
cristyce70c172010-01-07 17:15:30 +0000816 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +0000817 }
818 }
819 if ((channel & RedChannel) != 0)
820 {
821 (void) ResamplePixelColor(resample_filter[id],QuantumScale*q->red*
822 (clut_image->columns-adjust),QuantumScale*q->red*
823 (clut_image->rows-adjust),&pixel);
cristyce70c172010-01-07 17:15:30 +0000824 SetRedPixelComponent(q,ClampRedPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +0000825 }
826 if ((channel & GreenChannel) != 0)
827 {
828 (void) ResamplePixelColor(resample_filter[id],QuantumScale*q->green*
829 (clut_image->columns-adjust),QuantumScale*q->green*
830 (clut_image->rows-adjust),&pixel);
cristyce70c172010-01-07 17:15:30 +0000831 SetGreenPixelComponent(q,ClampGreenPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +0000832 }
833 if ((channel & BlueChannel) != 0)
834 {
835 (void) ResamplePixelColor(resample_filter[id],QuantumScale*q->blue*
836 (clut_image->columns-adjust),QuantumScale*q->blue*
837 (clut_image->rows-adjust),&pixel);
cristyce70c172010-01-07 17:15:30 +0000838 SetBluePixelComponent(q,ClampBluePixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +0000839 }
840 if (((channel & IndexChannel) != 0) &&
841 (image->colorspace == CMYKColorspace))
842 {
843 (void) ResamplePixelColor(resample_filter[id],QuantumScale*indexes[x]*
844 (clut_image->columns-adjust),QuantumScale*indexes[x]*
845 (clut_image->rows-adjust),&pixel);
cristyce70c172010-01-07 17:15:30 +0000846 indexes[x]=ClampToQuantum(pixel.index);
cristy3ed852e2009-09-05 21:47:34 +0000847 }
848 q++;
849 }
850 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
851 status=MagickFalse;
852 if (image->progress_monitor != (MagickProgressMonitor) NULL)
853 {
854 MagickBooleanType
855 proceed;
856
cristyb5d5f722009-11-04 03:03:49 +0000857#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +0000858 #pragma omp critical (MagickCore_ClutImageChannel)
859#endif
860 proceed=SetImageProgress(image,ClutImageTag,progress++,image->rows);
861 if (proceed == MagickFalse)
862 status=MagickFalse;
863 }
864 }
865 image_view=DestroyCacheView(image_view);
866 resample_filter=DestroyResampleFilterThreadSet(resample_filter);
867 /*
868 Enable alpha channel if CLUT image could enable it.
869 */
870 if ((clut_image->matte != MagickFalse) && ((channel & OpacityChannel) != 0))
871 (void) SetImageAlphaChannel(image,ActivateAlphaChannel);
872 return(status);
873}
874
875/*
876%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
877% %
878% %
879% %
880% C o n t r a s t I m a g e %
881% %
882% %
883% %
884%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
885%
886% ContrastImage() enhances the intensity differences between the lighter and
887% darker elements of the image. Set sharpen to a MagickTrue to increase the
888% image contrast otherwise the contrast is reduced.
889%
890% The format of the ContrastImage method is:
891%
892% MagickBooleanType ContrastImage(Image *image,
893% const MagickBooleanType sharpen)
894%
895% A description of each parameter follows:
896%
897% o image: the image.
898%
899% o sharpen: Increase or decrease image contrast.
900%
901*/
902
903static void Contrast(const int sign,Quantum *red,Quantum *green,Quantum *blue)
904{
905 double
906 brightness,
907 hue,
908 saturation;
909
910 /*
911 Enhance contrast: dark color become darker, light color become lighter.
912 */
913 assert(red != (Quantum *) NULL);
914 assert(green != (Quantum *) NULL);
915 assert(blue != (Quantum *) NULL);
916 hue=0.0;
917 saturation=0.0;
918 brightness=0.0;
919 ConvertRGBToHSB(*red,*green,*blue,&hue,&saturation,&brightness);
cristy31ac0f02011-03-12 02:04:47 +0000920 brightness+=0.5*sign*(0.5*(sin((double) (MagickPI*(brightness-0.5)))+1.0)-
cristy4205a3c2010-09-12 20:19:59 +0000921 brightness);
cristy3ed852e2009-09-05 21:47:34 +0000922 if (brightness > 1.0)
923 brightness=1.0;
924 else
925 if (brightness < 0.0)
926 brightness=0.0;
927 ConvertHSBToRGB(hue,saturation,brightness,red,green,blue);
928}
929
930MagickExport MagickBooleanType ContrastImage(Image *image,
931 const MagickBooleanType sharpen)
932{
933#define ContrastImageTag "Contrast/Image"
934
cristyc4c8d132010-01-07 01:58:38 +0000935 CacheView
936 *image_view;
937
cristy3ed852e2009-09-05 21:47:34 +0000938 ExceptionInfo
939 *exception;
940
941 int
942 sign;
943
cristy3ed852e2009-09-05 21:47:34 +0000944 MagickBooleanType
945 status;
946
cristybb503372010-05-27 20:51:26 +0000947 MagickOffsetType
948 progress;
949
950 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000951 i;
952
cristybb503372010-05-27 20:51:26 +0000953 ssize_t
954 y;
955
cristy3ed852e2009-09-05 21:47:34 +0000956 assert(image != (Image *) NULL);
957 assert(image->signature == MagickSignature);
958 if (image->debug != MagickFalse)
959 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
960 sign=sharpen != MagickFalse ? 1 : -1;
961 if (image->storage_class == PseudoClass)
962 {
963 /*
964 Contrast enhance colormap.
965 */
cristybb503372010-05-27 20:51:26 +0000966 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +0000967 Contrast(sign,&image->colormap[i].red,&image->colormap[i].green,
968 &image->colormap[i].blue);
969 }
970 /*
971 Contrast enhance image.
972 */
973 status=MagickTrue;
974 progress=0;
975 exception=(&image->exception);
976 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +0000977#if defined(MAGICKCORE_OPENMP_SUPPORT)
978 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000979#endif
cristybb503372010-05-27 20:51:26 +0000980 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000981 {
cristy3ed852e2009-09-05 21:47:34 +0000982 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +0000983 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000984
cristy8d4629b2010-08-30 17:59:46 +0000985 register ssize_t
986 x;
987
cristy3ed852e2009-09-05 21:47:34 +0000988 if (status == MagickFalse)
989 continue;
990 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
991 if (q == (PixelPacket *) NULL)
992 {
993 status=MagickFalse;
994 continue;
995 }
cristybb503372010-05-27 20:51:26 +0000996 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000997 {
998 Contrast(sign,&q->red,&q->green,&q->blue);
999 q++;
1000 }
1001 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
1002 status=MagickFalse;
1003 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1004 {
1005 MagickBooleanType
1006 proceed;
1007
cristyb5d5f722009-11-04 03:03:49 +00001008#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00001009 #pragma omp critical (MagickCore_ContrastImage)
1010#endif
1011 proceed=SetImageProgress(image,ContrastImageTag,progress++,image->rows);
1012 if (proceed == MagickFalse)
1013 status=MagickFalse;
1014 }
1015 }
1016 image_view=DestroyCacheView(image_view);
1017 return(status);
1018}
1019
1020/*
1021%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1022% %
1023% %
1024% %
1025% C o n t r a s t S t r e t c h I m a g e %
1026% %
1027% %
1028% %
1029%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1030%
1031% The ContrastStretchImage() is a simple image enhancement technique that
1032% attempts to improve the contrast in an image by `stretching' the range of
1033% intensity values it contains to span a desired range of values. It differs
1034% from the more sophisticated histogram equalization in that it can only
1035% apply % a linear scaling function to the image pixel values. As a result
1036% the `enhancement' is less harsh.
1037%
1038% The format of the ContrastStretchImage method is:
1039%
1040% MagickBooleanType ContrastStretchImage(Image *image,
1041% const char *levels)
1042% MagickBooleanType ContrastStretchImageChannel(Image *image,
cristybb503372010-05-27 20:51:26 +00001043% const size_t channel,const double black_point,
cristy3ed852e2009-09-05 21:47:34 +00001044% const double white_point)
1045%
1046% A description of each parameter follows:
1047%
1048% o image: the image.
1049%
1050% o channel: the channel.
1051%
1052% o black_point: the black point.
1053%
1054% o white_point: the white point.
1055%
1056% o levels: Specify the levels where the black and white points have the
1057% range of 0 to number-of-pixels (e.g. 1%, 10x90%, etc.).
1058%
1059*/
1060
1061MagickExport MagickBooleanType ContrastStretchImage(Image *image,
1062 const char *levels)
1063{
1064 double
1065 black_point,
1066 white_point;
1067
1068 GeometryInfo
1069 geometry_info;
1070
1071 MagickBooleanType
1072 status;
1073
1074 MagickStatusType
1075 flags;
1076
1077 /*
1078 Parse levels.
1079 */
1080 if (levels == (char *) NULL)
1081 return(MagickFalse);
1082 flags=ParseGeometry(levels,&geometry_info);
1083 black_point=geometry_info.rho;
1084 white_point=(double) image->columns*image->rows;
1085 if ((flags & SigmaValue) != 0)
1086 white_point=geometry_info.sigma;
1087 if ((flags & PercentValue) != 0)
1088 {
1089 black_point*=(double) QuantumRange/100.0;
1090 white_point*=(double) QuantumRange/100.0;
1091 }
1092 if ((flags & SigmaValue) == 0)
1093 white_point=(double) image->columns*image->rows-black_point;
1094 status=ContrastStretchImageChannel(image,DefaultChannels,black_point,
1095 white_point);
1096 return(status);
1097}
1098
1099MagickExport MagickBooleanType ContrastStretchImageChannel(Image *image,
1100 const ChannelType channel,const double black_point,const double white_point)
1101{
1102#define MaxRange(color) ((MagickRealType) ScaleQuantumToMap((Quantum) (color)))
1103#define ContrastStretchImageTag "ContrastStretch/Image"
1104
cristyc4c8d132010-01-07 01:58:38 +00001105 CacheView
1106 *image_view;
1107
cristy3ed852e2009-09-05 21:47:34 +00001108 double
1109 intensity;
1110
1111 ExceptionInfo
1112 *exception;
1113
cristy3ed852e2009-09-05 21:47:34 +00001114 MagickBooleanType
1115 status;
1116
cristybb503372010-05-27 20:51:26 +00001117 MagickOffsetType
1118 progress;
1119
cristy3ed852e2009-09-05 21:47:34 +00001120 MagickPixelPacket
1121 black,
1122 *histogram,
1123 *stretch_map,
1124 white;
1125
cristybb503372010-05-27 20:51:26 +00001126 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001127 i;
1128
cristybb503372010-05-27 20:51:26 +00001129 ssize_t
1130 y;
1131
cristy3ed852e2009-09-05 21:47:34 +00001132 /*
1133 Allocate histogram and stretch map.
1134 */
1135 assert(image != (Image *) NULL);
1136 assert(image->signature == MagickSignature);
1137 if (image->debug != MagickFalse)
1138 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1139 histogram=(MagickPixelPacket *) AcquireQuantumMemory(MaxMap+1UL,
1140 sizeof(*histogram));
1141 stretch_map=(MagickPixelPacket *) AcquireQuantumMemory(MaxMap+1UL,
1142 sizeof(*stretch_map));
1143 if ((histogram == (MagickPixelPacket *) NULL) ||
1144 (stretch_map == (MagickPixelPacket *) NULL))
1145 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
1146 image->filename);
1147 /*
1148 Form histogram.
1149 */
1150 status=MagickTrue;
1151 exception=(&image->exception);
1152 (void) ResetMagickMemory(histogram,0,(MaxMap+1)*sizeof(*histogram));
1153 image_view=AcquireCacheView(image);
cristybb503372010-05-27 20:51:26 +00001154 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001155 {
1156 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001157 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00001158
1159 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00001160 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00001161
cristybb503372010-05-27 20:51:26 +00001162 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001163 x;
1164
1165 if (status == MagickFalse)
1166 continue;
1167 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
1168 if (p == (const PixelPacket *) NULL)
1169 {
1170 status=MagickFalse;
1171 continue;
1172 }
1173 indexes=GetCacheViewAuthenticIndexQueue(image_view);
1174 if (channel == DefaultChannels)
cristybb503372010-05-27 20:51:26 +00001175 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001176 {
1177 Quantum
1178 intensity;
1179
1180 intensity=PixelIntensityToQuantum(p);
1181 histogram[ScaleQuantumToMap(intensity)].red++;
1182 histogram[ScaleQuantumToMap(intensity)].green++;
1183 histogram[ScaleQuantumToMap(intensity)].blue++;
1184 histogram[ScaleQuantumToMap(intensity)].index++;
1185 p++;
1186 }
1187 else
cristybb503372010-05-27 20:51:26 +00001188 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001189 {
1190 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001191 histogram[ScaleQuantumToMap(GetRedPixelComponent(p))].red++;
cristy3ed852e2009-09-05 21:47:34 +00001192 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001193 histogram[ScaleQuantumToMap(GetGreenPixelComponent(p))].green++;
cristy3ed852e2009-09-05 21:47:34 +00001194 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001195 histogram[ScaleQuantumToMap(GetBluePixelComponent(p))].blue++;
cristy3ed852e2009-09-05 21:47:34 +00001196 if ((channel & OpacityChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001197 histogram[ScaleQuantumToMap(GetOpacityPixelComponent(p))].opacity++;
cristy3ed852e2009-09-05 21:47:34 +00001198 if (((channel & IndexChannel) != 0) &&
1199 (image->colorspace == CMYKColorspace))
1200 histogram[ScaleQuantumToMap(indexes[x])].index++;
1201 p++;
1202 }
1203 }
1204 /*
1205 Find the histogram boundaries by locating the black/white levels.
1206 */
1207 black.red=0.0;
1208 white.red=MaxRange(QuantumRange);
1209 if ((channel & RedChannel) != 0)
1210 {
1211 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00001212 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +00001213 {
1214 intensity+=histogram[i].red;
1215 if (intensity > black_point)
1216 break;
1217 }
1218 black.red=(MagickRealType) i;
1219 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00001220 for (i=(ssize_t) MaxMap; i != 0; i--)
cristy3ed852e2009-09-05 21:47:34 +00001221 {
1222 intensity+=histogram[i].red;
1223 if (intensity > ((double) image->columns*image->rows-white_point))
1224 break;
1225 }
1226 white.red=(MagickRealType) i;
1227 }
1228 black.green=0.0;
1229 white.green=MaxRange(QuantumRange);
1230 if ((channel & GreenChannel) != 0)
1231 {
1232 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00001233 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +00001234 {
1235 intensity+=histogram[i].green;
1236 if (intensity > black_point)
1237 break;
1238 }
1239 black.green=(MagickRealType) i;
1240 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00001241 for (i=(ssize_t) MaxMap; i != 0; i--)
cristy3ed852e2009-09-05 21:47:34 +00001242 {
1243 intensity+=histogram[i].green;
1244 if (intensity > ((double) image->columns*image->rows-white_point))
1245 break;
1246 }
1247 white.green=(MagickRealType) i;
1248 }
1249 black.blue=0.0;
1250 white.blue=MaxRange(QuantumRange);
1251 if ((channel & BlueChannel) != 0)
1252 {
1253 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00001254 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +00001255 {
1256 intensity+=histogram[i].blue;
1257 if (intensity > black_point)
1258 break;
1259 }
1260 black.blue=(MagickRealType) i;
1261 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00001262 for (i=(ssize_t) MaxMap; i != 0; i--)
cristy3ed852e2009-09-05 21:47:34 +00001263 {
1264 intensity+=histogram[i].blue;
1265 if (intensity > ((double) image->columns*image->rows-white_point))
1266 break;
1267 }
1268 white.blue=(MagickRealType) i;
1269 }
1270 black.opacity=0.0;
1271 white.opacity=MaxRange(QuantumRange);
1272 if ((channel & OpacityChannel) != 0)
1273 {
1274 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00001275 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +00001276 {
1277 intensity+=histogram[i].opacity;
1278 if (intensity > black_point)
1279 break;
1280 }
1281 black.opacity=(MagickRealType) i;
1282 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00001283 for (i=(ssize_t) MaxMap; i != 0; i--)
cristy3ed852e2009-09-05 21:47:34 +00001284 {
1285 intensity+=histogram[i].opacity;
1286 if (intensity > ((double) image->columns*image->rows-white_point))
1287 break;
1288 }
1289 white.opacity=(MagickRealType) i;
1290 }
1291 black.index=0.0;
1292 white.index=MaxRange(QuantumRange);
1293 if (((channel & IndexChannel) != 0) && (image->colorspace == CMYKColorspace))
1294 {
1295 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00001296 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +00001297 {
1298 intensity+=histogram[i].index;
1299 if (intensity > black_point)
1300 break;
1301 }
1302 black.index=(MagickRealType) i;
1303 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00001304 for (i=(ssize_t) MaxMap; i != 0; i--)
cristy3ed852e2009-09-05 21:47:34 +00001305 {
1306 intensity+=histogram[i].index;
1307 if (intensity > ((double) image->columns*image->rows-white_point))
1308 break;
1309 }
1310 white.index=(MagickRealType) i;
1311 }
1312 histogram=(MagickPixelPacket *) RelinquishMagickMemory(histogram);
1313 /*
1314 Stretch the histogram to create the stretched image mapping.
1315 */
1316 (void) ResetMagickMemory(stretch_map,0,(MaxMap+1)*sizeof(*stretch_map));
cristyb5d5f722009-11-04 03:03:49 +00001317#if defined(MAGICKCORE_OPENMP_SUPPORT)
1318 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001319#endif
cristybb503372010-05-27 20:51:26 +00001320 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +00001321 {
1322 if ((channel & RedChannel) != 0)
1323 {
cristybb503372010-05-27 20:51:26 +00001324 if (i < (ssize_t) black.red)
cristy3ed852e2009-09-05 21:47:34 +00001325 stretch_map[i].red=0.0;
1326 else
cristybb503372010-05-27 20:51:26 +00001327 if (i > (ssize_t) white.red)
cristy3ed852e2009-09-05 21:47:34 +00001328 stretch_map[i].red=(MagickRealType) QuantumRange;
1329 else
1330 if (black.red != white.red)
1331 stretch_map[i].red=(MagickRealType) ScaleMapToQuantum(
1332 (MagickRealType) (MaxMap*(i-black.red)/(white.red-black.red)));
1333 }
1334 if ((channel & GreenChannel) != 0)
1335 {
cristybb503372010-05-27 20:51:26 +00001336 if (i < (ssize_t) black.green)
cristy3ed852e2009-09-05 21:47:34 +00001337 stretch_map[i].green=0.0;
1338 else
cristybb503372010-05-27 20:51:26 +00001339 if (i > (ssize_t) white.green)
cristy3ed852e2009-09-05 21:47:34 +00001340 stretch_map[i].green=(MagickRealType) QuantumRange;
1341 else
1342 if (black.green != white.green)
1343 stretch_map[i].green=(MagickRealType) ScaleMapToQuantum(
1344 (MagickRealType) (MaxMap*(i-black.green)/(white.green-
1345 black.green)));
1346 }
1347 if ((channel & BlueChannel) != 0)
1348 {
cristybb503372010-05-27 20:51:26 +00001349 if (i < (ssize_t) black.blue)
cristy3ed852e2009-09-05 21:47:34 +00001350 stretch_map[i].blue=0.0;
1351 else
cristybb503372010-05-27 20:51:26 +00001352 if (i > (ssize_t) white.blue)
cristy3ed852e2009-09-05 21:47:34 +00001353 stretch_map[i].blue=(MagickRealType) QuantumRange;
1354 else
1355 if (black.blue != white.blue)
1356 stretch_map[i].blue=(MagickRealType) ScaleMapToQuantum(
1357 (MagickRealType) (MaxMap*(i-black.blue)/(white.blue-
1358 black.blue)));
1359 }
1360 if ((channel & OpacityChannel) != 0)
1361 {
cristybb503372010-05-27 20:51:26 +00001362 if (i < (ssize_t) black.opacity)
cristy3ed852e2009-09-05 21:47:34 +00001363 stretch_map[i].opacity=0.0;
1364 else
cristybb503372010-05-27 20:51:26 +00001365 if (i > (ssize_t) white.opacity)
cristy3ed852e2009-09-05 21:47:34 +00001366 stretch_map[i].opacity=(MagickRealType) QuantumRange;
1367 else
1368 if (black.opacity != white.opacity)
1369 stretch_map[i].opacity=(MagickRealType) ScaleMapToQuantum(
1370 (MagickRealType) (MaxMap*(i-black.opacity)/(white.opacity-
1371 black.opacity)));
1372 }
1373 if (((channel & IndexChannel) != 0) &&
1374 (image->colorspace == CMYKColorspace))
1375 {
cristybb503372010-05-27 20:51:26 +00001376 if (i < (ssize_t) black.index)
cristy3ed852e2009-09-05 21:47:34 +00001377 stretch_map[i].index=0.0;
1378 else
cristybb503372010-05-27 20:51:26 +00001379 if (i > (ssize_t) white.index)
cristy3ed852e2009-09-05 21:47:34 +00001380 stretch_map[i].index=(MagickRealType) QuantumRange;
1381 else
1382 if (black.index != white.index)
1383 stretch_map[i].index=(MagickRealType) ScaleMapToQuantum(
1384 (MagickRealType) (MaxMap*(i-black.index)/(white.index-
1385 black.index)));
1386 }
1387 }
1388 /*
1389 Stretch the image.
1390 */
1391 if (((channel & OpacityChannel) != 0) || (((channel & IndexChannel) != 0) &&
1392 (image->colorspace == CMYKColorspace)))
1393 image->storage_class=DirectClass;
1394 if (image->storage_class == PseudoClass)
1395 {
1396 /*
1397 Stretch colormap.
1398 */
cristyb5d5f722009-11-04 03:03:49 +00001399#if defined(MAGICKCORE_OPENMP_SUPPORT)
1400 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001401#endif
cristybb503372010-05-27 20:51:26 +00001402 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00001403 {
1404 if ((channel & RedChannel) != 0)
1405 {
1406 if (black.red != white.red)
cristyce70c172010-01-07 17:15:30 +00001407 image->colormap[i].red=ClampToQuantum(stretch_map[
cristy3ed852e2009-09-05 21:47:34 +00001408 ScaleQuantumToMap(image->colormap[i].red)].red);
1409 }
1410 if ((channel & GreenChannel) != 0)
1411 {
1412 if (black.green != white.green)
cristyce70c172010-01-07 17:15:30 +00001413 image->colormap[i].green=ClampToQuantum(stretch_map[
cristy3ed852e2009-09-05 21:47:34 +00001414 ScaleQuantumToMap(image->colormap[i].green)].green);
1415 }
1416 if ((channel & BlueChannel) != 0)
1417 {
1418 if (black.blue != white.blue)
cristyce70c172010-01-07 17:15:30 +00001419 image->colormap[i].blue=ClampToQuantum(stretch_map[
cristy3ed852e2009-09-05 21:47:34 +00001420 ScaleQuantumToMap(image->colormap[i].blue)].blue);
1421 }
1422 if ((channel & OpacityChannel) != 0)
1423 {
1424 if (black.opacity != white.opacity)
cristyce70c172010-01-07 17:15:30 +00001425 image->colormap[i].opacity=ClampToQuantum(stretch_map[
cristy3ed852e2009-09-05 21:47:34 +00001426 ScaleQuantumToMap(image->colormap[i].opacity)].opacity);
1427 }
1428 }
1429 }
1430 /*
1431 Stretch image.
1432 */
1433 status=MagickTrue;
1434 progress=0;
cristyb5d5f722009-11-04 03:03:49 +00001435#if defined(MAGICKCORE_OPENMP_SUPPORT)
1436 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001437#endif
cristybb503372010-05-27 20:51:26 +00001438 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001439 {
1440 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00001441 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00001442
cristy3ed852e2009-09-05 21:47:34 +00001443 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001444 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001445
cristy8d4629b2010-08-30 17:59:46 +00001446 register ssize_t
1447 x;
1448
cristy3ed852e2009-09-05 21:47:34 +00001449 if (status == MagickFalse)
1450 continue;
1451 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
1452 if (q == (PixelPacket *) NULL)
1453 {
1454 status=MagickFalse;
1455 continue;
1456 }
1457 indexes=GetCacheViewAuthenticIndexQueue(image_view);
cristybb503372010-05-27 20:51:26 +00001458 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001459 {
1460 if ((channel & RedChannel) != 0)
1461 {
1462 if (black.red != white.red)
cristyce70c172010-01-07 17:15:30 +00001463 q->red=ClampToQuantum(stretch_map[ScaleQuantumToMap(q->red)].red);
cristy3ed852e2009-09-05 21:47:34 +00001464 }
1465 if ((channel & GreenChannel) != 0)
1466 {
1467 if (black.green != white.green)
cristyce70c172010-01-07 17:15:30 +00001468 q->green=ClampToQuantum(stretch_map[ScaleQuantumToMap(
cristy3ed852e2009-09-05 21:47:34 +00001469 q->green)].green);
1470 }
1471 if ((channel & BlueChannel) != 0)
1472 {
1473 if (black.blue != white.blue)
cristyce70c172010-01-07 17:15:30 +00001474 q->blue=ClampToQuantum(stretch_map[ScaleQuantumToMap(
cristy3ed852e2009-09-05 21:47:34 +00001475 q->blue)].blue);
1476 }
1477 if ((channel & OpacityChannel) != 0)
1478 {
1479 if (black.opacity != white.opacity)
cristyce70c172010-01-07 17:15:30 +00001480 q->opacity=ClampToQuantum(stretch_map[ScaleQuantumToMap(
cristy3ed852e2009-09-05 21:47:34 +00001481 q->opacity)].opacity);
1482 }
1483 if (((channel & IndexChannel) != 0) &&
1484 (image->colorspace == CMYKColorspace))
1485 {
1486 if (black.index != white.index)
cristyce70c172010-01-07 17:15:30 +00001487 indexes[x]=(IndexPacket) ClampToQuantum(stretch_map[
cristy3ed852e2009-09-05 21:47:34 +00001488 ScaleQuantumToMap(indexes[x])].index);
1489 }
1490 q++;
1491 }
1492 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
1493 status=MagickFalse;
1494 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1495 {
1496 MagickBooleanType
1497 proceed;
1498
cristyb5d5f722009-11-04 03:03:49 +00001499#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00001500 #pragma omp critical (MagickCore_ContrastStretchImageChannel)
1501#endif
1502 proceed=SetImageProgress(image,ContrastStretchImageTag,progress++,
1503 image->rows);
1504 if (proceed == MagickFalse)
1505 status=MagickFalse;
1506 }
1507 }
1508 image_view=DestroyCacheView(image_view);
1509 stretch_map=(MagickPixelPacket *) RelinquishMagickMemory(stretch_map);
1510 return(status);
1511}
1512
1513/*
1514%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1515% %
1516% %
1517% %
1518% E n h a n c e I m a g e %
1519% %
1520% %
1521% %
1522%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1523%
1524% EnhanceImage() applies a digital filter that improves the quality of a
1525% noisy image.
1526%
1527% The format of the EnhanceImage method is:
1528%
1529% Image *EnhanceImage(const Image *image,ExceptionInfo *exception)
1530%
1531% A description of each parameter follows:
1532%
1533% o image: the image.
1534%
1535% o exception: return any errors or warnings in this structure.
1536%
1537*/
1538MagickExport Image *EnhanceImage(const Image *image,ExceptionInfo *exception)
1539{
1540#define Enhance(weight) \
1541 mean=((MagickRealType) r->red+pixel.red)/2; \
1542 distance=(MagickRealType) r->red-(MagickRealType) pixel.red; \
1543 distance_squared=QuantumScale*(2.0*((MagickRealType) QuantumRange+1.0)+ \
1544 mean)*distance*distance; \
1545 mean=((MagickRealType) r->green+pixel.green)/2; \
1546 distance=(MagickRealType) r->green-(MagickRealType) pixel.green; \
1547 distance_squared+=4.0*distance*distance; \
1548 mean=((MagickRealType) r->blue+pixel.blue)/2; \
1549 distance=(MagickRealType) r->blue-(MagickRealType) pixel.blue; \
1550 distance_squared+=QuantumScale*(3.0*((MagickRealType) \
1551 QuantumRange+1.0)-1.0-mean)*distance*distance; \
1552 mean=((MagickRealType) r->opacity+pixel.opacity)/2; \
1553 distance=(MagickRealType) r->opacity-(MagickRealType) pixel.opacity; \
1554 distance_squared+=QuantumScale*(3.0*((MagickRealType) \
1555 QuantumRange+1.0)-1.0-mean)*distance*distance; \
1556 if (distance_squared < ((MagickRealType) QuantumRange*(MagickRealType) \
1557 QuantumRange/25.0f)) \
1558 { \
1559 aggregate.red+=(weight)*r->red; \
1560 aggregate.green+=(weight)*r->green; \
1561 aggregate.blue+=(weight)*r->blue; \
1562 aggregate.opacity+=(weight)*r->opacity; \
1563 total_weight+=(weight); \
1564 } \
1565 r++;
1566#define EnhanceImageTag "Enhance/Image"
1567
cristyc4c8d132010-01-07 01:58:38 +00001568 CacheView
1569 *enhance_view,
1570 *image_view;
1571
cristy3ed852e2009-09-05 21:47:34 +00001572 Image
1573 *enhance_image;
1574
cristy3ed852e2009-09-05 21:47:34 +00001575 MagickBooleanType
1576 status;
1577
cristybb503372010-05-27 20:51:26 +00001578 MagickOffsetType
1579 progress;
1580
cristy3ed852e2009-09-05 21:47:34 +00001581 MagickPixelPacket
1582 zero;
1583
cristybb503372010-05-27 20:51:26 +00001584 ssize_t
1585 y;
1586
cristy3ed852e2009-09-05 21:47:34 +00001587 /*
1588 Initialize enhanced image attributes.
1589 */
1590 assert(image != (const Image *) NULL);
1591 assert(image->signature == MagickSignature);
1592 if (image->debug != MagickFalse)
1593 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1594 assert(exception != (ExceptionInfo *) NULL);
1595 assert(exception->signature == MagickSignature);
1596 if ((image->columns < 5) || (image->rows < 5))
1597 return((Image *) NULL);
1598 enhance_image=CloneImage(image,image->columns,image->rows,MagickTrue,
1599 exception);
1600 if (enhance_image == (Image *) NULL)
1601 return((Image *) NULL);
1602 if (SetImageStorageClass(enhance_image,DirectClass) == MagickFalse)
1603 {
1604 InheritException(exception,&enhance_image->exception);
1605 enhance_image=DestroyImage(enhance_image);
1606 return((Image *) NULL);
1607 }
1608 /*
1609 Enhance image.
1610 */
1611 status=MagickTrue;
1612 progress=0;
1613 (void) ResetMagickMemory(&zero,0,sizeof(zero));
1614 image_view=AcquireCacheView(image);
1615 enhance_view=AcquireCacheView(enhance_image);
cristyb5d5f722009-11-04 03:03:49 +00001616#if defined(MAGICKCORE_OPENMP_SUPPORT)
1617 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001618#endif
cristybb503372010-05-27 20:51:26 +00001619 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001620 {
1621 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001622 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00001623
cristy3ed852e2009-09-05 21:47:34 +00001624 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001625 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001626
cristy8d4629b2010-08-30 17:59:46 +00001627 register ssize_t
1628 x;
1629
cristy3ed852e2009-09-05 21:47:34 +00001630 /*
1631 Read another scan line.
1632 */
1633 if (status == MagickFalse)
1634 continue;
1635 p=GetCacheViewVirtualPixels(image_view,-2,y-2,image->columns+4,5,exception);
1636 q=QueueCacheViewAuthenticPixels(enhance_view,0,y,enhance_image->columns,1,
1637 exception);
1638 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
1639 {
1640 status=MagickFalse;
1641 continue;
1642 }
cristybb503372010-05-27 20:51:26 +00001643 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001644 {
1645 MagickPixelPacket
1646 aggregate;
1647
1648 MagickRealType
1649 distance,
1650 distance_squared,
1651 mean,
1652 total_weight;
1653
1654 PixelPacket
1655 pixel;
1656
1657 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001658 *restrict r;
cristy3ed852e2009-09-05 21:47:34 +00001659
1660 /*
1661 Compute weighted average of target pixel color components.
1662 */
1663 aggregate=zero;
1664 total_weight=0.0;
1665 r=p+2*(image->columns+4)+2;
1666 pixel=(*r);
1667 r=p;
1668 Enhance(5.0); Enhance(8.0); Enhance(10.0); Enhance(8.0); Enhance(5.0);
1669 r=p+(image->columns+4);
1670 Enhance(8.0); Enhance(20.0); Enhance(40.0); Enhance(20.0); Enhance(8.0);
1671 r=p+2*(image->columns+4);
1672 Enhance(10.0); Enhance(40.0); Enhance(80.0); Enhance(40.0); Enhance(10.0);
1673 r=p+3*(image->columns+4);
1674 Enhance(8.0); Enhance(20.0); Enhance(40.0); Enhance(20.0); Enhance(8.0);
1675 r=p+4*(image->columns+4);
1676 Enhance(5.0); Enhance(8.0); Enhance(10.0); Enhance(8.0); Enhance(5.0);
1677 q->red=(Quantum) ((aggregate.red+(total_weight/2)-1)/total_weight);
1678 q->green=(Quantum) ((aggregate.green+(total_weight/2)-1)/total_weight);
1679 q->blue=(Quantum) ((aggregate.blue+(total_weight/2)-1)/total_weight);
1680 q->opacity=(Quantum) ((aggregate.opacity+(total_weight/2)-1)/
1681 total_weight);
1682 p++;
1683 q++;
1684 }
1685 if (SyncCacheViewAuthenticPixels(enhance_view,exception) == MagickFalse)
1686 status=MagickFalse;
1687 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1688 {
1689 MagickBooleanType
1690 proceed;
1691
cristyb5d5f722009-11-04 03:03:49 +00001692#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00001693 #pragma omp critical (MagickCore_EnhanceImage)
1694#endif
1695 proceed=SetImageProgress(image,EnhanceImageTag,progress++,image->rows);
1696 if (proceed == MagickFalse)
1697 status=MagickFalse;
1698 }
1699 }
1700 enhance_view=DestroyCacheView(enhance_view);
1701 image_view=DestroyCacheView(image_view);
1702 return(enhance_image);
1703}
1704
1705/*
1706%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1707% %
1708% %
1709% %
1710% E q u a l i z e I m a g e %
1711% %
1712% %
1713% %
1714%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1715%
1716% EqualizeImage() applies a histogram equalization to the image.
1717%
1718% The format of the EqualizeImage method is:
1719%
1720% MagickBooleanType EqualizeImage(Image *image)
1721% MagickBooleanType EqualizeImageChannel(Image *image,
1722% const ChannelType channel)
1723%
1724% A description of each parameter follows:
1725%
1726% o image: the image.
1727%
1728% o channel: the channel.
1729%
1730*/
1731
1732MagickExport MagickBooleanType EqualizeImage(Image *image)
1733{
1734 return(EqualizeImageChannel(image,DefaultChannels));
1735}
1736
1737MagickExport MagickBooleanType EqualizeImageChannel(Image *image,
1738 const ChannelType channel)
1739{
1740#define EqualizeImageTag "Equalize/Image"
1741
cristyc4c8d132010-01-07 01:58:38 +00001742 CacheView
1743 *image_view;
1744
cristy3ed852e2009-09-05 21:47:34 +00001745 ExceptionInfo
1746 *exception;
1747
cristy3ed852e2009-09-05 21:47:34 +00001748 MagickBooleanType
1749 status;
1750
cristybb503372010-05-27 20:51:26 +00001751 MagickOffsetType
1752 progress;
1753
cristy3ed852e2009-09-05 21:47:34 +00001754 MagickPixelPacket
1755 black,
1756 *equalize_map,
1757 *histogram,
1758 intensity,
1759 *map,
1760 white;
1761
cristybb503372010-05-27 20:51:26 +00001762 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001763 i;
1764
cristybb503372010-05-27 20:51:26 +00001765 ssize_t
1766 y;
1767
cristy3ed852e2009-09-05 21:47:34 +00001768 /*
1769 Allocate and initialize histogram arrays.
1770 */
1771 assert(image != (Image *) NULL);
1772 assert(image->signature == MagickSignature);
1773 if (image->debug != MagickFalse)
1774 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1775 equalize_map=(MagickPixelPacket *) AcquireQuantumMemory(MaxMap+1UL,
1776 sizeof(*equalize_map));
1777 histogram=(MagickPixelPacket *) AcquireQuantumMemory(MaxMap+1UL,
1778 sizeof(*histogram));
1779 map=(MagickPixelPacket *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*map));
1780 if ((equalize_map == (MagickPixelPacket *) NULL) ||
1781 (histogram == (MagickPixelPacket *) NULL) ||
1782 (map == (MagickPixelPacket *) NULL))
1783 {
1784 if (map != (MagickPixelPacket *) NULL)
1785 map=(MagickPixelPacket *) RelinquishMagickMemory(map);
1786 if (histogram != (MagickPixelPacket *) NULL)
1787 histogram=(MagickPixelPacket *) RelinquishMagickMemory(histogram);
1788 if (equalize_map != (MagickPixelPacket *) NULL)
1789 equalize_map=(MagickPixelPacket *) RelinquishMagickMemory(equalize_map);
1790 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
1791 image->filename);
1792 }
1793 /*
1794 Form histogram.
1795 */
1796 (void) ResetMagickMemory(histogram,0,(MaxMap+1)*sizeof(*histogram));
1797 exception=(&image->exception);
cristybb503372010-05-27 20:51:26 +00001798 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001799 {
1800 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00001801 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00001802
1803 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001804 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00001805
cristybb503372010-05-27 20:51:26 +00001806 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001807 x;
1808
1809 p=GetVirtualPixels(image,0,y,image->columns,1,exception);
1810 if (p == (const PixelPacket *) NULL)
1811 break;
1812 indexes=GetVirtualIndexQueue(image);
cristybb503372010-05-27 20:51:26 +00001813 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001814 {
1815 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001816 histogram[ScaleQuantumToMap(GetRedPixelComponent(p))].red++;
cristy3ed852e2009-09-05 21:47:34 +00001817 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001818 histogram[ScaleQuantumToMap(GetGreenPixelComponent(p))].green++;
cristy3ed852e2009-09-05 21:47:34 +00001819 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001820 histogram[ScaleQuantumToMap(GetBluePixelComponent(p))].blue++;
cristy3ed852e2009-09-05 21:47:34 +00001821 if ((channel & OpacityChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001822 histogram[ScaleQuantumToMap(GetOpacityPixelComponent(p))].opacity++;
cristy3ed852e2009-09-05 21:47:34 +00001823 if (((channel & IndexChannel) != 0) &&
1824 (image->colorspace == CMYKColorspace))
1825 histogram[ScaleQuantumToMap(indexes[x])].index++;
1826 p++;
1827 }
1828 }
1829 /*
1830 Integrate the histogram to get the equalization map.
1831 */
1832 (void) ResetMagickMemory(&intensity,0,sizeof(intensity));
cristybb503372010-05-27 20:51:26 +00001833 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +00001834 {
1835 if ((channel & RedChannel) != 0)
1836 intensity.red+=histogram[i].red;
1837 if ((channel & GreenChannel) != 0)
1838 intensity.green+=histogram[i].green;
1839 if ((channel & BlueChannel) != 0)
1840 intensity.blue+=histogram[i].blue;
1841 if ((channel & OpacityChannel) != 0)
1842 intensity.opacity+=histogram[i].opacity;
1843 if (((channel & IndexChannel) != 0) &&
1844 (image->colorspace == CMYKColorspace))
1845 intensity.index+=histogram[i].index;
1846 map[i]=intensity;
1847 }
1848 black=map[0];
1849 white=map[(int) MaxMap];
1850 (void) ResetMagickMemory(equalize_map,0,(MaxMap+1)*sizeof(*equalize_map));
cristyb5d5f722009-11-04 03:03:49 +00001851#if defined(MAGICKCORE_OPENMP_SUPPORT)
1852 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001853#endif
cristybb503372010-05-27 20:51:26 +00001854 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +00001855 {
1856 if (((channel & RedChannel) != 0) && (white.red != black.red))
1857 equalize_map[i].red=(MagickRealType) ScaleMapToQuantum((MagickRealType)
1858 ((MaxMap*(map[i].red-black.red))/(white.red-black.red)));
1859 if (((channel & GreenChannel) != 0) && (white.green != black.green))
1860 equalize_map[i].green=(MagickRealType) ScaleMapToQuantum((MagickRealType)
1861 ((MaxMap*(map[i].green-black.green))/(white.green-black.green)));
1862 if (((channel & BlueChannel) != 0) && (white.blue != black.blue))
1863 equalize_map[i].blue=(MagickRealType) ScaleMapToQuantum((MagickRealType)
1864 ((MaxMap*(map[i].blue-black.blue))/(white.blue-black.blue)));
1865 if (((channel & OpacityChannel) != 0) && (white.opacity != black.opacity))
1866 equalize_map[i].opacity=(MagickRealType) ScaleMapToQuantum(
1867 (MagickRealType) ((MaxMap*(map[i].opacity-black.opacity))/
1868 (white.opacity-black.opacity)));
1869 if ((((channel & IndexChannel) != 0) &&
1870 (image->colorspace == CMYKColorspace)) &&
1871 (white.index != black.index))
1872 equalize_map[i].index=(MagickRealType) ScaleMapToQuantum((MagickRealType)
1873 ((MaxMap*(map[i].index-black.index))/(white.index-black.index)));
1874 }
1875 histogram=(MagickPixelPacket *) RelinquishMagickMemory(histogram);
1876 map=(MagickPixelPacket *) RelinquishMagickMemory(map);
1877 if (image->storage_class == PseudoClass)
1878 {
1879 /*
1880 Equalize colormap.
1881 */
cristyb5d5f722009-11-04 03:03:49 +00001882#if defined(MAGICKCORE_OPENMP_SUPPORT)
1883 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001884#endif
cristybb503372010-05-27 20:51:26 +00001885 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00001886 {
1887 if (((channel & RedChannel) != 0) && (white.red != black.red))
cristyce70c172010-01-07 17:15:30 +00001888 image->colormap[i].red=ClampToQuantum(equalize_map[
cristy3ed852e2009-09-05 21:47:34 +00001889 ScaleQuantumToMap(image->colormap[i].red)].red);
1890 if (((channel & GreenChannel) != 0) && (white.green != black.green))
cristyce70c172010-01-07 17:15:30 +00001891 image->colormap[i].green=ClampToQuantum(equalize_map[
cristy3ed852e2009-09-05 21:47:34 +00001892 ScaleQuantumToMap(image->colormap[i].green)].green);
1893 if (((channel & BlueChannel) != 0) && (white.blue != black.blue))
cristyce70c172010-01-07 17:15:30 +00001894 image->colormap[i].blue=ClampToQuantum(equalize_map[
cristy3ed852e2009-09-05 21:47:34 +00001895 ScaleQuantumToMap(image->colormap[i].blue)].blue);
1896 if (((channel & OpacityChannel) != 0) &&
1897 (white.opacity != black.opacity))
cristyce70c172010-01-07 17:15:30 +00001898 image->colormap[i].opacity=ClampToQuantum(equalize_map[
cristy3ed852e2009-09-05 21:47:34 +00001899 ScaleQuantumToMap(image->colormap[i].opacity)].opacity);
1900 }
1901 }
1902 /*
1903 Equalize image.
1904 */
1905 status=MagickTrue;
1906 progress=0;
1907 exception=(&image->exception);
1908 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00001909#if defined(MAGICKCORE_OPENMP_SUPPORT)
1910 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001911#endif
cristybb503372010-05-27 20:51:26 +00001912 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001913 {
1914 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00001915 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00001916
cristy3ed852e2009-09-05 21:47:34 +00001917 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001918 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001919
cristy8d4629b2010-08-30 17:59:46 +00001920 register ssize_t
1921 x;
1922
cristy3ed852e2009-09-05 21:47:34 +00001923 if (status == MagickFalse)
1924 continue;
1925 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
1926 if (q == (PixelPacket *) NULL)
1927 {
1928 status=MagickFalse;
1929 continue;
1930 }
1931 indexes=GetCacheViewAuthenticIndexQueue(image_view);
cristybb503372010-05-27 20:51:26 +00001932 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001933 {
1934 if (((channel & RedChannel) != 0) && (white.red != black.red))
cristyce70c172010-01-07 17:15:30 +00001935 q->red=ClampToQuantum(equalize_map[ScaleQuantumToMap(q->red)].red);
cristy3ed852e2009-09-05 21:47:34 +00001936 if (((channel & GreenChannel) != 0) && (white.green != black.green))
cristyce70c172010-01-07 17:15:30 +00001937 q->green=ClampToQuantum(equalize_map[ScaleQuantumToMap(
cristy3ed852e2009-09-05 21:47:34 +00001938 q->green)].green);
1939 if (((channel & BlueChannel) != 0) && (white.blue != black.blue))
cristyce70c172010-01-07 17:15:30 +00001940 q->blue=ClampToQuantum(equalize_map[ScaleQuantumToMap(q->blue)].blue);
cristy3ed852e2009-09-05 21:47:34 +00001941 if (((channel & OpacityChannel) != 0) && (white.opacity != black.opacity))
cristyce70c172010-01-07 17:15:30 +00001942 q->opacity=ClampToQuantum(equalize_map[ScaleQuantumToMap(
cristy3ed852e2009-09-05 21:47:34 +00001943 q->opacity)].opacity);
1944 if ((((channel & IndexChannel) != 0) &&
1945 (image->colorspace == CMYKColorspace)) &&
1946 (white.index != black.index))
cristyce70c172010-01-07 17:15:30 +00001947 indexes[x]=ClampToQuantum(equalize_map[ScaleQuantumToMap(
cristy3ed852e2009-09-05 21:47:34 +00001948 indexes[x])].index);
1949 q++;
1950 }
1951 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
1952 status=MagickFalse;
1953 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1954 {
1955 MagickBooleanType
1956 proceed;
1957
cristyb5d5f722009-11-04 03:03:49 +00001958#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00001959 #pragma omp critical (MagickCore_EqualizeImageChannel)
1960#endif
1961 proceed=SetImageProgress(image,EqualizeImageTag,progress++,image->rows);
1962 if (proceed == MagickFalse)
1963 status=MagickFalse;
1964 }
1965 }
1966 image_view=DestroyCacheView(image_view);
1967 equalize_map=(MagickPixelPacket *) RelinquishMagickMemory(equalize_map);
1968 return(status);
1969}
1970
1971/*
1972%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1973% %
1974% %
1975% %
1976% G a m m a I m a g e %
1977% %
1978% %
1979% %
1980%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1981%
1982% GammaImage() gamma-corrects a particular image channel. The same
1983% image viewed on different devices will have perceptual differences in the
1984% way the image's intensities are represented on the screen. Specify
1985% individual gamma levels for the red, green, and blue channels, or adjust
1986% all three with the gamma parameter. Values typically range from 0.8 to 2.3.
1987%
1988% You can also reduce the influence of a particular channel with a gamma
1989% value of 0.
1990%
1991% The format of the GammaImage method is:
1992%
1993% MagickBooleanType GammaImage(Image *image,const double gamma)
1994% MagickBooleanType GammaImageChannel(Image *image,
1995% const ChannelType channel,const double gamma)
1996%
1997% A description of each parameter follows:
1998%
1999% o image: the image.
2000%
2001% o channel: the channel.
2002%
2003% o gamma: the image gamma.
2004%
2005*/
2006MagickExport MagickBooleanType GammaImage(Image *image,const char *level)
2007{
2008 GeometryInfo
2009 geometry_info;
2010
2011 MagickPixelPacket
2012 gamma;
2013
2014 MagickStatusType
2015 flags,
2016 status;
2017
2018 assert(image != (Image *) NULL);
2019 assert(image->signature == MagickSignature);
2020 if (image->debug != MagickFalse)
2021 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2022 if (level == (char *) NULL)
2023 return(MagickFalse);
2024 flags=ParseGeometry(level,&geometry_info);
2025 gamma.red=geometry_info.rho;
2026 gamma.green=geometry_info.sigma;
2027 if ((flags & SigmaValue) == 0)
2028 gamma.green=gamma.red;
2029 gamma.blue=geometry_info.xi;
2030 if ((flags & XiValue) == 0)
2031 gamma.blue=gamma.red;
2032 if ((gamma.red == 1.0) && (gamma.green == 1.0) && (gamma.blue == 1.0))
2033 return(MagickTrue);
2034 if ((gamma.red == gamma.green) && (gamma.green == gamma.blue))
2035 status=GammaImageChannel(image,(const ChannelType) (RedChannel |
2036 GreenChannel | BlueChannel),(double) gamma.red);
2037 else
2038 {
2039 status=GammaImageChannel(image,RedChannel,(double) gamma.red);
2040 status|=GammaImageChannel(image,GreenChannel,(double) gamma.green);
2041 status|=GammaImageChannel(image,BlueChannel,(double) gamma.blue);
2042 }
2043 return(status != 0 ? MagickTrue : MagickFalse);
2044}
2045
2046MagickExport MagickBooleanType GammaImageChannel(Image *image,
2047 const ChannelType channel,const double gamma)
2048{
2049#define GammaCorrectImageTag "GammaCorrect/Image"
2050
cristyc4c8d132010-01-07 01:58:38 +00002051 CacheView
2052 *image_view;
2053
cristy3ed852e2009-09-05 21:47:34 +00002054 ExceptionInfo
2055 *exception;
2056
cristy3ed852e2009-09-05 21:47:34 +00002057 MagickBooleanType
2058 status;
2059
cristybb503372010-05-27 20:51:26 +00002060 MagickOffsetType
2061 progress;
2062
cristy3ed852e2009-09-05 21:47:34 +00002063 Quantum
2064 *gamma_map;
2065
cristybb503372010-05-27 20:51:26 +00002066 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002067 i;
2068
cristybb503372010-05-27 20:51:26 +00002069 ssize_t
2070 y;
2071
cristy3ed852e2009-09-05 21:47:34 +00002072 /*
2073 Allocate and initialize gamma maps.
2074 */
2075 assert(image != (Image *) NULL);
2076 assert(image->signature == MagickSignature);
2077 if (image->debug != MagickFalse)
2078 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2079 if (gamma == 1.0)
2080 return(MagickTrue);
2081 gamma_map=(Quantum *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*gamma_map));
2082 if (gamma_map == (Quantum *) NULL)
2083 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
2084 image->filename);
2085 (void) ResetMagickMemory(gamma_map,0,(MaxMap+1)*sizeof(*gamma_map));
2086 if (gamma != 0.0)
cristyb5d5f722009-11-04 03:03:49 +00002087#if defined(MAGICKCORE_OPENMP_SUPPORT)
2088 #pragma omp parallel for schedule(dynamic,4)
cristy3ed852e2009-09-05 21:47:34 +00002089#endif
cristybb503372010-05-27 20:51:26 +00002090 for (i=0; i <= (ssize_t) MaxMap; i++)
cristyce70c172010-01-07 17:15:30 +00002091 gamma_map[i]=ClampToQuantum((MagickRealType) ScaleMapToQuantum((
cristy3ed852e2009-09-05 21:47:34 +00002092 MagickRealType) (MaxMap*pow((double) i/MaxMap,1.0/gamma))));
2093 if (image->storage_class == PseudoClass)
2094 {
2095 /*
2096 Gamma-correct colormap.
2097 */
cristyb5d5f722009-11-04 03:03:49 +00002098#if defined(MAGICKCORE_OPENMP_SUPPORT)
2099 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002100#endif
cristybb503372010-05-27 20:51:26 +00002101 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00002102 {
2103 if ((channel & RedChannel) != 0)
2104 image->colormap[i].red=gamma_map[
2105 ScaleQuantumToMap(image->colormap[i].red)];
2106 if ((channel & GreenChannel) != 0)
2107 image->colormap[i].green=gamma_map[
2108 ScaleQuantumToMap(image->colormap[i].green)];
2109 if ((channel & BlueChannel) != 0)
2110 image->colormap[i].blue=gamma_map[
2111 ScaleQuantumToMap(image->colormap[i].blue)];
2112 if ((channel & OpacityChannel) != 0)
2113 {
2114 if (image->matte == MagickFalse)
2115 image->colormap[i].opacity=gamma_map[
2116 ScaleQuantumToMap(image->colormap[i].opacity)];
2117 else
2118 image->colormap[i].opacity=(Quantum) QuantumRange-
2119 gamma_map[ScaleQuantumToMap((Quantum) (QuantumRange-
2120 image->colormap[i].opacity))];
2121 }
2122 }
2123 }
2124 /*
2125 Gamma-correct image.
2126 */
2127 status=MagickTrue;
2128 progress=0;
2129 exception=(&image->exception);
2130 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00002131#if defined(MAGICKCORE_OPENMP_SUPPORT)
2132 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002133#endif
cristybb503372010-05-27 20:51:26 +00002134 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002135 {
2136 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00002137 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00002138
cristy3ed852e2009-09-05 21:47:34 +00002139 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00002140 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002141
cristy8d4629b2010-08-30 17:59:46 +00002142 register ssize_t
2143 x;
2144
cristy3ed852e2009-09-05 21:47:34 +00002145 if (status == MagickFalse)
2146 continue;
2147 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2148 if (q == (PixelPacket *) NULL)
2149 {
2150 status=MagickFalse;
2151 continue;
2152 }
2153 indexes=GetCacheViewAuthenticIndexQueue(image_view);
cristybb503372010-05-27 20:51:26 +00002154 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002155 {
cristy6cbd7f52009-10-17 16:06:51 +00002156 if (channel == DefaultChannels)
cristy3ed852e2009-09-05 21:47:34 +00002157 {
cristy6cbd7f52009-10-17 16:06:51 +00002158 q->red=gamma_map[ScaleQuantumToMap(q->red)];
2159 q->green=gamma_map[ScaleQuantumToMap(q->green)];
2160 q->blue=gamma_map[ScaleQuantumToMap(q->blue)];
2161 }
2162 else
2163 {
2164 if ((channel & RedChannel) != 0)
2165 q->red=gamma_map[ScaleQuantumToMap(q->red)];
2166 if ((channel & GreenChannel) != 0)
2167 q->green=gamma_map[ScaleQuantumToMap(q->green)];
2168 if ((channel & BlueChannel) != 0)
2169 q->blue=gamma_map[ScaleQuantumToMap(q->blue)];
2170 if ((channel & OpacityChannel) != 0)
2171 {
2172 if (image->matte == MagickFalse)
2173 q->opacity=gamma_map[ScaleQuantumToMap(q->opacity)];
2174 else
2175 q->opacity=(Quantum) QuantumRange-gamma_map[
cristy46f08202010-01-10 04:04:21 +00002176 ScaleQuantumToMap((Quantum) GetAlphaPixelComponent(q))];
cristy6cbd7f52009-10-17 16:06:51 +00002177 }
cristy3ed852e2009-09-05 21:47:34 +00002178 }
2179 q++;
2180 }
2181 if (((channel & IndexChannel) != 0) &&
2182 (image->colorspace == CMYKColorspace))
cristybb503372010-05-27 20:51:26 +00002183 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002184 indexes[x]=gamma_map[ScaleQuantumToMap(indexes[x])];
2185 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2186 status=MagickFalse;
2187 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2188 {
2189 MagickBooleanType
2190 proceed;
2191
cristyb5d5f722009-11-04 03:03:49 +00002192#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00002193 #pragma omp critical (MagickCore_GammaImageChannel)
2194#endif
2195 proceed=SetImageProgress(image,GammaCorrectImageTag,progress++,
2196 image->rows);
2197 if (proceed == MagickFalse)
2198 status=MagickFalse;
2199 }
2200 }
2201 image_view=DestroyCacheView(image_view);
2202 gamma_map=(Quantum *) RelinquishMagickMemory(gamma_map);
2203 if (image->gamma != 0.0)
2204 image->gamma*=gamma;
2205 return(status);
2206}
2207
2208/*
2209%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2210% %
2211% %
2212% %
2213% H a l d C l u t I m a g e %
2214% %
2215% %
2216% %
2217%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2218%
2219% HaldClutImage() applies a Hald color lookup table to the image. A Hald
2220% color lookup table is a 3-dimensional color cube mapped to 2 dimensions.
2221% Create it with the HALD coder. You can apply any color transformation to
2222% the Hald image and then use this method to apply the transform to the
2223% image.
2224%
2225% The format of the HaldClutImage method is:
2226%
2227% MagickBooleanType HaldClutImage(Image *image,Image *hald_image)
2228% MagickBooleanType HaldClutImageChannel(Image *image,
2229% const ChannelType channel,Image *hald_image)
2230%
2231% A description of each parameter follows:
2232%
2233% o image: the image, which is replaced by indexed CLUT values
2234%
2235% o hald_image: the color lookup table image for replacement color values.
2236%
2237% o channel: the channel.
2238%
2239*/
2240
2241static inline size_t MagickMin(const size_t x,const size_t y)
2242{
2243 if (x < y)
2244 return(x);
2245 return(y);
2246}
2247
2248MagickExport MagickBooleanType HaldClutImage(Image *image,
2249 const Image *hald_image)
2250{
2251 return(HaldClutImageChannel(image,DefaultChannels,hald_image));
2252}
2253
2254MagickExport MagickBooleanType HaldClutImageChannel(Image *image,
2255 const ChannelType channel,const Image *hald_image)
2256{
2257#define HaldClutImageTag "Clut/Image"
2258
2259 typedef struct _HaldInfo
2260 {
2261 MagickRealType
2262 x,
2263 y,
2264 z;
2265 } HaldInfo;
2266
cristyfa112112010-01-04 17:48:07 +00002267 CacheView
2268 *image_view;
2269
cristy3ed852e2009-09-05 21:47:34 +00002270 double
2271 width;
2272
2273 ExceptionInfo
2274 *exception;
2275
cristy3ed852e2009-09-05 21:47:34 +00002276 MagickBooleanType
2277 status;
2278
cristybb503372010-05-27 20:51:26 +00002279 MagickOffsetType
2280 progress;
2281
cristy3ed852e2009-09-05 21:47:34 +00002282 MagickPixelPacket
2283 zero;
2284
2285 ResampleFilter
cristyfa112112010-01-04 17:48:07 +00002286 **restrict resample_filter;
cristy3ed852e2009-09-05 21:47:34 +00002287
2288 size_t
2289 cube_size,
2290 length,
2291 level;
2292
cristybb503372010-05-27 20:51:26 +00002293 ssize_t
2294 y;
2295
cristy3ed852e2009-09-05 21:47:34 +00002296 assert(image != (Image *) NULL);
2297 assert(image->signature == MagickSignature);
2298 if (image->debug != MagickFalse)
2299 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2300 assert(hald_image != (Image *) NULL);
2301 assert(hald_image->signature == MagickSignature);
2302 if (SetImageStorageClass(image,DirectClass) == MagickFalse)
2303 return(MagickFalse);
2304 if (image->matte == MagickFalse)
2305 (void) SetImageAlphaChannel(image,OpaqueAlphaChannel);
2306 /*
2307 Hald clut image.
2308 */
2309 status=MagickTrue;
2310 progress=0;
2311 length=MagickMin(hald_image->columns,hald_image->rows);
2312 for (level=2; (level*level*level) < length; level++) ;
2313 level*=level;
2314 cube_size=level*level;
2315 width=(double) hald_image->columns;
2316 GetMagickPixelPacket(hald_image,&zero);
2317 exception=(&image->exception);
cristyb2a11ae2010-02-22 00:53:36 +00002318 resample_filter=AcquireResampleFilterThreadSet(hald_image,
2319 UndefinedVirtualPixelMethod,MagickTrue,exception);
cristy3ed852e2009-09-05 21:47:34 +00002320 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00002321#if defined(MAGICKCORE_OPENMP_SUPPORT)
2322 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002323#endif
cristybb503372010-05-27 20:51:26 +00002324 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002325 {
cristy5c9e6f22010-09-17 17:31:01 +00002326 const int
2327 id = GetOpenMPThreadId();
2328
cristy3ed852e2009-09-05 21:47:34 +00002329 double
2330 offset;
2331
2332 HaldInfo
2333 point;
2334
2335 MagickPixelPacket
2336 pixel,
2337 pixel1,
2338 pixel2,
2339 pixel3,
2340 pixel4;
2341
2342 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00002343 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00002344
cristy3ed852e2009-09-05 21:47:34 +00002345 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00002346 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002347
cristy8d4629b2010-08-30 17:59:46 +00002348 register ssize_t
2349 x;
2350
cristy3ed852e2009-09-05 21:47:34 +00002351 if (status == MagickFalse)
2352 continue;
2353 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2354 if (q == (PixelPacket *) NULL)
2355 {
2356 status=MagickFalse;
2357 continue;
2358 }
2359 indexes=GetCacheViewAuthenticIndexQueue(image_view);
2360 pixel=zero;
2361 pixel1=zero;
2362 pixel2=zero;
2363 pixel3=zero;
2364 pixel4=zero;
cristybb503372010-05-27 20:51:26 +00002365 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002366 {
2367 point.x=QuantumScale*(level-1.0)*q->red;
2368 point.y=QuantumScale*(level-1.0)*q->green;
2369 point.z=QuantumScale*(level-1.0)*q->blue;
2370 offset=point.x+level*floor(point.y)+cube_size*floor(point.z);
2371 point.x-=floor(point.x);
2372 point.y-=floor(point.y);
2373 point.z-=floor(point.z);
2374 (void) ResamplePixelColor(resample_filter[id],fmod(offset,width),
2375 floor(offset/width),&pixel1);
2376 (void) ResamplePixelColor(resample_filter[id],fmod(offset+level,width),
2377 floor((offset+level)/width),&pixel2);
2378 MagickPixelCompositeAreaBlend(&pixel1,pixel1.opacity,&pixel2,
2379 pixel2.opacity,point.y,&pixel3);
2380 offset+=cube_size;
2381 (void) ResamplePixelColor(resample_filter[id],fmod(offset,width),
2382 floor(offset/width),&pixel1);
2383 (void) ResamplePixelColor(resample_filter[id],fmod(offset+level,width),
2384 floor((offset+level)/width),&pixel2);
2385 MagickPixelCompositeAreaBlend(&pixel1,pixel1.opacity,&pixel2,
2386 pixel2.opacity,point.y,&pixel4);
2387 MagickPixelCompositeAreaBlend(&pixel3,pixel3.opacity,&pixel4,
2388 pixel4.opacity,point.z,&pixel);
2389 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00002390 SetRedPixelComponent(q,ClampRedPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00002391 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00002392 SetGreenPixelComponent(q,ClampGreenPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00002393 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00002394 SetBluePixelComponent(q,ClampBluePixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00002395 if (((channel & OpacityChannel) != 0) && (image->matte != MagickFalse))
cristyce70c172010-01-07 17:15:30 +00002396 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00002397 if (((channel & IndexChannel) != 0) &&
2398 (image->colorspace == CMYKColorspace))
cristyce70c172010-01-07 17:15:30 +00002399 indexes[x]=ClampToQuantum(pixel.index);
cristy3ed852e2009-09-05 21:47:34 +00002400 q++;
2401 }
2402 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2403 status=MagickFalse;
2404 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2405 {
2406 MagickBooleanType
2407 proceed;
2408
cristyb5d5f722009-11-04 03:03:49 +00002409#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00002410 #pragma omp critical (MagickCore_HaldClutImageChannel)
2411#endif
2412 proceed=SetImageProgress(image,HaldClutImageTag,progress++,image->rows);
2413 if (proceed == MagickFalse)
2414 status=MagickFalse;
2415 }
2416 }
2417 image_view=DestroyCacheView(image_view);
2418 resample_filter=DestroyResampleFilterThreadSet(resample_filter);
2419 return(status);
2420}
2421
2422/*
2423%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2424% %
2425% %
2426% %
2427% L e v e l I m a g e %
2428% %
2429% %
2430% %
2431%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2432%
2433% LevelImage() adjusts the levels of a particular image channel by
2434% scaling the colors falling between specified white and black points to
2435% the full available quantum range.
2436%
2437% The parameters provided represent the black, and white points. The black
2438% point specifies the darkest color in the image. Colors darker than the
2439% black point are set to zero. White point specifies the lightest color in
2440% the image. Colors brighter than the white point are set to the maximum
2441% quantum value.
2442%
2443% If a '!' flag is given, map black and white colors to the given levels
2444% rather than mapping those levels to black and white. See
2445% LevelizeImageChannel() and LevelizeImageChannel(), below.
2446%
2447% Gamma specifies a gamma correction to apply to the image.
2448%
2449% The format of the LevelImage method is:
2450%
2451% MagickBooleanType LevelImage(Image *image,const char *levels)
2452%
2453% A description of each parameter follows:
2454%
2455% o image: the image.
2456%
2457% o levels: Specify the levels where the black and white points have the
2458% range of 0-QuantumRange, and gamma has the range 0-10 (e.g. 10x90%+2).
2459% A '!' flag inverts the re-mapping.
2460%
2461*/
2462
2463MagickExport MagickBooleanType LevelImage(Image *image,const char *levels)
2464{
2465 double
2466 black_point,
2467 gamma,
2468 white_point;
2469
2470 GeometryInfo
2471 geometry_info;
2472
2473 MagickBooleanType
2474 status;
2475
2476 MagickStatusType
2477 flags;
2478
2479 /*
2480 Parse levels.
2481 */
2482 if (levels == (char *) NULL)
2483 return(MagickFalse);
2484 flags=ParseGeometry(levels,&geometry_info);
2485 black_point=geometry_info.rho;
2486 white_point=(double) QuantumRange;
2487 if ((flags & SigmaValue) != 0)
2488 white_point=geometry_info.sigma;
2489 gamma=1.0;
2490 if ((flags & XiValue) != 0)
2491 gamma=geometry_info.xi;
2492 if ((flags & PercentValue) != 0)
2493 {
2494 black_point*=(double) image->columns*image->rows/100.0;
2495 white_point*=(double) image->columns*image->rows/100.0;
2496 }
2497 if ((flags & SigmaValue) == 0)
2498 white_point=(double) QuantumRange-black_point;
2499 if ((flags & AspectValue ) == 0)
2500 status=LevelImageChannel(image,DefaultChannels,black_point,white_point,
2501 gamma);
2502 else
cristy308b4e62009-09-21 14:40:44 +00002503 status=LevelizeImage(image,black_point,white_point,gamma);
cristy3ed852e2009-09-05 21:47:34 +00002504 return(status);
2505}
2506
2507/*
2508%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2509% %
2510% %
2511% %
cristy308b4e62009-09-21 14:40:44 +00002512% L e v e l i z e I m a g e %
cristy3ed852e2009-09-05 21:47:34 +00002513% %
2514% %
2515% %
2516%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2517%
cristy308b4e62009-09-21 14:40:44 +00002518% LevelizeImage() applies the normal level operation to the image, spreading
2519% out the values between the black and white points over the entire range of
2520% values. Gamma correction is also applied after the values has been mapped.
cristy3ed852e2009-09-05 21:47:34 +00002521%
2522% It is typically used to improve image contrast, or to provide a controlled
2523% linear threshold for the image. If the black and white points are set to
2524% the minimum and maximum values found in the image, the image can be
2525% normalized. or by swapping black and white values, negate the image.
2526%
cristy308b4e62009-09-21 14:40:44 +00002527% The format of the LevelizeImage method is:
cristy3ed852e2009-09-05 21:47:34 +00002528%
cristy308b4e62009-09-21 14:40:44 +00002529% MagickBooleanType LevelizeImage(Image *image,const double black_point,
2530% const double white_point,const double gamma)
2531% MagickBooleanType LevelizeImageChannel(Image *image,
2532% const ChannelType channel,const double black_point,
2533% const double white_point,const double gamma)
cristy3ed852e2009-09-05 21:47:34 +00002534%
2535% A description of each parameter follows:
2536%
2537% o image: the image.
2538%
2539% o channel: the channel.
2540%
2541% o black_point: The level which is to be mapped to zero (black)
2542%
2543% o white_point: The level which is to be mapped to QuantiumRange (white)
2544%
2545% o gamma: adjust gamma by this factor before mapping values.
2546% use 1.0 for purely linear stretching of image color values
2547%
2548*/
2549MagickExport MagickBooleanType LevelImageChannel(Image *image,
2550 const ChannelType channel,const double black_point,const double white_point,
2551 const double gamma)
2552{
2553#define LevelImageTag "Level/Image"
cristybcfb0432010-05-06 01:45:33 +00002554#define LevelQuantum(x) (ClampToQuantum((MagickRealType) QuantumRange* \
cristyc1f508d2010-04-22 01:21:47 +00002555 pow(scale*((double) (x)-black_point),1.0/gamma)))
cristy3ed852e2009-09-05 21:47:34 +00002556
cristyc4c8d132010-01-07 01:58:38 +00002557 CacheView
2558 *image_view;
2559
cristy3ed852e2009-09-05 21:47:34 +00002560 ExceptionInfo
2561 *exception;
2562
cristy3ed852e2009-09-05 21:47:34 +00002563 MagickBooleanType
2564 status;
2565
cristybb503372010-05-27 20:51:26 +00002566 MagickOffsetType
2567 progress;
2568
anthony7fe39fc2010-04-06 03:19:20 +00002569 register double
2570 scale;
2571
cristy8d4629b2010-08-30 17:59:46 +00002572 register ssize_t
2573 i;
2574
cristybb503372010-05-27 20:51:26 +00002575 ssize_t
2576 y;
2577
cristy3ed852e2009-09-05 21:47:34 +00002578 /*
2579 Allocate and initialize levels map.
2580 */
2581 assert(image != (Image *) NULL);
2582 assert(image->signature == MagickSignature);
2583 if (image->debug != MagickFalse)
2584 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
cristy8d4629b2010-08-30 17:59:46 +00002585 scale=(white_point != black_point) ? 1.0/(white_point-black_point) : 1.0;
cristy3ed852e2009-09-05 21:47:34 +00002586 if (image->storage_class == PseudoClass)
cristyb5d5f722009-11-04 03:03:49 +00002587#if defined(MAGICKCORE_OPENMP_SUPPORT)
2588 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002589#endif
cristybb503372010-05-27 20:51:26 +00002590 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00002591 {
2592 /*
2593 Level colormap.
2594 */
2595 if ((channel & RedChannel) != 0)
cristyc1f508d2010-04-22 01:21:47 +00002596 image->colormap[i].red=LevelQuantum(image->colormap[i].red);
cristy3ed852e2009-09-05 21:47:34 +00002597 if ((channel & GreenChannel) != 0)
cristyc1f508d2010-04-22 01:21:47 +00002598 image->colormap[i].green=LevelQuantum(image->colormap[i].green);
cristy3ed852e2009-09-05 21:47:34 +00002599 if ((channel & BlueChannel) != 0)
cristyc1f508d2010-04-22 01:21:47 +00002600 image->colormap[i].blue=LevelQuantum(image->colormap[i].blue);
cristy3ed852e2009-09-05 21:47:34 +00002601 if ((channel & OpacityChannel) != 0)
cristyc1f508d2010-04-22 01:21:47 +00002602 image->colormap[i].opacity=LevelQuantum(image->colormap[i].opacity);
cristy3ed852e2009-09-05 21:47:34 +00002603 }
2604 /*
2605 Level image.
2606 */
2607 status=MagickTrue;
2608 progress=0;
2609 exception=(&image->exception);
2610 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00002611#if defined(MAGICKCORE_OPENMP_SUPPORT)
2612 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002613#endif
cristybb503372010-05-27 20:51:26 +00002614 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002615 {
2616 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00002617 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00002618
cristy3ed852e2009-09-05 21:47:34 +00002619 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00002620 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002621
cristy8d4629b2010-08-30 17:59:46 +00002622 register ssize_t
2623 x;
2624
cristy3ed852e2009-09-05 21:47:34 +00002625 if (status == MagickFalse)
2626 continue;
2627 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2628 if (q == (PixelPacket *) NULL)
2629 {
2630 status=MagickFalse;
2631 continue;
2632 }
2633 indexes=GetCacheViewAuthenticIndexQueue(image_view);
cristybb503372010-05-27 20:51:26 +00002634 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002635 {
2636 if ((channel & RedChannel) != 0)
cristyc1f508d2010-04-22 01:21:47 +00002637 q->red=LevelQuantum(q->red);
cristy3ed852e2009-09-05 21:47:34 +00002638 if ((channel & GreenChannel) != 0)
cristyc1f508d2010-04-22 01:21:47 +00002639 q->green=LevelQuantum(q->green);
cristy3ed852e2009-09-05 21:47:34 +00002640 if ((channel & BlueChannel) != 0)
cristyc1f508d2010-04-22 01:21:47 +00002641 q->blue=LevelQuantum(q->blue);
cristy3ed852e2009-09-05 21:47:34 +00002642 if (((channel & OpacityChannel) != 0) &&
2643 (image->matte == MagickTrue))
cristybb503372010-05-27 20:51:26 +00002644 q->opacity=(Quantum) (QuantumRange-LevelQuantum(QuantumRange-
2645 q->opacity));
cristy3ed852e2009-09-05 21:47:34 +00002646 if (((channel & IndexChannel) != 0) &&
2647 (image->colorspace == CMYKColorspace))
cristyc1f508d2010-04-22 01:21:47 +00002648 indexes[x]=LevelQuantum(indexes[x]);
cristy3ed852e2009-09-05 21:47:34 +00002649 q++;
2650 }
2651 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2652 status=MagickFalse;
2653 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2654 {
2655 MagickBooleanType
2656 proceed;
2657
cristyb5d5f722009-11-04 03:03:49 +00002658#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00002659 #pragma omp critical (MagickCore_LevelImageChannel)
2660#endif
2661 proceed=SetImageProgress(image,LevelImageTag,progress++,image->rows);
2662 if (proceed == MagickFalse)
2663 status=MagickFalse;
2664 }
2665 }
2666 image_view=DestroyCacheView(image_view);
2667 return(status);
2668}
2669
2670/*
2671%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2672% %
2673% %
2674% %
2675% L e v e l i z e I m a g e C h a n n e l %
2676% %
2677% %
2678% %
2679%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2680%
2681% LevelizeImageChannel() applies the reversed LevelImage() operation to just
2682% the specific channels specified. It compresses the full range of color
2683% values, so that they lie between the given black and white points. Gamma is
2684% applied before the values are mapped.
2685%
2686% LevelizeImageChannel() can be called with by using a +level command line
2687% API option, or using a '!' on a -level or LevelImage() geometry string.
2688%
2689% It can be used for example de-contrast a greyscale image to the exact
2690% levels specified. Or by using specific levels for each channel of an image
2691% you can convert a gray-scale image to any linear color gradient, according
2692% to those levels.
2693%
2694% The format of the LevelizeImageChannel method is:
2695%
2696% MagickBooleanType LevelizeImageChannel(Image *image,
2697% const ChannelType channel,const char *levels)
2698%
2699% A description of each parameter follows:
2700%
2701% o image: the image.
2702%
2703% o channel: the channel.
2704%
2705% o black_point: The level to map zero (black) to.
2706%
2707% o white_point: The level to map QuantiumRange (white) to.
2708%
2709% o gamma: adjust gamma by this factor before mapping values.
2710%
2711*/
cristyd1a2c0f2011-02-09 14:14:50 +00002712
2713MagickExport MagickBooleanType LevelizeImage(Image *image,
2714 const double black_point,const double white_point,const double gamma)
2715{
2716 MagickBooleanType
2717 status;
2718
2719 status=LevelizeImageChannel(image,DefaultChannels,black_point,white_point,
2720 gamma);
2721 return(status);
2722}
2723
cristy3ed852e2009-09-05 21:47:34 +00002724MagickExport MagickBooleanType LevelizeImageChannel(Image *image,
2725 const ChannelType channel,const double black_point,const double white_point,
2726 const double gamma)
2727{
2728#define LevelizeImageTag "Levelize/Image"
cristyce70c172010-01-07 17:15:30 +00002729#define LevelizeValue(x) (ClampToQuantum(((MagickRealType) \
cristy3ed852e2009-09-05 21:47:34 +00002730 pow((double)(QuantumScale*(x)),1.0/gamma))*(white_point-black_point)+ \
2731 black_point))
2732
cristyc4c8d132010-01-07 01:58:38 +00002733 CacheView
2734 *image_view;
2735
cristy3ed852e2009-09-05 21:47:34 +00002736 ExceptionInfo
2737 *exception;
2738
cristy3ed852e2009-09-05 21:47:34 +00002739 MagickBooleanType
2740 status;
2741
cristybb503372010-05-27 20:51:26 +00002742 MagickOffsetType
2743 progress;
2744
2745 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002746 i;
2747
cristybb503372010-05-27 20:51:26 +00002748 ssize_t
2749 y;
2750
cristy3ed852e2009-09-05 21:47:34 +00002751 /*
2752 Allocate and initialize levels map.
2753 */
2754 assert(image != (Image *) NULL);
2755 assert(image->signature == MagickSignature);
2756 if (image->debug != MagickFalse)
2757 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2758 if (image->storage_class == PseudoClass)
cristyb5d5f722009-11-04 03:03:49 +00002759#if defined(MAGICKCORE_OPENMP_SUPPORT)
2760 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002761#endif
cristybb503372010-05-27 20:51:26 +00002762 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00002763 {
2764 /*
2765 Level colormap.
2766 */
2767 if ((channel & RedChannel) != 0)
2768 image->colormap[i].red=LevelizeValue(image->colormap[i].red);
2769 if ((channel & GreenChannel) != 0)
2770 image->colormap[i].green=LevelizeValue(image->colormap[i].green);
2771 if ((channel & BlueChannel) != 0)
2772 image->colormap[i].blue=LevelizeValue(image->colormap[i].blue);
2773 if ((channel & OpacityChannel) != 0)
2774 image->colormap[i].opacity=LevelizeValue(image->colormap[i].opacity);
2775 }
2776 /*
2777 Level image.
2778 */
2779 status=MagickTrue;
2780 progress=0;
2781 exception=(&image->exception);
2782 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00002783#if defined(MAGICKCORE_OPENMP_SUPPORT)
2784 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002785#endif
cristybb503372010-05-27 20:51:26 +00002786 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002787 {
2788 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00002789 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00002790
cristy3ed852e2009-09-05 21:47:34 +00002791 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00002792 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002793
cristy8d4629b2010-08-30 17:59:46 +00002794 register ssize_t
2795 x;
2796
cristy3ed852e2009-09-05 21:47:34 +00002797 if (status == MagickFalse)
2798 continue;
2799 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2800 if (q == (PixelPacket *) NULL)
2801 {
2802 status=MagickFalse;
2803 continue;
2804 }
2805 indexes=GetCacheViewAuthenticIndexQueue(image_view);
cristybb503372010-05-27 20:51:26 +00002806 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002807 {
2808 if ((channel & RedChannel) != 0)
2809 q->red=LevelizeValue(q->red);
2810 if ((channel & GreenChannel) != 0)
2811 q->green=LevelizeValue(q->green);
2812 if ((channel & BlueChannel) != 0)
2813 q->blue=LevelizeValue(q->blue);
2814 if (((channel & OpacityChannel) != 0) &&
2815 (image->matte == MagickTrue))
2816 q->opacity=LevelizeValue(q->opacity);
2817 if (((channel & IndexChannel) != 0) &&
2818 (image->colorspace == CMYKColorspace))
2819 indexes[x]=LevelizeValue(indexes[x]);
2820 q++;
2821 }
2822 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2823 status=MagickFalse;
2824 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2825 {
2826 MagickBooleanType
2827 proceed;
2828
cristyb5d5f722009-11-04 03:03:49 +00002829#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00002830 #pragma omp critical (MagickCore_LevelizeImageChannel)
2831#endif
2832 proceed=SetImageProgress(image,LevelizeImageTag,progress++,image->rows);
2833 if (proceed == MagickFalse)
2834 status=MagickFalse;
2835 }
2836 }
cristy8d4629b2010-08-30 17:59:46 +00002837 image_view=DestroyCacheView(image_view);
cristy3ed852e2009-09-05 21:47:34 +00002838 return(status);
2839}
2840
2841/*
2842%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2843% %
2844% %
2845% %
2846% L e v e l I m a g e C o l o r s %
2847% %
2848% %
2849% %
2850%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2851%
cristyee0f8d72009-09-19 00:58:29 +00002852% LevelImageColor() maps the given color to "black" and "white" values,
2853% linearly spreading out the colors, and level values on a channel by channel
2854% bases, as per LevelImage(). The given colors allows you to specify
glennrp1e7f7bc2011-03-02 19:25:28 +00002855% different level ranges for each of the color channels separately.
cristy3ed852e2009-09-05 21:47:34 +00002856%
2857% If the boolean 'invert' is set true the image values will modifyed in the
2858% reverse direction. That is any existing "black" and "white" colors in the
2859% image will become the color values given, with all other values compressed
2860% appropriatally. This effectivally maps a greyscale gradient into the given
2861% color gradient.
2862%
cristy308b4e62009-09-21 14:40:44 +00002863% The format of the LevelColorsImageChannel method is:
cristy3ed852e2009-09-05 21:47:34 +00002864%
cristy308b4e62009-09-21 14:40:44 +00002865% MagickBooleanType LevelColorsImage(Image *image,
cristyee0f8d72009-09-19 00:58:29 +00002866% const MagickPixelPacket *black_color,
2867% const MagickPixelPacket *white_color,const MagickBooleanType invert)
cristy308b4e62009-09-21 14:40:44 +00002868% MagickBooleanType LevelColorsImageChannel(Image *image,
2869% const ChannelType channel,const MagickPixelPacket *black_color,
2870% const MagickPixelPacket *white_color,const MagickBooleanType invert)
cristy3ed852e2009-09-05 21:47:34 +00002871%
2872% A description of each parameter follows:
2873%
2874% o image: the image.
2875%
2876% o channel: the channel.
2877%
2878% o black_color: The color to map black to/from
2879%
2880% o white_point: The color to map white to/from
2881%
2882% o invert: if true map the colors (levelize), rather than from (level)
2883%
2884*/
cristy308b4e62009-09-21 14:40:44 +00002885
2886MagickExport MagickBooleanType LevelColorsImage(Image *image,
cristy3ed852e2009-09-05 21:47:34 +00002887 const MagickPixelPacket *black_color,const MagickPixelPacket *white_color,
2888 const MagickBooleanType invert)
2889{
cristy308b4e62009-09-21 14:40:44 +00002890 MagickBooleanType
2891 status;
cristy3ed852e2009-09-05 21:47:34 +00002892
cristy308b4e62009-09-21 14:40:44 +00002893 status=LevelColorsImageChannel(image,DefaultChannels,black_color,white_color,
2894 invert);
2895 return(status);
2896}
2897
2898MagickExport MagickBooleanType LevelColorsImageChannel(Image *image,
2899 const ChannelType channel,const MagickPixelPacket *black_color,
2900 const MagickPixelPacket *white_color,const MagickBooleanType invert)
2901{
cristy3ed852e2009-09-05 21:47:34 +00002902 MagickStatusType
2903 status;
2904
2905 /*
2906 Allocate and initialize levels map.
2907 */
2908 assert(image != (Image *) NULL);
2909 assert(image->signature == MagickSignature);
2910 if (image->debug != MagickFalse)
2911 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2912 status=MagickFalse;
2913 if (invert == MagickFalse)
2914 {
2915 if ((channel & RedChannel) != 0)
2916 status|=LevelImageChannel(image,RedChannel,
2917 black_color->red,white_color->red,(double) 1.0);
2918 if ((channel & GreenChannel) != 0)
2919 status|=LevelImageChannel(image,GreenChannel,
2920 black_color->green,white_color->green,(double) 1.0);
2921 if ((channel & BlueChannel) != 0)
2922 status|=LevelImageChannel(image,BlueChannel,
2923 black_color->blue,white_color->blue,(double) 1.0);
2924 if (((channel & OpacityChannel) != 0) &&
2925 (image->matte == MagickTrue))
2926 status|=LevelImageChannel(image,OpacityChannel,
2927 black_color->opacity,white_color->opacity,(double) 1.0);
2928 if (((channel & IndexChannel) != 0) &&
2929 (image->colorspace == CMYKColorspace))
2930 status|=LevelImageChannel(image,IndexChannel,
2931 black_color->index,white_color->index,(double) 1.0);
2932 }
2933 else
2934 {
2935 if ((channel & RedChannel) != 0)
2936 status|=LevelizeImageChannel(image,RedChannel,
2937 black_color->red,white_color->red,(double) 1.0);
2938 if ((channel & GreenChannel) != 0)
2939 status|=LevelizeImageChannel(image,GreenChannel,
2940 black_color->green,white_color->green,(double) 1.0);
2941 if ((channel & BlueChannel) != 0)
2942 status|=LevelizeImageChannel(image,BlueChannel,
2943 black_color->blue,white_color->blue,(double) 1.0);
2944 if (((channel & OpacityChannel) != 0) &&
2945 (image->matte == MagickTrue))
2946 status|=LevelizeImageChannel(image,OpacityChannel,
2947 black_color->opacity,white_color->opacity,(double) 1.0);
2948 if (((channel & IndexChannel) != 0) &&
2949 (image->colorspace == CMYKColorspace))
2950 status|=LevelizeImageChannel(image,IndexChannel,
2951 black_color->index,white_color->index,(double) 1.0);
2952 }
2953 return(status == 0 ? MagickFalse : MagickTrue);
2954}
2955
2956/*
2957%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2958% %
2959% %
2960% %
2961% L i n e a r S t r e t c h I m a g e %
2962% %
2963% %
2964% %
2965%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2966%
2967% The LinearStretchImage() discards any pixels below the black point and
2968% above the white point and levels the remaining pixels.
2969%
2970% The format of the LinearStretchImage method is:
2971%
2972% MagickBooleanType LinearStretchImage(Image *image,
2973% const double black_point,const double white_point)
2974%
2975% A description of each parameter follows:
2976%
2977% o image: the image.
2978%
2979% o black_point: the black point.
2980%
2981% o white_point: the white point.
2982%
2983*/
2984MagickExport MagickBooleanType LinearStretchImage(Image *image,
2985 const double black_point,const double white_point)
2986{
2987#define LinearStretchImageTag "LinearStretch/Image"
2988
2989 ExceptionInfo
2990 *exception;
2991
cristy3ed852e2009-09-05 21:47:34 +00002992 MagickBooleanType
2993 status;
2994
2995 MagickRealType
2996 *histogram,
2997 intensity;
2998
cristy8d4629b2010-08-30 17:59:46 +00002999 ssize_t
3000 black,
3001 white,
3002 y;
3003
cristy3ed852e2009-09-05 21:47:34 +00003004 /*
3005 Allocate histogram and linear map.
3006 */
3007 assert(image != (Image *) NULL);
3008 assert(image->signature == MagickSignature);
3009 histogram=(MagickRealType *) AcquireQuantumMemory(MaxMap+1UL,
3010 sizeof(*histogram));
3011 if (histogram == (MagickRealType *) NULL)
3012 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
3013 image->filename);
3014 /*
3015 Form histogram.
3016 */
3017 (void) ResetMagickMemory(histogram,0,(MaxMap+1)*sizeof(*histogram));
3018 exception=(&image->exception);
cristybb503372010-05-27 20:51:26 +00003019 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003020 {
3021 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00003022 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00003023
cristybb503372010-05-27 20:51:26 +00003024 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003025 x;
3026
3027 p=GetVirtualPixels(image,0,y,image->columns,1,exception);
3028 if (p == (const PixelPacket *) NULL)
3029 break;
cristybb503372010-05-27 20:51:26 +00003030 for (x=(ssize_t) image->columns-1; x >= 0; x--)
cristy3ed852e2009-09-05 21:47:34 +00003031 {
3032 histogram[ScaleQuantumToMap(PixelIntensityToQuantum(p))]++;
3033 p++;
3034 }
3035 }
3036 /*
3037 Find the histogram boundaries by locating the black and white point levels.
3038 */
cristy3ed852e2009-09-05 21:47:34 +00003039 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00003040 for (black=0; black < (ssize_t) MaxMap; black++)
cristy3ed852e2009-09-05 21:47:34 +00003041 {
3042 intensity+=histogram[black];
3043 if (intensity >= black_point)
3044 break;
3045 }
3046 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00003047 for (white=(ssize_t) MaxMap; white != 0; white--)
cristy3ed852e2009-09-05 21:47:34 +00003048 {
3049 intensity+=histogram[white];
3050 if (intensity >= white_point)
3051 break;
3052 }
3053 histogram=(MagickRealType *) RelinquishMagickMemory(histogram);
3054 status=LevelImageChannel(image,DefaultChannels,(double) black,(double) white,
3055 1.0);
3056 return(status);
3057}
3058
3059/*
3060%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3061% %
3062% %
3063% %
3064% M o d u l a t e I m a g e %
3065% %
3066% %
3067% %
3068%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3069%
3070% ModulateImage() lets you control the brightness, saturation, and hue
3071% of an image. Modulate represents the brightness, saturation, and hue
3072% as one parameter (e.g. 90,150,100). If the image colorspace is HSL, the
3073% modulation is lightness, saturation, and hue. And if the colorspace is
3074% HWB, use blackness, whiteness, and hue.
3075%
3076% The format of the ModulateImage method is:
3077%
3078% MagickBooleanType ModulateImage(Image *image,const char *modulate)
3079%
3080% A description of each parameter follows:
3081%
3082% o image: the image.
3083%
3084% o modulate: Define the percent change in brightness, saturation, and
3085% hue.
3086%
3087*/
3088
3089static void ModulateHSB(const double percent_hue,
3090 const double percent_saturation,const double percent_brightness,
3091 Quantum *red,Quantum *green,Quantum *blue)
3092{
3093 double
3094 brightness,
3095 hue,
3096 saturation;
3097
3098 /*
3099 Increase or decrease color brightness, saturation, or hue.
3100 */
3101 assert(red != (Quantum *) NULL);
3102 assert(green != (Quantum *) NULL);
3103 assert(blue != (Quantum *) NULL);
3104 ConvertRGBToHSB(*red,*green,*blue,&hue,&saturation,&brightness);
3105 hue+=0.5*(0.01*percent_hue-1.0);
3106 while (hue < 0.0)
3107 hue+=1.0;
3108 while (hue > 1.0)
3109 hue-=1.0;
3110 saturation*=0.01*percent_saturation;
3111 brightness*=0.01*percent_brightness;
3112 ConvertHSBToRGB(hue,saturation,brightness,red,green,blue);
3113}
3114
3115static void ModulateHSL(const double percent_hue,
3116 const double percent_saturation,const double percent_lightness,
3117 Quantum *red,Quantum *green,Quantum *blue)
3118{
3119 double
3120 hue,
3121 lightness,
3122 saturation;
3123
3124 /*
3125 Increase or decrease color lightness, saturation, or hue.
3126 */
3127 assert(red != (Quantum *) NULL);
3128 assert(green != (Quantum *) NULL);
3129 assert(blue != (Quantum *) NULL);
3130 ConvertRGBToHSL(*red,*green,*blue,&hue,&saturation,&lightness);
3131 hue+=0.5*(0.01*percent_hue-1.0);
3132 while (hue < 0.0)
3133 hue+=1.0;
3134 while (hue > 1.0)
3135 hue-=1.0;
3136 saturation*=0.01*percent_saturation;
3137 lightness*=0.01*percent_lightness;
3138 ConvertHSLToRGB(hue,saturation,lightness,red,green,blue);
3139}
3140
3141static void ModulateHWB(const double percent_hue,const double percent_whiteness, const double percent_blackness,Quantum *red,Quantum *green,Quantum *blue)
3142{
3143 double
3144 blackness,
3145 hue,
3146 whiteness;
3147
3148 /*
3149 Increase or decrease color blackness, whiteness, or hue.
3150 */
3151 assert(red != (Quantum *) NULL);
3152 assert(green != (Quantum *) NULL);
3153 assert(blue != (Quantum *) NULL);
3154 ConvertRGBToHWB(*red,*green,*blue,&hue,&whiteness,&blackness);
3155 hue+=0.5*(0.01*percent_hue-1.0);
3156 while (hue < 0.0)
3157 hue+=1.0;
3158 while (hue > 1.0)
3159 hue-=1.0;
3160 blackness*=0.01*percent_blackness;
3161 whiteness*=0.01*percent_whiteness;
3162 ConvertHWBToRGB(hue,whiteness,blackness,red,green,blue);
3163}
3164
3165MagickExport MagickBooleanType ModulateImage(Image *image,const char *modulate)
3166{
3167#define ModulateImageTag "Modulate/Image"
3168
cristyc4c8d132010-01-07 01:58:38 +00003169 CacheView
3170 *image_view;
3171
cristy3ed852e2009-09-05 21:47:34 +00003172 ColorspaceType
3173 colorspace;
3174
3175 const char
3176 *artifact;
3177
3178 double
3179 percent_brightness,
3180 percent_hue,
3181 percent_saturation;
3182
3183 ExceptionInfo
3184 *exception;
3185
3186 GeometryInfo
3187 geometry_info;
3188
cristy3ed852e2009-09-05 21:47:34 +00003189 MagickBooleanType
3190 status;
3191
cristybb503372010-05-27 20:51:26 +00003192 MagickOffsetType
3193 progress;
3194
cristy3ed852e2009-09-05 21:47:34 +00003195 MagickStatusType
3196 flags;
3197
cristybb503372010-05-27 20:51:26 +00003198 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003199 i;
3200
cristybb503372010-05-27 20:51:26 +00003201 ssize_t
3202 y;
3203
cristy3ed852e2009-09-05 21:47:34 +00003204 /*
cristy2b726bd2010-01-11 01:05:39 +00003205 Initialize modulate table.
cristy3ed852e2009-09-05 21:47:34 +00003206 */
3207 assert(image != (Image *) NULL);
3208 assert(image->signature == MagickSignature);
3209 if (image->debug != MagickFalse)
3210 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3211 if (modulate == (char *) NULL)
3212 return(MagickFalse);
3213 flags=ParseGeometry(modulate,&geometry_info);
3214 percent_brightness=geometry_info.rho;
3215 percent_saturation=geometry_info.sigma;
3216 if ((flags & SigmaValue) == 0)
3217 percent_saturation=100.0;
3218 percent_hue=geometry_info.xi;
3219 if ((flags & XiValue) == 0)
3220 percent_hue=100.0;
3221 colorspace=UndefinedColorspace;
3222 artifact=GetImageArtifact(image,"modulate:colorspace");
3223 if (artifact != (const char *) NULL)
3224 colorspace=(ColorspaceType) ParseMagickOption(MagickColorspaceOptions,
3225 MagickFalse,artifact);
3226 if (image->storage_class == PseudoClass)
3227 {
3228 /*
3229 Modulate colormap.
3230 */
cristyb5d5f722009-11-04 03:03:49 +00003231#if defined(MAGICKCORE_OPENMP_SUPPORT)
3232 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003233#endif
cristybb503372010-05-27 20:51:26 +00003234 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00003235 switch (colorspace)
3236 {
3237 case HSBColorspace:
3238 {
3239 ModulateHSB(percent_hue,percent_saturation,percent_brightness,
3240 &image->colormap[i].red,&image->colormap[i].green,
3241 &image->colormap[i].blue);
3242 break;
3243 }
3244 case HSLColorspace:
3245 default:
3246 {
3247 ModulateHSL(percent_hue,percent_saturation,percent_brightness,
3248 &image->colormap[i].red,&image->colormap[i].green,
3249 &image->colormap[i].blue);
3250 break;
3251 }
3252 case HWBColorspace:
3253 {
3254 ModulateHWB(percent_hue,percent_saturation,percent_brightness,
3255 &image->colormap[i].red,&image->colormap[i].green,
3256 &image->colormap[i].blue);
3257 break;
3258 }
3259 }
3260 }
3261 /*
3262 Modulate image.
3263 */
3264 status=MagickTrue;
3265 progress=0;
3266 exception=(&image->exception);
3267 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00003268#if defined(MAGICKCORE_OPENMP_SUPPORT)
3269 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003270#endif
cristybb503372010-05-27 20:51:26 +00003271 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003272 {
cristy3ed852e2009-09-05 21:47:34 +00003273 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00003274 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003275
cristy8d4629b2010-08-30 17:59:46 +00003276 register ssize_t
3277 x;
3278
cristy3ed852e2009-09-05 21:47:34 +00003279 if (status == MagickFalse)
3280 continue;
3281 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
3282 if (q == (PixelPacket *) NULL)
3283 {
3284 status=MagickFalse;
3285 continue;
3286 }
cristybb503372010-05-27 20:51:26 +00003287 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003288 {
3289 switch (colorspace)
3290 {
3291 case HSBColorspace:
3292 {
3293 ModulateHSB(percent_hue,percent_saturation,percent_brightness,
3294 &q->red,&q->green,&q->blue);
3295 break;
3296 }
3297 case HSLColorspace:
3298 default:
3299 {
3300 ModulateHSL(percent_hue,percent_saturation,percent_brightness,
3301 &q->red,&q->green,&q->blue);
3302 break;
3303 }
3304 case HWBColorspace:
3305 {
3306 ModulateHWB(percent_hue,percent_saturation,percent_brightness,
3307 &q->red,&q->green,&q->blue);
3308 break;
3309 }
3310 }
3311 q++;
3312 }
3313 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
3314 status=MagickFalse;
3315 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3316 {
3317 MagickBooleanType
3318 proceed;
3319
cristyb5d5f722009-11-04 03:03:49 +00003320#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00003321 #pragma omp critical (MagickCore_ModulateImage)
3322#endif
3323 proceed=SetImageProgress(image,ModulateImageTag,progress++,image->rows);
3324 if (proceed == MagickFalse)
3325 status=MagickFalse;
3326 }
3327 }
3328 image_view=DestroyCacheView(image_view);
3329 return(status);
3330}
3331
3332/*
3333%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3334% %
3335% %
3336% %
3337% N e g a t e I m a g e %
3338% %
3339% %
3340% %
3341%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3342%
3343% NegateImage() negates the colors in the reference image. The grayscale
3344% option means that only grayscale values within the image are negated.
3345%
3346% The format of the NegateImageChannel method is:
3347%
3348% MagickBooleanType NegateImage(Image *image,
3349% const MagickBooleanType grayscale)
3350% MagickBooleanType NegateImageChannel(Image *image,
3351% const ChannelType channel,const MagickBooleanType grayscale)
3352%
3353% A description of each parameter follows:
3354%
3355% o image: the image.
3356%
3357% o channel: the channel.
3358%
3359% o grayscale: If MagickTrue, only negate grayscale pixels within the image.
3360%
3361*/
3362
3363MagickExport MagickBooleanType NegateImage(Image *image,
3364 const MagickBooleanType grayscale)
3365{
3366 MagickBooleanType
3367 status;
3368
3369 status=NegateImageChannel(image,DefaultChannels,grayscale);
3370 return(status);
3371}
3372
3373MagickExport MagickBooleanType NegateImageChannel(Image *image,
3374 const ChannelType channel,const MagickBooleanType grayscale)
3375{
3376#define NegateImageTag "Negate/Image"
3377
cristyc4c8d132010-01-07 01:58:38 +00003378 CacheView
3379 *image_view;
3380
cristy3ed852e2009-09-05 21:47:34 +00003381 ExceptionInfo
3382 *exception;
3383
cristy3ed852e2009-09-05 21:47:34 +00003384 MagickBooleanType
3385 status;
3386
cristybb503372010-05-27 20:51:26 +00003387 MagickOffsetType
3388 progress;
3389
3390 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003391 i;
3392
cristybb503372010-05-27 20:51:26 +00003393 ssize_t
3394 y;
3395
cristy3ed852e2009-09-05 21:47:34 +00003396 assert(image != (Image *) NULL);
3397 assert(image->signature == MagickSignature);
3398 if (image->debug != MagickFalse)
3399 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3400 if (image->storage_class == PseudoClass)
3401 {
3402 /*
3403 Negate colormap.
3404 */
cristyb5d5f722009-11-04 03:03:49 +00003405#if defined(MAGICKCORE_OPENMP_SUPPORT)
3406 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003407#endif
cristybb503372010-05-27 20:51:26 +00003408 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00003409 {
3410 if (grayscale != MagickFalse)
3411 if ((image->colormap[i].red != image->colormap[i].green) ||
3412 (image->colormap[i].green != image->colormap[i].blue))
3413 continue;
3414 if ((channel & RedChannel) != 0)
3415 image->colormap[i].red=(Quantum) QuantumRange-
3416 image->colormap[i].red;
3417 if ((channel & GreenChannel) != 0)
3418 image->colormap[i].green=(Quantum) QuantumRange-
3419 image->colormap[i].green;
3420 if ((channel & BlueChannel) != 0)
3421 image->colormap[i].blue=(Quantum) QuantumRange-
3422 image->colormap[i].blue;
3423 }
3424 }
3425 /*
3426 Negate image.
3427 */
3428 status=MagickTrue;
3429 progress=0;
3430 exception=(&image->exception);
3431 image_view=AcquireCacheView(image);
3432 if (grayscale != MagickFalse)
3433 {
cristyb5d5f722009-11-04 03:03:49 +00003434#if defined(MAGICKCORE_OPENMP_SUPPORT)
3435 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003436#endif
cristybb503372010-05-27 20:51:26 +00003437 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003438 {
3439 MagickBooleanType
3440 sync;
3441
3442 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00003443 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00003444
cristy3ed852e2009-09-05 21:47:34 +00003445 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00003446 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003447
cristy8d4629b2010-08-30 17:59:46 +00003448 register ssize_t
3449 x;
3450
cristy3ed852e2009-09-05 21:47:34 +00003451 if (status == MagickFalse)
3452 continue;
3453 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,
3454 exception);
3455 if (q == (PixelPacket *) NULL)
3456 {
3457 status=MagickFalse;
3458 continue;
3459 }
3460 indexes=GetCacheViewAuthenticIndexQueue(image_view);
cristybb503372010-05-27 20:51:26 +00003461 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003462 {
3463 if ((q->red != q->green) || (q->green != q->blue))
3464 {
3465 q++;
3466 continue;
3467 }
3468 if ((channel & RedChannel) != 0)
3469 q->red=(Quantum) QuantumRange-q->red;
3470 if ((channel & GreenChannel) != 0)
3471 q->green=(Quantum) QuantumRange-q->green;
3472 if ((channel & BlueChannel) != 0)
3473 q->blue=(Quantum) QuantumRange-q->blue;
3474 if ((channel & OpacityChannel) != 0)
3475 q->opacity=(Quantum) QuantumRange-q->opacity;
3476 if (((channel & IndexChannel) != 0) &&
3477 (image->colorspace == CMYKColorspace))
3478 indexes[x]=(IndexPacket) QuantumRange-indexes[x];
3479 q++;
3480 }
3481 sync=SyncCacheViewAuthenticPixels(image_view,exception);
3482 if (sync == MagickFalse)
3483 status=MagickFalse;
3484 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3485 {
3486 MagickBooleanType
3487 proceed;
3488
cristyb5d5f722009-11-04 03:03:49 +00003489#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00003490 #pragma omp critical (MagickCore_NegateImageChannel)
3491#endif
3492 proceed=SetImageProgress(image,NegateImageTag,progress++,
3493 image->rows);
3494 if (proceed == MagickFalse)
3495 status=MagickFalse;
3496 }
3497 }
3498 image_view=DestroyCacheView(image_view);
3499 return(MagickTrue);
3500 }
3501 /*
3502 Negate image.
3503 */
cristyb5d5f722009-11-04 03:03:49 +00003504#if defined(MAGICKCORE_OPENMP_SUPPORT)
3505 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003506#endif
cristybb503372010-05-27 20:51:26 +00003507 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003508 {
3509 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00003510 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00003511
cristy3ed852e2009-09-05 21:47:34 +00003512 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00003513 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003514
cristy8d4629b2010-08-30 17:59:46 +00003515 register ssize_t
3516 x;
3517
cristy3ed852e2009-09-05 21:47:34 +00003518 if (status == MagickFalse)
3519 continue;
3520 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
3521 if (q == (PixelPacket *) NULL)
3522 {
3523 status=MagickFalse;
3524 continue;
3525 }
3526 indexes=GetCacheViewAuthenticIndexQueue(image_view);
cristybb503372010-05-27 20:51:26 +00003527 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003528 {
3529 if ((channel & RedChannel) != 0)
3530 q->red=(Quantum) QuantumRange-q->red;
3531 if ((channel & GreenChannel) != 0)
3532 q->green=(Quantum) QuantumRange-q->green;
3533 if ((channel & BlueChannel) != 0)
3534 q->blue=(Quantum) QuantumRange-q->blue;
3535 if ((channel & OpacityChannel) != 0)
3536 q->opacity=(Quantum) QuantumRange-q->opacity;
3537 if (((channel & IndexChannel) != 0) &&
3538 (image->colorspace == CMYKColorspace))
3539 indexes[x]=(IndexPacket) QuantumRange-indexes[x];
3540 q++;
3541 }
3542 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
3543 status=MagickFalse;
3544 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3545 {
3546 MagickBooleanType
3547 proceed;
3548
cristyb5d5f722009-11-04 03:03:49 +00003549#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00003550 #pragma omp critical (MagickCore_NegateImageChannel)
3551#endif
3552 proceed=SetImageProgress(image,NegateImageTag,progress++,image->rows);
3553 if (proceed == MagickFalse)
3554 status=MagickFalse;
3555 }
3556 }
3557 image_view=DestroyCacheView(image_view);
3558 return(status);
3559}
3560
3561/*
3562%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3563% %
3564% %
3565% %
3566% N o r m a l i z e I m a g e %
3567% %
3568% %
3569% %
3570%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3571%
3572% The NormalizeImage() method enhances the contrast of a color image by
3573% mapping the darkest 2 percent of all pixel to black and the brightest
3574% 1 percent to white.
3575%
3576% The format of the NormalizeImage method is:
3577%
3578% MagickBooleanType NormalizeImage(Image *image)
3579% MagickBooleanType NormalizeImageChannel(Image *image,
3580% const ChannelType channel)
3581%
3582% A description of each parameter follows:
3583%
3584% o image: the image.
3585%
3586% o channel: the channel.
3587%
3588*/
3589
3590MagickExport MagickBooleanType NormalizeImage(Image *image)
3591{
3592 MagickBooleanType
3593 status;
3594
3595 status=NormalizeImageChannel(image,DefaultChannels);
3596 return(status);
3597}
3598
3599MagickExport MagickBooleanType NormalizeImageChannel(Image *image,
3600 const ChannelType channel)
3601{
3602 double
3603 black_point,
3604 white_point;
3605
cristy530239c2010-07-25 17:34:26 +00003606 black_point=(double) image->columns*image->rows*0.0015;
3607 white_point=(double) image->columns*image->rows*0.9995;
cristy3ed852e2009-09-05 21:47:34 +00003608 return(ContrastStretchImageChannel(image,channel,black_point,white_point));
3609}
3610
3611/*
3612%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3613% %
3614% %
3615% %
3616% S i g m o i d a l C o n t r a s t I m a g e %
3617% %
3618% %
3619% %
3620%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3621%
3622% SigmoidalContrastImage() adjusts the contrast of an image with a non-linear
3623% sigmoidal contrast algorithm. Increase the contrast of the image using a
3624% sigmoidal transfer function without saturating highlights or shadows.
3625% Contrast indicates how much to increase the contrast (0 is none; 3 is
3626% typical; 20 is pushing it); mid-point indicates where midtones fall in the
3627% resultant image (0 is white; 50% is middle-gray; 100% is black). Set
3628% sharpen to MagickTrue to increase the image contrast otherwise the contrast
3629% is reduced.
3630%
3631% The format of the SigmoidalContrastImage method is:
3632%
3633% MagickBooleanType SigmoidalContrastImage(Image *image,
3634% const MagickBooleanType sharpen,const char *levels)
3635% MagickBooleanType SigmoidalContrastImageChannel(Image *image,
3636% const ChannelType channel,const MagickBooleanType sharpen,
3637% const double contrast,const double midpoint)
3638%
3639% A description of each parameter follows:
3640%
3641% o image: the image.
3642%
3643% o channel: the channel.
3644%
3645% o sharpen: Increase or decrease image contrast.
3646%
cristyfa769582010-09-30 23:30:03 +00003647% o alpha: strength of the contrast, the larger the number the more
3648% 'threshold-like' it becomes.
cristy3ed852e2009-09-05 21:47:34 +00003649%
cristyfa769582010-09-30 23:30:03 +00003650% o beta: midpoint of the function as a color value 0 to QuantumRange.
cristy3ed852e2009-09-05 21:47:34 +00003651%
3652*/
3653
3654MagickExport MagickBooleanType SigmoidalContrastImage(Image *image,
3655 const MagickBooleanType sharpen,const char *levels)
3656{
3657 GeometryInfo
3658 geometry_info;
3659
3660 MagickBooleanType
3661 status;
3662
3663 MagickStatusType
3664 flags;
3665
3666 flags=ParseGeometry(levels,&geometry_info);
3667 if ((flags & SigmaValue) == 0)
3668 geometry_info.sigma=1.0*QuantumRange/2.0;
3669 if ((flags & PercentValue) != 0)
3670 geometry_info.sigma=1.0*QuantumRange*geometry_info.sigma/100.0;
3671 status=SigmoidalContrastImageChannel(image,DefaultChannels,sharpen,
3672 geometry_info.rho,geometry_info.sigma);
3673 return(status);
3674}
3675
3676MagickExport MagickBooleanType SigmoidalContrastImageChannel(Image *image,
3677 const ChannelType channel,const MagickBooleanType sharpen,
3678 const double contrast,const double midpoint)
3679{
3680#define SigmoidalContrastImageTag "SigmoidalContrast/Image"
3681
cristyc4c8d132010-01-07 01:58:38 +00003682 CacheView
3683 *image_view;
3684
cristy3ed852e2009-09-05 21:47:34 +00003685 ExceptionInfo
3686 *exception;
3687
cristy3ed852e2009-09-05 21:47:34 +00003688 MagickBooleanType
3689 status;
3690
cristybb503372010-05-27 20:51:26 +00003691 MagickOffsetType
3692 progress;
3693
cristy3ed852e2009-09-05 21:47:34 +00003694 MagickRealType
3695 *sigmoidal_map;
3696
cristybb503372010-05-27 20:51:26 +00003697 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003698 i;
3699
cristybb503372010-05-27 20:51:26 +00003700 ssize_t
3701 y;
3702
cristy3ed852e2009-09-05 21:47:34 +00003703 /*
3704 Allocate and initialize sigmoidal maps.
3705 */
3706 assert(image != (Image *) NULL);
3707 assert(image->signature == MagickSignature);
3708 if (image->debug != MagickFalse)
3709 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3710 sigmoidal_map=(MagickRealType *) AcquireQuantumMemory(MaxMap+1UL,
3711 sizeof(*sigmoidal_map));
3712 if (sigmoidal_map == (MagickRealType *) NULL)
3713 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
3714 image->filename);
3715 (void) ResetMagickMemory(sigmoidal_map,0,(MaxMap+1)*sizeof(*sigmoidal_map));
cristyb5d5f722009-11-04 03:03:49 +00003716#if defined(MAGICKCORE_OPENMP_SUPPORT)
3717 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003718#endif
cristybb503372010-05-27 20:51:26 +00003719 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +00003720 {
3721 if (sharpen != MagickFalse)
3722 {
3723 sigmoidal_map[i]=(MagickRealType) ScaleMapToQuantum((MagickRealType)
3724 (MaxMap*((1.0/(1.0+exp(contrast*(midpoint/(double) QuantumRange-
3725 (double) i/MaxMap))))-(1.0/(1.0+exp(contrast*(midpoint/
3726 (double) QuantumRange)))))/((1.0/(1.0+exp(contrast*(midpoint/
3727 (double) QuantumRange-1.0))))-(1.0/(1.0+exp(contrast*(midpoint/
3728 (double) QuantumRange)))))+0.5));
3729 continue;
3730 }
3731 sigmoidal_map[i]=(MagickRealType) ScaleMapToQuantum((MagickRealType)
3732 (MaxMap*(QuantumScale*midpoint-log((1.0-(1.0/(1.0+exp(midpoint/
3733 (double) QuantumRange*contrast))+((double) i/MaxMap)*((1.0/
3734 (1.0+exp(contrast*(midpoint/(double) QuantumRange-1.0))))-(1.0/
3735 (1.0+exp(midpoint/(double) QuantumRange*contrast))))))/
3736 (1.0/(1.0+exp(midpoint/(double) QuantumRange*contrast))+
3737 ((double) i/MaxMap)*((1.0/(1.0+exp(contrast*(midpoint/
3738 (double) QuantumRange-1.0))))-(1.0/(1.0+exp(midpoint/
3739 (double) QuantumRange*contrast))))))/contrast)));
3740 }
3741 if (image->storage_class == PseudoClass)
3742 {
3743 /*
3744 Sigmoidal-contrast enhance colormap.
3745 */
cristyb5d5f722009-11-04 03:03:49 +00003746#if defined(MAGICKCORE_OPENMP_SUPPORT)
3747 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003748#endif
cristybb503372010-05-27 20:51:26 +00003749 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00003750 {
3751 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003752 image->colormap[i].red=ClampToQuantum(sigmoidal_map[
cristy3ed852e2009-09-05 21:47:34 +00003753 ScaleQuantumToMap(image->colormap[i].red)]);
3754 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003755 image->colormap[i].green=ClampToQuantum(sigmoidal_map[
cristy3ed852e2009-09-05 21:47:34 +00003756 ScaleQuantumToMap(image->colormap[i].green)]);
3757 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003758 image->colormap[i].blue=ClampToQuantum(sigmoidal_map[
cristy3ed852e2009-09-05 21:47:34 +00003759 ScaleQuantumToMap(image->colormap[i].blue)]);
3760 if ((channel & OpacityChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003761 image->colormap[i].opacity=ClampToQuantum(sigmoidal_map[
cristy3ed852e2009-09-05 21:47:34 +00003762 ScaleQuantumToMap(image->colormap[i].opacity)]);
3763 }
3764 }
3765 /*
3766 Sigmoidal-contrast enhance image.
3767 */
3768 status=MagickTrue;
3769 progress=0;
3770 exception=(&image->exception);
3771 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00003772#if defined(MAGICKCORE_OPENMP_SUPPORT)
3773 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003774#endif
cristybb503372010-05-27 20:51:26 +00003775 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003776 {
3777 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00003778 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00003779
cristy3ed852e2009-09-05 21:47:34 +00003780 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00003781 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003782
cristy8d4629b2010-08-30 17:59:46 +00003783 register ssize_t
3784 x;
3785
cristy3ed852e2009-09-05 21:47:34 +00003786 if (status == MagickFalse)
3787 continue;
3788 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
3789 if (q == (PixelPacket *) NULL)
3790 {
3791 status=MagickFalse;
3792 continue;
3793 }
3794 indexes=GetCacheViewAuthenticIndexQueue(image_view);
cristybb503372010-05-27 20:51:26 +00003795 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003796 {
3797 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003798 q->red=ClampToQuantum(sigmoidal_map[ScaleQuantumToMap(q->red)]);
cristy3ed852e2009-09-05 21:47:34 +00003799 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003800 q->green=ClampToQuantum(sigmoidal_map[ScaleQuantumToMap(q->green)]);
cristy3ed852e2009-09-05 21:47:34 +00003801 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003802 q->blue=ClampToQuantum(sigmoidal_map[ScaleQuantumToMap(q->blue)]);
cristy3ed852e2009-09-05 21:47:34 +00003803 if ((channel & OpacityChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003804 q->opacity=ClampToQuantum(sigmoidal_map[ScaleQuantumToMap(q->opacity)]);
cristy3ed852e2009-09-05 21:47:34 +00003805 if (((channel & IndexChannel) != 0) &&
3806 (image->colorspace == CMYKColorspace))
cristyce70c172010-01-07 17:15:30 +00003807 indexes[x]=(IndexPacket) ClampToQuantum(sigmoidal_map[
cristy3ed852e2009-09-05 21:47:34 +00003808 ScaleQuantumToMap(indexes[x])]);
3809 q++;
3810 }
3811 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
3812 status=MagickFalse;
3813 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3814 {
3815 MagickBooleanType
3816 proceed;
3817
cristyb5d5f722009-11-04 03:03:49 +00003818#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00003819 #pragma omp critical (MagickCore_SigmoidalContrastImageChannel)
3820#endif
3821 proceed=SetImageProgress(image,SigmoidalContrastImageTag,progress++,
3822 image->rows);
3823 if (proceed == MagickFalse)
3824 status=MagickFalse;
3825 }
3826 }
3827 image_view=DestroyCacheView(image_view);
3828 sigmoidal_map=(MagickRealType *) RelinquishMagickMemory(sigmoidal_map);
3829 return(status);
3830}