blob: 6c5b9b724cfad330e02866097f14c88eae862f21 [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%
87% The format of the LevelImage method is:
88%
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);
462#if defined(MAGICKCORE_OPENMP_SUPPORT)
463 #pragma omp parallel for schedule(guided)
464#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 */
482#if defined(MAGICKCORE_OPENMP_SUPPORT)
483 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
484#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);
508#if defined(MAGICKCORE_OPENMP_SUPPORT)
509 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
510#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
548#if defined(MAGICKCORE_OPENMP_SUPPORT)
549 #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);
656#if defined(MAGICKCORE_OPENMP_SUPPORT)
657 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
658#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
772#if defined(MAGICKCORE_OPENMP_SUPPORT)
773 #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);
889#if defined(MAGICKCORE_OPENMP_SUPPORT)
890 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
891#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
920#if defined(MAGICKCORE_OPENMP_SUPPORT)
921 #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));
1227#if defined(MAGICKCORE_OPENMP_SUPPORT)
1228 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
1229#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 */
1309#if defined(MAGICKCORE_OPENMP_SUPPORT)
1310 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
1311#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;
1345#if defined(MAGICKCORE_OPENMP_SUPPORT)
1346 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
1347#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
1409#if defined(MAGICKCORE_OPENMP_SUPPORT)
1410 #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);
1524#if defined(MAGICKCORE_OPENMP_SUPPORT)
1525 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
1526#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
1600#if defined(MAGICKCORE_OPENMP_SUPPORT)
1601 #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));
1757#if defined(MAGICKCORE_OPENMP_SUPPORT)
1758 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
1759#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 */
1788#if defined(MAGICKCORE_OPENMP_SUPPORT)
1789 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
1790#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);
1815#if defined(MAGICKCORE_OPENMP_SUPPORT)
1816 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
1817#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
1864#if defined(MAGICKCORE_OPENMP_SUPPORT)
1865 #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)
1991#if defined(MAGICKCORE_OPENMP_SUPPORT)
1992 #pragma omp parallel for schedule(guided)
1993#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 */
2002#if defined(MAGICKCORE_OPENMP_SUPPORT)
2003 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
2004#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);
2035#if defined(MAGICKCORE_OPENMP_SUPPORT)
2036 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
2037#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 {
2060 if ((channel & RedChannel) != 0)
2061 q->red=gamma_map[ScaleQuantumToMap(q->red)];
2062 if ((channel & GreenChannel) != 0)
2063 q->green=gamma_map[ScaleQuantumToMap(q->green)];
2064 if ((channel & BlueChannel) != 0)
2065 q->blue=gamma_map[ScaleQuantumToMap(q->blue)];
2066 if ((channel & OpacityChannel) != 0)
2067 {
2068 if (image->matte == MagickFalse)
2069 q->opacity=gamma_map[ScaleQuantumToMap(q->opacity)];
2070 else
2071 q->opacity=(Quantum) QuantumRange-gamma_map[
2072 ScaleQuantumToMap((Quantum) (QuantumRange-q->opacity))];
2073 }
2074 q++;
2075 }
2076 if (((channel & IndexChannel) != 0) &&
2077 (image->colorspace == CMYKColorspace))
2078 for (x=0; x < (long) image->columns; x++)
2079 indexes[x]=gamma_map[ScaleQuantumToMap(indexes[x])];
2080 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2081 status=MagickFalse;
2082 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2083 {
2084 MagickBooleanType
2085 proceed;
2086
2087#if defined(MAGICKCORE_OPENMP_SUPPORT)
2088 #pragma omp critical (MagickCore_GammaImageChannel)
2089#endif
2090 proceed=SetImageProgress(image,GammaCorrectImageTag,progress++,
2091 image->rows);
2092 if (proceed == MagickFalse)
2093 status=MagickFalse;
2094 }
2095 }
2096 image_view=DestroyCacheView(image_view);
2097 gamma_map=(Quantum *) RelinquishMagickMemory(gamma_map);
2098 if (image->gamma != 0.0)
2099 image->gamma*=gamma;
2100 return(status);
2101}
2102
2103/*
2104%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2105% %
2106% %
2107% %
2108% H a l d C l u t I m a g e %
2109% %
2110% %
2111% %
2112%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2113%
2114% HaldClutImage() applies a Hald color lookup table to the image. A Hald
2115% color lookup table is a 3-dimensional color cube mapped to 2 dimensions.
2116% Create it with the HALD coder. You can apply any color transformation to
2117% the Hald image and then use this method to apply the transform to the
2118% image.
2119%
2120% The format of the HaldClutImage method is:
2121%
2122% MagickBooleanType HaldClutImage(Image *image,Image *hald_image)
2123% MagickBooleanType HaldClutImageChannel(Image *image,
2124% const ChannelType channel,Image *hald_image)
2125%
2126% A description of each parameter follows:
2127%
2128% o image: the image, which is replaced by indexed CLUT values
2129%
2130% o hald_image: the color lookup table image for replacement color values.
2131%
2132% o channel: the channel.
2133%
2134*/
2135
2136static inline size_t MagickMin(const size_t x,const size_t y)
2137{
2138 if (x < y)
2139 return(x);
2140 return(y);
2141}
2142
2143MagickExport MagickBooleanType HaldClutImage(Image *image,
2144 const Image *hald_image)
2145{
2146 return(HaldClutImageChannel(image,DefaultChannels,hald_image));
2147}
2148
2149MagickExport MagickBooleanType HaldClutImageChannel(Image *image,
2150 const ChannelType channel,const Image *hald_image)
2151{
2152#define HaldClutImageTag "Clut/Image"
2153
2154 typedef struct _HaldInfo
2155 {
2156 MagickRealType
2157 x,
2158 y,
2159 z;
2160 } HaldInfo;
2161
2162 double
2163 width;
2164
2165 ExceptionInfo
2166 *exception;
2167
2168 long
2169 progress,
2170 y;
2171
2172 MagickBooleanType
2173 status;
2174
2175 MagickPixelPacket
2176 zero;
2177
2178 ResampleFilter
2179 **resample_filter;
2180
2181 size_t
2182 cube_size,
2183 length,
2184 level;
2185
2186 CacheView
2187 *image_view;
2188
2189 assert(image != (Image *) NULL);
2190 assert(image->signature == MagickSignature);
2191 if (image->debug != MagickFalse)
2192 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2193 assert(hald_image != (Image *) NULL);
2194 assert(hald_image->signature == MagickSignature);
2195 if (SetImageStorageClass(image,DirectClass) == MagickFalse)
2196 return(MagickFalse);
2197 if (image->matte == MagickFalse)
2198 (void) SetImageAlphaChannel(image,OpaqueAlphaChannel);
2199 /*
2200 Hald clut image.
2201 */
2202 status=MagickTrue;
2203 progress=0;
2204 length=MagickMin(hald_image->columns,hald_image->rows);
2205 for (level=2; (level*level*level) < length; level++) ;
2206 level*=level;
2207 cube_size=level*level;
2208 width=(double) hald_image->columns;
2209 GetMagickPixelPacket(hald_image,&zero);
2210 exception=(&image->exception);
2211 resample_filter=AcquireResampleFilterThreadSet(hald_image,MagickTrue,
2212 exception);
2213 image_view=AcquireCacheView(image);
2214#if defined(MAGICKCORE_OPENMP_SUPPORT)
2215 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
2216#endif
2217 for (y=0; y < (long) image->rows; y++)
2218 {
2219 double
2220 offset;
2221
2222 HaldInfo
2223 point;
2224
2225 MagickPixelPacket
2226 pixel,
2227 pixel1,
2228 pixel2,
2229 pixel3,
2230 pixel4;
2231
2232 register IndexPacket
2233 *__restrict indexes;
2234
2235 register long
2236 id,
2237 x;
2238
2239 register PixelPacket
2240 *__restrict q;
2241
2242 if (status == MagickFalse)
2243 continue;
2244 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2245 if (q == (PixelPacket *) NULL)
2246 {
2247 status=MagickFalse;
2248 continue;
2249 }
2250 indexes=GetCacheViewAuthenticIndexQueue(image_view);
2251 pixel=zero;
2252 pixel1=zero;
2253 pixel2=zero;
2254 pixel3=zero;
2255 pixel4=zero;
2256 id=GetOpenMPThreadId();
2257 for (x=0; x < (long) image->columns; x++)
2258 {
2259 point.x=QuantumScale*(level-1.0)*q->red;
2260 point.y=QuantumScale*(level-1.0)*q->green;
2261 point.z=QuantumScale*(level-1.0)*q->blue;
2262 offset=point.x+level*floor(point.y)+cube_size*floor(point.z);
2263 point.x-=floor(point.x);
2264 point.y-=floor(point.y);
2265 point.z-=floor(point.z);
2266 (void) ResamplePixelColor(resample_filter[id],fmod(offset,width),
2267 floor(offset/width),&pixel1);
2268 (void) ResamplePixelColor(resample_filter[id],fmod(offset+level,width),
2269 floor((offset+level)/width),&pixel2);
2270 MagickPixelCompositeAreaBlend(&pixel1,pixel1.opacity,&pixel2,
2271 pixel2.opacity,point.y,&pixel3);
2272 offset+=cube_size;
2273 (void) ResamplePixelColor(resample_filter[id],fmod(offset,width),
2274 floor(offset/width),&pixel1);
2275 (void) ResamplePixelColor(resample_filter[id],fmod(offset+level,width),
2276 floor((offset+level)/width),&pixel2);
2277 MagickPixelCompositeAreaBlend(&pixel1,pixel1.opacity,&pixel2,
2278 pixel2.opacity,point.y,&pixel4);
2279 MagickPixelCompositeAreaBlend(&pixel3,pixel3.opacity,&pixel4,
2280 pixel4.opacity,point.z,&pixel);
2281 if ((channel & RedChannel) != 0)
2282 q->red=RoundToQuantum(pixel.red);
2283 if ((channel & GreenChannel) != 0)
2284 q->green=RoundToQuantum(pixel.green);
2285 if ((channel & BlueChannel) != 0)
2286 q->blue=RoundToQuantum(pixel.blue);
2287 if (((channel & OpacityChannel) != 0) && (image->matte != MagickFalse))
2288 q->opacity=RoundToQuantum(pixel.opacity);
2289 if (((channel & IndexChannel) != 0) &&
2290 (image->colorspace == CMYKColorspace))
2291 indexes[x]=RoundToQuantum(pixel.index);
2292 q++;
2293 }
2294 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2295 status=MagickFalse;
2296 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2297 {
2298 MagickBooleanType
2299 proceed;
2300
2301#if defined(MAGICKCORE_OPENMP_SUPPORT)
2302 #pragma omp critical (MagickCore_HaldClutImageChannel)
2303#endif
2304 proceed=SetImageProgress(image,HaldClutImageTag,progress++,image->rows);
2305 if (proceed == MagickFalse)
2306 status=MagickFalse;
2307 }
2308 }
2309 image_view=DestroyCacheView(image_view);
2310 resample_filter=DestroyResampleFilterThreadSet(resample_filter);
2311 return(status);
2312}
2313
2314/*
2315%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2316% %
2317% %
2318% %
2319% L e v e l I m a g e %
2320% %
2321% %
2322% %
2323%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2324%
2325% LevelImage() adjusts the levels of a particular image channel by
2326% scaling the colors falling between specified white and black points to
2327% the full available quantum range.
2328%
2329% The parameters provided represent the black, and white points. The black
2330% point specifies the darkest color in the image. Colors darker than the
2331% black point are set to zero. White point specifies the lightest color in
2332% the image. Colors brighter than the white point are set to the maximum
2333% quantum value.
2334%
2335% If a '!' flag is given, map black and white colors to the given levels
2336% rather than mapping those levels to black and white. See
2337% LevelizeImageChannel() and LevelizeImageChannel(), below.
2338%
2339% Gamma specifies a gamma correction to apply to the image.
2340%
2341% The format of the LevelImage method is:
2342%
2343% MagickBooleanType LevelImage(Image *image,const char *levels)
2344%
2345% A description of each parameter follows:
2346%
2347% o image: the image.
2348%
2349% o levels: Specify the levels where the black and white points have the
2350% range of 0-QuantumRange, and gamma has the range 0-10 (e.g. 10x90%+2).
2351% A '!' flag inverts the re-mapping.
2352%
2353*/
2354
2355MagickExport MagickBooleanType LevelImage(Image *image,const char *levels)
2356{
2357 double
2358 black_point,
2359 gamma,
2360 white_point;
2361
2362 GeometryInfo
2363 geometry_info;
2364
2365 MagickBooleanType
2366 status;
2367
2368 MagickStatusType
2369 flags;
2370
2371 /*
2372 Parse levels.
2373 */
2374 if (levels == (char *) NULL)
2375 return(MagickFalse);
2376 flags=ParseGeometry(levels,&geometry_info);
2377 black_point=geometry_info.rho;
2378 white_point=(double) QuantumRange;
2379 if ((flags & SigmaValue) != 0)
2380 white_point=geometry_info.sigma;
2381 gamma=1.0;
2382 if ((flags & XiValue) != 0)
2383 gamma=geometry_info.xi;
2384 if ((flags & PercentValue) != 0)
2385 {
2386 black_point*=(double) image->columns*image->rows/100.0;
2387 white_point*=(double) image->columns*image->rows/100.0;
2388 }
2389 if ((flags & SigmaValue) == 0)
2390 white_point=(double) QuantumRange-black_point;
2391 if ((flags & AspectValue ) == 0)
2392 status=LevelImageChannel(image,DefaultChannels,black_point,white_point,
2393 gamma);
2394 else
2395 status=LevelizeImageChannel(image,DefaultChannels,black_point,white_point,
2396 gamma);
2397 return(status);
2398}
2399
2400/*
2401%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2402% %
2403% %
2404% %
2405% L e v e l I m a g e C h a n n e l %
2406% %
2407% %
2408% %
2409%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2410%
2411% LevelImageChannel() applies the normal LevelImage() operation to just the
2412% Specific channels specified, spreading out the values between the black and
2413% white points over the entire range of values. Gamma correction is also
2414% applied after the values has been mapped.
2415%
2416% It is typically used to improve image contrast, or to provide a controlled
2417% linear threshold for the image. If the black and white points are set to
2418% the minimum and maximum values found in the image, the image can be
2419% normalized. or by swapping black and white values, negate the image.
2420%
2421% The format of the LevelizeImageChannel method is:
2422%
2423% MagickBooleanType LevelImageChannel(Image *image,
2424% const ChannelType channel,black_point,white_point,gamma)
2425%
2426% A description of each parameter follows:
2427%
2428% o image: the image.
2429%
2430% o channel: the channel.
2431%
2432% o black_point: The level which is to be mapped to zero (black)
2433%
2434% o white_point: The level which is to be mapped to QuantiumRange (white)
2435%
2436% o gamma: adjust gamma by this factor before mapping values.
2437% use 1.0 for purely linear stretching of image color values
2438%
2439*/
2440MagickExport MagickBooleanType LevelImageChannel(Image *image,
2441 const ChannelType channel,const double black_point,const double white_point,
2442 const double gamma)
2443{
2444#define LevelImageTag "Level/Image"
2445#define LevelValue(x) (RoundToQuantum((MagickRealType) QuantumRange* \
2446 pow(((double) (x)-black_point)/(white_point-black_point),1.0/gamma)))
2447
2448 ExceptionInfo
2449 *exception;
2450
2451 long
2452 progress,
2453 y;
2454
2455 MagickBooleanType
2456 status;
2457
2458 register long
2459 i;
2460
2461 CacheView
2462 *image_view;
2463
2464 /*
2465 Allocate and initialize levels map.
2466 */
2467 assert(image != (Image *) NULL);
2468 assert(image->signature == MagickSignature);
2469 if (image->debug != MagickFalse)
2470 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2471 if (image->storage_class == PseudoClass)
2472#if defined(MAGICKCORE_OPENMP_SUPPORT)
2473 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
2474#endif
2475 for (i=0; i < (long) image->colors; i++)
2476 {
2477 /*
2478 Level colormap.
2479 */
2480 if ((channel & RedChannel) != 0)
2481 image->colormap[i].red=LevelValue(image->colormap[i].red);
2482 if ((channel & GreenChannel) != 0)
2483 image->colormap[i].green=LevelValue(image->colormap[i].green);
2484 if ((channel & BlueChannel) != 0)
2485 image->colormap[i].blue=LevelValue(image->colormap[i].blue);
2486 if ((channel & OpacityChannel) != 0)
2487 image->colormap[i].opacity=LevelValue(image->colormap[i].opacity);
2488 }
2489 /*
2490 Level image.
2491 */
2492 status=MagickTrue;
2493 progress=0;
2494 exception=(&image->exception);
2495 image_view=AcquireCacheView(image);
2496#if defined(MAGICKCORE_OPENMP_SUPPORT)
2497 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
2498#endif
2499 for (y=0; y < (long) image->rows; y++)
2500 {
2501 register IndexPacket
2502 *__restrict indexes;
2503
2504 register long
2505 x;
2506
2507 register PixelPacket
2508 *__restrict q;
2509
2510 if (status == MagickFalse)
2511 continue;
2512 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2513 if (q == (PixelPacket *) NULL)
2514 {
2515 status=MagickFalse;
2516 continue;
2517 }
2518 indexes=GetCacheViewAuthenticIndexQueue(image_view);
2519 for (x=0; x < (long) image->columns; x++)
2520 {
2521 if ((channel & RedChannel) != 0)
2522 q->red=LevelValue(q->red);
2523 if ((channel & GreenChannel) != 0)
2524 q->green=LevelValue(q->green);
2525 if ((channel & BlueChannel) != 0)
2526 q->blue=LevelValue(q->blue);
2527 if (((channel & OpacityChannel) != 0) &&
2528 (image->matte == MagickTrue))
2529 q->opacity=LevelValue(q->opacity);
2530 if (((channel & IndexChannel) != 0) &&
2531 (image->colorspace == CMYKColorspace))
2532 indexes[x]=LevelValue(indexes[x]);
2533 q++;
2534 }
2535 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2536 status=MagickFalse;
2537 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2538 {
2539 MagickBooleanType
2540 proceed;
2541
2542#if defined(MAGICKCORE_OPENMP_SUPPORT)
2543 #pragma omp critical (MagickCore_LevelImageChannel)
2544#endif
2545 proceed=SetImageProgress(image,LevelImageTag,progress++,image->rows);
2546 if (proceed == MagickFalse)
2547 status=MagickFalse;
2548 }
2549 }
2550 image_view=DestroyCacheView(image_view);
2551 return(status);
2552}
2553
2554/*
2555%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2556% %
2557% %
2558% %
2559% L e v e l i z e I m a g e C h a n n e l %
2560% %
2561% %
2562% %
2563%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2564%
2565% LevelizeImageChannel() applies the reversed LevelImage() operation to just
2566% the specific channels specified. It compresses the full range of color
2567% values, so that they lie between the given black and white points. Gamma is
2568% applied before the values are mapped.
2569%
2570% LevelizeImageChannel() can be called with by using a +level command line
2571% API option, or using a '!' on a -level or LevelImage() geometry string.
2572%
2573% It can be used for example de-contrast a greyscale image to the exact
2574% levels specified. Or by using specific levels for each channel of an image
2575% you can convert a gray-scale image to any linear color gradient, according
2576% to those levels.
2577%
2578% The format of the LevelizeImageChannel method is:
2579%
2580% MagickBooleanType LevelizeImageChannel(Image *image,
2581% const ChannelType channel,const char *levels)
2582%
2583% A description of each parameter follows:
2584%
2585% o image: the image.
2586%
2587% o channel: the channel.
2588%
2589% o black_point: The level to map zero (black) to.
2590%
2591% o white_point: The level to map QuantiumRange (white) to.
2592%
2593% o gamma: adjust gamma by this factor before mapping values.
2594%
2595*/
2596MagickExport MagickBooleanType LevelizeImageChannel(Image *image,
2597 const ChannelType channel,const double black_point,const double white_point,
2598 const double gamma)
2599{
2600#define LevelizeImageTag "Levelize/Image"
2601#define LevelizeValue(x) (RoundToQuantum(((MagickRealType) \
2602 pow((double)(QuantumScale*(x)),1.0/gamma))*(white_point-black_point)+ \
2603 black_point))
2604
2605 ExceptionInfo
2606 *exception;
2607
2608 long
2609 progress,
2610 y;
2611
2612 MagickBooleanType
2613 status;
2614
2615 register long
2616 i;
2617
2618 CacheView
2619 *image_view;
2620
2621 /*
2622 Allocate and initialize levels map.
2623 */
2624 assert(image != (Image *) NULL);
2625 assert(image->signature == MagickSignature);
2626 if (image->debug != MagickFalse)
2627 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2628 if (image->storage_class == PseudoClass)
2629#if defined(MAGICKCORE_OPENMP_SUPPORT)
2630 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
2631#endif
2632 for (i=0; i < (long) image->colors; i++)
2633 {
2634 /*
2635 Level colormap.
2636 */
2637 if ((channel & RedChannel) != 0)
2638 image->colormap[i].red=LevelizeValue(image->colormap[i].red);
2639 if ((channel & GreenChannel) != 0)
2640 image->colormap[i].green=LevelizeValue(image->colormap[i].green);
2641 if ((channel & BlueChannel) != 0)
2642 image->colormap[i].blue=LevelizeValue(image->colormap[i].blue);
2643 if ((channel & OpacityChannel) != 0)
2644 image->colormap[i].opacity=LevelizeValue(image->colormap[i].opacity);
2645 }
2646 /*
2647 Level image.
2648 */
2649 status=MagickTrue;
2650 progress=0;
2651 exception=(&image->exception);
2652 image_view=AcquireCacheView(image);
2653#if defined(MAGICKCORE_OPENMP_SUPPORT)
2654 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
2655#endif
2656 for (y=0; y < (long) image->rows; y++)
2657 {
2658 register IndexPacket
2659 *__restrict indexes;
2660
2661 register long
2662 x;
2663
2664 register PixelPacket
2665 *__restrict q;
2666
2667 if (status == MagickFalse)
2668 continue;
2669 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2670 if (q == (PixelPacket *) NULL)
2671 {
2672 status=MagickFalse;
2673 continue;
2674 }
2675 indexes=GetCacheViewAuthenticIndexQueue(image_view);
2676 for (x=0; x < (long) image->columns; x++)
2677 {
2678 if ((channel & RedChannel) != 0)
2679 q->red=LevelizeValue(q->red);
2680 if ((channel & GreenChannel) != 0)
2681 q->green=LevelizeValue(q->green);
2682 if ((channel & BlueChannel) != 0)
2683 q->blue=LevelizeValue(q->blue);
2684 if (((channel & OpacityChannel) != 0) &&
2685 (image->matte == MagickTrue))
2686 q->opacity=LevelizeValue(q->opacity);
2687 if (((channel & IndexChannel) != 0) &&
2688 (image->colorspace == CMYKColorspace))
2689 indexes[x]=LevelizeValue(indexes[x]);
2690 q++;
2691 }
2692 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2693 status=MagickFalse;
2694 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2695 {
2696 MagickBooleanType
2697 proceed;
2698
2699#if defined(MAGICKCORE_OPENMP_SUPPORT)
2700 #pragma omp critical (MagickCore_LevelizeImageChannel)
2701#endif
2702 proceed=SetImageProgress(image,LevelizeImageTag,progress++,image->rows);
2703 if (proceed == MagickFalse)
2704 status=MagickFalse;
2705 }
2706 }
2707 return(status);
2708}
2709
2710/*
2711%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2712% %
2713% %
2714% %
2715% L e v e l I m a g e C o l o r s %
2716% %
2717% %
2718% %
2719%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2720%
2721% LevelImageColor() will map the given color to "black" and "white"
2722% values, limearly spreading out the colors, and level values on a channel by
2723% channel bases, as per LevelImage(). The given colors allows you to specify
2724% different level ranges for each of the color channels seperatally.
2725%
2726% If the boolean 'invert' is set true the image values will modifyed in the
2727% reverse direction. That is any existing "black" and "white" colors in the
2728% image will become the color values given, with all other values compressed
2729% appropriatally. This effectivally maps a greyscale gradient into the given
2730% color gradient.
2731%
2732% The format of the LevelImageColors method is:
2733%
2734% MagickBooleanType LevelImageColors(Image *image,const ChannelType channel,
2735% const MagickPixelPacket *black_color,const MagickPixelPacket *white_color,
2736% const MagickBooleanType invert)
2737%
2738% A description of each parameter follows:
2739%
2740% o image: the image.
2741%
2742% o channel: the channel.
2743%
2744% o black_color: The color to map black to/from
2745%
2746% o white_point: The color to map white to/from
2747%
2748% o invert: if true map the colors (levelize), rather than from (level)
2749%
2750*/
2751MagickBooleanType LevelImageColors(Image *image,const ChannelType channel,
2752 const MagickPixelPacket *black_color,const MagickPixelPacket *white_color,
2753 const MagickBooleanType invert)
2754{
2755#define LevelColorImageTag "LevelColor/Image"
2756
2757 MagickStatusType
2758 status;
2759
2760 /*
2761 Allocate and initialize levels map.
2762 */
2763 assert(image != (Image *) NULL);
2764 assert(image->signature == MagickSignature);
2765 if (image->debug != MagickFalse)
2766 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2767 status=MagickFalse;
2768 if (invert == MagickFalse)
2769 {
2770 if ((channel & RedChannel) != 0)
2771 status|=LevelImageChannel(image,RedChannel,
2772 black_color->red,white_color->red,(double) 1.0);
2773 if ((channel & GreenChannel) != 0)
2774 status|=LevelImageChannel(image,GreenChannel,
2775 black_color->green,white_color->green,(double) 1.0);
2776 if ((channel & BlueChannel) != 0)
2777 status|=LevelImageChannel(image,BlueChannel,
2778 black_color->blue,white_color->blue,(double) 1.0);
2779 if (((channel & OpacityChannel) != 0) &&
2780 (image->matte == MagickTrue))
2781 status|=LevelImageChannel(image,OpacityChannel,
2782 black_color->opacity,white_color->opacity,(double) 1.0);
2783 if (((channel & IndexChannel) != 0) &&
2784 (image->colorspace == CMYKColorspace))
2785 status|=LevelImageChannel(image,IndexChannel,
2786 black_color->index,white_color->index,(double) 1.0);
2787 }
2788 else
2789 {
2790 if ((channel & RedChannel) != 0)
2791 status|=LevelizeImageChannel(image,RedChannel,
2792 black_color->red,white_color->red,(double) 1.0);
2793 if ((channel & GreenChannel) != 0)
2794 status|=LevelizeImageChannel(image,GreenChannel,
2795 black_color->green,white_color->green,(double) 1.0);
2796 if ((channel & BlueChannel) != 0)
2797 status|=LevelizeImageChannel(image,BlueChannel,
2798 black_color->blue,white_color->blue,(double) 1.0);
2799 if (((channel & OpacityChannel) != 0) &&
2800 (image->matte == MagickTrue))
2801 status|=LevelizeImageChannel(image,OpacityChannel,
2802 black_color->opacity,white_color->opacity,(double) 1.0);
2803 if (((channel & IndexChannel) != 0) &&
2804 (image->colorspace == CMYKColorspace))
2805 status|=LevelizeImageChannel(image,IndexChannel,
2806 black_color->index,white_color->index,(double) 1.0);
2807 }
2808 return(status == 0 ? MagickFalse : MagickTrue);
2809}
2810
2811/*
2812%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2813% %
2814% %
2815% %
2816% L i n e a r S t r e t c h I m a g e %
2817% %
2818% %
2819% %
2820%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2821%
2822% The LinearStretchImage() discards any pixels below the black point and
2823% above the white point and levels the remaining pixels.
2824%
2825% The format of the LinearStretchImage method is:
2826%
2827% MagickBooleanType LinearStretchImage(Image *image,
2828% const double black_point,const double white_point)
2829%
2830% A description of each parameter follows:
2831%
2832% o image: the image.
2833%
2834% o black_point: the black point.
2835%
2836% o white_point: the white point.
2837%
2838*/
2839MagickExport MagickBooleanType LinearStretchImage(Image *image,
2840 const double black_point,const double white_point)
2841{
2842#define LinearStretchImageTag "LinearStretch/Image"
2843
2844 ExceptionInfo
2845 *exception;
2846
2847 long
2848 black,
2849 white,
2850 y;
2851
2852 MagickBooleanType
2853 status;
2854
2855 MagickRealType
2856 *histogram,
2857 intensity;
2858
2859 MagickSizeType
2860 number_pixels;
2861
2862 /*
2863 Allocate histogram and linear map.
2864 */
2865 assert(image != (Image *) NULL);
2866 assert(image->signature == MagickSignature);
2867 histogram=(MagickRealType *) AcquireQuantumMemory(MaxMap+1UL,
2868 sizeof(*histogram));
2869 if (histogram == (MagickRealType *) NULL)
2870 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
2871 image->filename);
2872 /*
2873 Form histogram.
2874 */
2875 (void) ResetMagickMemory(histogram,0,(MaxMap+1)*sizeof(*histogram));
2876 exception=(&image->exception);
2877 for (y=0; y < (long) image->rows; y++)
2878 {
2879 register const PixelPacket
2880 *__restrict p;
2881
2882 register long
2883 x;
2884
2885 p=GetVirtualPixels(image,0,y,image->columns,1,exception);
2886 if (p == (const PixelPacket *) NULL)
2887 break;
2888 for (x=(long) image->columns-1; x >= 0; x--)
2889 {
2890 histogram[ScaleQuantumToMap(PixelIntensityToQuantum(p))]++;
2891 p++;
2892 }
2893 }
2894 /*
2895 Find the histogram boundaries by locating the black and white point levels.
2896 */
2897 number_pixels=(MagickSizeType) image->columns*image->rows;
2898 intensity=0.0;
2899 for (black=0; black < (long) MaxMap; black++)
2900 {
2901 intensity+=histogram[black];
2902 if (intensity >= black_point)
2903 break;
2904 }
2905 intensity=0.0;
2906 for (white=(long) MaxMap; white != 0; white--)
2907 {
2908 intensity+=histogram[white];
2909 if (intensity >= white_point)
2910 break;
2911 }
2912 histogram=(MagickRealType *) RelinquishMagickMemory(histogram);
2913 status=LevelImageChannel(image,DefaultChannels,(double) black,(double) white,
2914 1.0);
2915 return(status);
2916}
2917
2918/*
2919%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2920% %
2921% %
2922% %
2923% M o d u l a t e I m a g e %
2924% %
2925% %
2926% %
2927%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2928%
2929% ModulateImage() lets you control the brightness, saturation, and hue
2930% of an image. Modulate represents the brightness, saturation, and hue
2931% as one parameter (e.g. 90,150,100). If the image colorspace is HSL, the
2932% modulation is lightness, saturation, and hue. And if the colorspace is
2933% HWB, use blackness, whiteness, and hue.
2934%
2935% The format of the ModulateImage method is:
2936%
2937% MagickBooleanType ModulateImage(Image *image,const char *modulate)
2938%
2939% A description of each parameter follows:
2940%
2941% o image: the image.
2942%
2943% o modulate: Define the percent change in brightness, saturation, and
2944% hue.
2945%
2946*/
2947
2948static void ModulateHSB(const double percent_hue,
2949 const double percent_saturation,const double percent_brightness,
2950 Quantum *red,Quantum *green,Quantum *blue)
2951{
2952 double
2953 brightness,
2954 hue,
2955 saturation;
2956
2957 /*
2958 Increase or decrease color brightness, saturation, or hue.
2959 */
2960 assert(red != (Quantum *) NULL);
2961 assert(green != (Quantum *) NULL);
2962 assert(blue != (Quantum *) NULL);
2963 ConvertRGBToHSB(*red,*green,*blue,&hue,&saturation,&brightness);
2964 hue+=0.5*(0.01*percent_hue-1.0);
2965 while (hue < 0.0)
2966 hue+=1.0;
2967 while (hue > 1.0)
2968 hue-=1.0;
2969 saturation*=0.01*percent_saturation;
2970 brightness*=0.01*percent_brightness;
2971 ConvertHSBToRGB(hue,saturation,brightness,red,green,blue);
2972}
2973
2974static void ModulateHSL(const double percent_hue,
2975 const double percent_saturation,const double percent_lightness,
2976 Quantum *red,Quantum *green,Quantum *blue)
2977{
2978 double
2979 hue,
2980 lightness,
2981 saturation;
2982
2983 /*
2984 Increase or decrease color lightness, saturation, or hue.
2985 */
2986 assert(red != (Quantum *) NULL);
2987 assert(green != (Quantum *) NULL);
2988 assert(blue != (Quantum *) NULL);
2989 ConvertRGBToHSL(*red,*green,*blue,&hue,&saturation,&lightness);
2990 hue+=0.5*(0.01*percent_hue-1.0);
2991 while (hue < 0.0)
2992 hue+=1.0;
2993 while (hue > 1.0)
2994 hue-=1.0;
2995 saturation*=0.01*percent_saturation;
2996 lightness*=0.01*percent_lightness;
2997 ConvertHSLToRGB(hue,saturation,lightness,red,green,blue);
2998}
2999
3000static void ModulateHWB(const double percent_hue,const double percent_whiteness, const double percent_blackness,Quantum *red,Quantum *green,Quantum *blue)
3001{
3002 double
3003 blackness,
3004 hue,
3005 whiteness;
3006
3007 /*
3008 Increase or decrease color blackness, whiteness, or hue.
3009 */
3010 assert(red != (Quantum *) NULL);
3011 assert(green != (Quantum *) NULL);
3012 assert(blue != (Quantum *) NULL);
3013 ConvertRGBToHWB(*red,*green,*blue,&hue,&whiteness,&blackness);
3014 hue+=0.5*(0.01*percent_hue-1.0);
3015 while (hue < 0.0)
3016 hue+=1.0;
3017 while (hue > 1.0)
3018 hue-=1.0;
3019 blackness*=0.01*percent_blackness;
3020 whiteness*=0.01*percent_whiteness;
3021 ConvertHWBToRGB(hue,whiteness,blackness,red,green,blue);
3022}
3023
3024MagickExport MagickBooleanType ModulateImage(Image *image,const char *modulate)
3025{
3026#define ModulateImageTag "Modulate/Image"
3027
3028 ColorspaceType
3029 colorspace;
3030
3031 const char
3032 *artifact;
3033
3034 double
3035 percent_brightness,
3036 percent_hue,
3037 percent_saturation;
3038
3039 ExceptionInfo
3040 *exception;
3041
3042 GeometryInfo
3043 geometry_info;
3044
3045 long
3046 progress,
3047 y;
3048
3049 MagickBooleanType
3050 status;
3051
3052 MagickStatusType
3053 flags;
3054
3055 register long
3056 i;
3057
3058 CacheView
3059 *image_view;
3060
3061 /*
3062 Initialize gamma table.
3063 */
3064 assert(image != (Image *) NULL);
3065 assert(image->signature == MagickSignature);
3066 if (image->debug != MagickFalse)
3067 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3068 if (modulate == (char *) NULL)
3069 return(MagickFalse);
3070 flags=ParseGeometry(modulate,&geometry_info);
3071 percent_brightness=geometry_info.rho;
3072 percent_saturation=geometry_info.sigma;
3073 if ((flags & SigmaValue) == 0)
3074 percent_saturation=100.0;
3075 percent_hue=geometry_info.xi;
3076 if ((flags & XiValue) == 0)
3077 percent_hue=100.0;
3078 colorspace=UndefinedColorspace;
3079 artifact=GetImageArtifact(image,"modulate:colorspace");
3080 if (artifact != (const char *) NULL)
3081 colorspace=(ColorspaceType) ParseMagickOption(MagickColorspaceOptions,
3082 MagickFalse,artifact);
3083 if (image->storage_class == PseudoClass)
3084 {
3085 /*
3086 Modulate colormap.
3087 */
3088#if defined(MAGICKCORE_OPENMP_SUPPORT)
3089 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
3090#endif
3091 for (i=0; i < (long) image->colors; i++)
3092 switch (colorspace)
3093 {
3094 case HSBColorspace:
3095 {
3096 ModulateHSB(percent_hue,percent_saturation,percent_brightness,
3097 &image->colormap[i].red,&image->colormap[i].green,
3098 &image->colormap[i].blue);
3099 break;
3100 }
3101 case HSLColorspace:
3102 default:
3103 {
3104 ModulateHSL(percent_hue,percent_saturation,percent_brightness,
3105 &image->colormap[i].red,&image->colormap[i].green,
3106 &image->colormap[i].blue);
3107 break;
3108 }
3109 case HWBColorspace:
3110 {
3111 ModulateHWB(percent_hue,percent_saturation,percent_brightness,
3112 &image->colormap[i].red,&image->colormap[i].green,
3113 &image->colormap[i].blue);
3114 break;
3115 }
3116 }
3117 }
3118 /*
3119 Modulate image.
3120 */
3121 status=MagickTrue;
3122 progress=0;
3123 exception=(&image->exception);
3124 image_view=AcquireCacheView(image);
3125#if defined(MAGICKCORE_OPENMP_SUPPORT)
3126 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
3127#endif
3128 for (y=0; y < (long) image->rows; y++)
3129 {
3130 register long
3131 x;
3132
3133 register PixelPacket
3134 *__restrict q;
3135
3136 if (status == MagickFalse)
3137 continue;
3138 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
3139 if (q == (PixelPacket *) NULL)
3140 {
3141 status=MagickFalse;
3142 continue;
3143 }
3144 for (x=0; x < (long) image->columns; x++)
3145 {
3146 switch (colorspace)
3147 {
3148 case HSBColorspace:
3149 {
3150 ModulateHSB(percent_hue,percent_saturation,percent_brightness,
3151 &q->red,&q->green,&q->blue);
3152 break;
3153 }
3154 case HSLColorspace:
3155 default:
3156 {
3157 ModulateHSL(percent_hue,percent_saturation,percent_brightness,
3158 &q->red,&q->green,&q->blue);
3159 break;
3160 }
3161 case HWBColorspace:
3162 {
3163 ModulateHWB(percent_hue,percent_saturation,percent_brightness,
3164 &q->red,&q->green,&q->blue);
3165 break;
3166 }
3167 }
3168 q++;
3169 }
3170 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
3171 status=MagickFalse;
3172 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3173 {
3174 MagickBooleanType
3175 proceed;
3176
3177#if defined(MAGICKCORE_OPENMP_SUPPORT)
3178 #pragma omp critical (MagickCore_ModulateImage)
3179#endif
3180 proceed=SetImageProgress(image,ModulateImageTag,progress++,image->rows);
3181 if (proceed == MagickFalse)
3182 status=MagickFalse;
3183 }
3184 }
3185 image_view=DestroyCacheView(image_view);
3186 return(status);
3187}
3188
3189/*
3190%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3191% %
3192% %
3193% %
3194% N e g a t e I m a g e %
3195% %
3196% %
3197% %
3198%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3199%
3200% NegateImage() negates the colors in the reference image. The grayscale
3201% option means that only grayscale values within the image are negated.
3202%
3203% The format of the NegateImageChannel method is:
3204%
3205% MagickBooleanType NegateImage(Image *image,
3206% const MagickBooleanType grayscale)
3207% MagickBooleanType NegateImageChannel(Image *image,
3208% const ChannelType channel,const MagickBooleanType grayscale)
3209%
3210% A description of each parameter follows:
3211%
3212% o image: the image.
3213%
3214% o channel: the channel.
3215%
3216% o grayscale: If MagickTrue, only negate grayscale pixels within the image.
3217%
3218*/
3219
3220MagickExport MagickBooleanType NegateImage(Image *image,
3221 const MagickBooleanType grayscale)
3222{
3223 MagickBooleanType
3224 status;
3225
3226 status=NegateImageChannel(image,DefaultChannels,grayscale);
3227 return(status);
3228}
3229
3230MagickExport MagickBooleanType NegateImageChannel(Image *image,
3231 const ChannelType channel,const MagickBooleanType grayscale)
3232{
3233#define NegateImageTag "Negate/Image"
3234
3235 ExceptionInfo
3236 *exception;
3237
3238 long
3239 progress,
3240 y;
3241
3242 MagickBooleanType
3243 status;
3244
3245 register long
3246 i;
3247
3248 CacheView
3249 *image_view;
3250
3251 assert(image != (Image *) NULL);
3252 assert(image->signature == MagickSignature);
3253 if (image->debug != MagickFalse)
3254 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3255 if (image->storage_class == PseudoClass)
3256 {
3257 /*
3258 Negate colormap.
3259 */
3260#if defined(MAGICKCORE_OPENMP_SUPPORT)
3261 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
3262#endif
3263 for (i=0; i < (long) image->colors; i++)
3264 {
3265 if (grayscale != MagickFalse)
3266 if ((image->colormap[i].red != image->colormap[i].green) ||
3267 (image->colormap[i].green != image->colormap[i].blue))
3268 continue;
3269 if ((channel & RedChannel) != 0)
3270 image->colormap[i].red=(Quantum) QuantumRange-
3271 image->colormap[i].red;
3272 if ((channel & GreenChannel) != 0)
3273 image->colormap[i].green=(Quantum) QuantumRange-
3274 image->colormap[i].green;
3275 if ((channel & BlueChannel) != 0)
3276 image->colormap[i].blue=(Quantum) QuantumRange-
3277 image->colormap[i].blue;
3278 }
3279 }
3280 /*
3281 Negate image.
3282 */
3283 status=MagickTrue;
3284 progress=0;
3285 exception=(&image->exception);
3286 image_view=AcquireCacheView(image);
3287 if (grayscale != MagickFalse)
3288 {
3289#if defined(MAGICKCORE_OPENMP_SUPPORT)
3290 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
3291#endif
3292 for (y=0; y < (long) image->rows; y++)
3293 {
3294 MagickBooleanType
3295 sync;
3296
3297 register IndexPacket
3298 *__restrict indexes;
3299
3300 register long
3301 x;
3302
3303 register PixelPacket
3304 *__restrict q;
3305
3306 if (status == MagickFalse)
3307 continue;
3308 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,
3309 exception);
3310 if (q == (PixelPacket *) NULL)
3311 {
3312 status=MagickFalse;
3313 continue;
3314 }
3315 indexes=GetCacheViewAuthenticIndexQueue(image_view);
3316 for (x=0; x < (long) image->columns; x++)
3317 {
3318 if ((q->red != q->green) || (q->green != q->blue))
3319 {
3320 q++;
3321 continue;
3322 }
3323 if ((channel & RedChannel) != 0)
3324 q->red=(Quantum) QuantumRange-q->red;
3325 if ((channel & GreenChannel) != 0)
3326 q->green=(Quantum) QuantumRange-q->green;
3327 if ((channel & BlueChannel) != 0)
3328 q->blue=(Quantum) QuantumRange-q->blue;
3329 if ((channel & OpacityChannel) != 0)
3330 q->opacity=(Quantum) QuantumRange-q->opacity;
3331 if (((channel & IndexChannel) != 0) &&
3332 (image->colorspace == CMYKColorspace))
3333 indexes[x]=(IndexPacket) QuantumRange-indexes[x];
3334 q++;
3335 }
3336 sync=SyncCacheViewAuthenticPixels(image_view,exception);
3337 if (sync == MagickFalse)
3338 status=MagickFalse;
3339 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3340 {
3341 MagickBooleanType
3342 proceed;
3343
3344#if defined(MAGICKCORE_OPENMP_SUPPORT)
3345 #pragma omp critical (MagickCore_NegateImageChannel)
3346#endif
3347 proceed=SetImageProgress(image,NegateImageTag,progress++,
3348 image->rows);
3349 if (proceed == MagickFalse)
3350 status=MagickFalse;
3351 }
3352 }
3353 image_view=DestroyCacheView(image_view);
3354 return(MagickTrue);
3355 }
3356 /*
3357 Negate image.
3358 */
3359#if defined(MAGICKCORE_OPENMP_SUPPORT)
3360 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
3361#endif
3362 for (y=0; y < (long) image->rows; y++)
3363 {
3364 register IndexPacket
3365 *__restrict indexes;
3366
3367 register long
3368 x;
3369
3370 register PixelPacket
3371 *__restrict q;
3372
3373 if (status == MagickFalse)
3374 continue;
3375 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
3376 if (q == (PixelPacket *) NULL)
3377 {
3378 status=MagickFalse;
3379 continue;
3380 }
3381 indexes=GetCacheViewAuthenticIndexQueue(image_view);
3382 for (x=0; x < (long) image->columns; x++)
3383 {
3384 if ((channel & RedChannel) != 0)
3385 q->red=(Quantum) QuantumRange-q->red;
3386 if ((channel & GreenChannel) != 0)
3387 q->green=(Quantum) QuantumRange-q->green;
3388 if ((channel & BlueChannel) != 0)
3389 q->blue=(Quantum) QuantumRange-q->blue;
3390 if ((channel & OpacityChannel) != 0)
3391 q->opacity=(Quantum) QuantumRange-q->opacity;
3392 if (((channel & IndexChannel) != 0) &&
3393 (image->colorspace == CMYKColorspace))
3394 indexes[x]=(IndexPacket) QuantumRange-indexes[x];
3395 q++;
3396 }
3397 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
3398 status=MagickFalse;
3399 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3400 {
3401 MagickBooleanType
3402 proceed;
3403
3404#if defined(MAGICKCORE_OPENMP_SUPPORT)
3405 #pragma omp critical (MagickCore_NegateImageChannel)
3406#endif
3407 proceed=SetImageProgress(image,NegateImageTag,progress++,image->rows);
3408 if (proceed == MagickFalse)
3409 status=MagickFalse;
3410 }
3411 }
3412 image_view=DestroyCacheView(image_view);
3413 return(status);
3414}
3415
3416/*
3417%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3418% %
3419% %
3420% %
3421% N o r m a l i z e I m a g e %
3422% %
3423% %
3424% %
3425%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3426%
3427% The NormalizeImage() method enhances the contrast of a color image by
3428% mapping the darkest 2 percent of all pixel to black and the brightest
3429% 1 percent to white.
3430%
3431% The format of the NormalizeImage method is:
3432%
3433% MagickBooleanType NormalizeImage(Image *image)
3434% MagickBooleanType NormalizeImageChannel(Image *image,
3435% const ChannelType channel)
3436%
3437% A description of each parameter follows:
3438%
3439% o image: the image.
3440%
3441% o channel: the channel.
3442%
3443*/
3444
3445MagickExport MagickBooleanType NormalizeImage(Image *image)
3446{
3447 MagickBooleanType
3448 status;
3449
3450 status=NormalizeImageChannel(image,DefaultChannels);
3451 return(status);
3452}
3453
3454MagickExport MagickBooleanType NormalizeImageChannel(Image *image,
3455 const ChannelType channel)
3456{
3457 double
3458 black_point,
3459 white_point;
3460
3461 black_point=(double) image->columns*image->rows*0.02;
3462 white_point=(double) image->columns*image->rows*0.99;
3463 return(ContrastStretchImageChannel(image,channel,black_point,white_point));
3464}
3465
3466/*
3467%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3468% %
3469% %
3470% %
3471% S i g m o i d a l C o n t r a s t I m a g e %
3472% %
3473% %
3474% %
3475%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3476%
3477% SigmoidalContrastImage() adjusts the contrast of an image with a non-linear
3478% sigmoidal contrast algorithm. Increase the contrast of the image using a
3479% sigmoidal transfer function without saturating highlights or shadows.
3480% Contrast indicates how much to increase the contrast (0 is none; 3 is
3481% typical; 20 is pushing it); mid-point indicates where midtones fall in the
3482% resultant image (0 is white; 50% is middle-gray; 100% is black). Set
3483% sharpen to MagickTrue to increase the image contrast otherwise the contrast
3484% is reduced.
3485%
3486% The format of the SigmoidalContrastImage method is:
3487%
3488% MagickBooleanType SigmoidalContrastImage(Image *image,
3489% const MagickBooleanType sharpen,const char *levels)
3490% MagickBooleanType SigmoidalContrastImageChannel(Image *image,
3491% const ChannelType channel,const MagickBooleanType sharpen,
3492% const double contrast,const double midpoint)
3493%
3494% A description of each parameter follows:
3495%
3496% o image: the image.
3497%
3498% o channel: the channel.
3499%
3500% o sharpen: Increase or decrease image contrast.
3501%
3502% o contrast: control the "shoulder" of the contast curve.
3503%
3504% o midpoint: control the "toe" of the contast curve.
3505%
3506*/
3507
3508MagickExport MagickBooleanType SigmoidalContrastImage(Image *image,
3509 const MagickBooleanType sharpen,const char *levels)
3510{
3511 GeometryInfo
3512 geometry_info;
3513
3514 MagickBooleanType
3515 status;
3516
3517 MagickStatusType
3518 flags;
3519
3520 flags=ParseGeometry(levels,&geometry_info);
3521 if ((flags & SigmaValue) == 0)
3522 geometry_info.sigma=1.0*QuantumRange/2.0;
3523 if ((flags & PercentValue) != 0)
3524 geometry_info.sigma=1.0*QuantumRange*geometry_info.sigma/100.0;
3525 status=SigmoidalContrastImageChannel(image,DefaultChannels,sharpen,
3526 geometry_info.rho,geometry_info.sigma);
3527 return(status);
3528}
3529
3530MagickExport MagickBooleanType SigmoidalContrastImageChannel(Image *image,
3531 const ChannelType channel,const MagickBooleanType sharpen,
3532 const double contrast,const double midpoint)
3533{
3534#define SigmoidalContrastImageTag "SigmoidalContrast/Image"
3535
3536 ExceptionInfo
3537 *exception;
3538
3539 long
3540 progress,
3541 y;
3542
3543 MagickBooleanType
3544 status;
3545
3546 MagickRealType
3547 *sigmoidal_map;
3548
3549 register long
3550 i;
3551
3552 CacheView
3553 *image_view;
3554
3555 /*
3556 Allocate and initialize sigmoidal maps.
3557 */
3558 assert(image != (Image *) NULL);
3559 assert(image->signature == MagickSignature);
3560 if (image->debug != MagickFalse)
3561 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3562 sigmoidal_map=(MagickRealType *) AcquireQuantumMemory(MaxMap+1UL,
3563 sizeof(*sigmoidal_map));
3564 if (sigmoidal_map == (MagickRealType *) NULL)
3565 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
3566 image->filename);
3567 (void) ResetMagickMemory(sigmoidal_map,0,(MaxMap+1)*sizeof(*sigmoidal_map));
3568#if defined(MAGICKCORE_OPENMP_SUPPORT)
3569 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
3570#endif
3571 for (i=0; i <= (long) MaxMap; i++)
3572 {
3573 if (sharpen != MagickFalse)
3574 {
3575 sigmoidal_map[i]=(MagickRealType) ScaleMapToQuantum((MagickRealType)
3576 (MaxMap*((1.0/(1.0+exp(contrast*(midpoint/(double) QuantumRange-
3577 (double) i/MaxMap))))-(1.0/(1.0+exp(contrast*(midpoint/
3578 (double) QuantumRange)))))/((1.0/(1.0+exp(contrast*(midpoint/
3579 (double) QuantumRange-1.0))))-(1.0/(1.0+exp(contrast*(midpoint/
3580 (double) QuantumRange)))))+0.5));
3581 continue;
3582 }
3583 sigmoidal_map[i]=(MagickRealType) ScaleMapToQuantum((MagickRealType)
3584 (MaxMap*(QuantumScale*midpoint-log((1.0-(1.0/(1.0+exp(midpoint/
3585 (double) QuantumRange*contrast))+((double) i/MaxMap)*((1.0/
3586 (1.0+exp(contrast*(midpoint/(double) QuantumRange-1.0))))-(1.0/
3587 (1.0+exp(midpoint/(double) QuantumRange*contrast))))))/
3588 (1.0/(1.0+exp(midpoint/(double) QuantumRange*contrast))+
3589 ((double) i/MaxMap)*((1.0/(1.0+exp(contrast*(midpoint/
3590 (double) QuantumRange-1.0))))-(1.0/(1.0+exp(midpoint/
3591 (double) QuantumRange*contrast))))))/contrast)));
3592 }
3593 if (image->storage_class == PseudoClass)
3594 {
3595 /*
3596 Sigmoidal-contrast enhance colormap.
3597 */
3598#if defined(MAGICKCORE_OPENMP_SUPPORT)
3599 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
3600#endif
3601 for (i=0; i < (long) image->colors; i++)
3602 {
3603 if ((channel & RedChannel) != 0)
3604 image->colormap[i].red=RoundToQuantum(sigmoidal_map[
3605 ScaleQuantumToMap(image->colormap[i].red)]);
3606 if ((channel & GreenChannel) != 0)
3607 image->colormap[i].green=RoundToQuantum(sigmoidal_map[
3608 ScaleQuantumToMap(image->colormap[i].green)]);
3609 if ((channel & BlueChannel) != 0)
3610 image->colormap[i].blue=RoundToQuantum(sigmoidal_map[
3611 ScaleQuantumToMap(image->colormap[i].blue)]);
3612 if ((channel & OpacityChannel) != 0)
3613 image->colormap[i].opacity=RoundToQuantum(sigmoidal_map[
3614 ScaleQuantumToMap(image->colormap[i].opacity)]);
3615 }
3616 }
3617 /*
3618 Sigmoidal-contrast enhance image.
3619 */
3620 status=MagickTrue;
3621 progress=0;
3622 exception=(&image->exception);
3623 image_view=AcquireCacheView(image);
3624#if defined(MAGICKCORE_OPENMP_SUPPORT)
3625 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
3626#endif
3627 for (y=0; y < (long) image->rows; y++)
3628 {
3629 register IndexPacket
3630 *__restrict indexes;
3631
3632 register long
3633 x;
3634
3635 register PixelPacket
3636 *__restrict q;
3637
3638 if (status == MagickFalse)
3639 continue;
3640 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
3641 if (q == (PixelPacket *) NULL)
3642 {
3643 status=MagickFalse;
3644 continue;
3645 }
3646 indexes=GetCacheViewAuthenticIndexQueue(image_view);
3647 for (x=0; x < (long) image->columns; x++)
3648 {
3649 if ((channel & RedChannel) != 0)
3650 q->red=RoundToQuantum(sigmoidal_map[ScaleQuantumToMap(q->red)]);
3651 if ((channel & GreenChannel) != 0)
3652 q->green=RoundToQuantum(sigmoidal_map[ScaleQuantumToMap(q->green)]);
3653 if ((channel & BlueChannel) != 0)
3654 q->blue=RoundToQuantum(sigmoidal_map[ScaleQuantumToMap(q->blue)]);
3655 if ((channel & OpacityChannel) != 0)
3656 q->opacity=RoundToQuantum(sigmoidal_map[ScaleQuantumToMap(q->opacity)]);
3657 if (((channel & IndexChannel) != 0) &&
3658 (image->colorspace == CMYKColorspace))
3659 indexes[x]=(IndexPacket) RoundToQuantum(sigmoidal_map[
3660 ScaleQuantumToMap(indexes[x])]);
3661 q++;
3662 }
3663 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
3664 status=MagickFalse;
3665 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3666 {
3667 MagickBooleanType
3668 proceed;
3669
3670#if defined(MAGICKCORE_OPENMP_SUPPORT)
3671 #pragma omp critical (MagickCore_SigmoidalContrastImageChannel)
3672#endif
3673 proceed=SetImageProgress(image,SigmoidalContrastImageTag,progress++,
3674 image->rows);
3675 if (proceed == MagickFalse)
3676 status=MagickFalse;
3677 }
3678 }
3679 image_view=DestroyCacheView(image_view);
3680 sigmoidal_map=(MagickRealType *) RelinquishMagickMemory(sigmoidal_map);
3681 return(status);
3682}