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