MagickCore 6.9.13
Loading...
Searching...
No Matches
enhance.c
1/*
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% Cristy %
17% July 1992 %
18% %
19% %
20% Copyright 1999 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% https://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/accelerate-private.h"
45#include "magick/artifact.h"
46#include "magick/attribute.h"
47#include "magick/cache.h"
48#include "magick/cache-view.h"
49#include "magick/channel.h"
50#include "magick/color.h"
51#include "magick/color-private.h"
52#include "magick/colorspace.h"
53#include "magick/colorspace-private.h"
54#include "magick/composite-private.h"
55#include "magick/enhance.h"
56#include "magick/exception.h"
57#include "magick/exception-private.h"
58#include "magick/fx.h"
59#include "magick/gem.h"
60#include "magick/geometry.h"
61#include "magick/histogram.h"
62#include "magick/image.h"
63#include "magick/image-private.h"
64#include "magick/memory_.h"
65#include "magick/monitor.h"
66#include "magick/monitor-private.h"
67#include "magick/opencl.h"
68#include "magick/opencl-private.h"
69#include "magick/option.h"
70#include "magick/pixel-accessor.h"
71#include "magick/pixel-private.h"
72#include "magick/quantum.h"
73#include "magick/quantum-private.h"
74#include "magick/resample.h"
75#include "magick/resample-private.h"
76#include "magick/resource_.h"
77#include "magick/statistic.h"
78#include "magick/string_.h"
79#include "magick/string-private.h"
80#include "magick/thread-private.h"
81#include "magick/threshold.h"
82#include "magick/token.h"
83#include "magick/xml-tree.h"
84
85/*
86%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
87% %
88% %
89% %
90% A u t o G a m m a I m a g e %
91% %
92% %
93% %
94%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
95%
96% AutoGammaImage() extract the 'mean' from the image and adjust the image
97% to try make set its gamma appropriatally.
98%
99% The format of the AutoGammaImage method is:
100%
101% MagickBooleanType AutoGammaImage(Image *image)
102% MagickBooleanType AutoGammaImageChannel(Image *image,
103% const ChannelType channel)
104%
105% A description of each parameter follows:
106%
107% o image: The image to auto-level
108%
109% o channel: The channels to auto-level. If the special 'SyncChannels'
110% flag is set all given channels is adjusted in the same way using the
111% mean average of those channels.
112%
113*/
114
115MagickExport MagickBooleanType AutoGammaImage(Image *image)
116{
117 return(AutoGammaImageChannel(image,DefaultChannels));
118}
119
120MagickExport MagickBooleanType AutoGammaImageChannel(Image *image,
121 const ChannelType channel)
122{
123 double
124 gamma,
125 mean,
126 logmean,
127 sans;
128
129 MagickStatusType
130 status;
131
132 logmean=log(0.5);
133 if ((channel & SyncChannels) != 0)
134 {
135 /*
136 Apply gamma correction equally accross all given channels
137 */
138 (void) GetImageChannelMean(image,channel,&mean,&sans,&image->exception);
139 gamma=log(mean*QuantumScale)/logmean;
140 return(LevelImageChannel(image,channel,0.0,(double) QuantumRange,gamma));
141 }
142 /*
143 Auto-gamma each channel separateally
144 */
145 status = MagickTrue;
146 if ((channel & RedChannel) != 0)
147 {
148 (void) GetImageChannelMean(image,RedChannel,&mean,&sans,
149 &image->exception);
150 gamma=log(mean*QuantumScale)/logmean;
151 status&=LevelImageChannel(image,RedChannel,0.0,(double) QuantumRange,
152 gamma);
153 }
154 if ((channel & GreenChannel) != 0)
155 {
156 (void) GetImageChannelMean(image,GreenChannel,&mean,&sans,
157 &image->exception);
158 gamma=log(mean*QuantumScale)/logmean;
159 status&=LevelImageChannel(image,GreenChannel,0.0,(double) QuantumRange,
160 gamma);
161 }
162 if ((channel & BlueChannel) != 0)
163 {
164 (void) GetImageChannelMean(image,BlueChannel,&mean,&sans,
165 &image->exception);
166 gamma=log(mean*QuantumScale)/logmean;
167 status&=LevelImageChannel(image,BlueChannel,0.0,(double) QuantumRange,
168 gamma);
169 }
170 if (((channel & OpacityChannel) != 0) &&
171 (image->matte != MagickFalse))
172 {
173 (void) GetImageChannelMean(image,OpacityChannel,&mean,&sans,
174 &image->exception);
175 gamma=log(mean*QuantumScale)/logmean;
176 status&=LevelImageChannel(image,OpacityChannel,0.0,(double) QuantumRange,
177 gamma);
178 }
179 if (((channel & IndexChannel) != 0) &&
180 (image->colorspace == CMYKColorspace))
181 {
182 (void) GetImageChannelMean(image,IndexChannel,&mean,&sans,
183 &image->exception);
184 gamma=log(mean*QuantumScale)/logmean;
185 status&=LevelImageChannel(image,IndexChannel,0.0,(double) QuantumRange,
186 gamma);
187 }
188 return(status != 0 ? MagickTrue : MagickFalse);
189}
190
191/*
192%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
193% %
194% %
195% %
196% A u t o L e v e l I m a g e %
197% %
198% %
199% %
200%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
201%
202% AutoLevelImage() adjusts the levels of a particular image channel by
203% scaling the minimum and maximum values to the full quantum range.
204%
205% The format of the LevelImage method is:
206%
207% MagickBooleanType AutoLevelImage(Image *image)
208% MagickBooleanType AutoLevelImageChannel(Image *image,
209% const ChannelType channel)
210%
211% A description of each parameter follows:
212%
213% o image: The image to auto-level
214%
215% o channel: The channels to auto-level. If the special 'SyncChannels'
216% flag is set the min/max/mean value of all given channels is used for
217% all given channels, to all channels in the same way.
218%
219*/
220
221MagickExport MagickBooleanType AutoLevelImage(Image *image)
222{
223 return(AutoLevelImageChannel(image,DefaultChannels));
224}
225
226MagickExport MagickBooleanType AutoLevelImageChannel(Image *image,
227 const ChannelType channel)
228{
229 /*
230 Convenience method for a min/max histogram stretch.
231 */
232 return(MinMaxStretchImage(image,channel,0.0,0.0));
233}
234
235/*
236%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
237% %
238% %
239% %
240% B r i g h t n e s s C o n t r a s t I m a g e %
241% %
242% %
243% %
244%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
245%
246% BrightnessContrastImage() changes the brightness and/or contrast of an
247% image. It converts the brightness and contrast parameters into slope and
248% intercept and calls a polynomial function to apply to the image.
249%
250% The format of the BrightnessContrastImage method is:
251%
252% MagickBooleanType BrightnessContrastImage(Image *image,
253% const double brightness,const double contrast)
254% MagickBooleanType BrightnessContrastImageChannel(Image *image,
255% const ChannelType channel,const double brightness,
256% const double contrast)
257%
258% A description of each parameter follows:
259%
260% o image: the image.
261%
262% o channel: the channel.
263%
264% o brightness: the brightness percent (-100 .. 100).
265%
266% o contrast: the contrast percent (-100 .. 100).
267%
268*/
269
270MagickExport MagickBooleanType BrightnessContrastImage(Image *image,
271 const double brightness,const double contrast)
272{
273 MagickBooleanType
274 status;
275
276 status=BrightnessContrastImageChannel(image,DefaultChannels,brightness,
277 contrast);
278 return(status);
279}
280
281MagickExport MagickBooleanType BrightnessContrastImageChannel(Image *image,
282 const ChannelType channel,const double brightness,const double contrast)
283{
284#define BrightnessContrastImageTag "BrightnessContrast/Image"
285
286 double
287 intercept,
288 coefficients[2],
289 slope;
290
291 MagickBooleanType
292 status;
293
294 /*
295 Compute slope and intercept.
296 */
297 assert(image != (Image *) NULL);
298 assert(image->signature == MagickCoreSignature);
299 if (IsEventLogging() != MagickFalse)
300 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
301 slope=100.0*PerceptibleReciprocal(100.0-contrast);
302 if (contrast < 0.0)
303 slope=0.01*contrast+1.0;
304 intercept=(0.01*brightness-0.5)*slope+0.5;
305 coefficients[0]=slope;
306 coefficients[1]=intercept;
307 status=FunctionImageChannel(image,channel,PolynomialFunction,2,coefficients,
308 &image->exception);
309 return(status);
310}
311
312/*
313%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
314% %
315% %
316% %
317% C o l o r D e c i s i o n L i s t I m a g e %
318% %
319% %
320% %
321%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
322%
323% ColorDecisionListImage() accepts a lightweight Color Correction Collection
324% (CCC) file which solely contains one or more color corrections and applies
325% the correction to the image. Here is a sample CCC file:
326%
327% <ColorCorrectionCollection xmlns="urn:ASC:CDL:v1.2">
328% <ColorCorrection id="cc03345">
329% <SOPNode>
330% <Slope> 0.9 1.2 0.5 </Slope>
331% <Offset> 0.4 -0.5 0.6 </Offset>
332% <Power> 1.0 0.8 1.5 </Power>
333% </SOPNode>
334% <SATNode>
335% <Saturation> 0.85 </Saturation>
336% </SATNode>
337% </ColorCorrection>
338% </ColorCorrectionCollection>
339%
340% which includes the slop, offset, and power for each of the RGB channels
341% as well as the saturation.
342%
343% The format of the ColorDecisionListImage method is:
344%
345% MagickBooleanType ColorDecisionListImage(Image *image,
346% const char *color_correction_collection)
347%
348% A description of each parameter follows:
349%
350% o image: the image.
351%
352% o color_correction_collection: the color correction collection in XML.
353%
354*/
355MagickExport MagickBooleanType ColorDecisionListImage(Image *image,
356 const char *color_correction_collection)
357{
358#define ColorDecisionListCorrectImageTag "ColorDecisionList/Image"
359
360 typedef struct _Correction
361 {
362 double
363 slope,
364 offset,
365 power;
366 } Correction;
367
368 typedef struct _ColorCorrection
369 {
370 Correction
371 red,
372 green,
373 blue;
374
375 double
376 saturation;
377 } ColorCorrection;
378
380 *image_view;
381
382 char
383 token[MaxTextExtent];
384
385 ColorCorrection
386 color_correction;
387
388 const char
389 *content,
390 *p;
391
393 *exception;
394
395 MagickBooleanType
396 status;
397
398 MagickOffsetType
399 progress;
400
402 *cdl_map;
403
404 ssize_t
405 i;
406
407 ssize_t
408 y;
409
411 *cc,
412 *ccc,
413 *sat,
414 *sop;
415
416 /*
417 Allocate and initialize cdl maps.
418 */
419 assert(image != (Image *) NULL);
420 assert(image->signature == MagickCoreSignature);
421 if (IsEventLogging() != MagickFalse)
422 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
423 if (color_correction_collection == (const char *) NULL)
424 return(MagickFalse);
425 exception=(&image->exception);
426 ccc=NewXMLTree((const char *) color_correction_collection,&image->exception);
427 if (ccc == (XMLTreeInfo *) NULL)
428 return(MagickFalse);
429 cc=GetXMLTreeChild(ccc,"ColorCorrection");
430 if (cc == (XMLTreeInfo *) NULL)
431 {
432 ccc=DestroyXMLTree(ccc);
433 return(MagickFalse);
434 }
435 color_correction.red.slope=1.0;
436 color_correction.red.offset=0.0;
437 color_correction.red.power=1.0;
438 color_correction.green.slope=1.0;
439 color_correction.green.offset=0.0;
440 color_correction.green.power=1.0;
441 color_correction.blue.slope=1.0;
442 color_correction.blue.offset=0.0;
443 color_correction.blue.power=1.0;
444 color_correction.saturation=0.0;
445 sop=GetXMLTreeChild(cc,"SOPNode");
446 if (sop != (XMLTreeInfo *) NULL)
447 {
449 *offset,
450 *power,
451 *slope;
452
453 slope=GetXMLTreeChild(sop,"Slope");
454 if (slope != (XMLTreeInfo *) NULL)
455 {
456 content=GetXMLTreeContent(slope);
457 p=(const char *) content;
458 for (i=0; (*p != '\0') && (i < 3); i++)
459 {
460 (void) GetNextToken(p,&p,MaxTextExtent,token);
461 if (*token == ',')
462 (void) GetNextToken(p,&p,MaxTextExtent,token);
463 switch (i)
464 {
465 case 0:
466 {
467 color_correction.red.slope=StringToDouble(token,(char **) NULL);
468 break;
469 }
470 case 1:
471 {
472 color_correction.green.slope=StringToDouble(token,
473 (char **) NULL);
474 break;
475 }
476 case 2:
477 {
478 color_correction.blue.slope=StringToDouble(token,
479 (char **) NULL);
480 break;
481 }
482 }
483 }
484 }
485 offset=GetXMLTreeChild(sop,"Offset");
486 if (offset != (XMLTreeInfo *) NULL)
487 {
488 content=GetXMLTreeContent(offset);
489 p=(const char *) content;
490 for (i=0; (*p != '\0') && (i < 3); i++)
491 {
492 (void) GetNextToken(p,&p,MaxTextExtent,token);
493 if (*token == ',')
494 (void) GetNextToken(p,&p,MaxTextExtent,token);
495 switch (i)
496 {
497 case 0:
498 {
499 color_correction.red.offset=StringToDouble(token,
500 (char **) NULL);
501 break;
502 }
503 case 1:
504 {
505 color_correction.green.offset=StringToDouble(token,
506 (char **) NULL);
507 break;
508 }
509 case 2:
510 {
511 color_correction.blue.offset=StringToDouble(token,
512 (char **) NULL);
513 break;
514 }
515 }
516 }
517 }
518 power=GetXMLTreeChild(sop,"Power");
519 if (power != (XMLTreeInfo *) NULL)
520 {
521 content=GetXMLTreeContent(power);
522 p=(const char *) content;
523 for (i=0; (*p != '\0') && (i < 3); i++)
524 {
525 (void) GetNextToken(p,&p,MaxTextExtent,token);
526 if (*token == ',')
527 (void) GetNextToken(p,&p,MaxTextExtent,token);
528 switch (i)
529 {
530 case 0:
531 {
532 color_correction.red.power=StringToDouble(token,(char **) NULL);
533 break;
534 }
535 case 1:
536 {
537 color_correction.green.power=StringToDouble(token,
538 (char **) NULL);
539 break;
540 }
541 case 2:
542 {
543 color_correction.blue.power=StringToDouble(token,
544 (char **) NULL);
545 break;
546 }
547 }
548 }
549 }
550 }
551 sat=GetXMLTreeChild(cc,"SATNode");
552 if (sat != (XMLTreeInfo *) NULL)
553 {
555 *saturation;
556
557 saturation=GetXMLTreeChild(sat,"Saturation");
558 if (saturation != (XMLTreeInfo *) NULL)
559 {
560 content=GetXMLTreeContent(saturation);
561 p=(const char *) content;
562 (void) GetNextToken(p,&p,MaxTextExtent,token);
563 color_correction.saturation=StringToDouble(token,(char **) NULL);
564 }
565 }
566 ccc=DestroyXMLTree(ccc);
567 if (image->debug != MagickFalse)
568 {
569 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
570 " Color Correction Collection:");
571 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
572 " color_correction.red.slope: %g",color_correction.red.slope);
573 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
574 " color_correction.red.offset: %g",color_correction.red.offset);
575 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
576 " color_correction.red.power: %g",color_correction.red.power);
577 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
578 " color_correction.green.slope: %g",color_correction.green.slope);
579 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
580 " color_correction.green.offset: %g",color_correction.green.offset);
581 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
582 " color_correction.green.power: %g",color_correction.green.power);
583 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
584 " color_correction.blue.slope: %g",color_correction.blue.slope);
585 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
586 " color_correction.blue.offset: %g",color_correction.blue.offset);
587 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
588 " color_correction.blue.power: %g",color_correction.blue.power);
589 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
590 " color_correction.saturation: %g",color_correction.saturation);
591 }
592 cdl_map=(PixelPacket *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*cdl_map));
593 if (cdl_map == (PixelPacket *) NULL)
594 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
595 image->filename);
596 for (i=0; i <= (ssize_t) MaxMap; i++)
597 {
598 cdl_map[i].red=ClampToQuantum((MagickRealType) ScaleMapToQuantum((
599 MagickRealType) (MaxMap*(pow(color_correction.red.slope*i/MaxMap+
600 color_correction.red.offset,color_correction.red.power)))));
601 cdl_map[i].green=ClampToQuantum((MagickRealType) ScaleMapToQuantum((
602 MagickRealType) (MaxMap*(pow(color_correction.green.slope*i/MaxMap+
603 color_correction.green.offset,color_correction.green.power)))));
604 cdl_map[i].blue=ClampToQuantum((MagickRealType) ScaleMapToQuantum((
605 MagickRealType) (MaxMap*(pow(color_correction.blue.slope*i/MaxMap+
606 color_correction.blue.offset,color_correction.blue.power)))));
607 }
608 if (image->storage_class == PseudoClass)
609 {
610 /*
611 Apply transfer function to colormap.
612 */
613 for (i=0; i < (ssize_t) image->colors; i++)
614 {
615 double
616 luma;
617
618 luma=0.212656*(double) image->colormap[i].red+0.715158*(double)
619 image->colormap[i].green+0.072186*(double) image->colormap[i].blue;
620 image->colormap[i].red=ClampToQuantum(luma+color_correction.saturation*
621 (double) cdl_map[ScaleQuantumToMap(image->colormap[i].red)].red-luma);
622 image->colormap[i].green=ClampToQuantum(luma+
623 color_correction.saturation*(double) cdl_map[ScaleQuantumToMap(
624 image->colormap[i].green)].green-luma);
625 image->colormap[i].blue=ClampToQuantum(luma+color_correction.saturation*
626 (double) cdl_map[ScaleQuantumToMap(image->colormap[i].blue)].blue-
627 luma);
628 }
629 }
630 /*
631 Apply transfer function to image.
632 */
633 status=MagickTrue;
634 progress=0;
635 image_view=AcquireAuthenticCacheView(image,exception);
636#if defined(MAGICKCORE_OPENMP_SUPPORT)
637 #pragma omp parallel for schedule(static) shared(progress,status) \
638 magick_number_threads(image,image,image->rows,1)
639#endif
640 for (y=0; y < (ssize_t) image->rows; y++)
641 {
642 double
643 luma;
644
646 *magick_restrict q;
647
648 ssize_t
649 x;
650
651 if (status == MagickFalse)
652 continue;
653 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
654 if (q == (PixelPacket *) NULL)
655 {
656 status=MagickFalse;
657 continue;
658 }
659 for (x=0; x < (ssize_t) image->columns; x++)
660 {
661 luma=0.212656*(double) GetPixelRed(q)+0.715158*(double) GetPixelGreen(q)+
662 0.072186*(double) GetPixelBlue(q);
663 SetPixelRed(q,ClampToQuantum(luma+color_correction.saturation*
664 ((double) cdl_map[ScaleQuantumToMap(GetPixelRed(q))].red-luma)));
665 SetPixelGreen(q,ClampToQuantum(luma+color_correction.saturation*
666 ((double) cdl_map[ScaleQuantumToMap(GetPixelGreen(q))].green-luma)));
667 SetPixelBlue(q,ClampToQuantum(luma+color_correction.saturation*
668 ((double) cdl_map[ScaleQuantumToMap(GetPixelBlue(q))].blue-luma)));
669 q++;
670 }
671 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
672 status=MagickFalse;
673 if (image->progress_monitor != (MagickProgressMonitor) NULL)
674 {
675 MagickBooleanType
676 proceed;
677
678#if defined(MAGICKCORE_OPENMP_SUPPORT)
679 #pragma omp atomic
680#endif
681 progress++;
682 proceed=SetImageProgress(image,ColorDecisionListCorrectImageTag,
683 progress,image->rows);
684 if (proceed == MagickFalse)
685 status=MagickFalse;
686 }
687 }
688 image_view=DestroyCacheView(image_view);
689 cdl_map=(PixelPacket *) RelinquishMagickMemory(cdl_map);
690 return(status);
691}
692
693/*
694%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
695% %
696% %
697% %
698% C l u t I m a g e %
699% %
700% %
701% %
702%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
703%
704% ClutImage() replaces each color value in the given image, by using it as an
705% index to lookup a replacement color value in a Color Look UP Table in the
706% form of an image. The values are extracted along a diagonal of the CLUT
707% image so either a horizontal or vertical gradient image can be used.
708%
709% Typically this is used to either re-color a gray-scale image according to a
710% color gradient in the CLUT image, or to perform a freeform histogram
711% (level) adjustment according to the (typically gray-scale) gradient in the
712% CLUT image.
713%
714% When the 'channel' mask includes the matte/alpha transparency channel but
715% one image has no such channel it is assumed that image is a simple
716% gray-scale image that will effect the alpha channel values, either for
717% gray-scale coloring (with transparent or semi-transparent colors), or
718% a histogram adjustment of existing alpha channel values. If both images
719% have matte channels, direct and normal indexing is applied, which is rarely
720% used.
721%
722% The format of the ClutImage method is:
723%
724% MagickBooleanType ClutImage(Image *image,Image *clut_image)
725% MagickBooleanType ClutImageChannel(Image *image,
726% const ChannelType channel,Image *clut_image)
727%
728% A description of each parameter follows:
729%
730% o image: the image, which is replaced by indexed CLUT values
731%
732% o clut_image: the color lookup table image for replacement color values.
733%
734% o channel: the channel.
735%
736*/
737
738MagickExport MagickBooleanType ClutImage(Image *image,const Image *clut_image)
739{
740 return(ClutImageChannel(image,DefaultChannels,clut_image));
741}
742
743MagickExport MagickBooleanType ClutImageChannel(Image *image,
744 const ChannelType channel,const Image *clut_image)
745{
746#define ClutImageTag "Clut/Image"
747
749 *clut_view,
750 *image_view;
751
753 *exception;
754
755 MagickBooleanType
756 status;
757
758 MagickOffsetType
759 progress;
760
762 *clut_map;
763
764 ssize_t
765 i;
766
767 ssize_t
768 adjust,
769 y;
770
771 assert(image != (Image *) NULL);
772 assert(image->signature == MagickCoreSignature);
773 assert(clut_image != (Image *) NULL);
774 assert(clut_image->signature == MagickCoreSignature);
775 if (IsEventLogging() != MagickFalse)
776 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
777 exception=(&image->exception);
778 if (SetImageStorageClass(image,DirectClass) == MagickFalse)
779 return(MagickFalse);
780 if ((IsGrayColorspace(image->colorspace) != MagickFalse) &&
781 (IsGrayColorspace(clut_image->colorspace) == MagickFalse))
782 (void) SetImageColorspace(image,sRGBColorspace);
783 clut_map=(MagickPixelPacket *) AcquireQuantumMemory(MaxMap+1UL,
784 sizeof(*clut_map));
785 if (clut_map == (MagickPixelPacket *) NULL)
786 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
787 image->filename);
788 /*
789 Clut image.
790 */
791 status=MagickTrue;
792 progress=0;
793 adjust=(ssize_t) (clut_image->interpolate == IntegerInterpolatePixel ? 0 : 1);
794 clut_view=AcquireAuthenticCacheView(clut_image,exception);
795 for (i=0; i <= (ssize_t) MaxMap; i++)
796 {
797 GetMagickPixelPacket(clut_image,clut_map+i);
798 status=InterpolateMagickPixelPacket(clut_image,clut_view,
799 UndefinedInterpolatePixel,(double) i*(clut_image->columns-adjust)/MaxMap,
800 (double) i*(clut_image->rows-adjust)/MaxMap,clut_map+i,exception);
801 if (status == MagickFalse)
802 break;
803 }
804 clut_view=DestroyCacheView(clut_view);
805 image_view=AcquireAuthenticCacheView(image,exception);
806#if defined(MAGICKCORE_OPENMP_SUPPORT)
807 #pragma omp parallel for schedule(static) shared(progress,status) \
808 magick_number_threads(image,image,image->rows,1)
809#endif
810 for (y=0; y < (ssize_t) image->rows; y++)
811 {
813 pixel;
814
815 IndexPacket
816 *magick_restrict indexes;
817
819 *magick_restrict q;
820
821 ssize_t
822 x;
823
824 if (status == MagickFalse)
825 continue;
826 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
827 if (q == (PixelPacket *) NULL)
828 {
829 status=MagickFalse;
830 continue;
831 }
832 indexes=GetCacheViewAuthenticIndexQueue(image_view);
833 GetMagickPixelPacket(image,&pixel);
834 for (x=0; x < (ssize_t) image->columns; x++)
835 {
836 SetMagickPixelPacket(image,q,indexes+x,&pixel);
837 if ((channel & RedChannel) != 0)
838 SetPixelRed(q,ClampPixelRed(clut_map+
839 ScaleQuantumToMap(GetPixelRed(q))));
840 if ((channel & GreenChannel) != 0)
841 SetPixelGreen(q,ClampPixelGreen(clut_map+
842 ScaleQuantumToMap(GetPixelGreen(q))));
843 if ((channel & BlueChannel) != 0)
844 SetPixelBlue(q,ClampPixelBlue(clut_map+
845 ScaleQuantumToMap(GetPixelBlue(q))));
846 if ((channel & OpacityChannel) != 0)
847 {
848 if (clut_image->matte == MagickFalse)
849 SetPixelAlpha(q,MagickPixelIntensityToQuantum(clut_map+
850 ScaleQuantumToMap((Quantum) GetPixelAlpha(q))));
851 else
852 if (image->matte == MagickFalse)
853 SetPixelOpacity(q,ClampPixelOpacity(clut_map+
854 ScaleQuantumToMap((Quantum) MagickPixelIntensity(&pixel))));
855 else
856 SetPixelOpacity(q,ClampPixelOpacity(
857 clut_map+ScaleQuantumToMap(GetPixelOpacity(q))));
858 }
859 if (((channel & IndexChannel) != 0) &&
860 (image->colorspace == CMYKColorspace))
861 SetPixelIndex(indexes+x,ClampToQuantum((clut_map+(ssize_t)
862 GetPixelIndex(indexes+x))->index));
863 q++;
864 }
865 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
866 status=MagickFalse;
867 if (image->progress_monitor != (MagickProgressMonitor) NULL)
868 {
869 MagickBooleanType
870 proceed;
871
872#if defined(MAGICKCORE_OPENMP_SUPPORT)
873 #pragma omp atomic
874#endif
875 progress++;
876 proceed=SetImageProgress(image,ClutImageTag,progress,image->rows);
877 if (proceed == MagickFalse)
878 status=MagickFalse;
879 }
880 }
881 image_view=DestroyCacheView(image_view);
882 clut_map=(MagickPixelPacket *) RelinquishMagickMemory(clut_map);
883 if ((clut_image->matte != MagickFalse) && ((channel & OpacityChannel) != 0))
884 (void) SetImageAlphaChannel(image,ActivateAlphaChannel);
885 return(status);
886}
887
888/*
889%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
890% %
891% %
892% %
893% C o n t r a s t I m a g e %
894% %
895% %
896% %
897%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
898%
899% ContrastImage() enhances the intensity differences between the lighter and
900% darker elements of the image. Set sharpen to a MagickTrue to increase the
901% image contrast otherwise the contrast is reduced.
902%
903% The format of the ContrastImage method is:
904%
905% MagickBooleanType ContrastImage(Image *image,
906% const MagickBooleanType sharpen)
907%
908% A description of each parameter follows:
909%
910% o image: the image.
911%
912% o sharpen: Increase or decrease image contrast.
913%
914*/
915
916static inline void Contrast(const int sign,Quantum *red,Quantum *green,
917 Quantum *blue)
918{
919 double
920 brightness = 0.0,
921 hue = 0.0,
922 saturation = 0.0;
923
924 /*
925 Enhance contrast: dark color become darker, light color become lighter.
926 */
927 ConvertRGBToHSB(*red,*green,*blue,&hue,&saturation,&brightness);
928 brightness+=0.5*sign*(0.5*(sin((double) (MagickPI*(brightness-0.5)))+1.0)-
929 brightness);
930 if (brightness > 1.0)
931 brightness=1.0;
932 else
933 if (brightness < 0.0)
934 brightness=0.0;
935 ConvertHSBToRGB(hue,saturation,brightness,red,green,blue);
936}
937
938MagickExport MagickBooleanType ContrastImage(Image *image,
939 const MagickBooleanType sharpen)
940{
941#define ContrastImageTag "Contrast/Image"
942
944 *image_view;
945
947 *exception;
948
949 int
950 sign;
951
952 MagickBooleanType
953 status;
954
955 MagickOffsetType
956 progress;
957
958 ssize_t
959 i;
960
961 ssize_t
962 y;
963 assert(image != (Image *) NULL);
964 assert(image->signature == MagickCoreSignature);
965 if (IsEventLogging() != MagickFalse)
966 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
967 sign=sharpen != MagickFalse ? 1 : -1;
968 if (image->storage_class == PseudoClass)
969 {
970 /*
971 Contrast enhance colormap.
972 */
973 for (i=0; i < (ssize_t) image->colors; i++)
974 Contrast(sign,&image->colormap[i].red,&image->colormap[i].green,
975 &image->colormap[i].blue);
976 }
977 /*
978 Contrast enhance image.
979 */
980#if defined(MAGICKCORE_OPENCL_SUPPORT)
981 status=AccelerateContrastImage(image,sharpen,&image->exception);
982 if (status != MagickFalse)
983 return status;
984#endif
985 status=MagickTrue;
986 progress=0;
987 exception=(&image->exception);
988 image_view=AcquireAuthenticCacheView(image,exception);
989#if defined(MAGICKCORE_OPENMP_SUPPORT)
990 #pragma omp parallel for schedule(static) shared(progress,status) \
991 magick_number_threads(image,image,image->rows,1)
992#endif
993 for (y=0; y < (ssize_t) image->rows; y++)
994 {
995 Quantum
996 blue,
997 green,
998 red;
999
1001 *magick_restrict q;
1002
1003 ssize_t
1004 x;
1005
1006 if (status == MagickFalse)
1007 continue;
1008 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
1009 if (q == (PixelPacket *) NULL)
1010 {
1011 status=MagickFalse;
1012 continue;
1013 }
1014 for (x=0; x < (ssize_t) image->columns; x++)
1015 {
1016 red=GetPixelRed(q);
1017 green=GetPixelGreen(q);
1018 blue=GetPixelBlue(q);
1019 Contrast(sign,&red,&green,&blue);
1020 SetPixelRed(q,red);
1021 SetPixelGreen(q,green);
1022 SetPixelBlue(q,blue);
1023 q++;
1024 }
1025 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
1026 status=MagickFalse;
1027 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1028 {
1029 MagickBooleanType
1030 proceed;
1031
1032#if defined(MAGICKCORE_OPENMP_SUPPORT)
1033 #pragma omp atomic
1034#endif
1035 progress++;
1036 proceed=SetImageProgress(image,ContrastImageTag,progress,image->rows);
1037 if (proceed == MagickFalse)
1038 status=MagickFalse;
1039 }
1040 }
1041 image_view=DestroyCacheView(image_view);
1042 return(status);
1043}
1044
1045/*
1046%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1047% %
1048% %
1049% %
1050% C o n t r a s t S t r e t c h I m a g e %
1051% %
1052% %
1053% %
1054%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1055%
1056% ContrastStretchImage() is a simple image enhancement technique that attempts
1057% to improve the contrast in an image by `stretching' the range of intensity
1058% values it contains to span a desired range of values. It differs from the
1059% more sophisticated histogram equalization in that it can only apply a
1060% linear scaling function to the image pixel values. As a result the
1061% `enhancement' is less harsh.
1062%
1063% The format of the ContrastStretchImage method is:
1064%
1065% MagickBooleanType ContrastStretchImage(Image *image,
1066% const char *levels)
1067% MagickBooleanType ContrastStretchImageChannel(Image *image,
1068% const size_t channel,const double black_point,
1069% const double white_point)
1070%
1071% A description of each parameter follows:
1072%
1073% o image: the image.
1074%
1075% o channel: the channel.
1076%
1077% o black_point: the black point.
1078%
1079% o white_point: the white point.
1080%
1081% o levels: Specify the levels where the black and white points have the
1082% range of 0 to number-of-pixels (e.g. 1%, 10x90%, etc.).
1083%
1084*/
1085
1086MagickExport MagickBooleanType ContrastStretchImage(Image *image,
1087 const char *levels)
1088{
1089 double
1090 black_point = 0.0,
1091 white_point = (double) image->columns*image->rows;
1092
1094 geometry_info;
1095
1096 MagickBooleanType
1097 status;
1098
1099 MagickStatusType
1100 flags;
1101
1102 /*
1103 Parse levels.
1104 */
1105 if (levels == (char *) NULL)
1106 return(MagickFalse);
1107 flags=ParseGeometry(levels,&geometry_info);
1108 if ((flags & RhoValue) != 0)
1109 black_point=geometry_info.rho;
1110 if ((flags & SigmaValue) != 0)
1111 white_point=geometry_info.sigma;
1112 if ((flags & PercentValue) != 0)
1113 {
1114 black_point*=(double) QuantumRange/100.0;
1115 white_point*=(double) QuantumRange/100.0;
1116 }
1117 if ((flags & SigmaValue) == 0)
1118 white_point=(double) image->columns*image->rows-black_point;
1119 status=ContrastStretchImageChannel(image,DefaultChannels,black_point,
1120 white_point);
1121 return(status);
1122}
1123
1124MagickExport MagickBooleanType ContrastStretchImageChannel(Image *image,
1125 const ChannelType channel,const double black_point,const double white_point)
1126{
1127#define MaxRange(color) ((MagickRealType) ScaleQuantumToMap((Quantum) (color)))
1128#define ContrastStretchImageTag "ContrastStretch/Image"
1129
1130 CacheView
1131 *image_view;
1132
1133 double
1134 intensity;
1135
1137 *exception;
1138
1139 MagickBooleanType
1140 status;
1141
1142 MagickOffsetType
1143 progress;
1144
1146 black,
1147 *histogram,
1148 white;
1149
1151 *stretch_map;
1152
1153 ssize_t
1154 i;
1155
1156 ssize_t
1157 y;
1158
1159 /*
1160 Allocate histogram and stretch map.
1161 */
1162 assert(image != (Image *) NULL);
1163 assert(image->signature == MagickCoreSignature);
1164 if (IsEventLogging() != MagickFalse)
1165 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1166 exception=(&image->exception);
1167
1168#if defined(MAGICKCORE_OPENCL_SUPPORT) && 0
1169 /* Call OpenCL version */
1170 status=AccelerateContrastStretchImageChannel(image,channel,black_point,
1171 white_point,&image->exception);
1172 if (status != MagickFalse)
1173 return status;
1174#endif
1175 histogram=(MagickPixelPacket *) AcquireQuantumMemory(MaxMap+1UL,
1176 sizeof(*histogram));
1177 stretch_map=(QuantumPixelPacket *) AcquireQuantumMemory(MaxMap+1UL,
1178 sizeof(*stretch_map));
1179 if ((histogram == (MagickPixelPacket *) NULL) ||
1180 (stretch_map == (QuantumPixelPacket *) NULL))
1181 {
1182 if (stretch_map != (QuantumPixelPacket *) NULL)
1183 stretch_map=(QuantumPixelPacket *) RelinquishMagickMemory(stretch_map);
1184 if (histogram != (MagickPixelPacket *) NULL)
1185 histogram=(MagickPixelPacket *) RelinquishMagickMemory(histogram);
1186 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
1187 image->filename);
1188 }
1189 /*
1190 Form histogram.
1191 */
1192 if (SetImageGray(image,exception) != MagickFalse)
1193 (void) SetImageColorspace(image,GRAYColorspace);
1194 status=MagickTrue;
1195 (void) memset(histogram,0,(MaxMap+1)*sizeof(*histogram));
1196 image_view=AcquireAuthenticCacheView(image,exception);
1197 for (y=0; y < (ssize_t) image->rows; y++)
1198 {
1199 const PixelPacket
1200 *magick_restrict p;
1201
1202 IndexPacket
1203 *magick_restrict indexes;
1204
1205 ssize_t
1206 x;
1207
1208 if (status == MagickFalse)
1209 continue;
1210 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
1211 if (p == (const PixelPacket *) NULL)
1212 {
1213 status=MagickFalse;
1214 continue;
1215 }
1216 indexes=GetCacheViewAuthenticIndexQueue(image_view);
1217 if ((channel & SyncChannels) != 0)
1218 for (x=0; x < (ssize_t) image->columns; x++)
1219 {
1220 Quantum
1221 intensity;
1222
1223 intensity=ClampToQuantum(GetPixelIntensity(image,p));
1224 histogram[ScaleQuantumToMap(intensity)].red++;
1225 histogram[ScaleQuantumToMap(intensity)].green++;
1226 histogram[ScaleQuantumToMap(intensity)].blue++;
1227 histogram[ScaleQuantumToMap(intensity)].index++;
1228 p++;
1229 }
1230 else
1231 for (x=0; x < (ssize_t) image->columns; x++)
1232 {
1233 if ((channel & RedChannel) != 0)
1234 histogram[ScaleQuantumToMap(GetPixelRed(p))].red++;
1235 if ((channel & GreenChannel) != 0)
1236 histogram[ScaleQuantumToMap(GetPixelGreen(p))].green++;
1237 if ((channel & BlueChannel) != 0)
1238 histogram[ScaleQuantumToMap(GetPixelBlue(p))].blue++;
1239 if ((channel & OpacityChannel) != 0)
1240 histogram[ScaleQuantumToMap(GetPixelOpacity(p))].opacity++;
1241 if (((channel & IndexChannel) != 0) &&
1242 (image->colorspace == CMYKColorspace))
1243 histogram[ScaleQuantumToMap(GetPixelIndex(indexes+x))].index++;
1244 p++;
1245 }
1246 }
1247 /*
1248 Find the histogram boundaries by locating the black/white levels.
1249 */
1250 black.red=0.0;
1251 white.red=MaxRange(QuantumRange);
1252 if ((channel & RedChannel) != 0)
1253 {
1254 intensity=0.0;
1255 for (i=0; i <= (ssize_t) MaxMap; i++)
1256 {
1257 intensity+=histogram[i].red;
1258 if (intensity > black_point)
1259 break;
1260 }
1261 black.red=(MagickRealType) i;
1262 intensity=0.0;
1263 for (i=(ssize_t) MaxMap; i != 0; i--)
1264 {
1265 intensity+=histogram[i].red;
1266 if (intensity > ((double) image->columns*image->rows-white_point))
1267 break;
1268 }
1269 white.red=(MagickRealType) i;
1270 }
1271 black.green=0.0;
1272 white.green=MaxRange(QuantumRange);
1273 if ((channel & GreenChannel) != 0)
1274 {
1275 intensity=0.0;
1276 for (i=0; i <= (ssize_t) MaxMap; i++)
1277 {
1278 intensity+=histogram[i].green;
1279 if (intensity > black_point)
1280 break;
1281 }
1282 black.green=(MagickRealType) i;
1283 intensity=0.0;
1284 for (i=(ssize_t) MaxMap; i != 0; i--)
1285 {
1286 intensity+=histogram[i].green;
1287 if (intensity > ((double) image->columns*image->rows-white_point))
1288 break;
1289 }
1290 white.green=(MagickRealType) i;
1291 }
1292 black.blue=0.0;
1293 white.blue=MaxRange(QuantumRange);
1294 if ((channel & BlueChannel) != 0)
1295 {
1296 intensity=0.0;
1297 for (i=0; i <= (ssize_t) MaxMap; i++)
1298 {
1299 intensity+=histogram[i].blue;
1300 if (intensity > black_point)
1301 break;
1302 }
1303 black.blue=(MagickRealType) i;
1304 intensity=0.0;
1305 for (i=(ssize_t) MaxMap; i != 0; i--)
1306 {
1307 intensity+=histogram[i].blue;
1308 if (intensity > ((double) image->columns*image->rows-white_point))
1309 break;
1310 }
1311 white.blue=(MagickRealType) i;
1312 }
1313 black.opacity=0.0;
1314 white.opacity=MaxRange(QuantumRange);
1315 if ((channel & OpacityChannel) != 0)
1316 {
1317 intensity=0.0;
1318 for (i=0; i <= (ssize_t) MaxMap; i++)
1319 {
1320 intensity+=histogram[i].opacity;
1321 if (intensity > black_point)
1322 break;
1323 }
1324 black.opacity=(MagickRealType) i;
1325 intensity=0.0;
1326 for (i=(ssize_t) MaxMap; i != 0; i--)
1327 {
1328 intensity+=histogram[i].opacity;
1329 if (intensity > ((double) image->columns*image->rows-white_point))
1330 break;
1331 }
1332 white.opacity=(MagickRealType) i;
1333 }
1334 black.index=0.0;
1335 white.index=MaxRange(QuantumRange);
1336 if (((channel & IndexChannel) != 0) && (image->colorspace == CMYKColorspace))
1337 {
1338 intensity=0.0;
1339 for (i=0; i <= (ssize_t) MaxMap; i++)
1340 {
1341 intensity+=histogram[i].index;
1342 if (intensity > black_point)
1343 break;
1344 }
1345 black.index=(MagickRealType) i;
1346 intensity=0.0;
1347 for (i=(ssize_t) MaxMap; i != 0; i--)
1348 {
1349 intensity+=histogram[i].index;
1350 if (intensity > ((double) image->columns*image->rows-white_point))
1351 break;
1352 }
1353 white.index=(MagickRealType) i;
1354 }
1355 histogram=(MagickPixelPacket *) RelinquishMagickMemory(histogram);
1356 /*
1357 Stretch the histogram to create the stretched image mapping.
1358 */
1359 (void) memset(stretch_map,0,(MaxMap+1)*sizeof(*stretch_map));
1360 for (i=0; i <= (ssize_t) MaxMap; i++)
1361 {
1362 if ((channel & RedChannel) != 0)
1363 {
1364 if (i < (ssize_t) black.red)
1365 stretch_map[i].red=(Quantum) 0;
1366 else
1367 if (i > (ssize_t) white.red)
1368 stretch_map[i].red=QuantumRange;
1369 else
1370 if (black.red != white.red)
1371 stretch_map[i].red=ScaleMapToQuantum((MagickRealType) (MaxMap*
1372 (i-black.red)/(white.red-black.red)));
1373 }
1374 if ((channel & GreenChannel) != 0)
1375 {
1376 if (i < (ssize_t) black.green)
1377 stretch_map[i].green=0;
1378 else
1379 if (i > (ssize_t) white.green)
1380 stretch_map[i].green=QuantumRange;
1381 else
1382 if (black.green != white.green)
1383 stretch_map[i].green=ScaleMapToQuantum((MagickRealType) (MaxMap*
1384 (i-black.green)/(white.green-black.green)));
1385 }
1386 if ((channel & BlueChannel) != 0)
1387 {
1388 if (i < (ssize_t) black.blue)
1389 stretch_map[i].blue=0;
1390 else
1391 if (i > (ssize_t) white.blue)
1392 stretch_map[i].blue= QuantumRange;
1393 else
1394 if (black.blue != white.blue)
1395 stretch_map[i].blue=ScaleMapToQuantum((MagickRealType) (MaxMap*
1396 (i-black.blue)/(white.blue-black.blue)));
1397 }
1398 if ((channel & OpacityChannel) != 0)
1399 {
1400 if (i < (ssize_t) black.opacity)
1401 stretch_map[i].opacity=0;
1402 else
1403 if (i > (ssize_t) white.opacity)
1404 stretch_map[i].opacity=QuantumRange;
1405 else
1406 if (black.opacity != white.opacity)
1407 stretch_map[i].opacity=ScaleMapToQuantum((MagickRealType) (MaxMap*
1408 (i-black.opacity)/(white.opacity-black.opacity)));
1409 }
1410 if (((channel & IndexChannel) != 0) &&
1411 (image->colorspace == CMYKColorspace))
1412 {
1413 if (i < (ssize_t) black.index)
1414 stretch_map[i].index=0;
1415 else
1416 if (i > (ssize_t) white.index)
1417 stretch_map[i].index=QuantumRange;
1418 else
1419 if (black.index != white.index)
1420 stretch_map[i].index=ScaleMapToQuantum((MagickRealType) (MaxMap*
1421 (i-black.index)/(white.index-black.index)));
1422 }
1423 }
1424 /*
1425 Stretch the image.
1426 */
1427 if (((channel & OpacityChannel) != 0) || (((channel & IndexChannel) != 0) &&
1428 (image->colorspace == CMYKColorspace)))
1429 image->storage_class=DirectClass;
1430 if (image->storage_class == PseudoClass)
1431 {
1432 /*
1433 Stretch colormap.
1434 */
1435 for (i=0; i < (ssize_t) image->colors; i++)
1436 {
1437 if ((channel & RedChannel) != 0)
1438 {
1439 if (black.red != white.red)
1440 image->colormap[i].red=stretch_map[
1441 ScaleQuantumToMap(image->colormap[i].red)].red;
1442 }
1443 if ((channel & GreenChannel) != 0)
1444 {
1445 if (black.green != white.green)
1446 image->colormap[i].green=stretch_map[
1447 ScaleQuantumToMap(image->colormap[i].green)].green;
1448 }
1449 if ((channel & BlueChannel) != 0)
1450 {
1451 if (black.blue != white.blue)
1452 image->colormap[i].blue=stretch_map[
1453 ScaleQuantumToMap(image->colormap[i].blue)].blue;
1454 }
1455 if ((channel & OpacityChannel) != 0)
1456 {
1457 if (black.opacity != white.opacity)
1458 image->colormap[i].opacity=stretch_map[
1459 ScaleQuantumToMap(image->colormap[i].opacity)].opacity;
1460 }
1461 }
1462 }
1463 /*
1464 Stretch image.
1465 */
1466 status=MagickTrue;
1467 progress=0;
1468#if defined(MAGICKCORE_OPENMP_SUPPORT)
1469 #pragma omp parallel for schedule(static) shared(progress,status) \
1470 magick_number_threads(image,image,image->rows,1)
1471#endif
1472 for (y=0; y < (ssize_t) image->rows; y++)
1473 {
1474 IndexPacket
1475 *magick_restrict indexes;
1476
1478 *magick_restrict q;
1479
1480 ssize_t
1481 x;
1482
1483 if (status == MagickFalse)
1484 continue;
1485 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
1486 if (q == (PixelPacket *) NULL)
1487 {
1488 status=MagickFalse;
1489 continue;
1490 }
1491 indexes=GetCacheViewAuthenticIndexQueue(image_view);
1492 for (x=0; x < (ssize_t) image->columns; x++)
1493 {
1494 if ((channel & RedChannel) != 0)
1495 {
1496 if (black.red != white.red)
1497 SetPixelRed(q,stretch_map[
1498 ScaleQuantumToMap(GetPixelRed(q))].red);
1499 }
1500 if ((channel & GreenChannel) != 0)
1501 {
1502 if (black.green != white.green)
1503 SetPixelGreen(q,stretch_map[
1504 ScaleQuantumToMap(GetPixelGreen(q))].green);
1505 }
1506 if ((channel & BlueChannel) != 0)
1507 {
1508 if (black.blue != white.blue)
1509 SetPixelBlue(q,stretch_map[
1510 ScaleQuantumToMap(GetPixelBlue(q))].blue);
1511 }
1512 if ((channel & OpacityChannel) != 0)
1513 {
1514 if (black.opacity != white.opacity)
1515 SetPixelOpacity(q,stretch_map[
1516 ScaleQuantumToMap(GetPixelOpacity(q))].opacity);
1517 }
1518 if (((channel & IndexChannel) != 0) &&
1519 (image->colorspace == CMYKColorspace))
1520 {
1521 if (black.index != white.index)
1522 SetPixelIndex(indexes+x,stretch_map[
1523 ScaleQuantumToMap(GetPixelIndex(indexes+x))].index);
1524 }
1525 q++;
1526 }
1527 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
1528 status=MagickFalse;
1529 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1530 {
1531 MagickBooleanType
1532 proceed;
1533
1534#if defined(MAGICKCORE_OPENMP_SUPPORT)
1535 #pragma omp atomic
1536#endif
1537 progress++;
1538 proceed=SetImageProgress(image,ContrastStretchImageTag,progress,
1539 image->rows);
1540 if (proceed == MagickFalse)
1541 status=MagickFalse;
1542 }
1543 }
1544 image_view=DestroyCacheView(image_view);
1545 stretch_map=(QuantumPixelPacket *) RelinquishMagickMemory(stretch_map);
1546 return(status);
1547}
1548
1549/*
1550%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1551% %
1552% %
1553% %
1554% E n h a n c e I m a g e %
1555% %
1556% %
1557% %
1558%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1559%
1560% EnhanceImage() applies a digital filter that improves the quality of a
1561% noisy image.
1562%
1563% The format of the EnhanceImage method is:
1564%
1565% Image *EnhanceImage(const Image *image,ExceptionInfo *exception)
1566%
1567% A description of each parameter follows:
1568%
1569% o image: the image.
1570%
1571% o exception: return any errors or warnings in this structure.
1572%
1573*/
1574MagickExport Image *EnhanceImage(const Image *image,ExceptionInfo *exception)
1575{
1576#define EnhancePixel(weight) \
1577 mean=QuantumScale*((double) GetPixelRed(r)+(double) pixel.red)/2.0; \
1578 distance=QuantumScale*((double) GetPixelRed(r)-(double) pixel.red); \
1579 distance_squared=(4.0+mean)*distance*distance; \
1580 mean=QuantumScale*((double) GetPixelGreen(r)+(double) pixel.green)/2.0; \
1581 distance=QuantumScale*((double) GetPixelGreen(r)-(double) pixel.green); \
1582 distance_squared+=(7.0-mean)*distance*distance; \
1583 mean=QuantumScale*((double) GetPixelBlue(r)+(double) pixel.blue)/2.0; \
1584 distance=QuantumScale*((double) GetPixelBlue(r)-(double) pixel.blue); \
1585 distance_squared+=(5.0-mean)*distance*distance; \
1586 mean=QuantumScale*((double) GetPixelOpacity(r)+(double) pixel.opacity)/2.0; \
1587 distance=QuantumScale*((double) GetPixelOpacity(r)-(double) pixel.opacity); \
1588 distance_squared+=(5.0-mean)*distance*distance; \
1589 if (distance_squared < 0.069) \
1590 { \
1591 aggregate.red+=(weight)*(double) GetPixelRed(r); \
1592 aggregate.green+=(weight)*(double) GetPixelGreen(r); \
1593 aggregate.blue+=(weight)*(double) GetPixelBlue(r); \
1594 aggregate.opacity+=(weight)*(double) GetPixelOpacity(r); \
1595 total_weight+=(weight); \
1596 } \
1597 r++;
1598#define EnhanceImageTag "Enhance/Image"
1599
1600 CacheView
1601 *enhance_view,
1602 *image_view;
1603
1604 Image
1605 *enhance_image;
1606
1607 MagickBooleanType
1608 status;
1609
1610 MagickOffsetType
1611 progress;
1612
1614 zero;
1615
1616 ssize_t
1617 y;
1618
1619 /*
1620 Initialize enhanced image attributes.
1621 */
1622 assert(image != (const Image *) NULL);
1623 assert(image->signature == MagickCoreSignature);
1624 if (IsEventLogging() != MagickFalse)
1625 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1626 assert(exception != (ExceptionInfo *) NULL);
1627 assert(exception->signature == MagickCoreSignature);
1628 if ((image->columns < 5) || (image->rows < 5))
1629 return((Image *) NULL);
1630 enhance_image=CloneImage(image,0,0,MagickTrue,exception);
1631 if (enhance_image == (Image *) NULL)
1632 return((Image *) NULL);
1633 if (SetImageStorageClass(enhance_image,DirectClass) == MagickFalse)
1634 {
1635 InheritException(exception,&enhance_image->exception);
1636 enhance_image=DestroyImage(enhance_image);
1637 return((Image *) NULL);
1638 }
1639 /*
1640 Enhance image.
1641 */
1642 status=MagickTrue;
1643 progress=0;
1644 (void) memset(&zero,0,sizeof(zero));
1645 image_view=AcquireAuthenticCacheView(image,exception);
1646 enhance_view=AcquireAuthenticCacheView(enhance_image,exception);
1647#if defined(MAGICKCORE_OPENMP_SUPPORT)
1648 #pragma omp parallel for schedule(static) shared(progress,status) \
1649 magick_number_threads(image,enhance_image,image->rows,1)
1650#endif
1651 for (y=0; y < (ssize_t) image->rows; y++)
1652 {
1653 const PixelPacket
1654 *magick_restrict p;
1655
1657 *magick_restrict q;
1658
1659 ssize_t
1660 x;
1661
1662 /*
1663 Read another scan line.
1664 */
1665 if (status == MagickFalse)
1666 continue;
1667 p=GetCacheViewVirtualPixels(image_view,-2,y-2,image->columns+4,5,exception);
1668 q=QueueCacheViewAuthenticPixels(enhance_view,0,y,enhance_image->columns,1,
1669 exception);
1670 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
1671 {
1672 status=MagickFalse;
1673 continue;
1674 }
1675 for (x=0; x < (ssize_t) image->columns; x++)
1676 {
1677 double
1678 distance,
1679 distance_squared,
1680 mean,
1681 total_weight;
1682
1684 aggregate;
1685
1687 pixel;
1688
1689 const PixelPacket
1690 *magick_restrict r;
1691
1692 /*
1693 Compute weighted average of target pixel color components.
1694 */
1695 aggregate=zero;
1696 total_weight=0.0;
1697 r=p+2*(image->columns+4)+2;
1698 pixel=(*r);
1699 r=p;
1700 EnhancePixel(5.0); EnhancePixel(8.0); EnhancePixel(10.0);
1701 EnhancePixel(8.0); EnhancePixel(5.0);
1702 r=p+(image->columns+4);
1703 EnhancePixel(8.0); EnhancePixel(20.0); EnhancePixel(40.0);
1704 EnhancePixel(20.0); EnhancePixel(8.0);
1705 r=p+2*(image->columns+4);
1706 EnhancePixel(10.0); EnhancePixel(40.0); EnhancePixel(80.0);
1707 EnhancePixel(40.0); EnhancePixel(10.0);
1708 r=p+3*(image->columns+4);
1709 EnhancePixel(8.0); EnhancePixel(20.0); EnhancePixel(40.0);
1710 EnhancePixel(20.0); EnhancePixel(8.0);
1711 r=p+4*(image->columns+4);
1712 EnhancePixel(5.0); EnhancePixel(8.0); EnhancePixel(10.0);
1713 EnhancePixel(8.0); EnhancePixel(5.0);
1714 if (total_weight > MagickEpsilon)
1715 {
1716 SetPixelRed(q,(aggregate.red+(total_weight/2)-1)/total_weight);
1717 SetPixelGreen(q,(aggregate.green+(total_weight/2)-1)/total_weight);
1718 SetPixelBlue(q,(aggregate.blue+(total_weight/2)-1)/total_weight);
1719 SetPixelOpacity(q,(aggregate.opacity+(total_weight/2)-1)/
1720 total_weight);
1721 }
1722 p++;
1723 q++;
1724 }
1725 if (SyncCacheViewAuthenticPixels(enhance_view,exception) == MagickFalse)
1726 status=MagickFalse;
1727 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1728 {
1729 MagickBooleanType
1730 proceed;
1731
1732#if defined(MAGICKCORE_OPENMP_SUPPORT)
1733 #pragma omp atomic
1734#endif
1735 progress++;
1736 proceed=SetImageProgress(image,EnhanceImageTag,progress,image->rows);
1737 if (proceed == MagickFalse)
1738 status=MagickFalse;
1739 }
1740 }
1741 enhance_view=DestroyCacheView(enhance_view);
1742 image_view=DestroyCacheView(image_view);
1743 if (status == MagickFalse)
1744 enhance_image=DestroyImage(enhance_image);
1745 return(enhance_image);
1746}
1747
1748/*
1749%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1750% %
1751% %
1752% %
1753% E q u a l i z e I m a g e %
1754% %
1755% %
1756% %
1757%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1758%
1759% EqualizeImage() applies a histogram equalization to the image.
1760%
1761% The format of the EqualizeImage method is:
1762%
1763% MagickBooleanType EqualizeImage(Image *image)
1764% MagickBooleanType EqualizeImageChannel(Image *image,
1765% const ChannelType channel)
1766%
1767% A description of each parameter follows:
1768%
1769% o image: the image.
1770%
1771% o channel: the channel.
1772%
1773*/
1774
1775MagickExport MagickBooleanType EqualizeImage(Image *image)
1776{
1777 return(EqualizeImageChannel(image,DefaultChannels));
1778}
1779
1780MagickExport MagickBooleanType EqualizeImageChannel(Image *image,
1781 const ChannelType channel)
1782{
1783#define EqualizeImageTag "Equalize/Image"
1784
1785 CacheView
1786 *image_view;
1787
1789 *exception;
1790
1791 MagickBooleanType
1792 status;
1793
1794 MagickOffsetType
1795 progress;
1796
1798 black,
1799 *histogram,
1800 intensity,
1801 *map,
1802 white;
1803
1805 *equalize_map;
1806
1807 ssize_t
1808 i;
1809
1810 ssize_t
1811 y;
1812
1813 assert(image != (Image *) NULL);
1814 assert(image->signature == MagickCoreSignature);
1815 if (IsEventLogging() != MagickFalse)
1816 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1817 exception=(&image->exception);
1818
1819#if defined(MAGICKCORE_OPENCL_SUPPORT)
1820 /* Call OpenCL version */
1821 status=AccelerateEqualizeImage(image,channel,&image->exception);
1822 if (status != MagickFalse)
1823 return status;
1824#endif
1825 /*
1826 Allocate and initialize histogram arrays.
1827 */
1828 equalize_map=(QuantumPixelPacket *) AcquireQuantumMemory(MaxMap+1UL,
1829 sizeof(*equalize_map));
1830 histogram=(MagickPixelPacket *) AcquireQuantumMemory(MaxMap+1UL,
1831 sizeof(*histogram));
1832 map=(MagickPixelPacket *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*map));
1833 if ((equalize_map == (QuantumPixelPacket *) NULL) ||
1834 (histogram == (MagickPixelPacket *) NULL) ||
1835 (map == (MagickPixelPacket *) NULL))
1836 {
1837 if (map != (MagickPixelPacket *) NULL)
1838 map=(MagickPixelPacket *) RelinquishMagickMemory(map);
1839 if (histogram != (MagickPixelPacket *) NULL)
1840 histogram=(MagickPixelPacket *) RelinquishMagickMemory(histogram);
1841 if (equalize_map != (QuantumPixelPacket *) NULL)
1842 equalize_map=(QuantumPixelPacket *) RelinquishMagickMemory(
1843 equalize_map);
1844 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
1845 image->filename);
1846 }
1847 /*
1848 Form histogram.
1849 */
1850 (void) memset(histogram,0,(MaxMap+1)*sizeof(*histogram));
1851 image_view=AcquireVirtualCacheView(image,exception);
1852 for (y=0; y < (ssize_t) image->rows; y++)
1853 {
1854 const IndexPacket
1855 *magick_restrict indexes;
1856
1857 const PixelPacket
1858 *magick_restrict p;
1859
1860 ssize_t
1861 x;
1862
1863 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
1864 if (p == (const PixelPacket *) NULL)
1865 break;
1866 indexes=GetCacheViewVirtualIndexQueue(image_view);
1867 if ((channel & SyncChannels) != 0)
1868 for (x=0; x < (ssize_t) image->columns; x++)
1869 {
1870 MagickRealType intensity=GetPixelIntensity(image,p);
1871 histogram[ScaleQuantumToMap(ClampToQuantum(intensity))].red++;
1872 p++;
1873 }
1874 else
1875 for (x=0; x < (ssize_t) image->columns; x++)
1876 {
1877 if ((channel & RedChannel) != 0)
1878 histogram[ScaleQuantumToMap(GetPixelRed(p))].red++;
1879 if ((channel & GreenChannel) != 0)
1880 histogram[ScaleQuantumToMap(GetPixelGreen(p))].green++;
1881 if ((channel & BlueChannel) != 0)
1882 histogram[ScaleQuantumToMap(GetPixelBlue(p))].blue++;
1883 if ((channel & OpacityChannel) != 0)
1884 histogram[ScaleQuantumToMap(GetPixelOpacity(p))].opacity++;
1885 if (((channel & IndexChannel) != 0) &&
1886 (image->colorspace == CMYKColorspace))
1887 histogram[ScaleQuantumToMap(GetPixelIndex(indexes+x))].index++;
1888 p++;
1889 }
1890 }
1891 image_view=DestroyCacheView(image_view);
1892 /*
1893 Integrate the histogram to get the equalization map.
1894 */
1895 (void) memset(&intensity,0,sizeof(intensity));
1896 for (i=0; i <= (ssize_t) MaxMap; i++)
1897 {
1898 if ((channel & SyncChannels) != 0)
1899 {
1900 intensity.red+=histogram[i].red;
1901 map[i]=intensity;
1902 continue;
1903 }
1904 if ((channel & RedChannel) != 0)
1905 intensity.red+=histogram[i].red;
1906 if ((channel & GreenChannel) != 0)
1907 intensity.green+=histogram[i].green;
1908 if ((channel & BlueChannel) != 0)
1909 intensity.blue+=histogram[i].blue;
1910 if ((channel & OpacityChannel) != 0)
1911 intensity.opacity+=histogram[i].opacity;
1912 if (((channel & IndexChannel) != 0) &&
1913 (image->colorspace == CMYKColorspace))
1914 intensity.index+=histogram[i].index;
1915 map[i]=intensity;
1916 }
1917 black=map[0];
1918 white=map[(int) MaxMap];
1919 (void) memset(equalize_map,0,(MaxMap+1)*sizeof(*equalize_map));
1920 for (i=0; i <= (ssize_t) MaxMap; i++)
1921 {
1922 if ((channel & SyncChannels) != 0)
1923 {
1924 if (white.red != black.red)
1925 equalize_map[i].red=ScaleMapToQuantum((MagickRealType) ((MaxMap*
1926 (map[i].red-black.red))/(white.red-black.red)));
1927 continue;
1928 }
1929 if (((channel & RedChannel) != 0) && (white.red != black.red))
1930 equalize_map[i].red=ScaleMapToQuantum((MagickRealType) ((MaxMap*
1931 (map[i].red-black.red))/(white.red-black.red)));
1932 if (((channel & GreenChannel) != 0) && (white.green != black.green))
1933 equalize_map[i].green=ScaleMapToQuantum((MagickRealType) ((MaxMap*
1934 (map[i].green-black.green))/(white.green-black.green)));
1935 if (((channel & BlueChannel) != 0) && (white.blue != black.blue))
1936 equalize_map[i].blue=ScaleMapToQuantum((MagickRealType) ((MaxMap*
1937 (map[i].blue-black.blue))/(white.blue-black.blue)));
1938 if (((channel & OpacityChannel) != 0) && (white.opacity != black.opacity))
1939 equalize_map[i].opacity=ScaleMapToQuantum((MagickRealType) ((MaxMap*
1940 (map[i].opacity-black.opacity))/(white.opacity-black.opacity)));
1941 if ((((channel & IndexChannel) != 0) &&
1942 (image->colorspace == CMYKColorspace)) &&
1943 (white.index != black.index))
1944 equalize_map[i].index=ScaleMapToQuantum((MagickRealType) ((MaxMap*
1945 (map[i].index-black.index))/(white.index-black.index)));
1946 }
1947 histogram=(MagickPixelPacket *) RelinquishMagickMemory(histogram);
1948 map=(MagickPixelPacket *) RelinquishMagickMemory(map);
1949 if (image->storage_class == PseudoClass)
1950 {
1951 /*
1952 Equalize colormap.
1953 */
1954 for (i=0; i < (ssize_t) image->colors; i++)
1955 {
1956 if ((channel & SyncChannels) != 0)
1957 {
1958 if (white.red != black.red)
1959 {
1960 image->colormap[i].red=equalize_map[
1961 ScaleQuantumToMap(image->colormap[i].red)].red;
1962 image->colormap[i].green=equalize_map[
1963 ScaleQuantumToMap(image->colormap[i].green)].red;
1964 image->colormap[i].blue=equalize_map[
1965 ScaleQuantumToMap(image->colormap[i].blue)].red;
1966 image->colormap[i].opacity=equalize_map[
1967 ScaleQuantumToMap(image->colormap[i].opacity)].red;
1968 }
1969 continue;
1970 }
1971 if (((channel & RedChannel) != 0) && (white.red != black.red))
1972 image->colormap[i].red=equalize_map[
1973 ScaleQuantumToMap(image->colormap[i].red)].red;
1974 if (((channel & GreenChannel) != 0) && (white.green != black.green))
1975 image->colormap[i].green=equalize_map[
1976 ScaleQuantumToMap(image->colormap[i].green)].green;
1977 if (((channel & BlueChannel) != 0) && (white.blue != black.blue))
1978 image->colormap[i].blue=equalize_map[
1979 ScaleQuantumToMap(image->colormap[i].blue)].blue;
1980 if (((channel & OpacityChannel) != 0) &&
1981 (white.opacity != black.opacity))
1982 image->colormap[i].opacity=equalize_map[
1983 ScaleQuantumToMap(image->colormap[i].opacity)].opacity;
1984 }
1985 }
1986 /*
1987 Equalize image.
1988 */
1989 status=MagickTrue;
1990 progress=0;
1991 image_view=AcquireAuthenticCacheView(image,exception);
1992#if defined(MAGICKCORE_OPENMP_SUPPORT)
1993 #pragma omp parallel for schedule(static) shared(progress,status) \
1994 magick_number_threads(image,image,image->rows,1)
1995#endif
1996 for (y=0; y < (ssize_t) image->rows; y++)
1997 {
1998 IndexPacket
1999 *magick_restrict indexes;
2000
2002 *magick_restrict q;
2003
2004 ssize_t
2005 x;
2006
2007 if (status == MagickFalse)
2008 continue;
2009 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2010 if (q == (PixelPacket *) NULL)
2011 {
2012 status=MagickFalse;
2013 continue;
2014 }
2015 indexes=GetCacheViewAuthenticIndexQueue(image_view);
2016 for (x=0; x < (ssize_t) image->columns; x++)
2017 {
2018 if ((channel & SyncChannels) != 0)
2019 {
2020 if (white.red != black.red)
2021 {
2022 SetPixelRed(q,equalize_map[
2023 ScaleQuantumToMap(GetPixelRed(q))].red);
2024 SetPixelGreen(q,equalize_map[
2025 ScaleQuantumToMap(GetPixelGreen(q))].red);
2026 SetPixelBlue(q,equalize_map[
2027 ScaleQuantumToMap(GetPixelBlue(q))].red);
2028 SetPixelOpacity(q,equalize_map[
2029 ScaleQuantumToMap(GetPixelOpacity(q))].red);
2030 if (image->colorspace == CMYKColorspace)
2031 SetPixelIndex(indexes+x,equalize_map[
2032 ScaleQuantumToMap(GetPixelIndex(indexes+x))].red);
2033 }
2034 q++;
2035 continue;
2036 }
2037 if (((channel & RedChannel) != 0) && (white.red != black.red))
2038 SetPixelRed(q,equalize_map[
2039 ScaleQuantumToMap(GetPixelRed(q))].red);
2040 if (((channel & GreenChannel) != 0) && (white.green != black.green))
2041 SetPixelGreen(q,equalize_map[
2042 ScaleQuantumToMap(GetPixelGreen(q))].green);
2043 if (((channel & BlueChannel) != 0) && (white.blue != black.blue))
2044 SetPixelBlue(q,equalize_map[
2045 ScaleQuantumToMap(GetPixelBlue(q))].blue);
2046 if (((channel & OpacityChannel) != 0) && (white.opacity != black.opacity))
2047 SetPixelOpacity(q,equalize_map[
2048 ScaleQuantumToMap(GetPixelOpacity(q))].opacity);
2049 if ((((channel & IndexChannel) != 0) &&
2050 (image->colorspace == CMYKColorspace)) &&
2051 (white.index != black.index))
2052 SetPixelIndex(indexes+x,equalize_map[
2053 ScaleQuantumToMap(GetPixelIndex(indexes+x))].index);
2054 q++;
2055 }
2056 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2057 status=MagickFalse;
2058 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2059 {
2060 MagickBooleanType
2061 proceed;
2062
2063#if defined(MAGICKCORE_OPENMP_SUPPORT)
2064 #pragma omp atomic
2065#endif
2066 progress++;
2067 proceed=SetImageProgress(image,EqualizeImageTag,progress,image->rows);
2068 if (proceed == MagickFalse)
2069 status=MagickFalse;
2070 }
2071 }
2072 image_view=DestroyCacheView(image_view);
2073 equalize_map=(QuantumPixelPacket *) RelinquishMagickMemory(equalize_map);
2074 return(status);
2075}
2076
2077/*
2078%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2079% %
2080% %
2081% %
2082% G a m m a I m a g e %
2083% %
2084% %
2085% %
2086%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2087%
2088% GammaImage() gamma-corrects a particular image channel. The same
2089% image viewed on different devices will have perceptual differences in the
2090% way the image's intensities are represented on the screen. Specify
2091% individual gamma levels for the red, green, and blue channels, or adjust
2092% all three with the gamma parameter. Values typically range from 0.8 to 2.3.
2093%
2094% You can also reduce the influence of a particular channel with a gamma
2095% value of 0.
2096%
2097% The format of the GammaImage method is:
2098%
2099% MagickBooleanType GammaImage(Image *image,const char *level)
2100% MagickBooleanType GammaImageChannel(Image *image,
2101% const ChannelType channel,const double gamma)
2102%
2103% A description of each parameter follows:
2104%
2105% o image: the image.
2106%
2107% o channel: the channel.
2108%
2109% o level: the image gamma as a string (e.g. 1.6,1.2,1.0).
2110%
2111% o gamma: the image gamma.
2112%
2113*/
2114
2115static inline double gamma_pow(const double value,const double gamma)
2116{
2117 return(value < 0.0 ? value : pow(value,gamma));
2118}
2119
2120MagickExport MagickBooleanType GammaImage(Image *image,const char *level)
2121{
2123 geometry_info;
2124
2126 gamma;
2127
2128 MagickStatusType
2129 flags,
2130 status;
2131
2132 assert(image != (Image *) NULL);
2133 assert(image->signature == MagickCoreSignature);
2134 if (IsEventLogging() != MagickFalse)
2135 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2136 if (level == (char *) NULL)
2137 return(MagickFalse);
2138 gamma.red=0.0;
2139 flags=ParseGeometry(level,&geometry_info);
2140 if ((flags & RhoValue) != 0)
2141 gamma.red=geometry_info.rho;
2142 gamma.green=gamma.red;
2143 if ((flags & SigmaValue) != 0)
2144 gamma.green=geometry_info.sigma;
2145 gamma.blue=gamma.red;
2146 if ((flags & XiValue) != 0)
2147 gamma.blue=geometry_info.xi;
2148 if ((gamma.red == 1.0) && (gamma.green == 1.0) && (gamma.blue == 1.0))
2149 return(MagickTrue);
2150 if ((gamma.red == gamma.green) && (gamma.green == gamma.blue))
2151 status=GammaImageChannel(image,(ChannelType) (RedChannel | GreenChannel |
2152 BlueChannel),(double) gamma.red);
2153 else
2154 {
2155 status=GammaImageChannel(image,RedChannel,(double) gamma.red);
2156 status&=GammaImageChannel(image,GreenChannel,(double) gamma.green);
2157 status&=GammaImageChannel(image,BlueChannel,(double) gamma.blue);
2158 }
2159 return(status != 0 ? MagickTrue : MagickFalse);
2160}
2161
2162MagickExport MagickBooleanType GammaImageChannel(Image *image,
2163 const ChannelType channel,const double gamma)
2164{
2165#define GammaImageTag "Gamma/Image"
2166
2167 CacheView
2168 *image_view;
2169
2171 *exception;
2172
2173 MagickBooleanType
2174 status;
2175
2176 MagickOffsetType
2177 progress;
2178
2179 Quantum
2180 *gamma_map;
2181
2182 ssize_t
2183 i;
2184
2185 ssize_t
2186 y;
2187
2188 /*
2189 Allocate and initialize gamma maps.
2190 */
2191 assert(image != (Image *) NULL);
2192 assert(image->signature == MagickCoreSignature);
2193 if (IsEventLogging() != MagickFalse)
2194 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2195 exception=(&image->exception);
2196 if (gamma == 1.0)
2197 return(MagickTrue);
2198 gamma_map=(Quantum *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*gamma_map));
2199 if (gamma_map == (Quantum *) NULL)
2200 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
2201 image->filename);
2202 (void) memset(gamma_map,0,(MaxMap+1)*sizeof(*gamma_map));
2203 if (gamma != 0.0)
2204 for (i=0; i <= (ssize_t) MaxMap; i++)
2205 gamma_map[i]=ClampToQuantum((MagickRealType) ScaleMapToQuantum((
2206 MagickRealType) (MaxMap*pow((double) i/MaxMap,
2207 PerceptibleReciprocal(gamma)))));
2208 if (image->storage_class == PseudoClass)
2209 {
2210 /*
2211 Gamma-correct colormap.
2212 */
2213 for (i=0; i < (ssize_t) image->colors; i++)
2214 {
2215#if !defined(MAGICKCORE_HDRI_SUPPORT)
2216 if ((channel & RedChannel) != 0)
2217 image->colormap[i].red=gamma_map[ScaleQuantumToMap(
2218 image->colormap[i].red)];
2219 if ((channel & GreenChannel) != 0)
2220 image->colormap[i].green=gamma_map[ScaleQuantumToMap(
2221 image->colormap[i].green)];
2222 if ((channel & BlueChannel) != 0)
2223 image->colormap[i].blue=gamma_map[ScaleQuantumToMap(
2224 image->colormap[i].blue)];
2225 if ((channel & OpacityChannel) != 0)
2226 {
2227 if (image->matte == MagickFalse)
2228 image->colormap[i].opacity=gamma_map[ScaleQuantumToMap(
2229 image->colormap[i].opacity)];
2230 else
2231 image->colormap[i].opacity=QuantumRange-gamma_map[
2232 ScaleQuantumToMap((Quantum) (QuantumRange-
2233 image->colormap[i].opacity))];
2234 }
2235#else
2236 if ((channel & RedChannel) != 0)
2237 image->colormap[i].red=(double) QuantumRange*gamma_pow(QuantumScale*
2238 (double) image->colormap[i].red,PerceptibleReciprocal(gamma));
2239 if ((channel & GreenChannel) != 0)
2240 image->colormap[i].green=(double) QuantumRange*gamma_pow(QuantumScale*
2241 (double) image->colormap[i].green,PerceptibleReciprocal(gamma));
2242 if ((channel & BlueChannel) != 0)
2243 image->colormap[i].blue=(double) QuantumRange*gamma_pow(QuantumScale*
2244 (double) image->colormap[i].blue,PerceptibleReciprocal(gamma));
2245 if ((channel & OpacityChannel) != 0)
2246 {
2247 if (image->matte == MagickFalse)
2248 image->colormap[i].opacity=(double) QuantumRange*
2249 gamma_pow(QuantumScale*(double) image->colormap[i].opacity,
2250 PerceptibleReciprocal(gamma));
2251 else
2252 image->colormap[i].opacity=(double) QuantumRange-(double)
2253 QuantumRange*gamma_pow(QuantumScale*((double) QuantumRange-
2254 (double) image->colormap[i].opacity),1.0/gamma);
2255 }
2256#endif
2257 }
2258 }
2259 /*
2260 Gamma-correct image.
2261 */
2262 status=MagickTrue;
2263 progress=0;
2264 image_view=AcquireAuthenticCacheView(image,exception);
2265#if defined(MAGICKCORE_OPENMP_SUPPORT)
2266 #pragma omp parallel for schedule(static) shared(progress,status) \
2267 magick_number_threads(image,image,image->rows,1)
2268#endif
2269 for (y=0; y < (ssize_t) image->rows; y++)
2270 {
2271 IndexPacket
2272 *magick_restrict indexes;
2273
2275 *magick_restrict q;
2276
2277 ssize_t
2278 x;
2279
2280 if (status == MagickFalse)
2281 continue;
2282 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2283 if (q == (PixelPacket *) NULL)
2284 {
2285 status=MagickFalse;
2286 continue;
2287 }
2288 indexes=GetCacheViewAuthenticIndexQueue(image_view);
2289 for (x=0; x < (ssize_t) image->columns; x++)
2290 {
2291#if !defined(MAGICKCORE_HDRI_SUPPORT)
2292 if ((channel & SyncChannels) != 0)
2293 {
2294 SetPixelRed(q,gamma_map[ScaleQuantumToMap(GetPixelRed(q))]);
2295 SetPixelGreen(q,gamma_map[ScaleQuantumToMap(GetPixelGreen(q))]);
2296 SetPixelBlue(q,gamma_map[ScaleQuantumToMap(GetPixelBlue(q))]);
2297 }
2298 else
2299 {
2300 if ((channel & RedChannel) != 0)
2301 SetPixelRed(q,gamma_map[ScaleQuantumToMap(GetPixelRed(q))]);
2302 if ((channel & GreenChannel) != 0)
2303 SetPixelGreen(q,gamma_map[ScaleQuantumToMap(GetPixelGreen(q))]);
2304 if ((channel & BlueChannel) != 0)
2305 SetPixelBlue(q,gamma_map[ScaleQuantumToMap(GetPixelBlue(q))]);
2306 if ((channel & OpacityChannel) != 0)
2307 {
2308 if (image->matte == MagickFalse)
2309 SetPixelOpacity(q,gamma_map[ScaleQuantumToMap(
2310 GetPixelOpacity(q))]);
2311 else
2312 SetPixelAlpha(q,gamma_map[ScaleQuantumToMap((Quantum)
2313 GetPixelAlpha(q))]);
2314 }
2315 }
2316#else
2317 if ((channel & SyncChannels) != 0)
2318 {
2319 SetPixelRed(q,(double) QuantumRange*gamma_pow(QuantumScale*(double)
2320 GetPixelRed(q),PerceptibleReciprocal(gamma)));
2321 SetPixelGreen(q,(double) QuantumRange*gamma_pow(QuantumScale*(double)
2322 GetPixelGreen(q),PerceptibleReciprocal(gamma)));
2323 SetPixelBlue(q,(double) QuantumRange*gamma_pow(QuantumScale*(double)
2324 GetPixelBlue(q),PerceptibleReciprocal(gamma)));
2325 }
2326 else
2327 {
2328 if ((channel & RedChannel) != 0)
2329 SetPixelRed(q,(double) QuantumRange*gamma_pow(QuantumScale*
2330 (double) GetPixelRed(q),PerceptibleReciprocal(gamma)));
2331 if ((channel & GreenChannel) != 0)
2332 SetPixelGreen(q,(double) QuantumRange*gamma_pow(QuantumScale*
2333 (double) GetPixelGreen(q),PerceptibleReciprocal(gamma)));
2334 if ((channel & BlueChannel) != 0)
2335 SetPixelBlue(q,(double) QuantumRange*gamma_pow(QuantumScale*
2336 (double) GetPixelBlue(q),PerceptibleReciprocal(gamma)));
2337 if ((channel & OpacityChannel) != 0)
2338 {
2339 if (image->matte == MagickFalse)
2340 SetPixelOpacity(q,(double) QuantumRange*gamma_pow(QuantumScale*
2341 (double) GetPixelOpacity(q),PerceptibleReciprocal(gamma)));
2342 else
2343 SetPixelAlpha(q,(double) QuantumRange*gamma_pow(QuantumScale*
2344 (double) GetPixelAlpha(q),PerceptibleReciprocal(gamma)));
2345 }
2346 }
2347#endif
2348 q++;
2349 }
2350 if (((channel & IndexChannel) != 0) &&
2351 (image->colorspace == CMYKColorspace))
2352 for (x=0; x < (ssize_t) image->columns; x++)
2353 SetPixelIndex(indexes+x,gamma_map[ScaleQuantumToMap(
2354 GetPixelIndex(indexes+x))]);
2355 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2356 status=MagickFalse;
2357 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2358 {
2359 MagickBooleanType
2360 proceed;
2361
2362#if defined(MAGICKCORE_OPENMP_SUPPORT)
2363 #pragma omp atomic
2364#endif
2365 progress++;
2366 proceed=SetImageProgress(image,GammaImageTag,progress,image->rows);
2367 if (proceed == MagickFalse)
2368 status=MagickFalse;
2369 }
2370 }
2371 image_view=DestroyCacheView(image_view);
2372 gamma_map=(Quantum *) RelinquishMagickMemory(gamma_map);
2373 if (image->gamma != 0.0)
2374 image->gamma*=gamma;
2375 return(status);
2376}
2377
2378/*
2379%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2380% %
2381% %
2382% %
2383% G r a y s c a l e I m a g e %
2384% %
2385% %
2386% %
2387%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2388%
2389% GrayscaleImage() converts the colors in the reference image to gray.
2390%
2391% The format of the GrayscaleImageChannel method is:
2392%
2393% MagickBooleanType GrayscaleImage(Image *image,
2394% const PixelIntensityMethod method)
2395%
2396% A description of each parameter follows:
2397%
2398% o image: the image.
2399%
2400% o channel: the channel.
2401%
2402*/
2403MagickExport MagickBooleanType GrayscaleImage(Image *image,
2404 const PixelIntensityMethod method)
2405{
2406#define GrayscaleImageTag "Grayscale/Image"
2407
2408 CacheView
2409 *image_view;
2410
2412 *exception;
2413
2414 MagickBooleanType
2415 status;
2416
2417 MagickOffsetType
2418 progress;
2419
2420 ssize_t
2421 y;
2422
2423 assert(image != (Image *) NULL);
2424 assert(image->signature == MagickCoreSignature);
2425 if (IsEventLogging() != MagickFalse)
2426 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2427 if (image->storage_class == PseudoClass)
2428 {
2429 if (SyncImage(image) == MagickFalse)
2430 return(MagickFalse);
2431 if (SetImageStorageClass(image,DirectClass) == MagickFalse)
2432 return(MagickFalse);
2433 }
2434
2435 /*
2436 Grayscale image.
2437 */
2438
2439 /* call opencl version */
2440#if defined(MAGICKCORE_OPENCL_SUPPORT)
2441 if (AccelerateGrayscaleImage(image,method,&image->exception) != MagickFalse)
2442 {
2443 image->intensity=method;
2444 image->type=GrayscaleType;
2445 if ((method == Rec601LuminancePixelIntensityMethod) ||
2446 (method == Rec709LuminancePixelIntensityMethod))
2447 return(SetImageColorspace(image,LinearGRAYColorspace));
2448 return(SetImageColorspace(image,GRAYColorspace));
2449 }
2450#endif
2451 status=MagickTrue;
2452 progress=0;
2453 exception=(&image->exception);
2454 image_view=AcquireAuthenticCacheView(image,exception);
2455#if defined(MAGICKCORE_OPENMP_SUPPORT)
2456 #pragma omp parallel for schedule(static) shared(progress,status) \
2457 magick_number_threads(image,image,image->rows,1)
2458#endif
2459 for (y=0; y < (ssize_t) image->rows; y++)
2460 {
2462 *magick_restrict q;
2463
2464 ssize_t
2465 x;
2466
2467 if (status == MagickFalse)
2468 continue;
2469 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2470 if (q == (PixelPacket *) NULL)
2471 {
2472 status=MagickFalse;
2473 continue;
2474 }
2475 for (x=0; x < (ssize_t) image->columns; x++)
2476 {
2477 MagickRealType
2478 blue,
2479 green,
2480 intensity,
2481 red;
2482
2483 red=(MagickRealType) q->red;
2484 green=(MagickRealType) q->green;
2485 blue=(MagickRealType) q->blue;
2486 intensity=0.0;
2487 switch (method)
2488 {
2489 case AveragePixelIntensityMethod:
2490 {
2491 intensity=(red+green+blue)/3.0;
2492 break;
2493 }
2494 case BrightnessPixelIntensityMethod:
2495 {
2496 intensity=MagickMax(MagickMax(red,green),blue);
2497 break;
2498 }
2499 case LightnessPixelIntensityMethod:
2500 {
2501 intensity=(MagickMin(MagickMin(red,green),blue)+
2502 MagickMax(MagickMax(red,green),blue))/2.0;
2503 break;
2504 }
2505 case MSPixelIntensityMethod:
2506 {
2507 intensity=(MagickRealType) (((double) red*(double) red+(double)
2508 green*(double) green+(double) blue*(double) blue)/
2509 (3.0*(double) QuantumRange));
2510 break;
2511 }
2512 case Rec601LumaPixelIntensityMethod:
2513 {
2514 if (image->colorspace == RGBColorspace)
2515 {
2516 red=EncodePixelGamma(red);
2517 green=EncodePixelGamma(green);
2518 blue=EncodePixelGamma(blue);
2519 }
2520 intensity=0.298839*red+0.586811*green+0.114350*blue;
2521 break;
2522 }
2523 case Rec601LuminancePixelIntensityMethod:
2524 {
2525 if (image->colorspace == sRGBColorspace)
2526 {
2527 red=DecodePixelGamma(red);
2528 green=DecodePixelGamma(green);
2529 blue=DecodePixelGamma(blue);
2530 }
2531 intensity=0.298839*red+0.586811*green+0.114350*blue;
2532 break;
2533 }
2534 case Rec709LumaPixelIntensityMethod:
2535 default:
2536 {
2537 if (image->colorspace == RGBColorspace)
2538 {
2539 red=EncodePixelGamma(red);
2540 green=EncodePixelGamma(green);
2541 blue=EncodePixelGamma(blue);
2542 }
2543 intensity=0.212656*red+0.715158*green+0.072186*blue;
2544 break;
2545 }
2546 case Rec709LuminancePixelIntensityMethod:
2547 {
2548 if (image->colorspace == sRGBColorspace)
2549 {
2550 red=DecodePixelGamma(red);
2551 green=DecodePixelGamma(green);
2552 blue=DecodePixelGamma(blue);
2553 }
2554 intensity=0.212656*red+0.715158*green+0.072186*blue;
2555 break;
2556 }
2557 case RMSPixelIntensityMethod:
2558 {
2559 intensity=(MagickRealType) (sqrt((double) red*red+green*green+
2560 blue*blue)/sqrt(3.0));
2561 break;
2562 }
2563 }
2564 SetPixelGray(q,ClampToQuantum(intensity));
2565 q++;
2566 }
2567 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2568 status=MagickFalse;
2569 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2570 {
2571 MagickBooleanType
2572 proceed;
2573
2574#if defined(MAGICKCORE_OPENMP_SUPPORT)
2575 #pragma omp atomic
2576#endif
2577 progress++;
2578 proceed=SetImageProgress(image,GrayscaleImageTag,progress,image->rows);
2579 if (proceed == MagickFalse)
2580 status=MagickFalse;
2581 }
2582 }
2583 image_view=DestroyCacheView(image_view);
2584 image->intensity=method;
2585 image->type=GrayscaleType;
2586 if ((method == Rec601LuminancePixelIntensityMethod) ||
2587 (method == Rec709LuminancePixelIntensityMethod))
2588 return(SetImageColorspace(image,LinearGRAYColorspace));
2589 return(SetImageColorspace(image,GRAYColorspace));
2590}
2591
2592/*
2593%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2594% %
2595% %
2596% %
2597% H a l d C l u t I m a g e %
2598% %
2599% %
2600% %
2601%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2602%
2603% HaldClutImage() applies a Hald color lookup table to the image. A Hald
2604% color lookup table is a 3-dimensional color cube mapped to 2 dimensions.
2605% Create it with the HALD coder. You can apply any color transformation to
2606% the Hald image and then use this method to apply the transform to the
2607% image.
2608%
2609% The format of the HaldClutImage method is:
2610%
2611% MagickBooleanType HaldClutImage(Image *image,Image *hald_image)
2612% MagickBooleanType HaldClutImageChannel(Image *image,
2613% const ChannelType channel,Image *hald_image)
2614%
2615% A description of each parameter follows:
2616%
2617% o image: the image, which is replaced by indexed CLUT values
2618%
2619% o hald_image: the color lookup table image for replacement color values.
2620%
2621% o channel: the channel.
2622%
2623*/
2624
2625MagickExport MagickBooleanType HaldClutImage(Image *image,
2626 const Image *hald_image)
2627{
2628 return(HaldClutImageChannel(image,DefaultChannels,hald_image));
2629}
2630
2631MagickExport MagickBooleanType HaldClutImageChannel(Image *image,
2632 const ChannelType channel,const Image *hald_image)
2633{
2634#define HaldClutImageTag "Clut/Image"
2635
2636 typedef struct _HaldInfo
2637 {
2638 MagickRealType
2639 x,
2640 y,
2641 z;
2642 } HaldInfo;
2643
2644 CacheView
2645 *hald_view,
2646 *image_view;
2647
2648 double
2649 width;
2650
2652 *exception;
2653
2654 MagickBooleanType
2655 status;
2656
2657 MagickOffsetType
2658 progress;
2659
2661 zero;
2662
2663 size_t
2664 cube_size,
2665 length,
2666 level;
2667
2668 ssize_t
2669 y;
2670
2671 assert(image != (Image *) NULL);
2672 assert(image->signature == MagickCoreSignature);
2673 if (IsEventLogging() != MagickFalse)
2674 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2675 assert(hald_image != (Image *) NULL);
2676 assert(hald_image->signature == MagickCoreSignature);
2677 if (SetImageStorageClass(image,DirectClass) == MagickFalse)
2678 return(MagickFalse);
2679 if (IsGrayColorspace(image->colorspace) != MagickFalse)
2680 (void) SetImageColorspace(image,sRGBColorspace);
2681 if (image->matte == MagickFalse)
2682 (void) SetImageAlphaChannel(image,OpaqueAlphaChannel);
2683 /*
2684 Hald clut image.
2685 */
2686 status=MagickTrue;
2687 progress=0;
2688 length=(size_t) MagickMin((MagickRealType) hald_image->columns,
2689 (MagickRealType) hald_image->rows);
2690 for (level=2; (level*level*level) < length; level++) ;
2691 level*=level;
2692 cube_size=level*level;
2693 width=(double) hald_image->columns;
2694 GetMagickPixelPacket(hald_image,&zero);
2695 exception=(&image->exception);
2696 image_view=AcquireAuthenticCacheView(image,exception);
2697 hald_view=AcquireAuthenticCacheView(hald_image,exception);
2698#if defined(MAGICKCORE_OPENMP_SUPPORT)
2699 #pragma omp parallel for schedule(static) shared(progress,status) \
2700 magick_number_threads(image,hald_image,image->rows,1)
2701#endif
2702 for (y=0; y < (ssize_t) image->rows; y++)
2703 {
2704 double
2705 area,
2706 offset;
2707
2708 HaldInfo
2709 point;
2710
2712 pixel,
2713 pixel1,
2714 pixel2,
2715 pixel3,
2716 pixel4;
2717
2718 IndexPacket
2719 *magick_restrict indexes;
2720
2722 *magick_restrict q;
2723
2724 ssize_t
2725 x;
2726
2727 if (status == MagickFalse)
2728 continue;
2729 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2730 if (q == (PixelPacket *) NULL)
2731 {
2732 status=MagickFalse;
2733 continue;
2734 }
2735 indexes=GetCacheViewAuthenticIndexQueue(hald_view);
2736 pixel=zero;
2737 pixel1=zero;
2738 pixel2=zero;
2739 pixel3=zero;
2740 pixel4=zero;
2741 for (x=0; x < (ssize_t) image->columns; x++)
2742 {
2743 point.x=QuantumScale*(level-1.0)*(double) GetPixelRed(q);
2744 point.y=QuantumScale*(level-1.0)*(double) GetPixelGreen(q);
2745 point.z=QuantumScale*(level-1.0)*(double) GetPixelBlue(q);
2746 offset=(double) (point.x+level*floor(point.y)+cube_size*floor(point.z));
2747 point.x-=floor(point.x);
2748 point.y-=floor(point.y);
2749 point.z-=floor(point.z);
2750 status=InterpolateMagickPixelPacket(image,hald_view,
2751 UndefinedInterpolatePixel,fmod(offset,width),floor(offset/width),
2752 &pixel1,exception);
2753 if (status == MagickFalse)
2754 break;
2755 status=InterpolateMagickPixelPacket(image,hald_view,
2756 UndefinedInterpolatePixel,fmod(offset+level,width),floor((offset+level)/
2757 width),&pixel2,exception);
2758 if (status == MagickFalse)
2759 break;
2760 area=point.y;
2761 if (hald_image->interpolate == NearestNeighborInterpolatePixel)
2762 area=(point.y < 0.5) ? 0.0 : 1.0;
2763 MagickPixelCompositeAreaBlend(&pixel1,pixel1.opacity,&pixel2,
2764 pixel2.opacity,area,&pixel3);
2765 offset+=cube_size;
2766 status=InterpolateMagickPixelPacket(image,hald_view,
2767 UndefinedInterpolatePixel,fmod(offset,width),floor(offset/width),
2768 &pixel1,exception);
2769 if (status == MagickFalse)
2770 break;
2771 status=InterpolateMagickPixelPacket(image,hald_view,
2772 UndefinedInterpolatePixel,fmod(offset+level,width),floor((offset+level)/
2773 width),&pixel2,exception);
2774 if (status == MagickFalse)
2775 break;
2776 MagickPixelCompositeAreaBlend(&pixel1,pixel1.opacity,&pixel2,
2777 pixel2.opacity,area,&pixel4);
2778 area=point.z;
2779 if (hald_image->interpolate == NearestNeighborInterpolatePixel)
2780 area=(point.z < 0.5)? 0.0 : 1.0;
2781 MagickPixelCompositeAreaBlend(&pixel3,pixel3.opacity,&pixel4,
2782 pixel4.opacity,area,&pixel);
2783 if ((channel & RedChannel) != 0)
2784 SetPixelRed(q,ClampToQuantum(pixel.red));
2785 if ((channel & GreenChannel) != 0)
2786 SetPixelGreen(q,ClampToQuantum(pixel.green));
2787 if ((channel & BlueChannel) != 0)
2788 SetPixelBlue(q,ClampToQuantum(pixel.blue));
2789 if (((channel & OpacityChannel) != 0) && (image->matte != MagickFalse))
2790 SetPixelOpacity(q,ClampToQuantum(pixel.opacity));
2791 if (((channel & IndexChannel) != 0) &&
2792 (image->colorspace == CMYKColorspace))
2793 SetPixelIndex(indexes+x,ClampToQuantum(pixel.index));
2794 q++;
2795 }
2796 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2797 status=MagickFalse;
2798 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2799 {
2800 MagickBooleanType
2801 proceed;
2802
2803#if defined(MAGICKCORE_OPENMP_SUPPORT)
2804 #pragma omp atomic
2805#endif
2806 progress++;
2807 proceed=SetImageProgress(image,HaldClutImageTag,progress,image->rows);
2808 if (proceed == MagickFalse)
2809 status=MagickFalse;
2810 }
2811 }
2812 hald_view=DestroyCacheView(hald_view);
2813 image_view=DestroyCacheView(image_view);
2814 return(status);
2815}
2816
2817/*
2818%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2819% %
2820% %
2821% %
2822% L e v e l I m a g e %
2823% %
2824% %
2825% %
2826%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2827%
2828% LevelImage() adjusts the levels of a particular image channel by
2829% scaling the colors falling between specified white and black points to
2830% the full available quantum range.
2831%
2832% The parameters provided represent the black, and white points. The black
2833% point specifies the darkest color in the image. Colors darker than the
2834% black point are set to zero. White point specifies the lightest color in
2835% the image. Colors brighter than the white point are set to the maximum
2836% quantum value.
2837%
2838% If a '!' flag is given, map black and white colors to the given levels
2839% rather than mapping those levels to black and white. See
2840% LevelizeImageChannel() and LevelizeImageChannel(), below.
2841%
2842% Gamma specifies a gamma correction to apply to the image.
2843%
2844% The format of the LevelImage method is:
2845%
2846% MagickBooleanType LevelImage(Image *image,const char *levels)
2847%
2848% A description of each parameter follows:
2849%
2850% o image: the image.
2851%
2852% o levels: Specify the levels where the black and white points have the
2853% range of 0-QuantumRange, and gamma has the range 0-10 (e.g. 10x90%+2).
2854% A '!' flag inverts the re-mapping.
2855%
2856*/
2857
2858MagickExport MagickBooleanType LevelImage(Image *image,const char *levels)
2859{
2860 double
2861 black_point = 0.0,
2862 gamma = 1.0,
2863 white_point = (double) QuantumRange;
2864
2866 geometry_info;
2867
2868 MagickBooleanType
2869 status;
2870
2871 MagickStatusType
2872 flags;
2873
2874 /*
2875 Parse levels.
2876 */
2877 if (levels == (char *) NULL)
2878 return(MagickFalse);
2879 flags=ParseGeometry(levels,&geometry_info);
2880 if ((flags & RhoValue) != 0)
2881 black_point=geometry_info.rho;
2882 if ((flags & SigmaValue) != 0)
2883 white_point=geometry_info.sigma;
2884 if ((flags & XiValue) != 0)
2885 gamma=geometry_info.xi;
2886 if ((flags & PercentValue) != 0)
2887 {
2888 black_point*=(double) image->columns*image->rows/100.0;
2889 white_point*=(double) image->columns*image->rows/100.0;
2890 }
2891 if ((flags & SigmaValue) == 0)
2892 white_point=(double) QuantumRange-black_point;
2893 if ((flags & AspectValue ) == 0)
2894 status=LevelImageChannel(image,DefaultChannels,black_point,white_point,
2895 gamma);
2896 else
2897 status=LevelizeImage(image,black_point,white_point,gamma);
2898 return(status);
2899}
2900
2901/*
2902%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2903% %
2904% %
2905% %
2906% L e v e l I m a g e %
2907% %
2908% %
2909% %
2910%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2911%
2912% LevelImage() applies the normal level operation to the image, spreading
2913% out the values between the black and white points over the entire range of
2914% values. Gamma correction is also applied after the values has been mapped.
2915%
2916% It is typically used to improve image contrast, or to provide a controlled
2917% linear threshold for the image. If the black and white points are set to
2918% the minimum and maximum values found in the image, the image can be
2919% normalized. or by swapping black and white values, negate the image.
2920%
2921% The format of the LevelImage method is:
2922%
2923% MagickBooleanType LevelImage(Image *image,const double black_point,
2924% const double white_point,const double gamma)
2925% MagickBooleanType LevelImageChannel(Image *image,
2926% const ChannelType channel,const double black_point,
2927% const double white_point,const double gamma)
2928%
2929% A description of each parameter follows:
2930%
2931% o image: the image.
2932%
2933% o channel: the channel.
2934%
2935% o black_point: The level which is to be mapped to zero (black)
2936%
2937% o white_point: The level which is to be mapped to QuantumRange (white)
2938%
2939% o gamma: adjust gamma by this factor before mapping values.
2940% use 1.0 for purely linear stretching of image color values
2941%
2942*/
2943
2944static inline double LevelPixel(const double black_point,
2945 const double white_point,const double gamma,const MagickRealType pixel)
2946{
2947 double
2948 level_pixel,
2949 scale;
2950
2951 scale=PerceptibleReciprocal(white_point-black_point);
2952 level_pixel=(double) QuantumRange*gamma_pow(scale*((double) pixel-
2953 black_point),PerceptibleReciprocal(gamma));
2954 return(level_pixel);
2955}
2956
2957MagickExport MagickBooleanType LevelImageChannel(Image *image,
2958 const ChannelType channel,const double black_point,const double white_point,
2959 const double gamma)
2960{
2961#define LevelImageTag "Level/Image"
2962
2963 CacheView
2964 *image_view;
2965
2967 *exception;
2968
2969 MagickBooleanType
2970 status;
2971
2972 MagickOffsetType
2973 progress;
2974
2975 ssize_t
2976 i;
2977
2978 ssize_t
2979 y;
2980
2981 /*
2982 Allocate and initialize levels map.
2983 */
2984 assert(image != (Image *) NULL);
2985 assert(image->signature == MagickCoreSignature);
2986 if (IsEventLogging() != MagickFalse)
2987 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2988 if (image->storage_class == PseudoClass)
2989 for (i=0; i < (ssize_t) image->colors; i++)
2990 {
2991 /*
2992 Level colormap.
2993 */
2994 if ((channel & RedChannel) != 0)
2995 image->colormap[i].red=(Quantum) ClampToQuantum(LevelPixel(black_point,
2996 white_point,gamma,(MagickRealType) image->colormap[i].red));
2997 if ((channel & GreenChannel) != 0)
2998 image->colormap[i].green=(Quantum) ClampToQuantum(LevelPixel(
2999 black_point,white_point,gamma,(MagickRealType)
3000 image->colormap[i].green));
3001 if ((channel & BlueChannel) != 0)
3002 image->colormap[i].blue=(Quantum) ClampToQuantum(LevelPixel(black_point,
3003 white_point,gamma,(MagickRealType) image->colormap[i].blue));
3004 if ((channel & OpacityChannel) != 0)
3005 image->colormap[i].opacity=(Quantum) (QuantumRange-(Quantum)
3006 ClampToQuantum(LevelPixel(black_point,white_point,gamma,
3007 (MagickRealType) (QuantumRange-image->colormap[i].opacity))));
3008 }
3009 /*
3010 Level image.
3011 */
3012 status=MagickTrue;
3013 progress=0;
3014 exception=(&image->exception);
3015 image_view=AcquireAuthenticCacheView(image,exception);
3016#if defined(MAGICKCORE_OPENMP_SUPPORT)
3017 #pragma omp parallel for schedule(static) shared(progress,status) \
3018 magick_number_threads(image,image,image->rows,1)
3019#endif
3020 for (y=0; y < (ssize_t) image->rows; y++)
3021 {
3022 IndexPacket
3023 *magick_restrict indexes;
3024
3026 *magick_restrict q;
3027
3028 ssize_t
3029 x;
3030
3031 if (status == MagickFalse)
3032 continue;
3033 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
3034 if (q == (PixelPacket *) NULL)
3035 {
3036 status=MagickFalse;
3037 continue;
3038 }
3039 indexes=GetCacheViewAuthenticIndexQueue(image_view);
3040 for (x=0; x < (ssize_t) image->columns; x++)
3041 {
3042 if ((channel & RedChannel) != 0)
3043 SetPixelRed(q,ClampToQuantum(LevelPixel(black_point,white_point,gamma,
3044 (MagickRealType) GetPixelRed(q))));
3045 if ((channel & GreenChannel) != 0)
3046 SetPixelGreen(q,ClampToQuantum(LevelPixel(black_point,white_point,gamma,
3047 (MagickRealType) GetPixelGreen(q))));
3048 if ((channel & BlueChannel) != 0)
3049 SetPixelBlue(q,ClampToQuantum(LevelPixel(black_point,white_point,gamma,
3050 (MagickRealType) GetPixelBlue(q))));
3051 if (((channel & OpacityChannel) != 0) &&
3052 (image->matte != MagickFalse))
3053 SetPixelAlpha(q,ClampToQuantum(LevelPixel(black_point,white_point,gamma,
3054 (MagickRealType) GetPixelAlpha(q))));
3055 if (((channel & IndexChannel) != 0) &&
3056 (image->colorspace == CMYKColorspace))
3057 SetPixelIndex(indexes+x,ClampToQuantum(LevelPixel(black_point,
3058 white_point,gamma,(MagickRealType) GetPixelIndex(indexes+x))));
3059 q++;
3060 }
3061 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
3062 status=MagickFalse;
3063 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3064 {
3065 MagickBooleanType
3066 proceed;
3067
3068#if defined(MAGICKCORE_OPENMP_SUPPORT)
3069 #pragma omp atomic
3070#endif
3071 progress++;
3072 proceed=SetImageProgress(image,LevelImageTag,progress,image->rows);
3073 if (proceed == MagickFalse)
3074 status=MagickFalse;
3075 }
3076 }
3077 image_view=DestroyCacheView(image_view);
3078 (void) ClampImage(image);
3079 return(status);
3080}
3081
3082/*
3083%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3084% %
3085% %
3086% %
3087% L e v e l i z e I m a g e C h a n n e l %
3088% %
3089% %
3090% %
3091%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3092%
3093% LevelizeImageChannel() applies the reversed LevelImage() operation to just
3094% the specific channels specified. It compresses the full range of color
3095% values, so that they lie between the given black and white points. Gamma is
3096% applied before the values are mapped.
3097%
3098% LevelizeImageChannel() can be called with by using a +level command line
3099% API option, or using a '!' on a -level or LevelImage() geometry string.
3100%
3101% It can be used for example de-contrast a greyscale image to the exact
3102% levels specified. Or by using specific levels for each channel of an image
3103% you can convert a gray-scale image to any linear color gradient, according
3104% to those levels.
3105%
3106% The format of the LevelizeImageChannel method is:
3107%
3108% MagickBooleanType LevelizeImageChannel(Image *image,
3109% const ChannelType channel,const char *levels)
3110%
3111% A description of each parameter follows:
3112%
3113% o image: the image.
3114%
3115% o channel: the channel.
3116%
3117% o black_point: The level to map zero (black) to.
3118%
3119% o white_point: The level to map QuantumRange (white) to.
3120%
3121% o gamma: adjust gamma by this factor before mapping values.
3122%
3123*/
3124
3125MagickExport MagickBooleanType LevelizeImage(Image *image,
3126 const double black_point,const double white_point,const double gamma)
3127{
3128 MagickBooleanType
3129 status;
3130
3131 status=LevelizeImageChannel(image,DefaultChannels,black_point,white_point,
3132 gamma);
3133 return(status);
3134}
3135
3136MagickExport MagickBooleanType LevelizeImageChannel(Image *image,
3137 const ChannelType channel,const double black_point,const double white_point,
3138 const double gamma)
3139{
3140#define LevelizeImageTag "Levelize/Image"
3141#define LevelizeValue(x) ClampToQuantum(((MagickRealType) gamma_pow((double) \
3142 (QuantumScale*(double) (x)),gamma))*((double) white_point-(double) \
3143 black_point)+(double) black_point)
3144
3145 CacheView
3146 *image_view;
3147
3149 *exception;
3150
3151 MagickBooleanType
3152 status;
3153
3154 MagickOffsetType
3155 progress;
3156
3157 ssize_t
3158 i;
3159
3160 ssize_t
3161 y;
3162
3163 /*
3164 Allocate and initialize levels map.
3165 */
3166 assert(image != (Image *) NULL);
3167 assert(image->signature == MagickCoreSignature);
3168 if (IsEventLogging() != MagickFalse)
3169 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3170 if (image->storage_class == PseudoClass)
3171 for (i=0; i < (ssize_t) image->colors; i++)
3172 {
3173 /*
3174 Level colormap.
3175 */
3176 if ((channel & RedChannel) != 0)
3177 image->colormap[i].red=LevelizeValue(image->colormap[i].red);
3178 if ((channel & GreenChannel) != 0)
3179 image->colormap[i].green=LevelizeValue(image->colormap[i].green);
3180 if ((channel & BlueChannel) != 0)
3181 image->colormap[i].blue=LevelizeValue(image->colormap[i].blue);
3182 if ((channel & OpacityChannel) != 0)
3183 image->colormap[i].opacity=(Quantum) (QuantumRange-LevelizeValue(
3184 QuantumRange-image->colormap[i].opacity));
3185 }
3186 /*
3187 Level image.
3188 */
3189 status=MagickTrue;
3190 progress=0;
3191 exception=(&image->exception);
3192 image_view=AcquireAuthenticCacheView(image,exception);
3193#if defined(MAGICKCORE_OPENMP_SUPPORT)
3194 #pragma omp parallel for schedule(static) shared(progress,status) \
3195 magick_number_threads(image,image,image->rows,1)
3196#endif
3197 for (y=0; y < (ssize_t) image->rows; y++)
3198 {
3199 IndexPacket
3200 *magick_restrict indexes;
3201
3203 *magick_restrict q;
3204
3205 ssize_t
3206 x;
3207
3208 if (status == MagickFalse)
3209 continue;
3210 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
3211 if (q == (PixelPacket *) NULL)
3212 {
3213 status=MagickFalse;
3214 continue;
3215 }
3216 indexes=GetCacheViewAuthenticIndexQueue(image_view);
3217 for (x=0; x < (ssize_t) image->columns; x++)
3218 {
3219 if ((channel & RedChannel) != 0)
3220 SetPixelRed(q,LevelizeValue(GetPixelRed(q)));
3221 if ((channel & GreenChannel) != 0)
3222 SetPixelGreen(q,LevelizeValue(GetPixelGreen(q)));
3223 if ((channel & BlueChannel) != 0)
3224 SetPixelBlue(q,LevelizeValue(GetPixelBlue(q)));
3225 if (((channel & OpacityChannel) != 0) &&
3226 (image->matte != MagickFalse))
3227 SetPixelAlpha(q,LevelizeValue(GetPixelAlpha(q)));
3228 if (((channel & IndexChannel) != 0) &&
3229 (image->colorspace == CMYKColorspace))
3230 SetPixelIndex(indexes+x,LevelizeValue(GetPixelIndex(indexes+x)));
3231 q++;
3232 }
3233 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
3234 status=MagickFalse;
3235 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3236 {
3237 MagickBooleanType
3238 proceed;
3239
3240#if defined(MAGICKCORE_OPENMP_SUPPORT)
3241 #pragma omp atomic
3242#endif
3243 progress++;
3244 proceed=SetImageProgress(image,LevelizeImageTag,progress,image->rows);
3245 if (proceed == MagickFalse)
3246 status=MagickFalse;
3247 }
3248 }
3249 image_view=DestroyCacheView(image_view);
3250 return(status);
3251}
3252
3253/*
3254%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3255% %
3256% %
3257% %
3258% L e v e l I m a g e C o l o r s %
3259% %
3260% %
3261% %
3262%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3263%
3264% LevelImageColor() maps the given color to "black" and "white" values,
3265% linearly spreading out the colors, and level values on a channel by channel
3266% bases, as per LevelImage(). The given colors allows you to specify
3267% different level ranges for each of the color channels separately.
3268%
3269% If the boolean 'invert' is set true the image values will modified in the
3270% reverse direction. That is any existing "black" and "white" colors in the
3271% image will become the color values given, with all other values compressed
3272% appropriatally. This effectivally maps a greyscale gradient into the given
3273% color gradient.
3274%
3275% The format of the LevelColorsImageChannel method is:
3276%
3277% MagickBooleanType LevelColorsImage(Image *image,
3278% const MagickPixelPacket *black_color,
3279% const MagickPixelPacket *white_color,const MagickBooleanType invert)
3280% MagickBooleanType LevelColorsImageChannel(Image *image,
3281% const ChannelType channel,const MagickPixelPacket *black_color,
3282% const MagickPixelPacket *white_color,const MagickBooleanType invert)
3283%
3284% A description of each parameter follows:
3285%
3286% o image: the image.
3287%
3288% o channel: the channel.
3289%
3290% o black_color: The color to map black to/from
3291%
3292% o white_point: The color to map white to/from
3293%
3294% o invert: if true map the colors (levelize), rather than from (level)
3295%
3296*/
3297
3298MagickExport MagickBooleanType LevelColorsImage(Image *image,
3299 const MagickPixelPacket *black_color,const MagickPixelPacket *white_color,
3300 const MagickBooleanType invert)
3301{
3302 MagickBooleanType
3303 status;
3304
3305 status=LevelColorsImageChannel(image,DefaultChannels,black_color,white_color,
3306 invert);
3307 return(status);
3308}
3309
3310MagickExport MagickBooleanType LevelColorsImageChannel(Image *image,
3311 const ChannelType channel,const MagickPixelPacket *black_color,
3312 const MagickPixelPacket *white_color,const MagickBooleanType invert)
3313{
3314 MagickStatusType
3315 status;
3316
3317 /*
3318 Allocate and initialize levels map.
3319 */
3320 assert(image != (Image *) NULL);
3321 assert(image->signature == MagickCoreSignature);
3322 if (IsEventLogging() != MagickFalse)
3323 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3324 if ((IsGrayColorspace(image->colorspace) != MagickFalse) &&
3325 ((IsGrayColorspace(black_color->colorspace) != MagickFalse) ||
3326 (IsGrayColorspace(white_color->colorspace) != MagickFalse)))
3327 (void) SetImageColorspace(image,sRGBColorspace);
3328 status=MagickTrue;
3329 if (invert == MagickFalse)
3330 {
3331 if ((channel & RedChannel) != 0)
3332 status&=LevelImageChannel(image,RedChannel,black_color->red,
3333 white_color->red,(double) 1.0);
3334 if ((channel & GreenChannel) != 0)
3335 status&=LevelImageChannel(image,GreenChannel,black_color->green,
3336 white_color->green,(double) 1.0);
3337 if ((channel & BlueChannel) != 0)
3338 status&=LevelImageChannel(image,BlueChannel,black_color->blue,
3339 white_color->blue,(double) 1.0);
3340 if (((channel & OpacityChannel) != 0) &&
3341 (image->matte != MagickFalse))
3342 status&=LevelImageChannel(image,OpacityChannel,black_color->opacity,
3343 white_color->opacity,(double) 1.0);
3344 if (((channel & IndexChannel) != 0) &&
3345 (image->colorspace == CMYKColorspace))
3346 status&=LevelImageChannel(image,IndexChannel,black_color->index,
3347 white_color->index,(double) 1.0);
3348 }
3349 else
3350 {
3351 if ((channel & RedChannel) != 0)
3352 status&=LevelizeImageChannel(image,RedChannel,black_color->red,
3353 white_color->red,(double) 1.0);
3354 if ((channel & GreenChannel) != 0)
3355 status&=LevelizeImageChannel(image,GreenChannel,black_color->green,
3356 white_color->green,(double) 1.0);
3357 if ((channel & BlueChannel) != 0)
3358 status&=LevelizeImageChannel(image,BlueChannel,black_color->blue,
3359 white_color->blue,(double) 1.0);
3360 if (((channel & OpacityChannel) != 0) &&
3361 (image->matte != MagickFalse))
3362 status&=LevelizeImageChannel(image,OpacityChannel,black_color->opacity,
3363 white_color->opacity,(double) 1.0);
3364 if (((channel & IndexChannel) != 0) &&
3365 (image->colorspace == CMYKColorspace))
3366 status&=LevelizeImageChannel(image,IndexChannel,black_color->index,
3367 white_color->index,(double) 1.0);
3368 }
3369 return(status == 0 ? MagickFalse : MagickTrue);
3370}
3371
3372/*
3373%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3374% %
3375% %
3376% %
3377% L i n e a r S t r e t c h I m a g e %
3378% %
3379% %
3380% %
3381%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3382%
3383% LinearStretchImage() discards any pixels below the black point and above
3384% the white point and levels the remaining pixels.
3385%
3386% The format of the LinearStretchImage method is:
3387%
3388% MagickBooleanType LinearStretchImage(Image *image,
3389% const double black_point,const double white_point)
3390%
3391% A description of each parameter follows:
3392%
3393% o image: the image.
3394%
3395% o black_point: the black point.
3396%
3397% o white_point: the white point.
3398%
3399*/
3400MagickExport MagickBooleanType LinearStretchImage(Image *image,
3401 const double black_point,const double white_point)
3402{
3403#define LinearStretchImageTag "LinearStretch/Image"
3404
3406 *exception;
3407
3408 MagickBooleanType
3409 status;
3410
3411 MagickRealType
3412 *histogram,
3413 intensity;
3414
3415 ssize_t
3416 black,
3417 white,
3418 y;
3419
3420 /*
3421 Allocate histogram and linear map.
3422 */
3423 assert(image != (Image *) NULL);
3424 assert(image->signature == MagickCoreSignature);
3425 exception=(&image->exception);
3426 histogram=(MagickRealType *) AcquireQuantumMemory(MaxMap+1UL,
3427 sizeof(*histogram));
3428 if (histogram == (MagickRealType *) NULL)
3429 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
3430 image->filename);
3431 /*
3432 Form histogram.
3433 */
3434 (void) memset(histogram,0,(MaxMap+1)*sizeof(*histogram));
3435 for (y=0; y < (ssize_t) image->rows; y++)
3436 {
3437 const PixelPacket
3438 *magick_restrict p;
3439
3440 ssize_t
3441 x;
3442
3443 p=GetVirtualPixels(image,0,y,image->columns,1,exception);
3444 if (p == (const PixelPacket *) NULL)
3445 break;
3446 for (x=(ssize_t) image->columns-1; x >= 0; x--)
3447 {
3448 histogram[ScaleQuantumToMap(ClampToQuantum(GetPixelIntensity(image,p)))]++;
3449 p++;
3450 }
3451 }
3452 /*
3453 Find the histogram boundaries by locating the black and white point levels.
3454 */
3455 intensity=0.0;
3456 for (black=0; black < (ssize_t) MaxMap; black++)
3457 {
3458 intensity+=histogram[black];
3459 if (intensity >= black_point)
3460 break;
3461 }
3462 intensity=0.0;
3463 for (white=(ssize_t) MaxMap; white != 0; white--)
3464 {
3465 intensity+=histogram[white];
3466 if (intensity >= white_point)
3467 break;
3468 }
3469 histogram=(MagickRealType *) RelinquishMagickMemory(histogram);
3470 status=LevelImageChannel(image,DefaultChannels,(double)
3471 ScaleMapToQuantum(black),(double) ScaleMapToQuantum(white),1.0);
3472 return(status);
3473}
3474
3475/*
3476%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3477% %
3478% %
3479% %
3480% M o d u l a t e I m a g e %
3481% %
3482% %
3483% %
3484%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3485%
3486% ModulateImage() lets you control the brightness, saturation, and hue
3487% of an image. Modulate represents the brightness, saturation, and hue
3488% as one parameter (e.g. 90,150,100). If the image colorspace is HSL, the
3489% modulation is lightness, saturation, and hue. For HWB, use blackness,
3490% whiteness, and hue. And for HCL, use chrome, luma, and hue.
3491%
3492% The format of the ModulateImage method is:
3493%
3494% MagickBooleanType ModulateImage(Image *image,const char *modulate)
3495%
3496% A description of each parameter follows:
3497%
3498% o image: the image.
3499%
3500% o modulate: Define the percent change in brightness, saturation, and
3501% hue.
3502%
3503*/
3504
3505static inline void ModulateHCL(const double percent_hue,
3506 const double percent_chroma,const double percent_luma,Quantum *red,
3507 Quantum *green,Quantum *blue)
3508{
3509 double
3510 hue,
3511 luma,
3512 chroma;
3513
3514 /*
3515 Increase or decrease color luma, chroma, or hue.
3516 */
3517 ConvertRGBToHCL(*red,*green,*blue,&hue,&chroma,&luma);
3518 hue+=fmod((percent_hue-100.0),200.0)/200.0;
3519 chroma*=0.01*percent_chroma;
3520 luma*=0.01*percent_luma;
3521 ConvertHCLToRGB(hue,chroma,luma,red,green,blue);
3522}
3523
3524static inline void ModulateHCLp(const double percent_hue,
3525 const double percent_chroma,const double percent_luma,Quantum *red,
3526 Quantum *green,Quantum *blue)
3527{
3528 double
3529 hue,
3530 luma,
3531 chroma;
3532
3533 /*
3534 Increase or decrease color luma, chroma, or hue.
3535 */
3536 ConvertRGBToHCLp(*red,*green,*blue,&hue,&chroma,&luma);
3537 hue+=fmod((percent_hue-100.0),200.0)/200.0;
3538 chroma*=0.01*percent_chroma;
3539 luma*=0.01*percent_luma;
3540 ConvertHCLpToRGB(hue,chroma,luma,red,green,blue);
3541}
3542
3543static inline void ModulateHSB(const double percent_hue,
3544 const double percent_saturation,const double percent_brightness,Quantum *red,
3545 Quantum *green,Quantum *blue)
3546{
3547 double
3548 brightness,
3549 hue,
3550 saturation;
3551
3552 /*
3553 Increase or decrease color brightness, saturation, or hue.
3554 */
3555 ConvertRGBToHSB(*red,*green,*blue,&hue,&saturation,&brightness);
3556 hue+=fmod((percent_hue-100.0),200.0)/200.0;
3557 saturation*=0.01*percent_saturation;
3558 brightness*=0.01*percent_brightness;
3559 ConvertHSBToRGB(hue,saturation,brightness,red,green,blue);
3560}
3561
3562static inline void ModulateHSI(const double percent_hue,
3563 const double percent_saturation,const double percent_intensity,Quantum *red,
3564 Quantum *green,Quantum *blue)
3565{
3566 double
3567 intensity,
3568 hue,
3569 saturation;
3570
3571 /*
3572 Increase or decrease color intensity, saturation, or hue.
3573 */
3574 ConvertRGBToHSI(*red,*green,*blue,&hue,&saturation,&intensity);
3575 hue+=fmod((percent_hue-100.0),200.0)/200.0;
3576 saturation*=0.01*percent_saturation;
3577 intensity*=0.01*percent_intensity;
3578 ConvertHSIToRGB(hue,saturation,intensity,red,green,blue);
3579}
3580
3581static inline void ModulateHSL(const double percent_hue,
3582 const double percent_saturation,const double percent_lightness,Quantum *red,
3583 Quantum *green,Quantum *blue)
3584{
3585 double
3586 hue,
3587 lightness,
3588 saturation;
3589
3590 /*
3591 Increase or decrease color lightness, saturation, or hue.
3592 */
3593 ConvertRGBToHSL(*red,*green,*blue,&hue,&saturation,&lightness);
3594 hue+=fmod((percent_hue-100.0),200.0)/200.0;
3595 saturation*=0.01*percent_saturation;
3596 lightness*=0.01*percent_lightness;
3597 ConvertHSLToRGB(hue,saturation,lightness,red,green,blue);
3598}
3599
3600static inline void ModulateHSV(const double percent_hue,
3601 const double percent_saturation,const double percent_value,Quantum *red,
3602 Quantum *green,Quantum *blue)
3603{
3604 double
3605 hue,
3606 saturation,
3607 value;
3608
3609 /*
3610 Increase or decrease color value, saturation, or hue.
3611 */
3612 ConvertRGBToHSV(*red,*green,*blue,&hue,&saturation,&value);
3613 hue+=fmod((percent_hue-100.0),200.0)/200.0;
3614 saturation*=0.01*percent_saturation;
3615 value*=0.01*percent_value;
3616 ConvertHSVToRGB(hue,saturation,value,red,green,blue);
3617}
3618
3619static inline void ModulateHWB(const double percent_hue,
3620 const double percent_whiteness,const double percent_blackness,Quantum *red,
3621 Quantum *green,Quantum *blue)
3622{
3623 double
3624 blackness,
3625 hue,
3626 whiteness;
3627
3628 /*
3629 Increase or decrease color blackness, whiteness, or hue.
3630 */
3631 ConvertRGBToHWB(*red,*green,*blue,&hue,&whiteness,&blackness);
3632 hue+=fmod((percent_hue-100.0),200.0)/200.0;
3633 blackness*=0.01*percent_blackness;
3634 whiteness*=0.01*percent_whiteness;
3635 ConvertHWBToRGB(hue,whiteness,blackness,red,green,blue);
3636}
3637
3638static inline void ModulateLCHab(const double percent_luma,
3639 const double percent_chroma,const double percent_hue,Quantum *red,
3640 Quantum *green,Quantum *blue)
3641{
3642 double
3643 hue,
3644 luma,
3645 chroma;
3646
3647 /*
3648 Increase or decrease color luma, chroma, or hue.
3649 */
3650 ConvertRGBToLCHab(*red,*green,*blue,&luma,&chroma,&hue);
3651 luma*=0.01*percent_luma;
3652 chroma*=0.01*percent_chroma;
3653 hue+=fmod((percent_hue-100.0),200.0)/200.0;
3654 ConvertLCHabToRGB(luma,chroma,hue,red,green,blue);
3655}
3656
3657static inline void ModulateLCHuv(const double percent_luma,
3658 const double percent_chroma,const double percent_hue,Quantum *red,
3659 Quantum *green,Quantum *blue)
3660{
3661 double
3662 hue,
3663 luma,
3664 chroma;
3665
3666 /*
3667 Increase or decrease color luma, chroma, or hue.
3668 */
3669 ConvertRGBToLCHuv(*red,*green,*blue,&luma,&chroma,&hue);
3670 luma*=0.01*percent_luma;
3671 chroma*=0.01*percent_chroma;
3672 hue+=fmod((percent_hue-100.0),200.0)/200.0;
3673 ConvertLCHuvToRGB(luma,chroma,hue,red,green,blue);
3674}
3675
3676MagickExport MagickBooleanType ModulateImage(Image *image,const char *modulate)
3677{
3678#define ModulateImageTag "Modulate/Image"
3679
3680 CacheView
3681 *image_view;
3682
3683 ColorspaceType
3684 colorspace;
3685
3686 const char
3687 *artifact;
3688
3689 double
3690 percent_brightness = 100.0,
3691 percent_hue = 100.0,
3692 percent_saturation = 100.0;
3693
3695 *exception;
3696
3698 geometry_info;
3699
3700 MagickBooleanType
3701 status;
3702
3703 MagickOffsetType
3704 progress;
3705
3706 MagickStatusType
3707 flags;
3708
3709 ssize_t
3710 i;
3711
3712 ssize_t
3713 y;
3714
3715 /*
3716 Initialize modulate table.
3717 */
3718 assert(image != (Image *) NULL);
3719 assert(image->signature == MagickCoreSignature);
3720 if (IsEventLogging() != MagickFalse)
3721 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3722 if (modulate == (char *) NULL)
3723 return(MagickFalse);
3724 if (IssRGBCompatibleColorspace(image->colorspace) == MagickFalse)
3725 (void) SetImageColorspace(image,sRGBColorspace);
3726 flags=ParseGeometry(modulate,&geometry_info);
3727 if ((flags & RhoValue) != 0)
3728 percent_brightness=geometry_info.rho;
3729 if ((flags & SigmaValue) != 0)
3730 percent_saturation=geometry_info.sigma;
3731 if ((flags & XiValue) != 0)
3732 percent_hue=geometry_info.xi;
3733 colorspace=UndefinedColorspace;
3734 artifact=GetImageArtifact(image,"modulate:colorspace");
3735 if (artifact != (const char *) NULL)
3736 colorspace=(ColorspaceType) ParseCommandOption(MagickColorspaceOptions,
3737 MagickFalse,artifact);
3738 if (image->storage_class == PseudoClass)
3739 for (i=0; i < (ssize_t) image->colors; i++)
3740 {
3741 Quantum
3742 blue,
3743 green,
3744 red;
3745
3746 /*
3747 Modulate image colormap.
3748 */
3749 red=image->colormap[i].red;
3750 green=image->colormap[i].green;
3751 blue=image->colormap[i].blue;
3752 switch (colorspace)
3753 {
3754 case HCLColorspace:
3755 {
3756 ModulateHCL(percent_hue,percent_saturation,percent_brightness,
3757 &red,&green,&blue);
3758 break;
3759 }
3760 case HCLpColorspace:
3761 {
3762 ModulateHCLp(percent_hue,percent_saturation,percent_brightness,
3763 &red,&green,&blue);
3764 break;
3765 }
3766 case HSBColorspace:
3767 {
3768 ModulateHSB(percent_hue,percent_saturation,percent_brightness,
3769 &red,&green,&blue);
3770 break;
3771 }
3772 case HSIColorspace:
3773 {
3774 ModulateHSI(percent_hue,percent_saturation,percent_brightness,
3775 &red,&green,&blue);
3776 break;
3777 }
3778 case HSLColorspace:
3779 default:
3780 {
3781 ModulateHSL(percent_hue,percent_saturation,percent_brightness,
3782 &red,&green,&blue);
3783 break;
3784 }
3785 case HSVColorspace:
3786 {
3787 ModulateHSV(percent_hue,percent_saturation,percent_brightness,
3788 &red,&green,&blue);
3789 break;
3790 }
3791 case HWBColorspace:
3792 {
3793 ModulateHWB(percent_hue,percent_saturation,percent_brightness,
3794 &red,&green,&blue);
3795 break;
3796 }
3797 case LCHabColorspace:
3798 case LCHColorspace:
3799 {
3800 ModulateLCHab(percent_brightness,percent_saturation,percent_hue,
3801 &red,&green,&blue);
3802 break;
3803 }
3804 case LCHuvColorspace:
3805 {
3806 ModulateLCHuv(percent_brightness,percent_saturation,percent_hue,
3807 &red,&green,&blue);
3808 break;
3809 }
3810 }
3811 image->colormap[i].red=red;
3812 image->colormap[i].green=green;
3813 image->colormap[i].blue=blue;
3814 }
3815
3816 /*
3817 Modulate image.
3818 */
3819
3820 /* call opencl version */
3821#if defined(MAGICKCORE_OPENCL_SUPPORT)
3822 status=AccelerateModulateImage(image,percent_brightness,percent_hue,
3823 percent_saturation,colorspace,&image->exception);
3824 if (status != MagickFalse)
3825 return status;
3826#endif
3827 status=MagickTrue;
3828 progress=0;
3829 exception=(&image->exception);
3830 image_view=AcquireAuthenticCacheView(image,exception);
3831#if defined(MAGICKCORE_OPENMP_SUPPORT)
3832 #pragma omp parallel for schedule(static) shared(progress,status) \
3833 magick_number_threads(image,image,image->rows,1)
3834#endif
3835 for (y=0; y < (ssize_t) image->rows; y++)
3836 {
3838 *magick_restrict q;
3839
3840 ssize_t
3841 x;
3842
3843 if (status == MagickFalse)
3844 continue;
3845 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
3846 if (q == (PixelPacket *) NULL)
3847 {
3848 status=MagickFalse;
3849 continue;
3850 }
3851 for (x=0; x < (ssize_t) image->columns; x++)
3852 {
3853 Quantum
3854 blue,
3855 green,
3856 red;
3857
3858 red=GetPixelRed(q);
3859 green=GetPixelGreen(q);
3860 blue=GetPixelBlue(q);
3861 switch (colorspace)
3862 {
3863 case HCLColorspace:
3864 {
3865 ModulateHCL(percent_hue,percent_saturation,percent_brightness,
3866 &red,&green,&blue);
3867 break;
3868 }
3869 case HCLpColorspace:
3870 {
3871 ModulateHCLp(percent_hue,percent_saturation,percent_brightness,
3872 &red,&green,&blue);
3873 break;
3874 }
3875 case HSBColorspace:
3876 {
3877 ModulateHSB(percent_hue,percent_saturation,percent_brightness,
3878 &red,&green,&blue);
3879 break;
3880 }
3881 case HSIColorspace:
3882 {
3883 ModulateHSI(percent_hue,percent_saturation,percent_brightness,
3884 &red,&green,&blue);
3885 break;
3886 }
3887 case HSLColorspace:
3888 default:
3889 {
3890 ModulateHSL(percent_hue,percent_saturation,percent_brightness,
3891 &red,&green,&blue);
3892 break;
3893 }
3894 case HSVColorspace:
3895 {
3896 ModulateHSV(percent_hue,percent_saturation,percent_brightness,
3897 &red,&green,&blue);
3898 break;
3899 }
3900 case HWBColorspace:
3901 {
3902 ModulateHWB(percent_hue,percent_saturation,percent_brightness,
3903 &red,&green,&blue);
3904 break;
3905 }
3906 case LCHabColorspace:
3907 {
3908 ModulateLCHab(percent_brightness,percent_saturation,percent_hue,
3909 &red,&green,&blue);
3910 break;
3911 }
3912 case LCHColorspace:
3913 case LCHuvColorspace:
3914 {
3915 ModulateLCHuv(percent_brightness,percent_saturation,percent_hue,
3916 &red,&green,&blue);
3917 break;
3918 }
3919 }
3920 SetPixelRed(q,red);
3921 SetPixelGreen(q,green);
3922 SetPixelBlue(q,blue);
3923 q++;
3924 }
3925 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
3926 status=MagickFalse;
3927 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3928 {
3929 MagickBooleanType
3930 proceed;
3931
3932#if defined(MAGICKCORE_OPENMP_SUPPORT)
3933 #pragma omp atomic
3934#endif
3935 progress++;
3936 proceed=SetImageProgress(image,ModulateImageTag,progress,image->rows);
3937 if (proceed == MagickFalse)
3938 status=MagickFalse;
3939 }
3940 }
3941 image_view=DestroyCacheView(image_view);
3942 return(status);
3943}
3944
3945/*
3946%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3947% %
3948% %
3949% %
3950% N e g a t e I m a g e %
3951% %
3952% %
3953% %
3954%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3955%
3956% NegateImage() negates the colors in the reference image. The grayscale
3957% option means that only grayscale values within the image are negated.
3958%
3959% The format of the NegateImageChannel method is:
3960%
3961% MagickBooleanType NegateImage(Image *image,
3962% const MagickBooleanType grayscale)
3963% MagickBooleanType NegateImageChannel(Image *image,
3964% const ChannelType channel,const MagickBooleanType grayscale)
3965%
3966% A description of each parameter follows:
3967%
3968% o image: the image.
3969%
3970% o channel: the channel.
3971%
3972% o grayscale: If MagickTrue, only negate grayscale pixels within the image.
3973%
3974*/
3975
3976MagickExport MagickBooleanType NegateImage(Image *image,
3977 const MagickBooleanType grayscale)
3978{
3979 MagickBooleanType
3980 status;
3981
3982 status=NegateImageChannel(image,DefaultChannels,grayscale);
3983 return(status);
3984}
3985
3986MagickExport MagickBooleanType NegateImageChannel(Image *image,
3987 const ChannelType channel,const MagickBooleanType grayscale)
3988{
3989#define NegateImageTag "Negate/Image"
3990
3991 CacheView
3992 *image_view;
3993
3995 *exception;
3996
3997 MagickBooleanType
3998 status;
3999
4000 MagickOffsetType
4001 progress;
4002
4003 ssize_t
4004 i;
4005
4006 ssize_t
4007 y;
4008
4009 assert(image != (Image *) NULL);
4010 assert(image->signature == MagickCoreSignature);
4011 if (IsEventLogging() != MagickFalse)
4012 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
4013 if (image->storage_class == PseudoClass)
4014 {
4015 /*
4016 Negate colormap.
4017 */
4018 for (i=0; i < (ssize_t) image->colors; i++)
4019 {
4020 if (grayscale != MagickFalse)
4021 if ((image->colormap[i].red != image->colormap[i].green) ||
4022 (image->colormap[i].green != image->colormap[i].blue))
4023 continue;
4024 if ((channel & RedChannel) != 0)
4025 image->colormap[i].red=QuantumRange-image->colormap[i].red;
4026 if ((channel & GreenChannel) != 0)
4027 image->colormap[i].green=QuantumRange-image->colormap[i].green;
4028 if ((channel & BlueChannel) != 0)
4029 image->colormap[i].blue=QuantumRange-image->colormap[i].blue;
4030 }
4031 }
4032 /*
4033 Negate image.
4034 */
4035 status=MagickTrue;
4036 progress=0;
4037 exception=(&image->exception);
4038 image_view=AcquireAuthenticCacheView(image,exception);
4039 if (grayscale != MagickFalse)
4040 {
4041#if defined(MAGICKCORE_OPENMP_SUPPORT)
4042 #pragma omp parallel for schedule(static) shared(progress,status) \
4043 magick_number_threads(image,image,image->rows,1)
4044#endif
4045 for (y=0; y < (ssize_t) image->rows; y++)
4046 {
4047 MagickBooleanType
4048 sync;
4049
4050 IndexPacket
4051 *magick_restrict indexes;
4052
4054 *magick_restrict q;
4055
4056 ssize_t
4057 x;
4058
4059 if (status == MagickFalse)
4060 continue;
4061 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,
4062 exception);
4063 if (q == (PixelPacket *) NULL)
4064 {
4065 status=MagickFalse;
4066 continue;
4067 }
4068 indexes=GetCacheViewAuthenticIndexQueue(image_view);
4069 for (x=0; x < (ssize_t) image->columns; x++)
4070 {
4071 if ((GetPixelRed(q) != GetPixelGreen(q)) ||
4072 (GetPixelGreen(q) != GetPixelBlue(q)))
4073 {
4074 q++;
4075 continue;
4076 }
4077 if ((channel & RedChannel) != 0)
4078 SetPixelRed(q,QuantumRange-GetPixelRed(q));
4079 if ((channel & GreenChannel) != 0)
4080 SetPixelGreen(q,QuantumRange-GetPixelGreen(q));
4081 if ((channel & BlueChannel) != 0)
4082 SetPixelBlue(q,QuantumRange-GetPixelBlue(q));
4083 if ((channel & OpacityChannel) != 0)
4084 SetPixelOpacity(q,QuantumRange-GetPixelOpacity(q));
4085 if (((channel & IndexChannel) != 0) &&
4086 (image->colorspace == CMYKColorspace))
4087 SetPixelIndex(indexes+x,QuantumRange-GetPixelIndex(indexes+x));
4088 q++;
4089 }
4090 sync=SyncCacheViewAuthenticPixels(image_view,exception);
4091 if (sync == MagickFalse)
4092 status=MagickFalse;
4093 if (image->progress_monitor != (MagickProgressMonitor) NULL)
4094 {
4095 MagickBooleanType
4096 proceed;
4097
4098#if defined(MAGICKCORE_OPENMP_SUPPORT)
4099 #pragma omp atomic
4100#endif
4101 progress++;
4102 proceed=SetImageProgress(image,NegateImageTag,progress,image->rows);
4103 if (proceed == MagickFalse)
4104 status=MagickFalse;
4105 }
4106 }
4107 image_view=DestroyCacheView(image_view);
4108 return(MagickTrue);
4109 }
4110 /*
4111 Negate image.
4112 */
4113#if defined(MAGICKCORE_OPENMP_SUPPORT)
4114 #pragma omp parallel for schedule(static) shared(progress,status) \
4115 magick_number_threads(image,image,image->rows,1)
4116#endif
4117 for (y=0; y < (ssize_t) image->rows; y++)
4118 {
4119 IndexPacket
4120 *magick_restrict indexes;
4121
4123 *magick_restrict q;
4124
4125 ssize_t
4126 x;
4127
4128 if (status == MagickFalse)
4129 continue;
4130 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
4131 if (q == (PixelPacket *) NULL)
4132 {
4133 status=MagickFalse;
4134 continue;
4135 }
4136 indexes=GetCacheViewAuthenticIndexQueue(image_view);
4137 if (channel == DefaultChannels)
4138 for (x=0; x < (ssize_t) image->columns; x++)
4139 {
4140 SetPixelRed(q+x,QuantumRange-GetPixelRed(q+x));
4141 SetPixelGreen(q+x,QuantumRange-GetPixelGreen(q+x));
4142 SetPixelBlue(q+x,QuantumRange-GetPixelBlue(q+x));
4143 }
4144 else
4145 for (x=0; x < (ssize_t) image->columns; x++)
4146 {
4147 if ((channel & RedChannel) != 0)
4148 SetPixelRed(q+x,QuantumRange-GetPixelRed(q+x));
4149 if ((channel & GreenChannel) != 0)
4150 SetPixelGreen(q+x,QuantumRange-GetPixelGreen(q+x));
4151 if ((channel & BlueChannel) != 0)
4152 SetPixelBlue(q+x,QuantumRange-GetPixelBlue(q+x));
4153 if ((channel & OpacityChannel) != 0)
4154 SetPixelOpacity(q+x,QuantumRange-GetPixelOpacity(q+x));
4155 }
4156 if (((channel & IndexChannel) != 0) &&
4157 (image->colorspace == CMYKColorspace))
4158 for (x=0; x < (ssize_t) image->columns; x++)
4159 SetPixelIndex(indexes+x,QuantumRange-GetPixelIndex(indexes+x));
4160 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
4161 status=MagickFalse;
4162 if (image->progress_monitor != (MagickProgressMonitor) NULL)
4163 {
4164 MagickBooleanType
4165 proceed;
4166
4167#if defined(MAGICKCORE_OPENMP_SUPPORT)
4168 #pragma omp atomic
4169#endif
4170 progress++;
4171 proceed=SetImageProgress(image,NegateImageTag,progress,image->rows);
4172 if (proceed == MagickFalse)
4173 status=MagickFalse;
4174 }
4175 }
4176 image_view=DestroyCacheView(image_view);
4177 return(status);
4178}
4179
4180/*
4181%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4182% %
4183% %
4184% %
4185% N o r m a l i z e I m a g e %
4186% %
4187% %
4188% %
4189%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4190%
4191% The NormalizeImage() method enhances the contrast of a color image by
4192% mapping the darkest 2 percent of all pixel to black and the brightest
4193% 1 percent to white.
4194%
4195% The format of the NormalizeImage method is:
4196%
4197% MagickBooleanType NormalizeImage(Image *image)
4198% MagickBooleanType NormalizeImageChannel(Image *image,
4199% const ChannelType channel)
4200%
4201% A description of each parameter follows:
4202%
4203% o image: the image.
4204%
4205% o channel: the channel.
4206%
4207*/
4208
4209MagickExport MagickBooleanType NormalizeImage(Image *image)
4210{
4211 MagickBooleanType
4212 status;
4213
4214 status=NormalizeImageChannel(image,DefaultChannels);
4215 return(status);
4216}
4217
4218MagickExport MagickBooleanType NormalizeImageChannel(Image *image,
4219 const ChannelType channel)
4220{
4221 double
4222 black_point,
4223 white_point;
4224
4225 black_point=0.02*image->columns*image->rows;
4226 white_point=0.99*image->columns*image->rows;
4227 return(ContrastStretchImageChannel(image,channel,black_point,white_point));
4228}
4229
4230/*
4231%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4232% %
4233% %
4234% %
4235% S i g m o i d a l C o n t r a s t I m a g e %
4236% %
4237% %
4238% %
4239%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4240%
4241% SigmoidalContrastImage() adjusts the contrast of an image with a non-linear
4242% sigmoidal contrast algorithm. Increase the contrast of the image using a
4243% sigmoidal transfer function without saturating highlights or shadows.
4244% Contrast indicates how much to increase the contrast (0 is none; 3 is
4245% typical; 20 is pushing it); mid-point indicates where midtones fall in the
4246% resultant image (0 is white; 50% is middle-gray; 100% is black). Set
4247% sharpen to MagickTrue to increase the image contrast otherwise the contrast
4248% is reduced.
4249%
4250% The format of the SigmoidalContrastImage method is:
4251%
4252% MagickBooleanType SigmoidalContrastImage(Image *image,
4253% const MagickBooleanType sharpen,const char *levels)
4254% MagickBooleanType SigmoidalContrastImageChannel(Image *image,
4255% const ChannelType channel,const MagickBooleanType sharpen,
4256% const double contrast,const double midpoint)
4257%
4258% A description of each parameter follows:
4259%
4260% o image: the image.
4261%
4262% o channel: the channel.
4263%
4264% o sharpen: Increase or decrease image contrast.
4265%
4266% o contrast: strength of the contrast, the larger the number the more
4267% 'threshold-like' it becomes.
4268%
4269% o midpoint: midpoint of the function as a color value 0 to QuantumRange.
4270%
4271*/
4272
4273/*
4274 ImageMagick 7 has a version of this function which does not use LUTs.
4275*/
4276
4277/*
4278 Sigmoidal function Sigmoidal with inflexion point moved to b and "slope
4279 constant" set to a.
4280
4281 The first version, based on the hyperbolic tangent tanh, when combined with
4282 the scaling step, is an exact arithmetic clone of the sigmoid function
4283 based on the logistic curve. The equivalence is based on the identity
4284
4285 1/(1+exp(-t)) = (1+tanh(t/2))/2
4286
4287 (http://de.wikipedia.org/wiki/Sigmoidfunktion) and the fact that the
4288 scaled sigmoidal derivation is invariant under affine transformations of
4289 the ordinate.
4290
4291 The tanh version is almost certainly more accurate and cheaper. The 0.5
4292 factor in the argument is to clone the legacy ImageMagick behavior. The
4293 reason for making the define depend on atanh even though it only uses tanh
4294 has to do with the construction of the inverse of the scaled sigmoidal.
4295*/
4296#if defined(MAGICKCORE_HAVE_ATANH)
4297#define Sigmoidal(a,b,x) ( tanh((0.5*(a))*((x)-(b))) )
4298#else
4299#define Sigmoidal(a,b,x) ( 1.0/(1.0+exp((a)*((b)-(x)))) )
4300#endif
4301/*
4302 Scaled sigmoidal function:
4303
4304 ( Sigmoidal(a,b,x) - Sigmoidal(a,b,0) ) /
4305 ( Sigmoidal(a,b,1) - Sigmoidal(a,b,0) )
4306
4307 See http://osdir.com/ml/video.image-magick.devel/2005-04/msg00006.html and
4308 http://www.cs.dartmouth.edu/farid/downloads/tutorials/fip.pdf. The limit
4309 of ScaledSigmoidal as a->0 is the identity, but a=0 gives a division by
4310 zero. This is fixed below by exiting immediately when contrast is small,
4311 leaving the image (or colormap) unmodified. This appears to be safe because
4312 the series expansion of the logistic sigmoidal function around x=b is
4313
4314 1/2-a*(b-x)/4+...
4315
4316 so that the key denominator s(1)-s(0) is about a/4 (a/2 with tanh).
4317*/
4318#define ScaledSigmoidal(a,b,x) ( \
4319 (Sigmoidal((a),(b),(x))-Sigmoidal((a),(b),0.0)) / \
4320 (Sigmoidal((a),(b),1.0)-Sigmoidal((a),(b),0.0)) )
4321/*
4322 Inverse of ScaledSigmoidal, used for +sigmoidal-contrast. Because b
4323 may be 0 or 1, the argument of the hyperbolic tangent (resp. logistic
4324 sigmoidal) may be outside of the interval (-1,1) (resp. (0,1)), even
4325 when creating a LUT from in gamut values, hence the branching. In
4326 addition, HDRI may have out of gamut values.
4327 InverseScaledSigmoidal is not a two-sided inverse of ScaledSigmoidal:
4328 It is only a right inverse. This is unavoidable.
4329*/
4330static inline double InverseScaledSigmoidal(const double a,const double b,
4331 const double x)
4332{
4333 const double sig0=Sigmoidal(a,b,0.0);
4334 const double sig1=Sigmoidal(a,b,1.0);
4335 const double argument=(sig1-sig0)*x+sig0;
4336 const double clamped=
4337 (
4338#if defined(MAGICKCORE_HAVE_ATANH)
4339 argument < -1+MagickEpsilon
4340 ?
4341 -1+MagickEpsilon
4342 :
4343 ( argument > 1-MagickEpsilon ? 1-MagickEpsilon : argument )
4344 );
4345 return(b+(2.0/a)*atanh(clamped));
4346#else
4347 argument < MagickEpsilon
4348 ?
4349 MagickEpsilon
4350 :
4351 ( argument > 1-MagickEpsilon ? 1-MagickEpsilon : argument )
4352 );
4353 return(b-log(1.0/clamped-1.0)/a);
4354#endif
4355}
4356
4357MagickExport MagickBooleanType SigmoidalContrastImage(Image *image,
4358 const MagickBooleanType sharpen,const char *levels)
4359{
4361 geometry_info;
4362
4363 MagickBooleanType
4364 status;
4365
4366 MagickStatusType
4367 flags;
4368
4369 flags=ParseGeometry(levels,&geometry_info);
4370 if ((flags & SigmaValue) == 0)
4371 geometry_info.sigma=1.0*(double) QuantumRange/2.0;
4372 if ((flags & PercentValue) != 0)
4373 geometry_info.sigma=1.0*(double) QuantumRange*geometry_info.sigma/100.0;
4374 status=SigmoidalContrastImageChannel(image,DefaultChannels,sharpen,
4375 geometry_info.rho,geometry_info.sigma);
4376 return(status);
4377}
4378
4379MagickExport MagickBooleanType SigmoidalContrastImageChannel(Image *image,
4380 const ChannelType channel,const MagickBooleanType sharpen,
4381 const double contrast,const double midpoint)
4382{
4383#define SigmoidalContrastImageTag "SigmoidalContrast/Image"
4384
4385 CacheView
4386 *image_view;
4387
4389 *exception;
4390
4391 MagickBooleanType
4392 status;
4393
4394 MagickOffsetType
4395 progress;
4396
4397 MagickRealType
4398 *sigmoidal_map;
4399
4400 ssize_t
4401 i;
4402
4403 ssize_t
4404 y;
4405
4406 /*
4407 Side effect: clamps values unless contrast<MagickEpsilon, in which
4408 case nothing is done.
4409 */
4410 if (contrast < MagickEpsilon)
4411 return(MagickTrue);
4412 /*
4413 Allocate and initialize sigmoidal maps.
4414 */
4415 assert(image != (Image *) NULL);
4416 assert(image->signature == MagickCoreSignature);
4417 if (IsEventLogging() != MagickFalse)
4418 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
4419 exception=(&image->exception);
4420 sigmoidal_map=(MagickRealType *) AcquireQuantumMemory(MaxMap+1UL,
4421 sizeof(*sigmoidal_map));
4422 if (sigmoidal_map == (MagickRealType *) NULL)
4423 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
4424 image->filename);
4425 (void) memset(sigmoidal_map,0,(MaxMap+1)*sizeof(*sigmoidal_map));
4426 if (sharpen != MagickFalse)
4427 for (i=0; i <= (ssize_t) MaxMap; i++)
4428 sigmoidal_map[i]=(MagickRealType) ScaleMapToQuantum((MagickRealType)
4429 (MaxMap*ScaledSigmoidal(contrast,QuantumScale*midpoint,(double) i/
4430 MaxMap)));
4431 else
4432 for (i=0; i <= (ssize_t) MaxMap; i++)
4433 sigmoidal_map[i]=(MagickRealType) ScaleMapToQuantum((MagickRealType) (
4434 MaxMap*InverseScaledSigmoidal(contrast,QuantumScale*midpoint,(double) i/
4435 MaxMap)));
4436 /*
4437 Sigmoidal-contrast enhance colormap.
4438 */
4439 if (image->storage_class == PseudoClass)
4440 for (i=0; i < (ssize_t) image->colors; i++)
4441 {
4442 if ((channel & RedChannel) != 0)
4443 image->colormap[i].red=ClampToQuantum(sigmoidal_map[
4444 ScaleQuantumToMap(image->colormap[i].red)]);
4445 if ((channel & GreenChannel) != 0)
4446 image->colormap[i].green=ClampToQuantum(sigmoidal_map[
4447 ScaleQuantumToMap(image->colormap[i].green)]);
4448 if ((channel & BlueChannel) != 0)
4449 image->colormap[i].blue=ClampToQuantum(sigmoidal_map[
4450 ScaleQuantumToMap(image->colormap[i].blue)]);
4451 if ((channel & OpacityChannel) != 0)
4452 image->colormap[i].opacity=ClampToQuantum(sigmoidal_map[
4453 ScaleQuantumToMap(image->colormap[i].opacity)]);
4454 }
4455 /*
4456 Sigmoidal-contrast enhance image.
4457 */
4458 status=MagickTrue;
4459 progress=0;
4460 image_view=AcquireAuthenticCacheView(image,exception);
4461#if defined(MAGICKCORE_OPENMP_SUPPORT)
4462 #pragma omp parallel for schedule(static) shared(progress,status) \
4463 magick_number_threads(image,image,image->rows,1)
4464#endif
4465 for (y=0; y < (ssize_t) image->rows; y++)
4466 {
4467 IndexPacket
4468 *magick_restrict indexes;
4469
4471 *magick_restrict q;
4472
4473 ssize_t
4474 x;
4475
4476 if (status == MagickFalse)
4477 continue;
4478 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
4479 if (q == (PixelPacket *) NULL)
4480 {
4481 status=MagickFalse;
4482 continue;
4483 }
4484 indexes=GetCacheViewAuthenticIndexQueue(image_view);
4485 for (x=0; x < (ssize_t) image->columns; x++)
4486 {
4487 if ((channel & RedChannel) != 0)
4488 SetPixelRed(q,ClampToQuantum(sigmoidal_map[ScaleQuantumToMap(
4489 GetPixelRed(q))]));
4490 if ((channel & GreenChannel) != 0)
4491 SetPixelGreen(q,ClampToQuantum(sigmoidal_map[ScaleQuantumToMap(
4492 GetPixelGreen(q))]));
4493 if ((channel & BlueChannel) != 0)
4494 SetPixelBlue(q,ClampToQuantum(sigmoidal_map[ScaleQuantumToMap(
4495 GetPixelBlue(q))]));
4496 if ((channel & OpacityChannel) != 0)
4497 SetPixelOpacity(q,ClampToQuantum(sigmoidal_map[ScaleQuantumToMap(
4498 GetPixelOpacity(q))]));
4499 if (((channel & IndexChannel) != 0) &&
4500 (image->colorspace == CMYKColorspace))
4501 SetPixelIndex(indexes+x,ClampToQuantum(sigmoidal_map[ScaleQuantumToMap(
4502 GetPixelIndex(indexes+x))]));
4503 q++;
4504 }
4505 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
4506 status=MagickFalse;
4507 if (image->progress_monitor != (MagickProgressMonitor) NULL)
4508 {
4509 MagickBooleanType
4510 proceed;
4511
4512#if defined(MAGICKCORE_OPENMP_SUPPORT)
4513 #pragma omp atomic
4514#endif
4515 progress++;
4516 proceed=SetImageProgress(image,SigmoidalContrastImageTag,progress,
4517 image->rows);
4518 if (proceed == MagickFalse)
4519 status=MagickFalse;
4520 }
4521 }
4522 image_view=DestroyCacheView(image_view);
4523 sigmoidal_map=(MagickRealType *) RelinquishMagickMemory(sigmoidal_map);
4524 return(status);
4525}