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