blob: ddb9877aea3a72ff394667c02bddb51c59de518d [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)
cristye6178502011-12-23 17:02:29 +0000343 #pragma omp parallel for schedule(static,4)
cristy444eda62011-08-10 02:07:46 +0000344#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)
cristye6178502011-12-23 17:02:29 +0000359 #pragma omp parallel for schedule(static,4) shared(progress,status)
cristy444eda62011-08-10 02:07:46 +0000360#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);
cristyca826b52012-01-18 18:50:46 +0000676 color_correction.saturation=StringToDouble(token,(char **) NULL);
cristy3ed852e2009-09-05 21:47:34 +0000677 }
678 }
679 ccc=DestroyXMLTree(ccc);
680 if (image->debug != MagickFalse)
681 {
682 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
683 " Color Correction Collection:");
684 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000685 " color_correction.red.slope: %g",color_correction.red.slope);
cristy3ed852e2009-09-05 21:47:34 +0000686 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000687 " color_correction.red.offset: %g",color_correction.red.offset);
cristy3ed852e2009-09-05 21:47:34 +0000688 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000689 " color_correction.red.power: %g",color_correction.red.power);
cristy3ed852e2009-09-05 21:47:34 +0000690 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000691 " color_correction.green.slope: %g",color_correction.green.slope);
cristy3ed852e2009-09-05 21:47:34 +0000692 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000693 " color_correction.green.offset: %g",color_correction.green.offset);
cristy3ed852e2009-09-05 21:47:34 +0000694 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000695 " color_correction.green.power: %g",color_correction.green.power);
cristy3ed852e2009-09-05 21:47:34 +0000696 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000697 " color_correction.blue.slope: %g",color_correction.blue.slope);
cristy3ed852e2009-09-05 21:47:34 +0000698 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000699 " color_correction.blue.offset: %g",color_correction.blue.offset);
cristy3ed852e2009-09-05 21:47:34 +0000700 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000701 " color_correction.blue.power: %g",color_correction.blue.power);
cristy3ed852e2009-09-05 21:47:34 +0000702 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000703 " color_correction.saturation: %g",color_correction.saturation);
cristy3ed852e2009-09-05 21:47:34 +0000704 }
cristy101ab702011-10-13 13:06:32 +0000705 cdl_map=(PixelInfo *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*cdl_map));
706 if (cdl_map == (PixelInfo *) NULL)
cristy3ed852e2009-09-05 21:47:34 +0000707 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
708 image->filename);
cristyb5d5f722009-11-04 03:03:49 +0000709#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristye6178502011-12-23 17:02:29 +0000710 #pragma omp parallel for schedule(static,4)
cristy3ed852e2009-09-05 21:47:34 +0000711#endif
cristybb503372010-05-27 20:51:26 +0000712 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +0000713 {
cristyda1f9c12011-10-02 21:39:49 +0000714 cdl_map[i].red=(MagickRealType) ScaleMapToQuantum((MagickRealType)
715 (MaxMap*(pow(color_correction.red.slope*i/MaxMap+
716 color_correction.red.offset,color_correction.red.power))));
717 cdl_map[i].green=(MagickRealType) ScaleMapToQuantum((MagickRealType)
718 (MaxMap*(pow(color_correction.green.slope*i/MaxMap+
719 color_correction.green.offset,color_correction.green.power))));
720 cdl_map[i].blue=(MagickRealType) ScaleMapToQuantum((MagickRealType)
721 (MaxMap*(pow(color_correction.blue.slope*i/MaxMap+
722 color_correction.blue.offset,color_correction.blue.power))));
cristy3ed852e2009-09-05 21:47:34 +0000723 }
724 if (image->storage_class == PseudoClass)
725 {
726 /*
727 Apply transfer function to colormap.
728 */
cristyb5d5f722009-11-04 03:03:49 +0000729#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy22e88202012-01-18 19:03:01 +0000730 #pragma omp parallel for schedule(static,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000731#endif
cristybb503372010-05-27 20:51:26 +0000732 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +0000733 {
734 double
735 luma;
736
737 luma=0.2126*image->colormap[i].red+0.7152*image->colormap[i].green+
738 0.0722*image->colormap[i].blue;
cristy22e88202012-01-18 19:03:01 +0000739 image->colormap[i].red=luma+color_correction.saturation*cdl_map[
740 ScaleQuantumToMap(ClampToQuantum(image->colormap[i].red))].red-
741 luma;
742 image->colormap[i].green=luma+color_correction.saturation*cdl_map[
743 ScaleQuantumToMap(ClampToQuantum(image->colormap[i].green))].green-
744 luma;
745 image->colormap[i].blue=luma+color_correction.saturation*cdl_map[
746 ScaleQuantumToMap(ClampToQuantum(image->colormap[i].blue))].blue-
747 luma;
cristy3ed852e2009-09-05 21:47:34 +0000748 }
749 }
750 /*
751 Apply transfer function to image.
752 */
753 status=MagickTrue;
754 progress=0;
cristy3ed852e2009-09-05 21:47:34 +0000755 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +0000756#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristye6178502011-12-23 17:02:29 +0000757 #pragma omp parallel for schedule(static,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000758#endif
cristybb503372010-05-27 20:51:26 +0000759 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000760 {
761 double
762 luma;
763
cristy4c08aed2011-07-01 19:47:50 +0000764 register Quantum
cristyc47d1f82009-11-26 01:44:43 +0000765 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000766
cristy8d4629b2010-08-30 17:59:46 +0000767 register ssize_t
768 x;
769
cristy3ed852e2009-09-05 21:47:34 +0000770 if (status == MagickFalse)
771 continue;
772 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristyacd2ed22011-08-30 01:44:23 +0000773 if (q == (Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +0000774 {
775 status=MagickFalse;
776 continue;
777 }
cristybb503372010-05-27 20:51:26 +0000778 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000779 {
cristy4c08aed2011-07-01 19:47:50 +0000780 luma=0.2126*GetPixelRed(image,q)+0.7152*GetPixelGreen(image,q)+0.0722*
781 GetPixelBlue(image,q);
782 SetPixelRed(image,ClampToQuantum(luma+color_correction.saturation*
783 (cdl_map[ScaleQuantumToMap(GetPixelRed(image,q))].red-luma)),q);
784 SetPixelGreen(image,ClampToQuantum(luma+color_correction.saturation*
785 (cdl_map[ScaleQuantumToMap(GetPixelGreen(image,q))].green-luma)),q);
786 SetPixelBlue(image,ClampToQuantum(luma+color_correction.saturation*
787 (cdl_map[ScaleQuantumToMap(GetPixelBlue(image,q))].blue-luma)),q);
cristyed231572011-07-14 02:18:59 +0000788 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +0000789 }
790 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
791 status=MagickFalse;
792 if (image->progress_monitor != (MagickProgressMonitor) NULL)
793 {
794 MagickBooleanType
795 proceed;
796
cristyb5d5f722009-11-04 03:03:49 +0000797#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +0000798 #pragma omp critical (MagickCore_ColorDecisionListImageChannel)
799#endif
800 proceed=SetImageProgress(image,ColorDecisionListCorrectImageTag,
801 progress++,image->rows);
802 if (proceed == MagickFalse)
803 status=MagickFalse;
804 }
805 }
806 image_view=DestroyCacheView(image_view);
cristy101ab702011-10-13 13:06:32 +0000807 cdl_map=(PixelInfo *) RelinquishMagickMemory(cdl_map);
cristy3ed852e2009-09-05 21:47:34 +0000808 return(status);
809}
810
811/*
812%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
813% %
814% %
815% %
cristy3ed852e2009-09-05 21:47:34 +0000816% C o n t r a s t I m a g e %
817% %
818% %
819% %
820%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
821%
822% ContrastImage() enhances the intensity differences between the lighter and
823% darker elements of the image. Set sharpen to a MagickTrue to increase the
824% image contrast otherwise the contrast is reduced.
825%
826% The format of the ContrastImage method is:
827%
828% MagickBooleanType ContrastImage(Image *image,
cristye23ec9d2011-08-16 18:15:40 +0000829% const MagickBooleanType sharpen,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000830%
831% A description of each parameter follows:
832%
833% o image: the image.
834%
835% o sharpen: Increase or decrease image contrast.
836%
cristye23ec9d2011-08-16 18:15:40 +0000837% o exception: return any errors or warnings in this structure.
838%
cristy3ed852e2009-09-05 21:47:34 +0000839*/
840
cristy3094b7f2011-10-01 23:18:02 +0000841static void Contrast(const int sign,double *red,double *green,double *blue)
cristy3ed852e2009-09-05 21:47:34 +0000842{
843 double
844 brightness,
845 hue,
846 saturation;
847
848 /*
849 Enhance contrast: dark color become darker, light color become lighter.
850 */
cristy3094b7f2011-10-01 23:18:02 +0000851 assert(red != (double *) NULL);
852 assert(green != (double *) NULL);
853 assert(blue != (double *) NULL);
cristy3ed852e2009-09-05 21:47:34 +0000854 hue=0.0;
855 saturation=0.0;
856 brightness=0.0;
857 ConvertRGBToHSB(*red,*green,*blue,&hue,&saturation,&brightness);
cristy31ac0f02011-03-12 02:04:47 +0000858 brightness+=0.5*sign*(0.5*(sin((double) (MagickPI*(brightness-0.5)))+1.0)-
cristy4205a3c2010-09-12 20:19:59 +0000859 brightness);
cristy3ed852e2009-09-05 21:47:34 +0000860 if (brightness > 1.0)
861 brightness=1.0;
862 else
863 if (brightness < 0.0)
864 brightness=0.0;
865 ConvertHSBToRGB(hue,saturation,brightness,red,green,blue);
866}
867
868MagickExport MagickBooleanType ContrastImage(Image *image,
cristye23ec9d2011-08-16 18:15:40 +0000869 const MagickBooleanType sharpen,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000870{
871#define ContrastImageTag "Contrast/Image"
872
cristyc4c8d132010-01-07 01:58:38 +0000873 CacheView
874 *image_view;
875
cristy3ed852e2009-09-05 21:47:34 +0000876 int
877 sign;
878
cristy3ed852e2009-09-05 21:47:34 +0000879 MagickBooleanType
880 status;
881
cristybb503372010-05-27 20:51:26 +0000882 MagickOffsetType
883 progress;
884
885 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000886 i;
887
cristybb503372010-05-27 20:51:26 +0000888 ssize_t
889 y;
890
cristy3ed852e2009-09-05 21:47:34 +0000891 assert(image != (Image *) NULL);
892 assert(image->signature == MagickSignature);
893 if (image->debug != MagickFalse)
894 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
895 sign=sharpen != MagickFalse ? 1 : -1;
896 if (image->storage_class == PseudoClass)
897 {
898 /*
899 Contrast enhance colormap.
900 */
cristybb503372010-05-27 20:51:26 +0000901 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +0000902 Contrast(sign,&image->colormap[i].red,&image->colormap[i].green,
903 &image->colormap[i].blue);
904 }
905 /*
906 Contrast enhance image.
907 */
908 status=MagickTrue;
909 progress=0;
cristy3ed852e2009-09-05 21:47:34 +0000910 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +0000911#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristye6178502011-12-23 17:02:29 +0000912 #pragma omp parallel for schedule(static,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000913#endif
cristybb503372010-05-27 20:51:26 +0000914 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000915 {
cristy3094b7f2011-10-01 23:18:02 +0000916 double
cristy5afeab82011-04-30 01:30:09 +0000917 blue,
918 green,
919 red;
920
cristy4c08aed2011-07-01 19:47:50 +0000921 register Quantum
cristyc47d1f82009-11-26 01:44:43 +0000922 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000923
cristy8d4629b2010-08-30 17:59:46 +0000924 register ssize_t
925 x;
926
cristy3ed852e2009-09-05 21:47:34 +0000927 if (status == MagickFalse)
928 continue;
929 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristyacd2ed22011-08-30 01:44:23 +0000930 if (q == (Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +0000931 {
932 status=MagickFalse;
933 continue;
934 }
cristybb503372010-05-27 20:51:26 +0000935 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000936 {
cristyda1f9c12011-10-02 21:39:49 +0000937 red=(double) GetPixelRed(image,q);
938 green=(double) GetPixelGreen(image,q);
939 blue=(double) GetPixelBlue(image,q);
cristy5afeab82011-04-30 01:30:09 +0000940 Contrast(sign,&red,&green,&blue);
cristyda1f9c12011-10-02 21:39:49 +0000941 SetPixelRed(image,ClampToQuantum(red),q);
942 SetPixelGreen(image,ClampToQuantum(green),q);
943 SetPixelBlue(image,ClampToQuantum(blue),q);
cristyed231572011-07-14 02:18:59 +0000944 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +0000945 }
946 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
947 status=MagickFalse;
948 if (image->progress_monitor != (MagickProgressMonitor) NULL)
949 {
950 MagickBooleanType
951 proceed;
952
cristyb5d5f722009-11-04 03:03:49 +0000953#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +0000954 #pragma omp critical (MagickCore_ContrastImage)
955#endif
956 proceed=SetImageProgress(image,ContrastImageTag,progress++,image->rows);
957 if (proceed == MagickFalse)
958 status=MagickFalse;
959 }
960 }
961 image_view=DestroyCacheView(image_view);
962 return(status);
963}
964
965/*
966%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
967% %
968% %
969% %
970% C o n t r a s t S t r e t c h I m a g e %
971% %
972% %
973% %
974%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
975%
cristyf1611782011-08-01 15:39:13 +0000976% ContrastStretchImage() is a simple image enhancement technique that attempts
977% to improve the contrast in an image by `stretching' the range of intensity
978% values it contains to span a desired range of values. It differs from the
979% more sophisticated histogram equalization in that it can only apply a
980% linear scaling function to the image pixel values. As a result the
981% `enhancement' is less harsh.
cristy3ed852e2009-09-05 21:47:34 +0000982%
983% The format of the ContrastStretchImage method is:
984%
985% MagickBooleanType ContrastStretchImage(Image *image,
cristye23ec9d2011-08-16 18:15:40 +0000986% const char *levels,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000987%
988% A description of each parameter follows:
989%
990% o image: the image.
991%
cristy3ed852e2009-09-05 21:47:34 +0000992% o black_point: the black point.
993%
994% o white_point: the white point.
995%
996% o levels: Specify the levels where the black and white points have the
997% range of 0 to number-of-pixels (e.g. 1%, 10x90%, etc.).
998%
cristye23ec9d2011-08-16 18:15:40 +0000999% o exception: return any errors or warnings in this structure.
1000%
cristy3ed852e2009-09-05 21:47:34 +00001001*/
cristy3ed852e2009-09-05 21:47:34 +00001002MagickExport MagickBooleanType ContrastStretchImage(Image *image,
cristye23ec9d2011-08-16 18:15:40 +00001003 const double black_point,const double white_point,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001004{
1005#define MaxRange(color) ((MagickRealType) ScaleQuantumToMap((Quantum) (color)))
1006#define ContrastStretchImageTag "ContrastStretch/Image"
1007
cristyc4c8d132010-01-07 01:58:38 +00001008 CacheView
1009 *image_view;
1010
cristy3ed852e2009-09-05 21:47:34 +00001011 MagickBooleanType
1012 status;
1013
cristybb503372010-05-27 20:51:26 +00001014 MagickOffsetType
1015 progress;
1016
cristyf45fec72011-08-23 16:02:32 +00001017 double
1018 *black,
cristy3ed852e2009-09-05 21:47:34 +00001019 *histogram,
1020 *stretch_map,
cristyf45fec72011-08-23 16:02:32 +00001021 *white;
cristy3ed852e2009-09-05 21:47:34 +00001022
cristybb503372010-05-27 20:51:26 +00001023 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001024 i;
1025
cristybb503372010-05-27 20:51:26 +00001026 ssize_t
1027 y;
1028
cristy3ed852e2009-09-05 21:47:34 +00001029 /*
1030 Allocate histogram and stretch map.
1031 */
1032 assert(image != (Image *) NULL);
1033 assert(image->signature == MagickSignature);
1034 if (image->debug != MagickFalse)
1035 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
cristyf45fec72011-08-23 16:02:32 +00001036 black=(double *) AcquireQuantumMemory(GetPixelChannels(image),sizeof(*black));
1037 white=(double *) AcquireQuantumMemory(GetPixelChannels(image),sizeof(*white));
1038 histogram=(double *) AcquireQuantumMemory(MaxMap+1UL,
cristy465571b2011-08-21 20:43:15 +00001039 GetPixelChannels(image)*sizeof(*histogram));
cristyf45fec72011-08-23 16:02:32 +00001040 stretch_map=(double *) AcquireQuantumMemory(MaxMap+1UL,
cristy465571b2011-08-21 20:43:15 +00001041 GetPixelChannels(image)*sizeof(*stretch_map));
cristyf45fec72011-08-23 16:02:32 +00001042 if ((black == (double *) NULL) || (white == (double *) NULL) ||
1043 (histogram == (double *) NULL) || (stretch_map == (double *) NULL))
cristy465571b2011-08-21 20:43:15 +00001044 {
cristyf45fec72011-08-23 16:02:32 +00001045 if (stretch_map != (double *) NULL)
1046 stretch_map=(double *) RelinquishMagickMemory(stretch_map);
1047 if (histogram != (double *) NULL)
1048 histogram=(double *) RelinquishMagickMemory(histogram);
1049 if (white != (double *) NULL)
1050 white=(double *) RelinquishMagickMemory(white);
1051 if (black != (double *) NULL)
1052 black=(double *) RelinquishMagickMemory(black);
cristy465571b2011-08-21 20:43:15 +00001053 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
1054 image->filename);
1055 }
cristy3ed852e2009-09-05 21:47:34 +00001056 /*
1057 Form histogram.
1058 */
1059 status=MagickTrue;
cristy465571b2011-08-21 20:43:15 +00001060 (void) ResetMagickMemory(histogram,0,(MaxMap+1)*GetPixelChannels(image)*
1061 sizeof(*histogram));
cristy3ed852e2009-09-05 21:47:34 +00001062 image_view=AcquireCacheView(image);
cristybb503372010-05-27 20:51:26 +00001063 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001064 {
cristy4c08aed2011-07-01 19:47:50 +00001065 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00001066 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00001067
cristybb503372010-05-27 20:51:26 +00001068 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001069 x;
1070
1071 if (status == MagickFalse)
1072 continue;
1073 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00001074 if (p == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001075 {
1076 status=MagickFalse;
1077 continue;
1078 }
cristy43547a52011-08-09 13:07:05 +00001079 for (x=0; x < (ssize_t) image->columns; x++)
1080 {
cristy465571b2011-08-21 20:43:15 +00001081 register ssize_t
1082 i;
1083
1084 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
cristy465571b2011-08-21 20:43:15 +00001085 histogram[GetPixelChannels(image)*ScaleQuantumToMap(p[i])+i]++;
cristy43547a52011-08-09 13:07:05 +00001086 p+=GetPixelChannels(image);
1087 }
cristy3ed852e2009-09-05 21:47:34 +00001088 }
1089 /*
1090 Find the histogram boundaries by locating the black/white levels.
1091 */
cristyb5d5f722009-11-04 03:03:49 +00001092#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristye6178502011-12-23 17:02:29 +00001093 #pragma omp parallel for schedule(static,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001094#endif
cristy465571b2011-08-21 20:43:15 +00001095 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
cristy3ed852e2009-09-05 21:47:34 +00001096 {
cristy465571b2011-08-21 20:43:15 +00001097 double
1098 intensity;
1099
1100 register ssize_t
1101 j;
1102
1103 black[i]=0.0;
1104 white[i]=MaxRange(QuantumRange);
1105 intensity=0.0;
1106 for (j=0; j <= (ssize_t) MaxMap; j++)
1107 {
1108 intensity+=histogram[GetPixelChannels(image)*j+i];
1109 if (intensity > black_point)
1110 break;
1111 }
1112 black[i]=(MagickRealType) j;
1113 intensity=0.0;
1114 for (j=(ssize_t) MaxMap; j != 0; j--)
1115 {
1116 intensity+=histogram[GetPixelChannels(image)*j+i];
1117 if (intensity > ((double) image->columns*image->rows-white_point))
1118 break;
1119 }
1120 white[i]=(MagickRealType) j;
cristy3ed852e2009-09-05 21:47:34 +00001121 }
cristy465571b2011-08-21 20:43:15 +00001122 histogram=(double *) RelinquishMagickMemory(histogram);
cristy3ed852e2009-09-05 21:47:34 +00001123 /*
cristy465571b2011-08-21 20:43:15 +00001124 Stretch the histogram to create the stretched image mapping.
cristy3ed852e2009-09-05 21:47:34 +00001125 */
cristy465571b2011-08-21 20:43:15 +00001126 (void) ResetMagickMemory(stretch_map,0,(MaxMap+1)*GetPixelChannels(image)*
1127 sizeof(*stretch_map));
1128#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristye6178502011-12-23 17:02:29 +00001129 #pragma omp parallel for schedule(static,4) shared(progress,status)
cristy465571b2011-08-21 20:43:15 +00001130#endif
1131 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1132 {
1133 register ssize_t
1134 j;
1135
1136 for (j=0; j <= (ssize_t) MaxMap; j++)
1137 {
1138 if (j < (ssize_t) black[i])
1139 stretch_map[GetPixelChannels(image)*j+i]=0.0;
1140 else
1141 if (j > (ssize_t) white[i])
1142 stretch_map[GetPixelChannels(image)*j+i]=(MagickRealType)
1143 QuantumRange;
1144 else
1145 if (black[i] != white[i])
1146 stretch_map[GetPixelChannels(image)*j+i]=(MagickRealType)
1147 ScaleMapToQuantum((MagickRealType) (MaxMap*(j-black[i])/
1148 (white[i]-black[i])));
1149 }
1150 }
cristy3ed852e2009-09-05 21:47:34 +00001151 if (image->storage_class == PseudoClass)
1152 {
cristy465571b2011-08-21 20:43:15 +00001153 register ssize_t
1154 j;
1155
cristy3ed852e2009-09-05 21:47:34 +00001156 /*
cristy465571b2011-08-21 20:43:15 +00001157 Stretch-contrast colormap.
cristy3ed852e2009-09-05 21:47:34 +00001158 */
cristyb5d5f722009-11-04 03:03:49 +00001159#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristye6178502011-12-23 17:02:29 +00001160 #pragma omp parallel for schedule(static,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001161#endif
cristy465571b2011-08-21 20:43:15 +00001162 for (j=0; j < (ssize_t) image->colors; j++)
cristy3ed852e2009-09-05 21:47:34 +00001163 {
cristyed231572011-07-14 02:18:59 +00001164 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001165 {
cristy465571b2011-08-21 20:43:15 +00001166 i=GetPixelChannelMapChannel(image,RedPixelChannel);
1167 if (black[i] != white[i])
cristyda1f9c12011-10-02 21:39:49 +00001168 image->colormap[j].red=stretch_map[GetPixelChannels(image)*
1169 ScaleQuantumToMap(ClampToQuantum(image->colormap[j].red))]+i;
cristy3ed852e2009-09-05 21:47:34 +00001170 }
cristyed231572011-07-14 02:18:59 +00001171 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001172 {
cristy465571b2011-08-21 20:43:15 +00001173 i=GetPixelChannelMapChannel(image,GreenPixelChannel);
1174 if (black[i] != white[i])
cristyda1f9c12011-10-02 21:39:49 +00001175 image->colormap[j].green=stretch_map[GetPixelChannels(image)*
1176 ScaleQuantumToMap(ClampToQuantum(image->colormap[j].green))]+i;
cristy3ed852e2009-09-05 21:47:34 +00001177 }
cristyed231572011-07-14 02:18:59 +00001178 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001179 {
cristy465571b2011-08-21 20:43:15 +00001180 i=GetPixelChannelMapChannel(image,BluePixelChannel);
1181 if (black[i] != white[i])
cristyda1f9c12011-10-02 21:39:49 +00001182 image->colormap[j].blue=stretch_map[GetPixelChannels(image)*
1183 ScaleQuantumToMap(ClampToQuantum(image->colormap[j].blue))]+i;
cristy3ed852e2009-09-05 21:47:34 +00001184 }
cristyed231572011-07-14 02:18:59 +00001185 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001186 {
cristy465571b2011-08-21 20:43:15 +00001187 i=GetPixelChannelMapChannel(image,AlphaPixelChannel);
1188 if (black[i] != white[i])
cristyda1f9c12011-10-02 21:39:49 +00001189 image->colormap[j].alpha=stretch_map[GetPixelChannels(image)*
1190 ScaleQuantumToMap(ClampToQuantum(image->colormap[j].alpha))]+i;
cristy3ed852e2009-09-05 21:47:34 +00001191 }
1192 }
1193 }
1194 /*
cristy465571b2011-08-21 20:43:15 +00001195 Stretch-contrast image.
cristy3ed852e2009-09-05 21:47:34 +00001196 */
1197 status=MagickTrue;
1198 progress=0;
cristyb5d5f722009-11-04 03:03:49 +00001199#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristye6178502011-12-23 17:02:29 +00001200 #pragma omp parallel for schedule(static,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001201#endif
cristybb503372010-05-27 20:51:26 +00001202 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001203 {
cristy4c08aed2011-07-01 19:47:50 +00001204 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00001205 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001206
cristy8d4629b2010-08-30 17:59:46 +00001207 register ssize_t
1208 x;
1209
cristy3ed852e2009-09-05 21:47:34 +00001210 if (status == MagickFalse)
1211 continue;
1212 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristyacd2ed22011-08-30 01:44:23 +00001213 if (q == (Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001214 {
1215 status=MagickFalse;
1216 continue;
1217 }
cristybb503372010-05-27 20:51:26 +00001218 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001219 {
cristy465571b2011-08-21 20:43:15 +00001220 register ssize_t
1221 i;
1222
1223 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1224 {
cristyabace412011-12-11 15:56:53 +00001225 PixelChannel
1226 channel;
1227
cristy465571b2011-08-21 20:43:15 +00001228 PixelTrait
1229 traits;
1230
cristyabace412011-12-11 15:56:53 +00001231 channel=GetPixelChannelMapChannel(image,i);
1232 traits=GetPixelChannelMapTraits(image,channel);
cristy7c0a0a42011-08-23 17:57:25 +00001233 if (((traits & UpdatePixelTrait) != 0) && (black[i] != white[i]))
cristy465571b2011-08-21 20:43:15 +00001234 q[i]=ClampToQuantum(stretch_map[GetPixelChannels(image)*
1235 ScaleQuantumToMap(q[i])+i]);
1236 }
cristyed231572011-07-14 02:18:59 +00001237 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00001238 }
1239 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
1240 status=MagickFalse;
1241 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1242 {
1243 MagickBooleanType
1244 proceed;
1245
cristyb5d5f722009-11-04 03:03:49 +00001246#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy50fbc382011-07-07 02:19:17 +00001247 #pragma omp critical (MagickCore_ContrastStretchImage)
cristy3ed852e2009-09-05 21:47:34 +00001248#endif
1249 proceed=SetImageProgress(image,ContrastStretchImageTag,progress++,
1250 image->rows);
1251 if (proceed == MagickFalse)
1252 status=MagickFalse;
1253 }
1254 }
1255 image_view=DestroyCacheView(image_view);
cristyf45fec72011-08-23 16:02:32 +00001256 stretch_map=(double *) RelinquishMagickMemory(stretch_map);
1257 white=(double *) RelinquishMagickMemory(white);
1258 black=(double *) RelinquishMagickMemory(black);
cristy3ed852e2009-09-05 21:47:34 +00001259 return(status);
1260}
1261
1262/*
1263%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1264% %
1265% %
1266% %
1267% E n h a n c e I m a g e %
1268% %
1269% %
1270% %
1271%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1272%
1273% EnhanceImage() applies a digital filter that improves the quality of a
1274% noisy image.
1275%
1276% The format of the EnhanceImage method is:
1277%
1278% Image *EnhanceImage(const Image *image,ExceptionInfo *exception)
1279%
1280% A description of each parameter follows:
1281%
1282% o image: the image.
1283%
1284% o exception: return any errors or warnings in this structure.
1285%
1286*/
1287MagickExport Image *EnhanceImage(const Image *image,ExceptionInfo *exception)
1288{
cristy6d8c3d72011-08-22 01:20:01 +00001289#define EnhancePixel(weight) \
cristy0beccfa2011-09-25 20:47:53 +00001290 mean=((MagickRealType) r[i]+GetPixelChannel(enhance_image,channel,q))/2.0; \
1291 distance=(MagickRealType) r[i]-(MagickRealType) GetPixelChannel( \
1292 enhance_image,channel,q); \
cristy3ed852e2009-09-05 21:47:34 +00001293 distance_squared=QuantumScale*(2.0*((MagickRealType) QuantumRange+1.0)+ \
cristy6d8c3d72011-08-22 01:20:01 +00001294 mean)*distance*distance; \
cristy3ed852e2009-09-05 21:47:34 +00001295 if (distance_squared < ((MagickRealType) QuantumRange*(MagickRealType) \
1296 QuantumRange/25.0f)) \
1297 { \
cristy6d8c3d72011-08-22 01:20:01 +00001298 aggregate+=(weight)*r[i]; \
cristy3ed852e2009-09-05 21:47:34 +00001299 total_weight+=(weight); \
1300 } \
cristy6d8c3d72011-08-22 01:20:01 +00001301 r+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00001302#define EnhanceImageTag "Enhance/Image"
1303
cristyc4c8d132010-01-07 01:58:38 +00001304 CacheView
1305 *enhance_view,
1306 *image_view;
1307
cristy3ed852e2009-09-05 21:47:34 +00001308 Image
1309 *enhance_image;
1310
cristy3ed852e2009-09-05 21:47:34 +00001311 MagickBooleanType
1312 status;
1313
cristybb503372010-05-27 20:51:26 +00001314 MagickOffsetType
1315 progress;
1316
cristybb503372010-05-27 20:51:26 +00001317 ssize_t
1318 y;
1319
cristy3ed852e2009-09-05 21:47:34 +00001320 /*
1321 Initialize enhanced image attributes.
1322 */
1323 assert(image != (const Image *) NULL);
1324 assert(image->signature == MagickSignature);
1325 if (image->debug != MagickFalse)
1326 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1327 assert(exception != (ExceptionInfo *) NULL);
1328 assert(exception->signature == MagickSignature);
cristy3ed852e2009-09-05 21:47:34 +00001329 enhance_image=CloneImage(image,image->columns,image->rows,MagickTrue,
1330 exception);
1331 if (enhance_image == (Image *) NULL)
1332 return((Image *) NULL);
cristy574cc262011-08-05 01:23:58 +00001333 if (SetImageStorageClass(enhance_image,DirectClass,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00001334 {
cristy3ed852e2009-09-05 21:47:34 +00001335 enhance_image=DestroyImage(enhance_image);
1336 return((Image *) NULL);
1337 }
1338 /*
1339 Enhance image.
1340 */
1341 status=MagickTrue;
1342 progress=0;
cristy3ed852e2009-09-05 21:47:34 +00001343 image_view=AcquireCacheView(image);
1344 enhance_view=AcquireCacheView(enhance_image);
cristyb5d5f722009-11-04 03:03:49 +00001345#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyd36a25e2012-01-18 14:30:53 +00001346 #pragma omp parallel for schedule(static,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001347#endif
cristybb503372010-05-27 20:51:26 +00001348 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001349 {
cristy4c08aed2011-07-01 19:47:50 +00001350 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00001351 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00001352
cristy4c08aed2011-07-01 19:47:50 +00001353 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00001354 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001355
cristy8d4629b2010-08-30 17:59:46 +00001356 register ssize_t
1357 x;
1358
cristy6d8c3d72011-08-22 01:20:01 +00001359 ssize_t
1360 center;
1361
cristy3ed852e2009-09-05 21:47:34 +00001362 if (status == MagickFalse)
1363 continue;
1364 p=GetCacheViewVirtualPixels(image_view,-2,y-2,image->columns+4,5,exception);
1365 q=QueueCacheViewAuthenticPixels(enhance_view,0,y,enhance_image->columns,1,
1366 exception);
cristy4c08aed2011-07-01 19:47:50 +00001367 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00001368 {
1369 status=MagickFalse;
1370 continue;
1371 }
cristyf45fec72011-08-23 16:02:32 +00001372 center=(ssize_t) GetPixelChannels(image)*(2*(image->columns+4)+2);
cristybb503372010-05-27 20:51:26 +00001373 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001374 {
cristy6d8c3d72011-08-22 01:20:01 +00001375 register ssize_t
1376 i;
cristy3ed852e2009-09-05 21:47:34 +00001377
cristy6d8c3d72011-08-22 01:20:01 +00001378 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1379 {
1380 MagickRealType
1381 aggregate,
1382 distance,
1383 distance_squared,
1384 mean,
1385 total_weight;
cristy3ed852e2009-09-05 21:47:34 +00001386
cristy6d8c3d72011-08-22 01:20:01 +00001387 PixelChannel
1388 channel;
cristy3ed852e2009-09-05 21:47:34 +00001389
cristy6d8c3d72011-08-22 01:20:01 +00001390 PixelTrait
1391 enhance_traits,
1392 traits;
cristy3ed852e2009-09-05 21:47:34 +00001393
cristy6d8c3d72011-08-22 01:20:01 +00001394 register const Quantum
1395 *restrict r;
1396
cristye2a912b2011-12-05 20:02:07 +00001397 channel=GetPixelChannelMapChannel(image,i);
cristyabace412011-12-11 15:56:53 +00001398 traits=GetPixelChannelMapTraits(image,channel);
cristy6d8c3d72011-08-22 01:20:01 +00001399 enhance_traits=GetPixelChannelMapTraits(enhance_image,channel);
cristy010d7d12011-08-31 01:02:48 +00001400 if ((traits == UndefinedPixelTrait) ||
1401 (enhance_traits == UndefinedPixelTrait))
cristy6d8c3d72011-08-22 01:20:01 +00001402 continue;
cristy0beccfa2011-09-25 20:47:53 +00001403 SetPixelChannel(enhance_image,channel,p[center+i],q);
cristy6d8c3d72011-08-22 01:20:01 +00001404 if ((enhance_traits & CopyPixelTrait) != 0)
1405 continue;
1406 /*
1407 Compute weighted average of target pixel color components.
1408 */
1409 aggregate=0.0;
1410 total_weight=0.0;
cristyf45fec72011-08-23 16:02:32 +00001411 r=p;
cristy6d8c3d72011-08-22 01:20:01 +00001412 EnhancePixel(5.0); EnhancePixel(8.0); EnhancePixel(10.0);
1413 EnhancePixel(8.0); EnhancePixel(5.0);
1414 r=p+1*GetPixelChannels(image)*(image->columns+4);
1415 EnhancePixel(8.0); EnhancePixel(20.0); EnhancePixel(40.0);
1416 EnhancePixel(20.0); EnhancePixel(8.0);
1417 r=p+2*GetPixelChannels(image)*(image->columns+4);
1418 EnhancePixel(10.0); EnhancePixel(40.0); EnhancePixel(80.0);
1419 EnhancePixel(40.0); EnhancePixel(10.0);
1420 r=p+3*GetPixelChannels(image)*(image->columns+4);
1421 EnhancePixel(8.0); EnhancePixel(20.0); EnhancePixel(40.0);
1422 EnhancePixel(20.0); EnhancePixel(8.0);
1423 r=p+4*GetPixelChannels(image)*(image->columns+4);
1424 EnhancePixel(5.0); EnhancePixel(8.0); EnhancePixel(10.0);
1425 EnhancePixel(8.0); EnhancePixel(5.0);
cristy0beccfa2011-09-25 20:47:53 +00001426 SetPixelChannel(enhance_image,channel,ClampToQuantum(aggregate/
1427 total_weight),q);
cristy6d8c3d72011-08-22 01:20:01 +00001428 }
cristyed231572011-07-14 02:18:59 +00001429 p+=GetPixelChannels(image);
1430 q+=GetPixelChannels(enhance_image);
cristy3ed852e2009-09-05 21:47:34 +00001431 }
1432 if (SyncCacheViewAuthenticPixels(enhance_view,exception) == MagickFalse)
1433 status=MagickFalse;
1434 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1435 {
1436 MagickBooleanType
1437 proceed;
1438
cristyb5d5f722009-11-04 03:03:49 +00001439#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00001440 #pragma omp critical (MagickCore_EnhanceImage)
1441#endif
1442 proceed=SetImageProgress(image,EnhanceImageTag,progress++,image->rows);
1443 if (proceed == MagickFalse)
1444 status=MagickFalse;
1445 }
1446 }
1447 enhance_view=DestroyCacheView(enhance_view);
1448 image_view=DestroyCacheView(image_view);
1449 return(enhance_image);
1450}
1451
1452/*
1453%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1454% %
1455% %
1456% %
1457% E q u a l i z e I m a g e %
1458% %
1459% %
1460% %
1461%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1462%
1463% EqualizeImage() applies a histogram equalization to the image.
1464%
1465% The format of the EqualizeImage method is:
1466%
cristy6d8c3d72011-08-22 01:20:01 +00001467% MagickBooleanType EqualizeImage(Image *image,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001468%
1469% A description of each parameter follows:
1470%
1471% o image: the image.
1472%
cristy6d8c3d72011-08-22 01:20:01 +00001473% o exception: return any errors or warnings in this structure.
cristy3ed852e2009-09-05 21:47:34 +00001474%
1475*/
cristy6d8c3d72011-08-22 01:20:01 +00001476MagickExport MagickBooleanType EqualizeImage(Image *image,
1477 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001478{
cristy3ed852e2009-09-05 21:47:34 +00001479#define EqualizeImageTag "Equalize/Image"
1480
cristyc4c8d132010-01-07 01:58:38 +00001481 CacheView
1482 *image_view;
1483
cristy3ed852e2009-09-05 21:47:34 +00001484 MagickBooleanType
1485 status;
1486
cristybb503372010-05-27 20:51:26 +00001487 MagickOffsetType
1488 progress;
1489
cristyf45fec72011-08-23 16:02:32 +00001490 MagickRealType
cristy5f95f4f2011-10-23 01:01:01 +00001491 black[CompositePixelChannel],
cristy3ed852e2009-09-05 21:47:34 +00001492 *equalize_map,
1493 *histogram,
cristy3ed852e2009-09-05 21:47:34 +00001494 *map,
cristy5f95f4f2011-10-23 01:01:01 +00001495 white[CompositePixelChannel];
cristy3ed852e2009-09-05 21:47:34 +00001496
cristybb503372010-05-27 20:51:26 +00001497 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001498 i;
1499
cristybb503372010-05-27 20:51:26 +00001500 ssize_t
1501 y;
1502
cristy3ed852e2009-09-05 21:47:34 +00001503 /*
1504 Allocate and initialize histogram arrays.
1505 */
1506 assert(image != (Image *) NULL);
1507 assert(image->signature == MagickSignature);
1508 if (image->debug != MagickFalse)
1509 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
cristyf45fec72011-08-23 16:02:32 +00001510 equalize_map=(MagickRealType *) AcquireQuantumMemory(MaxMap+1UL,
1511 GetPixelChannels(image)*sizeof(*equalize_map));
1512 histogram=(MagickRealType *) AcquireQuantumMemory(MaxMap+1UL,
1513 GetPixelChannels(image)*sizeof(*histogram));
1514 map=(MagickRealType *) AcquireQuantumMemory(MaxMap+1UL,
1515 GetPixelChannels(image)*sizeof(*map));
1516 if ((equalize_map == (MagickRealType *) NULL) ||
1517 (histogram == (MagickRealType *) NULL) ||
1518 (map == (MagickRealType *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00001519 {
cristyf45fec72011-08-23 16:02:32 +00001520 if (map != (MagickRealType *) NULL)
1521 map=(MagickRealType *) RelinquishMagickMemory(map);
1522 if (histogram != (MagickRealType *) NULL)
1523 histogram=(MagickRealType *) RelinquishMagickMemory(histogram);
1524 if (equalize_map != (MagickRealType *) NULL)
1525 equalize_map=(MagickRealType *) RelinquishMagickMemory(equalize_map);
cristy3ed852e2009-09-05 21:47:34 +00001526 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
1527 image->filename);
1528 }
1529 /*
1530 Form histogram.
1531 */
cristyf45fec72011-08-23 16:02:32 +00001532 status=MagickTrue;
1533 (void) ResetMagickMemory(histogram,0,(MaxMap+1)*GetPixelChannels(image)*
1534 sizeof(*histogram));
1535 image_view=AcquireCacheView(image);
cristybb503372010-05-27 20:51:26 +00001536 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001537 {
cristy4c08aed2011-07-01 19:47:50 +00001538 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00001539 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00001540
cristybb503372010-05-27 20:51:26 +00001541 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001542 x;
1543
cristyf45fec72011-08-23 16:02:32 +00001544 if (status == MagickFalse)
1545 continue;
1546 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00001547 if (p == (const Quantum *) NULL)
cristyf45fec72011-08-23 16:02:32 +00001548 {
1549 status=MagickFalse;
1550 continue;
1551 }
cristybb503372010-05-27 20:51:26 +00001552 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001553 {
cristyf45fec72011-08-23 16:02:32 +00001554 register ssize_t
1555 i;
1556
1557 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1558 histogram[GetPixelChannels(image)*ScaleQuantumToMap(p[i])+i]++;
cristyed231572011-07-14 02:18:59 +00001559 p+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00001560 }
1561 }
1562 /*
1563 Integrate the histogram to get the equalization map.
1564 */
cristyb5d5f722009-11-04 03:03:49 +00001565#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristye6178502011-12-23 17:02:29 +00001566 #pragma omp parallel for schedule(static,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001567#endif
cristyf45fec72011-08-23 16:02:32 +00001568 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
cristy3ed852e2009-09-05 21:47:34 +00001569 {
cristyf45fec72011-08-23 16:02:32 +00001570 MagickRealType
1571 intensity;
1572
1573 register ssize_t
1574 j;
1575
1576 intensity=0.0;
1577 for (j=0; j <= (ssize_t) MaxMap; j++)
1578 {
1579 intensity+=histogram[GetPixelChannels(image)*j+i];
1580 map[GetPixelChannels(image)*j+i]=intensity;
1581 }
cristy3ed852e2009-09-05 21:47:34 +00001582 }
cristyf45fec72011-08-23 16:02:32 +00001583 (void) ResetMagickMemory(equalize_map,0,(MaxMap+1)*GetPixelChannels(image)*
1584 sizeof(*equalize_map));
1585#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristye6178502011-12-23 17:02:29 +00001586 #pragma omp parallel for schedule(static,4) shared(progress,status)
cristyf45fec72011-08-23 16:02:32 +00001587#endif
1588 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1589 {
1590 register ssize_t
1591 j;
1592
1593 black[i]=map[i];
1594 white[i]=map[GetPixelChannels(image)*MaxMap+i];
1595 if (black[i] != white[i])
1596 for (j=0; j <= (ssize_t) MaxMap; j++)
1597 equalize_map[GetPixelChannels(image)*j+i]=(MagickRealType)
1598 ScaleMapToQuantum((MagickRealType) ((MaxMap*(map[
1599 GetPixelChannels(image)*j+i]-black[i]))/(white[i]-black[i])));
1600 }
1601 histogram=(MagickRealType *) RelinquishMagickMemory(histogram);
1602 map=(MagickRealType *) RelinquishMagickMemory(map);
cristy3ed852e2009-09-05 21:47:34 +00001603 if (image->storage_class == PseudoClass)
1604 {
cristyf54798b2011-11-21 18:38:23 +00001605 PixelChannel
1606 channel;
1607
cristyf45fec72011-08-23 16:02:32 +00001608 register ssize_t
1609 j;
1610
cristy3ed852e2009-09-05 21:47:34 +00001611 /*
1612 Equalize colormap.
1613 */
cristyb5d5f722009-11-04 03:03:49 +00001614#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristye6178502011-12-23 17:02:29 +00001615 #pragma omp parallel for schedule(static,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001616#endif
cristyf45fec72011-08-23 16:02:32 +00001617 for (j=0; j < (ssize_t) image->colors; j++)
cristy3ed852e2009-09-05 21:47:34 +00001618 {
cristyf54798b2011-11-21 18:38:23 +00001619 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristyf45fec72011-08-23 16:02:32 +00001620 {
cristyf54798b2011-11-21 18:38:23 +00001621 channel=GetPixelChannelMapChannel(image,RedPixelChannel);
1622 if (black[channel] != white[channel])
cristyda1f9c12011-10-02 21:39:49 +00001623 image->colormap[j].red=equalize_map[GetPixelChannels(image)*
cristyf54798b2011-11-21 18:38:23 +00001624 ScaleQuantumToMap(ClampToQuantum(image->colormap[j].red))]+
1625 channel;
cristyf45fec72011-08-23 16:02:32 +00001626 }
cristyf54798b2011-11-21 18:38:23 +00001627 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristyf45fec72011-08-23 16:02:32 +00001628 {
cristyf54798b2011-11-21 18:38:23 +00001629 channel=GetPixelChannelMapChannel(image,GreenPixelChannel);
1630 if (black[channel] != white[channel])
cristyda1f9c12011-10-02 21:39:49 +00001631 image->colormap[j].green=equalize_map[GetPixelChannels(image)*
cristyf54798b2011-11-21 18:38:23 +00001632 ScaleQuantumToMap(ClampToQuantum(image->colormap[j].green))]+
1633 channel;
cristyf45fec72011-08-23 16:02:32 +00001634 }
cristyf54798b2011-11-21 18:38:23 +00001635 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristyf45fec72011-08-23 16:02:32 +00001636 {
cristyf54798b2011-11-21 18:38:23 +00001637 channel=GetPixelChannelMapChannel(image,BluePixelChannel);
1638 if (black[channel] != white[channel])
cristyda1f9c12011-10-02 21:39:49 +00001639 image->colormap[j].blue=equalize_map[GetPixelChannels(image)*
cristyf54798b2011-11-21 18:38:23 +00001640 ScaleQuantumToMap(ClampToQuantum(image->colormap[j].blue))]+
1641 channel;
cristyf45fec72011-08-23 16:02:32 +00001642 }
cristyf54798b2011-11-21 18:38:23 +00001643 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristyf45fec72011-08-23 16:02:32 +00001644 {
cristyf54798b2011-11-21 18:38:23 +00001645 channel=GetPixelChannelMapChannel(image,AlphaPixelChannel);
1646 if (black[channel] != white[channel])
cristyda1f9c12011-10-02 21:39:49 +00001647 image->colormap[j].alpha=equalize_map[GetPixelChannels(image)*
cristyf54798b2011-11-21 18:38:23 +00001648 ScaleQuantumToMap(ClampToQuantum(image->colormap[j].alpha))]+
1649 channel;
cristyf45fec72011-08-23 16:02:32 +00001650 }
cristy3ed852e2009-09-05 21:47:34 +00001651 }
1652 }
1653 /*
1654 Equalize image.
1655 */
cristy3ed852e2009-09-05 21:47:34 +00001656 progress=0;
cristyb5d5f722009-11-04 03:03:49 +00001657#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristye6178502011-12-23 17:02:29 +00001658 #pragma omp parallel for schedule(static,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001659#endif
cristybb503372010-05-27 20:51:26 +00001660 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001661 {
cristy4c08aed2011-07-01 19:47:50 +00001662 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00001663 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001664
cristy8d4629b2010-08-30 17:59:46 +00001665 register ssize_t
1666 x;
1667
cristy3ed852e2009-09-05 21:47:34 +00001668 if (status == MagickFalse)
1669 continue;
1670 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristyacd2ed22011-08-30 01:44:23 +00001671 if (q == (Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001672 {
1673 status=MagickFalse;
1674 continue;
1675 }
cristybb503372010-05-27 20:51:26 +00001676 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001677 {
cristyf45fec72011-08-23 16:02:32 +00001678 register ssize_t
1679 i;
1680
1681 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1682 {
cristyabace412011-12-11 15:56:53 +00001683 PixelChannel
1684 channel;
1685
cristyf45fec72011-08-23 16:02:32 +00001686 PixelTrait
1687 traits;
1688
cristyabace412011-12-11 15:56:53 +00001689 channel=GetPixelChannelMapChannel(image,i);
1690 traits=GetPixelChannelMapTraits(image,channel);
cristy7c0a0a42011-08-23 17:57:25 +00001691 if (((traits & UpdatePixelTrait) != 0) && (black[i] != white[i]))
cristyf45fec72011-08-23 16:02:32 +00001692 q[i]=ClampToQuantum(equalize_map[GetPixelChannels(image)*
1693 ScaleQuantumToMap(q[i])+i]);
1694 }
cristyed231572011-07-14 02:18:59 +00001695 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00001696 }
1697 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
1698 status=MagickFalse;
1699 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1700 {
1701 MagickBooleanType
1702 proceed;
1703
cristyb5d5f722009-11-04 03:03:49 +00001704#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy50fbc382011-07-07 02:19:17 +00001705 #pragma omp critical (MagickCore_EqualizeImage)
cristy3ed852e2009-09-05 21:47:34 +00001706#endif
1707 proceed=SetImageProgress(image,EqualizeImageTag,progress++,image->rows);
1708 if (proceed == MagickFalse)
1709 status=MagickFalse;
1710 }
1711 }
1712 image_view=DestroyCacheView(image_view);
cristyf45fec72011-08-23 16:02:32 +00001713 equalize_map=(MagickRealType *) RelinquishMagickMemory(equalize_map);
cristy3ed852e2009-09-05 21:47:34 +00001714 return(status);
1715}
1716
1717/*
1718%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1719% %
1720% %
1721% %
1722% G a m m a I m a g e %
1723% %
1724% %
1725% %
1726%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1727%
1728% GammaImage() gamma-corrects a particular image channel. The same
1729% image viewed on different devices will have perceptual differences in the
1730% way the image's intensities are represented on the screen. Specify
1731% individual gamma levels for the red, green, and blue channels, or adjust
1732% all three with the gamma parameter. Values typically range from 0.8 to 2.3.
1733%
1734% You can also reduce the influence of a particular channel with a gamma
1735% value of 0.
1736%
1737% The format of the GammaImage method is:
1738%
cristyb3e7c6c2011-07-24 01:43:55 +00001739% MagickBooleanType GammaImage(Image *image,const double gamma,
1740% ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001741%
1742% A description of each parameter follows:
1743%
1744% o image: the image.
1745%
cristya6360142011-03-23 23:08:04 +00001746% o level: the image gamma as a string (e.g. 1.6,1.2,1.0).
1747%
cristy3ed852e2009-09-05 21:47:34 +00001748% o gamma: the image gamma.
1749%
1750*/
cristyb3e7c6c2011-07-24 01:43:55 +00001751MagickExport MagickBooleanType GammaImage(Image *image,const double gamma,
1752 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001753{
1754#define GammaCorrectImageTag "GammaCorrect/Image"
1755
cristyc4c8d132010-01-07 01:58:38 +00001756 CacheView
1757 *image_view;
1758
cristyda1f9c12011-10-02 21:39:49 +00001759 double
1760 *gamma_map;
1761
cristy3ed852e2009-09-05 21:47:34 +00001762 MagickBooleanType
1763 status;
1764
cristybb503372010-05-27 20:51:26 +00001765 MagickOffsetType
1766 progress;
1767
cristybb503372010-05-27 20:51:26 +00001768 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001769 i;
1770
cristybb503372010-05-27 20:51:26 +00001771 ssize_t
1772 y;
1773
cristy3ed852e2009-09-05 21:47:34 +00001774 /*
1775 Allocate and initialize gamma maps.
1776 */
1777 assert(image != (Image *) NULL);
1778 assert(image->signature == MagickSignature);
1779 if (image->debug != MagickFalse)
1780 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1781 if (gamma == 1.0)
1782 return(MagickTrue);
cristyda1f9c12011-10-02 21:39:49 +00001783 gamma_map=(double *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*gamma_map));
1784 if (gamma_map == (double *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001785 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
1786 image->filename);
1787 (void) ResetMagickMemory(gamma_map,0,(MaxMap+1)*sizeof(*gamma_map));
1788 if (gamma != 0.0)
cristyd476a8e2011-07-23 16:13:22 +00001789#if defined(MAGICKCORE_OPENMP_SUPPORT) && (MaxMap > 256)
1790 #pragma omp parallel for
cristy3ed852e2009-09-05 21:47:34 +00001791#endif
cristybb503372010-05-27 20:51:26 +00001792 for (i=0; i <= (ssize_t) MaxMap; i++)
cristyda1f9c12011-10-02 21:39:49 +00001793 gamma_map[i]=(MagickRealType) ScaleMapToQuantum((
1794 MagickRealType) (MaxMap*pow((double) i/MaxMap,1.0/gamma)));
cristy3ed852e2009-09-05 21:47:34 +00001795 if (image->storage_class == PseudoClass)
1796 {
1797 /*
1798 Gamma-correct colormap.
1799 */
cristyb5d5f722009-11-04 03:03:49 +00001800#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristye6178502011-12-23 17:02:29 +00001801 #pragma omp parallel for schedule(static,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001802#endif
cristybb503372010-05-27 20:51:26 +00001803 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00001804 {
cristyed231572011-07-14 02:18:59 +00001805 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001806 image->colormap[i].red=gamma_map[
cristyda1f9c12011-10-02 21:39:49 +00001807 ScaleQuantumToMap(ClampToQuantum(image->colormap[i].red))];
cristyed231572011-07-14 02:18:59 +00001808 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001809 image->colormap[i].green=gamma_map[
cristyda1f9c12011-10-02 21:39:49 +00001810 ScaleQuantumToMap(ClampToQuantum(image->colormap[i].green))];
cristyed231572011-07-14 02:18:59 +00001811 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001812 image->colormap[i].blue=gamma_map[
cristyda1f9c12011-10-02 21:39:49 +00001813 ScaleQuantumToMap(ClampToQuantum(image->colormap[i].blue))];
cristyed231572011-07-14 02:18:59 +00001814 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001815 image->colormap[i].alpha=gamma_map[
cristyda1f9c12011-10-02 21:39:49 +00001816 ScaleQuantumToMap(ClampToQuantum(image->colormap[i].alpha))];
cristy3ed852e2009-09-05 21:47:34 +00001817 }
1818 }
1819 /*
1820 Gamma-correct image.
1821 */
1822 status=MagickTrue;
1823 progress=0;
cristy3ed852e2009-09-05 21:47:34 +00001824 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00001825#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristye6178502011-12-23 17:02:29 +00001826 #pragma omp parallel for schedule(static,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001827#endif
cristybb503372010-05-27 20:51:26 +00001828 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001829 {
cristy4c08aed2011-07-01 19:47:50 +00001830 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00001831 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001832
cristy8d4629b2010-08-30 17:59:46 +00001833 register ssize_t
1834 x;
1835
cristy3ed852e2009-09-05 21:47:34 +00001836 if (status == MagickFalse)
1837 continue;
1838 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristyacd2ed22011-08-30 01:44:23 +00001839 if (q == (Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001840 {
1841 status=MagickFalse;
1842 continue;
1843 }
cristybb503372010-05-27 20:51:26 +00001844 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001845 {
cristyd476a8e2011-07-23 16:13:22 +00001846 register ssize_t
1847 i;
1848
cristya30d9ba2011-07-23 21:00:48 +00001849 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
cristyd476a8e2011-07-23 16:13:22 +00001850 {
cristyabace412011-12-11 15:56:53 +00001851 PixelChannel
1852 channel;
1853
cristyd476a8e2011-07-23 16:13:22 +00001854 PixelTrait
1855 traits;
1856
cristyabace412011-12-11 15:56:53 +00001857 channel=GetPixelChannelMapChannel(image,i);
1858 traits=GetPixelChannelMapTraits(image,channel);
cristyd476a8e2011-07-23 16:13:22 +00001859 if ((traits & UpdatePixelTrait) != 0)
cristyda1f9c12011-10-02 21:39:49 +00001860 q[i]=ClampToQuantum(gamma_map[ScaleQuantumToMap(q[i])]);
cristyd476a8e2011-07-23 16:13:22 +00001861 }
cristya30d9ba2011-07-23 21:00:48 +00001862 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00001863 }
cristy3ed852e2009-09-05 21:47:34 +00001864 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
1865 status=MagickFalse;
1866 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1867 {
1868 MagickBooleanType
1869 proceed;
1870
cristyb5d5f722009-11-04 03:03:49 +00001871#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy50fbc382011-07-07 02:19:17 +00001872 #pragma omp critical (MagickCore_GammaImage)
cristy3ed852e2009-09-05 21:47:34 +00001873#endif
1874 proceed=SetImageProgress(image,GammaCorrectImageTag,progress++,
1875 image->rows);
1876 if (proceed == MagickFalse)
1877 status=MagickFalse;
1878 }
1879 }
1880 image_view=DestroyCacheView(image_view);
cristyda1f9c12011-10-02 21:39:49 +00001881 gamma_map=(double *) RelinquishMagickMemory(gamma_map);
cristy3ed852e2009-09-05 21:47:34 +00001882 if (image->gamma != 0.0)
1883 image->gamma*=gamma;
1884 return(status);
1885}
1886
1887/*
1888%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1889% %
1890% %
1891% %
1892% H a l d C l u t I m a g e %
1893% %
1894% %
1895% %
1896%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1897%
1898% HaldClutImage() applies a Hald color lookup table to the image. A Hald
1899% color lookup table is a 3-dimensional color cube mapped to 2 dimensions.
1900% Create it with the HALD coder. You can apply any color transformation to
1901% the Hald image and then use this method to apply the transform to the
1902% image.
1903%
1904% The format of the HaldClutImage method is:
1905%
cristy7c0a0a42011-08-23 17:57:25 +00001906% MagickBooleanType HaldClutImage(Image *image,Image *hald_image,
1907% ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001908%
1909% A description of each parameter follows:
1910%
1911% o image: the image, which is replaced by indexed CLUT values
1912%
1913% o hald_image: the color lookup table image for replacement color values.
1914%
cristy7c0a0a42011-08-23 17:57:25 +00001915% o exception: return any errors or warnings in this structure.
1916%
cristy3ed852e2009-09-05 21:47:34 +00001917*/
1918
1919static inline size_t MagickMin(const size_t x,const size_t y)
1920{
1921 if (x < y)
1922 return(x);
1923 return(y);
1924}
1925
1926MagickExport MagickBooleanType HaldClutImage(Image *image,
cristy7c0a0a42011-08-23 17:57:25 +00001927 const Image *hald_image,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001928{
cristy3ed852e2009-09-05 21:47:34 +00001929#define HaldClutImageTag "Clut/Image"
1930
1931 typedef struct _HaldInfo
1932 {
1933 MagickRealType
1934 x,
1935 y,
1936 z;
1937 } HaldInfo;
1938
cristyfa112112010-01-04 17:48:07 +00001939 CacheView
cristyd551fbc2011-03-31 18:07:46 +00001940 *hald_view,
cristyfa112112010-01-04 17:48:07 +00001941 *image_view;
1942
cristy3ed852e2009-09-05 21:47:34 +00001943 double
1944 width;
1945
cristy3ed852e2009-09-05 21:47:34 +00001946 MagickBooleanType
1947 status;
1948
cristybb503372010-05-27 20:51:26 +00001949 MagickOffsetType
1950 progress;
1951
cristy4c08aed2011-07-01 19:47:50 +00001952 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00001953 zero;
1954
cristy3ed852e2009-09-05 21:47:34 +00001955 size_t
1956 cube_size,
1957 length,
1958 level;
1959
cristybb503372010-05-27 20:51:26 +00001960 ssize_t
1961 y;
1962
cristy3ed852e2009-09-05 21:47:34 +00001963 assert(image != (Image *) NULL);
1964 assert(image->signature == MagickSignature);
1965 if (image->debug != MagickFalse)
1966 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1967 assert(hald_image != (Image *) NULL);
1968 assert(hald_image->signature == MagickSignature);
cristy574cc262011-08-05 01:23:58 +00001969 if (SetImageStorageClass(image,DirectClass,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00001970 return(MagickFalse);
1971 if (image->matte == MagickFalse)
cristy63240882011-08-05 19:05:27 +00001972 (void) SetImageAlphaChannel(image,OpaqueAlphaChannel,exception);
cristy3ed852e2009-09-05 21:47:34 +00001973 /*
1974 Hald clut image.
1975 */
1976 status=MagickTrue;
1977 progress=0;
1978 length=MagickMin(hald_image->columns,hald_image->rows);
1979 for (level=2; (level*level*level) < length; level++) ;
1980 level*=level;
1981 cube_size=level*level;
1982 width=(double) hald_image->columns;
cristy4c08aed2011-07-01 19:47:50 +00001983 GetPixelInfo(hald_image,&zero);
cristy3ed852e2009-09-05 21:47:34 +00001984 image_view=AcquireCacheView(image);
cristyd551fbc2011-03-31 18:07:46 +00001985 hald_view=AcquireCacheView(hald_image);
cristyb5d5f722009-11-04 03:03:49 +00001986#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristye6178502011-12-23 17:02:29 +00001987 #pragma omp parallel for schedule(static,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001988#endif
cristybb503372010-05-27 20:51:26 +00001989 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001990 {
cristy4c08aed2011-07-01 19:47:50 +00001991 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00001992 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001993
cristy8d4629b2010-08-30 17:59:46 +00001994 register ssize_t
1995 x;
1996
cristy3ed852e2009-09-05 21:47:34 +00001997 if (status == MagickFalse)
1998 continue;
1999 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristyacd2ed22011-08-30 01:44:23 +00002000 if (q == (Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00002001 {
2002 status=MagickFalse;
2003 continue;
2004 }
cristybb503372010-05-27 20:51:26 +00002005 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002006 {
cristy7c0a0a42011-08-23 17:57:25 +00002007 double
2008 offset;
2009
2010 HaldInfo
2011 point;
2012
2013 PixelInfo
2014 pixel,
2015 pixel1,
2016 pixel2,
2017 pixel3,
2018 pixel4;
2019
cristy4c08aed2011-07-01 19:47:50 +00002020 point.x=QuantumScale*(level-1.0)*GetPixelRed(image,q);
2021 point.y=QuantumScale*(level-1.0)*GetPixelGreen(image,q);
2022 point.z=QuantumScale*(level-1.0)*GetPixelBlue(image,q);
cristy3ed852e2009-09-05 21:47:34 +00002023 offset=point.x+level*floor(point.y)+cube_size*floor(point.z);
2024 point.x-=floor(point.x);
2025 point.y-=floor(point.y);
2026 point.z-=floor(point.z);
cristy7c0a0a42011-08-23 17:57:25 +00002027 pixel1=zero;
2028 (void) InterpolatePixelInfo(image,hald_view,image->interpolate,
2029 fmod(offset,width),floor(offset/width),&pixel1,exception);
2030 pixel2=zero;
2031 (void) InterpolatePixelInfo(image,hald_view,image->interpolate,
2032 fmod(offset+level,width),floor((offset+level)/width),&pixel2,exception);
2033 pixel3=zero;
2034 CompositePixelInfoAreaBlend(&pixel1,pixel1.alpha,&pixel2,pixel2.alpha,
2035 point.y,&pixel3);
cristy3ed852e2009-09-05 21:47:34 +00002036 offset+=cube_size;
cristy7c0a0a42011-08-23 17:57:25 +00002037 (void) InterpolatePixelInfo(image,hald_view,image->interpolate,
2038 fmod(offset,width),floor(offset/width),&pixel1,exception);
2039 (void) InterpolatePixelInfo(image,hald_view,image->interpolate,
2040 fmod(offset+level,width),floor((offset+level)/width),&pixel2,exception);
2041 pixel4=zero;
2042 CompositePixelInfoAreaBlend(&pixel1,pixel1.alpha,&pixel2,pixel2.alpha,
2043 point.y,&pixel4);
2044 pixel=zero;
2045 CompositePixelInfoAreaBlend(&pixel3,pixel3.alpha,&pixel4,pixel4.alpha,
2046 point.z,&pixel);
cristyed231572011-07-14 02:18:59 +00002047 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristyf45fec72011-08-23 16:02:32 +00002048 SetPixelRed(image,ClampToQuantum(pixel.red),q);
cristyed231572011-07-14 02:18:59 +00002049 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristyf45fec72011-08-23 16:02:32 +00002050 SetPixelGreen(image,ClampToQuantum(pixel.green),q);
cristyed231572011-07-14 02:18:59 +00002051 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristyf45fec72011-08-23 16:02:32 +00002052 SetPixelBlue(image,ClampToQuantum(pixel.blue),q);
cristyed231572011-07-14 02:18:59 +00002053 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00002054 (image->colorspace == CMYKColorspace))
cristyf45fec72011-08-23 16:02:32 +00002055 SetPixelBlack(image,ClampToQuantum(pixel.black),q);
2056 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) &&
2057 (image->matte != MagickFalse))
2058 SetPixelAlpha(image,ClampToQuantum(pixel.alpha),q);
cristyed231572011-07-14 02:18:59 +00002059 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00002060 }
2061 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2062 status=MagickFalse;
2063 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2064 {
2065 MagickBooleanType
2066 proceed;
2067
cristyb5d5f722009-11-04 03:03:49 +00002068#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf89cb1d2011-07-07 01:24:37 +00002069 #pragma omp critical (MagickCore_HaldClutImage)
cristy3ed852e2009-09-05 21:47:34 +00002070#endif
2071 proceed=SetImageProgress(image,HaldClutImageTag,progress++,image->rows);
2072 if (proceed == MagickFalse)
2073 status=MagickFalse;
2074 }
2075 }
cristyd551fbc2011-03-31 18:07:46 +00002076 hald_view=DestroyCacheView(hald_view);
cristy3ed852e2009-09-05 21:47:34 +00002077 image_view=DestroyCacheView(image_view);
cristy3ed852e2009-09-05 21:47:34 +00002078 return(status);
2079}
2080
2081/*
2082%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2083% %
2084% %
2085% %
2086% L e v e l I m a g e %
2087% %
2088% %
2089% %
2090%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2091%
2092% LevelImage() adjusts the levels of a particular image channel by
2093% scaling the colors falling between specified white and black points to
2094% the full available quantum range.
2095%
2096% The parameters provided represent the black, and white points. The black
2097% point specifies the darkest color in the image. Colors darker than the
2098% black point are set to zero. White point specifies the lightest color in
2099% the image. Colors brighter than the white point are set to the maximum
2100% quantum value.
2101%
2102% If a '!' flag is given, map black and white colors to the given levels
2103% rather than mapping those levels to black and white. See
cristy50fbc382011-07-07 02:19:17 +00002104% LevelizeImage() below.
cristy3ed852e2009-09-05 21:47:34 +00002105%
2106% Gamma specifies a gamma correction to apply to the image.
2107%
2108% The format of the LevelImage method is:
2109%
cristy01e9afd2011-08-10 17:38:41 +00002110% MagickBooleanType LevelImage(Image *image,const double black_point,
2111% const double white_point,const double gamma,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002112%
2113% A description of each parameter follows:
2114%
2115% o image: the image.
2116%
cristy01e9afd2011-08-10 17:38:41 +00002117% o black_point: The level to map zero (black) to.
2118%
2119% o white_point: The level to map QuantumRange (white) to.
2120%
2121% o exception: return any errors or warnings in this structure.
cristy3ed852e2009-09-05 21:47:34 +00002122%
2123*/
cristy780e9ef2011-12-18 23:33:50 +00002124
cristyb8b0d162011-12-18 23:41:28 +00002125static inline MagickRealType LevelPixel(const double black_point,
cristy780e9ef2011-12-18 23:33:50 +00002126 const double white_point,const double gamma,const MagickRealType pixel)
2127{
2128 double
2129 level_pixel,
2130 scale;
2131
2132 if (pixel < black_point)
2133 return(0.0);
2134 if (pixel > white_point)
cristy0113ca22011-12-19 01:16:22 +00002135 return((MagickRealType) QuantumRange);
cristy780e9ef2011-12-18 23:33:50 +00002136 scale=(white_point != black_point) ? 1.0/(white_point-black_point) : 1.0;
2137 level_pixel=(MagickRealType) QuantumRange*pow(scale*((double) pixel-
2138 black_point),1.0/gamma);
2139 return(level_pixel);
2140}
2141
cristy7c0a0a42011-08-23 17:57:25 +00002142MagickExport MagickBooleanType LevelImage(Image *image,const double black_point,
2143 const double white_point,const double gamma,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002144{
2145#define LevelImageTag "Level/Image"
cristy3ed852e2009-09-05 21:47:34 +00002146
cristyc4c8d132010-01-07 01:58:38 +00002147 CacheView
2148 *image_view;
2149
cristy3ed852e2009-09-05 21:47:34 +00002150 MagickBooleanType
2151 status;
2152
cristybb503372010-05-27 20:51:26 +00002153 MagickOffsetType
2154 progress;
2155
cristy8d4629b2010-08-30 17:59:46 +00002156 register ssize_t
2157 i;
2158
cristybb503372010-05-27 20:51:26 +00002159 ssize_t
2160 y;
2161
cristy3ed852e2009-09-05 21:47:34 +00002162 /*
2163 Allocate and initialize levels map.
2164 */
2165 assert(image != (Image *) NULL);
2166 assert(image->signature == MagickSignature);
2167 if (image->debug != MagickFalse)
2168 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2169 if (image->storage_class == PseudoClass)
cristyb5d5f722009-11-04 03:03:49 +00002170#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristye6178502011-12-23 17:02:29 +00002171 #pragma omp parallel for schedule(static,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002172#endif
cristybb503372010-05-27 20:51:26 +00002173 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00002174 {
2175 /*
2176 Level colormap.
2177 */
cristyed231572011-07-14 02:18:59 +00002178 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy780e9ef2011-12-18 23:33:50 +00002179 image->colormap[i].red=(double) ClampToQuantum(LevelPixel(black_point,
2180 white_point,gamma,image->colormap[i].red));
cristyed231572011-07-14 02:18:59 +00002181 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy780e9ef2011-12-18 23:33:50 +00002182 image->colormap[i].green=(double) ClampToQuantum(LevelPixel(black_point,
2183 white_point,gamma,image->colormap[i].green));
cristyed231572011-07-14 02:18:59 +00002184 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy780e9ef2011-12-18 23:33:50 +00002185 image->colormap[i].blue=(double) ClampToQuantum(LevelPixel(black_point,
2186 white_point,gamma,image->colormap[i].blue));
cristyed231572011-07-14 02:18:59 +00002187 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy780e9ef2011-12-18 23:33:50 +00002188 image->colormap[i].alpha=(double) ClampToQuantum(LevelPixel(black_point,
2189 white_point,gamma,image->colormap[i].alpha));
cristy3ed852e2009-09-05 21:47:34 +00002190 }
2191 /*
2192 Level image.
2193 */
2194 status=MagickTrue;
2195 progress=0;
cristy3ed852e2009-09-05 21:47:34 +00002196 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00002197#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristye6178502011-12-23 17:02:29 +00002198 #pragma omp parallel for schedule(static,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002199#endif
cristybb503372010-05-27 20:51:26 +00002200 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002201 {
cristy4c08aed2011-07-01 19:47:50 +00002202 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00002203 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002204
cristy8d4629b2010-08-30 17:59:46 +00002205 register ssize_t
2206 x;
2207
cristy3ed852e2009-09-05 21:47:34 +00002208 if (status == MagickFalse)
2209 continue;
2210 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristyacd2ed22011-08-30 01:44:23 +00002211 if (q == (Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00002212 {
2213 status=MagickFalse;
2214 continue;
2215 }
cristybb503372010-05-27 20:51:26 +00002216 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002217 {
cristy7c0a0a42011-08-23 17:57:25 +00002218 register ssize_t
2219 i;
2220
2221 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
2222 {
cristyabace412011-12-11 15:56:53 +00002223 PixelChannel
2224 channel;
2225
cristy7c0a0a42011-08-23 17:57:25 +00002226 PixelTrait
2227 traits;
2228
cristyabace412011-12-11 15:56:53 +00002229 channel=GetPixelChannelMapChannel(image,i);
2230 traits=GetPixelChannelMapTraits(image,channel);
cristyb8b0d162011-12-18 23:41:28 +00002231 if (traits == UndefinedPixelTrait)
cristy7c0a0a42011-08-23 17:57:25 +00002232 continue;
cristy780e9ef2011-12-18 23:33:50 +00002233 q[i]=ClampToQuantum(LevelPixel(black_point,white_point,gamma,
2234 (MagickRealType) q[i]));
cristy7c0a0a42011-08-23 17:57:25 +00002235 }
cristyed231572011-07-14 02:18:59 +00002236 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00002237 }
2238 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2239 status=MagickFalse;
2240 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2241 {
2242 MagickBooleanType
2243 proceed;
2244
cristyb5d5f722009-11-04 03:03:49 +00002245#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf89cb1d2011-07-07 01:24:37 +00002246 #pragma omp critical (MagickCore_LevelImage)
cristy3ed852e2009-09-05 21:47:34 +00002247#endif
2248 proceed=SetImageProgress(image,LevelImageTag,progress++,image->rows);
2249 if (proceed == MagickFalse)
2250 status=MagickFalse;
2251 }
2252 }
2253 image_view=DestroyCacheView(image_view);
2254 return(status);
2255}
2256
2257/*
2258%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2259% %
2260% %
2261% %
cristy33bd5152011-08-24 01:42:24 +00002262% L e v e l i z e I m a g e %
cristy3ed852e2009-09-05 21:47:34 +00002263% %
2264% %
2265% %
2266%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2267%
cristy50fbc382011-07-07 02:19:17 +00002268% LevelizeImage() applies the reversed LevelImage() operation to just
cristy3ed852e2009-09-05 21:47:34 +00002269% the specific channels specified. It compresses the full range of color
2270% values, so that they lie between the given black and white points. Gamma is
2271% applied before the values are mapped.
2272%
cristy50fbc382011-07-07 02:19:17 +00002273% LevelizeImage() can be called with by using a +level command line
cristy3ed852e2009-09-05 21:47:34 +00002274% API option, or using a '!' on a -level or LevelImage() geometry string.
2275%
2276% It can be used for example de-contrast a greyscale image to the exact
2277% levels specified. Or by using specific levels for each channel of an image
2278% you can convert a gray-scale image to any linear color gradient, according
2279% to those levels.
2280%
cristy50fbc382011-07-07 02:19:17 +00002281% The format of the LevelizeImage method is:
cristy3ed852e2009-09-05 21:47:34 +00002282%
cristy50fbc382011-07-07 02:19:17 +00002283% MagickBooleanType LevelizeImage(Image *image,const double black_point,
cristy7c0a0a42011-08-23 17:57:25 +00002284% const double white_point,const double gamma,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002285%
2286% A description of each parameter follows:
2287%
2288% o image: the image.
2289%
cristy3ed852e2009-09-05 21:47:34 +00002290% o black_point: The level to map zero (black) to.
2291%
cristy01e9afd2011-08-10 17:38:41 +00002292% o white_point: The level to map QuantumRange (white) to.
cristy3ed852e2009-09-05 21:47:34 +00002293%
2294% o gamma: adjust gamma by this factor before mapping values.
2295%
cristy7c0a0a42011-08-23 17:57:25 +00002296% o exception: return any errors or warnings in this structure.
2297%
cristy3ed852e2009-09-05 21:47:34 +00002298*/
cristyd1a2c0f2011-02-09 14:14:50 +00002299MagickExport MagickBooleanType LevelizeImage(Image *image,
cristy7c0a0a42011-08-23 17:57:25 +00002300 const double black_point,const double white_point,const double gamma,
2301 ExceptionInfo *exception)
cristyd1a2c0f2011-02-09 14:14:50 +00002302{
cristy3ed852e2009-09-05 21:47:34 +00002303#define LevelizeImageTag "Levelize/Image"
cristyce70c172010-01-07 17:15:30 +00002304#define LevelizeValue(x) (ClampToQuantum(((MagickRealType) \
cristy50fbc382011-07-07 02:19:17 +00002305 pow((double) (QuantumScale*(x)),1.0/gamma))*(white_point-black_point)+ \
cristy3ed852e2009-09-05 21:47:34 +00002306 black_point))
2307
cristyc4c8d132010-01-07 01:58:38 +00002308 CacheView
2309 *image_view;
2310
cristy3ed852e2009-09-05 21:47:34 +00002311 MagickBooleanType
2312 status;
2313
cristybb503372010-05-27 20:51:26 +00002314 MagickOffsetType
2315 progress;
2316
2317 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002318 i;
2319
cristybb503372010-05-27 20:51:26 +00002320 ssize_t
2321 y;
2322
cristy3ed852e2009-09-05 21:47:34 +00002323 /*
2324 Allocate and initialize levels map.
2325 */
2326 assert(image != (Image *) NULL);
2327 assert(image->signature == MagickSignature);
2328 if (image->debug != MagickFalse)
2329 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2330 if (image->storage_class == PseudoClass)
cristyb5d5f722009-11-04 03:03:49 +00002331#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristya6d7a9b2012-01-18 20:04:48 +00002332 #pragma omp parallel for schedule(static,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002333#endif
cristybb503372010-05-27 20:51:26 +00002334 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00002335 {
2336 /*
2337 Level colormap.
2338 */
cristyed231572011-07-14 02:18:59 +00002339 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristyda1f9c12011-10-02 21:39:49 +00002340 image->colormap[i].red=(double) LevelizeValue(
2341 image->colormap[i].red);
cristyed231572011-07-14 02:18:59 +00002342 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristyda1f9c12011-10-02 21:39:49 +00002343 image->colormap[i].green=(double) LevelizeValue(
2344 image->colormap[i].green);
cristyed231572011-07-14 02:18:59 +00002345 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristyda1f9c12011-10-02 21:39:49 +00002346 image->colormap[i].blue=(double) LevelizeValue(
2347 image->colormap[i].blue);
cristyed231572011-07-14 02:18:59 +00002348 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristyda1f9c12011-10-02 21:39:49 +00002349 image->colormap[i].alpha=(double) LevelizeValue(
2350 image->colormap[i].alpha);
cristy3ed852e2009-09-05 21:47:34 +00002351 }
2352 /*
2353 Level image.
2354 */
2355 status=MagickTrue;
2356 progress=0;
cristy3ed852e2009-09-05 21:47:34 +00002357 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00002358#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristye6178502011-12-23 17:02:29 +00002359 #pragma omp parallel for schedule(static,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002360#endif
cristybb503372010-05-27 20:51:26 +00002361 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002362 {
cristy4c08aed2011-07-01 19:47:50 +00002363 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00002364 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002365
cristy8d4629b2010-08-30 17:59:46 +00002366 register ssize_t
2367 x;
2368
cristy3ed852e2009-09-05 21:47:34 +00002369 if (status == MagickFalse)
2370 continue;
2371 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristyacd2ed22011-08-30 01:44:23 +00002372 if (q == (Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00002373 {
2374 status=MagickFalse;
2375 continue;
2376 }
cristybb503372010-05-27 20:51:26 +00002377 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002378 {
cristy7c0a0a42011-08-23 17:57:25 +00002379 register ssize_t
2380 i;
2381
2382 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
2383 {
cristyabace412011-12-11 15:56:53 +00002384 PixelChannel
2385 channel;
2386
cristy7c0a0a42011-08-23 17:57:25 +00002387 PixelTrait
2388 traits;
2389
cristyabace412011-12-11 15:56:53 +00002390 channel=GetPixelChannelMapChannel(image,i);
2391 traits=GetPixelChannelMapTraits(image,channel);
cristyb8b0d162011-12-18 23:41:28 +00002392 if (traits == UndefinedPixelTrait)
2393 continue;
cristy780e9ef2011-12-18 23:33:50 +00002394 q[i]=LevelizeValue(q[i]);
cristy7c0a0a42011-08-23 17:57:25 +00002395 }
cristyed231572011-07-14 02:18:59 +00002396 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00002397 }
2398 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2399 status=MagickFalse;
2400 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2401 {
2402 MagickBooleanType
2403 proceed;
2404
cristyb5d5f722009-11-04 03:03:49 +00002405#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy50fbc382011-07-07 02:19:17 +00002406 #pragma omp critical (MagickCore_LevelizeImage)
cristy3ed852e2009-09-05 21:47:34 +00002407#endif
2408 proceed=SetImageProgress(image,LevelizeImageTag,progress++,image->rows);
2409 if (proceed == MagickFalse)
2410 status=MagickFalse;
2411 }
2412 }
cristy8d4629b2010-08-30 17:59:46 +00002413 image_view=DestroyCacheView(image_view);
cristy3ed852e2009-09-05 21:47:34 +00002414 return(status);
2415}
2416
2417/*
2418%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2419% %
2420% %
2421% %
2422% L e v e l I m a g e C o l o r s %
2423% %
2424% %
2425% %
2426%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2427%
cristy490408a2011-07-07 14:42:05 +00002428% LevelImageColors() maps the given color to "black" and "white" values,
cristyee0f8d72009-09-19 00:58:29 +00002429% linearly spreading out the colors, and level values on a channel by channel
2430% bases, as per LevelImage(). The given colors allows you to specify
glennrp1e7f7bc2011-03-02 19:25:28 +00002431% different level ranges for each of the color channels separately.
cristy3ed852e2009-09-05 21:47:34 +00002432%
2433% If the boolean 'invert' is set true the image values will modifyed in the
2434% reverse direction. That is any existing "black" and "white" colors in the
2435% image will become the color values given, with all other values compressed
2436% appropriatally. This effectivally maps a greyscale gradient into the given
2437% color gradient.
2438%
cristy490408a2011-07-07 14:42:05 +00002439% The format of the LevelImageColors method is:
cristy3ed852e2009-09-05 21:47:34 +00002440%
cristy490408a2011-07-07 14:42:05 +00002441% MagickBooleanType LevelImageColors(Image *image,
2442% const PixelInfo *black_color,const PixelInfo *white_color,
cristy7c0a0a42011-08-23 17:57:25 +00002443% const MagickBooleanType invert,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002444%
2445% A description of each parameter follows:
2446%
2447% o image: the image.
2448%
cristy3ed852e2009-09-05 21:47:34 +00002449% o black_color: The color to map black to/from
2450%
2451% o white_point: The color to map white to/from
2452%
2453% o invert: if true map the colors (levelize), rather than from (level)
2454%
cristy7c0a0a42011-08-23 17:57:25 +00002455% o exception: return any errors or warnings in this structure.
2456%
cristy3ed852e2009-09-05 21:47:34 +00002457*/
cristy490408a2011-07-07 14:42:05 +00002458MagickExport MagickBooleanType LevelImageColors(Image *image,
cristy4c08aed2011-07-01 19:47:50 +00002459 const PixelInfo *black_color,const PixelInfo *white_color,
cristy7c0a0a42011-08-23 17:57:25 +00002460 const MagickBooleanType invert,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002461{
cristybd5a96c2011-08-21 00:04:26 +00002462 ChannelType
2463 channel_mask;
2464
cristy3ed852e2009-09-05 21:47:34 +00002465 MagickStatusType
2466 status;
2467
2468 /*
2469 Allocate and initialize levels map.
2470 */
2471 assert(image != (Image *) NULL);
2472 assert(image->signature == MagickSignature);
2473 if (image->debug != MagickFalse)
2474 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2475 status=MagickFalse;
2476 if (invert == MagickFalse)
2477 {
cristyed231572011-07-14 02:18:59 +00002478 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristyf89cb1d2011-07-07 01:24:37 +00002479 {
cristybd5a96c2011-08-21 00:04:26 +00002480 channel_mask=SetPixelChannelMask(image,RedChannel);
cristy01e9afd2011-08-10 17:38:41 +00002481 status|=LevelImage(image,black_color->red,white_color->red,1.0,
cristy7c0a0a42011-08-23 17:57:25 +00002482 exception);
cristybd5a96c2011-08-21 00:04:26 +00002483 (void) SetPixelChannelMask(image,channel_mask);
cristyf89cb1d2011-07-07 01:24:37 +00002484 }
cristyed231572011-07-14 02:18:59 +00002485 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristyf89cb1d2011-07-07 01:24:37 +00002486 {
cristybd5a96c2011-08-21 00:04:26 +00002487 channel_mask=SetPixelChannelMask(image,GreenChannel);
cristy01e9afd2011-08-10 17:38:41 +00002488 status|=LevelImage(image,black_color->green,white_color->green,1.0,
cristy7c0a0a42011-08-23 17:57:25 +00002489 exception);
cristybd5a96c2011-08-21 00:04:26 +00002490 (void) SetPixelChannelMask(image,channel_mask);
cristyf89cb1d2011-07-07 01:24:37 +00002491 }
cristyed231572011-07-14 02:18:59 +00002492 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristyf89cb1d2011-07-07 01:24:37 +00002493 {
cristybd5a96c2011-08-21 00:04:26 +00002494 channel_mask=SetPixelChannelMask(image,BlueChannel);
cristy01e9afd2011-08-10 17:38:41 +00002495 status|=LevelImage(image,black_color->blue,white_color->blue,1.0,
cristy7c0a0a42011-08-23 17:57:25 +00002496 exception);
cristybd5a96c2011-08-21 00:04:26 +00002497 (void) SetPixelChannelMask(image,channel_mask);
cristyf89cb1d2011-07-07 01:24:37 +00002498 }
cristyed231572011-07-14 02:18:59 +00002499 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +00002500 (image->colorspace == CMYKColorspace))
cristyf89cb1d2011-07-07 01:24:37 +00002501 {
cristybd5a96c2011-08-21 00:04:26 +00002502 channel_mask=SetPixelChannelMask(image,BlackChannel);
cristy01e9afd2011-08-10 17:38:41 +00002503 status|=LevelImage(image,black_color->black,white_color->black,1.0,
cristy7c0a0a42011-08-23 17:57:25 +00002504 exception);
cristybd5a96c2011-08-21 00:04:26 +00002505 (void) SetPixelChannelMask(image,channel_mask);
cristyf89cb1d2011-07-07 01:24:37 +00002506 }
cristyed231572011-07-14 02:18:59 +00002507 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00002508 (image->matte == MagickTrue))
cristyf89cb1d2011-07-07 01:24:37 +00002509 {
cristybd5a96c2011-08-21 00:04:26 +00002510 channel_mask=SetPixelChannelMask(image,AlphaChannel);
cristy01e9afd2011-08-10 17:38:41 +00002511 status|=LevelImage(image,black_color->alpha,white_color->alpha,1.0,
cristy7c0a0a42011-08-23 17:57:25 +00002512 exception);
cristybd5a96c2011-08-21 00:04:26 +00002513 (void) SetPixelChannelMask(image,channel_mask);
cristyf89cb1d2011-07-07 01:24:37 +00002514 }
cristy3ed852e2009-09-05 21:47:34 +00002515 }
2516 else
2517 {
cristyed231572011-07-14 02:18:59 +00002518 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristyf89cb1d2011-07-07 01:24:37 +00002519 {
cristybd5a96c2011-08-21 00:04:26 +00002520 channel_mask=SetPixelChannelMask(image,RedChannel);
cristy7c0a0a42011-08-23 17:57:25 +00002521 status|=LevelizeImage(image,black_color->red,white_color->red,1.0,
2522 exception);
cristybd5a96c2011-08-21 00:04:26 +00002523 (void) SetPixelChannelMask(image,channel_mask);
cristyf89cb1d2011-07-07 01:24:37 +00002524 }
cristyed231572011-07-14 02:18:59 +00002525 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristyf89cb1d2011-07-07 01:24:37 +00002526 {
cristybd5a96c2011-08-21 00:04:26 +00002527 channel_mask=SetPixelChannelMask(image,GreenChannel);
cristy7c0a0a42011-08-23 17:57:25 +00002528 status|=LevelizeImage(image,black_color->green,white_color->green,1.0,
2529 exception);
cristybd5a96c2011-08-21 00:04:26 +00002530 (void) SetPixelChannelMask(image,channel_mask);
cristyf89cb1d2011-07-07 01:24:37 +00002531 }
cristyed231572011-07-14 02:18:59 +00002532 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristyf89cb1d2011-07-07 01:24:37 +00002533 {
cristybd5a96c2011-08-21 00:04:26 +00002534 channel_mask=SetPixelChannelMask(image,BlueChannel);
cristy7c0a0a42011-08-23 17:57:25 +00002535 status|=LevelizeImage(image,black_color->blue,white_color->blue,1.0,
2536 exception);
cristybd5a96c2011-08-21 00:04:26 +00002537 (void) SetPixelChannelMask(image,channel_mask);
cristyf89cb1d2011-07-07 01:24:37 +00002538 }
cristyed231572011-07-14 02:18:59 +00002539 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +00002540 (image->colorspace == CMYKColorspace))
cristyf89cb1d2011-07-07 01:24:37 +00002541 {
cristybd5a96c2011-08-21 00:04:26 +00002542 channel_mask=SetPixelChannelMask(image,BlackChannel);
cristy7c0a0a42011-08-23 17:57:25 +00002543 status|=LevelizeImage(image,black_color->black,white_color->black,1.0,
2544 exception);
cristybd5a96c2011-08-21 00:04:26 +00002545 (void) SetPixelChannelMask(image,channel_mask);
cristyf89cb1d2011-07-07 01:24:37 +00002546 }
cristyed231572011-07-14 02:18:59 +00002547 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00002548 (image->matte == MagickTrue))
cristyf89cb1d2011-07-07 01:24:37 +00002549 {
cristybd5a96c2011-08-21 00:04:26 +00002550 channel_mask=SetPixelChannelMask(image,AlphaChannel);
cristy7c0a0a42011-08-23 17:57:25 +00002551 status|=LevelizeImage(image,black_color->alpha,white_color->alpha,1.0,
2552 exception);
cristybd5a96c2011-08-21 00:04:26 +00002553 (void) SetPixelChannelMask(image,channel_mask);
cristyf89cb1d2011-07-07 01:24:37 +00002554 }
cristy3ed852e2009-09-05 21:47:34 +00002555 }
2556 return(status == 0 ? MagickFalse : MagickTrue);
2557}
2558
2559/*
2560%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2561% %
2562% %
2563% %
2564% L i n e a r S t r e t c h I m a g e %
2565% %
2566% %
2567% %
2568%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2569%
cristyf1611782011-08-01 15:39:13 +00002570% LinearStretchImage() discards any pixels below the black point and above
2571% the white point and levels the remaining pixels.
cristy3ed852e2009-09-05 21:47:34 +00002572%
2573% The format of the LinearStretchImage method is:
2574%
2575% MagickBooleanType LinearStretchImage(Image *image,
cristy33bd5152011-08-24 01:42:24 +00002576% const double black_point,const double white_point,
2577% ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002578%
2579% A description of each parameter follows:
2580%
2581% o image: the image.
2582%
2583% o black_point: the black point.
2584%
2585% o white_point: the white point.
2586%
cristy33bd5152011-08-24 01:42:24 +00002587% o exception: return any errors or warnings in this structure.
2588%
cristy3ed852e2009-09-05 21:47:34 +00002589*/
2590MagickExport MagickBooleanType LinearStretchImage(Image *image,
cristy33bd5152011-08-24 01:42:24 +00002591 const double black_point,const double white_point,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002592{
2593#define LinearStretchImageTag "LinearStretch/Image"
2594
cristy33bd5152011-08-24 01:42:24 +00002595 CacheView
2596 *image_view;
cristy3ed852e2009-09-05 21:47:34 +00002597
cristy3ed852e2009-09-05 21:47:34 +00002598 MagickBooleanType
2599 status;
2600
2601 MagickRealType
2602 *histogram,
2603 intensity;
2604
cristy8d4629b2010-08-30 17:59:46 +00002605 ssize_t
2606 black,
2607 white,
2608 y;
2609
cristy3ed852e2009-09-05 21:47:34 +00002610 /*
2611 Allocate histogram and linear map.
2612 */
2613 assert(image != (Image *) NULL);
2614 assert(image->signature == MagickSignature);
2615 histogram=(MagickRealType *) AcquireQuantumMemory(MaxMap+1UL,
2616 sizeof(*histogram));
2617 if (histogram == (MagickRealType *) NULL)
2618 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
2619 image->filename);
2620 /*
2621 Form histogram.
2622 */
2623 (void) ResetMagickMemory(histogram,0,(MaxMap+1)*sizeof(*histogram));
cristy33bd5152011-08-24 01:42:24 +00002624 image_view=AcquireCacheView(image);
cristybb503372010-05-27 20:51:26 +00002625 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002626 {
cristy4c08aed2011-07-01 19:47:50 +00002627 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00002628 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00002629
cristybb503372010-05-27 20:51:26 +00002630 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002631 x;
2632
cristy33bd5152011-08-24 01:42:24 +00002633 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00002634 if (p == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00002635 break;
cristy33bd5152011-08-24 01:42:24 +00002636 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002637 {
cristy4c08aed2011-07-01 19:47:50 +00002638 histogram[ScaleQuantumToMap(GetPixelIntensity(image,p))]++;
cristyed231572011-07-14 02:18:59 +00002639 p+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00002640 }
2641 }
cristy33bd5152011-08-24 01:42:24 +00002642 image_view=DestroyCacheView(image_view);
cristy3ed852e2009-09-05 21:47:34 +00002643 /*
2644 Find the histogram boundaries by locating the black and white point levels.
2645 */
cristy3ed852e2009-09-05 21:47:34 +00002646 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00002647 for (black=0; black < (ssize_t) MaxMap; black++)
cristy3ed852e2009-09-05 21:47:34 +00002648 {
2649 intensity+=histogram[black];
2650 if (intensity >= black_point)
2651 break;
2652 }
2653 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00002654 for (white=(ssize_t) MaxMap; white != 0; white--)
cristy3ed852e2009-09-05 21:47:34 +00002655 {
2656 intensity+=histogram[white];
2657 if (intensity >= white_point)
2658 break;
2659 }
2660 histogram=(MagickRealType *) RelinquishMagickMemory(histogram);
cristyc82a27b2011-10-21 01:07:16 +00002661 status=LevelImage(image,(double) black,(double) white,1.0,exception);
cristy3ed852e2009-09-05 21:47:34 +00002662 return(status);
2663}
2664
2665/*
2666%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2667% %
2668% %
2669% %
2670% M o d u l a t e I m a g e %
2671% %
2672% %
2673% %
2674%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2675%
2676% ModulateImage() lets you control the brightness, saturation, and hue
2677% of an image. Modulate represents the brightness, saturation, and hue
2678% as one parameter (e.g. 90,150,100). If the image colorspace is HSL, the
2679% modulation is lightness, saturation, and hue. And if the colorspace is
2680% HWB, use blackness, whiteness, and hue.
2681%
2682% The format of the ModulateImage method is:
2683%
cristy33bd5152011-08-24 01:42:24 +00002684% MagickBooleanType ModulateImage(Image *image,const char *modulate,
2685% ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002686%
2687% A description of each parameter follows:
2688%
2689% o image: the image.
2690%
cristy33bd5152011-08-24 01:42:24 +00002691% o modulate: Define the percent change in brightness, saturation, and hue.
2692%
2693% o exception: return any errors or warnings in this structure.
cristy3ed852e2009-09-05 21:47:34 +00002694%
2695*/
2696
2697static void ModulateHSB(const double percent_hue,
cristy3094b7f2011-10-01 23:18:02 +00002698 const double percent_saturation,const double percent_brightness,double *red,
2699 double *green,double *blue)
cristy3ed852e2009-09-05 21:47:34 +00002700{
2701 double
2702 brightness,
2703 hue,
2704 saturation;
2705
2706 /*
2707 Increase or decrease color brightness, saturation, or hue.
2708 */
cristy3094b7f2011-10-01 23:18:02 +00002709 assert(red != (double *) NULL);
2710 assert(green != (double *) NULL);
2711 assert(blue != (double *) NULL);
cristy3ed852e2009-09-05 21:47:34 +00002712 ConvertRGBToHSB(*red,*green,*blue,&hue,&saturation,&brightness);
2713 hue+=0.5*(0.01*percent_hue-1.0);
2714 while (hue < 0.0)
2715 hue+=1.0;
2716 while (hue > 1.0)
2717 hue-=1.0;
2718 saturation*=0.01*percent_saturation;
2719 brightness*=0.01*percent_brightness;
2720 ConvertHSBToRGB(hue,saturation,brightness,red,green,blue);
2721}
2722
2723static void ModulateHSL(const double percent_hue,
cristy3094b7f2011-10-01 23:18:02 +00002724 const double percent_saturation,const double percent_lightness,double *red,
2725 double *green,double *blue)
cristy3ed852e2009-09-05 21:47:34 +00002726{
2727 double
2728 hue,
2729 lightness,
2730 saturation;
2731
2732 /*
2733 Increase or decrease color lightness, saturation, or hue.
2734 */
cristy3094b7f2011-10-01 23:18:02 +00002735 assert(red != (double *) NULL);
2736 assert(green != (double *) NULL);
2737 assert(blue != (double *) NULL);
cristy3ed852e2009-09-05 21:47:34 +00002738 ConvertRGBToHSL(*red,*green,*blue,&hue,&saturation,&lightness);
2739 hue+=0.5*(0.01*percent_hue-1.0);
2740 while (hue < 0.0)
2741 hue+=1.0;
2742 while (hue > 1.0)
2743 hue-=1.0;
2744 saturation*=0.01*percent_saturation;
2745 lightness*=0.01*percent_lightness;
2746 ConvertHSLToRGB(hue,saturation,lightness,red,green,blue);
2747}
2748
cristy3094b7f2011-10-01 23:18:02 +00002749static 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 +00002750{
2751 double
2752 blackness,
2753 hue,
2754 whiteness;
2755
2756 /*
2757 Increase or decrease color blackness, whiteness, or hue.
2758 */
cristy3094b7f2011-10-01 23:18:02 +00002759 assert(red != (double *) NULL);
2760 assert(green != (double *) NULL);
2761 assert(blue != (double *) NULL);
cristy3ed852e2009-09-05 21:47:34 +00002762 ConvertRGBToHWB(*red,*green,*blue,&hue,&whiteness,&blackness);
2763 hue+=0.5*(0.01*percent_hue-1.0);
2764 while (hue < 0.0)
2765 hue+=1.0;
2766 while (hue > 1.0)
2767 hue-=1.0;
2768 blackness*=0.01*percent_blackness;
2769 whiteness*=0.01*percent_whiteness;
2770 ConvertHWBToRGB(hue,whiteness,blackness,red,green,blue);
2771}
2772
cristy33bd5152011-08-24 01:42:24 +00002773MagickExport MagickBooleanType ModulateImage(Image *image,const char *modulate,
2774 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002775{
2776#define ModulateImageTag "Modulate/Image"
2777
cristyc4c8d132010-01-07 01:58:38 +00002778 CacheView
2779 *image_view;
2780
cristy3ed852e2009-09-05 21:47:34 +00002781 ColorspaceType
2782 colorspace;
2783
2784 const char
2785 *artifact;
2786
2787 double
2788 percent_brightness,
2789 percent_hue,
2790 percent_saturation;
2791
cristy3ed852e2009-09-05 21:47:34 +00002792 GeometryInfo
2793 geometry_info;
2794
cristy3ed852e2009-09-05 21:47:34 +00002795 MagickBooleanType
2796 status;
2797
cristybb503372010-05-27 20:51:26 +00002798 MagickOffsetType
2799 progress;
2800
cristy3ed852e2009-09-05 21:47:34 +00002801 MagickStatusType
2802 flags;
2803
cristybb503372010-05-27 20:51:26 +00002804 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002805 i;
2806
cristybb503372010-05-27 20:51:26 +00002807 ssize_t
2808 y;
2809
cristy3ed852e2009-09-05 21:47:34 +00002810 /*
cristy2b726bd2010-01-11 01:05:39 +00002811 Initialize modulate table.
cristy3ed852e2009-09-05 21:47:34 +00002812 */
2813 assert(image != (Image *) NULL);
2814 assert(image->signature == MagickSignature);
2815 if (image->debug != MagickFalse)
2816 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2817 if (modulate == (char *) NULL)
2818 return(MagickFalse);
2819 flags=ParseGeometry(modulate,&geometry_info);
2820 percent_brightness=geometry_info.rho;
2821 percent_saturation=geometry_info.sigma;
2822 if ((flags & SigmaValue) == 0)
2823 percent_saturation=100.0;
2824 percent_hue=geometry_info.xi;
2825 if ((flags & XiValue) == 0)
2826 percent_hue=100.0;
2827 colorspace=UndefinedColorspace;
2828 artifact=GetImageArtifact(image,"modulate:colorspace");
2829 if (artifact != (const char *) NULL)
cristy042ee782011-04-22 18:48:30 +00002830 colorspace=(ColorspaceType) ParseCommandOption(MagickColorspaceOptions,
cristy3ed852e2009-09-05 21:47:34 +00002831 MagickFalse,artifact);
2832 if (image->storage_class == PseudoClass)
2833 {
2834 /*
2835 Modulate colormap.
2836 */
cristyb5d5f722009-11-04 03:03:49 +00002837#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristye6178502011-12-23 17:02:29 +00002838 #pragma omp parallel for schedule(static,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002839#endif
cristybb503372010-05-27 20:51:26 +00002840 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00002841 switch (colorspace)
2842 {
2843 case HSBColorspace:
2844 {
2845 ModulateHSB(percent_hue,percent_saturation,percent_brightness,
2846 &image->colormap[i].red,&image->colormap[i].green,
2847 &image->colormap[i].blue);
2848 break;
2849 }
2850 case HSLColorspace:
2851 default:
2852 {
2853 ModulateHSL(percent_hue,percent_saturation,percent_brightness,
2854 &image->colormap[i].red,&image->colormap[i].green,
2855 &image->colormap[i].blue);
2856 break;
2857 }
2858 case HWBColorspace:
2859 {
2860 ModulateHWB(percent_hue,percent_saturation,percent_brightness,
2861 &image->colormap[i].red,&image->colormap[i].green,
2862 &image->colormap[i].blue);
2863 break;
2864 }
2865 }
2866 }
2867 /*
2868 Modulate image.
2869 */
2870 status=MagickTrue;
2871 progress=0;
cristy3ed852e2009-09-05 21:47:34 +00002872 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00002873#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristye6178502011-12-23 17:02:29 +00002874 #pragma omp parallel for schedule(static,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002875#endif
cristybb503372010-05-27 20:51:26 +00002876 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002877 {
cristy3094b7f2011-10-01 23:18:02 +00002878 double
cristy5afeab82011-04-30 01:30:09 +00002879 blue,
2880 green,
2881 red;
2882
cristy4c08aed2011-07-01 19:47:50 +00002883 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00002884 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002885
cristy8d4629b2010-08-30 17:59:46 +00002886 register ssize_t
2887 x;
2888
cristy3ed852e2009-09-05 21:47:34 +00002889 if (status == MagickFalse)
2890 continue;
2891 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristyacd2ed22011-08-30 01:44:23 +00002892 if (q == (Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00002893 {
2894 status=MagickFalse;
2895 continue;
2896 }
cristybb503372010-05-27 20:51:26 +00002897 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002898 {
cristyda1f9c12011-10-02 21:39:49 +00002899 red=(double) GetPixelRed(image,q);
2900 green=(double) GetPixelGreen(image,q);
2901 blue=(double) GetPixelBlue(image,q);
cristy3ed852e2009-09-05 21:47:34 +00002902 switch (colorspace)
2903 {
2904 case HSBColorspace:
2905 {
2906 ModulateHSB(percent_hue,percent_saturation,percent_brightness,
cristy5afeab82011-04-30 01:30:09 +00002907 &red,&green,&blue);
cristy3ed852e2009-09-05 21:47:34 +00002908 break;
2909 }
2910 case HSLColorspace:
2911 default:
2912 {
2913 ModulateHSL(percent_hue,percent_saturation,percent_brightness,
cristy5afeab82011-04-30 01:30:09 +00002914 &red,&green,&blue);
cristy3ed852e2009-09-05 21:47:34 +00002915 break;
2916 }
2917 case HWBColorspace:
2918 {
2919 ModulateHWB(percent_hue,percent_saturation,percent_brightness,
cristy5afeab82011-04-30 01:30:09 +00002920 &red,&green,&blue);
cristy3ed852e2009-09-05 21:47:34 +00002921 break;
2922 }
2923 }
cristy3094b7f2011-10-01 23:18:02 +00002924 SetPixelRed(image,ClampToQuantum(red),q);
2925 SetPixelGreen(image,ClampToQuantum(green),q);
2926 SetPixelBlue(image,ClampToQuantum(blue),q);
cristyed231572011-07-14 02:18:59 +00002927 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00002928 }
2929 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2930 status=MagickFalse;
2931 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2932 {
2933 MagickBooleanType
2934 proceed;
2935
cristyb5d5f722009-11-04 03:03:49 +00002936#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00002937 #pragma omp critical (MagickCore_ModulateImage)
2938#endif
2939 proceed=SetImageProgress(image,ModulateImageTag,progress++,image->rows);
2940 if (proceed == MagickFalse)
2941 status=MagickFalse;
2942 }
2943 }
2944 image_view=DestroyCacheView(image_view);
2945 return(status);
2946}
2947
2948/*
2949%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2950% %
2951% %
2952% %
2953% N e g a t e I m a g e %
2954% %
2955% %
2956% %
2957%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2958%
2959% NegateImage() negates the colors in the reference image. The grayscale
2960% option means that only grayscale values within the image are negated.
2961%
cristy50fbc382011-07-07 02:19:17 +00002962% The format of the NegateImage method is:
cristy3ed852e2009-09-05 21:47:34 +00002963%
2964% MagickBooleanType NegateImage(Image *image,
cristyb3e7c6c2011-07-24 01:43:55 +00002965% const MagickBooleanType grayscale,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002966%
2967% A description of each parameter follows:
2968%
2969% o image: the image.
2970%
cristy3ed852e2009-09-05 21:47:34 +00002971% o grayscale: If MagickTrue, only negate grayscale pixels within the image.
2972%
cristyb3e7c6c2011-07-24 01:43:55 +00002973% o exception: return any errors or warnings in this structure.
2974%
cristy3ed852e2009-09-05 21:47:34 +00002975*/
cristy3ed852e2009-09-05 21:47:34 +00002976MagickExport MagickBooleanType NegateImage(Image *image,
cristyb3e7c6c2011-07-24 01:43:55 +00002977 const MagickBooleanType grayscale,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002978{
cristy3ed852e2009-09-05 21:47:34 +00002979#define NegateImageTag "Negate/Image"
2980
cristyc4c8d132010-01-07 01:58:38 +00002981 CacheView
2982 *image_view;
2983
cristy3ed852e2009-09-05 21:47:34 +00002984 MagickBooleanType
2985 status;
2986
cristybb503372010-05-27 20:51:26 +00002987 MagickOffsetType
2988 progress;
2989
2990 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002991 i;
2992
cristybb503372010-05-27 20:51:26 +00002993 ssize_t
2994 y;
2995
cristy3ed852e2009-09-05 21:47:34 +00002996 assert(image != (Image *) NULL);
2997 assert(image->signature == MagickSignature);
2998 if (image->debug != MagickFalse)
2999 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3000 if (image->storage_class == PseudoClass)
3001 {
3002 /*
3003 Negate colormap.
3004 */
cristyb5d5f722009-11-04 03:03:49 +00003005#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristye6178502011-12-23 17:02:29 +00003006 #pragma omp parallel for schedule(static,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003007#endif
cristybb503372010-05-27 20:51:26 +00003008 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00003009 {
3010 if (grayscale != MagickFalse)
3011 if ((image->colormap[i].red != image->colormap[i].green) ||
3012 (image->colormap[i].green != image->colormap[i].blue))
3013 continue;
cristyed231572011-07-14 02:18:59 +00003014 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00003015 image->colormap[i].red=(Quantum) QuantumRange-
3016 image->colormap[i].red;
cristyed231572011-07-14 02:18:59 +00003017 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00003018 image->colormap[i].green=(Quantum) QuantumRange-
3019 image->colormap[i].green;
cristyed231572011-07-14 02:18:59 +00003020 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00003021 image->colormap[i].blue=(Quantum) QuantumRange-
3022 image->colormap[i].blue;
3023 }
3024 }
3025 /*
3026 Negate image.
3027 */
3028 status=MagickTrue;
3029 progress=0;
cristy3ed852e2009-09-05 21:47:34 +00003030 image_view=AcquireCacheView(image);
3031 if (grayscale != MagickFalse)
3032 {
cristyb5d5f722009-11-04 03:03:49 +00003033#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristye6178502011-12-23 17:02:29 +00003034 #pragma omp parallel for schedule(static,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003035#endif
cristybb503372010-05-27 20:51:26 +00003036 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003037 {
3038 MagickBooleanType
3039 sync;
3040
cristy4c08aed2011-07-01 19:47:50 +00003041 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00003042 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003043
cristy8d4629b2010-08-30 17:59:46 +00003044 register ssize_t
3045 x;
3046
cristy3ed852e2009-09-05 21:47:34 +00003047 if (status == MagickFalse)
3048 continue;
3049 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,
3050 exception);
cristyacd2ed22011-08-30 01:44:23 +00003051 if (q == (Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00003052 {
3053 status=MagickFalse;
3054 continue;
3055 }
cristybb503372010-05-27 20:51:26 +00003056 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003057 {
cristy9aa95be2011-07-20 21:56:45 +00003058 register ssize_t
3059 i;
3060
cristyd476a8e2011-07-23 16:13:22 +00003061 if (IsPixelGray(image,q) != MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00003062 {
cristya30d9ba2011-07-23 21:00:48 +00003063 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00003064 continue;
3065 }
cristya30d9ba2011-07-23 21:00:48 +00003066 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
cristy9aa95be2011-07-20 21:56:45 +00003067 {
cristyabace412011-12-11 15:56:53 +00003068 PixelChannel
3069 channel;
3070
cristy1aaa3cd2011-08-21 23:48:17 +00003071 PixelTrait
cristy9aa95be2011-07-20 21:56:45 +00003072 traits;
3073
cristyabace412011-12-11 15:56:53 +00003074 channel=GetPixelChannelMapChannel(image,i);
3075 traits=GetPixelChannelMapTraits(image,channel);
cristy9aa95be2011-07-20 21:56:45 +00003076 if ((traits & UpdatePixelTrait) != 0)
3077 q[i]=QuantumRange-q[i];
3078 }
cristya30d9ba2011-07-23 21:00:48 +00003079 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00003080 }
3081 sync=SyncCacheViewAuthenticPixels(image_view,exception);
3082 if (sync == MagickFalse)
3083 status=MagickFalse;
3084 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3085 {
3086 MagickBooleanType
3087 proceed;
3088
cristyb5d5f722009-11-04 03:03:49 +00003089#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy50fbc382011-07-07 02:19:17 +00003090 #pragma omp critical (MagickCore_NegateImage)
cristy3ed852e2009-09-05 21:47:34 +00003091#endif
3092 proceed=SetImageProgress(image,NegateImageTag,progress++,
3093 image->rows);
3094 if (proceed == MagickFalse)
3095 status=MagickFalse;
3096 }
3097 }
3098 image_view=DestroyCacheView(image_view);
3099 return(MagickTrue);
3100 }
3101 /*
3102 Negate image.
3103 */
cristyb5d5f722009-11-04 03:03:49 +00003104#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristye6178502011-12-23 17:02:29 +00003105 #pragma omp parallel for schedule(static,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003106#endif
cristybb503372010-05-27 20:51:26 +00003107 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003108 {
cristy4c08aed2011-07-01 19:47:50 +00003109 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00003110 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003111
cristy8d4629b2010-08-30 17:59:46 +00003112 register ssize_t
3113 x;
3114
cristy3ed852e2009-09-05 21:47:34 +00003115 if (status == MagickFalse)
3116 continue;
3117 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristyacd2ed22011-08-30 01:44:23 +00003118 if (q == (Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00003119 {
3120 status=MagickFalse;
3121 continue;
3122 }
cristybb503372010-05-27 20:51:26 +00003123 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003124 {
cristyf7dc44c2011-07-20 14:41:15 +00003125 register ssize_t
3126 i;
3127
cristya30d9ba2011-07-23 21:00:48 +00003128 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
cristyf7dc44c2011-07-20 14:41:15 +00003129 {
cristyabace412011-12-11 15:56:53 +00003130 PixelChannel
3131 channel;
3132
cristy1aaa3cd2011-08-21 23:48:17 +00003133 PixelTrait
cristyf7dc44c2011-07-20 14:41:15 +00003134 traits;
3135
cristyabace412011-12-11 15:56:53 +00003136 channel=GetPixelChannelMapChannel(image,i);
3137 traits=GetPixelChannelMapTraits(image,channel);
cristyf7dc44c2011-07-20 14:41:15 +00003138 if ((traits & UpdatePixelTrait) != 0)
3139 q[i]=QuantumRange-q[i];
3140 }
cristya30d9ba2011-07-23 21:00:48 +00003141 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00003142 }
3143 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
3144 status=MagickFalse;
3145 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3146 {
3147 MagickBooleanType
3148 proceed;
3149
cristyb5d5f722009-11-04 03:03:49 +00003150#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy50fbc382011-07-07 02:19:17 +00003151 #pragma omp critical (MagickCore_NegateImage)
cristy3ed852e2009-09-05 21:47:34 +00003152#endif
3153 proceed=SetImageProgress(image,NegateImageTag,progress++,image->rows);
3154 if (proceed == MagickFalse)
3155 status=MagickFalse;
3156 }
3157 }
3158 image_view=DestroyCacheView(image_view);
3159 return(status);
3160}
3161
3162/*
3163%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3164% %
3165% %
3166% %
3167% N o r m a l i z e I m a g e %
3168% %
3169% %
3170% %
3171%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3172%
cristya4dfb122011-07-07 19:01:57 +00003173% NormalizeImage() enhances the contrast of a color image by mapping the
3174% darkest 2 percent of all pixel to black and the brightest 1 percent to white.
cristy3ed852e2009-09-05 21:47:34 +00003175%
3176% The format of the NormalizeImage method is:
3177%
cristye23ec9d2011-08-16 18:15:40 +00003178% MagickBooleanType NormalizeImage(Image *image,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003179%
3180% A description of each parameter follows:
3181%
3182% o image: the image.
3183%
cristye23ec9d2011-08-16 18:15:40 +00003184% o exception: return any errors or warnings in this structure.
3185%
cristy3ed852e2009-09-05 21:47:34 +00003186*/
cristye23ec9d2011-08-16 18:15:40 +00003187MagickExport MagickBooleanType NormalizeImage(Image *image,
3188 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003189{
cristy3ed852e2009-09-05 21:47:34 +00003190 double
3191 black_point,
3192 white_point;
3193
cristy530239c2010-07-25 17:34:26 +00003194 black_point=(double) image->columns*image->rows*0.0015;
3195 white_point=(double) image->columns*image->rows*0.9995;
cristye23ec9d2011-08-16 18:15:40 +00003196 return(ContrastStretchImage(image,black_point,white_point,exception));
cristy3ed852e2009-09-05 21:47:34 +00003197}
3198
3199/*
3200%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3201% %
3202% %
3203% %
3204% S i g m o i d a l C o n t r a s t I m a g e %
3205% %
3206% %
3207% %
3208%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3209%
3210% SigmoidalContrastImage() adjusts the contrast of an image with a non-linear
3211% sigmoidal contrast algorithm. Increase the contrast of the image using a
3212% sigmoidal transfer function without saturating highlights or shadows.
3213% Contrast indicates how much to increase the contrast (0 is none; 3 is
3214% typical; 20 is pushing it); mid-point indicates where midtones fall in the
3215% resultant image (0 is white; 50% is middle-gray; 100% is black). Set
3216% sharpen to MagickTrue to increase the image contrast otherwise the contrast
3217% is reduced.
3218%
3219% The format of the SigmoidalContrastImage method is:
3220%
3221% MagickBooleanType SigmoidalContrastImage(Image *image,
cristy33bd5152011-08-24 01:42:24 +00003222% const MagickBooleanType sharpen,const char *levels,
3223% ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003224%
3225% A description of each parameter follows:
3226%
3227% o image: the image.
3228%
cristy3ed852e2009-09-05 21:47:34 +00003229% o sharpen: Increase or decrease image contrast.
3230%
cristyfa769582010-09-30 23:30:03 +00003231% o alpha: strength of the contrast, the larger the number the more
3232% 'threshold-like' it becomes.
cristy3ed852e2009-09-05 21:47:34 +00003233%
cristyfa769582010-09-30 23:30:03 +00003234% o beta: midpoint of the function as a color value 0 to QuantumRange.
cristy3ed852e2009-09-05 21:47:34 +00003235%
cristy33bd5152011-08-24 01:42:24 +00003236% o exception: return any errors or warnings in this structure.
3237%
cristy3ed852e2009-09-05 21:47:34 +00003238*/
cristy3ed852e2009-09-05 21:47:34 +00003239MagickExport MagickBooleanType SigmoidalContrastImage(Image *image,
cristy33bd5152011-08-24 01:42:24 +00003240 const MagickBooleanType sharpen,const double contrast,const double midpoint,
3241 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003242{
3243#define SigmoidalContrastImageTag "SigmoidalContrast/Image"
3244
cristyc4c8d132010-01-07 01:58:38 +00003245 CacheView
3246 *image_view;
3247
cristy3ed852e2009-09-05 21:47:34 +00003248 MagickBooleanType
3249 status;
3250
cristybb503372010-05-27 20:51:26 +00003251 MagickOffsetType
3252 progress;
3253
cristy3ed852e2009-09-05 21:47:34 +00003254 MagickRealType
3255 *sigmoidal_map;
3256
cristybb503372010-05-27 20:51:26 +00003257 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003258 i;
3259
cristybb503372010-05-27 20:51:26 +00003260 ssize_t
3261 y;
3262
cristy3ed852e2009-09-05 21:47:34 +00003263 /*
3264 Allocate and initialize sigmoidal maps.
3265 */
3266 assert(image != (Image *) NULL);
3267 assert(image->signature == MagickSignature);
3268 if (image->debug != MagickFalse)
3269 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3270 sigmoidal_map=(MagickRealType *) AcquireQuantumMemory(MaxMap+1UL,
3271 sizeof(*sigmoidal_map));
3272 if (sigmoidal_map == (MagickRealType *) NULL)
3273 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
3274 image->filename);
3275 (void) ResetMagickMemory(sigmoidal_map,0,(MaxMap+1)*sizeof(*sigmoidal_map));
cristyb5d5f722009-11-04 03:03:49 +00003276#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristye6178502011-12-23 17:02:29 +00003277 #pragma omp parallel for schedule(static,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003278#endif
cristybb503372010-05-27 20:51:26 +00003279 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +00003280 {
3281 if (sharpen != MagickFalse)
3282 {
3283 sigmoidal_map[i]=(MagickRealType) ScaleMapToQuantum((MagickRealType)
3284 (MaxMap*((1.0/(1.0+exp(contrast*(midpoint/(double) QuantumRange-
cristy33bd5152011-08-24 01:42:24 +00003285 (double) i/MaxMap))))-(1.0/(1.0+exp(contrast*(midpoint/(double)
3286 QuantumRange)))))/((1.0/(1.0+exp(contrast*(midpoint/(double)
3287 QuantumRange-1.0))))-(1.0/(1.0+exp(contrast*(midpoint/(double)
3288 QuantumRange)))))+0.5));
cristy3ed852e2009-09-05 21:47:34 +00003289 continue;
3290 }
3291 sigmoidal_map[i]=(MagickRealType) ScaleMapToQuantum((MagickRealType)
cristy33bd5152011-08-24 01:42:24 +00003292 (MaxMap*(QuantumScale*midpoint-log((1.0-(1.0/(1.0+exp(midpoint/(double)
3293 QuantumRange*contrast))+((double) i/MaxMap)*((1.0/(1.0+exp(contrast*(
3294 midpoint/(double) QuantumRange-1.0))))-(1.0/(1.0+exp(midpoint/(double)
3295 QuantumRange*contrast))))))/(1.0/(1.0+exp(midpoint/(double) QuantumRange*
3296 contrast))+((double) i/MaxMap)*((1.0/(1.0+exp(contrast*(midpoint/(double)
3297 QuantumRange-1.0))))-(1.0/(1.0+exp(midpoint/(double) QuantumRange*
3298 contrast))))))/contrast)));
cristy3ed852e2009-09-05 21:47:34 +00003299 }
3300 if (image->storage_class == PseudoClass)
3301 {
3302 /*
3303 Sigmoidal-contrast enhance colormap.
3304 */
cristyb5d5f722009-11-04 03:03:49 +00003305#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristye6178502011-12-23 17:02:29 +00003306 #pragma omp parallel for schedule(static,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003307#endif
cristybb503372010-05-27 20:51:26 +00003308 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00003309 {
cristyed231572011-07-14 02:18:59 +00003310 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristyda1f9c12011-10-02 21:39:49 +00003311 image->colormap[i].red=sigmoidal_map[ScaleQuantumToMap(
3312 ClampToQuantum(image->colormap[i].red))];
cristyed231572011-07-14 02:18:59 +00003313 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristyda1f9c12011-10-02 21:39:49 +00003314 image->colormap[i].green=sigmoidal_map[ScaleQuantumToMap(
3315 ClampToQuantum(image->colormap[i].green))];
cristyed231572011-07-14 02:18:59 +00003316 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristyda1f9c12011-10-02 21:39:49 +00003317 image->colormap[i].blue=sigmoidal_map[ScaleQuantumToMap(
3318 ClampToQuantum(image->colormap[i].blue))];
cristyed231572011-07-14 02:18:59 +00003319 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristyda1f9c12011-10-02 21:39:49 +00003320 image->colormap[i].alpha=sigmoidal_map[ScaleQuantumToMap(
3321 ClampToQuantum(image->colormap[i].alpha))];
cristy3ed852e2009-09-05 21:47:34 +00003322 }
3323 }
3324 /*
3325 Sigmoidal-contrast enhance image.
3326 */
3327 status=MagickTrue;
3328 progress=0;
cristy3ed852e2009-09-05 21:47:34 +00003329 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00003330#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristye6178502011-12-23 17:02:29 +00003331 #pragma omp parallel for schedule(static,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003332#endif
cristybb503372010-05-27 20:51:26 +00003333 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003334 {
cristy4c08aed2011-07-01 19:47:50 +00003335 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00003336 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003337
cristy8d4629b2010-08-30 17:59:46 +00003338 register ssize_t
3339 x;
3340
cristy3ed852e2009-09-05 21:47:34 +00003341 if (status == MagickFalse)
3342 continue;
3343 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristyacd2ed22011-08-30 01:44:23 +00003344 if (q == (Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00003345 {
3346 status=MagickFalse;
3347 continue;
3348 }
cristybb503372010-05-27 20:51:26 +00003349 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003350 {
cristy33bd5152011-08-24 01:42:24 +00003351 register ssize_t
3352 i;
3353
3354 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
3355 {
cristyabace412011-12-11 15:56:53 +00003356 PixelChannel
3357 channel;
3358
cristy33bd5152011-08-24 01:42:24 +00003359 PixelTrait
3360 traits;
3361
cristyabace412011-12-11 15:56:53 +00003362 channel=GetPixelChannelMapChannel(image,i);
3363 traits=GetPixelChannelMapTraits(image,channel);
cristy33bd5152011-08-24 01:42:24 +00003364 if ((traits & UpdatePixelTrait) != 0)
3365 q[i]=ClampToQuantum(sigmoidal_map[ScaleQuantumToMap(q[i])]);
3366 }
cristyed231572011-07-14 02:18:59 +00003367 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00003368 }
3369 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
3370 status=MagickFalse;
3371 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3372 {
3373 MagickBooleanType
3374 proceed;
3375
cristyb5d5f722009-11-04 03:03:49 +00003376#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy9ee60942011-07-06 14:54:38 +00003377 #pragma omp critical (MagickCore_SigmoidalContrastImage)
cristy3ed852e2009-09-05 21:47:34 +00003378#endif
3379 proceed=SetImageProgress(image,SigmoidalContrastImageTag,progress++,
3380 image->rows);
3381 if (proceed == MagickFalse)
3382 status=MagickFalse;
3383 }
3384 }
3385 image_view=DestroyCacheView(image_view);
3386 sigmoidal_map=(MagickRealType *) RelinquishMagickMemory(sigmoidal_map);
3387 return(status);
3388}