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-2016 ImageMagick Studio LLC, a non-profit organization % 21 % dedicated to making software imaging solutions freely available. % 22 % % 23 % You may not use this file except in compliance with the License. You may % 24 % obtain a copy of the License at % 25 % % 26 % http://www.imagemagick.org/script/license.php % 27 % % 28 % Unless required by applicable law or agreed to in writing, software % 29 % distributed under the License is distributed on an "AS IS" BASIS, % 30 % WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. % 31 % See the License for the specific language governing permissions and % 32 % limitations under the License. % 33 % % 34 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 35 % 36 % 37 */ 38 39 /* 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,image->columns,image->rows,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 #if defined(MAGICKCORE_OPENMP_SUPPORT) 326 #pragma omp parallel for schedule(static,4) shared(status) \ 327 magick_threads(floodplane_image,image,floodplane_image->rows,1) 328 #endif 329 for (y=0; y < (ssize_t) image->rows; y++) 330 { 331 register const Quantum 332 *magick_restrict p; 333 334 register Quantum 335 *magick_restrict q; 336 337 register ssize_t 338 x; 339 340 /* 341 Tile fill color onto floodplane. 342 */ 343 if (status == MagickFalse) 344 continue; 345 p=GetCacheViewVirtualPixels(floodplane_view,0,y,image->columns,1,exception); 346 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception); 347 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL)) 348 { 349 status=MagickFalse; 350 continue; 351 } 352 for (x=0; x < (ssize_t) image->columns; x++) 353 { 354 if (GetPixelGray(floodplane_image,p) != 0) 355 { 356 GetFillColor(draw_info,x,y,&fill_color,exception); 357 SetPixelViaPixelInfo(image,&fill_color,q); 358 } 359 p+=GetPixelChannels(floodplane_image); 360 q+=GetPixelChannels(image); 361 } 362 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse) 363 status=MagickFalse; 364 } 365 floodplane_view=DestroyCacheView(floodplane_view); 366 image_view=DestroyCacheView(image_view); 367 segment_info=RelinquishVirtualMemory(segment_info); 368 floodplane_image=DestroyImage(floodplane_image); 369 return(status); 370 } 371 372 /* 374 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 375 % % 376 % % 377 % % 378 + G r a d i e n t I m a g e % 379 % % 380 % % 381 % % 382 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 383 % 384 % GradientImage() applies a continuously smooth color transitions along a 385 % vector from one color to another. 386 % 387 % Note, the interface of this method will change in the future to support 388 % more than one transistion. 389 % 390 % The format of the GradientImage method is: 391 % 392 % MagickBooleanType GradientImage(Image *image,const GradientType type, 393 % const SpreadMethod method,const PixelInfo *start_color, 394 % const PixelInfo *stop_color,ExceptionInfo *exception) 395 % 396 % A description of each parameter follows: 397 % 398 % o image: the image. 399 % 400 % o type: the gradient type: linear or radial. 401 % 402 % o spread: the gradient spread meathod: pad, reflect, or repeat. 403 % 404 % o start_color: the start color. 405 % 406 % o stop_color: the stop color. 407 % 408 % o exception: return any errors or warnings in this structure. 409 % 410 */ 411 MagickExport MagickBooleanType GradientImage(Image *image, 412 const GradientType type,const SpreadMethod method,const StopInfo *stops, 413 const size_t number_stops,ExceptionInfo *exception) 414 { 415 const char 416 *artifact; 417 418 DrawInfo 419 *draw_info; 420 421 GradientInfo 422 *gradient; 423 424 MagickBooleanType 425 status; 426 427 /* 428 Set gradient start-stop end points. 429 */ 430 assert(image != (const Image *) NULL); 431 assert(image->signature == MagickCoreSignature); 432 if (image->debug != MagickFalse) 433 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename); 434 assert(stops != (const StopInfo *) NULL); 435 assert(number_stops > 0); 436 draw_info=AcquireDrawInfo(); 437 gradient=(&draw_info->gradient); 438 gradient->type=type; 439 gradient->bounding_box.width=image->columns; 440 gradient->bounding_box.height=image->rows; 441 artifact=GetImageArtifact(image,"gradient:bounding-box"); 442 if (artifact != (const char *) NULL) 443 (void) ParseAbsoluteGeometry(artifact,&gradient->bounding_box); 444 gradient->gradient_vector.x2=(double) image->columns-1.0; 445 gradient->gradient_vector.y2=(double) image->rows-1.0; 446 artifact=GetImageArtifact(image,"gradient:direction"); 447 if (artifact != (const char *) NULL) 448 { 449 GravityType 450 direction; 451 452 direction=(GravityType) ParseCommandOption(MagickGravityOptions, 453 MagickFalse,artifact); 454 switch (direction) 455 { 456 case NorthWestGravity: 457 { 458 gradient->gradient_vector.x1=(double) image->columns-1.0; 459 gradient->gradient_vector.y1=(double) image->rows-1.0; 460 gradient->gradient_vector.x2=0.0; 461 gradient->gradient_vector.y2=0.0; 462 break; 463 } 464 case NorthGravity: 465 { 466 gradient->gradient_vector.x1=0.0; 467 gradient->gradient_vector.y1=(double) image->rows-1.0; 468 gradient->gradient_vector.x2=0.0; 469 gradient->gradient_vector.y2=0.0; 470 break; 471 } 472 case NorthEastGravity: 473 { 474 gradient->gradient_vector.x1=0.0; 475 gradient->gradient_vector.y1=(double) image->rows-1.0; 476 gradient->gradient_vector.x2=(double) image->columns-1.0; 477 gradient->gradient_vector.y2=0.0; 478 break; 479 } 480 case WestGravity: 481 { 482 gradient->gradient_vector.x1=(double) image->columns-1.0; 483 gradient->gradient_vector.y1=0.0; 484 gradient->gradient_vector.x2=0.0; 485 gradient->gradient_vector.y2=0.0; 486 break; 487 } 488 case EastGravity: 489 { 490 gradient->gradient_vector.x1=0.0; 491 gradient->gradient_vector.y1=0.0; 492 gradient->gradient_vector.x2=(double) image->columns-1.0; 493 gradient->gradient_vector.y2=0.0; 494 break; 495 } 496 case SouthWestGravity: 497 { 498 gradient->gradient_vector.x1=(double) image->columns-1.0; 499 gradient->gradient_vector.y1=0.0; 500 gradient->gradient_vector.x2=0.0; 501 gradient->gradient_vector.y2=(double) image->rows-1.0; 502 break; 503 } 504 case SouthGravity: 505 { 506 gradient->gradient_vector.x1=0.0; 507 gradient->gradient_vector.y1=0.0; 508 gradient->gradient_vector.x2=0.0; 509 gradient->gradient_vector.y2=(double) image->columns-1.0; 510 break; 511 } 512 case SouthEastGravity: 513 { 514 gradient->gradient_vector.x1=0.0; 515 gradient->gradient_vector.y1=0.0; 516 gradient->gradient_vector.x2=(double) image->columns-1.0; 517 gradient->gradient_vector.y2=(double) image->rows-1.0; 518 break; 519 } 520 default: 521 break; 522 } 523 } 524 artifact=GetImageArtifact(image,"gradient:angle"); 525 if (artifact != (const char *) NULL) 526 gradient->angle=StringToDouble(artifact,(char **) NULL); 527 artifact=GetImageArtifact(image,"gradient:vector"); 528 if (artifact != (const char *) NULL) 529 (void) sscanf(artifact,"%lf%*[ ,]%lf%*[ ,]%lf%*[ ,]%lf", 530 &gradient->gradient_vector.x1,&gradient->gradient_vector.y1, 531 &gradient->gradient_vector.x2,&gradient->gradient_vector.y2); 532 if ((GetImageArtifact(image,"gradient:angle") == (const char *) NULL) && 533 (GetImageArtifact(image,"gradient:direction") == (const char *) NULL) && 534 (GetImageArtifact(image,"gradient:extent") == (const char *) NULL) && 535 (GetImageArtifact(image,"gradient:vector") == (const char *) NULL)) 536 if ((type == LinearGradient) && (gradient->gradient_vector.y2 != 0.0)) 537 gradient->gradient_vector.x2=0.0; 538 gradient->center.x=(double) gradient->gradient_vector.x2/2.0; 539 gradient->center.y=(double) gradient->gradient_vector.y2/2.0; 540 artifact=GetImageArtifact(image,"gradient:center"); 541 if (artifact != (const char *) NULL) 542 (void) sscanf(artifact,"%lf%*[ ,]%lf",&gradient->center.x, 543 &gradient->center.y); 544 artifact=GetImageArtifact(image,"gradient:angle"); 545 if ((type == LinearGradient) && (artifact != (const char *) NULL)) 546 { 547 double 548 sine, 549 cosine, 550 distance; 551 552 /* 553 Reference https://drafts.csswg.org/css-images-3/#linear-gradients. 554 */ 555 sine=sin((double) DegreesToRadians(gradient->angle-90.0)); 556 cosine=cos((double) DegreesToRadians(gradient->angle-90.0)); 557 distance=fabs((double) image->columns*cosine)+ 558 fabs((double) image->rows*sine); 559 gradient->gradient_vector.x1=0.5*(image->columns-distance*cosine); 560 gradient->gradient_vector.y1=0.5*(image->rows-distance*sine); 561 gradient->gradient_vector.x2=0.5*(image->columns+distance*cosine); 562 gradient->gradient_vector.y2=0.5*(image->rows+distance*sine); 563 } 564 gradient->radii.x=(double) MagickMax(image->columns,image->rows)/2.0; 565 gradient->radii.y=gradient->radii.x; 566 artifact=GetImageArtifact(image,"gradient:extent"); 567 if (artifact != (const char *) NULL) 568 { 569 if (LocaleCompare(artifact,"Circle") == 0) 570 { 571 gradient->radii.x=(double) MagickMax(image->columns,image->rows)/2.0; 572 gradient->radii.y=gradient->radii.x; 573 } 574 if (LocaleCompare(artifact,"Diagonal") == 0) 575 { 576 gradient->radii.x=(double) (sqrt(image->columns*image->columns+ 577 image->rows*image->rows))/2.0; 578 gradient->radii.y=gradient->radii.x; 579 } 580 if (LocaleCompare(artifact,"Ellipse") == 0) 581 { 582 gradient->radii.x=(double) image->columns/2.0; 583 gradient->radii.y=(double) image->rows/2.0; 584 } 585 if (LocaleCompare(artifact,"Maximum") == 0) 586 { 587 gradient->radii.x=(double) MagickMax(image->columns,image->rows)/2.0; 588 gradient->radii.y=gradient->radii.x; 589 } 590 if (LocaleCompare(artifact,"Minimum") == 0) 591 { 592 gradient->radii.x=(double) (MagickMin(image->columns,image->rows))/ 593 2.0; 594 gradient->radii.y=gradient->radii.x; 595 } 596 } 597 artifact=GetImageArtifact(image,"gradient:radii"); 598 if (artifact != (const char *) NULL) 599 (void) sscanf(artifact,"%lf%*[ ,]%lf",&gradient->radii.x, 600 &gradient->radii.y); 601 gradient->radius=MagickMax(gradient->radii.x,gradient->radii.y); 602 gradient->spread=method; 603 /* 604 Define the gradient to fill between the stops. 605 */ 606 gradient->number_stops=number_stops; 607 gradient->stops=(StopInfo *) AcquireQuantumMemory(gradient->number_stops, 608 sizeof(*gradient->stops)); 609 if (gradient->stops == (StopInfo *) NULL) 610 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed", 611 image->filename); 612 (void) CopyMagickMemory(gradient->stops,stops,(size_t) number_stops* 613 sizeof(*stops)); 614 /* 615 Draw a gradient on the image. 616 */ 617 status=DrawGradientImage(image,draw_info,exception); 618 draw_info=DestroyDrawInfo(draw_info); 619 return(status); 620 } 621 622 /* 624 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 625 % % 626 % % 627 % % 628 % O i l P a i n t I m a g e % 629 % % 630 % % 631 % % 632 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 633 % 634 % OilPaintImage() applies a special effect filter that simulates an oil 635 % painting. Each pixel is replaced by the most frequent color occurring 636 % in a circular region defined by radius. 637 % 638 % The format of the OilPaintImage method is: 639 % 640 % Image *OilPaintImage(const Image *image,const double radius, 641 % const double sigma,ExceptionInfo *exception) 642 % 643 % A description of each parameter follows: 644 % 645 % o image: the image. 646 % 647 % o radius: the radius of the circular neighborhood. 648 % 649 % o sigma: the standard deviation of the Gaussian, in pixels. 650 % 651 % o exception: return any errors or warnings in this structure. 652 % 653 */ 654 655 static size_t **DestroyHistogramThreadSet(size_t **histogram) 656 { 657 register ssize_t 658 i; 659 660 assert(histogram != (size_t **) NULL); 661 for (i=0; i < (ssize_t) GetMagickResourceLimit(ThreadResource); i++) 662 if (histogram[i] != (size_t *) NULL) 663 histogram[i]=(size_t *) RelinquishMagickMemory(histogram[i]); 664 histogram=(size_t **) RelinquishMagickMemory(histogram); 665 return(histogram); 666 } 667 668 static size_t **AcquireHistogramThreadSet(const size_t count) 669 { 670 register ssize_t 671 i; 672 673 size_t 674 **histogram, 675 number_threads; 676 677 number_threads=(size_t) GetMagickResourceLimit(ThreadResource); 678 histogram=(size_t **) AcquireQuantumMemory(number_threads,sizeof(*histogram)); 679 if (histogram == (size_t **) NULL) 680 return((size_t **) NULL); 681 (void) ResetMagickMemory(histogram,0,number_threads*sizeof(*histogram)); 682 for (i=0; i < (ssize_t) number_threads; i++) 683 { 684 histogram[i]=(size_t *) AcquireQuantumMemory(count,sizeof(**histogram)); 685 if (histogram[i] == (size_t *) NULL) 686 return(DestroyHistogramThreadSet(histogram)); 687 } 688 return(histogram); 689 } 690 691 MagickExport Image *OilPaintImage(const Image *image,const double radius, 692 const double sigma,ExceptionInfo *exception) 693 { 694 #define NumberPaintBins 256 695 #define OilPaintImageTag "OilPaint/Image" 696 697 CacheView 698 *image_view, 699 *paint_view; 700 701 Image 702 *linear_image, 703 *paint_image; 704 705 MagickBooleanType 706 status; 707 708 MagickOffsetType 709 progress; 710 711 size_t 712 **histograms, 713 width; 714 715 ssize_t 716 center, 717 y; 718 719 /* 720 Initialize painted image attributes. 721 */ 722 assert(image != (const Image *) NULL); 723 assert(image->signature == MagickCoreSignature); 724 if (image->debug != MagickFalse) 725 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename); 726 assert(exception != (ExceptionInfo *) NULL); 727 assert(exception->signature == MagickCoreSignature); 728 width=GetOptimalKernelWidth2D(radius,sigma); 729 linear_image=CloneImage(image,0,0,MagickTrue,exception); 730 paint_image=CloneImage(image,image->columns,image->rows,MagickTrue,exception); 731 if ((linear_image == (Image *) NULL) || (paint_image == (Image *) NULL)) 732 { 733 if (linear_image != (Image *) NULL) 734 linear_image=DestroyImage(linear_image); 735 if (paint_image != (Image *) NULL) 736 linear_image=DestroyImage(paint_image); 737 return((Image *) NULL); 738 } 739 if (SetImageStorageClass(paint_image,DirectClass,exception) == MagickFalse) 740 { 741 linear_image=DestroyImage(linear_image); 742 paint_image=DestroyImage(paint_image); 743 return((Image *) NULL); 744 } 745 histograms=AcquireHistogramThreadSet(NumberPaintBins); 746 if (histograms == (size_t **) NULL) 747 { 748 linear_image=DestroyImage(linear_image); 749 paint_image=DestroyImage(paint_image); 750 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed"); 751 } 752 /* 753 Oil paint image. 754 */ 755 status=MagickTrue; 756 progress=0; 757 center=(ssize_t) GetPixelChannels(linear_image)*(linear_image->columns+width)* 758 (width/2L)+GetPixelChannels(linear_image)*(width/2L); 759 image_view=AcquireVirtualCacheView(linear_image,exception); 760 paint_view=AcquireAuthenticCacheView(paint_image,exception); 761 #if defined(MAGICKCORE_OPENMP_SUPPORT) 762 #pragma omp parallel for schedule(static,4) shared(progress,status) \ 763 magick_threads(linear_image,paint_image,linear_image->rows,1) 764 #endif 765 for (y=0; y < (ssize_t) linear_image->rows; y++) 766 { 767 register const Quantum 768 *magick_restrict p; 769 770 register Quantum 771 *magick_restrict q; 772 773 register size_t 774 *histogram; 775 776 register ssize_t 777 x; 778 779 if (status == MagickFalse) 780 continue; 781 p=GetCacheViewVirtualPixels(image_view,-((ssize_t) width/2L),y-(ssize_t) 782 (width/2L),linear_image->columns+width,width,exception); 783 q=QueueCacheViewAuthenticPixels(paint_view,0,y,paint_image->columns,1, 784 exception); 785 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL)) 786 { 787 status=MagickFalse; 788 continue; 789 } 790 histogram=histograms[GetOpenMPThreadId()]; 791 for (x=0; x < (ssize_t) linear_image->columns; x++) 792 { 793 register ssize_t 794 i, 795 u; 796 797 size_t 798 count; 799 800 ssize_t 801 j, 802 k, 803 n, 804 v; 805 806 /* 807 Assign most frequent color. 808 */ 809 k=0; 810 j=0; 811 count=0; 812 (void) ResetMagickMemory(histogram,0,NumberPaintBins* sizeof(*histogram)); 813 for (v=0; v < (ssize_t) width; v++) 814 { 815 for (u=0; u < (ssize_t) width; u++) 816 { 817 n=(ssize_t) ScaleQuantumToChar(ClampToQuantum(GetPixelIntensity( 818 linear_image,p+GetPixelChannels(linear_image)*(u+k)))); 819 histogram[n]++; 820 if (histogram[n] > count) 821 { 822 j=k+u; 823 count=histogram[n]; 824 } 825 } 826 k+=(ssize_t) (linear_image->columns+width); 827 } 828 for (i=0; i < (ssize_t) GetPixelChannels(linear_image); i++) 829 { 830 PixelChannel channel=GetPixelChannelChannel(linear_image,i); 831 PixelTrait traits=GetPixelChannelTraits(linear_image,channel); 832 PixelTrait paint_traits=GetPixelChannelTraits(paint_image,channel); 833 if ((traits == UndefinedPixelTrait) || 834 (paint_traits == UndefinedPixelTrait)) 835 continue; 836 if (((paint_traits & CopyPixelTrait) != 0) || 837 (GetPixelReadMask(linear_image,p) == 0)) 838 { 839 SetPixelChannel(paint_image,channel,p[center+i],q); 840 continue; 841 } 842 SetPixelChannel(paint_image,channel,p[j*GetPixelChannels(linear_image)+ 843 i],q); 844 } 845 p+=GetPixelChannels(linear_image); 846 q+=GetPixelChannels(paint_image); 847 } 848 if (SyncCacheViewAuthenticPixels(paint_view,exception) == MagickFalse) 849 status=MagickFalse; 850 if (linear_image->progress_monitor != (MagickProgressMonitor) NULL) 851 { 852 MagickBooleanType 853 proceed; 854 855 #if defined(MAGICKCORE_OPENMP_SUPPORT) 856 #pragma omp critical (MagickCore_OilPaintImage) 857 #endif 858 proceed=SetImageProgress(linear_image,OilPaintImageTag,progress++, 859 linear_image->rows); 860 if (proceed == MagickFalse) 861 status=MagickFalse; 862 } 863 } 864 paint_view=DestroyCacheView(paint_view); 865 image_view=DestroyCacheView(image_view); 866 histograms=DestroyHistogramThreadSet(histograms); 867 linear_image=DestroyImage(linear_image); 868 if (status == MagickFalse) 869 paint_image=DestroyImage(paint_image); 870 return(paint_image); 871 } 872 873 /* 875 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 876 % % 877 % % 878 % % 879 % O p a q u e P a i n t I m a g e % 880 % % 881 % % 882 % % 883 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 884 % 885 % OpaquePaintImage() changes any pixel that matches color with the color 886 % defined by fill argument. 887 % 888 % By default color must match a particular pixel color exactly. However, in 889 % many cases two colors may differ by a small amount. Fuzz defines how much 890 % tolerance is acceptable to consider two colors as the same. For example, 891 % set fuzz to 10 and the color red at intensities of 100 and 102 respectively 892 % are now interpreted as the same color. 893 % 894 % The format of the OpaquePaintImage method is: 895 % 896 % MagickBooleanType OpaquePaintImage(Image *image,const PixelInfo *target, 897 % const PixelInfo *fill,const MagickBooleanType invert, 898 % ExceptionInfo *exception) 899 % 900 % A description of each parameter follows: 901 % 902 % o image: the image. 903 % 904 % o target: the RGB value of the target color. 905 % 906 % o fill: the replacement color. 907 % 908 % o invert: paint any pixel that does not match the target color. 909 % 910 % o exception: return any errors or warnings in this structure. 911 % 912 */ 913 MagickExport MagickBooleanType OpaquePaintImage(Image *image, 914 const PixelInfo *target,const PixelInfo *fill,const MagickBooleanType invert, 915 ExceptionInfo *exception) 916 { 917 #define OpaquePaintImageTag "Opaque/Image" 918 919 CacheView 920 *image_view; 921 922 MagickBooleanType 923 status; 924 925 MagickOffsetType 926 progress; 927 928 PixelInfo 929 conform_fill, 930 conform_target, 931 zero; 932 933 ssize_t 934 y; 935 936 assert(image != (Image *) NULL); 937 assert(image->signature == MagickCoreSignature); 938 assert(target != (PixelInfo *) NULL); 939 assert(fill != (PixelInfo *) NULL); 940 if (image->debug != MagickFalse) 941 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename); 942 if (SetImageStorageClass(image,DirectClass,exception) == MagickFalse) 943 return(MagickFalse); 944 ConformPixelInfo(image,fill,&conform_fill,exception); 945 ConformPixelInfo(image,target,&conform_target,exception); 946 /* 947 Make image color opaque. 948 */ 949 status=MagickTrue; 950 progress=0; 951 GetPixelInfo(image,&zero); 952 image_view=AcquireAuthenticCacheView(image,exception); 953 #if defined(MAGICKCORE_OPENMP_SUPPORT) 954 #pragma omp parallel for schedule(static,4) shared(progress,status) \ 955 magick_threads(image,image,image->rows,1) 956 #endif 957 for (y=0; y < (ssize_t) image->rows; y++) 958 { 959 PixelInfo 960 pixel; 961 962 register Quantum 963 *magick_restrict q; 964 965 register ssize_t 966 x; 967 968 if (status == MagickFalse) 969 continue; 970 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception); 971 if (q == (Quantum *) NULL) 972 { 973 status=MagickFalse; 974 continue; 975 } 976 pixel=zero; 977 for (x=0; x < (ssize_t) image->columns; x++) 978 { 979 GetPixelInfoPixel(image,q,&pixel); 980 if (IsFuzzyEquivalencePixelInfo(&pixel,&conform_target) != invert) 981 SetPixelViaPixelInfo(image,&conform_fill,q); 982 q+=GetPixelChannels(image); 983 } 984 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse) 985 status=MagickFalse; 986 if (image->progress_monitor != (MagickProgressMonitor) NULL) 987 { 988 MagickBooleanType 989 proceed; 990 991 #if defined(MAGICKCORE_OPENMP_SUPPORT) 992 #pragma omp critical (MagickCore_OpaquePaintImage) 993 #endif 994 proceed=SetImageProgress(image,OpaquePaintImageTag,progress++, 995 image->rows); 996 if (proceed == MagickFalse) 997 status=MagickFalse; 998 } 999 } 1000 image_view=DestroyCacheView(image_view); 1001 return(status); 1002 } 1003 1004 /* 1006 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 1007 % % 1008 % % 1009 % % 1010 % T r a n s p a r e n t P a i n t I m a g e % 1011 % % 1012 % % 1013 % % 1014 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 1015 % 1016 % TransparentPaintImage() changes the opacity value associated with any pixel 1017 % that matches color to the value defined by opacity. 1018 % 1019 % By default color must match a particular pixel color exactly. However, in 1020 % many cases two colors may differ by a small amount. Fuzz defines how much 1021 % tolerance is acceptable to consider two colors as the same. For example, 1022 % set fuzz to 10 and the color red at intensities of 100 and 102 respectively 1023 % are now interpreted as the same color. 1024 % 1025 % The format of the TransparentPaintImage method is: 1026 % 1027 % MagickBooleanType TransparentPaintImage(Image *image, 1028 % const PixelInfo *target,const Quantum opacity, 1029 % const MagickBooleanType invert,ExceptionInfo *exception) 1030 % 1031 % A description of each parameter follows: 1032 % 1033 % o image: the image. 1034 % 1035 % o target: the target color. 1036 % 1037 % o opacity: the replacement opacity value. 1038 % 1039 % o invert: paint any pixel that does not match the target color. 1040 % 1041 % o exception: return any errors or warnings in this structure. 1042 % 1043 */ 1044 MagickExport MagickBooleanType TransparentPaintImage(Image *image, 1045 const PixelInfo *target,const Quantum opacity,const MagickBooleanType invert, 1046 ExceptionInfo *exception) 1047 { 1048 #define TransparentPaintImageTag "Transparent/Image" 1049 1050 CacheView 1051 *image_view; 1052 1053 MagickBooleanType 1054 status; 1055 1056 MagickOffsetType 1057 progress; 1058 1059 PixelInfo 1060 zero; 1061 1062 ssize_t 1063 y; 1064 1065 assert(image != (Image *) NULL); 1066 assert(image->signature == MagickCoreSignature); 1067 assert(target != (PixelInfo *) NULL); 1068 if (image->debug != MagickFalse) 1069 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename); 1070 if (SetImageStorageClass(image,DirectClass,exception) == MagickFalse) 1071 return(MagickFalse); 1072 if (image->alpha_trait == UndefinedPixelTrait) 1073 (void) SetImageAlphaChannel(image,OpaqueAlphaChannel,exception); 1074 /* 1075 Make image color transparent. 1076 */ 1077 status=MagickTrue; 1078 progress=0; 1079 GetPixelInfo(image,&zero); 1080 image_view=AcquireAuthenticCacheView(image,exception); 1081 #if defined(MAGICKCORE_OPENMP_SUPPORT) 1082 #pragma omp parallel for schedule(static,4) shared(progress,status) \ 1083 magick_threads(image,image,image->rows,1) 1084 #endif 1085 for (y=0; y < (ssize_t) image->rows; y++) 1086 { 1087 PixelInfo 1088 pixel; 1089 1090 register ssize_t 1091 x; 1092 1093 register Quantum 1094 *magick_restrict q; 1095 1096 if (status == MagickFalse) 1097 continue; 1098 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception); 1099 if (q == (Quantum *) NULL) 1100 { 1101 status=MagickFalse; 1102 continue; 1103 } 1104 pixel=zero; 1105 for (x=0; x < (ssize_t) image->columns; x++) 1106 { 1107 GetPixelInfoPixel(image,q,&pixel); 1108 if (IsFuzzyEquivalencePixelInfo(&pixel,target) != invert) 1109 SetPixelAlpha(image,opacity,q); 1110 q+=GetPixelChannels(image); 1111 } 1112 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse) 1113 status=MagickFalse; 1114 if (image->progress_monitor != (MagickProgressMonitor) NULL) 1115 { 1116 MagickBooleanType 1117 proceed; 1118 1119 #if defined(MAGICKCORE_OPENMP_SUPPORT) 1120 #pragma omp critical (MagickCore_TransparentPaintImage) 1121 #endif 1122 proceed=SetImageProgress(image,TransparentPaintImageTag,progress++, 1123 image->rows); 1124 if (proceed == MagickFalse) 1125 status=MagickFalse; 1126 } 1127 } 1128 image_view=DestroyCacheView(image_view); 1129 return(status); 1130 } 1131 1132 /* 1134 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 1135 % % 1136 % % 1137 % % 1138 % 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 % 1139 % % 1140 % % 1141 % % 1142 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 1143 % 1144 % TransparentPaintImageChroma() changes the opacity value associated with any 1145 % pixel that matches color to the value defined by opacity. 1146 % 1147 % As there is one fuzz value for the all the channels, TransparentPaintImage() 1148 % is not suitable for the operations like chroma, where the tolerance for 1149 % similarity of two color component (RGB) can be different. Thus we define 1150 % this method to take two target pixels (one low and one high) and all the 1151 % pixels of an image which are lying between these two pixels are made 1152 % transparent. 1153 % 1154 % The format of the TransparentPaintImageChroma method is: 1155 % 1156 % MagickBooleanType TransparentPaintImageChroma(Image *image, 1157 % const PixelInfo *low,const PixelInfo *high,const Quantum opacity, 1158 % const MagickBooleanType invert,ExceptionInfo *exception) 1159 % 1160 % A description of each parameter follows: 1161 % 1162 % o image: the image. 1163 % 1164 % o low: the low target color. 1165 % 1166 % o high: the high target color. 1167 % 1168 % o opacity: the replacement opacity value. 1169 % 1170 % o invert: paint any pixel that does not match the target color. 1171 % 1172 % o exception: return any errors or warnings in this structure. 1173 % 1174 */ 1175 MagickExport MagickBooleanType TransparentPaintImageChroma(Image *image, 1176 const PixelInfo *low,const PixelInfo *high,const Quantum opacity, 1177 const MagickBooleanType invert,ExceptionInfo *exception) 1178 { 1179 #define TransparentPaintImageTag "Transparent/Image" 1180 1181 CacheView 1182 *image_view; 1183 1184 MagickBooleanType 1185 status; 1186 1187 MagickOffsetType 1188 progress; 1189 1190 ssize_t 1191 y; 1192 1193 assert(image != (Image *) NULL); 1194 assert(image->signature == MagickCoreSignature); 1195 assert(high != (PixelInfo *) NULL); 1196 assert(low != (PixelInfo *) NULL); 1197 if (image->debug != MagickFalse) 1198 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename); 1199 if (SetImageStorageClass(image,DirectClass,exception) == MagickFalse) 1200 return(MagickFalse); 1201 if (image->alpha_trait == UndefinedPixelTrait) 1202 (void) SetImageAlphaChannel(image,OpaqueAlphaChannel,exception); 1203 /* 1204 Make image color transparent. 1205 */ 1206 status=MagickTrue; 1207 progress=0; 1208 image_view=AcquireAuthenticCacheView(image,exception); 1209 #if defined(MAGICKCORE_OPENMP_SUPPORT) 1210 #pragma omp parallel for schedule(static,4) shared(progress,status) \ 1211 magick_threads(image,image,image->rows,1) 1212 #endif 1213 for (y=0; y < (ssize_t) image->rows; y++) 1214 { 1215 MagickBooleanType 1216 match; 1217 1218 PixelInfo 1219 pixel; 1220 1221 register Quantum 1222 *magick_restrict q; 1223 1224 register ssize_t 1225 x; 1226 1227 if (status == MagickFalse) 1228 continue; 1229 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception); 1230 if (q == (Quantum *) NULL) 1231 { 1232 status=MagickFalse; 1233 continue; 1234 } 1235 GetPixelInfo(image,&pixel); 1236 for (x=0; x < (ssize_t) image->columns; x++) 1237 { 1238 GetPixelInfoPixel(image,q,&pixel); 1239 match=((pixel.red >= low->red) && (pixel.red <= high->red) && 1240 (pixel.green >= low->green) && (pixel.green <= high->green) && 1241 (pixel.blue >= low->blue) && (pixel.blue <= high->blue)) ? MagickTrue : 1242 MagickFalse; 1243 if (match != invert) 1244 SetPixelAlpha(image,opacity,q); 1245 q+=GetPixelChannels(image); 1246 } 1247 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse) 1248 status=MagickFalse; 1249 if (image->progress_monitor != (MagickProgressMonitor) NULL) 1250 { 1251 MagickBooleanType 1252 proceed; 1253 1254 #if defined(MAGICKCORE_OPENMP_SUPPORT) 1255 #pragma omp critical (MagickCore_TransparentPaintImageChroma) 1256 #endif 1257 proceed=SetImageProgress(image,TransparentPaintImageTag,progress++, 1258 image->rows); 1259 if (proceed == MagickFalse) 1260 status=MagickFalse; 1261 } 1262 } 1263 image_view=DestroyCacheView(image_view); 1264 return(status); 1265 } 1266