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