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