MagickCore 6.9.13
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 "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*/
114static 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
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=CastDoubleToUnsigned(max.x-min.x+0.5);
170 geometry.height=CastDoubleToUnsigned(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
219static void RadonProjection(const Image *image,MatrixInfo *source_matrix,
220 MatrixInfo *destination_matrix,const ssize_t sign,size_t *projection)
221{
223 *swap;
224
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
328static MagickBooleanType RadonTransform(const Image *image,
329 const double threshold,size_t *projection,ExceptionInfo *exception)
330{
332 *image_view;
333
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
505static void GetImageBackgroundColor(Image *image,const ssize_t offset,
506 ExceptionInfo *exception)
507{
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*(MagickRealType) GetPixelRed(p);
546 background.green+=QuantumScale*(MagickRealType) GetPixelGreen(p);
547 background.blue+=QuantumScale*(MagickRealType) GetPixelBlue(p);
548 background.opacity+=QuantumScale*(MagickRealType) GetPixelOpacity(p);
549 count++;
550 p++;
551 }
552 }
553 image_view=DestroyCacheView(image_view);
554 image->background_color.red=ClampToQuantum((MagickRealType) QuantumRange*
555 (MagickRealType) background.red/count);
556 image->background_color.green=ClampToQuantum((MagickRealType) QuantumRange*
557 (MagickRealType) background.green/count);
558 image->background_color.blue=ClampToQuantum((MagickRealType) QuantumRange*
559 (MagickRealType) background.blue/count);
560 image->background_color.opacity=ClampToQuantum((MagickRealType) QuantumRange*
561 (MagickRealType) background.opacity/count);
562}
563
564MagickExport 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*/
708MagickExport Image *IntegralRotateImage(const Image *image,size_t rotations,
709 ExceptionInfo *exception)
710{
711#define RotateImageTag "Rotate/Image"
712
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,2)
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+=(ptrdiff_t) 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,2)
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
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*/
1145static 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
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+=(ptrdiff_t) 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+=(ptrdiff_t) 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*/
1367static 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
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+=(ptrdiff_t) 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+=(ptrdiff_t) 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% Rotation" 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*/
1592MagickExport 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 Rotation" 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*/
1726MagickExport 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=CastDoubleToUnsigned(fabs((double) height*shear.x)+width+0.5);
1791 bounds.height=CastDoubleToUnsigned(fabs((double) bounds.width*shear.y)+height+0.5);
1792 shear_width=CastDoubleToUnsigned(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}