blob: bfb8f04d9a95538d45e0a86cf946cad030edc82c [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);
cristy574cc262011-08-05 01:23:58 +0000726 if (SetImageStorageClass(image,DirectClass,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +0000727 return(MagickFalse);
cristy574cc262011-08-05 01:23:58 +0000728 clut_map=(PixelInfo *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*clut_map));
cristy4c08aed2011-07-01 19:47:50 +0000729 if (clut_map == (PixelInfo *) NULL)
cristy49f37242011-03-22 18:18:23 +0000730 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
731 image->filename);
cristy3ed852e2009-09-05 21:47:34 +0000732 /*
733 Clut image.
734 */
735 status=MagickTrue;
736 progress=0;
cristybb503372010-05-27 20:51:26 +0000737 adjust=(ssize_t) (clut_image->interpolate == IntegerInterpolatePixel ? 0 : 1);
cristy3ed852e2009-09-05 21:47:34 +0000738 exception=(&image->exception);
cristy708333f2011-03-26 01:25:07 +0000739 clut_view=AcquireCacheView(clut_image);
cristyaf6bc722011-03-25 19:16:14 +0000740#if defined(MAGICKCORE_OPENMP_SUPPORT)
741 #pragma omp parallel for schedule(dynamic,4)
742#endif
cristy49f37242011-03-22 18:18:23 +0000743 for (i=0; i <= (ssize_t) MaxMap; i++)
744 {
cristy4c08aed2011-07-01 19:47:50 +0000745 GetPixelInfo(clut_image,clut_map+i);
746 (void) InterpolatePixelInfo(clut_image,clut_view,
cristy8a7c3e82011-03-26 02:10:53 +0000747 UndefinedInterpolatePixel,QuantumScale*i*(clut_image->columns-adjust),
748 QuantumScale*i*(clut_image->rows-adjust),clut_map+i,exception);
cristy49f37242011-03-22 18:18:23 +0000749 }
cristy708333f2011-03-26 01:25:07 +0000750 clut_view=DestroyCacheView(clut_view);
751 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +0000752#if defined(MAGICKCORE_OPENMP_SUPPORT)
753 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000754#endif
cristybb503372010-05-27 20:51:26 +0000755 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000756 {
cristy4c08aed2011-07-01 19:47:50 +0000757 PixelInfo
cristy3635df22011-03-25 00:16:16 +0000758 pixel;
759
cristy4c08aed2011-07-01 19:47:50 +0000760 register Quantum
cristyc47d1f82009-11-26 01:44:43 +0000761 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000762
cristy8d4629b2010-08-30 17:59:46 +0000763 register ssize_t
764 x;
765
cristy3ed852e2009-09-05 21:47:34 +0000766 if (status == MagickFalse)
767 continue;
768 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +0000769 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +0000770 {
771 status=MagickFalse;
772 continue;
773 }
cristy4c08aed2011-07-01 19:47:50 +0000774 GetPixelInfo(image,&pixel);
cristybb503372010-05-27 20:51:26 +0000775 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000776 {
cristy4c08aed2011-07-01 19:47:50 +0000777 SetPixelInfo(image,q,&pixel);
cristyed231572011-07-14 02:18:59 +0000778 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
779 SetPixelRed(image,ClampRedPixelChannel(clut_map+
cristy4c08aed2011-07-01 19:47:50 +0000780 ScaleQuantumToMap(GetPixelRed(image,q))),q);
cristyed231572011-07-14 02:18:59 +0000781 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
782 SetPixelGreen(image,ClampGreenPixelChannel(clut_map+
cristy4c08aed2011-07-01 19:47:50 +0000783 ScaleQuantumToMap(GetPixelGreen(image,q))),q);
cristyed231572011-07-14 02:18:59 +0000784 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
785 SetPixelBlue(image,ClampBluePixelChannel(clut_map+
cristy4c08aed2011-07-01 19:47:50 +0000786 ScaleQuantumToMap(GetPixelBlue(image,q))),q);
cristyed231572011-07-14 02:18:59 +0000787 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +0000788 (image->colorspace == CMYKColorspace))
cristyed231572011-07-14 02:18:59 +0000789 SetPixelBlack(image,ClampBlackPixelChannel(clut_map+
cristy4c08aed2011-07-01 19:47:50 +0000790 ScaleQuantumToMap(GetPixelBlack(image,q))),q);
cristyed231572011-07-14 02:18:59 +0000791 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy3635df22011-03-25 00:16:16 +0000792 {
793 if (clut_image->matte == MagickFalse)
cristy4c08aed2011-07-01 19:47:50 +0000794 SetPixelAlpha(image,GetPixelInfoIntensity(clut_map+
795 ScaleQuantumToMap((Quantum) GetPixelAlpha(image,q))),q);
cristy3635df22011-03-25 00:16:16 +0000796 else
797 if (image->matte == MagickFalse)
cristyed231572011-07-14 02:18:59 +0000798 SetPixelAlpha(image,ClampAlphaPixelChannel(clut_map+
cristy4c08aed2011-07-01 19:47:50 +0000799 ScaleQuantumToMap((Quantum) GetPixelInfoIntensity(&pixel))),q);
cristy3635df22011-03-25 00:16:16 +0000800 else
cristyed231572011-07-14 02:18:59 +0000801 SetPixelAlpha(image,ClampAlphaPixelChannel(clut_map+
cristy4c08aed2011-07-01 19:47:50 +0000802 ScaleQuantumToMap(GetPixelAlpha(image,q))),q);
cristy3635df22011-03-25 00:16:16 +0000803 }
cristyed231572011-07-14 02:18:59 +0000804 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +0000805 }
806 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
807 status=MagickFalse;
808 if (image->progress_monitor != (MagickProgressMonitor) NULL)
809 {
810 MagickBooleanType
811 proceed;
812
cristyb5d5f722009-11-04 03:03:49 +0000813#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf89cb1d2011-07-07 01:24:37 +0000814 #pragma omp critical (MagickCore_ClutImage)
cristy3ed852e2009-09-05 21:47:34 +0000815#endif
816 proceed=SetImageProgress(image,ClutImageTag,progress++,image->rows);
817 if (proceed == MagickFalse)
818 status=MagickFalse;
819 }
820 }
821 image_view=DestroyCacheView(image_view);
cristy4c08aed2011-07-01 19:47:50 +0000822 clut_map=(PixelInfo *) RelinquishMagickMemory(clut_map);
cristy2b9582a2011-07-04 17:38:56 +0000823 if ((clut_image->matte != MagickFalse) &&
cristyed231572011-07-14 02:18:59 +0000824 ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0))
cristy3ed852e2009-09-05 21:47:34 +0000825 (void) SetImageAlphaChannel(image,ActivateAlphaChannel);
826 return(status);
827}
828
829/*
830%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
831% %
832% %
833% %
834% C o n t r a s t I m a g e %
835% %
836% %
837% %
838%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
839%
840% ContrastImage() enhances the intensity differences between the lighter and
841% darker elements of the image. Set sharpen to a MagickTrue to increase the
842% image contrast otherwise the contrast is reduced.
843%
844% The format of the ContrastImage method is:
845%
846% MagickBooleanType ContrastImage(Image *image,
847% const MagickBooleanType sharpen)
848%
849% A description of each parameter follows:
850%
851% o image: the image.
852%
853% o sharpen: Increase or decrease image contrast.
854%
855*/
856
857static void Contrast(const int sign,Quantum *red,Quantum *green,Quantum *blue)
858{
859 double
860 brightness,
861 hue,
862 saturation;
863
864 /*
865 Enhance contrast: dark color become darker, light color become lighter.
866 */
867 assert(red != (Quantum *) NULL);
868 assert(green != (Quantum *) NULL);
869 assert(blue != (Quantum *) NULL);
870 hue=0.0;
871 saturation=0.0;
872 brightness=0.0;
873 ConvertRGBToHSB(*red,*green,*blue,&hue,&saturation,&brightness);
cristy31ac0f02011-03-12 02:04:47 +0000874 brightness+=0.5*sign*(0.5*(sin((double) (MagickPI*(brightness-0.5)))+1.0)-
cristy4205a3c2010-09-12 20:19:59 +0000875 brightness);
cristy3ed852e2009-09-05 21:47:34 +0000876 if (brightness > 1.0)
877 brightness=1.0;
878 else
879 if (brightness < 0.0)
880 brightness=0.0;
881 ConvertHSBToRGB(hue,saturation,brightness,red,green,blue);
882}
883
884MagickExport MagickBooleanType ContrastImage(Image *image,
885 const MagickBooleanType sharpen)
886{
887#define ContrastImageTag "Contrast/Image"
888
cristyc4c8d132010-01-07 01:58:38 +0000889 CacheView
890 *image_view;
891
cristy3ed852e2009-09-05 21:47:34 +0000892 ExceptionInfo
893 *exception;
894
895 int
896 sign;
897
cristy3ed852e2009-09-05 21:47:34 +0000898 MagickBooleanType
899 status;
900
cristybb503372010-05-27 20:51:26 +0000901 MagickOffsetType
902 progress;
903
904 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000905 i;
906
cristybb503372010-05-27 20:51:26 +0000907 ssize_t
908 y;
909
cristy3ed852e2009-09-05 21:47:34 +0000910 assert(image != (Image *) NULL);
911 assert(image->signature == MagickSignature);
912 if (image->debug != MagickFalse)
913 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
914 sign=sharpen != MagickFalse ? 1 : -1;
915 if (image->storage_class == PseudoClass)
916 {
917 /*
918 Contrast enhance colormap.
919 */
cristybb503372010-05-27 20:51:26 +0000920 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +0000921 Contrast(sign,&image->colormap[i].red,&image->colormap[i].green,
922 &image->colormap[i].blue);
923 }
924 /*
925 Contrast enhance image.
926 */
927 status=MagickTrue;
928 progress=0;
929 exception=(&image->exception);
930 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +0000931#if defined(MAGICKCORE_OPENMP_SUPPORT)
932 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000933#endif
cristybb503372010-05-27 20:51:26 +0000934 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000935 {
cristy5afeab82011-04-30 01:30:09 +0000936 Quantum
937 blue,
938 green,
939 red;
940
cristy4c08aed2011-07-01 19:47:50 +0000941 register Quantum
cristyc47d1f82009-11-26 01:44:43 +0000942 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000943
cristy8d4629b2010-08-30 17:59:46 +0000944 register ssize_t
945 x;
946
cristy3ed852e2009-09-05 21:47:34 +0000947 if (status == MagickFalse)
948 continue;
949 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +0000950 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +0000951 {
952 status=MagickFalse;
953 continue;
954 }
cristybb503372010-05-27 20:51:26 +0000955 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000956 {
cristy4c08aed2011-07-01 19:47:50 +0000957 red=GetPixelRed(image,q);
958 green=GetPixelGreen(image,q);
959 blue=GetPixelBlue(image,q);
cristy5afeab82011-04-30 01:30:09 +0000960 Contrast(sign,&red,&green,&blue);
cristy4c08aed2011-07-01 19:47:50 +0000961 SetPixelRed(image,red,q);
962 SetPixelGreen(image,green,q);
963 SetPixelBlue(image,blue,q);
cristyed231572011-07-14 02:18:59 +0000964 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +0000965 }
966 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
967 status=MagickFalse;
968 if (image->progress_monitor != (MagickProgressMonitor) NULL)
969 {
970 MagickBooleanType
971 proceed;
972
cristyb5d5f722009-11-04 03:03:49 +0000973#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +0000974 #pragma omp critical (MagickCore_ContrastImage)
975#endif
976 proceed=SetImageProgress(image,ContrastImageTag,progress++,image->rows);
977 if (proceed == MagickFalse)
978 status=MagickFalse;
979 }
980 }
981 image_view=DestroyCacheView(image_view);
982 return(status);
983}
984
985/*
986%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
987% %
988% %
989% %
990% C o n t r a s t S t r e t c h I m a g e %
991% %
992% %
993% %
994%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
995%
cristyf1611782011-08-01 15:39:13 +0000996% ContrastStretchImage() is a simple image enhancement technique that attempts
997% to improve the contrast in an image by `stretching' the range of intensity
998% values it contains to span a desired range of values. It differs from the
999% more sophisticated histogram equalization in that it can only apply a
1000% linear scaling function to the image pixel values. As a result the
1001% `enhancement' is less harsh.
cristy3ed852e2009-09-05 21:47:34 +00001002%
1003% The format of the ContrastStretchImage method is:
1004%
1005% MagickBooleanType ContrastStretchImage(Image *image,
1006% const char *levels)
cristy3ed852e2009-09-05 21:47:34 +00001007%
1008% A description of each parameter follows:
1009%
1010% o image: the image.
1011%
cristy3ed852e2009-09-05 21:47:34 +00001012% o black_point: the black point.
1013%
1014% o white_point: the white point.
1015%
1016% o levels: Specify the levels where the black and white points have the
1017% range of 0 to number-of-pixels (e.g. 1%, 10x90%, etc.).
1018%
1019*/
cristy3ed852e2009-09-05 21:47:34 +00001020MagickExport MagickBooleanType ContrastStretchImage(Image *image,
cristy50fbc382011-07-07 02:19:17 +00001021 const double black_point,const double white_point)
cristy3ed852e2009-09-05 21:47:34 +00001022{
1023#define MaxRange(color) ((MagickRealType) ScaleQuantumToMap((Quantum) (color)))
1024#define ContrastStretchImageTag "ContrastStretch/Image"
1025
cristyc4c8d132010-01-07 01:58:38 +00001026 CacheView
1027 *image_view;
1028
cristy3ed852e2009-09-05 21:47:34 +00001029 double
1030 intensity;
1031
1032 ExceptionInfo
1033 *exception;
1034
cristy3ed852e2009-09-05 21:47:34 +00001035 MagickBooleanType
1036 status;
1037
cristybb503372010-05-27 20:51:26 +00001038 MagickOffsetType
1039 progress;
1040
cristy4c08aed2011-07-01 19:47:50 +00001041 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00001042 black,
1043 *histogram,
1044 *stretch_map,
1045 white;
1046
cristybb503372010-05-27 20:51:26 +00001047 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001048 i;
1049
cristybb503372010-05-27 20:51:26 +00001050 ssize_t
1051 y;
1052
cristy3ed852e2009-09-05 21:47:34 +00001053 /*
1054 Allocate histogram and stretch map.
1055 */
1056 assert(image != (Image *) NULL);
1057 assert(image->signature == MagickSignature);
1058 if (image->debug != MagickFalse)
1059 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
cristy4c08aed2011-07-01 19:47:50 +00001060 histogram=(PixelInfo *) AcquireQuantumMemory(MaxMap+1UL,
cristy3ed852e2009-09-05 21:47:34 +00001061 sizeof(*histogram));
cristy4c08aed2011-07-01 19:47:50 +00001062 stretch_map=(PixelInfo *) AcquireQuantumMemory(MaxMap+1UL,
cristy3ed852e2009-09-05 21:47:34 +00001063 sizeof(*stretch_map));
cristy4c08aed2011-07-01 19:47:50 +00001064 if ((histogram == (PixelInfo *) NULL) ||
1065 (stretch_map == (PixelInfo *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00001066 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
1067 image->filename);
1068 /*
1069 Form histogram.
1070 */
1071 status=MagickTrue;
1072 exception=(&image->exception);
1073 (void) ResetMagickMemory(histogram,0,(MaxMap+1)*sizeof(*histogram));
1074 image_view=AcquireCacheView(image);
cristybb503372010-05-27 20:51:26 +00001075 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001076 {
cristy4c08aed2011-07-01 19:47:50 +00001077 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00001078 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00001079
cristybb503372010-05-27 20:51:26 +00001080 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001081 x;
1082
1083 if (status == MagickFalse)
1084 continue;
1085 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00001086 if (p == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001087 {
1088 status=MagickFalse;
1089 continue;
1090 }
cristy50fbc382011-07-07 02:19:17 +00001091 if (image->sync != MagickFalse)
cristybb503372010-05-27 20:51:26 +00001092 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001093 {
1094 Quantum
1095 intensity;
1096
cristy4c08aed2011-07-01 19:47:50 +00001097 intensity=GetPixelIntensity(image,p);
cristy3ed852e2009-09-05 21:47:34 +00001098 histogram[ScaleQuantumToMap(intensity)].red++;
1099 histogram[ScaleQuantumToMap(intensity)].green++;
1100 histogram[ScaleQuantumToMap(intensity)].blue++;
cristy4c08aed2011-07-01 19:47:50 +00001101 histogram[ScaleQuantumToMap(intensity)].black++;
cristyed231572011-07-14 02:18:59 +00001102 p+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00001103 }
1104 else
cristybb503372010-05-27 20:51:26 +00001105 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001106 {
cristyed231572011-07-14 02:18:59 +00001107 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001108 histogram[ScaleQuantumToMap(GetPixelRed(image,p))].red++;
cristyed231572011-07-14 02:18:59 +00001109 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001110 histogram[ScaleQuantumToMap(GetPixelGreen(image,p))].green++;
cristyed231572011-07-14 02:18:59 +00001111 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001112 histogram[ScaleQuantumToMap(GetPixelBlue(image,p))].blue++;
cristyed231572011-07-14 02:18:59 +00001113 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00001114 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +00001115 histogram[ScaleQuantumToMap(GetPixelBlack(image,p))].black++;
cristyed231572011-07-14 02:18:59 +00001116 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001117 histogram[ScaleQuantumToMap(GetPixelAlpha(image,p))].alpha++;
cristyed231572011-07-14 02:18:59 +00001118 p+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00001119 }
1120 }
1121 /*
1122 Find the histogram boundaries by locating the black/white levels.
1123 */
1124 black.red=0.0;
1125 white.red=MaxRange(QuantumRange);
cristyed231572011-07-14 02:18:59 +00001126 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001127 {
1128 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00001129 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +00001130 {
1131 intensity+=histogram[i].red;
1132 if (intensity > black_point)
1133 break;
1134 }
1135 black.red=(MagickRealType) i;
1136 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00001137 for (i=(ssize_t) MaxMap; i != 0; i--)
cristy3ed852e2009-09-05 21:47:34 +00001138 {
1139 intensity+=histogram[i].red;
1140 if (intensity > ((double) image->columns*image->rows-white_point))
1141 break;
1142 }
1143 white.red=(MagickRealType) i;
1144 }
1145 black.green=0.0;
1146 white.green=MaxRange(QuantumRange);
cristyed231572011-07-14 02:18:59 +00001147 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001148 {
1149 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00001150 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +00001151 {
1152 intensity+=histogram[i].green;
1153 if (intensity > black_point)
1154 break;
1155 }
1156 black.green=(MagickRealType) i;
1157 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00001158 for (i=(ssize_t) MaxMap; i != 0; i--)
cristy3ed852e2009-09-05 21:47:34 +00001159 {
1160 intensity+=histogram[i].green;
1161 if (intensity > ((double) image->columns*image->rows-white_point))
1162 break;
1163 }
1164 white.green=(MagickRealType) i;
1165 }
1166 black.blue=0.0;
1167 white.blue=MaxRange(QuantumRange);
cristyed231572011-07-14 02:18:59 +00001168 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001169 {
1170 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00001171 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +00001172 {
1173 intensity+=histogram[i].blue;
1174 if (intensity > black_point)
1175 break;
1176 }
1177 black.blue=(MagickRealType) i;
1178 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00001179 for (i=(ssize_t) MaxMap; i != 0; i--)
cristy3ed852e2009-09-05 21:47:34 +00001180 {
1181 intensity+=histogram[i].blue;
1182 if (intensity > ((double) image->columns*image->rows-white_point))
1183 break;
1184 }
1185 white.blue=(MagickRealType) i;
1186 }
cristy4c08aed2011-07-01 19:47:50 +00001187 black.alpha=0.0;
1188 white.alpha=MaxRange(QuantumRange);
cristyed231572011-07-14 02:18:59 +00001189 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001190 {
1191 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00001192 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +00001193 {
cristy4c08aed2011-07-01 19:47:50 +00001194 intensity+=histogram[i].alpha;
cristy3ed852e2009-09-05 21:47:34 +00001195 if (intensity > black_point)
1196 break;
1197 }
cristy4c08aed2011-07-01 19:47:50 +00001198 black.alpha=(MagickRealType) i;
cristy3ed852e2009-09-05 21:47:34 +00001199 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00001200 for (i=(ssize_t) MaxMap; i != 0; i--)
cristy3ed852e2009-09-05 21:47:34 +00001201 {
cristy4c08aed2011-07-01 19:47:50 +00001202 intensity+=histogram[i].alpha;
cristy3ed852e2009-09-05 21:47:34 +00001203 if (intensity > ((double) image->columns*image->rows-white_point))
1204 break;
1205 }
cristy4c08aed2011-07-01 19:47:50 +00001206 white.alpha=(MagickRealType) i;
cristy3ed852e2009-09-05 21:47:34 +00001207 }
cristy4c08aed2011-07-01 19:47:50 +00001208 black.black=0.0;
1209 white.black=MaxRange(QuantumRange);
cristyed231572011-07-14 02:18:59 +00001210 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) && (image->colorspace == CMYKColorspace))
cristy3ed852e2009-09-05 21:47:34 +00001211 {
1212 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00001213 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +00001214 {
cristy4c08aed2011-07-01 19:47:50 +00001215 intensity+=histogram[i].black;
cristy3ed852e2009-09-05 21:47:34 +00001216 if (intensity > black_point)
1217 break;
1218 }
cristy4c08aed2011-07-01 19:47:50 +00001219 black.black=(MagickRealType) i;
cristy3ed852e2009-09-05 21:47:34 +00001220 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00001221 for (i=(ssize_t) MaxMap; i != 0; i--)
cristy3ed852e2009-09-05 21:47:34 +00001222 {
cristy4c08aed2011-07-01 19:47:50 +00001223 intensity+=histogram[i].black;
cristy3ed852e2009-09-05 21:47:34 +00001224 if (intensity > ((double) image->columns*image->rows-white_point))
1225 break;
1226 }
cristy4c08aed2011-07-01 19:47:50 +00001227 white.black=(MagickRealType) i;
cristy3ed852e2009-09-05 21:47:34 +00001228 }
cristy4c08aed2011-07-01 19:47:50 +00001229 histogram=(PixelInfo *) RelinquishMagickMemory(histogram);
cristy3ed852e2009-09-05 21:47:34 +00001230 /*
1231 Stretch the histogram to create the stretched image mapping.
1232 */
1233 (void) ResetMagickMemory(stretch_map,0,(MaxMap+1)*sizeof(*stretch_map));
cristyb5d5f722009-11-04 03:03:49 +00001234#if defined(MAGICKCORE_OPENMP_SUPPORT)
1235 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001236#endif
cristybb503372010-05-27 20:51:26 +00001237 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +00001238 {
cristyed231572011-07-14 02:18:59 +00001239 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001240 {
cristybb503372010-05-27 20:51:26 +00001241 if (i < (ssize_t) black.red)
cristy3ed852e2009-09-05 21:47:34 +00001242 stretch_map[i].red=0.0;
1243 else
cristybb503372010-05-27 20:51:26 +00001244 if (i > (ssize_t) white.red)
cristy3ed852e2009-09-05 21:47:34 +00001245 stretch_map[i].red=(MagickRealType) QuantumRange;
1246 else
1247 if (black.red != white.red)
1248 stretch_map[i].red=(MagickRealType) ScaleMapToQuantum(
1249 (MagickRealType) (MaxMap*(i-black.red)/(white.red-black.red)));
1250 }
cristyed231572011-07-14 02:18:59 +00001251 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001252 {
cristybb503372010-05-27 20:51:26 +00001253 if (i < (ssize_t) black.green)
cristy3ed852e2009-09-05 21:47:34 +00001254 stretch_map[i].green=0.0;
1255 else
cristybb503372010-05-27 20:51:26 +00001256 if (i > (ssize_t) white.green)
cristy3ed852e2009-09-05 21:47:34 +00001257 stretch_map[i].green=(MagickRealType) QuantumRange;
1258 else
1259 if (black.green != white.green)
1260 stretch_map[i].green=(MagickRealType) ScaleMapToQuantum(
1261 (MagickRealType) (MaxMap*(i-black.green)/(white.green-
1262 black.green)));
1263 }
cristyed231572011-07-14 02:18:59 +00001264 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001265 {
cristybb503372010-05-27 20:51:26 +00001266 if (i < (ssize_t) black.blue)
cristy3ed852e2009-09-05 21:47:34 +00001267 stretch_map[i].blue=0.0;
1268 else
cristybb503372010-05-27 20:51:26 +00001269 if (i > (ssize_t) white.blue)
cristy3ed852e2009-09-05 21:47:34 +00001270 stretch_map[i].blue=(MagickRealType) QuantumRange;
1271 else
1272 if (black.blue != white.blue)
1273 stretch_map[i].blue=(MagickRealType) ScaleMapToQuantum(
1274 (MagickRealType) (MaxMap*(i-black.blue)/(white.blue-
1275 black.blue)));
1276 }
cristyed231572011-07-14 02:18:59 +00001277 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001278 {
cristy4c08aed2011-07-01 19:47:50 +00001279 if (i < (ssize_t) black.alpha)
1280 stretch_map[i].alpha=0.0;
cristy3ed852e2009-09-05 21:47:34 +00001281 else
cristy4c08aed2011-07-01 19:47:50 +00001282 if (i > (ssize_t) white.alpha)
1283 stretch_map[i].alpha=(MagickRealType) QuantumRange;
cristy3ed852e2009-09-05 21:47:34 +00001284 else
cristy4c08aed2011-07-01 19:47:50 +00001285 if (black.alpha != white.alpha)
1286 stretch_map[i].alpha=(MagickRealType) ScaleMapToQuantum(
1287 (MagickRealType) (MaxMap*(i-black.alpha)/(white.alpha-
1288 black.alpha)));
cristy3ed852e2009-09-05 21:47:34 +00001289 }
cristyed231572011-07-14 02:18:59 +00001290 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00001291 (image->colorspace == CMYKColorspace))
1292 {
cristy4c08aed2011-07-01 19:47:50 +00001293 if (i < (ssize_t) black.black)
1294 stretch_map[i].black=0.0;
cristy3ed852e2009-09-05 21:47:34 +00001295 else
cristy4c08aed2011-07-01 19:47:50 +00001296 if (i > (ssize_t) white.black)
1297 stretch_map[i].black=(MagickRealType) QuantumRange;
cristy3ed852e2009-09-05 21:47:34 +00001298 else
cristy4c08aed2011-07-01 19:47:50 +00001299 if (black.black != white.black)
1300 stretch_map[i].black=(MagickRealType) ScaleMapToQuantum(
1301 (MagickRealType) (MaxMap*(i-black.black)/(white.black-
1302 black.black)));
cristy3ed852e2009-09-05 21:47:34 +00001303 }
1304 }
1305 /*
1306 Stretch the image.
1307 */
cristyed231572011-07-14 02:18:59 +00001308 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) || (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00001309 (image->colorspace == CMYKColorspace)))
1310 image->storage_class=DirectClass;
1311 if (image->storage_class == PseudoClass)
1312 {
1313 /*
1314 Stretch colormap.
1315 */
cristyb5d5f722009-11-04 03:03:49 +00001316#if defined(MAGICKCORE_OPENMP_SUPPORT)
1317 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001318#endif
cristybb503372010-05-27 20:51:26 +00001319 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00001320 {
cristyed231572011-07-14 02:18:59 +00001321 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001322 {
1323 if (black.red != white.red)
cristyce70c172010-01-07 17:15:30 +00001324 image->colormap[i].red=ClampToQuantum(stretch_map[
cristy3ed852e2009-09-05 21:47:34 +00001325 ScaleQuantumToMap(image->colormap[i].red)].red);
1326 }
cristyed231572011-07-14 02:18:59 +00001327 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001328 {
1329 if (black.green != white.green)
cristyce70c172010-01-07 17:15:30 +00001330 image->colormap[i].green=ClampToQuantum(stretch_map[
cristy3ed852e2009-09-05 21:47:34 +00001331 ScaleQuantumToMap(image->colormap[i].green)].green);
1332 }
cristyed231572011-07-14 02:18:59 +00001333 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001334 {
1335 if (black.blue != white.blue)
cristyce70c172010-01-07 17:15:30 +00001336 image->colormap[i].blue=ClampToQuantum(stretch_map[
cristy3ed852e2009-09-05 21:47:34 +00001337 ScaleQuantumToMap(image->colormap[i].blue)].blue);
1338 }
cristyed231572011-07-14 02:18:59 +00001339 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001340 {
cristy4c08aed2011-07-01 19:47:50 +00001341 if (black.alpha != white.alpha)
1342 image->colormap[i].alpha=ClampToQuantum(stretch_map[
1343 ScaleQuantumToMap(image->colormap[i].alpha)].alpha);
cristy3ed852e2009-09-05 21:47:34 +00001344 }
1345 }
1346 }
1347 /*
1348 Stretch image.
1349 */
1350 status=MagickTrue;
1351 progress=0;
cristyb5d5f722009-11-04 03:03:49 +00001352#if defined(MAGICKCORE_OPENMP_SUPPORT)
1353 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001354#endif
cristybb503372010-05-27 20:51:26 +00001355 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001356 {
cristy4c08aed2011-07-01 19:47:50 +00001357 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00001358 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001359
cristy8d4629b2010-08-30 17:59:46 +00001360 register ssize_t
1361 x;
1362
cristy3ed852e2009-09-05 21:47:34 +00001363 if (status == MagickFalse)
1364 continue;
1365 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00001366 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001367 {
1368 status=MagickFalse;
1369 continue;
1370 }
cristybb503372010-05-27 20:51:26 +00001371 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001372 {
cristyed231572011-07-14 02:18:59 +00001373 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001374 {
1375 if (black.red != white.red)
cristy4c08aed2011-07-01 19:47:50 +00001376 SetPixelRed(image,ClampToQuantum(stretch_map[ScaleQuantumToMap(
1377 GetPixelRed(image,q))].red),q);
cristy3ed852e2009-09-05 21:47:34 +00001378 }
cristyed231572011-07-14 02:18:59 +00001379 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001380 {
1381 if (black.green != white.green)
cristy4c08aed2011-07-01 19:47:50 +00001382 SetPixelGreen(image,ClampToQuantum(stretch_map[ScaleQuantumToMap(
1383 GetPixelGreen(image,q))].green),q);
cristy3ed852e2009-09-05 21:47:34 +00001384 }
cristyed231572011-07-14 02:18:59 +00001385 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001386 {
1387 if (black.blue != white.blue)
cristy4c08aed2011-07-01 19:47:50 +00001388 SetPixelBlue(image,ClampToQuantum(stretch_map[ScaleQuantumToMap(
1389 GetPixelBlue(image,q))].blue),q);
1390 }
cristyed231572011-07-14 02:18:59 +00001391 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +00001392 (image->colorspace == CMYKColorspace))
1393 {
1394 if (black.black != white.black)
1395 SetPixelBlack(image,ClampToQuantum(stretch_map[ScaleQuantumToMap(
1396 GetPixelBlack(image,q))].black),q);
cristy3ed852e2009-09-05 21:47:34 +00001397 }
cristyed231572011-07-14 02:18:59 +00001398 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001399 {
cristy4c08aed2011-07-01 19:47:50 +00001400 if (black.alpha != white.alpha)
1401 SetPixelAlpha(image,ClampToQuantum(stretch_map[ScaleQuantumToMap(
1402 GetPixelAlpha(image,q))].alpha),q);
cristy3ed852e2009-09-05 21:47:34 +00001403 }
cristyed231572011-07-14 02:18:59 +00001404 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00001405 }
1406 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
1407 status=MagickFalse;
1408 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1409 {
1410 MagickBooleanType
1411 proceed;
1412
cristyb5d5f722009-11-04 03:03:49 +00001413#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy50fbc382011-07-07 02:19:17 +00001414 #pragma omp critical (MagickCore_ContrastStretchImage)
cristy3ed852e2009-09-05 21:47:34 +00001415#endif
1416 proceed=SetImageProgress(image,ContrastStretchImageTag,progress++,
1417 image->rows);
1418 if (proceed == MagickFalse)
1419 status=MagickFalse;
1420 }
1421 }
1422 image_view=DestroyCacheView(image_view);
cristy4c08aed2011-07-01 19:47:50 +00001423 stretch_map=(PixelInfo *) RelinquishMagickMemory(stretch_map);
cristy3ed852e2009-09-05 21:47:34 +00001424 return(status);
1425}
1426
1427/*
1428%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1429% %
1430% %
1431% %
1432% E n h a n c e I m a g e %
1433% %
1434% %
1435% %
1436%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1437%
1438% EnhanceImage() applies a digital filter that improves the quality of a
1439% noisy image.
1440%
1441% The format of the EnhanceImage method is:
1442%
1443% Image *EnhanceImage(const Image *image,ExceptionInfo *exception)
1444%
1445% A description of each parameter follows:
1446%
1447% o image: the image.
1448%
1449% o exception: return any errors or warnings in this structure.
1450%
1451*/
1452MagickExport Image *EnhanceImage(const Image *image,ExceptionInfo *exception)
1453{
1454#define Enhance(weight) \
cristy4c08aed2011-07-01 19:47:50 +00001455 mean=((MagickRealType) GetPixelRed(image,r)+pixel.red)/2; \
1456 distance=(MagickRealType) GetPixelRed(image,r)-(MagickRealType) pixel.red; \
cristy3ed852e2009-09-05 21:47:34 +00001457 distance_squared=QuantumScale*(2.0*((MagickRealType) QuantumRange+1.0)+ \
1458 mean)*distance*distance; \
cristy4c08aed2011-07-01 19:47:50 +00001459 mean=((MagickRealType) GetPixelGreen(image,r)+pixel.green)/2; \
1460 distance=(MagickRealType) GetPixelGreen(image,r)- \
1461 (MagickRealType) pixel.green; \
cristy3ed852e2009-09-05 21:47:34 +00001462 distance_squared+=4.0*distance*distance; \
cristy4c08aed2011-07-01 19:47:50 +00001463 mean=((MagickRealType) GetPixelBlue(image,r)+pixel.blue)/2; \
1464 distance=(MagickRealType) GetPixelBlue(image,r)- \
1465 (MagickRealType) pixel.blue; \
cristy3ed852e2009-09-05 21:47:34 +00001466 distance_squared+=QuantumScale*(3.0*((MagickRealType) \
1467 QuantumRange+1.0)-1.0-mean)*distance*distance; \
cristy4c08aed2011-07-01 19:47:50 +00001468 mean=((MagickRealType) GetPixelAlpha(image,r)+pixel.alpha)/2; \
1469 distance=(MagickRealType) GetPixelAlpha(image,r)-(MagickRealType) pixel.alpha; \
cristy3ed852e2009-09-05 21:47:34 +00001470 distance_squared+=QuantumScale*(3.0*((MagickRealType) \
1471 QuantumRange+1.0)-1.0-mean)*distance*distance; \
1472 if (distance_squared < ((MagickRealType) QuantumRange*(MagickRealType) \
1473 QuantumRange/25.0f)) \
1474 { \
cristy4c08aed2011-07-01 19:47:50 +00001475 aggregate.red+=(weight)*GetPixelRed(image,r); \
1476 aggregate.green+=(weight)*GetPixelGreen(image,r); \
1477 aggregate.blue+=(weight)*GetPixelBlue(image,r); \
1478 aggregate.alpha+=(weight)*GetPixelAlpha(image,r); \
cristy3ed852e2009-09-05 21:47:34 +00001479 total_weight+=(weight); \
1480 } \
1481 r++;
1482#define EnhanceImageTag "Enhance/Image"
1483
cristyc4c8d132010-01-07 01:58:38 +00001484 CacheView
1485 *enhance_view,
1486 *image_view;
1487
cristy3ed852e2009-09-05 21:47:34 +00001488 Image
1489 *enhance_image;
1490
cristy3ed852e2009-09-05 21:47:34 +00001491 MagickBooleanType
1492 status;
1493
cristybb503372010-05-27 20:51:26 +00001494 MagickOffsetType
1495 progress;
1496
cristy4c08aed2011-07-01 19:47:50 +00001497 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00001498 zero;
1499
cristybb503372010-05-27 20:51:26 +00001500 ssize_t
1501 y;
1502
cristy3ed852e2009-09-05 21:47:34 +00001503 /*
1504 Initialize enhanced image attributes.
1505 */
1506 assert(image != (const Image *) NULL);
1507 assert(image->signature == MagickSignature);
1508 if (image->debug != MagickFalse)
1509 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1510 assert(exception != (ExceptionInfo *) NULL);
1511 assert(exception->signature == MagickSignature);
1512 if ((image->columns < 5) || (image->rows < 5))
1513 return((Image *) NULL);
1514 enhance_image=CloneImage(image,image->columns,image->rows,MagickTrue,
1515 exception);
1516 if (enhance_image == (Image *) NULL)
1517 return((Image *) NULL);
cristy574cc262011-08-05 01:23:58 +00001518 if (SetImageStorageClass(enhance_image,DirectClass,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00001519 {
cristy3ed852e2009-09-05 21:47:34 +00001520 enhance_image=DestroyImage(enhance_image);
1521 return((Image *) NULL);
1522 }
1523 /*
1524 Enhance image.
1525 */
1526 status=MagickTrue;
1527 progress=0;
1528 (void) ResetMagickMemory(&zero,0,sizeof(zero));
1529 image_view=AcquireCacheView(image);
1530 enhance_view=AcquireCacheView(enhance_image);
cristyb5d5f722009-11-04 03:03:49 +00001531#if defined(MAGICKCORE_OPENMP_SUPPORT)
1532 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001533#endif
cristybb503372010-05-27 20:51:26 +00001534 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001535 {
cristy4c08aed2011-07-01 19:47:50 +00001536 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00001537 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00001538
cristy4c08aed2011-07-01 19:47:50 +00001539 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00001540 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001541
cristy8d4629b2010-08-30 17:59:46 +00001542 register ssize_t
1543 x;
1544
cristy3ed852e2009-09-05 21:47:34 +00001545 /*
1546 Read another scan line.
1547 */
1548 if (status == MagickFalse)
1549 continue;
1550 p=GetCacheViewVirtualPixels(image_view,-2,y-2,image->columns+4,5,exception);
1551 q=QueueCacheViewAuthenticPixels(enhance_view,0,y,enhance_image->columns,1,
1552 exception);
cristy4c08aed2011-07-01 19:47:50 +00001553 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00001554 {
1555 status=MagickFalse;
1556 continue;
1557 }
cristybb503372010-05-27 20:51:26 +00001558 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001559 {
cristy4c08aed2011-07-01 19:47:50 +00001560 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00001561 aggregate;
1562
1563 MagickRealType
1564 distance,
1565 distance_squared,
1566 mean,
1567 total_weight;
1568
1569 PixelPacket
1570 pixel;
1571
cristy4c08aed2011-07-01 19:47:50 +00001572 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00001573 *restrict r;
cristy3ed852e2009-09-05 21:47:34 +00001574
1575 /*
1576 Compute weighted average of target pixel color components.
1577 */
1578 aggregate=zero;
1579 total_weight=0.0;
1580 r=p+2*(image->columns+4)+2;
cristy4c08aed2011-07-01 19:47:50 +00001581 GetPixelPacket(image,r,&pixel);
cristy3ed852e2009-09-05 21:47:34 +00001582 r=p;
1583 Enhance(5.0); Enhance(8.0); Enhance(10.0); Enhance(8.0); Enhance(5.0);
1584 r=p+(image->columns+4);
1585 Enhance(8.0); Enhance(20.0); Enhance(40.0); Enhance(20.0); Enhance(8.0);
1586 r=p+2*(image->columns+4);
1587 Enhance(10.0); Enhance(40.0); Enhance(80.0); Enhance(40.0); Enhance(10.0);
1588 r=p+3*(image->columns+4);
1589 Enhance(8.0); Enhance(20.0); Enhance(40.0); Enhance(20.0); Enhance(8.0);
1590 r=p+4*(image->columns+4);
1591 Enhance(5.0); Enhance(8.0); Enhance(10.0); Enhance(8.0); Enhance(5.0);
cristy4c08aed2011-07-01 19:47:50 +00001592 SetPixelRed(enhance_image,(Quantum) ((aggregate.red+
1593 (total_weight/2)-1)/total_weight),q);
1594 SetPixelGreen(enhance_image,(Quantum) ((aggregate.green+
1595 (total_weight/2)-1)/total_weight),q);
1596 SetPixelBlue(enhance_image,(Quantum) ((aggregate.blue+
1597 (total_weight/2)-1)/total_weight),q);
1598 SetPixelAlpha(enhance_image,(Quantum) ((aggregate.alpha+
1599 (total_weight/2)-1)/total_weight),q);
cristyed231572011-07-14 02:18:59 +00001600 p+=GetPixelChannels(image);
1601 q+=GetPixelChannels(enhance_image);
cristy3ed852e2009-09-05 21:47:34 +00001602 }
1603 if (SyncCacheViewAuthenticPixels(enhance_view,exception) == MagickFalse)
1604 status=MagickFalse;
1605 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1606 {
1607 MagickBooleanType
1608 proceed;
1609
cristyb5d5f722009-11-04 03:03:49 +00001610#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00001611 #pragma omp critical (MagickCore_EnhanceImage)
1612#endif
1613 proceed=SetImageProgress(image,EnhanceImageTag,progress++,image->rows);
1614 if (proceed == MagickFalse)
1615 status=MagickFalse;
1616 }
1617 }
1618 enhance_view=DestroyCacheView(enhance_view);
1619 image_view=DestroyCacheView(image_view);
1620 return(enhance_image);
1621}
1622
1623/*
1624%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1625% %
1626% %
1627% %
1628% E q u a l i z e I m a g e %
1629% %
1630% %
1631% %
1632%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1633%
1634% EqualizeImage() applies a histogram equalization to the image.
1635%
1636% The format of the EqualizeImage method is:
1637%
1638% MagickBooleanType EqualizeImage(Image *image)
cristy3ed852e2009-09-05 21:47:34 +00001639%
1640% A description of each parameter follows:
1641%
1642% o image: the image.
1643%
1644% o channel: the channel.
1645%
1646*/
cristy3ed852e2009-09-05 21:47:34 +00001647MagickExport MagickBooleanType EqualizeImage(Image *image)
1648{
cristy3ed852e2009-09-05 21:47:34 +00001649#define EqualizeImageTag "Equalize/Image"
1650
cristyc4c8d132010-01-07 01:58:38 +00001651 CacheView
1652 *image_view;
1653
cristy3ed852e2009-09-05 21:47:34 +00001654 ExceptionInfo
1655 *exception;
1656
cristy3ed852e2009-09-05 21:47:34 +00001657 MagickBooleanType
1658 status;
1659
cristybb503372010-05-27 20:51:26 +00001660 MagickOffsetType
1661 progress;
1662
cristy4c08aed2011-07-01 19:47:50 +00001663 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00001664 black,
1665 *equalize_map,
1666 *histogram,
1667 intensity,
1668 *map,
1669 white;
1670
cristybb503372010-05-27 20:51:26 +00001671 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001672 i;
1673
cristybb503372010-05-27 20:51:26 +00001674 ssize_t
1675 y;
1676
cristy3ed852e2009-09-05 21:47:34 +00001677 /*
1678 Allocate and initialize histogram arrays.
1679 */
1680 assert(image != (Image *) NULL);
1681 assert(image->signature == MagickSignature);
1682 if (image->debug != MagickFalse)
1683 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
cristy4c08aed2011-07-01 19:47:50 +00001684 equalize_map=(PixelInfo *) AcquireQuantumMemory(MaxMap+1UL,
cristy3ed852e2009-09-05 21:47:34 +00001685 sizeof(*equalize_map));
cristy4c08aed2011-07-01 19:47:50 +00001686 histogram=(PixelInfo *) AcquireQuantumMemory(MaxMap+1UL,
cristy3ed852e2009-09-05 21:47:34 +00001687 sizeof(*histogram));
cristy4c08aed2011-07-01 19:47:50 +00001688 map=(PixelInfo *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*map));
1689 if ((equalize_map == (PixelInfo *) NULL) ||
1690 (histogram == (PixelInfo *) NULL) ||
1691 (map == (PixelInfo *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00001692 {
cristy4c08aed2011-07-01 19:47:50 +00001693 if (map != (PixelInfo *) NULL)
1694 map=(PixelInfo *) RelinquishMagickMemory(map);
1695 if (histogram != (PixelInfo *) NULL)
1696 histogram=(PixelInfo *) RelinquishMagickMemory(histogram);
1697 if (equalize_map != (PixelInfo *) NULL)
1698 equalize_map=(PixelInfo *) RelinquishMagickMemory(equalize_map);
cristy3ed852e2009-09-05 21:47:34 +00001699 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
1700 image->filename);
1701 }
1702 /*
1703 Form histogram.
1704 */
1705 (void) ResetMagickMemory(histogram,0,(MaxMap+1)*sizeof(*histogram));
1706 exception=(&image->exception);
cristybb503372010-05-27 20:51:26 +00001707 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001708 {
cristy4c08aed2011-07-01 19:47:50 +00001709 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00001710 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00001711
cristybb503372010-05-27 20:51:26 +00001712 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001713 x;
1714
1715 p=GetVirtualPixels(image,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00001716 if (p == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001717 break;
cristybb503372010-05-27 20:51:26 +00001718 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001719 {
cristyed231572011-07-14 02:18:59 +00001720 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001721 histogram[ScaleQuantumToMap(GetPixelRed(image,p))].red++;
cristyed231572011-07-14 02:18:59 +00001722 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001723 histogram[ScaleQuantumToMap(GetPixelGreen(image,p))].green++;
cristyed231572011-07-14 02:18:59 +00001724 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001725 histogram[ScaleQuantumToMap(GetPixelBlue(image,p))].blue++;
cristyed231572011-07-14 02:18:59 +00001726 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00001727 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +00001728 histogram[ScaleQuantumToMap(GetPixelBlack(image,p))].black++;
cristyed231572011-07-14 02:18:59 +00001729 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001730 histogram[ScaleQuantumToMap(GetPixelAlpha(image,p))].alpha++;
cristyed231572011-07-14 02:18:59 +00001731 p+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00001732 }
1733 }
1734 /*
1735 Integrate the histogram to get the equalization map.
1736 */
1737 (void) ResetMagickMemory(&intensity,0,sizeof(intensity));
cristybb503372010-05-27 20:51:26 +00001738 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +00001739 {
cristyed231572011-07-14 02:18:59 +00001740 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001741 intensity.red+=histogram[i].red;
cristyed231572011-07-14 02:18:59 +00001742 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001743 intensity.green+=histogram[i].green;
cristyed231572011-07-14 02:18:59 +00001744 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001745 intensity.blue+=histogram[i].blue;
cristyed231572011-07-14 02:18:59 +00001746 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00001747 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +00001748 intensity.black+=histogram[i].black;
cristyed231572011-07-14 02:18:59 +00001749 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001750 intensity.alpha+=histogram[i].alpha;
cristy3ed852e2009-09-05 21:47:34 +00001751 map[i]=intensity;
1752 }
1753 black=map[0];
1754 white=map[(int) MaxMap];
1755 (void) ResetMagickMemory(equalize_map,0,(MaxMap+1)*sizeof(*equalize_map));
cristyb5d5f722009-11-04 03:03:49 +00001756#if defined(MAGICKCORE_OPENMP_SUPPORT)
1757 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001758#endif
cristybb503372010-05-27 20:51:26 +00001759 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +00001760 {
cristyed231572011-07-14 02:18:59 +00001761 if (((GetPixelRedTraits(image) & UpdatePixelTrait) != 0) &&
cristy2b9582a2011-07-04 17:38:56 +00001762 (white.red != black.red))
cristy3ed852e2009-09-05 21:47:34 +00001763 equalize_map[i].red=(MagickRealType) ScaleMapToQuantum((MagickRealType)
1764 ((MaxMap*(map[i].red-black.red))/(white.red-black.red)));
cristyed231572011-07-14 02:18:59 +00001765 if (((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0) &&
cristy2b9582a2011-07-04 17:38:56 +00001766 (white.green != black.green))
cristy3ed852e2009-09-05 21:47:34 +00001767 equalize_map[i].green=(MagickRealType) ScaleMapToQuantum((MagickRealType)
1768 ((MaxMap*(map[i].green-black.green))/(white.green-black.green)));
cristyed231572011-07-14 02:18:59 +00001769 if (((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0) &&
cristy2b9582a2011-07-04 17:38:56 +00001770 (white.blue != black.blue))
cristy3ed852e2009-09-05 21:47:34 +00001771 equalize_map[i].blue=(MagickRealType) ScaleMapToQuantum((MagickRealType)
1772 ((MaxMap*(map[i].blue-black.blue))/(white.blue-black.blue)));
cristyed231572011-07-14 02:18:59 +00001773 if ((((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00001774 (image->colorspace == CMYKColorspace)) &&
cristy4c08aed2011-07-01 19:47:50 +00001775 (white.black != black.black))
1776 equalize_map[i].black=(MagickRealType) ScaleMapToQuantum((MagickRealType)
1777 ((MaxMap*(map[i].black-black.black))/(white.black-black.black)));
cristyed231572011-07-14 02:18:59 +00001778 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) &&
cristy2b9582a2011-07-04 17:38:56 +00001779 (white.alpha != black.alpha))
cristy4c08aed2011-07-01 19:47:50 +00001780 equalize_map[i].alpha=(MagickRealType) ScaleMapToQuantum(
1781 (MagickRealType) ((MaxMap*(map[i].alpha-black.alpha))/
1782 (white.alpha-black.alpha)));
cristy3ed852e2009-09-05 21:47:34 +00001783 }
cristy4c08aed2011-07-01 19:47:50 +00001784 histogram=(PixelInfo *) RelinquishMagickMemory(histogram);
1785 map=(PixelInfo *) RelinquishMagickMemory(map);
cristy3ed852e2009-09-05 21:47:34 +00001786 if (image->storage_class == PseudoClass)
1787 {
1788 /*
1789 Equalize colormap.
1790 */
cristyb5d5f722009-11-04 03:03:49 +00001791#if defined(MAGICKCORE_OPENMP_SUPPORT)
1792 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001793#endif
cristybb503372010-05-27 20:51:26 +00001794 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00001795 {
cristyed231572011-07-14 02:18:59 +00001796 if (((GetPixelRedTraits(image) & UpdatePixelTrait) != 0) &&
cristy2b9582a2011-07-04 17:38:56 +00001797 (white.red != black.red))
cristyce70c172010-01-07 17:15:30 +00001798 image->colormap[i].red=ClampToQuantum(equalize_map[
cristy3ed852e2009-09-05 21:47:34 +00001799 ScaleQuantumToMap(image->colormap[i].red)].red);
cristyed231572011-07-14 02:18:59 +00001800 if (((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0) &&
cristy2b9582a2011-07-04 17:38:56 +00001801 (white.green != black.green))
cristyce70c172010-01-07 17:15:30 +00001802 image->colormap[i].green=ClampToQuantum(equalize_map[
cristy3ed852e2009-09-05 21:47:34 +00001803 ScaleQuantumToMap(image->colormap[i].green)].green);
cristyed231572011-07-14 02:18:59 +00001804 if (((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0) &&
cristy2b9582a2011-07-04 17:38:56 +00001805 (white.blue != black.blue))
cristyce70c172010-01-07 17:15:30 +00001806 image->colormap[i].blue=ClampToQuantum(equalize_map[
cristy3ed852e2009-09-05 21:47:34 +00001807 ScaleQuantumToMap(image->colormap[i].blue)].blue);
cristyed231572011-07-14 02:18:59 +00001808 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +00001809 (white.alpha != black.alpha))
1810 image->colormap[i].alpha=ClampToQuantum(equalize_map[
1811 ScaleQuantumToMap(image->colormap[i].alpha)].alpha);
cristy3ed852e2009-09-05 21:47:34 +00001812 }
1813 }
1814 /*
1815 Equalize image.
1816 */
1817 status=MagickTrue;
1818 progress=0;
1819 exception=(&image->exception);
1820 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00001821#if defined(MAGICKCORE_OPENMP_SUPPORT)
1822 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001823#endif
cristybb503372010-05-27 20:51:26 +00001824 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001825 {
cristy4c08aed2011-07-01 19:47:50 +00001826 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00001827 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001828
cristy8d4629b2010-08-30 17:59:46 +00001829 register ssize_t
1830 x;
1831
cristy3ed852e2009-09-05 21:47:34 +00001832 if (status == MagickFalse)
1833 continue;
1834 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00001835 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001836 {
1837 status=MagickFalse;
1838 continue;
1839 }
cristybb503372010-05-27 20:51:26 +00001840 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001841 {
cristyed231572011-07-14 02:18:59 +00001842 if (((GetPixelRedTraits(image) & UpdatePixelTrait) != 0) &&
cristy2b9582a2011-07-04 17:38:56 +00001843 (white.red != black.red))
cristy4c08aed2011-07-01 19:47:50 +00001844 SetPixelRed(image,ClampToQuantum(equalize_map[
1845 ScaleQuantumToMap(GetPixelRed(image,q))].red),q);
cristyed231572011-07-14 02:18:59 +00001846 if (((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0) &&
cristy2b9582a2011-07-04 17:38:56 +00001847 (white.green != black.green))
cristy4c08aed2011-07-01 19:47:50 +00001848 SetPixelGreen(image,ClampToQuantum(equalize_map[
1849 ScaleQuantumToMap(GetPixelGreen(image,q))].green),q);
cristyed231572011-07-14 02:18:59 +00001850 if (((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0) &&
cristy2b9582a2011-07-04 17:38:56 +00001851 (white.blue != black.blue))
cristy4c08aed2011-07-01 19:47:50 +00001852 SetPixelBlue(image,ClampToQuantum(equalize_map[
1853 ScaleQuantumToMap(GetPixelBlue(image,q))].blue),q);
cristyed231572011-07-14 02:18:59 +00001854 if ((((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00001855 (image->colorspace == CMYKColorspace)) &&
cristy4c08aed2011-07-01 19:47:50 +00001856 (white.black != black.black))
1857 SetPixelBlack(image,ClampToQuantum(equalize_map[
1858 ScaleQuantumToMap(GetPixelBlack(image,q))].black),q);
cristyed231572011-07-14 02:18:59 +00001859 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) &&
cristy2b9582a2011-07-04 17:38:56 +00001860 (white.alpha != black.alpha))
cristy4c08aed2011-07-01 19:47:50 +00001861 SetPixelAlpha(image,ClampToQuantum(equalize_map[
1862 ScaleQuantumToMap(GetPixelAlpha(image,q))].alpha),q);
cristyed231572011-07-14 02:18:59 +00001863 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00001864 }
1865 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
1866 status=MagickFalse;
1867 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1868 {
1869 MagickBooleanType
1870 proceed;
1871
cristyb5d5f722009-11-04 03:03:49 +00001872#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy50fbc382011-07-07 02:19:17 +00001873 #pragma omp critical (MagickCore_EqualizeImage)
cristy3ed852e2009-09-05 21:47:34 +00001874#endif
1875 proceed=SetImageProgress(image,EqualizeImageTag,progress++,image->rows);
1876 if (proceed == MagickFalse)
1877 status=MagickFalse;
1878 }
1879 }
1880 image_view=DestroyCacheView(image_view);
cristy4c08aed2011-07-01 19:47:50 +00001881 equalize_map=(PixelInfo *) RelinquishMagickMemory(equalize_map);
cristy3ed852e2009-09-05 21:47:34 +00001882 return(status);
1883}
1884
1885/*
1886%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1887% %
1888% %
1889% %
1890% G a m m a I m a g e %
1891% %
1892% %
1893% %
1894%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1895%
1896% GammaImage() gamma-corrects a particular image channel. The same
1897% image viewed on different devices will have perceptual differences in the
1898% way the image's intensities are represented on the screen. Specify
1899% individual gamma levels for the red, green, and blue channels, or adjust
1900% all three with the gamma parameter. Values typically range from 0.8 to 2.3.
1901%
1902% You can also reduce the influence of a particular channel with a gamma
1903% value of 0.
1904%
1905% The format of the GammaImage method is:
1906%
cristyb3e7c6c2011-07-24 01:43:55 +00001907% MagickBooleanType GammaImage(Image *image,const double gamma,
1908% ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001909%
1910% A description of each parameter follows:
1911%
1912% o image: the image.
1913%
cristya6360142011-03-23 23:08:04 +00001914% o level: the image gamma as a string (e.g. 1.6,1.2,1.0).
1915%
cristy3ed852e2009-09-05 21:47:34 +00001916% o gamma: the image gamma.
1917%
1918*/
cristyb3e7c6c2011-07-24 01:43:55 +00001919MagickExport MagickBooleanType GammaImage(Image *image,const double gamma,
1920 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001921{
1922#define GammaCorrectImageTag "GammaCorrect/Image"
1923
cristyc4c8d132010-01-07 01:58:38 +00001924 CacheView
1925 *image_view;
1926
cristy3ed852e2009-09-05 21:47:34 +00001927 MagickBooleanType
1928 status;
1929
cristybb503372010-05-27 20:51:26 +00001930 MagickOffsetType
1931 progress;
1932
cristy3ed852e2009-09-05 21:47:34 +00001933 Quantum
1934 *gamma_map;
1935
cristybb503372010-05-27 20:51:26 +00001936 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001937 i;
1938
cristybb503372010-05-27 20:51:26 +00001939 ssize_t
1940 y;
1941
cristy3ed852e2009-09-05 21:47:34 +00001942 /*
1943 Allocate and initialize gamma maps.
1944 */
1945 assert(image != (Image *) NULL);
1946 assert(image->signature == MagickSignature);
1947 if (image->debug != MagickFalse)
1948 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1949 if (gamma == 1.0)
1950 return(MagickTrue);
1951 gamma_map=(Quantum *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*gamma_map));
1952 if (gamma_map == (Quantum *) NULL)
1953 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
1954 image->filename);
1955 (void) ResetMagickMemory(gamma_map,0,(MaxMap+1)*sizeof(*gamma_map));
1956 if (gamma != 0.0)
cristyd476a8e2011-07-23 16:13:22 +00001957#if defined(MAGICKCORE_OPENMP_SUPPORT) && (MaxMap > 256)
1958 #pragma omp parallel for
cristy3ed852e2009-09-05 21:47:34 +00001959#endif
cristybb503372010-05-27 20:51:26 +00001960 for (i=0; i <= (ssize_t) MaxMap; i++)
cristyce70c172010-01-07 17:15:30 +00001961 gamma_map[i]=ClampToQuantum((MagickRealType) ScaleMapToQuantum((
cristy3ed852e2009-09-05 21:47:34 +00001962 MagickRealType) (MaxMap*pow((double) i/MaxMap,1.0/gamma))));
1963 if (image->storage_class == PseudoClass)
1964 {
1965 /*
1966 Gamma-correct colormap.
1967 */
cristyb5d5f722009-11-04 03:03:49 +00001968#if defined(MAGICKCORE_OPENMP_SUPPORT)
1969 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001970#endif
cristybb503372010-05-27 20:51:26 +00001971 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00001972 {
cristyed231572011-07-14 02:18:59 +00001973 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001974 image->colormap[i].red=gamma_map[
1975 ScaleQuantumToMap(image->colormap[i].red)];
cristyed231572011-07-14 02:18:59 +00001976 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001977 image->colormap[i].green=gamma_map[
1978 ScaleQuantumToMap(image->colormap[i].green)];
cristyed231572011-07-14 02:18:59 +00001979 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001980 image->colormap[i].blue=gamma_map[
1981 ScaleQuantumToMap(image->colormap[i].blue)];
cristyed231572011-07-14 02:18:59 +00001982 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001983 image->colormap[i].alpha=gamma_map[
1984 ScaleQuantumToMap(image->colormap[i].alpha)];
cristy3ed852e2009-09-05 21:47:34 +00001985 }
1986 }
1987 /*
1988 Gamma-correct image.
1989 */
1990 status=MagickTrue;
1991 progress=0;
cristy3ed852e2009-09-05 21:47:34 +00001992 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00001993#if defined(MAGICKCORE_OPENMP_SUPPORT)
1994 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001995#endif
cristybb503372010-05-27 20:51:26 +00001996 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001997 {
cristy4c08aed2011-07-01 19:47:50 +00001998 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00001999 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002000
cristy8d4629b2010-08-30 17:59:46 +00002001 register ssize_t
2002 x;
2003
cristy3ed852e2009-09-05 21:47:34 +00002004 if (status == MagickFalse)
2005 continue;
2006 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00002007 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00002008 {
2009 status=MagickFalse;
2010 continue;
2011 }
cristybb503372010-05-27 20:51:26 +00002012 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002013 {
cristyd476a8e2011-07-23 16:13:22 +00002014 register ssize_t
2015 i;
2016
cristya30d9ba2011-07-23 21:00:48 +00002017 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
cristyd476a8e2011-07-23 16:13:22 +00002018 {
2019 PixelTrait
2020 traits;
2021
2022 traits=GetPixelChannelMapTraits(image,(PixelChannel) i);
2023 if ((traits & UpdatePixelTrait) != 0)
2024 q[i]=gamma_map[ScaleQuantumToMap(q[i])];
2025 }
cristya30d9ba2011-07-23 21:00:48 +00002026 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00002027 }
cristy3ed852e2009-09-05 21:47:34 +00002028 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2029 status=MagickFalse;
2030 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2031 {
2032 MagickBooleanType
2033 proceed;
2034
cristyb5d5f722009-11-04 03:03:49 +00002035#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy50fbc382011-07-07 02:19:17 +00002036 #pragma omp critical (MagickCore_GammaImage)
cristy3ed852e2009-09-05 21:47:34 +00002037#endif
2038 proceed=SetImageProgress(image,GammaCorrectImageTag,progress++,
2039 image->rows);
2040 if (proceed == MagickFalse)
2041 status=MagickFalse;
2042 }
2043 }
2044 image_view=DestroyCacheView(image_view);
2045 gamma_map=(Quantum *) RelinquishMagickMemory(gamma_map);
2046 if (image->gamma != 0.0)
2047 image->gamma*=gamma;
2048 return(status);
2049}
2050
2051/*
2052%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2053% %
2054% %
2055% %
2056% H a l d C l u t I m a g e %
2057% %
2058% %
2059% %
2060%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2061%
2062% HaldClutImage() applies a Hald color lookup table to the image. A Hald
2063% color lookup table is a 3-dimensional color cube mapped to 2 dimensions.
2064% Create it with the HALD coder. You can apply any color transformation to
2065% the Hald image and then use this method to apply the transform to the
2066% image.
2067%
2068% The format of the HaldClutImage method is:
2069%
2070% MagickBooleanType HaldClutImage(Image *image,Image *hald_image)
cristy3ed852e2009-09-05 21:47:34 +00002071%
2072% A description of each parameter follows:
2073%
2074% o image: the image, which is replaced by indexed CLUT values
2075%
2076% o hald_image: the color lookup table image for replacement color values.
2077%
cristy3ed852e2009-09-05 21:47:34 +00002078*/
2079
2080static inline size_t MagickMin(const size_t x,const size_t y)
2081{
2082 if (x < y)
2083 return(x);
2084 return(y);
2085}
2086
2087MagickExport MagickBooleanType HaldClutImage(Image *image,
2088 const Image *hald_image)
2089{
cristy3ed852e2009-09-05 21:47:34 +00002090#define HaldClutImageTag "Clut/Image"
2091
2092 typedef struct _HaldInfo
2093 {
2094 MagickRealType
2095 x,
2096 y,
2097 z;
2098 } HaldInfo;
2099
cristyfa112112010-01-04 17:48:07 +00002100 CacheView
cristyd551fbc2011-03-31 18:07:46 +00002101 *hald_view,
cristyfa112112010-01-04 17:48:07 +00002102 *image_view;
2103
cristy3ed852e2009-09-05 21:47:34 +00002104 double
2105 width;
2106
2107 ExceptionInfo
2108 *exception;
2109
cristy3ed852e2009-09-05 21:47:34 +00002110 MagickBooleanType
2111 status;
2112
cristybb503372010-05-27 20:51:26 +00002113 MagickOffsetType
2114 progress;
2115
cristy4c08aed2011-07-01 19:47:50 +00002116 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00002117 zero;
2118
cristy3ed852e2009-09-05 21:47:34 +00002119 size_t
2120 cube_size,
2121 length,
2122 level;
2123
cristybb503372010-05-27 20:51:26 +00002124 ssize_t
2125 y;
2126
cristy3ed852e2009-09-05 21:47:34 +00002127 assert(image != (Image *) NULL);
2128 assert(image->signature == MagickSignature);
2129 if (image->debug != MagickFalse)
2130 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2131 assert(hald_image != (Image *) NULL);
2132 assert(hald_image->signature == MagickSignature);
cristy574cc262011-08-05 01:23:58 +00002133 if (SetImageStorageClass(image,DirectClass,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00002134 return(MagickFalse);
2135 if (image->matte == MagickFalse)
2136 (void) SetImageAlphaChannel(image,OpaqueAlphaChannel);
2137 /*
2138 Hald clut image.
2139 */
2140 status=MagickTrue;
2141 progress=0;
2142 length=MagickMin(hald_image->columns,hald_image->rows);
2143 for (level=2; (level*level*level) < length; level++) ;
2144 level*=level;
2145 cube_size=level*level;
2146 width=(double) hald_image->columns;
cristy4c08aed2011-07-01 19:47:50 +00002147 GetPixelInfo(hald_image,&zero);
cristy3ed852e2009-09-05 21:47:34 +00002148 exception=(&image->exception);
cristy3ed852e2009-09-05 21:47:34 +00002149 image_view=AcquireCacheView(image);
cristyd551fbc2011-03-31 18:07:46 +00002150 hald_view=AcquireCacheView(hald_image);
cristyb5d5f722009-11-04 03:03:49 +00002151#if defined(MAGICKCORE_OPENMP_SUPPORT)
2152 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002153#endif
cristybb503372010-05-27 20:51:26 +00002154 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002155 {
2156 double
2157 offset;
2158
2159 HaldInfo
2160 point;
2161
cristy4c08aed2011-07-01 19:47:50 +00002162 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00002163 pixel,
2164 pixel1,
2165 pixel2,
2166 pixel3,
2167 pixel4;
2168
cristy4c08aed2011-07-01 19:47:50 +00002169 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00002170 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002171
cristy8d4629b2010-08-30 17:59:46 +00002172 register ssize_t
2173 x;
2174
cristy3ed852e2009-09-05 21:47:34 +00002175 if (status == MagickFalse)
2176 continue;
2177 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00002178 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00002179 {
2180 status=MagickFalse;
2181 continue;
2182 }
cristy3ed852e2009-09-05 21:47:34 +00002183 pixel=zero;
2184 pixel1=zero;
2185 pixel2=zero;
2186 pixel3=zero;
2187 pixel4=zero;
cristybb503372010-05-27 20:51:26 +00002188 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002189 {
cristy4c08aed2011-07-01 19:47:50 +00002190 point.x=QuantumScale*(level-1.0)*GetPixelRed(image,q);
2191 point.y=QuantumScale*(level-1.0)*GetPixelGreen(image,q);
2192 point.z=QuantumScale*(level-1.0)*GetPixelBlue(image,q);
cristy3ed852e2009-09-05 21:47:34 +00002193 offset=point.x+level*floor(point.y)+cube_size*floor(point.z);
2194 point.x-=floor(point.x);
2195 point.y-=floor(point.y);
2196 point.z-=floor(point.z);
cristy4c08aed2011-07-01 19:47:50 +00002197 (void) InterpolatePixelInfo(image,hald_view,
cristy8a7c3e82011-03-26 02:10:53 +00002198 UndefinedInterpolatePixel,fmod(offset,width),floor(offset/width),
2199 &pixel1,exception);
cristy4c08aed2011-07-01 19:47:50 +00002200 (void) InterpolatePixelInfo(image,hald_view,
cristy8a7c3e82011-03-26 02:10:53 +00002201 UndefinedInterpolatePixel,fmod(offset+level,width),floor((offset+level)/
2202 width),&pixel2,exception);
cristy4c08aed2011-07-01 19:47:50 +00002203 CompositePixelInfoAreaBlend(&pixel1,pixel1.alpha,&pixel2,
2204 pixel2.alpha,point.y,&pixel3);
cristy3ed852e2009-09-05 21:47:34 +00002205 offset+=cube_size;
cristy4c08aed2011-07-01 19:47:50 +00002206 (void) InterpolatePixelInfo(image,hald_view,
cristy8a7c3e82011-03-26 02:10:53 +00002207 UndefinedInterpolatePixel,fmod(offset,width),floor(offset/width),
2208 &pixel1,exception);
cristy4c08aed2011-07-01 19:47:50 +00002209 (void) InterpolatePixelInfo(image,hald_view,
cristy8a7c3e82011-03-26 02:10:53 +00002210 UndefinedInterpolatePixel,fmod(offset+level,width),floor((offset+level)/
2211 width),&pixel2,exception);
cristy4c08aed2011-07-01 19:47:50 +00002212 CompositePixelInfoAreaBlend(&pixel1,pixel1.alpha,&pixel2,
2213 pixel2.alpha,point.y,&pixel4);
2214 CompositePixelInfoAreaBlend(&pixel3,pixel3.alpha,&pixel4,
2215 pixel4.alpha,point.z,&pixel);
cristyed231572011-07-14 02:18:59 +00002216 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002217 SetPixelRed(image,
2218 ClampToQuantum(pixel.red),q);
cristyed231572011-07-14 02:18:59 +00002219 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002220 SetPixelGreen(image,
2221 ClampToQuantum(pixel.green),q);
cristyed231572011-07-14 02:18:59 +00002222 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002223 SetPixelBlue(image,
2224 ClampToQuantum(pixel.blue),q);
cristyed231572011-07-14 02:18:59 +00002225 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00002226 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +00002227 SetPixelBlack(image,
2228 ClampToQuantum(pixel.black),q);
cristyed231572011-07-14 02:18:59 +00002229 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) && (image->matte != MagickFalse))
cristy4c08aed2011-07-01 19:47:50 +00002230 SetPixelAlpha(image,
2231 ClampToQuantum(pixel.alpha),q);
cristyed231572011-07-14 02:18:59 +00002232 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00002233 }
2234 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2235 status=MagickFalse;
2236 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2237 {
2238 MagickBooleanType
2239 proceed;
2240
cristyb5d5f722009-11-04 03:03:49 +00002241#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf89cb1d2011-07-07 01:24:37 +00002242 #pragma omp critical (MagickCore_HaldClutImage)
cristy3ed852e2009-09-05 21:47:34 +00002243#endif
2244 proceed=SetImageProgress(image,HaldClutImageTag,progress++,image->rows);
2245 if (proceed == MagickFalse)
2246 status=MagickFalse;
2247 }
2248 }
cristyd551fbc2011-03-31 18:07:46 +00002249 hald_view=DestroyCacheView(hald_view);
cristy3ed852e2009-09-05 21:47:34 +00002250 image_view=DestroyCacheView(image_view);
cristy3ed852e2009-09-05 21:47:34 +00002251 return(status);
2252}
2253
2254/*
2255%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2256% %
2257% %
2258% %
2259% L e v e l I m a g e %
2260% %
2261% %
2262% %
2263%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2264%
2265% LevelImage() adjusts the levels of a particular image channel by
2266% scaling the colors falling between specified white and black points to
2267% the full available quantum range.
2268%
2269% The parameters provided represent the black, and white points. The black
2270% point specifies the darkest color in the image. Colors darker than the
2271% black point are set to zero. White point specifies the lightest color in
2272% the image. Colors brighter than the white point are set to the maximum
2273% quantum value.
2274%
2275% If a '!' flag is given, map black and white colors to the given levels
2276% rather than mapping those levels to black and white. See
cristy50fbc382011-07-07 02:19:17 +00002277% LevelizeImage() below.
cristy3ed852e2009-09-05 21:47:34 +00002278%
2279% Gamma specifies a gamma correction to apply to the image.
2280%
2281% The format of the LevelImage method is:
2282%
2283% MagickBooleanType LevelImage(Image *image,const char *levels)
2284%
2285% A description of each parameter follows:
2286%
2287% o image: the image.
2288%
2289% o levels: Specify the levels where the black and white points have the
2290% range of 0-QuantumRange, and gamma has the range 0-10 (e.g. 10x90%+2).
2291% A '!' flag inverts the re-mapping.
2292%
2293*/
cristyf89cb1d2011-07-07 01:24:37 +00002294MagickExport MagickBooleanType LevelImage(Image *image,
2295 const double black_point,const double white_point,const double gamma)
cristy3ed852e2009-09-05 21:47:34 +00002296{
2297#define LevelImageTag "Level/Image"
cristybcfb0432010-05-06 01:45:33 +00002298#define LevelQuantum(x) (ClampToQuantum((MagickRealType) QuantumRange* \
cristyc1f508d2010-04-22 01:21:47 +00002299 pow(scale*((double) (x)-black_point),1.0/gamma)))
cristy3ed852e2009-09-05 21:47:34 +00002300
cristyc4c8d132010-01-07 01:58:38 +00002301 CacheView
2302 *image_view;
2303
cristy3ed852e2009-09-05 21:47:34 +00002304 ExceptionInfo
2305 *exception;
2306
cristy3ed852e2009-09-05 21:47:34 +00002307 MagickBooleanType
2308 status;
2309
cristybb503372010-05-27 20:51:26 +00002310 MagickOffsetType
2311 progress;
2312
anthony7fe39fc2010-04-06 03:19:20 +00002313 register double
2314 scale;
2315
cristy8d4629b2010-08-30 17:59:46 +00002316 register ssize_t
2317 i;
2318
cristybb503372010-05-27 20:51:26 +00002319 ssize_t
2320 y;
2321
cristy3ed852e2009-09-05 21:47:34 +00002322 /*
2323 Allocate and initialize levels map.
2324 */
2325 assert(image != (Image *) NULL);
2326 assert(image->signature == MagickSignature);
2327 if (image->debug != MagickFalse)
2328 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
cristy8d4629b2010-08-30 17:59:46 +00002329 scale=(white_point != black_point) ? 1.0/(white_point-black_point) : 1.0;
cristy3ed852e2009-09-05 21:47:34 +00002330 if (image->storage_class == PseudoClass)
cristyb5d5f722009-11-04 03:03:49 +00002331#if defined(MAGICKCORE_OPENMP_SUPPORT)
2332 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002333#endif
cristybb503372010-05-27 20:51:26 +00002334 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00002335 {
2336 /*
2337 Level colormap.
2338 */
cristyed231572011-07-14 02:18:59 +00002339 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristyc1f508d2010-04-22 01:21:47 +00002340 image->colormap[i].red=LevelQuantum(image->colormap[i].red);
cristyed231572011-07-14 02:18:59 +00002341 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristyc1f508d2010-04-22 01:21:47 +00002342 image->colormap[i].green=LevelQuantum(image->colormap[i].green);
cristyed231572011-07-14 02:18:59 +00002343 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristyc1f508d2010-04-22 01:21:47 +00002344 image->colormap[i].blue=LevelQuantum(image->colormap[i].blue);
cristyed231572011-07-14 02:18:59 +00002345 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002346 image->colormap[i].alpha=LevelQuantum(image->colormap[i].alpha);
cristy3ed852e2009-09-05 21:47:34 +00002347 }
2348 /*
2349 Level image.
2350 */
2351 status=MagickTrue;
2352 progress=0;
2353 exception=(&image->exception);
2354 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00002355#if defined(MAGICKCORE_OPENMP_SUPPORT)
2356 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002357#endif
cristybb503372010-05-27 20:51:26 +00002358 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002359 {
cristy4c08aed2011-07-01 19:47:50 +00002360 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00002361 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002362
cristy8d4629b2010-08-30 17:59:46 +00002363 register ssize_t
2364 x;
2365
cristy3ed852e2009-09-05 21:47:34 +00002366 if (status == MagickFalse)
2367 continue;
2368 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00002369 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00002370 {
2371 status=MagickFalse;
2372 continue;
2373 }
cristybb503372010-05-27 20:51:26 +00002374 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002375 {
cristyed231572011-07-14 02:18:59 +00002376 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002377 SetPixelRed(image,LevelQuantum(
2378 GetPixelRed(image,q)),q);
cristyed231572011-07-14 02:18:59 +00002379 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002380 SetPixelGreen(image,
2381 LevelQuantum(GetPixelGreen(image,q)),q);
cristyed231572011-07-14 02:18:59 +00002382 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002383 SetPixelBlue(image,
2384 LevelQuantum(GetPixelBlue(image,q)),q);
cristyed231572011-07-14 02:18:59 +00002385 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00002386 (image->matte == MagickTrue))
cristy4c08aed2011-07-01 19:47:50 +00002387 SetPixelAlpha(image,
2388 LevelQuantum(GetPixelAlpha(image,q)),q);
cristyed231572011-07-14 02:18:59 +00002389 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00002390 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +00002391 SetPixelBlack(image,
2392 LevelQuantum(GetPixelBlack(image,q)),q);
cristyed231572011-07-14 02:18:59 +00002393 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00002394 }
2395 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2396 status=MagickFalse;
2397 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2398 {
2399 MagickBooleanType
2400 proceed;
2401
cristyb5d5f722009-11-04 03:03:49 +00002402#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf89cb1d2011-07-07 01:24:37 +00002403 #pragma omp critical (MagickCore_LevelImage)
cristy3ed852e2009-09-05 21:47:34 +00002404#endif
2405 proceed=SetImageProgress(image,LevelImageTag,progress++,image->rows);
2406 if (proceed == MagickFalse)
2407 status=MagickFalse;
2408 }
2409 }
2410 image_view=DestroyCacheView(image_view);
2411 return(status);
2412}
2413
2414/*
2415%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2416% %
2417% %
2418% %
2419% L e v e l i z e I m a g e C h a n n e l %
2420% %
2421% %
2422% %
2423%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2424%
cristy50fbc382011-07-07 02:19:17 +00002425% LevelizeImage() applies the reversed LevelImage() operation to just
cristy3ed852e2009-09-05 21:47:34 +00002426% the specific channels specified. It compresses the full range of color
2427% values, so that they lie between the given black and white points. Gamma is
2428% applied before the values are mapped.
2429%
cristy50fbc382011-07-07 02:19:17 +00002430% LevelizeImage() can be called with by using a +level command line
cristy3ed852e2009-09-05 21:47:34 +00002431% API option, or using a '!' on a -level or LevelImage() geometry string.
2432%
2433% It can be used for example de-contrast a greyscale image to the exact
2434% levels specified. Or by using specific levels for each channel of an image
2435% you can convert a gray-scale image to any linear color gradient, according
2436% to those levels.
2437%
cristy50fbc382011-07-07 02:19:17 +00002438% The format of the LevelizeImage method is:
cristy3ed852e2009-09-05 21:47:34 +00002439%
cristy50fbc382011-07-07 02:19:17 +00002440% MagickBooleanType LevelizeImage(Image *image,const double black_point,
2441% const double white_point,const double gamma)
cristy3ed852e2009-09-05 21:47:34 +00002442%
2443% A description of each parameter follows:
2444%
2445% o image: the image.
2446%
cristy3ed852e2009-09-05 21:47:34 +00002447% o black_point: The level to map zero (black) to.
2448%
2449% o white_point: The level to map QuantiumRange (white) to.
2450%
2451% o gamma: adjust gamma by this factor before mapping values.
2452%
2453*/
cristyd1a2c0f2011-02-09 14:14:50 +00002454MagickExport MagickBooleanType LevelizeImage(Image *image,
2455 const double black_point,const double white_point,const double gamma)
2456{
cristy3ed852e2009-09-05 21:47:34 +00002457#define LevelizeImageTag "Levelize/Image"
cristyce70c172010-01-07 17:15:30 +00002458#define LevelizeValue(x) (ClampToQuantum(((MagickRealType) \
cristy50fbc382011-07-07 02:19:17 +00002459 pow((double) (QuantumScale*(x)),1.0/gamma))*(white_point-black_point)+ \
cristy3ed852e2009-09-05 21:47:34 +00002460 black_point))
2461
cristyc4c8d132010-01-07 01:58:38 +00002462 CacheView
2463 *image_view;
2464
cristy3ed852e2009-09-05 21:47:34 +00002465 ExceptionInfo
2466 *exception;
2467
cristy3ed852e2009-09-05 21:47:34 +00002468 MagickBooleanType
2469 status;
2470
cristybb503372010-05-27 20:51:26 +00002471 MagickOffsetType
2472 progress;
2473
2474 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002475 i;
2476
cristybb503372010-05-27 20:51:26 +00002477 ssize_t
2478 y;
2479
cristy3ed852e2009-09-05 21:47:34 +00002480 /*
2481 Allocate and initialize levels map.
2482 */
2483 assert(image != (Image *) NULL);
2484 assert(image->signature == MagickSignature);
2485 if (image->debug != MagickFalse)
2486 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2487 if (image->storage_class == PseudoClass)
cristyb5d5f722009-11-04 03:03:49 +00002488#if defined(MAGICKCORE_OPENMP_SUPPORT)
2489 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002490#endif
cristybb503372010-05-27 20:51:26 +00002491 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00002492 {
2493 /*
2494 Level colormap.
2495 */
cristyed231572011-07-14 02:18:59 +00002496 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00002497 image->colormap[i].red=LevelizeValue(image->colormap[i].red);
cristyed231572011-07-14 02:18:59 +00002498 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00002499 image->colormap[i].green=LevelizeValue(image->colormap[i].green);
cristyed231572011-07-14 02:18:59 +00002500 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00002501 image->colormap[i].blue=LevelizeValue(image->colormap[i].blue);
cristyed231572011-07-14 02:18:59 +00002502 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002503 image->colormap[i].alpha=LevelizeValue(image->colormap[i].alpha);
cristy3ed852e2009-09-05 21:47:34 +00002504 }
2505 /*
2506 Level image.
2507 */
2508 status=MagickTrue;
2509 progress=0;
2510 exception=(&image->exception);
2511 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00002512#if defined(MAGICKCORE_OPENMP_SUPPORT)
2513 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002514#endif
cristybb503372010-05-27 20:51:26 +00002515 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002516 {
cristy4c08aed2011-07-01 19:47:50 +00002517 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00002518 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002519
cristy8d4629b2010-08-30 17:59:46 +00002520 register ssize_t
2521 x;
2522
cristy3ed852e2009-09-05 21:47:34 +00002523 if (status == MagickFalse)
2524 continue;
2525 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00002526 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00002527 {
2528 status=MagickFalse;
2529 continue;
2530 }
cristybb503372010-05-27 20:51:26 +00002531 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002532 {
cristyed231572011-07-14 02:18:59 +00002533 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002534 SetPixelRed(image,LevelizeValue(GetPixelRed(image,q)),q);
cristyed231572011-07-14 02:18:59 +00002535 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002536 SetPixelGreen(image,LevelizeValue(GetPixelGreen(image,q)),q);
cristyed231572011-07-14 02:18:59 +00002537 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002538 SetPixelBlue(image,LevelizeValue(GetPixelBlue(image,q)),q);
cristyed231572011-07-14 02:18:59 +00002539 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +00002540 (image->colorspace == CMYKColorspace))
2541 SetPixelBlack(image,LevelizeValue(GetPixelBlack(image,q)),q);
cristyed231572011-07-14 02:18:59 +00002542 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00002543 (image->matte == MagickTrue))
cristy4c08aed2011-07-01 19:47:50 +00002544 SetPixelAlpha(image,LevelizeValue(GetPixelAlpha(image,q)),q);
cristyed231572011-07-14 02:18:59 +00002545 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00002546 }
2547 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2548 status=MagickFalse;
2549 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2550 {
2551 MagickBooleanType
2552 proceed;
2553
cristyb5d5f722009-11-04 03:03:49 +00002554#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy50fbc382011-07-07 02:19:17 +00002555 #pragma omp critical (MagickCore_LevelizeImage)
cristy3ed852e2009-09-05 21:47:34 +00002556#endif
2557 proceed=SetImageProgress(image,LevelizeImageTag,progress++,image->rows);
2558 if (proceed == MagickFalse)
2559 status=MagickFalse;
2560 }
2561 }
cristy8d4629b2010-08-30 17:59:46 +00002562 image_view=DestroyCacheView(image_view);
cristy3ed852e2009-09-05 21:47:34 +00002563 return(status);
2564}
2565
2566/*
2567%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2568% %
2569% %
2570% %
2571% L e v e l I m a g e C o l o r s %
2572% %
2573% %
2574% %
2575%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2576%
cristy490408a2011-07-07 14:42:05 +00002577% LevelImageColors() maps the given color to "black" and "white" values,
cristyee0f8d72009-09-19 00:58:29 +00002578% linearly spreading out the colors, and level values on a channel by channel
2579% bases, as per LevelImage(). The given colors allows you to specify
glennrp1e7f7bc2011-03-02 19:25:28 +00002580% different level ranges for each of the color channels separately.
cristy3ed852e2009-09-05 21:47:34 +00002581%
2582% If the boolean 'invert' is set true the image values will modifyed in the
2583% reverse direction. That is any existing "black" and "white" colors in the
2584% image will become the color values given, with all other values compressed
2585% appropriatally. This effectivally maps a greyscale gradient into the given
2586% color gradient.
2587%
cristy490408a2011-07-07 14:42:05 +00002588% The format of the LevelImageColors method is:
cristy3ed852e2009-09-05 21:47:34 +00002589%
cristy490408a2011-07-07 14:42:05 +00002590% MagickBooleanType LevelImageColors(Image *image,
2591% const PixelInfo *black_color,const PixelInfo *white_color,
2592% const MagickBooleanType invert)
cristy3ed852e2009-09-05 21:47:34 +00002593%
2594% A description of each parameter follows:
2595%
2596% o image: the image.
2597%
cristy3ed852e2009-09-05 21:47:34 +00002598% o black_color: The color to map black to/from
2599%
2600% o white_point: The color to map white to/from
2601%
2602% o invert: if true map the colors (levelize), rather than from (level)
2603%
2604*/
cristy490408a2011-07-07 14:42:05 +00002605MagickExport MagickBooleanType LevelImageColors(Image *image,
cristy4c08aed2011-07-01 19:47:50 +00002606 const PixelInfo *black_color,const PixelInfo *white_color,
cristy3ed852e2009-09-05 21:47:34 +00002607 const MagickBooleanType invert)
2608{
cristy3ed852e2009-09-05 21:47:34 +00002609 MagickStatusType
2610 status;
2611
2612 /*
2613 Allocate and initialize levels map.
2614 */
2615 assert(image != (Image *) NULL);
2616 assert(image->signature == MagickSignature);
2617 if (image->debug != MagickFalse)
2618 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2619 status=MagickFalse;
2620 if (invert == MagickFalse)
2621 {
cristyed231572011-07-14 02:18:59 +00002622 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristyf89cb1d2011-07-07 01:24:37 +00002623 {
cristyed231572011-07-14 02:18:59 +00002624 PushPixelChannelMap(image,RedChannel);
cristyf89cb1d2011-07-07 01:24:37 +00002625 status|=LevelImage(image,black_color->red,white_color->red,1.0);
cristyed231572011-07-14 02:18:59 +00002626 PopPixelChannelMap(image);
cristyf89cb1d2011-07-07 01:24:37 +00002627 }
cristyed231572011-07-14 02:18:59 +00002628 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristyf89cb1d2011-07-07 01:24:37 +00002629 {
cristyed231572011-07-14 02:18:59 +00002630 PushPixelChannelMap(image,GreenChannel);
cristyf89cb1d2011-07-07 01:24:37 +00002631 status|=LevelImage(image,black_color->green,white_color->green,1.0);
cristyed231572011-07-14 02:18:59 +00002632 PopPixelChannelMap(image);
cristyf89cb1d2011-07-07 01:24:37 +00002633 }
cristyed231572011-07-14 02:18:59 +00002634 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristyf89cb1d2011-07-07 01:24:37 +00002635 {
cristyed231572011-07-14 02:18:59 +00002636 PushPixelChannelMap(image,BlueChannel);
cristyf89cb1d2011-07-07 01:24:37 +00002637 status|=LevelImage(image,black_color->blue,white_color->blue,1.0);
cristyed231572011-07-14 02:18:59 +00002638 PopPixelChannelMap(image);
cristyf89cb1d2011-07-07 01:24:37 +00002639 }
cristyed231572011-07-14 02:18:59 +00002640 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +00002641 (image->colorspace == CMYKColorspace))
cristyf89cb1d2011-07-07 01:24:37 +00002642 {
cristyed231572011-07-14 02:18:59 +00002643 PushPixelChannelMap(image,BlackChannel);
cristyf89cb1d2011-07-07 01:24:37 +00002644 status|=LevelImage(image,black_color->black,white_color->black,1.0);
cristyed231572011-07-14 02:18:59 +00002645 PopPixelChannelMap(image);
cristyf89cb1d2011-07-07 01:24:37 +00002646 }
cristyed231572011-07-14 02:18:59 +00002647 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00002648 (image->matte == MagickTrue))
cristyf89cb1d2011-07-07 01:24:37 +00002649 {
cristyed231572011-07-14 02:18:59 +00002650 PushPixelChannelMap(image,AlphaChannel);
cristyf89cb1d2011-07-07 01:24:37 +00002651 status|=LevelImage(image,black_color->alpha,white_color->alpha,1.0);
cristyed231572011-07-14 02:18:59 +00002652 PopPixelChannelMap(image);
cristyf89cb1d2011-07-07 01:24:37 +00002653 }
cristy3ed852e2009-09-05 21:47:34 +00002654 }
2655 else
2656 {
cristyed231572011-07-14 02:18:59 +00002657 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristyf89cb1d2011-07-07 01:24:37 +00002658 {
cristyed231572011-07-14 02:18:59 +00002659 PushPixelChannelMap(image,RedChannel);
cristy50fbc382011-07-07 02:19:17 +00002660 status|=LevelizeImage(image,black_color->red,white_color->red,1.0);
cristyed231572011-07-14 02:18:59 +00002661 PopPixelChannelMap(image);
cristyf89cb1d2011-07-07 01:24:37 +00002662 }
cristyed231572011-07-14 02:18:59 +00002663 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristyf89cb1d2011-07-07 01:24:37 +00002664 {
cristyed231572011-07-14 02:18:59 +00002665 PushPixelChannelMap(image,GreenChannel);
cristy50fbc382011-07-07 02:19:17 +00002666 status|=LevelizeImage(image,black_color->green,white_color->green,
2667 1.0);
cristyed231572011-07-14 02:18:59 +00002668 PopPixelChannelMap(image);
cristyf89cb1d2011-07-07 01:24:37 +00002669 }
cristyed231572011-07-14 02:18:59 +00002670 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristyf89cb1d2011-07-07 01:24:37 +00002671 {
cristyed231572011-07-14 02:18:59 +00002672 PushPixelChannelMap(image,BlueChannel);
cristy50fbc382011-07-07 02:19:17 +00002673 status|=LevelizeImage(image,black_color->blue,white_color->blue,1.0);
cristyed231572011-07-14 02:18:59 +00002674 PopPixelChannelMap(image);
cristyf89cb1d2011-07-07 01:24:37 +00002675 }
cristyed231572011-07-14 02:18:59 +00002676 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +00002677 (image->colorspace == CMYKColorspace))
cristyf89cb1d2011-07-07 01:24:37 +00002678 {
cristyed231572011-07-14 02:18:59 +00002679 PushPixelChannelMap(image,BlackChannel);
cristy50fbc382011-07-07 02:19:17 +00002680 status|=LevelizeImage(image,black_color->black,white_color->black,
2681 1.0);
cristyed231572011-07-14 02:18:59 +00002682 PopPixelChannelMap(image);
cristyf89cb1d2011-07-07 01:24:37 +00002683 }
cristyed231572011-07-14 02:18:59 +00002684 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00002685 (image->matte == MagickTrue))
cristyf89cb1d2011-07-07 01:24:37 +00002686 {
cristyed231572011-07-14 02:18:59 +00002687 PushPixelChannelMap(image,AlphaChannel);
cristy50fbc382011-07-07 02:19:17 +00002688 status|=LevelizeImage(image,black_color->alpha,white_color->alpha,
2689 1.0);
cristyed231572011-07-14 02:18:59 +00002690 PopPixelChannelMap(image);
cristyf89cb1d2011-07-07 01:24:37 +00002691 }
cristy3ed852e2009-09-05 21:47:34 +00002692 }
2693 return(status == 0 ? MagickFalse : MagickTrue);
2694}
2695
2696/*
2697%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2698% %
2699% %
2700% %
2701% L i n e a r S t r e t c h I m a g e %
2702% %
2703% %
2704% %
2705%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2706%
cristyf1611782011-08-01 15:39:13 +00002707% LinearStretchImage() discards any pixels below the black point and above
2708% the white point and levels the remaining pixels.
cristy3ed852e2009-09-05 21:47:34 +00002709%
2710% The format of the LinearStretchImage method is:
2711%
2712% MagickBooleanType LinearStretchImage(Image *image,
2713% const double black_point,const double white_point)
2714%
2715% A description of each parameter follows:
2716%
2717% o image: the image.
2718%
2719% o black_point: the black point.
2720%
2721% o white_point: the white point.
2722%
2723*/
2724MagickExport MagickBooleanType LinearStretchImage(Image *image,
2725 const double black_point,const double white_point)
2726{
2727#define LinearStretchImageTag "LinearStretch/Image"
2728
2729 ExceptionInfo
2730 *exception;
2731
cristy3ed852e2009-09-05 21:47:34 +00002732 MagickBooleanType
2733 status;
2734
2735 MagickRealType
2736 *histogram,
2737 intensity;
2738
cristy8d4629b2010-08-30 17:59:46 +00002739 ssize_t
2740 black,
2741 white,
2742 y;
2743
cristy3ed852e2009-09-05 21:47:34 +00002744 /*
2745 Allocate histogram and linear map.
2746 */
2747 assert(image != (Image *) NULL);
2748 assert(image->signature == MagickSignature);
2749 histogram=(MagickRealType *) AcquireQuantumMemory(MaxMap+1UL,
2750 sizeof(*histogram));
2751 if (histogram == (MagickRealType *) NULL)
2752 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
2753 image->filename);
2754 /*
2755 Form histogram.
2756 */
2757 (void) ResetMagickMemory(histogram,0,(MaxMap+1)*sizeof(*histogram));
2758 exception=(&image->exception);
cristybb503372010-05-27 20:51:26 +00002759 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002760 {
cristy4c08aed2011-07-01 19:47:50 +00002761 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00002762 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00002763
cristybb503372010-05-27 20:51:26 +00002764 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002765 x;
2766
2767 p=GetVirtualPixels(image,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00002768 if (p == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00002769 break;
cristybb503372010-05-27 20:51:26 +00002770 for (x=(ssize_t) image->columns-1; x >= 0; x--)
cristy3ed852e2009-09-05 21:47:34 +00002771 {
cristy4c08aed2011-07-01 19:47:50 +00002772 histogram[ScaleQuantumToMap(GetPixelIntensity(image,p))]++;
cristyed231572011-07-14 02:18:59 +00002773 p+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00002774 }
2775 }
2776 /*
2777 Find the histogram boundaries by locating the black and white point levels.
2778 */
cristy3ed852e2009-09-05 21:47:34 +00002779 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00002780 for (black=0; black < (ssize_t) MaxMap; black++)
cristy3ed852e2009-09-05 21:47:34 +00002781 {
2782 intensity+=histogram[black];
2783 if (intensity >= black_point)
2784 break;
2785 }
2786 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00002787 for (white=(ssize_t) MaxMap; white != 0; white--)
cristy3ed852e2009-09-05 21:47:34 +00002788 {
2789 intensity+=histogram[white];
2790 if (intensity >= white_point)
2791 break;
2792 }
2793 histogram=(MagickRealType *) RelinquishMagickMemory(histogram);
cristyf89cb1d2011-07-07 01:24:37 +00002794 status=LevelImage(image,(double) black,(double) white,1.0);
cristy3ed852e2009-09-05 21:47:34 +00002795 return(status);
2796}
2797
2798/*
2799%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2800% %
2801% %
2802% %
2803% M o d u l a t e I m a g e %
2804% %
2805% %
2806% %
2807%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2808%
2809% ModulateImage() lets you control the brightness, saturation, and hue
2810% of an image. Modulate represents the brightness, saturation, and hue
2811% as one parameter (e.g. 90,150,100). If the image colorspace is HSL, the
2812% modulation is lightness, saturation, and hue. And if the colorspace is
2813% HWB, use blackness, whiteness, and hue.
2814%
2815% The format of the ModulateImage method is:
2816%
2817% MagickBooleanType ModulateImage(Image *image,const char *modulate)
2818%
2819% A description of each parameter follows:
2820%
2821% o image: the image.
2822%
2823% o modulate: Define the percent change in brightness, saturation, and
2824% hue.
2825%
2826*/
2827
2828static void ModulateHSB(const double percent_hue,
2829 const double percent_saturation,const double percent_brightness,
2830 Quantum *red,Quantum *green,Quantum *blue)
2831{
2832 double
2833 brightness,
2834 hue,
2835 saturation;
2836
2837 /*
2838 Increase or decrease color brightness, saturation, or hue.
2839 */
2840 assert(red != (Quantum *) NULL);
2841 assert(green != (Quantum *) NULL);
2842 assert(blue != (Quantum *) NULL);
2843 ConvertRGBToHSB(*red,*green,*blue,&hue,&saturation,&brightness);
2844 hue+=0.5*(0.01*percent_hue-1.0);
2845 while (hue < 0.0)
2846 hue+=1.0;
2847 while (hue > 1.0)
2848 hue-=1.0;
2849 saturation*=0.01*percent_saturation;
2850 brightness*=0.01*percent_brightness;
2851 ConvertHSBToRGB(hue,saturation,brightness,red,green,blue);
2852}
2853
2854static void ModulateHSL(const double percent_hue,
2855 const double percent_saturation,const double percent_lightness,
2856 Quantum *red,Quantum *green,Quantum *blue)
2857{
2858 double
2859 hue,
2860 lightness,
2861 saturation;
2862
2863 /*
2864 Increase or decrease color lightness, saturation, or hue.
2865 */
2866 assert(red != (Quantum *) NULL);
2867 assert(green != (Quantum *) NULL);
2868 assert(blue != (Quantum *) NULL);
2869 ConvertRGBToHSL(*red,*green,*blue,&hue,&saturation,&lightness);
2870 hue+=0.5*(0.01*percent_hue-1.0);
2871 while (hue < 0.0)
2872 hue+=1.0;
2873 while (hue > 1.0)
2874 hue-=1.0;
2875 saturation*=0.01*percent_saturation;
2876 lightness*=0.01*percent_lightness;
2877 ConvertHSLToRGB(hue,saturation,lightness,red,green,blue);
2878}
2879
2880static void ModulateHWB(const double percent_hue,const double percent_whiteness, const double percent_blackness,Quantum *red,Quantum *green,Quantum *blue)
2881{
2882 double
2883 blackness,
2884 hue,
2885 whiteness;
2886
2887 /*
2888 Increase or decrease color blackness, whiteness, or hue.
2889 */
2890 assert(red != (Quantum *) NULL);
2891 assert(green != (Quantum *) NULL);
2892 assert(blue != (Quantum *) NULL);
2893 ConvertRGBToHWB(*red,*green,*blue,&hue,&whiteness,&blackness);
2894 hue+=0.5*(0.01*percent_hue-1.0);
2895 while (hue < 0.0)
2896 hue+=1.0;
2897 while (hue > 1.0)
2898 hue-=1.0;
2899 blackness*=0.01*percent_blackness;
2900 whiteness*=0.01*percent_whiteness;
2901 ConvertHWBToRGB(hue,whiteness,blackness,red,green,blue);
2902}
2903
2904MagickExport MagickBooleanType ModulateImage(Image *image,const char *modulate)
2905{
2906#define ModulateImageTag "Modulate/Image"
2907
cristyc4c8d132010-01-07 01:58:38 +00002908 CacheView
2909 *image_view;
2910
cristy3ed852e2009-09-05 21:47:34 +00002911 ColorspaceType
2912 colorspace;
2913
2914 const char
2915 *artifact;
2916
2917 double
2918 percent_brightness,
2919 percent_hue,
2920 percent_saturation;
2921
2922 ExceptionInfo
2923 *exception;
2924
2925 GeometryInfo
2926 geometry_info;
2927
cristy3ed852e2009-09-05 21:47:34 +00002928 MagickBooleanType
2929 status;
2930
cristybb503372010-05-27 20:51:26 +00002931 MagickOffsetType
2932 progress;
2933
cristy3ed852e2009-09-05 21:47:34 +00002934 MagickStatusType
2935 flags;
2936
cristybb503372010-05-27 20:51:26 +00002937 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002938 i;
2939
cristybb503372010-05-27 20:51:26 +00002940 ssize_t
2941 y;
2942
cristy3ed852e2009-09-05 21:47:34 +00002943 /*
cristy2b726bd2010-01-11 01:05:39 +00002944 Initialize modulate table.
cristy3ed852e2009-09-05 21:47:34 +00002945 */
2946 assert(image != (Image *) NULL);
2947 assert(image->signature == MagickSignature);
2948 if (image->debug != MagickFalse)
2949 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2950 if (modulate == (char *) NULL)
2951 return(MagickFalse);
2952 flags=ParseGeometry(modulate,&geometry_info);
2953 percent_brightness=geometry_info.rho;
2954 percent_saturation=geometry_info.sigma;
2955 if ((flags & SigmaValue) == 0)
2956 percent_saturation=100.0;
2957 percent_hue=geometry_info.xi;
2958 if ((flags & XiValue) == 0)
2959 percent_hue=100.0;
2960 colorspace=UndefinedColorspace;
2961 artifact=GetImageArtifact(image,"modulate:colorspace");
2962 if (artifact != (const char *) NULL)
cristy042ee782011-04-22 18:48:30 +00002963 colorspace=(ColorspaceType) ParseCommandOption(MagickColorspaceOptions,
cristy3ed852e2009-09-05 21:47:34 +00002964 MagickFalse,artifact);
2965 if (image->storage_class == PseudoClass)
2966 {
2967 /*
2968 Modulate colormap.
2969 */
cristyb5d5f722009-11-04 03:03:49 +00002970#if defined(MAGICKCORE_OPENMP_SUPPORT)
2971 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002972#endif
cristybb503372010-05-27 20:51:26 +00002973 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00002974 switch (colorspace)
2975 {
2976 case HSBColorspace:
2977 {
2978 ModulateHSB(percent_hue,percent_saturation,percent_brightness,
2979 &image->colormap[i].red,&image->colormap[i].green,
2980 &image->colormap[i].blue);
2981 break;
2982 }
2983 case HSLColorspace:
2984 default:
2985 {
2986 ModulateHSL(percent_hue,percent_saturation,percent_brightness,
2987 &image->colormap[i].red,&image->colormap[i].green,
2988 &image->colormap[i].blue);
2989 break;
2990 }
2991 case HWBColorspace:
2992 {
2993 ModulateHWB(percent_hue,percent_saturation,percent_brightness,
2994 &image->colormap[i].red,&image->colormap[i].green,
2995 &image->colormap[i].blue);
2996 break;
2997 }
2998 }
2999 }
3000 /*
3001 Modulate image.
3002 */
3003 status=MagickTrue;
3004 progress=0;
3005 exception=(&image->exception);
3006 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00003007#if defined(MAGICKCORE_OPENMP_SUPPORT)
3008 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003009#endif
cristybb503372010-05-27 20:51:26 +00003010 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003011 {
cristy5afeab82011-04-30 01:30:09 +00003012 Quantum
3013 blue,
3014 green,
3015 red;
3016
cristy4c08aed2011-07-01 19:47:50 +00003017 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00003018 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003019
cristy8d4629b2010-08-30 17:59:46 +00003020 register ssize_t
3021 x;
3022
cristy3ed852e2009-09-05 21:47:34 +00003023 if (status == MagickFalse)
3024 continue;
3025 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00003026 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00003027 {
3028 status=MagickFalse;
3029 continue;
3030 }
cristybb503372010-05-27 20:51:26 +00003031 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003032 {
cristy4c08aed2011-07-01 19:47:50 +00003033 red=GetPixelRed(image,q);
3034 green=GetPixelGreen(image,q);
3035 blue=GetPixelBlue(image,q);
cristy3ed852e2009-09-05 21:47:34 +00003036 switch (colorspace)
3037 {
3038 case HSBColorspace:
3039 {
3040 ModulateHSB(percent_hue,percent_saturation,percent_brightness,
cristy5afeab82011-04-30 01:30:09 +00003041 &red,&green,&blue);
cristy3ed852e2009-09-05 21:47:34 +00003042 break;
3043 }
3044 case HSLColorspace:
3045 default:
3046 {
3047 ModulateHSL(percent_hue,percent_saturation,percent_brightness,
cristy5afeab82011-04-30 01:30:09 +00003048 &red,&green,&blue);
cristy3ed852e2009-09-05 21:47:34 +00003049 break;
3050 }
3051 case HWBColorspace:
3052 {
3053 ModulateHWB(percent_hue,percent_saturation,percent_brightness,
cristy5afeab82011-04-30 01:30:09 +00003054 &red,&green,&blue);
cristy3ed852e2009-09-05 21:47:34 +00003055 break;
3056 }
3057 }
cristy4c08aed2011-07-01 19:47:50 +00003058 SetPixelRed(image,red,q);
3059 SetPixelGreen(image,green,q);
3060 SetPixelBlue(image,blue,q);
cristyed231572011-07-14 02:18:59 +00003061 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00003062 }
3063 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
3064 status=MagickFalse;
3065 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3066 {
3067 MagickBooleanType
3068 proceed;
3069
cristyb5d5f722009-11-04 03:03:49 +00003070#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00003071 #pragma omp critical (MagickCore_ModulateImage)
3072#endif
3073 proceed=SetImageProgress(image,ModulateImageTag,progress++,image->rows);
3074 if (proceed == MagickFalse)
3075 status=MagickFalse;
3076 }
3077 }
3078 image_view=DestroyCacheView(image_view);
3079 return(status);
3080}
3081
3082/*
3083%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3084% %
3085% %
3086% %
3087% N e g a t e I m a g e %
3088% %
3089% %
3090% %
3091%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3092%
3093% NegateImage() negates the colors in the reference image. The grayscale
3094% option means that only grayscale values within the image are negated.
3095%
cristy50fbc382011-07-07 02:19:17 +00003096% The format of the NegateImage method is:
cristy3ed852e2009-09-05 21:47:34 +00003097%
3098% MagickBooleanType NegateImage(Image *image,
cristyb3e7c6c2011-07-24 01:43:55 +00003099% const MagickBooleanType grayscale,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003100%
3101% A description of each parameter follows:
3102%
3103% o image: the image.
3104%
cristy3ed852e2009-09-05 21:47:34 +00003105% o grayscale: If MagickTrue, only negate grayscale pixels within the image.
3106%
cristyb3e7c6c2011-07-24 01:43:55 +00003107% o exception: return any errors or warnings in this structure.
3108%
cristy3ed852e2009-09-05 21:47:34 +00003109*/
cristy3ed852e2009-09-05 21:47:34 +00003110MagickExport MagickBooleanType NegateImage(Image *image,
cristyb3e7c6c2011-07-24 01:43:55 +00003111 const MagickBooleanType grayscale,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003112{
cristy3ed852e2009-09-05 21:47:34 +00003113#define NegateImageTag "Negate/Image"
3114
cristyc4c8d132010-01-07 01:58:38 +00003115 CacheView
3116 *image_view;
3117
cristy3ed852e2009-09-05 21:47:34 +00003118 MagickBooleanType
3119 status;
3120
cristybb503372010-05-27 20:51:26 +00003121 MagickOffsetType
3122 progress;
3123
3124 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003125 i;
3126
cristybb503372010-05-27 20:51:26 +00003127 ssize_t
3128 y;
3129
cristy3ed852e2009-09-05 21:47:34 +00003130 assert(image != (Image *) NULL);
3131 assert(image->signature == MagickSignature);
3132 if (image->debug != MagickFalse)
3133 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3134 if (image->storage_class == PseudoClass)
3135 {
3136 /*
3137 Negate colormap.
3138 */
cristyb5d5f722009-11-04 03:03:49 +00003139#if defined(MAGICKCORE_OPENMP_SUPPORT)
3140 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003141#endif
cristybb503372010-05-27 20:51:26 +00003142 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00003143 {
3144 if (grayscale != MagickFalse)
3145 if ((image->colormap[i].red != image->colormap[i].green) ||
3146 (image->colormap[i].green != image->colormap[i].blue))
3147 continue;
cristyed231572011-07-14 02:18:59 +00003148 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00003149 image->colormap[i].red=(Quantum) QuantumRange-
3150 image->colormap[i].red;
cristyed231572011-07-14 02:18:59 +00003151 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00003152 image->colormap[i].green=(Quantum) QuantumRange-
3153 image->colormap[i].green;
cristyed231572011-07-14 02:18:59 +00003154 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00003155 image->colormap[i].blue=(Quantum) QuantumRange-
3156 image->colormap[i].blue;
3157 }
3158 }
3159 /*
3160 Negate image.
3161 */
3162 status=MagickTrue;
3163 progress=0;
cristy3ed852e2009-09-05 21:47:34 +00003164 image_view=AcquireCacheView(image);
3165 if (grayscale != MagickFalse)
3166 {
cristyb5d5f722009-11-04 03:03:49 +00003167#if defined(MAGICKCORE_OPENMP_SUPPORT)
3168 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003169#endif
cristybb503372010-05-27 20:51:26 +00003170 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003171 {
3172 MagickBooleanType
3173 sync;
3174
cristy4c08aed2011-07-01 19:47:50 +00003175 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00003176 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003177
cristy8d4629b2010-08-30 17:59:46 +00003178 register ssize_t
3179 x;
3180
cristy3ed852e2009-09-05 21:47:34 +00003181 if (status == MagickFalse)
3182 continue;
3183 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,
3184 exception);
cristy4c08aed2011-07-01 19:47:50 +00003185 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00003186 {
3187 status=MagickFalse;
3188 continue;
3189 }
cristybb503372010-05-27 20:51:26 +00003190 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003191 {
cristy9aa95be2011-07-20 21:56:45 +00003192 register ssize_t
3193 i;
3194
cristyd476a8e2011-07-23 16:13:22 +00003195 if (IsPixelGray(image,q) != MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00003196 {
cristya30d9ba2011-07-23 21:00:48 +00003197 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00003198 continue;
3199 }
cristya30d9ba2011-07-23 21:00:48 +00003200 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
cristy9aa95be2011-07-20 21:56:45 +00003201 {
3202 PixelTrait
3203 traits;
3204
3205 traits=GetPixelChannelMapTraits(image,(PixelChannel) i);
3206 if ((traits & UpdatePixelTrait) != 0)
3207 q[i]=QuantumRange-q[i];
3208 }
cristya30d9ba2011-07-23 21:00:48 +00003209 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00003210 }
3211 sync=SyncCacheViewAuthenticPixels(image_view,exception);
3212 if (sync == MagickFalse)
3213 status=MagickFalse;
3214 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3215 {
3216 MagickBooleanType
3217 proceed;
3218
cristyb5d5f722009-11-04 03:03:49 +00003219#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy50fbc382011-07-07 02:19:17 +00003220 #pragma omp critical (MagickCore_NegateImage)
cristy3ed852e2009-09-05 21:47:34 +00003221#endif
3222 proceed=SetImageProgress(image,NegateImageTag,progress++,
3223 image->rows);
3224 if (proceed == MagickFalse)
3225 status=MagickFalse;
3226 }
3227 }
3228 image_view=DestroyCacheView(image_view);
3229 return(MagickTrue);
3230 }
3231 /*
3232 Negate image.
3233 */
cristyb5d5f722009-11-04 03:03:49 +00003234#if defined(MAGICKCORE_OPENMP_SUPPORT)
3235 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003236#endif
cristybb503372010-05-27 20:51:26 +00003237 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003238 {
cristy4c08aed2011-07-01 19:47:50 +00003239 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00003240 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003241
cristy8d4629b2010-08-30 17:59:46 +00003242 register ssize_t
3243 x;
3244
cristy3ed852e2009-09-05 21:47:34 +00003245 if (status == MagickFalse)
3246 continue;
3247 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00003248 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00003249 {
3250 status=MagickFalse;
3251 continue;
3252 }
cristybb503372010-05-27 20:51:26 +00003253 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003254 {
cristyf7dc44c2011-07-20 14:41:15 +00003255 register ssize_t
3256 i;
3257
cristya30d9ba2011-07-23 21:00:48 +00003258 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
cristyf7dc44c2011-07-20 14:41:15 +00003259 {
3260 PixelTrait
3261 traits;
3262
3263 traits=GetPixelChannelMapTraits(image,(PixelChannel) i);
3264 if ((traits & UpdatePixelTrait) != 0)
3265 q[i]=QuantumRange-q[i];
3266 }
cristya30d9ba2011-07-23 21:00:48 +00003267 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00003268 }
3269 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
3270 status=MagickFalse;
3271 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3272 {
3273 MagickBooleanType
3274 proceed;
3275
cristyb5d5f722009-11-04 03:03:49 +00003276#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy50fbc382011-07-07 02:19:17 +00003277 #pragma omp critical (MagickCore_NegateImage)
cristy3ed852e2009-09-05 21:47:34 +00003278#endif
3279 proceed=SetImageProgress(image,NegateImageTag,progress++,image->rows);
3280 if (proceed == MagickFalse)
3281 status=MagickFalse;
3282 }
3283 }
3284 image_view=DestroyCacheView(image_view);
3285 return(status);
3286}
3287
3288/*
3289%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3290% %
3291% %
3292% %
3293% N o r m a l i z e I m a g e %
3294% %
3295% %
3296% %
3297%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3298%
cristya4dfb122011-07-07 19:01:57 +00003299% NormalizeImage() enhances the contrast of a color image by mapping the
3300% darkest 2 percent of all pixel to black and the brightest 1 percent to white.
cristy3ed852e2009-09-05 21:47:34 +00003301%
3302% The format of the NormalizeImage method is:
3303%
3304% MagickBooleanType NormalizeImage(Image *image)
cristy3ed852e2009-09-05 21:47:34 +00003305%
3306% A description of each parameter follows:
3307%
3308% o image: the image.
3309%
cristy3ed852e2009-09-05 21:47:34 +00003310*/
cristy3ed852e2009-09-05 21:47:34 +00003311MagickExport MagickBooleanType NormalizeImage(Image *image)
3312{
cristy3ed852e2009-09-05 21:47:34 +00003313 double
3314 black_point,
3315 white_point;
3316
cristy530239c2010-07-25 17:34:26 +00003317 black_point=(double) image->columns*image->rows*0.0015;
3318 white_point=(double) image->columns*image->rows*0.9995;
cristy50fbc382011-07-07 02:19:17 +00003319 return(ContrastStretchImage(image,black_point,white_point));
cristy3ed852e2009-09-05 21:47:34 +00003320}
3321
3322/*
3323%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3324% %
3325% %
3326% %
3327% S i g m o i d a l C o n t r a s t I m a g e %
3328% %
3329% %
3330% %
3331%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3332%
3333% SigmoidalContrastImage() adjusts the contrast of an image with a non-linear
3334% sigmoidal contrast algorithm. Increase the contrast of the image using a
3335% sigmoidal transfer function without saturating highlights or shadows.
3336% Contrast indicates how much to increase the contrast (0 is none; 3 is
3337% typical; 20 is pushing it); mid-point indicates where midtones fall in the
3338% resultant image (0 is white; 50% is middle-gray; 100% is black). Set
3339% sharpen to MagickTrue to increase the image contrast otherwise the contrast
3340% is reduced.
3341%
3342% The format of the SigmoidalContrastImage method is:
3343%
3344% MagickBooleanType SigmoidalContrastImage(Image *image,
3345% const MagickBooleanType sharpen,const char *levels)
cristy3ed852e2009-09-05 21:47:34 +00003346%
3347% A description of each parameter follows:
3348%
3349% o image: the image.
3350%
cristy3ed852e2009-09-05 21:47:34 +00003351% o sharpen: Increase or decrease image contrast.
3352%
cristyfa769582010-09-30 23:30:03 +00003353% o alpha: strength of the contrast, the larger the number the more
3354% 'threshold-like' it becomes.
cristy3ed852e2009-09-05 21:47:34 +00003355%
cristyfa769582010-09-30 23:30:03 +00003356% o beta: midpoint of the function as a color value 0 to QuantumRange.
cristy3ed852e2009-09-05 21:47:34 +00003357%
3358*/
cristy3ed852e2009-09-05 21:47:34 +00003359MagickExport MagickBooleanType SigmoidalContrastImage(Image *image,
cristy9ee60942011-07-06 14:54:38 +00003360 const MagickBooleanType sharpen,const double contrast,const double midpoint)
cristy3ed852e2009-09-05 21:47:34 +00003361{
3362#define SigmoidalContrastImageTag "SigmoidalContrast/Image"
3363
cristyc4c8d132010-01-07 01:58:38 +00003364 CacheView
3365 *image_view;
3366
cristy3ed852e2009-09-05 21:47:34 +00003367 ExceptionInfo
3368 *exception;
3369
cristy3ed852e2009-09-05 21:47:34 +00003370 MagickBooleanType
3371 status;
3372
cristybb503372010-05-27 20:51:26 +00003373 MagickOffsetType
3374 progress;
3375
cristy3ed852e2009-09-05 21:47:34 +00003376 MagickRealType
3377 *sigmoidal_map;
3378
cristybb503372010-05-27 20:51:26 +00003379 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003380 i;
3381
cristybb503372010-05-27 20:51:26 +00003382 ssize_t
3383 y;
3384
cristy3ed852e2009-09-05 21:47:34 +00003385 /*
3386 Allocate and initialize sigmoidal maps.
3387 */
3388 assert(image != (Image *) NULL);
3389 assert(image->signature == MagickSignature);
3390 if (image->debug != MagickFalse)
3391 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3392 sigmoidal_map=(MagickRealType *) AcquireQuantumMemory(MaxMap+1UL,
3393 sizeof(*sigmoidal_map));
3394 if (sigmoidal_map == (MagickRealType *) NULL)
3395 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
3396 image->filename);
3397 (void) ResetMagickMemory(sigmoidal_map,0,(MaxMap+1)*sizeof(*sigmoidal_map));
cristyb5d5f722009-11-04 03:03:49 +00003398#if defined(MAGICKCORE_OPENMP_SUPPORT)
3399 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003400#endif
cristybb503372010-05-27 20:51:26 +00003401 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +00003402 {
3403 if (sharpen != MagickFalse)
3404 {
3405 sigmoidal_map[i]=(MagickRealType) ScaleMapToQuantum((MagickRealType)
3406 (MaxMap*((1.0/(1.0+exp(contrast*(midpoint/(double) QuantumRange-
3407 (double) i/MaxMap))))-(1.0/(1.0+exp(contrast*(midpoint/
3408 (double) QuantumRange)))))/((1.0/(1.0+exp(contrast*(midpoint/
3409 (double) QuantumRange-1.0))))-(1.0/(1.0+exp(contrast*(midpoint/
3410 (double) QuantumRange)))))+0.5));
3411 continue;
3412 }
3413 sigmoidal_map[i]=(MagickRealType) ScaleMapToQuantum((MagickRealType)
3414 (MaxMap*(QuantumScale*midpoint-log((1.0-(1.0/(1.0+exp(midpoint/
3415 (double) QuantumRange*contrast))+((double) i/MaxMap)*((1.0/
3416 (1.0+exp(contrast*(midpoint/(double) QuantumRange-1.0))))-(1.0/
3417 (1.0+exp(midpoint/(double) QuantumRange*contrast))))))/
3418 (1.0/(1.0+exp(midpoint/(double) QuantumRange*contrast))+
3419 ((double) i/MaxMap)*((1.0/(1.0+exp(contrast*(midpoint/
3420 (double) QuantumRange-1.0))))-(1.0/(1.0+exp(midpoint/
3421 (double) QuantumRange*contrast))))))/contrast)));
3422 }
3423 if (image->storage_class == PseudoClass)
3424 {
3425 /*
3426 Sigmoidal-contrast enhance colormap.
3427 */
cristyb5d5f722009-11-04 03:03:49 +00003428#if defined(MAGICKCORE_OPENMP_SUPPORT)
3429 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003430#endif
cristybb503372010-05-27 20:51:26 +00003431 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00003432 {
cristyed231572011-07-14 02:18:59 +00003433 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristyce70c172010-01-07 17:15:30 +00003434 image->colormap[i].red=ClampToQuantum(sigmoidal_map[
cristy3ed852e2009-09-05 21:47:34 +00003435 ScaleQuantumToMap(image->colormap[i].red)]);
cristyed231572011-07-14 02:18:59 +00003436 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristyce70c172010-01-07 17:15:30 +00003437 image->colormap[i].green=ClampToQuantum(sigmoidal_map[
cristy3ed852e2009-09-05 21:47:34 +00003438 ScaleQuantumToMap(image->colormap[i].green)]);
cristyed231572011-07-14 02:18:59 +00003439 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristyce70c172010-01-07 17:15:30 +00003440 image->colormap[i].blue=ClampToQuantum(sigmoidal_map[
cristy3ed852e2009-09-05 21:47:34 +00003441 ScaleQuantumToMap(image->colormap[i].blue)]);
cristyed231572011-07-14 02:18:59 +00003442 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003443 image->colormap[i].alpha=ClampToQuantum(sigmoidal_map[
3444 ScaleQuantumToMap(image->colormap[i].alpha)]);
cristy3ed852e2009-09-05 21:47:34 +00003445 }
3446 }
3447 /*
3448 Sigmoidal-contrast enhance image.
3449 */
3450 status=MagickTrue;
3451 progress=0;
3452 exception=(&image->exception);
3453 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00003454#if defined(MAGICKCORE_OPENMP_SUPPORT)
3455 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003456#endif
cristybb503372010-05-27 20:51:26 +00003457 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003458 {
cristy4c08aed2011-07-01 19:47:50 +00003459 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00003460 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003461
cristy8d4629b2010-08-30 17:59:46 +00003462 register ssize_t
3463 x;
3464
cristy3ed852e2009-09-05 21:47:34 +00003465 if (status == MagickFalse)
3466 continue;
3467 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00003468 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00003469 {
3470 status=MagickFalse;
3471 continue;
3472 }
cristybb503372010-05-27 20:51:26 +00003473 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003474 {
cristyed231572011-07-14 02:18:59 +00003475 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003476 SetPixelRed(image,ClampToQuantum(sigmoidal_map[ScaleQuantumToMap(
3477 GetPixelRed(image,q))]),q);
cristyed231572011-07-14 02:18:59 +00003478 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003479 SetPixelGreen(image,ClampToQuantum(sigmoidal_map[ScaleQuantumToMap(
3480 GetPixelGreen(image,q))]),q);
cristyed231572011-07-14 02:18:59 +00003481 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003482 SetPixelBlue(image,ClampToQuantum(sigmoidal_map[ScaleQuantumToMap(
3483 GetPixelBlue(image,q))]),q);
cristyed231572011-07-14 02:18:59 +00003484 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00003485 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +00003486 SetPixelBlack(image,ClampToQuantum(sigmoidal_map[ScaleQuantumToMap(
3487 GetPixelBlack(image,q))]),q);
cristyed231572011-07-14 02:18:59 +00003488 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003489 SetPixelAlpha(image,ClampToQuantum(sigmoidal_map[ScaleQuantumToMap(
3490 GetPixelAlpha(image,q))]),q);
cristyed231572011-07-14 02:18:59 +00003491 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00003492 }
3493 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
3494 status=MagickFalse;
3495 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3496 {
3497 MagickBooleanType
3498 proceed;
3499
cristyb5d5f722009-11-04 03:03:49 +00003500#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy9ee60942011-07-06 14:54:38 +00003501 #pragma omp critical (MagickCore_SigmoidalContrastImage)
cristy3ed852e2009-09-05 21:47:34 +00003502#endif
3503 proceed=SetImageProgress(image,SigmoidalContrastImageTag,progress++,
3504 image->rows);
3505 if (proceed == MagickFalse)
3506 status=MagickFalse;
3507 }
3508 }
3509 image_view=DestroyCacheView(image_view);
3510 sigmoidal_map=(MagickRealType *) RelinquishMagickMemory(sigmoidal_map);
3511 return(status);
3512}