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