shear.c revision dcfc1ad7e0ad199ccd86e9508e9c5775e4c4bbdb
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 (((MagickRealType) GetPixelRed(image,p) < threshold) || 743 ((MagickRealType) GetPixelGreen(image,p) < threshold) || 744 ((MagickRealType) GetPixelBlue(image,p) < threshold)) 745 byte|=0x01; 746 bit++; 747 if (bit == 8) 748 { 749 (void) SetRadonCell(source_cells,--i,y,bits[byte]); 750 bit=0; 751 byte=0; 752 } 753 p+=GetPixelComponents(image); 754 } 755 if (bit != 0) 756 { 757 byte<<=(8-bit); 758 (void) SetRadonCell(source_cells,--i,y,bits[byte]); 759 } 760 } 761 RadonProjection(source_cells,destination_cells,-1,projection); 762 (void) ResetRadonCells(source_cells); 763#if defined(MAGICKCORE_OPENMP_SUPPORT) 764 #pragma omp parallel for schedule(dynamic,4) shared(status) 765#endif 766 for (y=0; y < (ssize_t) image->rows; y++) 767 { 768 register const Quantum 769 *restrict p; 770 771 register ssize_t 772 i, 773 x; 774 775 size_t 776 bit, 777 byte; 778 779 if (status == MagickFalse) 780 continue; 781 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception); 782 if (p == (const Quantum *) NULL) 783 { 784 status=MagickFalse; 785 continue; 786 } 787 bit=0; 788 byte=0; 789 i=0; 790 for (x=0; x < (ssize_t) image->columns; x++) 791 { 792 byte<<=1; 793 if (((MagickRealType) GetPixelRed(image,p) < threshold) || 794 ((MagickRealType) GetPixelGreen(image,p) < threshold) || 795 ((MagickRealType) GetPixelBlue(image,p) < threshold)) 796 byte|=0x01; 797 bit++; 798 if (bit == 8) 799 { 800 (void) SetRadonCell(source_cells,i++,y,bits[byte]); 801 bit=0; 802 byte=0; 803 } 804 p+=GetPixelComponents(image); 805 } 806 if (bit != 0) 807 { 808 byte<<=(8-bit); 809 (void) SetRadonCell(source_cells,i++,y,bits[byte]); 810 } 811 } 812 RadonProjection(source_cells,destination_cells,1,projection); 813 image_view=DestroyCacheView(image_view); 814 destination_cells=DestroyRadonInfo(destination_cells); 815 source_cells=DestroyRadonInfo(source_cells); 816 return(MagickTrue); 817} 818 819static void GetImageBackgroundColor(Image *image,const ssize_t offset, 820 ExceptionInfo *exception) 821{ 822 CacheView 823 *image_view; 824 825 PixelInfo 826 background; 827 828 MagickRealType 829 count; 830 831 ssize_t 832 y; 833 834 /* 835 Compute average background color. 836 */ 837 if (offset <= 0) 838 return; 839 GetPixelInfo(image,&background); 840 count=0.0; 841 image_view=AcquireCacheView(image); 842 for (y=0; y < (ssize_t) image->rows; y++) 843 { 844 register const Quantum 845 *restrict p; 846 847 register ssize_t 848 x; 849 850 if ((y >= offset) && (y < ((ssize_t) image->rows-offset))) 851 continue; 852 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception); 853 if (p == (const Quantum *) NULL) 854 continue; 855 for (x=0; x < (ssize_t) image->columns; x++) 856 { 857 if ((x >= offset) && (x < ((ssize_t) image->columns-offset))) 858 continue; 859 background.red+=QuantumScale*GetPixelRed(image,p); 860 background.green+=QuantumScale*GetPixelGreen(image,p); 861 background.blue+=QuantumScale*GetPixelBlue(image,p); 862 background.alpha+=QuantumScale*GetPixelAlpha(image,p); 863 count++; 864 p+=GetPixelComponents(image); 865 } 866 } 867 image_view=DestroyCacheView(image_view); 868 image->background_color.red=ClampToQuantum((MagickRealType) QuantumRange* 869 background.red/count); 870 image->background_color.green=ClampToQuantum((MagickRealType) QuantumRange* 871 background.green/count); 872 image->background_color.blue=ClampToQuantum((MagickRealType) QuantumRange* 873 background.blue/count); 874 image->background_color.alpha=ClampToQuantum((MagickRealType) QuantumRange* 875 background.alpha/count); 876} 877 878MagickExport Image *DeskewImage(const Image *image,const double threshold, 879 ExceptionInfo *exception) 880{ 881 AffineMatrix 882 affine_matrix; 883 884 const char 885 *artifact; 886 887 double 888 degrees; 889 890 Image 891 *clone_image, 892 *crop_image, 893 *deskew_image, 894 *median_image; 895 896 MagickBooleanType 897 status; 898 899 RectangleInfo 900 geometry; 901 902 register ssize_t 903 i; 904 905 size_t 906 max_projection, 907 *projection, 908 width; 909 910 ssize_t 911 skew; 912 913 /* 914 Compute deskew angle. 915 */ 916 for (width=1; width < ((image->columns+7)/8); width<<=1) ; 917 projection=(size_t *) AcquireQuantumMemory((size_t) (2*width-1), 918 sizeof(*projection)); 919 if (projection == (size_t *) NULL) 920 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed"); 921 status=RadonTransform(image,threshold,projection,exception); 922 if (status == MagickFalse) 923 { 924 projection=(size_t *) RelinquishMagickMemory(projection); 925 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed"); 926 } 927 max_projection=0; 928 skew=0; 929 for (i=0; i < (ssize_t) (2*width-1); i++) 930 { 931 if (projection[i] > max_projection) 932 { 933 skew=i-(ssize_t) width+1; 934 max_projection=projection[i]; 935 } 936 } 937 projection=(size_t *) RelinquishMagickMemory(projection); 938 /* 939 Deskew image. 940 */ 941 clone_image=CloneImage(image,0,0,MagickTrue,exception); 942 if (clone_image == (Image *) NULL) 943 return((Image *) NULL); 944 (void) SetImageVirtualPixelMethod(clone_image,BackgroundVirtualPixelMethod); 945 degrees=RadiansToDegrees(-atan((double) skew/width/8)); 946 if (image->debug != MagickFalse) 947 (void) LogMagickEvent(TransformEvent,GetMagickModule(), 948 " Deskew angle: %g",degrees); 949 affine_matrix.sx=cos(DegreesToRadians(fmod((double) degrees,360.0))); 950 affine_matrix.rx=sin(DegreesToRadians(fmod((double) degrees,360.0))); 951 affine_matrix.ry=(-sin(DegreesToRadians(fmod((double) degrees,360.0)))); 952 affine_matrix.sy=cos(DegreesToRadians(fmod((double) degrees,360.0))); 953 affine_matrix.tx=0.0; 954 affine_matrix.ty=0.0; 955 artifact=GetImageArtifact(image,"deskew:auto-crop"); 956 if (artifact == (const char *) NULL) 957 { 958 deskew_image=AffineTransformImage(clone_image,&affine_matrix,exception); 959 clone_image=DestroyImage(clone_image); 960 return(deskew_image); 961 } 962 /* 963 Auto-crop image. 964 */ 965 GetImageBackgroundColor(clone_image,(ssize_t) StringToLong(artifact), 966 exception); 967 deskew_image=AffineTransformImage(clone_image,&affine_matrix,exception); 968 clone_image=DestroyImage(clone_image); 969 if (deskew_image == (Image *) NULL) 970 return((Image *) NULL); 971 median_image=StatisticImage(deskew_image,MedianStatistic,3,3,exception); 972 if (median_image == (Image *) NULL) 973 { 974 deskew_image=DestroyImage(deskew_image); 975 return((Image *) NULL); 976 } 977 geometry=GetImageBoundingBox(median_image,exception); 978 median_image=DestroyImage(median_image); 979 if (image->debug != MagickFalse) 980 (void) LogMagickEvent(TransformEvent,GetMagickModule()," Deskew geometry: " 981 "%.20gx%.20g%+.20g%+.20g",(double) geometry.width,(double) 982 geometry.height,(double) geometry.x,(double) geometry.y); 983 crop_image=CropImage(deskew_image,&geometry,exception); 984 deskew_image=DestroyImage(deskew_image); 985 return(crop_image); 986} 987 988/* 989%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 990% % 991% % 992% % 993+ I n t e g r a l R o t a t e I m a g e % 994% % 995% % 996% % 997%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 998% 999% IntegralRotateImage() rotates the image an integral of 90 degrees. It 1000% allocates the memory necessary for the new Image structure and returns a 1001% pointer to the rotated image. 1002% 1003% The format of the IntegralRotateImage method is: 1004% 1005% Image *IntegralRotateImage(const Image *image,size_t rotations, 1006% ExceptionInfo *exception) 1007% 1008% A description of each parameter follows. 1009% 1010% o image: the image. 1011% 1012% o rotations: Specifies the number of 90 degree rotations. 1013% 1014*/ 1015static Image *IntegralRotateImage(const Image *image,size_t rotations, 1016 ExceptionInfo *exception) 1017{ 1018#define RotateImageTag "Rotate/Image" 1019 1020 CacheView 1021 *image_view, 1022 *rotate_view; 1023 1024 Image 1025 *rotate_image; 1026 1027 MagickBooleanType 1028 status; 1029 1030 MagickOffsetType 1031 progress; 1032 1033 RectangleInfo 1034 page; 1035 1036 ssize_t 1037 y; 1038 1039 /* 1040 Initialize rotated image attributes. 1041 */ 1042 assert(image != (Image *) NULL); 1043 page=image->page; 1044 rotations%=4; 1045 if (rotations == 0) 1046 return(CloneImage(image,0,0,MagickTrue,exception)); 1047 if ((rotations == 1) || (rotations == 3)) 1048 rotate_image=CloneImage(image,image->rows,image->columns,MagickTrue, 1049 exception); 1050 else 1051 rotate_image=CloneImage(image,image->columns,image->rows,MagickTrue, 1052 exception); 1053 if (rotate_image == (Image *) NULL) 1054 return((Image *) NULL); 1055 /* 1056 Integral rotate the image. 1057 */ 1058 status=MagickTrue; 1059 progress=0; 1060 image_view=AcquireCacheView(image); 1061 rotate_view=AcquireCacheView(rotate_image); 1062 switch (rotations) 1063 { 1064 case 0: 1065 { 1066 /* 1067 Rotate 0 degrees. 1068 */ 1069 break; 1070 } 1071 case 1: 1072 { 1073 size_t 1074 tile_height, 1075 tile_width; 1076 1077 ssize_t 1078 tile_y; 1079 1080 /* 1081 Rotate 90 degrees. 1082 */ 1083 GetPixelCacheTileSize(image,&tile_width,&tile_height); 1084#if defined(MAGICKCORE_OPENMP_SUPPORT) 1085 #pragma omp parallel for schedule(static,1) shared(progress, status) omp_throttle(1) 1086#endif 1087 for (tile_y=0; tile_y < (ssize_t) image->rows; tile_y+=(ssize_t) tile_height) 1088 { 1089 register ssize_t 1090 tile_x; 1091 1092 if (status == MagickFalse) 1093 continue; 1094 for (tile_x=0; tile_x < (ssize_t) image->columns; tile_x+=(ssize_t) tile_width) 1095 { 1096 MagickBooleanType 1097 sync; 1098 1099 register const Quantum 1100 *restrict p; 1101 1102 register ssize_t 1103 y; 1104 1105 register Quantum 1106 *restrict q; 1107 1108 size_t 1109 height, 1110 width; 1111 1112 width=tile_width; 1113 if ((tile_x+(ssize_t) tile_width) > (ssize_t) image->columns) 1114 width=(size_t) (tile_width-(tile_x+tile_width- 1115 image->columns)); 1116 height=tile_height; 1117 if ((tile_y+(ssize_t) tile_height) > (ssize_t) image->rows) 1118 height=(size_t) (tile_height-(tile_y+tile_height- 1119 image->rows)); 1120 p=GetCacheViewVirtualPixels(image_view,tile_x,tile_y,width,height, 1121 exception); 1122 if (p == (const Quantum *) NULL) 1123 { 1124 status=MagickFalse; 1125 break; 1126 } 1127 for (y=0; y < (ssize_t) width; y++) 1128 { 1129 register const Quantum 1130 *restrict tile_pixels; 1131 1132 register ssize_t 1133 x; 1134 1135 q=QueueCacheViewAuthenticPixels(rotate_view,(ssize_t) 1136 (rotate_image->columns-(tile_y+height)),y+tile_x,height,1, 1137 exception); 1138 if (q == (const Quantum *) NULL) 1139 { 1140 status=MagickFalse; 1141 break; 1142 } 1143 tile_pixels=p+((height-1)*width+y)*GetPixelComponents(image); 1144 for (x=0; x < (ssize_t) height; x++) 1145 { 1146 SetPixelRed(rotate_image,GetPixelRed(image,tile_pixels),q); 1147 SetPixelGreen(rotate_image,GetPixelGreen(image,tile_pixels),q); 1148 SetPixelBlue(rotate_image,GetPixelBlue(image,tile_pixels),q); 1149 SetPixelAlpha(rotate_image,GetPixelAlpha(image,tile_pixels),q); 1150 tile_pixels-=width*GetPixelComponents(image); 1151 q+=GetPixelComponents(rotate_image); 1152 } 1153 sync=SyncCacheViewAuthenticPixels(rotate_view,exception); 1154 if (sync == MagickFalse) 1155 status=MagickFalse; 1156 } 1157 } 1158 if (image->progress_monitor != (MagickProgressMonitor) NULL) 1159 { 1160 MagickBooleanType 1161 proceed; 1162 1163 proceed=SetImageProgress(image,RotateImageTag,progress+=tile_height, 1164 image->rows); 1165 if (proceed == MagickFalse) 1166 status=MagickFalse; 1167 } 1168 } 1169 (void) SetImageProgress(image,RotateImageTag,(MagickOffsetType) 1170 image->rows-1,image->rows); 1171 Swap(page.width,page.height); 1172 Swap(page.x,page.y); 1173 if (page.width != 0) 1174 page.x=(ssize_t) (page.width-rotate_image->columns-page.x); 1175 break; 1176 } 1177 case 2: 1178 { 1179 /* 1180 Rotate 180 degrees. 1181 */ 1182#if defined(MAGICKCORE_OPENMP_SUPPORT) 1183 #pragma omp parallel for schedule(static,8) shared(progress,status) omp_throttle(1) 1184#endif 1185 for (y=0; y < (ssize_t) image->rows; y++) 1186 { 1187 MagickBooleanType 1188 sync; 1189 1190 register const Quantum 1191 *restrict p; 1192 1193 register ssize_t 1194 x; 1195 1196 register Quantum 1197 *restrict q; 1198 1199 if (status == MagickFalse) 1200 continue; 1201 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1, 1202 exception); 1203 q=QueueCacheViewAuthenticPixels(rotate_view,0,(ssize_t) (image->rows- 1204 y-1),image->columns,1,exception); 1205 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL)) 1206 { 1207 status=MagickFalse; 1208 continue; 1209 } 1210 q+=GetPixelComponents(rotate_image)*image->columns; 1211 for (x=0; x < (ssize_t) image->columns; x++) 1212 { 1213 q-=GetPixelComponents(rotate_image); 1214 SetPixelRed(rotate_image,GetPixelRed(image,p),q); 1215 SetPixelGreen(rotate_image,GetPixelGreen(image,p),q); 1216 SetPixelBlue(rotate_image,GetPixelBlue(image,p),q); 1217 SetPixelAlpha(rotate_image,GetPixelAlpha(image,p),q); 1218 p+=GetPixelComponents(image); 1219 } 1220 sync=SyncCacheViewAuthenticPixels(rotate_view,exception); 1221 if (sync == MagickFalse) 1222 status=MagickFalse; 1223 if (image->progress_monitor != (MagickProgressMonitor) NULL) 1224 { 1225 MagickBooleanType 1226 proceed; 1227 1228 proceed=SetImageProgress(image,RotateImageTag,progress++, 1229 image->rows); 1230 if (proceed == MagickFalse) 1231 status=MagickFalse; 1232 } 1233 } 1234 if (page.width != 0) 1235 page.x=(ssize_t) (page.width-rotate_image->columns-page.x); 1236 if (page.height != 0) 1237 page.y=(ssize_t) (page.height-rotate_image->rows-page.y); 1238 break; 1239 } 1240 case 3: 1241 { 1242 size_t 1243 tile_height, 1244 tile_width; 1245 1246 ssize_t 1247 tile_y; 1248 1249 /* 1250 Rotate 270 degrees. 1251 */ 1252 GetPixelCacheTileSize(image,&tile_width,&tile_height); 1253#if defined(MAGICKCORE_OPENMP_SUPPORT) 1254 #pragma omp parallel for schedule(static,1) shared(progress,status) omp_throttle(1) 1255#endif 1256 for (tile_y=0; tile_y < (ssize_t) image->rows; tile_y+=(ssize_t) tile_height) 1257 { 1258 register ssize_t 1259 tile_x; 1260 1261 if (status == MagickFalse) 1262 continue; 1263 for (tile_x=0; tile_x < (ssize_t) image->columns; tile_x+=(ssize_t) tile_width) 1264 { 1265 MagickBooleanType 1266 sync; 1267 1268 register const Quantum 1269 *restrict p; 1270 1271 register ssize_t 1272 y; 1273 1274 register Quantum 1275 *restrict q; 1276 1277 size_t 1278 height, 1279 width; 1280 1281 width=tile_width; 1282 if ((tile_x+(ssize_t) tile_width) > (ssize_t) image->columns) 1283 width=(size_t) (tile_width-(tile_x+tile_width- 1284 image->columns)); 1285 height=tile_height; 1286 if ((tile_y+(ssize_t) tile_height) > (ssize_t) image->rows) 1287 height=(size_t) (tile_height-(tile_y+tile_height- 1288 image->rows)); 1289 p=GetCacheViewVirtualPixels(image_view,tile_x,tile_y,width, 1290 height,exception); 1291 if (p == (const Quantum *) NULL) 1292 { 1293 status=MagickFalse; 1294 break; 1295 } 1296 for (y=0; y < (ssize_t) width; y++) 1297 { 1298 register const Quantum 1299 *restrict tile_pixels; 1300 1301 register ssize_t 1302 x; 1303 1304 q=QueueCacheViewAuthenticPixels(rotate_view,tile_y,(ssize_t) 1305 (y+rotate_image->rows-(tile_x+width)),height,1,exception); 1306 if (q == (const Quantum *) NULL) 1307 { 1308 status=MagickFalse; 1309 break; 1310 } 1311 tile_pixels=p+((width-1)-y)*GetPixelComponents(image); 1312 for (x=0; x < (ssize_t) height; x++) 1313 { 1314 SetPixelRed(rotate_image,GetPixelRed(image,tile_pixels),q); 1315 SetPixelGreen(rotate_image,GetPixelGreen(image,tile_pixels),q); 1316 SetPixelBlue(rotate_image,GetPixelBlue(image,tile_pixels),q); 1317 SetPixelAlpha(rotate_image,GetPixelAlpha(image,tile_pixels),q); 1318 tile_pixels+=width*GetPixelComponents(image); 1319 q+=GetPixelComponents(rotate_image); 1320 } 1321 sync=SyncCacheViewAuthenticPixels(rotate_view,exception); 1322 if (sync == MagickFalse) 1323 status=MagickFalse; 1324 } 1325 } 1326 if (image->progress_monitor != (MagickProgressMonitor) NULL) 1327 { 1328 MagickBooleanType 1329 proceed; 1330 1331 proceed=SetImageProgress(image,RotateImageTag,progress+=tile_height, 1332 image->rows); 1333 if (proceed == MagickFalse) 1334 status=MagickFalse; 1335 } 1336 } 1337 (void) SetImageProgress(image,RotateImageTag,(MagickOffsetType) 1338 image->rows-1,image->rows); 1339 Swap(page.width,page.height); 1340 Swap(page.x,page.y); 1341 if (page.height != 0) 1342 page.y=(ssize_t) (page.height-rotate_image->rows-page.y); 1343 break; 1344 } 1345 } 1346 rotate_view=DestroyCacheView(rotate_view); 1347 image_view=DestroyCacheView(image_view); 1348 rotate_image->type=image->type; 1349 rotate_image->page=page; 1350 if (status == MagickFalse) 1351 rotate_image=DestroyImage(rotate_image); 1352 return(rotate_image); 1353} 1354 1355/* 1356%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 1357% % 1358% % 1359% % 1360+ X S h e a r I m a g e % 1361% % 1362% % 1363% % 1364%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 1365% 1366% XShearImage() shears the image in the X direction with a shear angle of 1367% 'degrees'. Positive angles shear counter-clockwise (right-hand rule), and 1368% negative angles shear clockwise. Angles are measured relative to a vertical 1369% Y-axis. X shears will widen an image creating 'empty' triangles on the left 1370% and right sides of the source image. 1371% 1372% The format of the XShearImage method is: 1373% 1374% MagickBooleanType XShearImage(Image *image,const MagickRealType degrees, 1375% const size_t width,const size_t height, 1376% const ssize_t x_offset,const ssize_t y_offset,ExceptionInfo *exception) 1377% 1378% A description of each parameter follows. 1379% 1380% o image: the image. 1381% 1382% o degrees: A MagickRealType representing the shearing angle along the X 1383% axis. 1384% 1385% o width, height, x_offset, y_offset: Defines a region of the image 1386% to shear. 1387% 1388% o exception: return any errors or warnings in this structure. 1389% 1390*/ 1391static MagickBooleanType XShearImage(Image *image,const MagickRealType degrees, 1392 const size_t width,const size_t height,const ssize_t x_offset, 1393 const ssize_t y_offset,ExceptionInfo *exception) 1394{ 1395#define XShearImageTag "XShear/Image" 1396 1397 typedef enum 1398 { 1399 LEFT, 1400 RIGHT 1401 } ShearDirection; 1402 1403 CacheView 1404 *image_view; 1405 1406 MagickBooleanType 1407 status; 1408 1409 MagickOffsetType 1410 progress; 1411 1412 PixelInfo 1413 background; 1414 1415 ssize_t 1416 y; 1417 1418 assert(image != (Image *) NULL); 1419 assert(image->signature == MagickSignature); 1420 if (image->debug != MagickFalse) 1421 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename); 1422 GetPixelInfo(image,&background); 1423 SetPixelInfoPacket(image,&image->background_color,&background); 1424 if (image->colorspace == CMYKColorspace) 1425 ConvertRGBToCMYK(&background); 1426 /* 1427 X shear image. 1428 */ 1429 status=MagickTrue; 1430 progress=0; 1431 image_view=AcquireCacheView(image); 1432#if defined(MAGICKCORE_OPENMP_SUPPORT) 1433 #pragma omp parallel for schedule(dynamic,4) shared(progress, status) 1434#endif 1435 for (y=0; y < (ssize_t) height; y++) 1436 { 1437 PixelInfo 1438 pixel, 1439 source, 1440 destination; 1441 1442 MagickRealType 1443 area, 1444 displacement; 1445 1446 register Quantum 1447 *restrict p, 1448 *restrict q; 1449 1450 register ssize_t 1451 i; 1452 1453 ShearDirection 1454 direction; 1455 1456 ssize_t 1457 step; 1458 1459 if (status == MagickFalse) 1460 continue; 1461 p=GetCacheViewAuthenticPixels(image_view,0,y_offset+y,image->columns,1, 1462 exception); 1463 if (p == (Quantum *) NULL) 1464 { 1465 status=MagickFalse; 1466 continue; 1467 } 1468 p+=x_offset*GetPixelComponents(image); 1469 displacement=degrees*(MagickRealType) (y-height/2.0); 1470 if (displacement == 0.0) 1471 continue; 1472 if (displacement > 0.0) 1473 direction=RIGHT; 1474 else 1475 { 1476 displacement*=(-1.0); 1477 direction=LEFT; 1478 } 1479 step=(ssize_t) floor((double) displacement); 1480 area=(MagickRealType) (displacement-step); 1481 step++; 1482 pixel=background; 1483 GetPixelInfo(image,&source); 1484 GetPixelInfo(image,&destination); 1485 switch (direction) 1486 { 1487 case LEFT: 1488 { 1489 /* 1490 Transfer pixels left-to-right. 1491 */ 1492 if (step > x_offset) 1493 break; 1494 q=p-step*GetPixelComponents(image); 1495 for (i=0; i < (ssize_t) width; i++) 1496 { 1497 if ((x_offset+i) < step) 1498 { 1499 p+=GetPixelComponents(image); 1500 SetPixelInfo(image,p,&pixel); 1501 q+=GetPixelComponents(image); 1502 continue; 1503 } 1504 SetPixelInfo(image,p,&source); 1505 CompositePixelInfoAreaBlend(&pixel,(MagickRealType) pixel.alpha, 1506 &source,(MagickRealType) GetPixelAlpha(image,p),area, 1507 &destination); 1508 SetPixelPixelInfo(image,&destination,q); 1509 SetPixelInfo(image,p,&pixel); 1510 p+=GetPixelComponents(image); 1511 q+=GetPixelComponents(image); 1512 } 1513 CompositePixelInfoAreaBlend(&pixel,(MagickRealType) pixel.alpha, 1514 &background,(MagickRealType) background.alpha,area,&destination); 1515 SetPixelPixelInfo(image,&destination,q); 1516 q+=GetPixelComponents(image); 1517 for (i=0; i < (step-1); i++) 1518 { 1519 SetPixelPixelInfo(image,&background,q); 1520 q+=GetPixelComponents(image); 1521 } 1522 break; 1523 } 1524 case RIGHT: 1525 { 1526 /* 1527 Transfer pixels right-to-left. 1528 */ 1529 p+=width*GetPixelComponents(image); 1530 q=p+step*GetPixelComponents(image); 1531 for (i=0; i < (ssize_t) width; i++) 1532 { 1533 p-=GetPixelComponents(image); 1534 q-=GetPixelComponents(image); 1535 if ((size_t) (x_offset+width+step-i) >= image->columns) 1536 continue; 1537 SetPixelInfo(image,p,&source); 1538 CompositePixelInfoAreaBlend(&pixel,(MagickRealType) pixel.alpha, 1539 &source,(MagickRealType) GetPixelAlpha(image,p),area, 1540 &destination); 1541 SetPixelPixelInfo(image,&destination,q); 1542 SetPixelInfo(image,p,&pixel); 1543 } 1544 CompositePixelInfoAreaBlend(&pixel,(MagickRealType) pixel.alpha, 1545 &background,(MagickRealType) background.alpha,area,&destination); 1546 q-=GetPixelComponents(image); 1547 SetPixelPixelInfo(image,&destination,q); 1548 for (i=0; i < (step-1); i++) 1549 { 1550 q-=GetPixelComponents(image); 1551 SetPixelPixelInfo(image,&background,q); 1552 } 1553 break; 1554 } 1555 } 1556 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse) 1557 status=MagickFalse; 1558 if (image->progress_monitor != (MagickProgressMonitor) NULL) 1559 { 1560 MagickBooleanType 1561 proceed; 1562 1563#if defined(MAGICKCORE_OPENMP_SUPPORT) 1564 #pragma omp critical (MagickCore_XShearImage) 1565#endif 1566 proceed=SetImageProgress(image,XShearImageTag,progress++,height); 1567 if (proceed == MagickFalse) 1568 status=MagickFalse; 1569 } 1570 } 1571 image_view=DestroyCacheView(image_view); 1572 return(status); 1573} 1574 1575/* 1576%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 1577% % 1578% % 1579% % 1580+ Y S h e a r I m a g e % 1581% % 1582% % 1583% % 1584%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 1585% 1586% YShearImage shears the image in the Y direction with a shear angle of 1587% 'degrees'. Positive angles shear counter-clockwise (right-hand rule), and 1588% negative angles shear clockwise. Angles are measured relative to a 1589% horizontal X-axis. Y shears will increase the height of an image creating 1590% 'empty' triangles on the top and bottom of the source image. 1591% 1592% The format of the YShearImage method is: 1593% 1594% MagickBooleanType YShearImage(Image *image,const MagickRealType degrees, 1595% const size_t width,const size_t height, 1596% const ssize_t x_offset,const ssize_t y_offset,ExceptionInfo *exception) 1597% 1598% A description of each parameter follows. 1599% 1600% o image: the image. 1601% 1602% o degrees: A MagickRealType representing the shearing angle along the Y 1603% axis. 1604% 1605% o width, height, x_offset, y_offset: Defines a region of the image 1606% to shear. 1607% 1608% o exception: return any errors or warnings in this structure. 1609% 1610*/ 1611static MagickBooleanType YShearImage(Image *image,const MagickRealType degrees, 1612 const size_t width,const size_t height,const ssize_t x_offset, 1613 const ssize_t y_offset,ExceptionInfo *exception) 1614{ 1615#define YShearImageTag "YShear/Image" 1616 1617 typedef enum 1618 { 1619 UP, 1620 DOWN 1621 } ShearDirection; 1622 1623 CacheView 1624 *image_view; 1625 1626 MagickBooleanType 1627 status; 1628 1629 MagickOffsetType 1630 progress; 1631 1632 PixelInfo 1633 background; 1634 1635 ssize_t 1636 x; 1637 1638 assert(image != (Image *) NULL); 1639 assert(image->signature == MagickSignature); 1640 if (image->debug != MagickFalse) 1641 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename); 1642 GetPixelInfo(image,&background); 1643 SetPixelInfoPacket(image,&image->background_color,&background); 1644 if (image->colorspace == CMYKColorspace) 1645 ConvertRGBToCMYK(&background); 1646 /* 1647 Y Shear image. 1648 */ 1649 status=MagickTrue; 1650 progress=0; 1651 image_view=AcquireCacheView(image); 1652#if defined(MAGICKCORE_OPENMP_SUPPORT) 1653 #pragma omp parallel for schedule(dynamic,4) shared(progress, status) 1654#endif 1655 for (x=0; x < (ssize_t) width; x++) 1656 { 1657 ssize_t 1658 step; 1659 1660 MagickRealType 1661 area, 1662 displacement; 1663 1664 PixelInfo 1665 pixel, 1666 source, 1667 destination; 1668 1669 register Quantum 1670 *restrict p, 1671 *restrict q; 1672 1673 register ssize_t 1674 i; 1675 1676 ShearDirection 1677 direction; 1678 1679 if (status == MagickFalse) 1680 continue; 1681 p=GetCacheViewAuthenticPixels(image_view,x_offset+x,0,1,image->rows, 1682 exception); 1683 if (p == (Quantum *) NULL) 1684 { 1685 status=MagickFalse; 1686 continue; 1687 } 1688 p+=y_offset*GetPixelComponents(image); 1689 displacement=degrees*(MagickRealType) (x-width/2.0); 1690 if (displacement == 0.0) 1691 continue; 1692 if (displacement > 0.0) 1693 direction=DOWN; 1694 else 1695 { 1696 displacement*=(-1.0); 1697 direction=UP; 1698 } 1699 step=(ssize_t) floor((double) displacement); 1700 area=(MagickRealType) (displacement-step); 1701 step++; 1702 pixel=background; 1703 GetPixelInfo(image,&source); 1704 GetPixelInfo(image,&destination); 1705 switch (direction) 1706 { 1707 case UP: 1708 { 1709 /* 1710 Transfer pixels top-to-bottom. 1711 */ 1712 if (step > y_offset) 1713 break; 1714 q=p-step*GetPixelComponents(image); 1715 for (i=0; i < (ssize_t) height; i++) 1716 { 1717 if ((y_offset+i) < step) 1718 { 1719 p+=GetPixelComponents(image); 1720 SetPixelInfo(image,p,&pixel); 1721 q+=GetPixelComponents(image); 1722 continue; 1723 } 1724 SetPixelInfo(image,p,&source); 1725 CompositePixelInfoAreaBlend(&pixel,(MagickRealType) pixel.alpha, 1726 &source,(MagickRealType) GetPixelAlpha(image,p),area, 1727 &destination); 1728 SetPixelPixelInfo(image,&destination,q); 1729 SetPixelInfo(image,p,&pixel); 1730 p+=GetPixelComponents(image); 1731 q+=GetPixelComponents(image); 1732 } 1733 CompositePixelInfoAreaBlend(&pixel,(MagickRealType) pixel.alpha, 1734 &background,(MagickRealType) background.alpha,area,&destination); 1735 SetPixelPixelInfo(image,&destination,q); 1736 q+=GetPixelComponents(image); 1737 for (i=0; i < (step-1); i++) 1738 { 1739 SetPixelPixelInfo(image,&background,q); 1740 q+=GetPixelComponents(image); 1741 } 1742 break; 1743 } 1744 case DOWN: 1745 { 1746 /* 1747 Transfer pixels bottom-to-top. 1748 */ 1749 p+=height*GetPixelComponents(image); 1750 q=p+step*GetPixelComponents(image); 1751 for (i=0; i < (ssize_t) height; i++) 1752 { 1753 p-=GetPixelComponents(image); 1754 q-=GetPixelComponents(image); 1755 if ((size_t) (y_offset+height+step-i) >= image->rows) 1756 continue; 1757 SetPixelInfo(image,p,&source); 1758 CompositePixelInfoAreaBlend(&pixel,(MagickRealType) pixel.alpha, 1759 &source,(MagickRealType) GetPixelAlpha(image,p),area, 1760 &destination); 1761 SetPixelPixelInfo(image,&destination,q); 1762 SetPixelInfo(image,p,&pixel); 1763 } 1764 CompositePixelInfoAreaBlend(&pixel,(MagickRealType) pixel.alpha, 1765 &background,(MagickRealType) background.alpha,area,&destination); 1766 q-=GetPixelComponents(image); 1767 SetPixelPixelInfo(image,&destination,q); 1768 for (i=0; i < (step-1); i++) 1769 { 1770 q-=GetPixelComponents(image); 1771 SetPixelPixelInfo(image,&background,q); 1772 } 1773 break; 1774 } 1775 } 1776 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse) 1777 status=MagickFalse; 1778 if (image->progress_monitor != (MagickProgressMonitor) NULL) 1779 { 1780 MagickBooleanType 1781 proceed; 1782 1783#if defined(MAGICKCORE_OPENMP_SUPPORT) 1784 #pragma omp critical (MagickCore_YShearImage) 1785#endif 1786 proceed=SetImageProgress(image,YShearImageTag,progress++,image->rows); 1787 if (proceed == MagickFalse) 1788 status=MagickFalse; 1789 } 1790 } 1791 image_view=DestroyCacheView(image_view); 1792 return(status); 1793} 1794 1795/* 1796%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 1797% % 1798% % 1799% % 1800% R o t a t e I m a g e % 1801% % 1802% % 1803% % 1804%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 1805% 1806% RotateImage() creates a new image that is a rotated copy of an existing 1807% one. Positive angles rotate counter-clockwise (right-hand rule), while 1808% negative angles rotate clockwise. Rotated images are usually larger than 1809% the originals and have 'empty' triangular corners. X axis. Empty 1810% triangles left over from shearing the image are filled with the background 1811% color defined by member 'background_color' of the image. RotateImage 1812% allocates the memory necessary for the new Image structure and returns a 1813% pointer to the new image. 1814% 1815% RotateImage() is based on the paper "A Fast Algorithm for General 1816% Raster Rotatation" by Alan W. Paeth. RotateImage is adapted from a similar 1817% method based on the Paeth paper written by Michael Halle of the Spatial 1818% Imaging Group, MIT Media Lab. 1819% 1820% The format of the RotateImage method is: 1821% 1822% Image *RotateImage(const Image *image,const double degrees, 1823% ExceptionInfo *exception) 1824% 1825% A description of each parameter follows. 1826% 1827% o image: the image. 1828% 1829% o degrees: Specifies the number of degrees to rotate the image. 1830% 1831% o exception: return any errors or warnings in this structure. 1832% 1833*/ 1834MagickExport Image *RotateImage(const Image *image,const double degrees, 1835 ExceptionInfo *exception) 1836{ 1837 Image 1838 *integral_image, 1839 *rotate_image; 1840 1841 MagickBooleanType 1842 status; 1843 1844 MagickRealType 1845 angle; 1846 1847 PointInfo 1848 shear; 1849 1850 RectangleInfo 1851 border_info; 1852 1853 size_t 1854 height, 1855 rotations, 1856 width, 1857 y_width; 1858 1859 ssize_t 1860 x_offset, 1861 y_offset; 1862 1863 /* 1864 Adjust rotation angle. 1865 */ 1866 assert(image != (Image *) NULL); 1867 assert(image->signature == MagickSignature); 1868 if (image->debug != MagickFalse) 1869 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename); 1870 assert(exception != (ExceptionInfo *) NULL); 1871 assert(exception->signature == MagickSignature); 1872 angle=degrees; 1873 while (angle < -45.0) 1874 angle+=360.0; 1875 for (rotations=0; angle > 45.0; rotations++) 1876 angle-=90.0; 1877 rotations%=4; 1878 /* 1879 Calculate shear equations. 1880 */ 1881 integral_image=IntegralRotateImage(image,rotations,exception); 1882 if (integral_image == (Image *) NULL) 1883 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed"); 1884 shear.x=(-tan((double) DegreesToRadians(angle)/2.0)); 1885 shear.y=sin((double) DegreesToRadians(angle)); 1886 if ((shear.x == 0.0) && (shear.y == 0.0)) 1887 return(integral_image); 1888 if (SetImageStorageClass(integral_image,DirectClass) == MagickFalse) 1889 { 1890 InheritException(exception,&integral_image->exception); 1891 integral_image=DestroyImage(integral_image); 1892 return(integral_image); 1893 } 1894 if (integral_image->matte == MagickFalse) 1895 (void) SetImageAlphaChannel(integral_image,OpaqueAlphaChannel); 1896 /* 1897 Compute image size. 1898 */ 1899 width=image->columns; 1900 height=image->rows; 1901 if ((rotations == 1) || (rotations == 3)) 1902 { 1903 width=image->rows; 1904 height=image->columns; 1905 } 1906 y_width=width+(ssize_t) floor(fabs(shear.x)*height+0.5); 1907 x_offset=(ssize_t) ceil((double) width+((fabs(shear.y)*height)-width)/2.0- 1908 0.5); 1909 y_offset=(ssize_t) ceil((double) height+((fabs(shear.y)*y_width)-height)/2.0- 1910 0.5); 1911 /* 1912 Surround image with a border. 1913 */ 1914 integral_image->border_color=integral_image->background_color; 1915 integral_image->compose=CopyCompositeOp; 1916 border_info.width=(size_t) x_offset; 1917 border_info.height=(size_t) y_offset; 1918 rotate_image=BorderImage(integral_image,&border_info,exception); 1919 integral_image=DestroyImage(integral_image); 1920 if (rotate_image == (Image *) NULL) 1921 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed"); 1922 /* 1923 Rotate the image. 1924 */ 1925 status=XShearImage(rotate_image,shear.x,width,height,x_offset,(ssize_t) 1926 (rotate_image->rows-height)/2,exception); 1927 if (status == MagickFalse) 1928 { 1929 rotate_image=DestroyImage(rotate_image); 1930 return((Image *) NULL); 1931 } 1932 status=YShearImage(rotate_image,shear.y,y_width,height,(ssize_t) 1933 (rotate_image->columns-y_width)/2,y_offset,exception); 1934 if (status == MagickFalse) 1935 { 1936 rotate_image=DestroyImage(rotate_image); 1937 return((Image *) NULL); 1938 } 1939 status=XShearImage(rotate_image,shear.x,y_width,rotate_image->rows,(ssize_t) 1940 (rotate_image->columns-y_width)/2,0,exception); 1941 if (status == MagickFalse) 1942 { 1943 rotate_image=DestroyImage(rotate_image); 1944 return((Image *) NULL); 1945 } 1946 status=CropToFitImage(&rotate_image,shear.x,shear.y,(MagickRealType) width, 1947 (MagickRealType) height,MagickTrue,exception); 1948 if (status == MagickFalse) 1949 { 1950 rotate_image=DestroyImage(rotate_image); 1951 return((Image *) NULL); 1952 } 1953 rotate_image->compose=image->compose; 1954 rotate_image->page.width=0; 1955 rotate_image->page.height=0; 1956 return(rotate_image); 1957} 1958 1959/* 1960%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 1961% % 1962% % 1963% % 1964% S h e a r I m a g e % 1965% % 1966% % 1967% % 1968%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 1969% 1970% ShearImage() creates a new image that is a shear_image copy of an existing 1971% one. Shearing slides one edge of an image along the X or Y axis, creating 1972% a parallelogram. An X direction shear slides an edge along the X axis, 1973% while a Y direction shear slides an edge along the Y axis. The amount of 1974% the shear is controlled by a shear angle. For X direction shears, x_shear 1975% is measured relative to the Y axis, and similarly, for Y direction shears 1976% y_shear is measured relative to the X axis. Empty triangles left over from 1977% shearing the image are filled with the background color defined by member 1978% 'background_color' of the image.. ShearImage() allocates the memory 1979% necessary for the new Image structure and returns a pointer to the new image. 1980% 1981% ShearImage() is based on the paper "A Fast Algorithm for General Raster 1982% Rotatation" by Alan W. Paeth. 1983% 1984% The format of the ShearImage method is: 1985% 1986% Image *ShearImage(const Image *image,const double x_shear, 1987% const double y_shear,ExceptionInfo *exception) 1988% 1989% A description of each parameter follows. 1990% 1991% o image: the image. 1992% 1993% o x_shear, y_shear: Specifies the number of degrees to shear the image. 1994% 1995% o exception: return any errors or warnings in this structure. 1996% 1997*/ 1998MagickExport Image *ShearImage(const Image *image,const double x_shear, 1999 const double y_shear,ExceptionInfo *exception) 2000{ 2001 Image 2002 *integral_image, 2003 *shear_image; 2004 2005 ssize_t 2006 x_offset, 2007 y_offset; 2008 2009 MagickBooleanType 2010 status; 2011 2012 PointInfo 2013 shear; 2014 2015 RectangleInfo 2016 border_info; 2017 2018 size_t 2019 y_width; 2020 2021 assert(image != (Image *) NULL); 2022 assert(image->signature == MagickSignature); 2023 if (image->debug != MagickFalse) 2024 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename); 2025 assert(exception != (ExceptionInfo *) NULL); 2026 assert(exception->signature == MagickSignature); 2027 if ((x_shear != 0.0) && (fmod(x_shear,90.0) == 0.0)) 2028 ThrowImageException(ImageError,"AngleIsDiscontinuous"); 2029 if ((y_shear != 0.0) && (fmod(y_shear,90.0) == 0.0)) 2030 ThrowImageException(ImageError,"AngleIsDiscontinuous"); 2031 /* 2032 Initialize shear angle. 2033 */ 2034 integral_image=CloneImage(image,0,0,MagickTrue,exception); 2035 if (integral_image == (Image *) NULL) 2036 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed"); 2037 shear.x=(-tan(DegreesToRadians(fmod(x_shear,360.0)))); 2038 shear.y=tan(DegreesToRadians(fmod(y_shear,360.0))); 2039 if ((shear.x == 0.0) && (shear.y == 0.0)) 2040 return(integral_image); 2041 if (SetImageStorageClass(integral_image,DirectClass) == MagickFalse) 2042 { 2043 InheritException(exception,&integral_image->exception); 2044 integral_image=DestroyImage(integral_image); 2045 return(integral_image); 2046 } 2047 if (integral_image->matte == MagickFalse) 2048 (void) SetImageAlphaChannel(integral_image,OpaqueAlphaChannel); 2049 /* 2050 Compute image size. 2051 */ 2052 y_width=image->columns+(ssize_t) floor(fabs(shear.x)*image->rows+0.5); 2053 x_offset=(ssize_t) ceil((double) image->columns+((fabs(shear.x)*image->rows)- 2054 image->columns)/2.0-0.5); 2055 y_offset=(ssize_t) ceil((double) image->rows+((fabs(shear.y)*y_width)- 2056 image->rows)/2.0-0.5); 2057 /* 2058 Surround image with border. 2059 */ 2060 integral_image->border_color=integral_image->background_color; 2061 integral_image->compose=CopyCompositeOp; 2062 border_info.width=(size_t) x_offset; 2063 border_info.height=(size_t) y_offset; 2064 shear_image=BorderImage(integral_image,&border_info,exception); 2065 integral_image=DestroyImage(integral_image); 2066 if (shear_image == (Image *) NULL) 2067 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed"); 2068 /* 2069 Shear the image. 2070 */ 2071 if (shear_image->matte == MagickFalse) 2072 (void) SetImageAlphaChannel(shear_image,OpaqueAlphaChannel); 2073 status=XShearImage(shear_image,shear.x,image->columns,image->rows,x_offset, 2074 (ssize_t) (shear_image->rows-image->rows)/2,exception); 2075 if (status == MagickFalse) 2076 { 2077 shear_image=DestroyImage(shear_image); 2078 return((Image *) NULL); 2079 } 2080 status=YShearImage(shear_image,shear.y,y_width,image->rows,(ssize_t) 2081 (shear_image->columns-y_width)/2,y_offset,exception); 2082 if (status == MagickFalse) 2083 { 2084 shear_image=DestroyImage(shear_image); 2085 return((Image *) NULL); 2086 } 2087 status=CropToFitImage(&shear_image,shear.x,shear.y,(MagickRealType) 2088 image->columns,(MagickRealType) image->rows,MagickFalse,exception); 2089 if (status == MagickFalse) 2090 { 2091 shear_image=DestroyImage(shear_image); 2092 return((Image *) NULL); 2093 } 2094 shear_image->compose=image->compose; 2095 shear_image->page.width=0; 2096 shear_image->page.height=0; 2097 return(shear_image); 2098} 2099