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