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