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