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