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