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