1 /* 2 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 3 % % 4 % % 5 % % 6 % PPPP AAA IIIII N N TTTTT % 7 % P P A A I NN N T % 8 % PPPP AAAAA I N N N T % 9 % P A A I N NN T % 10 % P A A IIIII N N T % 11 % % 12 % % 13 % Methods to Paint on an Image % 14 % % 15 % Software Design % 16 % Cristy % 17 % July 1998 % 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 /* 41 Include declarations. 42 */ 43 #include "MagickCore/studio.h" 44 #include "MagickCore/artifact.h" 45 #include "MagickCore/channel.h" 46 #include "MagickCore/color.h" 47 #include "MagickCore/color-private.h" 48 #include "MagickCore/colorspace-private.h" 49 #include "MagickCore/composite.h" 50 #include "MagickCore/composite-private.h" 51 #include "MagickCore/draw.h" 52 #include "MagickCore/draw-private.h" 53 #include "MagickCore/exception.h" 54 #include "MagickCore/exception-private.h" 55 #include "MagickCore/gem.h" 56 #include "MagickCore/gem-private.h" 57 #include "MagickCore/monitor.h" 58 #include "MagickCore/monitor-private.h" 59 #include "MagickCore/option.h" 60 #include "MagickCore/paint.h" 61 #include "MagickCore/pixel-accessor.h" 62 #include "MagickCore/resource_.h" 63 #include "MagickCore/statistic.h" 64 #include "MagickCore/string_.h" 65 #include "MagickCore/string-private.h" 66 #include "MagickCore/thread-private.h" 67 68 /* 70 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 71 % % 72 % % 73 % % 74 % F l o o d f i l l P a i n t I m a g e % 75 % % 76 % % 77 % % 78 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 79 % 80 % FloodfillPaintImage() changes the color value of any pixel that matches 81 % target and is an immediate neighbor. If the method FillToBorderMethod is 82 % specified, the color value is changed for any neighbor pixel that does not 83 % match the bordercolor member of image. 84 % 85 % By default target must match a particular pixel color exactly. However, 86 % in many cases two colors may differ by a small amount. The fuzz member of 87 % image defines how much tolerance is acceptable to consider two colors as 88 % the same. For example, set fuzz to 10 and the color red at intensities of 89 % 100 and 102 respectively are now interpreted as the same color for the 90 % purposes of the floodfill. 91 % 92 % The format of the FloodfillPaintImage method is: 93 % 94 % MagickBooleanType FloodfillPaintImage(Image *image, 95 % const DrawInfo *draw_info,const PixelInfo target, 96 % const ssize_t x_offset,const ssize_t y_offset, 97 % const MagickBooleanType invert,ExceptionInfo *exception) 98 % 99 % A description of each parameter follows: 100 % 101 % o image: the image. 102 % 103 % o draw_info: the draw info. 104 % 105 % o target: the RGB value of the target color. 106 % 107 % o x_offset,y_offset: the starting location of the operation. 108 % 109 % o invert: paint any pixel that does not match the target color. 110 % 111 % o exception: return any errors or warnings in this structure. 112 % 113 */ 114 MagickExport MagickBooleanType FloodfillPaintImage(Image *image, 115 const DrawInfo *draw_info,const PixelInfo *target,const ssize_t x_offset, 116 const ssize_t y_offset,const MagickBooleanType invert, 117 ExceptionInfo *exception) 118 { 119 #define MaxStacksize 524288UL 120 #define PushSegmentStack(up,left,right,delta) \ 121 { \ 122 if (s >= (segment_stack+MaxStacksize)) \ 123 ThrowBinaryException(DrawError,"SegmentStackOverflow",image->filename) \ 124 else \ 125 { \ 126 if ((((up)+(delta)) >= 0) && (((up)+(delta)) < (ssize_t) image->rows)) \ 127 { \ 128 s->x1=(double) (left); \ 129 s->y1=(double) (up); \ 130 s->x2=(double) (right); \ 131 s->y2=(double) (delta); \ 132 s++; \ 133 } \ 134 } \ 135 } 136 137 CacheView 138 *floodplane_view, 139 *image_view; 140 141 Image 142 *floodplane_image; 143 144 MagickBooleanType 145 skip, 146 status; 147 148 MemoryInfo 149 *segment_info; 150 151 PixelInfo 152 fill_color, 153 pixel; 154 155 register SegmentInfo 156 *s; 157 158 SegmentInfo 159 *segment_stack; 160 161 ssize_t 162 offset, 163 start, 164 x1, 165 x2, 166 y; 167 168 /* 169 Check boundary conditions. 170 */ 171 assert(image != (Image *) NULL); 172 assert(image->signature == MagickCoreSignature); 173 if (image->debug != MagickFalse) 174 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename); 175 assert(draw_info != (DrawInfo *) NULL); 176 assert(draw_info->signature == MagickCoreSignature); 177 if ((x_offset < 0) || (x_offset >= (ssize_t) image->columns)) 178 return(MagickFalse); 179 if ((y_offset < 0) || (y_offset >= (ssize_t) image->rows)) 180 return(MagickFalse); 181 if (SetImageStorageClass(image,DirectClass,exception) == MagickFalse) 182 return(MagickFalse); 183 if (IsGrayColorspace(image->colorspace) != MagickFalse) 184 (void) SetImageColorspace(image,sRGBColorspace,exception); 185 if ((image->alpha_trait == UndefinedPixelTrait) && 186 (draw_info->fill.alpha_trait != UndefinedPixelTrait)) 187 (void) SetImageAlpha(image,OpaqueAlpha,exception); 188 /* 189 Set floodfill state. 190 */ 191 floodplane_image=CloneImage(image,0,0,MagickTrue, 192 exception); 193 if (floodplane_image == (Image *) NULL) 194 return(MagickFalse); 195 floodplane_image->alpha_trait=UndefinedPixelTrait; 196 floodplane_image->colorspace=GRAYColorspace; 197 (void) QueryColorCompliance("#000",AllCompliance, 198 &floodplane_image->background_color,exception); 199 (void) SetImageBackgroundColor(floodplane_image,exception); 200 segment_info=AcquireVirtualMemory(MaxStacksize,sizeof(*segment_stack)); 201 if (segment_info == (MemoryInfo *) NULL) 202 { 203 floodplane_image=DestroyImage(floodplane_image); 204 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed", 205 image->filename); 206 } 207 segment_stack=(SegmentInfo *) GetVirtualMemoryBlob(segment_info); 208 /* 209 Push initial segment on stack. 210 */ 211 status=MagickTrue; 212 start=0; 213 s=segment_stack; 214 PushSegmentStack(y_offset,x_offset,x_offset,1); 215 PushSegmentStack(y_offset+1,x_offset,x_offset,-1); 216 GetPixelInfo(image,&pixel); 217 image_view=AcquireVirtualCacheView(image,exception); 218 floodplane_view=AcquireAuthenticCacheView(floodplane_image,exception); 219 while (s > segment_stack) 220 { 221 register const Quantum 222 *magick_restrict p; 223 224 register Quantum 225 *magick_restrict q; 226 227 register ssize_t 228 x; 229 230 /* 231 Pop segment off stack. 232 */ 233 s--; 234 x1=(ssize_t) s->x1; 235 x2=(ssize_t) s->x2; 236 offset=(ssize_t) s->y2; 237 y=(ssize_t) s->y1+offset; 238 /* 239 Recolor neighboring pixels. 240 */ 241 p=GetCacheViewVirtualPixels(image_view,0,y,(size_t) (x1+1),1,exception); 242 q=GetCacheViewAuthenticPixels(floodplane_view,0,y,(size_t) (x1+1),1, 243 exception); 244 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL)) 245 break; 246 p+=x1*GetPixelChannels(image); 247 q+=x1*GetPixelChannels(floodplane_image); 248 for (x=x1; x >= 0; x--) 249 { 250 if (GetPixelGray(floodplane_image,q) != 0) 251 break; 252 GetPixelInfoPixel(image,p,&pixel); 253 if (IsFuzzyEquivalencePixelInfo(&pixel,target) == invert) 254 break; 255 SetPixelGray(floodplane_image,QuantumRange,q); 256 p-=GetPixelChannels(image); 257 q-=GetPixelChannels(floodplane_image); 258 } 259 if (SyncCacheViewAuthenticPixels(floodplane_view,exception) == MagickFalse) 260 break; 261 skip=x >= x1 ? MagickTrue : MagickFalse; 262 if (skip == MagickFalse) 263 { 264 start=x+1; 265 if (start < x1) 266 PushSegmentStack(y,start,x1-1,-offset); 267 x=x1+1; 268 } 269 do 270 { 271 if (skip == MagickFalse) 272 { 273 if (x < (ssize_t) image->columns) 274 { 275 p=GetCacheViewVirtualPixels(image_view,x,y,image->columns-x,1, 276 exception); 277 q=GetCacheViewAuthenticPixels(floodplane_view,x,y,image->columns- 278 x,1,exception); 279 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL)) 280 break; 281 for ( ; x < (ssize_t) image->columns; x++) 282 { 283 if (GetPixelGray(floodplane_image,q) != 0) 284 break; 285 GetPixelInfoPixel(image,p,&pixel); 286 if (IsFuzzyEquivalencePixelInfo(&pixel,target) == invert) 287 break; 288 SetPixelGray(floodplane_image,QuantumRange,q); 289 p+=GetPixelChannels(image); 290 q+=GetPixelChannels(floodplane_image); 291 } 292 status=SyncCacheViewAuthenticPixels(floodplane_view,exception); 293 if (status == MagickFalse) 294 break; 295 } 296 PushSegmentStack(y,start,x-1,offset); 297 if (x > (x2+1)) 298 PushSegmentStack(y,x2+1,x-1,-offset); 299 } 300 skip=MagickFalse; 301 x++; 302 if (x <= x2) 303 { 304 p=GetCacheViewVirtualPixels(image_view,x,y,(size_t) (x2-x+1),1, 305 exception); 306 q=GetCacheViewAuthenticPixels(floodplane_view,x,y,(size_t) (x2-x+1),1, 307 exception); 308 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL)) 309 break; 310 for ( ; x <= x2; x++) 311 { 312 if (GetPixelGray(floodplane_image,q) != 0) 313 break; 314 GetPixelInfoPixel(image,p,&pixel); 315 if (IsFuzzyEquivalencePixelInfo(&pixel,target) != invert) 316 break; 317 p+=GetPixelChannels(image); 318 q+=GetPixelChannels(floodplane_image); 319 } 320 } 321 start=x; 322 } while (x <= x2); 323 } 324 status=MagickTrue; 325 for (y=0; y < (ssize_t) image->rows; y++) 326 { 327 register const Quantum 328 *magick_restrict p; 329 330 register Quantum 331 *magick_restrict q; 332 333 register ssize_t 334 x; 335 336 /* 337 Tile fill color onto floodplane. 338 */ 339 if (status == MagickFalse) 340 continue; 341 p=GetCacheViewVirtualPixels(floodplane_view,0,y,image->columns,1,exception); 342 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception); 343 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL)) 344 { 345 status=MagickFalse; 346 continue; 347 } 348 for (x=0; x < (ssize_t) image->columns; x++) 349 { 350 if (GetPixelGray(floodplane_image,p) != 0) 351 { 352 GetFillColor(draw_info,x,y,&fill_color,exception); 353 SetPixelViaPixelInfo(image,&fill_color,q); 354 } 355 p+=GetPixelChannels(floodplane_image); 356 q+=GetPixelChannels(image); 357 } 358 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse) 359 status=MagickFalse; 360 } 361 floodplane_view=DestroyCacheView(floodplane_view); 362 image_view=DestroyCacheView(image_view); 363 segment_info=RelinquishVirtualMemory(segment_info); 364 floodplane_image=DestroyImage(floodplane_image); 365 return(status); 366 } 367 368 /* 370 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 371 % % 372 % % 373 % % 374 + G r a d i e n t I m a g e % 375 % % 376 % % 377 % % 378 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 379 % 380 % GradientImage() applies a continuously smooth color transitions along a 381 % vector from one color to another. 382 % 383 % Note, the interface of this method will change in the future to support 384 % more than one transistion. 385 % 386 % The format of the GradientImage method is: 387 % 388 % MagickBooleanType GradientImage(Image *image,const GradientType type, 389 % const SpreadMethod method,const PixelInfo *start_color, 390 % const PixelInfo *stop_color,ExceptionInfo *exception) 391 % 392 % A description of each parameter follows: 393 % 394 % o image: the image. 395 % 396 % o type: the gradient type: linear or radial. 397 % 398 % o spread: the gradient spread meathod: pad, reflect, or repeat. 399 % 400 % o start_color: the start color. 401 % 402 % o stop_color: the stop color. 403 % 404 % o exception: return any errors or warnings in this structure. 405 % 406 */ 407 MagickExport MagickBooleanType GradientImage(Image *image, 408 const GradientType type,const SpreadMethod method,const StopInfo *stops, 409 const size_t number_stops,ExceptionInfo *exception) 410 { 411 const char 412 *artifact; 413 414 DrawInfo 415 *draw_info; 416 417 GradientInfo 418 *gradient; 419 420 MagickBooleanType 421 status; 422 423 /* 424 Set gradient start-stop end points. 425 */ 426 assert(image != (const Image *) NULL); 427 assert(image->signature == MagickCoreSignature); 428 if (image->debug != MagickFalse) 429 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename); 430 assert(stops != (const StopInfo *) NULL); 431 assert(number_stops > 0); 432 draw_info=AcquireDrawInfo(); 433 gradient=(&draw_info->gradient); 434 gradient->type=type; 435 gradient->bounding_box.width=image->columns; 436 gradient->bounding_box.height=image->rows; 437 artifact=GetImageArtifact(image,"gradient:bounding-box"); 438 if (artifact != (const char *) NULL) 439 (void) ParseAbsoluteGeometry(artifact,&gradient->bounding_box); 440 gradient->gradient_vector.x2=(double) image->columns-1; 441 gradient->gradient_vector.y2=(double) image->rows-1; 442 artifact=GetImageArtifact(image,"gradient:direction"); 443 if (artifact != (const char *) NULL) 444 { 445 GravityType 446 direction; 447 448 direction=(GravityType) ParseCommandOption(MagickGravityOptions, 449 MagickFalse,artifact); 450 switch (direction) 451 { 452 case NorthWestGravity: 453 { 454 gradient->gradient_vector.x1=(double) image->columns-1; 455 gradient->gradient_vector.y1=(double) image->rows-1; 456 gradient->gradient_vector.x2=0.0; 457 gradient->gradient_vector.y2=0.0; 458 break; 459 } 460 case NorthGravity: 461 { 462 gradient->gradient_vector.x1=0.0; 463 gradient->gradient_vector.y1=(double) image->rows-1; 464 gradient->gradient_vector.x2=0.0; 465 gradient->gradient_vector.y2=0.0; 466 break; 467 } 468 case NorthEastGravity: 469 { 470 gradient->gradient_vector.x1=0.0; 471 gradient->gradient_vector.y1=(double) image->rows-1; 472 gradient->gradient_vector.x2=(double) image->columns-1; 473 gradient->gradient_vector.y2=0.0; 474 break; 475 } 476 case WestGravity: 477 { 478 gradient->gradient_vector.x1=(double) image->columns-1; 479 gradient->gradient_vector.y1=0.0; 480 gradient->gradient_vector.x2=0.0; 481 gradient->gradient_vector.y2=0.0; 482 break; 483 } 484 case EastGravity: 485 { 486 gradient->gradient_vector.x1=0.0; 487 gradient->gradient_vector.y1=0.0; 488 gradient->gradient_vector.x2=(double) image->columns-1; 489 gradient->gradient_vector.y2=0.0; 490 break; 491 } 492 case SouthWestGravity: 493 { 494 gradient->gradient_vector.x1=(double) image->columns-1; 495 gradient->gradient_vector.y1=0.0; 496 gradient->gradient_vector.x2=0.0; 497 gradient->gradient_vector.y2=(double) image->rows-1; 498 break; 499 } 500 case SouthGravity: 501 { 502 gradient->gradient_vector.x1=0.0; 503 gradient->gradient_vector.y1=0.0; 504 gradient->gradient_vector.x2=0.0; 505 gradient->gradient_vector.y2=(double) image->columns-1; 506 break; 507 } 508 case SouthEastGravity: 509 { 510 gradient->gradient_vector.x1=0.0; 511 gradient->gradient_vector.y1=0.0; 512 gradient->gradient_vector.x2=(double) image->columns-1; 513 gradient->gradient_vector.y2=(double) image->rows-1; 514 break; 515 } 516 default: 517 break; 518 } 519 } 520 artifact=GetImageArtifact(image,"gradient:angle"); 521 if (artifact != (const char *) NULL) 522 gradient->angle=StringToDouble(artifact,(char **) NULL); 523 artifact=GetImageArtifact(image,"gradient:vector"); 524 if (artifact != (const char *) NULL) 525 (void) sscanf(artifact,"%lf%*[ ,]%lf%*[ ,]%lf%*[ ,]%lf", 526 &gradient->gradient_vector.x1,&gradient->gradient_vector.y1, 527 &gradient->gradient_vector.x2,&gradient->gradient_vector.y2); 528 if ((GetImageArtifact(image,"gradient:angle") == (const char *) NULL) && 529 (GetImageArtifact(image,"gradient:direction") == (const char *) NULL) && 530 (GetImageArtifact(image,"gradient:extent") == (const char *) NULL) && 531 (GetImageArtifact(image,"gradient:vector") == (const char *) NULL)) 532 if ((type == LinearGradient) && (gradient->gradient_vector.y2 != 0.0)) 533 gradient->gradient_vector.x2=0.0; 534 gradient->center.x=(double) gradient->gradient_vector.x2/2.0; 535 gradient->center.y=(double) gradient->gradient_vector.y2/2.0; 536 artifact=GetImageArtifact(image,"gradient:center"); 537 if (artifact != (const char *) NULL) 538 (void) sscanf(artifact,"%lf%*[ ,]%lf",&gradient->center.x, 539 &gradient->center.y); 540 artifact=GetImageArtifact(image,"gradient:angle"); 541 if ((type == LinearGradient) && (artifact != (const char *) NULL)) 542 { 543 double 544 sine, 545 cosine, 546 distance; 547 548 /* 549 Reference https://drafts.csswg.org/css-images-3/#linear-gradients. 550 */ 551 sine=sin((double) DegreesToRadians(gradient->angle-90.0)); 552 cosine=cos((double) DegreesToRadians(gradient->angle-90.0)); 553 distance=fabs((double) (image->columns-1.0)*cosine)+ 554 fabs((double) (image->rows-1.0)*sine); 555 gradient->gradient_vector.x1=0.5*((image->columns-1.0)-distance*cosine); 556 gradient->gradient_vector.y1=0.5*((image->rows-1.0)-distance*sine); 557 gradient->gradient_vector.x2=0.5*((image->columns-1.0)+distance*cosine); 558 gradient->gradient_vector.y2=0.5*((image->rows-1.0)+distance*sine); 559 } 560 gradient->radii.x=(double) MagickMax((image->columns-1.0),(image->rows-1.0))/ 561 2.0; 562 gradient->radii.y=gradient->radii.x; 563 artifact=GetImageArtifact(image,"gradient:extent"); 564 if (artifact != (const char *) NULL) 565 { 566 if (LocaleCompare(artifact,"Circle") == 0) 567 { 568 gradient->radii.x=(double) MagickMax((image->columns-1.0), 569 (image->rows-1.0))/2.0; 570 gradient->radii.y=gradient->radii.x; 571 } 572 if (LocaleCompare(artifact,"Diagonal") == 0) 573 { 574 gradient->radii.x=(double) (sqrt((double) (image->columns-1.0)* 575 (image->columns-1.0)+(image->rows-1.0)*(image->rows-1.0)))/2.0; 576 gradient->radii.y=gradient->radii.x; 577 } 578 if (LocaleCompare(artifact,"Ellipse") == 0) 579 { 580 gradient->radii.x=(double) (image->columns-1.0)/2.0; 581 gradient->radii.y=(double) (image->rows-1.0)/2.0; 582 } 583 if (LocaleCompare(artifact,"Maximum") == 0) 584 { 585 gradient->radii.x=(double) MagickMax((image->columns-1.0), 586 (image->rows-1.0))/2.0; 587 gradient->radii.y=gradient->radii.x; 588 } 589 if (LocaleCompare(artifact,"Minimum") == 0) 590 { 591 gradient->radii.x=(double) (MagickMin((image->columns-1.0), 592 (image->rows-1.0)))/2.0; 593 gradient->radii.y=gradient->radii.x; 594 } 595 } 596 artifact=GetImageArtifact(image,"gradient:radii"); 597 if (artifact != (const char *) NULL) 598 (void) sscanf(artifact,"%lf%*[ ,]%lf",&gradient->radii.x, 599 &gradient->radii.y); 600 gradient->radius=MagickMax(gradient->radii.x,gradient->radii.y); 601 gradient->spread=method; 602 /* 603 Define the gradient to fill between the stops. 604 */ 605 gradient->number_stops=number_stops; 606 gradient->stops=(StopInfo *) AcquireQuantumMemory(gradient->number_stops, 607 sizeof(*gradient->stops)); 608 if (gradient->stops == (StopInfo *) NULL) 609 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed", 610 image->filename); 611 (void) memcpy(gradient->stops,stops,(size_t) number_stops* 612 sizeof(*stops)); 613 /* 614 Draw a gradient on the image. 615 */ 616 status=DrawGradientImage(image,draw_info,exception); 617 draw_info=DestroyDrawInfo(draw_info); 618 return(status); 619 } 620 621 /* 623 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 624 % % 625 % % 626 % % 627 % O i l P a i n t I m a g e % 628 % % 629 % % 630 % % 631 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 632 % 633 % OilPaintImage() applies a special effect filter that simulates an oil 634 % painting. Each pixel is replaced by the most frequent color occurring 635 % in a circular region defined by radius. 636 % 637 % The format of the OilPaintImage method is: 638 % 639 % Image *OilPaintImage(const Image *image,const double radius, 640 % const double sigma,ExceptionInfo *exception) 641 % 642 % A description of each parameter follows: 643 % 644 % o image: the image. 645 % 646 % o radius: the radius of the circular neighborhood. 647 % 648 % o sigma: the standard deviation of the Gaussian, in pixels. 649 % 650 % o exception: return any errors or warnings in this structure. 651 % 652 */ 653 654 static size_t **DestroyHistogramThreadSet(size_t **histogram) 655 { 656 register ssize_t 657 i; 658 659 assert(histogram != (size_t **) NULL); 660 for (i=0; i < (ssize_t) GetMagickResourceLimit(ThreadResource); i++) 661 if (histogram[i] != (size_t *) NULL) 662 histogram[i]=(size_t *) RelinquishMagickMemory(histogram[i]); 663 histogram=(size_t **) RelinquishMagickMemory(histogram); 664 return(histogram); 665 } 666 667 static size_t **AcquireHistogramThreadSet(const size_t count) 668 { 669 register ssize_t 670 i; 671 672 size_t 673 **histogram, 674 number_threads; 675 676 number_threads=(size_t) GetMagickResourceLimit(ThreadResource); 677 histogram=(size_t **) AcquireQuantumMemory(number_threads,sizeof(*histogram)); 678 if (histogram == (size_t **) NULL) 679 return((size_t **) NULL); 680 (void) memset(histogram,0,number_threads*sizeof(*histogram)); 681 for (i=0; i < (ssize_t) number_threads; i++) 682 { 683 histogram[i]=(size_t *) AcquireQuantumMemory(count,sizeof(**histogram)); 684 if (histogram[i] == (size_t *) NULL) 685 return(DestroyHistogramThreadSet(histogram)); 686 } 687 return(histogram); 688 } 689 690 MagickExport Image *OilPaintImage(const Image *image,const double radius, 691 const double sigma,ExceptionInfo *exception) 692 { 693 #define NumberPaintBins 256 694 #define OilPaintImageTag "OilPaint/Image" 695 696 CacheView 697 *image_view, 698 *paint_view; 699 700 Image 701 *linear_image, 702 *paint_image; 703 704 MagickBooleanType 705 status; 706 707 MagickOffsetType 708 progress; 709 710 size_t 711 **histograms, 712 width; 713 714 ssize_t 715 center, 716 y; 717 718 /* 719 Initialize painted image attributes. 720 */ 721 assert(image != (const Image *) NULL); 722 assert(image->signature == MagickCoreSignature); 723 if (image->debug != MagickFalse) 724 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename); 725 assert(exception != (ExceptionInfo *) NULL); 726 assert(exception->signature == MagickCoreSignature); 727 width=GetOptimalKernelWidth2D(radius,sigma); 728 linear_image=CloneImage(image,0,0,MagickTrue,exception); 729 paint_image=CloneImage(image,0,0,MagickTrue,exception); 730 if ((linear_image == (Image *) NULL) || (paint_image == (Image *) NULL)) 731 { 732 if (linear_image != (Image *) NULL) 733 linear_image=DestroyImage(linear_image); 734 if (paint_image != (Image *) NULL) 735 linear_image=DestroyImage(paint_image); 736 return((Image *) NULL); 737 } 738 if (SetImageStorageClass(paint_image,DirectClass,exception) == MagickFalse) 739 { 740 linear_image=DestroyImage(linear_image); 741 paint_image=DestroyImage(paint_image); 742 return((Image *) NULL); 743 } 744 histograms=AcquireHistogramThreadSet(NumberPaintBins); 745 if (histograms == (size_t **) NULL) 746 { 747 linear_image=DestroyImage(linear_image); 748 paint_image=DestroyImage(paint_image); 749 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed"); 750 } 751 /* 752 Oil paint image. 753 */ 754 status=MagickTrue; 755 progress=0; 756 center=(ssize_t) GetPixelChannels(linear_image)*(linear_image->columns+width)* 757 (width/2L)+GetPixelChannels(linear_image)*(width/2L); 758 image_view=AcquireVirtualCacheView(linear_image,exception); 759 paint_view=AcquireAuthenticCacheView(paint_image,exception); 760 #if defined(MAGICKCORE_OPENMP_SUPPORT) 761 #pragma omp parallel for schedule(static) shared(progress,status) \ 762 magick_number_threads(linear_image,paint_image,linear_image->rows,1) 763 #endif 764 for (y=0; y < (ssize_t) linear_image->rows; y++) 765 { 766 register const Quantum 767 *magick_restrict p; 768 769 register Quantum 770 *magick_restrict q; 771 772 register size_t 773 *histogram; 774 775 register ssize_t 776 x; 777 778 if (status == MagickFalse) 779 continue; 780 p=GetCacheViewVirtualPixels(image_view,-((ssize_t) width/2L),y-(ssize_t) 781 (width/2L),linear_image->columns+width,width,exception); 782 q=QueueCacheViewAuthenticPixels(paint_view,0,y,paint_image->columns,1, 783 exception); 784 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL)) 785 { 786 status=MagickFalse; 787 continue; 788 } 789 histogram=histograms[GetOpenMPThreadId()]; 790 for (x=0; x < (ssize_t) linear_image->columns; x++) 791 { 792 register ssize_t 793 i, 794 u; 795 796 size_t 797 count; 798 799 ssize_t 800 j, 801 k, 802 n, 803 v; 804 805 /* 806 Assign most frequent color. 807 */ 808 k=0; 809 j=0; 810 count=0; 811 (void) memset(histogram,0,NumberPaintBins* sizeof(*histogram)); 812 for (v=0; v < (ssize_t) width; v++) 813 { 814 for (u=0; u < (ssize_t) width; u++) 815 { 816 n=(ssize_t) ScaleQuantumToChar(ClampToQuantum(GetPixelIntensity( 817 linear_image,p+GetPixelChannels(linear_image)*(u+k)))); 818 histogram[n]++; 819 if (histogram[n] > count) 820 { 821 j=k+u; 822 count=histogram[n]; 823 } 824 } 825 k+=(ssize_t) (linear_image->columns+width); 826 } 827 for (i=0; i < (ssize_t) GetPixelChannels(linear_image); i++) 828 { 829 PixelChannel channel = GetPixelChannelChannel(linear_image,i); 830 PixelTrait traits = GetPixelChannelTraits(linear_image,channel); 831 PixelTrait paint_traits=GetPixelChannelTraits(paint_image,channel); 832 if ((traits == UndefinedPixelTrait) || 833 (paint_traits == UndefinedPixelTrait)) 834 continue; 835 if ((paint_traits & CopyPixelTrait) != 0) 836 { 837 SetPixelChannel(paint_image,channel,p[center+i],q); 838 continue; 839 } 840 SetPixelChannel(paint_image,channel,p[j*GetPixelChannels(linear_image)+ 841 i],q); 842 } 843 p+=GetPixelChannels(linear_image); 844 q+=GetPixelChannels(paint_image); 845 } 846 if (SyncCacheViewAuthenticPixels(paint_view,exception) == MagickFalse) 847 status=MagickFalse; 848 if (linear_image->progress_monitor != (MagickProgressMonitor) NULL) 849 { 850 MagickBooleanType 851 proceed; 852 853 #if defined(MAGICKCORE_OPENMP_SUPPORT) 854 #pragma omp atomic 855 #endif 856 progress++; 857 proceed=SetImageProgress(linear_image,OilPaintImageTag,progress, 858 linear_image->rows); 859 if (proceed == MagickFalse) 860 status=MagickFalse; 861 } 862 } 863 paint_view=DestroyCacheView(paint_view); 864 image_view=DestroyCacheView(image_view); 865 histograms=DestroyHistogramThreadSet(histograms); 866 linear_image=DestroyImage(linear_image); 867 if (status == MagickFalse) 868 paint_image=DestroyImage(paint_image); 869 return(paint_image); 870 } 871 872 /* 874 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 875 % % 876 % % 877 % % 878 % O p a q u e P a i n t I m a g e % 879 % % 880 % % 881 % % 882 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 883 % 884 % OpaquePaintImage() changes any pixel that matches color with the color 885 % defined by fill argument. 886 % 887 % By default color must match a particular pixel color exactly. However, in 888 % many cases two colors may differ by a small amount. Fuzz defines how much 889 % tolerance is acceptable to consider two colors as the same. For example, 890 % set fuzz to 10 and the color red at intensities of 100 and 102 respectively 891 % are now interpreted as the same color. 892 % 893 % The format of the OpaquePaintImage method is: 894 % 895 % MagickBooleanType OpaquePaintImage(Image *image,const PixelInfo *target, 896 % const PixelInfo *fill,const MagickBooleanType invert, 897 % ExceptionInfo *exception) 898 % 899 % A description of each parameter follows: 900 % 901 % o image: the image. 902 % 903 % o target: the RGB value of the target color. 904 % 905 % o fill: the replacement color. 906 % 907 % o invert: paint any pixel that does not match the target color. 908 % 909 % o exception: return any errors or warnings in this structure. 910 % 911 */ 912 MagickExport MagickBooleanType OpaquePaintImage(Image *image, 913 const PixelInfo *target,const PixelInfo *fill,const MagickBooleanType invert, 914 ExceptionInfo *exception) 915 { 916 #define OpaquePaintImageTag "Opaque/Image" 917 918 CacheView 919 *image_view; 920 921 MagickBooleanType 922 status; 923 924 MagickOffsetType 925 progress; 926 927 PixelInfo 928 conform_fill, 929 conform_target, 930 zero; 931 932 ssize_t 933 y; 934 935 assert(image != (Image *) NULL); 936 assert(image->signature == MagickCoreSignature); 937 assert(target != (PixelInfo *) NULL); 938 assert(fill != (PixelInfo *) NULL); 939 if (image->debug != MagickFalse) 940 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename); 941 if (SetImageStorageClass(image,DirectClass,exception) == MagickFalse) 942 return(MagickFalse); 943 ConformPixelInfo(image,fill,&conform_fill,exception); 944 ConformPixelInfo(image,target,&conform_target,exception); 945 /* 946 Make image color opaque. 947 */ 948 status=MagickTrue; 949 progress=0; 950 GetPixelInfo(image,&zero); 951 image_view=AcquireAuthenticCacheView(image,exception); 952 #if defined(MAGICKCORE_OPENMP_SUPPORT) 953 #pragma omp parallel for schedule(static) shared(progress,status) \ 954 magick_number_threads(image,image,image->rows,1) 955 #endif 956 for (y=0; y < (ssize_t) image->rows; y++) 957 { 958 PixelInfo 959 pixel; 960 961 register Quantum 962 *magick_restrict q; 963 964 register ssize_t 965 x; 966 967 if (status == MagickFalse) 968 continue; 969 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception); 970 if (q == (Quantum *) NULL) 971 { 972 status=MagickFalse; 973 continue; 974 } 975 pixel=zero; 976 for (x=0; x < (ssize_t) image->columns; x++) 977 { 978 GetPixelInfoPixel(image,q,&pixel); 979 if (IsFuzzyEquivalencePixelInfo(&pixel,&conform_target) != invert) 980 { 981 PixelTrait 982 traits; 983 984 traits=GetPixelChannelTraits(image,RedPixelChannel); 985 if ((traits & UpdatePixelTrait) != 0) 986 SetPixelRed(image,(Quantum) conform_fill.red,q); 987 traits=GetPixelChannelTraits(image,GreenPixelChannel); 988 if ((traits & UpdatePixelTrait) != 0) 989 SetPixelGreen(image,(Quantum) conform_fill.green,q); 990 traits=GetPixelChannelTraits(image,BluePixelChannel); 991 if ((traits & UpdatePixelTrait) != 0) 992 SetPixelBlue(image,(Quantum) conform_fill.blue,q); 993 traits=GetPixelChannelTraits(image,BlackPixelChannel); 994 if ((traits & UpdatePixelTrait) != 0) 995 SetPixelBlack(image,(Quantum) conform_fill.black,q); 996 traits=GetPixelChannelTraits(image,AlphaPixelChannel); 997 if ((traits & UpdatePixelTrait) != 0) 998 SetPixelAlpha(image,(Quantum) conform_fill.alpha,q); 999 } 1000 q+=GetPixelChannels(image); 1001 } 1002 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse) 1003 status=MagickFalse; 1004 if (image->progress_monitor != (MagickProgressMonitor) NULL) 1005 { 1006 MagickBooleanType 1007 proceed; 1008 1009 #if defined(MAGICKCORE_OPENMP_SUPPORT) 1010 #pragma omp atomic 1011 #endif 1012 progress++; 1013 proceed=SetImageProgress(image,OpaquePaintImageTag,progress, 1014 image->rows); 1015 if (proceed == MagickFalse) 1016 status=MagickFalse; 1017 } 1018 } 1019 image_view=DestroyCacheView(image_view); 1020 return(status); 1021 } 1022 1023 /* 1025 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 1026 % % 1027 % % 1028 % % 1029 % T r a n s p a r e n t P a i n t I m a g e % 1030 % % 1031 % % 1032 % % 1033 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 1034 % 1035 % TransparentPaintImage() changes the opacity value associated with any pixel 1036 % that matches color to the value defined by opacity. 1037 % 1038 % By default color must match a particular pixel color exactly. However, in 1039 % many cases two colors may differ by a small amount. Fuzz defines how much 1040 % tolerance is acceptable to consider two colors as the same. For example, 1041 % set fuzz to 10 and the color red at intensities of 100 and 102 respectively 1042 % are now interpreted as the same color. 1043 % 1044 % The format of the TransparentPaintImage method is: 1045 % 1046 % MagickBooleanType TransparentPaintImage(Image *image, 1047 % const PixelInfo *target,const Quantum opacity, 1048 % const MagickBooleanType invert,ExceptionInfo *exception) 1049 % 1050 % A description of each parameter follows: 1051 % 1052 % o image: the image. 1053 % 1054 % o target: the target color. 1055 % 1056 % o opacity: the replacement opacity value. 1057 % 1058 % o invert: paint any pixel that does not match the target color. 1059 % 1060 % o exception: return any errors or warnings in this structure. 1061 % 1062 */ 1063 MagickExport MagickBooleanType TransparentPaintImage(Image *image, 1064 const PixelInfo *target,const Quantum opacity,const MagickBooleanType invert, 1065 ExceptionInfo *exception) 1066 { 1067 #define TransparentPaintImageTag "Transparent/Image" 1068 1069 CacheView 1070 *image_view; 1071 1072 MagickBooleanType 1073 status; 1074 1075 MagickOffsetType 1076 progress; 1077 1078 PixelInfo 1079 zero; 1080 1081 ssize_t 1082 y; 1083 1084 assert(image != (Image *) NULL); 1085 assert(image->signature == MagickCoreSignature); 1086 assert(target != (PixelInfo *) NULL); 1087 if (image->debug != MagickFalse) 1088 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename); 1089 if (SetImageStorageClass(image,DirectClass,exception) == MagickFalse) 1090 return(MagickFalse); 1091 if (image->alpha_trait == UndefinedPixelTrait) 1092 (void) SetImageAlphaChannel(image,OpaqueAlphaChannel,exception); 1093 /* 1094 Make image color transparent. 1095 */ 1096 status=MagickTrue; 1097 progress=0; 1098 GetPixelInfo(image,&zero); 1099 image_view=AcquireAuthenticCacheView(image,exception); 1100 #if defined(MAGICKCORE_OPENMP_SUPPORT) 1101 #pragma omp parallel for schedule(static) shared(progress,status) \ 1102 magick_number_threads(image,image,image->rows,1) 1103 #endif 1104 for (y=0; y < (ssize_t) image->rows; y++) 1105 { 1106 PixelInfo 1107 pixel; 1108 1109 register ssize_t 1110 x; 1111 1112 register Quantum 1113 *magick_restrict q; 1114 1115 if (status == MagickFalse) 1116 continue; 1117 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception); 1118 if (q == (Quantum *) NULL) 1119 { 1120 status=MagickFalse; 1121 continue; 1122 } 1123 pixel=zero; 1124 for (x=0; x < (ssize_t) image->columns; x++) 1125 { 1126 GetPixelInfoPixel(image,q,&pixel); 1127 if (IsFuzzyEquivalencePixelInfo(&pixel,target) != invert) 1128 SetPixelAlpha(image,opacity,q); 1129 q+=GetPixelChannels(image); 1130 } 1131 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse) 1132 status=MagickFalse; 1133 if (image->progress_monitor != (MagickProgressMonitor) NULL) 1134 { 1135 MagickBooleanType 1136 proceed; 1137 1138 #if defined(MAGICKCORE_OPENMP_SUPPORT) 1139 #pragma omp atomic 1140 #endif 1141 progress++; 1142 proceed=SetImageProgress(image,TransparentPaintImageTag,progress, 1143 image->rows); 1144 if (proceed == MagickFalse) 1145 status=MagickFalse; 1146 } 1147 } 1148 image_view=DestroyCacheView(image_view); 1149 return(status); 1150 } 1151 1152 /* 1154 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 1155 % % 1156 % % 1157 % % 1158 % T r a n s p a r e n t P a i n t I m a g e C h r o m a % 1159 % % 1160 % % 1161 % % 1162 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 1163 % 1164 % TransparentPaintImageChroma() changes the opacity value associated with any 1165 % pixel that matches color to the value defined by opacity. 1166 % 1167 % As there is one fuzz value for the all the channels, TransparentPaintImage() 1168 % is not suitable for the operations like chroma, where the tolerance for 1169 % similarity of two color component (RGB) can be different. Thus we define 1170 % this method to take two target pixels (one low and one high) and all the 1171 % pixels of an image which are lying between these two pixels are made 1172 % transparent. 1173 % 1174 % The format of the TransparentPaintImageChroma method is: 1175 % 1176 % MagickBooleanType TransparentPaintImageChroma(Image *image, 1177 % const PixelInfo *low,const PixelInfo *high,const Quantum opacity, 1178 % const MagickBooleanType invert,ExceptionInfo *exception) 1179 % 1180 % A description of each parameter follows: 1181 % 1182 % o image: the image. 1183 % 1184 % o low: the low target color. 1185 % 1186 % o high: the high target color. 1187 % 1188 % o opacity: the replacement opacity value. 1189 % 1190 % o invert: paint any pixel that does not match the target color. 1191 % 1192 % o exception: return any errors or warnings in this structure. 1193 % 1194 */ 1195 MagickExport MagickBooleanType TransparentPaintImageChroma(Image *image, 1196 const PixelInfo *low,const PixelInfo *high,const Quantum opacity, 1197 const MagickBooleanType invert,ExceptionInfo *exception) 1198 { 1199 #define TransparentPaintImageTag "Transparent/Image" 1200 1201 CacheView 1202 *image_view; 1203 1204 MagickBooleanType 1205 status; 1206 1207 MagickOffsetType 1208 progress; 1209 1210 ssize_t 1211 y; 1212 1213 assert(image != (Image *) NULL); 1214 assert(image->signature == MagickCoreSignature); 1215 assert(high != (PixelInfo *) NULL); 1216 assert(low != (PixelInfo *) NULL); 1217 if (image->debug != MagickFalse) 1218 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename); 1219 if (SetImageStorageClass(image,DirectClass,exception) == MagickFalse) 1220 return(MagickFalse); 1221 if (image->alpha_trait == UndefinedPixelTrait) 1222 (void) SetImageAlphaChannel(image,OpaqueAlphaChannel,exception); 1223 /* 1224 Make image color transparent. 1225 */ 1226 status=MagickTrue; 1227 progress=0; 1228 image_view=AcquireAuthenticCacheView(image,exception); 1229 #if defined(MAGICKCORE_OPENMP_SUPPORT) 1230 #pragma omp parallel for schedule(static) shared(progress,status) \ 1231 magick_number_threads(image,image,image->rows,1) 1232 #endif 1233 for (y=0; y < (ssize_t) image->rows; y++) 1234 { 1235 MagickBooleanType 1236 match; 1237 1238 PixelInfo 1239 pixel; 1240 1241 register Quantum 1242 *magick_restrict q; 1243 1244 register ssize_t 1245 x; 1246 1247 if (status == MagickFalse) 1248 continue; 1249 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception); 1250 if (q == (Quantum *) NULL) 1251 { 1252 status=MagickFalse; 1253 continue; 1254 } 1255 GetPixelInfo(image,&pixel); 1256 for (x=0; x < (ssize_t) image->columns; x++) 1257 { 1258 GetPixelInfoPixel(image,q,&pixel); 1259 match=((pixel.red >= low->red) && (pixel.red <= high->red) && 1260 (pixel.green >= low->green) && (pixel.green <= high->green) && 1261 (pixel.blue >= low->blue) && (pixel.blue <= high->blue)) ? MagickTrue : 1262 MagickFalse; 1263 if (match != invert) 1264 SetPixelAlpha(image,opacity,q); 1265 q+=GetPixelChannels(image); 1266 } 1267 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse) 1268 status=MagickFalse; 1269 if (image->progress_monitor != (MagickProgressMonitor) NULL) 1270 { 1271 MagickBooleanType 1272 proceed; 1273 1274 #if defined(MAGICKCORE_OPENMP_SUPPORT) 1275 #pragma omp atomic 1276 #endif 1277 progress++; 1278 proceed=SetImageProgress(image,TransparentPaintImageTag,progress, 1279 image->rows); 1280 if (proceed == MagickFalse) 1281 status=MagickFalse; 1282 } 1283 } 1284 image_view=DestroyCacheView(image_view); 1285 return(status); 1286 } 1287