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