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/script/license.php %
27% %
28% Unless required by applicable law or agreed to in writing, software %
29% distributed under the License is distributed on an "AS IS" BASIS, %
30% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %
31% See the License for the specific language governing permissions and %
32% limitations under the License. %
33% %
34%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
35%
36%
37%
38*/
39
40/*
41 Include declarations.
42*/
43#include "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 for (i=0; i < (ssize_t) image->colors; i++)
1269 {
1270 /*
1271 Apply transfer function to colormap.
1272 */
1273 double
1274 luma;
1275
1276 luma=0.21267*image->colormap[i].red+0.71526*image->colormap[i].green+
1277 0.07217*image->colormap[i].blue;
1278 image->colormap[i].red=luma+color_correction.saturation*cdl_map[
1279 ScaleQuantumToMap(ClampToQuantum(image->colormap[i].red))].red-luma;
1280 image->colormap[i].green=luma+color_correction.saturation*cdl_map[
1281 ScaleQuantumToMap(ClampToQuantum(image->colormap[i].green))].green-luma;
1282 image->colormap[i].blue=luma+color_correction.saturation*cdl_map[
1283 ScaleQuantumToMap(ClampToQuantum(image->colormap[i].blue))].blue-luma;
1284 }
1285 /*
1286 Apply transfer function to image.
1287 */
1288 status=MagickTrue;
1289 progress=0;
1290 image_view=AcquireAuthenticCacheView(image,exception);
1291#if defined(MAGICKCORE_OPENMP_SUPPORT)
1292 #pragma omp parallel for schedule(static) shared(progress,status) \
1293 magick_number_threads(image,image,image->rows,1)
1294#endif
1295 for (y=0; y < (ssize_t) image->rows; y++)
1296 {
1297 double
1298 luma;
1299
1300 Quantum
1301 *magick_restrict q;
1302
1303 ssize_t
1304 x;
1305
1306 if (status == MagickFalse)
1307 continue;
1308 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
1309 if (q == (Quantum *) NULL)
1310 {
1311 status=MagickFalse;
1312 continue;
1313 }
1314 for (x=0; x < (ssize_t) image->columns; x++)
1315 {
1316 luma=0.21267*(double) GetPixelRed(image,q)+0.71526*(double)
1317 GetPixelGreen(image,q)+0.07217*(double) GetPixelBlue(image,q);
1318 SetPixelRed(image,ClampToQuantum(luma+color_correction.saturation*
1319 (cdl_map[ScaleQuantumToMap(GetPixelRed(image,q))].red-luma)),q);
1320 SetPixelGreen(image,ClampToQuantum(luma+color_correction.saturation*
1321 (cdl_map[ScaleQuantumToMap(GetPixelGreen(image,q))].green-luma)),q);
1322 SetPixelBlue(image,ClampToQuantum(luma+color_correction.saturation*
1323 (cdl_map[ScaleQuantumToMap(GetPixelBlue(image,q))].blue-luma)),q);
1324 q+=(ptrdiff_t) GetPixelChannels(image);
1325 }
1326 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
1327 status=MagickFalse;
1328 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1329 {
1330 MagickBooleanType
1331 proceed;
1332
1333#if defined(MAGICKCORE_OPENMP_SUPPORT)
1334 #pragma omp atomic
1335#endif
1336 progress++;
1337 proceed=SetImageProgress(image,ColorDecisionListCorrectImageTag,
1338 progress,image->rows);
1339 if (proceed == MagickFalse)
1340 status=MagickFalse;
1341 }
1342 }
1343 image_view=DestroyCacheView(image_view);
1344 cdl_map=(PixelInfo *) RelinquishMagickMemory(cdl_map);
1345 return(status);
1346}
1347
1348/*
1349%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1350% %
1351% %
1352% %
1353% C o n t r a s t I m a g e %
1354% %
1355% %
1356% %
1357%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1358%
1359% ContrastImage() enhances the intensity differences between the lighter and
1360% darker elements of the image. Set sharpen to a MagickTrue to increase the
1361% image contrast otherwise the contrast is reduced.
1362%
1363% The format of the ContrastImage method is:
1364%
1365% MagickBooleanType ContrastImage(Image *image,
1366% const MagickBooleanType sharpen,ExceptionInfo *exception)
1367%
1368% A description of each parameter follows:
1369%
1370% o image: the image.
1371%
1372% o sharpen: Increase or decrease image contrast.
1373%
1374% o exception: return any errors or warnings in this structure.
1375%
1376*/
1377
1378static inline void Contrast(const int sign,double *red,double *green,
1379 double *blue)
1380{
1381 double
1382 brightness = 0.0,
1383 hue = 0.0,
1384 saturation = 0.0;
1385
1386 /*
1387 Enhance contrast: dark color become darker, light color become lighter.
1388 */
1389 ConvertRGBToHSB(*red,*green,*blue,&hue,&saturation,&brightness);
1390 brightness+=0.5*sign*(0.5*(sin((double) (MagickPI*(brightness-0.5)))+1.0)-
1391 brightness);
1392 if (brightness > 1.0)
1393 brightness=1.0;
1394 else
1395 if (brightness < 0.0)
1396 brightness=0.0;
1397 ConvertHSBToRGB(hue,saturation,brightness,red,green,blue);
1398}
1399
1400MagickExport MagickBooleanType ContrastImage(Image *image,
1401 const MagickBooleanType sharpen,ExceptionInfo *exception)
1402{
1403#define ContrastImageTag "Contrast/Image"
1404
1405 CacheView
1406 *image_view;
1407
1408 int
1409 sign;
1410
1411 MagickBooleanType
1412 status;
1413
1414 MagickOffsetType
1415 progress;
1416
1417 ssize_t
1418 i;
1419
1420 ssize_t
1421 y;
1422
1423 assert(image != (Image *) NULL);
1424 assert(image->signature == MagickCoreSignature);
1425#if defined(MAGICKCORE_OPENCL_SUPPORT)
1426 if (AccelerateContrastImage(image,sharpen,exception) != MagickFalse)
1427 return(MagickTrue);
1428#endif
1429 if (IsEventLogging() != MagickFalse)
1430 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1431 sign=sharpen != MagickFalse ? 1 : -1;
1432 if (image->storage_class == PseudoClass)
1433 {
1434 /*
1435 Contrast enhance colormap.
1436 */
1437 for (i=0; i < (ssize_t) image->colors; i++)
1438 {
1439 double
1440 blue,
1441 green,
1442 red;
1443
1444 red=(double) image->colormap[i].red;
1445 green=(double) image->colormap[i].green;
1446 blue=(double) image->colormap[i].blue;
1447 Contrast(sign,&red,&green,&blue);
1448 image->colormap[i].red=(MagickRealType) red;
1449 image->colormap[i].green=(MagickRealType) green;
1450 image->colormap[i].blue=(MagickRealType) blue;
1451 }
1452 }
1453 /*
1454 Contrast enhance image.
1455 */
1456 status=MagickTrue;
1457 progress=0;
1458 image_view=AcquireAuthenticCacheView(image,exception);
1459#if defined(MAGICKCORE_OPENMP_SUPPORT)
1460 #pragma omp parallel for schedule(static) shared(progress,status) \
1461 magick_number_threads(image,image,image->rows,1)
1462#endif
1463 for (y=0; y < (ssize_t) image->rows; y++)
1464 {
1465 double
1466 blue,
1467 green,
1468 red;
1469
1470 Quantum
1471 *magick_restrict q;
1472
1473 ssize_t
1474 x;
1475
1476 if (status == MagickFalse)
1477 continue;
1478 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
1479 if (q == (Quantum *) NULL)
1480 {
1481 status=MagickFalse;
1482 continue;
1483 }
1484 for (x=0; x < (ssize_t) image->columns; x++)
1485 {
1486 red=(double) GetPixelRed(image,q);
1487 green=(double) GetPixelGreen(image,q);
1488 blue=(double) GetPixelBlue(image,q);
1489 Contrast(sign,&red,&green,&blue);
1490 SetPixelRed(image,ClampToQuantum(red),q);
1491 SetPixelGreen(image,ClampToQuantum(green),q);
1492 SetPixelBlue(image,ClampToQuantum(blue),q);
1493 q+=(ptrdiff_t) GetPixelChannels(image);
1494 }
1495 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
1496 status=MagickFalse;
1497 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1498 {
1499 MagickBooleanType
1500 proceed;
1501
1502#if defined(MAGICKCORE_OPENMP_SUPPORT)
1503 #pragma omp atomic
1504#endif
1505 progress++;
1506 proceed=SetImageProgress(image,ContrastImageTag,progress,image->rows);
1507 if (proceed == MagickFalse)
1508 status=MagickFalse;
1509 }
1510 }
1511 image_view=DestroyCacheView(image_view);
1512 return(status);
1513}
1514
1515/*
1516%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1517% %
1518% %
1519% %
1520% C o n t r a s t S t r e t c h I m a g e %
1521% %
1522% %
1523% %
1524%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1525%
1526% ContrastStretchImage() is a simple image enhancement technique that attempts
1527% to improve the contrast in an image by 'stretching' the range of intensity
1528% values it contains to span a desired range of values. It differs from the
1529% more sophisticated histogram equalization in that it can only apply a
1530% linear scaling function to the image pixel values. As a result the
1531% 'enhancement' is less harsh.
1532%
1533% The format of the ContrastStretchImage method is:
1534%
1535% MagickBooleanType ContrastStretchImage(Image *image,
1536% const char *levels,ExceptionInfo *exception)
1537%
1538% A description of each parameter follows:
1539%
1540% o image: the image.
1541%
1542% o black_point: the black point.
1543%
1544% o white_point: the white point.
1545%
1546% o levels: Specify the levels where the black and white points have the
1547% range of 0 to number-of-pixels (e.g. 1%, 10x90%, etc.).
1548%
1549% o exception: return any errors or warnings in this structure.
1550%
1551*/
1552MagickExport MagickBooleanType ContrastStretchImage(Image *image,
1553 const double black_point,const double white_point,ExceptionInfo *exception)
1554{
1555#define ContrastStretchImageTag "ContrastStretch/Image"
1556
1557 CacheView
1558 *image_view;
1559
1560 char
1561 property[MagickPathExtent];
1562
1563 double
1564 *histogram;
1565
1566 ImageType
1567 type;
1568
1569 MagickBooleanType
1570 status;
1571
1572 MagickOffsetType
1573 progress;
1574
1575 Quantum
1576 *black,
1577 *stretch_map,
1578 *white;
1579
1580 ssize_t
1581 i;
1582
1583 ssize_t
1584 y;
1585
1586 /*
1587 Allocate histogram and stretch map.
1588 */
1589 assert(image != (Image *) NULL);
1590 assert(image->signature == MagickCoreSignature);
1591 if (IsEventLogging() != MagickFalse)
1592 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1593 type=IdentifyImageType(image,exception);
1594 if (IsGrayImageType(type) != MagickFalse)
1595 (void) SetImageColorspace(image,GRAYColorspace,exception);
1596 black=(Quantum *) AcquireQuantumMemory(MaxPixelChannels,sizeof(*black));
1597 white=(Quantum *) AcquireQuantumMemory(MaxPixelChannels,sizeof(*white));
1598 stretch_map=(Quantum *) AcquireQuantumMemory(MaxMap+1UL,MaxPixelChannels*
1599 sizeof(*stretch_map));
1600 histogram=(double *) AcquireQuantumMemory(MaxMap+1UL,MaxPixelChannels*
1601 sizeof(*histogram));
1602 if ((black == (Quantum *) NULL) || (white == (Quantum *) NULL) ||
1603 (stretch_map == (Quantum *) NULL) || (histogram == (double *) NULL))
1604 {
1605 if (histogram != (double *) NULL)
1606 histogram=(double *) RelinquishMagickMemory(histogram);
1607 if (stretch_map != (Quantum *) NULL)
1608 stretch_map=(Quantum *) RelinquishMagickMemory(stretch_map);
1609 if (white != (Quantum *) NULL)
1610 white=(Quantum *) RelinquishMagickMemory(white);
1611 if (black != (Quantum *) NULL)
1612 black=(Quantum *) RelinquishMagickMemory(black);
1613 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
1614 image->filename);
1615 }
1616 /*
1617 Form histogram.
1618 */
1619 status=MagickTrue;
1620 (void) memset(histogram,0,(MaxMap+1)*GetPixelChannels(image)*
1621 sizeof(*histogram));
1622 image_view=AcquireVirtualCacheView(image,exception);
1623 for (y=0; y < (ssize_t) image->rows; y++)
1624 {
1625 const Quantum
1626 *magick_restrict p;
1627
1628 ssize_t
1629 x;
1630
1631 if (status == MagickFalse)
1632 continue;
1633 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
1634 if (p == (const Quantum *) NULL)
1635 {
1636 status=MagickFalse;
1637 continue;
1638 }
1639 for (x=0; x < (ssize_t) image->columns; x++)
1640 {
1641 double
1642 pixel;
1643
1644 pixel=GetPixelIntensity(image,p);
1645 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1646 {
1647 if (image->channel_mask != AllChannels)
1648 pixel=(double) p[i];
1649 histogram[GetPixelChannels(image)*ScaleQuantumToMap(
1650 ClampToQuantum(pixel))+(size_t) i]++;
1651 }
1652 p+=(ptrdiff_t) GetPixelChannels(image);
1653 }
1654 }
1655 image_view=DestroyCacheView(image_view);
1656 /*
1657 Find the histogram boundaries by locating the black/white levels.
1658 */
1659 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1660 {
1661 double
1662 intensity;
1663
1664 ssize_t
1665 j;
1666
1667 black[i]=(Quantum) 0;
1668 white[i]=(Quantum) ScaleQuantumToMap(QuantumRange);
1669 intensity=0.0;
1670 for (j=0; j <= (ssize_t) MaxMap; j++)
1671 {
1672 intensity+=histogram[(ssize_t) GetPixelChannels(image)*j+i];
1673 if (intensity > black_point)
1674 break;
1675 }
1676 black[i]=(Quantum) j;
1677 intensity=0.0;
1678 for (j=(ssize_t) MaxMap; j != 0; j--)
1679 {
1680 intensity+=histogram[(ssize_t) GetPixelChannels(image)*j+i];
1681 if (intensity > ((double) image->columns*image->rows-white_point))
1682 break;
1683 }
1684 white[i]=(Quantum) j;
1685 }
1686 histogram=(double *) RelinquishMagickMemory(histogram);
1687 /*
1688 Stretch the histogram to create the stretched image mapping.
1689 */
1690 (void) memset(stretch_map,0,(MaxMap+1)*GetPixelChannels(image)*
1691 sizeof(*stretch_map));
1692 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1693 {
1694 ssize_t
1695 j;
1696
1697 for (j=0; j <= (ssize_t) MaxMap; j++)
1698 {
1699 double
1700 gamma;
1701
1702 gamma=MagickSafeReciprocal(white[i]-black[i]);
1703 if (j < (ssize_t) black[i])
1704 stretch_map[(ssize_t) GetPixelChannels(image)*j+i]=(Quantum) 0;
1705 else
1706 if (j > (ssize_t) white[i])
1707 stretch_map[(ssize_t) GetPixelChannels(image)*j+i]=QuantumRange;
1708 else
1709 if (black[i] != white[i])
1710 stretch_map[(ssize_t) GetPixelChannels(image)*j+i]=
1711 ScaleMapToQuantum((double) (MaxMap*gamma*(j-(double) black[i])));
1712 }
1713 }
1714 if (image->storage_class == PseudoClass)
1715 {
1716 ssize_t
1717 j;
1718
1719 /*
1720 Stretch-contrast colormap.
1721 */
1722 for (j=0; j < (ssize_t) image->colors; j++)
1723 {
1724 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
1725 {
1726 i=GetPixelChannelOffset(image,RedPixelChannel);
1727 image->colormap[j].red=(MagickRealType) stretch_map[
1728 GetPixelChannels(image)*ScaleQuantumToMap(ClampToQuantum(
1729 image->colormap[j].red))+(size_t) i];
1730 }
1731 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
1732 {
1733 i=GetPixelChannelOffset(image,GreenPixelChannel);
1734 image->colormap[j].green=(MagickRealType) stretch_map[
1735 GetPixelChannels(image)*ScaleQuantumToMap(ClampToQuantum(
1736 image->colormap[j].green))+(size_t) i];
1737 }
1738 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
1739 {
1740 i=GetPixelChannelOffset(image,BluePixelChannel);
1741 image->colormap[j].blue=(MagickRealType) stretch_map[
1742 GetPixelChannels(image)*ScaleQuantumToMap(ClampToQuantum(
1743 image->colormap[j].blue))+(size_t) i];
1744 }
1745 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
1746 {
1747 i=GetPixelChannelOffset(image,AlphaPixelChannel);
1748 image->colormap[j].alpha=(MagickRealType) stretch_map[
1749 GetPixelChannels(image)*ScaleQuantumToMap(ClampToQuantum(
1750 image->colormap[j].alpha))+(size_t) i];
1751 }
1752 }
1753 }
1754 /*
1755 Stretch-contrast image.
1756 */
1757 status=MagickTrue;
1758 progress=0;
1759 image_view=AcquireAuthenticCacheView(image,exception);
1760#if defined(MAGICKCORE_OPENMP_SUPPORT)
1761 #pragma omp parallel for schedule(static) shared(progress,status) \
1762 magick_number_threads(image,image,image->rows,1)
1763#endif
1764 for (y=0; y < (ssize_t) image->rows; y++)
1765 {
1766 Quantum
1767 *magick_restrict q;
1768
1769 ssize_t
1770 x;
1771
1772 if (status == MagickFalse)
1773 continue;
1774 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
1775 if (q == (Quantum *) NULL)
1776 {
1777 status=MagickFalse;
1778 continue;
1779 }
1780 for (x=0; x < (ssize_t) image->columns; x++)
1781 {
1782 ssize_t
1783 j;
1784
1785 for (j=0; j < (ssize_t) GetPixelChannels(image); j++)
1786 {
1787 PixelChannel channel = GetPixelChannelChannel(image,j);
1788 PixelTrait traits = GetPixelChannelTraits(image,channel);
1789 if ((traits & UpdatePixelTrait) == 0)
1790 continue;
1791 if (black[j] == white[j])
1792 continue;
1793 q[j]=ClampToQuantum(stretch_map[GetPixelChannels(image)*
1794 ScaleQuantumToMap(q[j])+(size_t) j]);
1795 }
1796 q+=(ptrdiff_t) GetPixelChannels(image);
1797 }
1798 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
1799 status=MagickFalse;
1800 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1801 {
1802 MagickBooleanType
1803 proceed;
1804
1805#if defined(MAGICKCORE_OPENMP_SUPPORT)
1806 #pragma omp atomic
1807#endif
1808 progress++;
1809 proceed=SetImageProgress(image,ContrastStretchImageTag,progress,
1810 image->rows);
1811 if (proceed == MagickFalse)
1812 status=MagickFalse;
1813 }
1814 }
1815 image_view=DestroyCacheView(image_view);
1816 (void) FormatLocaleString(property,MagickPathExtent,"%gx%g%%",100.0*
1817 QuantumScale*GetPixelIntensity(image,black),100.0*QuantumScale*
1818 GetPixelIntensity(image,white));
1819 (void) SetImageProperty(image,"histogram:contrast-stretch",property,
1820 exception);
1821 white=(Quantum *) RelinquishMagickMemory(white);
1822 black=(Quantum *) RelinquishMagickMemory(black);
1823 stretch_map=(Quantum *) RelinquishMagickMemory(stretch_map);
1824 return(status);
1825}
1826
1827/*
1828%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1829% %
1830% %
1831% %
1832% E n h a n c e I m a g e %
1833% %
1834% %
1835% %
1836%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1837%
1838% EnhanceImage() applies a digital filter that improves the quality of a
1839% noisy image.
1840%
1841% The format of the EnhanceImage method is:
1842%
1843% Image *EnhanceImage(const Image *image,ExceptionInfo *exception)
1844%
1845% A description of each parameter follows:
1846%
1847% o image: the image.
1848%
1849% o exception: return any errors or warnings in this structure.
1850%
1851*/
1852MagickExport Image *EnhanceImage(const Image *image,ExceptionInfo *exception)
1853{
1854#define EnhanceImageTag "Enhance/Image"
1855#define EnhancePixel(weight) \
1856 mean=QuantumScale*((double) GetPixelRed(image,r)+pixel.red)/2.0; \
1857 distance=QuantumScale*((double) GetPixelRed(image,r)-pixel.red); \
1858 distance_squared=(4.0+mean)*distance*distance; \
1859 mean=QuantumScale*((double) GetPixelGreen(image,r)+pixel.green)/2.0; \
1860 distance=QuantumScale*((double) GetPixelGreen(image,r)-pixel.green); \
1861 distance_squared+=(7.0-mean)*distance*distance; \
1862 mean=QuantumScale*((double) GetPixelBlue(image,r)+pixel.blue)/2.0; \
1863 distance=QuantumScale*((double) GetPixelBlue(image,r)-pixel.blue); \
1864 distance_squared+=(5.0-mean)*distance*distance; \
1865 mean=QuantumScale*((double) GetPixelBlack(image,r)+pixel.black)/2.0; \
1866 distance=QuantumScale*((double) GetPixelBlack(image,r)-pixel.black); \
1867 distance_squared+=(5.0-mean)*distance*distance; \
1868 mean=QuantumScale*((double) GetPixelAlpha(image,r)+pixel.alpha)/2.0; \
1869 distance=QuantumScale*((double) GetPixelAlpha(image,r)-pixel.alpha); \
1870 distance_squared+=(5.0-mean)*distance*distance; \
1871 if (distance_squared < 0.069) \
1872 { \
1873 aggregate.red+=(weight)*(double) GetPixelRed(image,r); \
1874 aggregate.green+=(weight)*(double) GetPixelGreen(image,r); \
1875 aggregate.blue+=(weight)*(double) GetPixelBlue(image,r); \
1876 aggregate.black+=(weight)*(double) GetPixelBlack(image,r); \
1877 aggregate.alpha+=(weight)*(double) GetPixelAlpha(image,r); \
1878 total_weight+=(weight); \
1879 } \
1880 r+=(ptrdiff_t) GetPixelChannels(image);
1881
1882 CacheView
1883 *enhance_view,
1884 *image_view;
1885
1886 Image
1887 *enhance_image;
1888
1889 MagickBooleanType
1890 status;
1891
1892 MagickOffsetType
1893 progress;
1894
1895 ssize_t
1896 y;
1897
1898 /*
1899 Initialize enhanced image attributes.
1900 */
1901 assert(image != (const Image *) NULL);
1902 assert(image->signature == MagickCoreSignature);
1903 if (IsEventLogging() != MagickFalse)
1904 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1905 assert(exception != (ExceptionInfo *) NULL);
1906 assert(exception->signature == MagickCoreSignature);
1907 enhance_image=CloneImage(image,0,0,MagickTrue,
1908 exception);
1909 if (enhance_image == (Image *) NULL)
1910 return((Image *) NULL);
1911 if (SetImageStorageClass(enhance_image,DirectClass,exception) == MagickFalse)
1912 {
1913 enhance_image=DestroyImage(enhance_image);
1914 return((Image *) NULL);
1915 }
1916 /*
1917 Enhance image.
1918 */
1919 status=MagickTrue;
1920 progress=0;
1921 image_view=AcquireVirtualCacheView(image,exception);
1922 enhance_view=AcquireAuthenticCacheView(enhance_image,exception);
1923#if defined(MAGICKCORE_OPENMP_SUPPORT)
1924 #pragma omp parallel for schedule(static) shared(progress,status) \
1925 magick_number_threads(image,enhance_image,image->rows,1)
1926#endif
1927 for (y=0; y < (ssize_t) image->rows; y++)
1928 {
1929 PixelInfo
1930 pixel;
1931
1932 const Quantum
1933 *magick_restrict p;
1934
1935 Quantum
1936 *magick_restrict q;
1937
1938 ssize_t
1939 x;
1940
1941 ssize_t
1942 center;
1943
1944 if (status == MagickFalse)
1945 continue;
1946 p=GetCacheViewVirtualPixels(image_view,-2,y-2,image->columns+4,5,exception);
1947 q=QueueCacheViewAuthenticPixels(enhance_view,0,y,enhance_image->columns,1,
1948 exception);
1949 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
1950 {
1951 status=MagickFalse;
1952 continue;
1953 }
1954 center=(ssize_t) GetPixelChannels(image)*(2*((ssize_t) image->columns+4)+2);
1955 GetPixelInfo(image,&pixel);
1956 for (x=0; x < (ssize_t) image->columns; x++)
1957 {
1958 double
1959 distance,
1960 distance_squared,
1961 mean,
1962 total_weight;
1963
1964 PixelInfo
1965 aggregate;
1966
1967 const Quantum
1968 *magick_restrict r;
1969
1970 GetPixelInfo(image,&aggregate);
1971 total_weight=0.0;
1972 GetPixelInfoPixel(image,p+center,&pixel);
1973 r=p;
1974 EnhancePixel(5.0); EnhancePixel(8.0); EnhancePixel(10.0);
1975 EnhancePixel(8.0); EnhancePixel(5.0);
1976 r=p+GetPixelChannels(image)*(image->columns+4);
1977 EnhancePixel(8.0); EnhancePixel(20.0); EnhancePixel(40.0);
1978 EnhancePixel(20.0); EnhancePixel(8.0);
1979 r=p+2*GetPixelChannels(image)*(image->columns+4);
1980 EnhancePixel(10.0); EnhancePixel(40.0); EnhancePixel(80.0);
1981 EnhancePixel(40.0); EnhancePixel(10.0);
1982 r=p+3*GetPixelChannels(image)*(image->columns+4);
1983 EnhancePixel(8.0); EnhancePixel(20.0); EnhancePixel(40.0);
1984 EnhancePixel(20.0); EnhancePixel(8.0);
1985 r=p+4*GetPixelChannels(image)*(image->columns+4);
1986 EnhancePixel(5.0); EnhancePixel(8.0); EnhancePixel(10.0);
1987 EnhancePixel(8.0); EnhancePixel(5.0);
1988 if (total_weight > MagickEpsilon)
1989 {
1990 pixel.red=((aggregate.red+total_weight/2.0)/total_weight);
1991 pixel.green=((aggregate.green+total_weight/2.0)/total_weight);
1992 pixel.blue=((aggregate.blue+total_weight/2.0)/total_weight);
1993 pixel.black=((aggregate.black+total_weight/2.0)/total_weight);
1994 pixel.alpha=((aggregate.alpha+total_weight/2.0)/total_weight);
1995 }
1996 SetPixelViaPixelInfo(enhance_image,&pixel,q);
1997 p+=(ptrdiff_t) GetPixelChannels(image);
1998 q+=(ptrdiff_t) GetPixelChannels(enhance_image);
1999 }
2000 if (SyncCacheViewAuthenticPixels(enhance_view,exception) == MagickFalse)
2001 status=MagickFalse;
2002 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2003 {
2004 MagickBooleanType
2005 proceed;
2006
2007#if defined(MAGICKCORE_OPENMP_SUPPORT)
2008 #pragma omp atomic
2009#endif
2010 progress++;
2011 proceed=SetImageProgress(image,EnhanceImageTag,progress,image->rows);
2012 if (proceed == MagickFalse)
2013 status=MagickFalse;
2014 }
2015 }
2016 enhance_view=DestroyCacheView(enhance_view);
2017 image_view=DestroyCacheView(image_view);
2018 if (status == MagickFalse)
2019 enhance_image=DestroyImage(enhance_image);
2020 return(enhance_image);
2021}
2022
2023/*
2024%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2025% %
2026% %
2027% %
2028% E q u a l i z e I m a g e %
2029% %
2030% %
2031% %
2032%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2033%
2034% EqualizeImage() applies a histogram equalization to the image.
2035%
2036% The format of the EqualizeImage method is:
2037%
2038% MagickBooleanType EqualizeImage(Image *image,ExceptionInfo *exception)
2039%
2040% A description of each parameter follows:
2041%
2042% o image: the image.
2043%
2044% o exception: return any errors or warnings in this structure.
2045%
2046*/
2047MagickExport MagickBooleanType EqualizeImage(Image *image,
2048 ExceptionInfo *exception)
2049{
2050#define EqualizeImageTag "Equalize/Image"
2051
2052 CacheView
2053 *image_view;
2054
2055 double
2056 black[2*CompositePixelChannel+1],
2057 *equalize_map,
2058 *histogram,
2059 *map,
2060 white[2*CompositePixelChannel+1];
2061
2062 MagickBooleanType
2063 status;
2064
2065 MagickOffsetType
2066 progress;
2067
2068 ssize_t
2069 i;
2070
2071 ssize_t
2072 y;
2073
2074 /*
2075 Allocate and initialize histogram arrays.
2076 */
2077 assert(image != (Image *) NULL);
2078 assert(image->signature == MagickCoreSignature);
2079#if defined(MAGICKCORE_OPENCL_SUPPORT)
2080 if (AccelerateEqualizeImage(image,exception) != MagickFalse)
2081 return(MagickTrue);
2082#endif
2083 if (IsEventLogging() != MagickFalse)
2084 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2085 equalize_map=(double *) AcquireQuantumMemory(MaxMap+1UL,MaxPixelChannels*
2086 sizeof(*equalize_map));
2087 histogram=(double *) AcquireQuantumMemory(MaxMap+1UL,MaxPixelChannels*
2088 sizeof(*histogram));
2089 map=(double *) AcquireQuantumMemory(MaxMap+1UL,MaxPixelChannels*sizeof(*map));
2090 if ((equalize_map == (double *) NULL) || (histogram == (double *) NULL) ||
2091 (map == (double *) NULL))
2092 {
2093 if (map != (double *) NULL)
2094 map=(double *) RelinquishMagickMemory(map);
2095 if (histogram != (double *) NULL)
2096 histogram=(double *) RelinquishMagickMemory(histogram);
2097 if (equalize_map != (double *) NULL)
2098 equalize_map=(double *) RelinquishMagickMemory(equalize_map);
2099 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
2100 image->filename);
2101 }
2102 /*
2103 Form histogram.
2104 */
2105 status=MagickTrue;
2106 (void) memset(histogram,0,(MaxMap+1)*GetPixelChannels(image)*
2107 sizeof(*histogram));
2108 image_view=AcquireVirtualCacheView(image,exception);
2109 for (y=0; y < (ssize_t) image->rows; y++)
2110 {
2111 const Quantum
2112 *magick_restrict p;
2113
2114 ssize_t
2115 x;
2116
2117 if (status == MagickFalse)
2118 continue;
2119 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
2120 if (p == (const Quantum *) NULL)
2121 {
2122 status=MagickFalse;
2123 continue;
2124 }
2125 for (x=0; x < (ssize_t) image->columns; x++)
2126 {
2127 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
2128 {
2129 double
2130 intensity;
2131
2132 intensity=(double) p[i];
2133 if ((image->channel_mask & SyncChannels) != 0)
2134 intensity=GetPixelIntensity(image,p);
2135 histogram[GetPixelChannels(image)*ScaleQuantumToMap(
2136 ClampToQuantum(intensity))+(size_t) i]++;
2137 }
2138 p+=(ptrdiff_t) GetPixelChannels(image);
2139 }
2140 }
2141 image_view=DestroyCacheView(image_view);
2142 /*
2143 Integrate the histogram to get the equalization map.
2144 */
2145 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
2146 {
2147 double
2148 intensity;
2149
2150 ssize_t
2151 j;
2152
2153 intensity=0.0;
2154 for (j=0; j <= (ssize_t) MaxMap; j++)
2155 {
2156 intensity+=histogram[(ssize_t) GetPixelChannels(image)*j+i];
2157 map[(ssize_t) GetPixelChannels(image)*j+i]=intensity;
2158 }
2159 }
2160 (void) memset(equalize_map,0,(MaxMap+1)*GetPixelChannels(image)*
2161 sizeof(*equalize_map));
2162 (void) memset(black,0,sizeof(*black));
2163 (void) memset(white,0,sizeof(*white));
2164 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
2165 {
2166 ssize_t
2167 j;
2168
2169 black[i]=map[i];
2170 white[i]=map[GetPixelChannels(image)*MaxMap+(size_t) i];
2171 if (black[i] != white[i])
2172 for (j=0; j <= (ssize_t) MaxMap; j++)
2173 equalize_map[GetPixelChannels(image)*(size_t) j+(size_t) i]=(double)
2174 ScaleMapToQuantum((double) ((MaxMap*(map[GetPixelChannels(image)*
2175 (size_t) j+(size_t) i]-black[i]))/(white[i]-black[i])));
2176 }
2177 histogram=(double *) RelinquishMagickMemory(histogram);
2178 map=(double *) RelinquishMagickMemory(map);
2179 if (image->storage_class == PseudoClass)
2180 {
2181 ssize_t
2182 j;
2183
2184 /*
2185 Equalize colormap.
2186 */
2187 for (j=0; j < (ssize_t) image->colors; j++)
2188 {
2189 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
2190 {
2191 PixelChannel channel = GetPixelChannelChannel(image,
2192 RedPixelChannel);
2193 if (black[channel] != white[channel])
2194 image->colormap[j].red=equalize_map[(ssize_t)
2195 GetPixelChannels(image)*ScaleQuantumToMap(
2196 ClampToQuantum(image->colormap[j].red))+channel];
2197 }
2198 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
2199 {
2200 PixelChannel channel = GetPixelChannelChannel(image,
2201 GreenPixelChannel);
2202 if (black[channel] != white[channel])
2203 image->colormap[j].green=equalize_map[(ssize_t)
2204 GetPixelChannels(image)*ScaleQuantumToMap(
2205 ClampToQuantum(image->colormap[j].green))+channel];
2206 }
2207 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
2208 {
2209 PixelChannel channel = GetPixelChannelChannel(image,
2210 BluePixelChannel);
2211 if (black[channel] != white[channel])
2212 image->colormap[j].blue=equalize_map[(ssize_t)
2213 GetPixelChannels(image)*ScaleQuantumToMap(
2214 ClampToQuantum(image->colormap[j].blue))+channel];
2215 }
2216 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
2217 {
2218 PixelChannel channel = GetPixelChannelChannel(image,
2219 AlphaPixelChannel);
2220 if (black[channel] != white[channel])
2221 image->colormap[j].alpha=equalize_map[(ssize_t)
2222 GetPixelChannels(image)*ScaleQuantumToMap(
2223 ClampToQuantum(image->colormap[j].alpha))+channel];
2224 }
2225 }
2226 }
2227 /*
2228 Equalize image.
2229 */
2230 progress=0;
2231 image_view=AcquireAuthenticCacheView(image,exception);
2232#if defined(MAGICKCORE_OPENMP_SUPPORT)
2233 #pragma omp parallel for schedule(static) shared(progress,status) \
2234 magick_number_threads(image,image,image->rows,1)
2235#endif
2236 for (y=0; y < (ssize_t) image->rows; y++)
2237 {
2238 Quantum
2239 *magick_restrict q;
2240
2241 ssize_t
2242 x;
2243
2244 if (status == MagickFalse)
2245 continue;
2246 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2247 if (q == (Quantum *) NULL)
2248 {
2249 status=MagickFalse;
2250 continue;
2251 }
2252 for (x=0; x < (ssize_t) image->columns; x++)
2253 {
2254 ssize_t
2255 j;
2256
2257 for (j=0; j < (ssize_t) GetPixelChannels(image); j++)
2258 {
2259 PixelChannel channel = GetPixelChannelChannel(image,j);
2260 PixelTrait traits = GetPixelChannelTraits(image,channel);
2261 if (((traits & UpdatePixelTrait) == 0) || (black[j] == white[j]))
2262 continue;
2263 q[j]=ClampToQuantum(equalize_map[GetPixelChannels(image)*
2264 ScaleQuantumToMap(q[j])+(size_t) j]);
2265 }
2266 q+=(ptrdiff_t) GetPixelChannels(image);
2267 }
2268 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2269 status=MagickFalse;
2270 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2271 {
2272 MagickBooleanType
2273 proceed;
2274
2275#if defined(MAGICKCORE_OPENMP_SUPPORT)
2276 #pragma omp atomic
2277#endif
2278 progress++;
2279 proceed=SetImageProgress(image,EqualizeImageTag,progress,image->rows);
2280 if (proceed == MagickFalse)
2281 status=MagickFalse;
2282 }
2283 }
2284 image_view=DestroyCacheView(image_view);
2285 equalize_map=(double *) RelinquishMagickMemory(equalize_map);
2286 return(status);
2287}
2288
2289/*
2290%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2291% %
2292% %
2293% %
2294% G a m m a I m a g e %
2295% %
2296% %
2297% %
2298%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2299%
2300% GammaImage() gamma-corrects a particular image channel. The same
2301% image viewed on different devices will have perceptual differences in the
2302% way the image's intensities are represented on the screen. Specify
2303% individual gamma levels for the red, green, and blue channels, or adjust
2304% all three with the gamma parameter. Values typically range from 0.8 to 2.3.
2305%
2306% You can also reduce the influence of a particular channel with a gamma
2307% value of 0.
2308%
2309% The format of the GammaImage method is:
2310%
2311% MagickBooleanType GammaImage(Image *image,const double gamma,
2312% ExceptionInfo *exception)
2313%
2314% A description of each parameter follows:
2315%
2316% o image: the image.
2317%
2318% o level: the image gamma as a string (e.g. 1.6,1.2,1.0).
2319%
2320% o gamma: the image gamma.
2321%
2322*/
2323
2324static inline double gamma_pow(const double value,const double gamma)
2325{
2326 return(value < 0.0 ? value : pow(value,gamma));
2327}
2328
2329MagickExport MagickBooleanType GammaImage(Image *image,const double gamma,
2330 ExceptionInfo *exception)
2331{
2332#define GammaImageTag "Gamma/Image"
2333
2334 CacheView
2335 *image_view;
2336
2337 MagickBooleanType
2338 status;
2339
2340 MagickOffsetType
2341 progress;
2342
2343 Quantum
2344 *gamma_map;
2345
2346 ssize_t
2347 i;
2348
2349 ssize_t
2350 y;
2351
2352 /*
2353 Allocate and initialize gamma maps.
2354 */
2355 assert(image != (Image *) NULL);
2356 assert(image->signature == MagickCoreSignature);
2357 if (IsEventLogging() != MagickFalse)
2358 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2359 if (gamma == 1.0)
2360 return(MagickTrue);
2361 gamma_map=(Quantum *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*gamma_map));
2362 if (gamma_map == (Quantum *) NULL)
2363 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
2364 image->filename);
2365 (void) memset(gamma_map,0,(MaxMap+1)*sizeof(*gamma_map));
2366 if (gamma != 0.0)
2367 for (i=0; i <= (ssize_t) MaxMap; i++)
2368 gamma_map[i]=ScaleMapToQuantum((double) (MaxMap*pow((double) i/
2369 MaxMap,MagickSafeReciprocal(gamma))));
2370 if (image->storage_class == PseudoClass)
2371 for (i=0; i < (ssize_t) image->colors; i++)
2372 {
2373 /*
2374 Gamma-correct colormap.
2375 */
2376 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
2377 image->colormap[i].red=(double) gamma_map[ScaleQuantumToMap(
2378 ClampToQuantum(image->colormap[i].red))];
2379 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
2380 image->colormap[i].green=(double) gamma_map[ScaleQuantumToMap(
2381 ClampToQuantum(image->colormap[i].green))];
2382 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
2383 image->colormap[i].blue=(double) gamma_map[ScaleQuantumToMap(
2384 ClampToQuantum(image->colormap[i].blue))];
2385 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
2386 image->colormap[i].alpha=(double) gamma_map[ScaleQuantumToMap(
2387 ClampToQuantum(image->colormap[i].alpha))];
2388 }
2389 /*
2390 Gamma-correct image.
2391 */
2392 status=MagickTrue;
2393 progress=0;
2394 image_view=AcquireAuthenticCacheView(image,exception);
2395#if defined(MAGICKCORE_OPENMP_SUPPORT)
2396 #pragma omp parallel for schedule(static) shared(progress,status) \
2397 magick_number_threads(image,image,image->rows,1)
2398#endif
2399 for (y=0; y < (ssize_t) image->rows; y++)
2400 {
2401 Quantum
2402 *magick_restrict q;
2403
2404 ssize_t
2405 x;
2406
2407 if (status == MagickFalse)
2408 continue;
2409 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2410 if (q == (Quantum *) NULL)
2411 {
2412 status=MagickFalse;
2413 continue;
2414 }
2415 for (x=0; x < (ssize_t) image->columns; x++)
2416 {
2417 ssize_t
2418 j;
2419
2420 for (j=0; j < (ssize_t) GetPixelChannels(image); j++)
2421 {
2422 PixelChannel channel = GetPixelChannelChannel(image,j);
2423 PixelTrait traits = GetPixelChannelTraits(image,channel);
2424 if ((traits & UpdatePixelTrait) == 0)
2425 continue;
2426 q[j]=gamma_map[ScaleQuantumToMap(ClampToQuantum((MagickRealType)
2427 q[j]))];
2428 }
2429 q+=(ptrdiff_t) GetPixelChannels(image);
2430 }
2431 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2432 status=MagickFalse;
2433 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2434 {
2435 MagickBooleanType
2436 proceed;
2437
2438#if defined(MAGICKCORE_OPENMP_SUPPORT)
2439 #pragma omp atomic
2440#endif
2441 progress++;
2442 proceed=SetImageProgress(image,GammaImageTag,progress,image->rows);
2443 if (proceed == MagickFalse)
2444 status=MagickFalse;
2445 }
2446 }
2447 image_view=DestroyCacheView(image_view);
2448 gamma_map=(Quantum *) RelinquishMagickMemory(gamma_map);
2449 if (image->gamma != 0.0)
2450 image->gamma*=gamma;
2451 return(status);
2452}
2453
2454/*
2455%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2456% %
2457% %
2458% %
2459% G r a y s c a l e I m a g e %
2460% %
2461% %
2462% %
2463%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2464%
2465% GrayscaleImage() converts the image to grayscale.
2466%
2467% The format of the GrayscaleImage method is:
2468%
2469% MagickBooleanType GrayscaleImage(Image *image,
2470% const PixelIntensityMethod method ,ExceptionInfo *exception)
2471%
2472% A description of each parameter follows:
2473%
2474% o image: the image.
2475%
2476% o method: the pixel intensity method.
2477%
2478% o exception: return any errors or warnings in this structure.
2479%
2480*/
2481MagickExport MagickBooleanType GrayscaleImage(Image *image,
2482 const PixelIntensityMethod method,ExceptionInfo *exception)
2483{
2484#define GrayscaleImageTag "Grayscale/Image"
2485
2486 CacheView
2487 *image_view;
2488
2489 MagickBooleanType
2490 status;
2491
2492 MagickOffsetType
2493 progress;
2494
2495 ssize_t
2496 y;
2497
2498 assert(image != (Image *) NULL);
2499 assert(image->signature == MagickCoreSignature);
2500 if (IsEventLogging() != MagickFalse)
2501 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2502 if (image->storage_class == PseudoClass)
2503 {
2504 if (SyncImage(image,exception) == MagickFalse)
2505 return(MagickFalse);
2506 if (SetImageStorageClass(image,DirectClass,exception) == MagickFalse)
2507 return(MagickFalse);
2508 }
2509#if defined(MAGICKCORE_OPENCL_SUPPORT)
2510 if (AccelerateGrayscaleImage(image,method,exception) != MagickFalse)
2511 {
2512 image->intensity=method;
2513 image->type=GrayscaleType;
2514 if ((method == Rec601LuminancePixelIntensityMethod) ||
2515 (method == Rec709LuminancePixelIntensityMethod))
2516 return(SetImageColorspace(image,LinearGRAYColorspace,exception));
2517 return(SetImageColorspace(image,GRAYColorspace,exception));
2518 }
2519#endif
2520 /*
2521 Grayscale image.
2522 */
2523 status=MagickTrue;
2524 progress=0;
2525 image_view=AcquireAuthenticCacheView(image,exception);
2526#if defined(MAGICKCORE_OPENMP_SUPPORT)
2527 #pragma omp parallel for schedule(static) shared(progress,status) \
2528 magick_number_threads(image,image,image->rows,1)
2529#endif
2530 for (y=0; y < (ssize_t) image->rows; y++)
2531 {
2532 Quantum
2533 *magick_restrict q;
2534
2535 ssize_t
2536 x;
2537
2538 if (status == MagickFalse)
2539 continue;
2540 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2541 if (q == (Quantum *) NULL)
2542 {
2543 status=MagickFalse;
2544 continue;
2545 }
2546 for (x=0; x < (ssize_t) image->columns; x++)
2547 {
2548 MagickRealType
2549 blue,
2550 green,
2551 red,
2552 intensity;
2553
2554 red=(MagickRealType) GetPixelRed(image,q);
2555 green=(MagickRealType) GetPixelGreen(image,q);
2556 blue=(MagickRealType) GetPixelBlue(image,q);
2557 intensity=0.0;
2558 switch (method)
2559 {
2560 case AveragePixelIntensityMethod:
2561 {
2562 intensity=(red+green+blue)/3.0;
2563 break;
2564 }
2565 case BrightnessPixelIntensityMethod:
2566 {
2567 intensity=MagickMax(MagickMax(red,green),blue);
2568 break;
2569 }
2570 case LightnessPixelIntensityMethod:
2571 {
2572 intensity=(MagickMin(MagickMin(red,green),blue)+
2573 MagickMax(MagickMax(red,green),blue))/2.0;
2574 break;
2575 }
2576 case MSPixelIntensityMethod:
2577 {
2578 intensity=(MagickRealType) (((double) red*red+green*green+
2579 blue*blue)/3.0);
2580 break;
2581 }
2582 case Rec601LumaPixelIntensityMethod:
2583 {
2584 if (image->colorspace == RGBColorspace)
2585 {
2586 red=EncodePixelGamma(red);
2587 green=EncodePixelGamma(green);
2588 blue=EncodePixelGamma(blue);
2589 }
2590 intensity=0.298839*red+0.586811*green+0.114350*blue;
2591 break;
2592 }
2593 case Rec601LuminancePixelIntensityMethod:
2594 {
2595 if (image->colorspace == sRGBColorspace)
2596 {
2597 red=DecodePixelGamma(red);
2598 green=DecodePixelGamma(green);
2599 blue=DecodePixelGamma(blue);
2600 }
2601 intensity=0.298839*red+0.586811*green+0.114350*blue;
2602 break;
2603 }
2604 case Rec709LumaPixelIntensityMethod:
2605 default:
2606 {
2607 if (image->colorspace == RGBColorspace)
2608 {
2609 red=EncodePixelGamma(red);
2610 green=EncodePixelGamma(green);
2611 blue=EncodePixelGamma(blue);
2612 }
2613 intensity=0.212656*red+0.715158*green+0.072186*blue;
2614 break;
2615 }
2616 case Rec709LuminancePixelIntensityMethod:
2617 {
2618 if (image->colorspace == sRGBColorspace)
2619 {
2620 red=DecodePixelGamma(red);
2621 green=DecodePixelGamma(green);
2622 blue=DecodePixelGamma(blue);
2623 }
2624 intensity=0.212656*red+0.715158*green+0.072186*blue;
2625 break;
2626 }
2627 case RMSPixelIntensityMethod:
2628 {
2629 intensity=(MagickRealType) (sqrt((double) red*red+green*green+
2630 blue*blue)/sqrt(3.0));
2631 break;
2632 }
2633 }
2634 SetPixelGray(image,ClampToQuantum(intensity),q);
2635 q+=(ptrdiff_t) GetPixelChannels(image);
2636 }
2637 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2638 status=MagickFalse;
2639 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2640 {
2641 MagickBooleanType
2642 proceed;
2643
2644#if defined(MAGICKCORE_OPENMP_SUPPORT)
2645 #pragma omp atomic
2646#endif
2647 progress++;
2648 proceed=SetImageProgress(image,GrayscaleImageTag,progress,image->rows);
2649 if (proceed == MagickFalse)
2650 status=MagickFalse;
2651 }
2652 }
2653 image_view=DestroyCacheView(image_view);
2654 image->intensity=method;
2655 image->type=GrayscaleType;
2656 if ((method == Rec601LuminancePixelIntensityMethod) ||
2657 (method == Rec709LuminancePixelIntensityMethod))
2658 return(SetImageColorspace(image,LinearGRAYColorspace,exception));
2659 return(SetImageColorspace(image,GRAYColorspace,exception));
2660}
2661
2662/*
2663%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2664% %
2665% %
2666% %
2667% H a l d C l u t I m a g e %
2668% %
2669% %
2670% %
2671%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2672%
2673% HaldClutImage() applies a Hald color lookup table to the image. A Hald
2674% color lookup table is a 3-dimensional color cube mapped to 2 dimensions.
2675% Create it with the HALD coder. You can apply any color transformation to
2676% the Hald image and then use this method to apply the transform to the
2677% image.
2678%
2679% The format of the HaldClutImage method is:
2680%
2681% MagickBooleanType HaldClutImage(Image *image,Image *hald_image,
2682% ExceptionInfo *exception)
2683%
2684% A description of each parameter follows:
2685%
2686% o image: the image, which is replaced by indexed CLUT values
2687%
2688% o hald_image: the color lookup table image for replacement color values.
2689%
2690% o exception: return any errors or warnings in this structure.
2691%
2692*/
2693MagickExport MagickBooleanType HaldClutImage(Image *image,
2694 const Image *hald_image,ExceptionInfo *exception)
2695{
2696#define HaldClutImageTag "Clut/Image"
2697
2698 typedef struct _HaldInfo
2699 {
2700 double
2701 x,
2702 y,
2703 z;
2704 } HaldInfo;
2705
2706 CacheView
2707 *hald_view,
2708 *image_view;
2709
2710 double
2711 width;
2712
2713 MagickBooleanType
2714 status;
2715
2716 MagickOffsetType
2717 progress;
2718
2719 PixelInfo
2720 zero;
2721
2722 size_t
2723 cube_size,
2724 length,
2725 level;
2726
2727 ssize_t
2728 y;
2729
2730 assert(image != (Image *) NULL);
2731 assert(image->signature == MagickCoreSignature);
2732 if (IsEventLogging() != MagickFalse)
2733 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2734 assert(hald_image != (Image *) NULL);
2735 assert(hald_image->signature == MagickCoreSignature);
2736 if (SetImageStorageClass(image,DirectClass,exception) == MagickFalse)
2737 return(MagickFalse);
2738 if ((image->alpha_trait & BlendPixelTrait) == 0)
2739 (void) SetImageAlphaChannel(image,OpaqueAlphaChannel,exception);
2740 if (image->colorspace != hald_image->colorspace)
2741 (void) SetImageColorspace(image,hald_image->colorspace,exception);
2742 /*
2743 Hald clut image.
2744 */
2745 status=MagickTrue;
2746 progress=0;
2747 length=(size_t) MagickMin((MagickRealType) hald_image->columns,
2748 (MagickRealType) hald_image->rows);
2749 for (level=2; (level*level*level) < length; level++) ;
2750 level*=level;
2751 cube_size=level*level;
2752 width=(double) hald_image->columns;
2753 GetPixelInfo(hald_image,&zero);
2754 hald_view=AcquireVirtualCacheView(hald_image,exception);
2755 image_view=AcquireAuthenticCacheView(image,exception);
2756#if defined(MAGICKCORE_OPENMP_SUPPORT)
2757 #pragma omp parallel for schedule(static) shared(progress,status) \
2758 magick_number_threads(image,image,image->rows,1)
2759#endif
2760 for (y=0; y < (ssize_t) image->rows; y++)
2761 {
2762 Quantum
2763 *magick_restrict q;
2764
2765 ssize_t
2766 x;
2767
2768 if (status == MagickFalse)
2769 continue;
2770 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2771 if (q == (Quantum *) NULL)
2772 {
2773 status=MagickFalse;
2774 continue;
2775 }
2776 for (x=0; x < (ssize_t) image->columns; x++)
2777 {
2778 double
2779 area = 0.0,
2780 offset = 0.0;
2781
2782 HaldInfo
2783 point = { 0, 0, 0 };
2784
2785 PixelInfo
2786 pixel = zero,
2787 pixel1 = zero,
2788 pixel2 = zero,
2789 pixel3 = zero,
2790 pixel4 = zero;
2791
2792 point.x=QuantumScale*(level-1.0)*(double) GetPixelRed(image,q);
2793 point.y=QuantumScale*(level-1.0)*(double) GetPixelGreen(image,q);
2794 point.z=QuantumScale*(level-1.0)*(double) GetPixelBlue(image,q);
2795 offset=point.x+level*floor(point.y)+cube_size*floor(point.z);
2796 point.x-=floor(point.x);
2797 point.y-=floor(point.y);
2798 point.z-=floor(point.z);
2799 status=InterpolatePixelInfo(hald_image,hald_view,hald_image->interpolate,
2800 fmod(offset,width),floor(offset/width),&pixel1,exception);
2801 if (status == MagickFalse)
2802 break;
2803 status=InterpolatePixelInfo(hald_image,hald_view,hald_image->interpolate,
2804 fmod(offset+level,width),floor((offset+level)/width),&pixel2,exception);
2805 if (status == MagickFalse)
2806 break;
2807 area=point.y;
2808 if (hald_image->interpolate == NearestInterpolatePixel)
2809 area=(point.y < 0.5) ? 0.0 : 1.0;
2810 CompositePixelInfoAreaBlend(&pixel1,pixel1.alpha,&pixel2,pixel2.alpha,
2811 area,&pixel3);
2812 offset+=cube_size;
2813 status=InterpolatePixelInfo(hald_image,hald_view,hald_image->interpolate,
2814 fmod(offset,width),floor(offset/width),&pixel1,exception);
2815 if (status == MagickFalse)
2816 break;
2817 status=InterpolatePixelInfo(hald_image,hald_view,hald_image->interpolate,
2818 fmod(offset+level,width),floor((offset+level)/width),&pixel2,exception);
2819 if (status == MagickFalse)
2820 break;
2821 CompositePixelInfoAreaBlend(&pixel1,pixel1.alpha,&pixel2,pixel2.alpha,
2822 area,&pixel4);
2823 area=point.z;
2824 if (hald_image->interpolate == NearestInterpolatePixel)
2825 area=(point.z < 0.5)? 0.0 : 1.0;
2826 CompositePixelInfoAreaBlend(&pixel3,pixel3.alpha,&pixel4,pixel4.alpha,
2827 area,&pixel);
2828 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
2829 SetPixelRed(image,ClampToQuantum(pixel.red),q);
2830 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
2831 SetPixelGreen(image,ClampToQuantum(pixel.green),q);
2832 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
2833 SetPixelBlue(image,ClampToQuantum(pixel.blue),q);
2834 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
2835 (image->colorspace == CMYKColorspace))
2836 SetPixelBlack(image,ClampToQuantum(pixel.black),q);
2837 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) &&
2838 (image->alpha_trait != UndefinedPixelTrait))
2839 SetPixelAlpha(image,ClampToQuantum(pixel.alpha),q);
2840 q+=(ptrdiff_t) GetPixelChannels(image);
2841 }
2842 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2843 status=MagickFalse;
2844 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2845 {
2846 MagickBooleanType
2847 proceed;
2848
2849#if defined(MAGICKCORE_OPENMP_SUPPORT)
2850 #pragma omp atomic
2851#endif
2852 progress++;
2853 proceed=SetImageProgress(image,HaldClutImageTag,progress,image->rows);
2854 if (proceed == MagickFalse)
2855 status=MagickFalse;
2856 }
2857 }
2858 hald_view=DestroyCacheView(hald_view);
2859 image_view=DestroyCacheView(image_view);
2860 return(status);
2861}
2862
2863/*
2864%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2865% %
2866% %
2867% %
2868% L e v e l I m a g e %
2869% %
2870% %
2871% %
2872%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2873%
2874% LevelImage() adjusts the levels of a particular image channel by
2875% scaling the colors falling between specified white and black points to
2876% the full available quantum range.
2877%
2878% The parameters provided represent the black, and white points. The black
2879% point specifies the darkest color in the image. Colors darker than the
2880% black point are set to zero. White point specifies the lightest color in
2881% the image. Colors brighter than the white point are set to the maximum
2882% quantum value.
2883%
2884% If a '!' flag is given, map black and white colors to the given levels
2885% rather than mapping those levels to black and white. See
2886% LevelizeImage() below.
2887%
2888% Gamma specifies a gamma correction to apply to the image.
2889%
2890% The format of the LevelImage method is:
2891%
2892% MagickBooleanType LevelImage(Image *image,const double black_point,
2893% const double white_point,const double gamma,ExceptionInfo *exception)
2894%
2895% A description of each parameter follows:
2896%
2897% o image: the image.
2898%
2899% o black_point: The level to map zero (black) to.
2900%
2901% o white_point: The level to map QuantumRange (white) to.
2902%
2903% o exception: return any errors or warnings in this structure.
2904%
2905*/
2906
2907static inline double LevelPixel(const double black_point,
2908 const double white_point,const double gamma,const double pixel)
2909{
2910 double
2911 level_pixel,
2912 scale;
2913
2914 scale=MagickSafeReciprocal(white_point-black_point);
2915 level_pixel=(double) QuantumRange*gamma_pow(scale*((double) pixel-(double)
2916 black_point),MagickSafeReciprocal(gamma));
2917 return(level_pixel);
2918}
2919
2920MagickExport MagickBooleanType LevelImage(Image *image,const double black_point,
2921 const double white_point,const double gamma,ExceptionInfo *exception)
2922{
2923#define LevelImageTag "Level/Image"
2924
2925 CacheView
2926 *image_view;
2927
2928 MagickBooleanType
2929 status;
2930
2931 MagickOffsetType
2932 progress;
2933
2934 ssize_t
2935 i,
2936 y;
2937
2938 /*
2939 Allocate and initialize levels map.
2940 */
2941 assert(image != (Image *) NULL);
2942 assert(image->signature == MagickCoreSignature);
2943 if (IsEventLogging() != MagickFalse)
2944 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2945 if (image->storage_class == PseudoClass)
2946 for (i=0; i < (ssize_t) image->colors; i++)
2947 {
2948 /*
2949 Level colormap.
2950 */
2951 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
2952 image->colormap[i].red=(double) ClampToQuantum(LevelPixel(black_point,
2953 white_point,gamma,image->colormap[i].red));
2954 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
2955 image->colormap[i].green=(double) ClampToQuantum(LevelPixel(black_point,
2956 white_point,gamma,image->colormap[i].green));
2957 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
2958 image->colormap[i].blue=(double) ClampToQuantum(LevelPixel(black_point,
2959 white_point,gamma,image->colormap[i].blue));
2960 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
2961 image->colormap[i].alpha=(double) ClampToQuantum(LevelPixel(black_point,
2962 white_point,gamma,image->colormap[i].alpha));
2963 }
2964 /*
2965 Level image.
2966 */
2967 status=MagickTrue;
2968 progress=0;
2969 image_view=AcquireAuthenticCacheView(image,exception);
2970#if defined(MAGICKCORE_OPENMP_SUPPORT)
2971 #pragma omp parallel for schedule(static) shared(progress,status) \
2972 magick_number_threads(image,image,image->rows,1)
2973#endif
2974 for (y=0; y < (ssize_t) image->rows; y++)
2975 {
2976 Quantum
2977 *magick_restrict q;
2978
2979 ssize_t
2980 x;
2981
2982 if (status == MagickFalse)
2983 continue;
2984 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2985 if (q == (Quantum *) NULL)
2986 {
2987 status=MagickFalse;
2988 continue;
2989 }
2990 for (x=0; x < (ssize_t) image->columns; x++)
2991 {
2992 ssize_t
2993 j;
2994
2995 for (j=0; j < (ssize_t) GetPixelChannels(image); j++)
2996 {
2997 PixelChannel channel = GetPixelChannelChannel(image,j);
2998 PixelTrait traits = GetPixelChannelTraits(image,channel);
2999 if ((traits & UpdatePixelTrait) == 0)
3000 continue;
3001 q[j]=ClampToQuantum(LevelPixel(black_point,white_point,gamma,
3002 (double) q[j]));
3003 }
3004 q+=(ptrdiff_t) GetPixelChannels(image);
3005 }
3006 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
3007 status=MagickFalse;
3008 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3009 {
3010 MagickBooleanType
3011 proceed;
3012
3013#if defined(MAGICKCORE_OPENMP_SUPPORT)
3014 #pragma omp atomic
3015#endif
3016 progress++;
3017 proceed=SetImageProgress(image,LevelImageTag,progress,image->rows);
3018 if (proceed == MagickFalse)
3019 status=MagickFalse;
3020 }
3021 }
3022 image_view=DestroyCacheView(image_view);
3023 (void) ClampImage(image,exception);
3024 return(status);
3025}
3026
3027/*
3028%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3029% %
3030% %
3031% %
3032% L e v e l i z e I m a g e %
3033% %
3034% %
3035% %
3036%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3037%
3038% LevelizeImage() applies the reversed LevelImage() operation to just
3039% the specific channels specified. It compresses the full range of color
3040% values, so that they lie between the given black and white points. Gamma is
3041% applied before the values are mapped.
3042%
3043% LevelizeImage() can be called with by using a +level command line
3044% API option, or using a '!' on a -level or LevelImage() geometry string.
3045%
3046% It can be used to de-contrast a greyscale image to the exact levels
3047% specified. Or by using specific levels for each channel of an image you
3048% can convert a gray-scale image to any linear color gradient, according to
3049% those levels.
3050%
3051% The format of the LevelizeImage method is:
3052%
3053% MagickBooleanType LevelizeImage(Image *image,const double black_point,
3054% const double white_point,const double gamma,ExceptionInfo *exception)
3055%
3056% A description of each parameter follows:
3057%
3058% o image: the image.
3059%
3060% o black_point: The level to map zero (black) to.
3061%
3062% o white_point: The level to map QuantumRange (white) to.
3063%
3064% o gamma: adjust gamma by this factor before mapping values.
3065%
3066% o exception: return any errors or warnings in this structure.
3067%
3068*/
3069MagickExport MagickBooleanType LevelizeImage(Image *image,
3070 const double black_point,const double white_point,const double gamma,
3071 ExceptionInfo *exception)
3072{
3073#define LevelizeImageTag "Levelize/Image"
3074#define LevelizeValue(x) ClampToQuantum(((MagickRealType) gamma_pow((double) \
3075 (QuantumScale*((double) x)),gamma))*(white_point-black_point)+black_point)
3076
3077 CacheView
3078 *image_view;
3079
3080 MagickBooleanType
3081 status;
3082
3083 MagickOffsetType
3084 progress;
3085
3086 ssize_t
3087 i;
3088
3089 ssize_t
3090 y;
3091
3092 /*
3093 Allocate and initialize levels map.
3094 */
3095 assert(image != (Image *) NULL);
3096 assert(image->signature == MagickCoreSignature);
3097 if (IsEventLogging() != MagickFalse)
3098 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3099 if (image->storage_class == PseudoClass)
3100 for (i=0; i < (ssize_t) image->colors; i++)
3101 {
3102 /*
3103 Level colormap.
3104 */
3105 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
3106 image->colormap[i].red=(double) LevelizeValue(image->colormap[i].red);
3107 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
3108 image->colormap[i].green=(double) LevelizeValue(
3109 image->colormap[i].green);
3110 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
3111 image->colormap[i].blue=(double) LevelizeValue(image->colormap[i].blue);
3112 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
3113 image->colormap[i].alpha=(double) LevelizeValue(
3114 image->colormap[i].alpha);
3115 }
3116 /*
3117 Level image.
3118 */
3119 status=MagickTrue;
3120 progress=0;
3121 image_view=AcquireAuthenticCacheView(image,exception);
3122#if defined(MAGICKCORE_OPENMP_SUPPORT)
3123 #pragma omp parallel for schedule(static) shared(progress,status) \
3124 magick_number_threads(image,image,image->rows,1)
3125#endif
3126 for (y=0; y < (ssize_t) image->rows; y++)
3127 {
3128 Quantum
3129 *magick_restrict q;
3130
3131 ssize_t
3132 x;
3133
3134 if (status == MagickFalse)
3135 continue;
3136 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
3137 if (q == (Quantum *) NULL)
3138 {
3139 status=MagickFalse;
3140 continue;
3141 }
3142 for (x=0; x < (ssize_t) image->columns; x++)
3143 {
3144 ssize_t
3145 j;
3146
3147 for (j=0; j < (ssize_t) GetPixelChannels(image); j++)
3148 {
3149 PixelChannel channel = GetPixelChannelChannel(image,j);
3150 PixelTrait traits = GetPixelChannelTraits(image,channel);
3151 if ((traits & UpdatePixelTrait) == 0)
3152 continue;
3153 q[j]=LevelizeValue(q[j]);
3154 }
3155 q+=(ptrdiff_t) GetPixelChannels(image);
3156 }
3157 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
3158 status=MagickFalse;
3159 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3160 {
3161 MagickBooleanType
3162 proceed;
3163
3164#if defined(MAGICKCORE_OPENMP_SUPPORT)
3165 #pragma omp atomic
3166#endif
3167 progress++;
3168 proceed=SetImageProgress(image,LevelizeImageTag,progress,image->rows);
3169 if (proceed == MagickFalse)
3170 status=MagickFalse;
3171 }
3172 }
3173 image_view=DestroyCacheView(image_view);
3174 return(status);
3175}
3176
3177/*
3178%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3179% %
3180% %
3181% %
3182% L e v e l I m a g e C o l o r s %
3183% %
3184% %
3185% %
3186%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3187%
3188% LevelImageColors() maps the given color to "black" and "white" values,
3189% linearly spreading out the colors, and level values on a channel by channel
3190% bases, as per LevelImage(). The given colors allows you to specify
3191% different level ranges for each of the color channels separately.
3192%
3193% If the boolean 'invert' is set true the image values will modified in the
3194% reverse direction. That is any existing "black" and "white" colors in the
3195% image will become the color values given, with all other values compressed
3196% appropriately. This effectively maps a greyscale gradient into the given
3197% color gradient.
3198%
3199% The format of the LevelImageColors method is:
3200%
3201% MagickBooleanType LevelImageColors(Image *image,
3202% const PixelInfo *black_color,const PixelInfo *white_color,
3203% const MagickBooleanType invert,ExceptionInfo *exception)
3204%
3205% A description of each parameter follows:
3206%
3207% o image: the image.
3208%
3209% o black_color: The color to map black to/from
3210%
3211% o white_point: The color to map white to/from
3212%
3213% o invert: if true map the colors (levelize), rather than from (level)
3214%
3215% o exception: return any errors or warnings in this structure.
3216%
3217*/
3218MagickExport MagickBooleanType LevelImageColors(Image *image,
3219 const PixelInfo *black_color,const PixelInfo *white_color,
3220 const MagickBooleanType invert,ExceptionInfo *exception)
3221{
3222 ChannelType
3223 channel_mask;
3224
3225 MagickStatusType
3226 status;
3227
3228 /*
3229 Allocate and initialize levels map.
3230 */
3231 assert(image != (Image *) NULL);
3232 assert(image->signature == MagickCoreSignature);
3233 if (IsEventLogging() != MagickFalse)
3234 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3235 if ((IsGrayColorspace(image->colorspace) != MagickFalse) &&
3236 ((IsGrayColorspace(black_color->colorspace) == MagickFalse) ||
3237 (IsGrayColorspace(white_color->colorspace) == MagickFalse)))
3238 (void) SetImageColorspace(image,sRGBColorspace,exception);
3239 status=MagickTrue;
3240 if (invert == MagickFalse)
3241 {
3242 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
3243 {
3244 channel_mask=SetImageChannelMask(image,RedChannel);
3245 status&=(MagickStatusType) LevelImage(image,black_color->red,
3246 white_color->red,1.0,exception);
3247 (void) SetImageChannelMask(image,channel_mask);
3248 }
3249 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
3250 {
3251 channel_mask=SetImageChannelMask(image,GreenChannel);
3252 status&=(MagickStatusType) LevelImage(image,black_color->green,
3253 white_color->green,1.0,exception);
3254 (void) SetImageChannelMask(image,channel_mask);
3255 }
3256 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
3257 {
3258 channel_mask=SetImageChannelMask(image,BlueChannel);
3259 status&=(MagickStatusType) LevelImage(image,black_color->blue,
3260 white_color->blue,1.0,exception);
3261 (void) SetImageChannelMask(image,channel_mask);
3262 }
3263 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
3264 (image->colorspace == CMYKColorspace))
3265 {
3266 channel_mask=SetImageChannelMask(image,BlackChannel);
3267 status&=(MagickStatusType) LevelImage(image,black_color->black,
3268 white_color->black,1.0,exception);
3269 (void) SetImageChannelMask(image,channel_mask);
3270 }
3271 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) &&
3272 (image->alpha_trait != UndefinedPixelTrait))
3273 {
3274 channel_mask=SetImageChannelMask(image,AlphaChannel);
3275 status&=(MagickStatusType) LevelImage(image,black_color->alpha,
3276 white_color->alpha,1.0,exception);
3277 (void) SetImageChannelMask(image,channel_mask);
3278 }
3279 }
3280 else
3281 {
3282 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
3283 {
3284 channel_mask=SetImageChannelMask(image,RedChannel);
3285 status&=(MagickStatusType) LevelizeImage(image,black_color->red,
3286 white_color->red,1.0,exception);
3287 (void) SetImageChannelMask(image,channel_mask);
3288 }
3289 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
3290 {
3291 channel_mask=SetImageChannelMask(image,GreenChannel);
3292 status&=(MagickStatusType) LevelizeImage(image,black_color->green,
3293 white_color->green,1.0,exception);
3294 (void) SetImageChannelMask(image,channel_mask);
3295 }
3296 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
3297 {
3298 channel_mask=SetImageChannelMask(image,BlueChannel);
3299 status&=(MagickStatusType) LevelizeImage(image,black_color->blue,
3300 white_color->blue,1.0,exception);
3301 (void) SetImageChannelMask(image,channel_mask);
3302 }
3303 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
3304 (image->colorspace == CMYKColorspace))
3305 {
3306 channel_mask=SetImageChannelMask(image,BlackChannel);
3307 status&=(MagickStatusType) LevelizeImage(image,black_color->black,
3308 white_color->black,1.0,exception);
3309 (void) SetImageChannelMask(image,channel_mask);
3310 }
3311 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) &&
3312 (image->alpha_trait != UndefinedPixelTrait))
3313 {
3314 channel_mask=SetImageChannelMask(image,AlphaChannel);
3315 status&=(MagickStatusType) LevelizeImage(image,black_color->alpha,
3316 white_color->alpha,1.0,exception);
3317 (void) SetImageChannelMask(image,channel_mask);
3318 }
3319 }
3320 return(status != 0 ? MagickTrue : MagickFalse);
3321}
3322
3323/*
3324%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3325% %
3326% %
3327% %
3328% L i n e a r S t r e t c h I m a g e %
3329% %
3330% %
3331% %
3332%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3333%
3334% LinearStretchImage() discards any pixels below the black point and above
3335% the white point and levels the remaining pixels.
3336%
3337% The format of the LinearStretchImage method is:
3338%
3339% MagickBooleanType LinearStretchImage(Image *image,
3340% const double black_point,const double white_point,
3341% ExceptionInfo *exception)
3342%
3343% A description of each parameter follows:
3344%
3345% o image: the image.
3346%
3347% o black_point: the black point.
3348%
3349% o white_point: the white point.
3350%
3351% o exception: return any errors or warnings in this structure.
3352%
3353*/
3354MagickExport MagickBooleanType LinearStretchImage(Image *image,
3355 const double black_point,const double white_point,ExceptionInfo *exception)
3356{
3357#define LinearStretchImageTag "LinearStretch/Image"
3358
3359 CacheView
3360 *image_view;
3361
3362 char
3363 property[MagickPathExtent];
3364
3365 double
3366 *histogram,
3367 intensity;
3368
3369 MagickBooleanType
3370 status;
3371
3372 ssize_t
3373 black,
3374 white,
3375 y;
3376
3377 /*
3378 Allocate histogram and linear map.
3379 */
3380 assert(image != (Image *) NULL);
3381 assert(image->signature == MagickCoreSignature);
3382 histogram=(double *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*histogram));
3383 if (histogram == (double *) NULL)
3384 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
3385 image->filename);
3386 /*
3387 Form histogram.
3388 */
3389 (void) memset(histogram,0,(MaxMap+1)*sizeof(*histogram));
3390 image_view=AcquireVirtualCacheView(image,exception);
3391 for (y=0; y < (ssize_t) image->rows; y++)
3392 {
3393 const Quantum
3394 *magick_restrict p;
3395
3396 ssize_t
3397 x;
3398
3399 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
3400 if (p == (const Quantum *) NULL)
3401 break;
3402 for (x=0; x < (ssize_t) image->columns; x++)
3403 {
3404 intensity=GetPixelIntensity(image,p);
3405 histogram[ScaleQuantumToMap(ClampToQuantum(intensity))]++;
3406 p+=(ptrdiff_t) GetPixelChannels(image);
3407 }
3408 }
3409 image_view=DestroyCacheView(image_view);
3410 /*
3411 Find the histogram boundaries by locating the black and white point levels.
3412 */
3413 intensity=0.0;
3414 for (black=0; black < (ssize_t) MaxMap; black++)
3415 {
3416 intensity+=histogram[black];
3417 if (intensity >= black_point)
3418 break;
3419 }
3420 intensity=0.0;
3421 for (white=(ssize_t) MaxMap; white != 0; white--)
3422 {
3423 intensity+=histogram[white];
3424 if (intensity >= white_point)
3425 break;
3426 }
3427 histogram=(double *) RelinquishMagickMemory(histogram);
3428 status=LevelImage(image,(double) ScaleMapToQuantum((MagickRealType) black),
3429 (double) ScaleMapToQuantum((MagickRealType) white),1.0,exception);
3430 (void) FormatLocaleString(property,MagickPathExtent,"%gx%g%%",100.0*black/
3431 MaxMap,100.0*white/MaxMap);
3432 (void) SetImageProperty(image,"histogram:linear-stretch",property,exception);
3433 return(status);
3434}
3435
3436/*
3437%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3438% %
3439% %
3440% %
3441% M o d u l a t e I m a g e %
3442% %
3443% %
3444% %
3445%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3446%
3447% ModulateImage() lets you control the brightness, saturation, and hue
3448% of an image. Modulate represents the brightness, saturation, and hue
3449% as one parameter (e.g. 90,150,100). If the image colorspace is HSL, the
3450% modulation is lightness, saturation, and hue. For HWB, use blackness,
3451% whiteness, and hue. And for HCL, use chrome, luma, and hue.
3452%
3453% The format of the ModulateImage method is:
3454%
3455% MagickBooleanType ModulateImage(Image *image,const char *modulate,
3456% ExceptionInfo *exception)
3457%
3458% A description of each parameter follows:
3459%
3460% o image: the image.
3461%
3462% o modulate: Define the percent change in brightness, saturation, and hue.
3463%
3464% o exception: return any errors or warnings in this structure.
3465%
3466*/
3467
3468static inline void ModulateHCL(const double percent_hue,
3469 const double percent_chroma,const double percent_luma,double *red,
3470 double *green,double *blue)
3471{
3472 double
3473 hue,
3474 luma,
3475 chroma;
3476
3477 /*
3478 Increase or decrease color luma, chroma, or hue.
3479 */
3480 ConvertRGBToHCL(*red,*green,*blue,&hue,&chroma,&luma);
3481 hue+=fmod((percent_hue-100.0),200.0)/200.0;
3482 chroma*=0.01*percent_chroma;
3483 luma*=0.01*percent_luma;
3484 ConvertHCLToRGB(hue,chroma,luma,red,green,blue);
3485}
3486
3487static inline void ModulateHCLp(const double percent_hue,
3488 const double percent_chroma,const double percent_luma,double *red,
3489 double *green,double *blue)
3490{
3491 double
3492 hue,
3493 luma,
3494 chroma;
3495
3496 /*
3497 Increase or decrease color luma, chroma, or hue.
3498 */
3499 ConvertRGBToHCLp(*red,*green,*blue,&hue,&chroma,&luma);
3500 hue+=fmod((percent_hue-100.0),200.0)/200.0;
3501 chroma*=0.01*percent_chroma;
3502 luma*=0.01*percent_luma;
3503 ConvertHCLpToRGB(hue,chroma,luma,red,green,blue);
3504}
3505
3506static inline void ModulateHSB(const double percent_hue,
3507 const double percent_saturation,const double percent_brightness,double *red,
3508 double *green,double *blue)
3509{
3510 double
3511 brightness,
3512 hue,
3513 saturation;
3514
3515 /*
3516 Increase or decrease color brightness, saturation, or hue.
3517 */
3518 ConvertRGBToHSB(*red,*green,*blue,&hue,&saturation,&brightness);
3519 hue+=fmod((percent_hue-100.0),200.0)/200.0;
3520 saturation*=0.01*percent_saturation;
3521 brightness*=0.01*percent_brightness;
3522 ConvertHSBToRGB(hue,saturation,brightness,red,green,blue);
3523}
3524
3525static inline void ModulateHSI(const double percent_hue,
3526 const double percent_saturation,const double percent_intensity,double *red,
3527 double *green,double *blue)
3528{
3529 double
3530 intensity,
3531 hue,
3532 saturation;
3533
3534 /*
3535 Increase or decrease color intensity, saturation, or hue.
3536 */
3537 ConvertRGBToHSI(*red,*green,*blue,&hue,&saturation,&intensity);
3538 hue+=fmod((percent_hue-100.0),200.0)/200.0;
3539 saturation*=0.01*percent_saturation;
3540 intensity*=0.01*percent_intensity;
3541 ConvertHSIToRGB(hue,saturation,intensity,red,green,blue);
3542}
3543
3544static inline void ModulateHSL(const double percent_hue,
3545 const double percent_saturation,const double percent_lightness,double *red,
3546 double *green,double *blue)
3547{
3548 double
3549 hue,
3550 lightness,
3551 saturation;
3552
3553 /*
3554 Increase or decrease color lightness, saturation, or hue.
3555 */
3556 ConvertRGBToHSL(*red,*green,*blue,&hue,&saturation,&lightness);
3557 hue+=fmod((percent_hue-100.0),200.0)/200.0;
3558 saturation*=0.01*percent_saturation;
3559 lightness*=0.01*percent_lightness;
3560 ConvertHSLToRGB(hue,saturation,lightness,red,green,blue);
3561}
3562
3563static inline void ModulateHSV(const double percent_hue,
3564 const double percent_saturation,const double percent_value,double *red,
3565 double *green,double *blue)
3566{
3567 double
3568 hue,
3569 saturation,
3570 value;
3571
3572 /*
3573 Increase or decrease color value, saturation, or hue.
3574 */
3575 ConvertRGBToHSV(*red,*green,*blue,&hue,&saturation,&value);
3576 hue+=fmod((percent_hue-100.0),200.0)/200.0;
3577 saturation*=0.01*percent_saturation;
3578 value*=0.01*percent_value;
3579 ConvertHSVToRGB(hue,saturation,value,red,green,blue);
3580}
3581
3582static inline void ModulateHWB(const double percent_hue,
3583 const double percent_whiteness,const double percent_blackness,double *red,
3584 double *green,double *blue)
3585{
3586 double
3587 blackness,
3588 hue,
3589 whiteness;
3590
3591 /*
3592 Increase or decrease color blackness, whiteness, or hue.
3593 */
3594 ConvertRGBToHWB(*red,*green,*blue,&hue,&whiteness,&blackness);
3595 hue+=fmod((percent_hue-100.0),200.0)/200.0;
3596 blackness*=0.01*percent_blackness;
3597 whiteness*=0.01*percent_whiteness;
3598 ConvertHWBToRGB(hue,whiteness,blackness,red,green,blue);
3599}
3600
3601static inline void ModulateLCHab(const double percent_luma,
3602 const double percent_chroma,const double percent_hue,
3603 const IlluminantType illuminant,double *red,double *green,double *blue)
3604{
3605 double
3606 hue,
3607 luma,
3608 chroma;
3609
3610 /*
3611 Increase or decrease color luma, chroma, or hue.
3612 */
3613 ConvertRGBToLCHab(*red,*green,*blue,illuminant,&luma,&chroma,&hue);
3614 luma*=0.01*percent_luma;
3615 chroma*=0.01*percent_chroma;
3616 hue+=fmod((percent_hue-100.0),200.0)/200.0;
3617 ConvertLCHabToRGB(luma,chroma,hue,illuminant,red,green,blue);
3618}
3619
3620static inline void ModulateLCHuv(const double percent_luma,
3621 const double percent_chroma,const double percent_hue,
3622 const IlluminantType illuminant,double *red,double *green,double *blue)
3623{
3624 double
3625 hue,
3626 luma,
3627 chroma;
3628
3629 /*
3630 Increase or decrease color luma, chroma, or hue.
3631 */
3632 ConvertRGBToLCHuv(*red,*green,*blue,illuminant,&luma,&chroma,&hue);
3633 luma*=0.01*percent_luma;
3634 chroma*=0.01*percent_chroma;
3635 hue+=fmod((percent_hue-100.0),200.0)/200.0;
3636 ConvertLCHuvToRGB(luma,chroma,hue,illuminant,red,green,blue);
3637}
3638
3639MagickExport MagickBooleanType ModulateImage(Image *image,const char *modulate,
3640 ExceptionInfo *exception)
3641{
3642#define ModulateImageTag "Modulate/Image"
3643
3644 CacheView
3645 *image_view;
3646
3647 ColorspaceType
3648 colorspace = UndefinedColorspace;
3649
3650 const char
3651 *artifact;
3652
3653 double
3654 percent_brightness = 100.0,
3655 percent_hue = 100.0,
3656 percent_saturation = 100.0;
3657
3658 GeometryInfo
3659 geometry_info;
3660
3661 IlluminantType
3662 illuminant = D65Illuminant;
3663
3664 MagickBooleanType
3665 status;
3666
3667 MagickOffsetType
3668 progress;
3669
3670 MagickStatusType
3671 flags;
3672
3673 ssize_t
3674 i;
3675
3676 ssize_t
3677 y;
3678
3679 /*
3680 Initialize modulate table.
3681 */
3682 assert(image != (Image *) NULL);
3683 assert(image->signature == MagickCoreSignature);
3684 if (IsEventLogging() != MagickFalse)
3685 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3686 if (modulate == (char *) NULL)
3687 return(MagickFalse);
3688 if (IssRGBCompatibleColorspace(image->colorspace) == MagickFalse)
3689 (void) SetImageColorspace(image,sRGBColorspace,exception);
3690 flags=ParseGeometry(modulate,&geometry_info);
3691 if ((flags & RhoValue) != 0)
3692 percent_brightness=geometry_info.rho;
3693 if ((flags & SigmaValue) != 0)
3694 percent_saturation=geometry_info.sigma;
3695 if ((flags & XiValue) != 0)
3696 percent_hue=geometry_info.xi;
3697 artifact=GetImageArtifact(image,"modulate:colorspace");
3698 if (artifact != (const char *) NULL)
3699 colorspace=(ColorspaceType) ParseCommandOption(MagickColorspaceOptions,
3700 MagickFalse,artifact);
3701 artifact=GetImageArtifact(image,"color:illuminant");
3702 if (artifact != (const char *) NULL)
3703 {
3704 ssize_t
3705 illuminant_type;
3706
3707 illuminant_type=ParseCommandOption(MagickIlluminantOptions,MagickFalse,
3708 artifact);
3709 if (illuminant_type < 0)
3710 {
3711 illuminant=UndefinedIlluminant;
3712 colorspace=UndefinedColorspace;
3713 }
3714 else
3715 illuminant=(IlluminantType) illuminant_type;
3716 }
3717 if (image->storage_class == PseudoClass)
3718 for (i=0; i < (ssize_t) image->colors; i++)
3719 {
3720 double
3721 blue,
3722 green,
3723 red;
3724
3725 /*
3726 Modulate image colormap.
3727 */
3728 red=(double) image->colormap[i].red;
3729 green=(double) image->colormap[i].green;
3730 blue=(double) image->colormap[i].blue;
3731 switch (colorspace)
3732 {
3733 case HCLColorspace:
3734 {
3735 ModulateHCL(percent_hue,percent_saturation,percent_brightness,
3736 &red,&green,&blue);
3737 break;
3738 }
3739 case HCLpColorspace:
3740 {
3741 ModulateHCLp(percent_hue,percent_saturation,percent_brightness,
3742 &red,&green,&blue);
3743 break;
3744 }
3745 case HSBColorspace:
3746 {
3747 ModulateHSB(percent_hue,percent_saturation,percent_brightness,
3748 &red,&green,&blue);
3749 break;
3750 }
3751 case HSIColorspace:
3752 {
3753 ModulateHSI(percent_hue,percent_saturation,percent_brightness,
3754 &red,&green,&blue);
3755 break;
3756 }
3757 case HSLColorspace:
3758 default:
3759 {
3760 ModulateHSL(percent_hue,percent_saturation,percent_brightness,
3761 &red,&green,&blue);
3762 break;
3763 }
3764 case HSVColorspace:
3765 {
3766 ModulateHSV(percent_hue,percent_saturation,percent_brightness,
3767 &red,&green,&blue);
3768 break;
3769 }
3770 case HWBColorspace:
3771 {
3772 ModulateHWB(percent_hue,percent_saturation,percent_brightness,
3773 &red,&green,&blue);
3774 break;
3775 }
3776 case LCHColorspace:
3777 case LCHabColorspace:
3778 {
3779 ModulateLCHab(percent_brightness,percent_saturation,percent_hue,
3780 illuminant,&red,&green,&blue);
3781 break;
3782 }
3783 case LCHuvColorspace:
3784 {
3785 ModulateLCHuv(percent_brightness,percent_saturation,percent_hue,
3786 illuminant,&red,&green,&blue);
3787 break;
3788 }
3789 }
3790 image->colormap[i].red=red;
3791 image->colormap[i].green=green;
3792 image->colormap[i].blue=blue;
3793 }
3794 /*
3795 Modulate image.
3796 */
3797#if defined(MAGICKCORE_OPENCL_SUPPORT)
3798 if (AccelerateModulateImage(image,percent_brightness,percent_hue,
3799 percent_saturation,colorspace,exception) != MagickFalse)
3800 return(MagickTrue);
3801#endif
3802 status=MagickTrue;
3803 progress=0;
3804 image_view=AcquireAuthenticCacheView(image,exception);
3805#if defined(MAGICKCORE_OPENMP_SUPPORT)
3806 #pragma omp parallel for schedule(static) shared(progress,status) \
3807 magick_number_threads(image,image,image->rows,1)
3808#endif
3809 for (y=0; y < (ssize_t) image->rows; y++)
3810 {
3811 Quantum
3812 *magick_restrict q;
3813
3814 ssize_t
3815 x;
3816
3817 if (status == MagickFalse)
3818 continue;
3819 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
3820 if (q == (Quantum *) NULL)
3821 {
3822 status=MagickFalse;
3823 continue;
3824 }
3825 for (x=0; x < (ssize_t) image->columns; x++)
3826 {
3827 double
3828 blue,
3829 green,
3830 red;
3831
3832 red=(double) GetPixelRed(image,q);
3833 green=(double) GetPixelGreen(image,q);
3834 blue=(double) GetPixelBlue(image,q);
3835 switch (colorspace)
3836 {
3837 case HCLColorspace:
3838 {
3839 ModulateHCL(percent_hue,percent_saturation,percent_brightness,
3840 &red,&green,&blue);
3841 break;
3842 }
3843 case HCLpColorspace:
3844 {
3845 ModulateHCLp(percent_hue,percent_saturation,percent_brightness,
3846 &red,&green,&blue);
3847 break;
3848 }
3849 case HSBColorspace:
3850 {
3851 ModulateHSB(percent_hue,percent_saturation,percent_brightness,
3852 &red,&green,&blue);
3853 break;
3854 }
3855 case HSIColorspace:
3856 {
3857 ModulateHSI(percent_hue,percent_saturation,percent_brightness,
3858 &red,&green,&blue);
3859 break;
3860 }
3861 case HSLColorspace:
3862 default:
3863 {
3864 ModulateHSL(percent_hue,percent_saturation,percent_brightness,
3865 &red,&green,&blue);
3866 break;
3867 }
3868 case HSVColorspace:
3869 {
3870 ModulateHSV(percent_hue,percent_saturation,percent_brightness,
3871 &red,&green,&blue);
3872 break;
3873 }
3874 case HWBColorspace:
3875 {
3876 ModulateHWB(percent_hue,percent_saturation,percent_brightness,
3877 &red,&green,&blue);
3878 break;
3879 }
3880 case LCHColorspace:
3881 case LCHabColorspace:
3882 {
3883 ModulateLCHab(percent_brightness,percent_saturation,percent_hue,
3884 illuminant,&red,&green,&blue);
3885 break;
3886 }
3887 case LCHuvColorspace:
3888 {
3889 ModulateLCHuv(percent_brightness,percent_saturation,percent_hue,
3890 illuminant,&red,&green,&blue);
3891 break;
3892 }
3893 }
3894 SetPixelRed(image,ClampToQuantum(red),q);
3895 SetPixelGreen(image,ClampToQuantum(green),q);
3896 SetPixelBlue(image,ClampToQuantum(blue),q);
3897 q+=(ptrdiff_t) GetPixelChannels(image);
3898 }
3899 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
3900 status=MagickFalse;
3901 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3902 {
3903 MagickBooleanType
3904 proceed;
3905
3906#if defined(MAGICKCORE_OPENMP_SUPPORT)
3907 #pragma omp atomic
3908#endif
3909 progress++;
3910 proceed=SetImageProgress(image,ModulateImageTag,progress,image->rows);
3911 if (proceed == MagickFalse)
3912 status=MagickFalse;
3913 }
3914 }
3915 image_view=DestroyCacheView(image_view);
3916 return(status);
3917}
3918
3919/*
3920%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3921% %
3922% %
3923% %
3924% N e g a t e I m a g e %
3925% %
3926% %
3927% %
3928%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3929%
3930% NegateImage() negates the colors in the reference image. The grayscale
3931% option means that only grayscale values within the image are negated.
3932%
3933% The format of the NegateImage method is:
3934%
3935% MagickBooleanType NegateImage(Image *image,
3936% const MagickBooleanType grayscale,ExceptionInfo *exception)
3937%
3938% A description of each parameter follows:
3939%
3940% o image: the image.
3941%
3942% o grayscale: If MagickTrue, only negate grayscale pixels within the image.
3943%
3944% o exception: return any errors or warnings in this structure.
3945%
3946*/
3947MagickExport MagickBooleanType NegateImage(Image *image,
3948 const MagickBooleanType grayscale,ExceptionInfo *exception)
3949{
3950#define NegateImageTag "Negate/Image"
3951
3952 CacheView
3953 *image_view;
3954
3955 MagickBooleanType
3956 status;
3957
3958 MagickOffsetType
3959 progress;
3960
3961 ssize_t
3962 i;
3963
3964 ssize_t
3965 y;
3966
3967 assert(image != (Image *) NULL);
3968 assert(image->signature == MagickCoreSignature);
3969 if (IsEventLogging() != MagickFalse)
3970 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3971 if (image->storage_class == PseudoClass)
3972 for (i=0; i < (ssize_t) image->colors; i++)
3973 {
3974 /*
3975 Negate colormap.
3976 */
3977 if (grayscale != MagickFalse)
3978 if ((image->colormap[i].red != image->colormap[i].green) ||
3979 (image->colormap[i].green != image->colormap[i].blue))
3980 continue;
3981 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
3982 image->colormap[i].red=(double) QuantumRange-image->colormap[i].red;
3983 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
3984 image->colormap[i].green=(double) QuantumRange-image->colormap[i].green;
3985 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
3986 image->colormap[i].blue=(double) QuantumRange-image->colormap[i].blue;
3987 }
3988 /*
3989 Negate image.
3990 */
3991 status=MagickTrue;
3992 progress=0;
3993 image_view=AcquireAuthenticCacheView(image,exception);
3994 if( grayscale != MagickFalse )
3995 {
3996 for (y=0; y < (ssize_t) image->rows; y++)
3997 {
3998 MagickBooleanType
3999 sync;
4000
4001 Quantum
4002 *magick_restrict q;
4003
4004 ssize_t
4005 x;
4006
4007 if (status == MagickFalse)
4008 continue;
4009 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,
4010 exception);
4011 if (q == (Quantum *) NULL)
4012 {
4013 status=MagickFalse;
4014 continue;
4015 }
4016 for (x=0; x < (ssize_t) image->columns; x++)
4017 {
4018 ssize_t
4019 j;
4020
4021 if (IsPixelGray(image,q) == MagickFalse)
4022 {
4023 q+=(ptrdiff_t) GetPixelChannels(image);
4024 continue;
4025 }
4026 for (j=0; j < (ssize_t) GetPixelChannels(image); j++)
4027 {
4028 PixelChannel channel = GetPixelChannelChannel(image,j);
4029 PixelTrait traits = GetPixelChannelTraits(image,channel);
4030 if ((traits & UpdatePixelTrait) == 0)
4031 continue;
4032 q[j]=QuantumRange-q[j];
4033 }
4034 q+=(ptrdiff_t) GetPixelChannels(image);
4035 }
4036 sync=SyncCacheViewAuthenticPixels(image_view,exception);
4037 if (sync == MagickFalse)
4038 status=MagickFalse;
4039 if (image->progress_monitor != (MagickProgressMonitor) NULL)
4040 {
4041 MagickBooleanType
4042 proceed;
4043
4044 progress++;
4045 proceed=SetImageProgress(image,NegateImageTag,progress,image->rows);
4046 if (proceed == MagickFalse)
4047 status=MagickFalse;
4048 }
4049 }
4050 image_view=DestroyCacheView(image_view);
4051 return(MagickTrue);
4052 }
4053 /*
4054 Negate image.
4055 */
4056#if defined(MAGICKCORE_OPENMP_SUPPORT)
4057 #pragma omp parallel for schedule(static) shared(progress,status) \
4058 magick_number_threads(image,image,image->rows,1)
4059#endif
4060 for (y=0; y < (ssize_t) image->rows; y++)
4061 {
4062 Quantum
4063 *magick_restrict q;
4064
4065 ssize_t
4066 x;
4067
4068 if (status == MagickFalse)
4069 continue;
4070 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
4071 if (q == (Quantum *) NULL)
4072 {
4073 status=MagickFalse;
4074 continue;
4075 }
4076 for (x=0; x < (ssize_t) image->columns; x++)
4077 {
4078 ssize_t
4079 j;
4080
4081 for (j=0; j < (ssize_t) GetPixelChannels(image); j++)
4082 {
4083 PixelChannel channel = GetPixelChannelChannel(image,j);
4084 PixelTrait traits = GetPixelChannelTraits(image,channel);
4085 if ((traits & UpdatePixelTrait) == 0)
4086 continue;
4087 q[j]=QuantumRange-q[j];
4088 }
4089 q+=(ptrdiff_t) GetPixelChannels(image);
4090 }
4091 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
4092 status=MagickFalse;
4093 if (image->progress_monitor != (MagickProgressMonitor) NULL)
4094 {
4095 MagickBooleanType
4096 proceed;
4097
4098#if defined(MAGICKCORE_OPENMP_SUPPORT)
4099 #pragma omp atomic
4100#endif
4101 progress++;
4102 proceed=SetImageProgress(image,NegateImageTag,progress,image->rows);
4103 if (proceed == MagickFalse)
4104 status=MagickFalse;
4105 }
4106 }
4107 image_view=DestroyCacheView(image_view);
4108 return(status);
4109}
4110
4111/*
4112%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4113% %
4114% %
4115% %
4116% N o r m a l i z e I m a g e %
4117% %
4118% %
4119% %
4120%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4121%
4122% The NormalizeImage() method enhances the contrast of a color image by
4123% mapping the darkest 2 percent of all pixel to black and the brightest
4124% 1 percent to white.
4125%
4126% The format of the NormalizeImage method is:
4127%
4128% MagickBooleanType NormalizeImage(Image *image,ExceptionInfo *exception)
4129%
4130% A description of each parameter follows:
4131%
4132% o image: the image.
4133%
4134% o exception: return any errors or warnings in this structure.
4135%
4136*/
4137MagickExport MagickBooleanType NormalizeImage(Image *image,
4138 ExceptionInfo *exception)
4139{
4140 double
4141 black_point,
4142 white_point;
4143
4144 black_point=0.02*image->columns*image->rows;
4145 white_point=0.99*image->columns*image->rows;
4146 return(ContrastStretchImage(image,black_point,white_point,exception));
4147}
4148
4149/*
4150%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4151% %
4152% %
4153% %
4154% S i g m o i d a l C o n t r a s t I m a g e %
4155% %
4156% %
4157% %
4158%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4159%
4160% SigmoidalContrastImage() adjusts the contrast of an image with a non-linear
4161% sigmoidal contrast algorithm. Increase the contrast of the image using a
4162% sigmoidal transfer function without saturating highlights or shadows.
4163% Contrast indicates how much to increase the contrast (0 is none; 3 is
4164% typical; 20 is pushing it); mid-point indicates where midtones fall in the
4165% resultant image (0 is white; 50% is middle-gray; 100% is black). Set
4166% sharpen to MagickTrue to increase the image contrast otherwise the contrast
4167% is reduced.
4168%
4169% The format of the SigmoidalContrastImage method is:
4170%
4171% MagickBooleanType SigmoidalContrastImage(Image *image,
4172% const MagickBooleanType sharpen,const char *levels,
4173% ExceptionInfo *exception)
4174%
4175% A description of each parameter follows:
4176%
4177% o image: the image.
4178%
4179% o sharpen: Increase or decrease image contrast.
4180%
4181% o contrast: strength of the contrast, the larger the number the more
4182% 'threshold-like' it becomes.
4183%
4184% o midpoint: midpoint of the function as a color value 0 to QuantumRange.
4185%
4186% o exception: return any errors or warnings in this structure.
4187%
4188*/
4189
4190/*
4191 ImageMagick 6 has a version of this function which uses LUTs.
4192*/
4193
4194/*
4195 Sigmoidal function Sigmoidal with inflexion point moved to b and "slope
4196 constant" set to a.
4197
4198 The first version, based on the hyperbolic tangent tanh, when combined with
4199 the scaling step, is an exact arithmetic clone of the sigmoid function
4200 based on the logistic curve. The equivalence is based on the identity
4201
4202 1/(1+exp(-t)) = (1+tanh(t/2))/2
4203
4204 (http://de.wikipedia.org/wiki/Sigmoidfunktion) and the fact that the
4205 scaled sigmoidal derivation is invariant under affine transformations of
4206 the ordinate.
4207
4208 The tanh version is almost certainly more accurate and cheaper. The 0.5
4209 factor in the argument is to clone the legacy ImageMagick behavior. The
4210 reason for making the define depend on atanh even though it only uses tanh
4211 has to do with the construction of the inverse of the scaled sigmoidal.
4212*/
4213#if defined(MAGICKCORE_HAVE_ATANH)
4214#define Sigmoidal(a,b,x) ( tanh((0.5*(a))*((x)-(b))) )
4215#else
4216#define Sigmoidal(a,b,x) ( 1.0/(1.0+exp((a)*((b)-(x)))) )
4217#endif
4218/*
4219 Scaled sigmoidal function:
4220
4221 ( Sigmoidal(a,b,x) - Sigmoidal(a,b,0) ) /
4222 ( Sigmoidal(a,b,1) - Sigmoidal(a,b,0) )
4223
4224 See http://osdir.com/ml/video.image-magick.devel/2005-04/msg00006.html and
4225 http://www.cs.dartmouth.edu/farid/downloads/tutorials/fip.pdf. The limit
4226 of ScaledSigmoidal as a->0 is the identity, but a=0 gives a division by
4227 zero. This is fixed below by exiting immediately when contrast is small,
4228 leaving the image (or colormap) unmodified. This appears to be safe because
4229 the series expansion of the logistic sigmoidal function around x=b is
4230
4231 1/2-a*(b-x)/4+...
4232
4233 so that the key denominator s(1)-s(0) is about a/4 (a/2 with tanh).
4234*/
4235#define ScaledSigmoidal(a,b,x) ( \
4236 (Sigmoidal((a),(b),(x))-Sigmoidal((a),(b),0.0)) / \
4237 (Sigmoidal((a),(b),1.0)-Sigmoidal((a),(b),0.0)) )
4238/*
4239 Inverse of ScaledSigmoidal, used for +sigmoidal-contrast. Because b
4240 may be 0 or 1, the argument of the hyperbolic tangent (resp. logistic
4241 sigmoidal) may be outside of the interval (-1,1) (resp. (0,1)), even
4242 when creating a LUT from in gamut values, hence the branching. In
4243 addition, HDRI may have out of gamut values.
4244 InverseScaledSigmoidal is not a two-sided inverse of ScaledSigmoidal:
4245 It is only a right inverse. This is unavoidable.
4246*/
4247static inline double InverseScaledSigmoidal(const double a,const double b,
4248 const double x)
4249{
4250 const double sig0=Sigmoidal(a,b,0.0);
4251 const double sig1=Sigmoidal(a,b,1.0);
4252 const double argument=(sig1-sig0)*x+sig0;
4253 const double clamped=
4254 (
4255#if defined(MAGICKCORE_HAVE_ATANH)
4256 argument < -1+MagickEpsilon
4257 ?
4258 -1+MagickEpsilon
4259 :
4260 ( argument > 1-MagickEpsilon ? 1-MagickEpsilon : argument )
4261 );
4262 return(b+(2.0/a)*atanh(clamped));
4263#else
4264 argument < MagickEpsilon
4265 ?
4266 MagickEpsilon
4267 :
4268 ( argument > 1-MagickEpsilon ? 1-MagickEpsilon : argument )
4269 );
4270 return(b-log(1.0/clamped-1.0)/a);
4271#endif
4272}
4273
4274MagickExport MagickBooleanType SigmoidalContrastImage(Image *image,
4275 const MagickBooleanType sharpen,const double contrast,const double midpoint,
4276 ExceptionInfo *exception)
4277{
4278#define SigmoidalContrastImageTag "SigmoidalContrast/Image"
4279#define ScaledSig(x) (ClampToQuantum((double) QuantumRange* \
4280 ScaledSigmoidal(contrast,QuantumScale*midpoint,QuantumScale*((double) x))) )
4281#define InverseScaledSig(x) (ClampToQuantum((double) QuantumRange* \
4282 InverseScaledSigmoidal(contrast,QuantumScale*midpoint,QuantumScale* \
4283 ((double) x))) )
4284
4285 CacheView
4286 *image_view;
4287
4288 MagickBooleanType
4289 status;
4290
4291 MagickOffsetType
4292 progress;
4293
4294 ssize_t
4295 y;
4296
4297 /*
4298 Convenience macros.
4299 */
4300 assert(image != (Image *) NULL);
4301 assert(image->signature == MagickCoreSignature);
4302 if (IsEventLogging() != MagickFalse)
4303 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
4304 /*
4305 Side effect: may clamp values unless contrast<MagickEpsilon, in which
4306 case nothing is done.
4307 */
4308 if (contrast < MagickEpsilon)
4309 return(MagickTrue);
4310 /*
4311 Sigmoidal-contrast enhance colormap.
4312 */
4313 if (image->storage_class == PseudoClass)
4314 {
4315 ssize_t
4316 i;
4317
4318 if( sharpen != MagickFalse )
4319 for (i=0; i < (ssize_t) image->colors; i++)
4320 {
4321 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
4322 image->colormap[i].red=(MagickRealType) ScaledSig(
4323 image->colormap[i].red);
4324 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
4325 image->colormap[i].green=(MagickRealType) ScaledSig(
4326 image->colormap[i].green);
4327 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
4328 image->colormap[i].blue=(MagickRealType) ScaledSig(
4329 image->colormap[i].blue);
4330 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
4331 image->colormap[i].alpha=(MagickRealType) ScaledSig(
4332 image->colormap[i].alpha);
4333 }
4334 else
4335 for (i=0; i < (ssize_t) image->colors; i++)
4336 {
4337 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
4338 image->colormap[i].red=(MagickRealType) InverseScaledSig(
4339 image->colormap[i].red);
4340 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
4341 image->colormap[i].green=(MagickRealType) InverseScaledSig(
4342 image->colormap[i].green);
4343 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
4344 image->colormap[i].blue=(MagickRealType) InverseScaledSig(
4345 image->colormap[i].blue);
4346 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
4347 image->colormap[i].alpha=(MagickRealType) InverseScaledSig(
4348 image->colormap[i].alpha);
4349 }
4350 }
4351 /*
4352 Sigmoidal-contrast enhance image.
4353 */
4354 status=MagickTrue;
4355 progress=0;
4356 image_view=AcquireAuthenticCacheView(image,exception);
4357#if defined(MAGICKCORE_OPENMP_SUPPORT)
4358 #pragma omp parallel for schedule(static) shared(progress,status) \
4359 magick_number_threads(image,image,image->rows,1)
4360#endif
4361 for (y=0; y < (ssize_t) image->rows; y++)
4362 {
4363 Quantum
4364 *magick_restrict q;
4365
4366 ssize_t
4367 x;
4368
4369 if (status == MagickFalse)
4370 continue;
4371 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
4372 if (q == (Quantum *) NULL)
4373 {
4374 status=MagickFalse;
4375 continue;
4376 }
4377 for (x=0; x < (ssize_t) image->columns; x++)
4378 {
4379 ssize_t
4380 i;
4381
4382 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
4383 {
4384 PixelChannel channel = GetPixelChannelChannel(image,i);
4385 PixelTrait traits = GetPixelChannelTraits(image,channel);
4386 if ((traits & UpdatePixelTrait) == 0)
4387 continue;
4388 if( sharpen != MagickFalse )
4389 q[i]=ScaledSig(q[i]);
4390 else
4391 q[i]=InverseScaledSig(q[i]);
4392 }
4393 q+=(ptrdiff_t) GetPixelChannels(image);
4394 }
4395 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
4396 status=MagickFalse;
4397 if (image->progress_monitor != (MagickProgressMonitor) NULL)
4398 {
4399 MagickBooleanType
4400 proceed;
4401
4402#if defined(MAGICKCORE_OPENMP_SUPPORT)
4403 #pragma omp atomic
4404#endif
4405 progress++;
4406 proceed=SetImageProgress(image,SigmoidalContrastImageTag,progress,
4407 image->rows);
4408 if (proceed == MagickFalse)
4409 status=MagickFalse;
4410 }
4411 }
4412 image_view=DestroyCacheView(image_view);
4413 return(status);
4414}
4415
4416/*
4417%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4418% %
4419% %
4420% %
4421% W h i t e B a l a n c e I m a g e %
4422% %
4423% %
4424% %
4425%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4426%
4427% WhiteBalanceImage() applies white balancing to an image according to a
4428% grayworld assumption in the LAB colorspace.
4429%
4430% The format of the WhiteBalanceImage method is:
4431%
4432% MagickBooleanType WhiteBalanceImage(Image *image,
4433% ExceptionInfo *exception)
4434%
4435% A description of each parameter follows:
4436%
4437% o image: The image to auto-level
4438%
4439% o exception: return any errors or warnings in this structure.
4440%
4441*/
4442MagickExport MagickBooleanType WhiteBalanceImage(Image *image,
4443 ExceptionInfo *exception)
4444{
4445#define WhiteBalanceImageTag "WhiteBalance/Image"
4446
4447 CacheView
4448 *image_view;
4449
4450 const char
4451 *artifact;
4452
4453 double
4454 a_mean,
4455 b_mean;
4456
4457 MagickOffsetType
4458 progress;
4459
4460 MagickStatusType
4461 status;
4462
4463 ssize_t
4464 y;
4465
4466 /*
4467 White balance image.
4468 */
4469 assert(image != (Image *) NULL);
4470 assert(image->signature == MagickCoreSignature);
4471 if (IsEventLogging() != MagickFalse)
4472 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
4473 if (SetImageStorageClass(image,DirectClass,exception) == MagickFalse)
4474 return(MagickFalse);
4475 status=TransformImageColorspace(image,LabColorspace,exception);
4476 a_mean=0.0;
4477 b_mean=0.0;
4478 image_view=AcquireAuthenticCacheView(image,exception);
4479 for (y=0; y < (ssize_t) image->rows; y++)
4480 {
4481 const Quantum
4482 *magick_restrict p;
4483
4484 ssize_t
4485 x;
4486
4487 if (status == MagickFalse)
4488 continue;
4489 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
4490 if (p == (Quantum *) NULL)
4491 {
4492 status=MagickFalse;
4493 continue;
4494 }
4495 for (x=0; x < (ssize_t) image->columns; x++)
4496 {
4497 a_mean+=QuantumScale*(double) GetPixela(image,p)-0.5;
4498 b_mean+=QuantumScale*(double) GetPixelb(image,p)-0.5;
4499 p+=(ptrdiff_t) GetPixelChannels(image);
4500 }
4501 }
4502 a_mean/=((double) image->columns*image->rows);
4503 b_mean/=((double) image->columns*image->rows);
4504 progress=0;
4505#if defined(MAGICKCORE_OPENMP_SUPPORT)
4506 #pragma omp parallel for schedule(static) shared(progress,status) \
4507 magick_number_threads(image,image,image->rows,1)
4508#endif
4509 for (y=0; y < (ssize_t) image->rows; y++)
4510 {
4511 Quantum
4512 *magick_restrict q;
4513
4514 ssize_t
4515 x;
4516
4517 if (status == MagickFalse)
4518 continue;
4519 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
4520 if (q == (Quantum *) NULL)
4521 {
4522 status=MagickFalse;
4523 continue;
4524 }
4525 for (x=0; x < (ssize_t) image->columns; x++)
4526 {
4527 double
4528 a,
4529 b;
4530
4531 /*
4532 Scale the chroma distance shifted according to amount of luminance.
4533 */
4534 a=(double) GetPixela(image,q)-1.1*(double) GetPixelL(image,q)*a_mean;
4535 b=(double) GetPixelb(image,q)-1.1*(double) GetPixelL(image,q)*b_mean;
4536 SetPixela(image,ClampToQuantum(a),q);
4537 SetPixelb(image,ClampToQuantum(b),q);
4538 q+=(ptrdiff_t) GetPixelChannels(image);
4539 }
4540 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
4541 status=MagickFalse;
4542 if (image->progress_monitor != (MagickProgressMonitor) NULL)
4543 {
4544 MagickBooleanType
4545 proceed;
4546
4547#if defined(MAGICKCORE_OPENMP_SUPPORT)
4548 #pragma omp atomic
4549#endif
4550 progress++;
4551 proceed=SetImageProgress(image,WhiteBalanceImageTag,progress,image->rows);
4552 if (proceed == MagickFalse)
4553 status=MagickFalse;
4554 }
4555 }
4556 image_view=DestroyCacheView(image_view);
4557 artifact=GetImageArtifact(image,"white-balance:vibrance");
4558 if (artifact != (const char *) NULL)
4559 {
4560 ChannelType
4561 channel_mask;
4562
4563 double
4564 black_point = 0.0;
4565
4566 GeometryInfo
4567 geometry_info;
4568
4569 MagickStatusType
4570 flags;
4571
4572 /*
4573 Level the a & b channels.
4574 */
4575 flags=ParseGeometry(artifact,&geometry_info);
4576 if ((flags & RhoValue) != 0)
4577 black_point=geometry_info.rho;
4578 if ((flags & PercentValue) != 0)
4579 black_point*=((double) QuantumRange/100.0);
4580 channel_mask=SetImageChannelMask(image,(ChannelType) (aChannel |
4581 bChannel));
4582 status&=(MagickStatusType) LevelImage(image,black_point,(double)
4583 QuantumRange-black_point,1.0,exception);
4584 (void) SetImageChannelMask(image,channel_mask);
4585 }
4586 status&=(MagickStatusType) TransformImageColorspace(image,sRGBColorspace,
4587 exception);
4588 return(status != 0 ? MagickTrue : MagickFalse);
4589}