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