blob: c0767353ca0ede50e6b1d37d60de0ddbbb7baecd [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% %
20% Copyright 1999-2009 ImageMagick Studio LLC, a non-profit organization %
21% 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*/
43#include "magick/studio.h"
44#include "magick/artifact.h"
45#include "magick/cache.h"
46#include "magick/cache-view.h"
47#include "magick/color.h"
48#include "magick/color-private.h"
49#include "magick/colorspace.h"
50#include "magick/composite-private.h"
51#include "magick/enhance.h"
52#include "magick/exception.h"
53#include "magick/exception-private.h"
54#include "magick/gem.h"
55#include "magick/geometry.h"
56#include "magick/histogram.h"
57#include "magick/image.h"
58#include "magick/image-private.h"
59#include "magick/memory_.h"
60#include "magick/monitor.h"
61#include "magick/monitor-private.h"
62#include "magick/option.h"
63#include "magick/quantum.h"
64#include "magick/quantum-private.h"
65#include "magick/resample.h"
66#include "magick/resample-private.h"
67#include "magick/statistic.h"
68#include "magick/string_.h"
69#include "magick/thread-private.h"
70#include "magick/token.h"
71#include "magick/xml-tree.h"
72
73/*
74%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
75% %
76% %
77% %
78% A u t o G a m m a I m a g e %
79% %
80% %
81% %
82%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
83%
84% AutoGammaImage() extract the 'mean' from the image and adjust the image
85% to try make set its gamma appropriatally.
86%
cristy308b4e62009-09-21 14:40:44 +000087% The format of the AutoGammaImage method is:
cristy3ed852e2009-09-05 21:47:34 +000088%
89% MagickBooleanType AutoGammaImage(Image *image)
90% MagickBooleanType AutoGammaImageChannel(Image *image,
91% const ChannelType channel)
92%
93% A description of each parameter follows:
94%
95% o image: The image to auto-level
96%
97% o channel: The channels to auto-level. If the special 'SyncChannels'
98% flag is set all given channels is adjusted in the same way using the
99% mean average of those channels.
100%
101*/
102
103MagickExport MagickBooleanType AutoGammaImage(Image *image)
104{
105 return(AutoGammaImageChannel(image,DefaultChannels));
106}
107
108MagickExport MagickBooleanType AutoGammaImageChannel(Image *image,
109 const ChannelType channel)
110{
111 MagickStatusType
112 status;
113
114 double
anthony4efe5972009-09-11 06:46:40 +0000115 mean,junk,gamma,logmean;
116
117 logmean=log(0.5);
cristy3ed852e2009-09-05 21:47:34 +0000118
119 if ((channel & SyncChannels) != 0 )
120 {
121 /*
122 Apply gamma correction equally accross all given channels
123 */
124 GetImageChannelMean(image, channel, &mean, &junk, &image->exception);
anthony4efe5972009-09-11 06:46:40 +0000125 gamma = log(mean*QuantumScale)/logmean;
cristy3ed852e2009-09-05 21:47:34 +0000126 //return GammaImageChannel(image, channel, gamma);
127 return LevelImageChannel(image, channel,
128 0.0, (double)QuantumRange, gamma);
129 }
130
131 /*
132 auto-gamma each channel separateally
133 */
134 status = MagickTrue;
135 if ((channel & RedChannel) != 0)
136 {
137 GetImageChannelMean(image, RedChannel, &mean, &junk, &image->exception);
anthony4efe5972009-09-11 06:46:40 +0000138 gamma = log(mean*QuantumScale)/logmean;
cristy3ed852e2009-09-05 21:47:34 +0000139 //status = status && GammaImageChannel(image, RedChannel, gamma);
140 status = status && LevelImageChannel(image, RedChannel,
141 0.0, (double)QuantumRange, gamma);
142 }
143 if ((channel & GreenChannel) != 0)
144 {
145 GetImageChannelMean(image, GreenChannel, &mean, &junk, &image->exception);
anthony4efe5972009-09-11 06:46:40 +0000146 gamma = log(mean*QuantumScale)/logmean;
cristy3ed852e2009-09-05 21:47:34 +0000147 //status = status && GammaImageChannel(image, GreenChannel, gamma);
148 status = status && LevelImageChannel(image, GreenChannel,
149 0.0, (double)QuantumRange, gamma);
150 }
151 if ((channel & BlueChannel) != 0)
152 {
153 GetImageChannelMean(image, BlueChannel, &mean, &junk, &image->exception);
anthony4efe5972009-09-11 06:46:40 +0000154 gamma = log(mean*QuantumScale)/logmean;
cristy3ed852e2009-09-05 21:47:34 +0000155 //status = status && GammaImageChannel(image, BlueChannel, gamma);
156 status = status && LevelImageChannel(image, BlueChannel,
157 0.0, (double)QuantumRange, gamma);
158 }
159 if (((channel & OpacityChannel) != 0) &&
160 (image->matte == MagickTrue))
161 {
162 GetImageChannelMean(image, OpacityChannel, &mean, &junk, &image->exception);
anthony4efe5972009-09-11 06:46:40 +0000163 gamma = log(mean*QuantumScale)/logmean;
cristy3ed852e2009-09-05 21:47:34 +0000164 //status = status && GammaImageChannel(image, OpacityChannel, gamma);
165 status = status && LevelImageChannel(image, OpacityChannel,
166 0.0, (double)QuantumRange, gamma);
167 }
168 if (((channel & IndexChannel) != 0) &&
169 (image->colorspace == CMYKColorspace))
170 {
171 GetImageChannelMean(image, IndexChannel, &mean, &junk, &image->exception);
anthony4efe5972009-09-11 06:46:40 +0000172 gamma = log(mean*QuantumScale)/logmean;
cristy3ed852e2009-09-05 21:47:34 +0000173 //status = status && GammaImageChannel(image, IndexChannel, gamma);
174 status = status && LevelImageChannel(image, IndexChannel,
175 0.0, (double)QuantumRange, gamma);
176 }
177 return(status != 0 ? MagickTrue : MagickFalse);
178}
179
180/*
181%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
182% %
183% %
184% %
185% A u t o L e v e l I m a g e %
186% %
187% %
188% %
189%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
190%
191% AutoLevelImage() adjusts the levels of a particular image channel by
192% scaling the minimum and maximum values to the full quantum range.
193%
194% The format of the LevelImage method is:
195%
196% MagickBooleanType AutoLevelImage(Image *image)
197% MagickBooleanType AutoLevelImageChannel(Image *image,
198% const ChannelType channel)
199%
200% A description of each parameter follows:
201%
202% o image: The image to auto-level
203%
204% o channel: The channels to auto-level. If the special 'SyncChannels'
205% flag is set the min/max/mean value of all given channels is used for
206% all given channels, to all channels in the same way.
207%
208*/
209
210MagickExport MagickBooleanType AutoLevelImage(Image *image)
211{
212 return(AutoLevelImageChannel(image,DefaultChannels));
213}
214
215MagickExport MagickBooleanType AutoLevelImageChannel(Image *image,
216 const ChannelType channel)
217{
218 /*
219 This is simply a convenience function around a Min/Max Histogram Stretch
220 */
221 return MinMaxStretchImage(image, channel, 0.0, 0.0);
222}
223
224/*
225%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
226% %
227% %
228% %
229% C o l o r D e c i s i o n L i s t I m a g e %
230% %
231% %
232% %
233%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
234%
235% ColorDecisionListImage() accepts a lightweight Color Correction Collection
236% (CCC) file which solely contains one or more color corrections and applies
237% the correction to the image. Here is a sample CCC file:
238%
239% <ColorCorrectionCollection xmlns="urn:ASC:CDL:v1.2">
240% <ColorCorrection id="cc03345">
241% <SOPNode>
242% <Slope> 0.9 1.2 0.5 </Slope>
243% <Offset> 0.4 -0.5 0.6 </Offset>
244% <Power> 1.0 0.8 1.5 </Power>
245% </SOPNode>
246% <SATNode>
247% <Saturation> 0.85 </Saturation>
248% </SATNode>
249% </ColorCorrection>
250% </ColorCorrectionCollection>
251%
252% which includes the slop, offset, and power for each of the RGB channels
253% as well as the saturation.
254%
255% The format of the ColorDecisionListImage method is:
256%
257% MagickBooleanType ColorDecisionListImage(Image *image,
258% const char *color_correction_collection)
259%
260% A description of each parameter follows:
261%
262% o image: the image.
263%
264% o color_correction_collection: the color correction collection in XML.
265%
266*/
267MagickExport MagickBooleanType ColorDecisionListImage(Image *image,
268 const char *color_correction_collection)
269{
270#define ColorDecisionListCorrectImageTag "ColorDecisionList/Image"
271
272 typedef struct _Correction
273 {
274 double
275 slope,
276 offset,
277 power;
278 } Correction;
279
280 typedef struct _ColorCorrection
281 {
282 Correction
283 red,
284 green,
285 blue;
286
287 double
288 saturation;
289 } ColorCorrection;
290
291 char
292 token[MaxTextExtent];
293
294 ColorCorrection
295 color_correction;
296
297 const char
298 *content,
299 *p;
300
301 ExceptionInfo
302 *exception;
303
304 long
305 progress,
306 y;
307
308 MagickBooleanType
309 status;
310
311 PixelPacket
312 *cdl_map;
313
314 register long
315 i;
316
317 XMLTreeInfo
318 *cc,
319 *ccc,
320 *sat,
321 *sop;
322
323 CacheView
324 *image_view;
325
326 /*
327 Allocate and initialize cdl maps.
328 */
329 assert(image != (Image *) NULL);
330 assert(image->signature == MagickSignature);
331 if (image->debug != MagickFalse)
332 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
333 if (color_correction_collection == (const char *) NULL)
334 return(MagickFalse);
335 ccc=NewXMLTree((const char *) color_correction_collection,&image->exception);
336 if (ccc == (XMLTreeInfo *) NULL)
337 return(MagickFalse);
338 cc=GetXMLTreeChild(ccc,"ColorCorrection");
339 if (cc == (XMLTreeInfo *) NULL)
340 {
341 ccc=DestroyXMLTree(ccc);
342 return(MagickFalse);
343 }
344 color_correction.red.slope=1.0;
345 color_correction.red.offset=0.0;
346 color_correction.red.power=1.0;
347 color_correction.green.slope=1.0;
348 color_correction.green.offset=0.0;
349 color_correction.green.power=1.0;
350 color_correction.blue.slope=1.0;
351 color_correction.blue.offset=0.0;
352 color_correction.blue.power=1.0;
353 color_correction.saturation=0.0;
354 sop=GetXMLTreeChild(cc,"SOPNode");
355 if (sop != (XMLTreeInfo *) NULL)
356 {
357 XMLTreeInfo
358 *offset,
359 *power,
360 *slope;
361
362 slope=GetXMLTreeChild(sop,"Slope");
363 if (slope != (XMLTreeInfo *) NULL)
364 {
365 content=GetXMLTreeContent(slope);
366 p=(const char *) content;
367 for (i=0; (*p != '\0') && (i < 3); i++)
368 {
369 GetMagickToken(p,&p,token);
370 if (*token == ',')
371 GetMagickToken(p,&p,token);
372 switch (i)
373 {
374 case 0: color_correction.red.slope=atof(token); break;
375 case 1: color_correction.green.slope=atof(token); break;
376 case 2: color_correction.blue.slope=atof(token); break;
377 }
378 }
379 }
380 offset=GetXMLTreeChild(sop,"Offset");
381 if (offset != (XMLTreeInfo *) NULL)
382 {
383 content=GetXMLTreeContent(offset);
384 p=(const char *) content;
385 for (i=0; (*p != '\0') && (i < 3); i++)
386 {
387 GetMagickToken(p,&p,token);
388 if (*token == ',')
389 GetMagickToken(p,&p,token);
390 switch (i)
391 {
392 case 0: color_correction.red.offset=atof(token); break;
393 case 1: color_correction.green.offset=atof(token); break;
394 case 2: color_correction.blue.offset=atof(token); break;
395 }
396 }
397 }
398 power=GetXMLTreeChild(sop,"Power");
399 if (power != (XMLTreeInfo *) NULL)
400 {
401 content=GetXMLTreeContent(power);
402 p=(const char *) content;
403 for (i=0; (*p != '\0') && (i < 3); i++)
404 {
405 GetMagickToken(p,&p,token);
406 if (*token == ',')
407 GetMagickToken(p,&p,token);
408 switch (i)
409 {
410 case 0: color_correction.red.power=atof(token); break;
411 case 1: color_correction.green.power=atof(token); break;
412 case 2: color_correction.blue.power=atof(token); break;
413 }
414 }
415 }
416 }
417 sat=GetXMLTreeChild(cc,"SATNode");
418 if (sat != (XMLTreeInfo *) NULL)
419 {
420 XMLTreeInfo
421 *saturation;
422
423 saturation=GetXMLTreeChild(sat,"Saturation");
424 if (saturation != (XMLTreeInfo *) NULL)
425 {
426 content=GetXMLTreeContent(saturation);
427 p=(const char *) content;
428 GetMagickToken(p,&p,token);
429 color_correction.saturation=atof(token);
430 }
431 }
432 ccc=DestroyXMLTree(ccc);
433 if (image->debug != MagickFalse)
434 {
435 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
436 " Color Correction Collection:");
437 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
438 " color_correction.red.slope: %g",color_correction.red.slope);
439 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
440 " color_correction.red.offset: %g",color_correction.red.offset);
441 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
442 " color_correction.red.power: %g",color_correction.red.power);
443 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
444 " color_correction.green.slope: %g",color_correction.green.slope);
445 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
446 " color_correction.green.offset: %g",color_correction.green.offset);
447 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
448 " color_correction.green.power: %g",color_correction.green.power);
449 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
450 " color_correction.blue.slope: %g",color_correction.blue.slope);
451 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
452 " color_correction.blue.offset: %g",color_correction.blue.offset);
453 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
454 " color_correction.blue.power: %g",color_correction.blue.power);
455 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
456 " color_correction.saturation: %g",color_correction.saturation);
457 }
458 cdl_map=(PixelPacket *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*cdl_map));
459 if (cdl_map == (PixelPacket *) NULL)
460 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
461 image->filename);
cristy629a6c72009-09-13 23:28:22 +0000462#if defined(_OPENMP) && (_OPENMP >= 200203)
cristye0f584d2009-10-11 00:59:14 +0000463 #pragma omp parallel for
cristy3ed852e2009-09-05 21:47:34 +0000464#endif
465 for (i=0; i <= (long) MaxMap; i++)
466 {
467 cdl_map[i].red=RoundToQuantum((MagickRealType) ScaleMapToQuantum((
468 MagickRealType) (MaxMap*(pow(color_correction.red.slope*i/MaxMap+
469 color_correction.red.offset,color_correction.red.power)))));
470 cdl_map[i].green=RoundToQuantum((MagickRealType) ScaleMapToQuantum((
471 MagickRealType) (MaxMap*(pow(color_correction.green.slope*i/MaxMap+
472 color_correction.green.offset,color_correction.green.power)))));
473 cdl_map[i].blue=RoundToQuantum((MagickRealType) ScaleMapToQuantum((
474 MagickRealType) (MaxMap*(pow(color_correction.blue.slope*i/MaxMap+
475 color_correction.blue.offset,color_correction.blue.power)))));
476 }
477 if (image->storage_class == PseudoClass)
478 {
479 /*
480 Apply transfer function to colormap.
481 */
cristy629a6c72009-09-13 23:28:22 +0000482#if defined(_OPENMP) && (_OPENMP >= 200203)
cristye0f584d2009-10-11 00:59:14 +0000483 #pragma omp parallel for shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000484#endif
485 for (i=0; i < (long) image->colors; i++)
486 {
487 double
488 luma;
489
490 luma=0.2126*image->colormap[i].red+0.7152*image->colormap[i].green+
491 0.0722*image->colormap[i].blue;
492 image->colormap[i].red=RoundToQuantum(luma+color_correction.saturation*
493 cdl_map[ScaleQuantumToMap(image->colormap[i].red)].red-luma);
494 image->colormap[i].green=RoundToQuantum(luma+
495 color_correction.saturation*cdl_map[ScaleQuantumToMap(
496 image->colormap[i].green)].green-luma);
497 image->colormap[i].blue=RoundToQuantum(luma+color_correction.saturation*
498 cdl_map[ScaleQuantumToMap(image->colormap[i].blue)].blue-luma);
499 }
500 }
501 /*
502 Apply transfer function to image.
503 */
504 status=MagickTrue;
505 progress=0;
506 exception=(&image->exception);
507 image_view=AcquireCacheView(image);
cristy629a6c72009-09-13 23:28:22 +0000508#if defined(_OPENMP) && (_OPENMP >= 200203)
cristye0f584d2009-10-11 00:59:14 +0000509 #pragma omp parallel for shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000510#endif
511 for (y=0; y < (long) image->rows; y++)
512 {
513 double
514 luma;
515
516 register long
517 x;
518
519 register PixelPacket
520 *__restrict q;
521
522 if (status == MagickFalse)
523 continue;
524 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
525 if (q == (PixelPacket *) NULL)
526 {
527 status=MagickFalse;
528 continue;
529 }
530 for (x=0; x < (long) image->columns; x++)
531 {
532 luma=0.2126*q->red+0.7152*q->green+0.0722*q->blue;
533 q->red=RoundToQuantum(luma+color_correction.saturation*
534 (cdl_map[ScaleQuantumToMap(q->red)].red-luma));
535 q->green=RoundToQuantum(luma+color_correction.saturation*
536 (cdl_map[ScaleQuantumToMap(q->green)].green-luma));
537 q->blue=RoundToQuantum(luma+color_correction.saturation*
538 (cdl_map[ScaleQuantumToMap(q->blue)].blue-luma));
539 q++;
540 }
541 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
542 status=MagickFalse;
543 if (image->progress_monitor != (MagickProgressMonitor) NULL)
544 {
545 MagickBooleanType
546 proceed;
547
cristy629a6c72009-09-13 23:28:22 +0000548#if defined(_OPENMP) && (_OPENMP >= 200203)
cristy3ed852e2009-09-05 21:47:34 +0000549 #pragma omp critical (MagickCore_ColorDecisionListImageChannel)
550#endif
551 proceed=SetImageProgress(image,ColorDecisionListCorrectImageTag,
552 progress++,image->rows);
553 if (proceed == MagickFalse)
554 status=MagickFalse;
555 }
556 }
557 image_view=DestroyCacheView(image_view);
558 cdl_map=(PixelPacket *) RelinquishMagickMemory(cdl_map);
559 return(status);
560}
561
562/*
563%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
564% %
565% %
566% %
567% C l u t I m a g e %
568% %
569% %
570% %
571%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
572%
573% ClutImage() replaces each color value in the given image, by using it as an
574% index to lookup a replacement color value in a Color Look UP Table in the
575% form of an image. The values are extracted along a diagonal of the CLUT
576% image so either a horizontal or vertial gradient image can be used.
577%
578% Typically this is used to either re-color a gray-scale image according to a
579% color gradient in the CLUT image, or to perform a freeform histogram
580% (level) adjustment according to the (typically gray-scale) gradient in the
581% CLUT image.
582%
583% When the 'channel' mask includes the matte/alpha transparency channel but
584% one image has no such channel it is assumed that that image is a simple
585% gray-scale image that will effect the alpha channel values, either for
586% gray-scale coloring (with transparent or semi-transparent colors), or
587% a histogram adjustment of existing alpha channel values. If both images
588% have matte channels, direct and normal indexing is applied, which is rarely
589% used.
590%
591% The format of the ClutImage method is:
592%
593% MagickBooleanType ClutImage(Image *image,Image *clut_image)
594% MagickBooleanType ClutImageChannel(Image *image,
595% const ChannelType channel,Image *clut_image)
596%
597% A description of each parameter follows:
598%
599% o image: the image, which is replaced by indexed CLUT values
600%
601% o clut_image: the color lookup table image for replacement color values.
602%
603% o channel: the channel.
604%
605*/
606
607MagickExport MagickBooleanType ClutImage(Image *image,const Image *clut_image)
608{
609 return(ClutImageChannel(image,DefaultChannels,clut_image));
610}
611
612MagickExport MagickBooleanType ClutImageChannel(Image *image,
613 const ChannelType channel,const Image *clut_image)
614{
615#define ClutImageTag "Clut/Image"
616
617 ExceptionInfo
618 *exception;
619
620 long
621 adjust,
622 progress,
623 y;
624
625 MagickBooleanType
626 status;
627
628 MagickPixelPacket
629 zero;
630
631 ResampleFilter
632 **resample_filter;
633
634 CacheView
635 *image_view;
636
637 assert(image != (Image *) NULL);
638 assert(image->signature == MagickSignature);
639 if (image->debug != MagickFalse)
640 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
641 assert(clut_image != (Image *) NULL);
642 assert(clut_image->signature == MagickSignature);
643 if (SetImageStorageClass(image,DirectClass) == MagickFalse)
644 return(MagickFalse);
645 /*
646 Clut image.
647 */
648 status=MagickTrue;
649 progress=0;
650 GetMagickPixelPacket(clut_image,&zero);
651 adjust=clut_image->interpolate == IntegerInterpolatePixel ? 0 : 1;
652 exception=(&image->exception);
653 resample_filter=AcquireResampleFilterThreadSet(clut_image,MagickTrue,
654 exception);
655 image_view=AcquireCacheView(image);
cristy629a6c72009-09-13 23:28:22 +0000656#if defined(_OPENMP) && (_OPENMP >= 200203)
cristye0f584d2009-10-11 00:59:14 +0000657 #pragma omp parallel for shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000658#endif
659 for (y=0; y < (long) image->rows; y++)
660 {
661 MagickPixelPacket
662 pixel;
663
664 register IndexPacket
665 *__restrict indexes;
666
667 register long
668 id,
669 x;
670
671 register PixelPacket
672 *__restrict q;
673
674 if (status == MagickFalse)
675 continue;
676 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
677 if (q == (PixelPacket *) NULL)
678 {
679 status=MagickFalse;
680 continue;
681 }
682 indexes=GetCacheViewAuthenticIndexQueue(image_view);
683 pixel=zero;
684 id=GetOpenMPThreadId();
685 for (x=0; x < (long) image->columns; x++)
686 {
687 /*
688 PROGRAMMERS WARNING:
689
690 Apply OpacityChannel BEFORE the color channels. Do not re-order.
691
692 The handling special case 2 (coloring gray-scale), requires access to
693 the unmodified colors of the original image to determine the index
694 value. As such alpha/matte channel handling must be performed BEFORE,
695 any of the color channels are modified.
696
697 */
698 if ((channel & OpacityChannel) != 0)
699 {
700 if (clut_image->matte == MagickFalse)
701 {
702 /*
703 A gray-scale LUT replacement for an image alpha channel.
704 */
705 (void) ResamplePixelColor(resample_filter[id],QuantumScale*
706 (QuantumRange-q->opacity)*(clut_image->columns+adjust),
707 QuantumScale*(QuantumRange-q->opacity)*(clut_image->rows+
708 adjust),&pixel);
709 q->opacity=(Quantum) (QuantumRange-MagickPixelIntensityToQuantum(
710 &pixel));
711 }
712 else
713 if (image->matte == MagickFalse)
714 {
715 /*
716 A greyscale image being colored by a LUT with transparency.
717 */
718 (void) ResamplePixelColor(resample_filter[id],QuantumScale*
719 PixelIntensity(q)*(clut_image->columns-adjust),QuantumScale*
720 PixelIntensity(q)*(clut_image->rows-adjust),&pixel);
721 q->opacity=RoundToQuantum(pixel.opacity);
722 }
723 else
724 {
725 /*
726 Direct alpha channel lookup.
727 */
728 (void) ResamplePixelColor(resample_filter[id],QuantumScale*
729 q->opacity*(clut_image->columns-adjust),QuantumScale*
730 q->opacity* (clut_image->rows-adjust),&pixel);
731 q->opacity=RoundToQuantum(pixel.opacity);
732 }
733 }
734 if ((channel & RedChannel) != 0)
735 {
736 (void) ResamplePixelColor(resample_filter[id],QuantumScale*q->red*
737 (clut_image->columns-adjust),QuantumScale*q->red*
738 (clut_image->rows-adjust),&pixel);
739 q->red=RoundToQuantum(pixel.red);
740 }
741 if ((channel & GreenChannel) != 0)
742 {
743 (void) ResamplePixelColor(resample_filter[id],QuantumScale*q->green*
744 (clut_image->columns-adjust),QuantumScale*q->green*
745 (clut_image->rows-adjust),&pixel);
746 q->green=RoundToQuantum(pixel.green);
747 }
748 if ((channel & BlueChannel) != 0)
749 {
750 (void) ResamplePixelColor(resample_filter[id],QuantumScale*q->blue*
751 (clut_image->columns-adjust),QuantumScale*q->blue*
752 (clut_image->rows-adjust),&pixel);
753 q->blue=RoundToQuantum(pixel.blue);
754 }
755 if (((channel & IndexChannel) != 0) &&
756 (image->colorspace == CMYKColorspace))
757 {
758 (void) ResamplePixelColor(resample_filter[id],QuantumScale*indexes[x]*
759 (clut_image->columns-adjust),QuantumScale*indexes[x]*
760 (clut_image->rows-adjust),&pixel);
761 indexes[x]=RoundToQuantum(pixel.index);
762 }
763 q++;
764 }
765 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
766 status=MagickFalse;
767 if (image->progress_monitor != (MagickProgressMonitor) NULL)
768 {
769 MagickBooleanType
770 proceed;
771
cristy629a6c72009-09-13 23:28:22 +0000772#if defined(_OPENMP) && (_OPENMP >= 200203)
cristy3ed852e2009-09-05 21:47:34 +0000773 #pragma omp critical (MagickCore_ClutImageChannel)
774#endif
775 proceed=SetImageProgress(image,ClutImageTag,progress++,image->rows);
776 if (proceed == MagickFalse)
777 status=MagickFalse;
778 }
779 }
780 image_view=DestroyCacheView(image_view);
781 resample_filter=DestroyResampleFilterThreadSet(resample_filter);
782 /*
783 Enable alpha channel if CLUT image could enable it.
784 */
785 if ((clut_image->matte != MagickFalse) && ((channel & OpacityChannel) != 0))
786 (void) SetImageAlphaChannel(image,ActivateAlphaChannel);
787 return(status);
788}
789
790/*
791%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
792% %
793% %
794% %
795% C o n t r a s t I m a g e %
796% %
797% %
798% %
799%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
800%
801% ContrastImage() enhances the intensity differences between the lighter and
802% darker elements of the image. Set sharpen to a MagickTrue to increase the
803% image contrast otherwise the contrast is reduced.
804%
805% The format of the ContrastImage method is:
806%
807% MagickBooleanType ContrastImage(Image *image,
808% const MagickBooleanType sharpen)
809%
810% A description of each parameter follows:
811%
812% o image: the image.
813%
814% o sharpen: Increase or decrease image contrast.
815%
816*/
817
818static void Contrast(const int sign,Quantum *red,Quantum *green,Quantum *blue)
819{
820 double
821 brightness,
822 hue,
823 saturation;
824
825 /*
826 Enhance contrast: dark color become darker, light color become lighter.
827 */
828 assert(red != (Quantum *) NULL);
829 assert(green != (Quantum *) NULL);
830 assert(blue != (Quantum *) NULL);
831 hue=0.0;
832 saturation=0.0;
833 brightness=0.0;
834 ConvertRGBToHSB(*red,*green,*blue,&hue,&saturation,&brightness);
835 brightness+=0.5*sign*(0.5*(sin(MagickPI*(brightness-0.5))+1.0)-brightness);
836 if (brightness > 1.0)
837 brightness=1.0;
838 else
839 if (brightness < 0.0)
840 brightness=0.0;
841 ConvertHSBToRGB(hue,saturation,brightness,red,green,blue);
842}
843
844MagickExport MagickBooleanType ContrastImage(Image *image,
845 const MagickBooleanType sharpen)
846{
847#define ContrastImageTag "Contrast/Image"
848
849 ExceptionInfo
850 *exception;
851
852 int
853 sign;
854
855 long
856 progress,
857 y;
858
859 MagickBooleanType
860 status;
861
862 register long
863 i;
864
865 CacheView
866 *image_view;
867
868 assert(image != (Image *) NULL);
869 assert(image->signature == MagickSignature);
870 if (image->debug != MagickFalse)
871 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
872 sign=sharpen != MagickFalse ? 1 : -1;
873 if (image->storage_class == PseudoClass)
874 {
875 /*
876 Contrast enhance colormap.
877 */
878 for (i=0; i < (long) image->colors; i++)
879 Contrast(sign,&image->colormap[i].red,&image->colormap[i].green,
880 &image->colormap[i].blue);
881 }
882 /*
883 Contrast enhance image.
884 */
885 status=MagickTrue;
886 progress=0;
887 exception=(&image->exception);
888 image_view=AcquireCacheView(image);
cristy629a6c72009-09-13 23:28:22 +0000889#if defined(_OPENMP) && (_OPENMP >= 200203)
cristye0f584d2009-10-11 00:59:14 +0000890 #pragma omp parallel for shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000891#endif
892 for (y=0; y < (long) image->rows; y++)
893 {
894 register long
895 x;
896
897 register PixelPacket
898 *__restrict q;
899
900 if (status == MagickFalse)
901 continue;
902 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
903 if (q == (PixelPacket *) NULL)
904 {
905 status=MagickFalse;
906 continue;
907 }
908 for (x=0; x < (long) image->columns; x++)
909 {
910 Contrast(sign,&q->red,&q->green,&q->blue);
911 q++;
912 }
913 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
914 status=MagickFalse;
915 if (image->progress_monitor != (MagickProgressMonitor) NULL)
916 {
917 MagickBooleanType
918 proceed;
919
cristy629a6c72009-09-13 23:28:22 +0000920#if defined(_OPENMP) && (_OPENMP >= 200203)
cristy3ed852e2009-09-05 21:47:34 +0000921 #pragma omp critical (MagickCore_ContrastImage)
922#endif
923 proceed=SetImageProgress(image,ContrastImageTag,progress++,image->rows);
924 if (proceed == MagickFalse)
925 status=MagickFalse;
926 }
927 }
928 image_view=DestroyCacheView(image_view);
929 return(status);
930}
931
932/*
933%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
934% %
935% %
936% %
937% C o n t r a s t S t r e t c h I m a g e %
938% %
939% %
940% %
941%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
942%
943% The ContrastStretchImage() is a simple image enhancement technique that
944% attempts to improve the contrast in an image by `stretching' the range of
945% intensity values it contains to span a desired range of values. It differs
946% from the more sophisticated histogram equalization in that it can only
947% apply % a linear scaling function to the image pixel values. As a result
948% the `enhancement' is less harsh.
949%
950% The format of the ContrastStretchImage method is:
951%
952% MagickBooleanType ContrastStretchImage(Image *image,
953% const char *levels)
954% MagickBooleanType ContrastStretchImageChannel(Image *image,
955% const unsigned long channel,const double black_point,
956% const double white_point)
957%
958% A description of each parameter follows:
959%
960% o image: the image.
961%
962% o channel: the channel.
963%
964% o black_point: the black point.
965%
966% o white_point: the white point.
967%
968% o levels: Specify the levels where the black and white points have the
969% range of 0 to number-of-pixels (e.g. 1%, 10x90%, etc.).
970%
971*/
972
973MagickExport MagickBooleanType ContrastStretchImage(Image *image,
974 const char *levels)
975{
976 double
977 black_point,
978 white_point;
979
980 GeometryInfo
981 geometry_info;
982
983 MagickBooleanType
984 status;
985
986 MagickStatusType
987 flags;
988
989 /*
990 Parse levels.
991 */
992 if (levels == (char *) NULL)
993 return(MagickFalse);
994 flags=ParseGeometry(levels,&geometry_info);
995 black_point=geometry_info.rho;
996 white_point=(double) image->columns*image->rows;
997 if ((flags & SigmaValue) != 0)
998 white_point=geometry_info.sigma;
999 if ((flags & PercentValue) != 0)
1000 {
1001 black_point*=(double) QuantumRange/100.0;
1002 white_point*=(double) QuantumRange/100.0;
1003 }
1004 if ((flags & SigmaValue) == 0)
1005 white_point=(double) image->columns*image->rows-black_point;
1006 status=ContrastStretchImageChannel(image,DefaultChannels,black_point,
1007 white_point);
1008 return(status);
1009}
1010
1011MagickExport MagickBooleanType ContrastStretchImageChannel(Image *image,
1012 const ChannelType channel,const double black_point,const double white_point)
1013{
1014#define MaxRange(color) ((MagickRealType) ScaleQuantumToMap((Quantum) (color)))
1015#define ContrastStretchImageTag "ContrastStretch/Image"
1016
1017 double
1018 intensity;
1019
1020 ExceptionInfo
1021 *exception;
1022
1023 long
1024 progress,
1025 y;
1026
1027 MagickBooleanType
1028 status;
1029
1030 MagickPixelPacket
1031 black,
1032 *histogram,
1033 *stretch_map,
1034 white;
1035
1036 register long
1037 i;
1038
1039 CacheView
1040 *image_view;
1041
1042 /*
1043 Allocate histogram and stretch map.
1044 */
1045 assert(image != (Image *) NULL);
1046 assert(image->signature == MagickSignature);
1047 if (image->debug != MagickFalse)
1048 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1049 histogram=(MagickPixelPacket *) AcquireQuantumMemory(MaxMap+1UL,
1050 sizeof(*histogram));
1051 stretch_map=(MagickPixelPacket *) AcquireQuantumMemory(MaxMap+1UL,
1052 sizeof(*stretch_map));
1053 if ((histogram == (MagickPixelPacket *) NULL) ||
1054 (stretch_map == (MagickPixelPacket *) NULL))
1055 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
1056 image->filename);
1057 /*
1058 Form histogram.
1059 */
1060 status=MagickTrue;
1061 exception=(&image->exception);
1062 (void) ResetMagickMemory(histogram,0,(MaxMap+1)*sizeof(*histogram));
1063 image_view=AcquireCacheView(image);
1064 for (y=0; y < (long) image->rows; y++)
1065 {
1066 register const PixelPacket
1067 *__restrict p;
1068
1069 register IndexPacket
1070 *__restrict indexes;
1071
1072 register long
1073 x;
1074
1075 if (status == MagickFalse)
1076 continue;
1077 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
1078 if (p == (const PixelPacket *) NULL)
1079 {
1080 status=MagickFalse;
1081 continue;
1082 }
1083 indexes=GetCacheViewAuthenticIndexQueue(image_view);
1084 if (channel == DefaultChannels)
1085 for (x=0; x < (long) image->columns; x++)
1086 {
1087 Quantum
1088 intensity;
1089
1090 intensity=PixelIntensityToQuantum(p);
1091 histogram[ScaleQuantumToMap(intensity)].red++;
1092 histogram[ScaleQuantumToMap(intensity)].green++;
1093 histogram[ScaleQuantumToMap(intensity)].blue++;
1094 histogram[ScaleQuantumToMap(intensity)].index++;
1095 p++;
1096 }
1097 else
1098 for (x=0; x < (long) image->columns; x++)
1099 {
1100 if ((channel & RedChannel) != 0)
1101 histogram[ScaleQuantumToMap(p->red)].red++;
1102 if ((channel & GreenChannel) != 0)
1103 histogram[ScaleQuantumToMap(p->green)].green++;
1104 if ((channel & BlueChannel) != 0)
1105 histogram[ScaleQuantumToMap(p->blue)].blue++;
1106 if ((channel & OpacityChannel) != 0)
1107 histogram[ScaleQuantumToMap(p->opacity)].opacity++;
1108 if (((channel & IndexChannel) != 0) &&
1109 (image->colorspace == CMYKColorspace))
1110 histogram[ScaleQuantumToMap(indexes[x])].index++;
1111 p++;
1112 }
1113 }
1114 /*
1115 Find the histogram boundaries by locating the black/white levels.
1116 */
1117 black.red=0.0;
1118 white.red=MaxRange(QuantumRange);
1119 if ((channel & RedChannel) != 0)
1120 {
1121 intensity=0.0;
1122 for (i=0; i <= (long) MaxMap; i++)
1123 {
1124 intensity+=histogram[i].red;
1125 if (intensity > black_point)
1126 break;
1127 }
1128 black.red=(MagickRealType) i;
1129 intensity=0.0;
1130 for (i=(long) MaxMap; i != 0; i--)
1131 {
1132 intensity+=histogram[i].red;
1133 if (intensity > ((double) image->columns*image->rows-white_point))
1134 break;
1135 }
1136 white.red=(MagickRealType) i;
1137 }
1138 black.green=0.0;
1139 white.green=MaxRange(QuantumRange);
1140 if ((channel & GreenChannel) != 0)
1141 {
1142 intensity=0.0;
1143 for (i=0; i <= (long) MaxMap; i++)
1144 {
1145 intensity+=histogram[i].green;
1146 if (intensity > black_point)
1147 break;
1148 }
1149 black.green=(MagickRealType) i;
1150 intensity=0.0;
1151 for (i=(long) MaxMap; i != 0; i--)
1152 {
1153 intensity+=histogram[i].green;
1154 if (intensity > ((double) image->columns*image->rows-white_point))
1155 break;
1156 }
1157 white.green=(MagickRealType) i;
1158 }
1159 black.blue=0.0;
1160 white.blue=MaxRange(QuantumRange);
1161 if ((channel & BlueChannel) != 0)
1162 {
1163 intensity=0.0;
1164 for (i=0; i <= (long) MaxMap; i++)
1165 {
1166 intensity+=histogram[i].blue;
1167 if (intensity > black_point)
1168 break;
1169 }
1170 black.blue=(MagickRealType) i;
1171 intensity=0.0;
1172 for (i=(long) MaxMap; i != 0; i--)
1173 {
1174 intensity+=histogram[i].blue;
1175 if (intensity > ((double) image->columns*image->rows-white_point))
1176 break;
1177 }
1178 white.blue=(MagickRealType) i;
1179 }
1180 black.opacity=0.0;
1181 white.opacity=MaxRange(QuantumRange);
1182 if ((channel & OpacityChannel) != 0)
1183 {
1184 intensity=0.0;
1185 for (i=0; i <= (long) MaxMap; i++)
1186 {
1187 intensity+=histogram[i].opacity;
1188 if (intensity > black_point)
1189 break;
1190 }
1191 black.opacity=(MagickRealType) i;
1192 intensity=0.0;
1193 for (i=(long) MaxMap; i != 0; i--)
1194 {
1195 intensity+=histogram[i].opacity;
1196 if (intensity > ((double) image->columns*image->rows-white_point))
1197 break;
1198 }
1199 white.opacity=(MagickRealType) i;
1200 }
1201 black.index=0.0;
1202 white.index=MaxRange(QuantumRange);
1203 if (((channel & IndexChannel) != 0) && (image->colorspace == CMYKColorspace))
1204 {
1205 intensity=0.0;
1206 for (i=0; i <= (long) MaxMap; i++)
1207 {
1208 intensity+=histogram[i].index;
1209 if (intensity > black_point)
1210 break;
1211 }
1212 black.index=(MagickRealType) i;
1213 intensity=0.0;
1214 for (i=(long) MaxMap; i != 0; i--)
1215 {
1216 intensity+=histogram[i].index;
1217 if (intensity > ((double) image->columns*image->rows-white_point))
1218 break;
1219 }
1220 white.index=(MagickRealType) i;
1221 }
1222 histogram=(MagickPixelPacket *) RelinquishMagickMemory(histogram);
1223 /*
1224 Stretch the histogram to create the stretched image mapping.
1225 */
1226 (void) ResetMagickMemory(stretch_map,0,(MaxMap+1)*sizeof(*stretch_map));
cristy629a6c72009-09-13 23:28:22 +00001227#if defined(_OPENMP) && (_OPENMP >= 200203)
cristye0f584d2009-10-11 00:59:14 +00001228 #pragma omp parallel for shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001229#endif
1230 for (i=0; i <= (long) MaxMap; i++)
1231 {
1232 if ((channel & RedChannel) != 0)
1233 {
1234 if (i < (long) black.red)
1235 stretch_map[i].red=0.0;
1236 else
1237 if (i > (long) white.red)
1238 stretch_map[i].red=(MagickRealType) QuantumRange;
1239 else
1240 if (black.red != white.red)
1241 stretch_map[i].red=(MagickRealType) ScaleMapToQuantum(
1242 (MagickRealType) (MaxMap*(i-black.red)/(white.red-black.red)));
1243 }
1244 if ((channel & GreenChannel) != 0)
1245 {
1246 if (i < (long) black.green)
1247 stretch_map[i].green=0.0;
1248 else
1249 if (i > (long) white.green)
1250 stretch_map[i].green=(MagickRealType) QuantumRange;
1251 else
1252 if (black.green != white.green)
1253 stretch_map[i].green=(MagickRealType) ScaleMapToQuantum(
1254 (MagickRealType) (MaxMap*(i-black.green)/(white.green-
1255 black.green)));
1256 }
1257 if ((channel & BlueChannel) != 0)
1258 {
1259 if (i < (long) black.blue)
1260 stretch_map[i].blue=0.0;
1261 else
1262 if (i > (long) white.blue)
1263 stretch_map[i].blue=(MagickRealType) QuantumRange;
1264 else
1265 if (black.blue != white.blue)
1266 stretch_map[i].blue=(MagickRealType) ScaleMapToQuantum(
1267 (MagickRealType) (MaxMap*(i-black.blue)/(white.blue-
1268 black.blue)));
1269 }
1270 if ((channel & OpacityChannel) != 0)
1271 {
1272 if (i < (long) black.opacity)
1273 stretch_map[i].opacity=0.0;
1274 else
1275 if (i > (long) white.opacity)
1276 stretch_map[i].opacity=(MagickRealType) QuantumRange;
1277 else
1278 if (black.opacity != white.opacity)
1279 stretch_map[i].opacity=(MagickRealType) ScaleMapToQuantum(
1280 (MagickRealType) (MaxMap*(i-black.opacity)/(white.opacity-
1281 black.opacity)));
1282 }
1283 if (((channel & IndexChannel) != 0) &&
1284 (image->colorspace == CMYKColorspace))
1285 {
1286 if (i < (long) black.index)
1287 stretch_map[i].index=0.0;
1288 else
1289 if (i > (long) white.index)
1290 stretch_map[i].index=(MagickRealType) QuantumRange;
1291 else
1292 if (black.index != white.index)
1293 stretch_map[i].index=(MagickRealType) ScaleMapToQuantum(
1294 (MagickRealType) (MaxMap*(i-black.index)/(white.index-
1295 black.index)));
1296 }
1297 }
1298 /*
1299 Stretch the image.
1300 */
1301 if (((channel & OpacityChannel) != 0) || (((channel & IndexChannel) != 0) &&
1302 (image->colorspace == CMYKColorspace)))
1303 image->storage_class=DirectClass;
1304 if (image->storage_class == PseudoClass)
1305 {
1306 /*
1307 Stretch colormap.
1308 */
cristy629a6c72009-09-13 23:28:22 +00001309#if defined(_OPENMP) && (_OPENMP >= 200203)
cristye0f584d2009-10-11 00:59:14 +00001310 #pragma omp parallel for shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001311#endif
1312 for (i=0; i < (long) image->colors; i++)
1313 {
1314 if ((channel & RedChannel) != 0)
1315 {
1316 if (black.red != white.red)
1317 image->colormap[i].red=RoundToQuantum(stretch_map[
1318 ScaleQuantumToMap(image->colormap[i].red)].red);
1319 }
1320 if ((channel & GreenChannel) != 0)
1321 {
1322 if (black.green != white.green)
1323 image->colormap[i].green=RoundToQuantum(stretch_map[
1324 ScaleQuantumToMap(image->colormap[i].green)].green);
1325 }
1326 if ((channel & BlueChannel) != 0)
1327 {
1328 if (black.blue != white.blue)
1329 image->colormap[i].blue=RoundToQuantum(stretch_map[
1330 ScaleQuantumToMap(image->colormap[i].blue)].blue);
1331 }
1332 if ((channel & OpacityChannel) != 0)
1333 {
1334 if (black.opacity != white.opacity)
1335 image->colormap[i].opacity=RoundToQuantum(stretch_map[
1336 ScaleQuantumToMap(image->colormap[i].opacity)].opacity);
1337 }
1338 }
1339 }
1340 /*
1341 Stretch image.
1342 */
1343 status=MagickTrue;
1344 progress=0;
cristy629a6c72009-09-13 23:28:22 +00001345#if defined(_OPENMP) && (_OPENMP >= 200203)
cristye0f584d2009-10-11 00:59:14 +00001346 #pragma omp parallel for shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001347#endif
1348 for (y=0; y < (long) image->rows; y++)
1349 {
1350 register IndexPacket
1351 *__restrict indexes;
1352
1353 register long
1354 x;
1355
1356 register PixelPacket
1357 *__restrict q;
1358
1359 if (status == MagickFalse)
1360 continue;
1361 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
1362 if (q == (PixelPacket *) NULL)
1363 {
1364 status=MagickFalse;
1365 continue;
1366 }
1367 indexes=GetCacheViewAuthenticIndexQueue(image_view);
1368 for (x=0; x < (long) image->columns; x++)
1369 {
1370 if ((channel & RedChannel) != 0)
1371 {
1372 if (black.red != white.red)
1373 q->red=RoundToQuantum(stretch_map[ScaleQuantumToMap(q->red)].red);
1374 }
1375 if ((channel & GreenChannel) != 0)
1376 {
1377 if (black.green != white.green)
1378 q->green=RoundToQuantum(stretch_map[ScaleQuantumToMap(
1379 q->green)].green);
1380 }
1381 if ((channel & BlueChannel) != 0)
1382 {
1383 if (black.blue != white.blue)
1384 q->blue=RoundToQuantum(stretch_map[ScaleQuantumToMap(
1385 q->blue)].blue);
1386 }
1387 if ((channel & OpacityChannel) != 0)
1388 {
1389 if (black.opacity != white.opacity)
1390 q->opacity=RoundToQuantum(stretch_map[ScaleQuantumToMap(
1391 q->opacity)].opacity);
1392 }
1393 if (((channel & IndexChannel) != 0) &&
1394 (image->colorspace == CMYKColorspace))
1395 {
1396 if (black.index != white.index)
1397 indexes[x]=(IndexPacket) RoundToQuantum(stretch_map[
1398 ScaleQuantumToMap(indexes[x])].index);
1399 }
1400 q++;
1401 }
1402 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
1403 status=MagickFalse;
1404 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1405 {
1406 MagickBooleanType
1407 proceed;
1408
cristy629a6c72009-09-13 23:28:22 +00001409#if defined(_OPENMP) && (_OPENMP >= 200203)
cristy3ed852e2009-09-05 21:47:34 +00001410 #pragma omp critical (MagickCore_ContrastStretchImageChannel)
1411#endif
1412 proceed=SetImageProgress(image,ContrastStretchImageTag,progress++,
1413 image->rows);
1414 if (proceed == MagickFalse)
1415 status=MagickFalse;
1416 }
1417 }
1418 image_view=DestroyCacheView(image_view);
1419 stretch_map=(MagickPixelPacket *) RelinquishMagickMemory(stretch_map);
1420 return(status);
1421}
1422
1423/*
1424%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1425% %
1426% %
1427% %
1428% E n h a n c e I m a g e %
1429% %
1430% %
1431% %
1432%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1433%
1434% EnhanceImage() applies a digital filter that improves the quality of a
1435% noisy image.
1436%
1437% The format of the EnhanceImage method is:
1438%
1439% Image *EnhanceImage(const Image *image,ExceptionInfo *exception)
1440%
1441% A description of each parameter follows:
1442%
1443% o image: the image.
1444%
1445% o exception: return any errors or warnings in this structure.
1446%
1447*/
1448MagickExport Image *EnhanceImage(const Image *image,ExceptionInfo *exception)
1449{
1450#define Enhance(weight) \
1451 mean=((MagickRealType) r->red+pixel.red)/2; \
1452 distance=(MagickRealType) r->red-(MagickRealType) pixel.red; \
1453 distance_squared=QuantumScale*(2.0*((MagickRealType) QuantumRange+1.0)+ \
1454 mean)*distance*distance; \
1455 mean=((MagickRealType) r->green+pixel.green)/2; \
1456 distance=(MagickRealType) r->green-(MagickRealType) pixel.green; \
1457 distance_squared+=4.0*distance*distance; \
1458 mean=((MagickRealType) r->blue+pixel.blue)/2; \
1459 distance=(MagickRealType) r->blue-(MagickRealType) pixel.blue; \
1460 distance_squared+=QuantumScale*(3.0*((MagickRealType) \
1461 QuantumRange+1.0)-1.0-mean)*distance*distance; \
1462 mean=((MagickRealType) r->opacity+pixel.opacity)/2; \
1463 distance=(MagickRealType) r->opacity-(MagickRealType) pixel.opacity; \
1464 distance_squared+=QuantumScale*(3.0*((MagickRealType) \
1465 QuantumRange+1.0)-1.0-mean)*distance*distance; \
1466 if (distance_squared < ((MagickRealType) QuantumRange*(MagickRealType) \
1467 QuantumRange/25.0f)) \
1468 { \
1469 aggregate.red+=(weight)*r->red; \
1470 aggregate.green+=(weight)*r->green; \
1471 aggregate.blue+=(weight)*r->blue; \
1472 aggregate.opacity+=(weight)*r->opacity; \
1473 total_weight+=(weight); \
1474 } \
1475 r++;
1476#define EnhanceImageTag "Enhance/Image"
1477
1478 Image
1479 *enhance_image;
1480
1481 long
1482 progress,
1483 y;
1484
1485 MagickBooleanType
1486 status;
1487
1488 MagickPixelPacket
1489 zero;
1490
1491 CacheView
1492 *enhance_view,
1493 *image_view;
1494
1495 /*
1496 Initialize enhanced image attributes.
1497 */
1498 assert(image != (const Image *) NULL);
1499 assert(image->signature == MagickSignature);
1500 if (image->debug != MagickFalse)
1501 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1502 assert(exception != (ExceptionInfo *) NULL);
1503 assert(exception->signature == MagickSignature);
1504 if ((image->columns < 5) || (image->rows < 5))
1505 return((Image *) NULL);
1506 enhance_image=CloneImage(image,image->columns,image->rows,MagickTrue,
1507 exception);
1508 if (enhance_image == (Image *) NULL)
1509 return((Image *) NULL);
1510 if (SetImageStorageClass(enhance_image,DirectClass) == MagickFalse)
1511 {
1512 InheritException(exception,&enhance_image->exception);
1513 enhance_image=DestroyImage(enhance_image);
1514 return((Image *) NULL);
1515 }
1516 /*
1517 Enhance image.
1518 */
1519 status=MagickTrue;
1520 progress=0;
1521 (void) ResetMagickMemory(&zero,0,sizeof(zero));
1522 image_view=AcquireCacheView(image);
1523 enhance_view=AcquireCacheView(enhance_image);
cristy629a6c72009-09-13 23:28:22 +00001524#if defined(_OPENMP) && (_OPENMP >= 200203)
cristye0f584d2009-10-11 00:59:14 +00001525 #pragma omp parallel for shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001526#endif
1527 for (y=0; y < (long) image->rows; y++)
1528 {
1529 register const PixelPacket
1530 *__restrict p;
1531
1532 register long
1533 x;
1534
1535 register PixelPacket
1536 *__restrict q;
1537
1538 /*
1539 Read another scan line.
1540 */
1541 if (status == MagickFalse)
1542 continue;
1543 p=GetCacheViewVirtualPixels(image_view,-2,y-2,image->columns+4,5,exception);
1544 q=QueueCacheViewAuthenticPixels(enhance_view,0,y,enhance_image->columns,1,
1545 exception);
1546 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
1547 {
1548 status=MagickFalse;
1549 continue;
1550 }
1551 for (x=0; x < (long) image->columns; x++)
1552 {
1553 MagickPixelPacket
1554 aggregate;
1555
1556 MagickRealType
1557 distance,
1558 distance_squared,
1559 mean,
1560 total_weight;
1561
1562 PixelPacket
1563 pixel;
1564
1565 register const PixelPacket
1566 *__restrict r;
1567
1568 /*
1569 Compute weighted average of target pixel color components.
1570 */
1571 aggregate=zero;
1572 total_weight=0.0;
1573 r=p+2*(image->columns+4)+2;
1574 pixel=(*r);
1575 r=p;
1576 Enhance(5.0); Enhance(8.0); Enhance(10.0); Enhance(8.0); Enhance(5.0);
1577 r=p+(image->columns+4);
1578 Enhance(8.0); Enhance(20.0); Enhance(40.0); Enhance(20.0); Enhance(8.0);
1579 r=p+2*(image->columns+4);
1580 Enhance(10.0); Enhance(40.0); Enhance(80.0); Enhance(40.0); Enhance(10.0);
1581 r=p+3*(image->columns+4);
1582 Enhance(8.0); Enhance(20.0); Enhance(40.0); Enhance(20.0); Enhance(8.0);
1583 r=p+4*(image->columns+4);
1584 Enhance(5.0); Enhance(8.0); Enhance(10.0); Enhance(8.0); Enhance(5.0);
1585 q->red=(Quantum) ((aggregate.red+(total_weight/2)-1)/total_weight);
1586 q->green=(Quantum) ((aggregate.green+(total_weight/2)-1)/total_weight);
1587 q->blue=(Quantum) ((aggregate.blue+(total_weight/2)-1)/total_weight);
1588 q->opacity=(Quantum) ((aggregate.opacity+(total_weight/2)-1)/
1589 total_weight);
1590 p++;
1591 q++;
1592 }
1593 if (SyncCacheViewAuthenticPixels(enhance_view,exception) == MagickFalse)
1594 status=MagickFalse;
1595 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1596 {
1597 MagickBooleanType
1598 proceed;
1599
cristy629a6c72009-09-13 23:28:22 +00001600#if defined(_OPENMP) && (_OPENMP >= 200203)
cristy3ed852e2009-09-05 21:47:34 +00001601 #pragma omp critical (MagickCore_EnhanceImage)
1602#endif
1603 proceed=SetImageProgress(image,EnhanceImageTag,progress++,image->rows);
1604 if (proceed == MagickFalse)
1605 status=MagickFalse;
1606 }
1607 }
1608 enhance_view=DestroyCacheView(enhance_view);
1609 image_view=DestroyCacheView(image_view);
1610 return(enhance_image);
1611}
1612
1613/*
1614%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1615% %
1616% %
1617% %
1618% E q u a l i z e I m a g e %
1619% %
1620% %
1621% %
1622%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1623%
1624% EqualizeImage() applies a histogram equalization to the image.
1625%
1626% The format of the EqualizeImage method is:
1627%
1628% MagickBooleanType EqualizeImage(Image *image)
1629% MagickBooleanType EqualizeImageChannel(Image *image,
1630% const ChannelType channel)
1631%
1632% A description of each parameter follows:
1633%
1634% o image: the image.
1635%
1636% o channel: the channel.
1637%
1638*/
1639
1640MagickExport MagickBooleanType EqualizeImage(Image *image)
1641{
1642 return(EqualizeImageChannel(image,DefaultChannels));
1643}
1644
1645MagickExport MagickBooleanType EqualizeImageChannel(Image *image,
1646 const ChannelType channel)
1647{
1648#define EqualizeImageTag "Equalize/Image"
1649
1650 ExceptionInfo
1651 *exception;
1652
1653 long
1654 progress,
1655 y;
1656
1657 MagickBooleanType
1658 status;
1659
1660 MagickPixelPacket
1661 black,
1662 *equalize_map,
1663 *histogram,
1664 intensity,
1665 *map,
1666 white;
1667
1668 register long
1669 i;
1670
1671 CacheView
1672 *image_view;
1673
1674 /*
1675 Allocate and initialize histogram arrays.
1676 */
1677 assert(image != (Image *) NULL);
1678 assert(image->signature == MagickSignature);
1679 if (image->debug != MagickFalse)
1680 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1681 equalize_map=(MagickPixelPacket *) AcquireQuantumMemory(MaxMap+1UL,
1682 sizeof(*equalize_map));
1683 histogram=(MagickPixelPacket *) AcquireQuantumMemory(MaxMap+1UL,
1684 sizeof(*histogram));
1685 map=(MagickPixelPacket *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*map));
1686 if ((equalize_map == (MagickPixelPacket *) NULL) ||
1687 (histogram == (MagickPixelPacket *) NULL) ||
1688 (map == (MagickPixelPacket *) NULL))
1689 {
1690 if (map != (MagickPixelPacket *) NULL)
1691 map=(MagickPixelPacket *) RelinquishMagickMemory(map);
1692 if (histogram != (MagickPixelPacket *) NULL)
1693 histogram=(MagickPixelPacket *) RelinquishMagickMemory(histogram);
1694 if (equalize_map != (MagickPixelPacket *) NULL)
1695 equalize_map=(MagickPixelPacket *) RelinquishMagickMemory(equalize_map);
1696 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
1697 image->filename);
1698 }
1699 /*
1700 Form histogram.
1701 */
1702 (void) ResetMagickMemory(histogram,0,(MaxMap+1)*sizeof(*histogram));
1703 exception=(&image->exception);
1704 for (y=0; y < (long) image->rows; y++)
1705 {
1706 register const IndexPacket
1707 *__restrict indexes;
1708
1709 register const PixelPacket
1710 *__restrict p;
1711
1712 register long
1713 x;
1714
1715 p=GetVirtualPixels(image,0,y,image->columns,1,exception);
1716 if (p == (const PixelPacket *) NULL)
1717 break;
1718 indexes=GetVirtualIndexQueue(image);
1719 for (x=0; x < (long) image->columns; x++)
1720 {
1721 if ((channel & RedChannel) != 0)
1722 histogram[ScaleQuantumToMap(p->red)].red++;
1723 if ((channel & GreenChannel) != 0)
1724 histogram[ScaleQuantumToMap(p->green)].green++;
1725 if ((channel & BlueChannel) != 0)
1726 histogram[ScaleQuantumToMap(p->blue)].blue++;
1727 if ((channel & OpacityChannel) != 0)
1728 histogram[ScaleQuantumToMap(p->opacity)].opacity++;
1729 if (((channel & IndexChannel) != 0) &&
1730 (image->colorspace == CMYKColorspace))
1731 histogram[ScaleQuantumToMap(indexes[x])].index++;
1732 p++;
1733 }
1734 }
1735 /*
1736 Integrate the histogram to get the equalization map.
1737 */
1738 (void) ResetMagickMemory(&intensity,0,sizeof(intensity));
1739 for (i=0; i <= (long) MaxMap; i++)
1740 {
1741 if ((channel & RedChannel) != 0)
1742 intensity.red+=histogram[i].red;
1743 if ((channel & GreenChannel) != 0)
1744 intensity.green+=histogram[i].green;
1745 if ((channel & BlueChannel) != 0)
1746 intensity.blue+=histogram[i].blue;
1747 if ((channel & OpacityChannel) != 0)
1748 intensity.opacity+=histogram[i].opacity;
1749 if (((channel & IndexChannel) != 0) &&
1750 (image->colorspace == CMYKColorspace))
1751 intensity.index+=histogram[i].index;
1752 map[i]=intensity;
1753 }
1754 black=map[0];
1755 white=map[(int) MaxMap];
1756 (void) ResetMagickMemory(equalize_map,0,(MaxMap+1)*sizeof(*equalize_map));
cristy629a6c72009-09-13 23:28:22 +00001757#if defined(_OPENMP) && (_OPENMP >= 200203)
cristye0f584d2009-10-11 00:59:14 +00001758 #pragma omp parallel for shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001759#endif
1760 for (i=0; i <= (long) MaxMap; i++)
1761 {
1762 if (((channel & RedChannel) != 0) && (white.red != black.red))
1763 equalize_map[i].red=(MagickRealType) ScaleMapToQuantum((MagickRealType)
1764 ((MaxMap*(map[i].red-black.red))/(white.red-black.red)));
1765 if (((channel & GreenChannel) != 0) && (white.green != black.green))
1766 equalize_map[i].green=(MagickRealType) ScaleMapToQuantum((MagickRealType)
1767 ((MaxMap*(map[i].green-black.green))/(white.green-black.green)));
1768 if (((channel & BlueChannel) != 0) && (white.blue != black.blue))
1769 equalize_map[i].blue=(MagickRealType) ScaleMapToQuantum((MagickRealType)
1770 ((MaxMap*(map[i].blue-black.blue))/(white.blue-black.blue)));
1771 if (((channel & OpacityChannel) != 0) && (white.opacity != black.opacity))
1772 equalize_map[i].opacity=(MagickRealType) ScaleMapToQuantum(
1773 (MagickRealType) ((MaxMap*(map[i].opacity-black.opacity))/
1774 (white.opacity-black.opacity)));
1775 if ((((channel & IndexChannel) != 0) &&
1776 (image->colorspace == CMYKColorspace)) &&
1777 (white.index != black.index))
1778 equalize_map[i].index=(MagickRealType) ScaleMapToQuantum((MagickRealType)
1779 ((MaxMap*(map[i].index-black.index))/(white.index-black.index)));
1780 }
1781 histogram=(MagickPixelPacket *) RelinquishMagickMemory(histogram);
1782 map=(MagickPixelPacket *) RelinquishMagickMemory(map);
1783 if (image->storage_class == PseudoClass)
1784 {
1785 /*
1786 Equalize colormap.
1787 */
cristy629a6c72009-09-13 23:28:22 +00001788#if defined(_OPENMP) && (_OPENMP >= 200203)
cristye0f584d2009-10-11 00:59:14 +00001789 #pragma omp parallel for shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001790#endif
1791 for (i=0; i < (long) image->colors; i++)
1792 {
1793 if (((channel & RedChannel) != 0) && (white.red != black.red))
1794 image->colormap[i].red=RoundToQuantum(equalize_map[
1795 ScaleQuantumToMap(image->colormap[i].red)].red);
1796 if (((channel & GreenChannel) != 0) && (white.green != black.green))
1797 image->colormap[i].green=RoundToQuantum(equalize_map[
1798 ScaleQuantumToMap(image->colormap[i].green)].green);
1799 if (((channel & BlueChannel) != 0) && (white.blue != black.blue))
1800 image->colormap[i].blue=RoundToQuantum(equalize_map[
1801 ScaleQuantumToMap(image->colormap[i].blue)].blue);
1802 if (((channel & OpacityChannel) != 0) &&
1803 (white.opacity != black.opacity))
1804 image->colormap[i].opacity=RoundToQuantum(equalize_map[
1805 ScaleQuantumToMap(image->colormap[i].opacity)].opacity);
1806 }
1807 }
1808 /*
1809 Equalize image.
1810 */
1811 status=MagickTrue;
1812 progress=0;
1813 exception=(&image->exception);
1814 image_view=AcquireCacheView(image);
cristy629a6c72009-09-13 23:28:22 +00001815#if defined(_OPENMP) && (_OPENMP >= 200203)
cristye0f584d2009-10-11 00:59:14 +00001816 #pragma omp parallel for shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001817#endif
1818 for (y=0; y < (long) image->rows; y++)
1819 {
1820 register IndexPacket
1821 *__restrict indexes;
1822
1823 register long
1824 x;
1825
1826 register PixelPacket
1827 *__restrict q;
1828
1829 if (status == MagickFalse)
1830 continue;
1831 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
1832 if (q == (PixelPacket *) NULL)
1833 {
1834 status=MagickFalse;
1835 continue;
1836 }
1837 indexes=GetCacheViewAuthenticIndexQueue(image_view);
1838 for (x=0; x < (long) image->columns; x++)
1839 {
1840 if (((channel & RedChannel) != 0) && (white.red != black.red))
1841 q->red=RoundToQuantum(equalize_map[ScaleQuantumToMap(q->red)].red);
1842 if (((channel & GreenChannel) != 0) && (white.green != black.green))
1843 q->green=RoundToQuantum(equalize_map[ScaleQuantumToMap(
1844 q->green)].green);
1845 if (((channel & BlueChannel) != 0) && (white.blue != black.blue))
1846 q->blue=RoundToQuantum(equalize_map[ScaleQuantumToMap(q->blue)].blue);
1847 if (((channel & OpacityChannel) != 0) && (white.opacity != black.opacity))
1848 q->opacity=RoundToQuantum(equalize_map[ScaleQuantumToMap(
1849 q->opacity)].opacity);
1850 if ((((channel & IndexChannel) != 0) &&
1851 (image->colorspace == CMYKColorspace)) &&
1852 (white.index != black.index))
1853 indexes[x]=RoundToQuantum(equalize_map[ScaleQuantumToMap(
1854 indexes[x])].index);
1855 q++;
1856 }
1857 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
1858 status=MagickFalse;
1859 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1860 {
1861 MagickBooleanType
1862 proceed;
1863
cristy629a6c72009-09-13 23:28:22 +00001864#if defined(_OPENMP) && (_OPENMP >= 200203)
cristy3ed852e2009-09-05 21:47:34 +00001865 #pragma omp critical (MagickCore_EqualizeImageChannel)
1866#endif
1867 proceed=SetImageProgress(image,EqualizeImageTag,progress++,image->rows);
1868 if (proceed == MagickFalse)
1869 status=MagickFalse;
1870 }
1871 }
1872 image_view=DestroyCacheView(image_view);
1873 equalize_map=(MagickPixelPacket *) RelinquishMagickMemory(equalize_map);
1874 return(status);
1875}
1876
1877/*
1878%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1879% %
1880% %
1881% %
1882% G a m m a I m a g e %
1883% %
1884% %
1885% %
1886%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1887%
1888% GammaImage() gamma-corrects a particular image channel. The same
1889% image viewed on different devices will have perceptual differences in the
1890% way the image's intensities are represented on the screen. Specify
1891% individual gamma levels for the red, green, and blue channels, or adjust
1892% all three with the gamma parameter. Values typically range from 0.8 to 2.3.
1893%
1894% You can also reduce the influence of a particular channel with a gamma
1895% value of 0.
1896%
1897% The format of the GammaImage method is:
1898%
1899% MagickBooleanType GammaImage(Image *image,const double gamma)
1900% MagickBooleanType GammaImageChannel(Image *image,
1901% const ChannelType channel,const double gamma)
1902%
1903% A description of each parameter follows:
1904%
1905% o image: the image.
1906%
1907% o channel: the channel.
1908%
1909% o gamma: the image gamma.
1910%
1911*/
1912MagickExport MagickBooleanType GammaImage(Image *image,const char *level)
1913{
1914 GeometryInfo
1915 geometry_info;
1916
1917 MagickPixelPacket
1918 gamma;
1919
1920 MagickStatusType
1921 flags,
1922 status;
1923
1924 assert(image != (Image *) NULL);
1925 assert(image->signature == MagickSignature);
1926 if (image->debug != MagickFalse)
1927 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1928 if (level == (char *) NULL)
1929 return(MagickFalse);
1930 flags=ParseGeometry(level,&geometry_info);
1931 gamma.red=geometry_info.rho;
1932 gamma.green=geometry_info.sigma;
1933 if ((flags & SigmaValue) == 0)
1934 gamma.green=gamma.red;
1935 gamma.blue=geometry_info.xi;
1936 if ((flags & XiValue) == 0)
1937 gamma.blue=gamma.red;
1938 if ((gamma.red == 1.0) && (gamma.green == 1.0) && (gamma.blue == 1.0))
1939 return(MagickTrue);
1940 if ((gamma.red == gamma.green) && (gamma.green == gamma.blue))
1941 status=GammaImageChannel(image,(const ChannelType) (RedChannel |
1942 GreenChannel | BlueChannel),(double) gamma.red);
1943 else
1944 {
1945 status=GammaImageChannel(image,RedChannel,(double) gamma.red);
1946 status|=GammaImageChannel(image,GreenChannel,(double) gamma.green);
1947 status|=GammaImageChannel(image,BlueChannel,(double) gamma.blue);
1948 }
1949 return(status != 0 ? MagickTrue : MagickFalse);
1950}
1951
1952MagickExport MagickBooleanType GammaImageChannel(Image *image,
1953 const ChannelType channel,const double gamma)
1954{
1955#define GammaCorrectImageTag "GammaCorrect/Image"
1956
1957 ExceptionInfo
1958 *exception;
1959
1960 long
1961 progress,
1962 y;
1963
1964 MagickBooleanType
1965 status;
1966
1967 Quantum
1968 *gamma_map;
1969
1970 register long
1971 i;
1972
1973 CacheView
1974 *image_view;
1975
1976 /*
1977 Allocate and initialize gamma maps.
1978 */
1979 assert(image != (Image *) NULL);
1980 assert(image->signature == MagickSignature);
1981 if (image->debug != MagickFalse)
1982 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1983 if (gamma == 1.0)
1984 return(MagickTrue);
1985 gamma_map=(Quantum *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*gamma_map));
1986 if (gamma_map == (Quantum *) NULL)
1987 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
1988 image->filename);
1989 (void) ResetMagickMemory(gamma_map,0,(MaxMap+1)*sizeof(*gamma_map));
1990 if (gamma != 0.0)
cristy629a6c72009-09-13 23:28:22 +00001991#if defined(_OPENMP) && (_OPENMP >= 200203)
cristye0f584d2009-10-11 00:59:14 +00001992 #pragma omp parallel for
cristy3ed852e2009-09-05 21:47:34 +00001993#endif
1994 for (i=0; i <= (long) MaxMap; i++)
1995 gamma_map[i]=RoundToQuantum((MagickRealType) ScaleMapToQuantum((
1996 MagickRealType) (MaxMap*pow((double) i/MaxMap,1.0/gamma))));
1997 if (image->storage_class == PseudoClass)
1998 {
1999 /*
2000 Gamma-correct colormap.
2001 */
cristy629a6c72009-09-13 23:28:22 +00002002#if defined(_OPENMP) && (_OPENMP >= 200203)
cristye0f584d2009-10-11 00:59:14 +00002003 #pragma omp parallel for shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002004#endif
2005 for (i=0; i < (long) image->colors; i++)
2006 {
2007 if ((channel & RedChannel) != 0)
2008 image->colormap[i].red=gamma_map[
2009 ScaleQuantumToMap(image->colormap[i].red)];
2010 if ((channel & GreenChannel) != 0)
2011 image->colormap[i].green=gamma_map[
2012 ScaleQuantumToMap(image->colormap[i].green)];
2013 if ((channel & BlueChannel) != 0)
2014 image->colormap[i].blue=gamma_map[
2015 ScaleQuantumToMap(image->colormap[i].blue)];
2016 if ((channel & OpacityChannel) != 0)
2017 {
2018 if (image->matte == MagickFalse)
2019 image->colormap[i].opacity=gamma_map[
2020 ScaleQuantumToMap(image->colormap[i].opacity)];
2021 else
2022 image->colormap[i].opacity=(Quantum) QuantumRange-
2023 gamma_map[ScaleQuantumToMap((Quantum) (QuantumRange-
2024 image->colormap[i].opacity))];
2025 }
2026 }
2027 }
2028 /*
2029 Gamma-correct image.
2030 */
2031 status=MagickTrue;
2032 progress=0;
2033 exception=(&image->exception);
2034 image_view=AcquireCacheView(image);
cristy629a6c72009-09-13 23:28:22 +00002035#if defined(_OPENMP) && (_OPENMP >= 200203)
cristye0f584d2009-10-11 00:59:14 +00002036 #pragma omp parallel for shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002037#endif
2038 for (y=0; y < (long) image->rows; y++)
2039 {
2040 register IndexPacket
2041 *__restrict indexes;
2042
2043 register long
2044 x;
2045
2046 register PixelPacket
2047 *__restrict q;
2048
2049 if (status == MagickFalse)
2050 continue;
2051 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2052 if (q == (PixelPacket *) NULL)
2053 {
2054 status=MagickFalse;
2055 continue;
2056 }
2057 indexes=GetCacheViewAuthenticIndexQueue(image_view);
2058 for (x=0; x < (long) image->columns; x++)
2059 {
cristy6cbd7f52009-10-17 16:06:51 +00002060 if (channel == DefaultChannels)
cristy3ed852e2009-09-05 21:47:34 +00002061 {
cristy6cbd7f52009-10-17 16:06:51 +00002062 q->red=gamma_map[ScaleQuantumToMap(q->red)];
2063 q->green=gamma_map[ScaleQuantumToMap(q->green)];
2064 q->blue=gamma_map[ScaleQuantumToMap(q->blue)];
2065 }
2066 else
2067 {
2068 if ((channel & RedChannel) != 0)
2069 q->red=gamma_map[ScaleQuantumToMap(q->red)];
2070 if ((channel & GreenChannel) != 0)
2071 q->green=gamma_map[ScaleQuantumToMap(q->green)];
2072 if ((channel & BlueChannel) != 0)
2073 q->blue=gamma_map[ScaleQuantumToMap(q->blue)];
2074 if ((channel & OpacityChannel) != 0)
2075 {
2076 if (image->matte == MagickFalse)
2077 q->opacity=gamma_map[ScaleQuantumToMap(q->opacity)];
2078 else
2079 q->opacity=(Quantum) QuantumRange-gamma_map[
2080 ScaleQuantumToMap((Quantum) (QuantumRange-q->opacity))];
2081 }
cristy3ed852e2009-09-05 21:47:34 +00002082 }
2083 q++;
2084 }
2085 if (((channel & IndexChannel) != 0) &&
2086 (image->colorspace == CMYKColorspace))
2087 for (x=0; x < (long) image->columns; x++)
2088 indexes[x]=gamma_map[ScaleQuantumToMap(indexes[x])];
2089 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2090 status=MagickFalse;
2091 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2092 {
2093 MagickBooleanType
2094 proceed;
2095
cristy629a6c72009-09-13 23:28:22 +00002096#if defined(_OPENMP) && (_OPENMP >= 200203)
cristy3ed852e2009-09-05 21:47:34 +00002097 #pragma omp critical (MagickCore_GammaImageChannel)
2098#endif
2099 proceed=SetImageProgress(image,GammaCorrectImageTag,progress++,
2100 image->rows);
2101 if (proceed == MagickFalse)
2102 status=MagickFalse;
2103 }
2104 }
2105 image_view=DestroyCacheView(image_view);
2106 gamma_map=(Quantum *) RelinquishMagickMemory(gamma_map);
2107 if (image->gamma != 0.0)
2108 image->gamma*=gamma;
2109 return(status);
2110}
2111
2112/*
2113%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2114% %
2115% %
2116% %
2117% H a l d C l u t I m a g e %
2118% %
2119% %
2120% %
2121%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2122%
2123% HaldClutImage() applies a Hald color lookup table to the image. A Hald
2124% color lookup table is a 3-dimensional color cube mapped to 2 dimensions.
2125% Create it with the HALD coder. You can apply any color transformation to
2126% the Hald image and then use this method to apply the transform to the
2127% image.
2128%
2129% The format of the HaldClutImage method is:
2130%
2131% MagickBooleanType HaldClutImage(Image *image,Image *hald_image)
2132% MagickBooleanType HaldClutImageChannel(Image *image,
2133% const ChannelType channel,Image *hald_image)
2134%
2135% A description of each parameter follows:
2136%
2137% o image: the image, which is replaced by indexed CLUT values
2138%
2139% o hald_image: the color lookup table image for replacement color values.
2140%
2141% o channel: the channel.
2142%
2143*/
2144
2145static inline size_t MagickMin(const size_t x,const size_t y)
2146{
2147 if (x < y)
2148 return(x);
2149 return(y);
2150}
2151
2152MagickExport MagickBooleanType HaldClutImage(Image *image,
2153 const Image *hald_image)
2154{
2155 return(HaldClutImageChannel(image,DefaultChannels,hald_image));
2156}
2157
2158MagickExport MagickBooleanType HaldClutImageChannel(Image *image,
2159 const ChannelType channel,const Image *hald_image)
2160{
2161#define HaldClutImageTag "Clut/Image"
2162
2163 typedef struct _HaldInfo
2164 {
2165 MagickRealType
2166 x,
2167 y,
2168 z;
2169 } HaldInfo;
2170
2171 double
2172 width;
2173
2174 ExceptionInfo
2175 *exception;
2176
2177 long
2178 progress,
2179 y;
2180
2181 MagickBooleanType
2182 status;
2183
2184 MagickPixelPacket
2185 zero;
2186
2187 ResampleFilter
2188 **resample_filter;
2189
2190 size_t
2191 cube_size,
2192 length,
2193 level;
2194
2195 CacheView
2196 *image_view;
2197
2198 assert(image != (Image *) NULL);
2199 assert(image->signature == MagickSignature);
2200 if (image->debug != MagickFalse)
2201 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2202 assert(hald_image != (Image *) NULL);
2203 assert(hald_image->signature == MagickSignature);
2204 if (SetImageStorageClass(image,DirectClass) == MagickFalse)
2205 return(MagickFalse);
2206 if (image->matte == MagickFalse)
2207 (void) SetImageAlphaChannel(image,OpaqueAlphaChannel);
2208 /*
2209 Hald clut image.
2210 */
2211 status=MagickTrue;
2212 progress=0;
2213 length=MagickMin(hald_image->columns,hald_image->rows);
2214 for (level=2; (level*level*level) < length; level++) ;
2215 level*=level;
2216 cube_size=level*level;
2217 width=(double) hald_image->columns;
2218 GetMagickPixelPacket(hald_image,&zero);
2219 exception=(&image->exception);
2220 resample_filter=AcquireResampleFilterThreadSet(hald_image,MagickTrue,
2221 exception);
2222 image_view=AcquireCacheView(image);
cristy629a6c72009-09-13 23:28:22 +00002223#if defined(_OPENMP) && (_OPENMP >= 200203)
cristye0f584d2009-10-11 00:59:14 +00002224 #pragma omp parallel for shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002225#endif
2226 for (y=0; y < (long) image->rows; y++)
2227 {
2228 double
2229 offset;
2230
2231 HaldInfo
2232 point;
2233
2234 MagickPixelPacket
2235 pixel,
2236 pixel1,
2237 pixel2,
2238 pixel3,
2239 pixel4;
2240
2241 register IndexPacket
2242 *__restrict indexes;
2243
2244 register long
2245 id,
2246 x;
2247
2248 register PixelPacket
2249 *__restrict q;
2250
2251 if (status == MagickFalse)
2252 continue;
2253 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2254 if (q == (PixelPacket *) NULL)
2255 {
2256 status=MagickFalse;
2257 continue;
2258 }
2259 indexes=GetCacheViewAuthenticIndexQueue(image_view);
2260 pixel=zero;
2261 pixel1=zero;
2262 pixel2=zero;
2263 pixel3=zero;
2264 pixel4=zero;
2265 id=GetOpenMPThreadId();
2266 for (x=0; x < (long) image->columns; x++)
2267 {
2268 point.x=QuantumScale*(level-1.0)*q->red;
2269 point.y=QuantumScale*(level-1.0)*q->green;
2270 point.z=QuantumScale*(level-1.0)*q->blue;
2271 offset=point.x+level*floor(point.y)+cube_size*floor(point.z);
2272 point.x-=floor(point.x);
2273 point.y-=floor(point.y);
2274 point.z-=floor(point.z);
2275 (void) ResamplePixelColor(resample_filter[id],fmod(offset,width),
2276 floor(offset/width),&pixel1);
2277 (void) ResamplePixelColor(resample_filter[id],fmod(offset+level,width),
2278 floor((offset+level)/width),&pixel2);
2279 MagickPixelCompositeAreaBlend(&pixel1,pixel1.opacity,&pixel2,
2280 pixel2.opacity,point.y,&pixel3);
2281 offset+=cube_size;
2282 (void) ResamplePixelColor(resample_filter[id],fmod(offset,width),
2283 floor(offset/width),&pixel1);
2284 (void) ResamplePixelColor(resample_filter[id],fmod(offset+level,width),
2285 floor((offset+level)/width),&pixel2);
2286 MagickPixelCompositeAreaBlend(&pixel1,pixel1.opacity,&pixel2,
2287 pixel2.opacity,point.y,&pixel4);
2288 MagickPixelCompositeAreaBlend(&pixel3,pixel3.opacity,&pixel4,
2289 pixel4.opacity,point.z,&pixel);
2290 if ((channel & RedChannel) != 0)
2291 q->red=RoundToQuantum(pixel.red);
2292 if ((channel & GreenChannel) != 0)
2293 q->green=RoundToQuantum(pixel.green);
2294 if ((channel & BlueChannel) != 0)
2295 q->blue=RoundToQuantum(pixel.blue);
2296 if (((channel & OpacityChannel) != 0) && (image->matte != MagickFalse))
2297 q->opacity=RoundToQuantum(pixel.opacity);
2298 if (((channel & IndexChannel) != 0) &&
2299 (image->colorspace == CMYKColorspace))
2300 indexes[x]=RoundToQuantum(pixel.index);
2301 q++;
2302 }
2303 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2304 status=MagickFalse;
2305 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2306 {
2307 MagickBooleanType
2308 proceed;
2309
cristy629a6c72009-09-13 23:28:22 +00002310#if defined(_OPENMP) && (_OPENMP >= 200203)
cristy3ed852e2009-09-05 21:47:34 +00002311 #pragma omp critical (MagickCore_HaldClutImageChannel)
2312#endif
2313 proceed=SetImageProgress(image,HaldClutImageTag,progress++,image->rows);
2314 if (proceed == MagickFalse)
2315 status=MagickFalse;
2316 }
2317 }
2318 image_view=DestroyCacheView(image_view);
2319 resample_filter=DestroyResampleFilterThreadSet(resample_filter);
2320 return(status);
2321}
2322
2323/*
2324%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2325% %
2326% %
2327% %
2328% L e v e l I m a g e %
2329% %
2330% %
2331% %
2332%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2333%
2334% LevelImage() adjusts the levels of a particular image channel by
2335% scaling the colors falling between specified white and black points to
2336% the full available quantum range.
2337%
2338% The parameters provided represent the black, and white points. The black
2339% point specifies the darkest color in the image. Colors darker than the
2340% black point are set to zero. White point specifies the lightest color in
2341% the image. Colors brighter than the white point are set to the maximum
2342% quantum value.
2343%
2344% If a '!' flag is given, map black and white colors to the given levels
2345% rather than mapping those levels to black and white. See
2346% LevelizeImageChannel() and LevelizeImageChannel(), below.
2347%
2348% Gamma specifies a gamma correction to apply to the image.
2349%
2350% The format of the LevelImage method is:
2351%
2352% MagickBooleanType LevelImage(Image *image,const char *levels)
2353%
2354% A description of each parameter follows:
2355%
2356% o image: the image.
2357%
2358% o levels: Specify the levels where the black and white points have the
2359% range of 0-QuantumRange, and gamma has the range 0-10 (e.g. 10x90%+2).
2360% A '!' flag inverts the re-mapping.
2361%
2362*/
2363
2364MagickExport MagickBooleanType LevelImage(Image *image,const char *levels)
2365{
2366 double
2367 black_point,
2368 gamma,
2369 white_point;
2370
2371 GeometryInfo
2372 geometry_info;
2373
2374 MagickBooleanType
2375 status;
2376
2377 MagickStatusType
2378 flags;
2379
2380 /*
2381 Parse levels.
2382 */
2383 if (levels == (char *) NULL)
2384 return(MagickFalse);
2385 flags=ParseGeometry(levels,&geometry_info);
2386 black_point=geometry_info.rho;
2387 white_point=(double) QuantumRange;
2388 if ((flags & SigmaValue) != 0)
2389 white_point=geometry_info.sigma;
2390 gamma=1.0;
2391 if ((flags & XiValue) != 0)
2392 gamma=geometry_info.xi;
2393 if ((flags & PercentValue) != 0)
2394 {
2395 black_point*=(double) image->columns*image->rows/100.0;
2396 white_point*=(double) image->columns*image->rows/100.0;
2397 }
2398 if ((flags & SigmaValue) == 0)
2399 white_point=(double) QuantumRange-black_point;
2400 if ((flags & AspectValue ) == 0)
2401 status=LevelImageChannel(image,DefaultChannels,black_point,white_point,
2402 gamma);
2403 else
cristy308b4e62009-09-21 14:40:44 +00002404 status=LevelizeImage(image,black_point,white_point,gamma);
cristy3ed852e2009-09-05 21:47:34 +00002405 return(status);
2406}
2407
2408/*
2409%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2410% %
2411% %
2412% %
cristy308b4e62009-09-21 14:40:44 +00002413% L e v e l i z e I m a g e %
cristy3ed852e2009-09-05 21:47:34 +00002414% %
2415% %
2416% %
2417%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2418%
cristy308b4e62009-09-21 14:40:44 +00002419% LevelizeImage() applies the normal level operation to the image, spreading
2420% out the values between the black and white points over the entire range of
2421% values. Gamma correction is also applied after the values has been mapped.
cristy3ed852e2009-09-05 21:47:34 +00002422%
2423% It is typically used to improve image contrast, or to provide a controlled
2424% linear threshold for the image. If the black and white points are set to
2425% the minimum and maximum values found in the image, the image can be
2426% normalized. or by swapping black and white values, negate the image.
2427%
cristy308b4e62009-09-21 14:40:44 +00002428% The format of the LevelizeImage method is:
cristy3ed852e2009-09-05 21:47:34 +00002429%
cristy308b4e62009-09-21 14:40:44 +00002430% MagickBooleanType LevelizeImage(Image *image,const double black_point,
2431% const double white_point,const double gamma)
2432% MagickBooleanType LevelizeImageChannel(Image *image,
2433% const ChannelType channel,const double black_point,
2434% const double white_point,const double gamma)
cristy3ed852e2009-09-05 21:47:34 +00002435%
2436% A description of each parameter follows:
2437%
2438% o image: the image.
2439%
2440% o channel: the channel.
2441%
2442% o black_point: The level which is to be mapped to zero (black)
2443%
2444% o white_point: The level which is to be mapped to QuantiumRange (white)
2445%
2446% o gamma: adjust gamma by this factor before mapping values.
2447% use 1.0 for purely linear stretching of image color values
2448%
2449*/
cristy308b4e62009-09-21 14:40:44 +00002450
2451MagickExport MagickBooleanType LevelizeImage(Image *image,
2452 const double black_point,const double white_point,const double gamma)
2453{
2454 MagickBooleanType
2455 status;
2456
2457 status=LevelizeImageChannel(image,DefaultChannels,black_point,white_point,
2458 gamma);
2459 return(status);
2460}
2461
cristy3ed852e2009-09-05 21:47:34 +00002462MagickExport MagickBooleanType LevelImageChannel(Image *image,
2463 const ChannelType channel,const double black_point,const double white_point,
2464 const double gamma)
2465{
2466#define LevelImageTag "Level/Image"
2467#define LevelValue(x) (RoundToQuantum((MagickRealType) QuantumRange* \
2468 pow(((double) (x)-black_point)/(white_point-black_point),1.0/gamma)))
2469
2470 ExceptionInfo
2471 *exception;
2472
2473 long
2474 progress,
2475 y;
2476
2477 MagickBooleanType
2478 status;
2479
2480 register long
2481 i;
2482
2483 CacheView
2484 *image_view;
2485
2486 /*
2487 Allocate and initialize levels map.
2488 */
2489 assert(image != (Image *) NULL);
2490 assert(image->signature == MagickSignature);
2491 if (image->debug != MagickFalse)
2492 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2493 if (image->storage_class == PseudoClass)
cristy629a6c72009-09-13 23:28:22 +00002494#if defined(_OPENMP) && (_OPENMP >= 200203)
cristye0f584d2009-10-11 00:59:14 +00002495 #pragma omp parallel for shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002496#endif
2497 for (i=0; i < (long) image->colors; i++)
2498 {
2499 /*
2500 Level colormap.
2501 */
2502 if ((channel & RedChannel) != 0)
2503 image->colormap[i].red=LevelValue(image->colormap[i].red);
2504 if ((channel & GreenChannel) != 0)
2505 image->colormap[i].green=LevelValue(image->colormap[i].green);
2506 if ((channel & BlueChannel) != 0)
2507 image->colormap[i].blue=LevelValue(image->colormap[i].blue);
2508 if ((channel & OpacityChannel) != 0)
2509 image->colormap[i].opacity=LevelValue(image->colormap[i].opacity);
2510 }
2511 /*
2512 Level image.
2513 */
2514 status=MagickTrue;
2515 progress=0;
2516 exception=(&image->exception);
2517 image_view=AcquireCacheView(image);
cristy629a6c72009-09-13 23:28:22 +00002518#if defined(_OPENMP) && (_OPENMP >= 200203)
cristye0f584d2009-10-11 00:59:14 +00002519 #pragma omp parallel for shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002520#endif
2521 for (y=0; y < (long) image->rows; y++)
2522 {
2523 register IndexPacket
2524 *__restrict indexes;
2525
2526 register long
2527 x;
2528
2529 register PixelPacket
2530 *__restrict q;
2531
2532 if (status == MagickFalse)
2533 continue;
2534 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2535 if (q == (PixelPacket *) NULL)
2536 {
2537 status=MagickFalse;
2538 continue;
2539 }
2540 indexes=GetCacheViewAuthenticIndexQueue(image_view);
2541 for (x=0; x < (long) image->columns; x++)
2542 {
2543 if ((channel & RedChannel) != 0)
2544 q->red=LevelValue(q->red);
2545 if ((channel & GreenChannel) != 0)
2546 q->green=LevelValue(q->green);
2547 if ((channel & BlueChannel) != 0)
2548 q->blue=LevelValue(q->blue);
2549 if (((channel & OpacityChannel) != 0) &&
2550 (image->matte == MagickTrue))
2551 q->opacity=LevelValue(q->opacity);
2552 if (((channel & IndexChannel) != 0) &&
2553 (image->colorspace == CMYKColorspace))
2554 indexes[x]=LevelValue(indexes[x]);
2555 q++;
2556 }
2557 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2558 status=MagickFalse;
2559 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2560 {
2561 MagickBooleanType
2562 proceed;
2563
cristy629a6c72009-09-13 23:28:22 +00002564#if defined(_OPENMP) && (_OPENMP >= 200203)
cristy3ed852e2009-09-05 21:47:34 +00002565 #pragma omp critical (MagickCore_LevelImageChannel)
2566#endif
2567 proceed=SetImageProgress(image,LevelImageTag,progress++,image->rows);
2568 if (proceed == MagickFalse)
2569 status=MagickFalse;
2570 }
2571 }
2572 image_view=DestroyCacheView(image_view);
2573 return(status);
2574}
2575
2576/*
2577%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2578% %
2579% %
2580% %
2581% L e v e l i z e I m a g e C h a n n e l %
2582% %
2583% %
2584% %
2585%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2586%
2587% LevelizeImageChannel() applies the reversed LevelImage() operation to just
2588% the specific channels specified. It compresses the full range of color
2589% values, so that they lie between the given black and white points. Gamma is
2590% applied before the values are mapped.
2591%
2592% LevelizeImageChannel() can be called with by using a +level command line
2593% API option, or using a '!' on a -level or LevelImage() geometry string.
2594%
2595% It can be used for example de-contrast a greyscale image to the exact
2596% levels specified. Or by using specific levels for each channel of an image
2597% you can convert a gray-scale image to any linear color gradient, according
2598% to those levels.
2599%
2600% The format of the LevelizeImageChannel method is:
2601%
2602% MagickBooleanType LevelizeImageChannel(Image *image,
2603% const ChannelType channel,const char *levels)
2604%
2605% A description of each parameter follows:
2606%
2607% o image: the image.
2608%
2609% o channel: the channel.
2610%
2611% o black_point: The level to map zero (black) to.
2612%
2613% o white_point: The level to map QuantiumRange (white) to.
2614%
2615% o gamma: adjust gamma by this factor before mapping values.
2616%
2617*/
2618MagickExport MagickBooleanType LevelizeImageChannel(Image *image,
2619 const ChannelType channel,const double black_point,const double white_point,
2620 const double gamma)
2621{
2622#define LevelizeImageTag "Levelize/Image"
2623#define LevelizeValue(x) (RoundToQuantum(((MagickRealType) \
2624 pow((double)(QuantumScale*(x)),1.0/gamma))*(white_point-black_point)+ \
2625 black_point))
2626
2627 ExceptionInfo
2628 *exception;
2629
2630 long
2631 progress,
2632 y;
2633
2634 MagickBooleanType
2635 status;
2636
2637 register long
2638 i;
2639
2640 CacheView
2641 *image_view;
2642
2643 /*
2644 Allocate and initialize levels map.
2645 */
2646 assert(image != (Image *) NULL);
2647 assert(image->signature == MagickSignature);
2648 if (image->debug != MagickFalse)
2649 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2650 if (image->storage_class == PseudoClass)
cristy629a6c72009-09-13 23:28:22 +00002651#if defined(_OPENMP) && (_OPENMP >= 200203)
cristye0f584d2009-10-11 00:59:14 +00002652 #pragma omp parallel for shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002653#endif
2654 for (i=0; i < (long) image->colors; i++)
2655 {
2656 /*
2657 Level colormap.
2658 */
2659 if ((channel & RedChannel) != 0)
2660 image->colormap[i].red=LevelizeValue(image->colormap[i].red);
2661 if ((channel & GreenChannel) != 0)
2662 image->colormap[i].green=LevelizeValue(image->colormap[i].green);
2663 if ((channel & BlueChannel) != 0)
2664 image->colormap[i].blue=LevelizeValue(image->colormap[i].blue);
2665 if ((channel & OpacityChannel) != 0)
2666 image->colormap[i].opacity=LevelizeValue(image->colormap[i].opacity);
2667 }
2668 /*
2669 Level image.
2670 */
2671 status=MagickTrue;
2672 progress=0;
2673 exception=(&image->exception);
2674 image_view=AcquireCacheView(image);
cristy629a6c72009-09-13 23:28:22 +00002675#if defined(_OPENMP) && (_OPENMP >= 200203)
cristye0f584d2009-10-11 00:59:14 +00002676 #pragma omp parallel for shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002677#endif
2678 for (y=0; y < (long) image->rows; y++)
2679 {
2680 register IndexPacket
2681 *__restrict indexes;
2682
2683 register long
2684 x;
2685
2686 register PixelPacket
2687 *__restrict q;
2688
2689 if (status == MagickFalse)
2690 continue;
2691 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2692 if (q == (PixelPacket *) NULL)
2693 {
2694 status=MagickFalse;
2695 continue;
2696 }
2697 indexes=GetCacheViewAuthenticIndexQueue(image_view);
2698 for (x=0; x < (long) image->columns; x++)
2699 {
2700 if ((channel & RedChannel) != 0)
2701 q->red=LevelizeValue(q->red);
2702 if ((channel & GreenChannel) != 0)
2703 q->green=LevelizeValue(q->green);
2704 if ((channel & BlueChannel) != 0)
2705 q->blue=LevelizeValue(q->blue);
2706 if (((channel & OpacityChannel) != 0) &&
2707 (image->matte == MagickTrue))
2708 q->opacity=LevelizeValue(q->opacity);
2709 if (((channel & IndexChannel) != 0) &&
2710 (image->colorspace == CMYKColorspace))
2711 indexes[x]=LevelizeValue(indexes[x]);
2712 q++;
2713 }
2714 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2715 status=MagickFalse;
2716 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2717 {
2718 MagickBooleanType
2719 proceed;
2720
cristy629a6c72009-09-13 23:28:22 +00002721#if defined(_OPENMP) && (_OPENMP >= 200203)
cristy3ed852e2009-09-05 21:47:34 +00002722 #pragma omp critical (MagickCore_LevelizeImageChannel)
2723#endif
2724 proceed=SetImageProgress(image,LevelizeImageTag,progress++,image->rows);
2725 if (proceed == MagickFalse)
2726 status=MagickFalse;
2727 }
2728 }
2729 return(status);
2730}
2731
2732/*
2733%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2734% %
2735% %
2736% %
2737% L e v e l I m a g e C o l o r s %
2738% %
2739% %
2740% %
2741%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2742%
cristyee0f8d72009-09-19 00:58:29 +00002743% LevelImageColor() maps the given color to "black" and "white" values,
2744% linearly spreading out the colors, and level values on a channel by channel
2745% bases, as per LevelImage(). The given colors allows you to specify
cristy3ed852e2009-09-05 21:47:34 +00002746% different level ranges for each of the color channels seperatally.
2747%
2748% If the boolean 'invert' is set true the image values will modifyed in the
2749% reverse direction. That is any existing "black" and "white" colors in the
2750% image will become the color values given, with all other values compressed
2751% appropriatally. This effectivally maps a greyscale gradient into the given
2752% color gradient.
2753%
cristy308b4e62009-09-21 14:40:44 +00002754% The format of the LevelColorsImageChannel method is:
cristy3ed852e2009-09-05 21:47:34 +00002755%
cristy308b4e62009-09-21 14:40:44 +00002756% MagickBooleanType LevelColorsImage(Image *image,
cristyee0f8d72009-09-19 00:58:29 +00002757% const MagickPixelPacket *black_color,
2758% const MagickPixelPacket *white_color,const MagickBooleanType invert)
cristy308b4e62009-09-21 14:40:44 +00002759% MagickBooleanType LevelColorsImageChannel(Image *image,
2760% const ChannelType channel,const MagickPixelPacket *black_color,
2761% const MagickPixelPacket *white_color,const MagickBooleanType invert)
cristy3ed852e2009-09-05 21:47:34 +00002762%
2763% A description of each parameter follows:
2764%
2765% o image: the image.
2766%
2767% o channel: the channel.
2768%
2769% o black_color: The color to map black to/from
2770%
2771% o white_point: The color to map white to/from
2772%
2773% o invert: if true map the colors (levelize), rather than from (level)
2774%
2775*/
cristy308b4e62009-09-21 14:40:44 +00002776
2777MagickExport MagickBooleanType LevelColorsImage(Image *image,
cristy3ed852e2009-09-05 21:47:34 +00002778 const MagickPixelPacket *black_color,const MagickPixelPacket *white_color,
2779 const MagickBooleanType invert)
2780{
cristy308b4e62009-09-21 14:40:44 +00002781 MagickBooleanType
2782 status;
cristy3ed852e2009-09-05 21:47:34 +00002783
cristy308b4e62009-09-21 14:40:44 +00002784 status=LevelColorsImageChannel(image,DefaultChannels,black_color,white_color,
2785 invert);
2786 return(status);
2787}
2788
2789MagickExport MagickBooleanType LevelColorsImageChannel(Image *image,
2790 const ChannelType channel,const MagickPixelPacket *black_color,
2791 const MagickPixelPacket *white_color,const MagickBooleanType invert)
2792{
cristy3ed852e2009-09-05 21:47:34 +00002793 MagickStatusType
2794 status;
2795
2796 /*
2797 Allocate and initialize levels map.
2798 */
2799 assert(image != (Image *) NULL);
2800 assert(image->signature == MagickSignature);
2801 if (image->debug != MagickFalse)
2802 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2803 status=MagickFalse;
2804 if (invert == MagickFalse)
2805 {
2806 if ((channel & RedChannel) != 0)
2807 status|=LevelImageChannel(image,RedChannel,
2808 black_color->red,white_color->red,(double) 1.0);
2809 if ((channel & GreenChannel) != 0)
2810 status|=LevelImageChannel(image,GreenChannel,
2811 black_color->green,white_color->green,(double) 1.0);
2812 if ((channel & BlueChannel) != 0)
2813 status|=LevelImageChannel(image,BlueChannel,
2814 black_color->blue,white_color->blue,(double) 1.0);
2815 if (((channel & OpacityChannel) != 0) &&
2816 (image->matte == MagickTrue))
2817 status|=LevelImageChannel(image,OpacityChannel,
2818 black_color->opacity,white_color->opacity,(double) 1.0);
2819 if (((channel & IndexChannel) != 0) &&
2820 (image->colorspace == CMYKColorspace))
2821 status|=LevelImageChannel(image,IndexChannel,
2822 black_color->index,white_color->index,(double) 1.0);
2823 }
2824 else
2825 {
2826 if ((channel & RedChannel) != 0)
2827 status|=LevelizeImageChannel(image,RedChannel,
2828 black_color->red,white_color->red,(double) 1.0);
2829 if ((channel & GreenChannel) != 0)
2830 status|=LevelizeImageChannel(image,GreenChannel,
2831 black_color->green,white_color->green,(double) 1.0);
2832 if ((channel & BlueChannel) != 0)
2833 status|=LevelizeImageChannel(image,BlueChannel,
2834 black_color->blue,white_color->blue,(double) 1.0);
2835 if (((channel & OpacityChannel) != 0) &&
2836 (image->matte == MagickTrue))
2837 status|=LevelizeImageChannel(image,OpacityChannel,
2838 black_color->opacity,white_color->opacity,(double) 1.0);
2839 if (((channel & IndexChannel) != 0) &&
2840 (image->colorspace == CMYKColorspace))
2841 status|=LevelizeImageChannel(image,IndexChannel,
2842 black_color->index,white_color->index,(double) 1.0);
2843 }
2844 return(status == 0 ? MagickFalse : MagickTrue);
2845}
2846
2847/*
2848%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2849% %
2850% %
2851% %
2852% L i n e a r S t r e t c h I m a g e %
2853% %
2854% %
2855% %
2856%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2857%
2858% The LinearStretchImage() discards any pixels below the black point and
2859% above the white point and levels the remaining pixels.
2860%
2861% The format of the LinearStretchImage method is:
2862%
2863% MagickBooleanType LinearStretchImage(Image *image,
2864% const double black_point,const double white_point)
2865%
2866% A description of each parameter follows:
2867%
2868% o image: the image.
2869%
2870% o black_point: the black point.
2871%
2872% o white_point: the white point.
2873%
2874*/
2875MagickExport MagickBooleanType LinearStretchImage(Image *image,
2876 const double black_point,const double white_point)
2877{
2878#define LinearStretchImageTag "LinearStretch/Image"
2879
2880 ExceptionInfo
2881 *exception;
2882
2883 long
2884 black,
2885 white,
2886 y;
2887
2888 MagickBooleanType
2889 status;
2890
2891 MagickRealType
2892 *histogram,
2893 intensity;
2894
2895 MagickSizeType
2896 number_pixels;
2897
2898 /*
2899 Allocate histogram and linear map.
2900 */
2901 assert(image != (Image *) NULL);
2902 assert(image->signature == MagickSignature);
2903 histogram=(MagickRealType *) AcquireQuantumMemory(MaxMap+1UL,
2904 sizeof(*histogram));
2905 if (histogram == (MagickRealType *) NULL)
2906 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
2907 image->filename);
2908 /*
2909 Form histogram.
2910 */
2911 (void) ResetMagickMemory(histogram,0,(MaxMap+1)*sizeof(*histogram));
2912 exception=(&image->exception);
2913 for (y=0; y < (long) image->rows; y++)
2914 {
2915 register const PixelPacket
2916 *__restrict p;
2917
2918 register long
2919 x;
2920
2921 p=GetVirtualPixels(image,0,y,image->columns,1,exception);
2922 if (p == (const PixelPacket *) NULL)
2923 break;
2924 for (x=(long) image->columns-1; x >= 0; x--)
2925 {
2926 histogram[ScaleQuantumToMap(PixelIntensityToQuantum(p))]++;
2927 p++;
2928 }
2929 }
2930 /*
2931 Find the histogram boundaries by locating the black and white point levels.
2932 */
2933 number_pixels=(MagickSizeType) image->columns*image->rows;
2934 intensity=0.0;
2935 for (black=0; black < (long) MaxMap; black++)
2936 {
2937 intensity+=histogram[black];
2938 if (intensity >= black_point)
2939 break;
2940 }
2941 intensity=0.0;
2942 for (white=(long) MaxMap; white != 0; white--)
2943 {
2944 intensity+=histogram[white];
2945 if (intensity >= white_point)
2946 break;
2947 }
2948 histogram=(MagickRealType *) RelinquishMagickMemory(histogram);
2949 status=LevelImageChannel(image,DefaultChannels,(double) black,(double) white,
2950 1.0);
2951 return(status);
2952}
2953
2954/*
2955%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2956% %
2957% %
2958% %
2959% M o d u l a t e I m a g e %
2960% %
2961% %
2962% %
2963%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2964%
2965% ModulateImage() lets you control the brightness, saturation, and hue
2966% of an image. Modulate represents the brightness, saturation, and hue
2967% as one parameter (e.g. 90,150,100). If the image colorspace is HSL, the
2968% modulation is lightness, saturation, and hue. And if the colorspace is
2969% HWB, use blackness, whiteness, and hue.
2970%
2971% The format of the ModulateImage method is:
2972%
2973% MagickBooleanType ModulateImage(Image *image,const char *modulate)
2974%
2975% A description of each parameter follows:
2976%
2977% o image: the image.
2978%
2979% o modulate: Define the percent change in brightness, saturation, and
2980% hue.
2981%
2982*/
2983
2984static void ModulateHSB(const double percent_hue,
2985 const double percent_saturation,const double percent_brightness,
2986 Quantum *red,Quantum *green,Quantum *blue)
2987{
2988 double
2989 brightness,
2990 hue,
2991 saturation;
2992
2993 /*
2994 Increase or decrease color brightness, saturation, or hue.
2995 */
2996 assert(red != (Quantum *) NULL);
2997 assert(green != (Quantum *) NULL);
2998 assert(blue != (Quantum *) NULL);
2999 ConvertRGBToHSB(*red,*green,*blue,&hue,&saturation,&brightness);
3000 hue+=0.5*(0.01*percent_hue-1.0);
3001 while (hue < 0.0)
3002 hue+=1.0;
3003 while (hue > 1.0)
3004 hue-=1.0;
3005 saturation*=0.01*percent_saturation;
3006 brightness*=0.01*percent_brightness;
3007 ConvertHSBToRGB(hue,saturation,brightness,red,green,blue);
3008}
3009
3010static void ModulateHSL(const double percent_hue,
3011 const double percent_saturation,const double percent_lightness,
3012 Quantum *red,Quantum *green,Quantum *blue)
3013{
3014 double
3015 hue,
3016 lightness,
3017 saturation;
3018
3019 /*
3020 Increase or decrease color lightness, saturation, or hue.
3021 */
3022 assert(red != (Quantum *) NULL);
3023 assert(green != (Quantum *) NULL);
3024 assert(blue != (Quantum *) NULL);
3025 ConvertRGBToHSL(*red,*green,*blue,&hue,&saturation,&lightness);
3026 hue+=0.5*(0.01*percent_hue-1.0);
3027 while (hue < 0.0)
3028 hue+=1.0;
3029 while (hue > 1.0)
3030 hue-=1.0;
3031 saturation*=0.01*percent_saturation;
3032 lightness*=0.01*percent_lightness;
3033 ConvertHSLToRGB(hue,saturation,lightness,red,green,blue);
3034}
3035
3036static void ModulateHWB(const double percent_hue,const double percent_whiteness, const double percent_blackness,Quantum *red,Quantum *green,Quantum *blue)
3037{
3038 double
3039 blackness,
3040 hue,
3041 whiteness;
3042
3043 /*
3044 Increase or decrease color blackness, whiteness, or hue.
3045 */
3046 assert(red != (Quantum *) NULL);
3047 assert(green != (Quantum *) NULL);
3048 assert(blue != (Quantum *) NULL);
3049 ConvertRGBToHWB(*red,*green,*blue,&hue,&whiteness,&blackness);
3050 hue+=0.5*(0.01*percent_hue-1.0);
3051 while (hue < 0.0)
3052 hue+=1.0;
3053 while (hue > 1.0)
3054 hue-=1.0;
3055 blackness*=0.01*percent_blackness;
3056 whiteness*=0.01*percent_whiteness;
3057 ConvertHWBToRGB(hue,whiteness,blackness,red,green,blue);
3058}
3059
3060MagickExport MagickBooleanType ModulateImage(Image *image,const char *modulate)
3061{
3062#define ModulateImageTag "Modulate/Image"
3063
3064 ColorspaceType
3065 colorspace;
3066
3067 const char
3068 *artifact;
3069
3070 double
3071 percent_brightness,
3072 percent_hue,
3073 percent_saturation;
3074
3075 ExceptionInfo
3076 *exception;
3077
3078 GeometryInfo
3079 geometry_info;
3080
3081 long
3082 progress,
3083 y;
3084
3085 MagickBooleanType
3086 status;
3087
3088 MagickStatusType
3089 flags;
3090
3091 register long
3092 i;
3093
3094 CacheView
3095 *image_view;
3096
3097 /*
3098 Initialize gamma table.
3099 */
3100 assert(image != (Image *) NULL);
3101 assert(image->signature == MagickSignature);
3102 if (image->debug != MagickFalse)
3103 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3104 if (modulate == (char *) NULL)
3105 return(MagickFalse);
3106 flags=ParseGeometry(modulate,&geometry_info);
3107 percent_brightness=geometry_info.rho;
3108 percent_saturation=geometry_info.sigma;
3109 if ((flags & SigmaValue) == 0)
3110 percent_saturation=100.0;
3111 percent_hue=geometry_info.xi;
3112 if ((flags & XiValue) == 0)
3113 percent_hue=100.0;
3114 colorspace=UndefinedColorspace;
3115 artifact=GetImageArtifact(image,"modulate:colorspace");
3116 if (artifact != (const char *) NULL)
3117 colorspace=(ColorspaceType) ParseMagickOption(MagickColorspaceOptions,
3118 MagickFalse,artifact);
3119 if (image->storage_class == PseudoClass)
3120 {
3121 /*
3122 Modulate colormap.
3123 */
cristy629a6c72009-09-13 23:28:22 +00003124#if defined(_OPENMP) && (_OPENMP >= 200203)
cristye0f584d2009-10-11 00:59:14 +00003125 #pragma omp parallel for shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003126#endif
3127 for (i=0; i < (long) image->colors; i++)
3128 switch (colorspace)
3129 {
3130 case HSBColorspace:
3131 {
3132 ModulateHSB(percent_hue,percent_saturation,percent_brightness,
3133 &image->colormap[i].red,&image->colormap[i].green,
3134 &image->colormap[i].blue);
3135 break;
3136 }
3137 case HSLColorspace:
3138 default:
3139 {
3140 ModulateHSL(percent_hue,percent_saturation,percent_brightness,
3141 &image->colormap[i].red,&image->colormap[i].green,
3142 &image->colormap[i].blue);
3143 break;
3144 }
3145 case HWBColorspace:
3146 {
3147 ModulateHWB(percent_hue,percent_saturation,percent_brightness,
3148 &image->colormap[i].red,&image->colormap[i].green,
3149 &image->colormap[i].blue);
3150 break;
3151 }
3152 }
3153 }
3154 /*
3155 Modulate image.
3156 */
3157 status=MagickTrue;
3158 progress=0;
3159 exception=(&image->exception);
3160 image_view=AcquireCacheView(image);
cristy629a6c72009-09-13 23:28:22 +00003161#if defined(_OPENMP) && (_OPENMP >= 200203)
cristye0f584d2009-10-11 00:59:14 +00003162 #pragma omp parallel for shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003163#endif
3164 for (y=0; y < (long) image->rows; y++)
3165 {
3166 register long
3167 x;
3168
3169 register PixelPacket
3170 *__restrict q;
3171
3172 if (status == MagickFalse)
3173 continue;
3174 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
3175 if (q == (PixelPacket *) NULL)
3176 {
3177 status=MagickFalse;
3178 continue;
3179 }
3180 for (x=0; x < (long) image->columns; x++)
3181 {
3182 switch (colorspace)
3183 {
3184 case HSBColorspace:
3185 {
3186 ModulateHSB(percent_hue,percent_saturation,percent_brightness,
3187 &q->red,&q->green,&q->blue);
3188 break;
3189 }
3190 case HSLColorspace:
3191 default:
3192 {
3193 ModulateHSL(percent_hue,percent_saturation,percent_brightness,
3194 &q->red,&q->green,&q->blue);
3195 break;
3196 }
3197 case HWBColorspace:
3198 {
3199 ModulateHWB(percent_hue,percent_saturation,percent_brightness,
3200 &q->red,&q->green,&q->blue);
3201 break;
3202 }
3203 }
3204 q++;
3205 }
3206 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
3207 status=MagickFalse;
3208 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3209 {
3210 MagickBooleanType
3211 proceed;
3212
cristy629a6c72009-09-13 23:28:22 +00003213#if defined(_OPENMP) && (_OPENMP >= 200203)
cristy3ed852e2009-09-05 21:47:34 +00003214 #pragma omp critical (MagickCore_ModulateImage)
3215#endif
3216 proceed=SetImageProgress(image,ModulateImageTag,progress++,image->rows);
3217 if (proceed == MagickFalse)
3218 status=MagickFalse;
3219 }
3220 }
3221 image_view=DestroyCacheView(image_view);
3222 return(status);
3223}
3224
3225/*
3226%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3227% %
3228% %
3229% %
3230% N e g a t e I m a g e %
3231% %
3232% %
3233% %
3234%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3235%
3236% NegateImage() negates the colors in the reference image. The grayscale
3237% option means that only grayscale values within the image are negated.
3238%
3239% The format of the NegateImageChannel method is:
3240%
3241% MagickBooleanType NegateImage(Image *image,
3242% const MagickBooleanType grayscale)
3243% MagickBooleanType NegateImageChannel(Image *image,
3244% const ChannelType channel,const MagickBooleanType grayscale)
3245%
3246% A description of each parameter follows:
3247%
3248% o image: the image.
3249%
3250% o channel: the channel.
3251%
3252% o grayscale: If MagickTrue, only negate grayscale pixels within the image.
3253%
3254*/
3255
3256MagickExport MagickBooleanType NegateImage(Image *image,
3257 const MagickBooleanType grayscale)
3258{
3259 MagickBooleanType
3260 status;
3261
3262 status=NegateImageChannel(image,DefaultChannels,grayscale);
3263 return(status);
3264}
3265
3266MagickExport MagickBooleanType NegateImageChannel(Image *image,
3267 const ChannelType channel,const MagickBooleanType grayscale)
3268{
3269#define NegateImageTag "Negate/Image"
3270
3271 ExceptionInfo
3272 *exception;
3273
3274 long
3275 progress,
3276 y;
3277
3278 MagickBooleanType
3279 status;
3280
3281 register long
3282 i;
3283
3284 CacheView
3285 *image_view;
3286
3287 assert(image != (Image *) NULL);
3288 assert(image->signature == MagickSignature);
3289 if (image->debug != MagickFalse)
3290 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3291 if (image->storage_class == PseudoClass)
3292 {
3293 /*
3294 Negate colormap.
3295 */
cristy629a6c72009-09-13 23:28:22 +00003296#if defined(_OPENMP) && (_OPENMP >= 200203)
cristye0f584d2009-10-11 00:59:14 +00003297 #pragma omp parallel for shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003298#endif
3299 for (i=0; i < (long) image->colors; i++)
3300 {
3301 if (grayscale != MagickFalse)
3302 if ((image->colormap[i].red != image->colormap[i].green) ||
3303 (image->colormap[i].green != image->colormap[i].blue))
3304 continue;
3305 if ((channel & RedChannel) != 0)
3306 image->colormap[i].red=(Quantum) QuantumRange-
3307 image->colormap[i].red;
3308 if ((channel & GreenChannel) != 0)
3309 image->colormap[i].green=(Quantum) QuantumRange-
3310 image->colormap[i].green;
3311 if ((channel & BlueChannel) != 0)
3312 image->colormap[i].blue=(Quantum) QuantumRange-
3313 image->colormap[i].blue;
3314 }
3315 }
3316 /*
3317 Negate image.
3318 */
3319 status=MagickTrue;
3320 progress=0;
3321 exception=(&image->exception);
3322 image_view=AcquireCacheView(image);
3323 if (grayscale != MagickFalse)
3324 {
cristy629a6c72009-09-13 23:28:22 +00003325#if defined(_OPENMP) && (_OPENMP >= 200203)
cristye0f584d2009-10-11 00:59:14 +00003326 #pragma omp parallel for shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003327#endif
3328 for (y=0; y < (long) image->rows; y++)
3329 {
3330 MagickBooleanType
3331 sync;
3332
3333 register IndexPacket
3334 *__restrict indexes;
3335
3336 register long
3337 x;
3338
3339 register PixelPacket
3340 *__restrict q;
3341
3342 if (status == MagickFalse)
3343 continue;
3344 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,
3345 exception);
3346 if (q == (PixelPacket *) NULL)
3347 {
3348 status=MagickFalse;
3349 continue;
3350 }
3351 indexes=GetCacheViewAuthenticIndexQueue(image_view);
3352 for (x=0; x < (long) image->columns; x++)
3353 {
3354 if ((q->red != q->green) || (q->green != q->blue))
3355 {
3356 q++;
3357 continue;
3358 }
3359 if ((channel & RedChannel) != 0)
3360 q->red=(Quantum) QuantumRange-q->red;
3361 if ((channel & GreenChannel) != 0)
3362 q->green=(Quantum) QuantumRange-q->green;
3363 if ((channel & BlueChannel) != 0)
3364 q->blue=(Quantum) QuantumRange-q->blue;
3365 if ((channel & OpacityChannel) != 0)
3366 q->opacity=(Quantum) QuantumRange-q->opacity;
3367 if (((channel & IndexChannel) != 0) &&
3368 (image->colorspace == CMYKColorspace))
3369 indexes[x]=(IndexPacket) QuantumRange-indexes[x];
3370 q++;
3371 }
3372 sync=SyncCacheViewAuthenticPixels(image_view,exception);
3373 if (sync == MagickFalse)
3374 status=MagickFalse;
3375 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3376 {
3377 MagickBooleanType
3378 proceed;
3379
cristy629a6c72009-09-13 23:28:22 +00003380#if defined(_OPENMP) && (_OPENMP >= 200203)
cristy3ed852e2009-09-05 21:47:34 +00003381 #pragma omp critical (MagickCore_NegateImageChannel)
3382#endif
3383 proceed=SetImageProgress(image,NegateImageTag,progress++,
3384 image->rows);
3385 if (proceed == MagickFalse)
3386 status=MagickFalse;
3387 }
3388 }
3389 image_view=DestroyCacheView(image_view);
3390 return(MagickTrue);
3391 }
3392 /*
3393 Negate image.
3394 */
cristy629a6c72009-09-13 23:28:22 +00003395#if defined(_OPENMP) && (_OPENMP >= 200203)
cristye0f584d2009-10-11 00:59:14 +00003396 #pragma omp parallel for shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003397#endif
3398 for (y=0; y < (long) image->rows; y++)
3399 {
3400 register IndexPacket
3401 *__restrict indexes;
3402
3403 register long
3404 x;
3405
3406 register PixelPacket
3407 *__restrict q;
3408
3409 if (status == MagickFalse)
3410 continue;
3411 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
3412 if (q == (PixelPacket *) NULL)
3413 {
3414 status=MagickFalse;
3415 continue;
3416 }
3417 indexes=GetCacheViewAuthenticIndexQueue(image_view);
3418 for (x=0; x < (long) image->columns; x++)
3419 {
3420 if ((channel & RedChannel) != 0)
3421 q->red=(Quantum) QuantumRange-q->red;
3422 if ((channel & GreenChannel) != 0)
3423 q->green=(Quantum) QuantumRange-q->green;
3424 if ((channel & BlueChannel) != 0)
3425 q->blue=(Quantum) QuantumRange-q->blue;
3426 if ((channel & OpacityChannel) != 0)
3427 q->opacity=(Quantum) QuantumRange-q->opacity;
3428 if (((channel & IndexChannel) != 0) &&
3429 (image->colorspace == CMYKColorspace))
3430 indexes[x]=(IndexPacket) QuantumRange-indexes[x];
3431 q++;
3432 }
3433 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
3434 status=MagickFalse;
3435 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3436 {
3437 MagickBooleanType
3438 proceed;
3439
cristy629a6c72009-09-13 23:28:22 +00003440#if defined(_OPENMP) && (_OPENMP >= 200203)
cristy3ed852e2009-09-05 21:47:34 +00003441 #pragma omp critical (MagickCore_NegateImageChannel)
3442#endif
3443 proceed=SetImageProgress(image,NegateImageTag,progress++,image->rows);
3444 if (proceed == MagickFalse)
3445 status=MagickFalse;
3446 }
3447 }
3448 image_view=DestroyCacheView(image_view);
3449 return(status);
3450}
3451
3452/*
3453%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3454% %
3455% %
3456% %
3457% N o r m a l i z e I m a g e %
3458% %
3459% %
3460% %
3461%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3462%
3463% The NormalizeImage() method enhances the contrast of a color image by
3464% mapping the darkest 2 percent of all pixel to black and the brightest
3465% 1 percent to white.
3466%
3467% The format of the NormalizeImage method is:
3468%
3469% MagickBooleanType NormalizeImage(Image *image)
3470% MagickBooleanType NormalizeImageChannel(Image *image,
3471% const ChannelType channel)
3472%
3473% A description of each parameter follows:
3474%
3475% o image: the image.
3476%
3477% o channel: the channel.
3478%
3479*/
3480
3481MagickExport MagickBooleanType NormalizeImage(Image *image)
3482{
3483 MagickBooleanType
3484 status;
3485
3486 status=NormalizeImageChannel(image,DefaultChannels);
3487 return(status);
3488}
3489
3490MagickExport MagickBooleanType NormalizeImageChannel(Image *image,
3491 const ChannelType channel)
3492{
3493 double
3494 black_point,
3495 white_point;
3496
3497 black_point=(double) image->columns*image->rows*0.02;
3498 white_point=(double) image->columns*image->rows*0.99;
3499 return(ContrastStretchImageChannel(image,channel,black_point,white_point));
3500}
3501
3502/*
3503%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3504% %
3505% %
3506% %
3507% S i g m o i d a l C o n t r a s t I m a g e %
3508% %
3509% %
3510% %
3511%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3512%
3513% SigmoidalContrastImage() adjusts the contrast of an image with a non-linear
3514% sigmoidal contrast algorithm. Increase the contrast of the image using a
3515% sigmoidal transfer function without saturating highlights or shadows.
3516% Contrast indicates how much to increase the contrast (0 is none; 3 is
3517% typical; 20 is pushing it); mid-point indicates where midtones fall in the
3518% resultant image (0 is white; 50% is middle-gray; 100% is black). Set
3519% sharpen to MagickTrue to increase the image contrast otherwise the contrast
3520% is reduced.
3521%
3522% The format of the SigmoidalContrastImage method is:
3523%
3524% MagickBooleanType SigmoidalContrastImage(Image *image,
3525% const MagickBooleanType sharpen,const char *levels)
3526% MagickBooleanType SigmoidalContrastImageChannel(Image *image,
3527% const ChannelType channel,const MagickBooleanType sharpen,
3528% const double contrast,const double midpoint)
3529%
3530% A description of each parameter follows:
3531%
3532% o image: the image.
3533%
3534% o channel: the channel.
3535%
3536% o sharpen: Increase or decrease image contrast.
3537%
3538% o contrast: control the "shoulder" of the contast curve.
3539%
3540% o midpoint: control the "toe" of the contast curve.
3541%
3542*/
3543
3544MagickExport MagickBooleanType SigmoidalContrastImage(Image *image,
3545 const MagickBooleanType sharpen,const char *levels)
3546{
3547 GeometryInfo
3548 geometry_info;
3549
3550 MagickBooleanType
3551 status;
3552
3553 MagickStatusType
3554 flags;
3555
3556 flags=ParseGeometry(levels,&geometry_info);
3557 if ((flags & SigmaValue) == 0)
3558 geometry_info.sigma=1.0*QuantumRange/2.0;
3559 if ((flags & PercentValue) != 0)
3560 geometry_info.sigma=1.0*QuantumRange*geometry_info.sigma/100.0;
3561 status=SigmoidalContrastImageChannel(image,DefaultChannels,sharpen,
3562 geometry_info.rho,geometry_info.sigma);
3563 return(status);
3564}
3565
3566MagickExport MagickBooleanType SigmoidalContrastImageChannel(Image *image,
3567 const ChannelType channel,const MagickBooleanType sharpen,
3568 const double contrast,const double midpoint)
3569{
3570#define SigmoidalContrastImageTag "SigmoidalContrast/Image"
3571
3572 ExceptionInfo
3573 *exception;
3574
3575 long
3576 progress,
3577 y;
3578
3579 MagickBooleanType
3580 status;
3581
3582 MagickRealType
3583 *sigmoidal_map;
3584
3585 register long
3586 i;
3587
3588 CacheView
3589 *image_view;
3590
3591 /*
3592 Allocate and initialize sigmoidal maps.
3593 */
3594 assert(image != (Image *) NULL);
3595 assert(image->signature == MagickSignature);
3596 if (image->debug != MagickFalse)
3597 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3598 sigmoidal_map=(MagickRealType *) AcquireQuantumMemory(MaxMap+1UL,
3599 sizeof(*sigmoidal_map));
3600 if (sigmoidal_map == (MagickRealType *) NULL)
3601 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
3602 image->filename);
3603 (void) ResetMagickMemory(sigmoidal_map,0,(MaxMap+1)*sizeof(*sigmoidal_map));
cristy629a6c72009-09-13 23:28:22 +00003604#if defined(_OPENMP) && (_OPENMP >= 200203)
cristye0f584d2009-10-11 00:59:14 +00003605 #pragma omp parallel for shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003606#endif
3607 for (i=0; i <= (long) MaxMap; i++)
3608 {
3609 if (sharpen != MagickFalse)
3610 {
3611 sigmoidal_map[i]=(MagickRealType) ScaleMapToQuantum((MagickRealType)
3612 (MaxMap*((1.0/(1.0+exp(contrast*(midpoint/(double) QuantumRange-
3613 (double) i/MaxMap))))-(1.0/(1.0+exp(contrast*(midpoint/
3614 (double) QuantumRange)))))/((1.0/(1.0+exp(contrast*(midpoint/
3615 (double) QuantumRange-1.0))))-(1.0/(1.0+exp(contrast*(midpoint/
3616 (double) QuantumRange)))))+0.5));
3617 continue;
3618 }
3619 sigmoidal_map[i]=(MagickRealType) ScaleMapToQuantum((MagickRealType)
3620 (MaxMap*(QuantumScale*midpoint-log((1.0-(1.0/(1.0+exp(midpoint/
3621 (double) QuantumRange*contrast))+((double) i/MaxMap)*((1.0/
3622 (1.0+exp(contrast*(midpoint/(double) QuantumRange-1.0))))-(1.0/
3623 (1.0+exp(midpoint/(double) QuantumRange*contrast))))))/
3624 (1.0/(1.0+exp(midpoint/(double) QuantumRange*contrast))+
3625 ((double) i/MaxMap)*((1.0/(1.0+exp(contrast*(midpoint/
3626 (double) QuantumRange-1.0))))-(1.0/(1.0+exp(midpoint/
3627 (double) QuantumRange*contrast))))))/contrast)));
3628 }
3629 if (image->storage_class == PseudoClass)
3630 {
3631 /*
3632 Sigmoidal-contrast enhance colormap.
3633 */
cristy629a6c72009-09-13 23:28:22 +00003634#if defined(_OPENMP) && (_OPENMP >= 200203)
cristye0f584d2009-10-11 00:59:14 +00003635 #pragma omp parallel for shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003636#endif
3637 for (i=0; i < (long) image->colors; i++)
3638 {
3639 if ((channel & RedChannel) != 0)
3640 image->colormap[i].red=RoundToQuantum(sigmoidal_map[
3641 ScaleQuantumToMap(image->colormap[i].red)]);
3642 if ((channel & GreenChannel) != 0)
3643 image->colormap[i].green=RoundToQuantum(sigmoidal_map[
3644 ScaleQuantumToMap(image->colormap[i].green)]);
3645 if ((channel & BlueChannel) != 0)
3646 image->colormap[i].blue=RoundToQuantum(sigmoidal_map[
3647 ScaleQuantumToMap(image->colormap[i].blue)]);
3648 if ((channel & OpacityChannel) != 0)
3649 image->colormap[i].opacity=RoundToQuantum(sigmoidal_map[
3650 ScaleQuantumToMap(image->colormap[i].opacity)]);
3651 }
3652 }
3653 /*
3654 Sigmoidal-contrast enhance image.
3655 */
3656 status=MagickTrue;
3657 progress=0;
3658 exception=(&image->exception);
3659 image_view=AcquireCacheView(image);
cristy629a6c72009-09-13 23:28:22 +00003660#if defined(_OPENMP) && (_OPENMP >= 200203)
cristye0f584d2009-10-11 00:59:14 +00003661 #pragma omp parallel for shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003662#endif
3663 for (y=0; y < (long) image->rows; y++)
3664 {
3665 register IndexPacket
3666 *__restrict indexes;
3667
3668 register long
3669 x;
3670
3671 register PixelPacket
3672 *__restrict q;
3673
3674 if (status == MagickFalse)
3675 continue;
3676 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
3677 if (q == (PixelPacket *) NULL)
3678 {
3679 status=MagickFalse;
3680 continue;
3681 }
3682 indexes=GetCacheViewAuthenticIndexQueue(image_view);
3683 for (x=0; x < (long) image->columns; x++)
3684 {
3685 if ((channel & RedChannel) != 0)
3686 q->red=RoundToQuantum(sigmoidal_map[ScaleQuantumToMap(q->red)]);
3687 if ((channel & GreenChannel) != 0)
3688 q->green=RoundToQuantum(sigmoidal_map[ScaleQuantumToMap(q->green)]);
3689 if ((channel & BlueChannel) != 0)
3690 q->blue=RoundToQuantum(sigmoidal_map[ScaleQuantumToMap(q->blue)]);
3691 if ((channel & OpacityChannel) != 0)
3692 q->opacity=RoundToQuantum(sigmoidal_map[ScaleQuantumToMap(q->opacity)]);
3693 if (((channel & IndexChannel) != 0) &&
3694 (image->colorspace == CMYKColorspace))
3695 indexes[x]=(IndexPacket) RoundToQuantum(sigmoidal_map[
3696 ScaleQuantumToMap(indexes[x])]);
3697 q++;
3698 }
3699 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
3700 status=MagickFalse;
3701 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3702 {
3703 MagickBooleanType
3704 proceed;
3705
cristy629a6c72009-09-13 23:28:22 +00003706#if defined(_OPENMP) && (_OPENMP >= 200203)
cristy3ed852e2009-09-05 21:47:34 +00003707 #pragma omp critical (MagickCore_SigmoidalContrastImageChannel)
3708#endif
3709 proceed=SetImageProgress(image,SigmoidalContrastImageTag,progress++,
3710 image->rows);
3711 if (proceed == MagickFalse)
3712 status=MagickFalse;
3713 }
3714 }
3715 image_view=DestroyCacheView(image_view);
3716 sigmoidal_map=(MagickRealType *) RelinquishMagickMemory(sigmoidal_map);
3717 return(status);
3718}