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