blob: 2b54d699ca924844035343bf6cea915948c1a3a6 [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)
cristy564a5692012-01-20 23:56:26 +0000412 #pragma omp critical (MagickCore_ClutImage)
cristy444eda62011-08-10 02:07:46 +0000413#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)
cristy564a5692012-01-20 23:56:26 +0000798 #pragma omp critical (MagickCore_ColorDecisionListImageChannel)
cristy3ed852e2009-09-05 21:47:34 +0000799#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)
cristy564a5692012-01-20 23:56:26 +0000954 #pragma omp critical (MagickCore_ContrastImage)
cristy3ed852e2009-09-05 21:47:34 +0000955#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
cristy564a5692012-01-20 23:56:26 +00001026 size_t
1027 number_channels;
1028
cristybb503372010-05-27 20:51:26 +00001029 ssize_t
1030 y;
1031
cristy3ed852e2009-09-05 21:47:34 +00001032 /*
1033 Allocate histogram and stretch map.
1034 */
1035 assert(image != (Image *) NULL);
1036 assert(image->signature == MagickSignature);
1037 if (image->debug != MagickFalse)
1038 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
cristyf45fec72011-08-23 16:02:32 +00001039 black=(double *) AcquireQuantumMemory(GetPixelChannels(image),sizeof(*black));
1040 white=(double *) AcquireQuantumMemory(GetPixelChannels(image),sizeof(*white));
cristy564a5692012-01-20 23:56:26 +00001041 histogram=(double *) AcquireQuantumMemory(MaxMap+1UL,GetPixelChannels(image)*
1042 sizeof(*histogram));
cristyf45fec72011-08-23 16:02:32 +00001043 stretch_map=(double *) AcquireQuantumMemory(MaxMap+1UL,
cristy465571b2011-08-21 20:43:15 +00001044 GetPixelChannels(image)*sizeof(*stretch_map));
cristyf45fec72011-08-23 16:02:32 +00001045 if ((black == (double *) NULL) || (white == (double *) NULL) ||
1046 (histogram == (double *) NULL) || (stretch_map == (double *) NULL))
cristy465571b2011-08-21 20:43:15 +00001047 {
cristyf45fec72011-08-23 16:02:32 +00001048 if (stretch_map != (double *) NULL)
1049 stretch_map=(double *) RelinquishMagickMemory(stretch_map);
1050 if (histogram != (double *) NULL)
1051 histogram=(double *) RelinquishMagickMemory(histogram);
1052 if (white != (double *) NULL)
1053 white=(double *) RelinquishMagickMemory(white);
1054 if (black != (double *) NULL)
1055 black=(double *) RelinquishMagickMemory(black);
cristy465571b2011-08-21 20:43:15 +00001056 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
1057 image->filename);
1058 }
cristy3ed852e2009-09-05 21:47:34 +00001059 /*
1060 Form histogram.
1061 */
1062 status=MagickTrue;
cristy465571b2011-08-21 20:43:15 +00001063 (void) ResetMagickMemory(histogram,0,(MaxMap+1)*GetPixelChannels(image)*
1064 sizeof(*histogram));
cristy3ed852e2009-09-05 21:47:34 +00001065 image_view=AcquireCacheView(image);
cristybb503372010-05-27 20:51:26 +00001066 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001067 {
cristy4c08aed2011-07-01 19:47:50 +00001068 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00001069 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00001070
cristybb503372010-05-27 20:51:26 +00001071 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001072 x;
1073
1074 if (status == MagickFalse)
1075 continue;
1076 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00001077 if (p == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001078 {
1079 status=MagickFalse;
1080 continue;
1081 }
cristy43547a52011-08-09 13:07:05 +00001082 for (x=0; x < (ssize_t) image->columns; x++)
1083 {
cristy465571b2011-08-21 20:43:15 +00001084 register ssize_t
1085 i;
1086
1087 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
cristy465571b2011-08-21 20:43:15 +00001088 histogram[GetPixelChannels(image)*ScaleQuantumToMap(p[i])+i]++;
cristy43547a52011-08-09 13:07:05 +00001089 p+=GetPixelChannels(image);
1090 }
cristy3ed852e2009-09-05 21:47:34 +00001091 }
1092 /*
1093 Find the histogram boundaries by locating the black/white levels.
1094 */
cristy564a5692012-01-20 23:56:26 +00001095 number_channels=GetPixelChannels(image);
cristyb5d5f722009-11-04 03:03:49 +00001096#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristye6178502011-12-23 17:02:29 +00001097 #pragma omp parallel for schedule(static,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001098#endif
cristy564a5692012-01-20 23:56:26 +00001099 for (i=0; i < number_channels; i++)
cristy3ed852e2009-09-05 21:47:34 +00001100 {
cristy465571b2011-08-21 20:43:15 +00001101 double
1102 intensity;
1103
1104 register ssize_t
1105 j;
1106
1107 black[i]=0.0;
1108 white[i]=MaxRange(QuantumRange);
1109 intensity=0.0;
1110 for (j=0; j <= (ssize_t) MaxMap; j++)
1111 {
1112 intensity+=histogram[GetPixelChannels(image)*j+i];
1113 if (intensity > black_point)
1114 break;
1115 }
1116 black[i]=(MagickRealType) j;
1117 intensity=0.0;
1118 for (j=(ssize_t) MaxMap; j != 0; j--)
1119 {
1120 intensity+=histogram[GetPixelChannels(image)*j+i];
1121 if (intensity > ((double) image->columns*image->rows-white_point))
1122 break;
1123 }
1124 white[i]=(MagickRealType) j;
cristy3ed852e2009-09-05 21:47:34 +00001125 }
cristy465571b2011-08-21 20:43:15 +00001126 histogram=(double *) RelinquishMagickMemory(histogram);
cristy3ed852e2009-09-05 21:47:34 +00001127 /*
cristy465571b2011-08-21 20:43:15 +00001128 Stretch the histogram to create the stretched image mapping.
cristy3ed852e2009-09-05 21:47:34 +00001129 */
cristy465571b2011-08-21 20:43:15 +00001130 (void) ResetMagickMemory(stretch_map,0,(MaxMap+1)*GetPixelChannels(image)*
1131 sizeof(*stretch_map));
cristy564a5692012-01-20 23:56:26 +00001132 number_channels=GetPixelChannels(image);
cristy465571b2011-08-21 20:43:15 +00001133#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristye6178502011-12-23 17:02:29 +00001134 #pragma omp parallel for schedule(static,4) shared(progress,status)
cristy465571b2011-08-21 20:43:15 +00001135#endif
cristy564a5692012-01-20 23:56:26 +00001136 for (i=0; i < (ssize_t) number_channels; i++)
cristy465571b2011-08-21 20:43:15 +00001137 {
1138 register ssize_t
1139 j;
1140
1141 for (j=0; j <= (ssize_t) MaxMap; j++)
1142 {
1143 if (j < (ssize_t) black[i])
1144 stretch_map[GetPixelChannels(image)*j+i]=0.0;
1145 else
1146 if (j > (ssize_t) white[i])
1147 stretch_map[GetPixelChannels(image)*j+i]=(MagickRealType)
1148 QuantumRange;
1149 else
1150 if (black[i] != white[i])
1151 stretch_map[GetPixelChannels(image)*j+i]=(MagickRealType)
1152 ScaleMapToQuantum((MagickRealType) (MaxMap*(j-black[i])/
1153 (white[i]-black[i])));
1154 }
1155 }
cristy3ed852e2009-09-05 21:47:34 +00001156 if (image->storage_class == PseudoClass)
1157 {
cristy465571b2011-08-21 20:43:15 +00001158 register ssize_t
1159 j;
1160
cristy3ed852e2009-09-05 21:47:34 +00001161 /*
cristy465571b2011-08-21 20:43:15 +00001162 Stretch-contrast colormap.
cristy3ed852e2009-09-05 21:47:34 +00001163 */
cristyb5d5f722009-11-04 03:03:49 +00001164#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristye6178502011-12-23 17:02:29 +00001165 #pragma omp parallel for schedule(static,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001166#endif
cristy465571b2011-08-21 20:43:15 +00001167 for (j=0; j < (ssize_t) image->colors; j++)
cristy3ed852e2009-09-05 21:47:34 +00001168 {
cristyed231572011-07-14 02:18:59 +00001169 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001170 {
cristy465571b2011-08-21 20:43:15 +00001171 i=GetPixelChannelMapChannel(image,RedPixelChannel);
1172 if (black[i] != white[i])
cristyda1f9c12011-10-02 21:39:49 +00001173 image->colormap[j].red=stretch_map[GetPixelChannels(image)*
1174 ScaleQuantumToMap(ClampToQuantum(image->colormap[j].red))]+i;
cristy3ed852e2009-09-05 21:47:34 +00001175 }
cristyed231572011-07-14 02:18:59 +00001176 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001177 {
cristy465571b2011-08-21 20:43:15 +00001178 i=GetPixelChannelMapChannel(image,GreenPixelChannel);
1179 if (black[i] != white[i])
cristyda1f9c12011-10-02 21:39:49 +00001180 image->colormap[j].green=stretch_map[GetPixelChannels(image)*
1181 ScaleQuantumToMap(ClampToQuantum(image->colormap[j].green))]+i;
cristy3ed852e2009-09-05 21:47:34 +00001182 }
cristyed231572011-07-14 02:18:59 +00001183 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001184 {
cristy465571b2011-08-21 20:43:15 +00001185 i=GetPixelChannelMapChannel(image,BluePixelChannel);
1186 if (black[i] != white[i])
cristyda1f9c12011-10-02 21:39:49 +00001187 image->colormap[j].blue=stretch_map[GetPixelChannels(image)*
1188 ScaleQuantumToMap(ClampToQuantum(image->colormap[j].blue))]+i;
cristy3ed852e2009-09-05 21:47:34 +00001189 }
cristyed231572011-07-14 02:18:59 +00001190 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001191 {
cristy465571b2011-08-21 20:43:15 +00001192 i=GetPixelChannelMapChannel(image,AlphaPixelChannel);
1193 if (black[i] != white[i])
cristyda1f9c12011-10-02 21:39:49 +00001194 image->colormap[j].alpha=stretch_map[GetPixelChannels(image)*
1195 ScaleQuantumToMap(ClampToQuantum(image->colormap[j].alpha))]+i;
cristy3ed852e2009-09-05 21:47:34 +00001196 }
1197 }
1198 }
1199 /*
cristy465571b2011-08-21 20:43:15 +00001200 Stretch-contrast image.
cristy3ed852e2009-09-05 21:47:34 +00001201 */
1202 status=MagickTrue;
1203 progress=0;
cristyb5d5f722009-11-04 03:03:49 +00001204#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristye6178502011-12-23 17:02:29 +00001205 #pragma omp parallel for schedule(static,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001206#endif
cristybb503372010-05-27 20:51:26 +00001207 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001208 {
cristy4c08aed2011-07-01 19:47:50 +00001209 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00001210 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001211
cristy8d4629b2010-08-30 17:59:46 +00001212 register ssize_t
1213 x;
1214
cristy3ed852e2009-09-05 21:47:34 +00001215 if (status == MagickFalse)
1216 continue;
1217 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristyacd2ed22011-08-30 01:44:23 +00001218 if (q == (Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001219 {
1220 status=MagickFalse;
1221 continue;
1222 }
cristybb503372010-05-27 20:51:26 +00001223 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001224 {
cristy465571b2011-08-21 20:43:15 +00001225 register ssize_t
1226 i;
1227
1228 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1229 {
cristyabace412011-12-11 15:56:53 +00001230 PixelChannel
1231 channel;
1232
cristy465571b2011-08-21 20:43:15 +00001233 PixelTrait
1234 traits;
1235
cristyabace412011-12-11 15:56:53 +00001236 channel=GetPixelChannelMapChannel(image,i);
1237 traits=GetPixelChannelMapTraits(image,channel);
cristy7c0a0a42011-08-23 17:57:25 +00001238 if (((traits & UpdatePixelTrait) != 0) && (black[i] != white[i]))
cristy465571b2011-08-21 20:43:15 +00001239 q[i]=ClampToQuantum(stretch_map[GetPixelChannels(image)*
1240 ScaleQuantumToMap(q[i])+i]);
1241 }
cristyed231572011-07-14 02:18:59 +00001242 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00001243 }
1244 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
1245 status=MagickFalse;
1246 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1247 {
1248 MagickBooleanType
1249 proceed;
1250
cristyb5d5f722009-11-04 03:03:49 +00001251#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy564a5692012-01-20 23:56:26 +00001252 #pragma omp critical (MagickCore_ContrastStretchImage)
cristy3ed852e2009-09-05 21:47:34 +00001253#endif
1254 proceed=SetImageProgress(image,ContrastStretchImageTag,progress++,
1255 image->rows);
1256 if (proceed == MagickFalse)
1257 status=MagickFalse;
1258 }
1259 }
1260 image_view=DestroyCacheView(image_view);
cristyf45fec72011-08-23 16:02:32 +00001261 stretch_map=(double *) RelinquishMagickMemory(stretch_map);
1262 white=(double *) RelinquishMagickMemory(white);
1263 black=(double *) RelinquishMagickMemory(black);
cristy3ed852e2009-09-05 21:47:34 +00001264 return(status);
1265}
1266
1267/*
1268%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1269% %
1270% %
1271% %
1272% E n h a n c e I m a g e %
1273% %
1274% %
1275% %
1276%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1277%
1278% EnhanceImage() applies a digital filter that improves the quality of a
1279% noisy image.
1280%
1281% The format of the EnhanceImage method is:
1282%
1283% Image *EnhanceImage(const Image *image,ExceptionInfo *exception)
1284%
1285% A description of each parameter follows:
1286%
1287% o image: the image.
1288%
1289% o exception: return any errors or warnings in this structure.
1290%
1291*/
1292MagickExport Image *EnhanceImage(const Image *image,ExceptionInfo *exception)
1293{
cristy6d8c3d72011-08-22 01:20:01 +00001294#define EnhancePixel(weight) \
cristy0beccfa2011-09-25 20:47:53 +00001295 mean=((MagickRealType) r[i]+GetPixelChannel(enhance_image,channel,q))/2.0; \
1296 distance=(MagickRealType) r[i]-(MagickRealType) GetPixelChannel( \
1297 enhance_image,channel,q); \
cristy3ed852e2009-09-05 21:47:34 +00001298 distance_squared=QuantumScale*(2.0*((MagickRealType) QuantumRange+1.0)+ \
cristy6d8c3d72011-08-22 01:20:01 +00001299 mean)*distance*distance; \
cristy3ed852e2009-09-05 21:47:34 +00001300 if (distance_squared < ((MagickRealType) QuantumRange*(MagickRealType) \
1301 QuantumRange/25.0f)) \
1302 { \
cristy6d8c3d72011-08-22 01:20:01 +00001303 aggregate+=(weight)*r[i]; \
cristy3ed852e2009-09-05 21:47:34 +00001304 total_weight+=(weight); \
1305 } \
cristy6d8c3d72011-08-22 01:20:01 +00001306 r+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00001307#define EnhanceImageTag "Enhance/Image"
1308
cristyc4c8d132010-01-07 01:58:38 +00001309 CacheView
1310 *enhance_view,
1311 *image_view;
1312
cristy3ed852e2009-09-05 21:47:34 +00001313 Image
1314 *enhance_image;
1315
cristy3ed852e2009-09-05 21:47:34 +00001316 MagickBooleanType
1317 status;
1318
cristybb503372010-05-27 20:51:26 +00001319 MagickOffsetType
1320 progress;
1321
cristybb503372010-05-27 20:51:26 +00001322 ssize_t
1323 y;
1324
cristy3ed852e2009-09-05 21:47:34 +00001325 /*
1326 Initialize enhanced image attributes.
1327 */
1328 assert(image != (const Image *) NULL);
1329 assert(image->signature == MagickSignature);
1330 if (image->debug != MagickFalse)
1331 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1332 assert(exception != (ExceptionInfo *) NULL);
1333 assert(exception->signature == MagickSignature);
cristy3ed852e2009-09-05 21:47:34 +00001334 enhance_image=CloneImage(image,image->columns,image->rows,MagickTrue,
1335 exception);
1336 if (enhance_image == (Image *) NULL)
1337 return((Image *) NULL);
cristy574cc262011-08-05 01:23:58 +00001338 if (SetImageStorageClass(enhance_image,DirectClass,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00001339 {
cristy3ed852e2009-09-05 21:47:34 +00001340 enhance_image=DestroyImage(enhance_image);
1341 return((Image *) NULL);
1342 }
1343 /*
1344 Enhance image.
1345 */
1346 status=MagickTrue;
1347 progress=0;
cristy3ed852e2009-09-05 21:47:34 +00001348 image_view=AcquireCacheView(image);
1349 enhance_view=AcquireCacheView(enhance_image);
cristyb5d5f722009-11-04 03:03:49 +00001350#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyd36a25e2012-01-18 14:30:53 +00001351 #pragma omp parallel for schedule(static,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001352#endif
cristybb503372010-05-27 20:51:26 +00001353 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001354 {
cristy4c08aed2011-07-01 19:47:50 +00001355 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00001356 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00001357
cristy4c08aed2011-07-01 19:47:50 +00001358 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00001359 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001360
cristy8d4629b2010-08-30 17:59:46 +00001361 register ssize_t
1362 x;
1363
cristy6d8c3d72011-08-22 01:20:01 +00001364 ssize_t
1365 center;
1366
cristy3ed852e2009-09-05 21:47:34 +00001367 if (status == MagickFalse)
1368 continue;
1369 p=GetCacheViewVirtualPixels(image_view,-2,y-2,image->columns+4,5,exception);
1370 q=QueueCacheViewAuthenticPixels(enhance_view,0,y,enhance_image->columns,1,
1371 exception);
cristy4c08aed2011-07-01 19:47:50 +00001372 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00001373 {
1374 status=MagickFalse;
1375 continue;
1376 }
cristyf45fec72011-08-23 16:02:32 +00001377 center=(ssize_t) GetPixelChannels(image)*(2*(image->columns+4)+2);
cristybb503372010-05-27 20:51:26 +00001378 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001379 {
cristy6d8c3d72011-08-22 01:20:01 +00001380 register ssize_t
1381 i;
cristy3ed852e2009-09-05 21:47:34 +00001382
cristy6d8c3d72011-08-22 01:20:01 +00001383 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1384 {
1385 MagickRealType
1386 aggregate,
1387 distance,
1388 distance_squared,
1389 mean,
1390 total_weight;
cristy3ed852e2009-09-05 21:47:34 +00001391
cristy6d8c3d72011-08-22 01:20:01 +00001392 PixelChannel
1393 channel;
cristy3ed852e2009-09-05 21:47:34 +00001394
cristy6d8c3d72011-08-22 01:20:01 +00001395 PixelTrait
1396 enhance_traits,
1397 traits;
cristy3ed852e2009-09-05 21:47:34 +00001398
cristy6d8c3d72011-08-22 01:20:01 +00001399 register const Quantum
1400 *restrict r;
1401
cristye2a912b2011-12-05 20:02:07 +00001402 channel=GetPixelChannelMapChannel(image,i);
cristyabace412011-12-11 15:56:53 +00001403 traits=GetPixelChannelMapTraits(image,channel);
cristy6d8c3d72011-08-22 01:20:01 +00001404 enhance_traits=GetPixelChannelMapTraits(enhance_image,channel);
cristy010d7d12011-08-31 01:02:48 +00001405 if ((traits == UndefinedPixelTrait) ||
1406 (enhance_traits == UndefinedPixelTrait))
cristy6d8c3d72011-08-22 01:20:01 +00001407 continue;
cristy0beccfa2011-09-25 20:47:53 +00001408 SetPixelChannel(enhance_image,channel,p[center+i],q);
cristy6d8c3d72011-08-22 01:20:01 +00001409 if ((enhance_traits & CopyPixelTrait) != 0)
1410 continue;
1411 /*
1412 Compute weighted average of target pixel color components.
1413 */
1414 aggregate=0.0;
1415 total_weight=0.0;
cristyf45fec72011-08-23 16:02:32 +00001416 r=p;
cristy6d8c3d72011-08-22 01:20:01 +00001417 EnhancePixel(5.0); EnhancePixel(8.0); EnhancePixel(10.0);
1418 EnhancePixel(8.0); EnhancePixel(5.0);
1419 r=p+1*GetPixelChannels(image)*(image->columns+4);
1420 EnhancePixel(8.0); EnhancePixel(20.0); EnhancePixel(40.0);
1421 EnhancePixel(20.0); EnhancePixel(8.0);
1422 r=p+2*GetPixelChannels(image)*(image->columns+4);
1423 EnhancePixel(10.0); EnhancePixel(40.0); EnhancePixel(80.0);
1424 EnhancePixel(40.0); EnhancePixel(10.0);
1425 r=p+3*GetPixelChannels(image)*(image->columns+4);
1426 EnhancePixel(8.0); EnhancePixel(20.0); EnhancePixel(40.0);
1427 EnhancePixel(20.0); EnhancePixel(8.0);
1428 r=p+4*GetPixelChannels(image)*(image->columns+4);
1429 EnhancePixel(5.0); EnhancePixel(8.0); EnhancePixel(10.0);
1430 EnhancePixel(8.0); EnhancePixel(5.0);
cristy0beccfa2011-09-25 20:47:53 +00001431 SetPixelChannel(enhance_image,channel,ClampToQuantum(aggregate/
1432 total_weight),q);
cristy6d8c3d72011-08-22 01:20:01 +00001433 }
cristyed231572011-07-14 02:18:59 +00001434 p+=GetPixelChannels(image);
1435 q+=GetPixelChannels(enhance_image);
cristy3ed852e2009-09-05 21:47:34 +00001436 }
1437 if (SyncCacheViewAuthenticPixels(enhance_view,exception) == MagickFalse)
1438 status=MagickFalse;
1439 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1440 {
1441 MagickBooleanType
1442 proceed;
1443
cristyb5d5f722009-11-04 03:03:49 +00001444#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy564a5692012-01-20 23:56:26 +00001445 #pragma omp critical (MagickCore_EnhanceImage)
cristy3ed852e2009-09-05 21:47:34 +00001446#endif
1447 proceed=SetImageProgress(image,EnhanceImageTag,progress++,image->rows);
1448 if (proceed == MagickFalse)
1449 status=MagickFalse;
1450 }
1451 }
1452 enhance_view=DestroyCacheView(enhance_view);
1453 image_view=DestroyCacheView(image_view);
1454 return(enhance_image);
1455}
1456
1457/*
1458%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1459% %
1460% %
1461% %
1462% E q u a l i z e I m a g e %
1463% %
1464% %
1465% %
1466%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1467%
1468% EqualizeImage() applies a histogram equalization to the image.
1469%
1470% The format of the EqualizeImage method is:
1471%
cristy6d8c3d72011-08-22 01:20:01 +00001472% MagickBooleanType EqualizeImage(Image *image,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001473%
1474% A description of each parameter follows:
1475%
1476% o image: the image.
1477%
cristy6d8c3d72011-08-22 01:20:01 +00001478% o exception: return any errors or warnings in this structure.
cristy3ed852e2009-09-05 21:47:34 +00001479%
1480*/
cristy6d8c3d72011-08-22 01:20:01 +00001481MagickExport MagickBooleanType EqualizeImage(Image *image,
1482 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001483{
cristy3ed852e2009-09-05 21:47:34 +00001484#define EqualizeImageTag "Equalize/Image"
1485
cristyc4c8d132010-01-07 01:58:38 +00001486 CacheView
1487 *image_view;
1488
cristy3ed852e2009-09-05 21:47:34 +00001489 MagickBooleanType
1490 status;
1491
cristybb503372010-05-27 20:51:26 +00001492 MagickOffsetType
1493 progress;
1494
cristyf45fec72011-08-23 16:02:32 +00001495 MagickRealType
cristy5f95f4f2011-10-23 01:01:01 +00001496 black[CompositePixelChannel],
cristy3ed852e2009-09-05 21:47:34 +00001497 *equalize_map,
1498 *histogram,
cristy3ed852e2009-09-05 21:47:34 +00001499 *map,
cristy5f95f4f2011-10-23 01:01:01 +00001500 white[CompositePixelChannel];
cristy3ed852e2009-09-05 21:47:34 +00001501
cristybb503372010-05-27 20:51:26 +00001502 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001503 i;
1504
cristy564a5692012-01-20 23:56:26 +00001505 size_t
1506 number_channels;
1507
cristybb503372010-05-27 20:51:26 +00001508 ssize_t
1509 y;
1510
cristy3ed852e2009-09-05 21:47:34 +00001511 /*
1512 Allocate and initialize histogram arrays.
1513 */
1514 assert(image != (Image *) NULL);
1515 assert(image->signature == MagickSignature);
1516 if (image->debug != MagickFalse)
1517 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
cristyf45fec72011-08-23 16:02:32 +00001518 equalize_map=(MagickRealType *) AcquireQuantumMemory(MaxMap+1UL,
1519 GetPixelChannels(image)*sizeof(*equalize_map));
1520 histogram=(MagickRealType *) AcquireQuantumMemory(MaxMap+1UL,
1521 GetPixelChannels(image)*sizeof(*histogram));
1522 map=(MagickRealType *) AcquireQuantumMemory(MaxMap+1UL,
1523 GetPixelChannels(image)*sizeof(*map));
1524 if ((equalize_map == (MagickRealType *) NULL) ||
1525 (histogram == (MagickRealType *) NULL) ||
1526 (map == (MagickRealType *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00001527 {
cristyf45fec72011-08-23 16:02:32 +00001528 if (map != (MagickRealType *) NULL)
1529 map=(MagickRealType *) RelinquishMagickMemory(map);
1530 if (histogram != (MagickRealType *) NULL)
1531 histogram=(MagickRealType *) RelinquishMagickMemory(histogram);
1532 if (equalize_map != (MagickRealType *) NULL)
1533 equalize_map=(MagickRealType *) RelinquishMagickMemory(equalize_map);
cristy3ed852e2009-09-05 21:47:34 +00001534 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
1535 image->filename);
1536 }
1537 /*
1538 Form histogram.
1539 */
cristyf45fec72011-08-23 16:02:32 +00001540 status=MagickTrue;
1541 (void) ResetMagickMemory(histogram,0,(MaxMap+1)*GetPixelChannels(image)*
1542 sizeof(*histogram));
1543 image_view=AcquireCacheView(image);
cristybb503372010-05-27 20:51:26 +00001544 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001545 {
cristy4c08aed2011-07-01 19:47:50 +00001546 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00001547 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00001548
cristybb503372010-05-27 20:51:26 +00001549 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001550 x;
1551
cristyf45fec72011-08-23 16:02:32 +00001552 if (status == MagickFalse)
1553 continue;
1554 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00001555 if (p == (const Quantum *) NULL)
cristyf45fec72011-08-23 16:02:32 +00001556 {
1557 status=MagickFalse;
1558 continue;
1559 }
cristybb503372010-05-27 20:51:26 +00001560 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001561 {
cristyf45fec72011-08-23 16:02:32 +00001562 register ssize_t
1563 i;
1564
1565 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1566 histogram[GetPixelChannels(image)*ScaleQuantumToMap(p[i])+i]++;
cristyed231572011-07-14 02:18:59 +00001567 p+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00001568 }
1569 }
1570 /*
1571 Integrate the histogram to get the equalization map.
1572 */
cristy564a5692012-01-20 23:56:26 +00001573 number_channels=GetPixelChannels(image);
cristyb5d5f722009-11-04 03:03:49 +00001574#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristye6178502011-12-23 17:02:29 +00001575 #pragma omp parallel for schedule(static,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001576#endif
cristy564a5692012-01-20 23:56:26 +00001577 for (i=0; i < (size_t) number_channels; i++)
cristy3ed852e2009-09-05 21:47:34 +00001578 {
cristyf45fec72011-08-23 16:02:32 +00001579 MagickRealType
1580 intensity;
1581
1582 register ssize_t
1583 j;
1584
1585 intensity=0.0;
1586 for (j=0; j <= (ssize_t) MaxMap; j++)
1587 {
1588 intensity+=histogram[GetPixelChannels(image)*j+i];
1589 map[GetPixelChannels(image)*j+i]=intensity;
1590 }
cristy3ed852e2009-09-05 21:47:34 +00001591 }
cristyf45fec72011-08-23 16:02:32 +00001592 (void) ResetMagickMemory(equalize_map,0,(MaxMap+1)*GetPixelChannels(image)*
1593 sizeof(*equalize_map));
cristy564a5692012-01-20 23:56:26 +00001594 number_channels=GetPixelChannels(image);
cristyf45fec72011-08-23 16:02:32 +00001595#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristye6178502011-12-23 17:02:29 +00001596 #pragma omp parallel for schedule(static,4) shared(progress,status)
cristyf45fec72011-08-23 16:02:32 +00001597#endif
cristy564a5692012-01-20 23:56:26 +00001598 for (i=0; i < (ssize_t) number_channels; i++)
cristyf45fec72011-08-23 16:02:32 +00001599 {
1600 register ssize_t
1601 j;
1602
1603 black[i]=map[i];
1604 white[i]=map[GetPixelChannels(image)*MaxMap+i];
1605 if (black[i] != white[i])
1606 for (j=0; j <= (ssize_t) MaxMap; j++)
1607 equalize_map[GetPixelChannels(image)*j+i]=(MagickRealType)
1608 ScaleMapToQuantum((MagickRealType) ((MaxMap*(map[
1609 GetPixelChannels(image)*j+i]-black[i]))/(white[i]-black[i])));
1610 }
1611 histogram=(MagickRealType *) RelinquishMagickMemory(histogram);
1612 map=(MagickRealType *) RelinquishMagickMemory(map);
cristy3ed852e2009-09-05 21:47:34 +00001613 if (image->storage_class == PseudoClass)
1614 {
cristyf54798b2011-11-21 18:38:23 +00001615 PixelChannel
1616 channel;
1617
cristyf45fec72011-08-23 16:02:32 +00001618 register ssize_t
1619 j;
1620
cristy3ed852e2009-09-05 21:47:34 +00001621 /*
1622 Equalize colormap.
1623 */
cristyb5d5f722009-11-04 03:03:49 +00001624#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristye6178502011-12-23 17:02:29 +00001625 #pragma omp parallel for schedule(static,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001626#endif
cristyf45fec72011-08-23 16:02:32 +00001627 for (j=0; j < (ssize_t) image->colors; j++)
cristy3ed852e2009-09-05 21:47:34 +00001628 {
cristyf54798b2011-11-21 18:38:23 +00001629 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristyf45fec72011-08-23 16:02:32 +00001630 {
cristyf54798b2011-11-21 18:38:23 +00001631 channel=GetPixelChannelMapChannel(image,RedPixelChannel);
1632 if (black[channel] != white[channel])
cristyda1f9c12011-10-02 21:39:49 +00001633 image->colormap[j].red=equalize_map[GetPixelChannels(image)*
cristyf54798b2011-11-21 18:38:23 +00001634 ScaleQuantumToMap(ClampToQuantum(image->colormap[j].red))]+
1635 channel;
cristyf45fec72011-08-23 16:02:32 +00001636 }
cristyf54798b2011-11-21 18:38:23 +00001637 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristyf45fec72011-08-23 16:02:32 +00001638 {
cristyf54798b2011-11-21 18:38:23 +00001639 channel=GetPixelChannelMapChannel(image,GreenPixelChannel);
1640 if (black[channel] != white[channel])
cristyda1f9c12011-10-02 21:39:49 +00001641 image->colormap[j].green=equalize_map[GetPixelChannels(image)*
cristyf54798b2011-11-21 18:38:23 +00001642 ScaleQuantumToMap(ClampToQuantum(image->colormap[j].green))]+
1643 channel;
cristyf45fec72011-08-23 16:02:32 +00001644 }
cristyf54798b2011-11-21 18:38:23 +00001645 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristyf45fec72011-08-23 16:02:32 +00001646 {
cristyf54798b2011-11-21 18:38:23 +00001647 channel=GetPixelChannelMapChannel(image,BluePixelChannel);
1648 if (black[channel] != white[channel])
cristyda1f9c12011-10-02 21:39:49 +00001649 image->colormap[j].blue=equalize_map[GetPixelChannels(image)*
cristyf54798b2011-11-21 18:38:23 +00001650 ScaleQuantumToMap(ClampToQuantum(image->colormap[j].blue))]+
1651 channel;
cristyf45fec72011-08-23 16:02:32 +00001652 }
cristyf54798b2011-11-21 18:38:23 +00001653 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristyf45fec72011-08-23 16:02:32 +00001654 {
cristyf54798b2011-11-21 18:38:23 +00001655 channel=GetPixelChannelMapChannel(image,AlphaPixelChannel);
1656 if (black[channel] != white[channel])
cristyda1f9c12011-10-02 21:39:49 +00001657 image->colormap[j].alpha=equalize_map[GetPixelChannels(image)*
cristyf54798b2011-11-21 18:38:23 +00001658 ScaleQuantumToMap(ClampToQuantum(image->colormap[j].alpha))]+
1659 channel;
cristyf45fec72011-08-23 16:02:32 +00001660 }
cristy3ed852e2009-09-05 21:47:34 +00001661 }
1662 }
1663 /*
1664 Equalize image.
1665 */
cristy3ed852e2009-09-05 21:47:34 +00001666 progress=0;
cristyb5d5f722009-11-04 03:03:49 +00001667#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristye6178502011-12-23 17:02:29 +00001668 #pragma omp parallel for schedule(static,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001669#endif
cristybb503372010-05-27 20:51:26 +00001670 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001671 {
cristy4c08aed2011-07-01 19:47:50 +00001672 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00001673 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001674
cristy8d4629b2010-08-30 17:59:46 +00001675 register ssize_t
1676 x;
1677
cristy3ed852e2009-09-05 21:47:34 +00001678 if (status == MagickFalse)
1679 continue;
1680 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristyacd2ed22011-08-30 01:44:23 +00001681 if (q == (Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001682 {
1683 status=MagickFalse;
1684 continue;
1685 }
cristybb503372010-05-27 20:51:26 +00001686 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001687 {
cristyf45fec72011-08-23 16:02:32 +00001688 register ssize_t
1689 i;
1690
1691 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1692 {
cristyabace412011-12-11 15:56:53 +00001693 PixelChannel
1694 channel;
1695
cristyf45fec72011-08-23 16:02:32 +00001696 PixelTrait
1697 traits;
1698
cristyabace412011-12-11 15:56:53 +00001699 channel=GetPixelChannelMapChannel(image,i);
1700 traits=GetPixelChannelMapTraits(image,channel);
cristy7c0a0a42011-08-23 17:57:25 +00001701 if (((traits & UpdatePixelTrait) != 0) && (black[i] != white[i]))
cristyf45fec72011-08-23 16:02:32 +00001702 q[i]=ClampToQuantum(equalize_map[GetPixelChannels(image)*
1703 ScaleQuantumToMap(q[i])+i]);
1704 }
cristyed231572011-07-14 02:18:59 +00001705 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00001706 }
1707 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
1708 status=MagickFalse;
1709 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1710 {
1711 MagickBooleanType
1712 proceed;
1713
cristyb5d5f722009-11-04 03:03:49 +00001714#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy564a5692012-01-20 23:56:26 +00001715 #pragma omp critical (MagickCore_EqualizeImage)
cristy3ed852e2009-09-05 21:47:34 +00001716#endif
1717 proceed=SetImageProgress(image,EqualizeImageTag,progress++,image->rows);
1718 if (proceed == MagickFalse)
1719 status=MagickFalse;
1720 }
1721 }
1722 image_view=DestroyCacheView(image_view);
cristyf45fec72011-08-23 16:02:32 +00001723 equalize_map=(MagickRealType *) RelinquishMagickMemory(equalize_map);
cristy3ed852e2009-09-05 21:47:34 +00001724 return(status);
1725}
1726
1727/*
1728%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1729% %
1730% %
1731% %
1732% G a m m a I m a g e %
1733% %
1734% %
1735% %
1736%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1737%
1738% GammaImage() gamma-corrects a particular image channel. The same
1739% image viewed on different devices will have perceptual differences in the
1740% way the image's intensities are represented on the screen. Specify
1741% individual gamma levels for the red, green, and blue channels, or adjust
1742% all three with the gamma parameter. Values typically range from 0.8 to 2.3.
1743%
1744% You can also reduce the influence of a particular channel with a gamma
1745% value of 0.
1746%
1747% The format of the GammaImage method is:
1748%
cristyb3e7c6c2011-07-24 01:43:55 +00001749% MagickBooleanType GammaImage(Image *image,const double gamma,
1750% ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001751%
1752% A description of each parameter follows:
1753%
1754% o image: the image.
1755%
cristya6360142011-03-23 23:08:04 +00001756% o level: the image gamma as a string (e.g. 1.6,1.2,1.0).
1757%
cristy3ed852e2009-09-05 21:47:34 +00001758% o gamma: the image gamma.
1759%
1760*/
cristyb3e7c6c2011-07-24 01:43:55 +00001761MagickExport MagickBooleanType GammaImage(Image *image,const double gamma,
1762 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001763{
1764#define GammaCorrectImageTag "GammaCorrect/Image"
1765
cristyc4c8d132010-01-07 01:58:38 +00001766 CacheView
1767 *image_view;
1768
cristy3ed852e2009-09-05 21:47:34 +00001769 MagickBooleanType
1770 status;
1771
cristybb503372010-05-27 20:51:26 +00001772 MagickOffsetType
1773 progress;
1774
cristy19593872012-01-22 02:00:33 +00001775 Quantum
1776 *gamma_map;
1777
cristybb503372010-05-27 20:51:26 +00001778 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001779 i;
1780
cristybb503372010-05-27 20:51:26 +00001781 ssize_t
1782 y;
1783
cristy3ed852e2009-09-05 21:47:34 +00001784 /*
1785 Allocate and initialize gamma maps.
1786 */
1787 assert(image != (Image *) NULL);
1788 assert(image->signature == MagickSignature);
1789 if (image->debug != MagickFalse)
1790 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1791 if (gamma == 1.0)
1792 return(MagickTrue);
cristy19593872012-01-22 02:00:33 +00001793 gamma_map=(Quantum *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*gamma_map));
1794 if (gamma_map == (Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001795 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
1796 image->filename);
1797 (void) ResetMagickMemory(gamma_map,0,(MaxMap+1)*sizeof(*gamma_map));
1798 if (gamma != 0.0)
cristyd476a8e2011-07-23 16:13:22 +00001799#if defined(MAGICKCORE_OPENMP_SUPPORT) && (MaxMap > 256)
cristy564a5692012-01-20 23:56:26 +00001800 #pragma omp parallel for
cristy3ed852e2009-09-05 21:47:34 +00001801#endif
cristybb503372010-05-27 20:51:26 +00001802 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy19593872012-01-22 02:00:33 +00001803 gamma_map[i]=ScaleMapToQuantum((MagickRealType) (MaxMap*pow((double) i/
1804 MaxMap,1.0/gamma)));
cristy3ed852e2009-09-05 21:47:34 +00001805 if (image->storage_class == PseudoClass)
1806 {
1807 /*
1808 Gamma-correct colormap.
1809 */
cristyb5d5f722009-11-04 03:03:49 +00001810#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy19593872012-01-22 02:00:33 +00001811 #pragma omp parallel for schedule(static) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001812#endif
cristybb503372010-05-27 20:51:26 +00001813 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00001814 {
cristyed231572011-07-14 02:18:59 +00001815 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001816 image->colormap[i].red=gamma_map[
cristyda1f9c12011-10-02 21:39:49 +00001817 ScaleQuantumToMap(ClampToQuantum(image->colormap[i].red))];
cristyed231572011-07-14 02:18:59 +00001818 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001819 image->colormap[i].green=gamma_map[
cristyda1f9c12011-10-02 21:39:49 +00001820 ScaleQuantumToMap(ClampToQuantum(image->colormap[i].green))];
cristyed231572011-07-14 02:18:59 +00001821 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001822 image->colormap[i].blue=gamma_map[
cristyda1f9c12011-10-02 21:39:49 +00001823 ScaleQuantumToMap(ClampToQuantum(image->colormap[i].blue))];
cristyed231572011-07-14 02:18:59 +00001824 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001825 image->colormap[i].alpha=gamma_map[
cristyda1f9c12011-10-02 21:39:49 +00001826 ScaleQuantumToMap(ClampToQuantum(image->colormap[i].alpha))];
cristy3ed852e2009-09-05 21:47:34 +00001827 }
1828 }
1829 /*
1830 Gamma-correct image.
1831 */
1832 status=MagickTrue;
1833 progress=0;
cristy3ed852e2009-09-05 21:47:34 +00001834 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00001835#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristye6178502011-12-23 17:02:29 +00001836 #pragma omp parallel for schedule(static,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001837#endif
cristybb503372010-05-27 20:51:26 +00001838 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001839 {
cristy4c08aed2011-07-01 19:47:50 +00001840 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00001841 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001842
cristy8d4629b2010-08-30 17:59:46 +00001843 register ssize_t
1844 x;
1845
cristy3ed852e2009-09-05 21:47:34 +00001846 if (status == MagickFalse)
1847 continue;
1848 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristyacd2ed22011-08-30 01:44:23 +00001849 if (q == (Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001850 {
1851 status=MagickFalse;
1852 continue;
1853 }
cristybb503372010-05-27 20:51:26 +00001854 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001855 {
cristyd476a8e2011-07-23 16:13:22 +00001856 register ssize_t
1857 i;
1858
cristya30d9ba2011-07-23 21:00:48 +00001859 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
cristyd476a8e2011-07-23 16:13:22 +00001860 {
cristyabace412011-12-11 15:56:53 +00001861 PixelChannel
1862 channel;
1863
cristyd476a8e2011-07-23 16:13:22 +00001864 PixelTrait
1865 traits;
1866
cristyabace412011-12-11 15:56:53 +00001867 channel=GetPixelChannelMapChannel(image,i);
1868 traits=GetPixelChannelMapTraits(image,channel);
cristyd476a8e2011-07-23 16:13:22 +00001869 if ((traits & UpdatePixelTrait) != 0)
cristy19593872012-01-22 02:00:33 +00001870 q[i]=gamma_map[ScaleQuantumToMap(q[i])];
cristyd476a8e2011-07-23 16:13:22 +00001871 }
cristya30d9ba2011-07-23 21:00:48 +00001872 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00001873 }
cristy3ed852e2009-09-05 21:47:34 +00001874 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
1875 status=MagickFalse;
1876 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1877 {
1878 MagickBooleanType
1879 proceed;
1880
cristyb5d5f722009-11-04 03:03:49 +00001881#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy564a5692012-01-20 23:56:26 +00001882 #pragma omp critical (MagickCore_GammaImage)
cristy3ed852e2009-09-05 21:47:34 +00001883#endif
1884 proceed=SetImageProgress(image,GammaCorrectImageTag,progress++,
1885 image->rows);
1886 if (proceed == MagickFalse)
1887 status=MagickFalse;
1888 }
1889 }
1890 image_view=DestroyCacheView(image_view);
cristy19593872012-01-22 02:00:33 +00001891 gamma_map=(Quantum *) RelinquishMagickMemory(gamma_map);
cristy3ed852e2009-09-05 21:47:34 +00001892 if (image->gamma != 0.0)
1893 image->gamma*=gamma;
1894 return(status);
1895}
1896
1897/*
1898%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1899% %
1900% %
1901% %
1902% H a l d C l u t I m a g e %
1903% %
1904% %
1905% %
1906%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1907%
1908% HaldClutImage() applies a Hald color lookup table to the image. A Hald
1909% color lookup table is a 3-dimensional color cube mapped to 2 dimensions.
1910% Create it with the HALD coder. You can apply any color transformation to
1911% the Hald image and then use this method to apply the transform to the
1912% image.
1913%
1914% The format of the HaldClutImage method is:
1915%
cristy7c0a0a42011-08-23 17:57:25 +00001916% MagickBooleanType HaldClutImage(Image *image,Image *hald_image,
1917% ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001918%
1919% A description of each parameter follows:
1920%
1921% o image: the image, which is replaced by indexed CLUT values
1922%
1923% o hald_image: the color lookup table image for replacement color values.
1924%
cristy7c0a0a42011-08-23 17:57:25 +00001925% o exception: return any errors or warnings in this structure.
1926%
cristy3ed852e2009-09-05 21:47:34 +00001927*/
1928
1929static inline size_t MagickMin(const size_t x,const size_t y)
1930{
1931 if (x < y)
1932 return(x);
1933 return(y);
1934}
1935
1936MagickExport MagickBooleanType HaldClutImage(Image *image,
cristy7c0a0a42011-08-23 17:57:25 +00001937 const Image *hald_image,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001938{
cristy3ed852e2009-09-05 21:47:34 +00001939#define HaldClutImageTag "Clut/Image"
1940
1941 typedef struct _HaldInfo
1942 {
1943 MagickRealType
1944 x,
1945 y,
1946 z;
1947 } HaldInfo;
1948
cristyfa112112010-01-04 17:48:07 +00001949 CacheView
cristyd551fbc2011-03-31 18:07:46 +00001950 *hald_view,
cristyfa112112010-01-04 17:48:07 +00001951 *image_view;
1952
cristy3ed852e2009-09-05 21:47:34 +00001953 double
1954 width;
1955
cristy3ed852e2009-09-05 21:47:34 +00001956 MagickBooleanType
1957 status;
1958
cristybb503372010-05-27 20:51:26 +00001959 MagickOffsetType
1960 progress;
1961
cristy4c08aed2011-07-01 19:47:50 +00001962 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00001963 zero;
1964
cristy3ed852e2009-09-05 21:47:34 +00001965 size_t
1966 cube_size,
1967 length,
1968 level;
1969
cristybb503372010-05-27 20:51:26 +00001970 ssize_t
1971 y;
1972
cristy3ed852e2009-09-05 21:47:34 +00001973 assert(image != (Image *) NULL);
1974 assert(image->signature == MagickSignature);
1975 if (image->debug != MagickFalse)
1976 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1977 assert(hald_image != (Image *) NULL);
1978 assert(hald_image->signature == MagickSignature);
cristy574cc262011-08-05 01:23:58 +00001979 if (SetImageStorageClass(image,DirectClass,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00001980 return(MagickFalse);
1981 if (image->matte == MagickFalse)
cristy63240882011-08-05 19:05:27 +00001982 (void) SetImageAlphaChannel(image,OpaqueAlphaChannel,exception);
cristy3ed852e2009-09-05 21:47:34 +00001983 /*
1984 Hald clut image.
1985 */
1986 status=MagickTrue;
1987 progress=0;
1988 length=MagickMin(hald_image->columns,hald_image->rows);
1989 for (level=2; (level*level*level) < length; level++) ;
1990 level*=level;
1991 cube_size=level*level;
1992 width=(double) hald_image->columns;
cristy4c08aed2011-07-01 19:47:50 +00001993 GetPixelInfo(hald_image,&zero);
cristy3ed852e2009-09-05 21:47:34 +00001994 image_view=AcquireCacheView(image);
cristyd551fbc2011-03-31 18:07:46 +00001995 hald_view=AcquireCacheView(hald_image);
cristyb5d5f722009-11-04 03:03:49 +00001996#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristye6178502011-12-23 17:02:29 +00001997 #pragma omp parallel for schedule(static,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001998#endif
cristybb503372010-05-27 20:51:26 +00001999 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002000 {
cristy4c08aed2011-07-01 19:47:50 +00002001 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00002002 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002003
cristy8d4629b2010-08-30 17:59:46 +00002004 register ssize_t
2005 x;
2006
cristy3ed852e2009-09-05 21:47:34 +00002007 if (status == MagickFalse)
2008 continue;
2009 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristyacd2ed22011-08-30 01:44:23 +00002010 if (q == (Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00002011 {
2012 status=MagickFalse;
2013 continue;
2014 }
cristybb503372010-05-27 20:51:26 +00002015 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002016 {
cristy7c0a0a42011-08-23 17:57:25 +00002017 double
2018 offset;
2019
2020 HaldInfo
2021 point;
2022
2023 PixelInfo
2024 pixel,
2025 pixel1,
2026 pixel2,
2027 pixel3,
2028 pixel4;
2029
cristy4c08aed2011-07-01 19:47:50 +00002030 point.x=QuantumScale*(level-1.0)*GetPixelRed(image,q);
2031 point.y=QuantumScale*(level-1.0)*GetPixelGreen(image,q);
2032 point.z=QuantumScale*(level-1.0)*GetPixelBlue(image,q);
cristy3ed852e2009-09-05 21:47:34 +00002033 offset=point.x+level*floor(point.y)+cube_size*floor(point.z);
2034 point.x-=floor(point.x);
2035 point.y-=floor(point.y);
2036 point.z-=floor(point.z);
cristy7c0a0a42011-08-23 17:57:25 +00002037 pixel1=zero;
2038 (void) InterpolatePixelInfo(image,hald_view,image->interpolate,
2039 fmod(offset,width),floor(offset/width),&pixel1,exception);
2040 pixel2=zero;
2041 (void) InterpolatePixelInfo(image,hald_view,image->interpolate,
2042 fmod(offset+level,width),floor((offset+level)/width),&pixel2,exception);
2043 pixel3=zero;
2044 CompositePixelInfoAreaBlend(&pixel1,pixel1.alpha,&pixel2,pixel2.alpha,
2045 point.y,&pixel3);
cristy3ed852e2009-09-05 21:47:34 +00002046 offset+=cube_size;
cristy7c0a0a42011-08-23 17:57:25 +00002047 (void) InterpolatePixelInfo(image,hald_view,image->interpolate,
2048 fmod(offset,width),floor(offset/width),&pixel1,exception);
2049 (void) InterpolatePixelInfo(image,hald_view,image->interpolate,
2050 fmod(offset+level,width),floor((offset+level)/width),&pixel2,exception);
2051 pixel4=zero;
2052 CompositePixelInfoAreaBlend(&pixel1,pixel1.alpha,&pixel2,pixel2.alpha,
2053 point.y,&pixel4);
2054 pixel=zero;
2055 CompositePixelInfoAreaBlend(&pixel3,pixel3.alpha,&pixel4,pixel4.alpha,
2056 point.z,&pixel);
cristyed231572011-07-14 02:18:59 +00002057 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristyf45fec72011-08-23 16:02:32 +00002058 SetPixelRed(image,ClampToQuantum(pixel.red),q);
cristyed231572011-07-14 02:18:59 +00002059 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristyf45fec72011-08-23 16:02:32 +00002060 SetPixelGreen(image,ClampToQuantum(pixel.green),q);
cristyed231572011-07-14 02:18:59 +00002061 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristyf45fec72011-08-23 16:02:32 +00002062 SetPixelBlue(image,ClampToQuantum(pixel.blue),q);
cristyed231572011-07-14 02:18:59 +00002063 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00002064 (image->colorspace == CMYKColorspace))
cristyf45fec72011-08-23 16:02:32 +00002065 SetPixelBlack(image,ClampToQuantum(pixel.black),q);
2066 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) &&
2067 (image->matte != MagickFalse))
2068 SetPixelAlpha(image,ClampToQuantum(pixel.alpha),q);
cristyed231572011-07-14 02:18:59 +00002069 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00002070 }
2071 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2072 status=MagickFalse;
2073 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2074 {
2075 MagickBooleanType
2076 proceed;
2077
cristyb5d5f722009-11-04 03:03:49 +00002078#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy564a5692012-01-20 23:56:26 +00002079 #pragma omp critical (MagickCore_HaldClutImage)
cristy3ed852e2009-09-05 21:47:34 +00002080#endif
2081 proceed=SetImageProgress(image,HaldClutImageTag,progress++,image->rows);
2082 if (proceed == MagickFalse)
2083 status=MagickFalse;
2084 }
2085 }
cristyd551fbc2011-03-31 18:07:46 +00002086 hald_view=DestroyCacheView(hald_view);
cristy3ed852e2009-09-05 21:47:34 +00002087 image_view=DestroyCacheView(image_view);
cristy3ed852e2009-09-05 21:47:34 +00002088 return(status);
2089}
2090
2091/*
2092%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2093% %
2094% %
2095% %
2096% L e v e l I m a g e %
2097% %
2098% %
2099% %
2100%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2101%
2102% LevelImage() adjusts the levels of a particular image channel by
2103% scaling the colors falling between specified white and black points to
2104% the full available quantum range.
2105%
2106% The parameters provided represent the black, and white points. The black
2107% point specifies the darkest color in the image. Colors darker than the
2108% black point are set to zero. White point specifies the lightest color in
2109% the image. Colors brighter than the white point are set to the maximum
2110% quantum value.
2111%
2112% If a '!' flag is given, map black and white colors to the given levels
2113% rather than mapping those levels to black and white. See
cristy50fbc382011-07-07 02:19:17 +00002114% LevelizeImage() below.
cristy3ed852e2009-09-05 21:47:34 +00002115%
2116% Gamma specifies a gamma correction to apply to the image.
2117%
2118% The format of the LevelImage method is:
2119%
cristy01e9afd2011-08-10 17:38:41 +00002120% MagickBooleanType LevelImage(Image *image,const double black_point,
2121% const double white_point,const double gamma,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002122%
2123% A description of each parameter follows:
2124%
2125% o image: the image.
2126%
cristy01e9afd2011-08-10 17:38:41 +00002127% o black_point: The level to map zero (black) to.
2128%
2129% o white_point: The level to map QuantumRange (white) to.
2130%
2131% o exception: return any errors or warnings in this structure.
cristy3ed852e2009-09-05 21:47:34 +00002132%
2133*/
cristy780e9ef2011-12-18 23:33:50 +00002134
cristyb8b0d162011-12-18 23:41:28 +00002135static inline MagickRealType LevelPixel(const double black_point,
cristy780e9ef2011-12-18 23:33:50 +00002136 const double white_point,const double gamma,const MagickRealType pixel)
2137{
2138 double
2139 level_pixel,
2140 scale;
2141
2142 if (pixel < black_point)
2143 return(0.0);
2144 if (pixel > white_point)
cristy0113ca22011-12-19 01:16:22 +00002145 return((MagickRealType) QuantumRange);
cristy780e9ef2011-12-18 23:33:50 +00002146 scale=(white_point != black_point) ? 1.0/(white_point-black_point) : 1.0;
2147 level_pixel=(MagickRealType) QuantumRange*pow(scale*((double) pixel-
2148 black_point),1.0/gamma);
2149 return(level_pixel);
2150}
2151
cristy7c0a0a42011-08-23 17:57:25 +00002152MagickExport MagickBooleanType LevelImage(Image *image,const double black_point,
2153 const double white_point,const double gamma,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002154{
2155#define LevelImageTag "Level/Image"
cristy3ed852e2009-09-05 21:47:34 +00002156
cristyc4c8d132010-01-07 01:58:38 +00002157 CacheView
2158 *image_view;
2159
cristy3ed852e2009-09-05 21:47:34 +00002160 MagickBooleanType
2161 status;
2162
cristybb503372010-05-27 20:51:26 +00002163 MagickOffsetType
2164 progress;
2165
cristy8d4629b2010-08-30 17:59:46 +00002166 register ssize_t
2167 i;
2168
cristybb503372010-05-27 20:51:26 +00002169 ssize_t
2170 y;
2171
cristy3ed852e2009-09-05 21:47:34 +00002172 /*
2173 Allocate and initialize levels map.
2174 */
2175 assert(image != (Image *) NULL);
2176 assert(image->signature == MagickSignature);
2177 if (image->debug != MagickFalse)
2178 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2179 if (image->storage_class == PseudoClass)
cristyb5d5f722009-11-04 03:03:49 +00002180#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy564a5692012-01-20 23:56:26 +00002181 #pragma omp parallel for schedule(static,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002182#endif
cristybb503372010-05-27 20:51:26 +00002183 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00002184 {
2185 /*
2186 Level colormap.
2187 */
cristyed231572011-07-14 02:18:59 +00002188 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy780e9ef2011-12-18 23:33:50 +00002189 image->colormap[i].red=(double) ClampToQuantum(LevelPixel(black_point,
2190 white_point,gamma,image->colormap[i].red));
cristyed231572011-07-14 02:18:59 +00002191 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy780e9ef2011-12-18 23:33:50 +00002192 image->colormap[i].green=(double) ClampToQuantum(LevelPixel(black_point,
2193 white_point,gamma,image->colormap[i].green));
cristyed231572011-07-14 02:18:59 +00002194 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy780e9ef2011-12-18 23:33:50 +00002195 image->colormap[i].blue=(double) ClampToQuantum(LevelPixel(black_point,
2196 white_point,gamma,image->colormap[i].blue));
cristyed231572011-07-14 02:18:59 +00002197 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy780e9ef2011-12-18 23:33:50 +00002198 image->colormap[i].alpha=(double) ClampToQuantum(LevelPixel(black_point,
2199 white_point,gamma,image->colormap[i].alpha));
cristy3ed852e2009-09-05 21:47:34 +00002200 }
2201 /*
2202 Level image.
2203 */
2204 status=MagickTrue;
2205 progress=0;
cristy3ed852e2009-09-05 21:47:34 +00002206 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00002207#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristye6178502011-12-23 17:02:29 +00002208 #pragma omp parallel for schedule(static,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002209#endif
cristybb503372010-05-27 20:51:26 +00002210 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002211 {
cristy4c08aed2011-07-01 19:47:50 +00002212 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00002213 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002214
cristy8d4629b2010-08-30 17:59:46 +00002215 register ssize_t
2216 x;
2217
cristy3ed852e2009-09-05 21:47:34 +00002218 if (status == MagickFalse)
2219 continue;
2220 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristyacd2ed22011-08-30 01:44:23 +00002221 if (q == (Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00002222 {
2223 status=MagickFalse;
2224 continue;
2225 }
cristybb503372010-05-27 20:51:26 +00002226 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002227 {
cristy7c0a0a42011-08-23 17:57:25 +00002228 register ssize_t
2229 i;
2230
2231 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
2232 {
cristyabace412011-12-11 15:56:53 +00002233 PixelChannel
2234 channel;
2235
cristy7c0a0a42011-08-23 17:57:25 +00002236 PixelTrait
2237 traits;
2238
cristyabace412011-12-11 15:56:53 +00002239 channel=GetPixelChannelMapChannel(image,i);
2240 traits=GetPixelChannelMapTraits(image,channel);
cristyb8b0d162011-12-18 23:41:28 +00002241 if (traits == UndefinedPixelTrait)
cristy7c0a0a42011-08-23 17:57:25 +00002242 continue;
cristy780e9ef2011-12-18 23:33:50 +00002243 q[i]=ClampToQuantum(LevelPixel(black_point,white_point,gamma,
2244 (MagickRealType) q[i]));
cristy7c0a0a42011-08-23 17:57:25 +00002245 }
cristyed231572011-07-14 02:18:59 +00002246 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00002247 }
2248 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2249 status=MagickFalse;
2250 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2251 {
2252 MagickBooleanType
2253 proceed;
2254
cristyb5d5f722009-11-04 03:03:49 +00002255#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy564a5692012-01-20 23:56:26 +00002256 #pragma omp critical (MagickCore_LevelImage)
cristy3ed852e2009-09-05 21:47:34 +00002257#endif
2258 proceed=SetImageProgress(image,LevelImageTag,progress++,image->rows);
2259 if (proceed == MagickFalse)
2260 status=MagickFalse;
2261 }
2262 }
2263 image_view=DestroyCacheView(image_view);
2264 return(status);
2265}
2266
2267/*
2268%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2269% %
2270% %
2271% %
cristy33bd5152011-08-24 01:42:24 +00002272% L e v e l i z e I m a g e %
cristy3ed852e2009-09-05 21:47:34 +00002273% %
2274% %
2275% %
2276%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2277%
cristy50fbc382011-07-07 02:19:17 +00002278% LevelizeImage() applies the reversed LevelImage() operation to just
cristy3ed852e2009-09-05 21:47:34 +00002279% the specific channels specified. It compresses the full range of color
2280% values, so that they lie between the given black and white points. Gamma is
2281% applied before the values are mapped.
2282%
cristy50fbc382011-07-07 02:19:17 +00002283% LevelizeImage() can be called with by using a +level command line
cristy3ed852e2009-09-05 21:47:34 +00002284% API option, or using a '!' on a -level or LevelImage() geometry string.
2285%
2286% It can be used for example de-contrast a greyscale image to the exact
2287% levels specified. Or by using specific levels for each channel of an image
2288% you can convert a gray-scale image to any linear color gradient, according
2289% to those levels.
2290%
cristy50fbc382011-07-07 02:19:17 +00002291% The format of the LevelizeImage method is:
cristy3ed852e2009-09-05 21:47:34 +00002292%
cristy50fbc382011-07-07 02:19:17 +00002293% MagickBooleanType LevelizeImage(Image *image,const double black_point,
cristy7c0a0a42011-08-23 17:57:25 +00002294% const double white_point,const double gamma,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002295%
2296% A description of each parameter follows:
2297%
2298% o image: the image.
2299%
cristy3ed852e2009-09-05 21:47:34 +00002300% o black_point: The level to map zero (black) to.
2301%
cristy01e9afd2011-08-10 17:38:41 +00002302% o white_point: The level to map QuantumRange (white) to.
cristy3ed852e2009-09-05 21:47:34 +00002303%
2304% o gamma: adjust gamma by this factor before mapping values.
2305%
cristy7c0a0a42011-08-23 17:57:25 +00002306% o exception: return any errors or warnings in this structure.
2307%
cristy3ed852e2009-09-05 21:47:34 +00002308*/
cristyd1a2c0f2011-02-09 14:14:50 +00002309MagickExport MagickBooleanType LevelizeImage(Image *image,
cristy7c0a0a42011-08-23 17:57:25 +00002310 const double black_point,const double white_point,const double gamma,
2311 ExceptionInfo *exception)
cristyd1a2c0f2011-02-09 14:14:50 +00002312{
cristy3ed852e2009-09-05 21:47:34 +00002313#define LevelizeImageTag "Levelize/Image"
cristyce70c172010-01-07 17:15:30 +00002314#define LevelizeValue(x) (ClampToQuantum(((MagickRealType) \
cristy50fbc382011-07-07 02:19:17 +00002315 pow((double) (QuantumScale*(x)),1.0/gamma))*(white_point-black_point)+ \
cristy3ed852e2009-09-05 21:47:34 +00002316 black_point))
2317
cristyc4c8d132010-01-07 01:58:38 +00002318 CacheView
2319 *image_view;
2320
cristy3ed852e2009-09-05 21:47:34 +00002321 MagickBooleanType
2322 status;
2323
cristybb503372010-05-27 20:51:26 +00002324 MagickOffsetType
2325 progress;
2326
2327 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002328 i;
2329
cristybb503372010-05-27 20:51:26 +00002330 ssize_t
2331 y;
2332
cristy3ed852e2009-09-05 21:47:34 +00002333 /*
2334 Allocate and initialize levels map.
2335 */
2336 assert(image != (Image *) NULL);
2337 assert(image->signature == MagickSignature);
2338 if (image->debug != MagickFalse)
2339 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2340 if (image->storage_class == PseudoClass)
cristyb5d5f722009-11-04 03:03:49 +00002341#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristya6d7a9b2012-01-18 20:04:48 +00002342 #pragma omp parallel for schedule(static,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002343#endif
cristybb503372010-05-27 20:51:26 +00002344 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00002345 {
2346 /*
2347 Level colormap.
2348 */
cristyed231572011-07-14 02:18:59 +00002349 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristyda1f9c12011-10-02 21:39:49 +00002350 image->colormap[i].red=(double) LevelizeValue(
2351 image->colormap[i].red);
cristyed231572011-07-14 02:18:59 +00002352 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristyda1f9c12011-10-02 21:39:49 +00002353 image->colormap[i].green=(double) LevelizeValue(
2354 image->colormap[i].green);
cristyed231572011-07-14 02:18:59 +00002355 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristyda1f9c12011-10-02 21:39:49 +00002356 image->colormap[i].blue=(double) LevelizeValue(
2357 image->colormap[i].blue);
cristyed231572011-07-14 02:18:59 +00002358 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristyda1f9c12011-10-02 21:39:49 +00002359 image->colormap[i].alpha=(double) LevelizeValue(
2360 image->colormap[i].alpha);
cristy3ed852e2009-09-05 21:47:34 +00002361 }
2362 /*
2363 Level image.
2364 */
2365 status=MagickTrue;
2366 progress=0;
cristy3ed852e2009-09-05 21:47:34 +00002367 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00002368#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristye6178502011-12-23 17:02:29 +00002369 #pragma omp parallel for schedule(static,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002370#endif
cristybb503372010-05-27 20:51:26 +00002371 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002372 {
cristy4c08aed2011-07-01 19:47:50 +00002373 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00002374 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002375
cristy8d4629b2010-08-30 17:59:46 +00002376 register ssize_t
2377 x;
2378
cristy3ed852e2009-09-05 21:47:34 +00002379 if (status == MagickFalse)
2380 continue;
2381 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristyacd2ed22011-08-30 01:44:23 +00002382 if (q == (Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00002383 {
2384 status=MagickFalse;
2385 continue;
2386 }
cristybb503372010-05-27 20:51:26 +00002387 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002388 {
cristy7c0a0a42011-08-23 17:57:25 +00002389 register ssize_t
2390 i;
2391
2392 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
2393 {
cristyabace412011-12-11 15:56:53 +00002394 PixelChannel
2395 channel;
2396
cristy7c0a0a42011-08-23 17:57:25 +00002397 PixelTrait
2398 traits;
2399
cristyabace412011-12-11 15:56:53 +00002400 channel=GetPixelChannelMapChannel(image,i);
2401 traits=GetPixelChannelMapTraits(image,channel);
cristyb8b0d162011-12-18 23:41:28 +00002402 if (traits == UndefinedPixelTrait)
2403 continue;
cristy780e9ef2011-12-18 23:33:50 +00002404 q[i]=LevelizeValue(q[i]);
cristy7c0a0a42011-08-23 17:57:25 +00002405 }
cristyed231572011-07-14 02:18:59 +00002406 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00002407 }
2408 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2409 status=MagickFalse;
2410 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2411 {
2412 MagickBooleanType
2413 proceed;
2414
cristyb5d5f722009-11-04 03:03:49 +00002415#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy564a5692012-01-20 23:56:26 +00002416 #pragma omp critical (MagickCore_LevelizeImage)
cristy3ed852e2009-09-05 21:47:34 +00002417#endif
2418 proceed=SetImageProgress(image,LevelizeImageTag,progress++,image->rows);
2419 if (proceed == MagickFalse)
2420 status=MagickFalse;
2421 }
2422 }
cristy8d4629b2010-08-30 17:59:46 +00002423 image_view=DestroyCacheView(image_view);
cristy3ed852e2009-09-05 21:47:34 +00002424 return(status);
2425}
2426
2427/*
2428%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2429% %
2430% %
2431% %
2432% L e v e l I m a g e C o l o r s %
2433% %
2434% %
2435% %
2436%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2437%
cristy490408a2011-07-07 14:42:05 +00002438% LevelImageColors() maps the given color to "black" and "white" values,
cristyee0f8d72009-09-19 00:58:29 +00002439% linearly spreading out the colors, and level values on a channel by channel
2440% bases, as per LevelImage(). The given colors allows you to specify
glennrp1e7f7bc2011-03-02 19:25:28 +00002441% different level ranges for each of the color channels separately.
cristy3ed852e2009-09-05 21:47:34 +00002442%
2443% If the boolean 'invert' is set true the image values will modifyed in the
2444% reverse direction. That is any existing "black" and "white" colors in the
2445% image will become the color values given, with all other values compressed
2446% appropriatally. This effectivally maps a greyscale gradient into the given
2447% color gradient.
2448%
cristy490408a2011-07-07 14:42:05 +00002449% The format of the LevelImageColors method is:
cristy3ed852e2009-09-05 21:47:34 +00002450%
cristy490408a2011-07-07 14:42:05 +00002451% MagickBooleanType LevelImageColors(Image *image,
2452% const PixelInfo *black_color,const PixelInfo *white_color,
cristy7c0a0a42011-08-23 17:57:25 +00002453% const MagickBooleanType invert,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002454%
2455% A description of each parameter follows:
2456%
2457% o image: the image.
2458%
cristy3ed852e2009-09-05 21:47:34 +00002459% o black_color: The color to map black to/from
2460%
2461% o white_point: The color to map white to/from
2462%
2463% o invert: if true map the colors (levelize), rather than from (level)
2464%
cristy7c0a0a42011-08-23 17:57:25 +00002465% o exception: return any errors or warnings in this structure.
2466%
cristy3ed852e2009-09-05 21:47:34 +00002467*/
cristy490408a2011-07-07 14:42:05 +00002468MagickExport MagickBooleanType LevelImageColors(Image *image,
cristy4c08aed2011-07-01 19:47:50 +00002469 const PixelInfo *black_color,const PixelInfo *white_color,
cristy7c0a0a42011-08-23 17:57:25 +00002470 const MagickBooleanType invert,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002471{
cristybd5a96c2011-08-21 00:04:26 +00002472 ChannelType
2473 channel_mask;
2474
cristy3ed852e2009-09-05 21:47:34 +00002475 MagickStatusType
2476 status;
2477
2478 /*
2479 Allocate and initialize levels map.
2480 */
2481 assert(image != (Image *) NULL);
2482 assert(image->signature == MagickSignature);
2483 if (image->debug != MagickFalse)
2484 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2485 status=MagickFalse;
2486 if (invert == MagickFalse)
2487 {
cristyed231572011-07-14 02:18:59 +00002488 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristyf89cb1d2011-07-07 01:24:37 +00002489 {
cristybd5a96c2011-08-21 00:04:26 +00002490 channel_mask=SetPixelChannelMask(image,RedChannel);
cristy01e9afd2011-08-10 17:38:41 +00002491 status|=LevelImage(image,black_color->red,white_color->red,1.0,
cristy7c0a0a42011-08-23 17:57:25 +00002492 exception);
cristybd5a96c2011-08-21 00:04:26 +00002493 (void) SetPixelChannelMask(image,channel_mask);
cristyf89cb1d2011-07-07 01:24:37 +00002494 }
cristyed231572011-07-14 02:18:59 +00002495 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristyf89cb1d2011-07-07 01:24:37 +00002496 {
cristybd5a96c2011-08-21 00:04:26 +00002497 channel_mask=SetPixelChannelMask(image,GreenChannel);
cristy01e9afd2011-08-10 17:38:41 +00002498 status|=LevelImage(image,black_color->green,white_color->green,1.0,
cristy7c0a0a42011-08-23 17:57:25 +00002499 exception);
cristybd5a96c2011-08-21 00:04:26 +00002500 (void) SetPixelChannelMask(image,channel_mask);
cristyf89cb1d2011-07-07 01:24:37 +00002501 }
cristyed231572011-07-14 02:18:59 +00002502 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristyf89cb1d2011-07-07 01:24:37 +00002503 {
cristybd5a96c2011-08-21 00:04:26 +00002504 channel_mask=SetPixelChannelMask(image,BlueChannel);
cristy01e9afd2011-08-10 17:38:41 +00002505 status|=LevelImage(image,black_color->blue,white_color->blue,1.0,
cristy7c0a0a42011-08-23 17:57:25 +00002506 exception);
cristybd5a96c2011-08-21 00:04:26 +00002507 (void) SetPixelChannelMask(image,channel_mask);
cristyf89cb1d2011-07-07 01:24:37 +00002508 }
cristyed231572011-07-14 02:18:59 +00002509 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +00002510 (image->colorspace == CMYKColorspace))
cristyf89cb1d2011-07-07 01:24:37 +00002511 {
cristybd5a96c2011-08-21 00:04:26 +00002512 channel_mask=SetPixelChannelMask(image,BlackChannel);
cristy01e9afd2011-08-10 17:38:41 +00002513 status|=LevelImage(image,black_color->black,white_color->black,1.0,
cristy7c0a0a42011-08-23 17:57:25 +00002514 exception);
cristybd5a96c2011-08-21 00:04:26 +00002515 (void) SetPixelChannelMask(image,channel_mask);
cristyf89cb1d2011-07-07 01:24:37 +00002516 }
cristyed231572011-07-14 02:18:59 +00002517 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00002518 (image->matte == MagickTrue))
cristyf89cb1d2011-07-07 01:24:37 +00002519 {
cristybd5a96c2011-08-21 00:04:26 +00002520 channel_mask=SetPixelChannelMask(image,AlphaChannel);
cristy01e9afd2011-08-10 17:38:41 +00002521 status|=LevelImage(image,black_color->alpha,white_color->alpha,1.0,
cristy7c0a0a42011-08-23 17:57:25 +00002522 exception);
cristybd5a96c2011-08-21 00:04:26 +00002523 (void) SetPixelChannelMask(image,channel_mask);
cristyf89cb1d2011-07-07 01:24:37 +00002524 }
cristy3ed852e2009-09-05 21:47:34 +00002525 }
2526 else
2527 {
cristyed231572011-07-14 02:18:59 +00002528 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristyf89cb1d2011-07-07 01:24:37 +00002529 {
cristybd5a96c2011-08-21 00:04:26 +00002530 channel_mask=SetPixelChannelMask(image,RedChannel);
cristy7c0a0a42011-08-23 17:57:25 +00002531 status|=LevelizeImage(image,black_color->red,white_color->red,1.0,
2532 exception);
cristybd5a96c2011-08-21 00:04:26 +00002533 (void) SetPixelChannelMask(image,channel_mask);
cristyf89cb1d2011-07-07 01:24:37 +00002534 }
cristyed231572011-07-14 02:18:59 +00002535 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristyf89cb1d2011-07-07 01:24:37 +00002536 {
cristybd5a96c2011-08-21 00:04:26 +00002537 channel_mask=SetPixelChannelMask(image,GreenChannel);
cristy7c0a0a42011-08-23 17:57:25 +00002538 status|=LevelizeImage(image,black_color->green,white_color->green,1.0,
2539 exception);
cristybd5a96c2011-08-21 00:04:26 +00002540 (void) SetPixelChannelMask(image,channel_mask);
cristyf89cb1d2011-07-07 01:24:37 +00002541 }
cristyed231572011-07-14 02:18:59 +00002542 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristyf89cb1d2011-07-07 01:24:37 +00002543 {
cristybd5a96c2011-08-21 00:04:26 +00002544 channel_mask=SetPixelChannelMask(image,BlueChannel);
cristy7c0a0a42011-08-23 17:57:25 +00002545 status|=LevelizeImage(image,black_color->blue,white_color->blue,1.0,
2546 exception);
cristybd5a96c2011-08-21 00:04:26 +00002547 (void) SetPixelChannelMask(image,channel_mask);
cristyf89cb1d2011-07-07 01:24:37 +00002548 }
cristyed231572011-07-14 02:18:59 +00002549 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +00002550 (image->colorspace == CMYKColorspace))
cristyf89cb1d2011-07-07 01:24:37 +00002551 {
cristybd5a96c2011-08-21 00:04:26 +00002552 channel_mask=SetPixelChannelMask(image,BlackChannel);
cristy7c0a0a42011-08-23 17:57:25 +00002553 status|=LevelizeImage(image,black_color->black,white_color->black,1.0,
2554 exception);
cristybd5a96c2011-08-21 00:04:26 +00002555 (void) SetPixelChannelMask(image,channel_mask);
cristyf89cb1d2011-07-07 01:24:37 +00002556 }
cristyed231572011-07-14 02:18:59 +00002557 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00002558 (image->matte == MagickTrue))
cristyf89cb1d2011-07-07 01:24:37 +00002559 {
cristybd5a96c2011-08-21 00:04:26 +00002560 channel_mask=SetPixelChannelMask(image,AlphaChannel);
cristy7c0a0a42011-08-23 17:57:25 +00002561 status|=LevelizeImage(image,black_color->alpha,white_color->alpha,1.0,
2562 exception);
cristybd5a96c2011-08-21 00:04:26 +00002563 (void) SetPixelChannelMask(image,channel_mask);
cristyf89cb1d2011-07-07 01:24:37 +00002564 }
cristy3ed852e2009-09-05 21:47:34 +00002565 }
2566 return(status == 0 ? MagickFalse : MagickTrue);
2567}
2568
2569/*
2570%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2571% %
2572% %
2573% %
2574% L i n e a r S t r e t c h I m a g e %
2575% %
2576% %
2577% %
2578%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2579%
cristyf1611782011-08-01 15:39:13 +00002580% LinearStretchImage() discards any pixels below the black point and above
2581% the white point and levels the remaining pixels.
cristy3ed852e2009-09-05 21:47:34 +00002582%
2583% The format of the LinearStretchImage method is:
2584%
2585% MagickBooleanType LinearStretchImage(Image *image,
cristy33bd5152011-08-24 01:42:24 +00002586% const double black_point,const double white_point,
2587% ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002588%
2589% A description of each parameter follows:
2590%
2591% o image: the image.
2592%
2593% o black_point: the black point.
2594%
2595% o white_point: the white point.
2596%
cristy33bd5152011-08-24 01:42:24 +00002597% o exception: return any errors or warnings in this structure.
2598%
cristy3ed852e2009-09-05 21:47:34 +00002599*/
2600MagickExport MagickBooleanType LinearStretchImage(Image *image,
cristy33bd5152011-08-24 01:42:24 +00002601 const double black_point,const double white_point,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002602{
2603#define LinearStretchImageTag "LinearStretch/Image"
2604
cristy33bd5152011-08-24 01:42:24 +00002605 CacheView
2606 *image_view;
cristy3ed852e2009-09-05 21:47:34 +00002607
cristy3ed852e2009-09-05 21:47:34 +00002608 MagickBooleanType
2609 status;
2610
2611 MagickRealType
2612 *histogram,
2613 intensity;
2614
cristy8d4629b2010-08-30 17:59:46 +00002615 ssize_t
2616 black,
2617 white,
2618 y;
2619
cristy3ed852e2009-09-05 21:47:34 +00002620 /*
2621 Allocate histogram and linear map.
2622 */
2623 assert(image != (Image *) NULL);
2624 assert(image->signature == MagickSignature);
2625 histogram=(MagickRealType *) AcquireQuantumMemory(MaxMap+1UL,
2626 sizeof(*histogram));
2627 if (histogram == (MagickRealType *) NULL)
2628 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
2629 image->filename);
2630 /*
2631 Form histogram.
2632 */
2633 (void) ResetMagickMemory(histogram,0,(MaxMap+1)*sizeof(*histogram));
cristy33bd5152011-08-24 01:42:24 +00002634 image_view=AcquireCacheView(image);
cristybb503372010-05-27 20:51:26 +00002635 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002636 {
cristy4c08aed2011-07-01 19:47:50 +00002637 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00002638 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00002639
cristybb503372010-05-27 20:51:26 +00002640 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002641 x;
2642
cristy33bd5152011-08-24 01:42:24 +00002643 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00002644 if (p == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00002645 break;
cristy33bd5152011-08-24 01:42:24 +00002646 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002647 {
cristy4c08aed2011-07-01 19:47:50 +00002648 histogram[ScaleQuantumToMap(GetPixelIntensity(image,p))]++;
cristyed231572011-07-14 02:18:59 +00002649 p+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00002650 }
2651 }
cristy33bd5152011-08-24 01:42:24 +00002652 image_view=DestroyCacheView(image_view);
cristy3ed852e2009-09-05 21:47:34 +00002653 /*
2654 Find the histogram boundaries by locating the black and white point levels.
2655 */
cristy3ed852e2009-09-05 21:47:34 +00002656 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00002657 for (black=0; black < (ssize_t) MaxMap; black++)
cristy3ed852e2009-09-05 21:47:34 +00002658 {
2659 intensity+=histogram[black];
2660 if (intensity >= black_point)
2661 break;
2662 }
2663 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00002664 for (white=(ssize_t) MaxMap; white != 0; white--)
cristy3ed852e2009-09-05 21:47:34 +00002665 {
2666 intensity+=histogram[white];
2667 if (intensity >= white_point)
2668 break;
2669 }
2670 histogram=(MagickRealType *) RelinquishMagickMemory(histogram);
cristyc82a27b2011-10-21 01:07:16 +00002671 status=LevelImage(image,(double) black,(double) white,1.0,exception);
cristy3ed852e2009-09-05 21:47:34 +00002672 return(status);
2673}
2674
2675/*
2676%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2677% %
2678% %
2679% %
2680% M o d u l a t e I m a g e %
2681% %
2682% %
2683% %
2684%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2685%
2686% ModulateImage() lets you control the brightness, saturation, and hue
2687% of an image. Modulate represents the brightness, saturation, and hue
2688% as one parameter (e.g. 90,150,100). If the image colorspace is HSL, the
2689% modulation is lightness, saturation, and hue. And if the colorspace is
2690% HWB, use blackness, whiteness, and hue.
2691%
2692% The format of the ModulateImage method is:
2693%
cristy33bd5152011-08-24 01:42:24 +00002694% MagickBooleanType ModulateImage(Image *image,const char *modulate,
2695% ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002696%
2697% A description of each parameter follows:
2698%
2699% o image: the image.
2700%
cristy33bd5152011-08-24 01:42:24 +00002701% o modulate: Define the percent change in brightness, saturation, and hue.
2702%
2703% o exception: return any errors or warnings in this structure.
cristy3ed852e2009-09-05 21:47:34 +00002704%
2705*/
2706
2707static void ModulateHSB(const double percent_hue,
cristy3094b7f2011-10-01 23:18:02 +00002708 const double percent_saturation,const double percent_brightness,double *red,
2709 double *green,double *blue)
cristy3ed852e2009-09-05 21:47:34 +00002710{
2711 double
2712 brightness,
2713 hue,
2714 saturation;
2715
2716 /*
2717 Increase or decrease color brightness, saturation, or hue.
2718 */
cristy3094b7f2011-10-01 23:18:02 +00002719 assert(red != (double *) NULL);
2720 assert(green != (double *) NULL);
2721 assert(blue != (double *) NULL);
cristy3ed852e2009-09-05 21:47:34 +00002722 ConvertRGBToHSB(*red,*green,*blue,&hue,&saturation,&brightness);
2723 hue+=0.5*(0.01*percent_hue-1.0);
2724 while (hue < 0.0)
2725 hue+=1.0;
2726 while (hue > 1.0)
2727 hue-=1.0;
2728 saturation*=0.01*percent_saturation;
2729 brightness*=0.01*percent_brightness;
2730 ConvertHSBToRGB(hue,saturation,brightness,red,green,blue);
2731}
2732
2733static void ModulateHSL(const double percent_hue,
cristy3094b7f2011-10-01 23:18:02 +00002734 const double percent_saturation,const double percent_lightness,double *red,
2735 double *green,double *blue)
cristy3ed852e2009-09-05 21:47:34 +00002736{
2737 double
2738 hue,
2739 lightness,
2740 saturation;
2741
2742 /*
2743 Increase or decrease color lightness, saturation, or hue.
2744 */
cristy3094b7f2011-10-01 23:18:02 +00002745 assert(red != (double *) NULL);
2746 assert(green != (double *) NULL);
2747 assert(blue != (double *) NULL);
cristy3ed852e2009-09-05 21:47:34 +00002748 ConvertRGBToHSL(*red,*green,*blue,&hue,&saturation,&lightness);
2749 hue+=0.5*(0.01*percent_hue-1.0);
2750 while (hue < 0.0)
2751 hue+=1.0;
2752 while (hue > 1.0)
2753 hue-=1.0;
2754 saturation*=0.01*percent_saturation;
2755 lightness*=0.01*percent_lightness;
2756 ConvertHSLToRGB(hue,saturation,lightness,red,green,blue);
2757}
2758
cristy3094b7f2011-10-01 23:18:02 +00002759static 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 +00002760{
2761 double
2762 blackness,
2763 hue,
2764 whiteness;
2765
2766 /*
2767 Increase or decrease color blackness, whiteness, or hue.
2768 */
cristy3094b7f2011-10-01 23:18:02 +00002769 assert(red != (double *) NULL);
2770 assert(green != (double *) NULL);
2771 assert(blue != (double *) NULL);
cristy3ed852e2009-09-05 21:47:34 +00002772 ConvertRGBToHWB(*red,*green,*blue,&hue,&whiteness,&blackness);
2773 hue+=0.5*(0.01*percent_hue-1.0);
2774 while (hue < 0.0)
2775 hue+=1.0;
2776 while (hue > 1.0)
2777 hue-=1.0;
2778 blackness*=0.01*percent_blackness;
2779 whiteness*=0.01*percent_whiteness;
2780 ConvertHWBToRGB(hue,whiteness,blackness,red,green,blue);
2781}
2782
cristy33bd5152011-08-24 01:42:24 +00002783MagickExport MagickBooleanType ModulateImage(Image *image,const char *modulate,
2784 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002785{
2786#define ModulateImageTag "Modulate/Image"
2787
cristyc4c8d132010-01-07 01:58:38 +00002788 CacheView
2789 *image_view;
2790
cristy3ed852e2009-09-05 21:47:34 +00002791 ColorspaceType
2792 colorspace;
2793
2794 const char
2795 *artifact;
2796
2797 double
2798 percent_brightness,
2799 percent_hue,
2800 percent_saturation;
2801
cristy3ed852e2009-09-05 21:47:34 +00002802 GeometryInfo
2803 geometry_info;
2804
cristy3ed852e2009-09-05 21:47:34 +00002805 MagickBooleanType
2806 status;
2807
cristybb503372010-05-27 20:51:26 +00002808 MagickOffsetType
2809 progress;
2810
cristy3ed852e2009-09-05 21:47:34 +00002811 MagickStatusType
2812 flags;
2813
cristybb503372010-05-27 20:51:26 +00002814 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002815 i;
2816
cristybb503372010-05-27 20:51:26 +00002817 ssize_t
2818 y;
2819
cristy3ed852e2009-09-05 21:47:34 +00002820 /*
cristy2b726bd2010-01-11 01:05:39 +00002821 Initialize modulate table.
cristy3ed852e2009-09-05 21:47:34 +00002822 */
2823 assert(image != (Image *) NULL);
2824 assert(image->signature == MagickSignature);
2825 if (image->debug != MagickFalse)
2826 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2827 if (modulate == (char *) NULL)
2828 return(MagickFalse);
2829 flags=ParseGeometry(modulate,&geometry_info);
2830 percent_brightness=geometry_info.rho;
2831 percent_saturation=geometry_info.sigma;
2832 if ((flags & SigmaValue) == 0)
2833 percent_saturation=100.0;
2834 percent_hue=geometry_info.xi;
2835 if ((flags & XiValue) == 0)
2836 percent_hue=100.0;
2837 colorspace=UndefinedColorspace;
2838 artifact=GetImageArtifact(image,"modulate:colorspace");
2839 if (artifact != (const char *) NULL)
cristy042ee782011-04-22 18:48:30 +00002840 colorspace=(ColorspaceType) ParseCommandOption(MagickColorspaceOptions,
cristy3ed852e2009-09-05 21:47:34 +00002841 MagickFalse,artifact);
2842 if (image->storage_class == PseudoClass)
2843 {
2844 /*
2845 Modulate colormap.
2846 */
cristyb5d5f722009-11-04 03:03:49 +00002847#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy564a5692012-01-20 23:56:26 +00002848 #pragma omp parallel for schedule(static,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002849#endif
cristybb503372010-05-27 20:51:26 +00002850 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00002851 switch (colorspace)
2852 {
2853 case HSBColorspace:
2854 {
2855 ModulateHSB(percent_hue,percent_saturation,percent_brightness,
2856 &image->colormap[i].red,&image->colormap[i].green,
2857 &image->colormap[i].blue);
2858 break;
2859 }
2860 case HSLColorspace:
2861 default:
2862 {
2863 ModulateHSL(percent_hue,percent_saturation,percent_brightness,
2864 &image->colormap[i].red,&image->colormap[i].green,
2865 &image->colormap[i].blue);
2866 break;
2867 }
2868 case HWBColorspace:
2869 {
2870 ModulateHWB(percent_hue,percent_saturation,percent_brightness,
2871 &image->colormap[i].red,&image->colormap[i].green,
2872 &image->colormap[i].blue);
2873 break;
2874 }
2875 }
2876 }
2877 /*
2878 Modulate image.
2879 */
2880 status=MagickTrue;
2881 progress=0;
cristy3ed852e2009-09-05 21:47:34 +00002882 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00002883#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristye6178502011-12-23 17:02:29 +00002884 #pragma omp parallel for schedule(static,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002885#endif
cristybb503372010-05-27 20:51:26 +00002886 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002887 {
cristy3094b7f2011-10-01 23:18:02 +00002888 double
cristy5afeab82011-04-30 01:30:09 +00002889 blue,
2890 green,
2891 red;
2892
cristy4c08aed2011-07-01 19:47:50 +00002893 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00002894 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002895
cristy8d4629b2010-08-30 17:59:46 +00002896 register ssize_t
2897 x;
2898
cristy3ed852e2009-09-05 21:47:34 +00002899 if (status == MagickFalse)
2900 continue;
2901 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristyacd2ed22011-08-30 01:44:23 +00002902 if (q == (Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00002903 {
2904 status=MagickFalse;
2905 continue;
2906 }
cristybb503372010-05-27 20:51:26 +00002907 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002908 {
cristyda1f9c12011-10-02 21:39:49 +00002909 red=(double) GetPixelRed(image,q);
2910 green=(double) GetPixelGreen(image,q);
2911 blue=(double) GetPixelBlue(image,q);
cristy3ed852e2009-09-05 21:47:34 +00002912 switch (colorspace)
2913 {
2914 case HSBColorspace:
2915 {
2916 ModulateHSB(percent_hue,percent_saturation,percent_brightness,
cristy5afeab82011-04-30 01:30:09 +00002917 &red,&green,&blue);
cristy3ed852e2009-09-05 21:47:34 +00002918 break;
2919 }
2920 case HSLColorspace:
2921 default:
2922 {
2923 ModulateHSL(percent_hue,percent_saturation,percent_brightness,
cristy5afeab82011-04-30 01:30:09 +00002924 &red,&green,&blue);
cristy3ed852e2009-09-05 21:47:34 +00002925 break;
2926 }
2927 case HWBColorspace:
2928 {
2929 ModulateHWB(percent_hue,percent_saturation,percent_brightness,
cristy5afeab82011-04-30 01:30:09 +00002930 &red,&green,&blue);
cristy3ed852e2009-09-05 21:47:34 +00002931 break;
2932 }
2933 }
cristy3094b7f2011-10-01 23:18:02 +00002934 SetPixelRed(image,ClampToQuantum(red),q);
2935 SetPixelGreen(image,ClampToQuantum(green),q);
2936 SetPixelBlue(image,ClampToQuantum(blue),q);
cristyed231572011-07-14 02:18:59 +00002937 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00002938 }
2939 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2940 status=MagickFalse;
2941 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2942 {
2943 MagickBooleanType
2944 proceed;
2945
cristyb5d5f722009-11-04 03:03:49 +00002946#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy564a5692012-01-20 23:56:26 +00002947 #pragma omp critical (MagickCore_ModulateImage)
cristy3ed852e2009-09-05 21:47:34 +00002948#endif
2949 proceed=SetImageProgress(image,ModulateImageTag,progress++,image->rows);
2950 if (proceed == MagickFalse)
2951 status=MagickFalse;
2952 }
2953 }
2954 image_view=DestroyCacheView(image_view);
2955 return(status);
2956}
2957
2958/*
2959%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2960% %
2961% %
2962% %
2963% N e g a t e I m a g e %
2964% %
2965% %
2966% %
2967%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2968%
2969% NegateImage() negates the colors in the reference image. The grayscale
2970% option means that only grayscale values within the image are negated.
2971%
cristy50fbc382011-07-07 02:19:17 +00002972% The format of the NegateImage method is:
cristy3ed852e2009-09-05 21:47:34 +00002973%
2974% MagickBooleanType NegateImage(Image *image,
cristyb3e7c6c2011-07-24 01:43:55 +00002975% const MagickBooleanType grayscale,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002976%
2977% A description of each parameter follows:
2978%
2979% o image: the image.
2980%
cristy3ed852e2009-09-05 21:47:34 +00002981% o grayscale: If MagickTrue, only negate grayscale pixels within the image.
2982%
cristyb3e7c6c2011-07-24 01:43:55 +00002983% o exception: return any errors or warnings in this structure.
2984%
cristy3ed852e2009-09-05 21:47:34 +00002985*/
cristy3ed852e2009-09-05 21:47:34 +00002986MagickExport MagickBooleanType NegateImage(Image *image,
cristyb3e7c6c2011-07-24 01:43:55 +00002987 const MagickBooleanType grayscale,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002988{
cristy3ed852e2009-09-05 21:47:34 +00002989#define NegateImageTag "Negate/Image"
2990
cristyc4c8d132010-01-07 01:58:38 +00002991 CacheView
2992 *image_view;
2993
cristy3ed852e2009-09-05 21:47:34 +00002994 MagickBooleanType
2995 status;
2996
cristybb503372010-05-27 20:51:26 +00002997 MagickOffsetType
2998 progress;
2999
3000 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003001 i;
3002
cristybb503372010-05-27 20:51:26 +00003003 ssize_t
3004 y;
3005
cristy3ed852e2009-09-05 21:47:34 +00003006 assert(image != (Image *) NULL);
3007 assert(image->signature == MagickSignature);
3008 if (image->debug != MagickFalse)
3009 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3010 if (image->storage_class == PseudoClass)
3011 {
3012 /*
3013 Negate colormap.
3014 */
cristyb5d5f722009-11-04 03:03:49 +00003015#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy69cfa022012-01-23 12:47:29 +00003016 #pragma omp parallel for schedule(static) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003017#endif
cristybb503372010-05-27 20:51:26 +00003018 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00003019 {
3020 if (grayscale != MagickFalse)
3021 if ((image->colormap[i].red != image->colormap[i].green) ||
3022 (image->colormap[i].green != image->colormap[i].blue))
3023 continue;
cristyed231572011-07-14 02:18:59 +00003024 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00003025 image->colormap[i].red=(Quantum) QuantumRange-
3026 image->colormap[i].red;
cristyed231572011-07-14 02:18:59 +00003027 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00003028 image->colormap[i].green=(Quantum) QuantumRange-
3029 image->colormap[i].green;
cristyed231572011-07-14 02:18:59 +00003030 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00003031 image->colormap[i].blue=(Quantum) QuantumRange-
3032 image->colormap[i].blue;
3033 }
3034 }
3035 /*
3036 Negate image.
3037 */
3038 status=MagickTrue;
3039 progress=0;
cristy3ed852e2009-09-05 21:47:34 +00003040 image_view=AcquireCacheView(image);
3041 if (grayscale != MagickFalse)
3042 {
cristyb5d5f722009-11-04 03:03:49 +00003043#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy69cfa022012-01-23 12:47:29 +00003044 #pragma omp parallel for schedule(static) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003045#endif
cristybb503372010-05-27 20:51:26 +00003046 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003047 {
3048 MagickBooleanType
3049 sync;
3050
cristy4c08aed2011-07-01 19:47:50 +00003051 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00003052 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003053
cristy8d4629b2010-08-30 17:59:46 +00003054 register ssize_t
3055 x;
3056
cristy3ed852e2009-09-05 21:47:34 +00003057 if (status == MagickFalse)
3058 continue;
3059 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,
3060 exception);
cristyacd2ed22011-08-30 01:44:23 +00003061 if (q == (Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00003062 {
3063 status=MagickFalse;
3064 continue;
3065 }
cristybb503372010-05-27 20:51:26 +00003066 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003067 {
cristy9aa95be2011-07-20 21:56:45 +00003068 register ssize_t
3069 i;
3070
cristyd476a8e2011-07-23 16:13:22 +00003071 if (IsPixelGray(image,q) != MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00003072 {
cristya30d9ba2011-07-23 21:00:48 +00003073 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00003074 continue;
3075 }
cristya30d9ba2011-07-23 21:00:48 +00003076 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
cristy9aa95be2011-07-20 21:56:45 +00003077 {
cristyabace412011-12-11 15:56:53 +00003078 PixelChannel
3079 channel;
3080
cristy1aaa3cd2011-08-21 23:48:17 +00003081 PixelTrait
cristy9aa95be2011-07-20 21:56:45 +00003082 traits;
3083
cristyabace412011-12-11 15:56:53 +00003084 channel=GetPixelChannelMapChannel(image,i);
3085 traits=GetPixelChannelMapTraits(image,channel);
cristy9aa95be2011-07-20 21:56:45 +00003086 if ((traits & UpdatePixelTrait) != 0)
3087 q[i]=QuantumRange-q[i];
3088 }
cristya30d9ba2011-07-23 21:00:48 +00003089 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00003090 }
3091 sync=SyncCacheViewAuthenticPixels(image_view,exception);
3092 if (sync == MagickFalse)
3093 status=MagickFalse;
3094 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3095 {
3096 MagickBooleanType
3097 proceed;
3098
cristyb5d5f722009-11-04 03:03:49 +00003099#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy564a5692012-01-20 23:56:26 +00003100 #pragma omp critical (MagickCore_NegateImage)
cristy3ed852e2009-09-05 21:47:34 +00003101#endif
3102 proceed=SetImageProgress(image,NegateImageTag,progress++,
3103 image->rows);
3104 if (proceed == MagickFalse)
3105 status=MagickFalse;
3106 }
3107 }
3108 image_view=DestroyCacheView(image_view);
3109 return(MagickTrue);
3110 }
3111 /*
3112 Negate image.
3113 */
cristyb5d5f722009-11-04 03:03:49 +00003114#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy69cfa022012-01-23 12:47:29 +00003115 #pragma omp parallel for schedule(static) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003116#endif
cristybb503372010-05-27 20:51:26 +00003117 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003118 {
cristy4c08aed2011-07-01 19:47:50 +00003119 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00003120 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003121
cristy8d4629b2010-08-30 17:59:46 +00003122 register ssize_t
3123 x;
3124
cristy3ed852e2009-09-05 21:47:34 +00003125 if (status == MagickFalse)
3126 continue;
3127 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristyacd2ed22011-08-30 01:44:23 +00003128 if (q == (Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00003129 {
3130 status=MagickFalse;
3131 continue;
3132 }
cristybb503372010-05-27 20:51:26 +00003133 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003134 {
cristyf7dc44c2011-07-20 14:41:15 +00003135 register ssize_t
3136 i;
3137
cristya30d9ba2011-07-23 21:00:48 +00003138 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
cristyf7dc44c2011-07-20 14:41:15 +00003139 {
cristyabace412011-12-11 15:56:53 +00003140 PixelChannel
3141 channel;
3142
cristy1aaa3cd2011-08-21 23:48:17 +00003143 PixelTrait
cristyf7dc44c2011-07-20 14:41:15 +00003144 traits;
3145
cristyabace412011-12-11 15:56:53 +00003146 channel=GetPixelChannelMapChannel(image,i);
3147 traits=GetPixelChannelMapTraits(image,channel);
cristyf7dc44c2011-07-20 14:41:15 +00003148 if ((traits & UpdatePixelTrait) != 0)
3149 q[i]=QuantumRange-q[i];
3150 }
cristya30d9ba2011-07-23 21:00:48 +00003151 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00003152 }
3153 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
3154 status=MagickFalse;
3155 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3156 {
3157 MagickBooleanType
3158 proceed;
3159
cristyb5d5f722009-11-04 03:03:49 +00003160#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy564a5692012-01-20 23:56:26 +00003161 #pragma omp critical (MagickCore_NegateImage)
cristy3ed852e2009-09-05 21:47:34 +00003162#endif
3163 proceed=SetImageProgress(image,NegateImageTag,progress++,image->rows);
3164 if (proceed == MagickFalse)
3165 status=MagickFalse;
3166 }
3167 }
3168 image_view=DestroyCacheView(image_view);
3169 return(status);
3170}
3171
3172/*
3173%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3174% %
3175% %
3176% %
3177% N o r m a l i z e I m a g e %
3178% %
3179% %
3180% %
3181%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3182%
cristya4dfb122011-07-07 19:01:57 +00003183% NormalizeImage() enhances the contrast of a color image by mapping the
3184% darkest 2 percent of all pixel to black and the brightest 1 percent to white.
cristy3ed852e2009-09-05 21:47:34 +00003185%
3186% The format of the NormalizeImage method is:
3187%
cristye23ec9d2011-08-16 18:15:40 +00003188% MagickBooleanType NormalizeImage(Image *image,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003189%
3190% A description of each parameter follows:
3191%
3192% o image: the image.
3193%
cristye23ec9d2011-08-16 18:15:40 +00003194% o exception: return any errors or warnings in this structure.
3195%
cristy3ed852e2009-09-05 21:47:34 +00003196*/
cristye23ec9d2011-08-16 18:15:40 +00003197MagickExport MagickBooleanType NormalizeImage(Image *image,
3198 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003199{
cristy3ed852e2009-09-05 21:47:34 +00003200 double
3201 black_point,
3202 white_point;
3203
cristy530239c2010-07-25 17:34:26 +00003204 black_point=(double) image->columns*image->rows*0.0015;
3205 white_point=(double) image->columns*image->rows*0.9995;
cristye23ec9d2011-08-16 18:15:40 +00003206 return(ContrastStretchImage(image,black_point,white_point,exception));
cristy3ed852e2009-09-05 21:47:34 +00003207}
3208
3209/*
3210%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3211% %
3212% %
3213% %
3214% S i g m o i d a l C o n t r a s t I m a g e %
3215% %
3216% %
3217% %
3218%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3219%
3220% SigmoidalContrastImage() adjusts the contrast of an image with a non-linear
3221% sigmoidal contrast algorithm. Increase the contrast of the image using a
3222% sigmoidal transfer function without saturating highlights or shadows.
3223% Contrast indicates how much to increase the contrast (0 is none; 3 is
3224% typical; 20 is pushing it); mid-point indicates where midtones fall in the
3225% resultant image (0 is white; 50% is middle-gray; 100% is black). Set
3226% sharpen to MagickTrue to increase the image contrast otherwise the contrast
3227% is reduced.
3228%
3229% The format of the SigmoidalContrastImage method is:
3230%
3231% MagickBooleanType SigmoidalContrastImage(Image *image,
cristy33bd5152011-08-24 01:42:24 +00003232% const MagickBooleanType sharpen,const char *levels,
3233% ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003234%
3235% A description of each parameter follows:
3236%
3237% o image: the image.
3238%
cristy3ed852e2009-09-05 21:47:34 +00003239% o sharpen: Increase or decrease image contrast.
3240%
cristyfa769582010-09-30 23:30:03 +00003241% o alpha: strength of the contrast, the larger the number the more
3242% 'threshold-like' it becomes.
cristy3ed852e2009-09-05 21:47:34 +00003243%
cristyfa769582010-09-30 23:30:03 +00003244% o beta: midpoint of the function as a color value 0 to QuantumRange.
cristy3ed852e2009-09-05 21:47:34 +00003245%
cristy33bd5152011-08-24 01:42:24 +00003246% o exception: return any errors or warnings in this structure.
3247%
cristy3ed852e2009-09-05 21:47:34 +00003248*/
cristy3ed852e2009-09-05 21:47:34 +00003249MagickExport MagickBooleanType SigmoidalContrastImage(Image *image,
cristy33bd5152011-08-24 01:42:24 +00003250 const MagickBooleanType sharpen,const double contrast,const double midpoint,
3251 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003252{
3253#define SigmoidalContrastImageTag "SigmoidalContrast/Image"
3254
cristyc4c8d132010-01-07 01:58:38 +00003255 CacheView
3256 *image_view;
3257
cristy3ed852e2009-09-05 21:47:34 +00003258 MagickBooleanType
3259 status;
3260
cristybb503372010-05-27 20:51:26 +00003261 MagickOffsetType
3262 progress;
3263
cristy3ed852e2009-09-05 21:47:34 +00003264 MagickRealType
3265 *sigmoidal_map;
3266
cristybb503372010-05-27 20:51:26 +00003267 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003268 i;
3269
cristybb503372010-05-27 20:51:26 +00003270 ssize_t
3271 y;
3272
cristy3ed852e2009-09-05 21:47:34 +00003273 /*
3274 Allocate and initialize sigmoidal maps.
3275 */
3276 assert(image != (Image *) NULL);
3277 assert(image->signature == MagickSignature);
3278 if (image->debug != MagickFalse)
3279 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3280 sigmoidal_map=(MagickRealType *) AcquireQuantumMemory(MaxMap+1UL,
3281 sizeof(*sigmoidal_map));
3282 if (sigmoidal_map == (MagickRealType *) NULL)
3283 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
3284 image->filename);
3285 (void) ResetMagickMemory(sigmoidal_map,0,(MaxMap+1)*sizeof(*sigmoidal_map));
cristyb5d5f722009-11-04 03:03:49 +00003286#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy69cfa022012-01-23 12:47:29 +00003287 #pragma omp parallel for schedule(static) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003288#endif
cristybb503372010-05-27 20:51:26 +00003289 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +00003290 {
3291 if (sharpen != MagickFalse)
3292 {
3293 sigmoidal_map[i]=(MagickRealType) ScaleMapToQuantum((MagickRealType)
3294 (MaxMap*((1.0/(1.0+exp(contrast*(midpoint/(double) QuantumRange-
cristy33bd5152011-08-24 01:42:24 +00003295 (double) i/MaxMap))))-(1.0/(1.0+exp(contrast*(midpoint/(double)
3296 QuantumRange)))))/((1.0/(1.0+exp(contrast*(midpoint/(double)
3297 QuantumRange-1.0))))-(1.0/(1.0+exp(contrast*(midpoint/(double)
3298 QuantumRange)))))+0.5));
cristy3ed852e2009-09-05 21:47:34 +00003299 continue;
3300 }
3301 sigmoidal_map[i]=(MagickRealType) ScaleMapToQuantum((MagickRealType)
cristy33bd5152011-08-24 01:42:24 +00003302 (MaxMap*(QuantumScale*midpoint-log((1.0-(1.0/(1.0+exp(midpoint/(double)
3303 QuantumRange*contrast))+((double) i/MaxMap)*((1.0/(1.0+exp(contrast*(
3304 midpoint/(double) QuantumRange-1.0))))-(1.0/(1.0+exp(midpoint/(double)
3305 QuantumRange*contrast))))))/(1.0/(1.0+exp(midpoint/(double) QuantumRange*
3306 contrast))+((double) i/MaxMap)*((1.0/(1.0+exp(contrast*(midpoint/(double)
3307 QuantumRange-1.0))))-(1.0/(1.0+exp(midpoint/(double) QuantumRange*
3308 contrast))))))/contrast)));
cristy3ed852e2009-09-05 21:47:34 +00003309 }
3310 if (image->storage_class == PseudoClass)
3311 {
3312 /*
3313 Sigmoidal-contrast enhance colormap.
3314 */
cristyb5d5f722009-11-04 03:03:49 +00003315#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy564a5692012-01-20 23:56:26 +00003316 #pragma omp parallel for schedule(static,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003317#endif
cristybb503372010-05-27 20:51:26 +00003318 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00003319 {
cristyed231572011-07-14 02:18:59 +00003320 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristyda1f9c12011-10-02 21:39:49 +00003321 image->colormap[i].red=sigmoidal_map[ScaleQuantumToMap(
3322 ClampToQuantum(image->colormap[i].red))];
cristyed231572011-07-14 02:18:59 +00003323 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristyda1f9c12011-10-02 21:39:49 +00003324 image->colormap[i].green=sigmoidal_map[ScaleQuantumToMap(
3325 ClampToQuantum(image->colormap[i].green))];
cristyed231572011-07-14 02:18:59 +00003326 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristyda1f9c12011-10-02 21:39:49 +00003327 image->colormap[i].blue=sigmoidal_map[ScaleQuantumToMap(
3328 ClampToQuantum(image->colormap[i].blue))];
cristyed231572011-07-14 02:18:59 +00003329 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristyda1f9c12011-10-02 21:39:49 +00003330 image->colormap[i].alpha=sigmoidal_map[ScaleQuantumToMap(
3331 ClampToQuantum(image->colormap[i].alpha))];
cristy3ed852e2009-09-05 21:47:34 +00003332 }
3333 }
3334 /*
3335 Sigmoidal-contrast enhance image.
3336 */
3337 status=MagickTrue;
3338 progress=0;
cristy3ed852e2009-09-05 21:47:34 +00003339 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00003340#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristye6178502011-12-23 17:02:29 +00003341 #pragma omp parallel for schedule(static,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003342#endif
cristybb503372010-05-27 20:51:26 +00003343 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003344 {
cristy4c08aed2011-07-01 19:47:50 +00003345 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00003346 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003347
cristy8d4629b2010-08-30 17:59:46 +00003348 register ssize_t
3349 x;
3350
cristy3ed852e2009-09-05 21:47:34 +00003351 if (status == MagickFalse)
3352 continue;
3353 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristyacd2ed22011-08-30 01:44:23 +00003354 if (q == (Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00003355 {
3356 status=MagickFalse;
3357 continue;
3358 }
cristybb503372010-05-27 20:51:26 +00003359 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003360 {
cristy33bd5152011-08-24 01:42:24 +00003361 register ssize_t
3362 i;
3363
3364 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
3365 {
cristyabace412011-12-11 15:56:53 +00003366 PixelChannel
3367 channel;
3368
cristy33bd5152011-08-24 01:42:24 +00003369 PixelTrait
3370 traits;
3371
cristyabace412011-12-11 15:56:53 +00003372 channel=GetPixelChannelMapChannel(image,i);
3373 traits=GetPixelChannelMapTraits(image,channel);
cristy33bd5152011-08-24 01:42:24 +00003374 if ((traits & UpdatePixelTrait) != 0)
3375 q[i]=ClampToQuantum(sigmoidal_map[ScaleQuantumToMap(q[i])]);
3376 }
cristyed231572011-07-14 02:18:59 +00003377 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00003378 }
3379 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
3380 status=MagickFalse;
3381 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3382 {
3383 MagickBooleanType
3384 proceed;
3385
cristyb5d5f722009-11-04 03:03:49 +00003386#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy564a5692012-01-20 23:56:26 +00003387 #pragma omp critical (MagickCore_SigmoidalContrastImage)
cristy3ed852e2009-09-05 21:47:34 +00003388#endif
3389 proceed=SetImageProgress(image,SigmoidalContrastImageTag,progress++,
3390 image->rows);
3391 if (proceed == MagickFalse)
3392 status=MagickFalse;
3393 }
3394 }
3395 image_view=DestroyCacheView(image_view);
3396 sigmoidal_map=(MagickRealType *) RelinquishMagickMemory(sigmoidal_map);
3397 return(status);
3398}