1 /* 2 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 3 % % 4 % % 5 % % 6 % SSSSS H H EEEEE AAA RRRR % 7 % SS H H E A A R R % 8 % SSS HHHHH EEE AAAAA RRRR % 9 % SS H H E A A R R % 10 % SSSSS H H EEEEE A A R R % 11 % % 12 % % 13 % MagickCore Methods to Shear or Rotate an Image by an Arbitrary Angle % 14 % % 15 % Software Design % 16 % Cristy % 17 % July 1992 % 18 % % 19 % % 20 % Copyright 1999-2016 ImageMagick Studio LLC, a non-profit organization % 21 % dedicated to making software imaging solutions freely available. % 22 % % 23 % You may not use this file except in compliance with the License. You may % 24 % obtain a copy of the License at % 25 % % 26 % http://www.imagemagick.org/script/license.php % 27 % % 28 % Unless required by applicable law or agreed to in writing, software % 29 % distributed under the License is distributed on an "AS IS" BASIS, % 30 % WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. % 31 % See the License for the specific language governing permissions and % 32 % limitations under the License. % 33 % % 34 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 35 % 36 % The XShearImage() and YShearImage() methods are based on the paper "A Fast 37 % Algorithm for General Raster Rotatation" by Alan W. Paeth, Graphics 38 % Interface '86 (Vancouver). ShearRotateImage() is adapted from a similar 39 % method based on the Paeth paper written by Michael Halle of the Spatial 40 % Imaging Group, MIT Media Lab. 41 % 42 */ 43 44 /* 46 Include declarations. 47 */ 48 #include "MagickCore/studio.h" 49 #include "MagickCore/artifact.h" 50 #include "MagickCore/attribute.h" 51 #include "MagickCore/blob-private.h" 52 #include "MagickCore/cache-private.h" 53 #include "MagickCore/channel.h" 54 #include "MagickCore/color-private.h" 55 #include "MagickCore/colorspace-private.h" 56 #include "MagickCore/composite.h" 57 #include "MagickCore/composite-private.h" 58 #include "MagickCore/decorate.h" 59 #include "MagickCore/distort.h" 60 #include "MagickCore/draw.h" 61 #include "MagickCore/exception.h" 62 #include "MagickCore/exception-private.h" 63 #include "MagickCore/gem.h" 64 #include "MagickCore/geometry.h" 65 #include "MagickCore/image.h" 66 #include "MagickCore/image-private.h" 67 #include "MagickCore/matrix.h" 68 #include "MagickCore/memory_.h" 69 #include "MagickCore/list.h" 70 #include "MagickCore/monitor.h" 71 #include "MagickCore/monitor-private.h" 72 #include "MagickCore/nt-base-private.h" 73 #include "MagickCore/pixel-accessor.h" 74 #include "MagickCore/quantum.h" 75 #include "MagickCore/resource_.h" 76 #include "MagickCore/shear.h" 77 #include "MagickCore/statistic.h" 78 #include "MagickCore/string_.h" 79 #include "MagickCore/string-private.h" 80 #include "MagickCore/thread-private.h" 81 #include "MagickCore/threshold.h" 82 #include "MagickCore/transform.h" 83 84 /* 86 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 87 % % 88 % % 89 % % 90 + C r o p T o F i t I m a g e % 91 % % 92 % % 93 % % 94 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 95 % 96 % CropToFitImage() crops the sheared image as determined by the bounding box 97 % as defined by width and height and shearing angles. 98 % 99 % The format of the CropToFitImage method is: 100 % 101 % MagickBooleanType CropToFitImage(Image **image, 102 % const double x_shear,const double x_shear, 103 % const double width,const double height, 104 % const MagickBooleanType rotate,ExceptionInfo *exception) 105 % 106 % A description of each parameter follows. 107 % 108 % o image: the image. 109 % 110 % o x_shear, y_shear, width, height: Defines a region of the image to crop. 111 % 112 % o exception: return any errors or warnings in this structure. 113 % 114 */ 115 static MagickBooleanType CropToFitImage(Image **image, 116 const double x_shear,const double y_shear, 117 const double width,const double height, 118 const MagickBooleanType rotate,ExceptionInfo *exception) 119 { 120 Image 121 *crop_image; 122 123 PointInfo 124 extent[4], 125 min, 126 max; 127 128 RectangleInfo 129 geometry, 130 page; 131 132 register ssize_t 133 i; 134 135 /* 136 Calculate the rotated image size. 137 */ 138 extent[0].x=(double) (-width/2.0); 139 extent[0].y=(double) (-height/2.0); 140 extent[1].x=(double) width/2.0; 141 extent[1].y=(double) (-height/2.0); 142 extent[2].x=(double) (-width/2.0); 143 extent[2].y=(double) height/2.0; 144 extent[3].x=(double) width/2.0; 145 extent[3].y=(double) height/2.0; 146 for (i=0; i < 4; i++) 147 { 148 extent[i].x+=x_shear*extent[i].y; 149 extent[i].y+=y_shear*extent[i].x; 150 if (rotate != MagickFalse) 151 extent[i].x+=x_shear*extent[i].y; 152 extent[i].x+=(double) (*image)->columns/2.0; 153 extent[i].y+=(double) (*image)->rows/2.0; 154 } 155 min=extent[0]; 156 max=extent[0]; 157 for (i=1; i < 4; i++) 158 { 159 if (min.x > extent[i].x) 160 min.x=extent[i].x; 161 if (min.y > extent[i].y) 162 min.y=extent[i].y; 163 if (max.x < extent[i].x) 164 max.x=extent[i].x; 165 if (max.y < extent[i].y) 166 max.y=extent[i].y; 167 } 168 geometry.x=(ssize_t) ceil(min.x-0.5); 169 geometry.y=(ssize_t) ceil(min.y-0.5); 170 geometry.width=(size_t) floor(max.x-min.x+0.5); 171 geometry.height=(size_t) floor(max.y-min.y+0.5); 172 page=(*image)->page; 173 (void) ParseAbsoluteGeometry("0x0+0+0",&(*image)->page); 174 crop_image=CropImage(*image,&geometry,exception); 175 if (crop_image == (Image *) NULL) 176 return(MagickFalse); 177 crop_image->page=page; 178 *image=DestroyImage(*image); 179 *image=crop_image; 180 return(MagickTrue); 181 } 182 183 /* 185 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 186 % % 187 % % 188 % % 189 % D e s k e w I m a g e % 190 % % 191 % % 192 % % 193 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 194 % 195 % DeskewImage() removes skew from the image. Skew is an artifact that 196 % occurs in scanned images because of the camera being misaligned, 197 % imperfections in the scanning or surface, or simply because the paper was 198 % not placed completely flat when scanned. 199 % 200 % The result will be auto-croped if the artifact "deskew:auto-crop" is 201 % defined, while the amount the image is to be deskewed, in degrees is also 202 % saved as the artifact "deskew:angle". 203 % 204 % If the artifact "deskew:auto-crop" is given the image will be automatically 205 % cropped of the excess background. The value is the border width of all 206 % pixels around the edge that will be used to determine an average border 207 % color for the automatic trim. 208 % 209 % The format of the DeskewImage method is: 210 % 211 % Image *DeskewImage(const Image *image,const double threshold, 212 % ExceptionInfo *exception) 213 % 214 % A description of each parameter follows: 215 % 216 % o image: the image. 217 % 218 % o threshold: separate background from foreground. 219 % 220 % o exception: return any errors or warnings in this structure. 221 % 222 */ 223 224 static void RadonProjection(const Image *image,MatrixInfo *source_matrixs, 225 MatrixInfo *destination_matrixs,const ssize_t sign,size_t *projection) 226 { 227 MatrixInfo 228 *swap; 229 230 register MatrixInfo 231 *p, 232 *q; 233 234 register ssize_t 235 x; 236 237 size_t 238 step; 239 240 p=source_matrixs; 241 q=destination_matrixs; 242 for (step=1; step < GetMatrixColumns(p); step*=2) 243 { 244 for (x=0; x < (ssize_t) GetMatrixColumns(p); x+=2*(ssize_t) step) 245 { 246 register ssize_t 247 i; 248 249 ssize_t 250 y; 251 252 unsigned short 253 element, 254 neighbor; 255 256 for (i=0; i < (ssize_t) step; i++) 257 { 258 for (y=0; y < (ssize_t) (GetMatrixRows(p)-i-1); y++) 259 { 260 if (GetMatrixElement(p,x+i,y,&element) == MagickFalse) 261 continue; 262 if (GetMatrixElement(p,x+i+step,y+i,&neighbor) == MagickFalse) 263 continue; 264 neighbor+=element; 265 if (SetMatrixElement(q,x+2*i,y,&neighbor) == MagickFalse) 266 continue; 267 if (GetMatrixElement(p,x+i+step,y+i+1,&neighbor) == MagickFalse) 268 continue; 269 neighbor+=element; 270 if (SetMatrixElement(q,x+2*i+1,y,&neighbor) == MagickFalse) 271 continue; 272 } 273 for ( ; y < (ssize_t) (GetMatrixRows(p)-i); y++) 274 { 275 if (GetMatrixElement(p,x+i,y,&element) == MagickFalse) 276 continue; 277 if (GetMatrixElement(p,x+i+step,y+i,&neighbor) == MagickFalse) 278 continue; 279 neighbor+=element; 280 if (SetMatrixElement(q,x+2*i,y,&neighbor) == MagickFalse) 281 continue; 282 if (SetMatrixElement(q,x+2*i+1,y,&element) == MagickFalse) 283 continue; 284 } 285 for ( ; y < (ssize_t) GetMatrixRows(p); y++) 286 { 287 if (GetMatrixElement(p,x+i,y,&element) == MagickFalse) 288 continue; 289 if (SetMatrixElement(q,x+2*i,y,&element) == MagickFalse) 290 continue; 291 if (SetMatrixElement(q,x+2*i+1,y,&element) == MagickFalse) 292 continue; 293 } 294 } 295 } 296 swap=p; 297 p=q; 298 q=swap; 299 } 300 #if defined(MAGICKCORE_OPENMP_SUPPORT) 301 #pragma omp parallel for schedule(static,4) \ 302 magick_threads(image,image,1,1) 303 #endif 304 for (x=0; x < (ssize_t) GetMatrixColumns(p); x++) 305 { 306 register ssize_t 307 y; 308 309 size_t 310 sum; 311 312 sum=0; 313 for (y=0; y < (ssize_t) (GetMatrixRows(p)-1); y++) 314 { 315 ssize_t 316 delta; 317 318 unsigned short 319 element, 320 neighbor; 321 322 if (GetMatrixElement(p,x,y,&element) == MagickFalse) 323 continue; 324 if (GetMatrixElement(p,x,y+1,&neighbor) == MagickFalse) 325 continue; 326 delta=(ssize_t) element-(ssize_t) neighbor; 327 sum+=delta*delta; 328 } 329 projection[GetMatrixColumns(p)+sign*x-1]=sum; 330 } 331 } 332 333 static MagickBooleanType RadonTransform(const Image *image, 334 const double threshold,size_t *projection,ExceptionInfo *exception) 335 { 336 CacheView 337 *image_view; 338 339 MatrixInfo 340 *destination_matrixs, 341 *source_matrixs; 342 343 MagickBooleanType 344 status; 345 346 size_t 347 count, 348 width; 349 350 ssize_t 351 j, 352 y; 353 354 unsigned char 355 c; 356 357 unsigned short 358 bits[256]; 359 360 for (width=1; width < ((image->columns+7)/8); width<<=1) ; 361 source_matrixs=AcquireMatrixInfo(width,image->rows,sizeof(unsigned short), 362 exception); 363 destination_matrixs=AcquireMatrixInfo(width,image->rows,sizeof(unsigned short), 364 exception); 365 if ((source_matrixs == (MatrixInfo *) NULL) || 366 (destination_matrixs == (MatrixInfo *) NULL)) 367 { 368 if (destination_matrixs != (MatrixInfo *) NULL) 369 destination_matrixs=DestroyMatrixInfo(destination_matrixs); 370 if (source_matrixs != (MatrixInfo *) NULL) 371 source_matrixs=DestroyMatrixInfo(source_matrixs); 372 return(MagickFalse); 373 } 374 if (NullMatrix(source_matrixs) == MagickFalse) 375 { 376 destination_matrixs=DestroyMatrixInfo(destination_matrixs); 377 source_matrixs=DestroyMatrixInfo(source_matrixs); 378 return(MagickFalse); 379 } 380 for (j=0; j < 256; j++) 381 { 382 c=(unsigned char) j; 383 for (count=0; c != 0; c>>=1) 384 count+=c & 0x01; 385 bits[j]=(unsigned short) count; 386 } 387 status=MagickTrue; 388 image_view=AcquireVirtualCacheView(image,exception); 389 #if defined(MAGICKCORE_OPENMP_SUPPORT) 390 #pragma omp parallel for schedule(static,4) shared(status) \ 391 magick_threads(image,image,1,1) 392 #endif 393 for (y=0; y < (ssize_t) image->rows; y++) 394 { 395 register const Quantum 396 *magick_restrict p; 397 398 register ssize_t 399 i, 400 x; 401 402 size_t 403 bit, 404 byte; 405 406 unsigned short 407 value; 408 409 if (status == MagickFalse) 410 continue; 411 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception); 412 if (p == (const Quantum *) NULL) 413 { 414 status=MagickFalse; 415 continue; 416 } 417 bit=0; 418 byte=0; 419 i=(ssize_t) (image->columns+7)/8; 420 for (x=0; x < (ssize_t) image->columns; x++) 421 { 422 byte<<=1; 423 if (((MagickRealType) GetPixelRed(image,p) < threshold) || 424 ((MagickRealType) GetPixelGreen(image,p) < threshold) || 425 ((MagickRealType) GetPixelBlue(image,p) < threshold)) 426 byte|=0x01; 427 bit++; 428 if (bit == 8) 429 { 430 value=bits[byte]; 431 (void) SetMatrixElement(source_matrixs,--i,y,&value); 432 bit=0; 433 byte=0; 434 } 435 p+=GetPixelChannels(image); 436 } 437 if (bit != 0) 438 { 439 byte<<=(8-bit); 440 value=bits[byte]; 441 (void) SetMatrixElement(source_matrixs,--i,y,&value); 442 } 443 } 444 RadonProjection(image,source_matrixs,destination_matrixs,-1,projection); 445 (void) NullMatrix(source_matrixs); 446 #if defined(MAGICKCORE_OPENMP_SUPPORT) 447 #pragma omp parallel for schedule(static,4) shared(status) \ 448 magick_threads(image,image,image->rows,1) 449 #endif 450 for (y=0; y < (ssize_t) image->rows; y++) 451 { 452 register const Quantum 453 *magick_restrict p; 454 455 register ssize_t 456 i, 457 x; 458 459 size_t 460 bit, 461 byte; 462 463 unsigned short 464 value; 465 466 if (status == MagickFalse) 467 continue; 468 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception); 469 if (p == (const Quantum *) NULL) 470 { 471 status=MagickFalse; 472 continue; 473 } 474 bit=0; 475 byte=0; 476 i=0; 477 for (x=0; x < (ssize_t) image->columns; x++) 478 { 479 byte<<=1; 480 if (((MagickRealType) GetPixelRed(image,p) < threshold) || 481 ((MagickRealType) GetPixelGreen(image,p) < threshold) || 482 ((MagickRealType) GetPixelBlue(image,p) < threshold)) 483 byte|=0x01; 484 bit++; 485 if (bit == 8) 486 { 487 value=bits[byte]; 488 (void) SetMatrixElement(source_matrixs,i++,y,&value); 489 bit=0; 490 byte=0; 491 } 492 p+=GetPixelChannels(image); 493 } 494 if (bit != 0) 495 { 496 byte<<=(8-bit); 497 value=bits[byte]; 498 (void) SetMatrixElement(source_matrixs,i++,y,&value); 499 } 500 } 501 RadonProjection(image,source_matrixs,destination_matrixs,1,projection); 502 image_view=DestroyCacheView(image_view); 503 destination_matrixs=DestroyMatrixInfo(destination_matrixs); 504 source_matrixs=DestroyMatrixInfo(source_matrixs); 505 return(MagickTrue); 506 } 507 508 static void GetImageBackgroundColor(Image *image,const ssize_t offset, 509 ExceptionInfo *exception) 510 { 511 CacheView 512 *image_view; 513 514 PixelInfo 515 background; 516 517 double 518 count; 519 520 ssize_t 521 y; 522 523 /* 524 Compute average background color. 525 */ 526 if (offset <= 0) 527 return; 528 GetPixelInfo(image,&background); 529 count=0.0; 530 image_view=AcquireVirtualCacheView(image,exception); 531 for (y=0; y < (ssize_t) image->rows; y++) 532 { 533 register const Quantum 534 *magick_restrict p; 535 536 register ssize_t 537 x; 538 539 if ((y >= offset) && (y < ((ssize_t) image->rows-offset))) 540 continue; 541 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception); 542 if (p == (const Quantum *) NULL) 543 continue; 544 for (x=0; x < (ssize_t) image->columns; x++) 545 { 546 if ((x >= offset) && (x < ((ssize_t) image->columns-offset))) 547 continue; 548 background.red+=QuantumScale*GetPixelRed(image,p); 549 background.green+=QuantumScale*GetPixelGreen(image,p); 550 background.blue+=QuantumScale*GetPixelBlue(image,p); 551 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) 552 background.alpha+=QuantumScale*GetPixelAlpha(image,p); 553 count++; 554 p+=GetPixelChannels(image); 555 } 556 } 557 image_view=DestroyCacheView(image_view); 558 image->background_color.red=(double) ClampToQuantum(QuantumRange* 559 background.red/count); 560 image->background_color.green=(double) ClampToQuantum(QuantumRange* 561 background.green/count); 562 image->background_color.blue=(double) ClampToQuantum(QuantumRange* 563 background.blue/count); 564 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) 565 image->background_color.alpha=(double) ClampToQuantum(QuantumRange* 566 background.alpha/count); 567 } 568 569 MagickExport Image *DeskewImage(const Image *image,const double threshold, 570 ExceptionInfo *exception) 571 { 572 AffineMatrix 573 affine_matrix; 574 575 const char 576 *artifact; 577 578 double 579 degrees; 580 581 Image 582 *clone_image, 583 *crop_image, 584 *deskew_image, 585 *median_image; 586 587 MagickBooleanType 588 status; 589 590 RectangleInfo 591 geometry; 592 593 register ssize_t 594 i; 595 596 size_t 597 max_projection, 598 *projection, 599 width; 600 601 ssize_t 602 skew; 603 604 /* 605 Compute deskew angle. 606 */ 607 for (width=1; width < ((image->columns+7)/8); width<<=1) ; 608 projection=(size_t *) AcquireQuantumMemory((size_t) (2*width-1), 609 sizeof(*projection)); 610 if (projection == (size_t *) NULL) 611 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed"); 612 status=RadonTransform(image,threshold,projection,exception); 613 if (status == MagickFalse) 614 { 615 projection=(size_t *) RelinquishMagickMemory(projection); 616 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed"); 617 } 618 max_projection=0; 619 skew=0; 620 for (i=0; i < (ssize_t) (2*width-1); i++) 621 { 622 if (projection[i] > max_projection) 623 { 624 skew=i-(ssize_t) width+1; 625 max_projection=projection[i]; 626 } 627 } 628 projection=(size_t *) RelinquishMagickMemory(projection); 629 degrees=RadiansToDegrees(-atan((double) skew/width/8)); 630 if (image->debug != MagickFalse) 631 (void) LogMagickEvent(TransformEvent,GetMagickModule(), 632 " Deskew angle: %g",degrees); 633 /* 634 Deskew image. 635 */ 636 clone_image=CloneImage(image,0,0,MagickTrue,exception); 637 if (clone_image == (Image *) NULL) 638 return((Image *) NULL); 639 { 640 char 641 angle[MagickPathExtent]; 642 643 (void) FormatLocaleString(angle,MagickPathExtent,"%.20g",degrees); 644 (void) SetImageArtifact(clone_image,"deskew:angle",angle); 645 } 646 (void) SetImageVirtualPixelMethod(clone_image,BackgroundVirtualPixelMethod, 647 exception); 648 affine_matrix.sx=cos(DegreesToRadians(fmod((double) degrees,360.0))); 649 affine_matrix.rx=sin(DegreesToRadians(fmod((double) degrees,360.0))); 650 affine_matrix.ry=(-sin(DegreesToRadians(fmod((double) degrees,360.0)))); 651 affine_matrix.sy=cos(DegreesToRadians(fmod((double) degrees,360.0))); 652 affine_matrix.tx=0.0; 653 affine_matrix.ty=0.0; 654 artifact=GetImageArtifact(image,"deskew:auto-crop"); 655 if (IsStringTrue(artifact) == MagickFalse) 656 { 657 deskew_image=AffineTransformImage(clone_image,&affine_matrix,exception); 658 clone_image=DestroyImage(clone_image); 659 return(deskew_image); 660 } 661 /* 662 Auto-crop image. 663 */ 664 GetImageBackgroundColor(clone_image,(ssize_t) StringToLong(artifact), 665 exception); 666 deskew_image=AffineTransformImage(clone_image,&affine_matrix,exception); 667 clone_image=DestroyImage(clone_image); 668 if (deskew_image == (Image *) NULL) 669 return((Image *) NULL); 670 median_image=StatisticImage(deskew_image,MedianStatistic,3,3,exception); 671 if (median_image == (Image *) NULL) 672 { 673 deskew_image=DestroyImage(deskew_image); 674 return((Image *) NULL); 675 } 676 geometry=GetImageBoundingBox(median_image,exception); 677 median_image=DestroyImage(median_image); 678 if (image->debug != MagickFalse) 679 (void) LogMagickEvent(TransformEvent,GetMagickModule()," Deskew geometry: " 680 "%.20gx%.20g%+.20g%+.20g",(double) geometry.width,(double) 681 geometry.height,(double) geometry.x,(double) geometry.y); 682 crop_image=CropImage(deskew_image,&geometry,exception); 683 deskew_image=DestroyImage(deskew_image); 684 return(crop_image); 685 } 686 687 /* 689 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 690 % % 691 % % 692 % % 693 % I n t e g r a l R o t a t e I m a g e % 694 % % 695 % % 696 % % 697 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 698 % 699 % IntegralRotateImage() rotates the image an integral of 90 degrees. It 700 % allocates the memory necessary for the new Image structure and returns a 701 % pointer to the rotated image. 702 % 703 % The format of the IntegralRotateImage method is: 704 % 705 % Image *IntegralRotateImage(const Image *image,size_t rotations, 706 % ExceptionInfo *exception) 707 % 708 % A description of each parameter follows. 709 % 710 % o image: the image. 711 % 712 % o rotations: Specifies the number of 90 degree rotations. 713 % 714 */ 715 MagickExport Image *IntegralRotateImage(const Image *image,size_t rotations, 716 ExceptionInfo *exception) 717 { 718 #define RotateImageTag "Rotate/Image" 719 720 CacheView 721 *image_view, 722 *rotate_view; 723 724 Image 725 *rotate_image; 726 727 MagickBooleanType 728 status; 729 730 MagickOffsetType 731 progress; 732 733 RectangleInfo 734 page; 735 736 /* 737 Initialize rotated image attributes. 738 */ 739 assert(image != (Image *) NULL); 740 page=image->page; 741 rotations%=4; 742 if (rotations == 0) 743 return(CloneImage(image,0,0,MagickTrue,exception)); 744 if ((rotations == 1) || (rotations == 3)) 745 rotate_image=CloneImage(image,image->rows,image->columns,MagickTrue, 746 exception); 747 else 748 rotate_image=CloneImage(image,image->columns,image->rows,MagickTrue, 749 exception); 750 if (rotate_image == (Image *) NULL) 751 return((Image *) NULL); 752 /* 753 Integral rotate the image. 754 */ 755 status=MagickTrue; 756 progress=0; 757 image_view=AcquireVirtualCacheView(image,exception); 758 rotate_view=AcquireAuthenticCacheView(rotate_image,exception); 759 switch (rotations) 760 { 761 case 1: 762 { 763 size_t 764 tile_height, 765 tile_width; 766 767 ssize_t 768 tile_y; 769 770 /* 771 Rotate 90 degrees. 772 */ 773 GetPixelCacheTileSize(image,&tile_width,&tile_height); 774 tile_width=image->columns; 775 #if defined(MAGICKCORE_OPENMP_SUPPORT) 776 #pragma omp parallel for schedule(static,4) shared(status) \ 777 magick_threads(image,image,1,1) 778 #endif 779 for (tile_y=0; tile_y < (ssize_t) image->rows; tile_y+=(ssize_t) tile_height) 780 { 781 register ssize_t 782 tile_x; 783 784 if (status == MagickFalse) 785 continue; 786 tile_x=0; 787 for ( ; tile_x < (ssize_t) image->columns; tile_x+=(ssize_t) tile_width) 788 { 789 MagickBooleanType 790 sync; 791 792 register const Quantum 793 *magick_restrict p; 794 795 register Quantum 796 *magick_restrict q; 797 798 register ssize_t 799 y; 800 801 size_t 802 height, 803 width; 804 805 width=tile_width; 806 if ((tile_x+(ssize_t) tile_width) > (ssize_t) image->columns) 807 width=(size_t) (tile_width-(tile_x+tile_width-image->columns)); 808 height=tile_height; 809 if ((tile_y+(ssize_t) tile_height) > (ssize_t) image->rows) 810 height=(size_t) (tile_height-(tile_y+tile_height-image->rows)); 811 p=GetCacheViewVirtualPixels(image_view,tile_x,tile_y,width,height, 812 exception); 813 if (p == (const Quantum *) NULL) 814 { 815 status=MagickFalse; 816 break; 817 } 818 for (y=0; y < (ssize_t) width; y++) 819 { 820 register const Quantum 821 *magick_restrict tile_pixels; 822 823 register ssize_t 824 x; 825 826 if (status == MagickFalse) 827 continue; 828 q=QueueCacheViewAuthenticPixels(rotate_view,(ssize_t) 829 (rotate_image->columns-(tile_y+height)),y+tile_x,height,1, 830 exception); 831 if (q == (Quantum *) NULL) 832 { 833 status=MagickFalse; 834 continue; 835 } 836 tile_pixels=p+((height-1)*width+y)*GetPixelChannels(image); 837 for (x=0; x < (ssize_t) height; x++) 838 { 839 register ssize_t 840 i; 841 842 if (GetPixelReadMask(image,tile_pixels) == 0) 843 { 844 tile_pixels-=width*GetPixelChannels(image); 845 q+=GetPixelChannels(rotate_image); 846 continue; 847 } 848 for (i=0; i < (ssize_t) GetPixelChannels(image); i++) 849 { 850 PixelChannel channel=GetPixelChannelChannel(image,i); 851 PixelTrait traits=GetPixelChannelTraits(image,channel); 852 PixelTrait rotate_traits=GetPixelChannelTraits(rotate_image, 853 channel); 854 if ((traits == UndefinedPixelTrait) || 855 (rotate_traits == UndefinedPixelTrait)) 856 continue; 857 SetPixelChannel(rotate_image,channel,tile_pixels[i],q); 858 } 859 tile_pixels-=width*GetPixelChannels(image); 860 q+=GetPixelChannels(rotate_image); 861 } 862 sync=SyncCacheViewAuthenticPixels(rotate_view,exception); 863 if (sync == MagickFalse) 864 status=MagickFalse; 865 } 866 } 867 if (image->progress_monitor != (MagickProgressMonitor) NULL) 868 { 869 MagickBooleanType 870 proceed; 871 872 #if defined(MAGICKCORE_OPENMP_SUPPORT) 873 #pragma omp critical (MagickCore_IntegralRotateImage) 874 #endif 875 proceed=SetImageProgress(image,RotateImageTag,progress+=tile_height, 876 image->rows); 877 if (proceed == MagickFalse) 878 status=MagickFalse; 879 } 880 } 881 (void) SetImageProgress(image,RotateImageTag,(MagickOffsetType) 882 image->rows-1,image->rows); 883 Swap(page.width,page.height); 884 Swap(page.x,page.y); 885 if (page.width != 0) 886 page.x=(ssize_t) (page.width-rotate_image->columns-page.x); 887 break; 888 } 889 case 2: 890 { 891 register ssize_t 892 y; 893 894 /* 895 Rotate 180 degrees. 896 */ 897 #if defined(MAGICKCORE_OPENMP_SUPPORT) 898 #pragma omp parallel for schedule(static,4) shared(status) \ 899 magick_threads(image,image,1,1) 900 #endif 901 for (y=0; y < (ssize_t) image->rows; y++) 902 { 903 MagickBooleanType 904 sync; 905 906 register const Quantum 907 *magick_restrict p; 908 909 register Quantum 910 *magick_restrict q; 911 912 register ssize_t 913 x; 914 915 if (status == MagickFalse) 916 continue; 917 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception); 918 q=QueueCacheViewAuthenticPixels(rotate_view,0,(ssize_t) (image->rows-y- 919 1),image->columns,1,exception); 920 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL)) 921 { 922 status=MagickFalse; 923 continue; 924 } 925 q+=GetPixelChannels(rotate_image)*image->columns; 926 for (x=0; x < (ssize_t) image->columns; x++) 927 { 928 register ssize_t 929 i; 930 931 q-=GetPixelChannels(rotate_image); 932 if (GetPixelReadMask(image,p) == 0) 933 { 934 p+=GetPixelChannels(image); 935 continue; 936 } 937 for (i=0; i < (ssize_t) GetPixelChannels(image); i++) 938 { 939 PixelChannel channel=GetPixelChannelChannel(image,i); 940 PixelTrait traits=GetPixelChannelTraits(image,channel); 941 PixelTrait rotate_traits=GetPixelChannelTraits(rotate_image, 942 channel); 943 if ((traits == UndefinedPixelTrait) || 944 (rotate_traits == UndefinedPixelTrait)) 945 continue; 946 SetPixelChannel(rotate_image,channel,p[i],q); 947 } 948 p+=GetPixelChannels(image); 949 } 950 sync=SyncCacheViewAuthenticPixels(rotate_view,exception); 951 if (sync == MagickFalse) 952 status=MagickFalse; 953 if (image->progress_monitor != (MagickProgressMonitor) NULL) 954 { 955 MagickBooleanType 956 proceed; 957 958 #if defined(MAGICKCORE_OPENMP_SUPPORT) 959 #pragma omp critical (MagickCore_IntegralRotateImage) 960 #endif 961 proceed=SetImageProgress(image,RotateImageTag,progress++, 962 image->rows); 963 if (proceed == MagickFalse) 964 status=MagickFalse; 965 } 966 } 967 (void) SetImageProgress(image,RotateImageTag,(MagickOffsetType) 968 image->rows-1,image->rows); 969 Swap(page.width,page.height); 970 Swap(page.x,page.y); 971 if (page.width != 0) 972 page.x=(ssize_t) (page.width-rotate_image->columns-page.x); 973 break; 974 } 975 case 3: 976 { 977 size_t 978 tile_height, 979 tile_width; 980 981 ssize_t 982 tile_y; 983 984 /* 985 Rotate 270 degrees. 986 */ 987 GetPixelCacheTileSize(image,&tile_width,&tile_height); 988 tile_width=image->columns; 989 #if defined(MAGICKCORE_OPENMP_SUPPORT) 990 #pragma omp parallel for schedule(static,4) shared(status) \ 991 magick_threads(image,image,1,1) 992 #endif 993 for (tile_y=0; tile_y < (ssize_t) image->rows; tile_y+=(ssize_t) tile_height) 994 { 995 register ssize_t 996 tile_x; 997 998 if (status == MagickFalse) 999 continue; 1000 tile_x=0; 1001 for ( ; tile_x < (ssize_t) image->columns; tile_x+=(ssize_t) tile_width) 1002 { 1003 MagickBooleanType 1004 sync; 1005 1006 register const Quantum 1007 *magick_restrict p; 1008 1009 register Quantum 1010 *magick_restrict q; 1011 1012 register ssize_t 1013 y; 1014 1015 size_t 1016 height, 1017 width; 1018 1019 width=tile_width; 1020 if ((tile_x+(ssize_t) tile_width) > (ssize_t) image->columns) 1021 width=(size_t) (tile_width-(tile_x+tile_width-image->columns)); 1022 height=tile_height; 1023 if ((tile_y+(ssize_t) tile_height) > (ssize_t) image->rows) 1024 height=(size_t) (tile_height-(tile_y+tile_height-image->rows)); 1025 p=GetCacheViewVirtualPixels(image_view,tile_x,tile_y,width,height, 1026 exception); 1027 if (p == (const Quantum *) NULL) 1028 { 1029 status=MagickFalse; 1030 break; 1031 } 1032 for (y=0; y < (ssize_t) width; y++) 1033 { 1034 register const Quantum 1035 *magick_restrict tile_pixels; 1036 1037 register ssize_t 1038 x; 1039 1040 if (status == MagickFalse) 1041 continue; 1042 q=QueueCacheViewAuthenticPixels(rotate_view,tile_y,(ssize_t) (y+ 1043 rotate_image->rows-(tile_x+width)),height,1,exception); 1044 if (q == (Quantum *) NULL) 1045 { 1046 status=MagickFalse; 1047 continue; 1048 } 1049 tile_pixels=p+((width-1)-y)*GetPixelChannels(image); 1050 for (x=0; x < (ssize_t) height; x++) 1051 { 1052 register ssize_t 1053 i; 1054 1055 if (GetPixelReadMask(image,tile_pixels) == 0) 1056 { 1057 tile_pixels+=width*GetPixelChannels(image); 1058 q+=GetPixelChannels(rotate_image); 1059 continue; 1060 } 1061 for (i=0; i < (ssize_t) GetPixelChannels(image); i++) 1062 { 1063 PixelChannel channel=GetPixelChannelChannel(image,i); 1064 PixelTrait traits=GetPixelChannelTraits(image,channel); 1065 PixelTrait rotate_traits=GetPixelChannelTraits(rotate_image, 1066 channel); 1067 if ((traits == UndefinedPixelTrait) || 1068 (rotate_traits == UndefinedPixelTrait)) 1069 continue; 1070 SetPixelChannel(rotate_image,channel,tile_pixels[i],q); 1071 } 1072 tile_pixels+=width*GetPixelChannels(image); 1073 q+=GetPixelChannels(rotate_image); 1074 } 1075 #if defined(MAGICKCORE_OPENMP_SUPPORT) 1076 #pragma omp critical (MagickCore_IntegralRotateImage) 1077 #endif 1078 sync=SyncCacheViewAuthenticPixels(rotate_view,exception); 1079 if (sync == MagickFalse) 1080 status=MagickFalse; 1081 } 1082 } 1083 if (image->progress_monitor != (MagickProgressMonitor) NULL) 1084 { 1085 MagickBooleanType 1086 proceed; 1087 1088 proceed=SetImageProgress(image,RotateImageTag,progress+=tile_height, 1089 image->rows); 1090 if (proceed == MagickFalse) 1091 status=MagickFalse; 1092 } 1093 } 1094 (void) SetImageProgress(image,RotateImageTag,(MagickOffsetType) 1095 image->rows-1,image->rows); 1096 Swap(page.width,page.height); 1097 Swap(page.x,page.y); 1098 if (page.width != 0) 1099 page.x=(ssize_t) (page.width-rotate_image->columns-page.x); 1100 break; 1101 } 1102 default: 1103 break; 1104 } 1105 rotate_view=DestroyCacheView(rotate_view); 1106 image_view=DestroyCacheView(image_view); 1107 rotate_image->type=image->type; 1108 rotate_image->page=page; 1109 if (status == MagickFalse) 1110 rotate_image=DestroyImage(rotate_image); 1111 return(rotate_image); 1112 } 1113 1114 /* 1116 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 1117 % % 1118 % % 1119 % % 1120 + X S h e a r I m a g e % 1121 % % 1122 % % 1123 % % 1124 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 1125 % 1126 % XShearImage() shears the image in the X direction with a shear angle of 1127 % 'degrees'. Positive angles shear counter-clockwise (right-hand rule), and 1128 % negative angles shear clockwise. Angles are measured relative to a vertical 1129 % Y-axis. X shears will widen an image creating 'empty' triangles on the left 1130 % and right sides of the source image. 1131 % 1132 % The format of the XShearImage method is: 1133 % 1134 % MagickBooleanType XShearImage(Image *image,const double degrees, 1135 % const size_t width,const size_t height, 1136 % const ssize_t x_offset,const ssize_t y_offset,ExceptionInfo *exception) 1137 % 1138 % A description of each parameter follows. 1139 % 1140 % o image: the image. 1141 % 1142 % o degrees: A double representing the shearing angle along the X 1143 % axis. 1144 % 1145 % o width, height, x_offset, y_offset: Defines a region of the image 1146 % to shear. 1147 % 1148 % o exception: return any errors or warnings in this structure. 1149 % 1150 */ 1151 static MagickBooleanType XShearImage(Image *image,const double degrees, 1152 const size_t width,const size_t height,const ssize_t x_offset, 1153 const ssize_t y_offset,ExceptionInfo *exception) 1154 { 1155 #define XShearImageTag "XShear/Image" 1156 1157 typedef enum 1158 { 1159 LEFT, 1160 RIGHT 1161 } ShearDirection; 1162 1163 CacheView 1164 *image_view; 1165 1166 MagickBooleanType 1167 status; 1168 1169 MagickOffsetType 1170 progress; 1171 1172 PixelInfo 1173 background; 1174 1175 ssize_t 1176 y; 1177 1178 /* 1179 X shear image. 1180 */ 1181 assert(image != (Image *) NULL); 1182 assert(image->signature == MagickCoreSignature); 1183 if (image->debug != MagickFalse) 1184 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename); 1185 status=MagickTrue; 1186 background=image->background_color; 1187 progress=0; 1188 image_view=AcquireAuthenticCacheView(image,exception); 1189 #if defined(MAGICKCORE_OPENMP_SUPPORT) 1190 #pragma omp parallel for schedule(static,4) shared(progress,status) \ 1191 magick_threads(image,image,height,1) 1192 #endif 1193 for (y=0; y < (ssize_t) height; y++) 1194 { 1195 PixelInfo 1196 pixel, 1197 source, 1198 destination; 1199 1200 double 1201 area, 1202 displacement; 1203 1204 register Quantum 1205 *magick_restrict p, 1206 *magick_restrict q; 1207 1208 register ssize_t 1209 i; 1210 1211 ShearDirection 1212 direction; 1213 1214 ssize_t 1215 step; 1216 1217 if (status == MagickFalse) 1218 continue; 1219 p=GetCacheViewAuthenticPixels(image_view,0,y_offset+y,image->columns,1, 1220 exception); 1221 if (p == (Quantum *) NULL) 1222 { 1223 status=MagickFalse; 1224 continue; 1225 } 1226 p+=x_offset*GetPixelChannels(image); 1227 displacement=degrees*(double) (y-height/2.0); 1228 if (displacement == 0.0) 1229 continue; 1230 if (displacement > 0.0) 1231 direction=RIGHT; 1232 else 1233 { 1234 displacement*=(-1.0); 1235 direction=LEFT; 1236 } 1237 step=(ssize_t) floor((double) displacement); 1238 area=(double) (displacement-step); 1239 step++; 1240 pixel=background; 1241 GetPixelInfo(image,&source); 1242 GetPixelInfo(image,&destination); 1243 switch (direction) 1244 { 1245 case LEFT: 1246 { 1247 /* 1248 Transfer pixels left-to-right. 1249 */ 1250 if (step > x_offset) 1251 break; 1252 q=p-step*GetPixelChannels(image); 1253 for (i=0; i < (ssize_t) width; i++) 1254 { 1255 if ((x_offset+i) < step) 1256 { 1257 p+=GetPixelChannels(image); 1258 GetPixelInfoPixel(image,p,&pixel); 1259 q+=GetPixelChannels(image); 1260 continue; 1261 } 1262 GetPixelInfoPixel(image,p,&source); 1263 CompositePixelInfoAreaBlend(&pixel,(double) pixel.alpha, 1264 &source,(double) GetPixelAlpha(image,p),area,&destination); 1265 SetPixelViaPixelInfo(image,&destination,q); 1266 GetPixelInfoPixel(image,p,&pixel); 1267 p+=GetPixelChannels(image); 1268 q+=GetPixelChannels(image); 1269 } 1270 CompositePixelInfoAreaBlend(&pixel,(double) pixel.alpha, 1271 &background,(double) background.alpha,area,&destination); 1272 SetPixelViaPixelInfo(image,&destination,q); 1273 q+=GetPixelChannels(image); 1274 for (i=0; i < (step-1); i++) 1275 { 1276 SetPixelViaPixelInfo(image,&background,q); 1277 q+=GetPixelChannels(image); 1278 } 1279 break; 1280 } 1281 case RIGHT: 1282 { 1283 /* 1284 Transfer pixels right-to-left. 1285 */ 1286 p+=width*GetPixelChannels(image); 1287 q=p+step*GetPixelChannels(image); 1288 for (i=0; i < (ssize_t) width; i++) 1289 { 1290 p-=GetPixelChannels(image); 1291 q-=GetPixelChannels(image); 1292 if ((size_t) (x_offset+width+step-i) > image->columns) 1293 continue; 1294 GetPixelInfoPixel(image,p,&source); 1295 CompositePixelInfoAreaBlend(&pixel,(double) pixel.alpha, 1296 &source,(double) GetPixelAlpha(image,p),area,&destination); 1297 SetPixelViaPixelInfo(image,&destination,q); 1298 GetPixelInfoPixel(image,p,&pixel); 1299 } 1300 CompositePixelInfoAreaBlend(&pixel,(double) pixel.alpha, 1301 &background,(double) background.alpha,area,&destination); 1302 q-=GetPixelChannels(image); 1303 SetPixelViaPixelInfo(image,&destination,q); 1304 for (i=0; i < (step-1); i++) 1305 { 1306 q-=GetPixelChannels(image); 1307 SetPixelViaPixelInfo(image,&background,q); 1308 } 1309 break; 1310 } 1311 } 1312 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse) 1313 status=MagickFalse; 1314 if (image->progress_monitor != (MagickProgressMonitor) NULL) 1315 { 1316 MagickBooleanType 1317 proceed; 1318 1319 #if defined(MAGICKCORE_OPENMP_SUPPORT) 1320 #pragma omp critical (MagickCore_XShearImage) 1321 #endif 1322 proceed=SetImageProgress(image,XShearImageTag,progress++,height); 1323 if (proceed == MagickFalse) 1324 status=MagickFalse; 1325 } 1326 } 1327 image_view=DestroyCacheView(image_view); 1328 return(status); 1329 } 1330 1331 /* 1333 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 1334 % % 1335 % % 1336 % % 1337 + Y S h e a r I m a g e % 1338 % % 1339 % % 1340 % % 1341 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 1342 % 1343 % YShearImage shears the image in the Y direction with a shear angle of 1344 % 'degrees'. Positive angles shear counter-clockwise (right-hand rule), and 1345 % negative angles shear clockwise. Angles are measured relative to a 1346 % horizontal X-axis. Y shears will increase the height of an image creating 1347 % 'empty' triangles on the top and bottom of the source image. 1348 % 1349 % The format of the YShearImage method is: 1350 % 1351 % MagickBooleanType YShearImage(Image *image,const double degrees, 1352 % const size_t width,const size_t height, 1353 % const ssize_t x_offset,const ssize_t y_offset,ExceptionInfo *exception) 1354 % 1355 % A description of each parameter follows. 1356 % 1357 % o image: the image. 1358 % 1359 % o degrees: A double representing the shearing angle along the Y 1360 % axis. 1361 % 1362 % o width, height, x_offset, y_offset: Defines a region of the image 1363 % to shear. 1364 % 1365 % o exception: return any errors or warnings in this structure. 1366 % 1367 */ 1368 static MagickBooleanType YShearImage(Image *image,const double degrees, 1369 const size_t width,const size_t height,const ssize_t x_offset, 1370 const ssize_t y_offset,ExceptionInfo *exception) 1371 { 1372 #define YShearImageTag "YShear/Image" 1373 1374 typedef enum 1375 { 1376 UP, 1377 DOWN 1378 } ShearDirection; 1379 1380 CacheView 1381 *image_view; 1382 1383 MagickBooleanType 1384 status; 1385 1386 MagickOffsetType 1387 progress; 1388 1389 PixelInfo 1390 background; 1391 1392 ssize_t 1393 x; 1394 1395 /* 1396 Y Shear image. 1397 */ 1398 assert(image != (Image *) NULL); 1399 assert(image->signature == MagickCoreSignature); 1400 if (image->debug != MagickFalse) 1401 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename); 1402 status=MagickTrue; 1403 progress=0; 1404 background=image->background_color; 1405 image_view=AcquireAuthenticCacheView(image,exception); 1406 #if defined(MAGICKCORE_OPENMP_SUPPORT) 1407 #pragma omp parallel for schedule(static,4) shared(progress,status) \ 1408 magick_threads(image,image,width,1) 1409 #endif 1410 for (x=0; x < (ssize_t) width; x++) 1411 { 1412 ssize_t 1413 step; 1414 1415 double 1416 area, 1417 displacement; 1418 1419 PixelInfo 1420 pixel, 1421 source, 1422 destination; 1423 1424 register Quantum 1425 *magick_restrict p, 1426 *magick_restrict q; 1427 1428 register ssize_t 1429 i; 1430 1431 ShearDirection 1432 direction; 1433 1434 if (status == MagickFalse) 1435 continue; 1436 p=GetCacheViewAuthenticPixels(image_view,x_offset+x,0,1,image->rows, 1437 exception); 1438 if (p == (Quantum *) NULL) 1439 { 1440 status=MagickFalse; 1441 continue; 1442 } 1443 p+=y_offset*GetPixelChannels(image); 1444 displacement=degrees*(double) (x-width/2.0); 1445 if (displacement == 0.0) 1446 continue; 1447 if (displacement > 0.0) 1448 direction=DOWN; 1449 else 1450 { 1451 displacement*=(-1.0); 1452 direction=UP; 1453 } 1454 step=(ssize_t) floor((double) displacement); 1455 area=(double) (displacement-step); 1456 step++; 1457 pixel=background; 1458 GetPixelInfo(image,&source); 1459 GetPixelInfo(image,&destination); 1460 switch (direction) 1461 { 1462 case UP: 1463 { 1464 /* 1465 Transfer pixels top-to-bottom. 1466 */ 1467 if (step > y_offset) 1468 break; 1469 q=p-step*GetPixelChannels(image); 1470 for (i=0; i < (ssize_t) height; i++) 1471 { 1472 if ((y_offset+i) < step) 1473 { 1474 p+=GetPixelChannels(image); 1475 GetPixelInfoPixel(image,p,&pixel); 1476 q+=GetPixelChannels(image); 1477 continue; 1478 } 1479 GetPixelInfoPixel(image,p,&source); 1480 CompositePixelInfoAreaBlend(&pixel,(double) pixel.alpha, 1481 &source,(double) GetPixelAlpha(image,p),area, 1482 &destination); 1483 SetPixelViaPixelInfo(image,&destination,q); 1484 GetPixelInfoPixel(image,p,&pixel); 1485 p+=GetPixelChannels(image); 1486 q+=GetPixelChannels(image); 1487 } 1488 CompositePixelInfoAreaBlend(&pixel,(double) pixel.alpha, 1489 &background,(double) background.alpha,area,&destination); 1490 SetPixelViaPixelInfo(image,&destination,q); 1491 q+=GetPixelChannels(image); 1492 for (i=0; i < (step-1); i++) 1493 { 1494 SetPixelViaPixelInfo(image,&background,q); 1495 q+=GetPixelChannels(image); 1496 } 1497 break; 1498 } 1499 case DOWN: 1500 { 1501 /* 1502 Transfer pixels bottom-to-top. 1503 */ 1504 p+=height*GetPixelChannels(image); 1505 q=p+step*GetPixelChannels(image); 1506 for (i=0; i < (ssize_t) height; i++) 1507 { 1508 p-=GetPixelChannels(image); 1509 q-=GetPixelChannels(image); 1510 if ((size_t) (y_offset+height+step-i) > image->rows) 1511 continue; 1512 GetPixelInfoPixel(image,p,&source); 1513 CompositePixelInfoAreaBlend(&pixel,(double) pixel.alpha, 1514 &source,(double) GetPixelAlpha(image,p),area, 1515 &destination); 1516 SetPixelViaPixelInfo(image,&destination,q); 1517 GetPixelInfoPixel(image,p,&pixel); 1518 } 1519 CompositePixelInfoAreaBlend(&pixel,(double) pixel.alpha, 1520 &background,(double) background.alpha,area,&destination); 1521 q-=GetPixelChannels(image); 1522 SetPixelViaPixelInfo(image,&destination,q); 1523 for (i=0; i < (step-1); i++) 1524 { 1525 q-=GetPixelChannels(image); 1526 SetPixelViaPixelInfo(image,&background,q); 1527 } 1528 break; 1529 } 1530 } 1531 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse) 1532 status=MagickFalse; 1533 if (image->progress_monitor != (MagickProgressMonitor) NULL) 1534 { 1535 MagickBooleanType 1536 proceed; 1537 1538 #if defined(MAGICKCORE_OPENMP_SUPPORT) 1539 #pragma omp critical (MagickCore_YShearImage) 1540 #endif 1541 proceed=SetImageProgress(image,YShearImageTag,progress++,image->rows); 1542 if (proceed == MagickFalse) 1543 status=MagickFalse; 1544 } 1545 } 1546 image_view=DestroyCacheView(image_view); 1547 return(status); 1548 } 1549 1550 /* 1552 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 1553 % % 1554 % % 1555 % % 1556 % S h e a r I m a g e % 1557 % % 1558 % % 1559 % % 1560 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 1561 % 1562 % ShearImage() creates a new image that is a shear_image copy of an existing 1563 % one. Shearing slides one edge of an image along the X or Y axis, creating 1564 % a parallelogram. An X direction shear slides an edge along the X axis, 1565 % while a Y direction shear slides an edge along the Y axis. The amount of 1566 % the shear is controlled by a shear angle. For X direction shears, x_shear 1567 % is measured relative to the Y axis, and similarly, for Y direction shears 1568 % y_shear is measured relative to the X axis. Empty triangles left over from 1569 % shearing the image are filled with the background color defined by member 1570 % 'background_color' of the image.. ShearImage() allocates the memory 1571 % necessary for the new Image structure and returns a pointer to the new image. 1572 % 1573 % ShearImage() is based on the paper "A Fast Algorithm for General Raster 1574 % Rotatation" by Alan W. Paeth. 1575 % 1576 % The format of the ShearImage method is: 1577 % 1578 % Image *ShearImage(const Image *image,const double x_shear, 1579 % const double y_shear,ExceptionInfo *exception) 1580 % 1581 % A description of each parameter follows. 1582 % 1583 % o image: the image. 1584 % 1585 % o x_shear, y_shear: Specifies the number of degrees to shear the image. 1586 % 1587 % o exception: return any errors or warnings in this structure. 1588 % 1589 */ 1590 MagickExport Image *ShearImage(const Image *image,const double x_shear, 1591 const double y_shear,ExceptionInfo *exception) 1592 { 1593 Image 1594 *integral_image, 1595 *shear_image; 1596 1597 MagickBooleanType 1598 status; 1599 1600 PointInfo 1601 shear; 1602 1603 RectangleInfo 1604 border_info, 1605 bounds; 1606 1607 assert(image != (Image *) NULL); 1608 assert(image->signature == MagickCoreSignature); 1609 if (image->debug != MagickFalse) 1610 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename); 1611 assert(exception != (ExceptionInfo *) NULL); 1612 assert(exception->signature == MagickCoreSignature); 1613 if ((x_shear != 0.0) && (fmod(x_shear,90.0) == 0.0)) 1614 ThrowImageException(ImageError,"AngleIsDiscontinuous"); 1615 if ((y_shear != 0.0) && (fmod(y_shear,90.0) == 0.0)) 1616 ThrowImageException(ImageError,"AngleIsDiscontinuous"); 1617 /* 1618 Initialize shear angle. 1619 */ 1620 integral_image=CloneImage(image,0,0,MagickTrue,exception); 1621 if (integral_image == (Image *) NULL) 1622 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed"); 1623 shear.x=(-tan(DegreesToRadians(fmod(x_shear,360.0)))); 1624 shear.y=tan(DegreesToRadians(fmod(y_shear,360.0))); 1625 if ((shear.x == 0.0) && (shear.y == 0.0)) 1626 return(integral_image); 1627 if (SetImageStorageClass(integral_image,DirectClass,exception) == MagickFalse) 1628 { 1629 integral_image=DestroyImage(integral_image); 1630 return(integral_image); 1631 } 1632 if (integral_image->alpha_trait == UndefinedPixelTrait) 1633 (void) SetImageAlphaChannel(integral_image,OpaqueAlphaChannel,exception); 1634 /* 1635 Compute image size. 1636 */ 1637 bounds.width=image->columns+(ssize_t) floor(fabs(shear.x)*image->rows+0.5); 1638 bounds.x=(ssize_t) ceil((double) image->columns+((fabs(shear.x)*image->rows)- 1639 image->columns)/2.0-0.5); 1640 bounds.y=(ssize_t) ceil((double) image->rows+((fabs(shear.y)*bounds.width)- 1641 image->rows)/2.0-0.5); 1642 /* 1643 Surround image with border. 1644 */ 1645 integral_image->border_color=integral_image->background_color; 1646 integral_image->compose=CopyCompositeOp; 1647 border_info.width=(size_t) bounds.x; 1648 border_info.height=(size_t) bounds.y; 1649 shear_image=BorderImage(integral_image,&border_info,image->compose,exception); 1650 integral_image=DestroyImage(integral_image); 1651 if (shear_image == (Image *) NULL) 1652 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed"); 1653 /* 1654 Shear the image. 1655 */ 1656 if (shear_image->alpha_trait == UndefinedPixelTrait) 1657 (void) SetImageAlphaChannel(shear_image,OpaqueAlphaChannel,exception); 1658 status=XShearImage(shear_image,shear.x,image->columns,image->rows,bounds.x, 1659 (ssize_t) (shear_image->rows-image->rows)/2,exception); 1660 if (status == MagickFalse) 1661 { 1662 shear_image=DestroyImage(shear_image); 1663 return((Image *) NULL); 1664 } 1665 status=YShearImage(shear_image,shear.y,bounds.width,image->rows,(ssize_t) 1666 (shear_image->columns-bounds.width)/2,bounds.y,exception); 1667 if (status == MagickFalse) 1668 { 1669 shear_image=DestroyImage(shear_image); 1670 return((Image *) NULL); 1671 } 1672 status=CropToFitImage(&shear_image,shear.x,shear.y,(MagickRealType) 1673 image->columns,(MagickRealType) image->rows,MagickFalse,exception); 1674 shear_image->alpha_trait=image->alpha_trait; 1675 shear_image->compose=image->compose; 1676 shear_image->page.width=0; 1677 shear_image->page.height=0; 1678 if (status == MagickFalse) 1679 shear_image=DestroyImage(shear_image); 1680 return(shear_image); 1681 } 1682 1683 /* 1685 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 1686 % % 1687 % % 1688 % % 1689 % S h e a r R o t a t e I m a g e % 1690 % % 1691 % % 1692 % % 1693 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 1694 % 1695 % ShearRotateImage() creates a new image that is a rotated copy of an existing 1696 % one. Positive angles rotate counter-clockwise (right-hand rule), while 1697 % negative angles rotate clockwise. Rotated images are usually larger than 1698 % the originals and have 'empty' triangular corners. X axis. Empty 1699 % triangles left over from shearing the image are filled with the background 1700 % color defined by member 'background_color' of the image. ShearRotateImage 1701 % allocates the memory necessary for the new Image structure and returns a 1702 % pointer to the new image. 1703 % 1704 % ShearRotateImage() is based on the paper "A Fast Algorithm for General 1705 % Raster Rotatation" by Alan W. Paeth. ShearRotateImage is adapted from a 1706 % similar method based on the Paeth paper written by Michael Halle of the 1707 % Spatial Imaging Group, MIT Media Lab. 1708 % 1709 % The format of the ShearRotateImage method is: 1710 % 1711 % Image *ShearRotateImage(const Image *image,const double degrees, 1712 % ExceptionInfo *exception) 1713 % 1714 % A description of each parameter follows. 1715 % 1716 % o image: the image. 1717 % 1718 % o degrees: Specifies the number of degrees to rotate the image. 1719 % 1720 % o exception: return any errors or warnings in this structure. 1721 % 1722 */ 1723 MagickExport Image *ShearRotateImage(const Image *image,const double degrees, 1724 ExceptionInfo *exception) 1725 { 1726 Image 1727 *integral_image, 1728 *rotate_image; 1729 1730 MagickBooleanType 1731 status; 1732 1733 MagickRealType 1734 angle; 1735 1736 PointInfo 1737 shear; 1738 1739 RectangleInfo 1740 border_info, 1741 bounds; 1742 1743 size_t 1744 height, 1745 rotations, 1746 shear_width, 1747 width; 1748 1749 /* 1750 Adjust rotation angle. 1751 */ 1752 assert(image != (Image *) NULL); 1753 assert(image->signature == MagickCoreSignature); 1754 if (image->debug != MagickFalse) 1755 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename); 1756 assert(exception != (ExceptionInfo *) NULL); 1757 assert(exception->signature == MagickCoreSignature); 1758 angle=degrees; 1759 while (angle < -45.0) 1760 angle+=360.0; 1761 for (rotations=0; angle > 45.0; rotations++) 1762 angle-=90.0; 1763 rotations%=4; 1764 /* 1765 Calculate shear equations. 1766 */ 1767 integral_image=IntegralRotateImage(image,rotations,exception); 1768 if (integral_image == (Image *) NULL) 1769 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed"); 1770 shear.x=(-tan((double) DegreesToRadians(angle)/2.0)); 1771 shear.y=sin((double) DegreesToRadians(angle)); 1772 if ((shear.x == 0.0) && (shear.y == 0.0)) 1773 return(integral_image); 1774 if (SetImageStorageClass(integral_image,DirectClass,exception) == MagickFalse) 1775 { 1776 integral_image=DestroyImage(integral_image); 1777 return(integral_image); 1778 } 1779 if (integral_image->alpha_trait == UndefinedPixelTrait) 1780 (void) SetImageAlphaChannel(integral_image,OpaqueAlphaChannel,exception); 1781 /* 1782 Compute maximum bounds for 3 shear operations. 1783 */ 1784 width=integral_image->columns; 1785 height=integral_image->rows; 1786 bounds.width=(size_t) floor(fabs((double) height*shear.x)+width+0.5); 1787 bounds.height=(size_t) floor(fabs((double) bounds.width*shear.y)+height+0.5); 1788 shear_width=(size_t) floor(fabs((double) bounds.height*shear.x)+ 1789 bounds.width+0.5); 1790 bounds.x=(ssize_t) floor((double) ((shear_width > bounds.width) ? width : 1791 bounds.width-shear_width+2)/2.0+0.5); 1792 bounds.y=(ssize_t) floor(((double) bounds.height-height+2)/2.0+0.5); 1793 /* 1794 Surround image with a border. 1795 */ 1796 integral_image->border_color=integral_image->background_color; 1797 integral_image->compose=CopyCompositeOp; 1798 border_info.width=(size_t) bounds.x; 1799 border_info.height=(size_t) bounds.y; 1800 rotate_image=BorderImage(integral_image,&border_info,image->compose, 1801 exception); 1802 integral_image=DestroyImage(integral_image); 1803 if (rotate_image == (Image *) NULL) 1804 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed"); 1805 /* 1806 Rotate the image. 1807 */ 1808 status=XShearImage(rotate_image,shear.x,width,height,bounds.x,(ssize_t) 1809 (rotate_image->rows-height)/2,exception); 1810 if (status == MagickFalse) 1811 { 1812 rotate_image=DestroyImage(rotate_image); 1813 return((Image *) NULL); 1814 } 1815 status=YShearImage(rotate_image,shear.y,bounds.width,height,(ssize_t) 1816 (rotate_image->columns-bounds.width)/2,bounds.y,exception); 1817 if (status == MagickFalse) 1818 { 1819 rotate_image=DestroyImage(rotate_image); 1820 return((Image *) NULL); 1821 } 1822 status=XShearImage(rotate_image,shear.x,bounds.width,bounds.height,(ssize_t) 1823 (rotate_image->columns-bounds.width)/2,(ssize_t) (rotate_image->rows- 1824 bounds.height)/2,exception); 1825 if (status == MagickFalse) 1826 { 1827 rotate_image=DestroyImage(rotate_image); 1828 return((Image *) NULL); 1829 } 1830 status=CropToFitImage(&rotate_image,shear.x,shear.y,(MagickRealType) width, 1831 (MagickRealType) height,MagickTrue,exception); 1832 rotate_image->alpha_trait=image->alpha_trait; 1833 rotate_image->compose=image->compose; 1834 rotate_image->page.width=0; 1835 rotate_image->page.height=0; 1836 if (status == MagickFalse) 1837 rotate_image=DestroyImage(rotate_image); 1838 return(rotate_image); 1839 } 1840