|
MagickCore
6.7.5
|
00001 /* 00002 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 00003 % % 00004 % % 00005 % % 00006 % SSSSS H H EEEEE AAA RRRR % 00007 % SS H H E A A R R % 00008 % SSS HHHHH EEE AAAAA RRRR % 00009 % SS H H E A A R R % 00010 % SSSSS H H EEEEE A A R R % 00011 % % 00012 % % 00013 % MagickCore Methods to Shear or Rotate an Image by an Arbitrary Angle % 00014 % % 00015 % Software Design % 00016 % John Cristy % 00017 % July 1992 % 00018 % % 00019 % % 00020 % Copyright 1999-2012 ImageMagick Studio LLC, a non-profit organization % 00021 % dedicated to making software imaging solutions freely available. % 00022 % % 00023 % You may not use this file except in compliance with the License. You may % 00024 % obtain a copy of the License at % 00025 % % 00026 % http://www.imagemagick.org/script/license.php % 00027 % % 00028 % Unless required by applicable law or agreed to in writing, software % 00029 % distributed under the License is distributed on an "AS IS" BASIS, % 00030 % WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. % 00031 % See the License for the specific language governing permissions and % 00032 % limitations under the License. % 00033 % % 00034 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 00035 % 00036 % The XShearImage() and YShearImage() methods are based on the paper "A Fast 00037 % Algorithm for General Raster Rotatation" by Alan W. Paeth, Graphics 00038 % Interface '86 (Vancouver). ShearRotateImage() is adapted from a similar 00039 % method based on the Paeth paper written by Michael Halle of the Spatial 00040 % Imaging Group, MIT Media Lab. 00041 % 00042 */ 00043 00044 /* 00045 Include declarations. 00046 */ 00047 #include "MagickCore/studio.h" 00048 #include "MagickCore/artifact.h" 00049 #include "MagickCore/attribute.h" 00050 #include "MagickCore/blob-private.h" 00051 #include "MagickCore/cache-private.h" 00052 #include "MagickCore/color-private.h" 00053 #include "MagickCore/colorspace-private.h" 00054 #include "MagickCore/composite.h" 00055 #include "MagickCore/composite-private.h" 00056 #include "MagickCore/decorate.h" 00057 #include "MagickCore/distort.h" 00058 #include "MagickCore/draw.h" 00059 #include "MagickCore/exception.h" 00060 #include "MagickCore/exception-private.h" 00061 #include "MagickCore/gem.h" 00062 #include "MagickCore/geometry.h" 00063 #include "MagickCore/image.h" 00064 #include "MagickCore/image-private.h" 00065 #include "MagickCore/memory_.h" 00066 #include "MagickCore/list.h" 00067 #include "MagickCore/monitor.h" 00068 #include "MagickCore/monitor-private.h" 00069 #include "MagickCore/nt-base-private.h" 00070 #include "MagickCore/pixel-accessor.h" 00071 #include "MagickCore/quantum.h" 00072 #include "MagickCore/resource_.h" 00073 #include "MagickCore/shear.h" 00074 #include "MagickCore/statistic.h" 00075 #include "MagickCore/string_.h" 00076 #include "MagickCore/string-private.h" 00077 #include "MagickCore/thread-private.h" 00078 #include "MagickCore/threshold.h" 00079 #include "MagickCore/transform.h" 00080 00081 /* 00082 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 00083 % % 00084 % % 00085 % % 00086 + C r o p T o F i t I m a g e % 00087 % % 00088 % % 00089 % % 00090 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 00091 % 00092 % CropToFitImage() crops the sheared image as determined by the bounding box 00093 % as defined by width and height and shearing angles. 00094 % 00095 % The format of the CropToFitImage method is: 00096 % 00097 % MagickBooleanType CropToFitImage(Image **image, 00098 % const MagickRealType x_shear,const MagickRealType x_shear, 00099 % const MagickRealType width,const MagickRealType height, 00100 % const MagickBooleanType rotate,ExceptionInfo *exception) 00101 % 00102 % A description of each parameter follows. 00103 % 00104 % o image: the image. 00105 % 00106 % o x_shear, y_shear, width, height: Defines a region of the image to crop. 00107 % 00108 % o exception: return any errors or warnings in this structure. 00109 % 00110 */ 00111 static MagickBooleanType CropToFitImage(Image **image, 00112 const MagickRealType x_shear,const MagickRealType y_shear, 00113 const MagickRealType width,const MagickRealType height, 00114 const MagickBooleanType rotate,ExceptionInfo *exception) 00115 { 00116 Image 00117 *crop_image; 00118 00119 PointInfo 00120 extent[4], 00121 min, 00122 max; 00123 00124 RectangleInfo 00125 geometry, 00126 page; 00127 00128 register ssize_t 00129 i; 00130 00131 /* 00132 Calculate the rotated image size. 00133 */ 00134 extent[0].x=(double) (-width/2.0); 00135 extent[0].y=(double) (-height/2.0); 00136 extent[1].x=(double) width/2.0; 00137 extent[1].y=(double) (-height/2.0); 00138 extent[2].x=(double) (-width/2.0); 00139 extent[2].y=(double) height/2.0; 00140 extent[3].x=(double) width/2.0; 00141 extent[3].y=(double) height/2.0; 00142 for (i=0; i < 4; i++) 00143 { 00144 extent[i].x+=x_shear*extent[i].y; 00145 extent[i].y+=y_shear*extent[i].x; 00146 if (rotate != MagickFalse) 00147 extent[i].x+=x_shear*extent[i].y; 00148 extent[i].x+=(double) (*image)->columns/2.0; 00149 extent[i].y+=(double) (*image)->rows/2.0; 00150 } 00151 min=extent[0]; 00152 max=extent[0]; 00153 for (i=1; i < 4; i++) 00154 { 00155 if (min.x > extent[i].x) 00156 min.x=extent[i].x; 00157 if (min.y > extent[i].y) 00158 min.y=extent[i].y; 00159 if (max.x < extent[i].x) 00160 max.x=extent[i].x; 00161 if (max.y < extent[i].y) 00162 max.y=extent[i].y; 00163 } 00164 geometry.x=(ssize_t) ceil(min.x-0.5); 00165 geometry.y=(ssize_t) ceil(min.y-0.5); 00166 geometry.width=(size_t) floor(max.x-min.x+0.5); 00167 geometry.height=(size_t) floor(max.y-min.y+0.5); 00168 page=(*image)->page; 00169 (void) ParseAbsoluteGeometry("0x0+0+0",&(*image)->page); 00170 crop_image=CropImage(*image,&geometry,exception); 00171 if (crop_image == (Image *) NULL) 00172 return(MagickFalse); 00173 crop_image->page=page; 00174 *image=DestroyImage(*image); 00175 *image=crop_image; 00176 return(MagickTrue); 00177 } 00178 00179 /* 00180 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 00181 % % 00182 % % 00183 % % 00184 % D e s k e w I m a g e % 00185 % % 00186 % % 00187 % % 00188 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 00189 % 00190 % DeskewImage() removes skew from the image. Skew is an artifact that 00191 % occurs in scanned images because of the camera being misaligned, 00192 % imperfections in the scanning or surface, or simply because the paper was 00193 % not placed completely flat when scanned. 00194 % 00195 % The format of the DeskewImage method is: 00196 % 00197 % Image *DeskewImage(const Image *image,const double threshold, 00198 % ExceptionInfo *exception) 00199 % 00200 % A description of each parameter follows: 00201 % 00202 % o image: the image. 00203 % 00204 % o threshold: separate background from foreground. 00205 % 00206 % o exception: return any errors or warnings in this structure. 00207 % 00208 */ 00209 00210 typedef struct _RadonInfo 00211 { 00212 CacheType 00213 type; 00214 00215 size_t 00216 width, 00217 height; 00218 00219 MagickSizeType 00220 length; 00221 00222 MagickBooleanType 00223 mapped; 00224 00225 char 00226 path[MaxTextExtent]; 00227 00228 int 00229 file; 00230 00231 unsigned short 00232 *cells; 00233 } RadonInfo; 00234 00235 static RadonInfo *DestroyRadonInfo(RadonInfo *radon_info) 00236 { 00237 assert(radon_info != (RadonInfo *) NULL); 00238 switch (radon_info->type) 00239 { 00240 case MemoryCache: 00241 { 00242 if (radon_info->mapped == MagickFalse) 00243 radon_info->cells=(unsigned short *) RelinquishMagickMemory( 00244 radon_info->cells); 00245 else 00246 radon_info->cells=(unsigned short *) UnmapBlob(radon_info->cells, 00247 (size_t) radon_info->length); 00248 RelinquishMagickResource(MemoryResource,radon_info->length); 00249 break; 00250 } 00251 case MapCache: 00252 { 00253 radon_info->cells=(unsigned short *) UnmapBlob(radon_info->cells,(size_t) 00254 radon_info->length); 00255 RelinquishMagickResource(MapResource,radon_info->length); 00256 } 00257 case DiskCache: 00258 { 00259 if (radon_info->file != -1) 00260 (void) close(radon_info->file); 00261 (void) RelinquishUniqueFileResource(radon_info->path); 00262 RelinquishMagickResource(DiskResource,radon_info->length); 00263 break; 00264 } 00265 default: 00266 break; 00267 } 00268 return((RadonInfo *) RelinquishMagickMemory(radon_info)); 00269 } 00270 00271 static MagickBooleanType ResetRadonCells(RadonInfo *radon_info) 00272 { 00273 register ssize_t 00274 x; 00275 00276 ssize_t 00277 count, 00278 y; 00279 00280 unsigned short 00281 value; 00282 00283 if (radon_info->type != DiskCache) 00284 { 00285 (void) ResetMagickMemory(radon_info->cells,0,(size_t) radon_info->length); 00286 return(MagickTrue); 00287 } 00288 value=0; 00289 (void) lseek(radon_info->file,0,SEEK_SET); 00290 for (y=0; y < (ssize_t) radon_info->height; y++) 00291 { 00292 for (x=0; x < (ssize_t) radon_info->width; x++) 00293 { 00294 count=write(radon_info->file,&value,sizeof(*radon_info->cells)); 00295 if (count != (ssize_t) sizeof(*radon_info->cells)) 00296 break; 00297 } 00298 if (x < (ssize_t) radon_info->width) 00299 break; 00300 } 00301 return(y < (ssize_t) radon_info->height ? MagickFalse : MagickTrue); 00302 } 00303 00304 static RadonInfo *AcquireRadonInfo(const Image *image,const size_t width, 00305 const size_t height,ExceptionInfo *exception) 00306 { 00307 MagickBooleanType 00308 status; 00309 00310 RadonInfo 00311 *radon_info; 00312 00313 radon_info=(RadonInfo *) AcquireMagickMemory(sizeof(*radon_info)); 00314 if (radon_info == (RadonInfo *) NULL) 00315 return((RadonInfo *) NULL); 00316 (void) ResetMagickMemory(radon_info,0,sizeof(*radon_info)); 00317 radon_info->width=width; 00318 radon_info->height=height; 00319 radon_info->length=(MagickSizeType) width*height*sizeof(*radon_info->cells); 00320 radon_info->type=MemoryCache; 00321 status=AcquireMagickResource(AreaResource,radon_info->length); 00322 if ((status != MagickFalse) && 00323 (radon_info->length == (MagickSizeType) ((size_t) radon_info->length))) 00324 { 00325 status=AcquireMagickResource(MemoryResource,radon_info->length); 00326 if (status != MagickFalse) 00327 { 00328 radon_info->mapped=MagickFalse; 00329 radon_info->cells=(unsigned short *) AcquireMagickMemory((size_t) 00330 radon_info->length); 00331 if (radon_info->cells == (unsigned short *) NULL) 00332 { 00333 radon_info->mapped=MagickTrue; 00334 radon_info->cells=(unsigned short *) MapBlob(-1,IOMode,0,(size_t) 00335 radon_info->length); 00336 } 00337 if (radon_info->cells == (unsigned short *) NULL) 00338 RelinquishMagickResource(MemoryResource,radon_info->length); 00339 } 00340 } 00341 radon_info->file=(-1); 00342 if (radon_info->cells == (unsigned short *) NULL) 00343 { 00344 status=AcquireMagickResource(DiskResource,radon_info->length); 00345 if (status == MagickFalse) 00346 { 00347 (void) ThrowMagickException(exception,GetMagickModule(),CacheError, 00348 "CacheResourcesExhausted","`%s'",image->filename); 00349 return(DestroyRadonInfo(radon_info)); 00350 } 00351 radon_info->type=DiskCache; 00352 (void) AcquireMagickResource(MemoryResource,radon_info->length); 00353 radon_info->file=AcquireUniqueFileResource(radon_info->path); 00354 if (radon_info->file == -1) 00355 return(DestroyRadonInfo(radon_info)); 00356 status=AcquireMagickResource(MapResource,radon_info->length); 00357 if (status != MagickFalse) 00358 { 00359 status=ResetRadonCells(radon_info); 00360 if (status != MagickFalse) 00361 { 00362 radon_info->cells=(unsigned short *) MapBlob(radon_info->file, 00363 IOMode,0,(size_t) radon_info->length); 00364 if (radon_info->cells != (unsigned short *) NULL) 00365 radon_info->type=MapCache; 00366 else 00367 RelinquishMagickResource(MapResource,radon_info->length); 00368 } 00369 } 00370 } 00371 return(radon_info); 00372 } 00373 00374 static inline size_t MagickMin(const size_t x,const size_t y) 00375 { 00376 if (x < y) 00377 return(x); 00378 return(y); 00379 } 00380 00381 static inline ssize_t ReadRadonCell(const RadonInfo *radon_info, 00382 const MagickOffsetType offset,const size_t length,unsigned char *buffer) 00383 { 00384 register ssize_t 00385 i; 00386 00387 ssize_t 00388 count; 00389 00390 #if !defined(MAGICKCORE_HAVE_PPREAD) 00391 #if defined(MAGICKCORE_OPENMP_SUPPORT) 00392 #pragma omp critical (MagickCore_ReadRadonCell) 00393 #endif 00394 { 00395 i=(-1); 00396 if (lseek(radon_info->file,offset,SEEK_SET) >= 0) 00397 { 00398 #endif 00399 count=0; 00400 for (i=0; i < (ssize_t) length; i+=count) 00401 { 00402 #if !defined(MAGICKCORE_HAVE_PPREAD) 00403 count=read(radon_info->file,buffer+i,MagickMin(length-i,(size_t) 00404 SSIZE_MAX)); 00405 #else 00406 count=pread(radon_info->file,buffer+i,MagickMin(length-i,(size_t) 00407 SSIZE_MAX),offset+i); 00408 #endif 00409 if (count > 0) 00410 continue; 00411 count=0; 00412 if (errno != EINTR) 00413 { 00414 i=(-1); 00415 break; 00416 } 00417 } 00418 #if !defined(MAGICKCORE_HAVE_PPREAD) 00419 } 00420 } 00421 #endif 00422 return(i); 00423 } 00424 00425 static inline ssize_t WriteRadonCell(const RadonInfo *radon_info, 00426 const MagickOffsetType offset,const size_t length,const unsigned char *buffer) 00427 { 00428 register ssize_t 00429 i; 00430 00431 ssize_t 00432 count; 00433 00434 #if !defined(MAGICKCORE_HAVE_PWRITE) 00435 #if defined(MAGICKCORE_OPENMP_SUPPORT) 00436 #pragma omp critical (MagickCore_WriteRadonCell) 00437 #endif 00438 { 00439 if (lseek(radon_info->file,offset,SEEK_SET) >= 0) 00440 { 00441 #endif 00442 count=0; 00443 for (i=0; i < (ssize_t) length; i+=count) 00444 { 00445 #if !defined(MAGICKCORE_HAVE_PWRITE) 00446 count=write(radon_info->file,buffer+i,MagickMin(length-i,(size_t) 00447 SSIZE_MAX)); 00448 #else 00449 count=pwrite(radon_info->file,buffer+i,MagickMin(length-i,(size_t) 00450 SSIZE_MAX),offset+i); 00451 #endif 00452 if (count > 0) 00453 continue; 00454 count=0; 00455 if (errno != EINTR) 00456 { 00457 i=(-1); 00458 break; 00459 } 00460 } 00461 #if !defined(MAGICKCORE_HAVE_PWRITE) 00462 } 00463 } 00464 #endif 00465 return(i); 00466 } 00467 00468 static inline unsigned short GetRadonCell(const RadonInfo *radon_info, 00469 const ssize_t x,const ssize_t y) 00470 { 00471 MagickOffsetType 00472 i; 00473 00474 unsigned short 00475 value; 00476 00477 i=(MagickOffsetType) radon_info->height*x+y; 00478 if ((i < 0) || 00479 ((MagickSizeType) (i*sizeof(*radon_info->cells)) >= radon_info->length)) 00480 return(0); 00481 if (radon_info->type != DiskCache) 00482 return(radon_info->cells[i]); 00483 value=0; 00484 (void) ReadRadonCell(radon_info,i*sizeof(*radon_info->cells), 00485 sizeof(*radon_info->cells),(unsigned char *) &value); 00486 return(value); 00487 } 00488 00489 static inline MagickBooleanType SetRadonCell(const RadonInfo *radon_info, 00490 const ssize_t x,const ssize_t y,const unsigned short value) 00491 { 00492 MagickOffsetType 00493 i; 00494 00495 ssize_t 00496 count; 00497 00498 i=(MagickOffsetType) radon_info->height*x+y; 00499 if ((i < 0) || 00500 ((MagickSizeType) (i*sizeof(*radon_info->cells)) >= radon_info->length)) 00501 return(MagickFalse); 00502 if (radon_info->type != DiskCache) 00503 { 00504 radon_info->cells[i]=value; 00505 return(MagickTrue); 00506 } 00507 count=WriteRadonCell(radon_info,i*sizeof(*radon_info->cells), 00508 sizeof(*radon_info->cells),(const unsigned char *) &value); 00509 if (count != (ssize_t) sizeof(*radon_info->cells)) 00510 return(MagickFalse); 00511 return(MagickTrue); 00512 } 00513 00514 static void RadonProjection(RadonInfo *source_cells, 00515 RadonInfo *destination_cells,const ssize_t sign,size_t *projection) 00516 { 00517 RadonInfo 00518 *swap; 00519 00520 register ssize_t 00521 x; 00522 00523 register RadonInfo 00524 *p, 00525 *q; 00526 00527 size_t 00528 step; 00529 00530 p=source_cells; 00531 q=destination_cells; 00532 for (step=1; step < p->width; step*=2) 00533 { 00534 for (x=0; x < (ssize_t) p->width; x+=2*(ssize_t) step) 00535 { 00536 register ssize_t 00537 i; 00538 00539 ssize_t 00540 y; 00541 00542 unsigned short 00543 cell; 00544 00545 for (i=0; i < (ssize_t) step; i++) 00546 { 00547 for (y=0; y < (ssize_t) (p->height-i-1); y++) 00548 { 00549 cell=GetRadonCell(p,x+i,y); 00550 (void) SetRadonCell(q,x+2*i,y,cell+GetRadonCell(p,x+i+(ssize_t) 00551 step,y+i)); 00552 (void) SetRadonCell(q,x+2*i+1,y,cell+GetRadonCell(p,x+i+(ssize_t) 00553 step,y+i+1)); 00554 } 00555 for ( ; y < (ssize_t) (p->height-i); y++) 00556 { 00557 cell=GetRadonCell(p,x+i,y); 00558 (void) SetRadonCell(q,x+2*i,y,cell+GetRadonCell(p,x+i+(ssize_t) step, 00559 y+i)); 00560 (void) SetRadonCell(q,x+2*i+1,y,cell); 00561 } 00562 for ( ; y < (ssize_t) p->height; y++) 00563 { 00564 cell=GetRadonCell(p,x+i,y); 00565 (void) SetRadonCell(q,x+2*i,y,cell); 00566 (void) SetRadonCell(q,x+2*i+1,y,cell); 00567 } 00568 } 00569 } 00570 swap=p; 00571 p=q; 00572 q=swap; 00573 } 00574 #if defined(MAGICKCORE_OPENMP_SUPPORT) 00575 #pragma omp parallel for schedule(static,4) 00576 #endif 00577 for (x=0; x < (ssize_t) p->width; x++) 00578 { 00579 register ssize_t 00580 y; 00581 00582 size_t 00583 sum; 00584 00585 sum=0; 00586 for (y=0; y < (ssize_t) (p->height-1); y++) 00587 { 00588 ssize_t 00589 delta; 00590 00591 delta=GetRadonCell(p,x,y)-(ssize_t) GetRadonCell(p,x,y+1); 00592 sum+=delta*delta; 00593 } 00594 projection[p->width+sign*x-1]=sum; 00595 } 00596 } 00597 00598 static MagickBooleanType RadonTransform(const Image *image, 00599 const double threshold,size_t *projection,ExceptionInfo *exception) 00600 { 00601 CacheView 00602 *image_view; 00603 00604 MagickBooleanType 00605 status; 00606 00607 RadonInfo 00608 *destination_cells, 00609 *source_cells; 00610 00611 register ssize_t 00612 i; 00613 00614 size_t 00615 count, 00616 width; 00617 00618 ssize_t 00619 y; 00620 00621 unsigned char 00622 byte; 00623 00624 unsigned short 00625 bits[256]; 00626 00627 for (width=1; width < ((image->columns+7)/8); width<<=1) ; 00628 source_cells=AcquireRadonInfo(image,width,image->rows,exception); 00629 destination_cells=AcquireRadonInfo(image,width,image->rows,exception); 00630 if ((source_cells == (RadonInfo *) NULL) || 00631 (destination_cells == (RadonInfo *) NULL)) 00632 { 00633 if (destination_cells != (RadonInfo *) NULL) 00634 destination_cells=DestroyRadonInfo(destination_cells); 00635 if (source_cells != (RadonInfo *) NULL) 00636 source_cells=DestroyRadonInfo(source_cells); 00637 return(MagickFalse); 00638 } 00639 if (ResetRadonCells(source_cells) == MagickFalse) 00640 { 00641 destination_cells=DestroyRadonInfo(destination_cells); 00642 source_cells=DestroyRadonInfo(source_cells); 00643 return(MagickFalse); 00644 } 00645 for (i=0; i < 256; i++) 00646 { 00647 byte=(unsigned char) i; 00648 for (count=0; byte != 0; byte>>=1) 00649 count+=byte & 0x01; 00650 bits[i]=(unsigned short) count; 00651 } 00652 status=MagickTrue; 00653 image_view=AcquireCacheView(image); 00654 #if defined(MAGICKCORE_OPENMP_SUPPORT) 00655 #pragma omp parallel for schedule(static,4) shared(status) 00656 #endif 00657 for (y=0; y < (ssize_t) image->rows; y++) 00658 { 00659 register const Quantum 00660 *restrict p; 00661 00662 register ssize_t 00663 i, 00664 x; 00665 00666 size_t 00667 bit, 00668 byte; 00669 00670 if (status == MagickFalse) 00671 continue; 00672 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception); 00673 if (p == (const Quantum *) NULL) 00674 { 00675 status=MagickFalse; 00676 continue; 00677 } 00678 bit=0; 00679 byte=0; 00680 i=(ssize_t) (image->columns+7)/8; 00681 for (x=0; x < (ssize_t) image->columns; x++) 00682 { 00683 byte<<=1; 00684 if ((double) GetPixelIntensity(image,p) < threshold) 00685 byte|=0x01; 00686 bit++; 00687 if (bit == 8) 00688 { 00689 (void) SetRadonCell(source_cells,--i,y,bits[byte]); 00690 bit=0; 00691 byte=0; 00692 } 00693 p+=GetPixelChannels(image); 00694 } 00695 if (bit != 0) 00696 { 00697 byte<<=(8-bit); 00698 (void) SetRadonCell(source_cells,--i,y,bits[byte]); 00699 } 00700 } 00701 RadonProjection(source_cells,destination_cells,-1,projection); 00702 (void) ResetRadonCells(source_cells); 00703 #if defined(MAGICKCORE_OPENMP_SUPPORT) 00704 #pragma omp parallel for schedule(static,4) shared(status) 00705 #endif 00706 for (y=0; y < (ssize_t) image->rows; y++) 00707 { 00708 register const Quantum 00709 *restrict p; 00710 00711 register ssize_t 00712 i, 00713 x; 00714 00715 size_t 00716 bit, 00717 byte; 00718 00719 if (status == MagickFalse) 00720 continue; 00721 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception); 00722 if (p == (const Quantum *) NULL) 00723 { 00724 status=MagickFalse; 00725 continue; 00726 } 00727 bit=0; 00728 byte=0; 00729 i=0; 00730 for (x=0; x < (ssize_t) image->columns; x++) 00731 { 00732 byte<<=1; 00733 if ((double) GetPixelIntensity(image,p) < threshold) 00734 byte|=0x01; 00735 bit++; 00736 if (bit == 8) 00737 { 00738 (void) SetRadonCell(source_cells,i++,y,bits[byte]); 00739 bit=0; 00740 byte=0; 00741 } 00742 p+=GetPixelChannels(image); 00743 } 00744 if (bit != 0) 00745 { 00746 byte<<=(8-bit); 00747 (void) SetRadonCell(source_cells,i++,y,bits[byte]); 00748 } 00749 } 00750 RadonProjection(source_cells,destination_cells,1,projection); 00751 image_view=DestroyCacheView(image_view); 00752 destination_cells=DestroyRadonInfo(destination_cells); 00753 source_cells=DestroyRadonInfo(source_cells); 00754 return(MagickTrue); 00755 } 00756 00757 static void GetImageBackgroundColor(Image *image,const ssize_t offset, 00758 ExceptionInfo *exception) 00759 { 00760 CacheView 00761 *image_view; 00762 00763 PixelInfo 00764 background; 00765 00766 MagickRealType 00767 count; 00768 00769 ssize_t 00770 y; 00771 00772 /* 00773 Compute average background color. 00774 */ 00775 if (offset <= 0) 00776 return; 00777 GetPixelInfo(image,&background); 00778 count=0.0; 00779 image_view=AcquireCacheView(image); 00780 for (y=0; y < (ssize_t) image->rows; y++) 00781 { 00782 register const Quantum 00783 *restrict p; 00784 00785 register ssize_t 00786 x; 00787 00788 if ((y >= offset) && (y < ((ssize_t) image->rows-offset))) 00789 continue; 00790 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception); 00791 if (p == (const Quantum *) NULL) 00792 continue; 00793 for (x=0; x < (ssize_t) image->columns; x++) 00794 { 00795 if ((x >= offset) && (x < ((ssize_t) image->columns-offset))) 00796 continue; 00797 background.red+=QuantumScale*GetPixelRed(image,p); 00798 background.green+=QuantumScale*GetPixelGreen(image,p); 00799 background.blue+=QuantumScale*GetPixelBlue(image,p); 00800 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) 00801 background.alpha+=QuantumScale*GetPixelAlpha(image,p); 00802 count++; 00803 p+=GetPixelChannels(image); 00804 } 00805 } 00806 image_view=DestroyCacheView(image_view); 00807 image->background_color.red=(double) ClampToQuantum((MagickRealType) 00808 QuantumRange*background.red/count); 00809 image->background_color.green=(double) ClampToQuantum((MagickRealType) 00810 QuantumRange*background.green/count); 00811 image->background_color.blue=(double) ClampToQuantum((MagickRealType) 00812 QuantumRange*background.blue/count); 00813 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) 00814 image->background_color.alpha=(double) ClampToQuantum((MagickRealType) 00815 QuantumRange*background.alpha/count); 00816 } 00817 00818 MagickExport Image *DeskewImage(const Image *image,const double threshold, 00819 ExceptionInfo *exception) 00820 { 00821 AffineMatrix 00822 affine_matrix; 00823 00824 const char 00825 *artifact; 00826 00827 double 00828 degrees; 00829 00830 Image 00831 *clone_image, 00832 *crop_image, 00833 *deskew_image, 00834 *median_image; 00835 00836 MagickBooleanType 00837 status; 00838 00839 RectangleInfo 00840 geometry; 00841 00842 register ssize_t 00843 i; 00844 00845 size_t 00846 max_projection, 00847 *projection, 00848 width; 00849 00850 ssize_t 00851 skew; 00852 00853 /* 00854 Compute deskew angle. 00855 */ 00856 for (width=1; width < ((image->columns+7)/8); width<<=1) ; 00857 projection=(size_t *) AcquireQuantumMemory((size_t) (2*width-1), 00858 sizeof(*projection)); 00859 if (projection == (size_t *) NULL) 00860 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed"); 00861 status=RadonTransform(image,threshold,projection,exception); 00862 if (status == MagickFalse) 00863 { 00864 projection=(size_t *) RelinquishMagickMemory(projection); 00865 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed"); 00866 } 00867 max_projection=0; 00868 skew=0; 00869 for (i=0; i < (ssize_t) (2*width-1); i++) 00870 { 00871 if (projection[i] > max_projection) 00872 { 00873 skew=i-(ssize_t) width+1; 00874 max_projection=projection[i]; 00875 } 00876 } 00877 projection=(size_t *) RelinquishMagickMemory(projection); 00878 /* 00879 Deskew image. 00880 */ 00881 clone_image=CloneImage(image,0,0,MagickTrue,exception); 00882 if (clone_image == (Image *) NULL) 00883 return((Image *) NULL); 00884 (void) SetImageVirtualPixelMethod(clone_image,BackgroundVirtualPixelMethod, 00885 exception); 00886 degrees=RadiansToDegrees(-atan((double) skew/width/8)); 00887 if (image->debug != MagickFalse) 00888 (void) LogMagickEvent(TransformEvent,GetMagickModule(), 00889 " Deskew angle: %g",degrees); 00890 affine_matrix.sx=cos(DegreesToRadians(fmod((double) degrees,360.0))); 00891 affine_matrix.rx=sin(DegreesToRadians(fmod((double) degrees,360.0))); 00892 affine_matrix.ry=(-sin(DegreesToRadians(fmod((double) degrees,360.0)))); 00893 affine_matrix.sy=cos(DegreesToRadians(fmod((double) degrees,360.0))); 00894 affine_matrix.tx=0.0; 00895 affine_matrix.ty=0.0; 00896 artifact=GetImageArtifact(image,"deskew:auto-crop"); 00897 if (artifact == (const char *) NULL) 00898 { 00899 deskew_image=AffineTransformImage(clone_image,&affine_matrix,exception); 00900 clone_image=DestroyImage(clone_image); 00901 return(deskew_image); 00902 } 00903 /* 00904 Auto-crop image. 00905 */ 00906 GetImageBackgroundColor(clone_image,(ssize_t) StringToLong(artifact), 00907 exception); 00908 deskew_image=AffineTransformImage(clone_image,&affine_matrix,exception); 00909 clone_image=DestroyImage(clone_image); 00910 if (deskew_image == (Image *) NULL) 00911 return((Image *) NULL); 00912 median_image=StatisticImage(deskew_image,MedianStatistic,3,3,exception); 00913 if (median_image == (Image *) NULL) 00914 { 00915 deskew_image=DestroyImage(deskew_image); 00916 return((Image *) NULL); 00917 } 00918 geometry=GetImageBoundingBox(median_image,exception); 00919 median_image=DestroyImage(median_image); 00920 if (image->debug != MagickFalse) 00921 (void) LogMagickEvent(TransformEvent,GetMagickModule()," Deskew geometry: " 00922 "%.20gx%.20g%+.20g%+.20g",(double) geometry.width,(double) 00923 geometry.height,(double) geometry.x,(double) geometry.y); 00924 crop_image=CropImage(deskew_image,&geometry,exception); 00925 deskew_image=DestroyImage(deskew_image); 00926 return(crop_image); 00927 } 00928 00929 /* 00930 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 00931 % % 00932 % % 00933 % % 00934 % I n t e g r a l R o t a t e I m a g e % 00935 % % 00936 % % 00937 % % 00938 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 00939 % 00940 % IntegralRotateImage() rotates the image an integral of 90 degrees. It 00941 % allocates the memory necessary for the new Image structure and returns a 00942 % pointer to the rotated image. 00943 % 00944 % The format of the IntegralRotateImage method is: 00945 % 00946 % Image *IntegralRotateImage(const Image *image,size_t rotations, 00947 % ExceptionInfo *exception) 00948 % 00949 % A description of each parameter follows. 00950 % 00951 % o image: the image. 00952 % 00953 % o rotations: Specifies the number of 90 degree rotations. 00954 % 00955 */ 00956 MagickExport Image *IntegralRotateImage(const Image *image,size_t rotations, 00957 ExceptionInfo *exception) 00958 { 00959 #define RotateImageTag "Rotate/Image" 00960 00961 CacheView 00962 *image_view, 00963 *rotate_view; 00964 00965 Image 00966 *rotate_image; 00967 00968 MagickBooleanType 00969 status; 00970 00971 MagickOffsetType 00972 progress; 00973 00974 RectangleInfo 00975 page; 00976 00977 ssize_t 00978 y; 00979 00980 /* 00981 Initialize rotated image attributes. 00982 */ 00983 assert(image != (Image *) NULL); 00984 page=image->page; 00985 rotations%=4; 00986 if (rotations == 0) 00987 return(CloneImage(image,0,0,MagickTrue,exception)); 00988 if ((rotations == 1) || (rotations == 3)) 00989 rotate_image=CloneImage(image,image->rows,image->columns,MagickTrue, 00990 exception); 00991 else 00992 rotate_image=CloneImage(image,image->columns,image->rows,MagickTrue, 00993 exception); 00994 if (rotate_image == (Image *) NULL) 00995 return((Image *) NULL); 00996 /* 00997 Integral rotate the image. 00998 */ 00999 status=MagickTrue; 01000 progress=0; 01001 image_view=AcquireCacheView(image); 01002 rotate_view=AcquireCacheView(rotate_image); 01003 switch (rotations) 01004 { 01005 case 0: 01006 { 01007 /* 01008 Rotate 0 degrees. 01009 */ 01010 break; 01011 } 01012 case 1: 01013 { 01014 size_t 01015 tile_height, 01016 tile_width; 01017 01018 ssize_t 01019 tile_y; 01020 01021 /* 01022 Rotate 90 degrees. 01023 */ 01024 GetPixelCacheTileSize(image,&tile_width,&tile_height); 01025 #if defined(MAGICKCORE_OPENMP_SUPPORT) 01026 #pragma omp parallel for schedule(static,4) shared(progress,status) 01027 #endif 01028 for (tile_y=0; tile_y < (ssize_t) image->rows; tile_y+=(ssize_t) tile_height) 01029 { 01030 register ssize_t 01031 tile_x; 01032 01033 if (status == MagickFalse) 01034 continue; 01035 tile_x=0; 01036 for ( ; tile_x < (ssize_t) image->columns; tile_x+=(ssize_t) tile_width) 01037 { 01038 MagickBooleanType 01039 sync; 01040 01041 register const Quantum 01042 *restrict p; 01043 01044 register Quantum 01045 *restrict q; 01046 01047 register ssize_t 01048 y; 01049 01050 size_t 01051 height, 01052 width; 01053 01054 width=tile_width; 01055 if ((tile_x+(ssize_t) tile_width) > (ssize_t) image->columns) 01056 width=(size_t) (tile_width-(tile_x+tile_width-image->columns)); 01057 height=tile_height; 01058 if ((tile_y+(ssize_t) tile_height) > (ssize_t) image->rows) 01059 height=(size_t) (tile_height-(tile_y+tile_height-image->rows)); 01060 p=GetCacheViewVirtualPixels(image_view,tile_x,tile_y,width,height, 01061 exception); 01062 if (p == (const Quantum *) NULL) 01063 { 01064 status=MagickFalse; 01065 break; 01066 } 01067 for (y=0; y < (ssize_t) width; y++) 01068 { 01069 register const Quantum 01070 *restrict tile_pixels; 01071 01072 register ssize_t 01073 x; 01074 01075 if (status == MagickFalse) 01076 continue; 01077 q=QueueCacheViewAuthenticPixels(rotate_view,(ssize_t) 01078 (rotate_image->columns-(tile_y+height)),y+tile_x,height,1, 01079 exception); 01080 if (q == (Quantum *) NULL) 01081 { 01082 status=MagickFalse; 01083 continue; 01084 } 01085 tile_pixels=p+((height-1)*width+y)*GetPixelChannels(image); 01086 for (x=0; x < (ssize_t) height; x++) 01087 { 01088 register ssize_t 01089 i; 01090 01091 if (GetPixelMask(image,tile_pixels) != 0) 01092 { 01093 tile_pixels-=width*GetPixelChannels(image); 01094 q+=GetPixelChannels(rotate_image); 01095 continue; 01096 } 01097 for (i=0; i < (ssize_t) GetPixelChannels(image); i++) 01098 { 01099 PixelChannel 01100 channel; 01101 01102 PixelTrait 01103 rotate_traits, 01104 traits; 01105 01106 channel=GetPixelChannelMapChannel(image,i); 01107 traits=GetPixelChannelMapTraits(image,channel); 01108 rotate_traits=GetPixelChannelMapTraits(rotate_image,channel); 01109 if ((traits == UndefinedPixelTrait) || 01110 (rotate_traits == UndefinedPixelTrait)) 01111 continue; 01112 SetPixelChannel(rotate_image,channel,tile_pixels[i],q); 01113 } 01114 tile_pixels-=width*GetPixelChannels(image); 01115 q+=GetPixelChannels(rotate_image); 01116 } 01117 sync=SyncCacheViewAuthenticPixels(rotate_view,exception); 01118 if (sync == MagickFalse) 01119 status=MagickFalse; 01120 } 01121 } 01122 if (image->progress_monitor != (MagickProgressMonitor) NULL) 01123 { 01124 MagickBooleanType 01125 proceed; 01126 01127 #if defined(MAGICKCORE_OPENMP_SUPPORT) 01128 #pragma omp critical (MagickCore_IntegralRotateImage) 01129 #endif 01130 proceed=SetImageProgress(image,RotateImageTag,progress+=tile_height, 01131 image->rows); 01132 if (proceed == MagickFalse) 01133 status=MagickFalse; 01134 } 01135 } 01136 (void) SetImageProgress(image,RotateImageTag,(MagickOffsetType) 01137 image->rows-1,image->rows); 01138 Swap(page.width,page.height); 01139 Swap(page.x,page.y); 01140 if (page.width != 0) 01141 page.x=(ssize_t) (page.width-rotate_image->columns-page.x); 01142 break; 01143 } 01144 case 2: 01145 { 01146 /* 01147 Rotate 180 degrees. 01148 */ 01149 #if defined(MAGICKCORE_OPENMP_SUPPORT) 01150 #pragma omp parallel for schedule(static) shared(progress,status) 01151 #endif 01152 for (y=0; y < (ssize_t) image->rows; y++) 01153 { 01154 MagickBooleanType 01155 sync; 01156 01157 register const Quantum 01158 *restrict p; 01159 01160 register Quantum 01161 *restrict q; 01162 01163 register ssize_t 01164 x; 01165 01166 if (status == MagickFalse) 01167 continue; 01168 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception); 01169 q=QueueCacheViewAuthenticPixels(rotate_view,0,(ssize_t) (image->rows-y- 01170 1),image->columns,1,exception); 01171 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL)) 01172 { 01173 status=MagickFalse; 01174 continue; 01175 } 01176 q+=GetPixelChannels(rotate_image)*image->columns; 01177 for (x=0; x < (ssize_t) image->columns; x++) 01178 { 01179 register ssize_t 01180 i; 01181 01182 q-=GetPixelChannels(rotate_image); 01183 if (GetPixelMask(image,p) != 0) 01184 { 01185 p+=GetPixelChannels(image); 01186 continue; 01187 } 01188 for (i=0; i < (ssize_t) GetPixelChannels(image); i++) 01189 { 01190 PixelChannel 01191 channel; 01192 01193 PixelTrait 01194 rotate_traits, 01195 traits; 01196 01197 channel=GetPixelChannelMapChannel(image,i); 01198 traits=GetPixelChannelMapTraits(image,channel); 01199 rotate_traits=GetPixelChannelMapTraits(rotate_image,channel); 01200 if ((traits == UndefinedPixelTrait) || 01201 (rotate_traits == UndefinedPixelTrait)) 01202 continue; 01203 SetPixelChannel(rotate_image,channel,p[i],q); 01204 } 01205 p+=GetPixelChannels(image); 01206 } 01207 sync=SyncCacheViewAuthenticPixels(rotate_view,exception); 01208 if (sync == MagickFalse) 01209 status=MagickFalse; 01210 if (image->progress_monitor != (MagickProgressMonitor) NULL) 01211 { 01212 MagickBooleanType 01213 proceed; 01214 01215 #if defined(MAGICKCORE_OPENMP_SUPPORT) 01216 #pragma omp critical (MagickCore_IntegralRotateImage) 01217 #endif 01218 proceed=SetImageProgress(image,RotateImageTag,progress++, 01219 image->rows); 01220 if (proceed == MagickFalse) 01221 status=MagickFalse; 01222 } 01223 } 01224 (void) SetImageProgress(image,RotateImageTag,(MagickOffsetType) 01225 image->rows-1,image->rows); 01226 Swap(page.width,page.height); 01227 Swap(page.x,page.y); 01228 if (page.width != 0) 01229 page.x=(ssize_t) (page.width-rotate_image->columns-page.x); 01230 break; 01231 } 01232 case 3: 01233 { 01234 size_t 01235 tile_height, 01236 tile_width; 01237 01238 ssize_t 01239 tile_y; 01240 01241 /* 01242 Rotate 270 degrees. 01243 */ 01244 GetPixelCacheTileSize(image,&tile_width,&tile_height); 01245 #if defined(MAGICKCORE_OPENMP_SUPPORT) 01246 #pragma omp parallel for schedule(static,4) shared(progress,status) 01247 #endif 01248 for (tile_y=0; tile_y < (ssize_t) image->rows; tile_y+=(ssize_t) tile_height) 01249 { 01250 register ssize_t 01251 tile_x; 01252 01253 if (status == MagickFalse) 01254 continue; 01255 tile_x=0; 01256 for ( ; tile_x < (ssize_t) image->columns; tile_x+=(ssize_t) tile_width) 01257 { 01258 MagickBooleanType 01259 sync; 01260 01261 register const Quantum 01262 *restrict p; 01263 01264 register Quantum 01265 *restrict q; 01266 01267 register ssize_t 01268 y; 01269 01270 size_t 01271 height, 01272 width; 01273 01274 width=tile_width; 01275 if ((tile_x+(ssize_t) tile_width) > (ssize_t) image->columns) 01276 width=(size_t) (tile_width-(tile_x+tile_width-image->columns)); 01277 height=tile_height; 01278 if ((tile_y+(ssize_t) tile_height) > (ssize_t) image->rows) 01279 height=(size_t) (tile_height-(tile_y+tile_height-image->rows)); 01280 p=GetCacheViewVirtualPixels(image_view,tile_x,tile_y,width,height, 01281 exception); 01282 if (p == (const Quantum *) NULL) 01283 { 01284 status=MagickFalse; 01285 break; 01286 } 01287 for (y=0; y < (ssize_t) width; y++) 01288 { 01289 register const Quantum 01290 *restrict tile_pixels; 01291 01292 register ssize_t 01293 x; 01294 01295 if (status == MagickFalse) 01296 continue; 01297 q=QueueCacheViewAuthenticPixels(rotate_view,tile_y,(ssize_t) (y+ 01298 rotate_image->rows-(tile_x+width)),height,1,exception); 01299 if (q == (Quantum *) NULL) 01300 { 01301 status=MagickFalse; 01302 continue; 01303 } 01304 tile_pixels=p+((width-1)-y)*GetPixelChannels(image); 01305 for (x=0; x < (ssize_t) height; x++) 01306 { 01307 register ssize_t 01308 i; 01309 01310 if (GetPixelMask(image,tile_pixels) != 0) 01311 { 01312 tile_pixels+=width*GetPixelChannels(image); 01313 q+=GetPixelChannels(rotate_image); 01314 continue; 01315 } 01316 for (i=0; i < (ssize_t) GetPixelChannels(image); i++) 01317 { 01318 PixelChannel 01319 channel; 01320 01321 PixelTrait 01322 rotate_traits, 01323 traits; 01324 01325 channel=GetPixelChannelMapChannel(image,i); 01326 traits=GetPixelChannelMapTraits(image,channel); 01327 rotate_traits=GetPixelChannelMapTraits(rotate_image,channel); 01328 if ((traits == UndefinedPixelTrait) || 01329 (rotate_traits == UndefinedPixelTrait)) 01330 continue; 01331 SetPixelChannel(rotate_image,channel,tile_pixels[i],q); 01332 } 01333 tile_pixels+=width*GetPixelChannels(image); 01334 q+=GetPixelChannels(rotate_image); 01335 } 01336 sync=SyncCacheViewAuthenticPixels(rotate_view,exception); 01337 if (sync == MagickFalse) 01338 status=MagickFalse; 01339 } 01340 } 01341 if (image->progress_monitor != (MagickProgressMonitor) NULL) 01342 { 01343 MagickBooleanType 01344 proceed; 01345 01346 #if defined(MAGICKCORE_OPENMP_SUPPORT) 01347 #pragma omp critical (MagickCore_IntegralRotateImage) 01348 #endif 01349 proceed=SetImageProgress(image,RotateImageTag,progress+=tile_height, 01350 image->rows); 01351 if (proceed == MagickFalse) 01352 status=MagickFalse; 01353 } 01354 } 01355 (void) SetImageProgress(image,RotateImageTag,(MagickOffsetType) 01356 image->rows-1,image->rows); 01357 Swap(page.width,page.height); 01358 Swap(page.x,page.y); 01359 if (page.width != 0) 01360 page.x=(ssize_t) (page.width-rotate_image->columns-page.x); 01361 break; 01362 } 01363 } 01364 rotate_view=DestroyCacheView(rotate_view); 01365 image_view=DestroyCacheView(image_view); 01366 rotate_image->type=image->type; 01367 rotate_image->page=page; 01368 if (status == MagickFalse) 01369 rotate_image=DestroyImage(rotate_image); 01370 return(rotate_image); 01371 } 01372 01373 /* 01374 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 01375 % % 01376 % % 01377 % % 01378 + X S h e a r I m a g e % 01379 % % 01380 % % 01381 % % 01382 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 01383 % 01384 % XShearImage() shears the image in the X direction with a shear angle of 01385 % 'degrees'. Positive angles shear counter-clockwise (right-hand rule), and 01386 % negative angles shear clockwise. Angles are measured relative to a vertical 01387 % Y-axis. X shears will widen an image creating 'empty' triangles on the left 01388 % and right sides of the source image. 01389 % 01390 % The format of the XShearImage method is: 01391 % 01392 % MagickBooleanType XShearImage(Image *image,const MagickRealType degrees, 01393 % const size_t width,const size_t height, 01394 % const ssize_t x_offset,const ssize_t y_offset,ExceptionInfo *exception) 01395 % 01396 % A description of each parameter follows. 01397 % 01398 % o image: the image. 01399 % 01400 % o degrees: A MagickRealType representing the shearing angle along the X 01401 % axis. 01402 % 01403 % o width, height, x_offset, y_offset: Defines a region of the image 01404 % to shear. 01405 % 01406 % o exception: return any errors or warnings in this structure. 01407 % 01408 */ 01409 static MagickBooleanType XShearImage(Image *image,const MagickRealType degrees, 01410 const size_t width,const size_t height,const ssize_t x_offset, 01411 const ssize_t y_offset,ExceptionInfo *exception) 01412 { 01413 #define XShearImageTag "XShear/Image" 01414 01415 typedef enum 01416 { 01417 LEFT, 01418 RIGHT 01419 } ShearDirection; 01420 01421 CacheView 01422 *image_view; 01423 01424 MagickBooleanType 01425 status; 01426 01427 MagickOffsetType 01428 progress; 01429 01430 PixelInfo 01431 background; 01432 01433 ssize_t 01434 y; 01435 01436 /* 01437 X shear image. 01438 */ 01439 assert(image != (Image *) NULL); 01440 assert(image->signature == MagickSignature); 01441 if (image->debug != MagickFalse) 01442 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename); 01443 status=MagickTrue; 01444 background=image->background_color; 01445 progress=0; 01446 image_view=AcquireCacheView(image); 01447 #if defined(MAGICKCORE_OPENMP_SUPPORT) 01448 #pragma omp parallel for schedule(static,4) shared(progress,status) 01449 #endif 01450 for (y=0; y < (ssize_t) height; y++) 01451 { 01452 PixelInfo 01453 pixel, 01454 source, 01455 destination; 01456 01457 MagickRealType 01458 area, 01459 displacement; 01460 01461 register Quantum 01462 *restrict p, 01463 *restrict q; 01464 01465 register ssize_t 01466 i; 01467 01468 ShearDirection 01469 direction; 01470 01471 ssize_t 01472 step; 01473 01474 if (status == MagickFalse) 01475 continue; 01476 p=GetCacheViewAuthenticPixels(image_view,0,y_offset+y,image->columns,1, 01477 exception); 01478 if (p == (Quantum *) NULL) 01479 { 01480 status=MagickFalse; 01481 continue; 01482 } 01483 p+=x_offset*GetPixelChannels(image); 01484 displacement=degrees*(MagickRealType) (y-height/2.0); 01485 if (displacement == 0.0) 01486 continue; 01487 if (displacement > 0.0) 01488 direction=RIGHT; 01489 else 01490 { 01491 displacement*=(-1.0); 01492 direction=LEFT; 01493 } 01494 step=(ssize_t) floor((double) displacement); 01495 area=(MagickRealType) (displacement-step); 01496 step++; 01497 pixel=background; 01498 GetPixelInfo(image,&source); 01499 GetPixelInfo(image,&destination); 01500 switch (direction) 01501 { 01502 case LEFT: 01503 { 01504 /* 01505 Transfer pixels left-to-right. 01506 */ 01507 if (step > x_offset) 01508 break; 01509 q=p-step*GetPixelChannels(image); 01510 for (i=0; i < (ssize_t) width; i++) 01511 { 01512 if ((x_offset+i) < step) 01513 { 01514 p+=GetPixelChannels(image); 01515 GetPixelInfoPixel(image,p,&pixel); 01516 q+=GetPixelChannels(image); 01517 continue; 01518 } 01519 GetPixelInfoPixel(image,p,&source); 01520 CompositePixelInfoAreaBlend(&pixel,(MagickRealType) pixel.alpha, 01521 &source,(MagickRealType) GetPixelAlpha(image,p),area,&destination); 01522 SetPixelInfoPixel(image,&destination,q); 01523 GetPixelInfoPixel(image,p,&pixel); 01524 p+=GetPixelChannels(image); 01525 q+=GetPixelChannels(image); 01526 } 01527 CompositePixelInfoAreaBlend(&pixel,(MagickRealType) pixel.alpha, 01528 &background,(MagickRealType) background.alpha,area,&destination); 01529 SetPixelInfoPixel(image,&destination,q); 01530 q+=GetPixelChannels(image); 01531 for (i=0; i < (step-1); i++) 01532 { 01533 SetPixelInfoPixel(image,&background,q); 01534 q+=GetPixelChannels(image); 01535 } 01536 break; 01537 } 01538 case RIGHT: 01539 { 01540 /* 01541 Transfer pixels right-to-left. 01542 */ 01543 p+=width*GetPixelChannels(image); 01544 q=p+step*GetPixelChannels(image); 01545 for (i=0; i < (ssize_t) width; i++) 01546 { 01547 p-=GetPixelChannels(image); 01548 q-=GetPixelChannels(image); 01549 if ((size_t) (x_offset+width+step-i) >= image->columns) 01550 continue; 01551 GetPixelInfoPixel(image,p,&source); 01552 CompositePixelInfoAreaBlend(&pixel,(MagickRealType) pixel.alpha, 01553 &source,(MagickRealType) GetPixelAlpha(image,p),area,&destination); 01554 SetPixelInfoPixel(image,&destination,q); 01555 GetPixelInfoPixel(image,p,&pixel); 01556 } 01557 CompositePixelInfoAreaBlend(&pixel,(MagickRealType) pixel.alpha, 01558 &background,(MagickRealType) background.alpha,area,&destination); 01559 q-=GetPixelChannels(image); 01560 SetPixelInfoPixel(image,&destination,q); 01561 for (i=0; i < (step-1); i++) 01562 { 01563 q-=GetPixelChannels(image); 01564 SetPixelInfoPixel(image,&background,q); 01565 } 01566 break; 01567 } 01568 } 01569 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse) 01570 status=MagickFalse; 01571 if (image->progress_monitor != (MagickProgressMonitor) NULL) 01572 { 01573 MagickBooleanType 01574 proceed; 01575 01576 #if defined(MAGICKCORE_OPENMP_SUPPORT) 01577 #pragma omp critical (MagickCore_XShearImage) 01578 #endif 01579 proceed=SetImageProgress(image,XShearImageTag,progress++,height); 01580 if (proceed == MagickFalse) 01581 status=MagickFalse; 01582 } 01583 } 01584 image_view=DestroyCacheView(image_view); 01585 return(status); 01586 } 01587 01588 /* 01589 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 01590 % % 01591 % % 01592 % % 01593 + Y S h e a r I m a g e % 01594 % % 01595 % % 01596 % % 01597 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 01598 % 01599 % YShearImage shears the image in the Y direction with a shear angle of 01600 % 'degrees'. Positive angles shear counter-clockwise (right-hand rule), and 01601 % negative angles shear clockwise. Angles are measured relative to a 01602 % horizontal X-axis. Y shears will increase the height of an image creating 01603 % 'empty' triangles on the top and bottom of the source image. 01604 % 01605 % The format of the YShearImage method is: 01606 % 01607 % MagickBooleanType YShearImage(Image *image,const MagickRealType degrees, 01608 % const size_t width,const size_t height, 01609 % const ssize_t x_offset,const ssize_t y_offset,ExceptionInfo *exception) 01610 % 01611 % A description of each parameter follows. 01612 % 01613 % o image: the image. 01614 % 01615 % o degrees: A MagickRealType representing the shearing angle along the Y 01616 % axis. 01617 % 01618 % o width, height, x_offset, y_offset: Defines a region of the image 01619 % to shear. 01620 % 01621 % o exception: return any errors or warnings in this structure. 01622 % 01623 */ 01624 static MagickBooleanType YShearImage(Image *image,const MagickRealType degrees, 01625 const size_t width,const size_t height,const ssize_t x_offset, 01626 const ssize_t y_offset,ExceptionInfo *exception) 01627 { 01628 #define YShearImageTag "YShear/Image" 01629 01630 typedef enum 01631 { 01632 UP, 01633 DOWN 01634 } ShearDirection; 01635 01636 CacheView 01637 *image_view; 01638 01639 MagickBooleanType 01640 status; 01641 01642 MagickOffsetType 01643 progress; 01644 01645 PixelInfo 01646 background; 01647 01648 ssize_t 01649 x; 01650 01651 /* 01652 Y Shear image. 01653 */ 01654 assert(image != (Image *) NULL); 01655 assert(image->signature == MagickSignature); 01656 if (image->debug != MagickFalse) 01657 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename); 01658 status=MagickTrue; 01659 progress=0; 01660 background=image->background_color; 01661 image_view=AcquireCacheView(image); 01662 #if defined(MAGICKCORE_OPENMP_SUPPORT) 01663 #pragma omp parallel for schedule(static,4) shared(progress,status) 01664 #endif 01665 for (x=0; x < (ssize_t) width; x++) 01666 { 01667 ssize_t 01668 step; 01669 01670 MagickRealType 01671 area, 01672 displacement; 01673 01674 PixelInfo 01675 pixel, 01676 source, 01677 destination; 01678 01679 register Quantum 01680 *restrict p, 01681 *restrict q; 01682 01683 register ssize_t 01684 i; 01685 01686 ShearDirection 01687 direction; 01688 01689 if (status == MagickFalse) 01690 continue; 01691 p=GetCacheViewAuthenticPixels(image_view,x_offset+x,0,1,image->rows, 01692 exception); 01693 if (p == (Quantum *) NULL) 01694 { 01695 status=MagickFalse; 01696 continue; 01697 } 01698 p+=y_offset*GetPixelChannels(image); 01699 displacement=degrees*(MagickRealType) (x-width/2.0); 01700 if (displacement == 0.0) 01701 continue; 01702 if (displacement > 0.0) 01703 direction=DOWN; 01704 else 01705 { 01706 displacement*=(-1.0); 01707 direction=UP; 01708 } 01709 step=(ssize_t) floor((double) displacement); 01710 area=(MagickRealType) (displacement-step); 01711 step++; 01712 pixel=background; 01713 GetPixelInfo(image,&source); 01714 GetPixelInfo(image,&destination); 01715 switch (direction) 01716 { 01717 case UP: 01718 { 01719 /* 01720 Transfer pixels top-to-bottom. 01721 */ 01722 if (step > y_offset) 01723 break; 01724 q=p-step*GetPixelChannels(image); 01725 for (i=0; i < (ssize_t) height; i++) 01726 { 01727 if ((y_offset+i) < step) 01728 { 01729 p+=GetPixelChannels(image); 01730 GetPixelInfoPixel(image,p,&pixel); 01731 q+=GetPixelChannels(image); 01732 continue; 01733 } 01734 GetPixelInfoPixel(image,p,&source); 01735 CompositePixelInfoAreaBlend(&pixel,(MagickRealType) pixel.alpha, 01736 &source,(MagickRealType) GetPixelAlpha(image,p),area, 01737 &destination); 01738 SetPixelInfoPixel(image,&destination,q); 01739 GetPixelInfoPixel(image,p,&pixel); 01740 p+=GetPixelChannels(image); 01741 q+=GetPixelChannels(image); 01742 } 01743 CompositePixelInfoAreaBlend(&pixel,(MagickRealType) pixel.alpha, 01744 &background,(MagickRealType) background.alpha,area,&destination); 01745 SetPixelInfoPixel(image,&destination,q); 01746 q+=GetPixelChannels(image); 01747 for (i=0; i < (step-1); i++) 01748 { 01749 SetPixelInfoPixel(image,&background,q); 01750 q+=GetPixelChannels(image); 01751 } 01752 break; 01753 } 01754 case DOWN: 01755 { 01756 /* 01757 Transfer pixels bottom-to-top. 01758 */ 01759 p+=height*GetPixelChannels(image); 01760 q=p+step*GetPixelChannels(image); 01761 for (i=0; i < (ssize_t) height; i++) 01762 { 01763 p-=GetPixelChannels(image); 01764 q-=GetPixelChannels(image); 01765 if ((size_t) (y_offset+height+step-i) >= image->rows) 01766 continue; 01767 GetPixelInfoPixel(image,p,&source); 01768 CompositePixelInfoAreaBlend(&pixel,(MagickRealType) pixel.alpha, 01769 &source,(MagickRealType) GetPixelAlpha(image,p),area, 01770 &destination); 01771 SetPixelInfoPixel(image,&destination,q); 01772 GetPixelInfoPixel(image,p,&pixel); 01773 } 01774 CompositePixelInfoAreaBlend(&pixel,(MagickRealType) pixel.alpha, 01775 &background,(MagickRealType) background.alpha,area,&destination); 01776 q-=GetPixelChannels(image); 01777 SetPixelInfoPixel(image,&destination,q); 01778 for (i=0; i < (step-1); i++) 01779 { 01780 q-=GetPixelChannels(image); 01781 SetPixelInfoPixel(image,&background,q); 01782 } 01783 break; 01784 } 01785 } 01786 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse) 01787 status=MagickFalse; 01788 if (image->progress_monitor != (MagickProgressMonitor) NULL) 01789 { 01790 MagickBooleanType 01791 proceed; 01792 01793 #if defined(MAGICKCORE_OPENMP_SUPPORT) 01794 #pragma omp critical (MagickCore_YShearImage) 01795 #endif 01796 proceed=SetImageProgress(image,YShearImageTag,progress++,image->rows); 01797 if (proceed == MagickFalse) 01798 status=MagickFalse; 01799 } 01800 } 01801 image_view=DestroyCacheView(image_view); 01802 return(status); 01803 } 01804 01805 /* 01806 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 01807 % % 01808 % % 01809 % % 01810 % S h e a r I m a g e % 01811 % % 01812 % % 01813 % % 01814 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 01815 % 01816 % ShearImage() creates a new image that is a shear_image copy of an existing 01817 % one. Shearing slides one edge of an image along the X or Y axis, creating 01818 % a parallelogram. An X direction shear slides an edge along the X axis, 01819 % while a Y direction shear slides an edge along the Y axis. The amount of 01820 % the shear is controlled by a shear angle. For X direction shears, x_shear 01821 % is measured relative to the Y axis, and similarly, for Y direction shears 01822 % y_shear is measured relative to the X axis. Empty triangles left over from 01823 % shearing the image are filled with the background color defined by member 01824 % 'background_color' of the image.. ShearImage() allocates the memory 01825 % necessary for the new Image structure and returns a pointer to the new image. 01826 % 01827 % ShearImage() is based on the paper "A Fast Algorithm for General Raster 01828 % Rotatation" by Alan W. Paeth. 01829 % 01830 % The format of the ShearImage method is: 01831 % 01832 % Image *ShearImage(const Image *image,const double x_shear, 01833 % const double y_shear,ExceptionInfo *exception) 01834 % 01835 % A description of each parameter follows. 01836 % 01837 % o image: the image. 01838 % 01839 % o x_shear, y_shear: Specifies the number of degrees to shear the image. 01840 % 01841 % o exception: return any errors or warnings in this structure. 01842 % 01843 */ 01844 MagickExport Image *ShearImage(const Image *image,const double x_shear, 01845 const double y_shear,ExceptionInfo *exception) 01846 { 01847 Image 01848 *integral_image, 01849 *shear_image; 01850 01851 ssize_t 01852 x_offset, 01853 y_offset; 01854 01855 MagickBooleanType 01856 status; 01857 01858 PointInfo 01859 shear; 01860 01861 RectangleInfo 01862 border_info; 01863 01864 size_t 01865 y_width; 01866 01867 assert(image != (Image *) NULL); 01868 assert(image->signature == MagickSignature); 01869 if (image->debug != MagickFalse) 01870 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename); 01871 assert(exception != (ExceptionInfo *) NULL); 01872 assert(exception->signature == MagickSignature); 01873 if ((x_shear != 0.0) && (fmod(x_shear,90.0) == 0.0)) 01874 ThrowImageException(ImageError,"AngleIsDiscontinuous"); 01875 if ((y_shear != 0.0) && (fmod(y_shear,90.0) == 0.0)) 01876 ThrowImageException(ImageError,"AngleIsDiscontinuous"); 01877 /* 01878 Initialize shear angle. 01879 */ 01880 integral_image=CloneImage(image,0,0,MagickTrue,exception); 01881 if (integral_image == (Image *) NULL) 01882 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed"); 01883 shear.x=(-tan(DegreesToRadians(fmod(x_shear,360.0)))); 01884 shear.y=tan(DegreesToRadians(fmod(y_shear,360.0))); 01885 if ((shear.x == 0.0) && (shear.y == 0.0)) 01886 return(integral_image); 01887 if (SetImageStorageClass(integral_image,DirectClass,exception) == MagickFalse) 01888 { 01889 integral_image=DestroyImage(integral_image); 01890 return(integral_image); 01891 } 01892 if (integral_image->matte == MagickFalse) 01893 (void) SetImageAlphaChannel(integral_image,OpaqueAlphaChannel,exception); 01894 /* 01895 Compute image size. 01896 */ 01897 y_width=image->columns+(ssize_t) floor(fabs(shear.x)*image->rows+0.5); 01898 x_offset=(ssize_t) ceil((double) image->columns+((fabs(shear.x)*image->rows)- 01899 image->columns)/2.0-0.5); 01900 y_offset=(ssize_t) ceil((double) image->rows+((fabs(shear.y)*y_width)- 01901 image->rows)/2.0-0.5); 01902 /* 01903 Surround image with border. 01904 */ 01905 integral_image->border_color=integral_image->background_color; 01906 integral_image->compose=CopyCompositeOp; 01907 border_info.width=(size_t) x_offset; 01908 border_info.height=(size_t) y_offset; 01909 shear_image=BorderImage(integral_image,&border_info,image->compose,exception); 01910 integral_image=DestroyImage(integral_image); 01911 if (shear_image == (Image *) NULL) 01912 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed"); 01913 /* 01914 Shear the image. 01915 */ 01916 if (shear_image->matte == MagickFalse) 01917 (void) SetImageAlphaChannel(shear_image,OpaqueAlphaChannel,exception); 01918 status=XShearImage(shear_image,shear.x,image->columns,image->rows,x_offset, 01919 (ssize_t) (shear_image->rows-image->rows)/2,exception); 01920 if (status == MagickFalse) 01921 { 01922 shear_image=DestroyImage(shear_image); 01923 return((Image *) NULL); 01924 } 01925 status=YShearImage(shear_image,shear.y,y_width,image->rows,(ssize_t) 01926 (shear_image->columns-y_width)/2,y_offset,exception); 01927 if (status == MagickFalse) 01928 { 01929 shear_image=DestroyImage(shear_image); 01930 return((Image *) NULL); 01931 } 01932 status=CropToFitImage(&shear_image,shear.x,shear.y,(MagickRealType) 01933 image->columns,(MagickRealType) image->rows,MagickFalse,exception); 01934 if (status == MagickFalse) 01935 { 01936 shear_image=DestroyImage(shear_image); 01937 return((Image *) NULL); 01938 } 01939 shear_image->compose=image->compose; 01940 shear_image->page.width=0; 01941 shear_image->page.height=0; 01942 return(shear_image); 01943 } 01944 01945 /* 01946 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 01947 % % 01948 % % 01949 % % 01950 % S h e a r R o t a t e I m a g e % 01951 % % 01952 % % 01953 % % 01954 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 01955 % 01956 % ShearRotateImage() creates a new image that is a rotated copy of an existing 01957 % one. Positive angles rotate counter-clockwise (right-hand rule), while 01958 % negative angles rotate clockwise. Rotated images are usually larger than 01959 % the originals and have 'empty' triangular corners. X axis. Empty 01960 % triangles left over from shearing the image are filled with the background 01961 % color defined by member 'background_color' of the image. ShearRotateImage 01962 % allocates the memory necessary for the new Image structure and returns a 01963 % pointer to the new image. 01964 % 01965 % ShearRotateImage() is based on the paper "A Fast Algorithm for General 01966 % Raster Rotatation" by Alan W. Paeth. ShearRotateImage is adapted from a 01967 % similar method based on the Paeth paper written by Michael Halle of the 01968 % Spatial Imaging Group, MIT Media Lab. 01969 % 01970 % The format of the ShearRotateImage method is: 01971 % 01972 % Image *ShearRotateImage(const Image *image,const double degrees, 01973 % ExceptionInfo *exception) 01974 % 01975 % A description of each parameter follows. 01976 % 01977 % o image: the image. 01978 % 01979 % o degrees: Specifies the number of degrees to rotate the image. 01980 % 01981 % o exception: return any errors or warnings in this structure. 01982 % 01983 */ 01984 MagickExport Image *ShearRotateImage(const Image *image,const double degrees, 01985 ExceptionInfo *exception) 01986 { 01987 Image 01988 *integral_image, 01989 *rotate_image; 01990 01991 MagickBooleanType 01992 status; 01993 01994 MagickRealType 01995 angle; 01996 01997 PointInfo 01998 shear; 01999 02000 RectangleInfo 02001 border_info; 02002 02003 size_t 02004 height, 02005 rotations, 02006 width, 02007 y_width; 02008 02009 ssize_t 02010 x_offset, 02011 y_offset; 02012 02013 /* 02014 Adjust rotation angle. 02015 */ 02016 assert(image != (Image *) NULL); 02017 assert(image->signature == MagickSignature); 02018 if (image->debug != MagickFalse) 02019 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename); 02020 assert(exception != (ExceptionInfo *) NULL); 02021 assert(exception->signature == MagickSignature); 02022 angle=degrees; 02023 while (angle < -45.0) 02024 angle+=360.0; 02025 for (rotations=0; angle > 45.0; rotations++) 02026 angle-=90.0; 02027 rotations%=4; 02028 /* 02029 Calculate shear equations. 02030 */ 02031 integral_image=IntegralRotateImage(image,rotations,exception); 02032 if (integral_image == (Image *) NULL) 02033 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed"); 02034 shear.x=(-tan((double) DegreesToRadians(angle)/2.0)); 02035 shear.y=sin((double) DegreesToRadians(angle)); 02036 if ((shear.x == 0.0) && (shear.y == 0.0)) 02037 return(integral_image); 02038 if (SetImageStorageClass(integral_image,DirectClass,exception) == MagickFalse) 02039 { 02040 integral_image=DestroyImage(integral_image); 02041 return(integral_image); 02042 } 02043 if (integral_image->matte == MagickFalse) 02044 (void) SetImageAlphaChannel(integral_image,OpaqueAlphaChannel,exception); 02045 /* 02046 Compute image size. 02047 */ 02048 width=image->columns; 02049 height=image->rows; 02050 if ((rotations == 1) || (rotations == 3)) 02051 { 02052 width=image->rows; 02053 height=image->columns; 02054 } 02055 y_width=width+(ssize_t) floor(fabs(shear.x)*height+0.5); 02056 x_offset=(ssize_t) ceil((double) width+((fabs(shear.y)*height)-width)/2.0- 02057 0.5); 02058 y_offset=(ssize_t) ceil((double) height+((fabs(shear.y)*y_width)-height)/2.0- 02059 0.5); 02060 /* 02061 Surround image with a border. 02062 */ 02063 integral_image->border_color=integral_image->background_color; 02064 integral_image->compose=CopyCompositeOp; 02065 border_info.width=(size_t) x_offset; 02066 border_info.height=(size_t) y_offset; 02067 rotate_image=BorderImage(integral_image,&border_info,image->compose, 02068 exception); 02069 integral_image=DestroyImage(integral_image); 02070 if (rotate_image == (Image *) NULL) 02071 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed"); 02072 /* 02073 Rotate the image. 02074 */ 02075 status=XShearImage(rotate_image,shear.x,width,height,x_offset,(ssize_t) 02076 (rotate_image->rows-height)/2,exception); 02077 if (status == MagickFalse) 02078 { 02079 rotate_image=DestroyImage(rotate_image); 02080 return((Image *) NULL); 02081 } 02082 status=YShearImage(rotate_image,shear.y,y_width,height,(ssize_t) 02083 (rotate_image->columns-y_width)/2,y_offset,exception); 02084 if (status == MagickFalse) 02085 { 02086 rotate_image=DestroyImage(rotate_image); 02087 return((Image *) NULL); 02088 } 02089 status=XShearImage(rotate_image,shear.x,y_width,rotate_image->rows,(ssize_t) 02090 (rotate_image->columns-y_width)/2,0,exception); 02091 if (status == MagickFalse) 02092 { 02093 rotate_image=DestroyImage(rotate_image); 02094 return((Image *) NULL); 02095 } 02096 status=CropToFitImage(&rotate_image,shear.x,shear.y,(MagickRealType) width, 02097 (MagickRealType) height,MagickTrue,exception); 02098 if (status == MagickFalse) 02099 { 02100 rotate_image=DestroyImage(rotate_image); 02101 return((Image *) NULL); 02102 } 02103 rotate_image->compose=image->compose; 02104 rotate_image->page.width=0; 02105 rotate_image->page.height=0; 02106 return(rotate_image); 02107 }