blob: 3075c46c305aa78b8c4af468416347957bbea3dd [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
cristyabace412011-12-11 15:56:53 +0000137 PixelChannel
138 channel;
139
cristy01e9afd2011-08-10 17:38:41 +0000140 PixelTrait
141 traits;
142
cristyabace412011-12-11 15:56:53 +0000143 channel=GetPixelChannelMapChannel(image,i);
144 traits=GetPixelChannelMapTraits(image,channel);
cristy01e9afd2011-08-10 17:38:41 +0000145 if ((traits & UpdatePixelTrait) == 0)
146 continue;
cristya13d0822011-09-19 00:19:19 +0000147 channel_mask=SetPixelChannelMask(image,(ChannelType) (1 << i));
cristy01e9afd2011-08-10 17:38:41 +0000148 status=GetImageMean(image,&mean,&sans,exception);
149 gamma=log(mean*QuantumScale)/log_mean;
150 status&=LevelImage(image,0.0,(double) QuantumRange,gamma,exception);
cristybd5a96c2011-08-21 00:04:26 +0000151 (void) SetPixelChannelMask(image,channel_mask);
cristy01e9afd2011-08-10 17:38:41 +0000152 if (status == MagickFalse)
153 break;
154 }
cristy3ed852e2009-09-05 21:47:34 +0000155 return(status != 0 ? MagickTrue : MagickFalse);
156}
157
158/*
159%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
160% %
161% %
162% %
163% A u t o L e v e l I m a g e %
164% %
165% %
166% %
167%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
168%
169% AutoLevelImage() adjusts the levels of a particular image channel by
170% scaling the minimum and maximum values to the full quantum range.
171%
172% The format of the LevelImage method is:
173%
cristy95111202011-08-09 19:41:42 +0000174% MagickBooleanType AutoLevelImage(Image *image,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000175%
176% A description of each parameter follows:
177%
178% o image: The image to auto-level
179%
cristy95111202011-08-09 19:41:42 +0000180% o exception: return any errors or warnings in this structure.
181%
cristy3ed852e2009-09-05 21:47:34 +0000182*/
cristy95111202011-08-09 19:41:42 +0000183MagickExport MagickBooleanType AutoLevelImage(Image *image,
184 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000185{
cristyb303c3d2011-09-09 11:24:40 +0000186 return(MinMaxStretchImage(image,0.0,0.0,1.0,exception));
cristy3ed852e2009-09-05 21:47:34 +0000187}
188
189/*
190%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
191% %
192% %
193% %
cristya28d6b82010-01-11 20:03:47 +0000194% B r i g h t n e s s C o n t r a s t I m a g e %
195% %
196% %
197% %
198%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
199%
cristyf4356f92011-08-01 15:33:48 +0000200% BrightnessContrastImage() changes the brightness and/or contrast of an
201% image. It converts the brightness and contrast parameters into slope and
202% intercept and calls a polynomical function to apply to the image.
cristya28d6b82010-01-11 20:03:47 +0000203%
204% The format of the BrightnessContrastImage method is:
205%
206% MagickBooleanType BrightnessContrastImage(Image *image,
cristy444eda62011-08-10 02:07:46 +0000207% const double brightness,const double contrast,ExceptionInfo *exception)
cristya28d6b82010-01-11 20:03:47 +0000208%
209% A description of each parameter follows:
210%
211% o image: the image.
212%
cristya28d6b82010-01-11 20:03:47 +0000213% o brightness: the brightness percent (-100 .. 100).
214%
215% o contrast: the contrast percent (-100 .. 100).
216%
cristy444eda62011-08-10 02:07:46 +0000217% o exception: return any errors or warnings in this structure.
218%
cristya28d6b82010-01-11 20:03:47 +0000219*/
cristya28d6b82010-01-11 20:03:47 +0000220MagickExport MagickBooleanType BrightnessContrastImage(Image *image,
cristy444eda62011-08-10 02:07:46 +0000221 const double brightness,const double contrast,ExceptionInfo *exception)
cristya28d6b82010-01-11 20:03:47 +0000222{
cristya28d6b82010-01-11 20:03:47 +0000223#define BrightnessContastImageTag "BrightnessContast/Image"
224
225 double
226 alpha,
cristya28d6b82010-01-11 20:03:47 +0000227 coefficients[2],
cristye23ec9d2011-08-16 18:15:40 +0000228 intercept,
cristya28d6b82010-01-11 20:03:47 +0000229 slope;
230
231 MagickBooleanType
232 status;
233
234 /*
235 Compute slope and intercept.
236 */
237 assert(image != (Image *) NULL);
238 assert(image->signature == MagickSignature);
239 if (image->debug != MagickFalse)
240 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
241 alpha=contrast;
cristy4205a3c2010-09-12 20:19:59 +0000242 slope=tan((double) (MagickPI*(alpha/100.0+1.0)/4.0));
cristya28d6b82010-01-11 20:03:47 +0000243 if (slope < 0.0)
244 slope=0.0;
245 intercept=brightness/100.0+((100-brightness)/200.0)*(1.0-slope);
246 coefficients[0]=slope;
247 coefficients[1]=intercept;
cristy444eda62011-08-10 02:07:46 +0000248 status=FunctionImage(image,PolynomialFunction,2,coefficients,exception);
249 return(status);
250}
251
252/*
253%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
254% %
255% %
256% %
257% C l u t I m a g e %
258% %
259% %
260% %
261%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
262%
263% ClutImage() replaces each color value in the given image, by using it as an
264% index to lookup a replacement color value in a Color Look UP Table in the
265% form of an image. The values are extracted along a diagonal of the CLUT
266% image so either a horizontal or vertial gradient image can be used.
267%
268% Typically this is used to either re-color a gray-scale image according to a
269% color gradient in the CLUT image, or to perform a freeform histogram
270% (level) adjustment according to the (typically gray-scale) gradient in the
271% CLUT image.
272%
273% When the 'channel' mask includes the matte/alpha transparency channel but
274% one image has no such channel it is assumed that that image is a simple
275% gray-scale image that will effect the alpha channel values, either for
276% gray-scale coloring (with transparent or semi-transparent colors), or
277% a histogram adjustment of existing alpha channel values. If both images
278% have matte channels, direct and normal indexing is applied, which is rarely
279% used.
280%
281% The format of the ClutImage method is:
282%
283% MagickBooleanType ClutImage(Image *image,Image *clut_image,
cristy5c4e2582011-09-11 19:21:03 +0000284% const PixelInterpolateMethod method,ExceptionInfo *exception)
cristy444eda62011-08-10 02:07:46 +0000285%
286% A description of each parameter follows:
287%
288% o image: the image, which is replaced by indexed CLUT values
289%
290% o clut_image: the color lookup table image for replacement color values.
291%
cristy5c4e2582011-09-11 19:21:03 +0000292% o method: the pixel interpolation method.
cristy444eda62011-08-10 02:07:46 +0000293%
294% o exception: return any errors or warnings in this structure.
295%
296*/
297MagickExport MagickBooleanType ClutImage(Image *image,const Image *clut_image,
cristy5c4e2582011-09-11 19:21:03 +0000298 const PixelInterpolateMethod method,ExceptionInfo *exception)
cristy444eda62011-08-10 02:07:46 +0000299{
cristy444eda62011-08-10 02:07:46 +0000300#define ClutImageTag "Clut/Image"
301
302 CacheView
303 *clut_view,
304 *image_view;
305
306 double
307 *clut_map;
308
309 MagickBooleanType
310 status;
311
312 MagickOffsetType
313 progress;
314
315 register ssize_t
316 x;
317
318 ssize_t
319 adjust,
320 y;
321
322 assert(image != (Image *) NULL);
323 assert(image->signature == MagickSignature);
324 if (image->debug != MagickFalse)
325 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
326 assert(clut_image != (Image *) NULL);
327 assert(clut_image->signature == MagickSignature);
328 if (SetImageStorageClass(image,DirectClass,exception) == MagickFalse)
329 return(MagickFalse);
330 clut_map=(double *) AcquireQuantumMemory(MaxMap+1UL,GetPixelChannels(image)*
331 sizeof(*clut_map));
332 if (clut_map == (double *) NULL)
333 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
334 image->filename);
335 /*
336 Clut image.
337 */
338 status=MagickTrue;
339 progress=0;
340 adjust=(ssize_t) (clut_image->interpolate == IntegerInterpolatePixel ? 0 : 1);
341 clut_view=AcquireCacheView(clut_image);
342#if defined(MAGICKCORE_OPENMP_SUPPORT)
343 #pragma omp parallel for schedule(dynamic,4)
344#endif
345 for (x=0; x <= (ssize_t) MaxMap; x++)
346 {
347 register ssize_t
348 i;
349
350 for (i=0; i < (ssize_t) GetPixelChannels(clut_image); i++)
351 (void) InterpolatePixelChannel(clut_image,clut_view,(PixelChannel) i,
cristy5c4e2582011-09-11 19:21:03 +0000352 method,QuantumScale*x*(clut_image->columns-adjust),QuantumScale*x*
353 (clut_image->rows-adjust),clut_map+x*GetPixelChannels(clut_image)+i,
354 exception);
cristy444eda62011-08-10 02:07:46 +0000355 }
356 clut_view=DestroyCacheView(clut_view);
357 image_view=AcquireCacheView(image);
358#if defined(MAGICKCORE_OPENMP_SUPPORT)
359 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
360#endif
361 for (y=0; y < (ssize_t) image->rows; y++)
362 {
363 register Quantum
364 *restrict q;
365
366 register ssize_t
367 x;
368
369 if (status == MagickFalse)
370 continue;
371 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
372 if (q == (Quantum *) NULL)
373 {
374 status=MagickFalse;
375 continue;
376 }
377 for (x=0; x < (ssize_t) image->columns; x++)
378 {
379 register ssize_t
380 i;
381
382 for (i=0; i < (ssize_t) GetPixelChannels(clut_image); i++)
383 {
384 PixelChannel
385 channel;
386
cristy1aaa3cd2011-08-21 23:48:17 +0000387 PixelTrait
cristy444eda62011-08-10 02:07:46 +0000388 clut_traits,
389 traits;
390
cristye2a912b2011-12-05 20:02:07 +0000391 channel=GetPixelChannelMapChannel(clut_image,i);
cristyabace412011-12-11 15:56:53 +0000392 clut_traits=GetPixelChannelMapTraits(clut_image,channel);
cristy444eda62011-08-10 02:07:46 +0000393 traits=GetPixelChannelMapTraits(clut_image,channel);
cristy010d7d12011-08-31 01:02:48 +0000394 if ((traits == UndefinedPixelTrait) ||
395 (clut_traits == UndefinedPixelTrait) ||
396 ((traits & UpdatePixelTrait) == 0))
397 continue;
cristy0beccfa2011-09-25 20:47:53 +0000398 SetPixelChannel(clut_image,channel,ClampToQuantum(clut_map[
399 ScaleQuantumToMap(GetPixelChannel(clut_image,channel,q))*
400 GetPixelChannels(clut_image)+channel]),q);
cristy444eda62011-08-10 02:07:46 +0000401 }
402 q+=GetPixelChannels(image);
403 }
404 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
405 status=MagickFalse;
406 if (image->progress_monitor != (MagickProgressMonitor) NULL)
407 {
408 MagickBooleanType
409 proceed;
410
411#if defined(MAGICKCORE_OPENMP_SUPPORT)
412 #pragma omp critical (MagickCore_ClutImage)
413#endif
414 proceed=SetImageProgress(image,ClutImageTag,progress++,image->rows);
415 if (proceed == MagickFalse)
416 status=MagickFalse;
417 }
418 }
419 image_view=DestroyCacheView(image_view);
420 clut_map=(double *) RelinquishMagickMemory(clut_map);
421 if ((clut_image->matte != MagickFalse) &&
422 ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0))
423 (void) SetImageAlphaChannel(image,ActivateAlphaChannel,exception);
cristya28d6b82010-01-11 20:03:47 +0000424 return(status);
425}
426
427/*
428%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
429% %
430% %
431% %
cristy3ed852e2009-09-05 21:47:34 +0000432% C o l o r D e c i s i o n L i s t I m a g e %
433% %
434% %
435% %
436%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
437%
438% ColorDecisionListImage() accepts a lightweight Color Correction Collection
439% (CCC) file which solely contains one or more color corrections and applies
440% the correction to the image. Here is a sample CCC file:
441%
442% <ColorCorrectionCollection xmlns="urn:ASC:CDL:v1.2">
443% <ColorCorrection id="cc03345">
444% <SOPNode>
445% <Slope> 0.9 1.2 0.5 </Slope>
446% <Offset> 0.4 -0.5 0.6 </Offset>
447% <Power> 1.0 0.8 1.5 </Power>
448% </SOPNode>
449% <SATNode>
450% <Saturation> 0.85 </Saturation>
451% </SATNode>
452% </ColorCorrection>
453% </ColorCorrectionCollection>
454%
455% which includes the slop, offset, and power for each of the RGB channels
456% as well as the saturation.
457%
458% The format of the ColorDecisionListImage method is:
459%
460% MagickBooleanType ColorDecisionListImage(Image *image,
cristy1bfa9f02011-08-11 02:35:43 +0000461% const char *color_correction_collection,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000462%
463% A description of each parameter follows:
464%
465% o image: the image.
466%
467% o color_correction_collection: the color correction collection in XML.
468%
cristy1bfa9f02011-08-11 02:35:43 +0000469% o exception: return any errors or warnings in this structure.
470%
cristy3ed852e2009-09-05 21:47:34 +0000471*/
472MagickExport MagickBooleanType ColorDecisionListImage(Image *image,
cristy1bfa9f02011-08-11 02:35:43 +0000473 const char *color_correction_collection,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000474{
475#define ColorDecisionListCorrectImageTag "ColorDecisionList/Image"
476
477 typedef struct _Correction
478 {
479 double
480 slope,
481 offset,
482 power;
483 } Correction;
484
485 typedef struct _ColorCorrection
486 {
487 Correction
488 red,
489 green,
490 blue;
491
492 double
493 saturation;
494 } ColorCorrection;
495
cristyc4c8d132010-01-07 01:58:38 +0000496 CacheView
497 *image_view;
498
cristy3ed852e2009-09-05 21:47:34 +0000499 char
500 token[MaxTextExtent];
501
502 ColorCorrection
503 color_correction;
504
505 const char
506 *content,
507 *p;
508
cristy3ed852e2009-09-05 21:47:34 +0000509 MagickBooleanType
510 status;
511
cristybb503372010-05-27 20:51:26 +0000512 MagickOffsetType
513 progress;
514
cristy101ab702011-10-13 13:06:32 +0000515 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +0000516 *cdl_map;
517
cristybb503372010-05-27 20:51:26 +0000518 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000519 i;
520
cristybb503372010-05-27 20:51:26 +0000521 ssize_t
522 y;
523
cristy3ed852e2009-09-05 21:47:34 +0000524 XMLTreeInfo
525 *cc,
526 *ccc,
527 *sat,
528 *sop;
529
cristy3ed852e2009-09-05 21:47:34 +0000530 /*
531 Allocate and initialize cdl maps.
532 */
533 assert(image != (Image *) NULL);
534 assert(image->signature == MagickSignature);
535 if (image->debug != MagickFalse)
536 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
537 if (color_correction_collection == (const char *) NULL)
538 return(MagickFalse);
cristy1bfa9f02011-08-11 02:35:43 +0000539 ccc=NewXMLTree((const char *) color_correction_collection,exception);
cristy3ed852e2009-09-05 21:47:34 +0000540 if (ccc == (XMLTreeInfo *) NULL)
541 return(MagickFalse);
542 cc=GetXMLTreeChild(ccc,"ColorCorrection");
543 if (cc == (XMLTreeInfo *) NULL)
544 {
545 ccc=DestroyXMLTree(ccc);
546 return(MagickFalse);
547 }
548 color_correction.red.slope=1.0;
549 color_correction.red.offset=0.0;
550 color_correction.red.power=1.0;
551 color_correction.green.slope=1.0;
552 color_correction.green.offset=0.0;
553 color_correction.green.power=1.0;
554 color_correction.blue.slope=1.0;
555 color_correction.blue.offset=0.0;
556 color_correction.blue.power=1.0;
557 color_correction.saturation=0.0;
558 sop=GetXMLTreeChild(cc,"SOPNode");
559 if (sop != (XMLTreeInfo *) NULL)
560 {
561 XMLTreeInfo
562 *offset,
563 *power,
564 *slope;
565
566 slope=GetXMLTreeChild(sop,"Slope");
567 if (slope != (XMLTreeInfo *) NULL)
568 {
569 content=GetXMLTreeContent(slope);
570 p=(const char *) content;
571 for (i=0; (*p != '\0') && (i < 3); i++)
572 {
573 GetMagickToken(p,&p,token);
574 if (*token == ',')
575 GetMagickToken(p,&p,token);
576 switch (i)
577 {
cristyc1acd842011-05-19 23:05:47 +0000578 case 0:
579 {
cristy9b34e302011-11-05 02:15:45 +0000580 color_correction.red.slope=StringToDouble(token,(char **) NULL);
cristyc1acd842011-05-19 23:05:47 +0000581 break;
582 }
583 case 1:
584 {
cristydbdd0e32011-11-04 23:29:40 +0000585 color_correction.green.slope=StringToDouble(token,
cristyc1acd842011-05-19 23:05:47 +0000586 (char **) NULL);
587 break;
588 }
589 case 2:
590 {
cristydbdd0e32011-11-04 23:29:40 +0000591 color_correction.blue.slope=StringToDouble(token,
cristyc1acd842011-05-19 23:05:47 +0000592 (char **) NULL);
593 break;
594 }
cristy3ed852e2009-09-05 21:47:34 +0000595 }
596 }
597 }
598 offset=GetXMLTreeChild(sop,"Offset");
599 if (offset != (XMLTreeInfo *) NULL)
600 {
601 content=GetXMLTreeContent(offset);
602 p=(const char *) content;
603 for (i=0; (*p != '\0') && (i < 3); i++)
604 {
605 GetMagickToken(p,&p,token);
606 if (*token == ',')
607 GetMagickToken(p,&p,token);
608 switch (i)
609 {
cristyc1acd842011-05-19 23:05:47 +0000610 case 0:
611 {
cristydbdd0e32011-11-04 23:29:40 +0000612 color_correction.red.offset=StringToDouble(token,
cristyc1acd842011-05-19 23:05:47 +0000613 (char **) NULL);
614 break;
615 }
616 case 1:
617 {
cristydbdd0e32011-11-04 23:29:40 +0000618 color_correction.green.offset=StringToDouble(token,
cristyc1acd842011-05-19 23:05:47 +0000619 (char **) NULL);
620 break;
621 }
622 case 2:
623 {
cristydbdd0e32011-11-04 23:29:40 +0000624 color_correction.blue.offset=StringToDouble(token,
cristyc1acd842011-05-19 23:05:47 +0000625 (char **) NULL);
626 break;
627 }
cristy3ed852e2009-09-05 21:47:34 +0000628 }
629 }
630 }
631 power=GetXMLTreeChild(sop,"Power");
632 if (power != (XMLTreeInfo *) NULL)
633 {
634 content=GetXMLTreeContent(power);
635 p=(const char *) content;
636 for (i=0; (*p != '\0') && (i < 3); i++)
637 {
638 GetMagickToken(p,&p,token);
639 if (*token == ',')
640 GetMagickToken(p,&p,token);
641 switch (i)
642 {
cristyc1acd842011-05-19 23:05:47 +0000643 case 0:
644 {
cristy9b34e302011-11-05 02:15:45 +0000645 color_correction.red.power=StringToDouble(token,(char **) NULL);
cristyc1acd842011-05-19 23:05:47 +0000646 break;
647 }
648 case 1:
649 {
cristydbdd0e32011-11-04 23:29:40 +0000650 color_correction.green.power=StringToDouble(token,
cristyc1acd842011-05-19 23:05:47 +0000651 (char **) NULL);
652 break;
653 }
654 case 2:
655 {
cristydbdd0e32011-11-04 23:29:40 +0000656 color_correction.blue.power=StringToDouble(token,
cristyc1acd842011-05-19 23:05:47 +0000657 (char **) NULL);
658 break;
659 }
cristy3ed852e2009-09-05 21:47:34 +0000660 }
661 }
662 }
663 }
664 sat=GetXMLTreeChild(cc,"SATNode");
665 if (sat != (XMLTreeInfo *) NULL)
666 {
667 XMLTreeInfo
668 *saturation;
669
670 saturation=GetXMLTreeChild(sat,"Saturation");
671 if (saturation != (XMLTreeInfo *) NULL)
672 {
673 content=GetXMLTreeContent(saturation);
674 p=(const char *) content;
675 GetMagickToken(p,&p,token);
cristydbdd0e32011-11-04 23:29:40 +0000676 color_correction.saturation=StringToDouble(token,
cristyc1acd842011-05-19 23:05:47 +0000677 (char **) NULL);
cristy3ed852e2009-09-05 21:47:34 +0000678 }
679 }
680 ccc=DestroyXMLTree(ccc);
681 if (image->debug != MagickFalse)
682 {
683 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
684 " Color Correction Collection:");
685 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000686 " color_correction.red.slope: %g",color_correction.red.slope);
cristy3ed852e2009-09-05 21:47:34 +0000687 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000688 " color_correction.red.offset: %g",color_correction.red.offset);
cristy3ed852e2009-09-05 21:47:34 +0000689 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000690 " color_correction.red.power: %g",color_correction.red.power);
cristy3ed852e2009-09-05 21:47:34 +0000691 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000692 " color_correction.green.slope: %g",color_correction.green.slope);
cristy3ed852e2009-09-05 21:47:34 +0000693 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000694 " color_correction.green.offset: %g",color_correction.green.offset);
cristy3ed852e2009-09-05 21:47:34 +0000695 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000696 " color_correction.green.power: %g",color_correction.green.power);
cristy3ed852e2009-09-05 21:47:34 +0000697 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000698 " color_correction.blue.slope: %g",color_correction.blue.slope);
cristy3ed852e2009-09-05 21:47:34 +0000699 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000700 " color_correction.blue.offset: %g",color_correction.blue.offset);
cristy3ed852e2009-09-05 21:47:34 +0000701 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000702 " color_correction.blue.power: %g",color_correction.blue.power);
cristy3ed852e2009-09-05 21:47:34 +0000703 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000704 " color_correction.saturation: %g",color_correction.saturation);
cristy3ed852e2009-09-05 21:47:34 +0000705 }
cristy101ab702011-10-13 13:06:32 +0000706 cdl_map=(PixelInfo *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*cdl_map));
707 if (cdl_map == (PixelInfo *) NULL)
cristy3ed852e2009-09-05 21:47:34 +0000708 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
709 image->filename);
cristyb5d5f722009-11-04 03:03:49 +0000710#if defined(MAGICKCORE_OPENMP_SUPPORT)
711 #pragma omp parallel for schedule(dynamic,4)
cristy3ed852e2009-09-05 21:47:34 +0000712#endif
cristybb503372010-05-27 20:51:26 +0000713 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +0000714 {
cristyda1f9c12011-10-02 21:39:49 +0000715 cdl_map[i].red=(MagickRealType) ScaleMapToQuantum((MagickRealType)
716 (MaxMap*(pow(color_correction.red.slope*i/MaxMap+
717 color_correction.red.offset,color_correction.red.power))));
718 cdl_map[i].green=(MagickRealType) ScaleMapToQuantum((MagickRealType)
719 (MaxMap*(pow(color_correction.green.slope*i/MaxMap+
720 color_correction.green.offset,color_correction.green.power))));
721 cdl_map[i].blue=(MagickRealType) ScaleMapToQuantum((MagickRealType)
722 (MaxMap*(pow(color_correction.blue.slope*i/MaxMap+
723 color_correction.blue.offset,color_correction.blue.power))));
cristy3ed852e2009-09-05 21:47:34 +0000724 }
725 if (image->storage_class == PseudoClass)
726 {
727 /*
728 Apply transfer function to colormap.
729 */
cristyb5d5f722009-11-04 03:03:49 +0000730#if defined(MAGICKCORE_OPENMP_SUPPORT)
731 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000732#endif
cristybb503372010-05-27 20:51:26 +0000733 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +0000734 {
735 double
736 luma;
737
738 luma=0.2126*image->colormap[i].red+0.7152*image->colormap[i].green+
739 0.0722*image->colormap[i].blue;
cristyda1f9c12011-10-02 21:39:49 +0000740 image->colormap[i].red=luma+color_correction.saturation*
741 cdl_map[ScaleQuantumToMap(ClampToQuantum(
742 image->colormap[i].red))].red-luma;
743 image->colormap[i].green=luma+color_correction.saturation*
744 cdl_map[ScaleQuantumToMap(ClampToQuantum(
745 image->colormap[i].green))].green-luma;
746 image->colormap[i].blue=luma+color_correction.saturation*
747 cdl_map[ScaleQuantumToMap(ClampToQuantum(
748 image->colormap[i].blue))].blue-luma;
cristy3ed852e2009-09-05 21:47:34 +0000749 }
750 }
751 /*
752 Apply transfer function to image.
753 */
754 status=MagickTrue;
755 progress=0;
cristy3ed852e2009-09-05 21:47:34 +0000756 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +0000757#if defined(MAGICKCORE_OPENMP_SUPPORT)
758 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000759#endif
cristybb503372010-05-27 20:51:26 +0000760 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000761 {
762 double
763 luma;
764
cristy4c08aed2011-07-01 19:47:50 +0000765 register Quantum
cristyc47d1f82009-11-26 01:44:43 +0000766 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000767
cristy8d4629b2010-08-30 17:59:46 +0000768 register ssize_t
769 x;
770
cristy3ed852e2009-09-05 21:47:34 +0000771 if (status == MagickFalse)
772 continue;
773 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristyacd2ed22011-08-30 01:44:23 +0000774 if (q == (Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +0000775 {
776 status=MagickFalse;
777 continue;
778 }
cristybb503372010-05-27 20:51:26 +0000779 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000780 {
cristy4c08aed2011-07-01 19:47:50 +0000781 luma=0.2126*GetPixelRed(image,q)+0.7152*GetPixelGreen(image,q)+0.0722*
782 GetPixelBlue(image,q);
783 SetPixelRed(image,ClampToQuantum(luma+color_correction.saturation*
784 (cdl_map[ScaleQuantumToMap(GetPixelRed(image,q))].red-luma)),q);
785 SetPixelGreen(image,ClampToQuantum(luma+color_correction.saturation*
786 (cdl_map[ScaleQuantumToMap(GetPixelGreen(image,q))].green-luma)),q);
787 SetPixelBlue(image,ClampToQuantum(luma+color_correction.saturation*
788 (cdl_map[ScaleQuantumToMap(GetPixelBlue(image,q))].blue-luma)),q);
cristyed231572011-07-14 02:18:59 +0000789 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +0000790 }
791 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
792 status=MagickFalse;
793 if (image->progress_monitor != (MagickProgressMonitor) NULL)
794 {
795 MagickBooleanType
796 proceed;
797
cristyb5d5f722009-11-04 03:03:49 +0000798#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +0000799 #pragma omp critical (MagickCore_ColorDecisionListImageChannel)
800#endif
801 proceed=SetImageProgress(image,ColorDecisionListCorrectImageTag,
802 progress++,image->rows);
803 if (proceed == MagickFalse)
804 status=MagickFalse;
805 }
806 }
807 image_view=DestroyCacheView(image_view);
cristy101ab702011-10-13 13:06:32 +0000808 cdl_map=(PixelInfo *) RelinquishMagickMemory(cdl_map);
cristy3ed852e2009-09-05 21:47:34 +0000809 return(status);
810}
811
812/*
813%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
814% %
815% %
816% %
cristy3ed852e2009-09-05 21:47:34 +0000817% C o n t r a s t I m a g e %
818% %
819% %
820% %
821%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
822%
823% ContrastImage() enhances the intensity differences between the lighter and
824% darker elements of the image. Set sharpen to a MagickTrue to increase the
825% image contrast otherwise the contrast is reduced.
826%
827% The format of the ContrastImage method is:
828%
829% MagickBooleanType ContrastImage(Image *image,
cristye23ec9d2011-08-16 18:15:40 +0000830% const MagickBooleanType sharpen,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000831%
832% A description of each parameter follows:
833%
834% o image: the image.
835%
836% o sharpen: Increase or decrease image contrast.
837%
cristye23ec9d2011-08-16 18:15:40 +0000838% o exception: return any errors or warnings in this structure.
839%
cristy3ed852e2009-09-05 21:47:34 +0000840*/
841
cristy3094b7f2011-10-01 23:18:02 +0000842static void Contrast(const int sign,double *red,double *green,double *blue)
cristy3ed852e2009-09-05 21:47:34 +0000843{
844 double
845 brightness,
846 hue,
847 saturation;
848
849 /*
850 Enhance contrast: dark color become darker, light color become lighter.
851 */
cristy3094b7f2011-10-01 23:18:02 +0000852 assert(red != (double *) NULL);
853 assert(green != (double *) NULL);
854 assert(blue != (double *) NULL);
cristy3ed852e2009-09-05 21:47:34 +0000855 hue=0.0;
856 saturation=0.0;
857 brightness=0.0;
858 ConvertRGBToHSB(*red,*green,*blue,&hue,&saturation,&brightness);
cristy31ac0f02011-03-12 02:04:47 +0000859 brightness+=0.5*sign*(0.5*(sin((double) (MagickPI*(brightness-0.5)))+1.0)-
cristy4205a3c2010-09-12 20:19:59 +0000860 brightness);
cristy3ed852e2009-09-05 21:47:34 +0000861 if (brightness > 1.0)
862 brightness=1.0;
863 else
864 if (brightness < 0.0)
865 brightness=0.0;
866 ConvertHSBToRGB(hue,saturation,brightness,red,green,blue);
867}
868
869MagickExport MagickBooleanType ContrastImage(Image *image,
cristye23ec9d2011-08-16 18:15:40 +0000870 const MagickBooleanType sharpen,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000871{
872#define ContrastImageTag "Contrast/Image"
873
cristyc4c8d132010-01-07 01:58:38 +0000874 CacheView
875 *image_view;
876
cristy3ed852e2009-09-05 21:47:34 +0000877 int
878 sign;
879
cristy3ed852e2009-09-05 21:47:34 +0000880 MagickBooleanType
881 status;
882
cristybb503372010-05-27 20:51:26 +0000883 MagickOffsetType
884 progress;
885
886 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000887 i;
888
cristybb503372010-05-27 20:51:26 +0000889 ssize_t
890 y;
891
cristy3ed852e2009-09-05 21:47:34 +0000892 assert(image != (Image *) NULL);
893 assert(image->signature == MagickSignature);
894 if (image->debug != MagickFalse)
895 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
896 sign=sharpen != MagickFalse ? 1 : -1;
897 if (image->storage_class == PseudoClass)
898 {
899 /*
900 Contrast enhance colormap.
901 */
cristybb503372010-05-27 20:51:26 +0000902 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +0000903 Contrast(sign,&image->colormap[i].red,&image->colormap[i].green,
904 &image->colormap[i].blue);
905 }
906 /*
907 Contrast enhance image.
908 */
909 status=MagickTrue;
910 progress=0;
cristy3ed852e2009-09-05 21:47:34 +0000911 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +0000912#if defined(MAGICKCORE_OPENMP_SUPPORT)
913 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000914#endif
cristybb503372010-05-27 20:51:26 +0000915 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000916 {
cristy3094b7f2011-10-01 23:18:02 +0000917 double
cristy5afeab82011-04-30 01:30:09 +0000918 blue,
919 green,
920 red;
921
cristy4c08aed2011-07-01 19:47:50 +0000922 register Quantum
cristyc47d1f82009-11-26 01:44:43 +0000923 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000924
cristy8d4629b2010-08-30 17:59:46 +0000925 register ssize_t
926 x;
927
cristy3ed852e2009-09-05 21:47:34 +0000928 if (status == MagickFalse)
929 continue;
930 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristyacd2ed22011-08-30 01:44:23 +0000931 if (q == (Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +0000932 {
933 status=MagickFalse;
934 continue;
935 }
cristybb503372010-05-27 20:51:26 +0000936 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000937 {
cristyda1f9c12011-10-02 21:39:49 +0000938 red=(double) GetPixelRed(image,q);
939 green=(double) GetPixelGreen(image,q);
940 blue=(double) GetPixelBlue(image,q);
cristy5afeab82011-04-30 01:30:09 +0000941 Contrast(sign,&red,&green,&blue);
cristyda1f9c12011-10-02 21:39:49 +0000942 SetPixelRed(image,ClampToQuantum(red),q);
943 SetPixelGreen(image,ClampToQuantum(green),q);
944 SetPixelBlue(image,ClampToQuantum(blue),q);
cristyed231572011-07-14 02:18:59 +0000945 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +0000946 }
947 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
948 status=MagickFalse;
949 if (image->progress_monitor != (MagickProgressMonitor) NULL)
950 {
951 MagickBooleanType
952 proceed;
953
cristyb5d5f722009-11-04 03:03:49 +0000954#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +0000955 #pragma omp critical (MagickCore_ContrastImage)
956#endif
957 proceed=SetImageProgress(image,ContrastImageTag,progress++,image->rows);
958 if (proceed == MagickFalse)
959 status=MagickFalse;
960 }
961 }
962 image_view=DestroyCacheView(image_view);
963 return(status);
964}
965
966/*
967%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
968% %
969% %
970% %
971% C o n t r a s t S t r e t c h I m a g e %
972% %
973% %
974% %
975%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
976%
cristyf1611782011-08-01 15:39:13 +0000977% ContrastStretchImage() is a simple image enhancement technique that attempts
978% to improve the contrast in an image by `stretching' the range of intensity
979% values it contains to span a desired range of values. It differs from the
980% more sophisticated histogram equalization in that it can only apply a
981% linear scaling function to the image pixel values. As a result the
982% `enhancement' is less harsh.
cristy3ed852e2009-09-05 21:47:34 +0000983%
984% The format of the ContrastStretchImage method is:
985%
986% MagickBooleanType ContrastStretchImage(Image *image,
cristye23ec9d2011-08-16 18:15:40 +0000987% const char *levels,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000988%
989% A description of each parameter follows:
990%
991% o image: the image.
992%
cristy3ed852e2009-09-05 21:47:34 +0000993% o black_point: the black point.
994%
995% o white_point: the white point.
996%
997% o levels: Specify the levels where the black and white points have the
998% range of 0 to number-of-pixels (e.g. 1%, 10x90%, etc.).
999%
cristye23ec9d2011-08-16 18:15:40 +00001000% o exception: return any errors or warnings in this structure.
1001%
cristy3ed852e2009-09-05 21:47:34 +00001002*/
cristy3ed852e2009-09-05 21:47:34 +00001003MagickExport MagickBooleanType ContrastStretchImage(Image *image,
cristye23ec9d2011-08-16 18:15:40 +00001004 const double black_point,const double white_point,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001005{
1006#define MaxRange(color) ((MagickRealType) ScaleQuantumToMap((Quantum) (color)))
1007#define ContrastStretchImageTag "ContrastStretch/Image"
1008
cristyc4c8d132010-01-07 01:58:38 +00001009 CacheView
1010 *image_view;
1011
cristy3ed852e2009-09-05 21:47:34 +00001012 MagickBooleanType
1013 status;
1014
cristybb503372010-05-27 20:51:26 +00001015 MagickOffsetType
1016 progress;
1017
cristyf45fec72011-08-23 16:02:32 +00001018 double
1019 *black,
cristy3ed852e2009-09-05 21:47:34 +00001020 *histogram,
1021 *stretch_map,
cristyf45fec72011-08-23 16:02:32 +00001022 *white;
cristy3ed852e2009-09-05 21:47:34 +00001023
cristybb503372010-05-27 20:51:26 +00001024 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001025 i;
1026
cristybb503372010-05-27 20:51:26 +00001027 ssize_t
1028 y;
1029
cristy3ed852e2009-09-05 21:47:34 +00001030 /*
1031 Allocate histogram and stretch map.
1032 */
1033 assert(image != (Image *) NULL);
1034 assert(image->signature == MagickSignature);
1035 if (image->debug != MagickFalse)
1036 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
cristyf45fec72011-08-23 16:02:32 +00001037 black=(double *) AcquireQuantumMemory(GetPixelChannels(image),sizeof(*black));
1038 white=(double *) AcquireQuantumMemory(GetPixelChannels(image),sizeof(*white));
1039 histogram=(double *) AcquireQuantumMemory(MaxMap+1UL,
cristy465571b2011-08-21 20:43:15 +00001040 GetPixelChannels(image)*sizeof(*histogram));
cristyf45fec72011-08-23 16:02:32 +00001041 stretch_map=(double *) AcquireQuantumMemory(MaxMap+1UL,
cristy465571b2011-08-21 20:43:15 +00001042 GetPixelChannels(image)*sizeof(*stretch_map));
cristyf45fec72011-08-23 16:02:32 +00001043 if ((black == (double *) NULL) || (white == (double *) NULL) ||
1044 (histogram == (double *) NULL) || (stretch_map == (double *) NULL))
cristy465571b2011-08-21 20:43:15 +00001045 {
cristyf45fec72011-08-23 16:02:32 +00001046 if (stretch_map != (double *) NULL)
1047 stretch_map=(double *) RelinquishMagickMemory(stretch_map);
1048 if (histogram != (double *) NULL)
1049 histogram=(double *) RelinquishMagickMemory(histogram);
1050 if (white != (double *) NULL)
1051 white=(double *) RelinquishMagickMemory(white);
1052 if (black != (double *) NULL)
1053 black=(double *) RelinquishMagickMemory(black);
cristy465571b2011-08-21 20:43:15 +00001054 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
1055 image->filename);
1056 }
cristy3ed852e2009-09-05 21:47:34 +00001057 /*
1058 Form histogram.
1059 */
1060 status=MagickTrue;
cristy465571b2011-08-21 20:43:15 +00001061 (void) ResetMagickMemory(histogram,0,(MaxMap+1)*GetPixelChannels(image)*
1062 sizeof(*histogram));
cristy3ed852e2009-09-05 21:47:34 +00001063 image_view=AcquireCacheView(image);
cristybb503372010-05-27 20:51:26 +00001064 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001065 {
cristy4c08aed2011-07-01 19:47:50 +00001066 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00001067 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00001068
cristybb503372010-05-27 20:51:26 +00001069 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001070 x;
1071
1072 if (status == MagickFalse)
1073 continue;
1074 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00001075 if (p == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001076 {
1077 status=MagickFalse;
1078 continue;
1079 }
cristy43547a52011-08-09 13:07:05 +00001080 for (x=0; x < (ssize_t) image->columns; x++)
1081 {
cristy465571b2011-08-21 20:43:15 +00001082 register ssize_t
1083 i;
1084
1085 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
cristy465571b2011-08-21 20:43:15 +00001086 histogram[GetPixelChannels(image)*ScaleQuantumToMap(p[i])+i]++;
cristy43547a52011-08-09 13:07:05 +00001087 p+=GetPixelChannels(image);
1088 }
cristy3ed852e2009-09-05 21:47:34 +00001089 }
1090 /*
1091 Find the histogram boundaries by locating the black/white levels.
1092 */
cristyb5d5f722009-11-04 03:03:49 +00001093#if defined(MAGICKCORE_OPENMP_SUPPORT)
1094 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001095#endif
cristy465571b2011-08-21 20:43:15 +00001096 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
cristy3ed852e2009-09-05 21:47:34 +00001097 {
cristy465571b2011-08-21 20:43:15 +00001098 double
1099 intensity;
1100
1101 register ssize_t
1102 j;
1103
1104 black[i]=0.0;
1105 white[i]=MaxRange(QuantumRange);
1106 intensity=0.0;
1107 for (j=0; j <= (ssize_t) MaxMap; j++)
1108 {
1109 intensity+=histogram[GetPixelChannels(image)*j+i];
1110 if (intensity > black_point)
1111 break;
1112 }
1113 black[i]=(MagickRealType) j;
1114 intensity=0.0;
1115 for (j=(ssize_t) MaxMap; j != 0; j--)
1116 {
1117 intensity+=histogram[GetPixelChannels(image)*j+i];
1118 if (intensity > ((double) image->columns*image->rows-white_point))
1119 break;
1120 }
1121 white[i]=(MagickRealType) j;
cristy3ed852e2009-09-05 21:47:34 +00001122 }
cristy465571b2011-08-21 20:43:15 +00001123 histogram=(double *) RelinquishMagickMemory(histogram);
cristy3ed852e2009-09-05 21:47:34 +00001124 /*
cristy465571b2011-08-21 20:43:15 +00001125 Stretch the histogram to create the stretched image mapping.
cristy3ed852e2009-09-05 21:47:34 +00001126 */
cristy465571b2011-08-21 20:43:15 +00001127 (void) ResetMagickMemory(stretch_map,0,(MaxMap+1)*GetPixelChannels(image)*
1128 sizeof(*stretch_map));
1129#if defined(MAGICKCORE_OPENMP_SUPPORT)
1130 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
1131#endif
1132 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1133 {
1134 register ssize_t
1135 j;
1136
1137 for (j=0; j <= (ssize_t) MaxMap; j++)
1138 {
1139 if (j < (ssize_t) black[i])
1140 stretch_map[GetPixelChannels(image)*j+i]=0.0;
1141 else
1142 if (j > (ssize_t) white[i])
1143 stretch_map[GetPixelChannels(image)*j+i]=(MagickRealType)
1144 QuantumRange;
1145 else
1146 if (black[i] != white[i])
1147 stretch_map[GetPixelChannels(image)*j+i]=(MagickRealType)
1148 ScaleMapToQuantum((MagickRealType) (MaxMap*(j-black[i])/
1149 (white[i]-black[i])));
1150 }
1151 }
cristy3ed852e2009-09-05 21:47:34 +00001152 if (image->storage_class == PseudoClass)
1153 {
cristy465571b2011-08-21 20:43:15 +00001154 register ssize_t
1155 j;
1156
cristy3ed852e2009-09-05 21:47:34 +00001157 /*
cristy465571b2011-08-21 20:43:15 +00001158 Stretch-contrast colormap.
cristy3ed852e2009-09-05 21:47:34 +00001159 */
cristyb5d5f722009-11-04 03:03:49 +00001160#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy000d38b2011-09-08 13:13:04 +00001161 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001162#endif
cristy465571b2011-08-21 20:43:15 +00001163 for (j=0; j < (ssize_t) image->colors; j++)
cristy3ed852e2009-09-05 21:47:34 +00001164 {
cristyed231572011-07-14 02:18:59 +00001165 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001166 {
cristy465571b2011-08-21 20:43:15 +00001167 i=GetPixelChannelMapChannel(image,RedPixelChannel);
1168 if (black[i] != white[i])
cristyda1f9c12011-10-02 21:39:49 +00001169 image->colormap[j].red=stretch_map[GetPixelChannels(image)*
1170 ScaleQuantumToMap(ClampToQuantum(image->colormap[j].red))]+i;
cristy3ed852e2009-09-05 21:47:34 +00001171 }
cristyed231572011-07-14 02:18:59 +00001172 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001173 {
cristy465571b2011-08-21 20:43:15 +00001174 i=GetPixelChannelMapChannel(image,GreenPixelChannel);
1175 if (black[i] != white[i])
cristyda1f9c12011-10-02 21:39:49 +00001176 image->colormap[j].green=stretch_map[GetPixelChannels(image)*
1177 ScaleQuantumToMap(ClampToQuantum(image->colormap[j].green))]+i;
cristy3ed852e2009-09-05 21:47:34 +00001178 }
cristyed231572011-07-14 02:18:59 +00001179 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001180 {
cristy465571b2011-08-21 20:43:15 +00001181 i=GetPixelChannelMapChannel(image,BluePixelChannel);
1182 if (black[i] != white[i])
cristyda1f9c12011-10-02 21:39:49 +00001183 image->colormap[j].blue=stretch_map[GetPixelChannels(image)*
1184 ScaleQuantumToMap(ClampToQuantum(image->colormap[j].blue))]+i;
cristy3ed852e2009-09-05 21:47:34 +00001185 }
cristyed231572011-07-14 02:18:59 +00001186 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001187 {
cristy465571b2011-08-21 20:43:15 +00001188 i=GetPixelChannelMapChannel(image,AlphaPixelChannel);
1189 if (black[i] != white[i])
cristyda1f9c12011-10-02 21:39:49 +00001190 image->colormap[j].alpha=stretch_map[GetPixelChannels(image)*
1191 ScaleQuantumToMap(ClampToQuantum(image->colormap[j].alpha))]+i;
cristy3ed852e2009-09-05 21:47:34 +00001192 }
1193 }
1194 }
1195 /*
cristy465571b2011-08-21 20:43:15 +00001196 Stretch-contrast image.
cristy3ed852e2009-09-05 21:47:34 +00001197 */
1198 status=MagickTrue;
1199 progress=0;
cristyb5d5f722009-11-04 03:03:49 +00001200#if defined(MAGICKCORE_OPENMP_SUPPORT)
1201 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001202#endif
cristybb503372010-05-27 20:51:26 +00001203 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001204 {
cristy4c08aed2011-07-01 19:47:50 +00001205 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00001206 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001207
cristy8d4629b2010-08-30 17:59:46 +00001208 register ssize_t
1209 x;
1210
cristy3ed852e2009-09-05 21:47:34 +00001211 if (status == MagickFalse)
1212 continue;
1213 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristyacd2ed22011-08-30 01:44:23 +00001214 if (q == (Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001215 {
1216 status=MagickFalse;
1217 continue;
1218 }
cristybb503372010-05-27 20:51:26 +00001219 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001220 {
cristy465571b2011-08-21 20:43:15 +00001221 register ssize_t
1222 i;
1223
1224 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1225 {
cristyabace412011-12-11 15:56:53 +00001226 PixelChannel
1227 channel;
1228
cristy465571b2011-08-21 20:43:15 +00001229 PixelTrait
1230 traits;
1231
cristyabace412011-12-11 15:56:53 +00001232 channel=GetPixelChannelMapChannel(image,i);
1233 traits=GetPixelChannelMapTraits(image,channel);
cristy7c0a0a42011-08-23 17:57:25 +00001234 if (((traits & UpdatePixelTrait) != 0) && (black[i] != white[i]))
cristy465571b2011-08-21 20:43:15 +00001235 q[i]=ClampToQuantum(stretch_map[GetPixelChannels(image)*
1236 ScaleQuantumToMap(q[i])+i]);
1237 }
cristyed231572011-07-14 02:18:59 +00001238 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00001239 }
1240 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
1241 status=MagickFalse;
1242 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1243 {
1244 MagickBooleanType
1245 proceed;
1246
cristyb5d5f722009-11-04 03:03:49 +00001247#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy50fbc382011-07-07 02:19:17 +00001248 #pragma omp critical (MagickCore_ContrastStretchImage)
cristy3ed852e2009-09-05 21:47:34 +00001249#endif
1250 proceed=SetImageProgress(image,ContrastStretchImageTag,progress++,
1251 image->rows);
1252 if (proceed == MagickFalse)
1253 status=MagickFalse;
1254 }
1255 }
1256 image_view=DestroyCacheView(image_view);
cristyf45fec72011-08-23 16:02:32 +00001257 stretch_map=(double *) RelinquishMagickMemory(stretch_map);
1258 white=(double *) RelinquishMagickMemory(white);
1259 black=(double *) RelinquishMagickMemory(black);
cristy3ed852e2009-09-05 21:47:34 +00001260 return(status);
1261}
1262
1263/*
1264%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1265% %
1266% %
1267% %
1268% E n h a n c e I m a g e %
1269% %
1270% %
1271% %
1272%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1273%
1274% EnhanceImage() applies a digital filter that improves the quality of a
1275% noisy image.
1276%
1277% The format of the EnhanceImage method is:
1278%
1279% Image *EnhanceImage(const Image *image,ExceptionInfo *exception)
1280%
1281% A description of each parameter follows:
1282%
1283% o image: the image.
1284%
1285% o exception: return any errors or warnings in this structure.
1286%
1287*/
1288MagickExport Image *EnhanceImage(const Image *image,ExceptionInfo *exception)
1289{
cristy6d8c3d72011-08-22 01:20:01 +00001290#define EnhancePixel(weight) \
cristy0beccfa2011-09-25 20:47:53 +00001291 mean=((MagickRealType) r[i]+GetPixelChannel(enhance_image,channel,q))/2.0; \
1292 distance=(MagickRealType) r[i]-(MagickRealType) GetPixelChannel( \
1293 enhance_image,channel,q); \
cristy3ed852e2009-09-05 21:47:34 +00001294 distance_squared=QuantumScale*(2.0*((MagickRealType) QuantumRange+1.0)+ \
cristy6d8c3d72011-08-22 01:20:01 +00001295 mean)*distance*distance; \
cristy3ed852e2009-09-05 21:47:34 +00001296 if (distance_squared < ((MagickRealType) QuantumRange*(MagickRealType) \
1297 QuantumRange/25.0f)) \
1298 { \
cristy6d8c3d72011-08-22 01:20:01 +00001299 aggregate+=(weight)*r[i]; \
cristy3ed852e2009-09-05 21:47:34 +00001300 total_weight+=(weight); \
1301 } \
cristy6d8c3d72011-08-22 01:20:01 +00001302 r+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00001303#define EnhanceImageTag "Enhance/Image"
1304
cristyc4c8d132010-01-07 01:58:38 +00001305 CacheView
1306 *enhance_view,
1307 *image_view;
1308
cristy3ed852e2009-09-05 21:47:34 +00001309 Image
1310 *enhance_image;
1311
cristy3ed852e2009-09-05 21:47:34 +00001312 MagickBooleanType
1313 status;
1314
cristybb503372010-05-27 20:51:26 +00001315 MagickOffsetType
1316 progress;
1317
cristybb503372010-05-27 20:51:26 +00001318 ssize_t
1319 y;
1320
cristy3ed852e2009-09-05 21:47:34 +00001321 /*
1322 Initialize enhanced image attributes.
1323 */
1324 assert(image != (const Image *) NULL);
1325 assert(image->signature == MagickSignature);
1326 if (image->debug != MagickFalse)
1327 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1328 assert(exception != (ExceptionInfo *) NULL);
1329 assert(exception->signature == MagickSignature);
cristy3ed852e2009-09-05 21:47:34 +00001330 enhance_image=CloneImage(image,image->columns,image->rows,MagickTrue,
1331 exception);
1332 if (enhance_image == (Image *) NULL)
1333 return((Image *) NULL);
cristy574cc262011-08-05 01:23:58 +00001334 if (SetImageStorageClass(enhance_image,DirectClass,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00001335 {
cristy3ed852e2009-09-05 21:47:34 +00001336 enhance_image=DestroyImage(enhance_image);
1337 return((Image *) NULL);
1338 }
1339 /*
1340 Enhance image.
1341 */
1342 status=MagickTrue;
1343 progress=0;
cristy3ed852e2009-09-05 21:47:34 +00001344 image_view=AcquireCacheView(image);
1345 enhance_view=AcquireCacheView(enhance_image);
cristyb5d5f722009-11-04 03:03:49 +00001346#if defined(MAGICKCORE_OPENMP_SUPPORT)
1347 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001348#endif
cristybb503372010-05-27 20:51:26 +00001349 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001350 {
cristy4c08aed2011-07-01 19:47:50 +00001351 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00001352 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00001353
cristy4c08aed2011-07-01 19:47:50 +00001354 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00001355 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001356
cristy8d4629b2010-08-30 17:59:46 +00001357 register ssize_t
1358 x;
1359
cristy6d8c3d72011-08-22 01:20:01 +00001360 ssize_t
1361 center;
1362
cristy3ed852e2009-09-05 21:47:34 +00001363 if (status == MagickFalse)
1364 continue;
1365 p=GetCacheViewVirtualPixels(image_view,-2,y-2,image->columns+4,5,exception);
1366 q=QueueCacheViewAuthenticPixels(enhance_view,0,y,enhance_image->columns,1,
1367 exception);
cristy4c08aed2011-07-01 19:47:50 +00001368 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00001369 {
1370 status=MagickFalse;
1371 continue;
1372 }
cristyf45fec72011-08-23 16:02:32 +00001373 center=(ssize_t) GetPixelChannels(image)*(2*(image->columns+4)+2);
cristybb503372010-05-27 20:51:26 +00001374 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001375 {
cristy6d8c3d72011-08-22 01:20:01 +00001376 register ssize_t
1377 i;
cristy3ed852e2009-09-05 21:47:34 +00001378
cristy6d8c3d72011-08-22 01:20:01 +00001379 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1380 {
1381 MagickRealType
1382 aggregate,
1383 distance,
1384 distance_squared,
1385 mean,
1386 total_weight;
cristy3ed852e2009-09-05 21:47:34 +00001387
cristy6d8c3d72011-08-22 01:20:01 +00001388 PixelChannel
1389 channel;
cristy3ed852e2009-09-05 21:47:34 +00001390
cristy6d8c3d72011-08-22 01:20:01 +00001391 PixelTrait
1392 enhance_traits,
1393 traits;
cristy3ed852e2009-09-05 21:47:34 +00001394
cristy6d8c3d72011-08-22 01:20:01 +00001395 register const Quantum
1396 *restrict r;
1397
cristye2a912b2011-12-05 20:02:07 +00001398 channel=GetPixelChannelMapChannel(image,i);
cristyabace412011-12-11 15:56:53 +00001399 traits=GetPixelChannelMapTraits(image,channel);
cristy6d8c3d72011-08-22 01:20:01 +00001400 enhance_traits=GetPixelChannelMapTraits(enhance_image,channel);
cristy010d7d12011-08-31 01:02:48 +00001401 if ((traits == UndefinedPixelTrait) ||
1402 (enhance_traits == UndefinedPixelTrait))
cristy6d8c3d72011-08-22 01:20:01 +00001403 continue;
cristy0beccfa2011-09-25 20:47:53 +00001404 SetPixelChannel(enhance_image,channel,p[center+i],q);
cristy6d8c3d72011-08-22 01:20:01 +00001405 if ((enhance_traits & CopyPixelTrait) != 0)
1406 continue;
1407 /*
1408 Compute weighted average of target pixel color components.
1409 */
1410 aggregate=0.0;
1411 total_weight=0.0;
cristyf45fec72011-08-23 16:02:32 +00001412 r=p;
cristy6d8c3d72011-08-22 01:20:01 +00001413 EnhancePixel(5.0); EnhancePixel(8.0); EnhancePixel(10.0);
1414 EnhancePixel(8.0); EnhancePixel(5.0);
1415 r=p+1*GetPixelChannels(image)*(image->columns+4);
1416 EnhancePixel(8.0); EnhancePixel(20.0); EnhancePixel(40.0);
1417 EnhancePixel(20.0); EnhancePixel(8.0);
1418 r=p+2*GetPixelChannels(image)*(image->columns+4);
1419 EnhancePixel(10.0); EnhancePixel(40.0); EnhancePixel(80.0);
1420 EnhancePixel(40.0); EnhancePixel(10.0);
1421 r=p+3*GetPixelChannels(image)*(image->columns+4);
1422 EnhancePixel(8.0); EnhancePixel(20.0); EnhancePixel(40.0);
1423 EnhancePixel(20.0); EnhancePixel(8.0);
1424 r=p+4*GetPixelChannels(image)*(image->columns+4);
1425 EnhancePixel(5.0); EnhancePixel(8.0); EnhancePixel(10.0);
1426 EnhancePixel(8.0); EnhancePixel(5.0);
cristy0beccfa2011-09-25 20:47:53 +00001427 SetPixelChannel(enhance_image,channel,ClampToQuantum(aggregate/
1428 total_weight),q);
cristy6d8c3d72011-08-22 01:20:01 +00001429 }
cristyed231572011-07-14 02:18:59 +00001430 p+=GetPixelChannels(image);
1431 q+=GetPixelChannels(enhance_image);
cristy3ed852e2009-09-05 21:47:34 +00001432 }
1433 if (SyncCacheViewAuthenticPixels(enhance_view,exception) == MagickFalse)
1434 status=MagickFalse;
1435 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1436 {
1437 MagickBooleanType
1438 proceed;
1439
cristyb5d5f722009-11-04 03:03:49 +00001440#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00001441 #pragma omp critical (MagickCore_EnhanceImage)
1442#endif
1443 proceed=SetImageProgress(image,EnhanceImageTag,progress++,image->rows);
1444 if (proceed == MagickFalse)
1445 status=MagickFalse;
1446 }
1447 }
1448 enhance_view=DestroyCacheView(enhance_view);
1449 image_view=DestroyCacheView(image_view);
1450 return(enhance_image);
1451}
1452
1453/*
1454%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1455% %
1456% %
1457% %
1458% E q u a l i z e I m a g e %
1459% %
1460% %
1461% %
1462%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1463%
1464% EqualizeImage() applies a histogram equalization to the image.
1465%
1466% The format of the EqualizeImage method is:
1467%
cristy6d8c3d72011-08-22 01:20:01 +00001468% MagickBooleanType EqualizeImage(Image *image,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001469%
1470% A description of each parameter follows:
1471%
1472% o image: the image.
1473%
cristy6d8c3d72011-08-22 01:20:01 +00001474% o exception: return any errors or warnings in this structure.
cristy3ed852e2009-09-05 21:47:34 +00001475%
1476*/
cristy6d8c3d72011-08-22 01:20:01 +00001477MagickExport MagickBooleanType EqualizeImage(Image *image,
1478 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001479{
cristy3ed852e2009-09-05 21:47:34 +00001480#define EqualizeImageTag "Equalize/Image"
1481
cristyc4c8d132010-01-07 01:58:38 +00001482 CacheView
1483 *image_view;
1484
cristy3ed852e2009-09-05 21:47:34 +00001485 MagickBooleanType
1486 status;
1487
cristybb503372010-05-27 20:51:26 +00001488 MagickOffsetType
1489 progress;
1490
cristyf45fec72011-08-23 16:02:32 +00001491 MagickRealType
cristy5f95f4f2011-10-23 01:01:01 +00001492 black[CompositePixelChannel],
cristy3ed852e2009-09-05 21:47:34 +00001493 *equalize_map,
1494 *histogram,
cristy3ed852e2009-09-05 21:47:34 +00001495 *map,
cristy5f95f4f2011-10-23 01:01:01 +00001496 white[CompositePixelChannel];
cristy3ed852e2009-09-05 21:47:34 +00001497
cristybb503372010-05-27 20:51:26 +00001498 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001499 i;
1500
cristybb503372010-05-27 20:51:26 +00001501 ssize_t
1502 y;
1503
cristy3ed852e2009-09-05 21:47:34 +00001504 /*
1505 Allocate and initialize histogram arrays.
1506 */
1507 assert(image != (Image *) NULL);
1508 assert(image->signature == MagickSignature);
1509 if (image->debug != MagickFalse)
1510 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
cristyf45fec72011-08-23 16:02:32 +00001511 equalize_map=(MagickRealType *) AcquireQuantumMemory(MaxMap+1UL,
1512 GetPixelChannels(image)*sizeof(*equalize_map));
1513 histogram=(MagickRealType *) AcquireQuantumMemory(MaxMap+1UL,
1514 GetPixelChannels(image)*sizeof(*histogram));
1515 map=(MagickRealType *) AcquireQuantumMemory(MaxMap+1UL,
1516 GetPixelChannels(image)*sizeof(*map));
1517 if ((equalize_map == (MagickRealType *) NULL) ||
1518 (histogram == (MagickRealType *) NULL) ||
1519 (map == (MagickRealType *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00001520 {
cristyf45fec72011-08-23 16:02:32 +00001521 if (map != (MagickRealType *) NULL)
1522 map=(MagickRealType *) RelinquishMagickMemory(map);
1523 if (histogram != (MagickRealType *) NULL)
1524 histogram=(MagickRealType *) RelinquishMagickMemory(histogram);
1525 if (equalize_map != (MagickRealType *) NULL)
1526 equalize_map=(MagickRealType *) RelinquishMagickMemory(equalize_map);
cristy3ed852e2009-09-05 21:47:34 +00001527 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
1528 image->filename);
1529 }
1530 /*
1531 Form histogram.
1532 */
cristyf45fec72011-08-23 16:02:32 +00001533 status=MagickTrue;
1534 (void) ResetMagickMemory(histogram,0,(MaxMap+1)*GetPixelChannels(image)*
1535 sizeof(*histogram));
1536 image_view=AcquireCacheView(image);
cristybb503372010-05-27 20:51:26 +00001537 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001538 {
cristy4c08aed2011-07-01 19:47:50 +00001539 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00001540 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00001541
cristybb503372010-05-27 20:51:26 +00001542 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001543 x;
1544
cristyf45fec72011-08-23 16:02:32 +00001545 if (status == MagickFalse)
1546 continue;
1547 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00001548 if (p == (const Quantum *) NULL)
cristyf45fec72011-08-23 16:02:32 +00001549 {
1550 status=MagickFalse;
1551 continue;
1552 }
cristybb503372010-05-27 20:51:26 +00001553 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001554 {
cristyf45fec72011-08-23 16:02:32 +00001555 register ssize_t
1556 i;
1557
1558 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1559 histogram[GetPixelChannels(image)*ScaleQuantumToMap(p[i])+i]++;
cristyed231572011-07-14 02:18:59 +00001560 p+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00001561 }
1562 }
1563 /*
1564 Integrate the histogram to get the equalization map.
1565 */
cristyb5d5f722009-11-04 03:03:49 +00001566#if defined(MAGICKCORE_OPENMP_SUPPORT)
1567 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001568#endif
cristyf45fec72011-08-23 16:02:32 +00001569 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
cristy3ed852e2009-09-05 21:47:34 +00001570 {
cristyf45fec72011-08-23 16:02:32 +00001571 MagickRealType
1572 intensity;
1573
1574 register ssize_t
1575 j;
1576
1577 intensity=0.0;
1578 for (j=0; j <= (ssize_t) MaxMap; j++)
1579 {
1580 intensity+=histogram[GetPixelChannels(image)*j+i];
1581 map[GetPixelChannels(image)*j+i]=intensity;
1582 }
cristy3ed852e2009-09-05 21:47:34 +00001583 }
cristyf45fec72011-08-23 16:02:32 +00001584 (void) ResetMagickMemory(equalize_map,0,(MaxMap+1)*GetPixelChannels(image)*
1585 sizeof(*equalize_map));
1586#if defined(MAGICKCORE_OPENMP_SUPPORT)
1587 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
1588#endif
1589 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1590 {
1591 register ssize_t
1592 j;
1593
1594 black[i]=map[i];
1595 white[i]=map[GetPixelChannels(image)*MaxMap+i];
1596 if (black[i] != white[i])
1597 for (j=0; j <= (ssize_t) MaxMap; j++)
1598 equalize_map[GetPixelChannels(image)*j+i]=(MagickRealType)
1599 ScaleMapToQuantum((MagickRealType) ((MaxMap*(map[
1600 GetPixelChannels(image)*j+i]-black[i]))/(white[i]-black[i])));
1601 }
1602 histogram=(MagickRealType *) RelinquishMagickMemory(histogram);
1603 map=(MagickRealType *) RelinquishMagickMemory(map);
cristy3ed852e2009-09-05 21:47:34 +00001604 if (image->storage_class == PseudoClass)
1605 {
cristyf54798b2011-11-21 18:38:23 +00001606 PixelChannel
1607 channel;
1608
cristyf45fec72011-08-23 16:02:32 +00001609 register ssize_t
1610 j;
1611
cristy3ed852e2009-09-05 21:47:34 +00001612 /*
1613 Equalize colormap.
1614 */
cristyb5d5f722009-11-04 03:03:49 +00001615#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristydb483812011-09-08 13:17:11 +00001616 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001617#endif
cristyf45fec72011-08-23 16:02:32 +00001618 for (j=0; j < (ssize_t) image->colors; j++)
cristy3ed852e2009-09-05 21:47:34 +00001619 {
cristyf54798b2011-11-21 18:38:23 +00001620 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristyf45fec72011-08-23 16:02:32 +00001621 {
cristyf54798b2011-11-21 18:38:23 +00001622 channel=GetPixelChannelMapChannel(image,RedPixelChannel);
1623 if (black[channel] != white[channel])
cristyda1f9c12011-10-02 21:39:49 +00001624 image->colormap[j].red=equalize_map[GetPixelChannels(image)*
cristyf54798b2011-11-21 18:38:23 +00001625 ScaleQuantumToMap(ClampToQuantum(image->colormap[j].red))]+
1626 channel;
cristyf45fec72011-08-23 16:02:32 +00001627 }
cristyf54798b2011-11-21 18:38:23 +00001628 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristyf45fec72011-08-23 16:02:32 +00001629 {
cristyf54798b2011-11-21 18:38:23 +00001630 channel=GetPixelChannelMapChannel(image,GreenPixelChannel);
1631 if (black[channel] != white[channel])
cristyda1f9c12011-10-02 21:39:49 +00001632 image->colormap[j].green=equalize_map[GetPixelChannels(image)*
cristyf54798b2011-11-21 18:38:23 +00001633 ScaleQuantumToMap(ClampToQuantum(image->colormap[j].green))]+
1634 channel;
cristyf45fec72011-08-23 16:02:32 +00001635 }
cristyf54798b2011-11-21 18:38:23 +00001636 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristyf45fec72011-08-23 16:02:32 +00001637 {
cristyf54798b2011-11-21 18:38:23 +00001638 channel=GetPixelChannelMapChannel(image,BluePixelChannel);
1639 if (black[channel] != white[channel])
cristyda1f9c12011-10-02 21:39:49 +00001640 image->colormap[j].blue=equalize_map[GetPixelChannels(image)*
cristyf54798b2011-11-21 18:38:23 +00001641 ScaleQuantumToMap(ClampToQuantum(image->colormap[j].blue))]+
1642 channel;
cristyf45fec72011-08-23 16:02:32 +00001643 }
cristyf54798b2011-11-21 18:38:23 +00001644 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristyf45fec72011-08-23 16:02:32 +00001645 {
cristyf54798b2011-11-21 18:38:23 +00001646 channel=GetPixelChannelMapChannel(image,AlphaPixelChannel);
1647 if (black[channel] != white[channel])
cristyda1f9c12011-10-02 21:39:49 +00001648 image->colormap[j].alpha=equalize_map[GetPixelChannels(image)*
cristyf54798b2011-11-21 18:38:23 +00001649 ScaleQuantumToMap(ClampToQuantum(image->colormap[j].alpha))]+
1650 channel;
cristyf45fec72011-08-23 16:02:32 +00001651 }
cristy3ed852e2009-09-05 21:47:34 +00001652 }
1653 }
1654 /*
1655 Equalize image.
1656 */
cristy3ed852e2009-09-05 21:47:34 +00001657 progress=0;
cristyb5d5f722009-11-04 03:03:49 +00001658#if defined(MAGICKCORE_OPENMP_SUPPORT)
1659 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001660#endif
cristybb503372010-05-27 20:51:26 +00001661 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001662 {
cristy4c08aed2011-07-01 19:47:50 +00001663 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00001664 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001665
cristy8d4629b2010-08-30 17:59:46 +00001666 register ssize_t
1667 x;
1668
cristy3ed852e2009-09-05 21:47:34 +00001669 if (status == MagickFalse)
1670 continue;
1671 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristyacd2ed22011-08-30 01:44:23 +00001672 if (q == (Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001673 {
1674 status=MagickFalse;
1675 continue;
1676 }
cristybb503372010-05-27 20:51:26 +00001677 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001678 {
cristyf45fec72011-08-23 16:02:32 +00001679 register ssize_t
1680 i;
1681
1682 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1683 {
cristyabace412011-12-11 15:56:53 +00001684 PixelChannel
1685 channel;
1686
cristyf45fec72011-08-23 16:02:32 +00001687 PixelTrait
1688 traits;
1689
cristyabace412011-12-11 15:56:53 +00001690 channel=GetPixelChannelMapChannel(image,i);
1691 traits=GetPixelChannelMapTraits(image,channel);
cristy7c0a0a42011-08-23 17:57:25 +00001692 if (((traits & UpdatePixelTrait) != 0) && (black[i] != white[i]))
cristyf45fec72011-08-23 16:02:32 +00001693 q[i]=ClampToQuantum(equalize_map[GetPixelChannels(image)*
1694 ScaleQuantumToMap(q[i])+i]);
1695 }
cristyed231572011-07-14 02:18:59 +00001696 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00001697 }
1698 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
1699 status=MagickFalse;
1700 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1701 {
1702 MagickBooleanType
1703 proceed;
1704
cristyb5d5f722009-11-04 03:03:49 +00001705#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy50fbc382011-07-07 02:19:17 +00001706 #pragma omp critical (MagickCore_EqualizeImage)
cristy3ed852e2009-09-05 21:47:34 +00001707#endif
1708 proceed=SetImageProgress(image,EqualizeImageTag,progress++,image->rows);
1709 if (proceed == MagickFalse)
1710 status=MagickFalse;
1711 }
1712 }
1713 image_view=DestroyCacheView(image_view);
cristyf45fec72011-08-23 16:02:32 +00001714 equalize_map=(MagickRealType *) RelinquishMagickMemory(equalize_map);
cristy3ed852e2009-09-05 21:47:34 +00001715 return(status);
1716}
1717
1718/*
1719%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1720% %
1721% %
1722% %
1723% G a m m a I m a g e %
1724% %
1725% %
1726% %
1727%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1728%
1729% GammaImage() gamma-corrects a particular image channel. The same
1730% image viewed on different devices will have perceptual differences in the
1731% way the image's intensities are represented on the screen. Specify
1732% individual gamma levels for the red, green, and blue channels, or adjust
1733% all three with the gamma parameter. Values typically range from 0.8 to 2.3.
1734%
1735% You can also reduce the influence of a particular channel with a gamma
1736% value of 0.
1737%
1738% The format of the GammaImage method is:
1739%
cristyb3e7c6c2011-07-24 01:43:55 +00001740% MagickBooleanType GammaImage(Image *image,const double gamma,
1741% ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001742%
1743% A description of each parameter follows:
1744%
1745% o image: the image.
1746%
cristya6360142011-03-23 23:08:04 +00001747% o level: the image gamma as a string (e.g. 1.6,1.2,1.0).
1748%
cristy3ed852e2009-09-05 21:47:34 +00001749% o gamma: the image gamma.
1750%
1751*/
cristyb3e7c6c2011-07-24 01:43:55 +00001752MagickExport MagickBooleanType GammaImage(Image *image,const double gamma,
1753 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001754{
1755#define GammaCorrectImageTag "GammaCorrect/Image"
1756
cristyc4c8d132010-01-07 01:58:38 +00001757 CacheView
1758 *image_view;
1759
cristyda1f9c12011-10-02 21:39:49 +00001760 double
1761 *gamma_map;
1762
cristy3ed852e2009-09-05 21:47:34 +00001763 MagickBooleanType
1764 status;
1765
cristybb503372010-05-27 20:51:26 +00001766 MagickOffsetType
1767 progress;
1768
cristybb503372010-05-27 20:51:26 +00001769 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001770 i;
1771
cristybb503372010-05-27 20:51:26 +00001772 ssize_t
1773 y;
1774
cristy3ed852e2009-09-05 21:47:34 +00001775 /*
1776 Allocate and initialize gamma maps.
1777 */
1778 assert(image != (Image *) NULL);
1779 assert(image->signature == MagickSignature);
1780 if (image->debug != MagickFalse)
1781 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1782 if (gamma == 1.0)
1783 return(MagickTrue);
cristyda1f9c12011-10-02 21:39:49 +00001784 gamma_map=(double *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*gamma_map));
1785 if (gamma_map == (double *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001786 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
1787 image->filename);
1788 (void) ResetMagickMemory(gamma_map,0,(MaxMap+1)*sizeof(*gamma_map));
1789 if (gamma != 0.0)
cristyd476a8e2011-07-23 16:13:22 +00001790#if defined(MAGICKCORE_OPENMP_SUPPORT) && (MaxMap > 256)
1791 #pragma omp parallel for
cristy3ed852e2009-09-05 21:47:34 +00001792#endif
cristybb503372010-05-27 20:51:26 +00001793 for (i=0; i <= (ssize_t) MaxMap; i++)
cristyda1f9c12011-10-02 21:39:49 +00001794 gamma_map[i]=(MagickRealType) ScaleMapToQuantum((
1795 MagickRealType) (MaxMap*pow((double) i/MaxMap,1.0/gamma)));
cristy3ed852e2009-09-05 21:47:34 +00001796 if (image->storage_class == PseudoClass)
1797 {
1798 /*
1799 Gamma-correct colormap.
1800 */
cristyb5d5f722009-11-04 03:03:49 +00001801#if defined(MAGICKCORE_OPENMP_SUPPORT)
1802 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001803#endif
cristybb503372010-05-27 20:51:26 +00001804 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00001805 {
cristyed231572011-07-14 02:18:59 +00001806 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001807 image->colormap[i].red=gamma_map[
cristyda1f9c12011-10-02 21:39:49 +00001808 ScaleQuantumToMap(ClampToQuantum(image->colormap[i].red))];
cristyed231572011-07-14 02:18:59 +00001809 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001810 image->colormap[i].green=gamma_map[
cristyda1f9c12011-10-02 21:39:49 +00001811 ScaleQuantumToMap(ClampToQuantum(image->colormap[i].green))];
cristyed231572011-07-14 02:18:59 +00001812 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001813 image->colormap[i].blue=gamma_map[
cristyda1f9c12011-10-02 21:39:49 +00001814 ScaleQuantumToMap(ClampToQuantum(image->colormap[i].blue))];
cristyed231572011-07-14 02:18:59 +00001815 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001816 image->colormap[i].alpha=gamma_map[
cristyda1f9c12011-10-02 21:39:49 +00001817 ScaleQuantumToMap(ClampToQuantum(image->colormap[i].alpha))];
cristy3ed852e2009-09-05 21:47:34 +00001818 }
1819 }
1820 /*
1821 Gamma-correct image.
1822 */
1823 status=MagickTrue;
1824 progress=0;
cristy3ed852e2009-09-05 21:47:34 +00001825 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00001826#if defined(MAGICKCORE_OPENMP_SUPPORT)
1827 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001828#endif
cristybb503372010-05-27 20:51:26 +00001829 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001830 {
cristy4c08aed2011-07-01 19:47:50 +00001831 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00001832 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001833
cristy8d4629b2010-08-30 17:59:46 +00001834 register ssize_t
1835 x;
1836
cristy3ed852e2009-09-05 21:47:34 +00001837 if (status == MagickFalse)
1838 continue;
1839 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristyacd2ed22011-08-30 01:44:23 +00001840 if (q == (Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001841 {
1842 status=MagickFalse;
1843 continue;
1844 }
cristybb503372010-05-27 20:51:26 +00001845 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001846 {
cristyd476a8e2011-07-23 16:13:22 +00001847 register ssize_t
1848 i;
1849
cristya30d9ba2011-07-23 21:00:48 +00001850 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
cristyd476a8e2011-07-23 16:13:22 +00001851 {
cristyabace412011-12-11 15:56:53 +00001852 PixelChannel
1853 channel;
1854
cristyd476a8e2011-07-23 16:13:22 +00001855 PixelTrait
1856 traits;
1857
cristyabace412011-12-11 15:56:53 +00001858 channel=GetPixelChannelMapChannel(image,i);
1859 traits=GetPixelChannelMapTraits(image,channel);
cristyd476a8e2011-07-23 16:13:22 +00001860 if ((traits & UpdatePixelTrait) != 0)
cristyda1f9c12011-10-02 21:39:49 +00001861 q[i]=ClampToQuantum(gamma_map[ScaleQuantumToMap(q[i])]);
cristyd476a8e2011-07-23 16:13:22 +00001862 }
cristya30d9ba2011-07-23 21:00:48 +00001863 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00001864 }
cristy3ed852e2009-09-05 21:47:34 +00001865 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
1866 status=MagickFalse;
1867 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1868 {
1869 MagickBooleanType
1870 proceed;
1871
cristyb5d5f722009-11-04 03:03:49 +00001872#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy50fbc382011-07-07 02:19:17 +00001873 #pragma omp critical (MagickCore_GammaImage)
cristy3ed852e2009-09-05 21:47:34 +00001874#endif
1875 proceed=SetImageProgress(image,GammaCorrectImageTag,progress++,
1876 image->rows);
1877 if (proceed == MagickFalse)
1878 status=MagickFalse;
1879 }
1880 }
1881 image_view=DestroyCacheView(image_view);
cristyda1f9c12011-10-02 21:39:49 +00001882 gamma_map=(double *) RelinquishMagickMemory(gamma_map);
cristy3ed852e2009-09-05 21:47:34 +00001883 if (image->gamma != 0.0)
1884 image->gamma*=gamma;
1885 return(status);
1886}
1887
1888/*
1889%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1890% %
1891% %
1892% %
1893% H a l d C l u t I m a g e %
1894% %
1895% %
1896% %
1897%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1898%
1899% HaldClutImage() applies a Hald color lookup table to the image. A Hald
1900% color lookup table is a 3-dimensional color cube mapped to 2 dimensions.
1901% Create it with the HALD coder. You can apply any color transformation to
1902% the Hald image and then use this method to apply the transform to the
1903% image.
1904%
1905% The format of the HaldClutImage method is:
1906%
cristy7c0a0a42011-08-23 17:57:25 +00001907% MagickBooleanType HaldClutImage(Image *image,Image *hald_image,
1908% ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001909%
1910% A description of each parameter follows:
1911%
1912% o image: the image, which is replaced by indexed CLUT values
1913%
1914% o hald_image: the color lookup table image for replacement color values.
1915%
cristy7c0a0a42011-08-23 17:57:25 +00001916% o exception: return any errors or warnings in this structure.
1917%
cristy3ed852e2009-09-05 21:47:34 +00001918*/
1919
1920static inline size_t MagickMin(const size_t x,const size_t y)
1921{
1922 if (x < y)
1923 return(x);
1924 return(y);
1925}
1926
1927MagickExport MagickBooleanType HaldClutImage(Image *image,
cristy7c0a0a42011-08-23 17:57:25 +00001928 const Image *hald_image,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001929{
cristy3ed852e2009-09-05 21:47:34 +00001930#define HaldClutImageTag "Clut/Image"
1931
1932 typedef struct _HaldInfo
1933 {
1934 MagickRealType
1935 x,
1936 y,
1937 z;
1938 } HaldInfo;
1939
cristyfa112112010-01-04 17:48:07 +00001940 CacheView
cristyd551fbc2011-03-31 18:07:46 +00001941 *hald_view,
cristyfa112112010-01-04 17:48:07 +00001942 *image_view;
1943
cristy3ed852e2009-09-05 21:47:34 +00001944 double
1945 width;
1946
cristy3ed852e2009-09-05 21:47:34 +00001947 MagickBooleanType
1948 status;
1949
cristybb503372010-05-27 20:51:26 +00001950 MagickOffsetType
1951 progress;
1952
cristy4c08aed2011-07-01 19:47:50 +00001953 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00001954 zero;
1955
cristy3ed852e2009-09-05 21:47:34 +00001956 size_t
1957 cube_size,
1958 length,
1959 level;
1960
cristybb503372010-05-27 20:51:26 +00001961 ssize_t
1962 y;
1963
cristy3ed852e2009-09-05 21:47:34 +00001964 assert(image != (Image *) NULL);
1965 assert(image->signature == MagickSignature);
1966 if (image->debug != MagickFalse)
1967 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1968 assert(hald_image != (Image *) NULL);
1969 assert(hald_image->signature == MagickSignature);
cristy574cc262011-08-05 01:23:58 +00001970 if (SetImageStorageClass(image,DirectClass,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00001971 return(MagickFalse);
1972 if (image->matte == MagickFalse)
cristy63240882011-08-05 19:05:27 +00001973 (void) SetImageAlphaChannel(image,OpaqueAlphaChannel,exception);
cristy3ed852e2009-09-05 21:47:34 +00001974 /*
1975 Hald clut image.
1976 */
1977 status=MagickTrue;
1978 progress=0;
1979 length=MagickMin(hald_image->columns,hald_image->rows);
1980 for (level=2; (level*level*level) < length; level++) ;
1981 level*=level;
1982 cube_size=level*level;
1983 width=(double) hald_image->columns;
cristy4c08aed2011-07-01 19:47:50 +00001984 GetPixelInfo(hald_image,&zero);
cristy3ed852e2009-09-05 21:47:34 +00001985 image_view=AcquireCacheView(image);
cristyd551fbc2011-03-31 18:07:46 +00001986 hald_view=AcquireCacheView(hald_image);
cristyb5d5f722009-11-04 03:03:49 +00001987#if defined(MAGICKCORE_OPENMP_SUPPORT)
1988 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001989#endif
cristybb503372010-05-27 20:51:26 +00001990 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001991 {
cristy4c08aed2011-07-01 19:47:50 +00001992 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00001993 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001994
cristy8d4629b2010-08-30 17:59:46 +00001995 register ssize_t
1996 x;
1997
cristy3ed852e2009-09-05 21:47:34 +00001998 if (status == MagickFalse)
1999 continue;
2000 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristyacd2ed22011-08-30 01:44:23 +00002001 if (q == (Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00002002 {
2003 status=MagickFalse;
2004 continue;
2005 }
cristybb503372010-05-27 20:51:26 +00002006 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002007 {
cristy7c0a0a42011-08-23 17:57:25 +00002008 double
2009 offset;
2010
2011 HaldInfo
2012 point;
2013
2014 PixelInfo
2015 pixel,
2016 pixel1,
2017 pixel2,
2018 pixel3,
2019 pixel4;
2020
cristy4c08aed2011-07-01 19:47:50 +00002021 point.x=QuantumScale*(level-1.0)*GetPixelRed(image,q);
2022 point.y=QuantumScale*(level-1.0)*GetPixelGreen(image,q);
2023 point.z=QuantumScale*(level-1.0)*GetPixelBlue(image,q);
cristy3ed852e2009-09-05 21:47:34 +00002024 offset=point.x+level*floor(point.y)+cube_size*floor(point.z);
2025 point.x-=floor(point.x);
2026 point.y-=floor(point.y);
2027 point.z-=floor(point.z);
cristy7c0a0a42011-08-23 17:57:25 +00002028 pixel1=zero;
2029 (void) InterpolatePixelInfo(image,hald_view,image->interpolate,
2030 fmod(offset,width),floor(offset/width),&pixel1,exception);
2031 pixel2=zero;
2032 (void) InterpolatePixelInfo(image,hald_view,image->interpolate,
2033 fmod(offset+level,width),floor((offset+level)/width),&pixel2,exception);
2034 pixel3=zero;
2035 CompositePixelInfoAreaBlend(&pixel1,pixel1.alpha,&pixel2,pixel2.alpha,
2036 point.y,&pixel3);
cristy3ed852e2009-09-05 21:47:34 +00002037 offset+=cube_size;
cristy7c0a0a42011-08-23 17:57:25 +00002038 (void) InterpolatePixelInfo(image,hald_view,image->interpolate,
2039 fmod(offset,width),floor(offset/width),&pixel1,exception);
2040 (void) InterpolatePixelInfo(image,hald_view,image->interpolate,
2041 fmod(offset+level,width),floor((offset+level)/width),&pixel2,exception);
2042 pixel4=zero;
2043 CompositePixelInfoAreaBlend(&pixel1,pixel1.alpha,&pixel2,pixel2.alpha,
2044 point.y,&pixel4);
2045 pixel=zero;
2046 CompositePixelInfoAreaBlend(&pixel3,pixel3.alpha,&pixel4,pixel4.alpha,
2047 point.z,&pixel);
cristyed231572011-07-14 02:18:59 +00002048 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristyf45fec72011-08-23 16:02:32 +00002049 SetPixelRed(image,ClampToQuantum(pixel.red),q);
cristyed231572011-07-14 02:18:59 +00002050 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristyf45fec72011-08-23 16:02:32 +00002051 SetPixelGreen(image,ClampToQuantum(pixel.green),q);
cristyed231572011-07-14 02:18:59 +00002052 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristyf45fec72011-08-23 16:02:32 +00002053 SetPixelBlue(image,ClampToQuantum(pixel.blue),q);
cristyed231572011-07-14 02:18:59 +00002054 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00002055 (image->colorspace == CMYKColorspace))
cristyf45fec72011-08-23 16:02:32 +00002056 SetPixelBlack(image,ClampToQuantum(pixel.black),q);
2057 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) &&
2058 (image->matte != MagickFalse))
2059 SetPixelAlpha(image,ClampToQuantum(pixel.alpha),q);
cristyed231572011-07-14 02:18:59 +00002060 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00002061 }
2062 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2063 status=MagickFalse;
2064 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2065 {
2066 MagickBooleanType
2067 proceed;
2068
cristyb5d5f722009-11-04 03:03:49 +00002069#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf89cb1d2011-07-07 01:24:37 +00002070 #pragma omp critical (MagickCore_HaldClutImage)
cristy3ed852e2009-09-05 21:47:34 +00002071#endif
2072 proceed=SetImageProgress(image,HaldClutImageTag,progress++,image->rows);
2073 if (proceed == MagickFalse)
2074 status=MagickFalse;
2075 }
2076 }
cristyd551fbc2011-03-31 18:07:46 +00002077 hald_view=DestroyCacheView(hald_view);
cristy3ed852e2009-09-05 21:47:34 +00002078 image_view=DestroyCacheView(image_view);
cristy3ed852e2009-09-05 21:47:34 +00002079 return(status);
2080}
2081
2082/*
2083%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2084% %
2085% %
2086% %
2087% L e v e l I m a g e %
2088% %
2089% %
2090% %
2091%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2092%
2093% LevelImage() adjusts the levels of a particular image channel by
2094% scaling the colors falling between specified white and black points to
2095% the full available quantum range.
2096%
2097% The parameters provided represent the black, and white points. The black
2098% point specifies the darkest color in the image. Colors darker than the
2099% black point are set to zero. White point specifies the lightest color in
2100% the image. Colors brighter than the white point are set to the maximum
2101% quantum value.
2102%
2103% If a '!' flag is given, map black and white colors to the given levels
2104% rather than mapping those levels to black and white. See
cristy50fbc382011-07-07 02:19:17 +00002105% LevelizeImage() below.
cristy3ed852e2009-09-05 21:47:34 +00002106%
2107% Gamma specifies a gamma correction to apply to the image.
2108%
2109% The format of the LevelImage method is:
2110%
cristy01e9afd2011-08-10 17:38:41 +00002111% MagickBooleanType LevelImage(Image *image,const double black_point,
2112% const double white_point,const double gamma,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002113%
2114% A description of each parameter follows:
2115%
2116% o image: the image.
2117%
cristy01e9afd2011-08-10 17:38:41 +00002118% o black_point: The level to map zero (black) to.
2119%
2120% o white_point: The level to map QuantumRange (white) to.
2121%
2122% o exception: return any errors or warnings in this structure.
cristy3ed852e2009-09-05 21:47:34 +00002123%
2124*/
cristy780e9ef2011-12-18 23:33:50 +00002125
2126static MagickRealType LevelPixel(const double black_point,
2127 const double white_point,const double gamma,const MagickRealType pixel)
2128{
2129 double
2130 level_pixel,
2131 scale;
2132
2133 if (pixel < black_point)
2134 return(0.0);
2135 if (pixel > white_point)
2136 return(white_point);
2137 scale=(white_point != black_point) ? 1.0/(white_point-black_point) : 1.0;
2138 level_pixel=(MagickRealType) QuantumRange*pow(scale*((double) pixel-
2139 black_point),1.0/gamma);
2140 return(level_pixel);
2141}
2142
cristy7c0a0a42011-08-23 17:57:25 +00002143MagickExport MagickBooleanType LevelImage(Image *image,const double black_point,
2144 const double white_point,const double gamma,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002145{
2146#define LevelImageTag "Level/Image"
cristy3ed852e2009-09-05 21:47:34 +00002147
cristyc4c8d132010-01-07 01:58:38 +00002148 CacheView
2149 *image_view;
2150
cristy3ed852e2009-09-05 21:47:34 +00002151 MagickBooleanType
2152 status;
2153
cristybb503372010-05-27 20:51:26 +00002154 MagickOffsetType
2155 progress;
2156
cristy8d4629b2010-08-30 17:59:46 +00002157 register ssize_t
2158 i;
2159
cristybb503372010-05-27 20:51:26 +00002160 ssize_t
2161 y;
2162
cristy3ed852e2009-09-05 21:47:34 +00002163 /*
2164 Allocate and initialize levels map.
2165 */
2166 assert(image != (Image *) NULL);
2167 assert(image->signature == MagickSignature);
2168 if (image->debug != MagickFalse)
2169 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2170 if (image->storage_class == PseudoClass)
cristyb5d5f722009-11-04 03:03:49 +00002171#if defined(MAGICKCORE_OPENMP_SUPPORT)
2172 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002173#endif
cristybb503372010-05-27 20:51:26 +00002174 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00002175 {
2176 /*
2177 Level colormap.
2178 */
cristyed231572011-07-14 02:18:59 +00002179 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy780e9ef2011-12-18 23:33:50 +00002180 image->colormap[i].red=(double) ClampToQuantum(LevelPixel(black_point,
2181 white_point,gamma,image->colormap[i].red));
cristyed231572011-07-14 02:18:59 +00002182 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy780e9ef2011-12-18 23:33:50 +00002183 image->colormap[i].green=(double) ClampToQuantum(LevelPixel(black_point,
2184 white_point,gamma,image->colormap[i].green));
cristyed231572011-07-14 02:18:59 +00002185 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy780e9ef2011-12-18 23:33:50 +00002186 image->colormap[i].blue=(double) ClampToQuantum(LevelPixel(black_point,
2187 white_point,gamma,image->colormap[i].blue));
cristyed231572011-07-14 02:18:59 +00002188 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy780e9ef2011-12-18 23:33:50 +00002189 image->colormap[i].alpha=(double) ClampToQuantum(LevelPixel(black_point,
2190 white_point,gamma,image->colormap[i].alpha));
cristy3ed852e2009-09-05 21:47:34 +00002191 }
2192 /*
2193 Level image.
2194 */
2195 status=MagickTrue;
2196 progress=0;
cristy3ed852e2009-09-05 21:47:34 +00002197 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00002198#if defined(MAGICKCORE_OPENMP_SUPPORT)
2199 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002200#endif
cristybb503372010-05-27 20:51:26 +00002201 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002202 {
cristy4c08aed2011-07-01 19:47:50 +00002203 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00002204 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002205
cristy8d4629b2010-08-30 17:59:46 +00002206 register ssize_t
2207 x;
2208
cristy3ed852e2009-09-05 21:47:34 +00002209 if (status == MagickFalse)
2210 continue;
2211 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristyacd2ed22011-08-30 01:44:23 +00002212 if (q == (Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00002213 {
2214 status=MagickFalse;
2215 continue;
2216 }
cristybb503372010-05-27 20:51:26 +00002217 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002218 {
cristy7c0a0a42011-08-23 17:57:25 +00002219 register ssize_t
2220 i;
2221
2222 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
2223 {
cristyabace412011-12-11 15:56:53 +00002224 PixelChannel
2225 channel;
2226
cristy7c0a0a42011-08-23 17:57:25 +00002227 PixelTrait
2228 traits;
2229
cristyabace412011-12-11 15:56:53 +00002230 channel=GetPixelChannelMapChannel(image,i);
2231 traits=GetPixelChannelMapTraits(image,channel);
cristy010d7d12011-08-31 01:02:48 +00002232 if ((traits == UndefinedPixelTrait) ||
2233 ((traits & UpdatePixelTrait) == 0))
cristy7c0a0a42011-08-23 17:57:25 +00002234 continue;
cristy780e9ef2011-12-18 23:33:50 +00002235 q[i]=ClampToQuantum(LevelPixel(black_point,white_point,gamma,
2236 (MagickRealType) q[i]));
cristy7c0a0a42011-08-23 17:57:25 +00002237 }
cristyed231572011-07-14 02:18:59 +00002238 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00002239 }
2240 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2241 status=MagickFalse;
2242 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2243 {
2244 MagickBooleanType
2245 proceed;
2246
cristyb5d5f722009-11-04 03:03:49 +00002247#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf89cb1d2011-07-07 01:24:37 +00002248 #pragma omp critical (MagickCore_LevelImage)
cristy3ed852e2009-09-05 21:47:34 +00002249#endif
2250 proceed=SetImageProgress(image,LevelImageTag,progress++,image->rows);
2251 if (proceed == MagickFalse)
2252 status=MagickFalse;
2253 }
2254 }
2255 image_view=DestroyCacheView(image_view);
2256 return(status);
2257}
2258
2259/*
2260%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2261% %
2262% %
2263% %
cristy33bd5152011-08-24 01:42:24 +00002264% L e v e l i z e I m a g e %
cristy3ed852e2009-09-05 21:47:34 +00002265% %
2266% %
2267% %
2268%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2269%
cristy50fbc382011-07-07 02:19:17 +00002270% LevelizeImage() applies the reversed LevelImage() operation to just
cristy3ed852e2009-09-05 21:47:34 +00002271% the specific channels specified. It compresses the full range of color
2272% values, so that they lie between the given black and white points. Gamma is
2273% applied before the values are mapped.
2274%
cristy50fbc382011-07-07 02:19:17 +00002275% LevelizeImage() can be called with by using a +level command line
cristy3ed852e2009-09-05 21:47:34 +00002276% API option, or using a '!' on a -level or LevelImage() geometry string.
2277%
2278% It can be used for example de-contrast a greyscale image to the exact
2279% levels specified. Or by using specific levels for each channel of an image
2280% you can convert a gray-scale image to any linear color gradient, according
2281% to those levels.
2282%
cristy50fbc382011-07-07 02:19:17 +00002283% The format of the LevelizeImage method is:
cristy3ed852e2009-09-05 21:47:34 +00002284%
cristy50fbc382011-07-07 02:19:17 +00002285% MagickBooleanType LevelizeImage(Image *image,const double black_point,
cristy7c0a0a42011-08-23 17:57:25 +00002286% const double white_point,const double gamma,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002287%
2288% A description of each parameter follows:
2289%
2290% o image: the image.
2291%
cristy3ed852e2009-09-05 21:47:34 +00002292% o black_point: The level to map zero (black) to.
2293%
cristy01e9afd2011-08-10 17:38:41 +00002294% o white_point: The level to map QuantumRange (white) to.
cristy3ed852e2009-09-05 21:47:34 +00002295%
2296% o gamma: adjust gamma by this factor before mapping values.
2297%
cristy7c0a0a42011-08-23 17:57:25 +00002298% o exception: return any errors or warnings in this structure.
2299%
cristy3ed852e2009-09-05 21:47:34 +00002300*/
cristyd1a2c0f2011-02-09 14:14:50 +00002301MagickExport MagickBooleanType LevelizeImage(Image *image,
cristy7c0a0a42011-08-23 17:57:25 +00002302 const double black_point,const double white_point,const double gamma,
2303 ExceptionInfo *exception)
cristyd1a2c0f2011-02-09 14:14:50 +00002304{
cristy3ed852e2009-09-05 21:47:34 +00002305#define LevelizeImageTag "Levelize/Image"
cristyce70c172010-01-07 17:15:30 +00002306#define LevelizeValue(x) (ClampToQuantum(((MagickRealType) \
cristy50fbc382011-07-07 02:19:17 +00002307 pow((double) (QuantumScale*(x)),1.0/gamma))*(white_point-black_point)+ \
cristy3ed852e2009-09-05 21:47:34 +00002308 black_point))
2309
cristyc4c8d132010-01-07 01:58:38 +00002310 CacheView
2311 *image_view;
2312
cristy3ed852e2009-09-05 21:47:34 +00002313 MagickBooleanType
2314 status;
2315
cristybb503372010-05-27 20:51:26 +00002316 MagickOffsetType
2317 progress;
2318
2319 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002320 i;
2321
cristybb503372010-05-27 20:51:26 +00002322 ssize_t
2323 y;
2324
cristy3ed852e2009-09-05 21:47:34 +00002325 /*
2326 Allocate and initialize levels map.
2327 */
2328 assert(image != (Image *) NULL);
2329 assert(image->signature == MagickSignature);
2330 if (image->debug != MagickFalse)
2331 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2332 if (image->storage_class == PseudoClass)
cristyb5d5f722009-11-04 03:03:49 +00002333#if defined(MAGICKCORE_OPENMP_SUPPORT)
2334 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002335#endif
cristybb503372010-05-27 20:51:26 +00002336 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00002337 {
2338 /*
2339 Level colormap.
2340 */
cristyed231572011-07-14 02:18:59 +00002341 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristyda1f9c12011-10-02 21:39:49 +00002342 image->colormap[i].red=(double) LevelizeValue(
2343 image->colormap[i].red);
cristyed231572011-07-14 02:18:59 +00002344 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristyda1f9c12011-10-02 21:39:49 +00002345 image->colormap[i].green=(double) LevelizeValue(
2346 image->colormap[i].green);
cristyed231572011-07-14 02:18:59 +00002347 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristyda1f9c12011-10-02 21:39:49 +00002348 image->colormap[i].blue=(double) LevelizeValue(
2349 image->colormap[i].blue);
cristyed231572011-07-14 02:18:59 +00002350 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristyda1f9c12011-10-02 21:39:49 +00002351 image->colormap[i].alpha=(double) LevelizeValue(
2352 image->colormap[i].alpha);
cristy3ed852e2009-09-05 21:47:34 +00002353 }
2354 /*
2355 Level image.
2356 */
2357 status=MagickTrue;
2358 progress=0;
cristy3ed852e2009-09-05 21:47:34 +00002359 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00002360#if defined(MAGICKCORE_OPENMP_SUPPORT)
2361 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002362#endif
cristybb503372010-05-27 20:51:26 +00002363 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002364 {
cristy4c08aed2011-07-01 19:47:50 +00002365 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00002366 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002367
cristy8d4629b2010-08-30 17:59:46 +00002368 register ssize_t
2369 x;
2370
cristy3ed852e2009-09-05 21:47:34 +00002371 if (status == MagickFalse)
2372 continue;
2373 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristyacd2ed22011-08-30 01:44:23 +00002374 if (q == (Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00002375 {
2376 status=MagickFalse;
2377 continue;
2378 }
cristybb503372010-05-27 20:51:26 +00002379 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002380 {
cristy7c0a0a42011-08-23 17:57:25 +00002381 register ssize_t
2382 i;
2383
2384 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
2385 {
cristyabace412011-12-11 15:56:53 +00002386 PixelChannel
2387 channel;
2388
cristy7c0a0a42011-08-23 17:57:25 +00002389 PixelTrait
2390 traits;
2391
cristyabace412011-12-11 15:56:53 +00002392 channel=GetPixelChannelMapChannel(image,i);
2393 traits=GetPixelChannelMapTraits(image,channel);
cristy780e9ef2011-12-18 23:33:50 +00002394 if ((traits == UndefinedPixelTrait) ||
2395 ((traits & UpdatePixelTrait) == 0))
2396 q[i]=LevelizeValue(q[i]);
cristy7c0a0a42011-08-23 17:57:25 +00002397 }
cristyed231572011-07-14 02:18:59 +00002398 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00002399 }
2400 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2401 status=MagickFalse;
2402 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2403 {
2404 MagickBooleanType
2405 proceed;
2406
cristyb5d5f722009-11-04 03:03:49 +00002407#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy50fbc382011-07-07 02:19:17 +00002408 #pragma omp critical (MagickCore_LevelizeImage)
cristy3ed852e2009-09-05 21:47:34 +00002409#endif
2410 proceed=SetImageProgress(image,LevelizeImageTag,progress++,image->rows);
2411 if (proceed == MagickFalse)
2412 status=MagickFalse;
2413 }
2414 }
cristy8d4629b2010-08-30 17:59:46 +00002415 image_view=DestroyCacheView(image_view);
cristy3ed852e2009-09-05 21:47:34 +00002416 return(status);
2417}
2418
2419/*
2420%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2421% %
2422% %
2423% %
2424% L e v e l I m a g e C o l o r s %
2425% %
2426% %
2427% %
2428%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2429%
cristy490408a2011-07-07 14:42:05 +00002430% LevelImageColors() maps the given color to "black" and "white" values,
cristyee0f8d72009-09-19 00:58:29 +00002431% linearly spreading out the colors, and level values on a channel by channel
2432% bases, as per LevelImage(). The given colors allows you to specify
glennrp1e7f7bc2011-03-02 19:25:28 +00002433% different level ranges for each of the color channels separately.
cristy3ed852e2009-09-05 21:47:34 +00002434%
2435% If the boolean 'invert' is set true the image values will modifyed in the
2436% reverse direction. That is any existing "black" and "white" colors in the
2437% image will become the color values given, with all other values compressed
2438% appropriatally. This effectivally maps a greyscale gradient into the given
2439% color gradient.
2440%
cristy490408a2011-07-07 14:42:05 +00002441% The format of the LevelImageColors method is:
cristy3ed852e2009-09-05 21:47:34 +00002442%
cristy490408a2011-07-07 14:42:05 +00002443% MagickBooleanType LevelImageColors(Image *image,
2444% const PixelInfo *black_color,const PixelInfo *white_color,
cristy7c0a0a42011-08-23 17:57:25 +00002445% const MagickBooleanType invert,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002446%
2447% A description of each parameter follows:
2448%
2449% o image: the image.
2450%
cristy3ed852e2009-09-05 21:47:34 +00002451% o black_color: The color to map black to/from
2452%
2453% o white_point: The color to map white to/from
2454%
2455% o invert: if true map the colors (levelize), rather than from (level)
2456%
cristy7c0a0a42011-08-23 17:57:25 +00002457% o exception: return any errors or warnings in this structure.
2458%
cristy3ed852e2009-09-05 21:47:34 +00002459*/
cristy490408a2011-07-07 14:42:05 +00002460MagickExport MagickBooleanType LevelImageColors(Image *image,
cristy4c08aed2011-07-01 19:47:50 +00002461 const PixelInfo *black_color,const PixelInfo *white_color,
cristy7c0a0a42011-08-23 17:57:25 +00002462 const MagickBooleanType invert,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002463{
cristybd5a96c2011-08-21 00:04:26 +00002464 ChannelType
2465 channel_mask;
2466
cristy3ed852e2009-09-05 21:47:34 +00002467 MagickStatusType
2468 status;
2469
2470 /*
2471 Allocate and initialize levels map.
2472 */
2473 assert(image != (Image *) NULL);
2474 assert(image->signature == MagickSignature);
2475 if (image->debug != MagickFalse)
2476 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2477 status=MagickFalse;
2478 if (invert == MagickFalse)
2479 {
cristyed231572011-07-14 02:18:59 +00002480 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristyf89cb1d2011-07-07 01:24:37 +00002481 {
cristybd5a96c2011-08-21 00:04:26 +00002482 channel_mask=SetPixelChannelMask(image,RedChannel);
cristy01e9afd2011-08-10 17:38:41 +00002483 status|=LevelImage(image,black_color->red,white_color->red,1.0,
cristy7c0a0a42011-08-23 17:57:25 +00002484 exception);
cristybd5a96c2011-08-21 00:04:26 +00002485 (void) SetPixelChannelMask(image,channel_mask);
cristyf89cb1d2011-07-07 01:24:37 +00002486 }
cristyed231572011-07-14 02:18:59 +00002487 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristyf89cb1d2011-07-07 01:24:37 +00002488 {
cristybd5a96c2011-08-21 00:04:26 +00002489 channel_mask=SetPixelChannelMask(image,GreenChannel);
cristy01e9afd2011-08-10 17:38:41 +00002490 status|=LevelImage(image,black_color->green,white_color->green,1.0,
cristy7c0a0a42011-08-23 17:57:25 +00002491 exception);
cristybd5a96c2011-08-21 00:04:26 +00002492 (void) SetPixelChannelMask(image,channel_mask);
cristyf89cb1d2011-07-07 01:24:37 +00002493 }
cristyed231572011-07-14 02:18:59 +00002494 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristyf89cb1d2011-07-07 01:24:37 +00002495 {
cristybd5a96c2011-08-21 00:04:26 +00002496 channel_mask=SetPixelChannelMask(image,BlueChannel);
cristy01e9afd2011-08-10 17:38:41 +00002497 status|=LevelImage(image,black_color->blue,white_color->blue,1.0,
cristy7c0a0a42011-08-23 17:57:25 +00002498 exception);
cristybd5a96c2011-08-21 00:04:26 +00002499 (void) SetPixelChannelMask(image,channel_mask);
cristyf89cb1d2011-07-07 01:24:37 +00002500 }
cristyed231572011-07-14 02:18:59 +00002501 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +00002502 (image->colorspace == CMYKColorspace))
cristyf89cb1d2011-07-07 01:24:37 +00002503 {
cristybd5a96c2011-08-21 00:04:26 +00002504 channel_mask=SetPixelChannelMask(image,BlackChannel);
cristy01e9afd2011-08-10 17:38:41 +00002505 status|=LevelImage(image,black_color->black,white_color->black,1.0,
cristy7c0a0a42011-08-23 17:57:25 +00002506 exception);
cristybd5a96c2011-08-21 00:04:26 +00002507 (void) SetPixelChannelMask(image,channel_mask);
cristyf89cb1d2011-07-07 01:24:37 +00002508 }
cristyed231572011-07-14 02:18:59 +00002509 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00002510 (image->matte == MagickTrue))
cristyf89cb1d2011-07-07 01:24:37 +00002511 {
cristybd5a96c2011-08-21 00:04:26 +00002512 channel_mask=SetPixelChannelMask(image,AlphaChannel);
cristy01e9afd2011-08-10 17:38:41 +00002513 status|=LevelImage(image,black_color->alpha,white_color->alpha,1.0,
cristy7c0a0a42011-08-23 17:57:25 +00002514 exception);
cristybd5a96c2011-08-21 00:04:26 +00002515 (void) SetPixelChannelMask(image,channel_mask);
cristyf89cb1d2011-07-07 01:24:37 +00002516 }
cristy3ed852e2009-09-05 21:47:34 +00002517 }
2518 else
2519 {
cristyed231572011-07-14 02:18:59 +00002520 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristyf89cb1d2011-07-07 01:24:37 +00002521 {
cristybd5a96c2011-08-21 00:04:26 +00002522 channel_mask=SetPixelChannelMask(image,RedChannel);
cristy7c0a0a42011-08-23 17:57:25 +00002523 status|=LevelizeImage(image,black_color->red,white_color->red,1.0,
2524 exception);
cristybd5a96c2011-08-21 00:04:26 +00002525 (void) SetPixelChannelMask(image,channel_mask);
cristyf89cb1d2011-07-07 01:24:37 +00002526 }
cristyed231572011-07-14 02:18:59 +00002527 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristyf89cb1d2011-07-07 01:24:37 +00002528 {
cristybd5a96c2011-08-21 00:04:26 +00002529 channel_mask=SetPixelChannelMask(image,GreenChannel);
cristy7c0a0a42011-08-23 17:57:25 +00002530 status|=LevelizeImage(image,black_color->green,white_color->green,1.0,
2531 exception);
cristybd5a96c2011-08-21 00:04:26 +00002532 (void) SetPixelChannelMask(image,channel_mask);
cristyf89cb1d2011-07-07 01:24:37 +00002533 }
cristyed231572011-07-14 02:18:59 +00002534 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristyf89cb1d2011-07-07 01:24:37 +00002535 {
cristybd5a96c2011-08-21 00:04:26 +00002536 channel_mask=SetPixelChannelMask(image,BlueChannel);
cristy7c0a0a42011-08-23 17:57:25 +00002537 status|=LevelizeImage(image,black_color->blue,white_color->blue,1.0,
2538 exception);
cristybd5a96c2011-08-21 00:04:26 +00002539 (void) SetPixelChannelMask(image,channel_mask);
cristyf89cb1d2011-07-07 01:24:37 +00002540 }
cristyed231572011-07-14 02:18:59 +00002541 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +00002542 (image->colorspace == CMYKColorspace))
cristyf89cb1d2011-07-07 01:24:37 +00002543 {
cristybd5a96c2011-08-21 00:04:26 +00002544 channel_mask=SetPixelChannelMask(image,BlackChannel);
cristy7c0a0a42011-08-23 17:57:25 +00002545 status|=LevelizeImage(image,black_color->black,white_color->black,1.0,
2546 exception);
cristybd5a96c2011-08-21 00:04:26 +00002547 (void) SetPixelChannelMask(image,channel_mask);
cristyf89cb1d2011-07-07 01:24:37 +00002548 }
cristyed231572011-07-14 02:18:59 +00002549 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00002550 (image->matte == MagickTrue))
cristyf89cb1d2011-07-07 01:24:37 +00002551 {
cristybd5a96c2011-08-21 00:04:26 +00002552 channel_mask=SetPixelChannelMask(image,AlphaChannel);
cristy7c0a0a42011-08-23 17:57:25 +00002553 status|=LevelizeImage(image,black_color->alpha,white_color->alpha,1.0,
2554 exception);
cristybd5a96c2011-08-21 00:04:26 +00002555 (void) SetPixelChannelMask(image,channel_mask);
cristyf89cb1d2011-07-07 01:24:37 +00002556 }
cristy3ed852e2009-09-05 21:47:34 +00002557 }
2558 return(status == 0 ? MagickFalse : MagickTrue);
2559}
2560
2561/*
2562%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2563% %
2564% %
2565% %
2566% L i n e a r S t r e t c h I m a g e %
2567% %
2568% %
2569% %
2570%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2571%
cristyf1611782011-08-01 15:39:13 +00002572% LinearStretchImage() discards any pixels below the black point and above
2573% the white point and levels the remaining pixels.
cristy3ed852e2009-09-05 21:47:34 +00002574%
2575% The format of the LinearStretchImage method is:
2576%
2577% MagickBooleanType LinearStretchImage(Image *image,
cristy33bd5152011-08-24 01:42:24 +00002578% const double black_point,const double white_point,
2579% ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002580%
2581% A description of each parameter follows:
2582%
2583% o image: the image.
2584%
2585% o black_point: the black point.
2586%
2587% o white_point: the white point.
2588%
cristy33bd5152011-08-24 01:42:24 +00002589% o exception: return any errors or warnings in this structure.
2590%
cristy3ed852e2009-09-05 21:47:34 +00002591*/
2592MagickExport MagickBooleanType LinearStretchImage(Image *image,
cristy33bd5152011-08-24 01:42:24 +00002593 const double black_point,const double white_point,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002594{
2595#define LinearStretchImageTag "LinearStretch/Image"
2596
cristy33bd5152011-08-24 01:42:24 +00002597 CacheView
2598 *image_view;
cristy3ed852e2009-09-05 21:47:34 +00002599
cristy3ed852e2009-09-05 21:47:34 +00002600 MagickBooleanType
2601 status;
2602
2603 MagickRealType
2604 *histogram,
2605 intensity;
2606
cristy8d4629b2010-08-30 17:59:46 +00002607 ssize_t
2608 black,
2609 white,
2610 y;
2611
cristy3ed852e2009-09-05 21:47:34 +00002612 /*
2613 Allocate histogram and linear map.
2614 */
2615 assert(image != (Image *) NULL);
2616 assert(image->signature == MagickSignature);
2617 histogram=(MagickRealType *) AcquireQuantumMemory(MaxMap+1UL,
2618 sizeof(*histogram));
2619 if (histogram == (MagickRealType *) NULL)
2620 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
2621 image->filename);
2622 /*
2623 Form histogram.
2624 */
2625 (void) ResetMagickMemory(histogram,0,(MaxMap+1)*sizeof(*histogram));
cristy33bd5152011-08-24 01:42:24 +00002626 image_view=AcquireCacheView(image);
cristybb503372010-05-27 20:51:26 +00002627 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002628 {
cristy4c08aed2011-07-01 19:47:50 +00002629 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00002630 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00002631
cristybb503372010-05-27 20:51:26 +00002632 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002633 x;
2634
cristy33bd5152011-08-24 01:42:24 +00002635 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00002636 if (p == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00002637 break;
cristy33bd5152011-08-24 01:42:24 +00002638 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002639 {
cristy4c08aed2011-07-01 19:47:50 +00002640 histogram[ScaleQuantumToMap(GetPixelIntensity(image,p))]++;
cristyed231572011-07-14 02:18:59 +00002641 p+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00002642 }
2643 }
cristy33bd5152011-08-24 01:42:24 +00002644 image_view=DestroyCacheView(image_view);
cristy3ed852e2009-09-05 21:47:34 +00002645 /*
2646 Find the histogram boundaries by locating the black and white point levels.
2647 */
cristy3ed852e2009-09-05 21:47:34 +00002648 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00002649 for (black=0; black < (ssize_t) MaxMap; black++)
cristy3ed852e2009-09-05 21:47:34 +00002650 {
2651 intensity+=histogram[black];
2652 if (intensity >= black_point)
2653 break;
2654 }
2655 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00002656 for (white=(ssize_t) MaxMap; white != 0; white--)
cristy3ed852e2009-09-05 21:47:34 +00002657 {
2658 intensity+=histogram[white];
2659 if (intensity >= white_point)
2660 break;
2661 }
2662 histogram=(MagickRealType *) RelinquishMagickMemory(histogram);
cristyc82a27b2011-10-21 01:07:16 +00002663 status=LevelImage(image,(double) black,(double) white,1.0,exception);
cristy3ed852e2009-09-05 21:47:34 +00002664 return(status);
2665}
2666
2667/*
2668%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2669% %
2670% %
2671% %
2672% M o d u l a t e I m a g e %
2673% %
2674% %
2675% %
2676%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2677%
2678% ModulateImage() lets you control the brightness, saturation, and hue
2679% of an image. Modulate represents the brightness, saturation, and hue
2680% as one parameter (e.g. 90,150,100). If the image colorspace is HSL, the
2681% modulation is lightness, saturation, and hue. And if the colorspace is
2682% HWB, use blackness, whiteness, and hue.
2683%
2684% The format of the ModulateImage method is:
2685%
cristy33bd5152011-08-24 01:42:24 +00002686% MagickBooleanType ModulateImage(Image *image,const char *modulate,
2687% ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002688%
2689% A description of each parameter follows:
2690%
2691% o image: the image.
2692%
cristy33bd5152011-08-24 01:42:24 +00002693% o modulate: Define the percent change in brightness, saturation, and hue.
2694%
2695% o exception: return any errors or warnings in this structure.
cristy3ed852e2009-09-05 21:47:34 +00002696%
2697*/
2698
2699static void ModulateHSB(const double percent_hue,
cristy3094b7f2011-10-01 23:18:02 +00002700 const double percent_saturation,const double percent_brightness,double *red,
2701 double *green,double *blue)
cristy3ed852e2009-09-05 21:47:34 +00002702{
2703 double
2704 brightness,
2705 hue,
2706 saturation;
2707
2708 /*
2709 Increase or decrease color brightness, saturation, or hue.
2710 */
cristy3094b7f2011-10-01 23:18:02 +00002711 assert(red != (double *) NULL);
2712 assert(green != (double *) NULL);
2713 assert(blue != (double *) NULL);
cristy3ed852e2009-09-05 21:47:34 +00002714 ConvertRGBToHSB(*red,*green,*blue,&hue,&saturation,&brightness);
2715 hue+=0.5*(0.01*percent_hue-1.0);
2716 while (hue < 0.0)
2717 hue+=1.0;
2718 while (hue > 1.0)
2719 hue-=1.0;
2720 saturation*=0.01*percent_saturation;
2721 brightness*=0.01*percent_brightness;
2722 ConvertHSBToRGB(hue,saturation,brightness,red,green,blue);
2723}
2724
2725static void ModulateHSL(const double percent_hue,
cristy3094b7f2011-10-01 23:18:02 +00002726 const double percent_saturation,const double percent_lightness,double *red,
2727 double *green,double *blue)
cristy3ed852e2009-09-05 21:47:34 +00002728{
2729 double
2730 hue,
2731 lightness,
2732 saturation;
2733
2734 /*
2735 Increase or decrease color lightness, saturation, or hue.
2736 */
cristy3094b7f2011-10-01 23:18:02 +00002737 assert(red != (double *) NULL);
2738 assert(green != (double *) NULL);
2739 assert(blue != (double *) NULL);
cristy3ed852e2009-09-05 21:47:34 +00002740 ConvertRGBToHSL(*red,*green,*blue,&hue,&saturation,&lightness);
2741 hue+=0.5*(0.01*percent_hue-1.0);
2742 while (hue < 0.0)
2743 hue+=1.0;
2744 while (hue > 1.0)
2745 hue-=1.0;
2746 saturation*=0.01*percent_saturation;
2747 lightness*=0.01*percent_lightness;
2748 ConvertHSLToRGB(hue,saturation,lightness,red,green,blue);
2749}
2750
cristy3094b7f2011-10-01 23:18:02 +00002751static 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 +00002752{
2753 double
2754 blackness,
2755 hue,
2756 whiteness;
2757
2758 /*
2759 Increase or decrease color blackness, whiteness, or hue.
2760 */
cristy3094b7f2011-10-01 23:18:02 +00002761 assert(red != (double *) NULL);
2762 assert(green != (double *) NULL);
2763 assert(blue != (double *) NULL);
cristy3ed852e2009-09-05 21:47:34 +00002764 ConvertRGBToHWB(*red,*green,*blue,&hue,&whiteness,&blackness);
2765 hue+=0.5*(0.01*percent_hue-1.0);
2766 while (hue < 0.0)
2767 hue+=1.0;
2768 while (hue > 1.0)
2769 hue-=1.0;
2770 blackness*=0.01*percent_blackness;
2771 whiteness*=0.01*percent_whiteness;
2772 ConvertHWBToRGB(hue,whiteness,blackness,red,green,blue);
2773}
2774
cristy33bd5152011-08-24 01:42:24 +00002775MagickExport MagickBooleanType ModulateImage(Image *image,const char *modulate,
2776 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002777{
2778#define ModulateImageTag "Modulate/Image"
2779
cristyc4c8d132010-01-07 01:58:38 +00002780 CacheView
2781 *image_view;
2782
cristy3ed852e2009-09-05 21:47:34 +00002783 ColorspaceType
2784 colorspace;
2785
2786 const char
2787 *artifact;
2788
2789 double
2790 percent_brightness,
2791 percent_hue,
2792 percent_saturation;
2793
cristy3ed852e2009-09-05 21:47:34 +00002794 GeometryInfo
2795 geometry_info;
2796
cristy3ed852e2009-09-05 21:47:34 +00002797 MagickBooleanType
2798 status;
2799
cristybb503372010-05-27 20:51:26 +00002800 MagickOffsetType
2801 progress;
2802
cristy3ed852e2009-09-05 21:47:34 +00002803 MagickStatusType
2804 flags;
2805
cristybb503372010-05-27 20:51:26 +00002806 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002807 i;
2808
cristybb503372010-05-27 20:51:26 +00002809 ssize_t
2810 y;
2811
cristy3ed852e2009-09-05 21:47:34 +00002812 /*
cristy2b726bd2010-01-11 01:05:39 +00002813 Initialize modulate table.
cristy3ed852e2009-09-05 21:47:34 +00002814 */
2815 assert(image != (Image *) NULL);
2816 assert(image->signature == MagickSignature);
2817 if (image->debug != MagickFalse)
2818 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2819 if (modulate == (char *) NULL)
2820 return(MagickFalse);
2821 flags=ParseGeometry(modulate,&geometry_info);
2822 percent_brightness=geometry_info.rho;
2823 percent_saturation=geometry_info.sigma;
2824 if ((flags & SigmaValue) == 0)
2825 percent_saturation=100.0;
2826 percent_hue=geometry_info.xi;
2827 if ((flags & XiValue) == 0)
2828 percent_hue=100.0;
2829 colorspace=UndefinedColorspace;
2830 artifact=GetImageArtifact(image,"modulate:colorspace");
2831 if (artifact != (const char *) NULL)
cristy042ee782011-04-22 18:48:30 +00002832 colorspace=(ColorspaceType) ParseCommandOption(MagickColorspaceOptions,
cristy3ed852e2009-09-05 21:47:34 +00002833 MagickFalse,artifact);
2834 if (image->storage_class == PseudoClass)
2835 {
2836 /*
2837 Modulate colormap.
2838 */
cristyb5d5f722009-11-04 03:03:49 +00002839#if defined(MAGICKCORE_OPENMP_SUPPORT)
2840 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002841#endif
cristybb503372010-05-27 20:51:26 +00002842 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00002843 switch (colorspace)
2844 {
2845 case HSBColorspace:
2846 {
2847 ModulateHSB(percent_hue,percent_saturation,percent_brightness,
2848 &image->colormap[i].red,&image->colormap[i].green,
2849 &image->colormap[i].blue);
2850 break;
2851 }
2852 case HSLColorspace:
2853 default:
2854 {
2855 ModulateHSL(percent_hue,percent_saturation,percent_brightness,
2856 &image->colormap[i].red,&image->colormap[i].green,
2857 &image->colormap[i].blue);
2858 break;
2859 }
2860 case HWBColorspace:
2861 {
2862 ModulateHWB(percent_hue,percent_saturation,percent_brightness,
2863 &image->colormap[i].red,&image->colormap[i].green,
2864 &image->colormap[i].blue);
2865 break;
2866 }
2867 }
2868 }
2869 /*
2870 Modulate image.
2871 */
2872 status=MagickTrue;
2873 progress=0;
cristy3ed852e2009-09-05 21:47:34 +00002874 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00002875#if defined(MAGICKCORE_OPENMP_SUPPORT)
2876 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002877#endif
cristybb503372010-05-27 20:51:26 +00002878 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002879 {
cristy3094b7f2011-10-01 23:18:02 +00002880 double
cristy5afeab82011-04-30 01:30:09 +00002881 blue,
2882 green,
2883 red;
2884
cristy4c08aed2011-07-01 19:47:50 +00002885 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00002886 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002887
cristy8d4629b2010-08-30 17:59:46 +00002888 register ssize_t
2889 x;
2890
cristy3ed852e2009-09-05 21:47:34 +00002891 if (status == MagickFalse)
2892 continue;
2893 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristyacd2ed22011-08-30 01:44:23 +00002894 if (q == (Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00002895 {
2896 status=MagickFalse;
2897 continue;
2898 }
cristybb503372010-05-27 20:51:26 +00002899 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002900 {
cristyda1f9c12011-10-02 21:39:49 +00002901 red=(double) GetPixelRed(image,q);
2902 green=(double) GetPixelGreen(image,q);
2903 blue=(double) GetPixelBlue(image,q);
cristy3ed852e2009-09-05 21:47:34 +00002904 switch (colorspace)
2905 {
2906 case HSBColorspace:
2907 {
2908 ModulateHSB(percent_hue,percent_saturation,percent_brightness,
cristy5afeab82011-04-30 01:30:09 +00002909 &red,&green,&blue);
cristy3ed852e2009-09-05 21:47:34 +00002910 break;
2911 }
2912 case HSLColorspace:
2913 default:
2914 {
2915 ModulateHSL(percent_hue,percent_saturation,percent_brightness,
cristy5afeab82011-04-30 01:30:09 +00002916 &red,&green,&blue);
cristy3ed852e2009-09-05 21:47:34 +00002917 break;
2918 }
2919 case HWBColorspace:
2920 {
2921 ModulateHWB(percent_hue,percent_saturation,percent_brightness,
cristy5afeab82011-04-30 01:30:09 +00002922 &red,&green,&blue);
cristy3ed852e2009-09-05 21:47:34 +00002923 break;
2924 }
2925 }
cristy3094b7f2011-10-01 23:18:02 +00002926 SetPixelRed(image,ClampToQuantum(red),q);
2927 SetPixelGreen(image,ClampToQuantum(green),q);
2928 SetPixelBlue(image,ClampToQuantum(blue),q);
cristyed231572011-07-14 02:18:59 +00002929 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00002930 }
2931 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2932 status=MagickFalse;
2933 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2934 {
2935 MagickBooleanType
2936 proceed;
2937
cristyb5d5f722009-11-04 03:03:49 +00002938#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00002939 #pragma omp critical (MagickCore_ModulateImage)
2940#endif
2941 proceed=SetImageProgress(image,ModulateImageTag,progress++,image->rows);
2942 if (proceed == MagickFalse)
2943 status=MagickFalse;
2944 }
2945 }
2946 image_view=DestroyCacheView(image_view);
2947 return(status);
2948}
2949
2950/*
2951%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2952% %
2953% %
2954% %
2955% N e g a t e I m a g e %
2956% %
2957% %
2958% %
2959%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2960%
2961% NegateImage() negates the colors in the reference image. The grayscale
2962% option means that only grayscale values within the image are negated.
2963%
cristy50fbc382011-07-07 02:19:17 +00002964% The format of the NegateImage method is:
cristy3ed852e2009-09-05 21:47:34 +00002965%
2966% MagickBooleanType NegateImage(Image *image,
cristyb3e7c6c2011-07-24 01:43:55 +00002967% const MagickBooleanType grayscale,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002968%
2969% A description of each parameter follows:
2970%
2971% o image: the image.
2972%
cristy3ed852e2009-09-05 21:47:34 +00002973% o grayscale: If MagickTrue, only negate grayscale pixels within the image.
2974%
cristyb3e7c6c2011-07-24 01:43:55 +00002975% o exception: return any errors or warnings in this structure.
2976%
cristy3ed852e2009-09-05 21:47:34 +00002977*/
cristy3ed852e2009-09-05 21:47:34 +00002978MagickExport MagickBooleanType NegateImage(Image *image,
cristyb3e7c6c2011-07-24 01:43:55 +00002979 const MagickBooleanType grayscale,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002980{
cristy3ed852e2009-09-05 21:47:34 +00002981#define NegateImageTag "Negate/Image"
2982
cristyc4c8d132010-01-07 01:58:38 +00002983 CacheView
2984 *image_view;
2985
cristy3ed852e2009-09-05 21:47:34 +00002986 MagickBooleanType
2987 status;
2988
cristybb503372010-05-27 20:51:26 +00002989 MagickOffsetType
2990 progress;
2991
2992 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002993 i;
2994
cristybb503372010-05-27 20:51:26 +00002995 ssize_t
2996 y;
2997
cristy3ed852e2009-09-05 21:47:34 +00002998 assert(image != (Image *) NULL);
2999 assert(image->signature == MagickSignature);
3000 if (image->debug != MagickFalse)
3001 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3002 if (image->storage_class == PseudoClass)
3003 {
3004 /*
3005 Negate colormap.
3006 */
cristyb5d5f722009-11-04 03:03:49 +00003007#if defined(MAGICKCORE_OPENMP_SUPPORT)
3008 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003009#endif
cristybb503372010-05-27 20:51:26 +00003010 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00003011 {
3012 if (grayscale != MagickFalse)
3013 if ((image->colormap[i].red != image->colormap[i].green) ||
3014 (image->colormap[i].green != image->colormap[i].blue))
3015 continue;
cristyed231572011-07-14 02:18:59 +00003016 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00003017 image->colormap[i].red=(Quantum) QuantumRange-
3018 image->colormap[i].red;
cristyed231572011-07-14 02:18:59 +00003019 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00003020 image->colormap[i].green=(Quantum) QuantumRange-
3021 image->colormap[i].green;
cristyed231572011-07-14 02:18:59 +00003022 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00003023 image->colormap[i].blue=(Quantum) QuantumRange-
3024 image->colormap[i].blue;
3025 }
3026 }
3027 /*
3028 Negate image.
3029 */
3030 status=MagickTrue;
3031 progress=0;
cristy3ed852e2009-09-05 21:47:34 +00003032 image_view=AcquireCacheView(image);
3033 if (grayscale != MagickFalse)
3034 {
cristyb5d5f722009-11-04 03:03:49 +00003035#if defined(MAGICKCORE_OPENMP_SUPPORT)
3036 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003037#endif
cristybb503372010-05-27 20:51:26 +00003038 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003039 {
3040 MagickBooleanType
3041 sync;
3042
cristy4c08aed2011-07-01 19:47:50 +00003043 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00003044 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003045
cristy8d4629b2010-08-30 17:59:46 +00003046 register ssize_t
3047 x;
3048
cristy3ed852e2009-09-05 21:47:34 +00003049 if (status == MagickFalse)
3050 continue;
3051 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,
3052 exception);
cristyacd2ed22011-08-30 01:44:23 +00003053 if (q == (Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00003054 {
3055 status=MagickFalse;
3056 continue;
3057 }
cristybb503372010-05-27 20:51:26 +00003058 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003059 {
cristy9aa95be2011-07-20 21:56:45 +00003060 register ssize_t
3061 i;
3062
cristyd476a8e2011-07-23 16:13:22 +00003063 if (IsPixelGray(image,q) != MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00003064 {
cristya30d9ba2011-07-23 21:00:48 +00003065 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00003066 continue;
3067 }
cristya30d9ba2011-07-23 21:00:48 +00003068 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
cristy9aa95be2011-07-20 21:56:45 +00003069 {
cristyabace412011-12-11 15:56:53 +00003070 PixelChannel
3071 channel;
3072
cristy1aaa3cd2011-08-21 23:48:17 +00003073 PixelTrait
cristy9aa95be2011-07-20 21:56:45 +00003074 traits;
3075
cristyabace412011-12-11 15:56:53 +00003076 channel=GetPixelChannelMapChannel(image,i);
3077 traits=GetPixelChannelMapTraits(image,channel);
cristy9aa95be2011-07-20 21:56:45 +00003078 if ((traits & UpdatePixelTrait) != 0)
3079 q[i]=QuantumRange-q[i];
3080 }
cristya30d9ba2011-07-23 21:00:48 +00003081 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00003082 }
3083 sync=SyncCacheViewAuthenticPixels(image_view,exception);
3084 if (sync == MagickFalse)
3085 status=MagickFalse;
3086 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3087 {
3088 MagickBooleanType
3089 proceed;
3090
cristyb5d5f722009-11-04 03:03:49 +00003091#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy50fbc382011-07-07 02:19:17 +00003092 #pragma omp critical (MagickCore_NegateImage)
cristy3ed852e2009-09-05 21:47:34 +00003093#endif
3094 proceed=SetImageProgress(image,NegateImageTag,progress++,
3095 image->rows);
3096 if (proceed == MagickFalse)
3097 status=MagickFalse;
3098 }
3099 }
3100 image_view=DestroyCacheView(image_view);
3101 return(MagickTrue);
3102 }
3103 /*
3104 Negate image.
3105 */
cristyb5d5f722009-11-04 03:03:49 +00003106#if defined(MAGICKCORE_OPENMP_SUPPORT)
3107 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003108#endif
cristybb503372010-05-27 20:51:26 +00003109 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003110 {
cristy4c08aed2011-07-01 19:47:50 +00003111 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00003112 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003113
cristy8d4629b2010-08-30 17:59:46 +00003114 register ssize_t
3115 x;
3116
cristy3ed852e2009-09-05 21:47:34 +00003117 if (status == MagickFalse)
3118 continue;
3119 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristyacd2ed22011-08-30 01:44:23 +00003120 if (q == (Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00003121 {
3122 status=MagickFalse;
3123 continue;
3124 }
cristybb503372010-05-27 20:51:26 +00003125 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003126 {
cristyf7dc44c2011-07-20 14:41:15 +00003127 register ssize_t
3128 i;
3129
cristya30d9ba2011-07-23 21:00:48 +00003130 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
cristyf7dc44c2011-07-20 14:41:15 +00003131 {
cristyabace412011-12-11 15:56:53 +00003132 PixelChannel
3133 channel;
3134
cristy1aaa3cd2011-08-21 23:48:17 +00003135 PixelTrait
cristyf7dc44c2011-07-20 14:41:15 +00003136 traits;
3137
cristyabace412011-12-11 15:56:53 +00003138 channel=GetPixelChannelMapChannel(image,i);
3139 traits=GetPixelChannelMapTraits(image,channel);
cristyf7dc44c2011-07-20 14:41:15 +00003140 if ((traits & UpdatePixelTrait) != 0)
3141 q[i]=QuantumRange-q[i];
3142 }
cristya30d9ba2011-07-23 21:00:48 +00003143 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00003144 }
3145 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
3146 status=MagickFalse;
3147 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3148 {
3149 MagickBooleanType
3150 proceed;
3151
cristyb5d5f722009-11-04 03:03:49 +00003152#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy50fbc382011-07-07 02:19:17 +00003153 #pragma omp critical (MagickCore_NegateImage)
cristy3ed852e2009-09-05 21:47:34 +00003154#endif
3155 proceed=SetImageProgress(image,NegateImageTag,progress++,image->rows);
3156 if (proceed == MagickFalse)
3157 status=MagickFalse;
3158 }
3159 }
3160 image_view=DestroyCacheView(image_view);
3161 return(status);
3162}
3163
3164/*
3165%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3166% %
3167% %
3168% %
3169% N o r m a l i z e I m a g e %
3170% %
3171% %
3172% %
3173%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3174%
cristya4dfb122011-07-07 19:01:57 +00003175% NormalizeImage() enhances the contrast of a color image by mapping the
3176% darkest 2 percent of all pixel to black and the brightest 1 percent to white.
cristy3ed852e2009-09-05 21:47:34 +00003177%
3178% The format of the NormalizeImage method is:
3179%
cristye23ec9d2011-08-16 18:15:40 +00003180% MagickBooleanType NormalizeImage(Image *image,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003181%
3182% A description of each parameter follows:
3183%
3184% o image: the image.
3185%
cristye23ec9d2011-08-16 18:15:40 +00003186% o exception: return any errors or warnings in this structure.
3187%
cristy3ed852e2009-09-05 21:47:34 +00003188*/
cristye23ec9d2011-08-16 18:15:40 +00003189MagickExport MagickBooleanType NormalizeImage(Image *image,
3190 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003191{
cristy3ed852e2009-09-05 21:47:34 +00003192 double
3193 black_point,
3194 white_point;
3195
cristy530239c2010-07-25 17:34:26 +00003196 black_point=(double) image->columns*image->rows*0.0015;
3197 white_point=(double) image->columns*image->rows*0.9995;
cristye23ec9d2011-08-16 18:15:40 +00003198 return(ContrastStretchImage(image,black_point,white_point,exception));
cristy3ed852e2009-09-05 21:47:34 +00003199}
3200
3201/*
3202%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3203% %
3204% %
3205% %
3206% S i g m o i d a l C o n t r a s t I m a g e %
3207% %
3208% %
3209% %
3210%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3211%
3212% SigmoidalContrastImage() adjusts the contrast of an image with a non-linear
3213% sigmoidal contrast algorithm. Increase the contrast of the image using a
3214% sigmoidal transfer function without saturating highlights or shadows.
3215% Contrast indicates how much to increase the contrast (0 is none; 3 is
3216% typical; 20 is pushing it); mid-point indicates where midtones fall in the
3217% resultant image (0 is white; 50% is middle-gray; 100% is black). Set
3218% sharpen to MagickTrue to increase the image contrast otherwise the contrast
3219% is reduced.
3220%
3221% The format of the SigmoidalContrastImage method is:
3222%
3223% MagickBooleanType SigmoidalContrastImage(Image *image,
cristy33bd5152011-08-24 01:42:24 +00003224% const MagickBooleanType sharpen,const char *levels,
3225% ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003226%
3227% A description of each parameter follows:
3228%
3229% o image: the image.
3230%
cristy3ed852e2009-09-05 21:47:34 +00003231% o sharpen: Increase or decrease image contrast.
3232%
cristyfa769582010-09-30 23:30:03 +00003233% o alpha: strength of the contrast, the larger the number the more
3234% 'threshold-like' it becomes.
cristy3ed852e2009-09-05 21:47:34 +00003235%
cristyfa769582010-09-30 23:30:03 +00003236% o beta: midpoint of the function as a color value 0 to QuantumRange.
cristy3ed852e2009-09-05 21:47:34 +00003237%
cristy33bd5152011-08-24 01:42:24 +00003238% o exception: return any errors or warnings in this structure.
3239%
cristy3ed852e2009-09-05 21:47:34 +00003240*/
cristy3ed852e2009-09-05 21:47:34 +00003241MagickExport MagickBooleanType SigmoidalContrastImage(Image *image,
cristy33bd5152011-08-24 01:42:24 +00003242 const MagickBooleanType sharpen,const double contrast,const double midpoint,
3243 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003244{
3245#define SigmoidalContrastImageTag "SigmoidalContrast/Image"
3246
cristyc4c8d132010-01-07 01:58:38 +00003247 CacheView
3248 *image_view;
3249
cristy3ed852e2009-09-05 21:47:34 +00003250 MagickBooleanType
3251 status;
3252
cristybb503372010-05-27 20:51:26 +00003253 MagickOffsetType
3254 progress;
3255
cristy3ed852e2009-09-05 21:47:34 +00003256 MagickRealType
3257 *sigmoidal_map;
3258
cristybb503372010-05-27 20:51:26 +00003259 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003260 i;
3261
cristybb503372010-05-27 20:51:26 +00003262 ssize_t
3263 y;
3264
cristy3ed852e2009-09-05 21:47:34 +00003265 /*
3266 Allocate and initialize sigmoidal maps.
3267 */
3268 assert(image != (Image *) NULL);
3269 assert(image->signature == MagickSignature);
3270 if (image->debug != MagickFalse)
3271 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3272 sigmoidal_map=(MagickRealType *) AcquireQuantumMemory(MaxMap+1UL,
3273 sizeof(*sigmoidal_map));
3274 if (sigmoidal_map == (MagickRealType *) NULL)
3275 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
3276 image->filename);
3277 (void) ResetMagickMemory(sigmoidal_map,0,(MaxMap+1)*sizeof(*sigmoidal_map));
cristyb5d5f722009-11-04 03:03:49 +00003278#if defined(MAGICKCORE_OPENMP_SUPPORT)
3279 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003280#endif
cristybb503372010-05-27 20:51:26 +00003281 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +00003282 {
3283 if (sharpen != MagickFalse)
3284 {
3285 sigmoidal_map[i]=(MagickRealType) ScaleMapToQuantum((MagickRealType)
3286 (MaxMap*((1.0/(1.0+exp(contrast*(midpoint/(double) QuantumRange-
cristy33bd5152011-08-24 01:42:24 +00003287 (double) i/MaxMap))))-(1.0/(1.0+exp(contrast*(midpoint/(double)
3288 QuantumRange)))))/((1.0/(1.0+exp(contrast*(midpoint/(double)
3289 QuantumRange-1.0))))-(1.0/(1.0+exp(contrast*(midpoint/(double)
3290 QuantumRange)))))+0.5));
cristy3ed852e2009-09-05 21:47:34 +00003291 continue;
3292 }
3293 sigmoidal_map[i]=(MagickRealType) ScaleMapToQuantum((MagickRealType)
cristy33bd5152011-08-24 01:42:24 +00003294 (MaxMap*(QuantumScale*midpoint-log((1.0-(1.0/(1.0+exp(midpoint/(double)
3295 QuantumRange*contrast))+((double) i/MaxMap)*((1.0/(1.0+exp(contrast*(
3296 midpoint/(double) QuantumRange-1.0))))-(1.0/(1.0+exp(midpoint/(double)
3297 QuantumRange*contrast))))))/(1.0/(1.0+exp(midpoint/(double) QuantumRange*
3298 contrast))+((double) i/MaxMap)*((1.0/(1.0+exp(contrast*(midpoint/(double)
3299 QuantumRange-1.0))))-(1.0/(1.0+exp(midpoint/(double) QuantumRange*
3300 contrast))))))/contrast)));
cristy3ed852e2009-09-05 21:47:34 +00003301 }
3302 if (image->storage_class == PseudoClass)
3303 {
3304 /*
3305 Sigmoidal-contrast enhance colormap.
3306 */
cristyb5d5f722009-11-04 03:03:49 +00003307#if defined(MAGICKCORE_OPENMP_SUPPORT)
3308 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003309#endif
cristybb503372010-05-27 20:51:26 +00003310 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00003311 {
cristyed231572011-07-14 02:18:59 +00003312 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristyda1f9c12011-10-02 21:39:49 +00003313 image->colormap[i].red=sigmoidal_map[ScaleQuantumToMap(
3314 ClampToQuantum(image->colormap[i].red))];
cristyed231572011-07-14 02:18:59 +00003315 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristyda1f9c12011-10-02 21:39:49 +00003316 image->colormap[i].green=sigmoidal_map[ScaleQuantumToMap(
3317 ClampToQuantum(image->colormap[i].green))];
cristyed231572011-07-14 02:18:59 +00003318 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristyda1f9c12011-10-02 21:39:49 +00003319 image->colormap[i].blue=sigmoidal_map[ScaleQuantumToMap(
3320 ClampToQuantum(image->colormap[i].blue))];
cristyed231572011-07-14 02:18:59 +00003321 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristyda1f9c12011-10-02 21:39:49 +00003322 image->colormap[i].alpha=sigmoidal_map[ScaleQuantumToMap(
3323 ClampToQuantum(image->colormap[i].alpha))];
cristy3ed852e2009-09-05 21:47:34 +00003324 }
3325 }
3326 /*
3327 Sigmoidal-contrast enhance image.
3328 */
3329 status=MagickTrue;
3330 progress=0;
cristy3ed852e2009-09-05 21:47:34 +00003331 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00003332#if defined(MAGICKCORE_OPENMP_SUPPORT)
3333 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003334#endif
cristybb503372010-05-27 20:51:26 +00003335 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003336 {
cristy4c08aed2011-07-01 19:47:50 +00003337 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00003338 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003339
cristy8d4629b2010-08-30 17:59:46 +00003340 register ssize_t
3341 x;
3342
cristy3ed852e2009-09-05 21:47:34 +00003343 if (status == MagickFalse)
3344 continue;
3345 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristyacd2ed22011-08-30 01:44:23 +00003346 if (q == (Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00003347 {
3348 status=MagickFalse;
3349 continue;
3350 }
cristybb503372010-05-27 20:51:26 +00003351 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003352 {
cristy33bd5152011-08-24 01:42:24 +00003353 register ssize_t
3354 i;
3355
3356 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
3357 {
cristyabace412011-12-11 15:56:53 +00003358 PixelChannel
3359 channel;
3360
cristy33bd5152011-08-24 01:42:24 +00003361 PixelTrait
3362 traits;
3363
cristyabace412011-12-11 15:56:53 +00003364 channel=GetPixelChannelMapChannel(image,i);
3365 traits=GetPixelChannelMapTraits(image,channel);
cristy33bd5152011-08-24 01:42:24 +00003366 if ((traits & UpdatePixelTrait) != 0)
3367 q[i]=ClampToQuantum(sigmoidal_map[ScaleQuantumToMap(q[i])]);
3368 }
cristyed231572011-07-14 02:18:59 +00003369 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00003370 }
3371 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
3372 status=MagickFalse;
3373 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3374 {
3375 MagickBooleanType
3376 proceed;
3377
cristyb5d5f722009-11-04 03:03:49 +00003378#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy9ee60942011-07-06 14:54:38 +00003379 #pragma omp critical (MagickCore_SigmoidalContrastImage)
cristy3ed852e2009-09-05 21:47:34 +00003380#endif
3381 proceed=SetImageProgress(image,SigmoidalContrastImageTag,progress++,
3382 image->rows);
3383 if (proceed == MagickFalse)
3384 status=MagickFalse;
3385 }
3386 }
3387 image_view=DestroyCacheView(image_view);
3388 sigmoidal_map=(MagickRealType *) RelinquishMagickMemory(sigmoidal_map);
3389 return(status);
3390}