|
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 degrees=RadiansToDegrees(-atan((double) skew/width/8)); 00886 if (image->debug != MagickFalse) 00887 (void) LogMagickEvent(TransformEvent,GetMagickModule(), 00888 " Deskew angle: %g",degrees); 00889 affine_matrix.sx=cos(DegreesToRadians(fmod((double) degrees,360.0))); 00890 affine_matrix.rx=sin(DegreesToRadians(fmod((double) degrees,360.0))); 00891 affine_matrix.ry=(-sin(DegreesToRadians(fmod((double) degrees,360.0)))); 00892 affine_matrix.sy=cos(DegreesToRadians(fmod((double) degrees,360.0))); 00893 affine_matrix.tx=0.0; 00894 affine_matrix.ty=0.0; 00895 artifact=GetImageArtifact(image,"deskew:auto-crop"); 00896 if (artifact == (const char *) NULL) 00897 { 00898 deskew_image=AffineTransformImage(clone_image,&affine_matrix,exception); 00899 clone_image=DestroyImage(clone_image); 00900 return(deskew_image); 00901 } 00902 /* 00903 Auto-crop image. 00904 */ 00905 GetImageBackgroundColor(clone_image,(ssize_t) StringToLong(artifact), 00906 exception); 00907 deskew_image=AffineTransformImage(clone_image,&affine_matrix,exception); 00908 clone_image=DestroyImage(clone_image); 00909 if (deskew_image == (Image *) NULL) 00910 return((Image *) NULL); 00911 median_image=StatisticImage(deskew_image,MedianStatistic,3,3,exception); 00912 if (median_image == (Image *) NULL) 00913 { 00914 deskew_image=DestroyImage(deskew_image); 00915 return((Image *) NULL); 00916 } 00917 geometry=GetImageBoundingBox(median_image,exception); 00918 median_image=DestroyImage(median_image); 00919 if (image->debug != MagickFalse) 00920 (void) LogMagickEvent(TransformEvent,GetMagickModule()," Deskew geometry: " 00921 "%.20gx%.20g%+.20g%+.20g",(double) geometry.width,(double) 00922 geometry.height,(double) geometry.x,(double) geometry.y); 00923 crop_image=CropImage(deskew_image,&geometry,exception); 00924 deskew_image=DestroyImage(deskew_image); 00925 return(crop_image); 00926 } 00927 00928 /* 00929 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 00930 % % 00931 % % 00932 % % 00933 % I n t e g r a l R o t a t e I m a g e % 00934 % % 00935 % % 00936 % % 00937 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 00938 % 00939 % IntegralRotateImage() rotates the image an integral of 90 degrees. It 00940 % allocates the memory necessary for the new Image structure and returns a 00941 % pointer to the rotated image. 00942 % 00943 % The format of the IntegralRotateImage method is: 00944 % 00945 % Image *IntegralRotateImage(const Image *image,size_t rotations, 00946 % ExceptionInfo *exception) 00947 % 00948 % A description of each parameter follows. 00949 % 00950 % o image: the image. 00951 % 00952 % o rotations: Specifies the number of 90 degree rotations. 00953 % 00954 */ 00955 MagickExport Image *IntegralRotateImage(const Image *image,size_t rotations, 00956 ExceptionInfo *exception) 00957 { 00958 #define RotateImageTag "Rotate/Image" 00959 00960 CacheView 00961 *image_view, 00962 *rotate_view; 00963 00964 Image 00965 *rotate_image; 00966 00967 MagickBooleanType 00968 status; 00969 00970 MagickOffsetType 00971 progress; 00972 00973 RectangleInfo 00974 page; 00975 00976 ssize_t 00977 y; 00978 00979 /* 00980 Initialize rotated image attributes. 00981 */ 00982 assert(image != (Image *) NULL); 00983 page=image->page; 00984 rotations%=4; 00985 if (rotations == 0) 00986 return(CloneImage(image,0,0,MagickTrue,exception)); 00987 if ((rotations == 1) || (rotations == 3)) 00988 rotate_image=CloneImage(image,image->rows,image->columns,MagickTrue, 00989 exception); 00990 else 00991 rotate_image=CloneImage(image,image->columns,image->rows,MagickTrue, 00992 exception); 00993 if (rotate_image == (Image *) NULL) 00994 return((Image *) NULL); 00995 /* 00996 Integral rotate the image. 00997 */ 00998 status=MagickTrue; 00999 progress=0; 01000 image_view=AcquireCacheView(image); 01001 rotate_view=AcquireCacheView(rotate_image); 01002 switch (rotations) 01003 { 01004 case 0: 01005 { 01006 /* 01007 Rotate 0 degrees. 01008 */ 01009 break; 01010 } 01011 case 1: 01012 { 01013 size_t 01014 tile_height, 01015 tile_width; 01016 01017 ssize_t 01018 tile_y; 01019 01020 /* 01021 Rotate 90 degrees. 01022 */ 01023 GetPixelCacheTileSize(image,&tile_width,&tile_height); 01024 #if defined(MAGICKCORE_OPENMP_SUPPORT) 01025 #pragma omp parallel for schedule(static,4) shared(progress,status) 01026 #endif 01027 for (tile_y=0; tile_y < (ssize_t) image->rows; tile_y+=(ssize_t) tile_height) 01028 { 01029 register ssize_t 01030 tile_x; 01031 01032 if (status == MagickFalse) 01033 continue; 01034 tile_x=0; 01035 for ( ; tile_x < (ssize_t) image->columns; tile_x+=(ssize_t) tile_width) 01036 { 01037 MagickBooleanType 01038 sync; 01039 01040 register const Quantum 01041 *restrict p; 01042 01043 register Quantum 01044 *restrict q; 01045 01046 register ssize_t 01047 y; 01048 01049 size_t 01050 height, 01051 width; 01052 01053 width=tile_width; 01054 if ((tile_x+(ssize_t) tile_width) > (ssize_t) image->columns) 01055 width=(size_t) (tile_width-(tile_x+tile_width-image->columns)); 01056 height=tile_height; 01057 if ((tile_y+(ssize_t) tile_height) > (ssize_t) image->rows) 01058 height=(size_t) (tile_height-(tile_y+tile_height-image->rows)); 01059 p=GetCacheViewVirtualPixels(image_view,tile_x,tile_y,width,height, 01060 exception); 01061 if (p == (const Quantum *) NULL) 01062 { 01063 status=MagickFalse; 01064 break; 01065 } 01066 for (y=0; y < (ssize_t) width; y++) 01067 { 01068 register const Quantum 01069 *restrict tile_pixels; 01070 01071 register ssize_t 01072 x; 01073 01074 if (status == MagickFalse) 01075 continue; 01076 q=QueueCacheViewAuthenticPixels(rotate_view,(ssize_t) 01077 (rotate_image->columns-(tile_y+height)),y+tile_x,height,1, 01078 exception); 01079 if (q == (Quantum *) NULL) 01080 { 01081 status=MagickFalse; 01082 continue; 01083 } 01084 tile_pixels=p+((height-1)*width+y)*GetPixelChannels(image); 01085 for (x=0; x < (ssize_t) height; x++) 01086 { 01087 register ssize_t 01088 i; 01089 01090 if (GetPixelMask(image,tile_pixels) != 0) 01091 { 01092 tile_pixels-=width*GetPixelChannels(image); 01093 q+=GetPixelChannels(rotate_image); 01094 continue; 01095 } 01096 for (i=0; i < (ssize_t) GetPixelChannels(image); i++) 01097 { 01098 PixelChannel 01099 channel; 01100 01101 PixelTrait 01102 rotate_traits, 01103 traits; 01104 01105 channel=GetPixelChannelMapChannel(image,i); 01106 traits=GetPixelChannelMapTraits(image,channel); 01107 rotate_traits=GetPixelChannelMapTraits(rotate_image,channel); 01108 if ((traits == UndefinedPixelTrait) || 01109 (rotate_traits == UndefinedPixelTrait)) 01110 continue; 01111 SetPixelChannel(rotate_image,channel,tile_pixels[i],q); 01112 } 01113 tile_pixels-=width*GetPixelChannels(image); 01114 q+=GetPixelChannels(rotate_image); 01115 } 01116 sync=SyncCacheViewAuthenticPixels(rotate_view,exception); 01117 if (sync == MagickFalse) 01118 status=MagickFalse; 01119 } 01120 } 01121 if (image->progress_monitor != (MagickProgressMonitor) NULL) 01122 { 01123 MagickBooleanType 01124 proceed; 01125 01126 #if defined(MAGICKCORE_OPENMP_SUPPORT) 01127 #pragma omp critical (MagickCore_IntegralRotateImage) 01128 #endif 01129 proceed=SetImageProgress(image,RotateImageTag,progress+=tile_height, 01130 image->rows); 01131 if (proceed == MagickFalse) 01132 status=MagickFalse; 01133 } 01134 } 01135 (void) SetImageProgress(image,RotateImageTag,(MagickOffsetType) 01136 image->rows-1,image->rows); 01137 Swap(page.width,page.height); 01138 Swap(page.x,page.y); 01139 if (page.width != 0) 01140 page.x=(ssize_t) (page.width-rotate_image->columns-page.x); 01141 break; 01142 } 01143 case 2: 01144 { 01145 /* 01146 Rotate 180 degrees. 01147 */ 01148 #if defined(MAGICKCORE_OPENMP_SUPPORT) 01149 #pragma omp parallel for schedule(static) shared(progress,status) 01150 #endif 01151 for (y=0; y < (ssize_t) image->rows; y++) 01152 { 01153 MagickBooleanType 01154 sync; 01155 01156 register const Quantum 01157 *restrict p; 01158 01159 register Quantum 01160 *restrict q; 01161 01162 register ssize_t 01163 x; 01164 01165 if (status == MagickFalse) 01166 continue; 01167 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception); 01168 q=QueueCacheViewAuthenticPixels(rotate_view,0,(ssize_t) (image->rows-y- 01169 1),image->columns,1,exception); 01170 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL)) 01171 { 01172 status=MagickFalse; 01173 continue; 01174 } 01175 q+=GetPixelChannels(rotate_image)*image->columns; 01176 for (x=0; x < (ssize_t) image->columns; x++) 01177 { 01178 register ssize_t 01179 i; 01180 01181 q-=GetPixelChannels(rotate_image); 01182 if (GetPixelMask(image,p) != 0) 01183 { 01184 p+=GetPixelChannels(image); 01185 continue; 01186 } 01187 for (i=0; i < (ssize_t) GetPixelChannels(image); i++) 01188 { 01189 PixelChannel 01190 channel; 01191 01192 PixelTrait 01193 rotate_traits, 01194 traits; 01195 01196 channel=GetPixelChannelMapChannel(image,i); 01197 traits=GetPixelChannelMapTraits(image,channel); 01198 rotate_traits=GetPixelChannelMapTraits(rotate_image,channel); 01199 if ((traits == UndefinedPixelTrait) || 01200 (rotate_traits == UndefinedPixelTrait)) 01201 continue; 01202 SetPixelChannel(rotate_image,channel,p[i],q); 01203 } 01204 p+=GetPixelChannels(image); 01205 } 01206 sync=SyncCacheViewAuthenticPixels(rotate_view,exception); 01207 if (sync == MagickFalse) 01208 status=MagickFalse; 01209 if (image->progress_monitor != (MagickProgressMonitor) NULL) 01210 { 01211 MagickBooleanType 01212 proceed; 01213 01214 #if defined(MAGICKCORE_OPENMP_SUPPORT) 01215 #pragma omp critical (MagickCore_IntegralRotateImage) 01216 #endif 01217 proceed=SetImageProgress(image,RotateImageTag,progress++, 01218 image->rows); 01219 if (proceed == MagickFalse) 01220 status=MagickFalse; 01221 } 01222 } 01223 (void) SetImageProgress(image,RotateImageTag,(MagickOffsetType) 01224 image->rows-1,image->rows); 01225 Swap(page.width,page.height); 01226 Swap(page.x,page.y); 01227 if (page.width != 0) 01228 page.x=(ssize_t) (page.width-rotate_image->columns-page.x); 01229 break; 01230 } 01231 case 3: 01232 { 01233 size_t 01234 tile_height, 01235 tile_width; 01236 01237 ssize_t 01238 tile_y; 01239 01240 /* 01241 Rotate 270 degrees. 01242 */ 01243 GetPixelCacheTileSize(image,&tile_width,&tile_height); 01244 #if defined(MAGICKCORE_OPENMP_SUPPORT) 01245 #pragma omp parallel for schedule(static,4) shared(progress,status) 01246 #endif 01247 for (tile_y=0; tile_y < (ssize_t) image->rows; tile_y+=(ssize_t) tile_height) 01248 { 01249 register ssize_t 01250 tile_x; 01251 01252 if (status == MagickFalse) 01253 continue; 01254 tile_x=0; 01255 for ( ; tile_x < (ssize_t) image->columns; tile_x+=(ssize_t) tile_width) 01256 { 01257 MagickBooleanType 01258 sync; 01259 01260 register const Quantum 01261 *restrict p; 01262 01263 register Quantum 01264 *restrict q; 01265 01266 register ssize_t 01267 y; 01268 01269 size_t 01270 height, 01271 width; 01272 01273 width=tile_width; 01274 if ((tile_x+(ssize_t) tile_width) > (ssize_t) image->columns) 01275 width=(size_t) (tile_width-(tile_x+tile_width-image->columns)); 01276 height=tile_height; 01277 if ((tile_y+(ssize_t) tile_height) > (ssize_t) image->rows) 01278 height=(size_t) (tile_height-(tile_y+tile_height-image->rows)); 01279 p=GetCacheViewVirtualPixels(image_view,tile_x,tile_y,width,height, 01280 exception); 01281 if (p == (const Quantum *) NULL) 01282 { 01283 status=MagickFalse; 01284 break; 01285 } 01286 for (y=0; y < (ssize_t) width; y++) 01287 { 01288 register const Quantum 01289 *restrict tile_pixels; 01290 01291 register ssize_t 01292 x; 01293 01294 if (status == MagickFalse) 01295 continue; 01296 q=QueueCacheViewAuthenticPixels(rotate_view,tile_y,(ssize_t) (y+ 01297 rotate_image->rows-(tile_x+width)),height,1,exception); 01298 if (q == (Quantum *) NULL) 01299 { 01300 status=MagickFalse; 01301 continue; 01302 } 01303 tile_pixels=p+((width-1)-y)*GetPixelChannels(image); 01304 for (x=0; x < (ssize_t) height; x++) 01305 { 01306 register ssize_t 01307 i; 01308 01309 if (GetPixelMask(image,tile_pixels) != 0) 01310 { 01311 tile_pixels+=width*GetPixelChannels(image); 01312 q+=GetPixelChannels(rotate_image); 01313 continue; 01314 } 01315 for (i=0; i < (ssize_t) GetPixelChannels(image); i++) 01316 { 01317 PixelChannel 01318 channel; 01319 01320 PixelTrait 01321 rotate_traits, 01322 traits; 01323 01324 channel=GetPixelChannelMapChannel(image,i); 01325 traits=GetPixelChannelMapTraits(image,channel); 01326 rotate_traits=GetPixelChannelMapTraits(rotate_image,channel); 01327 if ((traits == UndefinedPixelTrait) || 01328 (rotate_traits == UndefinedPixelTrait)) 01329 continue; 01330 SetPixelChannel(rotate_image,channel,tile_pixels[i],q); 01331 } 01332 tile_pixels+=width*GetPixelChannels(image); 01333 q+=GetPixelChannels(rotate_image); 01334 } 01335 sync=SyncCacheViewAuthenticPixels(rotate_view,exception); 01336 if (sync == MagickFalse) 01337 status=MagickFalse; 01338 } 01339 } 01340 if (image->progress_monitor != (MagickProgressMonitor) NULL) 01341 { 01342 MagickBooleanType 01343 proceed; 01344 01345 #if defined(MAGICKCORE_OPENMP_SUPPORT) 01346 #pragma omp critical (MagickCore_IntegralRotateImage) 01347 #endif 01348 proceed=SetImageProgress(image,RotateImageTag,progress+=tile_height, 01349 image->rows); 01350 if (proceed == MagickFalse) 01351 status=MagickFalse; 01352 } 01353 } 01354 (void) SetImageProgress(image,RotateImageTag,(MagickOffsetType) 01355 image->rows-1,image->rows); 01356 Swap(page.width,page.height); 01357 Swap(page.x,page.y); 01358 if (page.width != 0) 01359 page.x=(ssize_t) (page.width-rotate_image->columns-page.x); 01360 break; 01361 } 01362 } 01363 rotate_view=DestroyCacheView(rotate_view); 01364 image_view=DestroyCacheView(image_view); 01365 rotate_image->type=image->type; 01366 rotate_image->page=page; 01367 if (status == MagickFalse) 01368 rotate_image=DestroyImage(rotate_image); 01369 return(rotate_image); 01370 } 01371 01372 /* 01373 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 01374 % % 01375 % % 01376 % % 01377 + X S h e a r I m a g e % 01378 % % 01379 % % 01380 % % 01381 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 01382 % 01383 % XShearImage() shears the image in the X direction with a shear angle of 01384 % 'degrees'. Positive angles shear counter-clockwise (right-hand rule), and 01385 % negative angles shear clockwise. Angles are measured relative to a vertical 01386 % Y-axis. X shears will widen an image creating 'empty' triangles on the left 01387 % and right sides of the source image. 01388 % 01389 % The format of the XShearImage method is: 01390 % 01391 % MagickBooleanType XShearImage(Image *image,const MagickRealType degrees, 01392 % const size_t width,const size_t height, 01393 % const ssize_t x_offset,const ssize_t y_offset,ExceptionInfo *exception) 01394 % 01395 % A description of each parameter follows. 01396 % 01397 % o image: the image. 01398 % 01399 % o degrees: A MagickRealType representing the shearing angle along the X 01400 % axis. 01401 % 01402 % o width, height, x_offset, y_offset: Defines a region of the image 01403 % to shear. 01404 % 01405 % o exception: return any errors or warnings in this structure. 01406 % 01407 */ 01408 static MagickBooleanType XShearImage(Image *image,const MagickRealType degrees, 01409 const size_t width,const size_t height,const ssize_t x_offset, 01410 const ssize_t y_offset,ExceptionInfo *exception) 01411 { 01412 #define XShearImageTag "XShear/Image" 01413 01414 typedef enum 01415 { 01416 LEFT, 01417 RIGHT 01418 } ShearDirection; 01419 01420 CacheView 01421 *image_view; 01422 01423 MagickBooleanType 01424 status; 01425 01426 MagickOffsetType 01427 progress; 01428 01429 PixelInfo 01430 background; 01431 01432 ssize_t 01433 y; 01434 01435 /* 01436 X shear image. 01437 */ 01438 assert(image != (Image *) NULL); 01439 assert(image->signature == MagickSignature); 01440 if (image->debug != MagickFalse) 01441 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename); 01442 status=MagickTrue; 01443 background=image->background_color; 01444 progress=0; 01445 image_view=AcquireCacheView(image); 01446 #if defined(MAGICKCORE_OPENMP_SUPPORT) 01447 #pragma omp parallel for schedule(static,4) shared(progress,status) 01448 #endif 01449 for (y=0; y < (ssize_t) height; y++) 01450 { 01451 PixelInfo 01452 pixel, 01453 source, 01454 destination; 01455 01456 MagickRealType 01457 area, 01458 displacement; 01459 01460 register Quantum 01461 *restrict p, 01462 *restrict q; 01463 01464 register ssize_t 01465 i; 01466 01467 ShearDirection 01468 direction; 01469 01470 ssize_t 01471 step; 01472 01473 if (status == MagickFalse) 01474 continue; 01475 p=GetCacheViewAuthenticPixels(image_view,0,y_offset+y,image->columns,1, 01476 exception); 01477 if (p == (Quantum *) NULL) 01478 { 01479 status=MagickFalse; 01480 continue; 01481 } 01482 p+=x_offset*GetPixelChannels(image); 01483 displacement=degrees*(MagickRealType) (y-height/2.0); 01484 if (displacement == 0.0) 01485 continue; 01486 if (displacement > 0.0) 01487 direction=RIGHT; 01488 else 01489 { 01490 displacement*=(-1.0); 01491 direction=LEFT; 01492 } 01493 step=(ssize_t) floor((double) displacement); 01494 area=(MagickRealType) (displacement-step); 01495 step++; 01496 pixel=background; 01497 GetPixelInfo(image,&source); 01498 GetPixelInfo(image,&destination); 01499 switch (direction) 01500 { 01501 case LEFT: 01502 { 01503 /* 01504 Transfer pixels left-to-right. 01505 */ 01506 if (step > x_offset) 01507 break; 01508 q=p-step*GetPixelChannels(image); 01509 for (i=0; i < (ssize_t) width; i++) 01510 { 01511 if ((x_offset+i) < step) 01512 { 01513 p+=GetPixelChannels(image); 01514 GetPixelInfoPixel(image,p,&pixel); 01515 q+=GetPixelChannels(image); 01516 continue; 01517 } 01518 GetPixelInfoPixel(image,p,&source); 01519 CompositePixelInfoAreaBlend(&pixel,(MagickRealType) pixel.alpha, 01520 &source,(MagickRealType) GetPixelAlpha(image,p),area,&destination); 01521 SetPixelInfoPixel(image,&destination,q); 01522 GetPixelInfoPixel(image,p,&pixel); 01523 p+=GetPixelChannels(image); 01524 q+=GetPixelChannels(image); 01525 } 01526 CompositePixelInfoAreaBlend(&pixel,(MagickRealType) pixel.alpha, 01527 &background,(MagickRealType) background.alpha,area,&destination); 01528 SetPixelInfoPixel(image,&destination,q); 01529 q+=GetPixelChannels(image); 01530 for (i=0; i < (step-1); i++) 01531 { 01532 SetPixelInfoPixel(image,&background,q); 01533 q+=GetPixelChannels(image); 01534 } 01535 break; 01536 } 01537 case RIGHT: 01538 { 01539 /* 01540 Transfer pixels right-to-left. 01541 */ 01542 p+=width*GetPixelChannels(image); 01543 q=p+step*GetPixelChannels(image); 01544 for (i=0; i < (ssize_t) width; i++) 01545 { 01546 p-=GetPixelChannels(image); 01547 q-=GetPixelChannels(image); 01548 if ((size_t) (x_offset+width+step-i) >= image->columns) 01549 continue; 01550 GetPixelInfoPixel(image,p,&source); 01551 CompositePixelInfoAreaBlend(&pixel,(MagickRealType) pixel.alpha, 01552 &source,(MagickRealType) GetPixelAlpha(image,p),area,&destination); 01553 SetPixelInfoPixel(image,&destination,q); 01554 GetPixelInfoPixel(image,p,&pixel); 01555 } 01556 CompositePixelInfoAreaBlend(&pixel,(MagickRealType) pixel.alpha, 01557 &background,(MagickRealType) background.alpha,area,&destination); 01558 q-=GetPixelChannels(image); 01559 SetPixelInfoPixel(image,&destination,q); 01560 for (i=0; i < (step-1); i++) 01561 { 01562 q-=GetPixelChannels(image); 01563 SetPixelInfoPixel(image,&background,q); 01564 } 01565 break; 01566 } 01567 } 01568 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse) 01569 status=MagickFalse; 01570 if (image->progress_monitor != (MagickProgressMonitor) NULL) 01571 { 01572 MagickBooleanType 01573 proceed; 01574 01575 #if defined(MAGICKCORE_OPENMP_SUPPORT) 01576 #pragma omp critical (MagickCore_XShearImage) 01577 #endif 01578 proceed=SetImageProgress(image,XShearImageTag,progress++,height); 01579 if (proceed == MagickFalse) 01580 status=MagickFalse; 01581 } 01582 } 01583 image_view=DestroyCacheView(image_view); 01584 return(status); 01585 } 01586 01587 /* 01588 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 01589 % % 01590 % % 01591 % % 01592 + Y S h e a r I m a g e % 01593 % % 01594 % % 01595 % % 01596 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 01597 % 01598 % YShearImage shears the image in the Y direction with a shear angle of 01599 % 'degrees'. Positive angles shear counter-clockwise (right-hand rule), and 01600 % negative angles shear clockwise. Angles are measured relative to a 01601 % horizontal X-axis. Y shears will increase the height of an image creating 01602 % 'empty' triangles on the top and bottom of the source image. 01603 % 01604 % The format of the YShearImage method is: 01605 % 01606 % MagickBooleanType YShearImage(Image *image,const MagickRealType degrees, 01607 % const size_t width,const size_t height, 01608 % const ssize_t x_offset,const ssize_t y_offset,ExceptionInfo *exception) 01609 % 01610 % A description of each parameter follows. 01611 % 01612 % o image: the image. 01613 % 01614 % o degrees: A MagickRealType representing the shearing angle along the Y 01615 % axis. 01616 % 01617 % o width, height, x_offset, y_offset: Defines a region of the image 01618 % to shear. 01619 % 01620 % o exception: return any errors or warnings in this structure. 01621 % 01622 */ 01623 static MagickBooleanType YShearImage(Image *image,const MagickRealType degrees, 01624 const size_t width,const size_t height,const ssize_t x_offset, 01625 const ssize_t y_offset,ExceptionInfo *exception) 01626 { 01627 #define YShearImageTag "YShear/Image" 01628 01629 typedef enum 01630 { 01631 UP, 01632 DOWN 01633 } ShearDirection; 01634 01635 CacheView 01636 *image_view; 01637 01638 MagickBooleanType 01639 status; 01640 01641 MagickOffsetType 01642 progress; 01643 01644 PixelInfo 01645 background; 01646 01647 ssize_t 01648 x; 01649 01650 /* 01651 Y Shear image. 01652 */ 01653 assert(image != (Image *) NULL); 01654 assert(image->signature == MagickSignature); 01655 if (image->debug != MagickFalse) 01656 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename); 01657 status=MagickTrue; 01658 progress=0; 01659 background=image->background_color; 01660 image_view=AcquireCacheView(image); 01661 #if defined(MAGICKCORE_OPENMP_SUPPORT) 01662 #pragma omp parallel for schedule(static,4) shared(progress,status) 01663 #endif 01664 for (x=0; x < (ssize_t) width; x++) 01665 { 01666 ssize_t 01667 step; 01668 01669 MagickRealType 01670 area, 01671 displacement; 01672 01673 PixelInfo 01674 pixel, 01675 source, 01676 destination; 01677 01678 register Quantum 01679 *restrict p, 01680 *restrict q; 01681 01682 register ssize_t 01683 i; 01684 01685 ShearDirection 01686 direction; 01687 01688 if (status == MagickFalse) 01689 continue; 01690 p=GetCacheViewAuthenticPixels(image_view,x_offset+x,0,1,image->rows, 01691 exception); 01692 if (p == (Quantum *) NULL) 01693 { 01694 status=MagickFalse; 01695 continue; 01696 } 01697 p+=y_offset*GetPixelChannels(image); 01698 displacement=degrees*(MagickRealType) (x-width/2.0); 01699 if (displacement == 0.0) 01700 continue; 01701 if (displacement > 0.0) 01702 direction=DOWN; 01703 else 01704 { 01705 displacement*=(-1.0); 01706 direction=UP; 01707 } 01708 step=(ssize_t) floor((double) displacement); 01709 area=(MagickRealType) (displacement-step); 01710 step++; 01711 pixel=background; 01712 GetPixelInfo(image,&source); 01713 GetPixelInfo(image,&destination); 01714 switch (direction) 01715 { 01716 case UP: 01717 { 01718 /* 01719 Transfer pixels top-to-bottom. 01720 */ 01721 if (step > y_offset) 01722 break; 01723 q=p-step*GetPixelChannels(image); 01724 for (i=0; i < (ssize_t) height; i++) 01725 { 01726 if ((y_offset+i) < step) 01727 { 01728 p+=GetPixelChannels(image); 01729 GetPixelInfoPixel(image,p,&pixel); 01730 q+=GetPixelChannels(image); 01731 continue; 01732 } 01733 GetPixelInfoPixel(image,p,&source); 01734 CompositePixelInfoAreaBlend(&pixel,(MagickRealType) pixel.alpha, 01735 &source,(MagickRealType) GetPixelAlpha(image,p),area, 01736 &destination); 01737 SetPixelInfoPixel(image,&destination,q); 01738 GetPixelInfoPixel(image,p,&pixel); 01739 p+=GetPixelChannels(image); 01740 q+=GetPixelChannels(image); 01741 } 01742 CompositePixelInfoAreaBlend(&pixel,(MagickRealType) pixel.alpha, 01743 &background,(MagickRealType) background.alpha,area,&destination); 01744 SetPixelInfoPixel(image,&destination,q); 01745 q+=GetPixelChannels(image); 01746 for (i=0; i < (step-1); i++) 01747 { 01748 SetPixelInfoPixel(image,&background,q); 01749 q+=GetPixelChannels(image); 01750 } 01751 break; 01752 } 01753 case DOWN: 01754 { 01755 /* 01756 Transfer pixels bottom-to-top. 01757 */ 01758 p+=height*GetPixelChannels(image); 01759 q=p+step*GetPixelChannels(image); 01760 for (i=0; i < (ssize_t) height; i++) 01761 { 01762 p-=GetPixelChannels(image); 01763 q-=GetPixelChannels(image); 01764 if ((size_t) (y_offset+height+step-i) >= image->rows) 01765 continue; 01766 GetPixelInfoPixel(image,p,&source); 01767 CompositePixelInfoAreaBlend(&pixel,(MagickRealType) pixel.alpha, 01768 &source,(MagickRealType) GetPixelAlpha(image,p),area, 01769 &destination); 01770 SetPixelInfoPixel(image,&destination,q); 01771 GetPixelInfoPixel(image,p,&pixel); 01772 } 01773 CompositePixelInfoAreaBlend(&pixel,(MagickRealType) pixel.alpha, 01774 &background,(MagickRealType) background.alpha,area,&destination); 01775 q-=GetPixelChannels(image); 01776 SetPixelInfoPixel(image,&destination,q); 01777 for (i=0; i < (step-1); i++) 01778 { 01779 q-=GetPixelChannels(image); 01780 SetPixelInfoPixel(image,&background,q); 01781 } 01782 break; 01783 } 01784 } 01785 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse) 01786 status=MagickFalse; 01787 if (image->progress_monitor != (MagickProgressMonitor) NULL) 01788 { 01789 MagickBooleanType 01790 proceed; 01791 01792 #if defined(MAGICKCORE_OPENMP_SUPPORT) 01793 #pragma omp critical (MagickCore_YShearImage) 01794 #endif 01795 proceed=SetImageProgress(image,YShearImageTag,progress++,image->rows); 01796 if (proceed == MagickFalse) 01797 status=MagickFalse; 01798 } 01799 } 01800 image_view=DestroyCacheView(image_view); 01801 return(status); 01802 } 01803 01804 /* 01805 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 01806 % % 01807 % % 01808 % % 01809 % S h e a r I m a g e % 01810 % % 01811 % % 01812 % % 01813 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 01814 % 01815 % ShearImage() creates a new image that is a shear_image copy of an existing 01816 % one. Shearing slides one edge of an image along the X or Y axis, creating 01817 % a parallelogram. An X direction shear slides an edge along the X axis, 01818 % while a Y direction shear slides an edge along the Y axis. The amount of 01819 % the shear is controlled by a shear angle. For X direction shears, x_shear 01820 % is measured relative to the Y axis, and similarly, for Y direction shears 01821 % y_shear is measured relative to the X axis. Empty triangles left over from 01822 % shearing the image are filled with the background color defined by member 01823 % 'background_color' of the image.. ShearImage() allocates the memory 01824 % necessary for the new Image structure and returns a pointer to the new image. 01825 % 01826 % ShearImage() is based on the paper "A Fast Algorithm for General Raster 01827 % Rotatation" by Alan W. Paeth. 01828 % 01829 % The format of the ShearImage method is: 01830 % 01831 % Image *ShearImage(const Image *image,const double x_shear, 01832 % const double y_shear,ExceptionInfo *exception) 01833 % 01834 % A description of each parameter follows. 01835 % 01836 % o image: the image. 01837 % 01838 % o x_shear, y_shear: Specifies the number of degrees to shear the image. 01839 % 01840 % o exception: return any errors or warnings in this structure. 01841 % 01842 */ 01843 MagickExport Image *ShearImage(const Image *image,const double x_shear, 01844 const double y_shear,ExceptionInfo *exception) 01845 { 01846 Image 01847 *integral_image, 01848 *shear_image; 01849 01850 ssize_t 01851 x_offset, 01852 y_offset; 01853 01854 MagickBooleanType 01855 status; 01856 01857 PointInfo 01858 shear; 01859 01860 RectangleInfo 01861 border_info; 01862 01863 size_t 01864 y_width; 01865 01866 assert(image != (Image *) NULL); 01867 assert(image->signature == MagickSignature); 01868 if (image->debug != MagickFalse) 01869 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename); 01870 assert(exception != (ExceptionInfo *) NULL); 01871 assert(exception->signature == MagickSignature); 01872 if ((x_shear != 0.0) && (fmod(x_shear,90.0) == 0.0)) 01873 ThrowImageException(ImageError,"AngleIsDiscontinuous"); 01874 if ((y_shear != 0.0) && (fmod(y_shear,90.0) == 0.0)) 01875 ThrowImageException(ImageError,"AngleIsDiscontinuous"); 01876 /* 01877 Initialize shear angle. 01878 */ 01879 integral_image=CloneImage(image,0,0,MagickTrue,exception); 01880 if (integral_image == (Image *) NULL) 01881 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed"); 01882 shear.x=(-tan(DegreesToRadians(fmod(x_shear,360.0)))); 01883 shear.y=tan(DegreesToRadians(fmod(y_shear,360.0))); 01884 if ((shear.x == 0.0) && (shear.y == 0.0)) 01885 return(integral_image); 01886 if (SetImageStorageClass(integral_image,DirectClass,exception) == MagickFalse) 01887 { 01888 integral_image=DestroyImage(integral_image); 01889 return(integral_image); 01890 } 01891 if (integral_image->matte == MagickFalse) 01892 (void) SetImageAlphaChannel(integral_image,OpaqueAlphaChannel,exception); 01893 /* 01894 Compute image size. 01895 */ 01896 y_width=image->columns+(ssize_t) floor(fabs(shear.x)*image->rows+0.5); 01897 x_offset=(ssize_t) ceil((double) image->columns+((fabs(shear.x)*image->rows)- 01898 image->columns)/2.0-0.5); 01899 y_offset=(ssize_t) ceil((double) image->rows+((fabs(shear.y)*y_width)- 01900 image->rows)/2.0-0.5); 01901 /* 01902 Surround image with border. 01903 */ 01904 integral_image->border_color=integral_image->background_color; 01905 integral_image->compose=CopyCompositeOp; 01906 border_info.width=(size_t) x_offset; 01907 border_info.height=(size_t) y_offset; 01908 shear_image=BorderImage(integral_image,&border_info,image->compose,exception); 01909 integral_image=DestroyImage(integral_image); 01910 if (shear_image == (Image *) NULL) 01911 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed"); 01912 /* 01913 Shear the image. 01914 */ 01915 if (shear_image->matte == MagickFalse) 01916 (void) SetImageAlphaChannel(shear_image,OpaqueAlphaChannel,exception); 01917 status=XShearImage(shear_image,shear.x,image->columns,image->rows,x_offset, 01918 (ssize_t) (shear_image->rows-image->rows)/2,exception); 01919 if (status == MagickFalse) 01920 { 01921 shear_image=DestroyImage(shear_image); 01922 return((Image *) NULL); 01923 } 01924 status=YShearImage(shear_image,shear.y,y_width,image->rows,(ssize_t) 01925 (shear_image->columns-y_width)/2,y_offset,exception); 01926 if (status == MagickFalse) 01927 { 01928 shear_image=DestroyImage(shear_image); 01929 return((Image *) NULL); 01930 } 01931 status=CropToFitImage(&shear_image,shear.x,shear.y,(MagickRealType) 01932 image->columns,(MagickRealType) image->rows,MagickFalse,exception); 01933 if (status == MagickFalse) 01934 { 01935 shear_image=DestroyImage(shear_image); 01936 return((Image *) NULL); 01937 } 01938 shear_image->compose=image->compose; 01939 shear_image->page.width=0; 01940 shear_image->page.height=0; 01941 return(shear_image); 01942 } 01943 01944 /* 01945 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 01946 % % 01947 % % 01948 % % 01949 % S h e a r R o t a t e I m a g e % 01950 % % 01951 % % 01952 % % 01953 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 01954 % 01955 % ShearRotateImage() creates a new image that is a rotated copy of an existing 01956 % one. Positive angles rotate counter-clockwise (right-hand rule), while 01957 % negative angles rotate clockwise. Rotated images are usually larger than 01958 % the originals and have 'empty' triangular corners. X axis. Empty 01959 % triangles left over from shearing the image are filled with the background 01960 % color defined by member 'background_color' of the image. ShearRotateImage 01961 % allocates the memory necessary for the new Image structure and returns a 01962 % pointer to the new image. 01963 % 01964 % ShearRotateImage() is based on the paper "A Fast Algorithm for General 01965 % Raster Rotatation" by Alan W. Paeth. ShearRotateImage is adapted from a 01966 % similar method based on the Paeth paper written by Michael Halle of the 01967 % Spatial Imaging Group, MIT Media Lab. 01968 % 01969 % The format of the ShearRotateImage method is: 01970 % 01971 % Image *ShearRotateImage(const Image *image,const double degrees, 01972 % ExceptionInfo *exception) 01973 % 01974 % A description of each parameter follows. 01975 % 01976 % o image: the image. 01977 % 01978 % o degrees: Specifies the number of degrees to rotate the image. 01979 % 01980 % o exception: return any errors or warnings in this structure. 01981 % 01982 */ 01983 MagickExport Image *ShearRotateImage(const Image *image,const double degrees, 01984 ExceptionInfo *exception) 01985 { 01986 Image 01987 *integral_image, 01988 *rotate_image; 01989 01990 MagickBooleanType 01991 status; 01992 01993 MagickRealType 01994 angle; 01995 01996 PointInfo 01997 shear; 01998 01999 RectangleInfo 02000 border_info; 02001 02002 size_t 02003 height, 02004 rotations, 02005 width, 02006 y_width; 02007 02008 ssize_t 02009 x_offset, 02010 y_offset; 02011 02012 /* 02013 Adjust rotation angle. 02014 */ 02015 assert(image != (Image *) NULL); 02016 assert(image->signature == MagickSignature); 02017 if (image->debug != MagickFalse) 02018 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename); 02019 assert(exception != (ExceptionInfo *) NULL); 02020 assert(exception->signature == MagickSignature); 02021 angle=degrees; 02022 while (angle < -45.0) 02023 angle+=360.0; 02024 for (rotations=0; angle > 45.0; rotations++) 02025 angle-=90.0; 02026 rotations%=4; 02027 /* 02028 Calculate shear equations. 02029 */ 02030 integral_image=IntegralRotateImage(image,rotations,exception); 02031 if (integral_image == (Image *) NULL) 02032 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed"); 02033 shear.x=(-tan((double) DegreesToRadians(angle)/2.0)); 02034 shear.y=sin((double) DegreesToRadians(angle)); 02035 if ((shear.x == 0.0) && (shear.y == 0.0)) 02036 return(integral_image); 02037 if (SetImageStorageClass(integral_image,DirectClass,exception) == MagickFalse) 02038 { 02039 integral_image=DestroyImage(integral_image); 02040 return(integral_image); 02041 } 02042 if (integral_image->matte == MagickFalse) 02043 (void) SetImageAlphaChannel(integral_image,OpaqueAlphaChannel,exception); 02044 /* 02045 Compute image size. 02046 */ 02047 width=image->columns; 02048 height=image->rows; 02049 if ((rotations == 1) || (rotations == 3)) 02050 { 02051 width=image->rows; 02052 height=image->columns; 02053 } 02054 y_width=width+(ssize_t) floor(fabs(shear.x)*height+0.5); 02055 x_offset=(ssize_t) ceil((double) width+((fabs(shear.y)*height)-width)/2.0- 02056 0.5); 02057 y_offset=(ssize_t) ceil((double) height+((fabs(shear.y)*y_width)-height)/2.0- 02058 0.5); 02059 /* 02060 Surround image with a border. 02061 */ 02062 integral_image->border_color=integral_image->background_color; 02063 integral_image->compose=CopyCompositeOp; 02064 border_info.width=(size_t) x_offset; 02065 border_info.height=(size_t) y_offset; 02066 rotate_image=BorderImage(integral_image,&border_info,image->compose, 02067 exception); 02068 integral_image=DestroyImage(integral_image); 02069 if (rotate_image == (Image *) NULL) 02070 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed"); 02071 /* 02072 Rotate the image. 02073 */ 02074 status=XShearImage(rotate_image,shear.x,width,height,x_offset,(ssize_t) 02075 (rotate_image->rows-height)/2,exception); 02076 if (status == MagickFalse) 02077 { 02078 rotate_image=DestroyImage(rotate_image); 02079 return((Image *) NULL); 02080 } 02081 status=YShearImage(rotate_image,shear.y,y_width,height,(ssize_t) 02082 (rotate_image->columns-y_width)/2,y_offset,exception); 02083 if (status == MagickFalse) 02084 { 02085 rotate_image=DestroyImage(rotate_image); 02086 return((Image *) NULL); 02087 } 02088 status=XShearImage(rotate_image,shear.x,y_width,rotate_image->rows,(ssize_t) 02089 (rotate_image->columns-y_width)/2,0,exception); 02090 if (status == MagickFalse) 02091 { 02092 rotate_image=DestroyImage(rotate_image); 02093 return((Image *) NULL); 02094 } 02095 status=CropToFitImage(&rotate_image,shear.x,shear.y,(MagickRealType) width, 02096 (MagickRealType) height,MagickTrue,exception); 02097 if (status == MagickFalse) 02098 { 02099 rotate_image=DestroyImage(rotate_image); 02100 return((Image *) NULL); 02101 } 02102 rotate_image->compose=image->compose; 02103 rotate_image->page.width=0; 02104 rotate_image->page.height=0; 02105 return(rotate_image); 02106 }