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