blob: 9a00a21b1adc6ec4dd34ad5efbda93396b02394f [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"
cristyd1dd6e42011-09-04 01:46:08 +000056#include "MagickCore/gem-private.h"
cristy4c08aed2011-07-01 19:47:50 +000057#include "MagickCore/geometry.h"
58#include "MagickCore/histogram.h"
59#include "MagickCore/image.h"
60#include "MagickCore/image-private.h"
61#include "MagickCore/memory_.h"
62#include "MagickCore/monitor.h"
63#include "MagickCore/monitor-private.h"
64#include "MagickCore/option.h"
65#include "MagickCore/pixel-accessor.h"
66#include "MagickCore/quantum.h"
67#include "MagickCore/quantum-private.h"
68#include "MagickCore/resample.h"
69#include "MagickCore/resample-private.h"
70#include "MagickCore/statistic.h"
71#include "MagickCore/string_.h"
72#include "MagickCore/string-private.h"
73#include "MagickCore/thread-private.h"
74#include "MagickCore/token.h"
75#include "MagickCore/xml-tree.h"
cristy433d1182011-09-04 13:38:52 +000076#include "MagickCore/xml-tree-private.h"
cristy3ed852e2009-09-05 21:47:34 +000077
78/*
79%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
80% %
81% %
82% %
83% A u t o G a m m a I m a g e %
84% %
85% %
86% %
87%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
88%
89% AutoGammaImage() extract the 'mean' from the image and adjust the image
90% to try make set its gamma appropriatally.
91%
cristy308b4e62009-09-21 14:40:44 +000092% The format of the AutoGammaImage method is:
cristy3ed852e2009-09-05 21:47:34 +000093%
cristy95111202011-08-09 19:41:42 +000094% MagickBooleanType AutoGammaImage(Image *image,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +000095%
96% A description of each parameter follows:
97%
98% o image: The image to auto-level
99%
cristy95111202011-08-09 19:41:42 +0000100% o exception: return any errors or warnings in this structure.
101%
cristy3ed852e2009-09-05 21:47:34 +0000102*/
cristy95111202011-08-09 19:41:42 +0000103MagickExport MagickBooleanType AutoGammaImage(Image *image,
104 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000105{
cristy3ed852e2009-09-05 21:47:34 +0000106 double
cristy4c08aed2011-07-01 19:47:50 +0000107 gamma,
108 log_mean,
109 mean,
110 sans;
anthony4efe5972009-09-11 06:46:40 +0000111
cristy95111202011-08-09 19:41:42 +0000112 MagickStatusType
113 status;
114
cristy01e9afd2011-08-10 17:38:41 +0000115 register ssize_t
116 i;
117
cristy4c08aed2011-07-01 19:47:50 +0000118 log_mean=log(0.5);
cristy5f95f4f2011-10-23 01:01:01 +0000119 if (image->channel_mask == DefaultChannels)
cristy3ed852e2009-09-05 21:47:34 +0000120 {
121 /*
cristy01e9afd2011-08-10 17:38:41 +0000122 Apply gamma correction equally across all given channels.
cristy3ed852e2009-09-05 21:47:34 +0000123 */
cristy95111202011-08-09 19:41:42 +0000124 (void) GetImageMean(image,&mean,&sans,exception);
cristy4c08aed2011-07-01 19:47:50 +0000125 gamma=log(mean*QuantumScale)/log_mean;
cristy01e9afd2011-08-10 17:38:41 +0000126 return(LevelImage(image,0.0,(double) QuantumRange,gamma,exception));
cristy3ed852e2009-09-05 21:47:34 +0000127 }
cristy3ed852e2009-09-05 21:47:34 +0000128 /*
cristy4c08aed2011-07-01 19:47:50 +0000129 Auto-gamma each channel separately.
cristy3ed852e2009-09-05 21:47:34 +0000130 */
cristy4c08aed2011-07-01 19:47:50 +0000131 status=MagickTrue;
cristy01e9afd2011-08-10 17:38:41 +0000132 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
133 {
cristybd5a96c2011-08-21 00:04:26 +0000134 ChannelType
135 channel_mask;
136
cristy01e9afd2011-08-10 17:38:41 +0000137 PixelTrait
138 traits;
139
cristye2a912b2011-12-05 20:02:07 +0000140 traits=GetPixelChannelMapTraits(image,i);
cristy01e9afd2011-08-10 17:38:41 +0000141 if ((traits & UpdatePixelTrait) == 0)
142 continue;
cristya13d0822011-09-19 00:19:19 +0000143 channel_mask=SetPixelChannelMask(image,(ChannelType) (1 << i));
cristy01e9afd2011-08-10 17:38:41 +0000144 status=GetImageMean(image,&mean,&sans,exception);
145 gamma=log(mean*QuantumScale)/log_mean;
146 status&=LevelImage(image,0.0,(double) QuantumRange,gamma,exception);
cristybd5a96c2011-08-21 00:04:26 +0000147 (void) SetPixelChannelMask(image,channel_mask);
cristy01e9afd2011-08-10 17:38:41 +0000148 if (status == MagickFalse)
149 break;
150 }
cristy3ed852e2009-09-05 21:47:34 +0000151 return(status != 0 ? MagickTrue : MagickFalse);
152}
153
154/*
155%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
156% %
157% %
158% %
159% A u t o L e v e l I m a g e %
160% %
161% %
162% %
163%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
164%
165% AutoLevelImage() adjusts the levels of a particular image channel by
166% scaling the minimum and maximum values to the full quantum range.
167%
168% The format of the LevelImage method is:
169%
cristy95111202011-08-09 19:41:42 +0000170% MagickBooleanType AutoLevelImage(Image *image,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000171%
172% A description of each parameter follows:
173%
174% o image: The image to auto-level
175%
cristy95111202011-08-09 19:41:42 +0000176% o exception: return any errors or warnings in this structure.
177%
cristy3ed852e2009-09-05 21:47:34 +0000178*/
cristy95111202011-08-09 19:41:42 +0000179MagickExport MagickBooleanType AutoLevelImage(Image *image,
180 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000181{
cristyb303c3d2011-09-09 11:24:40 +0000182 return(MinMaxStretchImage(image,0.0,0.0,1.0,exception));
cristy3ed852e2009-09-05 21:47:34 +0000183}
184
185/*
186%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
187% %
188% %
189% %
cristya28d6b82010-01-11 20:03:47 +0000190% B r i g h t n e s s C o n t r a s t I m a g e %
191% %
192% %
193% %
194%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
195%
cristyf4356f92011-08-01 15:33:48 +0000196% BrightnessContrastImage() changes the brightness and/or contrast of an
197% image. It converts the brightness and contrast parameters into slope and
198% intercept and calls a polynomical function to apply to the image.
cristya28d6b82010-01-11 20:03:47 +0000199%
200% The format of the BrightnessContrastImage method is:
201%
202% MagickBooleanType BrightnessContrastImage(Image *image,
cristy444eda62011-08-10 02:07:46 +0000203% const double brightness,const double contrast,ExceptionInfo *exception)
cristya28d6b82010-01-11 20:03:47 +0000204%
205% A description of each parameter follows:
206%
207% o image: the image.
208%
cristya28d6b82010-01-11 20:03:47 +0000209% o brightness: the brightness percent (-100 .. 100).
210%
211% o contrast: the contrast percent (-100 .. 100).
212%
cristy444eda62011-08-10 02:07:46 +0000213% o exception: return any errors or warnings in this structure.
214%
cristya28d6b82010-01-11 20:03:47 +0000215*/
cristya28d6b82010-01-11 20:03:47 +0000216MagickExport MagickBooleanType BrightnessContrastImage(Image *image,
cristy444eda62011-08-10 02:07:46 +0000217 const double brightness,const double contrast,ExceptionInfo *exception)
cristya28d6b82010-01-11 20:03:47 +0000218{
cristya28d6b82010-01-11 20:03:47 +0000219#define BrightnessContastImageTag "BrightnessContast/Image"
220
221 double
222 alpha,
cristya28d6b82010-01-11 20:03:47 +0000223 coefficients[2],
cristye23ec9d2011-08-16 18:15:40 +0000224 intercept,
cristya28d6b82010-01-11 20:03:47 +0000225 slope;
226
227 MagickBooleanType
228 status;
229
230 /*
231 Compute slope and intercept.
232 */
233 assert(image != (Image *) NULL);
234 assert(image->signature == MagickSignature);
235 if (image->debug != MagickFalse)
236 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
237 alpha=contrast;
cristy4205a3c2010-09-12 20:19:59 +0000238 slope=tan((double) (MagickPI*(alpha/100.0+1.0)/4.0));
cristya28d6b82010-01-11 20:03:47 +0000239 if (slope < 0.0)
240 slope=0.0;
241 intercept=brightness/100.0+((100-brightness)/200.0)*(1.0-slope);
242 coefficients[0]=slope;
243 coefficients[1]=intercept;
cristy444eda62011-08-10 02:07:46 +0000244 status=FunctionImage(image,PolynomialFunction,2,coefficients,exception);
245 return(status);
246}
247
248/*
249%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
250% %
251% %
252% %
253% C l u t I m a g e %
254% %
255% %
256% %
257%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
258%
259% ClutImage() replaces each color value in the given image, by using it as an
260% index to lookup a replacement color value in a Color Look UP Table in the
261% form of an image. The values are extracted along a diagonal of the CLUT
262% image so either a horizontal or vertial gradient image can be used.
263%
264% Typically this is used to either re-color a gray-scale image according to a
265% color gradient in the CLUT image, or to perform a freeform histogram
266% (level) adjustment according to the (typically gray-scale) gradient in the
267% CLUT image.
268%
269% When the 'channel' mask includes the matte/alpha transparency channel but
270% one image has no such channel it is assumed that that image is a simple
271% gray-scale image that will effect the alpha channel values, either for
272% gray-scale coloring (with transparent or semi-transparent colors), or
273% a histogram adjustment of existing alpha channel values. If both images
274% have matte channels, direct and normal indexing is applied, which is rarely
275% used.
276%
277% The format of the ClutImage method is:
278%
279% MagickBooleanType ClutImage(Image *image,Image *clut_image,
cristy5c4e2582011-09-11 19:21:03 +0000280% const PixelInterpolateMethod method,ExceptionInfo *exception)
cristy444eda62011-08-10 02:07:46 +0000281%
282% A description of each parameter follows:
283%
284% o image: the image, which is replaced by indexed CLUT values
285%
286% o clut_image: the color lookup table image for replacement color values.
287%
cristy5c4e2582011-09-11 19:21:03 +0000288% o method: the pixel interpolation method.
cristy444eda62011-08-10 02:07:46 +0000289%
290% o exception: return any errors or warnings in this structure.
291%
292*/
293MagickExport MagickBooleanType ClutImage(Image *image,const Image *clut_image,
cristy5c4e2582011-09-11 19:21:03 +0000294 const PixelInterpolateMethod method,ExceptionInfo *exception)
cristy444eda62011-08-10 02:07:46 +0000295{
cristy444eda62011-08-10 02:07:46 +0000296#define ClutImageTag "Clut/Image"
297
298 CacheView
299 *clut_view,
300 *image_view;
301
302 double
303 *clut_map;
304
305 MagickBooleanType
306 status;
307
308 MagickOffsetType
309 progress;
310
311 register ssize_t
312 x;
313
314 ssize_t
315 adjust,
316 y;
317
318 assert(image != (Image *) NULL);
319 assert(image->signature == MagickSignature);
320 if (image->debug != MagickFalse)
321 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
322 assert(clut_image != (Image *) NULL);
323 assert(clut_image->signature == MagickSignature);
324 if (SetImageStorageClass(image,DirectClass,exception) == MagickFalse)
325 return(MagickFalse);
326 clut_map=(double *) AcquireQuantumMemory(MaxMap+1UL,GetPixelChannels(image)*
327 sizeof(*clut_map));
328 if (clut_map == (double *) NULL)
329 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
330 image->filename);
331 /*
332 Clut image.
333 */
334 status=MagickTrue;
335 progress=0;
336 adjust=(ssize_t) (clut_image->interpolate == IntegerInterpolatePixel ? 0 : 1);
337 clut_view=AcquireCacheView(clut_image);
338#if defined(MAGICKCORE_OPENMP_SUPPORT)
339 #pragma omp parallel for schedule(dynamic,4)
340#endif
341 for (x=0; x <= (ssize_t) MaxMap; x++)
342 {
343 register ssize_t
344 i;
345
346 for (i=0; i < (ssize_t) GetPixelChannels(clut_image); i++)
347 (void) InterpolatePixelChannel(clut_image,clut_view,(PixelChannel) i,
cristy5c4e2582011-09-11 19:21:03 +0000348 method,QuantumScale*x*(clut_image->columns-adjust),QuantumScale*x*
349 (clut_image->rows-adjust),clut_map+x*GetPixelChannels(clut_image)+i,
350 exception);
cristy444eda62011-08-10 02:07:46 +0000351 }
352 clut_view=DestroyCacheView(clut_view);
353 image_view=AcquireCacheView(image);
354#if defined(MAGICKCORE_OPENMP_SUPPORT)
355 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
356#endif
357 for (y=0; y < (ssize_t) image->rows; y++)
358 {
359 register Quantum
360 *restrict q;
361
362 register ssize_t
363 x;
364
365 if (status == MagickFalse)
366 continue;
367 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
368 if (q == (Quantum *) NULL)
369 {
370 status=MagickFalse;
371 continue;
372 }
373 for (x=0; x < (ssize_t) image->columns; x++)
374 {
375 register ssize_t
376 i;
377
378 for (i=0; i < (ssize_t) GetPixelChannels(clut_image); i++)
379 {
380 PixelChannel
381 channel;
382
cristy1aaa3cd2011-08-21 23:48:17 +0000383 PixelTrait
cristy444eda62011-08-10 02:07:46 +0000384 clut_traits,
385 traits;
386
cristye2a912b2011-12-05 20:02:07 +0000387 clut_traits=GetPixelChannelMapTraits(clut_image,i);
388 channel=GetPixelChannelMapChannel(clut_image,i);
cristy444eda62011-08-10 02:07:46 +0000389 traits=GetPixelChannelMapTraits(clut_image,channel);
cristy010d7d12011-08-31 01:02:48 +0000390 if ((traits == UndefinedPixelTrait) ||
391 (clut_traits == UndefinedPixelTrait) ||
392 ((traits & UpdatePixelTrait) == 0))
393 continue;
cristy0beccfa2011-09-25 20:47:53 +0000394 SetPixelChannel(clut_image,channel,ClampToQuantum(clut_map[
395 ScaleQuantumToMap(GetPixelChannel(clut_image,channel,q))*
396 GetPixelChannels(clut_image)+channel]),q);
cristy444eda62011-08-10 02:07:46 +0000397 }
398 q+=GetPixelChannels(image);
399 }
400 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
401 status=MagickFalse;
402 if (image->progress_monitor != (MagickProgressMonitor) NULL)
403 {
404 MagickBooleanType
405 proceed;
406
407#if defined(MAGICKCORE_OPENMP_SUPPORT)
408 #pragma omp critical (MagickCore_ClutImage)
409#endif
410 proceed=SetImageProgress(image,ClutImageTag,progress++,image->rows);
411 if (proceed == MagickFalse)
412 status=MagickFalse;
413 }
414 }
415 image_view=DestroyCacheView(image_view);
416 clut_map=(double *) RelinquishMagickMemory(clut_map);
417 if ((clut_image->matte != MagickFalse) &&
418 ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0))
419 (void) SetImageAlphaChannel(image,ActivateAlphaChannel,exception);
cristya28d6b82010-01-11 20:03:47 +0000420 return(status);
421}
422
423/*
424%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
425% %
426% %
427% %
cristy3ed852e2009-09-05 21:47:34 +0000428% C o l o r D e c i s i o n L i s t I m a g e %
429% %
430% %
431% %
432%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
433%
434% ColorDecisionListImage() accepts a lightweight Color Correction Collection
435% (CCC) file which solely contains one or more color corrections and applies
436% the correction to the image. Here is a sample CCC file:
437%
438% <ColorCorrectionCollection xmlns="urn:ASC:CDL:v1.2">
439% <ColorCorrection id="cc03345">
440% <SOPNode>
441% <Slope> 0.9 1.2 0.5 </Slope>
442% <Offset> 0.4 -0.5 0.6 </Offset>
443% <Power> 1.0 0.8 1.5 </Power>
444% </SOPNode>
445% <SATNode>
446% <Saturation> 0.85 </Saturation>
447% </SATNode>
448% </ColorCorrection>
449% </ColorCorrectionCollection>
450%
451% which includes the slop, offset, and power for each of the RGB channels
452% as well as the saturation.
453%
454% The format of the ColorDecisionListImage method is:
455%
456% MagickBooleanType ColorDecisionListImage(Image *image,
cristy1bfa9f02011-08-11 02:35:43 +0000457% const char *color_correction_collection,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000458%
459% A description of each parameter follows:
460%
461% o image: the image.
462%
463% o color_correction_collection: the color correction collection in XML.
464%
cristy1bfa9f02011-08-11 02:35:43 +0000465% o exception: return any errors or warnings in this structure.
466%
cristy3ed852e2009-09-05 21:47:34 +0000467*/
468MagickExport MagickBooleanType ColorDecisionListImage(Image *image,
cristy1bfa9f02011-08-11 02:35:43 +0000469 const char *color_correction_collection,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000470{
471#define ColorDecisionListCorrectImageTag "ColorDecisionList/Image"
472
473 typedef struct _Correction
474 {
475 double
476 slope,
477 offset,
478 power;
479 } Correction;
480
481 typedef struct _ColorCorrection
482 {
483 Correction
484 red,
485 green,
486 blue;
487
488 double
489 saturation;
490 } ColorCorrection;
491
cristyc4c8d132010-01-07 01:58:38 +0000492 CacheView
493 *image_view;
494
cristy3ed852e2009-09-05 21:47:34 +0000495 char
496 token[MaxTextExtent];
497
498 ColorCorrection
499 color_correction;
500
501 const char
502 *content,
503 *p;
504
cristy3ed852e2009-09-05 21:47:34 +0000505 MagickBooleanType
506 status;
507
cristybb503372010-05-27 20:51:26 +0000508 MagickOffsetType
509 progress;
510
cristy101ab702011-10-13 13:06:32 +0000511 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +0000512 *cdl_map;
513
cristybb503372010-05-27 20:51:26 +0000514 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000515 i;
516
cristybb503372010-05-27 20:51:26 +0000517 ssize_t
518 y;
519
cristy3ed852e2009-09-05 21:47:34 +0000520 XMLTreeInfo
521 *cc,
522 *ccc,
523 *sat,
524 *sop;
525
cristy3ed852e2009-09-05 21:47:34 +0000526 /*
527 Allocate and initialize cdl maps.
528 */
529 assert(image != (Image *) NULL);
530 assert(image->signature == MagickSignature);
531 if (image->debug != MagickFalse)
532 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
533 if (color_correction_collection == (const char *) NULL)
534 return(MagickFalse);
cristy1bfa9f02011-08-11 02:35:43 +0000535 ccc=NewXMLTree((const char *) color_correction_collection,exception);
cristy3ed852e2009-09-05 21:47:34 +0000536 if (ccc == (XMLTreeInfo *) NULL)
537 return(MagickFalse);
538 cc=GetXMLTreeChild(ccc,"ColorCorrection");
539 if (cc == (XMLTreeInfo *) NULL)
540 {
541 ccc=DestroyXMLTree(ccc);
542 return(MagickFalse);
543 }
544 color_correction.red.slope=1.0;
545 color_correction.red.offset=0.0;
546 color_correction.red.power=1.0;
547 color_correction.green.slope=1.0;
548 color_correction.green.offset=0.0;
549 color_correction.green.power=1.0;
550 color_correction.blue.slope=1.0;
551 color_correction.blue.offset=0.0;
552 color_correction.blue.power=1.0;
553 color_correction.saturation=0.0;
554 sop=GetXMLTreeChild(cc,"SOPNode");
555 if (sop != (XMLTreeInfo *) NULL)
556 {
557 XMLTreeInfo
558 *offset,
559 *power,
560 *slope;
561
562 slope=GetXMLTreeChild(sop,"Slope");
563 if (slope != (XMLTreeInfo *) NULL)
564 {
565 content=GetXMLTreeContent(slope);
566 p=(const char *) content;
567 for (i=0; (*p != '\0') && (i < 3); i++)
568 {
569 GetMagickToken(p,&p,token);
570 if (*token == ',')
571 GetMagickToken(p,&p,token);
572 switch (i)
573 {
cristyc1acd842011-05-19 23:05:47 +0000574 case 0:
575 {
cristy9b34e302011-11-05 02:15:45 +0000576 color_correction.red.slope=StringToDouble(token,(char **) NULL);
cristyc1acd842011-05-19 23:05:47 +0000577 break;
578 }
579 case 1:
580 {
cristydbdd0e32011-11-04 23:29:40 +0000581 color_correction.green.slope=StringToDouble(token,
cristyc1acd842011-05-19 23:05:47 +0000582 (char **) NULL);
583 break;
584 }
585 case 2:
586 {
cristydbdd0e32011-11-04 23:29:40 +0000587 color_correction.blue.slope=StringToDouble(token,
cristyc1acd842011-05-19 23:05:47 +0000588 (char **) NULL);
589 break;
590 }
cristy3ed852e2009-09-05 21:47:34 +0000591 }
592 }
593 }
594 offset=GetXMLTreeChild(sop,"Offset");
595 if (offset != (XMLTreeInfo *) NULL)
596 {
597 content=GetXMLTreeContent(offset);
598 p=(const char *) content;
599 for (i=0; (*p != '\0') && (i < 3); i++)
600 {
601 GetMagickToken(p,&p,token);
602 if (*token == ',')
603 GetMagickToken(p,&p,token);
604 switch (i)
605 {
cristyc1acd842011-05-19 23:05:47 +0000606 case 0:
607 {
cristydbdd0e32011-11-04 23:29:40 +0000608 color_correction.red.offset=StringToDouble(token,
cristyc1acd842011-05-19 23:05:47 +0000609 (char **) NULL);
610 break;
611 }
612 case 1:
613 {
cristydbdd0e32011-11-04 23:29:40 +0000614 color_correction.green.offset=StringToDouble(token,
cristyc1acd842011-05-19 23:05:47 +0000615 (char **) NULL);
616 break;
617 }
618 case 2:
619 {
cristydbdd0e32011-11-04 23:29:40 +0000620 color_correction.blue.offset=StringToDouble(token,
cristyc1acd842011-05-19 23:05:47 +0000621 (char **) NULL);
622 break;
623 }
cristy3ed852e2009-09-05 21:47:34 +0000624 }
625 }
626 }
627 power=GetXMLTreeChild(sop,"Power");
628 if (power != (XMLTreeInfo *) NULL)
629 {
630 content=GetXMLTreeContent(power);
631 p=(const char *) content;
632 for (i=0; (*p != '\0') && (i < 3); i++)
633 {
634 GetMagickToken(p,&p,token);
635 if (*token == ',')
636 GetMagickToken(p,&p,token);
637 switch (i)
638 {
cristyc1acd842011-05-19 23:05:47 +0000639 case 0:
640 {
cristy9b34e302011-11-05 02:15:45 +0000641 color_correction.red.power=StringToDouble(token,(char **) NULL);
cristyc1acd842011-05-19 23:05:47 +0000642 break;
643 }
644 case 1:
645 {
cristydbdd0e32011-11-04 23:29:40 +0000646 color_correction.green.power=StringToDouble(token,
cristyc1acd842011-05-19 23:05:47 +0000647 (char **) NULL);
648 break;
649 }
650 case 2:
651 {
cristydbdd0e32011-11-04 23:29:40 +0000652 color_correction.blue.power=StringToDouble(token,
cristyc1acd842011-05-19 23:05:47 +0000653 (char **) NULL);
654 break;
655 }
cristy3ed852e2009-09-05 21:47:34 +0000656 }
657 }
658 }
659 }
660 sat=GetXMLTreeChild(cc,"SATNode");
661 if (sat != (XMLTreeInfo *) NULL)
662 {
663 XMLTreeInfo
664 *saturation;
665
666 saturation=GetXMLTreeChild(sat,"Saturation");
667 if (saturation != (XMLTreeInfo *) NULL)
668 {
669 content=GetXMLTreeContent(saturation);
670 p=(const char *) content;
671 GetMagickToken(p,&p,token);
cristydbdd0e32011-11-04 23:29:40 +0000672 color_correction.saturation=StringToDouble(token,
cristyc1acd842011-05-19 23:05:47 +0000673 (char **) NULL);
cristy3ed852e2009-09-05 21:47:34 +0000674 }
675 }
676 ccc=DestroyXMLTree(ccc);
677 if (image->debug != MagickFalse)
678 {
679 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
680 " Color Correction Collection:");
681 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000682 " color_correction.red.slope: %g",color_correction.red.slope);
cristy3ed852e2009-09-05 21:47:34 +0000683 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000684 " color_correction.red.offset: %g",color_correction.red.offset);
cristy3ed852e2009-09-05 21:47:34 +0000685 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000686 " color_correction.red.power: %g",color_correction.red.power);
cristy3ed852e2009-09-05 21:47:34 +0000687 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000688 " color_correction.green.slope: %g",color_correction.green.slope);
cristy3ed852e2009-09-05 21:47:34 +0000689 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000690 " color_correction.green.offset: %g",color_correction.green.offset);
cristy3ed852e2009-09-05 21:47:34 +0000691 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000692 " color_correction.green.power: %g",color_correction.green.power);
cristy3ed852e2009-09-05 21:47:34 +0000693 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000694 " color_correction.blue.slope: %g",color_correction.blue.slope);
cristy3ed852e2009-09-05 21:47:34 +0000695 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000696 " color_correction.blue.offset: %g",color_correction.blue.offset);
cristy3ed852e2009-09-05 21:47:34 +0000697 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000698 " color_correction.blue.power: %g",color_correction.blue.power);
cristy3ed852e2009-09-05 21:47:34 +0000699 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000700 " color_correction.saturation: %g",color_correction.saturation);
cristy3ed852e2009-09-05 21:47:34 +0000701 }
cristy101ab702011-10-13 13:06:32 +0000702 cdl_map=(PixelInfo *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*cdl_map));
703 if (cdl_map == (PixelInfo *) NULL)
cristy3ed852e2009-09-05 21:47:34 +0000704 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
705 image->filename);
cristyb5d5f722009-11-04 03:03:49 +0000706#if defined(MAGICKCORE_OPENMP_SUPPORT)
707 #pragma omp parallel for schedule(dynamic,4)
cristy3ed852e2009-09-05 21:47:34 +0000708#endif
cristybb503372010-05-27 20:51:26 +0000709 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +0000710 {
cristyda1f9c12011-10-02 21:39:49 +0000711 cdl_map[i].red=(MagickRealType) ScaleMapToQuantum((MagickRealType)
712 (MaxMap*(pow(color_correction.red.slope*i/MaxMap+
713 color_correction.red.offset,color_correction.red.power))));
714 cdl_map[i].green=(MagickRealType) ScaleMapToQuantum((MagickRealType)
715 (MaxMap*(pow(color_correction.green.slope*i/MaxMap+
716 color_correction.green.offset,color_correction.green.power))));
717 cdl_map[i].blue=(MagickRealType) ScaleMapToQuantum((MagickRealType)
718 (MaxMap*(pow(color_correction.blue.slope*i/MaxMap+
719 color_correction.blue.offset,color_correction.blue.power))));
cristy3ed852e2009-09-05 21:47:34 +0000720 }
721 if (image->storage_class == PseudoClass)
722 {
723 /*
724 Apply transfer function to colormap.
725 */
cristyb5d5f722009-11-04 03:03:49 +0000726#if defined(MAGICKCORE_OPENMP_SUPPORT)
727 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000728#endif
cristybb503372010-05-27 20:51:26 +0000729 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +0000730 {
731 double
732 luma;
733
734 luma=0.2126*image->colormap[i].red+0.7152*image->colormap[i].green+
735 0.0722*image->colormap[i].blue;
cristyda1f9c12011-10-02 21:39:49 +0000736 image->colormap[i].red=luma+color_correction.saturation*
737 cdl_map[ScaleQuantumToMap(ClampToQuantum(
738 image->colormap[i].red))].red-luma;
739 image->colormap[i].green=luma+color_correction.saturation*
740 cdl_map[ScaleQuantumToMap(ClampToQuantum(
741 image->colormap[i].green))].green-luma;
742 image->colormap[i].blue=luma+color_correction.saturation*
743 cdl_map[ScaleQuantumToMap(ClampToQuantum(
744 image->colormap[i].blue))].blue-luma;
cristy3ed852e2009-09-05 21:47:34 +0000745 }
746 }
747 /*
748 Apply transfer function to image.
749 */
750 status=MagickTrue;
751 progress=0;
cristy3ed852e2009-09-05 21:47:34 +0000752 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +0000753#if defined(MAGICKCORE_OPENMP_SUPPORT)
754 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000755#endif
cristybb503372010-05-27 20:51:26 +0000756 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000757 {
758 double
759 luma;
760
cristy4c08aed2011-07-01 19:47:50 +0000761 register Quantum
cristyc47d1f82009-11-26 01:44:43 +0000762 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000763
cristy8d4629b2010-08-30 17:59:46 +0000764 register ssize_t
765 x;
766
cristy3ed852e2009-09-05 21:47:34 +0000767 if (status == MagickFalse)
768 continue;
769 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristyacd2ed22011-08-30 01:44:23 +0000770 if (q == (Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +0000771 {
772 status=MagickFalse;
773 continue;
774 }
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 luma=0.2126*GetPixelRed(image,q)+0.7152*GetPixelGreen(image,q)+0.0722*
778 GetPixelBlue(image,q);
779 SetPixelRed(image,ClampToQuantum(luma+color_correction.saturation*
780 (cdl_map[ScaleQuantumToMap(GetPixelRed(image,q))].red-luma)),q);
781 SetPixelGreen(image,ClampToQuantum(luma+color_correction.saturation*
782 (cdl_map[ScaleQuantumToMap(GetPixelGreen(image,q))].green-luma)),q);
783 SetPixelBlue(image,ClampToQuantum(luma+color_correction.saturation*
784 (cdl_map[ScaleQuantumToMap(GetPixelBlue(image,q))].blue-luma)),q);
cristyed231572011-07-14 02:18:59 +0000785 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +0000786 }
787 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
788 status=MagickFalse;
789 if (image->progress_monitor != (MagickProgressMonitor) NULL)
790 {
791 MagickBooleanType
792 proceed;
793
cristyb5d5f722009-11-04 03:03:49 +0000794#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +0000795 #pragma omp critical (MagickCore_ColorDecisionListImageChannel)
796#endif
797 proceed=SetImageProgress(image,ColorDecisionListCorrectImageTag,
798 progress++,image->rows);
799 if (proceed == MagickFalse)
800 status=MagickFalse;
801 }
802 }
803 image_view=DestroyCacheView(image_view);
cristy101ab702011-10-13 13:06:32 +0000804 cdl_map=(PixelInfo *) RelinquishMagickMemory(cdl_map);
cristy3ed852e2009-09-05 21:47:34 +0000805 return(status);
806}
807
808/*
809%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
810% %
811% %
812% %
cristy3ed852e2009-09-05 21:47:34 +0000813% C o n t r a s t I m a g e %
814% %
815% %
816% %
817%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
818%
819% ContrastImage() enhances the intensity differences between the lighter and
820% darker elements of the image. Set sharpen to a MagickTrue to increase the
821% image contrast otherwise the contrast is reduced.
822%
823% The format of the ContrastImage method is:
824%
825% MagickBooleanType ContrastImage(Image *image,
cristye23ec9d2011-08-16 18:15:40 +0000826% const MagickBooleanType sharpen,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000827%
828% A description of each parameter follows:
829%
830% o image: the image.
831%
832% o sharpen: Increase or decrease image contrast.
833%
cristye23ec9d2011-08-16 18:15:40 +0000834% o exception: return any errors or warnings in this structure.
835%
cristy3ed852e2009-09-05 21:47:34 +0000836*/
837
cristy3094b7f2011-10-01 23:18:02 +0000838static void Contrast(const int sign,double *red,double *green,double *blue)
cristy3ed852e2009-09-05 21:47:34 +0000839{
840 double
841 brightness,
842 hue,
843 saturation;
844
845 /*
846 Enhance contrast: dark color become darker, light color become lighter.
847 */
cristy3094b7f2011-10-01 23:18:02 +0000848 assert(red != (double *) NULL);
849 assert(green != (double *) NULL);
850 assert(blue != (double *) NULL);
cristy3ed852e2009-09-05 21:47:34 +0000851 hue=0.0;
852 saturation=0.0;
853 brightness=0.0;
854 ConvertRGBToHSB(*red,*green,*blue,&hue,&saturation,&brightness);
cristy31ac0f02011-03-12 02:04:47 +0000855 brightness+=0.5*sign*(0.5*(sin((double) (MagickPI*(brightness-0.5)))+1.0)-
cristy4205a3c2010-09-12 20:19:59 +0000856 brightness);
cristy3ed852e2009-09-05 21:47:34 +0000857 if (brightness > 1.0)
858 brightness=1.0;
859 else
860 if (brightness < 0.0)
861 brightness=0.0;
862 ConvertHSBToRGB(hue,saturation,brightness,red,green,blue);
863}
864
865MagickExport MagickBooleanType ContrastImage(Image *image,
cristye23ec9d2011-08-16 18:15:40 +0000866 const MagickBooleanType sharpen,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000867{
868#define ContrastImageTag "Contrast/Image"
869
cristyc4c8d132010-01-07 01:58:38 +0000870 CacheView
871 *image_view;
872
cristy3ed852e2009-09-05 21:47:34 +0000873 int
874 sign;
875
cristy3ed852e2009-09-05 21:47:34 +0000876 MagickBooleanType
877 status;
878
cristybb503372010-05-27 20:51:26 +0000879 MagickOffsetType
880 progress;
881
882 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000883 i;
884
cristybb503372010-05-27 20:51:26 +0000885 ssize_t
886 y;
887
cristy3ed852e2009-09-05 21:47:34 +0000888 assert(image != (Image *) NULL);
889 assert(image->signature == MagickSignature);
890 if (image->debug != MagickFalse)
891 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
892 sign=sharpen != MagickFalse ? 1 : -1;
893 if (image->storage_class == PseudoClass)
894 {
895 /*
896 Contrast enhance colormap.
897 */
cristybb503372010-05-27 20:51:26 +0000898 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +0000899 Contrast(sign,&image->colormap[i].red,&image->colormap[i].green,
900 &image->colormap[i].blue);
901 }
902 /*
903 Contrast enhance image.
904 */
905 status=MagickTrue;
906 progress=0;
cristy3ed852e2009-09-05 21:47:34 +0000907 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +0000908#if defined(MAGICKCORE_OPENMP_SUPPORT)
909 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000910#endif
cristybb503372010-05-27 20:51:26 +0000911 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000912 {
cristy3094b7f2011-10-01 23:18:02 +0000913 double
cristy5afeab82011-04-30 01:30:09 +0000914 blue,
915 green,
916 red;
917
cristy4c08aed2011-07-01 19:47:50 +0000918 register Quantum
cristyc47d1f82009-11-26 01:44:43 +0000919 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000920
cristy8d4629b2010-08-30 17:59:46 +0000921 register ssize_t
922 x;
923
cristy3ed852e2009-09-05 21:47:34 +0000924 if (status == MagickFalse)
925 continue;
926 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristyacd2ed22011-08-30 01:44:23 +0000927 if (q == (Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +0000928 {
929 status=MagickFalse;
930 continue;
931 }
cristybb503372010-05-27 20:51:26 +0000932 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000933 {
cristyda1f9c12011-10-02 21:39:49 +0000934 red=(double) GetPixelRed(image,q);
935 green=(double) GetPixelGreen(image,q);
936 blue=(double) GetPixelBlue(image,q);
cristy5afeab82011-04-30 01:30:09 +0000937 Contrast(sign,&red,&green,&blue);
cristyda1f9c12011-10-02 21:39:49 +0000938 SetPixelRed(image,ClampToQuantum(red),q);
939 SetPixelGreen(image,ClampToQuantum(green),q);
940 SetPixelBlue(image,ClampToQuantum(blue),q);
cristyed231572011-07-14 02:18:59 +0000941 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +0000942 }
943 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
944 status=MagickFalse;
945 if (image->progress_monitor != (MagickProgressMonitor) NULL)
946 {
947 MagickBooleanType
948 proceed;
949
cristyb5d5f722009-11-04 03:03:49 +0000950#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +0000951 #pragma omp critical (MagickCore_ContrastImage)
952#endif
953 proceed=SetImageProgress(image,ContrastImageTag,progress++,image->rows);
954 if (proceed == MagickFalse)
955 status=MagickFalse;
956 }
957 }
958 image_view=DestroyCacheView(image_view);
959 return(status);
960}
961
962/*
963%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
964% %
965% %
966% %
967% C o n t r a s t S t r e t c h I m a g e %
968% %
969% %
970% %
971%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
972%
cristyf1611782011-08-01 15:39:13 +0000973% ContrastStretchImage() is a simple image enhancement technique that attempts
974% to improve the contrast in an image by `stretching' the range of intensity
975% values it contains to span a desired range of values. It differs from the
976% more sophisticated histogram equalization in that it can only apply a
977% linear scaling function to the image pixel values. As a result the
978% `enhancement' is less harsh.
cristy3ed852e2009-09-05 21:47:34 +0000979%
980% The format of the ContrastStretchImage method is:
981%
982% MagickBooleanType ContrastStretchImage(Image *image,
cristye23ec9d2011-08-16 18:15:40 +0000983% const char *levels,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000984%
985% A description of each parameter follows:
986%
987% o image: the image.
988%
cristy3ed852e2009-09-05 21:47:34 +0000989% o black_point: the black point.
990%
991% o white_point: the white point.
992%
993% o levels: Specify the levels where the black and white points have the
994% range of 0 to number-of-pixels (e.g. 1%, 10x90%, etc.).
995%
cristye23ec9d2011-08-16 18:15:40 +0000996% o exception: return any errors or warnings in this structure.
997%
cristy3ed852e2009-09-05 21:47:34 +0000998*/
cristy3ed852e2009-09-05 21:47:34 +0000999MagickExport MagickBooleanType ContrastStretchImage(Image *image,
cristye23ec9d2011-08-16 18:15:40 +00001000 const double black_point,const double white_point,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001001{
1002#define MaxRange(color) ((MagickRealType) ScaleQuantumToMap((Quantum) (color)))
1003#define ContrastStretchImageTag "ContrastStretch/Image"
1004
cristyc4c8d132010-01-07 01:58:38 +00001005 CacheView
1006 *image_view;
1007
cristy3ed852e2009-09-05 21:47:34 +00001008 MagickBooleanType
1009 status;
1010
cristybb503372010-05-27 20:51:26 +00001011 MagickOffsetType
1012 progress;
1013
cristyf45fec72011-08-23 16:02:32 +00001014 double
1015 *black,
cristy3ed852e2009-09-05 21:47:34 +00001016 *histogram,
1017 *stretch_map,
cristyf45fec72011-08-23 16:02:32 +00001018 *white;
cristy3ed852e2009-09-05 21:47:34 +00001019
cristybb503372010-05-27 20:51:26 +00001020 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001021 i;
1022
cristybb503372010-05-27 20:51:26 +00001023 ssize_t
1024 y;
1025
cristy3ed852e2009-09-05 21:47:34 +00001026 /*
1027 Allocate histogram and stretch map.
1028 */
1029 assert(image != (Image *) NULL);
1030 assert(image->signature == MagickSignature);
1031 if (image->debug != MagickFalse)
1032 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
cristyf45fec72011-08-23 16:02:32 +00001033 black=(double *) AcquireQuantumMemory(GetPixelChannels(image),sizeof(*black));
1034 white=(double *) AcquireQuantumMemory(GetPixelChannels(image),sizeof(*white));
1035 histogram=(double *) AcquireQuantumMemory(MaxMap+1UL,
cristy465571b2011-08-21 20:43:15 +00001036 GetPixelChannels(image)*sizeof(*histogram));
cristyf45fec72011-08-23 16:02:32 +00001037 stretch_map=(double *) AcquireQuantumMemory(MaxMap+1UL,
cristy465571b2011-08-21 20:43:15 +00001038 GetPixelChannels(image)*sizeof(*stretch_map));
cristyf45fec72011-08-23 16:02:32 +00001039 if ((black == (double *) NULL) || (white == (double *) NULL) ||
1040 (histogram == (double *) NULL) || (stretch_map == (double *) NULL))
cristy465571b2011-08-21 20:43:15 +00001041 {
cristyf45fec72011-08-23 16:02:32 +00001042 if (stretch_map != (double *) NULL)
1043 stretch_map=(double *) RelinquishMagickMemory(stretch_map);
1044 if (histogram != (double *) NULL)
1045 histogram=(double *) RelinquishMagickMemory(histogram);
1046 if (white != (double *) NULL)
1047 white=(double *) RelinquishMagickMemory(white);
1048 if (black != (double *) NULL)
1049 black=(double *) RelinquishMagickMemory(black);
cristy465571b2011-08-21 20:43:15 +00001050 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
1051 image->filename);
1052 }
cristy3ed852e2009-09-05 21:47:34 +00001053 /*
1054 Form histogram.
1055 */
1056 status=MagickTrue;
cristy465571b2011-08-21 20:43:15 +00001057 (void) ResetMagickMemory(histogram,0,(MaxMap+1)*GetPixelChannels(image)*
1058 sizeof(*histogram));
cristy3ed852e2009-09-05 21:47:34 +00001059 image_view=AcquireCacheView(image);
cristybb503372010-05-27 20:51:26 +00001060 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001061 {
cristy4c08aed2011-07-01 19:47:50 +00001062 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00001063 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00001064
cristybb503372010-05-27 20:51:26 +00001065 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001066 x;
1067
1068 if (status == MagickFalse)
1069 continue;
1070 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00001071 if (p == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001072 {
1073 status=MagickFalse;
1074 continue;
1075 }
cristy43547a52011-08-09 13:07:05 +00001076 for (x=0; x < (ssize_t) image->columns; x++)
1077 {
cristy465571b2011-08-21 20:43:15 +00001078 register ssize_t
1079 i;
1080
1081 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
cristy465571b2011-08-21 20:43:15 +00001082 histogram[GetPixelChannels(image)*ScaleQuantumToMap(p[i])+i]++;
cristy43547a52011-08-09 13:07:05 +00001083 p+=GetPixelChannels(image);
1084 }
cristy3ed852e2009-09-05 21:47:34 +00001085 }
1086 /*
1087 Find the histogram boundaries by locating the black/white levels.
1088 */
cristyb5d5f722009-11-04 03:03:49 +00001089#if defined(MAGICKCORE_OPENMP_SUPPORT)
1090 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001091#endif
cristy465571b2011-08-21 20:43:15 +00001092 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
cristy3ed852e2009-09-05 21:47:34 +00001093 {
cristy465571b2011-08-21 20:43:15 +00001094 double
1095 intensity;
1096
1097 register ssize_t
1098 j;
1099
1100 black[i]=0.0;
1101 white[i]=MaxRange(QuantumRange);
1102 intensity=0.0;
1103 for (j=0; j <= (ssize_t) MaxMap; j++)
1104 {
1105 intensity+=histogram[GetPixelChannels(image)*j+i];
1106 if (intensity > black_point)
1107 break;
1108 }
1109 black[i]=(MagickRealType) j;
1110 intensity=0.0;
1111 for (j=(ssize_t) MaxMap; j != 0; j--)
1112 {
1113 intensity+=histogram[GetPixelChannels(image)*j+i];
1114 if (intensity > ((double) image->columns*image->rows-white_point))
1115 break;
1116 }
1117 white[i]=(MagickRealType) j;
cristy3ed852e2009-09-05 21:47:34 +00001118 }
cristy465571b2011-08-21 20:43:15 +00001119 histogram=(double *) RelinquishMagickMemory(histogram);
cristy3ed852e2009-09-05 21:47:34 +00001120 /*
cristy465571b2011-08-21 20:43:15 +00001121 Stretch the histogram to create the stretched image mapping.
cristy3ed852e2009-09-05 21:47:34 +00001122 */
cristy465571b2011-08-21 20:43:15 +00001123 (void) ResetMagickMemory(stretch_map,0,(MaxMap+1)*GetPixelChannels(image)*
1124 sizeof(*stretch_map));
1125#if defined(MAGICKCORE_OPENMP_SUPPORT)
1126 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
1127#endif
1128 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1129 {
1130 register ssize_t
1131 j;
1132
1133 for (j=0; j <= (ssize_t) MaxMap; j++)
1134 {
1135 if (j < (ssize_t) black[i])
1136 stretch_map[GetPixelChannels(image)*j+i]=0.0;
1137 else
1138 if (j > (ssize_t) white[i])
1139 stretch_map[GetPixelChannels(image)*j+i]=(MagickRealType)
1140 QuantumRange;
1141 else
1142 if (black[i] != white[i])
1143 stretch_map[GetPixelChannels(image)*j+i]=(MagickRealType)
1144 ScaleMapToQuantum((MagickRealType) (MaxMap*(j-black[i])/
1145 (white[i]-black[i])));
1146 }
1147 }
cristy3ed852e2009-09-05 21:47:34 +00001148 if (image->storage_class == PseudoClass)
1149 {
cristy465571b2011-08-21 20:43:15 +00001150 register ssize_t
1151 j;
1152
cristy3ed852e2009-09-05 21:47:34 +00001153 /*
cristy465571b2011-08-21 20:43:15 +00001154 Stretch-contrast colormap.
cristy3ed852e2009-09-05 21:47:34 +00001155 */
cristyb5d5f722009-11-04 03:03:49 +00001156#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy000d38b2011-09-08 13:13:04 +00001157 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001158#endif
cristy465571b2011-08-21 20:43:15 +00001159 for (j=0; j < (ssize_t) image->colors; j++)
cristy3ed852e2009-09-05 21:47:34 +00001160 {
cristyed231572011-07-14 02:18:59 +00001161 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001162 {
cristy465571b2011-08-21 20:43:15 +00001163 i=GetPixelChannelMapChannel(image,RedPixelChannel);
1164 if (black[i] != white[i])
cristyda1f9c12011-10-02 21:39:49 +00001165 image->colormap[j].red=stretch_map[GetPixelChannels(image)*
1166 ScaleQuantumToMap(ClampToQuantum(image->colormap[j].red))]+i;
cristy3ed852e2009-09-05 21:47:34 +00001167 }
cristyed231572011-07-14 02:18:59 +00001168 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001169 {
cristy465571b2011-08-21 20:43:15 +00001170 i=GetPixelChannelMapChannel(image,GreenPixelChannel);
1171 if (black[i] != white[i])
cristyda1f9c12011-10-02 21:39:49 +00001172 image->colormap[j].green=stretch_map[GetPixelChannels(image)*
1173 ScaleQuantumToMap(ClampToQuantum(image->colormap[j].green))]+i;
cristy3ed852e2009-09-05 21:47:34 +00001174 }
cristyed231572011-07-14 02:18:59 +00001175 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001176 {
cristy465571b2011-08-21 20:43:15 +00001177 i=GetPixelChannelMapChannel(image,BluePixelChannel);
1178 if (black[i] != white[i])
cristyda1f9c12011-10-02 21:39:49 +00001179 image->colormap[j].blue=stretch_map[GetPixelChannels(image)*
1180 ScaleQuantumToMap(ClampToQuantum(image->colormap[j].blue))]+i;
cristy3ed852e2009-09-05 21:47:34 +00001181 }
cristyed231572011-07-14 02:18:59 +00001182 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001183 {
cristy465571b2011-08-21 20:43:15 +00001184 i=GetPixelChannelMapChannel(image,AlphaPixelChannel);
1185 if (black[i] != white[i])
cristyda1f9c12011-10-02 21:39:49 +00001186 image->colormap[j].alpha=stretch_map[GetPixelChannels(image)*
1187 ScaleQuantumToMap(ClampToQuantum(image->colormap[j].alpha))]+i;
cristy3ed852e2009-09-05 21:47:34 +00001188 }
1189 }
1190 }
1191 /*
cristy465571b2011-08-21 20:43:15 +00001192 Stretch-contrast image.
cristy3ed852e2009-09-05 21:47:34 +00001193 */
1194 status=MagickTrue;
1195 progress=0;
cristyb5d5f722009-11-04 03:03:49 +00001196#if defined(MAGICKCORE_OPENMP_SUPPORT)
1197 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001198#endif
cristybb503372010-05-27 20:51:26 +00001199 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001200 {
cristy4c08aed2011-07-01 19:47:50 +00001201 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00001202 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001203
cristy8d4629b2010-08-30 17:59:46 +00001204 register ssize_t
1205 x;
1206
cristy3ed852e2009-09-05 21:47:34 +00001207 if (status == MagickFalse)
1208 continue;
1209 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristyacd2ed22011-08-30 01:44:23 +00001210 if (q == (Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001211 {
1212 status=MagickFalse;
1213 continue;
1214 }
cristybb503372010-05-27 20:51:26 +00001215 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001216 {
cristy465571b2011-08-21 20:43:15 +00001217 register ssize_t
1218 i;
1219
1220 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1221 {
1222 PixelTrait
1223 traits;
1224
cristye2a912b2011-12-05 20:02:07 +00001225 traits=GetPixelChannelMapTraits(image,i);
cristy7c0a0a42011-08-23 17:57:25 +00001226 if (((traits & UpdatePixelTrait) != 0) && (black[i] != white[i]))
cristy465571b2011-08-21 20:43:15 +00001227 q[i]=ClampToQuantum(stretch_map[GetPixelChannels(image)*
1228 ScaleQuantumToMap(q[i])+i]);
1229 }
cristyed231572011-07-14 02:18:59 +00001230 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00001231 }
1232 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
1233 status=MagickFalse;
1234 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1235 {
1236 MagickBooleanType
1237 proceed;
1238
cristyb5d5f722009-11-04 03:03:49 +00001239#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy50fbc382011-07-07 02:19:17 +00001240 #pragma omp critical (MagickCore_ContrastStretchImage)
cristy3ed852e2009-09-05 21:47:34 +00001241#endif
1242 proceed=SetImageProgress(image,ContrastStretchImageTag,progress++,
1243 image->rows);
1244 if (proceed == MagickFalse)
1245 status=MagickFalse;
1246 }
1247 }
1248 image_view=DestroyCacheView(image_view);
cristyf45fec72011-08-23 16:02:32 +00001249 stretch_map=(double *) RelinquishMagickMemory(stretch_map);
1250 white=(double *) RelinquishMagickMemory(white);
1251 black=(double *) RelinquishMagickMemory(black);
cristy3ed852e2009-09-05 21:47:34 +00001252 return(status);
1253}
1254
1255/*
1256%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1257% %
1258% %
1259% %
1260% E n h a n c e I m a g e %
1261% %
1262% %
1263% %
1264%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1265%
1266% EnhanceImage() applies a digital filter that improves the quality of a
1267% noisy image.
1268%
1269% The format of the EnhanceImage method is:
1270%
1271% Image *EnhanceImage(const Image *image,ExceptionInfo *exception)
1272%
1273% A description of each parameter follows:
1274%
1275% o image: the image.
1276%
1277% o exception: return any errors or warnings in this structure.
1278%
1279*/
1280MagickExport Image *EnhanceImage(const Image *image,ExceptionInfo *exception)
1281{
cristy6d8c3d72011-08-22 01:20:01 +00001282#define EnhancePixel(weight) \
cristy0beccfa2011-09-25 20:47:53 +00001283 mean=((MagickRealType) r[i]+GetPixelChannel(enhance_image,channel,q))/2.0; \
1284 distance=(MagickRealType) r[i]-(MagickRealType) GetPixelChannel( \
1285 enhance_image,channel,q); \
cristy3ed852e2009-09-05 21:47:34 +00001286 distance_squared=QuantumScale*(2.0*((MagickRealType) QuantumRange+1.0)+ \
cristy6d8c3d72011-08-22 01:20:01 +00001287 mean)*distance*distance; \
cristy3ed852e2009-09-05 21:47:34 +00001288 if (distance_squared < ((MagickRealType) QuantumRange*(MagickRealType) \
1289 QuantumRange/25.0f)) \
1290 { \
cristy6d8c3d72011-08-22 01:20:01 +00001291 aggregate+=(weight)*r[i]; \
cristy3ed852e2009-09-05 21:47:34 +00001292 total_weight+=(weight); \
1293 } \
cristy6d8c3d72011-08-22 01:20:01 +00001294 r+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00001295#define EnhanceImageTag "Enhance/Image"
1296
cristyc4c8d132010-01-07 01:58:38 +00001297 CacheView
1298 *enhance_view,
1299 *image_view;
1300
cristy3ed852e2009-09-05 21:47:34 +00001301 Image
1302 *enhance_image;
1303
cristy3ed852e2009-09-05 21:47:34 +00001304 MagickBooleanType
1305 status;
1306
cristybb503372010-05-27 20:51:26 +00001307 MagickOffsetType
1308 progress;
1309
cristybb503372010-05-27 20:51:26 +00001310 ssize_t
1311 y;
1312
cristy3ed852e2009-09-05 21:47:34 +00001313 /*
1314 Initialize enhanced image attributes.
1315 */
1316 assert(image != (const Image *) NULL);
1317 assert(image->signature == MagickSignature);
1318 if (image->debug != MagickFalse)
1319 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1320 assert(exception != (ExceptionInfo *) NULL);
1321 assert(exception->signature == MagickSignature);
cristy3ed852e2009-09-05 21:47:34 +00001322 enhance_image=CloneImage(image,image->columns,image->rows,MagickTrue,
1323 exception);
1324 if (enhance_image == (Image *) NULL)
1325 return((Image *) NULL);
cristy574cc262011-08-05 01:23:58 +00001326 if (SetImageStorageClass(enhance_image,DirectClass,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00001327 {
cristy3ed852e2009-09-05 21:47:34 +00001328 enhance_image=DestroyImage(enhance_image);
1329 return((Image *) NULL);
1330 }
1331 /*
1332 Enhance image.
1333 */
1334 status=MagickTrue;
1335 progress=0;
cristy3ed852e2009-09-05 21:47:34 +00001336 image_view=AcquireCacheView(image);
1337 enhance_view=AcquireCacheView(enhance_image);
cristyb5d5f722009-11-04 03:03:49 +00001338#if defined(MAGICKCORE_OPENMP_SUPPORT)
1339 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001340#endif
cristybb503372010-05-27 20:51:26 +00001341 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001342 {
cristy4c08aed2011-07-01 19:47:50 +00001343 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00001344 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00001345
cristy4c08aed2011-07-01 19:47:50 +00001346 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00001347 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001348
cristy8d4629b2010-08-30 17:59:46 +00001349 register ssize_t
1350 x;
1351
cristy6d8c3d72011-08-22 01:20:01 +00001352 ssize_t
1353 center;
1354
cristy3ed852e2009-09-05 21:47:34 +00001355 if (status == MagickFalse)
1356 continue;
1357 p=GetCacheViewVirtualPixels(image_view,-2,y-2,image->columns+4,5,exception);
1358 q=QueueCacheViewAuthenticPixels(enhance_view,0,y,enhance_image->columns,1,
1359 exception);
cristy4c08aed2011-07-01 19:47:50 +00001360 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00001361 {
1362 status=MagickFalse;
1363 continue;
1364 }
cristyf45fec72011-08-23 16:02:32 +00001365 center=(ssize_t) GetPixelChannels(image)*(2*(image->columns+4)+2);
cristybb503372010-05-27 20:51:26 +00001366 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001367 {
cristy6d8c3d72011-08-22 01:20:01 +00001368 register ssize_t
1369 i;
cristy3ed852e2009-09-05 21:47:34 +00001370
cristy6d8c3d72011-08-22 01:20:01 +00001371 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1372 {
1373 MagickRealType
1374 aggregate,
1375 distance,
1376 distance_squared,
1377 mean,
1378 total_weight;
cristy3ed852e2009-09-05 21:47:34 +00001379
cristy6d8c3d72011-08-22 01:20:01 +00001380 PixelChannel
1381 channel;
cristy3ed852e2009-09-05 21:47:34 +00001382
cristy6d8c3d72011-08-22 01:20:01 +00001383 PixelTrait
1384 enhance_traits,
1385 traits;
cristy3ed852e2009-09-05 21:47:34 +00001386
cristy6d8c3d72011-08-22 01:20:01 +00001387 register const Quantum
1388 *restrict r;
1389
cristye2a912b2011-12-05 20:02:07 +00001390 traits=GetPixelChannelMapTraits(image,i);
1391 channel=GetPixelChannelMapChannel(image,i);
cristy6d8c3d72011-08-22 01:20:01 +00001392 enhance_traits=GetPixelChannelMapTraits(enhance_image,channel);
cristy010d7d12011-08-31 01:02:48 +00001393 if ((traits == UndefinedPixelTrait) ||
1394 (enhance_traits == UndefinedPixelTrait))
cristy6d8c3d72011-08-22 01:20:01 +00001395 continue;
cristy0beccfa2011-09-25 20:47:53 +00001396 SetPixelChannel(enhance_image,channel,p[center+i],q);
cristy6d8c3d72011-08-22 01:20:01 +00001397 if ((enhance_traits & CopyPixelTrait) != 0)
1398 continue;
1399 /*
1400 Compute weighted average of target pixel color components.
1401 */
1402 aggregate=0.0;
1403 total_weight=0.0;
cristyf45fec72011-08-23 16:02:32 +00001404 r=p;
cristy6d8c3d72011-08-22 01:20:01 +00001405 EnhancePixel(5.0); EnhancePixel(8.0); EnhancePixel(10.0);
1406 EnhancePixel(8.0); EnhancePixel(5.0);
1407 r=p+1*GetPixelChannels(image)*(image->columns+4);
1408 EnhancePixel(8.0); EnhancePixel(20.0); EnhancePixel(40.0);
1409 EnhancePixel(20.0); EnhancePixel(8.0);
1410 r=p+2*GetPixelChannels(image)*(image->columns+4);
1411 EnhancePixel(10.0); EnhancePixel(40.0); EnhancePixel(80.0);
1412 EnhancePixel(40.0); EnhancePixel(10.0);
1413 r=p+3*GetPixelChannels(image)*(image->columns+4);
1414 EnhancePixel(8.0); EnhancePixel(20.0); EnhancePixel(40.0);
1415 EnhancePixel(20.0); EnhancePixel(8.0);
1416 r=p+4*GetPixelChannels(image)*(image->columns+4);
1417 EnhancePixel(5.0); EnhancePixel(8.0); EnhancePixel(10.0);
1418 EnhancePixel(8.0); EnhancePixel(5.0);
cristy0beccfa2011-09-25 20:47:53 +00001419 SetPixelChannel(enhance_image,channel,ClampToQuantum(aggregate/
1420 total_weight),q);
cristy6d8c3d72011-08-22 01:20:01 +00001421 }
cristyed231572011-07-14 02:18:59 +00001422 p+=GetPixelChannels(image);
1423 q+=GetPixelChannels(enhance_image);
cristy3ed852e2009-09-05 21:47:34 +00001424 }
1425 if (SyncCacheViewAuthenticPixels(enhance_view,exception) == MagickFalse)
1426 status=MagickFalse;
1427 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1428 {
1429 MagickBooleanType
1430 proceed;
1431
cristyb5d5f722009-11-04 03:03:49 +00001432#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00001433 #pragma omp critical (MagickCore_EnhanceImage)
1434#endif
1435 proceed=SetImageProgress(image,EnhanceImageTag,progress++,image->rows);
1436 if (proceed == MagickFalse)
1437 status=MagickFalse;
1438 }
1439 }
1440 enhance_view=DestroyCacheView(enhance_view);
1441 image_view=DestroyCacheView(image_view);
1442 return(enhance_image);
1443}
1444
1445/*
1446%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1447% %
1448% %
1449% %
1450% E q u a l i z e I m a g e %
1451% %
1452% %
1453% %
1454%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1455%
1456% EqualizeImage() applies a histogram equalization to the image.
1457%
1458% The format of the EqualizeImage method is:
1459%
cristy6d8c3d72011-08-22 01:20:01 +00001460% MagickBooleanType EqualizeImage(Image *image,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001461%
1462% A description of each parameter follows:
1463%
1464% o image: the image.
1465%
cristy6d8c3d72011-08-22 01:20:01 +00001466% o exception: return any errors or warnings in this structure.
cristy3ed852e2009-09-05 21:47:34 +00001467%
1468*/
cristy6d8c3d72011-08-22 01:20:01 +00001469MagickExport MagickBooleanType EqualizeImage(Image *image,
1470 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001471{
cristy3ed852e2009-09-05 21:47:34 +00001472#define EqualizeImageTag "Equalize/Image"
1473
cristyc4c8d132010-01-07 01:58:38 +00001474 CacheView
1475 *image_view;
1476
cristy3ed852e2009-09-05 21:47:34 +00001477 MagickBooleanType
1478 status;
1479
cristybb503372010-05-27 20:51:26 +00001480 MagickOffsetType
1481 progress;
1482
cristyf45fec72011-08-23 16:02:32 +00001483 MagickRealType
cristy5f95f4f2011-10-23 01:01:01 +00001484 black[CompositePixelChannel],
cristy3ed852e2009-09-05 21:47:34 +00001485 *equalize_map,
1486 *histogram,
cristy3ed852e2009-09-05 21:47:34 +00001487 *map,
cristy5f95f4f2011-10-23 01:01:01 +00001488 white[CompositePixelChannel];
cristy3ed852e2009-09-05 21:47:34 +00001489
cristybb503372010-05-27 20:51:26 +00001490 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001491 i;
1492
cristybb503372010-05-27 20:51:26 +00001493 ssize_t
1494 y;
1495
cristy3ed852e2009-09-05 21:47:34 +00001496 /*
1497 Allocate and initialize histogram arrays.
1498 */
1499 assert(image != (Image *) NULL);
1500 assert(image->signature == MagickSignature);
1501 if (image->debug != MagickFalse)
1502 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
cristyf45fec72011-08-23 16:02:32 +00001503 equalize_map=(MagickRealType *) AcquireQuantumMemory(MaxMap+1UL,
1504 GetPixelChannels(image)*sizeof(*equalize_map));
1505 histogram=(MagickRealType *) AcquireQuantumMemory(MaxMap+1UL,
1506 GetPixelChannels(image)*sizeof(*histogram));
1507 map=(MagickRealType *) AcquireQuantumMemory(MaxMap+1UL,
1508 GetPixelChannels(image)*sizeof(*map));
1509 if ((equalize_map == (MagickRealType *) NULL) ||
1510 (histogram == (MagickRealType *) NULL) ||
1511 (map == (MagickRealType *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00001512 {
cristyf45fec72011-08-23 16:02:32 +00001513 if (map != (MagickRealType *) NULL)
1514 map=(MagickRealType *) RelinquishMagickMemory(map);
1515 if (histogram != (MagickRealType *) NULL)
1516 histogram=(MagickRealType *) RelinquishMagickMemory(histogram);
1517 if (equalize_map != (MagickRealType *) NULL)
1518 equalize_map=(MagickRealType *) RelinquishMagickMemory(equalize_map);
cristy3ed852e2009-09-05 21:47:34 +00001519 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
1520 image->filename);
1521 }
1522 /*
1523 Form histogram.
1524 */
cristyf45fec72011-08-23 16:02:32 +00001525 status=MagickTrue;
1526 (void) ResetMagickMemory(histogram,0,(MaxMap+1)*GetPixelChannels(image)*
1527 sizeof(*histogram));
1528 image_view=AcquireCacheView(image);
cristybb503372010-05-27 20:51:26 +00001529 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001530 {
cristy4c08aed2011-07-01 19:47:50 +00001531 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00001532 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00001533
cristybb503372010-05-27 20:51:26 +00001534 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001535 x;
1536
cristyf45fec72011-08-23 16:02:32 +00001537 if (status == MagickFalse)
1538 continue;
1539 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00001540 if (p == (const Quantum *) NULL)
cristyf45fec72011-08-23 16:02:32 +00001541 {
1542 status=MagickFalse;
1543 continue;
1544 }
cristybb503372010-05-27 20:51:26 +00001545 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001546 {
cristyf45fec72011-08-23 16:02:32 +00001547 register ssize_t
1548 i;
1549
1550 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1551 histogram[GetPixelChannels(image)*ScaleQuantumToMap(p[i])+i]++;
cristyed231572011-07-14 02:18:59 +00001552 p+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00001553 }
1554 }
1555 /*
1556 Integrate the histogram to get the equalization map.
1557 */
cristyb5d5f722009-11-04 03:03:49 +00001558#if defined(MAGICKCORE_OPENMP_SUPPORT)
1559 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001560#endif
cristyf45fec72011-08-23 16:02:32 +00001561 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
cristy3ed852e2009-09-05 21:47:34 +00001562 {
cristyf45fec72011-08-23 16:02:32 +00001563 MagickRealType
1564 intensity;
1565
1566 register ssize_t
1567 j;
1568
1569 intensity=0.0;
1570 for (j=0; j <= (ssize_t) MaxMap; j++)
1571 {
1572 intensity+=histogram[GetPixelChannels(image)*j+i];
1573 map[GetPixelChannels(image)*j+i]=intensity;
1574 }
cristy3ed852e2009-09-05 21:47:34 +00001575 }
cristyf45fec72011-08-23 16:02:32 +00001576 (void) ResetMagickMemory(equalize_map,0,(MaxMap+1)*GetPixelChannels(image)*
1577 sizeof(*equalize_map));
1578#if defined(MAGICKCORE_OPENMP_SUPPORT)
1579 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
1580#endif
1581 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1582 {
1583 register ssize_t
1584 j;
1585
1586 black[i]=map[i];
1587 white[i]=map[GetPixelChannels(image)*MaxMap+i];
1588 if (black[i] != white[i])
1589 for (j=0; j <= (ssize_t) MaxMap; j++)
1590 equalize_map[GetPixelChannels(image)*j+i]=(MagickRealType)
1591 ScaleMapToQuantum((MagickRealType) ((MaxMap*(map[
1592 GetPixelChannels(image)*j+i]-black[i]))/(white[i]-black[i])));
1593 }
1594 histogram=(MagickRealType *) RelinquishMagickMemory(histogram);
1595 map=(MagickRealType *) RelinquishMagickMemory(map);
cristy3ed852e2009-09-05 21:47:34 +00001596 if (image->storage_class == PseudoClass)
1597 {
cristyf54798b2011-11-21 18:38:23 +00001598 PixelChannel
1599 channel;
1600
cristyf45fec72011-08-23 16:02:32 +00001601 register ssize_t
1602 j;
1603
cristy3ed852e2009-09-05 21:47:34 +00001604 /*
1605 Equalize colormap.
1606 */
cristyb5d5f722009-11-04 03:03:49 +00001607#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristydb483812011-09-08 13:17:11 +00001608 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001609#endif
cristyf45fec72011-08-23 16:02:32 +00001610 for (j=0; j < (ssize_t) image->colors; j++)
cristy3ed852e2009-09-05 21:47:34 +00001611 {
cristyf54798b2011-11-21 18:38:23 +00001612 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristyf45fec72011-08-23 16:02:32 +00001613 {
cristyf54798b2011-11-21 18:38:23 +00001614 channel=GetPixelChannelMapChannel(image,RedPixelChannel);
1615 if (black[channel] != white[channel])
cristyda1f9c12011-10-02 21:39:49 +00001616 image->colormap[j].red=equalize_map[GetPixelChannels(image)*
cristyf54798b2011-11-21 18:38:23 +00001617 ScaleQuantumToMap(ClampToQuantum(image->colormap[j].red))]+
1618 channel;
cristyf45fec72011-08-23 16:02:32 +00001619 }
cristyf54798b2011-11-21 18:38:23 +00001620 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristyf45fec72011-08-23 16:02:32 +00001621 {
cristyf54798b2011-11-21 18:38:23 +00001622 channel=GetPixelChannelMapChannel(image,GreenPixelChannel);
1623 if (black[channel] != white[channel])
cristyda1f9c12011-10-02 21:39:49 +00001624 image->colormap[j].green=equalize_map[GetPixelChannels(image)*
cristyf54798b2011-11-21 18:38:23 +00001625 ScaleQuantumToMap(ClampToQuantum(image->colormap[j].green))]+
1626 channel;
cristyf45fec72011-08-23 16:02:32 +00001627 }
cristyf54798b2011-11-21 18:38:23 +00001628 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristyf45fec72011-08-23 16:02:32 +00001629 {
cristyf54798b2011-11-21 18:38:23 +00001630 channel=GetPixelChannelMapChannel(image,BluePixelChannel);
1631 if (black[channel] != white[channel])
cristyda1f9c12011-10-02 21:39:49 +00001632 image->colormap[j].blue=equalize_map[GetPixelChannels(image)*
cristyf54798b2011-11-21 18:38:23 +00001633 ScaleQuantumToMap(ClampToQuantum(image->colormap[j].blue))]+
1634 channel;
cristyf45fec72011-08-23 16:02:32 +00001635 }
cristyf54798b2011-11-21 18:38:23 +00001636 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristyf45fec72011-08-23 16:02:32 +00001637 {
cristyf54798b2011-11-21 18:38:23 +00001638 channel=GetPixelChannelMapChannel(image,AlphaPixelChannel);
1639 if (black[channel] != white[channel])
cristyda1f9c12011-10-02 21:39:49 +00001640 image->colormap[j].alpha=equalize_map[GetPixelChannels(image)*
cristyf54798b2011-11-21 18:38:23 +00001641 ScaleQuantumToMap(ClampToQuantum(image->colormap[j].alpha))]+
1642 channel;
cristyf45fec72011-08-23 16:02:32 +00001643 }
cristy3ed852e2009-09-05 21:47:34 +00001644 }
1645 }
1646 /*
1647 Equalize image.
1648 */
cristy3ed852e2009-09-05 21:47:34 +00001649 progress=0;
cristyb5d5f722009-11-04 03:03:49 +00001650#if defined(MAGICKCORE_OPENMP_SUPPORT)
1651 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001652#endif
cristybb503372010-05-27 20:51:26 +00001653 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001654 {
cristy4c08aed2011-07-01 19:47:50 +00001655 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00001656 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001657
cristy8d4629b2010-08-30 17:59:46 +00001658 register ssize_t
1659 x;
1660
cristy3ed852e2009-09-05 21:47:34 +00001661 if (status == MagickFalse)
1662 continue;
1663 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristyacd2ed22011-08-30 01:44:23 +00001664 if (q == (Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001665 {
1666 status=MagickFalse;
1667 continue;
1668 }
cristybb503372010-05-27 20:51:26 +00001669 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001670 {
cristyf45fec72011-08-23 16:02:32 +00001671 register ssize_t
1672 i;
1673
1674 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1675 {
1676 PixelTrait
1677 traits;
1678
cristye2a912b2011-12-05 20:02:07 +00001679 traits=GetPixelChannelMapTraits(image,i);
cristy7c0a0a42011-08-23 17:57:25 +00001680 if (((traits & UpdatePixelTrait) != 0) && (black[i] != white[i]))
cristyf45fec72011-08-23 16:02:32 +00001681 q[i]=ClampToQuantum(equalize_map[GetPixelChannels(image)*
1682 ScaleQuantumToMap(q[i])+i]);
1683 }
cristyed231572011-07-14 02:18:59 +00001684 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00001685 }
1686 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
1687 status=MagickFalse;
1688 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1689 {
1690 MagickBooleanType
1691 proceed;
1692
cristyb5d5f722009-11-04 03:03:49 +00001693#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy50fbc382011-07-07 02:19:17 +00001694 #pragma omp critical (MagickCore_EqualizeImage)
cristy3ed852e2009-09-05 21:47:34 +00001695#endif
1696 proceed=SetImageProgress(image,EqualizeImageTag,progress++,image->rows);
1697 if (proceed == MagickFalse)
1698 status=MagickFalse;
1699 }
1700 }
1701 image_view=DestroyCacheView(image_view);
cristyf45fec72011-08-23 16:02:32 +00001702 equalize_map=(MagickRealType *) RelinquishMagickMemory(equalize_map);
cristy3ed852e2009-09-05 21:47:34 +00001703 return(status);
1704}
1705
1706/*
1707%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1708% %
1709% %
1710% %
1711% G a m m a I m a g e %
1712% %
1713% %
1714% %
1715%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1716%
1717% GammaImage() gamma-corrects a particular image channel. The same
1718% image viewed on different devices will have perceptual differences in the
1719% way the image's intensities are represented on the screen. Specify
1720% individual gamma levels for the red, green, and blue channels, or adjust
1721% all three with the gamma parameter. Values typically range from 0.8 to 2.3.
1722%
1723% You can also reduce the influence of a particular channel with a gamma
1724% value of 0.
1725%
1726% The format of the GammaImage method is:
1727%
cristyb3e7c6c2011-07-24 01:43:55 +00001728% MagickBooleanType GammaImage(Image *image,const double gamma,
1729% ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001730%
1731% A description of each parameter follows:
1732%
1733% o image: the image.
1734%
cristya6360142011-03-23 23:08:04 +00001735% o level: the image gamma as a string (e.g. 1.6,1.2,1.0).
1736%
cristy3ed852e2009-09-05 21:47:34 +00001737% o gamma: the image gamma.
1738%
1739*/
cristyb3e7c6c2011-07-24 01:43:55 +00001740MagickExport MagickBooleanType GammaImage(Image *image,const double gamma,
1741 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001742{
1743#define GammaCorrectImageTag "GammaCorrect/Image"
1744
cristyc4c8d132010-01-07 01:58:38 +00001745 CacheView
1746 *image_view;
1747
cristyda1f9c12011-10-02 21:39:49 +00001748 double
1749 *gamma_map;
1750
cristy3ed852e2009-09-05 21:47:34 +00001751 MagickBooleanType
1752 status;
1753
cristybb503372010-05-27 20:51:26 +00001754 MagickOffsetType
1755 progress;
1756
cristybb503372010-05-27 20:51:26 +00001757 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001758 i;
1759
cristybb503372010-05-27 20:51:26 +00001760 ssize_t
1761 y;
1762
cristy3ed852e2009-09-05 21:47:34 +00001763 /*
1764 Allocate and initialize gamma maps.
1765 */
1766 assert(image != (Image *) NULL);
1767 assert(image->signature == MagickSignature);
1768 if (image->debug != MagickFalse)
1769 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1770 if (gamma == 1.0)
1771 return(MagickTrue);
cristyda1f9c12011-10-02 21:39:49 +00001772 gamma_map=(double *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*gamma_map));
1773 if (gamma_map == (double *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001774 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
1775 image->filename);
1776 (void) ResetMagickMemory(gamma_map,0,(MaxMap+1)*sizeof(*gamma_map));
1777 if (gamma != 0.0)
cristyd476a8e2011-07-23 16:13:22 +00001778#if defined(MAGICKCORE_OPENMP_SUPPORT) && (MaxMap > 256)
1779 #pragma omp parallel for
cristy3ed852e2009-09-05 21:47:34 +00001780#endif
cristybb503372010-05-27 20:51:26 +00001781 for (i=0; i <= (ssize_t) MaxMap; i++)
cristyda1f9c12011-10-02 21:39:49 +00001782 gamma_map[i]=(MagickRealType) ScaleMapToQuantum((
1783 MagickRealType) (MaxMap*pow((double) i/MaxMap,1.0/gamma)));
cristy3ed852e2009-09-05 21:47:34 +00001784 if (image->storage_class == PseudoClass)
1785 {
1786 /*
1787 Gamma-correct colormap.
1788 */
cristyb5d5f722009-11-04 03:03:49 +00001789#if defined(MAGICKCORE_OPENMP_SUPPORT)
1790 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001791#endif
cristybb503372010-05-27 20:51:26 +00001792 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00001793 {
cristyed231572011-07-14 02:18:59 +00001794 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001795 image->colormap[i].red=gamma_map[
cristyda1f9c12011-10-02 21:39:49 +00001796 ScaleQuantumToMap(ClampToQuantum(image->colormap[i].red))];
cristyed231572011-07-14 02:18:59 +00001797 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001798 image->colormap[i].green=gamma_map[
cristyda1f9c12011-10-02 21:39:49 +00001799 ScaleQuantumToMap(ClampToQuantum(image->colormap[i].green))];
cristyed231572011-07-14 02:18:59 +00001800 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001801 image->colormap[i].blue=gamma_map[
cristyda1f9c12011-10-02 21:39:49 +00001802 ScaleQuantumToMap(ClampToQuantum(image->colormap[i].blue))];
cristyed231572011-07-14 02:18:59 +00001803 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001804 image->colormap[i].alpha=gamma_map[
cristyda1f9c12011-10-02 21:39:49 +00001805 ScaleQuantumToMap(ClampToQuantum(image->colormap[i].alpha))];
cristy3ed852e2009-09-05 21:47:34 +00001806 }
1807 }
1808 /*
1809 Gamma-correct image.
1810 */
1811 status=MagickTrue;
1812 progress=0;
cristy3ed852e2009-09-05 21:47:34 +00001813 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00001814#if defined(MAGICKCORE_OPENMP_SUPPORT)
1815 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001816#endif
cristybb503372010-05-27 20:51:26 +00001817 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001818 {
cristy4c08aed2011-07-01 19:47:50 +00001819 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00001820 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001821
cristy8d4629b2010-08-30 17:59:46 +00001822 register ssize_t
1823 x;
1824
cristy3ed852e2009-09-05 21:47:34 +00001825 if (status == MagickFalse)
1826 continue;
1827 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristyacd2ed22011-08-30 01:44:23 +00001828 if (q == (Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001829 {
1830 status=MagickFalse;
1831 continue;
1832 }
cristybb503372010-05-27 20:51:26 +00001833 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001834 {
cristyd476a8e2011-07-23 16:13:22 +00001835 register ssize_t
1836 i;
1837
cristya30d9ba2011-07-23 21:00:48 +00001838 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
cristyd476a8e2011-07-23 16:13:22 +00001839 {
1840 PixelTrait
1841 traits;
1842
cristye2a912b2011-12-05 20:02:07 +00001843 traits=GetPixelChannelMapTraits(image,i);
cristyd476a8e2011-07-23 16:13:22 +00001844 if ((traits & UpdatePixelTrait) != 0)
cristyda1f9c12011-10-02 21:39:49 +00001845 q[i]=ClampToQuantum(gamma_map[ScaleQuantumToMap(q[i])]);
cristyd476a8e2011-07-23 16:13:22 +00001846 }
cristya30d9ba2011-07-23 21:00:48 +00001847 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00001848 }
cristy3ed852e2009-09-05 21:47:34 +00001849 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
1850 status=MagickFalse;
1851 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1852 {
1853 MagickBooleanType
1854 proceed;
1855
cristyb5d5f722009-11-04 03:03:49 +00001856#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy50fbc382011-07-07 02:19:17 +00001857 #pragma omp critical (MagickCore_GammaImage)
cristy3ed852e2009-09-05 21:47:34 +00001858#endif
1859 proceed=SetImageProgress(image,GammaCorrectImageTag,progress++,
1860 image->rows);
1861 if (proceed == MagickFalse)
1862 status=MagickFalse;
1863 }
1864 }
1865 image_view=DestroyCacheView(image_view);
cristyda1f9c12011-10-02 21:39:49 +00001866 gamma_map=(double *) RelinquishMagickMemory(gamma_map);
cristy3ed852e2009-09-05 21:47:34 +00001867 if (image->gamma != 0.0)
1868 image->gamma*=gamma;
1869 return(status);
1870}
1871
1872/*
1873%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1874% %
1875% %
1876% %
1877% H a l d C l u t I m a g e %
1878% %
1879% %
1880% %
1881%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1882%
1883% HaldClutImage() applies a Hald color lookup table to the image. A Hald
1884% color lookup table is a 3-dimensional color cube mapped to 2 dimensions.
1885% Create it with the HALD coder. You can apply any color transformation to
1886% the Hald image and then use this method to apply the transform to the
1887% image.
1888%
1889% The format of the HaldClutImage method is:
1890%
cristy7c0a0a42011-08-23 17:57:25 +00001891% MagickBooleanType HaldClutImage(Image *image,Image *hald_image,
1892% ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001893%
1894% A description of each parameter follows:
1895%
1896% o image: the image, which is replaced by indexed CLUT values
1897%
1898% o hald_image: the color lookup table image for replacement color values.
1899%
cristy7c0a0a42011-08-23 17:57:25 +00001900% o exception: return any errors or warnings in this structure.
1901%
cristy3ed852e2009-09-05 21:47:34 +00001902*/
1903
1904static inline size_t MagickMin(const size_t x,const size_t y)
1905{
1906 if (x < y)
1907 return(x);
1908 return(y);
1909}
1910
1911MagickExport MagickBooleanType HaldClutImage(Image *image,
cristy7c0a0a42011-08-23 17:57:25 +00001912 const Image *hald_image,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001913{
cristy3ed852e2009-09-05 21:47:34 +00001914#define HaldClutImageTag "Clut/Image"
1915
1916 typedef struct _HaldInfo
1917 {
1918 MagickRealType
1919 x,
1920 y,
1921 z;
1922 } HaldInfo;
1923
cristyfa112112010-01-04 17:48:07 +00001924 CacheView
cristyd551fbc2011-03-31 18:07:46 +00001925 *hald_view,
cristyfa112112010-01-04 17:48:07 +00001926 *image_view;
1927
cristy3ed852e2009-09-05 21:47:34 +00001928 double
1929 width;
1930
cristy3ed852e2009-09-05 21:47:34 +00001931 MagickBooleanType
1932 status;
1933
cristybb503372010-05-27 20:51:26 +00001934 MagickOffsetType
1935 progress;
1936
cristy4c08aed2011-07-01 19:47:50 +00001937 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00001938 zero;
1939
cristy3ed852e2009-09-05 21:47:34 +00001940 size_t
1941 cube_size,
1942 length,
1943 level;
1944
cristybb503372010-05-27 20:51:26 +00001945 ssize_t
1946 y;
1947
cristy3ed852e2009-09-05 21:47:34 +00001948 assert(image != (Image *) NULL);
1949 assert(image->signature == MagickSignature);
1950 if (image->debug != MagickFalse)
1951 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1952 assert(hald_image != (Image *) NULL);
1953 assert(hald_image->signature == MagickSignature);
cristy574cc262011-08-05 01:23:58 +00001954 if (SetImageStorageClass(image,DirectClass,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00001955 return(MagickFalse);
1956 if (image->matte == MagickFalse)
cristy63240882011-08-05 19:05:27 +00001957 (void) SetImageAlphaChannel(image,OpaqueAlphaChannel,exception);
cristy3ed852e2009-09-05 21:47:34 +00001958 /*
1959 Hald clut image.
1960 */
1961 status=MagickTrue;
1962 progress=0;
1963 length=MagickMin(hald_image->columns,hald_image->rows);
1964 for (level=2; (level*level*level) < length; level++) ;
1965 level*=level;
1966 cube_size=level*level;
1967 width=(double) hald_image->columns;
cristy4c08aed2011-07-01 19:47:50 +00001968 GetPixelInfo(hald_image,&zero);
cristy3ed852e2009-09-05 21:47:34 +00001969 image_view=AcquireCacheView(image);
cristyd551fbc2011-03-31 18:07:46 +00001970 hald_view=AcquireCacheView(hald_image);
cristyb5d5f722009-11-04 03:03:49 +00001971#if defined(MAGICKCORE_OPENMP_SUPPORT)
1972 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001973#endif
cristybb503372010-05-27 20:51:26 +00001974 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001975 {
cristy4c08aed2011-07-01 19:47:50 +00001976 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00001977 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001978
cristy8d4629b2010-08-30 17:59:46 +00001979 register ssize_t
1980 x;
1981
cristy3ed852e2009-09-05 21:47:34 +00001982 if (status == MagickFalse)
1983 continue;
1984 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristyacd2ed22011-08-30 01:44:23 +00001985 if (q == (Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001986 {
1987 status=MagickFalse;
1988 continue;
1989 }
cristybb503372010-05-27 20:51:26 +00001990 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001991 {
cristy7c0a0a42011-08-23 17:57:25 +00001992 double
1993 offset;
1994
1995 HaldInfo
1996 point;
1997
1998 PixelInfo
1999 pixel,
2000 pixel1,
2001 pixel2,
2002 pixel3,
2003 pixel4;
2004
cristy4c08aed2011-07-01 19:47:50 +00002005 point.x=QuantumScale*(level-1.0)*GetPixelRed(image,q);
2006 point.y=QuantumScale*(level-1.0)*GetPixelGreen(image,q);
2007 point.z=QuantumScale*(level-1.0)*GetPixelBlue(image,q);
cristy3ed852e2009-09-05 21:47:34 +00002008 offset=point.x+level*floor(point.y)+cube_size*floor(point.z);
2009 point.x-=floor(point.x);
2010 point.y-=floor(point.y);
2011 point.z-=floor(point.z);
cristy7c0a0a42011-08-23 17:57:25 +00002012 pixel1=zero;
2013 (void) InterpolatePixelInfo(image,hald_view,image->interpolate,
2014 fmod(offset,width),floor(offset/width),&pixel1,exception);
2015 pixel2=zero;
2016 (void) InterpolatePixelInfo(image,hald_view,image->interpolate,
2017 fmod(offset+level,width),floor((offset+level)/width),&pixel2,exception);
2018 pixel3=zero;
2019 CompositePixelInfoAreaBlend(&pixel1,pixel1.alpha,&pixel2,pixel2.alpha,
2020 point.y,&pixel3);
cristy3ed852e2009-09-05 21:47:34 +00002021 offset+=cube_size;
cristy7c0a0a42011-08-23 17:57:25 +00002022 (void) InterpolatePixelInfo(image,hald_view,image->interpolate,
2023 fmod(offset,width),floor(offset/width),&pixel1,exception);
2024 (void) InterpolatePixelInfo(image,hald_view,image->interpolate,
2025 fmod(offset+level,width),floor((offset+level)/width),&pixel2,exception);
2026 pixel4=zero;
2027 CompositePixelInfoAreaBlend(&pixel1,pixel1.alpha,&pixel2,pixel2.alpha,
2028 point.y,&pixel4);
2029 pixel=zero;
2030 CompositePixelInfoAreaBlend(&pixel3,pixel3.alpha,&pixel4,pixel4.alpha,
2031 point.z,&pixel);
cristyed231572011-07-14 02:18:59 +00002032 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristyf45fec72011-08-23 16:02:32 +00002033 SetPixelRed(image,ClampToQuantum(pixel.red),q);
cristyed231572011-07-14 02:18:59 +00002034 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristyf45fec72011-08-23 16:02:32 +00002035 SetPixelGreen(image,ClampToQuantum(pixel.green),q);
cristyed231572011-07-14 02:18:59 +00002036 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristyf45fec72011-08-23 16:02:32 +00002037 SetPixelBlue(image,ClampToQuantum(pixel.blue),q);
cristyed231572011-07-14 02:18:59 +00002038 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00002039 (image->colorspace == CMYKColorspace))
cristyf45fec72011-08-23 16:02:32 +00002040 SetPixelBlack(image,ClampToQuantum(pixel.black),q);
2041 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) &&
2042 (image->matte != MagickFalse))
2043 SetPixelAlpha(image,ClampToQuantum(pixel.alpha),q);
cristyed231572011-07-14 02:18:59 +00002044 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00002045 }
2046 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2047 status=MagickFalse;
2048 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2049 {
2050 MagickBooleanType
2051 proceed;
2052
cristyb5d5f722009-11-04 03:03:49 +00002053#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf89cb1d2011-07-07 01:24:37 +00002054 #pragma omp critical (MagickCore_HaldClutImage)
cristy3ed852e2009-09-05 21:47:34 +00002055#endif
2056 proceed=SetImageProgress(image,HaldClutImageTag,progress++,image->rows);
2057 if (proceed == MagickFalse)
2058 status=MagickFalse;
2059 }
2060 }
cristyd551fbc2011-03-31 18:07:46 +00002061 hald_view=DestroyCacheView(hald_view);
cristy3ed852e2009-09-05 21:47:34 +00002062 image_view=DestroyCacheView(image_view);
cristy3ed852e2009-09-05 21:47:34 +00002063 return(status);
2064}
2065
2066/*
2067%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2068% %
2069% %
2070% %
2071% L e v e l I m a g e %
2072% %
2073% %
2074% %
2075%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2076%
2077% LevelImage() adjusts the levels of a particular image channel by
2078% scaling the colors falling between specified white and black points to
2079% the full available quantum range.
2080%
2081% The parameters provided represent the black, and white points. The black
2082% point specifies the darkest color in the image. Colors darker than the
2083% black point are set to zero. White point specifies the lightest color in
2084% the image. Colors brighter than the white point are set to the maximum
2085% quantum value.
2086%
2087% If a '!' flag is given, map black and white colors to the given levels
2088% rather than mapping those levels to black and white. See
cristy50fbc382011-07-07 02:19:17 +00002089% LevelizeImage() below.
cristy3ed852e2009-09-05 21:47:34 +00002090%
2091% Gamma specifies a gamma correction to apply to the image.
2092%
2093% The format of the LevelImage method is:
2094%
cristy01e9afd2011-08-10 17:38:41 +00002095% MagickBooleanType LevelImage(Image *image,const double black_point,
2096% const double white_point,const double gamma,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002097%
2098% A description of each parameter follows:
2099%
2100% o image: the image.
2101%
cristy01e9afd2011-08-10 17:38:41 +00002102% o black_point: The level to map zero (black) to.
2103%
2104% o white_point: The level to map QuantumRange (white) to.
2105%
2106% o exception: return any errors or warnings in this structure.
cristy3ed852e2009-09-05 21:47:34 +00002107%
2108*/
cristy7c0a0a42011-08-23 17:57:25 +00002109MagickExport MagickBooleanType LevelImage(Image *image,const double black_point,
2110 const double white_point,const double gamma,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002111{
2112#define LevelImageTag "Level/Image"
cristybcfb0432010-05-06 01:45:33 +00002113#define LevelQuantum(x) (ClampToQuantum((MagickRealType) QuantumRange* \
cristyc1f508d2010-04-22 01:21:47 +00002114 pow(scale*((double) (x)-black_point),1.0/gamma)))
cristy3ed852e2009-09-05 21:47:34 +00002115
cristyc4c8d132010-01-07 01:58:38 +00002116 CacheView
2117 *image_view;
2118
cristy3ed852e2009-09-05 21:47:34 +00002119 MagickBooleanType
2120 status;
2121
cristybb503372010-05-27 20:51:26 +00002122 MagickOffsetType
2123 progress;
2124
anthony7fe39fc2010-04-06 03:19:20 +00002125 register double
2126 scale;
2127
cristy8d4629b2010-08-30 17:59:46 +00002128 register ssize_t
2129 i;
2130
cristybb503372010-05-27 20:51:26 +00002131 ssize_t
2132 y;
2133
cristy3ed852e2009-09-05 21:47:34 +00002134 /*
2135 Allocate and initialize levels map.
2136 */
2137 assert(image != (Image *) NULL);
2138 assert(image->signature == MagickSignature);
2139 if (image->debug != MagickFalse)
2140 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
cristy8d4629b2010-08-30 17:59:46 +00002141 scale=(white_point != black_point) ? 1.0/(white_point-black_point) : 1.0;
cristy3ed852e2009-09-05 21:47:34 +00002142 if (image->storage_class == PseudoClass)
cristyb5d5f722009-11-04 03:03:49 +00002143#if defined(MAGICKCORE_OPENMP_SUPPORT)
2144 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002145#endif
cristybb503372010-05-27 20:51:26 +00002146 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00002147 {
2148 /*
2149 Level colormap.
2150 */
cristyed231572011-07-14 02:18:59 +00002151 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristyda1f9c12011-10-02 21:39:49 +00002152 image->colormap[i].red=(double) LevelQuantum(
2153 image->colormap[i].red);
cristyed231572011-07-14 02:18:59 +00002154 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristyda1f9c12011-10-02 21:39:49 +00002155 image->colormap[i].green=(double) LevelQuantum(
2156 image->colormap[i].green);
cristyed231572011-07-14 02:18:59 +00002157 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristyda1f9c12011-10-02 21:39:49 +00002158 image->colormap[i].blue=(double) LevelQuantum(
2159 image->colormap[i].blue);
cristyed231572011-07-14 02:18:59 +00002160 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristyda1f9c12011-10-02 21:39:49 +00002161 image->colormap[i].alpha=(double) LevelQuantum(
2162 image->colormap[i].alpha);
cristy3ed852e2009-09-05 21:47:34 +00002163 }
2164 /*
2165 Level image.
2166 */
2167 status=MagickTrue;
2168 progress=0;
cristy3ed852e2009-09-05 21:47:34 +00002169 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00002170#if defined(MAGICKCORE_OPENMP_SUPPORT)
2171 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002172#endif
cristybb503372010-05-27 20:51:26 +00002173 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002174 {
cristy4c08aed2011-07-01 19:47:50 +00002175 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00002176 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002177
cristy8d4629b2010-08-30 17:59:46 +00002178 register ssize_t
2179 x;
2180
cristy3ed852e2009-09-05 21:47:34 +00002181 if (status == MagickFalse)
2182 continue;
2183 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristyacd2ed22011-08-30 01:44:23 +00002184 if (q == (Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00002185 {
2186 status=MagickFalse;
2187 continue;
2188 }
cristybb503372010-05-27 20:51:26 +00002189 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002190 {
cristy7c0a0a42011-08-23 17:57:25 +00002191 register ssize_t
2192 i;
2193
2194 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
2195 {
2196 PixelTrait
2197 traits;
2198
cristye2a912b2011-12-05 20:02:07 +00002199 traits=GetPixelChannelMapTraits(image,i);
cristy010d7d12011-08-31 01:02:48 +00002200 if ((traits == UndefinedPixelTrait) ||
2201 ((traits & UpdatePixelTrait) == 0))
cristy7c0a0a42011-08-23 17:57:25 +00002202 continue;
2203 q[i]=LevelQuantum(q[i]);
2204 }
cristyed231572011-07-14 02:18:59 +00002205 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00002206 }
2207 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2208 status=MagickFalse;
2209 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2210 {
2211 MagickBooleanType
2212 proceed;
2213
cristyb5d5f722009-11-04 03:03:49 +00002214#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf89cb1d2011-07-07 01:24:37 +00002215 #pragma omp critical (MagickCore_LevelImage)
cristy3ed852e2009-09-05 21:47:34 +00002216#endif
2217 proceed=SetImageProgress(image,LevelImageTag,progress++,image->rows);
2218 if (proceed == MagickFalse)
2219 status=MagickFalse;
2220 }
2221 }
2222 image_view=DestroyCacheView(image_view);
2223 return(status);
2224}
2225
2226/*
2227%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2228% %
2229% %
2230% %
cristy33bd5152011-08-24 01:42:24 +00002231% L e v e l i z e I m a g e %
cristy3ed852e2009-09-05 21:47:34 +00002232% %
2233% %
2234% %
2235%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2236%
cristy50fbc382011-07-07 02:19:17 +00002237% LevelizeImage() applies the reversed LevelImage() operation to just
cristy3ed852e2009-09-05 21:47:34 +00002238% the specific channels specified. It compresses the full range of color
2239% values, so that they lie between the given black and white points. Gamma is
2240% applied before the values are mapped.
2241%
cristy50fbc382011-07-07 02:19:17 +00002242% LevelizeImage() can be called with by using a +level command line
cristy3ed852e2009-09-05 21:47:34 +00002243% API option, or using a '!' on a -level or LevelImage() geometry string.
2244%
2245% It can be used for example de-contrast a greyscale image to the exact
2246% levels specified. Or by using specific levels for each channel of an image
2247% you can convert a gray-scale image to any linear color gradient, according
2248% to those levels.
2249%
cristy50fbc382011-07-07 02:19:17 +00002250% The format of the LevelizeImage method is:
cristy3ed852e2009-09-05 21:47:34 +00002251%
cristy50fbc382011-07-07 02:19:17 +00002252% MagickBooleanType LevelizeImage(Image *image,const double black_point,
cristy7c0a0a42011-08-23 17:57:25 +00002253% const double white_point,const double gamma,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002254%
2255% A description of each parameter follows:
2256%
2257% o image: the image.
2258%
cristy3ed852e2009-09-05 21:47:34 +00002259% o black_point: The level to map zero (black) to.
2260%
cristy01e9afd2011-08-10 17:38:41 +00002261% o white_point: The level to map QuantumRange (white) to.
cristy3ed852e2009-09-05 21:47:34 +00002262%
2263% o gamma: adjust gamma by this factor before mapping values.
2264%
cristy7c0a0a42011-08-23 17:57:25 +00002265% o exception: return any errors or warnings in this structure.
2266%
cristy3ed852e2009-09-05 21:47:34 +00002267*/
cristyd1a2c0f2011-02-09 14:14:50 +00002268MagickExport MagickBooleanType LevelizeImage(Image *image,
cristy7c0a0a42011-08-23 17:57:25 +00002269 const double black_point,const double white_point,const double gamma,
2270 ExceptionInfo *exception)
cristyd1a2c0f2011-02-09 14:14:50 +00002271{
cristy3ed852e2009-09-05 21:47:34 +00002272#define LevelizeImageTag "Levelize/Image"
cristyce70c172010-01-07 17:15:30 +00002273#define LevelizeValue(x) (ClampToQuantum(((MagickRealType) \
cristy50fbc382011-07-07 02:19:17 +00002274 pow((double) (QuantumScale*(x)),1.0/gamma))*(white_point-black_point)+ \
cristy3ed852e2009-09-05 21:47:34 +00002275 black_point))
2276
cristyc4c8d132010-01-07 01:58:38 +00002277 CacheView
2278 *image_view;
2279
cristy3ed852e2009-09-05 21:47:34 +00002280 MagickBooleanType
2281 status;
2282
cristybb503372010-05-27 20:51:26 +00002283 MagickOffsetType
2284 progress;
2285
2286 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002287 i;
2288
cristybb503372010-05-27 20:51:26 +00002289 ssize_t
2290 y;
2291
cristy3ed852e2009-09-05 21:47:34 +00002292 /*
2293 Allocate and initialize levels map.
2294 */
2295 assert(image != (Image *) NULL);
2296 assert(image->signature == MagickSignature);
2297 if (image->debug != MagickFalse)
2298 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2299 if (image->storage_class == PseudoClass)
cristyb5d5f722009-11-04 03:03:49 +00002300#if defined(MAGICKCORE_OPENMP_SUPPORT)
2301 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002302#endif
cristybb503372010-05-27 20:51:26 +00002303 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00002304 {
2305 /*
2306 Level colormap.
2307 */
cristyed231572011-07-14 02:18:59 +00002308 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristyda1f9c12011-10-02 21:39:49 +00002309 image->colormap[i].red=(double) LevelizeValue(
2310 image->colormap[i].red);
cristyed231572011-07-14 02:18:59 +00002311 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristyda1f9c12011-10-02 21:39:49 +00002312 image->colormap[i].green=(double) LevelizeValue(
2313 image->colormap[i].green);
cristyed231572011-07-14 02:18:59 +00002314 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristyda1f9c12011-10-02 21:39:49 +00002315 image->colormap[i].blue=(double) LevelizeValue(
2316 image->colormap[i].blue);
cristyed231572011-07-14 02:18:59 +00002317 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristyda1f9c12011-10-02 21:39:49 +00002318 image->colormap[i].alpha=(double) LevelizeValue(
2319 image->colormap[i].alpha);
cristy3ed852e2009-09-05 21:47:34 +00002320 }
2321 /*
2322 Level image.
2323 */
2324 status=MagickTrue;
2325 progress=0;
cristy3ed852e2009-09-05 21:47:34 +00002326 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00002327#if defined(MAGICKCORE_OPENMP_SUPPORT)
2328 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002329#endif
cristybb503372010-05-27 20:51:26 +00002330 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002331 {
cristy4c08aed2011-07-01 19:47:50 +00002332 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00002333 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002334
cristy8d4629b2010-08-30 17:59:46 +00002335 register ssize_t
2336 x;
2337
cristy3ed852e2009-09-05 21:47:34 +00002338 if (status == MagickFalse)
2339 continue;
2340 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristyacd2ed22011-08-30 01:44:23 +00002341 if (q == (Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00002342 {
2343 status=MagickFalse;
2344 continue;
2345 }
cristybb503372010-05-27 20:51:26 +00002346 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002347 {
cristy7c0a0a42011-08-23 17:57:25 +00002348 register ssize_t
2349 i;
2350
2351 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
2352 {
2353 PixelTrait
2354 traits;
2355
cristye2a912b2011-12-05 20:02:07 +00002356 traits=GetPixelChannelMapTraits(image,i);
cristy7c0a0a42011-08-23 17:57:25 +00002357 if ((traits & UpdatePixelTrait) != 0)
2358 q[i]=LevelizeValue(q[i]);
2359 }
cristyed231572011-07-14 02:18:59 +00002360 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00002361 }
2362 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2363 status=MagickFalse;
2364 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2365 {
2366 MagickBooleanType
2367 proceed;
2368
cristyb5d5f722009-11-04 03:03:49 +00002369#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy50fbc382011-07-07 02:19:17 +00002370 #pragma omp critical (MagickCore_LevelizeImage)
cristy3ed852e2009-09-05 21:47:34 +00002371#endif
2372 proceed=SetImageProgress(image,LevelizeImageTag,progress++,image->rows);
2373 if (proceed == MagickFalse)
2374 status=MagickFalse;
2375 }
2376 }
cristy8d4629b2010-08-30 17:59:46 +00002377 image_view=DestroyCacheView(image_view);
cristy3ed852e2009-09-05 21:47:34 +00002378 return(status);
2379}
2380
2381/*
2382%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2383% %
2384% %
2385% %
2386% L e v e l I m a g e C o l o r s %
2387% %
2388% %
2389% %
2390%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2391%
cristy490408a2011-07-07 14:42:05 +00002392% LevelImageColors() maps the given color to "black" and "white" values,
cristyee0f8d72009-09-19 00:58:29 +00002393% linearly spreading out the colors, and level values on a channel by channel
2394% bases, as per LevelImage(). The given colors allows you to specify
glennrp1e7f7bc2011-03-02 19:25:28 +00002395% different level ranges for each of the color channels separately.
cristy3ed852e2009-09-05 21:47:34 +00002396%
2397% If the boolean 'invert' is set true the image values will modifyed in the
2398% reverse direction. That is any existing "black" and "white" colors in the
2399% image will become the color values given, with all other values compressed
2400% appropriatally. This effectivally maps a greyscale gradient into the given
2401% color gradient.
2402%
cristy490408a2011-07-07 14:42:05 +00002403% The format of the LevelImageColors method is:
cristy3ed852e2009-09-05 21:47:34 +00002404%
cristy490408a2011-07-07 14:42:05 +00002405% MagickBooleanType LevelImageColors(Image *image,
2406% const PixelInfo *black_color,const PixelInfo *white_color,
cristy7c0a0a42011-08-23 17:57:25 +00002407% const MagickBooleanType invert,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002408%
2409% A description of each parameter follows:
2410%
2411% o image: the image.
2412%
cristy3ed852e2009-09-05 21:47:34 +00002413% o black_color: The color to map black to/from
2414%
2415% o white_point: The color to map white to/from
2416%
2417% o invert: if true map the colors (levelize), rather than from (level)
2418%
cristy7c0a0a42011-08-23 17:57:25 +00002419% o exception: return any errors or warnings in this structure.
2420%
cristy3ed852e2009-09-05 21:47:34 +00002421*/
cristy490408a2011-07-07 14:42:05 +00002422MagickExport MagickBooleanType LevelImageColors(Image *image,
cristy4c08aed2011-07-01 19:47:50 +00002423 const PixelInfo *black_color,const PixelInfo *white_color,
cristy7c0a0a42011-08-23 17:57:25 +00002424 const MagickBooleanType invert,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002425{
cristybd5a96c2011-08-21 00:04:26 +00002426 ChannelType
2427 channel_mask;
2428
cristy3ed852e2009-09-05 21:47:34 +00002429 MagickStatusType
2430 status;
2431
2432 /*
2433 Allocate and initialize levels map.
2434 */
2435 assert(image != (Image *) NULL);
2436 assert(image->signature == MagickSignature);
2437 if (image->debug != MagickFalse)
2438 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2439 status=MagickFalse;
2440 if (invert == MagickFalse)
2441 {
cristyed231572011-07-14 02:18:59 +00002442 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristyf89cb1d2011-07-07 01:24:37 +00002443 {
cristybd5a96c2011-08-21 00:04:26 +00002444 channel_mask=SetPixelChannelMask(image,RedChannel);
cristy01e9afd2011-08-10 17:38:41 +00002445 status|=LevelImage(image,black_color->red,white_color->red,1.0,
cristy7c0a0a42011-08-23 17:57:25 +00002446 exception);
cristybd5a96c2011-08-21 00:04:26 +00002447 (void) SetPixelChannelMask(image,channel_mask);
cristyf89cb1d2011-07-07 01:24:37 +00002448 }
cristyed231572011-07-14 02:18:59 +00002449 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristyf89cb1d2011-07-07 01:24:37 +00002450 {
cristybd5a96c2011-08-21 00:04:26 +00002451 channel_mask=SetPixelChannelMask(image,GreenChannel);
cristy01e9afd2011-08-10 17:38:41 +00002452 status|=LevelImage(image,black_color->green,white_color->green,1.0,
cristy7c0a0a42011-08-23 17:57:25 +00002453 exception);
cristybd5a96c2011-08-21 00:04:26 +00002454 (void) SetPixelChannelMask(image,channel_mask);
cristyf89cb1d2011-07-07 01:24:37 +00002455 }
cristyed231572011-07-14 02:18:59 +00002456 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristyf89cb1d2011-07-07 01:24:37 +00002457 {
cristybd5a96c2011-08-21 00:04:26 +00002458 channel_mask=SetPixelChannelMask(image,BlueChannel);
cristy01e9afd2011-08-10 17:38:41 +00002459 status|=LevelImage(image,black_color->blue,white_color->blue,1.0,
cristy7c0a0a42011-08-23 17:57:25 +00002460 exception);
cristybd5a96c2011-08-21 00:04:26 +00002461 (void) SetPixelChannelMask(image,channel_mask);
cristyf89cb1d2011-07-07 01:24:37 +00002462 }
cristyed231572011-07-14 02:18:59 +00002463 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +00002464 (image->colorspace == CMYKColorspace))
cristyf89cb1d2011-07-07 01:24:37 +00002465 {
cristybd5a96c2011-08-21 00:04:26 +00002466 channel_mask=SetPixelChannelMask(image,BlackChannel);
cristy01e9afd2011-08-10 17:38:41 +00002467 status|=LevelImage(image,black_color->black,white_color->black,1.0,
cristy7c0a0a42011-08-23 17:57:25 +00002468 exception);
cristybd5a96c2011-08-21 00:04:26 +00002469 (void) SetPixelChannelMask(image,channel_mask);
cristyf89cb1d2011-07-07 01:24:37 +00002470 }
cristyed231572011-07-14 02:18:59 +00002471 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00002472 (image->matte == MagickTrue))
cristyf89cb1d2011-07-07 01:24:37 +00002473 {
cristybd5a96c2011-08-21 00:04:26 +00002474 channel_mask=SetPixelChannelMask(image,AlphaChannel);
cristy01e9afd2011-08-10 17:38:41 +00002475 status|=LevelImage(image,black_color->alpha,white_color->alpha,1.0,
cristy7c0a0a42011-08-23 17:57:25 +00002476 exception);
cristybd5a96c2011-08-21 00:04:26 +00002477 (void) SetPixelChannelMask(image,channel_mask);
cristyf89cb1d2011-07-07 01:24:37 +00002478 }
cristy3ed852e2009-09-05 21:47:34 +00002479 }
2480 else
2481 {
cristyed231572011-07-14 02:18:59 +00002482 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristyf89cb1d2011-07-07 01:24:37 +00002483 {
cristybd5a96c2011-08-21 00:04:26 +00002484 channel_mask=SetPixelChannelMask(image,RedChannel);
cristy7c0a0a42011-08-23 17:57:25 +00002485 status|=LevelizeImage(image,black_color->red,white_color->red,1.0,
2486 exception);
cristybd5a96c2011-08-21 00:04:26 +00002487 (void) SetPixelChannelMask(image,channel_mask);
cristyf89cb1d2011-07-07 01:24:37 +00002488 }
cristyed231572011-07-14 02:18:59 +00002489 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristyf89cb1d2011-07-07 01:24:37 +00002490 {
cristybd5a96c2011-08-21 00:04:26 +00002491 channel_mask=SetPixelChannelMask(image,GreenChannel);
cristy7c0a0a42011-08-23 17:57:25 +00002492 status|=LevelizeImage(image,black_color->green,white_color->green,1.0,
2493 exception);
cristybd5a96c2011-08-21 00:04:26 +00002494 (void) SetPixelChannelMask(image,channel_mask);
cristyf89cb1d2011-07-07 01:24:37 +00002495 }
cristyed231572011-07-14 02:18:59 +00002496 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristyf89cb1d2011-07-07 01:24:37 +00002497 {
cristybd5a96c2011-08-21 00:04:26 +00002498 channel_mask=SetPixelChannelMask(image,BlueChannel);
cristy7c0a0a42011-08-23 17:57:25 +00002499 status|=LevelizeImage(image,black_color->blue,white_color->blue,1.0,
2500 exception);
cristybd5a96c2011-08-21 00:04:26 +00002501 (void) SetPixelChannelMask(image,channel_mask);
cristyf89cb1d2011-07-07 01:24:37 +00002502 }
cristyed231572011-07-14 02:18:59 +00002503 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +00002504 (image->colorspace == CMYKColorspace))
cristyf89cb1d2011-07-07 01:24:37 +00002505 {
cristybd5a96c2011-08-21 00:04:26 +00002506 channel_mask=SetPixelChannelMask(image,BlackChannel);
cristy7c0a0a42011-08-23 17:57:25 +00002507 status|=LevelizeImage(image,black_color->black,white_color->black,1.0,
2508 exception);
cristybd5a96c2011-08-21 00:04:26 +00002509 (void) SetPixelChannelMask(image,channel_mask);
cristyf89cb1d2011-07-07 01:24:37 +00002510 }
cristyed231572011-07-14 02:18:59 +00002511 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00002512 (image->matte == MagickTrue))
cristyf89cb1d2011-07-07 01:24:37 +00002513 {
cristybd5a96c2011-08-21 00:04:26 +00002514 channel_mask=SetPixelChannelMask(image,AlphaChannel);
cristy7c0a0a42011-08-23 17:57:25 +00002515 status|=LevelizeImage(image,black_color->alpha,white_color->alpha,1.0,
2516 exception);
cristybd5a96c2011-08-21 00:04:26 +00002517 (void) SetPixelChannelMask(image,channel_mask);
cristyf89cb1d2011-07-07 01:24:37 +00002518 }
cristy3ed852e2009-09-05 21:47:34 +00002519 }
2520 return(status == 0 ? MagickFalse : MagickTrue);
2521}
2522
2523/*
2524%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2525% %
2526% %
2527% %
2528% L i n e a r S t r e t c h I m a g e %
2529% %
2530% %
2531% %
2532%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2533%
cristyf1611782011-08-01 15:39:13 +00002534% LinearStretchImage() discards any pixels below the black point and above
2535% the white point and levels the remaining pixels.
cristy3ed852e2009-09-05 21:47:34 +00002536%
2537% The format of the LinearStretchImage method is:
2538%
2539% MagickBooleanType LinearStretchImage(Image *image,
cristy33bd5152011-08-24 01:42:24 +00002540% const double black_point,const double white_point,
2541% ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002542%
2543% A description of each parameter follows:
2544%
2545% o image: the image.
2546%
2547% o black_point: the black point.
2548%
2549% o white_point: the white point.
2550%
cristy33bd5152011-08-24 01:42:24 +00002551% o exception: return any errors or warnings in this structure.
2552%
cristy3ed852e2009-09-05 21:47:34 +00002553*/
2554MagickExport MagickBooleanType LinearStretchImage(Image *image,
cristy33bd5152011-08-24 01:42:24 +00002555 const double black_point,const double white_point,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002556{
2557#define LinearStretchImageTag "LinearStretch/Image"
2558
cristy33bd5152011-08-24 01:42:24 +00002559 CacheView
2560 *image_view;
cristy3ed852e2009-09-05 21:47:34 +00002561
cristy3ed852e2009-09-05 21:47:34 +00002562 MagickBooleanType
2563 status;
2564
2565 MagickRealType
2566 *histogram,
2567 intensity;
2568
cristy8d4629b2010-08-30 17:59:46 +00002569 ssize_t
2570 black,
2571 white,
2572 y;
2573
cristy3ed852e2009-09-05 21:47:34 +00002574 /*
2575 Allocate histogram and linear map.
2576 */
2577 assert(image != (Image *) NULL);
2578 assert(image->signature == MagickSignature);
2579 histogram=(MagickRealType *) AcquireQuantumMemory(MaxMap+1UL,
2580 sizeof(*histogram));
2581 if (histogram == (MagickRealType *) NULL)
2582 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
2583 image->filename);
2584 /*
2585 Form histogram.
2586 */
2587 (void) ResetMagickMemory(histogram,0,(MaxMap+1)*sizeof(*histogram));
cristy33bd5152011-08-24 01:42:24 +00002588 image_view=AcquireCacheView(image);
cristybb503372010-05-27 20:51:26 +00002589 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002590 {
cristy4c08aed2011-07-01 19:47:50 +00002591 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00002592 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00002593
cristybb503372010-05-27 20:51:26 +00002594 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002595 x;
2596
cristy33bd5152011-08-24 01:42:24 +00002597 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00002598 if (p == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00002599 break;
cristy33bd5152011-08-24 01:42:24 +00002600 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002601 {
cristy4c08aed2011-07-01 19:47:50 +00002602 histogram[ScaleQuantumToMap(GetPixelIntensity(image,p))]++;
cristyed231572011-07-14 02:18:59 +00002603 p+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00002604 }
2605 }
cristy33bd5152011-08-24 01:42:24 +00002606 image_view=DestroyCacheView(image_view);
cristy3ed852e2009-09-05 21:47:34 +00002607 /*
2608 Find the histogram boundaries by locating the black and white point levels.
2609 */
cristy3ed852e2009-09-05 21:47:34 +00002610 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00002611 for (black=0; black < (ssize_t) MaxMap; black++)
cristy3ed852e2009-09-05 21:47:34 +00002612 {
2613 intensity+=histogram[black];
2614 if (intensity >= black_point)
2615 break;
2616 }
2617 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00002618 for (white=(ssize_t) MaxMap; white != 0; white--)
cristy3ed852e2009-09-05 21:47:34 +00002619 {
2620 intensity+=histogram[white];
2621 if (intensity >= white_point)
2622 break;
2623 }
2624 histogram=(MagickRealType *) RelinquishMagickMemory(histogram);
cristyc82a27b2011-10-21 01:07:16 +00002625 status=LevelImage(image,(double) black,(double) white,1.0,exception);
cristy3ed852e2009-09-05 21:47:34 +00002626 return(status);
2627}
2628
2629/*
2630%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2631% %
2632% %
2633% %
2634% M o d u l a t e I m a g e %
2635% %
2636% %
2637% %
2638%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2639%
2640% ModulateImage() lets you control the brightness, saturation, and hue
2641% of an image. Modulate represents the brightness, saturation, and hue
2642% as one parameter (e.g. 90,150,100). If the image colorspace is HSL, the
2643% modulation is lightness, saturation, and hue. And if the colorspace is
2644% HWB, use blackness, whiteness, and hue.
2645%
2646% The format of the ModulateImage method is:
2647%
cristy33bd5152011-08-24 01:42:24 +00002648% MagickBooleanType ModulateImage(Image *image,const char *modulate,
2649% ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002650%
2651% A description of each parameter follows:
2652%
2653% o image: the image.
2654%
cristy33bd5152011-08-24 01:42:24 +00002655% o modulate: Define the percent change in brightness, saturation, and hue.
2656%
2657% o exception: return any errors or warnings in this structure.
cristy3ed852e2009-09-05 21:47:34 +00002658%
2659*/
2660
2661static void ModulateHSB(const double percent_hue,
cristy3094b7f2011-10-01 23:18:02 +00002662 const double percent_saturation,const double percent_brightness,double *red,
2663 double *green,double *blue)
cristy3ed852e2009-09-05 21:47:34 +00002664{
2665 double
2666 brightness,
2667 hue,
2668 saturation;
2669
2670 /*
2671 Increase or decrease color brightness, saturation, or hue.
2672 */
cristy3094b7f2011-10-01 23:18:02 +00002673 assert(red != (double *) NULL);
2674 assert(green != (double *) NULL);
2675 assert(blue != (double *) NULL);
cristy3ed852e2009-09-05 21:47:34 +00002676 ConvertRGBToHSB(*red,*green,*blue,&hue,&saturation,&brightness);
2677 hue+=0.5*(0.01*percent_hue-1.0);
2678 while (hue < 0.0)
2679 hue+=1.0;
2680 while (hue > 1.0)
2681 hue-=1.0;
2682 saturation*=0.01*percent_saturation;
2683 brightness*=0.01*percent_brightness;
2684 ConvertHSBToRGB(hue,saturation,brightness,red,green,blue);
2685}
2686
2687static void ModulateHSL(const double percent_hue,
cristy3094b7f2011-10-01 23:18:02 +00002688 const double percent_saturation,const double percent_lightness,double *red,
2689 double *green,double *blue)
cristy3ed852e2009-09-05 21:47:34 +00002690{
2691 double
2692 hue,
2693 lightness,
2694 saturation;
2695
2696 /*
2697 Increase or decrease color lightness, saturation, or hue.
2698 */
cristy3094b7f2011-10-01 23:18:02 +00002699 assert(red != (double *) NULL);
2700 assert(green != (double *) NULL);
2701 assert(blue != (double *) NULL);
cristy3ed852e2009-09-05 21:47:34 +00002702 ConvertRGBToHSL(*red,*green,*blue,&hue,&saturation,&lightness);
2703 hue+=0.5*(0.01*percent_hue-1.0);
2704 while (hue < 0.0)
2705 hue+=1.0;
2706 while (hue > 1.0)
2707 hue-=1.0;
2708 saturation*=0.01*percent_saturation;
2709 lightness*=0.01*percent_lightness;
2710 ConvertHSLToRGB(hue,saturation,lightness,red,green,blue);
2711}
2712
cristy3094b7f2011-10-01 23:18:02 +00002713static void ModulateHWB(const double percent_hue,const double percent_whiteness, const double percent_blackness,double *red,double *green,double *blue)
cristy3ed852e2009-09-05 21:47:34 +00002714{
2715 double
2716 blackness,
2717 hue,
2718 whiteness;
2719
2720 /*
2721 Increase or decrease color blackness, whiteness, or hue.
2722 */
cristy3094b7f2011-10-01 23:18:02 +00002723 assert(red != (double *) NULL);
2724 assert(green != (double *) NULL);
2725 assert(blue != (double *) NULL);
cristy3ed852e2009-09-05 21:47:34 +00002726 ConvertRGBToHWB(*red,*green,*blue,&hue,&whiteness,&blackness);
2727 hue+=0.5*(0.01*percent_hue-1.0);
2728 while (hue < 0.0)
2729 hue+=1.0;
2730 while (hue > 1.0)
2731 hue-=1.0;
2732 blackness*=0.01*percent_blackness;
2733 whiteness*=0.01*percent_whiteness;
2734 ConvertHWBToRGB(hue,whiteness,blackness,red,green,blue);
2735}
2736
cristy33bd5152011-08-24 01:42:24 +00002737MagickExport MagickBooleanType ModulateImage(Image *image,const char *modulate,
2738 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002739{
2740#define ModulateImageTag "Modulate/Image"
2741
cristyc4c8d132010-01-07 01:58:38 +00002742 CacheView
2743 *image_view;
2744
cristy3ed852e2009-09-05 21:47:34 +00002745 ColorspaceType
2746 colorspace;
2747
2748 const char
2749 *artifact;
2750
2751 double
2752 percent_brightness,
2753 percent_hue,
2754 percent_saturation;
2755
cristy3ed852e2009-09-05 21:47:34 +00002756 GeometryInfo
2757 geometry_info;
2758
cristy3ed852e2009-09-05 21:47:34 +00002759 MagickBooleanType
2760 status;
2761
cristybb503372010-05-27 20:51:26 +00002762 MagickOffsetType
2763 progress;
2764
cristy3ed852e2009-09-05 21:47:34 +00002765 MagickStatusType
2766 flags;
2767
cristybb503372010-05-27 20:51:26 +00002768 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002769 i;
2770
cristybb503372010-05-27 20:51:26 +00002771 ssize_t
2772 y;
2773
cristy3ed852e2009-09-05 21:47:34 +00002774 /*
cristy2b726bd2010-01-11 01:05:39 +00002775 Initialize modulate table.
cristy3ed852e2009-09-05 21:47:34 +00002776 */
2777 assert(image != (Image *) NULL);
2778 assert(image->signature == MagickSignature);
2779 if (image->debug != MagickFalse)
2780 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2781 if (modulate == (char *) NULL)
2782 return(MagickFalse);
2783 flags=ParseGeometry(modulate,&geometry_info);
2784 percent_brightness=geometry_info.rho;
2785 percent_saturation=geometry_info.sigma;
2786 if ((flags & SigmaValue) == 0)
2787 percent_saturation=100.0;
2788 percent_hue=geometry_info.xi;
2789 if ((flags & XiValue) == 0)
2790 percent_hue=100.0;
2791 colorspace=UndefinedColorspace;
2792 artifact=GetImageArtifact(image,"modulate:colorspace");
2793 if (artifact != (const char *) NULL)
cristy042ee782011-04-22 18:48:30 +00002794 colorspace=(ColorspaceType) ParseCommandOption(MagickColorspaceOptions,
cristy3ed852e2009-09-05 21:47:34 +00002795 MagickFalse,artifact);
2796 if (image->storage_class == PseudoClass)
2797 {
2798 /*
2799 Modulate colormap.
2800 */
cristyb5d5f722009-11-04 03:03:49 +00002801#if defined(MAGICKCORE_OPENMP_SUPPORT)
2802 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002803#endif
cristybb503372010-05-27 20:51:26 +00002804 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00002805 switch (colorspace)
2806 {
2807 case HSBColorspace:
2808 {
2809 ModulateHSB(percent_hue,percent_saturation,percent_brightness,
2810 &image->colormap[i].red,&image->colormap[i].green,
2811 &image->colormap[i].blue);
2812 break;
2813 }
2814 case HSLColorspace:
2815 default:
2816 {
2817 ModulateHSL(percent_hue,percent_saturation,percent_brightness,
2818 &image->colormap[i].red,&image->colormap[i].green,
2819 &image->colormap[i].blue);
2820 break;
2821 }
2822 case HWBColorspace:
2823 {
2824 ModulateHWB(percent_hue,percent_saturation,percent_brightness,
2825 &image->colormap[i].red,&image->colormap[i].green,
2826 &image->colormap[i].blue);
2827 break;
2828 }
2829 }
2830 }
2831 /*
2832 Modulate image.
2833 */
2834 status=MagickTrue;
2835 progress=0;
cristy3ed852e2009-09-05 21:47:34 +00002836 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00002837#if defined(MAGICKCORE_OPENMP_SUPPORT)
2838 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002839#endif
cristybb503372010-05-27 20:51:26 +00002840 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002841 {
cristy3094b7f2011-10-01 23:18:02 +00002842 double
cristy5afeab82011-04-30 01:30:09 +00002843 blue,
2844 green,
2845 red;
2846
cristy4c08aed2011-07-01 19:47:50 +00002847 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00002848 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002849
cristy8d4629b2010-08-30 17:59:46 +00002850 register ssize_t
2851 x;
2852
cristy3ed852e2009-09-05 21:47:34 +00002853 if (status == MagickFalse)
2854 continue;
2855 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristyacd2ed22011-08-30 01:44:23 +00002856 if (q == (Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00002857 {
2858 status=MagickFalse;
2859 continue;
2860 }
cristybb503372010-05-27 20:51:26 +00002861 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002862 {
cristyda1f9c12011-10-02 21:39:49 +00002863 red=(double) GetPixelRed(image,q);
2864 green=(double) GetPixelGreen(image,q);
2865 blue=(double) GetPixelBlue(image,q);
cristy3ed852e2009-09-05 21:47:34 +00002866 switch (colorspace)
2867 {
2868 case HSBColorspace:
2869 {
2870 ModulateHSB(percent_hue,percent_saturation,percent_brightness,
cristy5afeab82011-04-30 01:30:09 +00002871 &red,&green,&blue);
cristy3ed852e2009-09-05 21:47:34 +00002872 break;
2873 }
2874 case HSLColorspace:
2875 default:
2876 {
2877 ModulateHSL(percent_hue,percent_saturation,percent_brightness,
cristy5afeab82011-04-30 01:30:09 +00002878 &red,&green,&blue);
cristy3ed852e2009-09-05 21:47:34 +00002879 break;
2880 }
2881 case HWBColorspace:
2882 {
2883 ModulateHWB(percent_hue,percent_saturation,percent_brightness,
cristy5afeab82011-04-30 01:30:09 +00002884 &red,&green,&blue);
cristy3ed852e2009-09-05 21:47:34 +00002885 break;
2886 }
2887 }
cristy3094b7f2011-10-01 23:18:02 +00002888 SetPixelRed(image,ClampToQuantum(red),q);
2889 SetPixelGreen(image,ClampToQuantum(green),q);
2890 SetPixelBlue(image,ClampToQuantum(blue),q);
cristyed231572011-07-14 02:18:59 +00002891 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00002892 }
2893 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2894 status=MagickFalse;
2895 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2896 {
2897 MagickBooleanType
2898 proceed;
2899
cristyb5d5f722009-11-04 03:03:49 +00002900#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00002901 #pragma omp critical (MagickCore_ModulateImage)
2902#endif
2903 proceed=SetImageProgress(image,ModulateImageTag,progress++,image->rows);
2904 if (proceed == MagickFalse)
2905 status=MagickFalse;
2906 }
2907 }
2908 image_view=DestroyCacheView(image_view);
2909 return(status);
2910}
2911
2912/*
2913%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2914% %
2915% %
2916% %
2917% N e g a t e I m a g e %
2918% %
2919% %
2920% %
2921%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2922%
2923% NegateImage() negates the colors in the reference image. The grayscale
2924% option means that only grayscale values within the image are negated.
2925%
cristy50fbc382011-07-07 02:19:17 +00002926% The format of the NegateImage method is:
cristy3ed852e2009-09-05 21:47:34 +00002927%
2928% MagickBooleanType NegateImage(Image *image,
cristyb3e7c6c2011-07-24 01:43:55 +00002929% const MagickBooleanType grayscale,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002930%
2931% A description of each parameter follows:
2932%
2933% o image: the image.
2934%
cristy3ed852e2009-09-05 21:47:34 +00002935% o grayscale: If MagickTrue, only negate grayscale pixels within the image.
2936%
cristyb3e7c6c2011-07-24 01:43:55 +00002937% o exception: return any errors or warnings in this structure.
2938%
cristy3ed852e2009-09-05 21:47:34 +00002939*/
cristy3ed852e2009-09-05 21:47:34 +00002940MagickExport MagickBooleanType NegateImage(Image *image,
cristyb3e7c6c2011-07-24 01:43:55 +00002941 const MagickBooleanType grayscale,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002942{
cristy3ed852e2009-09-05 21:47:34 +00002943#define NegateImageTag "Negate/Image"
2944
cristyc4c8d132010-01-07 01:58:38 +00002945 CacheView
2946 *image_view;
2947
cristy3ed852e2009-09-05 21:47:34 +00002948 MagickBooleanType
2949 status;
2950
cristybb503372010-05-27 20:51:26 +00002951 MagickOffsetType
2952 progress;
2953
2954 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002955 i;
2956
cristybb503372010-05-27 20:51:26 +00002957 ssize_t
2958 y;
2959
cristy3ed852e2009-09-05 21:47:34 +00002960 assert(image != (Image *) NULL);
2961 assert(image->signature == MagickSignature);
2962 if (image->debug != MagickFalse)
2963 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2964 if (image->storage_class == PseudoClass)
2965 {
2966 /*
2967 Negate colormap.
2968 */
cristyb5d5f722009-11-04 03:03:49 +00002969#if defined(MAGICKCORE_OPENMP_SUPPORT)
2970 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002971#endif
cristybb503372010-05-27 20:51:26 +00002972 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00002973 {
2974 if (grayscale != MagickFalse)
2975 if ((image->colormap[i].red != image->colormap[i].green) ||
2976 (image->colormap[i].green != image->colormap[i].blue))
2977 continue;
cristyed231572011-07-14 02:18:59 +00002978 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00002979 image->colormap[i].red=(Quantum) QuantumRange-
2980 image->colormap[i].red;
cristyed231572011-07-14 02:18:59 +00002981 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00002982 image->colormap[i].green=(Quantum) QuantumRange-
2983 image->colormap[i].green;
cristyed231572011-07-14 02:18:59 +00002984 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00002985 image->colormap[i].blue=(Quantum) QuantumRange-
2986 image->colormap[i].blue;
2987 }
2988 }
2989 /*
2990 Negate image.
2991 */
2992 status=MagickTrue;
2993 progress=0;
cristy3ed852e2009-09-05 21:47:34 +00002994 image_view=AcquireCacheView(image);
2995 if (grayscale != MagickFalse)
2996 {
cristyb5d5f722009-11-04 03:03:49 +00002997#if defined(MAGICKCORE_OPENMP_SUPPORT)
2998 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002999#endif
cristybb503372010-05-27 20:51:26 +00003000 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003001 {
3002 MagickBooleanType
3003 sync;
3004
cristy4c08aed2011-07-01 19:47:50 +00003005 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00003006 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003007
cristy8d4629b2010-08-30 17:59:46 +00003008 register ssize_t
3009 x;
3010
cristy3ed852e2009-09-05 21:47:34 +00003011 if (status == MagickFalse)
3012 continue;
3013 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,
3014 exception);
cristyacd2ed22011-08-30 01:44:23 +00003015 if (q == (Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00003016 {
3017 status=MagickFalse;
3018 continue;
3019 }
cristybb503372010-05-27 20:51:26 +00003020 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003021 {
cristy9aa95be2011-07-20 21:56:45 +00003022 register ssize_t
3023 i;
3024
cristyd476a8e2011-07-23 16:13:22 +00003025 if (IsPixelGray(image,q) != MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00003026 {
cristya30d9ba2011-07-23 21:00:48 +00003027 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00003028 continue;
3029 }
cristya30d9ba2011-07-23 21:00:48 +00003030 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
cristy9aa95be2011-07-20 21:56:45 +00003031 {
cristy1aaa3cd2011-08-21 23:48:17 +00003032 PixelTrait
cristy9aa95be2011-07-20 21:56:45 +00003033 traits;
3034
cristye2a912b2011-12-05 20:02:07 +00003035 traits=GetPixelChannelMapTraits(image,i);
cristy9aa95be2011-07-20 21:56:45 +00003036 if ((traits & UpdatePixelTrait) != 0)
3037 q[i]=QuantumRange-q[i];
3038 }
cristya30d9ba2011-07-23 21:00:48 +00003039 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00003040 }
3041 sync=SyncCacheViewAuthenticPixels(image_view,exception);
3042 if (sync == MagickFalse)
3043 status=MagickFalse;
3044 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3045 {
3046 MagickBooleanType
3047 proceed;
3048
cristyb5d5f722009-11-04 03:03:49 +00003049#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy50fbc382011-07-07 02:19:17 +00003050 #pragma omp critical (MagickCore_NegateImage)
cristy3ed852e2009-09-05 21:47:34 +00003051#endif
3052 proceed=SetImageProgress(image,NegateImageTag,progress++,
3053 image->rows);
3054 if (proceed == MagickFalse)
3055 status=MagickFalse;
3056 }
3057 }
3058 image_view=DestroyCacheView(image_view);
3059 return(MagickTrue);
3060 }
3061 /*
3062 Negate image.
3063 */
cristyb5d5f722009-11-04 03:03:49 +00003064#if defined(MAGICKCORE_OPENMP_SUPPORT)
3065 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003066#endif
cristybb503372010-05-27 20:51:26 +00003067 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003068 {
cristy4c08aed2011-07-01 19:47:50 +00003069 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00003070 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003071
cristy8d4629b2010-08-30 17:59:46 +00003072 register ssize_t
3073 x;
3074
cristy3ed852e2009-09-05 21:47:34 +00003075 if (status == MagickFalse)
3076 continue;
3077 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristyacd2ed22011-08-30 01:44:23 +00003078 if (q == (Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00003079 {
3080 status=MagickFalse;
3081 continue;
3082 }
cristybb503372010-05-27 20:51:26 +00003083 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003084 {
cristyf7dc44c2011-07-20 14:41:15 +00003085 register ssize_t
3086 i;
3087
cristya30d9ba2011-07-23 21:00:48 +00003088 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
cristyf7dc44c2011-07-20 14:41:15 +00003089 {
cristy1aaa3cd2011-08-21 23:48:17 +00003090 PixelTrait
cristyf7dc44c2011-07-20 14:41:15 +00003091 traits;
3092
cristye2a912b2011-12-05 20:02:07 +00003093 traits=GetPixelChannelMapTraits(image,i);
cristyf7dc44c2011-07-20 14:41:15 +00003094 if ((traits & UpdatePixelTrait) != 0)
3095 q[i]=QuantumRange-q[i];
3096 }
cristya30d9ba2011-07-23 21:00:48 +00003097 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00003098 }
3099 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
3100 status=MagickFalse;
3101 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3102 {
3103 MagickBooleanType
3104 proceed;
3105
cristyb5d5f722009-11-04 03:03:49 +00003106#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy50fbc382011-07-07 02:19:17 +00003107 #pragma omp critical (MagickCore_NegateImage)
cristy3ed852e2009-09-05 21:47:34 +00003108#endif
3109 proceed=SetImageProgress(image,NegateImageTag,progress++,image->rows);
3110 if (proceed == MagickFalse)
3111 status=MagickFalse;
3112 }
3113 }
3114 image_view=DestroyCacheView(image_view);
3115 return(status);
3116}
3117
3118/*
3119%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3120% %
3121% %
3122% %
3123% N o r m a l i z e I m a g e %
3124% %
3125% %
3126% %
3127%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3128%
cristya4dfb122011-07-07 19:01:57 +00003129% NormalizeImage() enhances the contrast of a color image by mapping the
3130% darkest 2 percent of all pixel to black and the brightest 1 percent to white.
cristy3ed852e2009-09-05 21:47:34 +00003131%
3132% The format of the NormalizeImage method is:
3133%
cristye23ec9d2011-08-16 18:15:40 +00003134% MagickBooleanType NormalizeImage(Image *image,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003135%
3136% A description of each parameter follows:
3137%
3138% o image: the image.
3139%
cristye23ec9d2011-08-16 18:15:40 +00003140% o exception: return any errors or warnings in this structure.
3141%
cristy3ed852e2009-09-05 21:47:34 +00003142*/
cristye23ec9d2011-08-16 18:15:40 +00003143MagickExport MagickBooleanType NormalizeImage(Image *image,
3144 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003145{
cristy3ed852e2009-09-05 21:47:34 +00003146 double
3147 black_point,
3148 white_point;
3149
cristy530239c2010-07-25 17:34:26 +00003150 black_point=(double) image->columns*image->rows*0.0015;
3151 white_point=(double) image->columns*image->rows*0.9995;
cristye23ec9d2011-08-16 18:15:40 +00003152 return(ContrastStretchImage(image,black_point,white_point,exception));
cristy3ed852e2009-09-05 21:47:34 +00003153}
3154
3155/*
3156%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3157% %
3158% %
3159% %
3160% S i g m o i d a l C o n t r a s t I m a g e %
3161% %
3162% %
3163% %
3164%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3165%
3166% SigmoidalContrastImage() adjusts the contrast of an image with a non-linear
3167% sigmoidal contrast algorithm. Increase the contrast of the image using a
3168% sigmoidal transfer function without saturating highlights or shadows.
3169% Contrast indicates how much to increase the contrast (0 is none; 3 is
3170% typical; 20 is pushing it); mid-point indicates where midtones fall in the
3171% resultant image (0 is white; 50% is middle-gray; 100% is black). Set
3172% sharpen to MagickTrue to increase the image contrast otherwise the contrast
3173% is reduced.
3174%
3175% The format of the SigmoidalContrastImage method is:
3176%
3177% MagickBooleanType SigmoidalContrastImage(Image *image,
cristy33bd5152011-08-24 01:42:24 +00003178% const MagickBooleanType sharpen,const char *levels,
3179% ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003180%
3181% A description of each parameter follows:
3182%
3183% o image: the image.
3184%
cristy3ed852e2009-09-05 21:47:34 +00003185% o sharpen: Increase or decrease image contrast.
3186%
cristyfa769582010-09-30 23:30:03 +00003187% o alpha: strength of the contrast, the larger the number the more
3188% 'threshold-like' it becomes.
cristy3ed852e2009-09-05 21:47:34 +00003189%
cristyfa769582010-09-30 23:30:03 +00003190% o beta: midpoint of the function as a color value 0 to QuantumRange.
cristy3ed852e2009-09-05 21:47:34 +00003191%
cristy33bd5152011-08-24 01:42:24 +00003192% o exception: return any errors or warnings in this structure.
3193%
cristy3ed852e2009-09-05 21:47:34 +00003194*/
cristy3ed852e2009-09-05 21:47:34 +00003195MagickExport MagickBooleanType SigmoidalContrastImage(Image *image,
cristy33bd5152011-08-24 01:42:24 +00003196 const MagickBooleanType sharpen,const double contrast,const double midpoint,
3197 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003198{
3199#define SigmoidalContrastImageTag "SigmoidalContrast/Image"
3200
cristyc4c8d132010-01-07 01:58:38 +00003201 CacheView
3202 *image_view;
3203
cristy3ed852e2009-09-05 21:47:34 +00003204 MagickBooleanType
3205 status;
3206
cristybb503372010-05-27 20:51:26 +00003207 MagickOffsetType
3208 progress;
3209
cristy3ed852e2009-09-05 21:47:34 +00003210 MagickRealType
3211 *sigmoidal_map;
3212
cristybb503372010-05-27 20:51:26 +00003213 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003214 i;
3215
cristybb503372010-05-27 20:51:26 +00003216 ssize_t
3217 y;
3218
cristy3ed852e2009-09-05 21:47:34 +00003219 /*
3220 Allocate and initialize sigmoidal maps.
3221 */
3222 assert(image != (Image *) NULL);
3223 assert(image->signature == MagickSignature);
3224 if (image->debug != MagickFalse)
3225 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3226 sigmoidal_map=(MagickRealType *) AcquireQuantumMemory(MaxMap+1UL,
3227 sizeof(*sigmoidal_map));
3228 if (sigmoidal_map == (MagickRealType *) NULL)
3229 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
3230 image->filename);
3231 (void) ResetMagickMemory(sigmoidal_map,0,(MaxMap+1)*sizeof(*sigmoidal_map));
cristyb5d5f722009-11-04 03:03:49 +00003232#if defined(MAGICKCORE_OPENMP_SUPPORT)
3233 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003234#endif
cristybb503372010-05-27 20:51:26 +00003235 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +00003236 {
3237 if (sharpen != MagickFalse)
3238 {
3239 sigmoidal_map[i]=(MagickRealType) ScaleMapToQuantum((MagickRealType)
3240 (MaxMap*((1.0/(1.0+exp(contrast*(midpoint/(double) QuantumRange-
cristy33bd5152011-08-24 01:42:24 +00003241 (double) i/MaxMap))))-(1.0/(1.0+exp(contrast*(midpoint/(double)
3242 QuantumRange)))))/((1.0/(1.0+exp(contrast*(midpoint/(double)
3243 QuantumRange-1.0))))-(1.0/(1.0+exp(contrast*(midpoint/(double)
3244 QuantumRange)))))+0.5));
cristy3ed852e2009-09-05 21:47:34 +00003245 continue;
3246 }
3247 sigmoidal_map[i]=(MagickRealType) ScaleMapToQuantum((MagickRealType)
cristy33bd5152011-08-24 01:42:24 +00003248 (MaxMap*(QuantumScale*midpoint-log((1.0-(1.0/(1.0+exp(midpoint/(double)
3249 QuantumRange*contrast))+((double) i/MaxMap)*((1.0/(1.0+exp(contrast*(
3250 midpoint/(double) QuantumRange-1.0))))-(1.0/(1.0+exp(midpoint/(double)
3251 QuantumRange*contrast))))))/(1.0/(1.0+exp(midpoint/(double) QuantumRange*
3252 contrast))+((double) i/MaxMap)*((1.0/(1.0+exp(contrast*(midpoint/(double)
3253 QuantumRange-1.0))))-(1.0/(1.0+exp(midpoint/(double) QuantumRange*
3254 contrast))))))/contrast)));
cristy3ed852e2009-09-05 21:47:34 +00003255 }
3256 if (image->storage_class == PseudoClass)
3257 {
3258 /*
3259 Sigmoidal-contrast enhance colormap.
3260 */
cristyb5d5f722009-11-04 03:03:49 +00003261#if defined(MAGICKCORE_OPENMP_SUPPORT)
3262 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003263#endif
cristybb503372010-05-27 20:51:26 +00003264 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00003265 {
cristyed231572011-07-14 02:18:59 +00003266 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristyda1f9c12011-10-02 21:39:49 +00003267 image->colormap[i].red=sigmoidal_map[ScaleQuantumToMap(
3268 ClampToQuantum(image->colormap[i].red))];
cristyed231572011-07-14 02:18:59 +00003269 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristyda1f9c12011-10-02 21:39:49 +00003270 image->colormap[i].green=sigmoidal_map[ScaleQuantumToMap(
3271 ClampToQuantum(image->colormap[i].green))];
cristyed231572011-07-14 02:18:59 +00003272 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristyda1f9c12011-10-02 21:39:49 +00003273 image->colormap[i].blue=sigmoidal_map[ScaleQuantumToMap(
3274 ClampToQuantum(image->colormap[i].blue))];
cristyed231572011-07-14 02:18:59 +00003275 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristyda1f9c12011-10-02 21:39:49 +00003276 image->colormap[i].alpha=sigmoidal_map[ScaleQuantumToMap(
3277 ClampToQuantum(image->colormap[i].alpha))];
cristy3ed852e2009-09-05 21:47:34 +00003278 }
3279 }
3280 /*
3281 Sigmoidal-contrast enhance image.
3282 */
3283 status=MagickTrue;
3284 progress=0;
cristy3ed852e2009-09-05 21:47:34 +00003285 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00003286#if defined(MAGICKCORE_OPENMP_SUPPORT)
3287 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003288#endif
cristybb503372010-05-27 20:51:26 +00003289 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003290 {
cristy4c08aed2011-07-01 19:47:50 +00003291 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00003292 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003293
cristy8d4629b2010-08-30 17:59:46 +00003294 register ssize_t
3295 x;
3296
cristy3ed852e2009-09-05 21:47:34 +00003297 if (status == MagickFalse)
3298 continue;
3299 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristyacd2ed22011-08-30 01:44:23 +00003300 if (q == (Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00003301 {
3302 status=MagickFalse;
3303 continue;
3304 }
cristybb503372010-05-27 20:51:26 +00003305 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003306 {
cristy33bd5152011-08-24 01:42:24 +00003307 register ssize_t
3308 i;
3309
3310 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
3311 {
3312 PixelTrait
3313 traits;
3314
cristye2a912b2011-12-05 20:02:07 +00003315 traits=GetPixelChannelMapTraits(image,i);
cristy33bd5152011-08-24 01:42:24 +00003316 if ((traits & UpdatePixelTrait) != 0)
3317 q[i]=ClampToQuantum(sigmoidal_map[ScaleQuantumToMap(q[i])]);
3318 }
cristyed231572011-07-14 02:18:59 +00003319 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00003320 }
3321 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
3322 status=MagickFalse;
3323 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3324 {
3325 MagickBooleanType
3326 proceed;
3327
cristyb5d5f722009-11-04 03:03:49 +00003328#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy9ee60942011-07-06 14:54:38 +00003329 #pragma omp critical (MagickCore_SigmoidalContrastImage)
cristy3ed852e2009-09-05 21:47:34 +00003330#endif
3331 proceed=SetImageProgress(image,SigmoidalContrastImageTag,progress++,
3332 image->rows);
3333 if (proceed == MagickFalse)
3334 status=MagickFalse;
3335 }
3336 }
3337 image_view=DestroyCacheView(image_view);
3338 sigmoidal_map=(MagickRealType *) RelinquishMagickMemory(sigmoidal_map);
3339 return(status);
3340}