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