MagickCore  6.7.5
shear.c
Go to the documentation of this file.
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 }