blob: 84200598a7aa73087d93cd10e65a534b1ba070d2 [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*/
cristy4c08aed2011-07-01 19:47:50 +000043#include "MagickCore/studio.h"
44#include "MagickCore/artifact.h"
45#include "MagickCore/cache.h"
46#include "MagickCore/cache-view.h"
47#include "MagickCore/color.h"
48#include "MagickCore/color-private.h"
49#include "MagickCore/colorspace.h"
50#include "MagickCore/composite-private.h"
51#include "MagickCore/enhance.h"
52#include "MagickCore/exception.h"
53#include "MagickCore/exception-private.h"
54#include "MagickCore/fx.h"
55#include "MagickCore/gem.h"
56#include "MagickCore/geometry.h"
57#include "MagickCore/histogram.h"
58#include "MagickCore/image.h"
59#include "MagickCore/image-private.h"
60#include "MagickCore/memory_.h"
61#include "MagickCore/monitor.h"
62#include "MagickCore/monitor-private.h"
63#include "MagickCore/option.h"
64#include "MagickCore/pixel-accessor.h"
65#include "MagickCore/quantum.h"
66#include "MagickCore/quantum-private.h"
67#include "MagickCore/resample.h"
68#include "MagickCore/resample-private.h"
69#include "MagickCore/statistic.h"
70#include "MagickCore/string_.h"
71#include "MagickCore/string-private.h"
72#include "MagickCore/thread-private.h"
73#include "MagickCore/token.h"
74#include "MagickCore/xml-tree.h"
cristy3ed852e2009-09-05 21:47:34 +000075
76/*
77%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
78% %
79% %
80% %
81% A u t o G a m m a I m a g e %
82% %
83% %
84% %
85%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
86%
87% AutoGammaImage() extract the 'mean' from the image and adjust the image
88% to try make set its gamma appropriatally.
89%
cristy308b4e62009-09-21 14:40:44 +000090% The format of the AutoGammaImage method is:
cristy3ed852e2009-09-05 21:47:34 +000091%
92% MagickBooleanType AutoGammaImage(Image *image)
cristy3ed852e2009-09-05 21:47:34 +000093%
94% A description of each parameter follows:
95%
96% o image: The image to auto-level
97%
cristy3ed852e2009-09-05 21:47:34 +000098*/
cristy3ed852e2009-09-05 21:47:34 +000099MagickExport MagickBooleanType AutoGammaImage(Image *image)
100{
cristy3ed852e2009-09-05 21:47:34 +0000101 MagickStatusType
102 status;
103
104 double
cristy4c08aed2011-07-01 19:47:50 +0000105 gamma,
106 log_mean,
107 mean,
108 sans;
anthony4efe5972009-09-11 06:46:40 +0000109
cristy4c08aed2011-07-01 19:47:50 +0000110 log_mean=log(0.5);
cristyab015852011-07-06 01:03:32 +0000111 if (image->sync != MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +0000112 {
113 /*
cristya30d9ba2011-07-23 21:00:48 +0000114 Apply gamma correction equally accross all given channels.
cristy3ed852e2009-09-05 21:47:34 +0000115 */
cristyd42d9952011-07-08 14:21:50 +0000116 (void) GetImageMean(image,&mean,&sans,&image->exception);
cristy4c08aed2011-07-01 19:47:50 +0000117 gamma=log(mean*QuantumScale)/log_mean;
cristyf89cb1d2011-07-07 01:24:37 +0000118 return(LevelImage(image,0.0,(double) QuantumRange,gamma));
cristy3ed852e2009-09-05 21:47:34 +0000119 }
cristy3ed852e2009-09-05 21:47:34 +0000120 /*
cristy4c08aed2011-07-01 19:47:50 +0000121 Auto-gamma each channel separately.
cristy3ed852e2009-09-05 21:47:34 +0000122 */
cristy4c08aed2011-07-01 19:47:50 +0000123 status=MagickTrue;
cristyed231572011-07-14 02:18:59 +0000124 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +0000125 {
cristyed231572011-07-14 02:18:59 +0000126 PushPixelChannelMap(image,RedChannel);
cristyd42d9952011-07-08 14:21:50 +0000127 (void) GetImageMean(image,&mean,&sans,&image->exception);
128 gamma=log(mean*QuantumScale)/log_mean;
cristy490408a2011-07-07 14:42:05 +0000129 status=status && LevelImage(image,0.0,(double) QuantumRange,gamma);
cristyed231572011-07-14 02:18:59 +0000130 PopPixelChannelMap(image);
cristy3ed852e2009-09-05 21:47:34 +0000131 }
cristyed231572011-07-14 02:18:59 +0000132 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +0000133 {
cristyed231572011-07-14 02:18:59 +0000134 PushPixelChannelMap(image,GreenChannel);
cristyd42d9952011-07-08 14:21:50 +0000135 (void) GetImageMean(image,&mean,&sans,&image->exception);
136 gamma=log(mean*QuantumScale)/log_mean;
cristy490408a2011-07-07 14:42:05 +0000137 status=status && LevelImage(image,0.0,(double) QuantumRange,gamma);
cristyed231572011-07-14 02:18:59 +0000138 PopPixelChannelMap(image);
cristy3ed852e2009-09-05 21:47:34 +0000139 }
cristyed231572011-07-14 02:18:59 +0000140 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +0000141 {
cristyed231572011-07-14 02:18:59 +0000142 PushPixelChannelMap(image,BlueChannel);
cristyd42d9952011-07-08 14:21:50 +0000143 (void) GetImageMean(image,&mean,&sans,&image->exception);
144 gamma=log(mean*QuantumScale)/log_mean;
cristy490408a2011-07-07 14:42:05 +0000145 status=status && LevelImage(image,0.0,(double) QuantumRange,gamma);
cristyed231572011-07-14 02:18:59 +0000146 PopPixelChannelMap(image);
cristy4c08aed2011-07-01 19:47:50 +0000147 }
cristyed231572011-07-14 02:18:59 +0000148 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +0000149 (image->colorspace == CMYKColorspace))
150 {
cristyed231572011-07-14 02:18:59 +0000151 PushPixelChannelMap(image,BlackChannel);
cristyd42d9952011-07-08 14:21:50 +0000152 (void) GetImageMean(image,&mean,&sans,&image->exception);
153 gamma=log(mean*QuantumScale)/log_mean;
cristy490408a2011-07-07 14:42:05 +0000154 status=status && LevelImage(image,0.0,(double) QuantumRange,gamma);
cristyed231572011-07-14 02:18:59 +0000155 PopPixelChannelMap(image);
cristy3ed852e2009-09-05 21:47:34 +0000156 }
cristyed231572011-07-14 02:18:59 +0000157 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +0000158 (image->matte == MagickTrue))
159 {
cristyed231572011-07-14 02:18:59 +0000160 PushPixelChannelMap(image,AlphaChannel);
cristyd42d9952011-07-08 14:21:50 +0000161 (void) GetImageMean(image,&mean,&sans,&image->exception);
162 gamma=log(mean*QuantumScale)/log_mean;
cristy490408a2011-07-07 14:42:05 +0000163 status=status && LevelImage(image,0.0,(double) QuantumRange,gamma);
cristyed231572011-07-14 02:18:59 +0000164 PopPixelChannelMap(image);
cristy3ed852e2009-09-05 21:47:34 +0000165 }
166 return(status != 0 ? MagickTrue : MagickFalse);
167}
168
169/*
170%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
171% %
172% %
173% %
174% A u t o L e v e l I m a g e %
175% %
176% %
177% %
178%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
179%
180% AutoLevelImage() adjusts the levels of a particular image channel by
181% scaling the minimum and maximum values to the full quantum range.
182%
183% The format of the LevelImage method is:
184%
185% MagickBooleanType AutoLevelImage(Image *image)
cristy3ed852e2009-09-05 21:47:34 +0000186%
187% A description of each parameter follows:
188%
189% o image: The image to auto-level
190%
cristy3ed852e2009-09-05 21:47:34 +0000191*/
cristy3ed852e2009-09-05 21:47:34 +0000192MagickExport MagickBooleanType AutoLevelImage(Image *image)
193{
cristy490408a2011-07-07 14:42:05 +0000194 return(MinMaxStretchImage(image,0.0,0.0));
cristy3ed852e2009-09-05 21:47:34 +0000195}
196
197/*
198%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
199% %
200% %
201% %
cristya28d6b82010-01-11 20:03:47 +0000202% B r i g h t n e s s C o n t r a s t I m a g e %
203% %
204% %
205% %
206%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
207%
cristyf4356f92011-08-01 15:33:48 +0000208% BrightnessContrastImage() changes the brightness and/or contrast of an
209% image. It converts the brightness and contrast parameters into slope and
210% intercept and calls a polynomical function to apply to the image.
cristya28d6b82010-01-11 20:03:47 +0000211%
212% The format of the BrightnessContrastImage method is:
213%
214% MagickBooleanType BrightnessContrastImage(Image *image,
215% const double brightness,const double contrast)
cristya28d6b82010-01-11 20:03:47 +0000216%
217% A description of each parameter follows:
218%
219% o image: the image.
220%
cristya28d6b82010-01-11 20:03:47 +0000221% o brightness: the brightness percent (-100 .. 100).
222%
223% o contrast: the contrast percent (-100 .. 100).
224%
225*/
cristya28d6b82010-01-11 20:03:47 +0000226MagickExport MagickBooleanType BrightnessContrastImage(Image *image,
227 const double brightness,const double contrast)
228{
cristya28d6b82010-01-11 20:03:47 +0000229#define BrightnessContastImageTag "BrightnessContast/Image"
230
231 double
232 alpha,
233 intercept,
234 coefficients[2],
235 slope;
236
237 MagickBooleanType
238 status;
239
240 /*
241 Compute slope and intercept.
242 */
243 assert(image != (Image *) NULL);
244 assert(image->signature == MagickSignature);
245 if (image->debug != MagickFalse)
246 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
247 alpha=contrast;
cristy4205a3c2010-09-12 20:19:59 +0000248 slope=tan((double) (MagickPI*(alpha/100.0+1.0)/4.0));
cristya28d6b82010-01-11 20:03:47 +0000249 if (slope < 0.0)
250 slope=0.0;
251 intercept=brightness/100.0+((100-brightness)/200.0)*(1.0-slope);
252 coefficients[0]=slope;
253 coefficients[1]=intercept;
cristyd42d9952011-07-08 14:21:50 +0000254 status=FunctionImage(image,PolynomialFunction,2,coefficients,
255 &image->exception);
cristya28d6b82010-01-11 20:03:47 +0000256 return(status);
257}
258
259/*
260%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
261% %
262% %
263% %
cristy3ed852e2009-09-05 21:47:34 +0000264% C o l o r D e c i s i o n L i s t I m a g e %
265% %
266% %
267% %
268%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
269%
270% ColorDecisionListImage() accepts a lightweight Color Correction Collection
271% (CCC) file which solely contains one or more color corrections and applies
272% the correction to the image. Here is a sample CCC file:
273%
274% <ColorCorrectionCollection xmlns="urn:ASC:CDL:v1.2">
275% <ColorCorrection id="cc03345">
276% <SOPNode>
277% <Slope> 0.9 1.2 0.5 </Slope>
278% <Offset> 0.4 -0.5 0.6 </Offset>
279% <Power> 1.0 0.8 1.5 </Power>
280% </SOPNode>
281% <SATNode>
282% <Saturation> 0.85 </Saturation>
283% </SATNode>
284% </ColorCorrection>
285% </ColorCorrectionCollection>
286%
287% which includes the slop, offset, and power for each of the RGB channels
288% as well as the saturation.
289%
290% The format of the ColorDecisionListImage method is:
291%
292% MagickBooleanType ColorDecisionListImage(Image *image,
293% const char *color_correction_collection)
294%
295% A description of each parameter follows:
296%
297% o image: the image.
298%
299% o color_correction_collection: the color correction collection in XML.
300%
301*/
302MagickExport MagickBooleanType ColorDecisionListImage(Image *image,
303 const char *color_correction_collection)
304{
305#define ColorDecisionListCorrectImageTag "ColorDecisionList/Image"
306
307 typedef struct _Correction
308 {
309 double
310 slope,
311 offset,
312 power;
313 } Correction;
314
315 typedef struct _ColorCorrection
316 {
317 Correction
318 red,
319 green,
320 blue;
321
322 double
323 saturation;
324 } ColorCorrection;
325
cristyc4c8d132010-01-07 01:58:38 +0000326 CacheView
327 *image_view;
328
cristy3ed852e2009-09-05 21:47:34 +0000329 char
330 token[MaxTextExtent];
331
332 ColorCorrection
333 color_correction;
334
335 const char
336 *content,
337 *p;
338
339 ExceptionInfo
340 *exception;
341
cristy3ed852e2009-09-05 21:47:34 +0000342 MagickBooleanType
343 status;
344
cristybb503372010-05-27 20:51:26 +0000345 MagickOffsetType
346 progress;
347
cristy3ed852e2009-09-05 21:47:34 +0000348 PixelPacket
349 *cdl_map;
350
cristybb503372010-05-27 20:51:26 +0000351 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000352 i;
353
cristybb503372010-05-27 20:51:26 +0000354 ssize_t
355 y;
356
cristy3ed852e2009-09-05 21:47:34 +0000357 XMLTreeInfo
358 *cc,
359 *ccc,
360 *sat,
361 *sop;
362
cristy3ed852e2009-09-05 21:47:34 +0000363 /*
364 Allocate and initialize cdl maps.
365 */
366 assert(image != (Image *) NULL);
367 assert(image->signature == MagickSignature);
368 if (image->debug != MagickFalse)
369 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
370 if (color_correction_collection == (const char *) NULL)
371 return(MagickFalse);
372 ccc=NewXMLTree((const char *) color_correction_collection,&image->exception);
373 if (ccc == (XMLTreeInfo *) NULL)
374 return(MagickFalse);
375 cc=GetXMLTreeChild(ccc,"ColorCorrection");
376 if (cc == (XMLTreeInfo *) NULL)
377 {
378 ccc=DestroyXMLTree(ccc);
379 return(MagickFalse);
380 }
381 color_correction.red.slope=1.0;
382 color_correction.red.offset=0.0;
383 color_correction.red.power=1.0;
384 color_correction.green.slope=1.0;
385 color_correction.green.offset=0.0;
386 color_correction.green.power=1.0;
387 color_correction.blue.slope=1.0;
388 color_correction.blue.offset=0.0;
389 color_correction.blue.power=1.0;
390 color_correction.saturation=0.0;
391 sop=GetXMLTreeChild(cc,"SOPNode");
392 if (sop != (XMLTreeInfo *) NULL)
393 {
394 XMLTreeInfo
395 *offset,
396 *power,
397 *slope;
398
399 slope=GetXMLTreeChild(sop,"Slope");
400 if (slope != (XMLTreeInfo *) NULL)
401 {
402 content=GetXMLTreeContent(slope);
403 p=(const char *) content;
404 for (i=0; (*p != '\0') && (i < 3); i++)
405 {
406 GetMagickToken(p,&p,token);
407 if (*token == ',')
408 GetMagickToken(p,&p,token);
409 switch (i)
410 {
cristyc1acd842011-05-19 23:05:47 +0000411 case 0:
412 {
413 color_correction.red.slope=InterpretLocaleValue(token,
414 (char **) NULL);
415 break;
416 }
417 case 1:
418 {
419 color_correction.green.slope=InterpretLocaleValue(token,
420 (char **) NULL);
421 break;
422 }
423 case 2:
424 {
425 color_correction.blue.slope=InterpretLocaleValue(token,
426 (char **) NULL);
427 break;
428 }
cristy3ed852e2009-09-05 21:47:34 +0000429 }
430 }
431 }
432 offset=GetXMLTreeChild(sop,"Offset");
433 if (offset != (XMLTreeInfo *) NULL)
434 {
435 content=GetXMLTreeContent(offset);
436 p=(const char *) content;
437 for (i=0; (*p != '\0') && (i < 3); i++)
438 {
439 GetMagickToken(p,&p,token);
440 if (*token == ',')
441 GetMagickToken(p,&p,token);
442 switch (i)
443 {
cristyc1acd842011-05-19 23:05:47 +0000444 case 0:
445 {
446 color_correction.red.offset=InterpretLocaleValue(token,
447 (char **) NULL);
448 break;
449 }
450 case 1:
451 {
452 color_correction.green.offset=InterpretLocaleValue(token,
453 (char **) NULL);
454 break;
455 }
456 case 2:
457 {
458 color_correction.blue.offset=InterpretLocaleValue(token,
459 (char **) NULL);
460 break;
461 }
cristy3ed852e2009-09-05 21:47:34 +0000462 }
463 }
464 }
465 power=GetXMLTreeChild(sop,"Power");
466 if (power != (XMLTreeInfo *) NULL)
467 {
468 content=GetXMLTreeContent(power);
469 p=(const char *) content;
470 for (i=0; (*p != '\0') && (i < 3); i++)
471 {
472 GetMagickToken(p,&p,token);
473 if (*token == ',')
474 GetMagickToken(p,&p,token);
475 switch (i)
476 {
cristyc1acd842011-05-19 23:05:47 +0000477 case 0:
478 {
479 color_correction.red.power=InterpretLocaleValue(token,
480 (char **) NULL);
481 break;
482 }
483 case 1:
484 {
485 color_correction.green.power=InterpretLocaleValue(token,
486 (char **) NULL);
487 break;
488 }
489 case 2:
490 {
491 color_correction.blue.power=InterpretLocaleValue(token,
492 (char **) NULL);
493 break;
494 }
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);
cristyc1acd842011-05-19 23:05:47 +0000511 color_correction.saturation=InterpretLocaleValue(token,
512 (char **) NULL);
cristy3ed852e2009-09-05 21:47:34 +0000513 }
514 }
515 ccc=DestroyXMLTree(ccc);
516 if (image->debug != MagickFalse)
517 {
518 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
519 " Color Correction Collection:");
520 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000521 " color_correction.red.slope: %g",color_correction.red.slope);
cristy3ed852e2009-09-05 21:47:34 +0000522 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000523 " color_correction.red.offset: %g",color_correction.red.offset);
cristy3ed852e2009-09-05 21:47:34 +0000524 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000525 " color_correction.red.power: %g",color_correction.red.power);
cristy3ed852e2009-09-05 21:47:34 +0000526 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000527 " color_correction.green.slope: %g",color_correction.green.slope);
cristy3ed852e2009-09-05 21:47:34 +0000528 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000529 " color_correction.green.offset: %g",color_correction.green.offset);
cristy3ed852e2009-09-05 21:47:34 +0000530 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000531 " color_correction.green.power: %g",color_correction.green.power);
cristy3ed852e2009-09-05 21:47:34 +0000532 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000533 " color_correction.blue.slope: %g",color_correction.blue.slope);
cristy3ed852e2009-09-05 21:47:34 +0000534 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000535 " color_correction.blue.offset: %g",color_correction.blue.offset);
cristy3ed852e2009-09-05 21:47:34 +0000536 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000537 " color_correction.blue.power: %g",color_correction.blue.power);
cristy3ed852e2009-09-05 21:47:34 +0000538 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000539 " color_correction.saturation: %g",color_correction.saturation);
cristy3ed852e2009-09-05 21:47:34 +0000540 }
541 cdl_map=(PixelPacket *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*cdl_map));
542 if (cdl_map == (PixelPacket *) NULL)
543 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
544 image->filename);
cristyb5d5f722009-11-04 03:03:49 +0000545#if defined(MAGICKCORE_OPENMP_SUPPORT)
546 #pragma omp parallel for schedule(dynamic,4)
cristy3ed852e2009-09-05 21:47:34 +0000547#endif
cristybb503372010-05-27 20:51:26 +0000548 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +0000549 {
cristyce70c172010-01-07 17:15:30 +0000550 cdl_map[i].red=ClampToQuantum((MagickRealType) ScaleMapToQuantum((
cristy3ed852e2009-09-05 21:47:34 +0000551 MagickRealType) (MaxMap*(pow(color_correction.red.slope*i/MaxMap+
552 color_correction.red.offset,color_correction.red.power)))));
cristyce70c172010-01-07 17:15:30 +0000553 cdl_map[i].green=ClampToQuantum((MagickRealType) ScaleMapToQuantum((
cristy3ed852e2009-09-05 21:47:34 +0000554 MagickRealType) (MaxMap*(pow(color_correction.green.slope*i/MaxMap+
555 color_correction.green.offset,color_correction.green.power)))));
cristyce70c172010-01-07 17:15:30 +0000556 cdl_map[i].blue=ClampToQuantum((MagickRealType) ScaleMapToQuantum((
cristy3ed852e2009-09-05 21:47:34 +0000557 MagickRealType) (MaxMap*(pow(color_correction.blue.slope*i/MaxMap+
558 color_correction.blue.offset,color_correction.blue.power)))));
559 }
560 if (image->storage_class == PseudoClass)
561 {
562 /*
563 Apply transfer function to colormap.
564 */
cristyb5d5f722009-11-04 03:03:49 +0000565#if defined(MAGICKCORE_OPENMP_SUPPORT)
566 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000567#endif
cristybb503372010-05-27 20:51:26 +0000568 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +0000569 {
570 double
571 luma;
572
573 luma=0.2126*image->colormap[i].red+0.7152*image->colormap[i].green+
574 0.0722*image->colormap[i].blue;
cristyce70c172010-01-07 17:15:30 +0000575 image->colormap[i].red=ClampToQuantum(luma+color_correction.saturation*
cristy3ed852e2009-09-05 21:47:34 +0000576 cdl_map[ScaleQuantumToMap(image->colormap[i].red)].red-luma);
cristyce70c172010-01-07 17:15:30 +0000577 image->colormap[i].green=ClampToQuantum(luma+
cristy3ed852e2009-09-05 21:47:34 +0000578 color_correction.saturation*cdl_map[ScaleQuantumToMap(
579 image->colormap[i].green)].green-luma);
cristyce70c172010-01-07 17:15:30 +0000580 image->colormap[i].blue=ClampToQuantum(luma+color_correction.saturation*
cristy3ed852e2009-09-05 21:47:34 +0000581 cdl_map[ScaleQuantumToMap(image->colormap[i].blue)].blue-luma);
582 }
583 }
584 /*
585 Apply transfer function to image.
586 */
587 status=MagickTrue;
588 progress=0;
589 exception=(&image->exception);
590 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +0000591#if defined(MAGICKCORE_OPENMP_SUPPORT)
592 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000593#endif
cristybb503372010-05-27 20:51:26 +0000594 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000595 {
596 double
597 luma;
598
cristy4c08aed2011-07-01 19:47:50 +0000599 register Quantum
cristyc47d1f82009-11-26 01:44:43 +0000600 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000601
cristy8d4629b2010-08-30 17:59:46 +0000602 register ssize_t
603 x;
604
cristy3ed852e2009-09-05 21:47:34 +0000605 if (status == MagickFalse)
606 continue;
607 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +0000608 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +0000609 {
610 status=MagickFalse;
611 continue;
612 }
cristybb503372010-05-27 20:51:26 +0000613 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000614 {
cristy4c08aed2011-07-01 19:47:50 +0000615 luma=0.2126*GetPixelRed(image,q)+0.7152*GetPixelGreen(image,q)+0.0722*
616 GetPixelBlue(image,q);
617 SetPixelRed(image,ClampToQuantum(luma+color_correction.saturation*
618 (cdl_map[ScaleQuantumToMap(GetPixelRed(image,q))].red-luma)),q);
619 SetPixelGreen(image,ClampToQuantum(luma+color_correction.saturation*
620 (cdl_map[ScaleQuantumToMap(GetPixelGreen(image,q))].green-luma)),q);
621 SetPixelBlue(image,ClampToQuantum(luma+color_correction.saturation*
622 (cdl_map[ScaleQuantumToMap(GetPixelBlue(image,q))].blue-luma)),q);
cristyed231572011-07-14 02:18:59 +0000623 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +0000624 }
625 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
626 status=MagickFalse;
627 if (image->progress_monitor != (MagickProgressMonitor) NULL)
628 {
629 MagickBooleanType
630 proceed;
631
cristyb5d5f722009-11-04 03:03:49 +0000632#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +0000633 #pragma omp critical (MagickCore_ColorDecisionListImageChannel)
634#endif
635 proceed=SetImageProgress(image,ColorDecisionListCorrectImageTag,
636 progress++,image->rows);
637 if (proceed == MagickFalse)
638 status=MagickFalse;
639 }
640 }
641 image_view=DestroyCacheView(image_view);
642 cdl_map=(PixelPacket *) RelinquishMagickMemory(cdl_map);
643 return(status);
644}
645
646/*
647%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
648% %
649% %
650% %
651% C l u t I m a g e %
652% %
653% %
654% %
655%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
656%
657% ClutImage() replaces each color value in the given image, by using it as an
658% index to lookup a replacement color value in a Color Look UP Table in the
cristycee97112010-05-28 00:44:52 +0000659% form of an image. The values are extracted along a diagonal of the CLUT
cristy3ed852e2009-09-05 21:47:34 +0000660% image so either a horizontal or vertial gradient image can be used.
661%
662% Typically this is used to either re-color a gray-scale image according to a
663% color gradient in the CLUT image, or to perform a freeform histogram
664% (level) adjustment according to the (typically gray-scale) gradient in the
665% CLUT image.
666%
667% When the 'channel' mask includes the matte/alpha transparency channel but
668% one image has no such channel it is assumed that that image is a simple
669% gray-scale image that will effect the alpha channel values, either for
670% gray-scale coloring (with transparent or semi-transparent colors), or
671% a histogram adjustment of existing alpha channel values. If both images
672% have matte channels, direct and normal indexing is applied, which is rarely
673% used.
674%
675% The format of the ClutImage method is:
676%
677% MagickBooleanType ClutImage(Image *image,Image *clut_image)
cristy3ed852e2009-09-05 21:47:34 +0000678%
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*/
cristy3ed852e2009-09-05 21:47:34 +0000688MagickExport MagickBooleanType ClutImage(Image *image,const Image *clut_image)
689{
cristyed231572011-07-14 02:18:59 +0000690#define ClampAlphaPixelChannel(pixel) ClampToQuantum((pixel)->alpha)
691#define ClampBlackPixelChannel(pixel) ClampToQuantum((pixel)->black)
692#define ClampBluePixelChannel(pixel) ClampToQuantum((pixel)->blue)
693#define ClampGreenPixelChannel(pixel) ClampToQuantum((pixel)->green)
694#define ClampRedPixelChannel(pixel) ClampToQuantum((pixel)->red)
cristy3ed852e2009-09-05 21:47:34 +0000695#define ClutImageTag "Clut/Image"
696
cristyfa112112010-01-04 17:48:07 +0000697 CacheView
cristy708333f2011-03-26 01:25:07 +0000698 *clut_view,
cristyfa112112010-01-04 17:48:07 +0000699 *image_view;
700
cristy3ed852e2009-09-05 21:47:34 +0000701 ExceptionInfo
702 *exception;
703
cristy3ed852e2009-09-05 21:47:34 +0000704 MagickBooleanType
705 status;
706
cristybb503372010-05-27 20:51:26 +0000707 MagickOffsetType
708 progress;
709
cristy4c08aed2011-07-01 19:47:50 +0000710 PixelInfo
cristy49f37242011-03-22 18:18:23 +0000711 *clut_map;
712
713 register ssize_t
714 i;
cristy3ed852e2009-09-05 21:47:34 +0000715
cristybb503372010-05-27 20:51:26 +0000716 ssize_t
717 adjust,
718 y;
719
cristy3ed852e2009-09-05 21:47:34 +0000720 assert(image != (Image *) NULL);
721 assert(image->signature == MagickSignature);
722 if (image->debug != MagickFalse)
723 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
724 assert(clut_image != (Image *) NULL);
725 assert(clut_image->signature == MagickSignature);
726 if (SetImageStorageClass(image,DirectClass) == MagickFalse)
727 return(MagickFalse);
cristy4c08aed2011-07-01 19:47:50 +0000728 clut_map=(PixelInfo *) AcquireQuantumMemory(MaxMap+1UL,
cristy49f37242011-03-22 18:18:23 +0000729 sizeof(*clut_map));
cristy4c08aed2011-07-01 19:47:50 +0000730 if (clut_map == (PixelInfo *) NULL)
cristy49f37242011-03-22 18:18:23 +0000731 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
732 image->filename);
cristy3ed852e2009-09-05 21:47:34 +0000733 /*
734 Clut image.
735 */
736 status=MagickTrue;
737 progress=0;
cristybb503372010-05-27 20:51:26 +0000738 adjust=(ssize_t) (clut_image->interpolate == IntegerInterpolatePixel ? 0 : 1);
cristy3ed852e2009-09-05 21:47:34 +0000739 exception=(&image->exception);
cristy708333f2011-03-26 01:25:07 +0000740 clut_view=AcquireCacheView(clut_image);
cristyaf6bc722011-03-25 19:16:14 +0000741#if defined(MAGICKCORE_OPENMP_SUPPORT)
742 #pragma omp parallel for schedule(dynamic,4)
743#endif
cristy49f37242011-03-22 18:18:23 +0000744 for (i=0; i <= (ssize_t) MaxMap; i++)
745 {
cristy4c08aed2011-07-01 19:47:50 +0000746 GetPixelInfo(clut_image,clut_map+i);
747 (void) InterpolatePixelInfo(clut_image,clut_view,
cristy8a7c3e82011-03-26 02:10:53 +0000748 UndefinedInterpolatePixel,QuantumScale*i*(clut_image->columns-adjust),
749 QuantumScale*i*(clut_image->rows-adjust),clut_map+i,exception);
cristy49f37242011-03-22 18:18:23 +0000750 }
cristy708333f2011-03-26 01:25:07 +0000751 clut_view=DestroyCacheView(clut_view);
752 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +0000753#if defined(MAGICKCORE_OPENMP_SUPPORT)
754 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000755#endif
cristybb503372010-05-27 20:51:26 +0000756 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000757 {
cristy4c08aed2011-07-01 19:47:50 +0000758 PixelInfo
cristy3635df22011-03-25 00:16:16 +0000759 pixel;
760
cristy4c08aed2011-07-01 19:47:50 +0000761 register Quantum
cristyc47d1f82009-11-26 01:44:43 +0000762 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000763
cristy8d4629b2010-08-30 17:59:46 +0000764 register ssize_t
765 x;
766
cristy3ed852e2009-09-05 21:47:34 +0000767 if (status == MagickFalse)
768 continue;
769 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +0000770 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +0000771 {
772 status=MagickFalse;
773 continue;
774 }
cristy4c08aed2011-07-01 19:47:50 +0000775 GetPixelInfo(image,&pixel);
cristybb503372010-05-27 20:51:26 +0000776 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000777 {
cristy4c08aed2011-07-01 19:47:50 +0000778 SetPixelInfo(image,q,&pixel);
cristyed231572011-07-14 02:18:59 +0000779 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
780 SetPixelRed(image,ClampRedPixelChannel(clut_map+
cristy4c08aed2011-07-01 19:47:50 +0000781 ScaleQuantumToMap(GetPixelRed(image,q))),q);
cristyed231572011-07-14 02:18:59 +0000782 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
783 SetPixelGreen(image,ClampGreenPixelChannel(clut_map+
cristy4c08aed2011-07-01 19:47:50 +0000784 ScaleQuantumToMap(GetPixelGreen(image,q))),q);
cristyed231572011-07-14 02:18:59 +0000785 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
786 SetPixelBlue(image,ClampBluePixelChannel(clut_map+
cristy4c08aed2011-07-01 19:47:50 +0000787 ScaleQuantumToMap(GetPixelBlue(image,q))),q);
cristyed231572011-07-14 02:18:59 +0000788 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +0000789 (image->colorspace == CMYKColorspace))
cristyed231572011-07-14 02:18:59 +0000790 SetPixelBlack(image,ClampBlackPixelChannel(clut_map+
cristy4c08aed2011-07-01 19:47:50 +0000791 ScaleQuantumToMap(GetPixelBlack(image,q))),q);
cristyed231572011-07-14 02:18:59 +0000792 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy3635df22011-03-25 00:16:16 +0000793 {
794 if (clut_image->matte == MagickFalse)
cristy4c08aed2011-07-01 19:47:50 +0000795 SetPixelAlpha(image,GetPixelInfoIntensity(clut_map+
796 ScaleQuantumToMap((Quantum) GetPixelAlpha(image,q))),q);
cristy3635df22011-03-25 00:16:16 +0000797 else
798 if (image->matte == MagickFalse)
cristyed231572011-07-14 02:18:59 +0000799 SetPixelAlpha(image,ClampAlphaPixelChannel(clut_map+
cristy4c08aed2011-07-01 19:47:50 +0000800 ScaleQuantumToMap((Quantum) GetPixelInfoIntensity(&pixel))),q);
cristy3635df22011-03-25 00:16:16 +0000801 else
cristyed231572011-07-14 02:18:59 +0000802 SetPixelAlpha(image,ClampAlphaPixelChannel(clut_map+
cristy4c08aed2011-07-01 19:47:50 +0000803 ScaleQuantumToMap(GetPixelAlpha(image,q))),q);
cristy3635df22011-03-25 00:16:16 +0000804 }
cristyed231572011-07-14 02:18:59 +0000805 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +0000806 }
807 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
808 status=MagickFalse;
809 if (image->progress_monitor != (MagickProgressMonitor) NULL)
810 {
811 MagickBooleanType
812 proceed;
813
cristyb5d5f722009-11-04 03:03:49 +0000814#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf89cb1d2011-07-07 01:24:37 +0000815 #pragma omp critical (MagickCore_ClutImage)
cristy3ed852e2009-09-05 21:47:34 +0000816#endif
817 proceed=SetImageProgress(image,ClutImageTag,progress++,image->rows);
818 if (proceed == MagickFalse)
819 status=MagickFalse;
820 }
821 }
822 image_view=DestroyCacheView(image_view);
cristy4c08aed2011-07-01 19:47:50 +0000823 clut_map=(PixelInfo *) RelinquishMagickMemory(clut_map);
cristy2b9582a2011-07-04 17:38:56 +0000824 if ((clut_image->matte != MagickFalse) &&
cristyed231572011-07-14 02:18:59 +0000825 ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0))
cristy3ed852e2009-09-05 21:47:34 +0000826 (void) SetImageAlphaChannel(image,ActivateAlphaChannel);
827 return(status);
828}
829
830/*
831%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
832% %
833% %
834% %
835% C o n t r a s t I m a g e %
836% %
837% %
838% %
839%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
840%
841% ContrastImage() enhances the intensity differences between the lighter and
842% darker elements of the image. Set sharpen to a MagickTrue to increase the
843% image contrast otherwise the contrast is reduced.
844%
845% The format of the ContrastImage method is:
846%
847% MagickBooleanType ContrastImage(Image *image,
848% const MagickBooleanType sharpen)
849%
850% A description of each parameter follows:
851%
852% o image: the image.
853%
854% o sharpen: Increase or decrease image contrast.
855%
856*/
857
858static void Contrast(const int sign,Quantum *red,Quantum *green,Quantum *blue)
859{
860 double
861 brightness,
862 hue,
863 saturation;
864
865 /*
866 Enhance contrast: dark color become darker, light color become lighter.
867 */
868 assert(red != (Quantum *) NULL);
869 assert(green != (Quantum *) NULL);
870 assert(blue != (Quantum *) NULL);
871 hue=0.0;
872 saturation=0.0;
873 brightness=0.0;
874 ConvertRGBToHSB(*red,*green,*blue,&hue,&saturation,&brightness);
cristy31ac0f02011-03-12 02:04:47 +0000875 brightness+=0.5*sign*(0.5*(sin((double) (MagickPI*(brightness-0.5)))+1.0)-
cristy4205a3c2010-09-12 20:19:59 +0000876 brightness);
cristy3ed852e2009-09-05 21:47:34 +0000877 if (brightness > 1.0)
878 brightness=1.0;
879 else
880 if (brightness < 0.0)
881 brightness=0.0;
882 ConvertHSBToRGB(hue,saturation,brightness,red,green,blue);
883}
884
885MagickExport MagickBooleanType ContrastImage(Image *image,
886 const MagickBooleanType sharpen)
887{
888#define ContrastImageTag "Contrast/Image"
889
cristyc4c8d132010-01-07 01:58:38 +0000890 CacheView
891 *image_view;
892
cristy3ed852e2009-09-05 21:47:34 +0000893 ExceptionInfo
894 *exception;
895
896 int
897 sign;
898
cristy3ed852e2009-09-05 21:47:34 +0000899 MagickBooleanType
900 status;
901
cristybb503372010-05-27 20:51:26 +0000902 MagickOffsetType
903 progress;
904
905 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000906 i;
907
cristybb503372010-05-27 20:51:26 +0000908 ssize_t
909 y;
910
cristy3ed852e2009-09-05 21:47:34 +0000911 assert(image != (Image *) NULL);
912 assert(image->signature == MagickSignature);
913 if (image->debug != MagickFalse)
914 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
915 sign=sharpen != MagickFalse ? 1 : -1;
916 if (image->storage_class == PseudoClass)
917 {
918 /*
919 Contrast enhance colormap.
920 */
cristybb503372010-05-27 20:51:26 +0000921 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +0000922 Contrast(sign,&image->colormap[i].red,&image->colormap[i].green,
923 &image->colormap[i].blue);
924 }
925 /*
926 Contrast enhance image.
927 */
928 status=MagickTrue;
929 progress=0;
930 exception=(&image->exception);
931 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +0000932#if defined(MAGICKCORE_OPENMP_SUPPORT)
933 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000934#endif
cristybb503372010-05-27 20:51:26 +0000935 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000936 {
cristy5afeab82011-04-30 01:30:09 +0000937 Quantum
938 blue,
939 green,
940 red;
941
cristy4c08aed2011-07-01 19:47:50 +0000942 register Quantum
cristyc47d1f82009-11-26 01:44:43 +0000943 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000944
cristy8d4629b2010-08-30 17:59:46 +0000945 register ssize_t
946 x;
947
cristy3ed852e2009-09-05 21:47:34 +0000948 if (status == MagickFalse)
949 continue;
950 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +0000951 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +0000952 {
953 status=MagickFalse;
954 continue;
955 }
cristybb503372010-05-27 20:51:26 +0000956 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000957 {
cristy4c08aed2011-07-01 19:47:50 +0000958 red=GetPixelRed(image,q);
959 green=GetPixelGreen(image,q);
960 blue=GetPixelBlue(image,q);
cristy5afeab82011-04-30 01:30:09 +0000961 Contrast(sign,&red,&green,&blue);
cristy4c08aed2011-07-01 19:47:50 +0000962 SetPixelRed(image,red,q);
963 SetPixelGreen(image,green,q);
964 SetPixelBlue(image,blue,q);
cristyed231572011-07-14 02:18:59 +0000965 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +0000966 }
967 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
968 status=MagickFalse;
969 if (image->progress_monitor != (MagickProgressMonitor) NULL)
970 {
971 MagickBooleanType
972 proceed;
973
cristyb5d5f722009-11-04 03:03:49 +0000974#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +0000975 #pragma omp critical (MagickCore_ContrastImage)
976#endif
977 proceed=SetImageProgress(image,ContrastImageTag,progress++,image->rows);
978 if (proceed == MagickFalse)
979 status=MagickFalse;
980 }
981 }
982 image_view=DestroyCacheView(image_view);
983 return(status);
984}
985
986/*
987%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
988% %
989% %
990% %
991% C o n t r a s t S t r e t c h I m a g e %
992% %
993% %
994% %
995%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
996%
cristyf1611782011-08-01 15:39:13 +0000997% ContrastStretchImage() is a simple image enhancement technique that attempts
998% to improve the contrast in an image by `stretching' the range of intensity
999% values it contains to span a desired range of values. It differs from the
1000% more sophisticated histogram equalization in that it can only apply a
1001% linear scaling function to the image pixel values. As a result the
1002% `enhancement' is less harsh.
cristy3ed852e2009-09-05 21:47:34 +00001003%
1004% The format of the ContrastStretchImage method is:
1005%
1006% MagickBooleanType ContrastStretchImage(Image *image,
1007% const char *levels)
cristy3ed852e2009-09-05 21:47:34 +00001008%
1009% A description of each parameter follows:
1010%
1011% o image: the image.
1012%
cristy3ed852e2009-09-05 21:47:34 +00001013% o black_point: the black point.
1014%
1015% o white_point: the white point.
1016%
1017% o levels: Specify the levels where the black and white points have the
1018% range of 0 to number-of-pixels (e.g. 1%, 10x90%, etc.).
1019%
1020*/
cristy3ed852e2009-09-05 21:47:34 +00001021MagickExport MagickBooleanType ContrastStretchImage(Image *image,
cristy50fbc382011-07-07 02:19:17 +00001022 const double black_point,const double white_point)
cristy3ed852e2009-09-05 21:47:34 +00001023{
1024#define MaxRange(color) ((MagickRealType) ScaleQuantumToMap((Quantum) (color)))
1025#define ContrastStretchImageTag "ContrastStretch/Image"
1026
cristyc4c8d132010-01-07 01:58:38 +00001027 CacheView
1028 *image_view;
1029
cristy3ed852e2009-09-05 21:47:34 +00001030 double
1031 intensity;
1032
1033 ExceptionInfo
1034 *exception;
1035
cristy3ed852e2009-09-05 21:47:34 +00001036 MagickBooleanType
1037 status;
1038
cristybb503372010-05-27 20:51:26 +00001039 MagickOffsetType
1040 progress;
1041
cristy4c08aed2011-07-01 19:47:50 +00001042 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00001043 black,
1044 *histogram,
1045 *stretch_map,
1046 white;
1047
cristybb503372010-05-27 20:51:26 +00001048 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001049 i;
1050
cristybb503372010-05-27 20:51:26 +00001051 ssize_t
1052 y;
1053
cristy3ed852e2009-09-05 21:47:34 +00001054 /*
1055 Allocate histogram and stretch map.
1056 */
1057 assert(image != (Image *) NULL);
1058 assert(image->signature == MagickSignature);
1059 if (image->debug != MagickFalse)
1060 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
cristy4c08aed2011-07-01 19:47:50 +00001061 histogram=(PixelInfo *) AcquireQuantumMemory(MaxMap+1UL,
cristy3ed852e2009-09-05 21:47:34 +00001062 sizeof(*histogram));
cristy4c08aed2011-07-01 19:47:50 +00001063 stretch_map=(PixelInfo *) AcquireQuantumMemory(MaxMap+1UL,
cristy3ed852e2009-09-05 21:47:34 +00001064 sizeof(*stretch_map));
cristy4c08aed2011-07-01 19:47:50 +00001065 if ((histogram == (PixelInfo *) NULL) ||
1066 (stretch_map == (PixelInfo *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00001067 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
1068 image->filename);
1069 /*
1070 Form histogram.
1071 */
1072 status=MagickTrue;
1073 exception=(&image->exception);
1074 (void) ResetMagickMemory(histogram,0,(MaxMap+1)*sizeof(*histogram));
1075 image_view=AcquireCacheView(image);
cristybb503372010-05-27 20:51:26 +00001076 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001077 {
cristy4c08aed2011-07-01 19:47:50 +00001078 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00001079 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00001080
cristybb503372010-05-27 20:51:26 +00001081 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001082 x;
1083
1084 if (status == MagickFalse)
1085 continue;
1086 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00001087 if (p == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001088 {
1089 status=MagickFalse;
1090 continue;
1091 }
cristy50fbc382011-07-07 02:19:17 +00001092 if (image->sync != MagickFalse)
cristybb503372010-05-27 20:51:26 +00001093 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001094 {
1095 Quantum
1096 intensity;
1097
cristy4c08aed2011-07-01 19:47:50 +00001098 intensity=GetPixelIntensity(image,p);
cristy3ed852e2009-09-05 21:47:34 +00001099 histogram[ScaleQuantumToMap(intensity)].red++;
1100 histogram[ScaleQuantumToMap(intensity)].green++;
1101 histogram[ScaleQuantumToMap(intensity)].blue++;
cristy4c08aed2011-07-01 19:47:50 +00001102 histogram[ScaleQuantumToMap(intensity)].black++;
cristyed231572011-07-14 02:18:59 +00001103 p+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00001104 }
1105 else
cristybb503372010-05-27 20:51:26 +00001106 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001107 {
cristyed231572011-07-14 02:18:59 +00001108 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001109 histogram[ScaleQuantumToMap(GetPixelRed(image,p))].red++;
cristyed231572011-07-14 02:18:59 +00001110 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001111 histogram[ScaleQuantumToMap(GetPixelGreen(image,p))].green++;
cristyed231572011-07-14 02:18:59 +00001112 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001113 histogram[ScaleQuantumToMap(GetPixelBlue(image,p))].blue++;
cristyed231572011-07-14 02:18:59 +00001114 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00001115 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +00001116 histogram[ScaleQuantumToMap(GetPixelBlack(image,p))].black++;
cristyed231572011-07-14 02:18:59 +00001117 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001118 histogram[ScaleQuantumToMap(GetPixelAlpha(image,p))].alpha++;
cristyed231572011-07-14 02:18:59 +00001119 p+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00001120 }
1121 }
1122 /*
1123 Find the histogram boundaries by locating the black/white levels.
1124 */
1125 black.red=0.0;
1126 white.red=MaxRange(QuantumRange);
cristyed231572011-07-14 02:18:59 +00001127 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001128 {
1129 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00001130 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +00001131 {
1132 intensity+=histogram[i].red;
1133 if (intensity > black_point)
1134 break;
1135 }
1136 black.red=(MagickRealType) i;
1137 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00001138 for (i=(ssize_t) MaxMap; i != 0; i--)
cristy3ed852e2009-09-05 21:47:34 +00001139 {
1140 intensity+=histogram[i].red;
1141 if (intensity > ((double) image->columns*image->rows-white_point))
1142 break;
1143 }
1144 white.red=(MagickRealType) i;
1145 }
1146 black.green=0.0;
1147 white.green=MaxRange(QuantumRange);
cristyed231572011-07-14 02:18:59 +00001148 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001149 {
1150 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00001151 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +00001152 {
1153 intensity+=histogram[i].green;
1154 if (intensity > black_point)
1155 break;
1156 }
1157 black.green=(MagickRealType) i;
1158 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00001159 for (i=(ssize_t) MaxMap; i != 0; i--)
cristy3ed852e2009-09-05 21:47:34 +00001160 {
1161 intensity+=histogram[i].green;
1162 if (intensity > ((double) image->columns*image->rows-white_point))
1163 break;
1164 }
1165 white.green=(MagickRealType) i;
1166 }
1167 black.blue=0.0;
1168 white.blue=MaxRange(QuantumRange);
cristyed231572011-07-14 02:18:59 +00001169 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001170 {
1171 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00001172 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +00001173 {
1174 intensity+=histogram[i].blue;
1175 if (intensity > black_point)
1176 break;
1177 }
1178 black.blue=(MagickRealType) i;
1179 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00001180 for (i=(ssize_t) MaxMap; i != 0; i--)
cristy3ed852e2009-09-05 21:47:34 +00001181 {
1182 intensity+=histogram[i].blue;
1183 if (intensity > ((double) image->columns*image->rows-white_point))
1184 break;
1185 }
1186 white.blue=(MagickRealType) i;
1187 }
cristy4c08aed2011-07-01 19:47:50 +00001188 black.alpha=0.0;
1189 white.alpha=MaxRange(QuantumRange);
cristyed231572011-07-14 02:18:59 +00001190 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001191 {
1192 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00001193 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +00001194 {
cristy4c08aed2011-07-01 19:47:50 +00001195 intensity+=histogram[i].alpha;
cristy3ed852e2009-09-05 21:47:34 +00001196 if (intensity > black_point)
1197 break;
1198 }
cristy4c08aed2011-07-01 19:47:50 +00001199 black.alpha=(MagickRealType) i;
cristy3ed852e2009-09-05 21:47:34 +00001200 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00001201 for (i=(ssize_t) MaxMap; i != 0; i--)
cristy3ed852e2009-09-05 21:47:34 +00001202 {
cristy4c08aed2011-07-01 19:47:50 +00001203 intensity+=histogram[i].alpha;
cristy3ed852e2009-09-05 21:47:34 +00001204 if (intensity > ((double) image->columns*image->rows-white_point))
1205 break;
1206 }
cristy4c08aed2011-07-01 19:47:50 +00001207 white.alpha=(MagickRealType) i;
cristy3ed852e2009-09-05 21:47:34 +00001208 }
cristy4c08aed2011-07-01 19:47:50 +00001209 black.black=0.0;
1210 white.black=MaxRange(QuantumRange);
cristyed231572011-07-14 02:18:59 +00001211 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) && (image->colorspace == CMYKColorspace))
cristy3ed852e2009-09-05 21:47:34 +00001212 {
1213 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00001214 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +00001215 {
cristy4c08aed2011-07-01 19:47:50 +00001216 intensity+=histogram[i].black;
cristy3ed852e2009-09-05 21:47:34 +00001217 if (intensity > black_point)
1218 break;
1219 }
cristy4c08aed2011-07-01 19:47:50 +00001220 black.black=(MagickRealType) i;
cristy3ed852e2009-09-05 21:47:34 +00001221 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00001222 for (i=(ssize_t) MaxMap; i != 0; i--)
cristy3ed852e2009-09-05 21:47:34 +00001223 {
cristy4c08aed2011-07-01 19:47:50 +00001224 intensity+=histogram[i].black;
cristy3ed852e2009-09-05 21:47:34 +00001225 if (intensity > ((double) image->columns*image->rows-white_point))
1226 break;
1227 }
cristy4c08aed2011-07-01 19:47:50 +00001228 white.black=(MagickRealType) i;
cristy3ed852e2009-09-05 21:47:34 +00001229 }
cristy4c08aed2011-07-01 19:47:50 +00001230 histogram=(PixelInfo *) RelinquishMagickMemory(histogram);
cristy3ed852e2009-09-05 21:47:34 +00001231 /*
1232 Stretch the histogram to create the stretched image mapping.
1233 */
1234 (void) ResetMagickMemory(stretch_map,0,(MaxMap+1)*sizeof(*stretch_map));
cristyb5d5f722009-11-04 03:03:49 +00001235#if defined(MAGICKCORE_OPENMP_SUPPORT)
1236 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001237#endif
cristybb503372010-05-27 20:51:26 +00001238 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +00001239 {
cristyed231572011-07-14 02:18:59 +00001240 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001241 {
cristybb503372010-05-27 20:51:26 +00001242 if (i < (ssize_t) black.red)
cristy3ed852e2009-09-05 21:47:34 +00001243 stretch_map[i].red=0.0;
1244 else
cristybb503372010-05-27 20:51:26 +00001245 if (i > (ssize_t) white.red)
cristy3ed852e2009-09-05 21:47:34 +00001246 stretch_map[i].red=(MagickRealType) QuantumRange;
1247 else
1248 if (black.red != white.red)
1249 stretch_map[i].red=(MagickRealType) ScaleMapToQuantum(
1250 (MagickRealType) (MaxMap*(i-black.red)/(white.red-black.red)));
1251 }
cristyed231572011-07-14 02:18:59 +00001252 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001253 {
cristybb503372010-05-27 20:51:26 +00001254 if (i < (ssize_t) black.green)
cristy3ed852e2009-09-05 21:47:34 +00001255 stretch_map[i].green=0.0;
1256 else
cristybb503372010-05-27 20:51:26 +00001257 if (i > (ssize_t) white.green)
cristy3ed852e2009-09-05 21:47:34 +00001258 stretch_map[i].green=(MagickRealType) QuantumRange;
1259 else
1260 if (black.green != white.green)
1261 stretch_map[i].green=(MagickRealType) ScaleMapToQuantum(
1262 (MagickRealType) (MaxMap*(i-black.green)/(white.green-
1263 black.green)));
1264 }
cristyed231572011-07-14 02:18:59 +00001265 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001266 {
cristybb503372010-05-27 20:51:26 +00001267 if (i < (ssize_t) black.blue)
cristy3ed852e2009-09-05 21:47:34 +00001268 stretch_map[i].blue=0.0;
1269 else
cristybb503372010-05-27 20:51:26 +00001270 if (i > (ssize_t) white.blue)
cristy3ed852e2009-09-05 21:47:34 +00001271 stretch_map[i].blue=(MagickRealType) QuantumRange;
1272 else
1273 if (black.blue != white.blue)
1274 stretch_map[i].blue=(MagickRealType) ScaleMapToQuantum(
1275 (MagickRealType) (MaxMap*(i-black.blue)/(white.blue-
1276 black.blue)));
1277 }
cristyed231572011-07-14 02:18:59 +00001278 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001279 {
cristy4c08aed2011-07-01 19:47:50 +00001280 if (i < (ssize_t) black.alpha)
1281 stretch_map[i].alpha=0.0;
cristy3ed852e2009-09-05 21:47:34 +00001282 else
cristy4c08aed2011-07-01 19:47:50 +00001283 if (i > (ssize_t) white.alpha)
1284 stretch_map[i].alpha=(MagickRealType) QuantumRange;
cristy3ed852e2009-09-05 21:47:34 +00001285 else
cristy4c08aed2011-07-01 19:47:50 +00001286 if (black.alpha != white.alpha)
1287 stretch_map[i].alpha=(MagickRealType) ScaleMapToQuantum(
1288 (MagickRealType) (MaxMap*(i-black.alpha)/(white.alpha-
1289 black.alpha)));
cristy3ed852e2009-09-05 21:47:34 +00001290 }
cristyed231572011-07-14 02:18:59 +00001291 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00001292 (image->colorspace == CMYKColorspace))
1293 {
cristy4c08aed2011-07-01 19:47:50 +00001294 if (i < (ssize_t) black.black)
1295 stretch_map[i].black=0.0;
cristy3ed852e2009-09-05 21:47:34 +00001296 else
cristy4c08aed2011-07-01 19:47:50 +00001297 if (i > (ssize_t) white.black)
1298 stretch_map[i].black=(MagickRealType) QuantumRange;
cristy3ed852e2009-09-05 21:47:34 +00001299 else
cristy4c08aed2011-07-01 19:47:50 +00001300 if (black.black != white.black)
1301 stretch_map[i].black=(MagickRealType) ScaleMapToQuantum(
1302 (MagickRealType) (MaxMap*(i-black.black)/(white.black-
1303 black.black)));
cristy3ed852e2009-09-05 21:47:34 +00001304 }
1305 }
1306 /*
1307 Stretch the image.
1308 */
cristyed231572011-07-14 02:18:59 +00001309 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) || (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00001310 (image->colorspace == CMYKColorspace)))
1311 image->storage_class=DirectClass;
1312 if (image->storage_class == PseudoClass)
1313 {
1314 /*
1315 Stretch colormap.
1316 */
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) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00001321 {
cristyed231572011-07-14 02:18:59 +00001322 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001323 {
1324 if (black.red != white.red)
cristyce70c172010-01-07 17:15:30 +00001325 image->colormap[i].red=ClampToQuantum(stretch_map[
cristy3ed852e2009-09-05 21:47:34 +00001326 ScaleQuantumToMap(image->colormap[i].red)].red);
1327 }
cristyed231572011-07-14 02:18:59 +00001328 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001329 {
1330 if (black.green != white.green)
cristyce70c172010-01-07 17:15:30 +00001331 image->colormap[i].green=ClampToQuantum(stretch_map[
cristy3ed852e2009-09-05 21:47:34 +00001332 ScaleQuantumToMap(image->colormap[i].green)].green);
1333 }
cristyed231572011-07-14 02:18:59 +00001334 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001335 {
1336 if (black.blue != white.blue)
cristyce70c172010-01-07 17:15:30 +00001337 image->colormap[i].blue=ClampToQuantum(stretch_map[
cristy3ed852e2009-09-05 21:47:34 +00001338 ScaleQuantumToMap(image->colormap[i].blue)].blue);
1339 }
cristyed231572011-07-14 02:18:59 +00001340 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001341 {
cristy4c08aed2011-07-01 19:47:50 +00001342 if (black.alpha != white.alpha)
1343 image->colormap[i].alpha=ClampToQuantum(stretch_map[
1344 ScaleQuantumToMap(image->colormap[i].alpha)].alpha);
cristy3ed852e2009-09-05 21:47:34 +00001345 }
1346 }
1347 }
1348 /*
1349 Stretch image.
1350 */
1351 status=MagickTrue;
1352 progress=0;
cristyb5d5f722009-11-04 03:03:49 +00001353#if defined(MAGICKCORE_OPENMP_SUPPORT)
1354 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001355#endif
cristybb503372010-05-27 20:51:26 +00001356 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001357 {
cristy4c08aed2011-07-01 19:47:50 +00001358 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00001359 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001360
cristy8d4629b2010-08-30 17:59:46 +00001361 register ssize_t
1362 x;
1363
cristy3ed852e2009-09-05 21:47:34 +00001364 if (status == MagickFalse)
1365 continue;
1366 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00001367 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001368 {
1369 status=MagickFalse;
1370 continue;
1371 }
cristybb503372010-05-27 20:51:26 +00001372 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001373 {
cristyed231572011-07-14 02:18:59 +00001374 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001375 {
1376 if (black.red != white.red)
cristy4c08aed2011-07-01 19:47:50 +00001377 SetPixelRed(image,ClampToQuantum(stretch_map[ScaleQuantumToMap(
1378 GetPixelRed(image,q))].red),q);
cristy3ed852e2009-09-05 21:47:34 +00001379 }
cristyed231572011-07-14 02:18:59 +00001380 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001381 {
1382 if (black.green != white.green)
cristy4c08aed2011-07-01 19:47:50 +00001383 SetPixelGreen(image,ClampToQuantum(stretch_map[ScaleQuantumToMap(
1384 GetPixelGreen(image,q))].green),q);
cristy3ed852e2009-09-05 21:47:34 +00001385 }
cristyed231572011-07-14 02:18:59 +00001386 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001387 {
1388 if (black.blue != white.blue)
cristy4c08aed2011-07-01 19:47:50 +00001389 SetPixelBlue(image,ClampToQuantum(stretch_map[ScaleQuantumToMap(
1390 GetPixelBlue(image,q))].blue),q);
1391 }
cristyed231572011-07-14 02:18:59 +00001392 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +00001393 (image->colorspace == CMYKColorspace))
1394 {
1395 if (black.black != white.black)
1396 SetPixelBlack(image,ClampToQuantum(stretch_map[ScaleQuantumToMap(
1397 GetPixelBlack(image,q))].black),q);
cristy3ed852e2009-09-05 21:47:34 +00001398 }
cristyed231572011-07-14 02:18:59 +00001399 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001400 {
cristy4c08aed2011-07-01 19:47:50 +00001401 if (black.alpha != white.alpha)
1402 SetPixelAlpha(image,ClampToQuantum(stretch_map[ScaleQuantumToMap(
1403 GetPixelAlpha(image,q))].alpha),q);
cristy3ed852e2009-09-05 21:47:34 +00001404 }
cristyed231572011-07-14 02:18:59 +00001405 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00001406 }
1407 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
1408 status=MagickFalse;
1409 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1410 {
1411 MagickBooleanType
1412 proceed;
1413
cristyb5d5f722009-11-04 03:03:49 +00001414#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy50fbc382011-07-07 02:19:17 +00001415 #pragma omp critical (MagickCore_ContrastStretchImage)
cristy3ed852e2009-09-05 21:47:34 +00001416#endif
1417 proceed=SetImageProgress(image,ContrastStretchImageTag,progress++,
1418 image->rows);
1419 if (proceed == MagickFalse)
1420 status=MagickFalse;
1421 }
1422 }
1423 image_view=DestroyCacheView(image_view);
cristy4c08aed2011-07-01 19:47:50 +00001424 stretch_map=(PixelInfo *) RelinquishMagickMemory(stretch_map);
cristy3ed852e2009-09-05 21:47:34 +00001425 return(status);
1426}
1427
1428/*
1429%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1430% %
1431% %
1432% %
1433% E n h a n c e I m a g e %
1434% %
1435% %
1436% %
1437%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1438%
1439% EnhanceImage() applies a digital filter that improves the quality of a
1440% noisy image.
1441%
1442% The format of the EnhanceImage method is:
1443%
1444% Image *EnhanceImage(const Image *image,ExceptionInfo *exception)
1445%
1446% A description of each parameter follows:
1447%
1448% o image: the image.
1449%
1450% o exception: return any errors or warnings in this structure.
1451%
1452*/
1453MagickExport Image *EnhanceImage(const Image *image,ExceptionInfo *exception)
1454{
1455#define Enhance(weight) \
cristy4c08aed2011-07-01 19:47:50 +00001456 mean=((MagickRealType) GetPixelRed(image,r)+pixel.red)/2; \
1457 distance=(MagickRealType) GetPixelRed(image,r)-(MagickRealType) pixel.red; \
cristy3ed852e2009-09-05 21:47:34 +00001458 distance_squared=QuantumScale*(2.0*((MagickRealType) QuantumRange+1.0)+ \
1459 mean)*distance*distance; \
cristy4c08aed2011-07-01 19:47:50 +00001460 mean=((MagickRealType) GetPixelGreen(image,r)+pixel.green)/2; \
1461 distance=(MagickRealType) GetPixelGreen(image,r)- \
1462 (MagickRealType) pixel.green; \
cristy3ed852e2009-09-05 21:47:34 +00001463 distance_squared+=4.0*distance*distance; \
cristy4c08aed2011-07-01 19:47:50 +00001464 mean=((MagickRealType) GetPixelBlue(image,r)+pixel.blue)/2; \
1465 distance=(MagickRealType) GetPixelBlue(image,r)- \
1466 (MagickRealType) pixel.blue; \
cristy3ed852e2009-09-05 21:47:34 +00001467 distance_squared+=QuantumScale*(3.0*((MagickRealType) \
1468 QuantumRange+1.0)-1.0-mean)*distance*distance; \
cristy4c08aed2011-07-01 19:47:50 +00001469 mean=((MagickRealType) GetPixelAlpha(image,r)+pixel.alpha)/2; \
1470 distance=(MagickRealType) GetPixelAlpha(image,r)-(MagickRealType) pixel.alpha; \
cristy3ed852e2009-09-05 21:47:34 +00001471 distance_squared+=QuantumScale*(3.0*((MagickRealType) \
1472 QuantumRange+1.0)-1.0-mean)*distance*distance; \
1473 if (distance_squared < ((MagickRealType) QuantumRange*(MagickRealType) \
1474 QuantumRange/25.0f)) \
1475 { \
cristy4c08aed2011-07-01 19:47:50 +00001476 aggregate.red+=(weight)*GetPixelRed(image,r); \
1477 aggregate.green+=(weight)*GetPixelGreen(image,r); \
1478 aggregate.blue+=(weight)*GetPixelBlue(image,r); \
1479 aggregate.alpha+=(weight)*GetPixelAlpha(image,r); \
cristy3ed852e2009-09-05 21:47:34 +00001480 total_weight+=(weight); \
1481 } \
1482 r++;
1483#define EnhanceImageTag "Enhance/Image"
1484
cristyc4c8d132010-01-07 01:58:38 +00001485 CacheView
1486 *enhance_view,
1487 *image_view;
1488
cristy3ed852e2009-09-05 21:47:34 +00001489 Image
1490 *enhance_image;
1491
cristy3ed852e2009-09-05 21:47:34 +00001492 MagickBooleanType
1493 status;
1494
cristybb503372010-05-27 20:51:26 +00001495 MagickOffsetType
1496 progress;
1497
cristy4c08aed2011-07-01 19:47:50 +00001498 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00001499 zero;
1500
cristybb503372010-05-27 20:51:26 +00001501 ssize_t
1502 y;
1503
cristy3ed852e2009-09-05 21:47:34 +00001504 /*
1505 Initialize enhanced image attributes.
1506 */
1507 assert(image != (const Image *) NULL);
1508 assert(image->signature == MagickSignature);
1509 if (image->debug != MagickFalse)
1510 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1511 assert(exception != (ExceptionInfo *) NULL);
1512 assert(exception->signature == MagickSignature);
1513 if ((image->columns < 5) || (image->rows < 5))
1514 return((Image *) NULL);
1515 enhance_image=CloneImage(image,image->columns,image->rows,MagickTrue,
1516 exception);
1517 if (enhance_image == (Image *) NULL)
1518 return((Image *) NULL);
1519 if (SetImageStorageClass(enhance_image,DirectClass) == MagickFalse)
1520 {
1521 InheritException(exception,&enhance_image->exception);
1522 enhance_image=DestroyImage(enhance_image);
1523 return((Image *) NULL);
1524 }
1525 /*
1526 Enhance image.
1527 */
1528 status=MagickTrue;
1529 progress=0;
1530 (void) ResetMagickMemory(&zero,0,sizeof(zero));
1531 image_view=AcquireCacheView(image);
1532 enhance_view=AcquireCacheView(enhance_image);
cristyb5d5f722009-11-04 03:03:49 +00001533#if defined(MAGICKCORE_OPENMP_SUPPORT)
1534 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001535#endif
cristybb503372010-05-27 20:51:26 +00001536 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001537 {
cristy4c08aed2011-07-01 19:47:50 +00001538 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00001539 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00001540
cristy4c08aed2011-07-01 19:47:50 +00001541 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00001542 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001543
cristy8d4629b2010-08-30 17:59:46 +00001544 register ssize_t
1545 x;
1546
cristy3ed852e2009-09-05 21:47:34 +00001547 /*
1548 Read another scan line.
1549 */
1550 if (status == MagickFalse)
1551 continue;
1552 p=GetCacheViewVirtualPixels(image_view,-2,y-2,image->columns+4,5,exception);
1553 q=QueueCacheViewAuthenticPixels(enhance_view,0,y,enhance_image->columns,1,
1554 exception);
cristy4c08aed2011-07-01 19:47:50 +00001555 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00001556 {
1557 status=MagickFalse;
1558 continue;
1559 }
cristybb503372010-05-27 20:51:26 +00001560 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001561 {
cristy4c08aed2011-07-01 19:47:50 +00001562 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00001563 aggregate;
1564
1565 MagickRealType
1566 distance,
1567 distance_squared,
1568 mean,
1569 total_weight;
1570
1571 PixelPacket
1572 pixel;
1573
cristy4c08aed2011-07-01 19:47:50 +00001574 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00001575 *restrict r;
cristy3ed852e2009-09-05 21:47:34 +00001576
1577 /*
1578 Compute weighted average of target pixel color components.
1579 */
1580 aggregate=zero;
1581 total_weight=0.0;
1582 r=p+2*(image->columns+4)+2;
cristy4c08aed2011-07-01 19:47:50 +00001583 GetPixelPacket(image,r,&pixel);
cristy3ed852e2009-09-05 21:47:34 +00001584 r=p;
1585 Enhance(5.0); Enhance(8.0); Enhance(10.0); Enhance(8.0); Enhance(5.0);
1586 r=p+(image->columns+4);
1587 Enhance(8.0); Enhance(20.0); Enhance(40.0); Enhance(20.0); Enhance(8.0);
1588 r=p+2*(image->columns+4);
1589 Enhance(10.0); Enhance(40.0); Enhance(80.0); Enhance(40.0); Enhance(10.0);
1590 r=p+3*(image->columns+4);
1591 Enhance(8.0); Enhance(20.0); Enhance(40.0); Enhance(20.0); Enhance(8.0);
1592 r=p+4*(image->columns+4);
1593 Enhance(5.0); Enhance(8.0); Enhance(10.0); Enhance(8.0); Enhance(5.0);
cristy4c08aed2011-07-01 19:47:50 +00001594 SetPixelRed(enhance_image,(Quantum) ((aggregate.red+
1595 (total_weight/2)-1)/total_weight),q);
1596 SetPixelGreen(enhance_image,(Quantum) ((aggregate.green+
1597 (total_weight/2)-1)/total_weight),q);
1598 SetPixelBlue(enhance_image,(Quantum) ((aggregate.blue+
1599 (total_weight/2)-1)/total_weight),q);
1600 SetPixelAlpha(enhance_image,(Quantum) ((aggregate.alpha+
1601 (total_weight/2)-1)/total_weight),q);
cristyed231572011-07-14 02:18:59 +00001602 p+=GetPixelChannels(image);
1603 q+=GetPixelChannels(enhance_image);
cristy3ed852e2009-09-05 21:47:34 +00001604 }
1605 if (SyncCacheViewAuthenticPixels(enhance_view,exception) == MagickFalse)
1606 status=MagickFalse;
1607 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1608 {
1609 MagickBooleanType
1610 proceed;
1611
cristyb5d5f722009-11-04 03:03:49 +00001612#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00001613 #pragma omp critical (MagickCore_EnhanceImage)
1614#endif
1615 proceed=SetImageProgress(image,EnhanceImageTag,progress++,image->rows);
1616 if (proceed == MagickFalse)
1617 status=MagickFalse;
1618 }
1619 }
1620 enhance_view=DestroyCacheView(enhance_view);
1621 image_view=DestroyCacheView(image_view);
1622 return(enhance_image);
1623}
1624
1625/*
1626%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1627% %
1628% %
1629% %
1630% E q u a l i z e I m a g e %
1631% %
1632% %
1633% %
1634%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1635%
1636% EqualizeImage() applies a histogram equalization to the image.
1637%
1638% The format of the EqualizeImage method is:
1639%
1640% MagickBooleanType EqualizeImage(Image *image)
cristy3ed852e2009-09-05 21:47:34 +00001641%
1642% A description of each parameter follows:
1643%
1644% o image: the image.
1645%
1646% o channel: the channel.
1647%
1648*/
cristy3ed852e2009-09-05 21:47:34 +00001649MagickExport MagickBooleanType EqualizeImage(Image *image)
1650{
cristy3ed852e2009-09-05 21:47:34 +00001651#define EqualizeImageTag "Equalize/Image"
1652
cristyc4c8d132010-01-07 01:58:38 +00001653 CacheView
1654 *image_view;
1655
cristy3ed852e2009-09-05 21:47:34 +00001656 ExceptionInfo
1657 *exception;
1658
cristy3ed852e2009-09-05 21:47:34 +00001659 MagickBooleanType
1660 status;
1661
cristybb503372010-05-27 20:51:26 +00001662 MagickOffsetType
1663 progress;
1664
cristy4c08aed2011-07-01 19:47:50 +00001665 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00001666 black,
1667 *equalize_map,
1668 *histogram,
1669 intensity,
1670 *map,
1671 white;
1672
cristybb503372010-05-27 20:51:26 +00001673 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001674 i;
1675
cristybb503372010-05-27 20:51:26 +00001676 ssize_t
1677 y;
1678
cristy3ed852e2009-09-05 21:47:34 +00001679 /*
1680 Allocate and initialize histogram arrays.
1681 */
1682 assert(image != (Image *) NULL);
1683 assert(image->signature == MagickSignature);
1684 if (image->debug != MagickFalse)
1685 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
cristy4c08aed2011-07-01 19:47:50 +00001686 equalize_map=(PixelInfo *) AcquireQuantumMemory(MaxMap+1UL,
cristy3ed852e2009-09-05 21:47:34 +00001687 sizeof(*equalize_map));
cristy4c08aed2011-07-01 19:47:50 +00001688 histogram=(PixelInfo *) AcquireQuantumMemory(MaxMap+1UL,
cristy3ed852e2009-09-05 21:47:34 +00001689 sizeof(*histogram));
cristy4c08aed2011-07-01 19:47:50 +00001690 map=(PixelInfo *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*map));
1691 if ((equalize_map == (PixelInfo *) NULL) ||
1692 (histogram == (PixelInfo *) NULL) ||
1693 (map == (PixelInfo *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00001694 {
cristy4c08aed2011-07-01 19:47:50 +00001695 if (map != (PixelInfo *) NULL)
1696 map=(PixelInfo *) RelinquishMagickMemory(map);
1697 if (histogram != (PixelInfo *) NULL)
1698 histogram=(PixelInfo *) RelinquishMagickMemory(histogram);
1699 if (equalize_map != (PixelInfo *) NULL)
1700 equalize_map=(PixelInfo *) RelinquishMagickMemory(equalize_map);
cristy3ed852e2009-09-05 21:47:34 +00001701 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
1702 image->filename);
1703 }
1704 /*
1705 Form histogram.
1706 */
1707 (void) ResetMagickMemory(histogram,0,(MaxMap+1)*sizeof(*histogram));
1708 exception=(&image->exception);
cristybb503372010-05-27 20:51:26 +00001709 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001710 {
cristy4c08aed2011-07-01 19:47:50 +00001711 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00001712 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00001713
cristybb503372010-05-27 20:51:26 +00001714 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001715 x;
1716
1717 p=GetVirtualPixels(image,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00001718 if (p == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001719 break;
cristybb503372010-05-27 20:51:26 +00001720 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001721 {
cristyed231572011-07-14 02:18:59 +00001722 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001723 histogram[ScaleQuantumToMap(GetPixelRed(image,p))].red++;
cristyed231572011-07-14 02:18:59 +00001724 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001725 histogram[ScaleQuantumToMap(GetPixelGreen(image,p))].green++;
cristyed231572011-07-14 02:18:59 +00001726 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001727 histogram[ScaleQuantumToMap(GetPixelBlue(image,p))].blue++;
cristyed231572011-07-14 02:18:59 +00001728 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00001729 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +00001730 histogram[ScaleQuantumToMap(GetPixelBlack(image,p))].black++;
cristyed231572011-07-14 02:18:59 +00001731 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001732 histogram[ScaleQuantumToMap(GetPixelAlpha(image,p))].alpha++;
cristyed231572011-07-14 02:18:59 +00001733 p+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00001734 }
1735 }
1736 /*
1737 Integrate the histogram to get the equalization map.
1738 */
1739 (void) ResetMagickMemory(&intensity,0,sizeof(intensity));
cristybb503372010-05-27 20:51:26 +00001740 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +00001741 {
cristyed231572011-07-14 02:18:59 +00001742 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001743 intensity.red+=histogram[i].red;
cristyed231572011-07-14 02:18:59 +00001744 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001745 intensity.green+=histogram[i].green;
cristyed231572011-07-14 02:18:59 +00001746 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001747 intensity.blue+=histogram[i].blue;
cristyed231572011-07-14 02:18:59 +00001748 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00001749 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +00001750 intensity.black+=histogram[i].black;
cristyed231572011-07-14 02:18:59 +00001751 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001752 intensity.alpha+=histogram[i].alpha;
cristy3ed852e2009-09-05 21:47:34 +00001753 map[i]=intensity;
1754 }
1755 black=map[0];
1756 white=map[(int) MaxMap];
1757 (void) ResetMagickMemory(equalize_map,0,(MaxMap+1)*sizeof(*equalize_map));
cristyb5d5f722009-11-04 03:03:49 +00001758#if defined(MAGICKCORE_OPENMP_SUPPORT)
1759 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001760#endif
cristybb503372010-05-27 20:51:26 +00001761 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +00001762 {
cristyed231572011-07-14 02:18:59 +00001763 if (((GetPixelRedTraits(image) & UpdatePixelTrait) != 0) &&
cristy2b9582a2011-07-04 17:38:56 +00001764 (white.red != black.red))
cristy3ed852e2009-09-05 21:47:34 +00001765 equalize_map[i].red=(MagickRealType) ScaleMapToQuantum((MagickRealType)
1766 ((MaxMap*(map[i].red-black.red))/(white.red-black.red)));
cristyed231572011-07-14 02:18:59 +00001767 if (((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0) &&
cristy2b9582a2011-07-04 17:38:56 +00001768 (white.green != black.green))
cristy3ed852e2009-09-05 21:47:34 +00001769 equalize_map[i].green=(MagickRealType) ScaleMapToQuantum((MagickRealType)
1770 ((MaxMap*(map[i].green-black.green))/(white.green-black.green)));
cristyed231572011-07-14 02:18:59 +00001771 if (((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0) &&
cristy2b9582a2011-07-04 17:38:56 +00001772 (white.blue != black.blue))
cristy3ed852e2009-09-05 21:47:34 +00001773 equalize_map[i].blue=(MagickRealType) ScaleMapToQuantum((MagickRealType)
1774 ((MaxMap*(map[i].blue-black.blue))/(white.blue-black.blue)));
cristyed231572011-07-14 02:18:59 +00001775 if ((((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00001776 (image->colorspace == CMYKColorspace)) &&
cristy4c08aed2011-07-01 19:47:50 +00001777 (white.black != black.black))
1778 equalize_map[i].black=(MagickRealType) ScaleMapToQuantum((MagickRealType)
1779 ((MaxMap*(map[i].black-black.black))/(white.black-black.black)));
cristyed231572011-07-14 02:18:59 +00001780 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) &&
cristy2b9582a2011-07-04 17:38:56 +00001781 (white.alpha != black.alpha))
cristy4c08aed2011-07-01 19:47:50 +00001782 equalize_map[i].alpha=(MagickRealType) ScaleMapToQuantum(
1783 (MagickRealType) ((MaxMap*(map[i].alpha-black.alpha))/
1784 (white.alpha-black.alpha)));
cristy3ed852e2009-09-05 21:47:34 +00001785 }
cristy4c08aed2011-07-01 19:47:50 +00001786 histogram=(PixelInfo *) RelinquishMagickMemory(histogram);
1787 map=(PixelInfo *) RelinquishMagickMemory(map);
cristy3ed852e2009-09-05 21:47:34 +00001788 if (image->storage_class == PseudoClass)
1789 {
1790 /*
1791 Equalize colormap.
1792 */
cristyb5d5f722009-11-04 03:03:49 +00001793#if defined(MAGICKCORE_OPENMP_SUPPORT)
1794 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001795#endif
cristybb503372010-05-27 20:51:26 +00001796 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00001797 {
cristyed231572011-07-14 02:18:59 +00001798 if (((GetPixelRedTraits(image) & UpdatePixelTrait) != 0) &&
cristy2b9582a2011-07-04 17:38:56 +00001799 (white.red != black.red))
cristyce70c172010-01-07 17:15:30 +00001800 image->colormap[i].red=ClampToQuantum(equalize_map[
cristy3ed852e2009-09-05 21:47:34 +00001801 ScaleQuantumToMap(image->colormap[i].red)].red);
cristyed231572011-07-14 02:18:59 +00001802 if (((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0) &&
cristy2b9582a2011-07-04 17:38:56 +00001803 (white.green != black.green))
cristyce70c172010-01-07 17:15:30 +00001804 image->colormap[i].green=ClampToQuantum(equalize_map[
cristy3ed852e2009-09-05 21:47:34 +00001805 ScaleQuantumToMap(image->colormap[i].green)].green);
cristyed231572011-07-14 02:18:59 +00001806 if (((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0) &&
cristy2b9582a2011-07-04 17:38:56 +00001807 (white.blue != black.blue))
cristyce70c172010-01-07 17:15:30 +00001808 image->colormap[i].blue=ClampToQuantum(equalize_map[
cristy3ed852e2009-09-05 21:47:34 +00001809 ScaleQuantumToMap(image->colormap[i].blue)].blue);
cristyed231572011-07-14 02:18:59 +00001810 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +00001811 (white.alpha != black.alpha))
1812 image->colormap[i].alpha=ClampToQuantum(equalize_map[
1813 ScaleQuantumToMap(image->colormap[i].alpha)].alpha);
cristy3ed852e2009-09-05 21:47:34 +00001814 }
1815 }
1816 /*
1817 Equalize image.
1818 */
1819 status=MagickTrue;
1820 progress=0;
1821 exception=(&image->exception);
1822 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00001823#if defined(MAGICKCORE_OPENMP_SUPPORT)
1824 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001825#endif
cristybb503372010-05-27 20:51:26 +00001826 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001827 {
cristy4c08aed2011-07-01 19:47:50 +00001828 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00001829 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001830
cristy8d4629b2010-08-30 17:59:46 +00001831 register ssize_t
1832 x;
1833
cristy3ed852e2009-09-05 21:47:34 +00001834 if (status == MagickFalse)
1835 continue;
1836 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00001837 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001838 {
1839 status=MagickFalse;
1840 continue;
1841 }
cristybb503372010-05-27 20:51:26 +00001842 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001843 {
cristyed231572011-07-14 02:18:59 +00001844 if (((GetPixelRedTraits(image) & UpdatePixelTrait) != 0) &&
cristy2b9582a2011-07-04 17:38:56 +00001845 (white.red != black.red))
cristy4c08aed2011-07-01 19:47:50 +00001846 SetPixelRed(image,ClampToQuantum(equalize_map[
1847 ScaleQuantumToMap(GetPixelRed(image,q))].red),q);
cristyed231572011-07-14 02:18:59 +00001848 if (((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0) &&
cristy2b9582a2011-07-04 17:38:56 +00001849 (white.green != black.green))
cristy4c08aed2011-07-01 19:47:50 +00001850 SetPixelGreen(image,ClampToQuantum(equalize_map[
1851 ScaleQuantumToMap(GetPixelGreen(image,q))].green),q);
cristyed231572011-07-14 02:18:59 +00001852 if (((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0) &&
cristy2b9582a2011-07-04 17:38:56 +00001853 (white.blue != black.blue))
cristy4c08aed2011-07-01 19:47:50 +00001854 SetPixelBlue(image,ClampToQuantum(equalize_map[
1855 ScaleQuantumToMap(GetPixelBlue(image,q))].blue),q);
cristyed231572011-07-14 02:18:59 +00001856 if ((((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00001857 (image->colorspace == CMYKColorspace)) &&
cristy4c08aed2011-07-01 19:47:50 +00001858 (white.black != black.black))
1859 SetPixelBlack(image,ClampToQuantum(equalize_map[
1860 ScaleQuantumToMap(GetPixelBlack(image,q))].black),q);
cristyed231572011-07-14 02:18:59 +00001861 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) &&
cristy2b9582a2011-07-04 17:38:56 +00001862 (white.alpha != black.alpha))
cristy4c08aed2011-07-01 19:47:50 +00001863 SetPixelAlpha(image,ClampToQuantum(equalize_map[
1864 ScaleQuantumToMap(GetPixelAlpha(image,q))].alpha),q);
cristyed231572011-07-14 02:18:59 +00001865 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00001866 }
1867 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
1868 status=MagickFalse;
1869 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1870 {
1871 MagickBooleanType
1872 proceed;
1873
cristyb5d5f722009-11-04 03:03:49 +00001874#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy50fbc382011-07-07 02:19:17 +00001875 #pragma omp critical (MagickCore_EqualizeImage)
cristy3ed852e2009-09-05 21:47:34 +00001876#endif
1877 proceed=SetImageProgress(image,EqualizeImageTag,progress++,image->rows);
1878 if (proceed == MagickFalse)
1879 status=MagickFalse;
1880 }
1881 }
1882 image_view=DestroyCacheView(image_view);
cristy4c08aed2011-07-01 19:47:50 +00001883 equalize_map=(PixelInfo *) RelinquishMagickMemory(equalize_map);
cristy3ed852e2009-09-05 21:47:34 +00001884 return(status);
1885}
1886
1887/*
1888%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1889% %
1890% %
1891% %
1892% G a m m a I m a g e %
1893% %
1894% %
1895% %
1896%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1897%
1898% GammaImage() gamma-corrects a particular image channel. The same
1899% image viewed on different devices will have perceptual differences in the
1900% way the image's intensities are represented on the screen. Specify
1901% individual gamma levels for the red, green, and blue channels, or adjust
1902% all three with the gamma parameter. Values typically range from 0.8 to 2.3.
1903%
1904% You can also reduce the influence of a particular channel with a gamma
1905% value of 0.
1906%
1907% The format of the GammaImage method is:
1908%
cristyb3e7c6c2011-07-24 01:43:55 +00001909% MagickBooleanType GammaImage(Image *image,const double gamma,
1910% ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001911%
1912% A description of each parameter follows:
1913%
1914% o image: the image.
1915%
cristya6360142011-03-23 23:08:04 +00001916% o level: the image gamma as a string (e.g. 1.6,1.2,1.0).
1917%
cristy3ed852e2009-09-05 21:47:34 +00001918% o gamma: the image gamma.
1919%
1920*/
cristyb3e7c6c2011-07-24 01:43:55 +00001921MagickExport MagickBooleanType GammaImage(Image *image,const double gamma,
1922 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001923{
1924#define GammaCorrectImageTag "GammaCorrect/Image"
1925
cristyc4c8d132010-01-07 01:58:38 +00001926 CacheView
1927 *image_view;
1928
cristy3ed852e2009-09-05 21:47:34 +00001929 MagickBooleanType
1930 status;
1931
cristybb503372010-05-27 20:51:26 +00001932 MagickOffsetType
1933 progress;
1934
cristy3ed852e2009-09-05 21:47:34 +00001935 Quantum
1936 *gamma_map;
1937
cristybb503372010-05-27 20:51:26 +00001938 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001939 i;
1940
cristybb503372010-05-27 20:51:26 +00001941 ssize_t
1942 y;
1943
cristy3ed852e2009-09-05 21:47:34 +00001944 /*
1945 Allocate and initialize gamma maps.
1946 */
1947 assert(image != (Image *) NULL);
1948 assert(image->signature == MagickSignature);
1949 if (image->debug != MagickFalse)
1950 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1951 if (gamma == 1.0)
1952 return(MagickTrue);
1953 gamma_map=(Quantum *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*gamma_map));
1954 if (gamma_map == (Quantum *) NULL)
1955 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
1956 image->filename);
1957 (void) ResetMagickMemory(gamma_map,0,(MaxMap+1)*sizeof(*gamma_map));
1958 if (gamma != 0.0)
cristyd476a8e2011-07-23 16:13:22 +00001959#if defined(MAGICKCORE_OPENMP_SUPPORT) && (MaxMap > 256)
1960 #pragma omp parallel for
cristy3ed852e2009-09-05 21:47:34 +00001961#endif
cristybb503372010-05-27 20:51:26 +00001962 for (i=0; i <= (ssize_t) MaxMap; i++)
cristyce70c172010-01-07 17:15:30 +00001963 gamma_map[i]=ClampToQuantum((MagickRealType) ScaleMapToQuantum((
cristy3ed852e2009-09-05 21:47:34 +00001964 MagickRealType) (MaxMap*pow((double) i/MaxMap,1.0/gamma))));
1965 if (image->storage_class == PseudoClass)
1966 {
1967 /*
1968 Gamma-correct colormap.
1969 */
cristyb5d5f722009-11-04 03:03:49 +00001970#if defined(MAGICKCORE_OPENMP_SUPPORT)
1971 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001972#endif
cristybb503372010-05-27 20:51:26 +00001973 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00001974 {
cristyed231572011-07-14 02:18:59 +00001975 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001976 image->colormap[i].red=gamma_map[
1977 ScaleQuantumToMap(image->colormap[i].red)];
cristyed231572011-07-14 02:18:59 +00001978 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001979 image->colormap[i].green=gamma_map[
1980 ScaleQuantumToMap(image->colormap[i].green)];
cristyed231572011-07-14 02:18:59 +00001981 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001982 image->colormap[i].blue=gamma_map[
1983 ScaleQuantumToMap(image->colormap[i].blue)];
cristyed231572011-07-14 02:18:59 +00001984 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001985 image->colormap[i].alpha=gamma_map[
1986 ScaleQuantumToMap(image->colormap[i].alpha)];
cristy3ed852e2009-09-05 21:47:34 +00001987 }
1988 }
1989 /*
1990 Gamma-correct image.
1991 */
1992 status=MagickTrue;
1993 progress=0;
cristy3ed852e2009-09-05 21:47:34 +00001994 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00001995#if defined(MAGICKCORE_OPENMP_SUPPORT)
1996 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001997#endif
cristybb503372010-05-27 20:51:26 +00001998 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001999 {
cristy4c08aed2011-07-01 19:47:50 +00002000 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00002001 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002002
cristy8d4629b2010-08-30 17:59:46 +00002003 register ssize_t
2004 x;
2005
cristy3ed852e2009-09-05 21:47:34 +00002006 if (status == MagickFalse)
2007 continue;
2008 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00002009 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00002010 {
2011 status=MagickFalse;
2012 continue;
2013 }
cristybb503372010-05-27 20:51:26 +00002014 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002015 {
cristyd476a8e2011-07-23 16:13:22 +00002016 register ssize_t
2017 i;
2018
cristya30d9ba2011-07-23 21:00:48 +00002019 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
cristyd476a8e2011-07-23 16:13:22 +00002020 {
2021 PixelTrait
2022 traits;
2023
2024 traits=GetPixelChannelMapTraits(image,(PixelChannel) i);
2025 if ((traits & UpdatePixelTrait) != 0)
2026 q[i]=gamma_map[ScaleQuantumToMap(q[i])];
2027 }
cristya30d9ba2011-07-23 21:00:48 +00002028 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00002029 }
cristy3ed852e2009-09-05 21:47:34 +00002030 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2031 status=MagickFalse;
2032 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2033 {
2034 MagickBooleanType
2035 proceed;
2036
cristyb5d5f722009-11-04 03:03:49 +00002037#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy50fbc382011-07-07 02:19:17 +00002038 #pragma omp critical (MagickCore_GammaImage)
cristy3ed852e2009-09-05 21:47:34 +00002039#endif
2040 proceed=SetImageProgress(image,GammaCorrectImageTag,progress++,
2041 image->rows);
2042 if (proceed == MagickFalse)
2043 status=MagickFalse;
2044 }
2045 }
2046 image_view=DestroyCacheView(image_view);
2047 gamma_map=(Quantum *) RelinquishMagickMemory(gamma_map);
2048 if (image->gamma != 0.0)
2049 image->gamma*=gamma;
2050 return(status);
2051}
2052
2053/*
2054%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2055% %
2056% %
2057% %
2058% H a l d C l u t I m a g e %
2059% %
2060% %
2061% %
2062%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2063%
2064% HaldClutImage() applies a Hald color lookup table to the image. A Hald
2065% color lookup table is a 3-dimensional color cube mapped to 2 dimensions.
2066% Create it with the HALD coder. You can apply any color transformation to
2067% the Hald image and then use this method to apply the transform to the
2068% image.
2069%
2070% The format of the HaldClutImage method is:
2071%
2072% MagickBooleanType HaldClutImage(Image *image,Image *hald_image)
cristy3ed852e2009-09-05 21:47:34 +00002073%
2074% A description of each parameter follows:
2075%
2076% o image: the image, which is replaced by indexed CLUT values
2077%
2078% o hald_image: the color lookup table image for replacement color values.
2079%
cristy3ed852e2009-09-05 21:47:34 +00002080*/
2081
2082static inline size_t MagickMin(const size_t x,const size_t y)
2083{
2084 if (x < y)
2085 return(x);
2086 return(y);
2087}
2088
2089MagickExport MagickBooleanType HaldClutImage(Image *image,
2090 const Image *hald_image)
2091{
cristy3ed852e2009-09-05 21:47:34 +00002092#define HaldClutImageTag "Clut/Image"
2093
2094 typedef struct _HaldInfo
2095 {
2096 MagickRealType
2097 x,
2098 y,
2099 z;
2100 } HaldInfo;
2101
cristyfa112112010-01-04 17:48:07 +00002102 CacheView
cristyd551fbc2011-03-31 18:07:46 +00002103 *hald_view,
cristyfa112112010-01-04 17:48:07 +00002104 *image_view;
2105
cristy3ed852e2009-09-05 21:47:34 +00002106 double
2107 width;
2108
2109 ExceptionInfo
2110 *exception;
2111
cristy3ed852e2009-09-05 21:47:34 +00002112 MagickBooleanType
2113 status;
2114
cristybb503372010-05-27 20:51:26 +00002115 MagickOffsetType
2116 progress;
2117
cristy4c08aed2011-07-01 19:47:50 +00002118 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00002119 zero;
2120
cristy3ed852e2009-09-05 21:47:34 +00002121 size_t
2122 cube_size,
2123 length,
2124 level;
2125
cristybb503372010-05-27 20:51:26 +00002126 ssize_t
2127 y;
2128
cristy3ed852e2009-09-05 21:47:34 +00002129 assert(image != (Image *) NULL);
2130 assert(image->signature == MagickSignature);
2131 if (image->debug != MagickFalse)
2132 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2133 assert(hald_image != (Image *) NULL);
2134 assert(hald_image->signature == MagickSignature);
2135 if (SetImageStorageClass(image,DirectClass) == MagickFalse)
2136 return(MagickFalse);
2137 if (image->matte == MagickFalse)
2138 (void) SetImageAlphaChannel(image,OpaqueAlphaChannel);
2139 /*
2140 Hald clut image.
2141 */
2142 status=MagickTrue;
2143 progress=0;
2144 length=MagickMin(hald_image->columns,hald_image->rows);
2145 for (level=2; (level*level*level) < length; level++) ;
2146 level*=level;
2147 cube_size=level*level;
2148 width=(double) hald_image->columns;
cristy4c08aed2011-07-01 19:47:50 +00002149 GetPixelInfo(hald_image,&zero);
cristy3ed852e2009-09-05 21:47:34 +00002150 exception=(&image->exception);
cristy3ed852e2009-09-05 21:47:34 +00002151 image_view=AcquireCacheView(image);
cristyd551fbc2011-03-31 18:07:46 +00002152 hald_view=AcquireCacheView(hald_image);
cristyb5d5f722009-11-04 03:03:49 +00002153#if defined(MAGICKCORE_OPENMP_SUPPORT)
2154 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002155#endif
cristybb503372010-05-27 20:51:26 +00002156 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002157 {
2158 double
2159 offset;
2160
2161 HaldInfo
2162 point;
2163
cristy4c08aed2011-07-01 19:47:50 +00002164 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00002165 pixel,
2166 pixel1,
2167 pixel2,
2168 pixel3,
2169 pixel4;
2170
cristy4c08aed2011-07-01 19:47:50 +00002171 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00002172 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002173
cristy8d4629b2010-08-30 17:59:46 +00002174 register ssize_t
2175 x;
2176
cristy3ed852e2009-09-05 21:47:34 +00002177 if (status == MagickFalse)
2178 continue;
2179 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00002180 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00002181 {
2182 status=MagickFalse;
2183 continue;
2184 }
cristy3ed852e2009-09-05 21:47:34 +00002185 pixel=zero;
2186 pixel1=zero;
2187 pixel2=zero;
2188 pixel3=zero;
2189 pixel4=zero;
cristybb503372010-05-27 20:51:26 +00002190 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002191 {
cristy4c08aed2011-07-01 19:47:50 +00002192 point.x=QuantumScale*(level-1.0)*GetPixelRed(image,q);
2193 point.y=QuantumScale*(level-1.0)*GetPixelGreen(image,q);
2194 point.z=QuantumScale*(level-1.0)*GetPixelBlue(image,q);
cristy3ed852e2009-09-05 21:47:34 +00002195 offset=point.x+level*floor(point.y)+cube_size*floor(point.z);
2196 point.x-=floor(point.x);
2197 point.y-=floor(point.y);
2198 point.z-=floor(point.z);
cristy4c08aed2011-07-01 19:47:50 +00002199 (void) InterpolatePixelInfo(image,hald_view,
cristy8a7c3e82011-03-26 02:10:53 +00002200 UndefinedInterpolatePixel,fmod(offset,width),floor(offset/width),
2201 &pixel1,exception);
cristy4c08aed2011-07-01 19:47:50 +00002202 (void) InterpolatePixelInfo(image,hald_view,
cristy8a7c3e82011-03-26 02:10:53 +00002203 UndefinedInterpolatePixel,fmod(offset+level,width),floor((offset+level)/
2204 width),&pixel2,exception);
cristy4c08aed2011-07-01 19:47:50 +00002205 CompositePixelInfoAreaBlend(&pixel1,pixel1.alpha,&pixel2,
2206 pixel2.alpha,point.y,&pixel3);
cristy3ed852e2009-09-05 21:47:34 +00002207 offset+=cube_size;
cristy4c08aed2011-07-01 19:47:50 +00002208 (void) InterpolatePixelInfo(image,hald_view,
cristy8a7c3e82011-03-26 02:10:53 +00002209 UndefinedInterpolatePixel,fmod(offset,width),floor(offset/width),
2210 &pixel1,exception);
cristy4c08aed2011-07-01 19:47:50 +00002211 (void) InterpolatePixelInfo(image,hald_view,
cristy8a7c3e82011-03-26 02:10:53 +00002212 UndefinedInterpolatePixel,fmod(offset+level,width),floor((offset+level)/
2213 width),&pixel2,exception);
cristy4c08aed2011-07-01 19:47:50 +00002214 CompositePixelInfoAreaBlend(&pixel1,pixel1.alpha,&pixel2,
2215 pixel2.alpha,point.y,&pixel4);
2216 CompositePixelInfoAreaBlend(&pixel3,pixel3.alpha,&pixel4,
2217 pixel4.alpha,point.z,&pixel);
cristyed231572011-07-14 02:18:59 +00002218 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002219 SetPixelRed(image,
2220 ClampToQuantum(pixel.red),q);
cristyed231572011-07-14 02:18:59 +00002221 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002222 SetPixelGreen(image,
2223 ClampToQuantum(pixel.green),q);
cristyed231572011-07-14 02:18:59 +00002224 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002225 SetPixelBlue(image,
2226 ClampToQuantum(pixel.blue),q);
cristyed231572011-07-14 02:18:59 +00002227 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00002228 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +00002229 SetPixelBlack(image,
2230 ClampToQuantum(pixel.black),q);
cristyed231572011-07-14 02:18:59 +00002231 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) && (image->matte != MagickFalse))
cristy4c08aed2011-07-01 19:47:50 +00002232 SetPixelAlpha(image,
2233 ClampToQuantum(pixel.alpha),q);
cristyed231572011-07-14 02:18:59 +00002234 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00002235 }
2236 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2237 status=MagickFalse;
2238 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2239 {
2240 MagickBooleanType
2241 proceed;
2242
cristyb5d5f722009-11-04 03:03:49 +00002243#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf89cb1d2011-07-07 01:24:37 +00002244 #pragma omp critical (MagickCore_HaldClutImage)
cristy3ed852e2009-09-05 21:47:34 +00002245#endif
2246 proceed=SetImageProgress(image,HaldClutImageTag,progress++,image->rows);
2247 if (proceed == MagickFalse)
2248 status=MagickFalse;
2249 }
2250 }
cristyd551fbc2011-03-31 18:07:46 +00002251 hald_view=DestroyCacheView(hald_view);
cristy3ed852e2009-09-05 21:47:34 +00002252 image_view=DestroyCacheView(image_view);
cristy3ed852e2009-09-05 21:47:34 +00002253 return(status);
2254}
2255
2256/*
2257%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2258% %
2259% %
2260% %
2261% L e v e l I m a g e %
2262% %
2263% %
2264% %
2265%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2266%
2267% LevelImage() adjusts the levels of a particular image channel by
2268% scaling the colors falling between specified white and black points to
2269% the full available quantum range.
2270%
2271% The parameters provided represent the black, and white points. The black
2272% point specifies the darkest color in the image. Colors darker than the
2273% black point are set to zero. White point specifies the lightest color in
2274% the image. Colors brighter than the white point are set to the maximum
2275% quantum value.
2276%
2277% If a '!' flag is given, map black and white colors to the given levels
2278% rather than mapping those levels to black and white. See
cristy50fbc382011-07-07 02:19:17 +00002279% LevelizeImage() below.
cristy3ed852e2009-09-05 21:47:34 +00002280%
2281% Gamma specifies a gamma correction to apply to the image.
2282%
2283% The format of the LevelImage method is:
2284%
2285% MagickBooleanType LevelImage(Image *image,const char *levels)
2286%
2287% A description of each parameter follows:
2288%
2289% o image: the image.
2290%
2291% o levels: Specify the levels where the black and white points have the
2292% range of 0-QuantumRange, and gamma has the range 0-10 (e.g. 10x90%+2).
2293% A '!' flag inverts the re-mapping.
2294%
2295*/
cristyf89cb1d2011-07-07 01:24:37 +00002296MagickExport MagickBooleanType LevelImage(Image *image,
2297 const double black_point,const double white_point,const double gamma)
cristy3ed852e2009-09-05 21:47:34 +00002298{
2299#define LevelImageTag "Level/Image"
cristybcfb0432010-05-06 01:45:33 +00002300#define LevelQuantum(x) (ClampToQuantum((MagickRealType) QuantumRange* \
cristyc1f508d2010-04-22 01:21:47 +00002301 pow(scale*((double) (x)-black_point),1.0/gamma)))
cristy3ed852e2009-09-05 21:47:34 +00002302
cristyc4c8d132010-01-07 01:58:38 +00002303 CacheView
2304 *image_view;
2305
cristy3ed852e2009-09-05 21:47:34 +00002306 ExceptionInfo
2307 *exception;
2308
cristy3ed852e2009-09-05 21:47:34 +00002309 MagickBooleanType
2310 status;
2311
cristybb503372010-05-27 20:51:26 +00002312 MagickOffsetType
2313 progress;
2314
anthony7fe39fc2010-04-06 03:19:20 +00002315 register double
2316 scale;
2317
cristy8d4629b2010-08-30 17:59:46 +00002318 register ssize_t
2319 i;
2320
cristybb503372010-05-27 20:51:26 +00002321 ssize_t
2322 y;
2323
cristy3ed852e2009-09-05 21:47:34 +00002324 /*
2325 Allocate and initialize levels map.
2326 */
2327 assert(image != (Image *) NULL);
2328 assert(image->signature == MagickSignature);
2329 if (image->debug != MagickFalse)
2330 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
cristy8d4629b2010-08-30 17:59:46 +00002331 scale=(white_point != black_point) ? 1.0/(white_point-black_point) : 1.0;
cristy3ed852e2009-09-05 21:47:34 +00002332 if (image->storage_class == PseudoClass)
cristyb5d5f722009-11-04 03:03:49 +00002333#if defined(MAGICKCORE_OPENMP_SUPPORT)
2334 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002335#endif
cristybb503372010-05-27 20:51:26 +00002336 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00002337 {
2338 /*
2339 Level colormap.
2340 */
cristyed231572011-07-14 02:18:59 +00002341 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristyc1f508d2010-04-22 01:21:47 +00002342 image->colormap[i].red=LevelQuantum(image->colormap[i].red);
cristyed231572011-07-14 02:18:59 +00002343 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristyc1f508d2010-04-22 01:21:47 +00002344 image->colormap[i].green=LevelQuantum(image->colormap[i].green);
cristyed231572011-07-14 02:18:59 +00002345 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristyc1f508d2010-04-22 01:21:47 +00002346 image->colormap[i].blue=LevelQuantum(image->colormap[i].blue);
cristyed231572011-07-14 02:18:59 +00002347 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002348 image->colormap[i].alpha=LevelQuantum(image->colormap[i].alpha);
cristy3ed852e2009-09-05 21:47:34 +00002349 }
2350 /*
2351 Level image.
2352 */
2353 status=MagickTrue;
2354 progress=0;
2355 exception=(&image->exception);
2356 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00002357#if defined(MAGICKCORE_OPENMP_SUPPORT)
2358 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002359#endif
cristybb503372010-05-27 20:51:26 +00002360 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002361 {
cristy4c08aed2011-07-01 19:47:50 +00002362 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00002363 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002364
cristy8d4629b2010-08-30 17:59:46 +00002365 register ssize_t
2366 x;
2367
cristy3ed852e2009-09-05 21:47:34 +00002368 if (status == MagickFalse)
2369 continue;
2370 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00002371 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00002372 {
2373 status=MagickFalse;
2374 continue;
2375 }
cristybb503372010-05-27 20:51:26 +00002376 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002377 {
cristyed231572011-07-14 02:18:59 +00002378 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002379 SetPixelRed(image,LevelQuantum(
2380 GetPixelRed(image,q)),q);
cristyed231572011-07-14 02:18:59 +00002381 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002382 SetPixelGreen(image,
2383 LevelQuantum(GetPixelGreen(image,q)),q);
cristyed231572011-07-14 02:18:59 +00002384 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002385 SetPixelBlue(image,
2386 LevelQuantum(GetPixelBlue(image,q)),q);
cristyed231572011-07-14 02:18:59 +00002387 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00002388 (image->matte == MagickTrue))
cristy4c08aed2011-07-01 19:47:50 +00002389 SetPixelAlpha(image,
2390 LevelQuantum(GetPixelAlpha(image,q)),q);
cristyed231572011-07-14 02:18:59 +00002391 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00002392 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +00002393 SetPixelBlack(image,
2394 LevelQuantum(GetPixelBlack(image,q)),q);
cristyed231572011-07-14 02:18:59 +00002395 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00002396 }
2397 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2398 status=MagickFalse;
2399 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2400 {
2401 MagickBooleanType
2402 proceed;
2403
cristyb5d5f722009-11-04 03:03:49 +00002404#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf89cb1d2011-07-07 01:24:37 +00002405 #pragma omp critical (MagickCore_LevelImage)
cristy3ed852e2009-09-05 21:47:34 +00002406#endif
2407 proceed=SetImageProgress(image,LevelImageTag,progress++,image->rows);
2408 if (proceed == MagickFalse)
2409 status=MagickFalse;
2410 }
2411 }
2412 image_view=DestroyCacheView(image_view);
2413 return(status);
2414}
2415
2416/*
2417%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2418% %
2419% %
2420% %
2421% L e v e l i z e I m a g e C h a n n e l %
2422% %
2423% %
2424% %
2425%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2426%
cristy50fbc382011-07-07 02:19:17 +00002427% LevelizeImage() applies the reversed LevelImage() operation to just
cristy3ed852e2009-09-05 21:47:34 +00002428% the specific channels specified. It compresses the full range of color
2429% values, so that they lie between the given black and white points. Gamma is
2430% applied before the values are mapped.
2431%
cristy50fbc382011-07-07 02:19:17 +00002432% LevelizeImage() can be called with by using a +level command line
cristy3ed852e2009-09-05 21:47:34 +00002433% API option, or using a '!' on a -level or LevelImage() geometry string.
2434%
2435% It can be used for example de-contrast a greyscale image to the exact
2436% levels specified. Or by using specific levels for each channel of an image
2437% you can convert a gray-scale image to any linear color gradient, according
2438% to those levels.
2439%
cristy50fbc382011-07-07 02:19:17 +00002440% The format of the LevelizeImage method is:
cristy3ed852e2009-09-05 21:47:34 +00002441%
cristy50fbc382011-07-07 02:19:17 +00002442% MagickBooleanType LevelizeImage(Image *image,const double black_point,
2443% const double white_point,const double gamma)
cristy3ed852e2009-09-05 21:47:34 +00002444%
2445% A description of each parameter follows:
2446%
2447% o image: the image.
2448%
cristy3ed852e2009-09-05 21:47:34 +00002449% o black_point: The level to map zero (black) to.
2450%
2451% o white_point: The level to map QuantiumRange (white) to.
2452%
2453% o gamma: adjust gamma by this factor before mapping values.
2454%
2455*/
cristyd1a2c0f2011-02-09 14:14:50 +00002456MagickExport MagickBooleanType LevelizeImage(Image *image,
2457 const double black_point,const double white_point,const double gamma)
2458{
cristy3ed852e2009-09-05 21:47:34 +00002459#define LevelizeImageTag "Levelize/Image"
cristyce70c172010-01-07 17:15:30 +00002460#define LevelizeValue(x) (ClampToQuantum(((MagickRealType) \
cristy50fbc382011-07-07 02:19:17 +00002461 pow((double) (QuantumScale*(x)),1.0/gamma))*(white_point-black_point)+ \
cristy3ed852e2009-09-05 21:47:34 +00002462 black_point))
2463
cristyc4c8d132010-01-07 01:58:38 +00002464 CacheView
2465 *image_view;
2466
cristy3ed852e2009-09-05 21:47:34 +00002467 ExceptionInfo
2468 *exception;
2469
cristy3ed852e2009-09-05 21:47:34 +00002470 MagickBooleanType
2471 status;
2472
cristybb503372010-05-27 20:51:26 +00002473 MagickOffsetType
2474 progress;
2475
2476 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002477 i;
2478
cristybb503372010-05-27 20:51:26 +00002479 ssize_t
2480 y;
2481
cristy3ed852e2009-09-05 21:47:34 +00002482 /*
2483 Allocate and initialize levels map.
2484 */
2485 assert(image != (Image *) NULL);
2486 assert(image->signature == MagickSignature);
2487 if (image->debug != MagickFalse)
2488 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2489 if (image->storage_class == PseudoClass)
cristyb5d5f722009-11-04 03:03:49 +00002490#if defined(MAGICKCORE_OPENMP_SUPPORT)
2491 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002492#endif
cristybb503372010-05-27 20:51:26 +00002493 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00002494 {
2495 /*
2496 Level colormap.
2497 */
cristyed231572011-07-14 02:18:59 +00002498 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00002499 image->colormap[i].red=LevelizeValue(image->colormap[i].red);
cristyed231572011-07-14 02:18:59 +00002500 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00002501 image->colormap[i].green=LevelizeValue(image->colormap[i].green);
cristyed231572011-07-14 02:18:59 +00002502 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00002503 image->colormap[i].blue=LevelizeValue(image->colormap[i].blue);
cristyed231572011-07-14 02:18:59 +00002504 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002505 image->colormap[i].alpha=LevelizeValue(image->colormap[i].alpha);
cristy3ed852e2009-09-05 21:47:34 +00002506 }
2507 /*
2508 Level image.
2509 */
2510 status=MagickTrue;
2511 progress=0;
2512 exception=(&image->exception);
2513 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00002514#if defined(MAGICKCORE_OPENMP_SUPPORT)
2515 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002516#endif
cristybb503372010-05-27 20:51:26 +00002517 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002518 {
cristy4c08aed2011-07-01 19:47:50 +00002519 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00002520 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002521
cristy8d4629b2010-08-30 17:59:46 +00002522 register ssize_t
2523 x;
2524
cristy3ed852e2009-09-05 21:47:34 +00002525 if (status == MagickFalse)
2526 continue;
2527 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00002528 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00002529 {
2530 status=MagickFalse;
2531 continue;
2532 }
cristybb503372010-05-27 20:51:26 +00002533 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002534 {
cristyed231572011-07-14 02:18:59 +00002535 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002536 SetPixelRed(image,LevelizeValue(GetPixelRed(image,q)),q);
cristyed231572011-07-14 02:18:59 +00002537 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002538 SetPixelGreen(image,LevelizeValue(GetPixelGreen(image,q)),q);
cristyed231572011-07-14 02:18:59 +00002539 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002540 SetPixelBlue(image,LevelizeValue(GetPixelBlue(image,q)),q);
cristyed231572011-07-14 02:18:59 +00002541 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +00002542 (image->colorspace == CMYKColorspace))
2543 SetPixelBlack(image,LevelizeValue(GetPixelBlack(image,q)),q);
cristyed231572011-07-14 02:18:59 +00002544 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00002545 (image->matte == MagickTrue))
cristy4c08aed2011-07-01 19:47:50 +00002546 SetPixelAlpha(image,LevelizeValue(GetPixelAlpha(image,q)),q);
cristyed231572011-07-14 02:18:59 +00002547 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00002548 }
2549 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2550 status=MagickFalse;
2551 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2552 {
2553 MagickBooleanType
2554 proceed;
2555
cristyb5d5f722009-11-04 03:03:49 +00002556#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy50fbc382011-07-07 02:19:17 +00002557 #pragma omp critical (MagickCore_LevelizeImage)
cristy3ed852e2009-09-05 21:47:34 +00002558#endif
2559 proceed=SetImageProgress(image,LevelizeImageTag,progress++,image->rows);
2560 if (proceed == MagickFalse)
2561 status=MagickFalse;
2562 }
2563 }
cristy8d4629b2010-08-30 17:59:46 +00002564 image_view=DestroyCacheView(image_view);
cristy3ed852e2009-09-05 21:47:34 +00002565 return(status);
2566}
2567
2568/*
2569%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2570% %
2571% %
2572% %
2573% L e v e l I m a g e C o l o r s %
2574% %
2575% %
2576% %
2577%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2578%
cristy490408a2011-07-07 14:42:05 +00002579% LevelImageColors() maps the given color to "black" and "white" values,
cristyee0f8d72009-09-19 00:58:29 +00002580% linearly spreading out the colors, and level values on a channel by channel
2581% bases, as per LevelImage(). The given colors allows you to specify
glennrp1e7f7bc2011-03-02 19:25:28 +00002582% different level ranges for each of the color channels separately.
cristy3ed852e2009-09-05 21:47:34 +00002583%
2584% If the boolean 'invert' is set true the image values will modifyed in the
2585% reverse direction. That is any existing "black" and "white" colors in the
2586% image will become the color values given, with all other values compressed
2587% appropriatally. This effectivally maps a greyscale gradient into the given
2588% color gradient.
2589%
cristy490408a2011-07-07 14:42:05 +00002590% The format of the LevelImageColors method is:
cristy3ed852e2009-09-05 21:47:34 +00002591%
cristy490408a2011-07-07 14:42:05 +00002592% MagickBooleanType LevelImageColors(Image *image,
2593% const PixelInfo *black_color,const PixelInfo *white_color,
2594% const MagickBooleanType invert)
cristy3ed852e2009-09-05 21:47:34 +00002595%
2596% A description of each parameter follows:
2597%
2598% o image: the image.
2599%
cristy3ed852e2009-09-05 21:47:34 +00002600% o black_color: The color to map black to/from
2601%
2602% o white_point: The color to map white to/from
2603%
2604% o invert: if true map the colors (levelize), rather than from (level)
2605%
2606*/
cristy490408a2011-07-07 14:42:05 +00002607MagickExport MagickBooleanType LevelImageColors(Image *image,
cristy4c08aed2011-07-01 19:47:50 +00002608 const PixelInfo *black_color,const PixelInfo *white_color,
cristy3ed852e2009-09-05 21:47:34 +00002609 const MagickBooleanType invert)
2610{
cristy3ed852e2009-09-05 21:47:34 +00002611 MagickStatusType
2612 status;
2613
2614 /*
2615 Allocate and initialize levels map.
2616 */
2617 assert(image != (Image *) NULL);
2618 assert(image->signature == MagickSignature);
2619 if (image->debug != MagickFalse)
2620 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2621 status=MagickFalse;
2622 if (invert == MagickFalse)
2623 {
cristyed231572011-07-14 02:18:59 +00002624 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristyf89cb1d2011-07-07 01:24:37 +00002625 {
cristyed231572011-07-14 02:18:59 +00002626 PushPixelChannelMap(image,RedChannel);
cristyf89cb1d2011-07-07 01:24:37 +00002627 status|=LevelImage(image,black_color->red,white_color->red,1.0);
cristyed231572011-07-14 02:18:59 +00002628 PopPixelChannelMap(image);
cristyf89cb1d2011-07-07 01:24:37 +00002629 }
cristyed231572011-07-14 02:18:59 +00002630 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristyf89cb1d2011-07-07 01:24:37 +00002631 {
cristyed231572011-07-14 02:18:59 +00002632 PushPixelChannelMap(image,GreenChannel);
cristyf89cb1d2011-07-07 01:24:37 +00002633 status|=LevelImage(image,black_color->green,white_color->green,1.0);
cristyed231572011-07-14 02:18:59 +00002634 PopPixelChannelMap(image);
cristyf89cb1d2011-07-07 01:24:37 +00002635 }
cristyed231572011-07-14 02:18:59 +00002636 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristyf89cb1d2011-07-07 01:24:37 +00002637 {
cristyed231572011-07-14 02:18:59 +00002638 PushPixelChannelMap(image,BlueChannel);
cristyf89cb1d2011-07-07 01:24:37 +00002639 status|=LevelImage(image,black_color->blue,white_color->blue,1.0);
cristyed231572011-07-14 02:18:59 +00002640 PopPixelChannelMap(image);
cristyf89cb1d2011-07-07 01:24:37 +00002641 }
cristyed231572011-07-14 02:18:59 +00002642 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +00002643 (image->colorspace == CMYKColorspace))
cristyf89cb1d2011-07-07 01:24:37 +00002644 {
cristyed231572011-07-14 02:18:59 +00002645 PushPixelChannelMap(image,BlackChannel);
cristyf89cb1d2011-07-07 01:24:37 +00002646 status|=LevelImage(image,black_color->black,white_color->black,1.0);
cristyed231572011-07-14 02:18:59 +00002647 PopPixelChannelMap(image);
cristyf89cb1d2011-07-07 01:24:37 +00002648 }
cristyed231572011-07-14 02:18:59 +00002649 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00002650 (image->matte == MagickTrue))
cristyf89cb1d2011-07-07 01:24:37 +00002651 {
cristyed231572011-07-14 02:18:59 +00002652 PushPixelChannelMap(image,AlphaChannel);
cristyf89cb1d2011-07-07 01:24:37 +00002653 status|=LevelImage(image,black_color->alpha,white_color->alpha,1.0);
cristyed231572011-07-14 02:18:59 +00002654 PopPixelChannelMap(image);
cristyf89cb1d2011-07-07 01:24:37 +00002655 }
cristy3ed852e2009-09-05 21:47:34 +00002656 }
2657 else
2658 {
cristyed231572011-07-14 02:18:59 +00002659 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristyf89cb1d2011-07-07 01:24:37 +00002660 {
cristyed231572011-07-14 02:18:59 +00002661 PushPixelChannelMap(image,RedChannel);
cristy50fbc382011-07-07 02:19:17 +00002662 status|=LevelizeImage(image,black_color->red,white_color->red,1.0);
cristyed231572011-07-14 02:18:59 +00002663 PopPixelChannelMap(image);
cristyf89cb1d2011-07-07 01:24:37 +00002664 }
cristyed231572011-07-14 02:18:59 +00002665 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristyf89cb1d2011-07-07 01:24:37 +00002666 {
cristyed231572011-07-14 02:18:59 +00002667 PushPixelChannelMap(image,GreenChannel);
cristy50fbc382011-07-07 02:19:17 +00002668 status|=LevelizeImage(image,black_color->green,white_color->green,
2669 1.0);
cristyed231572011-07-14 02:18:59 +00002670 PopPixelChannelMap(image);
cristyf89cb1d2011-07-07 01:24:37 +00002671 }
cristyed231572011-07-14 02:18:59 +00002672 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristyf89cb1d2011-07-07 01:24:37 +00002673 {
cristyed231572011-07-14 02:18:59 +00002674 PushPixelChannelMap(image,BlueChannel);
cristy50fbc382011-07-07 02:19:17 +00002675 status|=LevelizeImage(image,black_color->blue,white_color->blue,1.0);
cristyed231572011-07-14 02:18:59 +00002676 PopPixelChannelMap(image);
cristyf89cb1d2011-07-07 01:24:37 +00002677 }
cristyed231572011-07-14 02:18:59 +00002678 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +00002679 (image->colorspace == CMYKColorspace))
cristyf89cb1d2011-07-07 01:24:37 +00002680 {
cristyed231572011-07-14 02:18:59 +00002681 PushPixelChannelMap(image,BlackChannel);
cristy50fbc382011-07-07 02:19:17 +00002682 status|=LevelizeImage(image,black_color->black,white_color->black,
2683 1.0);
cristyed231572011-07-14 02:18:59 +00002684 PopPixelChannelMap(image);
cristyf89cb1d2011-07-07 01:24:37 +00002685 }
cristyed231572011-07-14 02:18:59 +00002686 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00002687 (image->matte == MagickTrue))
cristyf89cb1d2011-07-07 01:24:37 +00002688 {
cristyed231572011-07-14 02:18:59 +00002689 PushPixelChannelMap(image,AlphaChannel);
cristy50fbc382011-07-07 02:19:17 +00002690 status|=LevelizeImage(image,black_color->alpha,white_color->alpha,
2691 1.0);
cristyed231572011-07-14 02:18:59 +00002692 PopPixelChannelMap(image);
cristyf89cb1d2011-07-07 01:24:37 +00002693 }
cristy3ed852e2009-09-05 21:47:34 +00002694 }
2695 return(status == 0 ? MagickFalse : MagickTrue);
2696}
2697
2698/*
2699%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2700% %
2701% %
2702% %
2703% L i n e a r S t r e t c h I m a g e %
2704% %
2705% %
2706% %
2707%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2708%
cristyf1611782011-08-01 15:39:13 +00002709% LinearStretchImage() discards any pixels below the black point and above
2710% the white point and levels the remaining pixels.
cristy3ed852e2009-09-05 21:47:34 +00002711%
2712% The format of the LinearStretchImage method is:
2713%
2714% MagickBooleanType LinearStretchImage(Image *image,
2715% const double black_point,const double white_point)
2716%
2717% A description of each parameter follows:
2718%
2719% o image: the image.
2720%
2721% o black_point: the black point.
2722%
2723% o white_point: the white point.
2724%
2725*/
2726MagickExport MagickBooleanType LinearStretchImage(Image *image,
2727 const double black_point,const double white_point)
2728{
2729#define LinearStretchImageTag "LinearStretch/Image"
2730
2731 ExceptionInfo
2732 *exception;
2733
cristy3ed852e2009-09-05 21:47:34 +00002734 MagickBooleanType
2735 status;
2736
2737 MagickRealType
2738 *histogram,
2739 intensity;
2740
cristy8d4629b2010-08-30 17:59:46 +00002741 ssize_t
2742 black,
2743 white,
2744 y;
2745
cristy3ed852e2009-09-05 21:47:34 +00002746 /*
2747 Allocate histogram and linear map.
2748 */
2749 assert(image != (Image *) NULL);
2750 assert(image->signature == MagickSignature);
2751 histogram=(MagickRealType *) AcquireQuantumMemory(MaxMap+1UL,
2752 sizeof(*histogram));
2753 if (histogram == (MagickRealType *) NULL)
2754 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
2755 image->filename);
2756 /*
2757 Form histogram.
2758 */
2759 (void) ResetMagickMemory(histogram,0,(MaxMap+1)*sizeof(*histogram));
2760 exception=(&image->exception);
cristybb503372010-05-27 20:51:26 +00002761 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002762 {
cristy4c08aed2011-07-01 19:47:50 +00002763 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00002764 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00002765
cristybb503372010-05-27 20:51:26 +00002766 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002767 x;
2768
2769 p=GetVirtualPixels(image,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00002770 if (p == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00002771 break;
cristybb503372010-05-27 20:51:26 +00002772 for (x=(ssize_t) image->columns-1; x >= 0; x--)
cristy3ed852e2009-09-05 21:47:34 +00002773 {
cristy4c08aed2011-07-01 19:47:50 +00002774 histogram[ScaleQuantumToMap(GetPixelIntensity(image,p))]++;
cristyed231572011-07-14 02:18:59 +00002775 p+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00002776 }
2777 }
2778 /*
2779 Find the histogram boundaries by locating the black and white point levels.
2780 */
cristy3ed852e2009-09-05 21:47:34 +00002781 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00002782 for (black=0; black < (ssize_t) MaxMap; black++)
cristy3ed852e2009-09-05 21:47:34 +00002783 {
2784 intensity+=histogram[black];
2785 if (intensity >= black_point)
2786 break;
2787 }
2788 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00002789 for (white=(ssize_t) MaxMap; white != 0; white--)
cristy3ed852e2009-09-05 21:47:34 +00002790 {
2791 intensity+=histogram[white];
2792 if (intensity >= white_point)
2793 break;
2794 }
2795 histogram=(MagickRealType *) RelinquishMagickMemory(histogram);
cristyf89cb1d2011-07-07 01:24:37 +00002796 status=LevelImage(image,(double) black,(double) white,1.0);
cristy3ed852e2009-09-05 21:47:34 +00002797 return(status);
2798}
2799
2800/*
2801%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2802% %
2803% %
2804% %
2805% M o d u l a t e I m a g e %
2806% %
2807% %
2808% %
2809%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2810%
2811% ModulateImage() lets you control the brightness, saturation, and hue
2812% of an image. Modulate represents the brightness, saturation, and hue
2813% as one parameter (e.g. 90,150,100). If the image colorspace is HSL, the
2814% modulation is lightness, saturation, and hue. And if the colorspace is
2815% HWB, use blackness, whiteness, and hue.
2816%
2817% The format of the ModulateImage method is:
2818%
2819% MagickBooleanType ModulateImage(Image *image,const char *modulate)
2820%
2821% A description of each parameter follows:
2822%
2823% o image: the image.
2824%
2825% o modulate: Define the percent change in brightness, saturation, and
2826% hue.
2827%
2828*/
2829
2830static void ModulateHSB(const double percent_hue,
2831 const double percent_saturation,const double percent_brightness,
2832 Quantum *red,Quantum *green,Quantum *blue)
2833{
2834 double
2835 brightness,
2836 hue,
2837 saturation;
2838
2839 /*
2840 Increase or decrease color brightness, saturation, or hue.
2841 */
2842 assert(red != (Quantum *) NULL);
2843 assert(green != (Quantum *) NULL);
2844 assert(blue != (Quantum *) NULL);
2845 ConvertRGBToHSB(*red,*green,*blue,&hue,&saturation,&brightness);
2846 hue+=0.5*(0.01*percent_hue-1.0);
2847 while (hue < 0.0)
2848 hue+=1.0;
2849 while (hue > 1.0)
2850 hue-=1.0;
2851 saturation*=0.01*percent_saturation;
2852 brightness*=0.01*percent_brightness;
2853 ConvertHSBToRGB(hue,saturation,brightness,red,green,blue);
2854}
2855
2856static void ModulateHSL(const double percent_hue,
2857 const double percent_saturation,const double percent_lightness,
2858 Quantum *red,Quantum *green,Quantum *blue)
2859{
2860 double
2861 hue,
2862 lightness,
2863 saturation;
2864
2865 /*
2866 Increase or decrease color lightness, saturation, or hue.
2867 */
2868 assert(red != (Quantum *) NULL);
2869 assert(green != (Quantum *) NULL);
2870 assert(blue != (Quantum *) NULL);
2871 ConvertRGBToHSL(*red,*green,*blue,&hue,&saturation,&lightness);
2872 hue+=0.5*(0.01*percent_hue-1.0);
2873 while (hue < 0.0)
2874 hue+=1.0;
2875 while (hue > 1.0)
2876 hue-=1.0;
2877 saturation*=0.01*percent_saturation;
2878 lightness*=0.01*percent_lightness;
2879 ConvertHSLToRGB(hue,saturation,lightness,red,green,blue);
2880}
2881
2882static void ModulateHWB(const double percent_hue,const double percent_whiteness, const double percent_blackness,Quantum *red,Quantum *green,Quantum *blue)
2883{
2884 double
2885 blackness,
2886 hue,
2887 whiteness;
2888
2889 /*
2890 Increase or decrease color blackness, whiteness, or hue.
2891 */
2892 assert(red != (Quantum *) NULL);
2893 assert(green != (Quantum *) NULL);
2894 assert(blue != (Quantum *) NULL);
2895 ConvertRGBToHWB(*red,*green,*blue,&hue,&whiteness,&blackness);
2896 hue+=0.5*(0.01*percent_hue-1.0);
2897 while (hue < 0.0)
2898 hue+=1.0;
2899 while (hue > 1.0)
2900 hue-=1.0;
2901 blackness*=0.01*percent_blackness;
2902 whiteness*=0.01*percent_whiteness;
2903 ConvertHWBToRGB(hue,whiteness,blackness,red,green,blue);
2904}
2905
2906MagickExport MagickBooleanType ModulateImage(Image *image,const char *modulate)
2907{
2908#define ModulateImageTag "Modulate/Image"
2909
cristyc4c8d132010-01-07 01:58:38 +00002910 CacheView
2911 *image_view;
2912
cristy3ed852e2009-09-05 21:47:34 +00002913 ColorspaceType
2914 colorspace;
2915
2916 const char
2917 *artifact;
2918
2919 double
2920 percent_brightness,
2921 percent_hue,
2922 percent_saturation;
2923
2924 ExceptionInfo
2925 *exception;
2926
2927 GeometryInfo
2928 geometry_info;
2929
cristy3ed852e2009-09-05 21:47:34 +00002930 MagickBooleanType
2931 status;
2932
cristybb503372010-05-27 20:51:26 +00002933 MagickOffsetType
2934 progress;
2935
cristy3ed852e2009-09-05 21:47:34 +00002936 MagickStatusType
2937 flags;
2938
cristybb503372010-05-27 20:51:26 +00002939 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002940 i;
2941
cristybb503372010-05-27 20:51:26 +00002942 ssize_t
2943 y;
2944
cristy3ed852e2009-09-05 21:47:34 +00002945 /*
cristy2b726bd2010-01-11 01:05:39 +00002946 Initialize modulate table.
cristy3ed852e2009-09-05 21:47:34 +00002947 */
2948 assert(image != (Image *) NULL);
2949 assert(image->signature == MagickSignature);
2950 if (image->debug != MagickFalse)
2951 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2952 if (modulate == (char *) NULL)
2953 return(MagickFalse);
2954 flags=ParseGeometry(modulate,&geometry_info);
2955 percent_brightness=geometry_info.rho;
2956 percent_saturation=geometry_info.sigma;
2957 if ((flags & SigmaValue) == 0)
2958 percent_saturation=100.0;
2959 percent_hue=geometry_info.xi;
2960 if ((flags & XiValue) == 0)
2961 percent_hue=100.0;
2962 colorspace=UndefinedColorspace;
2963 artifact=GetImageArtifact(image,"modulate:colorspace");
2964 if (artifact != (const char *) NULL)
cristy042ee782011-04-22 18:48:30 +00002965 colorspace=(ColorspaceType) ParseCommandOption(MagickColorspaceOptions,
cristy3ed852e2009-09-05 21:47:34 +00002966 MagickFalse,artifact);
2967 if (image->storage_class == PseudoClass)
2968 {
2969 /*
2970 Modulate colormap.
2971 */
cristyb5d5f722009-11-04 03:03:49 +00002972#if defined(MAGICKCORE_OPENMP_SUPPORT)
2973 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002974#endif
cristybb503372010-05-27 20:51:26 +00002975 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00002976 switch (colorspace)
2977 {
2978 case HSBColorspace:
2979 {
2980 ModulateHSB(percent_hue,percent_saturation,percent_brightness,
2981 &image->colormap[i].red,&image->colormap[i].green,
2982 &image->colormap[i].blue);
2983 break;
2984 }
2985 case HSLColorspace:
2986 default:
2987 {
2988 ModulateHSL(percent_hue,percent_saturation,percent_brightness,
2989 &image->colormap[i].red,&image->colormap[i].green,
2990 &image->colormap[i].blue);
2991 break;
2992 }
2993 case HWBColorspace:
2994 {
2995 ModulateHWB(percent_hue,percent_saturation,percent_brightness,
2996 &image->colormap[i].red,&image->colormap[i].green,
2997 &image->colormap[i].blue);
2998 break;
2999 }
3000 }
3001 }
3002 /*
3003 Modulate image.
3004 */
3005 status=MagickTrue;
3006 progress=0;
3007 exception=(&image->exception);
3008 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00003009#if defined(MAGICKCORE_OPENMP_SUPPORT)
3010 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003011#endif
cristybb503372010-05-27 20:51:26 +00003012 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003013 {
cristy5afeab82011-04-30 01:30:09 +00003014 Quantum
3015 blue,
3016 green,
3017 red;
3018
cristy4c08aed2011-07-01 19:47:50 +00003019 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00003020 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003021
cristy8d4629b2010-08-30 17:59:46 +00003022 register ssize_t
3023 x;
3024
cristy3ed852e2009-09-05 21:47:34 +00003025 if (status == MagickFalse)
3026 continue;
3027 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00003028 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00003029 {
3030 status=MagickFalse;
3031 continue;
3032 }
cristybb503372010-05-27 20:51:26 +00003033 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003034 {
cristy4c08aed2011-07-01 19:47:50 +00003035 red=GetPixelRed(image,q);
3036 green=GetPixelGreen(image,q);
3037 blue=GetPixelBlue(image,q);
cristy3ed852e2009-09-05 21:47:34 +00003038 switch (colorspace)
3039 {
3040 case HSBColorspace:
3041 {
3042 ModulateHSB(percent_hue,percent_saturation,percent_brightness,
cristy5afeab82011-04-30 01:30:09 +00003043 &red,&green,&blue);
cristy3ed852e2009-09-05 21:47:34 +00003044 break;
3045 }
3046 case HSLColorspace:
3047 default:
3048 {
3049 ModulateHSL(percent_hue,percent_saturation,percent_brightness,
cristy5afeab82011-04-30 01:30:09 +00003050 &red,&green,&blue);
cristy3ed852e2009-09-05 21:47:34 +00003051 break;
3052 }
3053 case HWBColorspace:
3054 {
3055 ModulateHWB(percent_hue,percent_saturation,percent_brightness,
cristy5afeab82011-04-30 01:30:09 +00003056 &red,&green,&blue);
cristy3ed852e2009-09-05 21:47:34 +00003057 break;
3058 }
3059 }
cristy4c08aed2011-07-01 19:47:50 +00003060 SetPixelRed(image,red,q);
3061 SetPixelGreen(image,green,q);
3062 SetPixelBlue(image,blue,q);
cristyed231572011-07-14 02:18:59 +00003063 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00003064 }
3065 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
3066 status=MagickFalse;
3067 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3068 {
3069 MagickBooleanType
3070 proceed;
3071
cristyb5d5f722009-11-04 03:03:49 +00003072#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00003073 #pragma omp critical (MagickCore_ModulateImage)
3074#endif
3075 proceed=SetImageProgress(image,ModulateImageTag,progress++,image->rows);
3076 if (proceed == MagickFalse)
3077 status=MagickFalse;
3078 }
3079 }
3080 image_view=DestroyCacheView(image_view);
3081 return(status);
3082}
3083
3084/*
3085%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3086% %
3087% %
3088% %
3089% N e g a t e I m a g e %
3090% %
3091% %
3092% %
3093%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3094%
3095% NegateImage() negates the colors in the reference image. The grayscale
3096% option means that only grayscale values within the image are negated.
3097%
cristy50fbc382011-07-07 02:19:17 +00003098% The format of the NegateImage method is:
cristy3ed852e2009-09-05 21:47:34 +00003099%
3100% MagickBooleanType NegateImage(Image *image,
cristyb3e7c6c2011-07-24 01:43:55 +00003101% const MagickBooleanType grayscale,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003102%
3103% A description of each parameter follows:
3104%
3105% o image: the image.
3106%
cristy3ed852e2009-09-05 21:47:34 +00003107% o grayscale: If MagickTrue, only negate grayscale pixels within the image.
3108%
cristyb3e7c6c2011-07-24 01:43:55 +00003109% o exception: return any errors or warnings in this structure.
3110%
cristy3ed852e2009-09-05 21:47:34 +00003111*/
cristy3ed852e2009-09-05 21:47:34 +00003112MagickExport MagickBooleanType NegateImage(Image *image,
cristyb3e7c6c2011-07-24 01:43:55 +00003113 const MagickBooleanType grayscale,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003114{
cristy3ed852e2009-09-05 21:47:34 +00003115#define NegateImageTag "Negate/Image"
3116
cristyc4c8d132010-01-07 01:58:38 +00003117 CacheView
3118 *image_view;
3119
cristy3ed852e2009-09-05 21:47:34 +00003120 MagickBooleanType
3121 status;
3122
cristybb503372010-05-27 20:51:26 +00003123 MagickOffsetType
3124 progress;
3125
3126 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003127 i;
3128
cristybb503372010-05-27 20:51:26 +00003129 ssize_t
3130 y;
3131
cristy3ed852e2009-09-05 21:47:34 +00003132 assert(image != (Image *) NULL);
3133 assert(image->signature == MagickSignature);
3134 if (image->debug != MagickFalse)
3135 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3136 if (image->storage_class == PseudoClass)
3137 {
3138 /*
3139 Negate colormap.
3140 */
cristyb5d5f722009-11-04 03:03:49 +00003141#if defined(MAGICKCORE_OPENMP_SUPPORT)
3142 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003143#endif
cristybb503372010-05-27 20:51:26 +00003144 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00003145 {
3146 if (grayscale != MagickFalse)
3147 if ((image->colormap[i].red != image->colormap[i].green) ||
3148 (image->colormap[i].green != image->colormap[i].blue))
3149 continue;
cristyed231572011-07-14 02:18:59 +00003150 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00003151 image->colormap[i].red=(Quantum) QuantumRange-
3152 image->colormap[i].red;
cristyed231572011-07-14 02:18:59 +00003153 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00003154 image->colormap[i].green=(Quantum) QuantumRange-
3155 image->colormap[i].green;
cristyed231572011-07-14 02:18:59 +00003156 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00003157 image->colormap[i].blue=(Quantum) QuantumRange-
3158 image->colormap[i].blue;
3159 }
3160 }
3161 /*
3162 Negate image.
3163 */
3164 status=MagickTrue;
3165 progress=0;
cristy3ed852e2009-09-05 21:47:34 +00003166 image_view=AcquireCacheView(image);
3167 if (grayscale != MagickFalse)
3168 {
cristyb5d5f722009-11-04 03:03:49 +00003169#if defined(MAGICKCORE_OPENMP_SUPPORT)
3170 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003171#endif
cristybb503372010-05-27 20:51:26 +00003172 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003173 {
3174 MagickBooleanType
3175 sync;
3176
cristy4c08aed2011-07-01 19:47:50 +00003177 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00003178 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003179
cristy8d4629b2010-08-30 17:59:46 +00003180 register ssize_t
3181 x;
3182
cristy3ed852e2009-09-05 21:47:34 +00003183 if (status == MagickFalse)
3184 continue;
3185 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,
3186 exception);
cristy4c08aed2011-07-01 19:47:50 +00003187 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00003188 {
3189 status=MagickFalse;
3190 continue;
3191 }
cristybb503372010-05-27 20:51:26 +00003192 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003193 {
cristy9aa95be2011-07-20 21:56:45 +00003194 register ssize_t
3195 i;
3196
cristyd476a8e2011-07-23 16:13:22 +00003197 if (IsPixelGray(image,q) != MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00003198 {
cristya30d9ba2011-07-23 21:00:48 +00003199 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00003200 continue;
3201 }
cristya30d9ba2011-07-23 21:00:48 +00003202 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
cristy9aa95be2011-07-20 21:56:45 +00003203 {
3204 PixelTrait
3205 traits;
3206
3207 traits=GetPixelChannelMapTraits(image,(PixelChannel) i);
3208 if ((traits & UpdatePixelTrait) != 0)
3209 q[i]=QuantumRange-q[i];
3210 }
cristya30d9ba2011-07-23 21:00:48 +00003211 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00003212 }
3213 sync=SyncCacheViewAuthenticPixels(image_view,exception);
3214 if (sync == MagickFalse)
3215 status=MagickFalse;
3216 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3217 {
3218 MagickBooleanType
3219 proceed;
3220
cristyb5d5f722009-11-04 03:03:49 +00003221#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy50fbc382011-07-07 02:19:17 +00003222 #pragma omp critical (MagickCore_NegateImage)
cristy3ed852e2009-09-05 21:47:34 +00003223#endif
3224 proceed=SetImageProgress(image,NegateImageTag,progress++,
3225 image->rows);
3226 if (proceed == MagickFalse)
3227 status=MagickFalse;
3228 }
3229 }
3230 image_view=DestroyCacheView(image_view);
3231 return(MagickTrue);
3232 }
3233 /*
3234 Negate image.
3235 */
cristyb5d5f722009-11-04 03:03:49 +00003236#if defined(MAGICKCORE_OPENMP_SUPPORT)
3237 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003238#endif
cristybb503372010-05-27 20:51:26 +00003239 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003240 {
cristy4c08aed2011-07-01 19:47:50 +00003241 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00003242 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003243
cristy8d4629b2010-08-30 17:59:46 +00003244 register ssize_t
3245 x;
3246
cristy3ed852e2009-09-05 21:47:34 +00003247 if (status == MagickFalse)
3248 continue;
3249 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00003250 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00003251 {
3252 status=MagickFalse;
3253 continue;
3254 }
cristybb503372010-05-27 20:51:26 +00003255 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003256 {
cristyf7dc44c2011-07-20 14:41:15 +00003257 register ssize_t
3258 i;
3259
cristya30d9ba2011-07-23 21:00:48 +00003260 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
cristyf7dc44c2011-07-20 14:41:15 +00003261 {
3262 PixelTrait
3263 traits;
3264
3265 traits=GetPixelChannelMapTraits(image,(PixelChannel) i);
3266 if ((traits & UpdatePixelTrait) != 0)
3267 q[i]=QuantumRange-q[i];
3268 }
cristya30d9ba2011-07-23 21:00:48 +00003269 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00003270 }
3271 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
3272 status=MagickFalse;
3273 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3274 {
3275 MagickBooleanType
3276 proceed;
3277
cristyb5d5f722009-11-04 03:03:49 +00003278#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy50fbc382011-07-07 02:19:17 +00003279 #pragma omp critical (MagickCore_NegateImage)
cristy3ed852e2009-09-05 21:47:34 +00003280#endif
3281 proceed=SetImageProgress(image,NegateImageTag,progress++,image->rows);
3282 if (proceed == MagickFalse)
3283 status=MagickFalse;
3284 }
3285 }
3286 image_view=DestroyCacheView(image_view);
3287 return(status);
3288}
3289
3290/*
3291%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3292% %
3293% %
3294% %
3295% N o r m a l i z e I m a g e %
3296% %
3297% %
3298% %
3299%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3300%
cristya4dfb122011-07-07 19:01:57 +00003301% NormalizeImage() enhances the contrast of a color image by mapping the
3302% darkest 2 percent of all pixel to black and the brightest 1 percent to white.
cristy3ed852e2009-09-05 21:47:34 +00003303%
3304% The format of the NormalizeImage method is:
3305%
3306% MagickBooleanType NormalizeImage(Image *image)
cristy3ed852e2009-09-05 21:47:34 +00003307%
3308% A description of each parameter follows:
3309%
3310% o image: the image.
3311%
cristy3ed852e2009-09-05 21:47:34 +00003312*/
cristy3ed852e2009-09-05 21:47:34 +00003313MagickExport MagickBooleanType NormalizeImage(Image *image)
3314{
cristy3ed852e2009-09-05 21:47:34 +00003315 double
3316 black_point,
3317 white_point;
3318
cristy530239c2010-07-25 17:34:26 +00003319 black_point=(double) image->columns*image->rows*0.0015;
3320 white_point=(double) image->columns*image->rows*0.9995;
cristy50fbc382011-07-07 02:19:17 +00003321 return(ContrastStretchImage(image,black_point,white_point));
cristy3ed852e2009-09-05 21:47:34 +00003322}
3323
3324/*
3325%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3326% %
3327% %
3328% %
3329% S i g m o i d a l C o n t r a s t I m a g e %
3330% %
3331% %
3332% %
3333%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3334%
3335% SigmoidalContrastImage() adjusts the contrast of an image with a non-linear
3336% sigmoidal contrast algorithm. Increase the contrast of the image using a
3337% sigmoidal transfer function without saturating highlights or shadows.
3338% Contrast indicates how much to increase the contrast (0 is none; 3 is
3339% typical; 20 is pushing it); mid-point indicates where midtones fall in the
3340% resultant image (0 is white; 50% is middle-gray; 100% is black). Set
3341% sharpen to MagickTrue to increase the image contrast otherwise the contrast
3342% is reduced.
3343%
3344% The format of the SigmoidalContrastImage method is:
3345%
3346% MagickBooleanType SigmoidalContrastImage(Image *image,
3347% const MagickBooleanType sharpen,const char *levels)
cristy3ed852e2009-09-05 21:47:34 +00003348%
3349% A description of each parameter follows:
3350%
3351% o image: the image.
3352%
cristy3ed852e2009-09-05 21:47:34 +00003353% o sharpen: Increase or decrease image contrast.
3354%
cristyfa769582010-09-30 23:30:03 +00003355% o alpha: strength of the contrast, the larger the number the more
3356% 'threshold-like' it becomes.
cristy3ed852e2009-09-05 21:47:34 +00003357%
cristyfa769582010-09-30 23:30:03 +00003358% o beta: midpoint of the function as a color value 0 to QuantumRange.
cristy3ed852e2009-09-05 21:47:34 +00003359%
3360*/
cristy3ed852e2009-09-05 21:47:34 +00003361MagickExport MagickBooleanType SigmoidalContrastImage(Image *image,
cristy9ee60942011-07-06 14:54:38 +00003362 const MagickBooleanType sharpen,const double contrast,const double midpoint)
cristy3ed852e2009-09-05 21:47:34 +00003363{
3364#define SigmoidalContrastImageTag "SigmoidalContrast/Image"
3365
cristyc4c8d132010-01-07 01:58:38 +00003366 CacheView
3367 *image_view;
3368
cristy3ed852e2009-09-05 21:47:34 +00003369 ExceptionInfo
3370 *exception;
3371
cristy3ed852e2009-09-05 21:47:34 +00003372 MagickBooleanType
3373 status;
3374
cristybb503372010-05-27 20:51:26 +00003375 MagickOffsetType
3376 progress;
3377
cristy3ed852e2009-09-05 21:47:34 +00003378 MagickRealType
3379 *sigmoidal_map;
3380
cristybb503372010-05-27 20:51:26 +00003381 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003382 i;
3383
cristybb503372010-05-27 20:51:26 +00003384 ssize_t
3385 y;
3386
cristy3ed852e2009-09-05 21:47:34 +00003387 /*
3388 Allocate and initialize sigmoidal maps.
3389 */
3390 assert(image != (Image *) NULL);
3391 assert(image->signature == MagickSignature);
3392 if (image->debug != MagickFalse)
3393 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3394 sigmoidal_map=(MagickRealType *) AcquireQuantumMemory(MaxMap+1UL,
3395 sizeof(*sigmoidal_map));
3396 if (sigmoidal_map == (MagickRealType *) NULL)
3397 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
3398 image->filename);
3399 (void) ResetMagickMemory(sigmoidal_map,0,(MaxMap+1)*sizeof(*sigmoidal_map));
cristyb5d5f722009-11-04 03:03:49 +00003400#if defined(MAGICKCORE_OPENMP_SUPPORT)
3401 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003402#endif
cristybb503372010-05-27 20:51:26 +00003403 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +00003404 {
3405 if (sharpen != MagickFalse)
3406 {
3407 sigmoidal_map[i]=(MagickRealType) ScaleMapToQuantum((MagickRealType)
3408 (MaxMap*((1.0/(1.0+exp(contrast*(midpoint/(double) QuantumRange-
3409 (double) i/MaxMap))))-(1.0/(1.0+exp(contrast*(midpoint/
3410 (double) QuantumRange)))))/((1.0/(1.0+exp(contrast*(midpoint/
3411 (double) QuantumRange-1.0))))-(1.0/(1.0+exp(contrast*(midpoint/
3412 (double) QuantumRange)))))+0.5));
3413 continue;
3414 }
3415 sigmoidal_map[i]=(MagickRealType) ScaleMapToQuantum((MagickRealType)
3416 (MaxMap*(QuantumScale*midpoint-log((1.0-(1.0/(1.0+exp(midpoint/
3417 (double) QuantumRange*contrast))+((double) i/MaxMap)*((1.0/
3418 (1.0+exp(contrast*(midpoint/(double) QuantumRange-1.0))))-(1.0/
3419 (1.0+exp(midpoint/(double) QuantumRange*contrast))))))/
3420 (1.0/(1.0+exp(midpoint/(double) QuantumRange*contrast))+
3421 ((double) i/MaxMap)*((1.0/(1.0+exp(contrast*(midpoint/
3422 (double) QuantumRange-1.0))))-(1.0/(1.0+exp(midpoint/
3423 (double) QuantumRange*contrast))))))/contrast)));
3424 }
3425 if (image->storage_class == PseudoClass)
3426 {
3427 /*
3428 Sigmoidal-contrast enhance colormap.
3429 */
cristyb5d5f722009-11-04 03:03:49 +00003430#if defined(MAGICKCORE_OPENMP_SUPPORT)
3431 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003432#endif
cristybb503372010-05-27 20:51:26 +00003433 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00003434 {
cristyed231572011-07-14 02:18:59 +00003435 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristyce70c172010-01-07 17:15:30 +00003436 image->colormap[i].red=ClampToQuantum(sigmoidal_map[
cristy3ed852e2009-09-05 21:47:34 +00003437 ScaleQuantumToMap(image->colormap[i].red)]);
cristyed231572011-07-14 02:18:59 +00003438 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristyce70c172010-01-07 17:15:30 +00003439 image->colormap[i].green=ClampToQuantum(sigmoidal_map[
cristy3ed852e2009-09-05 21:47:34 +00003440 ScaleQuantumToMap(image->colormap[i].green)]);
cristyed231572011-07-14 02:18:59 +00003441 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristyce70c172010-01-07 17:15:30 +00003442 image->colormap[i].blue=ClampToQuantum(sigmoidal_map[
cristy3ed852e2009-09-05 21:47:34 +00003443 ScaleQuantumToMap(image->colormap[i].blue)]);
cristyed231572011-07-14 02:18:59 +00003444 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003445 image->colormap[i].alpha=ClampToQuantum(sigmoidal_map[
3446 ScaleQuantumToMap(image->colormap[i].alpha)]);
cristy3ed852e2009-09-05 21:47:34 +00003447 }
3448 }
3449 /*
3450 Sigmoidal-contrast enhance image.
3451 */
3452 status=MagickTrue;
3453 progress=0;
3454 exception=(&image->exception);
3455 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00003456#if defined(MAGICKCORE_OPENMP_SUPPORT)
3457 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003458#endif
cristybb503372010-05-27 20:51:26 +00003459 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003460 {
cristy4c08aed2011-07-01 19:47:50 +00003461 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00003462 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003463
cristy8d4629b2010-08-30 17:59:46 +00003464 register ssize_t
3465 x;
3466
cristy3ed852e2009-09-05 21:47:34 +00003467 if (status == MagickFalse)
3468 continue;
3469 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00003470 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00003471 {
3472 status=MagickFalse;
3473 continue;
3474 }
cristybb503372010-05-27 20:51:26 +00003475 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003476 {
cristyed231572011-07-14 02:18:59 +00003477 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003478 SetPixelRed(image,ClampToQuantum(sigmoidal_map[ScaleQuantumToMap(
3479 GetPixelRed(image,q))]),q);
cristyed231572011-07-14 02:18:59 +00003480 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003481 SetPixelGreen(image,ClampToQuantum(sigmoidal_map[ScaleQuantumToMap(
3482 GetPixelGreen(image,q))]),q);
cristyed231572011-07-14 02:18:59 +00003483 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003484 SetPixelBlue(image,ClampToQuantum(sigmoidal_map[ScaleQuantumToMap(
3485 GetPixelBlue(image,q))]),q);
cristyed231572011-07-14 02:18:59 +00003486 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00003487 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +00003488 SetPixelBlack(image,ClampToQuantum(sigmoidal_map[ScaleQuantumToMap(
3489 GetPixelBlack(image,q))]),q);
cristyed231572011-07-14 02:18:59 +00003490 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003491 SetPixelAlpha(image,ClampToQuantum(sigmoidal_map[ScaleQuantumToMap(
3492 GetPixelAlpha(image,q))]),q);
cristyed231572011-07-14 02:18:59 +00003493 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00003494 }
3495 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
3496 status=MagickFalse;
3497 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3498 {
3499 MagickBooleanType
3500 proceed;
3501
cristyb5d5f722009-11-04 03:03:49 +00003502#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy9ee60942011-07-06 14:54:38 +00003503 #pragma omp critical (MagickCore_SigmoidalContrastImage)
cristy3ed852e2009-09-05 21:47:34 +00003504#endif
3505 proceed=SetImageProgress(image,SigmoidalContrastImageTag,progress++,
3506 image->rows);
3507 if (proceed == MagickFalse)
3508 status=MagickFalse;
3509 }
3510 }
3511 image_view=DestroyCacheView(image_view);
3512 sigmoidal_map=(MagickRealType *) RelinquishMagickMemory(sigmoidal_map);
3513 return(status);
3514}