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