blob: c9ea75d6da6ae00cddca120897146f01f14eb5fa [file] [log] [blame]
cristy3ed852e2009-09-05 21:47:34 +00001/*
2%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3% %
4% %
5% %
6% EEEEE N N H H AAA N N CCCC EEEEE %
7% E NN N H H A A NN N C E %
8% EEE N N N HHHHH AAAAA N N N C EEE %
9% E N NN H H A A N NN C E %
10% EEEEE N N H H A A N N CCCC EEEEE %
11% %
12% %
13% MagickCore Image Enhancement Methods %
14% %
15% Software Design %
16% John Cristy %
17% July 1992 %
18% %
19% %
cristy7e41fe82010-12-04 23:12:08 +000020% Copyright 1999-2011 ImageMagick Studio LLC, a non-profit organization %
cristy3ed852e2009-09-05 21:47:34 +000021% dedicated to making software imaging solutions freely available. %
22% %
23% You may not use this file except in compliance with the License. You may %
24% obtain a copy of the License at %
25% %
26% http://www.imagemagick.org/script/license.php %
27% %
28% Unless required by applicable law or agreed to in writing, software %
29% distributed under the License is distributed on an "AS IS" BASIS, %
30% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %
31% See the License for the specific language governing permissions and %
32% limitations under the License. %
33% %
34%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
35%
36%
37%
38*/
39
40/*
41 Include declarations.
42*/
cristy4c08aed2011-07-01 19:47:50 +000043#include "MagickCore/studio.h"
44#include "MagickCore/artifact.h"
45#include "MagickCore/cache.h"
46#include "MagickCore/cache-view.h"
47#include "MagickCore/color.h"
48#include "MagickCore/color-private.h"
49#include "MagickCore/colorspace.h"
50#include "MagickCore/composite-private.h"
51#include "MagickCore/enhance.h"
52#include "MagickCore/exception.h"
53#include "MagickCore/exception-private.h"
54#include "MagickCore/fx.h"
55#include "MagickCore/gem.h"
cristyd1dd6e42011-09-04 01:46:08 +000056#include "MagickCore/gem-private.h"
cristy4c08aed2011-07-01 19:47:50 +000057#include "MagickCore/geometry.h"
58#include "MagickCore/histogram.h"
59#include "MagickCore/image.h"
60#include "MagickCore/image-private.h"
61#include "MagickCore/memory_.h"
62#include "MagickCore/monitor.h"
63#include "MagickCore/monitor-private.h"
64#include "MagickCore/option.h"
65#include "MagickCore/pixel-accessor.h"
66#include "MagickCore/quantum.h"
67#include "MagickCore/quantum-private.h"
68#include "MagickCore/resample.h"
69#include "MagickCore/resample-private.h"
70#include "MagickCore/statistic.h"
71#include "MagickCore/string_.h"
72#include "MagickCore/string-private.h"
73#include "MagickCore/thread-private.h"
74#include "MagickCore/token.h"
75#include "MagickCore/xml-tree.h"
cristy433d1182011-09-04 13:38:52 +000076#include "MagickCore/xml-tree-private.h"
cristy3ed852e2009-09-05 21:47:34 +000077
78/*
79%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
80% %
81% %
82% %
83% A u t o G a m m a I m a g e %
84% %
85% %
86% %
87%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
88%
89% AutoGammaImage() extract the 'mean' from the image and adjust the image
90% to try make set its gamma appropriatally.
91%
cristy308b4e62009-09-21 14:40:44 +000092% The format of the AutoGammaImage method is:
cristy3ed852e2009-09-05 21:47:34 +000093%
cristy95111202011-08-09 19:41:42 +000094% MagickBooleanType AutoGammaImage(Image *image,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +000095%
96% A description of each parameter follows:
97%
98% o image: The image to auto-level
99%
cristy95111202011-08-09 19:41:42 +0000100% o exception: return any errors or warnings in this structure.
101%
cristy3ed852e2009-09-05 21:47:34 +0000102*/
cristy95111202011-08-09 19:41:42 +0000103MagickExport MagickBooleanType AutoGammaImage(Image *image,
104 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000105{
cristy3ed852e2009-09-05 21:47:34 +0000106 double
cristy4c08aed2011-07-01 19:47:50 +0000107 gamma,
108 log_mean,
109 mean,
110 sans;
anthony4efe5972009-09-11 06:46:40 +0000111
cristy95111202011-08-09 19:41:42 +0000112 MagickStatusType
113 status;
114
cristy01e9afd2011-08-10 17:38:41 +0000115 register ssize_t
116 i;
117
cristy4c08aed2011-07-01 19:47:50 +0000118 log_mean=log(0.5);
cristyab015852011-07-06 01:03:32 +0000119 if (image->sync != MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +0000120 {
121 /*
cristy01e9afd2011-08-10 17:38:41 +0000122 Apply gamma correction equally across all given channels.
cristy3ed852e2009-09-05 21:47:34 +0000123 */
cristy95111202011-08-09 19:41:42 +0000124 (void) GetImageMean(image,&mean,&sans,exception);
cristy4c08aed2011-07-01 19:47:50 +0000125 gamma=log(mean*QuantumScale)/log_mean;
cristy01e9afd2011-08-10 17:38:41 +0000126 return(LevelImage(image,0.0,(double) QuantumRange,gamma,exception));
cristy3ed852e2009-09-05 21:47:34 +0000127 }
cristy3ed852e2009-09-05 21:47:34 +0000128 /*
cristy4c08aed2011-07-01 19:47:50 +0000129 Auto-gamma each channel separately.
cristy3ed852e2009-09-05 21:47:34 +0000130 */
cristy4c08aed2011-07-01 19:47:50 +0000131 status=MagickTrue;
cristy01e9afd2011-08-10 17:38:41 +0000132 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
133 {
cristybd5a96c2011-08-21 00:04:26 +0000134 ChannelType
135 channel_mask;
136
cristy01e9afd2011-08-10 17:38:41 +0000137 PixelTrait
138 traits;
139
140 traits=GetPixelChannelMapTraits(image,(PixelChannel) i);
141 if ((traits & UpdatePixelTrait) == 0)
142 continue;
cristya13d0822011-09-19 00:19:19 +0000143 channel_mask=SetPixelChannelMask(image,(ChannelType) (1 << i));
cristy01e9afd2011-08-10 17:38:41 +0000144 status=GetImageMean(image,&mean,&sans,exception);
145 gamma=log(mean*QuantumScale)/log_mean;
146 status&=LevelImage(image,0.0,(double) QuantumRange,gamma,exception);
cristybd5a96c2011-08-21 00:04:26 +0000147 (void) SetPixelChannelMask(image,channel_mask);
cristy01e9afd2011-08-10 17:38:41 +0000148 if (status == MagickFalse)
149 break;
150 }
cristy3ed852e2009-09-05 21:47:34 +0000151 return(status != 0 ? MagickTrue : MagickFalse);
152}
153
154/*
155%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
156% %
157% %
158% %
159% A u t o L e v e l I m a g e %
160% %
161% %
162% %
163%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
164%
165% AutoLevelImage() adjusts the levels of a particular image channel by
166% scaling the minimum and maximum values to the full quantum range.
167%
168% The format of the LevelImage method is:
169%
cristy95111202011-08-09 19:41:42 +0000170% MagickBooleanType AutoLevelImage(Image *image,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000171%
172% A description of each parameter follows:
173%
174% o image: The image to auto-level
175%
cristy95111202011-08-09 19:41:42 +0000176% o exception: return any errors or warnings in this structure.
177%
cristy3ed852e2009-09-05 21:47:34 +0000178*/
cristy95111202011-08-09 19:41:42 +0000179MagickExport MagickBooleanType AutoLevelImage(Image *image,
180 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000181{
cristyb303c3d2011-09-09 11:24:40 +0000182 return(MinMaxStretchImage(image,0.0,0.0,1.0,exception));
cristy3ed852e2009-09-05 21:47:34 +0000183}
184
185/*
186%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
187% %
188% %
189% %
cristya28d6b82010-01-11 20:03:47 +0000190% B r i g h t n e s s C o n t r a s t I m a g e %
191% %
192% %
193% %
194%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
195%
cristyf4356f92011-08-01 15:33:48 +0000196% BrightnessContrastImage() changes the brightness and/or contrast of an
197% image. It converts the brightness and contrast parameters into slope and
198% intercept and calls a polynomical function to apply to the image.
cristya28d6b82010-01-11 20:03:47 +0000199%
200% The format of the BrightnessContrastImage method is:
201%
202% MagickBooleanType BrightnessContrastImage(Image *image,
cristy444eda62011-08-10 02:07:46 +0000203% const double brightness,const double contrast,ExceptionInfo *exception)
cristya28d6b82010-01-11 20:03:47 +0000204%
205% A description of each parameter follows:
206%
207% o image: the image.
208%
cristya28d6b82010-01-11 20:03:47 +0000209% o brightness: the brightness percent (-100 .. 100).
210%
211% o contrast: the contrast percent (-100 .. 100).
212%
cristy444eda62011-08-10 02:07:46 +0000213% o exception: return any errors or warnings in this structure.
214%
cristya28d6b82010-01-11 20:03:47 +0000215*/
cristya28d6b82010-01-11 20:03:47 +0000216MagickExport MagickBooleanType BrightnessContrastImage(Image *image,
cristy444eda62011-08-10 02:07:46 +0000217 const double brightness,const double contrast,ExceptionInfo *exception)
cristya28d6b82010-01-11 20:03:47 +0000218{
cristya28d6b82010-01-11 20:03:47 +0000219#define BrightnessContastImageTag "BrightnessContast/Image"
220
221 double
222 alpha,
cristya28d6b82010-01-11 20:03:47 +0000223 coefficients[2],
cristye23ec9d2011-08-16 18:15:40 +0000224 intercept,
cristya28d6b82010-01-11 20:03:47 +0000225 slope;
226
227 MagickBooleanType
228 status;
229
230 /*
231 Compute slope and intercept.
232 */
233 assert(image != (Image *) NULL);
234 assert(image->signature == MagickSignature);
235 if (image->debug != MagickFalse)
236 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
237 alpha=contrast;
cristy4205a3c2010-09-12 20:19:59 +0000238 slope=tan((double) (MagickPI*(alpha/100.0+1.0)/4.0));
cristya28d6b82010-01-11 20:03:47 +0000239 if (slope < 0.0)
240 slope=0.0;
241 intercept=brightness/100.0+((100-brightness)/200.0)*(1.0-slope);
242 coefficients[0]=slope;
243 coefficients[1]=intercept;
cristy444eda62011-08-10 02:07:46 +0000244 status=FunctionImage(image,PolynomialFunction,2,coefficients,exception);
245 return(status);
246}
247
248/*
249%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
250% %
251% %
252% %
253% C l u t I m a g e %
254% %
255% %
256% %
257%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
258%
259% ClutImage() replaces each color value in the given image, by using it as an
260% index to lookup a replacement color value in a Color Look UP Table in the
261% form of an image. The values are extracted along a diagonal of the CLUT
262% image so either a horizontal or vertial gradient image can be used.
263%
264% Typically this is used to either re-color a gray-scale image according to a
265% color gradient in the CLUT image, or to perform a freeform histogram
266% (level) adjustment according to the (typically gray-scale) gradient in the
267% CLUT image.
268%
269% When the 'channel' mask includes the matte/alpha transparency channel but
270% one image has no such channel it is assumed that that image is a simple
271% gray-scale image that will effect the alpha channel values, either for
272% gray-scale coloring (with transparent or semi-transparent colors), or
273% a histogram adjustment of existing alpha channel values. If both images
274% have matte channels, direct and normal indexing is applied, which is rarely
275% used.
276%
277% The format of the ClutImage method is:
278%
279% MagickBooleanType ClutImage(Image *image,Image *clut_image,
cristy5c4e2582011-09-11 19:21:03 +0000280% const PixelInterpolateMethod method,ExceptionInfo *exception)
cristy444eda62011-08-10 02:07:46 +0000281%
282% A description of each parameter follows:
283%
284% o image: the image, which is replaced by indexed CLUT values
285%
286% o clut_image: the color lookup table image for replacement color values.
287%
cristy5c4e2582011-09-11 19:21:03 +0000288% o method: the pixel interpolation method.
cristy444eda62011-08-10 02:07:46 +0000289%
290% o exception: return any errors or warnings in this structure.
291%
292*/
293MagickExport MagickBooleanType ClutImage(Image *image,const Image *clut_image,
cristy5c4e2582011-09-11 19:21:03 +0000294 const PixelInterpolateMethod method,ExceptionInfo *exception)
cristy444eda62011-08-10 02:07:46 +0000295{
cristy444eda62011-08-10 02:07:46 +0000296#define ClutImageTag "Clut/Image"
297
298 CacheView
299 *clut_view,
300 *image_view;
301
302 double
303 *clut_map;
304
305 MagickBooleanType
306 status;
307
308 MagickOffsetType
309 progress;
310
311 register ssize_t
312 x;
313
314 ssize_t
315 adjust,
316 y;
317
318 assert(image != (Image *) NULL);
319 assert(image->signature == MagickSignature);
320 if (image->debug != MagickFalse)
321 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
322 assert(clut_image != (Image *) NULL);
323 assert(clut_image->signature == MagickSignature);
324 if (SetImageStorageClass(image,DirectClass,exception) == MagickFalse)
325 return(MagickFalse);
326 clut_map=(double *) AcquireQuantumMemory(MaxMap+1UL,GetPixelChannels(image)*
327 sizeof(*clut_map));
328 if (clut_map == (double *) NULL)
329 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
330 image->filename);
331 /*
332 Clut image.
333 */
334 status=MagickTrue;
335 progress=0;
336 adjust=(ssize_t) (clut_image->interpolate == IntegerInterpolatePixel ? 0 : 1);
337 clut_view=AcquireCacheView(clut_image);
338#if defined(MAGICKCORE_OPENMP_SUPPORT)
339 #pragma omp parallel for schedule(dynamic,4)
340#endif
341 for (x=0; x <= (ssize_t) MaxMap; x++)
342 {
343 register ssize_t
344 i;
345
346 for (i=0; i < (ssize_t) GetPixelChannels(clut_image); i++)
347 (void) InterpolatePixelChannel(clut_image,clut_view,(PixelChannel) i,
cristy5c4e2582011-09-11 19:21:03 +0000348 method,QuantumScale*x*(clut_image->columns-adjust),QuantumScale*x*
349 (clut_image->rows-adjust),clut_map+x*GetPixelChannels(clut_image)+i,
350 exception);
cristy444eda62011-08-10 02:07:46 +0000351 }
352 clut_view=DestroyCacheView(clut_view);
353 image_view=AcquireCacheView(image);
354#if defined(MAGICKCORE_OPENMP_SUPPORT)
355 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
356#endif
357 for (y=0; y < (ssize_t) image->rows; y++)
358 {
359 register Quantum
360 *restrict q;
361
362 register ssize_t
363 x;
364
365 if (status == MagickFalse)
366 continue;
367 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
368 if (q == (Quantum *) NULL)
369 {
370 status=MagickFalse;
371 continue;
372 }
373 for (x=0; x < (ssize_t) image->columns; x++)
374 {
375 register ssize_t
376 i;
377
378 for (i=0; i < (ssize_t) GetPixelChannels(clut_image); i++)
379 {
380 PixelChannel
381 channel;
382
cristy1aaa3cd2011-08-21 23:48:17 +0000383 PixelTrait
cristy444eda62011-08-10 02:07:46 +0000384 clut_traits,
385 traits;
386
387 clut_traits=GetPixelChannelMapTraits(clut_image,(PixelChannel) i);
cristy444eda62011-08-10 02:07:46 +0000388 channel=GetPixelChannelMapChannel(clut_image,(PixelChannel) i);
389 traits=GetPixelChannelMapTraits(clut_image,channel);
cristy010d7d12011-08-31 01:02:48 +0000390 if ((traits == UndefinedPixelTrait) ||
391 (clut_traits == UndefinedPixelTrait) ||
392 ((traits & UpdatePixelTrait) == 0))
393 continue;
394 q[channel]=ClampToQuantum(clut_map[ScaleQuantumToMap(q[channel])*
395 GetPixelChannels(clut_image)+channel]);
cristy444eda62011-08-10 02:07:46 +0000396 }
397 q+=GetPixelChannels(image);
398 }
399 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
400 status=MagickFalse;
401 if (image->progress_monitor != (MagickProgressMonitor) NULL)
402 {
403 MagickBooleanType
404 proceed;
405
406#if defined(MAGICKCORE_OPENMP_SUPPORT)
407 #pragma omp critical (MagickCore_ClutImage)
408#endif
409 proceed=SetImageProgress(image,ClutImageTag,progress++,image->rows);
410 if (proceed == MagickFalse)
411 status=MagickFalse;
412 }
413 }
414 image_view=DestroyCacheView(image_view);
415 clut_map=(double *) RelinquishMagickMemory(clut_map);
416 if ((clut_image->matte != MagickFalse) &&
417 ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0))
418 (void) SetImageAlphaChannel(image,ActivateAlphaChannel,exception);
cristya28d6b82010-01-11 20:03:47 +0000419 return(status);
420}
421
422/*
423%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
424% %
425% %
426% %
cristy3ed852e2009-09-05 21:47:34 +0000427% C o l o r D e c i s i o n L i s t I m a g e %
428% %
429% %
430% %
431%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
432%
433% ColorDecisionListImage() accepts a lightweight Color Correction Collection
434% (CCC) file which solely contains one or more color corrections and applies
435% the correction to the image. Here is a sample CCC file:
436%
437% <ColorCorrectionCollection xmlns="urn:ASC:CDL:v1.2">
438% <ColorCorrection id="cc03345">
439% <SOPNode>
440% <Slope> 0.9 1.2 0.5 </Slope>
441% <Offset> 0.4 -0.5 0.6 </Offset>
442% <Power> 1.0 0.8 1.5 </Power>
443% </SOPNode>
444% <SATNode>
445% <Saturation> 0.85 </Saturation>
446% </SATNode>
447% </ColorCorrection>
448% </ColorCorrectionCollection>
449%
450% which includes the slop, offset, and power for each of the RGB channels
451% as well as the saturation.
452%
453% The format of the ColorDecisionListImage method is:
454%
455% MagickBooleanType ColorDecisionListImage(Image *image,
cristy1bfa9f02011-08-11 02:35:43 +0000456% const char *color_correction_collection,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000457%
458% A description of each parameter follows:
459%
460% o image: the image.
461%
462% o color_correction_collection: the color correction collection in XML.
463%
cristy1bfa9f02011-08-11 02:35:43 +0000464% o exception: return any errors or warnings in this structure.
465%
cristy3ed852e2009-09-05 21:47:34 +0000466*/
467MagickExport MagickBooleanType ColorDecisionListImage(Image *image,
cristy1bfa9f02011-08-11 02:35:43 +0000468 const char *color_correction_collection,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000469{
470#define ColorDecisionListCorrectImageTag "ColorDecisionList/Image"
471
472 typedef struct _Correction
473 {
474 double
475 slope,
476 offset,
477 power;
478 } Correction;
479
480 typedef struct _ColorCorrection
481 {
482 Correction
483 red,
484 green,
485 blue;
486
487 double
488 saturation;
489 } ColorCorrection;
490
cristyc4c8d132010-01-07 01:58:38 +0000491 CacheView
492 *image_view;
493
cristy3ed852e2009-09-05 21:47:34 +0000494 char
495 token[MaxTextExtent];
496
497 ColorCorrection
498 color_correction;
499
500 const char
501 *content,
502 *p;
503
cristy3ed852e2009-09-05 21:47:34 +0000504 MagickBooleanType
505 status;
506
cristybb503372010-05-27 20:51:26 +0000507 MagickOffsetType
508 progress;
509
cristy3ed852e2009-09-05 21:47:34 +0000510 PixelPacket
511 *cdl_map;
512
cristybb503372010-05-27 20:51:26 +0000513 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000514 i;
515
cristybb503372010-05-27 20:51:26 +0000516 ssize_t
517 y;
518
cristy3ed852e2009-09-05 21:47:34 +0000519 XMLTreeInfo
520 *cc,
521 *ccc,
522 *sat,
523 *sop;
524
cristy3ed852e2009-09-05 21:47:34 +0000525 /*
526 Allocate and initialize cdl maps.
527 */
528 assert(image != (Image *) NULL);
529 assert(image->signature == MagickSignature);
530 if (image->debug != MagickFalse)
531 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
532 if (color_correction_collection == (const char *) NULL)
533 return(MagickFalse);
cristy1bfa9f02011-08-11 02:35:43 +0000534 ccc=NewXMLTree((const char *) color_correction_collection,exception);
cristy3ed852e2009-09-05 21:47:34 +0000535 if (ccc == (XMLTreeInfo *) NULL)
536 return(MagickFalse);
537 cc=GetXMLTreeChild(ccc,"ColorCorrection");
538 if (cc == (XMLTreeInfo *) NULL)
539 {
540 ccc=DestroyXMLTree(ccc);
541 return(MagickFalse);
542 }
543 color_correction.red.slope=1.0;
544 color_correction.red.offset=0.0;
545 color_correction.red.power=1.0;
546 color_correction.green.slope=1.0;
547 color_correction.green.offset=0.0;
548 color_correction.green.power=1.0;
549 color_correction.blue.slope=1.0;
550 color_correction.blue.offset=0.0;
551 color_correction.blue.power=1.0;
552 color_correction.saturation=0.0;
553 sop=GetXMLTreeChild(cc,"SOPNode");
554 if (sop != (XMLTreeInfo *) NULL)
555 {
556 XMLTreeInfo
557 *offset,
558 *power,
559 *slope;
560
561 slope=GetXMLTreeChild(sop,"Slope");
562 if (slope != (XMLTreeInfo *) NULL)
563 {
564 content=GetXMLTreeContent(slope);
565 p=(const char *) content;
566 for (i=0; (*p != '\0') && (i < 3); i++)
567 {
568 GetMagickToken(p,&p,token);
569 if (*token == ',')
570 GetMagickToken(p,&p,token);
571 switch (i)
572 {
cristyc1acd842011-05-19 23:05:47 +0000573 case 0:
574 {
575 color_correction.red.slope=InterpretLocaleValue(token,
576 (char **) NULL);
577 break;
578 }
579 case 1:
580 {
581 color_correction.green.slope=InterpretLocaleValue(token,
582 (char **) NULL);
583 break;
584 }
585 case 2:
586 {
587 color_correction.blue.slope=InterpretLocaleValue(token,
588 (char **) NULL);
589 break;
590 }
cristy3ed852e2009-09-05 21:47:34 +0000591 }
592 }
593 }
594 offset=GetXMLTreeChild(sop,"Offset");
595 if (offset != (XMLTreeInfo *) NULL)
596 {
597 content=GetXMLTreeContent(offset);
598 p=(const char *) content;
599 for (i=0; (*p != '\0') && (i < 3); i++)
600 {
601 GetMagickToken(p,&p,token);
602 if (*token == ',')
603 GetMagickToken(p,&p,token);
604 switch (i)
605 {
cristyc1acd842011-05-19 23:05:47 +0000606 case 0:
607 {
608 color_correction.red.offset=InterpretLocaleValue(token,
609 (char **) NULL);
610 break;
611 }
612 case 1:
613 {
614 color_correction.green.offset=InterpretLocaleValue(token,
615 (char **) NULL);
616 break;
617 }
618 case 2:
619 {
620 color_correction.blue.offset=InterpretLocaleValue(token,
621 (char **) NULL);
622 break;
623 }
cristy3ed852e2009-09-05 21:47:34 +0000624 }
625 }
626 }
627 power=GetXMLTreeChild(sop,"Power");
628 if (power != (XMLTreeInfo *) NULL)
629 {
630 content=GetXMLTreeContent(power);
631 p=(const char *) content;
632 for (i=0; (*p != '\0') && (i < 3); i++)
633 {
634 GetMagickToken(p,&p,token);
635 if (*token == ',')
636 GetMagickToken(p,&p,token);
637 switch (i)
638 {
cristyc1acd842011-05-19 23:05:47 +0000639 case 0:
640 {
641 color_correction.red.power=InterpretLocaleValue(token,
642 (char **) NULL);
643 break;
644 }
645 case 1:
646 {
647 color_correction.green.power=InterpretLocaleValue(token,
648 (char **) NULL);
649 break;
650 }
651 case 2:
652 {
653 color_correction.blue.power=InterpretLocaleValue(token,
654 (char **) NULL);
655 break;
656 }
cristy3ed852e2009-09-05 21:47:34 +0000657 }
658 }
659 }
660 }
661 sat=GetXMLTreeChild(cc,"SATNode");
662 if (sat != (XMLTreeInfo *) NULL)
663 {
664 XMLTreeInfo
665 *saturation;
666
667 saturation=GetXMLTreeChild(sat,"Saturation");
668 if (saturation != (XMLTreeInfo *) NULL)
669 {
670 content=GetXMLTreeContent(saturation);
671 p=(const char *) content;
672 GetMagickToken(p,&p,token);
cristyc1acd842011-05-19 23:05:47 +0000673 color_correction.saturation=InterpretLocaleValue(token,
674 (char **) NULL);
cristy3ed852e2009-09-05 21:47:34 +0000675 }
676 }
677 ccc=DestroyXMLTree(ccc);
678 if (image->debug != MagickFalse)
679 {
680 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
681 " Color Correction Collection:");
682 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000683 " color_correction.red.slope: %g",color_correction.red.slope);
cristy3ed852e2009-09-05 21:47:34 +0000684 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000685 " color_correction.red.offset: %g",color_correction.red.offset);
cristy3ed852e2009-09-05 21:47:34 +0000686 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000687 " color_correction.red.power: %g",color_correction.red.power);
cristy3ed852e2009-09-05 21:47:34 +0000688 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000689 " color_correction.green.slope: %g",color_correction.green.slope);
cristy3ed852e2009-09-05 21:47:34 +0000690 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000691 " color_correction.green.offset: %g",color_correction.green.offset);
cristy3ed852e2009-09-05 21:47:34 +0000692 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000693 " color_correction.green.power: %g",color_correction.green.power);
cristy3ed852e2009-09-05 21:47:34 +0000694 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000695 " color_correction.blue.slope: %g",color_correction.blue.slope);
cristy3ed852e2009-09-05 21:47:34 +0000696 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000697 " color_correction.blue.offset: %g",color_correction.blue.offset);
cristy3ed852e2009-09-05 21:47:34 +0000698 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000699 " color_correction.blue.power: %g",color_correction.blue.power);
cristy3ed852e2009-09-05 21:47:34 +0000700 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000701 " color_correction.saturation: %g",color_correction.saturation);
cristy3ed852e2009-09-05 21:47:34 +0000702 }
703 cdl_map=(PixelPacket *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*cdl_map));
704 if (cdl_map == (PixelPacket *) NULL)
705 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
706 image->filename);
cristyb5d5f722009-11-04 03:03:49 +0000707#if defined(MAGICKCORE_OPENMP_SUPPORT)
708 #pragma omp parallel for schedule(dynamic,4)
cristy3ed852e2009-09-05 21:47:34 +0000709#endif
cristybb503372010-05-27 20:51:26 +0000710 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +0000711 {
cristy1bfa9f02011-08-11 02:35:43 +0000712 cdl_map[i].red=ClampToQuantum((MagickRealType) ScaleMapToQuantum(
713 (MagickRealType) (MaxMap*(pow(color_correction.red.slope*i/MaxMap+
cristy3ed852e2009-09-05 21:47:34 +0000714 color_correction.red.offset,color_correction.red.power)))));
cristy1bfa9f02011-08-11 02:35:43 +0000715 cdl_map[i].green=ClampToQuantum((MagickRealType) ScaleMapToQuantum(
716 (MagickRealType) (MaxMap*(pow(color_correction.green.slope*i/MaxMap+
cristy3ed852e2009-09-05 21:47:34 +0000717 color_correction.green.offset,color_correction.green.power)))));
cristy1bfa9f02011-08-11 02:35:43 +0000718 cdl_map[i].blue=ClampToQuantum((MagickRealType) ScaleMapToQuantum(
719 (MagickRealType) (MaxMap*(pow(color_correction.blue.slope*i/MaxMap+
cristy3ed852e2009-09-05 21:47:34 +0000720 color_correction.blue.offset,color_correction.blue.power)))));
721 }
722 if (image->storage_class == PseudoClass)
723 {
724 /*
725 Apply transfer function to colormap.
726 */
cristyb5d5f722009-11-04 03:03:49 +0000727#if defined(MAGICKCORE_OPENMP_SUPPORT)
728 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000729#endif
cristybb503372010-05-27 20:51:26 +0000730 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +0000731 {
732 double
733 luma;
734
735 luma=0.2126*image->colormap[i].red+0.7152*image->colormap[i].green+
736 0.0722*image->colormap[i].blue;
cristy1bfa9f02011-08-11 02:35:43 +0000737 image->colormap[i].red=ClampToQuantum(luma+
738 color_correction.saturation*cdl_map[ScaleQuantumToMap(
739 image->colormap[i].red)].red-luma);
cristyce70c172010-01-07 17:15:30 +0000740 image->colormap[i].green=ClampToQuantum(luma+
cristy3ed852e2009-09-05 21:47:34 +0000741 color_correction.saturation*cdl_map[ScaleQuantumToMap(
742 image->colormap[i].green)].green-luma);
cristy1bfa9f02011-08-11 02:35:43 +0000743 image->colormap[i].blue=ClampToQuantum(luma+
744 color_correction.saturation*cdl_map[ScaleQuantumToMap(
745 image->colormap[i].blue)].blue-luma);
cristy3ed852e2009-09-05 21:47:34 +0000746 }
747 }
748 /*
749 Apply transfer function to image.
750 */
751 status=MagickTrue;
752 progress=0;
cristy3ed852e2009-09-05 21:47:34 +0000753 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +0000754#if defined(MAGICKCORE_OPENMP_SUPPORT)
755 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000756#endif
cristybb503372010-05-27 20:51:26 +0000757 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000758 {
759 double
760 luma;
761
cristy4c08aed2011-07-01 19:47:50 +0000762 register Quantum
cristyc47d1f82009-11-26 01:44:43 +0000763 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000764
cristy8d4629b2010-08-30 17:59:46 +0000765 register ssize_t
766 x;
767
cristy3ed852e2009-09-05 21:47:34 +0000768 if (status == MagickFalse)
769 continue;
770 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristyacd2ed22011-08-30 01:44:23 +0000771 if (q == (Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +0000772 {
773 status=MagickFalse;
774 continue;
775 }
cristybb503372010-05-27 20:51:26 +0000776 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000777 {
cristy4c08aed2011-07-01 19:47:50 +0000778 luma=0.2126*GetPixelRed(image,q)+0.7152*GetPixelGreen(image,q)+0.0722*
779 GetPixelBlue(image,q);
780 SetPixelRed(image,ClampToQuantum(luma+color_correction.saturation*
781 (cdl_map[ScaleQuantumToMap(GetPixelRed(image,q))].red-luma)),q);
782 SetPixelGreen(image,ClampToQuantum(luma+color_correction.saturation*
783 (cdl_map[ScaleQuantumToMap(GetPixelGreen(image,q))].green-luma)),q);
784 SetPixelBlue(image,ClampToQuantum(luma+color_correction.saturation*
785 (cdl_map[ScaleQuantumToMap(GetPixelBlue(image,q))].blue-luma)),q);
cristyed231572011-07-14 02:18:59 +0000786 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +0000787 }
788 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
789 status=MagickFalse;
790 if (image->progress_monitor != (MagickProgressMonitor) NULL)
791 {
792 MagickBooleanType
793 proceed;
794
cristyb5d5f722009-11-04 03:03:49 +0000795#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +0000796 #pragma omp critical (MagickCore_ColorDecisionListImageChannel)
797#endif
798 proceed=SetImageProgress(image,ColorDecisionListCorrectImageTag,
799 progress++,image->rows);
800 if (proceed == MagickFalse)
801 status=MagickFalse;
802 }
803 }
804 image_view=DestroyCacheView(image_view);
805 cdl_map=(PixelPacket *) RelinquishMagickMemory(cdl_map);
806 return(status);
807}
808
809/*
810%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
811% %
812% %
813% %
cristy3ed852e2009-09-05 21:47:34 +0000814% C o n t r a s t I m a g e %
815% %
816% %
817% %
818%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
819%
820% ContrastImage() enhances the intensity differences between the lighter and
821% darker elements of the image. Set sharpen to a MagickTrue to increase the
822% image contrast otherwise the contrast is reduced.
823%
824% The format of the ContrastImage method is:
825%
826% MagickBooleanType ContrastImage(Image *image,
cristye23ec9d2011-08-16 18:15:40 +0000827% const MagickBooleanType sharpen,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000828%
829% A description of each parameter follows:
830%
831% o image: the image.
832%
833% o sharpen: Increase or decrease image contrast.
834%
cristye23ec9d2011-08-16 18:15:40 +0000835% o exception: return any errors or warnings in this structure.
836%
cristy3ed852e2009-09-05 21:47:34 +0000837*/
838
839static void Contrast(const int sign,Quantum *red,Quantum *green,Quantum *blue)
840{
841 double
842 brightness,
843 hue,
844 saturation;
845
846 /*
847 Enhance contrast: dark color become darker, light color become lighter.
848 */
849 assert(red != (Quantum *) NULL);
850 assert(green != (Quantum *) NULL);
851 assert(blue != (Quantum *) NULL);
852 hue=0.0;
853 saturation=0.0;
854 brightness=0.0;
855 ConvertRGBToHSB(*red,*green,*blue,&hue,&saturation,&brightness);
cristy31ac0f02011-03-12 02:04:47 +0000856 brightness+=0.5*sign*(0.5*(sin((double) (MagickPI*(brightness-0.5)))+1.0)-
cristy4205a3c2010-09-12 20:19:59 +0000857 brightness);
cristy3ed852e2009-09-05 21:47:34 +0000858 if (brightness > 1.0)
859 brightness=1.0;
860 else
861 if (brightness < 0.0)
862 brightness=0.0;
863 ConvertHSBToRGB(hue,saturation,brightness,red,green,blue);
864}
865
866MagickExport MagickBooleanType ContrastImage(Image *image,
cristye23ec9d2011-08-16 18:15:40 +0000867 const MagickBooleanType sharpen,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000868{
869#define ContrastImageTag "Contrast/Image"
870
cristyc4c8d132010-01-07 01:58:38 +0000871 CacheView
872 *image_view;
873
cristy3ed852e2009-09-05 21:47:34 +0000874 int
875 sign;
876
cristy3ed852e2009-09-05 21:47:34 +0000877 MagickBooleanType
878 status;
879
cristybb503372010-05-27 20:51:26 +0000880 MagickOffsetType
881 progress;
882
883 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000884 i;
885
cristybb503372010-05-27 20:51:26 +0000886 ssize_t
887 y;
888
cristy3ed852e2009-09-05 21:47:34 +0000889 assert(image != (Image *) NULL);
890 assert(image->signature == MagickSignature);
891 if (image->debug != MagickFalse)
892 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
893 sign=sharpen != MagickFalse ? 1 : -1;
894 if (image->storage_class == PseudoClass)
895 {
896 /*
897 Contrast enhance colormap.
898 */
cristybb503372010-05-27 20:51:26 +0000899 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +0000900 Contrast(sign,&image->colormap[i].red,&image->colormap[i].green,
901 &image->colormap[i].blue);
902 }
903 /*
904 Contrast enhance image.
905 */
906 status=MagickTrue;
907 progress=0;
cristy3ed852e2009-09-05 21:47:34 +0000908 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +0000909#if defined(MAGICKCORE_OPENMP_SUPPORT)
910 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000911#endif
cristybb503372010-05-27 20:51:26 +0000912 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000913 {
cristy5afeab82011-04-30 01:30:09 +0000914 Quantum
915 blue,
916 green,
917 red;
918
cristy4c08aed2011-07-01 19:47:50 +0000919 register Quantum
cristyc47d1f82009-11-26 01:44:43 +0000920 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000921
cristy8d4629b2010-08-30 17:59:46 +0000922 register ssize_t
923 x;
924
cristy3ed852e2009-09-05 21:47:34 +0000925 if (status == MagickFalse)
926 continue;
927 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristyacd2ed22011-08-30 01:44:23 +0000928 if (q == (Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +0000929 {
930 status=MagickFalse;
931 continue;
932 }
cristybb503372010-05-27 20:51:26 +0000933 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000934 {
cristy4c08aed2011-07-01 19:47:50 +0000935 red=GetPixelRed(image,q);
936 green=GetPixelGreen(image,q);
937 blue=GetPixelBlue(image,q);
cristy5afeab82011-04-30 01:30:09 +0000938 Contrast(sign,&red,&green,&blue);
cristy4c08aed2011-07-01 19:47:50 +0000939 SetPixelRed(image,red,q);
940 SetPixelGreen(image,green,q);
941 SetPixelBlue(image,blue,q);
cristyed231572011-07-14 02:18:59 +0000942 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +0000943 }
944 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
945 status=MagickFalse;
946 if (image->progress_monitor != (MagickProgressMonitor) NULL)
947 {
948 MagickBooleanType
949 proceed;
950
cristyb5d5f722009-11-04 03:03:49 +0000951#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +0000952 #pragma omp critical (MagickCore_ContrastImage)
953#endif
954 proceed=SetImageProgress(image,ContrastImageTag,progress++,image->rows);
955 if (proceed == MagickFalse)
956 status=MagickFalse;
957 }
958 }
959 image_view=DestroyCacheView(image_view);
960 return(status);
961}
962
963/*
964%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
965% %
966% %
967% %
968% C o n t r a s t S t r e t c h I m a g e %
969% %
970% %
971% %
972%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
973%
cristyf1611782011-08-01 15:39:13 +0000974% ContrastStretchImage() is a simple image enhancement technique that attempts
975% to improve the contrast in an image by `stretching' the range of intensity
976% values it contains to span a desired range of values. It differs from the
977% more sophisticated histogram equalization in that it can only apply a
978% linear scaling function to the image pixel values. As a result the
979% `enhancement' is less harsh.
cristy3ed852e2009-09-05 21:47:34 +0000980%
981% The format of the ContrastStretchImage method is:
982%
983% MagickBooleanType ContrastStretchImage(Image *image,
cristye23ec9d2011-08-16 18:15:40 +0000984% const char *levels,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000985%
986% A description of each parameter follows:
987%
988% o image: the image.
989%
cristy3ed852e2009-09-05 21:47:34 +0000990% o black_point: the black point.
991%
992% o white_point: the white point.
993%
994% o levels: Specify the levels where the black and white points have the
995% range of 0 to number-of-pixels (e.g. 1%, 10x90%, etc.).
996%
cristye23ec9d2011-08-16 18:15:40 +0000997% o exception: return any errors or warnings in this structure.
998%
cristy3ed852e2009-09-05 21:47:34 +0000999*/
cristy3ed852e2009-09-05 21:47:34 +00001000MagickExport MagickBooleanType ContrastStretchImage(Image *image,
cristye23ec9d2011-08-16 18:15:40 +00001001 const double black_point,const double white_point,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001002{
1003#define MaxRange(color) ((MagickRealType) ScaleQuantumToMap((Quantum) (color)))
1004#define ContrastStretchImageTag "ContrastStretch/Image"
1005
cristyc4c8d132010-01-07 01:58:38 +00001006 CacheView
1007 *image_view;
1008
cristy3ed852e2009-09-05 21:47:34 +00001009 MagickBooleanType
1010 status;
1011
cristybb503372010-05-27 20:51:26 +00001012 MagickOffsetType
1013 progress;
1014
cristyf45fec72011-08-23 16:02:32 +00001015 double
1016 *black,
cristy3ed852e2009-09-05 21:47:34 +00001017 *histogram,
1018 *stretch_map,
cristyf45fec72011-08-23 16:02:32 +00001019 *white;
cristy3ed852e2009-09-05 21:47:34 +00001020
cristybb503372010-05-27 20:51:26 +00001021 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001022 i;
1023
cristybb503372010-05-27 20:51:26 +00001024 ssize_t
1025 y;
1026
cristy3ed852e2009-09-05 21:47:34 +00001027 /*
1028 Allocate histogram and stretch map.
1029 */
1030 assert(image != (Image *) NULL);
1031 assert(image->signature == MagickSignature);
1032 if (image->debug != MagickFalse)
1033 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
cristyf45fec72011-08-23 16:02:32 +00001034 black=(double *) AcquireQuantumMemory(GetPixelChannels(image),sizeof(*black));
1035 white=(double *) AcquireQuantumMemory(GetPixelChannels(image),sizeof(*white));
1036 histogram=(double *) AcquireQuantumMemory(MaxMap+1UL,
cristy465571b2011-08-21 20:43:15 +00001037 GetPixelChannels(image)*sizeof(*histogram));
cristyf45fec72011-08-23 16:02:32 +00001038 stretch_map=(double *) AcquireQuantumMemory(MaxMap+1UL,
cristy465571b2011-08-21 20:43:15 +00001039 GetPixelChannels(image)*sizeof(*stretch_map));
cristyf45fec72011-08-23 16:02:32 +00001040 if ((black == (double *) NULL) || (white == (double *) NULL) ||
1041 (histogram == (double *) NULL) || (stretch_map == (double *) NULL))
cristy465571b2011-08-21 20:43:15 +00001042 {
cristyf45fec72011-08-23 16:02:32 +00001043 if (stretch_map != (double *) NULL)
1044 stretch_map=(double *) RelinquishMagickMemory(stretch_map);
1045 if (histogram != (double *) NULL)
1046 histogram=(double *) RelinquishMagickMemory(histogram);
1047 if (white != (double *) NULL)
1048 white=(double *) RelinquishMagickMemory(white);
1049 if (black != (double *) NULL)
1050 black=(double *) RelinquishMagickMemory(black);
cristy465571b2011-08-21 20:43:15 +00001051 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
1052 image->filename);
1053 }
cristy3ed852e2009-09-05 21:47:34 +00001054 /*
1055 Form histogram.
1056 */
1057 status=MagickTrue;
cristy465571b2011-08-21 20:43:15 +00001058 (void) ResetMagickMemory(histogram,0,(MaxMap+1)*GetPixelChannels(image)*
1059 sizeof(*histogram));
cristy3ed852e2009-09-05 21:47:34 +00001060 image_view=AcquireCacheView(image);
cristybb503372010-05-27 20:51:26 +00001061 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001062 {
cristy4c08aed2011-07-01 19:47:50 +00001063 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00001064 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00001065
cristybb503372010-05-27 20:51:26 +00001066 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001067 x;
1068
1069 if (status == MagickFalse)
1070 continue;
1071 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00001072 if (p == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001073 {
1074 status=MagickFalse;
1075 continue;
1076 }
cristy43547a52011-08-09 13:07:05 +00001077 for (x=0; x < (ssize_t) image->columns; x++)
1078 {
cristy465571b2011-08-21 20:43:15 +00001079 register ssize_t
1080 i;
1081
1082 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
cristy465571b2011-08-21 20:43:15 +00001083 histogram[GetPixelChannels(image)*ScaleQuantumToMap(p[i])+i]++;
cristy43547a52011-08-09 13:07:05 +00001084 p+=GetPixelChannels(image);
1085 }
cristy3ed852e2009-09-05 21:47:34 +00001086 }
1087 /*
1088 Find the histogram boundaries by locating the black/white levels.
1089 */
cristyb5d5f722009-11-04 03:03:49 +00001090#if defined(MAGICKCORE_OPENMP_SUPPORT)
1091 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001092#endif
cristy465571b2011-08-21 20:43:15 +00001093 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
cristy3ed852e2009-09-05 21:47:34 +00001094 {
cristy465571b2011-08-21 20:43:15 +00001095 double
1096 intensity;
1097
1098 register ssize_t
1099 j;
1100
1101 black[i]=0.0;
1102 white[i]=MaxRange(QuantumRange);
1103 intensity=0.0;
1104 for (j=0; j <= (ssize_t) MaxMap; j++)
1105 {
1106 intensity+=histogram[GetPixelChannels(image)*j+i];
1107 if (intensity > black_point)
1108 break;
1109 }
1110 black[i]=(MagickRealType) j;
1111 intensity=0.0;
1112 for (j=(ssize_t) MaxMap; j != 0; j--)
1113 {
1114 intensity+=histogram[GetPixelChannels(image)*j+i];
1115 if (intensity > ((double) image->columns*image->rows-white_point))
1116 break;
1117 }
1118 white[i]=(MagickRealType) j;
cristy3ed852e2009-09-05 21:47:34 +00001119 }
cristy465571b2011-08-21 20:43:15 +00001120 histogram=(double *) RelinquishMagickMemory(histogram);
cristy3ed852e2009-09-05 21:47:34 +00001121 /*
cristy465571b2011-08-21 20:43:15 +00001122 Stretch the histogram to create the stretched image mapping.
cristy3ed852e2009-09-05 21:47:34 +00001123 */
cristy465571b2011-08-21 20:43:15 +00001124 (void) ResetMagickMemory(stretch_map,0,(MaxMap+1)*GetPixelChannels(image)*
1125 sizeof(*stretch_map));
1126#if defined(MAGICKCORE_OPENMP_SUPPORT)
1127 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
1128#endif
1129 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1130 {
1131 register ssize_t
1132 j;
1133
1134 for (j=0; j <= (ssize_t) MaxMap; j++)
1135 {
1136 if (j < (ssize_t) black[i])
1137 stretch_map[GetPixelChannels(image)*j+i]=0.0;
1138 else
1139 if (j > (ssize_t) white[i])
1140 stretch_map[GetPixelChannels(image)*j+i]=(MagickRealType)
1141 QuantumRange;
1142 else
1143 if (black[i] != white[i])
1144 stretch_map[GetPixelChannels(image)*j+i]=(MagickRealType)
1145 ScaleMapToQuantum((MagickRealType) (MaxMap*(j-black[i])/
1146 (white[i]-black[i])));
1147 }
1148 }
cristy3ed852e2009-09-05 21:47:34 +00001149 if (image->storage_class == PseudoClass)
1150 {
cristy465571b2011-08-21 20:43:15 +00001151 register ssize_t
1152 j;
1153
cristy3ed852e2009-09-05 21:47:34 +00001154 /*
cristy465571b2011-08-21 20:43:15 +00001155 Stretch-contrast colormap.
cristy3ed852e2009-09-05 21:47:34 +00001156 */
cristyb5d5f722009-11-04 03:03:49 +00001157#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy000d38b2011-09-08 13:13:04 +00001158 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001159#endif
cristy465571b2011-08-21 20:43:15 +00001160 for (j=0; j < (ssize_t) image->colors; j++)
cristy3ed852e2009-09-05 21:47:34 +00001161 {
cristyed231572011-07-14 02:18:59 +00001162 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001163 {
cristy465571b2011-08-21 20:43:15 +00001164 i=GetPixelChannelMapChannel(image,RedPixelChannel);
1165 if (black[i] != white[i])
cristy000d38b2011-09-08 13:13:04 +00001166 image->colormap[j].red=ClampToQuantum(stretch_map[
cristy465571b2011-08-21 20:43:15 +00001167 GetPixelChannels(image)*ScaleQuantumToMap(
cristy000d38b2011-09-08 13:13:04 +00001168 image->colormap[j].red)]+i);
cristy3ed852e2009-09-05 21:47:34 +00001169 }
cristyed231572011-07-14 02:18:59 +00001170 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001171 {
cristy465571b2011-08-21 20:43:15 +00001172 i=GetPixelChannelMapChannel(image,GreenPixelChannel);
1173 if (black[i] != white[i])
cristy000d38b2011-09-08 13:13:04 +00001174 image->colormap[j].green=ClampToQuantum(stretch_map[
cristy465571b2011-08-21 20:43:15 +00001175 GetPixelChannels(image)*ScaleQuantumToMap(
cristy000d38b2011-09-08 13:13:04 +00001176 image->colormap[j].green)]+i);
cristy3ed852e2009-09-05 21:47:34 +00001177 }
cristyed231572011-07-14 02:18:59 +00001178 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001179 {
cristy465571b2011-08-21 20:43:15 +00001180 i=GetPixelChannelMapChannel(image,BluePixelChannel);
1181 if (black[i] != white[i])
cristy000d38b2011-09-08 13:13:04 +00001182 image->colormap[j].blue=ClampToQuantum(stretch_map[
cristy465571b2011-08-21 20:43:15 +00001183 GetPixelChannels(image)*ScaleQuantumToMap(
cristy000d38b2011-09-08 13:13:04 +00001184 image->colormap[j].blue)]+i);
cristy3ed852e2009-09-05 21:47:34 +00001185 }
cristyed231572011-07-14 02:18:59 +00001186 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001187 {
cristy465571b2011-08-21 20:43:15 +00001188 i=GetPixelChannelMapChannel(image,AlphaPixelChannel);
1189 if (black[i] != white[i])
cristy000d38b2011-09-08 13:13:04 +00001190 image->colormap[j].alpha=ClampToQuantum(stretch_map[
cristy465571b2011-08-21 20:43:15 +00001191 GetPixelChannels(image)*ScaleQuantumToMap(
cristy000d38b2011-09-08 13:13:04 +00001192 image->colormap[j].alpha)]+i);
cristy3ed852e2009-09-05 21:47:34 +00001193 }
1194 }
1195 }
1196 /*
cristy465571b2011-08-21 20:43:15 +00001197 Stretch-contrast image.
cristy3ed852e2009-09-05 21:47:34 +00001198 */
1199 status=MagickTrue;
1200 progress=0;
cristyb5d5f722009-11-04 03:03:49 +00001201#if defined(MAGICKCORE_OPENMP_SUPPORT)
1202 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001203#endif
cristybb503372010-05-27 20:51:26 +00001204 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001205 {
cristy4c08aed2011-07-01 19:47:50 +00001206 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00001207 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001208
cristy8d4629b2010-08-30 17:59:46 +00001209 register ssize_t
1210 x;
1211
cristy3ed852e2009-09-05 21:47:34 +00001212 if (status == MagickFalse)
1213 continue;
1214 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristyacd2ed22011-08-30 01:44:23 +00001215 if (q == (Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001216 {
1217 status=MagickFalse;
1218 continue;
1219 }
cristybb503372010-05-27 20:51:26 +00001220 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001221 {
cristy465571b2011-08-21 20:43:15 +00001222 register ssize_t
1223 i;
1224
1225 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1226 {
1227 PixelTrait
1228 traits;
1229
1230 traits=GetPixelChannelMapTraits(image,(PixelChannel) i);
cristy7c0a0a42011-08-23 17:57:25 +00001231 if (((traits & UpdatePixelTrait) != 0) && (black[i] != white[i]))
cristy465571b2011-08-21 20:43:15 +00001232 q[i]=ClampToQuantum(stretch_map[GetPixelChannels(image)*
1233 ScaleQuantumToMap(q[i])+i]);
1234 }
cristyed231572011-07-14 02:18:59 +00001235 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00001236 }
1237 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
1238 status=MagickFalse;
1239 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1240 {
1241 MagickBooleanType
1242 proceed;
1243
cristyb5d5f722009-11-04 03:03:49 +00001244#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy50fbc382011-07-07 02:19:17 +00001245 #pragma omp critical (MagickCore_ContrastStretchImage)
cristy3ed852e2009-09-05 21:47:34 +00001246#endif
1247 proceed=SetImageProgress(image,ContrastStretchImageTag,progress++,
1248 image->rows);
1249 if (proceed == MagickFalse)
1250 status=MagickFalse;
1251 }
1252 }
1253 image_view=DestroyCacheView(image_view);
cristyf45fec72011-08-23 16:02:32 +00001254 stretch_map=(double *) RelinquishMagickMemory(stretch_map);
1255 white=(double *) RelinquishMagickMemory(white);
1256 black=(double *) RelinquishMagickMemory(black);
cristy3ed852e2009-09-05 21:47:34 +00001257 return(status);
1258}
1259
1260/*
1261%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1262% %
1263% %
1264% %
1265% E n h a n c e I m a g e %
1266% %
1267% %
1268% %
1269%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1270%
1271% EnhanceImage() applies a digital filter that improves the quality of a
1272% noisy image.
1273%
1274% The format of the EnhanceImage method is:
1275%
1276% Image *EnhanceImage(const Image *image,ExceptionInfo *exception)
1277%
1278% A description of each parameter follows:
1279%
1280% o image: the image.
1281%
1282% o exception: return any errors or warnings in this structure.
1283%
1284*/
1285MagickExport Image *EnhanceImage(const Image *image,ExceptionInfo *exception)
1286{
cristy6d8c3d72011-08-22 01:20:01 +00001287#define EnhancePixel(weight) \
1288 mean=((MagickRealType) r[i]+q[channel])/2.0; \
1289 distance=(MagickRealType) r[i]-(MagickRealType) q[channel]; \
cristy3ed852e2009-09-05 21:47:34 +00001290 distance_squared=QuantumScale*(2.0*((MagickRealType) QuantumRange+1.0)+ \
cristy6d8c3d72011-08-22 01:20:01 +00001291 mean)*distance*distance; \
cristy3ed852e2009-09-05 21:47:34 +00001292 if (distance_squared < ((MagickRealType) QuantumRange*(MagickRealType) \
1293 QuantumRange/25.0f)) \
1294 { \
cristy6d8c3d72011-08-22 01:20:01 +00001295 aggregate+=(weight)*r[i]; \
cristy3ed852e2009-09-05 21:47:34 +00001296 total_weight+=(weight); \
1297 } \
cristy6d8c3d72011-08-22 01:20:01 +00001298 r+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00001299#define EnhanceImageTag "Enhance/Image"
1300
cristyc4c8d132010-01-07 01:58:38 +00001301 CacheView
1302 *enhance_view,
1303 *image_view;
1304
cristy3ed852e2009-09-05 21:47:34 +00001305 Image
1306 *enhance_image;
1307
cristy3ed852e2009-09-05 21:47:34 +00001308 MagickBooleanType
1309 status;
1310
cristybb503372010-05-27 20:51:26 +00001311 MagickOffsetType
1312 progress;
1313
cristybb503372010-05-27 20:51:26 +00001314 ssize_t
1315 y;
1316
cristy3ed852e2009-09-05 21:47:34 +00001317 /*
1318 Initialize enhanced image attributes.
1319 */
1320 assert(image != (const Image *) NULL);
1321 assert(image->signature == MagickSignature);
1322 if (image->debug != MagickFalse)
1323 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1324 assert(exception != (ExceptionInfo *) NULL);
1325 assert(exception->signature == MagickSignature);
cristy3ed852e2009-09-05 21:47:34 +00001326 enhance_image=CloneImage(image,image->columns,image->rows,MagickTrue,
1327 exception);
1328 if (enhance_image == (Image *) NULL)
1329 return((Image *) NULL);
cristy574cc262011-08-05 01:23:58 +00001330 if (SetImageStorageClass(enhance_image,DirectClass,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00001331 {
cristy3ed852e2009-09-05 21:47:34 +00001332 enhance_image=DestroyImage(enhance_image);
1333 return((Image *) NULL);
1334 }
1335 /*
1336 Enhance image.
1337 */
1338 status=MagickTrue;
1339 progress=0;
cristy3ed852e2009-09-05 21:47:34 +00001340 image_view=AcquireCacheView(image);
1341 enhance_view=AcquireCacheView(enhance_image);
cristyb5d5f722009-11-04 03:03:49 +00001342#if defined(MAGICKCORE_OPENMP_SUPPORT)
1343 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001344#endif
cristybb503372010-05-27 20:51:26 +00001345 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001346 {
cristy4c08aed2011-07-01 19:47:50 +00001347 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00001348 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00001349
cristy4c08aed2011-07-01 19:47:50 +00001350 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00001351 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001352
cristy8d4629b2010-08-30 17:59:46 +00001353 register ssize_t
1354 x;
1355
cristy6d8c3d72011-08-22 01:20:01 +00001356 ssize_t
1357 center;
1358
cristy3ed852e2009-09-05 21:47:34 +00001359 if (status == MagickFalse)
1360 continue;
1361 p=GetCacheViewVirtualPixels(image_view,-2,y-2,image->columns+4,5,exception);
1362 q=QueueCacheViewAuthenticPixels(enhance_view,0,y,enhance_image->columns,1,
1363 exception);
cristy4c08aed2011-07-01 19:47:50 +00001364 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00001365 {
1366 status=MagickFalse;
1367 continue;
1368 }
cristyf45fec72011-08-23 16:02:32 +00001369 center=(ssize_t) GetPixelChannels(image)*(2*(image->columns+4)+2);
cristybb503372010-05-27 20:51:26 +00001370 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001371 {
cristy6d8c3d72011-08-22 01:20:01 +00001372 register ssize_t
1373 i;
cristy3ed852e2009-09-05 21:47:34 +00001374
cristy6d8c3d72011-08-22 01:20:01 +00001375 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1376 {
1377 MagickRealType
1378 aggregate,
1379 distance,
1380 distance_squared,
1381 mean,
1382 total_weight;
cristy3ed852e2009-09-05 21:47:34 +00001383
cristy6d8c3d72011-08-22 01:20:01 +00001384 PixelChannel
1385 channel;
cristy3ed852e2009-09-05 21:47:34 +00001386
cristy6d8c3d72011-08-22 01:20:01 +00001387 PixelTrait
1388 enhance_traits,
1389 traits;
cristy3ed852e2009-09-05 21:47:34 +00001390
cristy6d8c3d72011-08-22 01:20:01 +00001391 register const Quantum
1392 *restrict r;
1393
1394 traits=GetPixelChannelMapTraits(image,(PixelChannel) i);
cristy6d8c3d72011-08-22 01:20:01 +00001395 channel=GetPixelChannelMapChannel(image,(PixelChannel) i);
1396 enhance_traits=GetPixelChannelMapTraits(enhance_image,channel);
cristy010d7d12011-08-31 01:02:48 +00001397 if ((traits == UndefinedPixelTrait) ||
1398 (enhance_traits == UndefinedPixelTrait))
cristy6d8c3d72011-08-22 01:20:01 +00001399 continue;
1400 q[channel]=p[center+i];
1401 if ((enhance_traits & CopyPixelTrait) != 0)
1402 continue;
1403 /*
1404 Compute weighted average of target pixel color components.
1405 */
1406 aggregate=0.0;
1407 total_weight=0.0;
cristyf45fec72011-08-23 16:02:32 +00001408 r=p;
cristy6d8c3d72011-08-22 01:20:01 +00001409 EnhancePixel(5.0); EnhancePixel(8.0); EnhancePixel(10.0);
1410 EnhancePixel(8.0); EnhancePixel(5.0);
1411 r=p+1*GetPixelChannels(image)*(image->columns+4);
1412 EnhancePixel(8.0); EnhancePixel(20.0); EnhancePixel(40.0);
1413 EnhancePixel(20.0); EnhancePixel(8.0);
1414 r=p+2*GetPixelChannels(image)*(image->columns+4);
1415 EnhancePixel(10.0); EnhancePixel(40.0); EnhancePixel(80.0);
1416 EnhancePixel(40.0); EnhancePixel(10.0);
1417 r=p+3*GetPixelChannels(image)*(image->columns+4);
1418 EnhancePixel(8.0); EnhancePixel(20.0); EnhancePixel(40.0);
1419 EnhancePixel(20.0); EnhancePixel(8.0);
1420 r=p+4*GetPixelChannels(image)*(image->columns+4);
1421 EnhancePixel(5.0); EnhancePixel(8.0); EnhancePixel(10.0);
1422 EnhancePixel(8.0); EnhancePixel(5.0);
1423 q[channel]=ClampToQuantum(aggregate/total_weight);
1424 }
cristyed231572011-07-14 02:18:59 +00001425 p+=GetPixelChannels(image);
1426 q+=GetPixelChannels(enhance_image);
cristy3ed852e2009-09-05 21:47:34 +00001427 }
1428 if (SyncCacheViewAuthenticPixels(enhance_view,exception) == MagickFalse)
1429 status=MagickFalse;
1430 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1431 {
1432 MagickBooleanType
1433 proceed;
1434
cristyb5d5f722009-11-04 03:03:49 +00001435#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00001436 #pragma omp critical (MagickCore_EnhanceImage)
1437#endif
1438 proceed=SetImageProgress(image,EnhanceImageTag,progress++,image->rows);
1439 if (proceed == MagickFalse)
1440 status=MagickFalse;
1441 }
1442 }
1443 enhance_view=DestroyCacheView(enhance_view);
1444 image_view=DestroyCacheView(image_view);
1445 return(enhance_image);
1446}
1447
1448/*
1449%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1450% %
1451% %
1452% %
1453% E q u a l i z e I m a g e %
1454% %
1455% %
1456% %
1457%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1458%
1459% EqualizeImage() applies a histogram equalization to the image.
1460%
1461% The format of the EqualizeImage method is:
1462%
cristy6d8c3d72011-08-22 01:20:01 +00001463% MagickBooleanType EqualizeImage(Image *image,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001464%
1465% A description of each parameter follows:
1466%
1467% o image: the image.
1468%
cristy6d8c3d72011-08-22 01:20:01 +00001469% o exception: return any errors or warnings in this structure.
cristy3ed852e2009-09-05 21:47:34 +00001470%
1471*/
cristy6d8c3d72011-08-22 01:20:01 +00001472MagickExport MagickBooleanType EqualizeImage(Image *image,
1473 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001474{
cristy3ed852e2009-09-05 21:47:34 +00001475#define EqualizeImageTag "Equalize/Image"
1476
cristyc4c8d132010-01-07 01:58:38 +00001477 CacheView
1478 *image_view;
1479
cristy3ed852e2009-09-05 21:47:34 +00001480 MagickBooleanType
1481 status;
1482
cristybb503372010-05-27 20:51:26 +00001483 MagickOffsetType
1484 progress;
1485
cristyf45fec72011-08-23 16:02:32 +00001486 MagickRealType
1487 black[MaxPixelChannels],
cristy3ed852e2009-09-05 21:47:34 +00001488 *equalize_map,
1489 *histogram,
cristy3ed852e2009-09-05 21:47:34 +00001490 *map,
cristyf45fec72011-08-23 16:02:32 +00001491 white[MaxPixelChannels];
cristy3ed852e2009-09-05 21:47:34 +00001492
cristybb503372010-05-27 20:51:26 +00001493 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001494 i;
1495
cristybb503372010-05-27 20:51:26 +00001496 ssize_t
1497 y;
1498
cristy3ed852e2009-09-05 21:47:34 +00001499 /*
1500 Allocate and initialize histogram arrays.
1501 */
1502 assert(image != (Image *) NULL);
1503 assert(image->signature == MagickSignature);
1504 if (image->debug != MagickFalse)
1505 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
cristyf45fec72011-08-23 16:02:32 +00001506 equalize_map=(MagickRealType *) AcquireQuantumMemory(MaxMap+1UL,
1507 GetPixelChannels(image)*sizeof(*equalize_map));
1508 histogram=(MagickRealType *) AcquireQuantumMemory(MaxMap+1UL,
1509 GetPixelChannels(image)*sizeof(*histogram));
1510 map=(MagickRealType *) AcquireQuantumMemory(MaxMap+1UL,
1511 GetPixelChannels(image)*sizeof(*map));
1512 if ((equalize_map == (MagickRealType *) NULL) ||
1513 (histogram == (MagickRealType *) NULL) ||
1514 (map == (MagickRealType *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00001515 {
cristyf45fec72011-08-23 16:02:32 +00001516 if (map != (MagickRealType *) NULL)
1517 map=(MagickRealType *) RelinquishMagickMemory(map);
1518 if (histogram != (MagickRealType *) NULL)
1519 histogram=(MagickRealType *) RelinquishMagickMemory(histogram);
1520 if (equalize_map != (MagickRealType *) NULL)
1521 equalize_map=(MagickRealType *) RelinquishMagickMemory(equalize_map);
cristy3ed852e2009-09-05 21:47:34 +00001522 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
1523 image->filename);
1524 }
1525 /*
1526 Form histogram.
1527 */
cristyf45fec72011-08-23 16:02:32 +00001528 status=MagickTrue;
1529 (void) ResetMagickMemory(histogram,0,(MaxMap+1)*GetPixelChannels(image)*
1530 sizeof(*histogram));
1531 image_view=AcquireCacheView(image);
cristybb503372010-05-27 20:51:26 +00001532 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001533 {
cristy4c08aed2011-07-01 19:47:50 +00001534 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00001535 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00001536
cristybb503372010-05-27 20:51:26 +00001537 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001538 x;
1539
cristyf45fec72011-08-23 16:02:32 +00001540 if (status == MagickFalse)
1541 continue;
1542 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00001543 if (p == (const Quantum *) NULL)
cristyf45fec72011-08-23 16:02:32 +00001544 {
1545 status=MagickFalse;
1546 continue;
1547 }
cristybb503372010-05-27 20:51:26 +00001548 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001549 {
cristyf45fec72011-08-23 16:02:32 +00001550 register ssize_t
1551 i;
1552
1553 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1554 histogram[GetPixelChannels(image)*ScaleQuantumToMap(p[i])+i]++;
cristyed231572011-07-14 02:18:59 +00001555 p+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00001556 }
1557 }
1558 /*
1559 Integrate the histogram to get the equalization map.
1560 */
cristyb5d5f722009-11-04 03:03:49 +00001561#if defined(MAGICKCORE_OPENMP_SUPPORT)
1562 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001563#endif
cristyf45fec72011-08-23 16:02:32 +00001564 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
cristy3ed852e2009-09-05 21:47:34 +00001565 {
cristyf45fec72011-08-23 16:02:32 +00001566 MagickRealType
1567 intensity;
1568
1569 register ssize_t
1570 j;
1571
1572 intensity=0.0;
1573 for (j=0; j <= (ssize_t) MaxMap; j++)
1574 {
1575 intensity+=histogram[GetPixelChannels(image)*j+i];
1576 map[GetPixelChannels(image)*j+i]=intensity;
1577 }
cristy3ed852e2009-09-05 21:47:34 +00001578 }
cristyf45fec72011-08-23 16:02:32 +00001579 (void) ResetMagickMemory(equalize_map,0,(MaxMap+1)*GetPixelChannels(image)*
1580 sizeof(*equalize_map));
1581#if defined(MAGICKCORE_OPENMP_SUPPORT)
1582 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
1583#endif
1584 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1585 {
1586 register ssize_t
1587 j;
1588
1589 black[i]=map[i];
1590 white[i]=map[GetPixelChannels(image)*MaxMap+i];
1591 if (black[i] != white[i])
1592 for (j=0; j <= (ssize_t) MaxMap; j++)
1593 equalize_map[GetPixelChannels(image)*j+i]=(MagickRealType)
1594 ScaleMapToQuantum((MagickRealType) ((MaxMap*(map[
1595 GetPixelChannels(image)*j+i]-black[i]))/(white[i]-black[i])));
1596 }
1597 histogram=(MagickRealType *) RelinquishMagickMemory(histogram);
1598 map=(MagickRealType *) RelinquishMagickMemory(map);
cristy3ed852e2009-09-05 21:47:34 +00001599 if (image->storage_class == PseudoClass)
1600 {
cristyf45fec72011-08-23 16:02:32 +00001601 register ssize_t
1602 j;
1603
cristy3ed852e2009-09-05 21:47:34 +00001604 /*
1605 Equalize colormap.
1606 */
cristyb5d5f722009-11-04 03:03:49 +00001607#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristydb483812011-09-08 13:17:11 +00001608 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001609#endif
cristyf45fec72011-08-23 16:02:32 +00001610 for (j=0; j < (ssize_t) image->colors; j++)
cristy3ed852e2009-09-05 21:47:34 +00001611 {
cristyed231572011-07-14 02:18:59 +00001612 if (((GetPixelRedTraits(image) & UpdatePixelTrait) != 0) &&
cristyf45fec72011-08-23 16:02:32 +00001613 (white[i]!= black[i]))
1614 {
1615 i=GetPixelChannelMapChannel(image,RedPixelChannel);
1616 if (black[i] != white[i])
cristydb483812011-09-08 13:17:11 +00001617 image->colormap[j].red=ClampToQuantum(equalize_map[
cristyf45fec72011-08-23 16:02:32 +00001618 GetPixelChannels(image)*ScaleQuantumToMap(
cristydb483812011-09-08 13:17:11 +00001619 image->colormap[j].red)]+i);
cristyf45fec72011-08-23 16:02:32 +00001620 }
cristyed231572011-07-14 02:18:59 +00001621 if (((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0) &&
cristyf45fec72011-08-23 16:02:32 +00001622 (white[i]!= black[i]))
1623 {
1624 i=GetPixelChannelMapChannel(image,GreenPixelChannel);
1625 if (black[i] != white[i])
cristydb483812011-09-08 13:17:11 +00001626 image->colormap[j].green=ClampToQuantum(equalize_map[
cristyf45fec72011-08-23 16:02:32 +00001627 GetPixelChannels(image)*ScaleQuantumToMap(
cristydb483812011-09-08 13:17:11 +00001628 image->colormap[j].red)]+i);
cristyf45fec72011-08-23 16:02:32 +00001629 }
cristyed231572011-07-14 02:18:59 +00001630 if (((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0) &&
cristyf45fec72011-08-23 16:02:32 +00001631 (white[i]!= black[i]))
1632 {
1633 i=GetPixelChannelMapChannel(image,BluePixelChannel);
1634 if (black[i] != white[i])
cristydb483812011-09-08 13:17:11 +00001635 image->colormap[j].blue=ClampToQuantum(equalize_map[
cristyf45fec72011-08-23 16:02:32 +00001636 GetPixelChannels(image)*ScaleQuantumToMap(
cristydb483812011-09-08 13:17:11 +00001637 image->colormap[j].blue)]+i);
cristyf45fec72011-08-23 16:02:32 +00001638 }
cristyed231572011-07-14 02:18:59 +00001639 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) &&
cristyf45fec72011-08-23 16:02:32 +00001640 (white[i]!= black[i]))
1641 {
1642 i=GetPixelChannelMapChannel(image,AlphaPixelChannel);
1643 if (black[i] != white[i])
cristydb483812011-09-08 13:17:11 +00001644 image->colormap[j].alpha=ClampToQuantum(equalize_map[
cristyf45fec72011-08-23 16:02:32 +00001645 GetPixelChannels(image)*ScaleQuantumToMap(
cristydb483812011-09-08 13:17:11 +00001646 image->colormap[j].alpha)]+i);
cristyf45fec72011-08-23 16:02:32 +00001647 }
cristy3ed852e2009-09-05 21:47:34 +00001648 }
1649 }
1650 /*
1651 Equalize image.
1652 */
cristy3ed852e2009-09-05 21:47:34 +00001653 progress=0;
cristyb5d5f722009-11-04 03:03:49 +00001654#if defined(MAGICKCORE_OPENMP_SUPPORT)
1655 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001656#endif
cristybb503372010-05-27 20:51:26 +00001657 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001658 {
cristy4c08aed2011-07-01 19:47:50 +00001659 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00001660 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001661
cristy8d4629b2010-08-30 17:59:46 +00001662 register ssize_t
1663 x;
1664
cristy3ed852e2009-09-05 21:47:34 +00001665 if (status == MagickFalse)
1666 continue;
1667 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristyacd2ed22011-08-30 01:44:23 +00001668 if (q == (Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001669 {
1670 status=MagickFalse;
1671 continue;
1672 }
cristybb503372010-05-27 20:51:26 +00001673 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001674 {
cristyf45fec72011-08-23 16:02:32 +00001675 register ssize_t
1676 i;
1677
1678 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1679 {
1680 PixelTrait
1681 traits;
1682
1683 traits=GetPixelChannelMapTraits(image,(PixelChannel) i);
cristy7c0a0a42011-08-23 17:57:25 +00001684 if (((traits & UpdatePixelTrait) != 0) && (black[i] != white[i]))
cristyf45fec72011-08-23 16:02:32 +00001685 q[i]=ClampToQuantum(equalize_map[GetPixelChannels(image)*
1686 ScaleQuantumToMap(q[i])+i]);
1687 }
cristyed231572011-07-14 02:18:59 +00001688 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00001689 }
1690 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
1691 status=MagickFalse;
1692 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1693 {
1694 MagickBooleanType
1695 proceed;
1696
cristyb5d5f722009-11-04 03:03:49 +00001697#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy50fbc382011-07-07 02:19:17 +00001698 #pragma omp critical (MagickCore_EqualizeImage)
cristy3ed852e2009-09-05 21:47:34 +00001699#endif
1700 proceed=SetImageProgress(image,EqualizeImageTag,progress++,image->rows);
1701 if (proceed == MagickFalse)
1702 status=MagickFalse;
1703 }
1704 }
1705 image_view=DestroyCacheView(image_view);
cristyf45fec72011-08-23 16:02:32 +00001706 equalize_map=(MagickRealType *) RelinquishMagickMemory(equalize_map);
cristy3ed852e2009-09-05 21:47:34 +00001707 return(status);
1708}
1709
1710/*
1711%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1712% %
1713% %
1714% %
1715% G a m m a I m a g e %
1716% %
1717% %
1718% %
1719%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1720%
1721% GammaImage() gamma-corrects a particular image channel. The same
1722% image viewed on different devices will have perceptual differences in the
1723% way the image's intensities are represented on the screen. Specify
1724% individual gamma levels for the red, green, and blue channels, or adjust
1725% all three with the gamma parameter. Values typically range from 0.8 to 2.3.
1726%
1727% You can also reduce the influence of a particular channel with a gamma
1728% value of 0.
1729%
1730% The format of the GammaImage method is:
1731%
cristyb3e7c6c2011-07-24 01:43:55 +00001732% MagickBooleanType GammaImage(Image *image,const double gamma,
1733% ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001734%
1735% A description of each parameter follows:
1736%
1737% o image: the image.
1738%
cristya6360142011-03-23 23:08:04 +00001739% o level: the image gamma as a string (e.g. 1.6,1.2,1.0).
1740%
cristy3ed852e2009-09-05 21:47:34 +00001741% o gamma: the image gamma.
1742%
1743*/
cristyb3e7c6c2011-07-24 01:43:55 +00001744MagickExport MagickBooleanType GammaImage(Image *image,const double gamma,
1745 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001746{
1747#define GammaCorrectImageTag "GammaCorrect/Image"
1748
cristyc4c8d132010-01-07 01:58:38 +00001749 CacheView
1750 *image_view;
1751
cristy3ed852e2009-09-05 21:47:34 +00001752 MagickBooleanType
1753 status;
1754
cristybb503372010-05-27 20:51:26 +00001755 MagickOffsetType
1756 progress;
1757
cristy3ed852e2009-09-05 21:47:34 +00001758 Quantum
1759 *gamma_map;
1760
cristybb503372010-05-27 20:51:26 +00001761 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001762 i;
1763
cristybb503372010-05-27 20:51:26 +00001764 ssize_t
1765 y;
1766
cristy3ed852e2009-09-05 21:47:34 +00001767 /*
1768 Allocate and initialize gamma maps.
1769 */
1770 assert(image != (Image *) NULL);
1771 assert(image->signature == MagickSignature);
1772 if (image->debug != MagickFalse)
1773 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1774 if (gamma == 1.0)
1775 return(MagickTrue);
1776 gamma_map=(Quantum *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*gamma_map));
1777 if (gamma_map == (Quantum *) NULL)
1778 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
1779 image->filename);
1780 (void) ResetMagickMemory(gamma_map,0,(MaxMap+1)*sizeof(*gamma_map));
1781 if (gamma != 0.0)
cristyd476a8e2011-07-23 16:13:22 +00001782#if defined(MAGICKCORE_OPENMP_SUPPORT) && (MaxMap > 256)
1783 #pragma omp parallel for
cristy3ed852e2009-09-05 21:47:34 +00001784#endif
cristybb503372010-05-27 20:51:26 +00001785 for (i=0; i <= (ssize_t) MaxMap; i++)
cristyce70c172010-01-07 17:15:30 +00001786 gamma_map[i]=ClampToQuantum((MagickRealType) ScaleMapToQuantum((
cristy3ed852e2009-09-05 21:47:34 +00001787 MagickRealType) (MaxMap*pow((double) i/MaxMap,1.0/gamma))));
1788 if (image->storage_class == PseudoClass)
1789 {
1790 /*
1791 Gamma-correct colormap.
1792 */
cristyb5d5f722009-11-04 03:03:49 +00001793#if defined(MAGICKCORE_OPENMP_SUPPORT)
1794 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001795#endif
cristybb503372010-05-27 20:51:26 +00001796 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00001797 {
cristyed231572011-07-14 02:18:59 +00001798 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001799 image->colormap[i].red=gamma_map[
1800 ScaleQuantumToMap(image->colormap[i].red)];
cristyed231572011-07-14 02:18:59 +00001801 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001802 image->colormap[i].green=gamma_map[
1803 ScaleQuantumToMap(image->colormap[i].green)];
cristyed231572011-07-14 02:18:59 +00001804 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001805 image->colormap[i].blue=gamma_map[
1806 ScaleQuantumToMap(image->colormap[i].blue)];
cristyed231572011-07-14 02:18:59 +00001807 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001808 image->colormap[i].alpha=gamma_map[
1809 ScaleQuantumToMap(image->colormap[i].alpha)];
cristy3ed852e2009-09-05 21:47:34 +00001810 }
1811 }
1812 /*
1813 Gamma-correct image.
1814 */
1815 status=MagickTrue;
1816 progress=0;
cristy3ed852e2009-09-05 21:47:34 +00001817 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00001818#if defined(MAGICKCORE_OPENMP_SUPPORT)
1819 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001820#endif
cristybb503372010-05-27 20:51:26 +00001821 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001822 {
cristy4c08aed2011-07-01 19:47:50 +00001823 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00001824 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001825
cristy8d4629b2010-08-30 17:59:46 +00001826 register ssize_t
1827 x;
1828
cristy3ed852e2009-09-05 21:47:34 +00001829 if (status == MagickFalse)
1830 continue;
1831 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristyacd2ed22011-08-30 01:44:23 +00001832 if (q == (Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001833 {
1834 status=MagickFalse;
1835 continue;
1836 }
cristybb503372010-05-27 20:51:26 +00001837 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001838 {
cristyd476a8e2011-07-23 16:13:22 +00001839 register ssize_t
1840 i;
1841
cristya30d9ba2011-07-23 21:00:48 +00001842 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
cristyd476a8e2011-07-23 16:13:22 +00001843 {
1844 PixelTrait
1845 traits;
1846
1847 traits=GetPixelChannelMapTraits(image,(PixelChannel) i);
1848 if ((traits & UpdatePixelTrait) != 0)
1849 q[i]=gamma_map[ScaleQuantumToMap(q[i])];
1850 }
cristya30d9ba2011-07-23 21:00:48 +00001851 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00001852 }
cristy3ed852e2009-09-05 21:47:34 +00001853 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
1854 status=MagickFalse;
1855 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1856 {
1857 MagickBooleanType
1858 proceed;
1859
cristyb5d5f722009-11-04 03:03:49 +00001860#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy50fbc382011-07-07 02:19:17 +00001861 #pragma omp critical (MagickCore_GammaImage)
cristy3ed852e2009-09-05 21:47:34 +00001862#endif
1863 proceed=SetImageProgress(image,GammaCorrectImageTag,progress++,
1864 image->rows);
1865 if (proceed == MagickFalse)
1866 status=MagickFalse;
1867 }
1868 }
1869 image_view=DestroyCacheView(image_view);
1870 gamma_map=(Quantum *) RelinquishMagickMemory(gamma_map);
1871 if (image->gamma != 0.0)
1872 image->gamma*=gamma;
1873 return(status);
1874}
1875
1876/*
1877%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1878% %
1879% %
1880% %
1881% H a l d C l u t I m a g e %
1882% %
1883% %
1884% %
1885%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1886%
1887% HaldClutImage() applies a Hald color lookup table to the image. A Hald
1888% color lookup table is a 3-dimensional color cube mapped to 2 dimensions.
1889% Create it with the HALD coder. You can apply any color transformation to
1890% the Hald image and then use this method to apply the transform to the
1891% image.
1892%
1893% The format of the HaldClutImage method is:
1894%
cristy7c0a0a42011-08-23 17:57:25 +00001895% MagickBooleanType HaldClutImage(Image *image,Image *hald_image,
1896% ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001897%
1898% A description of each parameter follows:
1899%
1900% o image: the image, which is replaced by indexed CLUT values
1901%
1902% o hald_image: the color lookup table image for replacement color values.
1903%
cristy7c0a0a42011-08-23 17:57:25 +00001904% o exception: return any errors or warnings in this structure.
1905%
cristy3ed852e2009-09-05 21:47:34 +00001906*/
1907
1908static inline size_t MagickMin(const size_t x,const size_t y)
1909{
1910 if (x < y)
1911 return(x);
1912 return(y);
1913}
1914
1915MagickExport MagickBooleanType HaldClutImage(Image *image,
cristy7c0a0a42011-08-23 17:57:25 +00001916 const Image *hald_image,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001917{
cristy3ed852e2009-09-05 21:47:34 +00001918#define HaldClutImageTag "Clut/Image"
1919
1920 typedef struct _HaldInfo
1921 {
1922 MagickRealType
1923 x,
1924 y,
1925 z;
1926 } HaldInfo;
1927
cristyfa112112010-01-04 17:48:07 +00001928 CacheView
cristyd551fbc2011-03-31 18:07:46 +00001929 *hald_view,
cristyfa112112010-01-04 17:48:07 +00001930 *image_view;
1931
cristy3ed852e2009-09-05 21:47:34 +00001932 double
1933 width;
1934
cristy3ed852e2009-09-05 21:47:34 +00001935 MagickBooleanType
1936 status;
1937
cristybb503372010-05-27 20:51:26 +00001938 MagickOffsetType
1939 progress;
1940
cristy4c08aed2011-07-01 19:47:50 +00001941 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00001942 zero;
1943
cristy3ed852e2009-09-05 21:47:34 +00001944 size_t
1945 cube_size,
1946 length,
1947 level;
1948
cristybb503372010-05-27 20:51:26 +00001949 ssize_t
1950 y;
1951
cristy3ed852e2009-09-05 21:47:34 +00001952 assert(image != (Image *) NULL);
1953 assert(image->signature == MagickSignature);
1954 if (image->debug != MagickFalse)
1955 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1956 assert(hald_image != (Image *) NULL);
1957 assert(hald_image->signature == MagickSignature);
cristy574cc262011-08-05 01:23:58 +00001958 if (SetImageStorageClass(image,DirectClass,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00001959 return(MagickFalse);
1960 if (image->matte == MagickFalse)
cristy63240882011-08-05 19:05:27 +00001961 (void) SetImageAlphaChannel(image,OpaqueAlphaChannel,exception);
cristy3ed852e2009-09-05 21:47:34 +00001962 /*
1963 Hald clut image.
1964 */
1965 status=MagickTrue;
1966 progress=0;
1967 length=MagickMin(hald_image->columns,hald_image->rows);
1968 for (level=2; (level*level*level) < length; level++) ;
1969 level*=level;
1970 cube_size=level*level;
1971 width=(double) hald_image->columns;
cristy4c08aed2011-07-01 19:47:50 +00001972 GetPixelInfo(hald_image,&zero);
cristy3ed852e2009-09-05 21:47:34 +00001973 image_view=AcquireCacheView(image);
cristyd551fbc2011-03-31 18:07:46 +00001974 hald_view=AcquireCacheView(hald_image);
cristyb5d5f722009-11-04 03:03:49 +00001975#if defined(MAGICKCORE_OPENMP_SUPPORT)
1976 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001977#endif
cristybb503372010-05-27 20:51:26 +00001978 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001979 {
cristy4c08aed2011-07-01 19:47:50 +00001980 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00001981 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001982
cristy8d4629b2010-08-30 17:59:46 +00001983 register ssize_t
1984 x;
1985
cristy3ed852e2009-09-05 21:47:34 +00001986 if (status == MagickFalse)
1987 continue;
1988 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristyacd2ed22011-08-30 01:44:23 +00001989 if (q == (Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001990 {
1991 status=MagickFalse;
1992 continue;
1993 }
cristybb503372010-05-27 20:51:26 +00001994 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001995 {
cristy7c0a0a42011-08-23 17:57:25 +00001996 double
1997 offset;
1998
1999 HaldInfo
2000 point;
2001
2002 PixelInfo
2003 pixel,
2004 pixel1,
2005 pixel2,
2006 pixel3,
2007 pixel4;
2008
cristy4c08aed2011-07-01 19:47:50 +00002009 point.x=QuantumScale*(level-1.0)*GetPixelRed(image,q);
2010 point.y=QuantumScale*(level-1.0)*GetPixelGreen(image,q);
2011 point.z=QuantumScale*(level-1.0)*GetPixelBlue(image,q);
cristy3ed852e2009-09-05 21:47:34 +00002012 offset=point.x+level*floor(point.y)+cube_size*floor(point.z);
2013 point.x-=floor(point.x);
2014 point.y-=floor(point.y);
2015 point.z-=floor(point.z);
cristy7c0a0a42011-08-23 17:57:25 +00002016 pixel1=zero;
2017 (void) InterpolatePixelInfo(image,hald_view,image->interpolate,
2018 fmod(offset,width),floor(offset/width),&pixel1,exception);
2019 pixel2=zero;
2020 (void) InterpolatePixelInfo(image,hald_view,image->interpolate,
2021 fmod(offset+level,width),floor((offset+level)/width),&pixel2,exception);
2022 pixel3=zero;
2023 CompositePixelInfoAreaBlend(&pixel1,pixel1.alpha,&pixel2,pixel2.alpha,
2024 point.y,&pixel3);
cristy3ed852e2009-09-05 21:47:34 +00002025 offset+=cube_size;
cristy7c0a0a42011-08-23 17:57:25 +00002026 (void) InterpolatePixelInfo(image,hald_view,image->interpolate,
2027 fmod(offset,width),floor(offset/width),&pixel1,exception);
2028 (void) InterpolatePixelInfo(image,hald_view,image->interpolate,
2029 fmod(offset+level,width),floor((offset+level)/width),&pixel2,exception);
2030 pixel4=zero;
2031 CompositePixelInfoAreaBlend(&pixel1,pixel1.alpha,&pixel2,pixel2.alpha,
2032 point.y,&pixel4);
2033 pixel=zero;
2034 CompositePixelInfoAreaBlend(&pixel3,pixel3.alpha,&pixel4,pixel4.alpha,
2035 point.z,&pixel);
cristyed231572011-07-14 02:18:59 +00002036 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristyf45fec72011-08-23 16:02:32 +00002037 SetPixelRed(image,ClampToQuantum(pixel.red),q);
cristyed231572011-07-14 02:18:59 +00002038 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristyf45fec72011-08-23 16:02:32 +00002039 SetPixelGreen(image,ClampToQuantum(pixel.green),q);
cristyed231572011-07-14 02:18:59 +00002040 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristyf45fec72011-08-23 16:02:32 +00002041 SetPixelBlue(image,ClampToQuantum(pixel.blue),q);
cristyed231572011-07-14 02:18:59 +00002042 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00002043 (image->colorspace == CMYKColorspace))
cristyf45fec72011-08-23 16:02:32 +00002044 SetPixelBlack(image,ClampToQuantum(pixel.black),q);
2045 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) &&
2046 (image->matte != MagickFalse))
2047 SetPixelAlpha(image,ClampToQuantum(pixel.alpha),q);
cristyed231572011-07-14 02:18:59 +00002048 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00002049 }
2050 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2051 status=MagickFalse;
2052 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2053 {
2054 MagickBooleanType
2055 proceed;
2056
cristyb5d5f722009-11-04 03:03:49 +00002057#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf89cb1d2011-07-07 01:24:37 +00002058 #pragma omp critical (MagickCore_HaldClutImage)
cristy3ed852e2009-09-05 21:47:34 +00002059#endif
2060 proceed=SetImageProgress(image,HaldClutImageTag,progress++,image->rows);
2061 if (proceed == MagickFalse)
2062 status=MagickFalse;
2063 }
2064 }
cristyd551fbc2011-03-31 18:07:46 +00002065 hald_view=DestroyCacheView(hald_view);
cristy3ed852e2009-09-05 21:47:34 +00002066 image_view=DestroyCacheView(image_view);
cristy3ed852e2009-09-05 21:47:34 +00002067 return(status);
2068}
2069
2070/*
2071%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2072% %
2073% %
2074% %
2075% L e v e l I m a g e %
2076% %
2077% %
2078% %
2079%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2080%
2081% LevelImage() adjusts the levels of a particular image channel by
2082% scaling the colors falling between specified white and black points to
2083% the full available quantum range.
2084%
2085% The parameters provided represent the black, and white points. The black
2086% point specifies the darkest color in the image. Colors darker than the
2087% black point are set to zero. White point specifies the lightest color in
2088% the image. Colors brighter than the white point are set to the maximum
2089% quantum value.
2090%
2091% If a '!' flag is given, map black and white colors to the given levels
2092% rather than mapping those levels to black and white. See
cristy50fbc382011-07-07 02:19:17 +00002093% LevelizeImage() below.
cristy3ed852e2009-09-05 21:47:34 +00002094%
2095% Gamma specifies a gamma correction to apply to the image.
2096%
2097% The format of the LevelImage method is:
2098%
cristy01e9afd2011-08-10 17:38:41 +00002099% MagickBooleanType LevelImage(Image *image,const double black_point,
2100% const double white_point,const double gamma,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002101%
2102% A description of each parameter follows:
2103%
2104% o image: the image.
2105%
cristy01e9afd2011-08-10 17:38:41 +00002106% o black_point: The level to map zero (black) to.
2107%
2108% o white_point: The level to map QuantumRange (white) to.
2109%
2110% o exception: return any errors or warnings in this structure.
cristy3ed852e2009-09-05 21:47:34 +00002111%
2112*/
cristy7c0a0a42011-08-23 17:57:25 +00002113MagickExport MagickBooleanType LevelImage(Image *image,const double black_point,
2114 const double white_point,const double gamma,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002115{
2116#define LevelImageTag "Level/Image"
cristybcfb0432010-05-06 01:45:33 +00002117#define LevelQuantum(x) (ClampToQuantum((MagickRealType) QuantumRange* \
cristyc1f508d2010-04-22 01:21:47 +00002118 pow(scale*((double) (x)-black_point),1.0/gamma)))
cristy3ed852e2009-09-05 21:47:34 +00002119
cristyc4c8d132010-01-07 01:58:38 +00002120 CacheView
2121 *image_view;
2122
cristy3ed852e2009-09-05 21:47:34 +00002123 MagickBooleanType
2124 status;
2125
cristybb503372010-05-27 20:51:26 +00002126 MagickOffsetType
2127 progress;
2128
anthony7fe39fc2010-04-06 03:19:20 +00002129 register double
2130 scale;
2131
cristy8d4629b2010-08-30 17:59:46 +00002132 register ssize_t
2133 i;
2134
cristybb503372010-05-27 20:51:26 +00002135 ssize_t
2136 y;
2137
cristy3ed852e2009-09-05 21:47:34 +00002138 /*
2139 Allocate and initialize levels map.
2140 */
2141 assert(image != (Image *) NULL);
2142 assert(image->signature == MagickSignature);
2143 if (image->debug != MagickFalse)
2144 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
cristy8d4629b2010-08-30 17:59:46 +00002145 scale=(white_point != black_point) ? 1.0/(white_point-black_point) : 1.0;
cristy3ed852e2009-09-05 21:47:34 +00002146 if (image->storage_class == PseudoClass)
cristyb5d5f722009-11-04 03:03:49 +00002147#if defined(MAGICKCORE_OPENMP_SUPPORT)
2148 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002149#endif
cristybb503372010-05-27 20:51:26 +00002150 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00002151 {
2152 /*
2153 Level colormap.
2154 */
cristyed231572011-07-14 02:18:59 +00002155 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristyc1f508d2010-04-22 01:21:47 +00002156 image->colormap[i].red=LevelQuantum(image->colormap[i].red);
cristyed231572011-07-14 02:18:59 +00002157 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristyc1f508d2010-04-22 01:21:47 +00002158 image->colormap[i].green=LevelQuantum(image->colormap[i].green);
cristyed231572011-07-14 02:18:59 +00002159 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristyc1f508d2010-04-22 01:21:47 +00002160 image->colormap[i].blue=LevelQuantum(image->colormap[i].blue);
cristyed231572011-07-14 02:18:59 +00002161 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002162 image->colormap[i].alpha=LevelQuantum(image->colormap[i].alpha);
cristy3ed852e2009-09-05 21:47:34 +00002163 }
2164 /*
2165 Level image.
2166 */
2167 status=MagickTrue;
2168 progress=0;
cristy3ed852e2009-09-05 21:47:34 +00002169 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00002170#if defined(MAGICKCORE_OPENMP_SUPPORT)
2171 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002172#endif
cristybb503372010-05-27 20:51:26 +00002173 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002174 {
cristy4c08aed2011-07-01 19:47:50 +00002175 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00002176 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002177
cristy8d4629b2010-08-30 17:59:46 +00002178 register ssize_t
2179 x;
2180
cristy3ed852e2009-09-05 21:47:34 +00002181 if (status == MagickFalse)
2182 continue;
2183 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristyacd2ed22011-08-30 01:44:23 +00002184 if (q == (Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00002185 {
2186 status=MagickFalse;
2187 continue;
2188 }
cristybb503372010-05-27 20:51:26 +00002189 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002190 {
cristy7c0a0a42011-08-23 17:57:25 +00002191 register ssize_t
2192 i;
2193
2194 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
2195 {
2196 PixelTrait
2197 traits;
2198
2199 traits=GetPixelChannelMapTraits(image,(PixelChannel) i);
cristy010d7d12011-08-31 01:02:48 +00002200 if ((traits == UndefinedPixelTrait) ||
2201 ((traits & UpdatePixelTrait) == 0))
cristy7c0a0a42011-08-23 17:57:25 +00002202 continue;
2203 q[i]=LevelQuantum(q[i]);
2204 }
cristyed231572011-07-14 02:18:59 +00002205 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00002206 }
2207 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2208 status=MagickFalse;
2209 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2210 {
2211 MagickBooleanType
2212 proceed;
2213
cristyb5d5f722009-11-04 03:03:49 +00002214#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf89cb1d2011-07-07 01:24:37 +00002215 #pragma omp critical (MagickCore_LevelImage)
cristy3ed852e2009-09-05 21:47:34 +00002216#endif
2217 proceed=SetImageProgress(image,LevelImageTag,progress++,image->rows);
2218 if (proceed == MagickFalse)
2219 status=MagickFalse;
2220 }
2221 }
2222 image_view=DestroyCacheView(image_view);
2223 return(status);
2224}
2225
2226/*
2227%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2228% %
2229% %
2230% %
cristy33bd5152011-08-24 01:42:24 +00002231% L e v e l i z e I m a g e %
cristy3ed852e2009-09-05 21:47:34 +00002232% %
2233% %
2234% %
2235%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2236%
cristy50fbc382011-07-07 02:19:17 +00002237% LevelizeImage() applies the reversed LevelImage() operation to just
cristy3ed852e2009-09-05 21:47:34 +00002238% the specific channels specified. It compresses the full range of color
2239% values, so that they lie between the given black and white points. Gamma is
2240% applied before the values are mapped.
2241%
cristy50fbc382011-07-07 02:19:17 +00002242% LevelizeImage() can be called with by using a +level command line
cristy3ed852e2009-09-05 21:47:34 +00002243% API option, or using a '!' on a -level or LevelImage() geometry string.
2244%
2245% It can be used for example de-contrast a greyscale image to the exact
2246% levels specified. Or by using specific levels for each channel of an image
2247% you can convert a gray-scale image to any linear color gradient, according
2248% to those levels.
2249%
cristy50fbc382011-07-07 02:19:17 +00002250% The format of the LevelizeImage method is:
cristy3ed852e2009-09-05 21:47:34 +00002251%
cristy50fbc382011-07-07 02:19:17 +00002252% MagickBooleanType LevelizeImage(Image *image,const double black_point,
cristy7c0a0a42011-08-23 17:57:25 +00002253% const double white_point,const double gamma,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002254%
2255% A description of each parameter follows:
2256%
2257% o image: the image.
2258%
cristy3ed852e2009-09-05 21:47:34 +00002259% o black_point: The level to map zero (black) to.
2260%
cristy01e9afd2011-08-10 17:38:41 +00002261% o white_point: The level to map QuantumRange (white) to.
cristy3ed852e2009-09-05 21:47:34 +00002262%
2263% o gamma: adjust gamma by this factor before mapping values.
2264%
cristy7c0a0a42011-08-23 17:57:25 +00002265% o exception: return any errors or warnings in this structure.
2266%
cristy3ed852e2009-09-05 21:47:34 +00002267*/
cristyd1a2c0f2011-02-09 14:14:50 +00002268MagickExport MagickBooleanType LevelizeImage(Image *image,
cristy7c0a0a42011-08-23 17:57:25 +00002269 const double black_point,const double white_point,const double gamma,
2270 ExceptionInfo *exception)
cristyd1a2c0f2011-02-09 14:14:50 +00002271{
cristy3ed852e2009-09-05 21:47:34 +00002272#define LevelizeImageTag "Levelize/Image"
cristyce70c172010-01-07 17:15:30 +00002273#define LevelizeValue(x) (ClampToQuantum(((MagickRealType) \
cristy50fbc382011-07-07 02:19:17 +00002274 pow((double) (QuantumScale*(x)),1.0/gamma))*(white_point-black_point)+ \
cristy3ed852e2009-09-05 21:47:34 +00002275 black_point))
2276
cristyc4c8d132010-01-07 01:58:38 +00002277 CacheView
2278 *image_view;
2279
cristy3ed852e2009-09-05 21:47:34 +00002280 MagickBooleanType
2281 status;
2282
cristybb503372010-05-27 20:51:26 +00002283 MagickOffsetType
2284 progress;
2285
2286 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002287 i;
2288
cristybb503372010-05-27 20:51:26 +00002289 ssize_t
2290 y;
2291
cristy3ed852e2009-09-05 21:47:34 +00002292 /*
2293 Allocate and initialize levels map.
2294 */
2295 assert(image != (Image *) NULL);
2296 assert(image->signature == MagickSignature);
2297 if (image->debug != MagickFalse)
2298 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2299 if (image->storage_class == PseudoClass)
cristyb5d5f722009-11-04 03:03:49 +00002300#if defined(MAGICKCORE_OPENMP_SUPPORT)
2301 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002302#endif
cristybb503372010-05-27 20:51:26 +00002303 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00002304 {
2305 /*
2306 Level colormap.
2307 */
cristyed231572011-07-14 02:18:59 +00002308 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00002309 image->colormap[i].red=LevelizeValue(image->colormap[i].red);
cristyed231572011-07-14 02:18:59 +00002310 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00002311 image->colormap[i].green=LevelizeValue(image->colormap[i].green);
cristyed231572011-07-14 02:18:59 +00002312 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00002313 image->colormap[i].blue=LevelizeValue(image->colormap[i].blue);
cristyed231572011-07-14 02:18:59 +00002314 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002315 image->colormap[i].alpha=LevelizeValue(image->colormap[i].alpha);
cristy3ed852e2009-09-05 21:47:34 +00002316 }
2317 /*
2318 Level image.
2319 */
2320 status=MagickTrue;
2321 progress=0;
2322 exception=(&image->exception);
2323 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00002324#if defined(MAGICKCORE_OPENMP_SUPPORT)
2325 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002326#endif
cristybb503372010-05-27 20:51:26 +00002327 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002328 {
cristy4c08aed2011-07-01 19:47:50 +00002329 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00002330 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002331
cristy8d4629b2010-08-30 17:59:46 +00002332 register ssize_t
2333 x;
2334
cristy3ed852e2009-09-05 21:47:34 +00002335 if (status == MagickFalse)
2336 continue;
2337 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristyacd2ed22011-08-30 01:44:23 +00002338 if (q == (Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00002339 {
2340 status=MagickFalse;
2341 continue;
2342 }
cristybb503372010-05-27 20:51:26 +00002343 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002344 {
cristy7c0a0a42011-08-23 17:57:25 +00002345 register ssize_t
2346 i;
2347
2348 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
2349 {
2350 PixelTrait
2351 traits;
2352
2353 traits=GetPixelChannelMapTraits(image,(PixelChannel) i);
2354 if ((traits & UpdatePixelTrait) != 0)
2355 q[i]=LevelizeValue(q[i]);
2356 }
cristyed231572011-07-14 02:18:59 +00002357 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00002358 }
2359 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2360 status=MagickFalse;
2361 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2362 {
2363 MagickBooleanType
2364 proceed;
2365
cristyb5d5f722009-11-04 03:03:49 +00002366#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy50fbc382011-07-07 02:19:17 +00002367 #pragma omp critical (MagickCore_LevelizeImage)
cristy3ed852e2009-09-05 21:47:34 +00002368#endif
2369 proceed=SetImageProgress(image,LevelizeImageTag,progress++,image->rows);
2370 if (proceed == MagickFalse)
2371 status=MagickFalse;
2372 }
2373 }
cristy8d4629b2010-08-30 17:59:46 +00002374 image_view=DestroyCacheView(image_view);
cristy3ed852e2009-09-05 21:47:34 +00002375 return(status);
2376}
2377
2378/*
2379%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2380% %
2381% %
2382% %
2383% L e v e l I m a g e C o l o r s %
2384% %
2385% %
2386% %
2387%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2388%
cristy490408a2011-07-07 14:42:05 +00002389% LevelImageColors() maps the given color to "black" and "white" values,
cristyee0f8d72009-09-19 00:58:29 +00002390% linearly spreading out the colors, and level values on a channel by channel
2391% bases, as per LevelImage(). The given colors allows you to specify
glennrp1e7f7bc2011-03-02 19:25:28 +00002392% different level ranges for each of the color channels separately.
cristy3ed852e2009-09-05 21:47:34 +00002393%
2394% If the boolean 'invert' is set true the image values will modifyed in the
2395% reverse direction. That is any existing "black" and "white" colors in the
2396% image will become the color values given, with all other values compressed
2397% appropriatally. This effectivally maps a greyscale gradient into the given
2398% color gradient.
2399%
cristy490408a2011-07-07 14:42:05 +00002400% The format of the LevelImageColors method is:
cristy3ed852e2009-09-05 21:47:34 +00002401%
cristy490408a2011-07-07 14:42:05 +00002402% MagickBooleanType LevelImageColors(Image *image,
2403% const PixelInfo *black_color,const PixelInfo *white_color,
cristy7c0a0a42011-08-23 17:57:25 +00002404% const MagickBooleanType invert,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002405%
2406% A description of each parameter follows:
2407%
2408% o image: the image.
2409%
cristy3ed852e2009-09-05 21:47:34 +00002410% o black_color: The color to map black to/from
2411%
2412% o white_point: The color to map white to/from
2413%
2414% o invert: if true map the colors (levelize), rather than from (level)
2415%
cristy7c0a0a42011-08-23 17:57:25 +00002416% o exception: return any errors or warnings in this structure.
2417%
cristy3ed852e2009-09-05 21:47:34 +00002418*/
cristy490408a2011-07-07 14:42:05 +00002419MagickExport MagickBooleanType LevelImageColors(Image *image,
cristy4c08aed2011-07-01 19:47:50 +00002420 const PixelInfo *black_color,const PixelInfo *white_color,
cristy7c0a0a42011-08-23 17:57:25 +00002421 const MagickBooleanType invert,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002422{
cristybd5a96c2011-08-21 00:04:26 +00002423 ChannelType
2424 channel_mask;
2425
cristy3ed852e2009-09-05 21:47:34 +00002426 MagickStatusType
2427 status;
2428
2429 /*
2430 Allocate and initialize levels map.
2431 */
2432 assert(image != (Image *) NULL);
2433 assert(image->signature == MagickSignature);
2434 if (image->debug != MagickFalse)
2435 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2436 status=MagickFalse;
2437 if (invert == MagickFalse)
2438 {
cristyed231572011-07-14 02:18:59 +00002439 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristyf89cb1d2011-07-07 01:24:37 +00002440 {
cristybd5a96c2011-08-21 00:04:26 +00002441 channel_mask=SetPixelChannelMask(image,RedChannel);
cristy01e9afd2011-08-10 17:38:41 +00002442 status|=LevelImage(image,black_color->red,white_color->red,1.0,
cristy7c0a0a42011-08-23 17:57:25 +00002443 exception);
cristybd5a96c2011-08-21 00:04:26 +00002444 (void) SetPixelChannelMask(image,channel_mask);
cristyf89cb1d2011-07-07 01:24:37 +00002445 }
cristyed231572011-07-14 02:18:59 +00002446 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristyf89cb1d2011-07-07 01:24:37 +00002447 {
cristybd5a96c2011-08-21 00:04:26 +00002448 channel_mask=SetPixelChannelMask(image,GreenChannel);
cristy01e9afd2011-08-10 17:38:41 +00002449 status|=LevelImage(image,black_color->green,white_color->green,1.0,
cristy7c0a0a42011-08-23 17:57:25 +00002450 exception);
cristybd5a96c2011-08-21 00:04:26 +00002451 (void) SetPixelChannelMask(image,channel_mask);
cristyf89cb1d2011-07-07 01:24:37 +00002452 }
cristyed231572011-07-14 02:18:59 +00002453 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristyf89cb1d2011-07-07 01:24:37 +00002454 {
cristybd5a96c2011-08-21 00:04:26 +00002455 channel_mask=SetPixelChannelMask(image,BlueChannel);
cristy01e9afd2011-08-10 17:38:41 +00002456 status|=LevelImage(image,black_color->blue,white_color->blue,1.0,
cristy7c0a0a42011-08-23 17:57:25 +00002457 exception);
cristybd5a96c2011-08-21 00:04:26 +00002458 (void) SetPixelChannelMask(image,channel_mask);
cristyf89cb1d2011-07-07 01:24:37 +00002459 }
cristyed231572011-07-14 02:18:59 +00002460 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +00002461 (image->colorspace == CMYKColorspace))
cristyf89cb1d2011-07-07 01:24:37 +00002462 {
cristybd5a96c2011-08-21 00:04:26 +00002463 channel_mask=SetPixelChannelMask(image,BlackChannel);
cristy01e9afd2011-08-10 17:38:41 +00002464 status|=LevelImage(image,black_color->black,white_color->black,1.0,
cristy7c0a0a42011-08-23 17:57:25 +00002465 exception);
cristybd5a96c2011-08-21 00:04:26 +00002466 (void) SetPixelChannelMask(image,channel_mask);
cristyf89cb1d2011-07-07 01:24:37 +00002467 }
cristyed231572011-07-14 02:18:59 +00002468 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00002469 (image->matte == MagickTrue))
cristyf89cb1d2011-07-07 01:24:37 +00002470 {
cristybd5a96c2011-08-21 00:04:26 +00002471 channel_mask=SetPixelChannelMask(image,AlphaChannel);
cristy01e9afd2011-08-10 17:38:41 +00002472 status|=LevelImage(image,black_color->alpha,white_color->alpha,1.0,
cristy7c0a0a42011-08-23 17:57:25 +00002473 exception);
cristybd5a96c2011-08-21 00:04:26 +00002474 (void) SetPixelChannelMask(image,channel_mask);
cristyf89cb1d2011-07-07 01:24:37 +00002475 }
cristy3ed852e2009-09-05 21:47:34 +00002476 }
2477 else
2478 {
cristyed231572011-07-14 02:18:59 +00002479 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristyf89cb1d2011-07-07 01:24:37 +00002480 {
cristybd5a96c2011-08-21 00:04:26 +00002481 channel_mask=SetPixelChannelMask(image,RedChannel);
cristy7c0a0a42011-08-23 17:57:25 +00002482 status|=LevelizeImage(image,black_color->red,white_color->red,1.0,
2483 exception);
cristybd5a96c2011-08-21 00:04:26 +00002484 (void) SetPixelChannelMask(image,channel_mask);
cristyf89cb1d2011-07-07 01:24:37 +00002485 }
cristyed231572011-07-14 02:18:59 +00002486 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristyf89cb1d2011-07-07 01:24:37 +00002487 {
cristybd5a96c2011-08-21 00:04:26 +00002488 channel_mask=SetPixelChannelMask(image,GreenChannel);
cristy7c0a0a42011-08-23 17:57:25 +00002489 status|=LevelizeImage(image,black_color->green,white_color->green,1.0,
2490 exception);
cristybd5a96c2011-08-21 00:04:26 +00002491 (void) SetPixelChannelMask(image,channel_mask);
cristyf89cb1d2011-07-07 01:24:37 +00002492 }
cristyed231572011-07-14 02:18:59 +00002493 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristyf89cb1d2011-07-07 01:24:37 +00002494 {
cristybd5a96c2011-08-21 00:04:26 +00002495 channel_mask=SetPixelChannelMask(image,BlueChannel);
cristy7c0a0a42011-08-23 17:57:25 +00002496 status|=LevelizeImage(image,black_color->blue,white_color->blue,1.0,
2497 exception);
cristybd5a96c2011-08-21 00:04:26 +00002498 (void) SetPixelChannelMask(image,channel_mask);
cristyf89cb1d2011-07-07 01:24:37 +00002499 }
cristyed231572011-07-14 02:18:59 +00002500 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +00002501 (image->colorspace == CMYKColorspace))
cristyf89cb1d2011-07-07 01:24:37 +00002502 {
cristybd5a96c2011-08-21 00:04:26 +00002503 channel_mask=SetPixelChannelMask(image,BlackChannel);
cristy7c0a0a42011-08-23 17:57:25 +00002504 status|=LevelizeImage(image,black_color->black,white_color->black,1.0,
2505 exception);
cristybd5a96c2011-08-21 00:04:26 +00002506 (void) SetPixelChannelMask(image,channel_mask);
cristyf89cb1d2011-07-07 01:24:37 +00002507 }
cristyed231572011-07-14 02:18:59 +00002508 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00002509 (image->matte == MagickTrue))
cristyf89cb1d2011-07-07 01:24:37 +00002510 {
cristybd5a96c2011-08-21 00:04:26 +00002511 channel_mask=SetPixelChannelMask(image,AlphaChannel);
cristy7c0a0a42011-08-23 17:57:25 +00002512 status|=LevelizeImage(image,black_color->alpha,white_color->alpha,1.0,
2513 exception);
cristybd5a96c2011-08-21 00:04:26 +00002514 (void) SetPixelChannelMask(image,channel_mask);
cristyf89cb1d2011-07-07 01:24:37 +00002515 }
cristy3ed852e2009-09-05 21:47:34 +00002516 }
2517 return(status == 0 ? MagickFalse : MagickTrue);
2518}
2519
2520/*
2521%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2522% %
2523% %
2524% %
2525% L i n e a r S t r e t c h I m a g e %
2526% %
2527% %
2528% %
2529%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2530%
cristyf1611782011-08-01 15:39:13 +00002531% LinearStretchImage() discards any pixels below the black point and above
2532% the white point and levels the remaining pixels.
cristy3ed852e2009-09-05 21:47:34 +00002533%
2534% The format of the LinearStretchImage method is:
2535%
2536% MagickBooleanType LinearStretchImage(Image *image,
cristy33bd5152011-08-24 01:42:24 +00002537% const double black_point,const double white_point,
2538% ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002539%
2540% A description of each parameter follows:
2541%
2542% o image: the image.
2543%
2544% o black_point: the black point.
2545%
2546% o white_point: the white point.
2547%
cristy33bd5152011-08-24 01:42:24 +00002548% o exception: return any errors or warnings in this structure.
2549%
cristy3ed852e2009-09-05 21:47:34 +00002550*/
2551MagickExport MagickBooleanType LinearStretchImage(Image *image,
cristy33bd5152011-08-24 01:42:24 +00002552 const double black_point,const double white_point,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002553{
2554#define LinearStretchImageTag "LinearStretch/Image"
2555
cristy33bd5152011-08-24 01:42:24 +00002556 CacheView
2557 *image_view;
cristy3ed852e2009-09-05 21:47:34 +00002558
cristy3ed852e2009-09-05 21:47:34 +00002559 MagickBooleanType
2560 status;
2561
2562 MagickRealType
2563 *histogram,
2564 intensity;
2565
cristy8d4629b2010-08-30 17:59:46 +00002566 ssize_t
2567 black,
2568 white,
2569 y;
2570
cristy3ed852e2009-09-05 21:47:34 +00002571 /*
2572 Allocate histogram and linear map.
2573 */
2574 assert(image != (Image *) NULL);
2575 assert(image->signature == MagickSignature);
2576 histogram=(MagickRealType *) AcquireQuantumMemory(MaxMap+1UL,
2577 sizeof(*histogram));
2578 if (histogram == (MagickRealType *) NULL)
2579 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
2580 image->filename);
2581 /*
2582 Form histogram.
2583 */
2584 (void) ResetMagickMemory(histogram,0,(MaxMap+1)*sizeof(*histogram));
cristy33bd5152011-08-24 01:42:24 +00002585 image_view=AcquireCacheView(image);
cristybb503372010-05-27 20:51:26 +00002586 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002587 {
cristy4c08aed2011-07-01 19:47:50 +00002588 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00002589 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00002590
cristybb503372010-05-27 20:51:26 +00002591 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002592 x;
2593
cristy33bd5152011-08-24 01:42:24 +00002594 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00002595 if (p == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00002596 break;
cristy33bd5152011-08-24 01:42:24 +00002597 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002598 {
cristy4c08aed2011-07-01 19:47:50 +00002599 histogram[ScaleQuantumToMap(GetPixelIntensity(image,p))]++;
cristyed231572011-07-14 02:18:59 +00002600 p+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00002601 }
2602 }
cristy33bd5152011-08-24 01:42:24 +00002603 image_view=DestroyCacheView(image_view);
cristy3ed852e2009-09-05 21:47:34 +00002604 /*
2605 Find the histogram boundaries by locating the black and white point levels.
2606 */
cristy3ed852e2009-09-05 21:47:34 +00002607 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00002608 for (black=0; black < (ssize_t) MaxMap; black++)
cristy3ed852e2009-09-05 21:47:34 +00002609 {
2610 intensity+=histogram[black];
2611 if (intensity >= black_point)
2612 break;
2613 }
2614 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00002615 for (white=(ssize_t) MaxMap; white != 0; white--)
cristy3ed852e2009-09-05 21:47:34 +00002616 {
2617 intensity+=histogram[white];
2618 if (intensity >= white_point)
2619 break;
2620 }
2621 histogram=(MagickRealType *) RelinquishMagickMemory(histogram);
cristy01e9afd2011-08-10 17:38:41 +00002622 status=LevelImage(image,(double) black,(double) white,1.0,&image->exception);
cristy3ed852e2009-09-05 21:47:34 +00002623 return(status);
2624}
2625
2626/*
2627%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2628% %
2629% %
2630% %
2631% M o d u l a t e I m a g e %
2632% %
2633% %
2634% %
2635%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2636%
2637% ModulateImage() lets you control the brightness, saturation, and hue
2638% of an image. Modulate represents the brightness, saturation, and hue
2639% as one parameter (e.g. 90,150,100). If the image colorspace is HSL, the
2640% modulation is lightness, saturation, and hue. And if the colorspace is
2641% HWB, use blackness, whiteness, and hue.
2642%
2643% The format of the ModulateImage method is:
2644%
cristy33bd5152011-08-24 01:42:24 +00002645% MagickBooleanType ModulateImage(Image *image,const char *modulate,
2646% ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002647%
2648% A description of each parameter follows:
2649%
2650% o image: the image.
2651%
cristy33bd5152011-08-24 01:42:24 +00002652% o modulate: Define the percent change in brightness, saturation, and hue.
2653%
2654% o exception: return any errors or warnings in this structure.
cristy3ed852e2009-09-05 21:47:34 +00002655%
2656*/
2657
2658static void ModulateHSB(const double percent_hue,
2659 const double percent_saturation,const double percent_brightness,
2660 Quantum *red,Quantum *green,Quantum *blue)
2661{
2662 double
2663 brightness,
2664 hue,
2665 saturation;
2666
2667 /*
2668 Increase or decrease color brightness, saturation, or hue.
2669 */
2670 assert(red != (Quantum *) NULL);
2671 assert(green != (Quantum *) NULL);
2672 assert(blue != (Quantum *) NULL);
2673 ConvertRGBToHSB(*red,*green,*blue,&hue,&saturation,&brightness);
2674 hue+=0.5*(0.01*percent_hue-1.0);
2675 while (hue < 0.0)
2676 hue+=1.0;
2677 while (hue > 1.0)
2678 hue-=1.0;
2679 saturation*=0.01*percent_saturation;
2680 brightness*=0.01*percent_brightness;
2681 ConvertHSBToRGB(hue,saturation,brightness,red,green,blue);
2682}
2683
2684static void ModulateHSL(const double percent_hue,
2685 const double percent_saturation,const double percent_lightness,
2686 Quantum *red,Quantum *green,Quantum *blue)
2687{
2688 double
2689 hue,
2690 lightness,
2691 saturation;
2692
2693 /*
2694 Increase or decrease color lightness, saturation, or hue.
2695 */
2696 assert(red != (Quantum *) NULL);
2697 assert(green != (Quantum *) NULL);
2698 assert(blue != (Quantum *) NULL);
2699 ConvertRGBToHSL(*red,*green,*blue,&hue,&saturation,&lightness);
2700 hue+=0.5*(0.01*percent_hue-1.0);
2701 while (hue < 0.0)
2702 hue+=1.0;
2703 while (hue > 1.0)
2704 hue-=1.0;
2705 saturation*=0.01*percent_saturation;
2706 lightness*=0.01*percent_lightness;
2707 ConvertHSLToRGB(hue,saturation,lightness,red,green,blue);
2708}
2709
2710static void ModulateHWB(const double percent_hue,const double percent_whiteness, const double percent_blackness,Quantum *red,Quantum *green,Quantum *blue)
2711{
2712 double
2713 blackness,
2714 hue,
2715 whiteness;
2716
2717 /*
2718 Increase or decrease color blackness, whiteness, or hue.
2719 */
2720 assert(red != (Quantum *) NULL);
2721 assert(green != (Quantum *) NULL);
2722 assert(blue != (Quantum *) NULL);
2723 ConvertRGBToHWB(*red,*green,*blue,&hue,&whiteness,&blackness);
2724 hue+=0.5*(0.01*percent_hue-1.0);
2725 while (hue < 0.0)
2726 hue+=1.0;
2727 while (hue > 1.0)
2728 hue-=1.0;
2729 blackness*=0.01*percent_blackness;
2730 whiteness*=0.01*percent_whiteness;
2731 ConvertHWBToRGB(hue,whiteness,blackness,red,green,blue);
2732}
2733
cristy33bd5152011-08-24 01:42:24 +00002734MagickExport MagickBooleanType ModulateImage(Image *image,const char *modulate,
2735 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002736{
2737#define ModulateImageTag "Modulate/Image"
2738
cristyc4c8d132010-01-07 01:58:38 +00002739 CacheView
2740 *image_view;
2741
cristy3ed852e2009-09-05 21:47:34 +00002742 ColorspaceType
2743 colorspace;
2744
2745 const char
2746 *artifact;
2747
2748 double
2749 percent_brightness,
2750 percent_hue,
2751 percent_saturation;
2752
cristy3ed852e2009-09-05 21:47:34 +00002753 GeometryInfo
2754 geometry_info;
2755
cristy3ed852e2009-09-05 21:47:34 +00002756 MagickBooleanType
2757 status;
2758
cristybb503372010-05-27 20:51:26 +00002759 MagickOffsetType
2760 progress;
2761
cristy3ed852e2009-09-05 21:47:34 +00002762 MagickStatusType
2763 flags;
2764
cristybb503372010-05-27 20:51:26 +00002765 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002766 i;
2767
cristybb503372010-05-27 20:51:26 +00002768 ssize_t
2769 y;
2770
cristy3ed852e2009-09-05 21:47:34 +00002771 /*
cristy2b726bd2010-01-11 01:05:39 +00002772 Initialize modulate table.
cristy3ed852e2009-09-05 21:47:34 +00002773 */
2774 assert(image != (Image *) NULL);
2775 assert(image->signature == MagickSignature);
2776 if (image->debug != MagickFalse)
2777 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2778 if (modulate == (char *) NULL)
2779 return(MagickFalse);
2780 flags=ParseGeometry(modulate,&geometry_info);
2781 percent_brightness=geometry_info.rho;
2782 percent_saturation=geometry_info.sigma;
2783 if ((flags & SigmaValue) == 0)
2784 percent_saturation=100.0;
2785 percent_hue=geometry_info.xi;
2786 if ((flags & XiValue) == 0)
2787 percent_hue=100.0;
2788 colorspace=UndefinedColorspace;
2789 artifact=GetImageArtifact(image,"modulate:colorspace");
2790 if (artifact != (const char *) NULL)
cristy042ee782011-04-22 18:48:30 +00002791 colorspace=(ColorspaceType) ParseCommandOption(MagickColorspaceOptions,
cristy3ed852e2009-09-05 21:47:34 +00002792 MagickFalse,artifact);
2793 if (image->storage_class == PseudoClass)
2794 {
2795 /*
2796 Modulate colormap.
2797 */
cristyb5d5f722009-11-04 03:03:49 +00002798#if defined(MAGICKCORE_OPENMP_SUPPORT)
2799 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002800#endif
cristybb503372010-05-27 20:51:26 +00002801 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00002802 switch (colorspace)
2803 {
2804 case HSBColorspace:
2805 {
2806 ModulateHSB(percent_hue,percent_saturation,percent_brightness,
2807 &image->colormap[i].red,&image->colormap[i].green,
2808 &image->colormap[i].blue);
2809 break;
2810 }
2811 case HSLColorspace:
2812 default:
2813 {
2814 ModulateHSL(percent_hue,percent_saturation,percent_brightness,
2815 &image->colormap[i].red,&image->colormap[i].green,
2816 &image->colormap[i].blue);
2817 break;
2818 }
2819 case HWBColorspace:
2820 {
2821 ModulateHWB(percent_hue,percent_saturation,percent_brightness,
2822 &image->colormap[i].red,&image->colormap[i].green,
2823 &image->colormap[i].blue);
2824 break;
2825 }
2826 }
2827 }
2828 /*
2829 Modulate image.
2830 */
2831 status=MagickTrue;
2832 progress=0;
cristy3ed852e2009-09-05 21:47:34 +00002833 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00002834#if defined(MAGICKCORE_OPENMP_SUPPORT)
2835 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002836#endif
cristybb503372010-05-27 20:51:26 +00002837 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002838 {
cristy5afeab82011-04-30 01:30:09 +00002839 Quantum
2840 blue,
2841 green,
2842 red;
2843
cristy4c08aed2011-07-01 19:47:50 +00002844 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00002845 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002846
cristy8d4629b2010-08-30 17:59:46 +00002847 register ssize_t
2848 x;
2849
cristy3ed852e2009-09-05 21:47:34 +00002850 if (status == MagickFalse)
2851 continue;
2852 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristyacd2ed22011-08-30 01:44:23 +00002853 if (q == (Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00002854 {
2855 status=MagickFalse;
2856 continue;
2857 }
cristybb503372010-05-27 20:51:26 +00002858 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002859 {
cristy4c08aed2011-07-01 19:47:50 +00002860 red=GetPixelRed(image,q);
2861 green=GetPixelGreen(image,q);
2862 blue=GetPixelBlue(image,q);
cristy3ed852e2009-09-05 21:47:34 +00002863 switch (colorspace)
2864 {
2865 case HSBColorspace:
2866 {
2867 ModulateHSB(percent_hue,percent_saturation,percent_brightness,
cristy5afeab82011-04-30 01:30:09 +00002868 &red,&green,&blue);
cristy3ed852e2009-09-05 21:47:34 +00002869 break;
2870 }
2871 case HSLColorspace:
2872 default:
2873 {
2874 ModulateHSL(percent_hue,percent_saturation,percent_brightness,
cristy5afeab82011-04-30 01:30:09 +00002875 &red,&green,&blue);
cristy3ed852e2009-09-05 21:47:34 +00002876 break;
2877 }
2878 case HWBColorspace:
2879 {
2880 ModulateHWB(percent_hue,percent_saturation,percent_brightness,
cristy5afeab82011-04-30 01:30:09 +00002881 &red,&green,&blue);
cristy3ed852e2009-09-05 21:47:34 +00002882 break;
2883 }
2884 }
cristy4c08aed2011-07-01 19:47:50 +00002885 SetPixelRed(image,red,q);
2886 SetPixelGreen(image,green,q);
2887 SetPixelBlue(image,blue,q);
cristyed231572011-07-14 02:18:59 +00002888 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00002889 }
2890 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2891 status=MagickFalse;
2892 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2893 {
2894 MagickBooleanType
2895 proceed;
2896
cristyb5d5f722009-11-04 03:03:49 +00002897#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00002898 #pragma omp critical (MagickCore_ModulateImage)
2899#endif
2900 proceed=SetImageProgress(image,ModulateImageTag,progress++,image->rows);
2901 if (proceed == MagickFalse)
2902 status=MagickFalse;
2903 }
2904 }
2905 image_view=DestroyCacheView(image_view);
2906 return(status);
2907}
2908
2909/*
2910%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2911% %
2912% %
2913% %
2914% N e g a t e I m a g e %
2915% %
2916% %
2917% %
2918%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2919%
2920% NegateImage() negates the colors in the reference image. The grayscale
2921% option means that only grayscale values within the image are negated.
2922%
cristy50fbc382011-07-07 02:19:17 +00002923% The format of the NegateImage method is:
cristy3ed852e2009-09-05 21:47:34 +00002924%
2925% MagickBooleanType NegateImage(Image *image,
cristyb3e7c6c2011-07-24 01:43:55 +00002926% const MagickBooleanType grayscale,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002927%
2928% A description of each parameter follows:
2929%
2930% o image: the image.
2931%
cristy3ed852e2009-09-05 21:47:34 +00002932% o grayscale: If MagickTrue, only negate grayscale pixels within the image.
2933%
cristyb3e7c6c2011-07-24 01:43:55 +00002934% o exception: return any errors or warnings in this structure.
2935%
cristy3ed852e2009-09-05 21:47:34 +00002936*/
cristy3ed852e2009-09-05 21:47:34 +00002937MagickExport MagickBooleanType NegateImage(Image *image,
cristyb3e7c6c2011-07-24 01:43:55 +00002938 const MagickBooleanType grayscale,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002939{
cristy3ed852e2009-09-05 21:47:34 +00002940#define NegateImageTag "Negate/Image"
2941
cristyc4c8d132010-01-07 01:58:38 +00002942 CacheView
2943 *image_view;
2944
cristy3ed852e2009-09-05 21:47:34 +00002945 MagickBooleanType
2946 status;
2947
cristybb503372010-05-27 20:51:26 +00002948 MagickOffsetType
2949 progress;
2950
2951 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002952 i;
2953
cristybb503372010-05-27 20:51:26 +00002954 ssize_t
2955 y;
2956
cristy3ed852e2009-09-05 21:47:34 +00002957 assert(image != (Image *) NULL);
2958 assert(image->signature == MagickSignature);
2959 if (image->debug != MagickFalse)
2960 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2961 if (image->storage_class == PseudoClass)
2962 {
2963 /*
2964 Negate colormap.
2965 */
cristyb5d5f722009-11-04 03:03:49 +00002966#if defined(MAGICKCORE_OPENMP_SUPPORT)
2967 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002968#endif
cristybb503372010-05-27 20:51:26 +00002969 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00002970 {
2971 if (grayscale != MagickFalse)
2972 if ((image->colormap[i].red != image->colormap[i].green) ||
2973 (image->colormap[i].green != image->colormap[i].blue))
2974 continue;
cristyed231572011-07-14 02:18:59 +00002975 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00002976 image->colormap[i].red=(Quantum) QuantumRange-
2977 image->colormap[i].red;
cristyed231572011-07-14 02:18:59 +00002978 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00002979 image->colormap[i].green=(Quantum) QuantumRange-
2980 image->colormap[i].green;
cristyed231572011-07-14 02:18:59 +00002981 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00002982 image->colormap[i].blue=(Quantum) QuantumRange-
2983 image->colormap[i].blue;
2984 }
2985 }
2986 /*
2987 Negate image.
2988 */
2989 status=MagickTrue;
2990 progress=0;
cristy3ed852e2009-09-05 21:47:34 +00002991 image_view=AcquireCacheView(image);
2992 if (grayscale != MagickFalse)
2993 {
cristyb5d5f722009-11-04 03:03:49 +00002994#if defined(MAGICKCORE_OPENMP_SUPPORT)
2995 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002996#endif
cristybb503372010-05-27 20:51:26 +00002997 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002998 {
2999 MagickBooleanType
3000 sync;
3001
cristy4c08aed2011-07-01 19:47:50 +00003002 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00003003 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003004
cristy8d4629b2010-08-30 17:59:46 +00003005 register ssize_t
3006 x;
3007
cristy3ed852e2009-09-05 21:47:34 +00003008 if (status == MagickFalse)
3009 continue;
3010 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,
3011 exception);
cristyacd2ed22011-08-30 01:44:23 +00003012 if (q == (Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00003013 {
3014 status=MagickFalse;
3015 continue;
3016 }
cristybb503372010-05-27 20:51:26 +00003017 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003018 {
cristy9aa95be2011-07-20 21:56:45 +00003019 register ssize_t
3020 i;
3021
cristyd476a8e2011-07-23 16:13:22 +00003022 if (IsPixelGray(image,q) != MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00003023 {
cristya30d9ba2011-07-23 21:00:48 +00003024 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00003025 continue;
3026 }
cristya30d9ba2011-07-23 21:00:48 +00003027 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
cristy9aa95be2011-07-20 21:56:45 +00003028 {
cristy1aaa3cd2011-08-21 23:48:17 +00003029 PixelTrait
cristy9aa95be2011-07-20 21:56:45 +00003030 traits;
3031
3032 traits=GetPixelChannelMapTraits(image,(PixelChannel) i);
3033 if ((traits & UpdatePixelTrait) != 0)
3034 q[i]=QuantumRange-q[i];
3035 }
cristya30d9ba2011-07-23 21:00:48 +00003036 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00003037 }
3038 sync=SyncCacheViewAuthenticPixels(image_view,exception);
3039 if (sync == MagickFalse)
3040 status=MagickFalse;
3041 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3042 {
3043 MagickBooleanType
3044 proceed;
3045
cristyb5d5f722009-11-04 03:03:49 +00003046#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy50fbc382011-07-07 02:19:17 +00003047 #pragma omp critical (MagickCore_NegateImage)
cristy3ed852e2009-09-05 21:47:34 +00003048#endif
3049 proceed=SetImageProgress(image,NegateImageTag,progress++,
3050 image->rows);
3051 if (proceed == MagickFalse)
3052 status=MagickFalse;
3053 }
3054 }
3055 image_view=DestroyCacheView(image_view);
3056 return(MagickTrue);
3057 }
3058 /*
3059 Negate image.
3060 */
cristyb5d5f722009-11-04 03:03:49 +00003061#if defined(MAGICKCORE_OPENMP_SUPPORT)
3062 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003063#endif
cristybb503372010-05-27 20:51:26 +00003064 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003065 {
cristy4c08aed2011-07-01 19:47:50 +00003066 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00003067 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003068
cristy8d4629b2010-08-30 17:59:46 +00003069 register ssize_t
3070 x;
3071
cristy3ed852e2009-09-05 21:47:34 +00003072 if (status == MagickFalse)
3073 continue;
3074 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristyacd2ed22011-08-30 01:44:23 +00003075 if (q == (Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00003076 {
3077 status=MagickFalse;
3078 continue;
3079 }
cristybb503372010-05-27 20:51:26 +00003080 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003081 {
cristyf7dc44c2011-07-20 14:41:15 +00003082 register ssize_t
3083 i;
3084
cristya30d9ba2011-07-23 21:00:48 +00003085 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
cristyf7dc44c2011-07-20 14:41:15 +00003086 {
cristy1aaa3cd2011-08-21 23:48:17 +00003087 PixelTrait
cristyf7dc44c2011-07-20 14:41:15 +00003088 traits;
3089
3090 traits=GetPixelChannelMapTraits(image,(PixelChannel) i);
3091 if ((traits & UpdatePixelTrait) != 0)
3092 q[i]=QuantumRange-q[i];
3093 }
cristya30d9ba2011-07-23 21:00:48 +00003094 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00003095 }
3096 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
3097 status=MagickFalse;
3098 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3099 {
3100 MagickBooleanType
3101 proceed;
3102
cristyb5d5f722009-11-04 03:03:49 +00003103#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy50fbc382011-07-07 02:19:17 +00003104 #pragma omp critical (MagickCore_NegateImage)
cristy3ed852e2009-09-05 21:47:34 +00003105#endif
3106 proceed=SetImageProgress(image,NegateImageTag,progress++,image->rows);
3107 if (proceed == MagickFalse)
3108 status=MagickFalse;
3109 }
3110 }
3111 image_view=DestroyCacheView(image_view);
3112 return(status);
3113}
3114
3115/*
3116%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3117% %
3118% %
3119% %
3120% N o r m a l i z e I m a g e %
3121% %
3122% %
3123% %
3124%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3125%
cristya4dfb122011-07-07 19:01:57 +00003126% NormalizeImage() enhances the contrast of a color image by mapping the
3127% darkest 2 percent of all pixel to black and the brightest 1 percent to white.
cristy3ed852e2009-09-05 21:47:34 +00003128%
3129% The format of the NormalizeImage method is:
3130%
cristye23ec9d2011-08-16 18:15:40 +00003131% MagickBooleanType NormalizeImage(Image *image,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003132%
3133% A description of each parameter follows:
3134%
3135% o image: the image.
3136%
cristye23ec9d2011-08-16 18:15:40 +00003137% o exception: return any errors or warnings in this structure.
3138%
cristy3ed852e2009-09-05 21:47:34 +00003139*/
cristye23ec9d2011-08-16 18:15:40 +00003140MagickExport MagickBooleanType NormalizeImage(Image *image,
3141 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003142{
cristy3ed852e2009-09-05 21:47:34 +00003143 double
3144 black_point,
3145 white_point;
3146
cristy530239c2010-07-25 17:34:26 +00003147 black_point=(double) image->columns*image->rows*0.0015;
3148 white_point=(double) image->columns*image->rows*0.9995;
cristye23ec9d2011-08-16 18:15:40 +00003149 return(ContrastStretchImage(image,black_point,white_point,exception));
cristy3ed852e2009-09-05 21:47:34 +00003150}
3151
3152/*
3153%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3154% %
3155% %
3156% %
3157% S i g m o i d a l C o n t r a s t I m a g e %
3158% %
3159% %
3160% %
3161%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3162%
3163% SigmoidalContrastImage() adjusts the contrast of an image with a non-linear
3164% sigmoidal contrast algorithm. Increase the contrast of the image using a
3165% sigmoidal transfer function without saturating highlights or shadows.
3166% Contrast indicates how much to increase the contrast (0 is none; 3 is
3167% typical; 20 is pushing it); mid-point indicates where midtones fall in the
3168% resultant image (0 is white; 50% is middle-gray; 100% is black). Set
3169% sharpen to MagickTrue to increase the image contrast otherwise the contrast
3170% is reduced.
3171%
3172% The format of the SigmoidalContrastImage method is:
3173%
3174% MagickBooleanType SigmoidalContrastImage(Image *image,
cristy33bd5152011-08-24 01:42:24 +00003175% const MagickBooleanType sharpen,const char *levels,
3176% ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003177%
3178% A description of each parameter follows:
3179%
3180% o image: the image.
3181%
cristy3ed852e2009-09-05 21:47:34 +00003182% o sharpen: Increase or decrease image contrast.
3183%
cristyfa769582010-09-30 23:30:03 +00003184% o alpha: strength of the contrast, the larger the number the more
3185% 'threshold-like' it becomes.
cristy3ed852e2009-09-05 21:47:34 +00003186%
cristyfa769582010-09-30 23:30:03 +00003187% o beta: midpoint of the function as a color value 0 to QuantumRange.
cristy3ed852e2009-09-05 21:47:34 +00003188%
cristy33bd5152011-08-24 01:42:24 +00003189% o exception: return any errors or warnings in this structure.
3190%
cristy3ed852e2009-09-05 21:47:34 +00003191*/
cristy3ed852e2009-09-05 21:47:34 +00003192MagickExport MagickBooleanType SigmoidalContrastImage(Image *image,
cristy33bd5152011-08-24 01:42:24 +00003193 const MagickBooleanType sharpen,const double contrast,const double midpoint,
3194 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003195{
3196#define SigmoidalContrastImageTag "SigmoidalContrast/Image"
3197
cristyc4c8d132010-01-07 01:58:38 +00003198 CacheView
3199 *image_view;
3200
cristy3ed852e2009-09-05 21:47:34 +00003201 MagickBooleanType
3202 status;
3203
cristybb503372010-05-27 20:51:26 +00003204 MagickOffsetType
3205 progress;
3206
cristy3ed852e2009-09-05 21:47:34 +00003207 MagickRealType
3208 *sigmoidal_map;
3209
cristybb503372010-05-27 20:51:26 +00003210 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003211 i;
3212
cristybb503372010-05-27 20:51:26 +00003213 ssize_t
3214 y;
3215
cristy3ed852e2009-09-05 21:47:34 +00003216 /*
3217 Allocate and initialize sigmoidal maps.
3218 */
3219 assert(image != (Image *) NULL);
3220 assert(image->signature == MagickSignature);
3221 if (image->debug != MagickFalse)
3222 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3223 sigmoidal_map=(MagickRealType *) AcquireQuantumMemory(MaxMap+1UL,
3224 sizeof(*sigmoidal_map));
3225 if (sigmoidal_map == (MagickRealType *) NULL)
3226 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
3227 image->filename);
3228 (void) ResetMagickMemory(sigmoidal_map,0,(MaxMap+1)*sizeof(*sigmoidal_map));
cristyb5d5f722009-11-04 03:03:49 +00003229#if defined(MAGICKCORE_OPENMP_SUPPORT)
3230 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003231#endif
cristybb503372010-05-27 20:51:26 +00003232 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +00003233 {
3234 if (sharpen != MagickFalse)
3235 {
3236 sigmoidal_map[i]=(MagickRealType) ScaleMapToQuantum((MagickRealType)
3237 (MaxMap*((1.0/(1.0+exp(contrast*(midpoint/(double) QuantumRange-
cristy33bd5152011-08-24 01:42:24 +00003238 (double) i/MaxMap))))-(1.0/(1.0+exp(contrast*(midpoint/(double)
3239 QuantumRange)))))/((1.0/(1.0+exp(contrast*(midpoint/(double)
3240 QuantumRange-1.0))))-(1.0/(1.0+exp(contrast*(midpoint/(double)
3241 QuantumRange)))))+0.5));
cristy3ed852e2009-09-05 21:47:34 +00003242 continue;
3243 }
3244 sigmoidal_map[i]=(MagickRealType) ScaleMapToQuantum((MagickRealType)
cristy33bd5152011-08-24 01:42:24 +00003245 (MaxMap*(QuantumScale*midpoint-log((1.0-(1.0/(1.0+exp(midpoint/(double)
3246 QuantumRange*contrast))+((double) i/MaxMap)*((1.0/(1.0+exp(contrast*(
3247 midpoint/(double) QuantumRange-1.0))))-(1.0/(1.0+exp(midpoint/(double)
3248 QuantumRange*contrast))))))/(1.0/(1.0+exp(midpoint/(double) QuantumRange*
3249 contrast))+((double) i/MaxMap)*((1.0/(1.0+exp(contrast*(midpoint/(double)
3250 QuantumRange-1.0))))-(1.0/(1.0+exp(midpoint/(double) QuantumRange*
3251 contrast))))))/contrast)));
cristy3ed852e2009-09-05 21:47:34 +00003252 }
3253 if (image->storage_class == PseudoClass)
3254 {
3255 /*
3256 Sigmoidal-contrast enhance colormap.
3257 */
cristyb5d5f722009-11-04 03:03:49 +00003258#if defined(MAGICKCORE_OPENMP_SUPPORT)
3259 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003260#endif
cristybb503372010-05-27 20:51:26 +00003261 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00003262 {
cristyed231572011-07-14 02:18:59 +00003263 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristyce70c172010-01-07 17:15:30 +00003264 image->colormap[i].red=ClampToQuantum(sigmoidal_map[
cristy3ed852e2009-09-05 21:47:34 +00003265 ScaleQuantumToMap(image->colormap[i].red)]);
cristyed231572011-07-14 02:18:59 +00003266 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristyce70c172010-01-07 17:15:30 +00003267 image->colormap[i].green=ClampToQuantum(sigmoidal_map[
cristy3ed852e2009-09-05 21:47:34 +00003268 ScaleQuantumToMap(image->colormap[i].green)]);
cristyed231572011-07-14 02:18:59 +00003269 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristyce70c172010-01-07 17:15:30 +00003270 image->colormap[i].blue=ClampToQuantum(sigmoidal_map[
cristy3ed852e2009-09-05 21:47:34 +00003271 ScaleQuantumToMap(image->colormap[i].blue)]);
cristyed231572011-07-14 02:18:59 +00003272 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003273 image->colormap[i].alpha=ClampToQuantum(sigmoidal_map[
3274 ScaleQuantumToMap(image->colormap[i].alpha)]);
cristy3ed852e2009-09-05 21:47:34 +00003275 }
3276 }
3277 /*
3278 Sigmoidal-contrast enhance image.
3279 */
3280 status=MagickTrue;
3281 progress=0;
cristy3ed852e2009-09-05 21:47:34 +00003282 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00003283#if defined(MAGICKCORE_OPENMP_SUPPORT)
3284 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003285#endif
cristybb503372010-05-27 20:51:26 +00003286 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003287 {
cristy4c08aed2011-07-01 19:47:50 +00003288 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00003289 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003290
cristy8d4629b2010-08-30 17:59:46 +00003291 register ssize_t
3292 x;
3293
cristy3ed852e2009-09-05 21:47:34 +00003294 if (status == MagickFalse)
3295 continue;
3296 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristyacd2ed22011-08-30 01:44:23 +00003297 if (q == (Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00003298 {
3299 status=MagickFalse;
3300 continue;
3301 }
cristybb503372010-05-27 20:51:26 +00003302 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003303 {
cristy33bd5152011-08-24 01:42:24 +00003304 register ssize_t
3305 i;
3306
3307 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
3308 {
3309 PixelTrait
3310 traits;
3311
3312 traits=GetPixelChannelMapTraits(image,(PixelChannel) i);
3313 if ((traits & UpdatePixelTrait) != 0)
3314 q[i]=ClampToQuantum(sigmoidal_map[ScaleQuantumToMap(q[i])]);
3315 }
cristyed231572011-07-14 02:18:59 +00003316 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00003317 }
3318 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
3319 status=MagickFalse;
3320 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3321 {
3322 MagickBooleanType
3323 proceed;
3324
cristyb5d5f722009-11-04 03:03:49 +00003325#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy9ee60942011-07-06 14:54:38 +00003326 #pragma omp critical (MagickCore_SigmoidalContrastImage)
cristy3ed852e2009-09-05 21:47:34 +00003327#endif
3328 proceed=SetImageProgress(image,SigmoidalContrastImageTag,progress++,
3329 image->rows);
3330 if (proceed == MagickFalse)
3331 status=MagickFalse;
3332 }
3333 }
3334 image_view=DestroyCacheView(image_view);
3335 sigmoidal_map=(MagickRealType *) RelinquishMagickMemory(sigmoidal_map);
3336 return(status);
3337}