blob: 8653e8bb1e68f45c66a95b25b50963a1109019c3 [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% %
cristy1454be72011-12-19 01:52:48 +000020% Copyright 1999-2012 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
cristyb8b0d162011-12-18 23:41:28 +00002126static inline MagickRealType LevelPixel(const double black_point,
cristy780e9ef2011-12-18 23:33:50 +00002127 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)
cristy0113ca22011-12-19 01:16:22 +00002136 return((MagickRealType) QuantumRange);
cristy780e9ef2011-12-18 23:33:50 +00002137 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);
cristyb8b0d162011-12-18 23:41:28 +00002232 if (traits == UndefinedPixelTrait)
cristy7c0a0a42011-08-23 17:57:25 +00002233 continue;
cristy780e9ef2011-12-18 23:33:50 +00002234 q[i]=ClampToQuantum(LevelPixel(black_point,white_point,gamma,
2235 (MagickRealType) q[i]));
cristy7c0a0a42011-08-23 17:57:25 +00002236 }
cristyed231572011-07-14 02:18:59 +00002237 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00002238 }
2239 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2240 status=MagickFalse;
2241 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2242 {
2243 MagickBooleanType
2244 proceed;
2245
cristyb5d5f722009-11-04 03:03:49 +00002246#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf89cb1d2011-07-07 01:24:37 +00002247 #pragma omp critical (MagickCore_LevelImage)
cristy3ed852e2009-09-05 21:47:34 +00002248#endif
2249 proceed=SetImageProgress(image,LevelImageTag,progress++,image->rows);
2250 if (proceed == MagickFalse)
2251 status=MagickFalse;
2252 }
2253 }
2254 image_view=DestroyCacheView(image_view);
2255 return(status);
2256}
2257
2258/*
2259%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2260% %
2261% %
2262% %
cristy33bd5152011-08-24 01:42:24 +00002263% L e v e l i z e I m a g e %
cristy3ed852e2009-09-05 21:47:34 +00002264% %
2265% %
2266% %
2267%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2268%
cristy50fbc382011-07-07 02:19:17 +00002269% LevelizeImage() applies the reversed LevelImage() operation to just
cristy3ed852e2009-09-05 21:47:34 +00002270% the specific channels specified. It compresses the full range of color
2271% values, so that they lie between the given black and white points. Gamma is
2272% applied before the values are mapped.
2273%
cristy50fbc382011-07-07 02:19:17 +00002274% LevelizeImage() can be called with by using a +level command line
cristy3ed852e2009-09-05 21:47:34 +00002275% API option, or using a '!' on a -level or LevelImage() geometry string.
2276%
2277% It can be used for example de-contrast a greyscale image to the exact
2278% levels specified. Or by using specific levels for each channel of an image
2279% you can convert a gray-scale image to any linear color gradient, according
2280% to those levels.
2281%
cristy50fbc382011-07-07 02:19:17 +00002282% The format of the LevelizeImage method is:
cristy3ed852e2009-09-05 21:47:34 +00002283%
cristy50fbc382011-07-07 02:19:17 +00002284% MagickBooleanType LevelizeImage(Image *image,const double black_point,
cristy7c0a0a42011-08-23 17:57:25 +00002285% const double white_point,const double gamma,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002286%
2287% A description of each parameter follows:
2288%
2289% o image: the image.
2290%
cristy3ed852e2009-09-05 21:47:34 +00002291% o black_point: The level to map zero (black) to.
2292%
cristy01e9afd2011-08-10 17:38:41 +00002293% o white_point: The level to map QuantumRange (white) to.
cristy3ed852e2009-09-05 21:47:34 +00002294%
2295% o gamma: adjust gamma by this factor before mapping values.
2296%
cristy7c0a0a42011-08-23 17:57:25 +00002297% o exception: return any errors or warnings in this structure.
2298%
cristy3ed852e2009-09-05 21:47:34 +00002299*/
cristyd1a2c0f2011-02-09 14:14:50 +00002300MagickExport MagickBooleanType LevelizeImage(Image *image,
cristy7c0a0a42011-08-23 17:57:25 +00002301 const double black_point,const double white_point,const double gamma,
2302 ExceptionInfo *exception)
cristyd1a2c0f2011-02-09 14:14:50 +00002303{
cristy3ed852e2009-09-05 21:47:34 +00002304#define LevelizeImageTag "Levelize/Image"
cristyce70c172010-01-07 17:15:30 +00002305#define LevelizeValue(x) (ClampToQuantum(((MagickRealType) \
cristy50fbc382011-07-07 02:19:17 +00002306 pow((double) (QuantumScale*(x)),1.0/gamma))*(white_point-black_point)+ \
cristy3ed852e2009-09-05 21:47:34 +00002307 black_point))
2308
cristyc4c8d132010-01-07 01:58:38 +00002309 CacheView
2310 *image_view;
2311
cristy3ed852e2009-09-05 21:47:34 +00002312 MagickBooleanType
2313 status;
2314
cristybb503372010-05-27 20:51:26 +00002315 MagickOffsetType
2316 progress;
2317
2318 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002319 i;
2320
cristybb503372010-05-27 20:51:26 +00002321 ssize_t
2322 y;
2323
cristy3ed852e2009-09-05 21:47:34 +00002324 /*
2325 Allocate and initialize levels map.
2326 */
2327 assert(image != (Image *) NULL);
2328 assert(image->signature == MagickSignature);
2329 if (image->debug != MagickFalse)
2330 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2331 if (image->storage_class == PseudoClass)
cristyb5d5f722009-11-04 03:03:49 +00002332#if defined(MAGICKCORE_OPENMP_SUPPORT)
2333 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002334#endif
cristybb503372010-05-27 20:51:26 +00002335 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00002336 {
2337 /*
2338 Level colormap.
2339 */
cristyed231572011-07-14 02:18:59 +00002340 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristyda1f9c12011-10-02 21:39:49 +00002341 image->colormap[i].red=(double) LevelizeValue(
2342 image->colormap[i].red);
cristyed231572011-07-14 02:18:59 +00002343 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristyda1f9c12011-10-02 21:39:49 +00002344 image->colormap[i].green=(double) LevelizeValue(
2345 image->colormap[i].green);
cristyed231572011-07-14 02:18:59 +00002346 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristyda1f9c12011-10-02 21:39:49 +00002347 image->colormap[i].blue=(double) LevelizeValue(
2348 image->colormap[i].blue);
cristyed231572011-07-14 02:18:59 +00002349 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristyda1f9c12011-10-02 21:39:49 +00002350 image->colormap[i].alpha=(double) LevelizeValue(
2351 image->colormap[i].alpha);
cristy3ed852e2009-09-05 21:47:34 +00002352 }
2353 /*
2354 Level image.
2355 */
2356 status=MagickTrue;
2357 progress=0;
cristy3ed852e2009-09-05 21:47:34 +00002358 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00002359#if defined(MAGICKCORE_OPENMP_SUPPORT)
2360 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002361#endif
cristybb503372010-05-27 20:51:26 +00002362 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002363 {
cristy4c08aed2011-07-01 19:47:50 +00002364 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00002365 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002366
cristy8d4629b2010-08-30 17:59:46 +00002367 register ssize_t
2368 x;
2369
cristy3ed852e2009-09-05 21:47:34 +00002370 if (status == MagickFalse)
2371 continue;
2372 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristyacd2ed22011-08-30 01:44:23 +00002373 if (q == (Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00002374 {
2375 status=MagickFalse;
2376 continue;
2377 }
cristybb503372010-05-27 20:51:26 +00002378 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002379 {
cristy7c0a0a42011-08-23 17:57:25 +00002380 register ssize_t
2381 i;
2382
2383 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
2384 {
cristyabace412011-12-11 15:56:53 +00002385 PixelChannel
2386 channel;
2387
cristy7c0a0a42011-08-23 17:57:25 +00002388 PixelTrait
2389 traits;
2390
cristyabace412011-12-11 15:56:53 +00002391 channel=GetPixelChannelMapChannel(image,i);
2392 traits=GetPixelChannelMapTraits(image,channel);
cristyb8b0d162011-12-18 23:41:28 +00002393 if (traits == UndefinedPixelTrait)
2394 continue;
cristy780e9ef2011-12-18 23:33:50 +00002395 q[i]=LevelizeValue(q[i]);
cristy7c0a0a42011-08-23 17:57:25 +00002396 }
cristyed231572011-07-14 02:18:59 +00002397 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00002398 }
2399 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2400 status=MagickFalse;
2401 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2402 {
2403 MagickBooleanType
2404 proceed;
2405
cristyb5d5f722009-11-04 03:03:49 +00002406#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy50fbc382011-07-07 02:19:17 +00002407 #pragma omp critical (MagickCore_LevelizeImage)
cristy3ed852e2009-09-05 21:47:34 +00002408#endif
2409 proceed=SetImageProgress(image,LevelizeImageTag,progress++,image->rows);
2410 if (proceed == MagickFalse)
2411 status=MagickFalse;
2412 }
2413 }
cristy8d4629b2010-08-30 17:59:46 +00002414 image_view=DestroyCacheView(image_view);
cristy3ed852e2009-09-05 21:47:34 +00002415 return(status);
2416}
2417
2418/*
2419%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2420% %
2421% %
2422% %
2423% L e v e l I m a g e C o l o r s %
2424% %
2425% %
2426% %
2427%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2428%
cristy490408a2011-07-07 14:42:05 +00002429% LevelImageColors() maps the given color to "black" and "white" values,
cristyee0f8d72009-09-19 00:58:29 +00002430% linearly spreading out the colors, and level values on a channel by channel
2431% bases, as per LevelImage(). The given colors allows you to specify
glennrp1e7f7bc2011-03-02 19:25:28 +00002432% different level ranges for each of the color channels separately.
cristy3ed852e2009-09-05 21:47:34 +00002433%
2434% If the boolean 'invert' is set true the image values will modifyed in the
2435% reverse direction. That is any existing "black" and "white" colors in the
2436% image will become the color values given, with all other values compressed
2437% appropriatally. This effectivally maps a greyscale gradient into the given
2438% color gradient.
2439%
cristy490408a2011-07-07 14:42:05 +00002440% The format of the LevelImageColors method is:
cristy3ed852e2009-09-05 21:47:34 +00002441%
cristy490408a2011-07-07 14:42:05 +00002442% MagickBooleanType LevelImageColors(Image *image,
2443% const PixelInfo *black_color,const PixelInfo *white_color,
cristy7c0a0a42011-08-23 17:57:25 +00002444% const MagickBooleanType invert,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002445%
2446% A description of each parameter follows:
2447%
2448% o image: the image.
2449%
cristy3ed852e2009-09-05 21:47:34 +00002450% o black_color: The color to map black to/from
2451%
2452% o white_point: The color to map white to/from
2453%
2454% o invert: if true map the colors (levelize), rather than from (level)
2455%
cristy7c0a0a42011-08-23 17:57:25 +00002456% o exception: return any errors or warnings in this structure.
2457%
cristy3ed852e2009-09-05 21:47:34 +00002458*/
cristy490408a2011-07-07 14:42:05 +00002459MagickExport MagickBooleanType LevelImageColors(Image *image,
cristy4c08aed2011-07-01 19:47:50 +00002460 const PixelInfo *black_color,const PixelInfo *white_color,
cristy7c0a0a42011-08-23 17:57:25 +00002461 const MagickBooleanType invert,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002462{
cristybd5a96c2011-08-21 00:04:26 +00002463 ChannelType
2464 channel_mask;
2465
cristy3ed852e2009-09-05 21:47:34 +00002466 MagickStatusType
2467 status;
2468
2469 /*
2470 Allocate and initialize levels map.
2471 */
2472 assert(image != (Image *) NULL);
2473 assert(image->signature == MagickSignature);
2474 if (image->debug != MagickFalse)
2475 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2476 status=MagickFalse;
2477 if (invert == MagickFalse)
2478 {
cristyed231572011-07-14 02:18:59 +00002479 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristyf89cb1d2011-07-07 01:24:37 +00002480 {
cristybd5a96c2011-08-21 00:04:26 +00002481 channel_mask=SetPixelChannelMask(image,RedChannel);
cristy01e9afd2011-08-10 17:38:41 +00002482 status|=LevelImage(image,black_color->red,white_color->red,1.0,
cristy7c0a0a42011-08-23 17:57:25 +00002483 exception);
cristybd5a96c2011-08-21 00:04:26 +00002484 (void) SetPixelChannelMask(image,channel_mask);
cristyf89cb1d2011-07-07 01:24:37 +00002485 }
cristyed231572011-07-14 02:18:59 +00002486 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristyf89cb1d2011-07-07 01:24:37 +00002487 {
cristybd5a96c2011-08-21 00:04:26 +00002488 channel_mask=SetPixelChannelMask(image,GreenChannel);
cristy01e9afd2011-08-10 17:38:41 +00002489 status|=LevelImage(image,black_color->green,white_color->green,1.0,
cristy7c0a0a42011-08-23 17:57:25 +00002490 exception);
cristybd5a96c2011-08-21 00:04:26 +00002491 (void) SetPixelChannelMask(image,channel_mask);
cristyf89cb1d2011-07-07 01:24:37 +00002492 }
cristyed231572011-07-14 02:18:59 +00002493 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristyf89cb1d2011-07-07 01:24:37 +00002494 {
cristybd5a96c2011-08-21 00:04:26 +00002495 channel_mask=SetPixelChannelMask(image,BlueChannel);
cristy01e9afd2011-08-10 17:38:41 +00002496 status|=LevelImage(image,black_color->blue,white_color->blue,1.0,
cristy7c0a0a42011-08-23 17:57:25 +00002497 exception);
cristybd5a96c2011-08-21 00:04:26 +00002498 (void) SetPixelChannelMask(image,channel_mask);
cristyf89cb1d2011-07-07 01:24:37 +00002499 }
cristyed231572011-07-14 02:18:59 +00002500 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +00002501 (image->colorspace == CMYKColorspace))
cristyf89cb1d2011-07-07 01:24:37 +00002502 {
cristybd5a96c2011-08-21 00:04:26 +00002503 channel_mask=SetPixelChannelMask(image,BlackChannel);
cristy01e9afd2011-08-10 17:38:41 +00002504 status|=LevelImage(image,black_color->black,white_color->black,1.0,
cristy7c0a0a42011-08-23 17:57:25 +00002505 exception);
cristybd5a96c2011-08-21 00:04:26 +00002506 (void) SetPixelChannelMask(image,channel_mask);
cristyf89cb1d2011-07-07 01:24:37 +00002507 }
cristyed231572011-07-14 02:18:59 +00002508 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00002509 (image->matte == MagickTrue))
cristyf89cb1d2011-07-07 01:24:37 +00002510 {
cristybd5a96c2011-08-21 00:04:26 +00002511 channel_mask=SetPixelChannelMask(image,AlphaChannel);
cristy01e9afd2011-08-10 17:38:41 +00002512 status|=LevelImage(image,black_color->alpha,white_color->alpha,1.0,
cristy7c0a0a42011-08-23 17:57:25 +00002513 exception);
cristybd5a96c2011-08-21 00:04:26 +00002514 (void) SetPixelChannelMask(image,channel_mask);
cristyf89cb1d2011-07-07 01:24:37 +00002515 }
cristy3ed852e2009-09-05 21:47:34 +00002516 }
2517 else
2518 {
cristyed231572011-07-14 02:18:59 +00002519 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristyf89cb1d2011-07-07 01:24:37 +00002520 {
cristybd5a96c2011-08-21 00:04:26 +00002521 channel_mask=SetPixelChannelMask(image,RedChannel);
cristy7c0a0a42011-08-23 17:57:25 +00002522 status|=LevelizeImage(image,black_color->red,white_color->red,1.0,
2523 exception);
cristybd5a96c2011-08-21 00:04:26 +00002524 (void) SetPixelChannelMask(image,channel_mask);
cristyf89cb1d2011-07-07 01:24:37 +00002525 }
cristyed231572011-07-14 02:18:59 +00002526 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristyf89cb1d2011-07-07 01:24:37 +00002527 {
cristybd5a96c2011-08-21 00:04:26 +00002528 channel_mask=SetPixelChannelMask(image,GreenChannel);
cristy7c0a0a42011-08-23 17:57:25 +00002529 status|=LevelizeImage(image,black_color->green,white_color->green,1.0,
2530 exception);
cristybd5a96c2011-08-21 00:04:26 +00002531 (void) SetPixelChannelMask(image,channel_mask);
cristyf89cb1d2011-07-07 01:24:37 +00002532 }
cristyed231572011-07-14 02:18:59 +00002533 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristyf89cb1d2011-07-07 01:24:37 +00002534 {
cristybd5a96c2011-08-21 00:04:26 +00002535 channel_mask=SetPixelChannelMask(image,BlueChannel);
cristy7c0a0a42011-08-23 17:57:25 +00002536 status|=LevelizeImage(image,black_color->blue,white_color->blue,1.0,
2537 exception);
cristybd5a96c2011-08-21 00:04:26 +00002538 (void) SetPixelChannelMask(image,channel_mask);
cristyf89cb1d2011-07-07 01:24:37 +00002539 }
cristyed231572011-07-14 02:18:59 +00002540 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +00002541 (image->colorspace == CMYKColorspace))
cristyf89cb1d2011-07-07 01:24:37 +00002542 {
cristybd5a96c2011-08-21 00:04:26 +00002543 channel_mask=SetPixelChannelMask(image,BlackChannel);
cristy7c0a0a42011-08-23 17:57:25 +00002544 status|=LevelizeImage(image,black_color->black,white_color->black,1.0,
2545 exception);
cristybd5a96c2011-08-21 00:04:26 +00002546 (void) SetPixelChannelMask(image,channel_mask);
cristyf89cb1d2011-07-07 01:24:37 +00002547 }
cristyed231572011-07-14 02:18:59 +00002548 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00002549 (image->matte == MagickTrue))
cristyf89cb1d2011-07-07 01:24:37 +00002550 {
cristybd5a96c2011-08-21 00:04:26 +00002551 channel_mask=SetPixelChannelMask(image,AlphaChannel);
cristy7c0a0a42011-08-23 17:57:25 +00002552 status|=LevelizeImage(image,black_color->alpha,white_color->alpha,1.0,
2553 exception);
cristybd5a96c2011-08-21 00:04:26 +00002554 (void) SetPixelChannelMask(image,channel_mask);
cristyf89cb1d2011-07-07 01:24:37 +00002555 }
cristy3ed852e2009-09-05 21:47:34 +00002556 }
2557 return(status == 0 ? MagickFalse : MagickTrue);
2558}
2559
2560/*
2561%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2562% %
2563% %
2564% %
2565% L i n e a r S t r e t c h I m a g e %
2566% %
2567% %
2568% %
2569%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2570%
cristyf1611782011-08-01 15:39:13 +00002571% LinearStretchImage() discards any pixels below the black point and above
2572% the white point and levels the remaining pixels.
cristy3ed852e2009-09-05 21:47:34 +00002573%
2574% The format of the LinearStretchImage method is:
2575%
2576% MagickBooleanType LinearStretchImage(Image *image,
cristy33bd5152011-08-24 01:42:24 +00002577% const double black_point,const double white_point,
2578% ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002579%
2580% A description of each parameter follows:
2581%
2582% o image: the image.
2583%
2584% o black_point: the black point.
2585%
2586% o white_point: the white point.
2587%
cristy33bd5152011-08-24 01:42:24 +00002588% o exception: return any errors or warnings in this structure.
2589%
cristy3ed852e2009-09-05 21:47:34 +00002590*/
2591MagickExport MagickBooleanType LinearStretchImage(Image *image,
cristy33bd5152011-08-24 01:42:24 +00002592 const double black_point,const double white_point,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002593{
2594#define LinearStretchImageTag "LinearStretch/Image"
2595
cristy33bd5152011-08-24 01:42:24 +00002596 CacheView
2597 *image_view;
cristy3ed852e2009-09-05 21:47:34 +00002598
cristy3ed852e2009-09-05 21:47:34 +00002599 MagickBooleanType
2600 status;
2601
2602 MagickRealType
2603 *histogram,
2604 intensity;
2605
cristy8d4629b2010-08-30 17:59:46 +00002606 ssize_t
2607 black,
2608 white,
2609 y;
2610
cristy3ed852e2009-09-05 21:47:34 +00002611 /*
2612 Allocate histogram and linear map.
2613 */
2614 assert(image != (Image *) NULL);
2615 assert(image->signature == MagickSignature);
2616 histogram=(MagickRealType *) AcquireQuantumMemory(MaxMap+1UL,
2617 sizeof(*histogram));
2618 if (histogram == (MagickRealType *) NULL)
2619 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
2620 image->filename);
2621 /*
2622 Form histogram.
2623 */
2624 (void) ResetMagickMemory(histogram,0,(MaxMap+1)*sizeof(*histogram));
cristy33bd5152011-08-24 01:42:24 +00002625 image_view=AcquireCacheView(image);
cristybb503372010-05-27 20:51:26 +00002626 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002627 {
cristy4c08aed2011-07-01 19:47:50 +00002628 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00002629 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00002630
cristybb503372010-05-27 20:51:26 +00002631 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002632 x;
2633
cristy33bd5152011-08-24 01:42:24 +00002634 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00002635 if (p == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00002636 break;
cristy33bd5152011-08-24 01:42:24 +00002637 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002638 {
cristy4c08aed2011-07-01 19:47:50 +00002639 histogram[ScaleQuantumToMap(GetPixelIntensity(image,p))]++;
cristyed231572011-07-14 02:18:59 +00002640 p+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00002641 }
2642 }
cristy33bd5152011-08-24 01:42:24 +00002643 image_view=DestroyCacheView(image_view);
cristy3ed852e2009-09-05 21:47:34 +00002644 /*
2645 Find the histogram boundaries by locating the black and white point levels.
2646 */
cristy3ed852e2009-09-05 21:47:34 +00002647 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00002648 for (black=0; black < (ssize_t) MaxMap; black++)
cristy3ed852e2009-09-05 21:47:34 +00002649 {
2650 intensity+=histogram[black];
2651 if (intensity >= black_point)
2652 break;
2653 }
2654 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00002655 for (white=(ssize_t) MaxMap; white != 0; white--)
cristy3ed852e2009-09-05 21:47:34 +00002656 {
2657 intensity+=histogram[white];
2658 if (intensity >= white_point)
2659 break;
2660 }
2661 histogram=(MagickRealType *) RelinquishMagickMemory(histogram);
cristyc82a27b2011-10-21 01:07:16 +00002662 status=LevelImage(image,(double) black,(double) white,1.0,exception);
cristy3ed852e2009-09-05 21:47:34 +00002663 return(status);
2664}
2665
2666/*
2667%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2668% %
2669% %
2670% %
2671% M o d u l a t e I m a g e %
2672% %
2673% %
2674% %
2675%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2676%
2677% ModulateImage() lets you control the brightness, saturation, and hue
2678% of an image. Modulate represents the brightness, saturation, and hue
2679% as one parameter (e.g. 90,150,100). If the image colorspace is HSL, the
2680% modulation is lightness, saturation, and hue. And if the colorspace is
2681% HWB, use blackness, whiteness, and hue.
2682%
2683% The format of the ModulateImage method is:
2684%
cristy33bd5152011-08-24 01:42:24 +00002685% MagickBooleanType ModulateImage(Image *image,const char *modulate,
2686% ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002687%
2688% A description of each parameter follows:
2689%
2690% o image: the image.
2691%
cristy33bd5152011-08-24 01:42:24 +00002692% o modulate: Define the percent change in brightness, saturation, and hue.
2693%
2694% o exception: return any errors or warnings in this structure.
cristy3ed852e2009-09-05 21:47:34 +00002695%
2696*/
2697
2698static void ModulateHSB(const double percent_hue,
cristy3094b7f2011-10-01 23:18:02 +00002699 const double percent_saturation,const double percent_brightness,double *red,
2700 double *green,double *blue)
cristy3ed852e2009-09-05 21:47:34 +00002701{
2702 double
2703 brightness,
2704 hue,
2705 saturation;
2706
2707 /*
2708 Increase or decrease color brightness, saturation, or hue.
2709 */
cristy3094b7f2011-10-01 23:18:02 +00002710 assert(red != (double *) NULL);
2711 assert(green != (double *) NULL);
2712 assert(blue != (double *) NULL);
cristy3ed852e2009-09-05 21:47:34 +00002713 ConvertRGBToHSB(*red,*green,*blue,&hue,&saturation,&brightness);
2714 hue+=0.5*(0.01*percent_hue-1.0);
2715 while (hue < 0.0)
2716 hue+=1.0;
2717 while (hue > 1.0)
2718 hue-=1.0;
2719 saturation*=0.01*percent_saturation;
2720 brightness*=0.01*percent_brightness;
2721 ConvertHSBToRGB(hue,saturation,brightness,red,green,blue);
2722}
2723
2724static void ModulateHSL(const double percent_hue,
cristy3094b7f2011-10-01 23:18:02 +00002725 const double percent_saturation,const double percent_lightness,double *red,
2726 double *green,double *blue)
cristy3ed852e2009-09-05 21:47:34 +00002727{
2728 double
2729 hue,
2730 lightness,
2731 saturation;
2732
2733 /*
2734 Increase or decrease color lightness, saturation, or hue.
2735 */
cristy3094b7f2011-10-01 23:18:02 +00002736 assert(red != (double *) NULL);
2737 assert(green != (double *) NULL);
2738 assert(blue != (double *) NULL);
cristy3ed852e2009-09-05 21:47:34 +00002739 ConvertRGBToHSL(*red,*green,*blue,&hue,&saturation,&lightness);
2740 hue+=0.5*(0.01*percent_hue-1.0);
2741 while (hue < 0.0)
2742 hue+=1.0;
2743 while (hue > 1.0)
2744 hue-=1.0;
2745 saturation*=0.01*percent_saturation;
2746 lightness*=0.01*percent_lightness;
2747 ConvertHSLToRGB(hue,saturation,lightness,red,green,blue);
2748}
2749
cristy3094b7f2011-10-01 23:18:02 +00002750static 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 +00002751{
2752 double
2753 blackness,
2754 hue,
2755 whiteness;
2756
2757 /*
2758 Increase or decrease color blackness, whiteness, or hue.
2759 */
cristy3094b7f2011-10-01 23:18:02 +00002760 assert(red != (double *) NULL);
2761 assert(green != (double *) NULL);
2762 assert(blue != (double *) NULL);
cristy3ed852e2009-09-05 21:47:34 +00002763 ConvertRGBToHWB(*red,*green,*blue,&hue,&whiteness,&blackness);
2764 hue+=0.5*(0.01*percent_hue-1.0);
2765 while (hue < 0.0)
2766 hue+=1.0;
2767 while (hue > 1.0)
2768 hue-=1.0;
2769 blackness*=0.01*percent_blackness;
2770 whiteness*=0.01*percent_whiteness;
2771 ConvertHWBToRGB(hue,whiteness,blackness,red,green,blue);
2772}
2773
cristy33bd5152011-08-24 01:42:24 +00002774MagickExport MagickBooleanType ModulateImage(Image *image,const char *modulate,
2775 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002776{
2777#define ModulateImageTag "Modulate/Image"
2778
cristyc4c8d132010-01-07 01:58:38 +00002779 CacheView
2780 *image_view;
2781
cristy3ed852e2009-09-05 21:47:34 +00002782 ColorspaceType
2783 colorspace;
2784
2785 const char
2786 *artifact;
2787
2788 double
2789 percent_brightness,
2790 percent_hue,
2791 percent_saturation;
2792
cristy3ed852e2009-09-05 21:47:34 +00002793 GeometryInfo
2794 geometry_info;
2795
cristy3ed852e2009-09-05 21:47:34 +00002796 MagickBooleanType
2797 status;
2798
cristybb503372010-05-27 20:51:26 +00002799 MagickOffsetType
2800 progress;
2801
cristy3ed852e2009-09-05 21:47:34 +00002802 MagickStatusType
2803 flags;
2804
cristybb503372010-05-27 20:51:26 +00002805 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002806 i;
2807
cristybb503372010-05-27 20:51:26 +00002808 ssize_t
2809 y;
2810
cristy3ed852e2009-09-05 21:47:34 +00002811 /*
cristy2b726bd2010-01-11 01:05:39 +00002812 Initialize modulate table.
cristy3ed852e2009-09-05 21:47:34 +00002813 */
2814 assert(image != (Image *) NULL);
2815 assert(image->signature == MagickSignature);
2816 if (image->debug != MagickFalse)
2817 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2818 if (modulate == (char *) NULL)
2819 return(MagickFalse);
2820 flags=ParseGeometry(modulate,&geometry_info);
2821 percent_brightness=geometry_info.rho;
2822 percent_saturation=geometry_info.sigma;
2823 if ((flags & SigmaValue) == 0)
2824 percent_saturation=100.0;
2825 percent_hue=geometry_info.xi;
2826 if ((flags & XiValue) == 0)
2827 percent_hue=100.0;
2828 colorspace=UndefinedColorspace;
2829 artifact=GetImageArtifact(image,"modulate:colorspace");
2830 if (artifact != (const char *) NULL)
cristy042ee782011-04-22 18:48:30 +00002831 colorspace=(ColorspaceType) ParseCommandOption(MagickColorspaceOptions,
cristy3ed852e2009-09-05 21:47:34 +00002832 MagickFalse,artifact);
2833 if (image->storage_class == PseudoClass)
2834 {
2835 /*
2836 Modulate colormap.
2837 */
cristyb5d5f722009-11-04 03:03:49 +00002838#if defined(MAGICKCORE_OPENMP_SUPPORT)
2839 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002840#endif
cristybb503372010-05-27 20:51:26 +00002841 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00002842 switch (colorspace)
2843 {
2844 case HSBColorspace:
2845 {
2846 ModulateHSB(percent_hue,percent_saturation,percent_brightness,
2847 &image->colormap[i].red,&image->colormap[i].green,
2848 &image->colormap[i].blue);
2849 break;
2850 }
2851 case HSLColorspace:
2852 default:
2853 {
2854 ModulateHSL(percent_hue,percent_saturation,percent_brightness,
2855 &image->colormap[i].red,&image->colormap[i].green,
2856 &image->colormap[i].blue);
2857 break;
2858 }
2859 case HWBColorspace:
2860 {
2861 ModulateHWB(percent_hue,percent_saturation,percent_brightness,
2862 &image->colormap[i].red,&image->colormap[i].green,
2863 &image->colormap[i].blue);
2864 break;
2865 }
2866 }
2867 }
2868 /*
2869 Modulate image.
2870 */
2871 status=MagickTrue;
2872 progress=0;
cristy3ed852e2009-09-05 21:47:34 +00002873 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00002874#if defined(MAGICKCORE_OPENMP_SUPPORT)
2875 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002876#endif
cristybb503372010-05-27 20:51:26 +00002877 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002878 {
cristy3094b7f2011-10-01 23:18:02 +00002879 double
cristy5afeab82011-04-30 01:30:09 +00002880 blue,
2881 green,
2882 red;
2883
cristy4c08aed2011-07-01 19:47:50 +00002884 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00002885 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002886
cristy8d4629b2010-08-30 17:59:46 +00002887 register ssize_t
2888 x;
2889
cristy3ed852e2009-09-05 21:47:34 +00002890 if (status == MagickFalse)
2891 continue;
2892 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristyacd2ed22011-08-30 01:44:23 +00002893 if (q == (Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00002894 {
2895 status=MagickFalse;
2896 continue;
2897 }
cristybb503372010-05-27 20:51:26 +00002898 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002899 {
cristyda1f9c12011-10-02 21:39:49 +00002900 red=(double) GetPixelRed(image,q);
2901 green=(double) GetPixelGreen(image,q);
2902 blue=(double) GetPixelBlue(image,q);
cristy3ed852e2009-09-05 21:47:34 +00002903 switch (colorspace)
2904 {
2905 case HSBColorspace:
2906 {
2907 ModulateHSB(percent_hue,percent_saturation,percent_brightness,
cristy5afeab82011-04-30 01:30:09 +00002908 &red,&green,&blue);
cristy3ed852e2009-09-05 21:47:34 +00002909 break;
2910 }
2911 case HSLColorspace:
2912 default:
2913 {
2914 ModulateHSL(percent_hue,percent_saturation,percent_brightness,
cristy5afeab82011-04-30 01:30:09 +00002915 &red,&green,&blue);
cristy3ed852e2009-09-05 21:47:34 +00002916 break;
2917 }
2918 case HWBColorspace:
2919 {
2920 ModulateHWB(percent_hue,percent_saturation,percent_brightness,
cristy5afeab82011-04-30 01:30:09 +00002921 &red,&green,&blue);
cristy3ed852e2009-09-05 21:47:34 +00002922 break;
2923 }
2924 }
cristy3094b7f2011-10-01 23:18:02 +00002925 SetPixelRed(image,ClampToQuantum(red),q);
2926 SetPixelGreen(image,ClampToQuantum(green),q);
2927 SetPixelBlue(image,ClampToQuantum(blue),q);
cristyed231572011-07-14 02:18:59 +00002928 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00002929 }
2930 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2931 status=MagickFalse;
2932 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2933 {
2934 MagickBooleanType
2935 proceed;
2936
cristyb5d5f722009-11-04 03:03:49 +00002937#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00002938 #pragma omp critical (MagickCore_ModulateImage)
2939#endif
2940 proceed=SetImageProgress(image,ModulateImageTag,progress++,image->rows);
2941 if (proceed == MagickFalse)
2942 status=MagickFalse;
2943 }
2944 }
2945 image_view=DestroyCacheView(image_view);
2946 return(status);
2947}
2948
2949/*
2950%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2951% %
2952% %
2953% %
2954% N e g a t e I m a g e %
2955% %
2956% %
2957% %
2958%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2959%
2960% NegateImage() negates the colors in the reference image. The grayscale
2961% option means that only grayscale values within the image are negated.
2962%
cristy50fbc382011-07-07 02:19:17 +00002963% The format of the NegateImage method is:
cristy3ed852e2009-09-05 21:47:34 +00002964%
2965% MagickBooleanType NegateImage(Image *image,
cristyb3e7c6c2011-07-24 01:43:55 +00002966% const MagickBooleanType grayscale,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002967%
2968% A description of each parameter follows:
2969%
2970% o image: the image.
2971%
cristy3ed852e2009-09-05 21:47:34 +00002972% o grayscale: If MagickTrue, only negate grayscale pixels within the image.
2973%
cristyb3e7c6c2011-07-24 01:43:55 +00002974% o exception: return any errors or warnings in this structure.
2975%
cristy3ed852e2009-09-05 21:47:34 +00002976*/
cristy3ed852e2009-09-05 21:47:34 +00002977MagickExport MagickBooleanType NegateImage(Image *image,
cristyb3e7c6c2011-07-24 01:43:55 +00002978 const MagickBooleanType grayscale,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002979{
cristy3ed852e2009-09-05 21:47:34 +00002980#define NegateImageTag "Negate/Image"
2981
cristyc4c8d132010-01-07 01:58:38 +00002982 CacheView
2983 *image_view;
2984
cristy3ed852e2009-09-05 21:47:34 +00002985 MagickBooleanType
2986 status;
2987
cristybb503372010-05-27 20:51:26 +00002988 MagickOffsetType
2989 progress;
2990
2991 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002992 i;
2993
cristybb503372010-05-27 20:51:26 +00002994 ssize_t
2995 y;
2996
cristy3ed852e2009-09-05 21:47:34 +00002997 assert(image != (Image *) NULL);
2998 assert(image->signature == MagickSignature);
2999 if (image->debug != MagickFalse)
3000 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3001 if (image->storage_class == PseudoClass)
3002 {
3003 /*
3004 Negate colormap.
3005 */
cristyb5d5f722009-11-04 03:03:49 +00003006#if defined(MAGICKCORE_OPENMP_SUPPORT)
3007 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003008#endif
cristybb503372010-05-27 20:51:26 +00003009 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00003010 {
3011 if (grayscale != MagickFalse)
3012 if ((image->colormap[i].red != image->colormap[i].green) ||
3013 (image->colormap[i].green != image->colormap[i].blue))
3014 continue;
cristyed231572011-07-14 02:18:59 +00003015 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00003016 image->colormap[i].red=(Quantum) QuantumRange-
3017 image->colormap[i].red;
cristyed231572011-07-14 02:18:59 +00003018 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00003019 image->colormap[i].green=(Quantum) QuantumRange-
3020 image->colormap[i].green;
cristyed231572011-07-14 02:18:59 +00003021 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00003022 image->colormap[i].blue=(Quantum) QuantumRange-
3023 image->colormap[i].blue;
3024 }
3025 }
3026 /*
3027 Negate image.
3028 */
3029 status=MagickTrue;
3030 progress=0;
cristy3ed852e2009-09-05 21:47:34 +00003031 image_view=AcquireCacheView(image);
3032 if (grayscale != MagickFalse)
3033 {
cristyb5d5f722009-11-04 03:03:49 +00003034#if defined(MAGICKCORE_OPENMP_SUPPORT)
3035 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003036#endif
cristybb503372010-05-27 20:51:26 +00003037 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003038 {
3039 MagickBooleanType
3040 sync;
3041
cristy4c08aed2011-07-01 19:47:50 +00003042 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00003043 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003044
cristy8d4629b2010-08-30 17:59:46 +00003045 register ssize_t
3046 x;
3047
cristy3ed852e2009-09-05 21:47:34 +00003048 if (status == MagickFalse)
3049 continue;
3050 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,
3051 exception);
cristyacd2ed22011-08-30 01:44:23 +00003052 if (q == (Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00003053 {
3054 status=MagickFalse;
3055 continue;
3056 }
cristybb503372010-05-27 20:51:26 +00003057 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003058 {
cristy9aa95be2011-07-20 21:56:45 +00003059 register ssize_t
3060 i;
3061
cristyd476a8e2011-07-23 16:13:22 +00003062 if (IsPixelGray(image,q) != MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00003063 {
cristya30d9ba2011-07-23 21:00:48 +00003064 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00003065 continue;
3066 }
cristya30d9ba2011-07-23 21:00:48 +00003067 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
cristy9aa95be2011-07-20 21:56:45 +00003068 {
cristyabace412011-12-11 15:56:53 +00003069 PixelChannel
3070 channel;
3071
cristy1aaa3cd2011-08-21 23:48:17 +00003072 PixelTrait
cristy9aa95be2011-07-20 21:56:45 +00003073 traits;
3074
cristyabace412011-12-11 15:56:53 +00003075 channel=GetPixelChannelMapChannel(image,i);
3076 traits=GetPixelChannelMapTraits(image,channel);
cristy9aa95be2011-07-20 21:56:45 +00003077 if ((traits & UpdatePixelTrait) != 0)
3078 q[i]=QuantumRange-q[i];
3079 }
cristya30d9ba2011-07-23 21:00:48 +00003080 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00003081 }
3082 sync=SyncCacheViewAuthenticPixels(image_view,exception);
3083 if (sync == MagickFalse)
3084 status=MagickFalse;
3085 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3086 {
3087 MagickBooleanType
3088 proceed;
3089
cristyb5d5f722009-11-04 03:03:49 +00003090#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy50fbc382011-07-07 02:19:17 +00003091 #pragma omp critical (MagickCore_NegateImage)
cristy3ed852e2009-09-05 21:47:34 +00003092#endif
3093 proceed=SetImageProgress(image,NegateImageTag,progress++,
3094 image->rows);
3095 if (proceed == MagickFalse)
3096 status=MagickFalse;
3097 }
3098 }
3099 image_view=DestroyCacheView(image_view);
3100 return(MagickTrue);
3101 }
3102 /*
3103 Negate image.
3104 */
cristyb5d5f722009-11-04 03:03:49 +00003105#if defined(MAGICKCORE_OPENMP_SUPPORT)
3106 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003107#endif
cristybb503372010-05-27 20:51:26 +00003108 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003109 {
cristy4c08aed2011-07-01 19:47:50 +00003110 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00003111 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003112
cristy8d4629b2010-08-30 17:59:46 +00003113 register ssize_t
3114 x;
3115
cristy3ed852e2009-09-05 21:47:34 +00003116 if (status == MagickFalse)
3117 continue;
3118 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristyacd2ed22011-08-30 01:44:23 +00003119 if (q == (Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00003120 {
3121 status=MagickFalse;
3122 continue;
3123 }
cristybb503372010-05-27 20:51:26 +00003124 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003125 {
cristyf7dc44c2011-07-20 14:41:15 +00003126 register ssize_t
3127 i;
3128
cristya30d9ba2011-07-23 21:00:48 +00003129 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
cristyf7dc44c2011-07-20 14:41:15 +00003130 {
cristyabace412011-12-11 15:56:53 +00003131 PixelChannel
3132 channel;
3133
cristy1aaa3cd2011-08-21 23:48:17 +00003134 PixelTrait
cristyf7dc44c2011-07-20 14:41:15 +00003135 traits;
3136
cristyabace412011-12-11 15:56:53 +00003137 channel=GetPixelChannelMapChannel(image,i);
3138 traits=GetPixelChannelMapTraits(image,channel);
cristyf7dc44c2011-07-20 14:41:15 +00003139 if ((traits & UpdatePixelTrait) != 0)
3140 q[i]=QuantumRange-q[i];
3141 }
cristya30d9ba2011-07-23 21:00:48 +00003142 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00003143 }
3144 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
3145 status=MagickFalse;
3146 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3147 {
3148 MagickBooleanType
3149 proceed;
3150
cristyb5d5f722009-11-04 03:03:49 +00003151#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy50fbc382011-07-07 02:19:17 +00003152 #pragma omp critical (MagickCore_NegateImage)
cristy3ed852e2009-09-05 21:47:34 +00003153#endif
3154 proceed=SetImageProgress(image,NegateImageTag,progress++,image->rows);
3155 if (proceed == MagickFalse)
3156 status=MagickFalse;
3157 }
3158 }
3159 image_view=DestroyCacheView(image_view);
3160 return(status);
3161}
3162
3163/*
3164%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3165% %
3166% %
3167% %
3168% N o r m a l i z e I m a g e %
3169% %
3170% %
3171% %
3172%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3173%
cristya4dfb122011-07-07 19:01:57 +00003174% NormalizeImage() enhances the contrast of a color image by mapping the
3175% darkest 2 percent of all pixel to black and the brightest 1 percent to white.
cristy3ed852e2009-09-05 21:47:34 +00003176%
3177% The format of the NormalizeImage method is:
3178%
cristye23ec9d2011-08-16 18:15:40 +00003179% MagickBooleanType NormalizeImage(Image *image,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003180%
3181% A description of each parameter follows:
3182%
3183% o image: the image.
3184%
cristye23ec9d2011-08-16 18:15:40 +00003185% o exception: return any errors or warnings in this structure.
3186%
cristy3ed852e2009-09-05 21:47:34 +00003187*/
cristye23ec9d2011-08-16 18:15:40 +00003188MagickExport MagickBooleanType NormalizeImage(Image *image,
3189 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003190{
cristy3ed852e2009-09-05 21:47:34 +00003191 double
3192 black_point,
3193 white_point;
3194
cristy530239c2010-07-25 17:34:26 +00003195 black_point=(double) image->columns*image->rows*0.0015;
3196 white_point=(double) image->columns*image->rows*0.9995;
cristye23ec9d2011-08-16 18:15:40 +00003197 return(ContrastStretchImage(image,black_point,white_point,exception));
cristy3ed852e2009-09-05 21:47:34 +00003198}
3199
3200/*
3201%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3202% %
3203% %
3204% %
3205% S i g m o i d a l C o n t r a s t I m a g e %
3206% %
3207% %
3208% %
3209%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3210%
3211% SigmoidalContrastImage() adjusts the contrast of an image with a non-linear
3212% sigmoidal contrast algorithm. Increase the contrast of the image using a
3213% sigmoidal transfer function without saturating highlights or shadows.
3214% Contrast indicates how much to increase the contrast (0 is none; 3 is
3215% typical; 20 is pushing it); mid-point indicates where midtones fall in the
3216% resultant image (0 is white; 50% is middle-gray; 100% is black). Set
3217% sharpen to MagickTrue to increase the image contrast otherwise the contrast
3218% is reduced.
3219%
3220% The format of the SigmoidalContrastImage method is:
3221%
3222% MagickBooleanType SigmoidalContrastImage(Image *image,
cristy33bd5152011-08-24 01:42:24 +00003223% const MagickBooleanType sharpen,const char *levels,
3224% ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003225%
3226% A description of each parameter follows:
3227%
3228% o image: the image.
3229%
cristy3ed852e2009-09-05 21:47:34 +00003230% o sharpen: Increase or decrease image contrast.
3231%
cristyfa769582010-09-30 23:30:03 +00003232% o alpha: strength of the contrast, the larger the number the more
3233% 'threshold-like' it becomes.
cristy3ed852e2009-09-05 21:47:34 +00003234%
cristyfa769582010-09-30 23:30:03 +00003235% o beta: midpoint of the function as a color value 0 to QuantumRange.
cristy3ed852e2009-09-05 21:47:34 +00003236%
cristy33bd5152011-08-24 01:42:24 +00003237% o exception: return any errors or warnings in this structure.
3238%
cristy3ed852e2009-09-05 21:47:34 +00003239*/
cristy3ed852e2009-09-05 21:47:34 +00003240MagickExport MagickBooleanType SigmoidalContrastImage(Image *image,
cristy33bd5152011-08-24 01:42:24 +00003241 const MagickBooleanType sharpen,const double contrast,const double midpoint,
3242 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003243{
3244#define SigmoidalContrastImageTag "SigmoidalContrast/Image"
3245
cristyc4c8d132010-01-07 01:58:38 +00003246 CacheView
3247 *image_view;
3248
cristy3ed852e2009-09-05 21:47:34 +00003249 MagickBooleanType
3250 status;
3251
cristybb503372010-05-27 20:51:26 +00003252 MagickOffsetType
3253 progress;
3254
cristy3ed852e2009-09-05 21:47:34 +00003255 MagickRealType
3256 *sigmoidal_map;
3257
cristybb503372010-05-27 20:51:26 +00003258 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003259 i;
3260
cristybb503372010-05-27 20:51:26 +00003261 ssize_t
3262 y;
3263
cristy3ed852e2009-09-05 21:47:34 +00003264 /*
3265 Allocate and initialize sigmoidal maps.
3266 */
3267 assert(image != (Image *) NULL);
3268 assert(image->signature == MagickSignature);
3269 if (image->debug != MagickFalse)
3270 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3271 sigmoidal_map=(MagickRealType *) AcquireQuantumMemory(MaxMap+1UL,
3272 sizeof(*sigmoidal_map));
3273 if (sigmoidal_map == (MagickRealType *) NULL)
3274 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
3275 image->filename);
3276 (void) ResetMagickMemory(sigmoidal_map,0,(MaxMap+1)*sizeof(*sigmoidal_map));
cristyb5d5f722009-11-04 03:03:49 +00003277#if defined(MAGICKCORE_OPENMP_SUPPORT)
3278 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003279#endif
cristybb503372010-05-27 20:51:26 +00003280 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +00003281 {
3282 if (sharpen != MagickFalse)
3283 {
3284 sigmoidal_map[i]=(MagickRealType) ScaleMapToQuantum((MagickRealType)
3285 (MaxMap*((1.0/(1.0+exp(contrast*(midpoint/(double) QuantumRange-
cristy33bd5152011-08-24 01:42:24 +00003286 (double) i/MaxMap))))-(1.0/(1.0+exp(contrast*(midpoint/(double)
3287 QuantumRange)))))/((1.0/(1.0+exp(contrast*(midpoint/(double)
3288 QuantumRange-1.0))))-(1.0/(1.0+exp(contrast*(midpoint/(double)
3289 QuantumRange)))))+0.5));
cristy3ed852e2009-09-05 21:47:34 +00003290 continue;
3291 }
3292 sigmoidal_map[i]=(MagickRealType) ScaleMapToQuantum((MagickRealType)
cristy33bd5152011-08-24 01:42:24 +00003293 (MaxMap*(QuantumScale*midpoint-log((1.0-(1.0/(1.0+exp(midpoint/(double)
3294 QuantumRange*contrast))+((double) i/MaxMap)*((1.0/(1.0+exp(contrast*(
3295 midpoint/(double) QuantumRange-1.0))))-(1.0/(1.0+exp(midpoint/(double)
3296 QuantumRange*contrast))))))/(1.0/(1.0+exp(midpoint/(double) QuantumRange*
3297 contrast))+((double) i/MaxMap)*((1.0/(1.0+exp(contrast*(midpoint/(double)
3298 QuantumRange-1.0))))-(1.0/(1.0+exp(midpoint/(double) QuantumRange*
3299 contrast))))))/contrast)));
cristy3ed852e2009-09-05 21:47:34 +00003300 }
3301 if (image->storage_class == PseudoClass)
3302 {
3303 /*
3304 Sigmoidal-contrast enhance colormap.
3305 */
cristyb5d5f722009-11-04 03:03:49 +00003306#if defined(MAGICKCORE_OPENMP_SUPPORT)
3307 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003308#endif
cristybb503372010-05-27 20:51:26 +00003309 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00003310 {
cristyed231572011-07-14 02:18:59 +00003311 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristyda1f9c12011-10-02 21:39:49 +00003312 image->colormap[i].red=sigmoidal_map[ScaleQuantumToMap(
3313 ClampToQuantum(image->colormap[i].red))];
cristyed231572011-07-14 02:18:59 +00003314 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristyda1f9c12011-10-02 21:39:49 +00003315 image->colormap[i].green=sigmoidal_map[ScaleQuantumToMap(
3316 ClampToQuantum(image->colormap[i].green))];
cristyed231572011-07-14 02:18:59 +00003317 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristyda1f9c12011-10-02 21:39:49 +00003318 image->colormap[i].blue=sigmoidal_map[ScaleQuantumToMap(
3319 ClampToQuantum(image->colormap[i].blue))];
cristyed231572011-07-14 02:18:59 +00003320 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristyda1f9c12011-10-02 21:39:49 +00003321 image->colormap[i].alpha=sigmoidal_map[ScaleQuantumToMap(
3322 ClampToQuantum(image->colormap[i].alpha))];
cristy3ed852e2009-09-05 21:47:34 +00003323 }
3324 }
3325 /*
3326 Sigmoidal-contrast enhance image.
3327 */
3328 status=MagickTrue;
3329 progress=0;
cristy3ed852e2009-09-05 21:47:34 +00003330 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00003331#if defined(MAGICKCORE_OPENMP_SUPPORT)
3332 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003333#endif
cristybb503372010-05-27 20:51:26 +00003334 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003335 {
cristy4c08aed2011-07-01 19:47:50 +00003336 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00003337 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003338
cristy8d4629b2010-08-30 17:59:46 +00003339 register ssize_t
3340 x;
3341
cristy3ed852e2009-09-05 21:47:34 +00003342 if (status == MagickFalse)
3343 continue;
3344 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristyacd2ed22011-08-30 01:44:23 +00003345 if (q == (Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00003346 {
3347 status=MagickFalse;
3348 continue;
3349 }
cristybb503372010-05-27 20:51:26 +00003350 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003351 {
cristy33bd5152011-08-24 01:42:24 +00003352 register ssize_t
3353 i;
3354
3355 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
3356 {
cristyabace412011-12-11 15:56:53 +00003357 PixelChannel
3358 channel;
3359
cristy33bd5152011-08-24 01:42:24 +00003360 PixelTrait
3361 traits;
3362
cristyabace412011-12-11 15:56:53 +00003363 channel=GetPixelChannelMapChannel(image,i);
3364 traits=GetPixelChannelMapTraits(image,channel);
cristy33bd5152011-08-24 01:42:24 +00003365 if ((traits & UpdatePixelTrait) != 0)
3366 q[i]=ClampToQuantum(sigmoidal_map[ScaleQuantumToMap(q[i])]);
3367 }
cristyed231572011-07-14 02:18:59 +00003368 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00003369 }
3370 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
3371 status=MagickFalse;
3372 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3373 {
3374 MagickBooleanType
3375 proceed;
3376
cristyb5d5f722009-11-04 03:03:49 +00003377#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy9ee60942011-07-06 14:54:38 +00003378 #pragma omp critical (MagickCore_SigmoidalContrastImage)
cristy3ed852e2009-09-05 21:47:34 +00003379#endif
3380 proceed=SetImageProgress(image,SigmoidalContrastImageTag,progress++,
3381 image->rows);
3382 if (proceed == MagickFalse)
3383 status=MagickFalse;
3384 }
3385 }
3386 image_view=DestroyCacheView(image_view);
3387 sigmoidal_map=(MagickRealType *) RelinquishMagickMemory(sigmoidal_map);
3388 return(status);
3389}