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