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