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