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