blob: ee3c6a509d75523fe3a6fcccd559cfc1956c074a [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% %
cristy7e41fe82010-12-04 23:12:08 +000020% Copyright 1999-2011 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"
cristyf2f27272009-12-17 14:48:46 +000075#include "magick/string_.h"
76#include "magick/string-private.h"
cristy09d81172010-10-21 16:15:05 +000077#include "magick/thread-private.h"
cristy3ed852e2009-09-05 21:47:34 +000078#include "magick/threshold.h"
79#include "magick/transform.h"
80
81/*
82%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
83% %
84% %
85% %
86% A f f i n e T r a n s f o r m I m a g e %
87% %
88% %
89% %
90%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
91%
92% AffineTransformImage() transforms an image as dictated by the affine matrix.
93% It allocates the memory necessary for the new Image structure and returns
94% a pointer to the new image.
95%
96% The format of the AffineTransformImage method is:
97%
98% Image *AffineTransformImage(const Image *image,
99% AffineMatrix *affine_matrix,ExceptionInfo *exception)
100%
101% A description of each parameter follows:
102%
103% o image: the image.
104%
105% o affine_matrix: the affine matrix.
106%
107% o exception: return any errors or warnings in this structure.
108%
109*/
110MagickExport Image *AffineTransformImage(const Image *image,
111 const AffineMatrix *affine_matrix,ExceptionInfo *exception)
112{
113 double
114 distort[6];
115
116 Image
117 *deskew_image;
118
119 /*
120 Affine transform image.
121 */
122 assert(image->signature == MagickSignature);
123 if (image->debug != MagickFalse)
124 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
125 assert(affine_matrix != (AffineMatrix *) NULL);
126 assert(exception != (ExceptionInfo *) NULL);
127 assert(exception->signature == MagickSignature);
128 distort[0]=affine_matrix->sx;
129 distort[1]=affine_matrix->rx;
130 distort[2]=affine_matrix->ry;
131 distort[3]=affine_matrix->sy;
132 distort[4]=affine_matrix->tx;
133 distort[5]=affine_matrix->ty;
134 deskew_image=DistortImage(image,AffineProjectionDistortion,6,distort,
135 MagickTrue,exception);
136 return(deskew_image);
137}
138
139/*
140%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
141% %
142% %
143% %
144+ C r o p T o F i t I m a g e %
145% %
146% %
147% %
148%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
149%
150% CropToFitImage() crops the sheared image as determined by the bounding box
151% as defined by width and height and shearing angles.
152%
153% The format of the CropToFitImage method is:
154%
cristyecc2c142010-01-17 22:25:46 +0000155% MagickBooleanType CropToFitImage(Image **image,
156% const MagickRealType x_shear,const MagickRealType x_shear,
157% const MagickRealType width,const MagickRealType height,
158% const MagickBooleanType rotate,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000159%
160% A description of each parameter follows.
161%
162% o image: the image.
163%
164% o x_shear, y_shear, width, height: Defines a region of the image to crop.
165%
166% o exception: return any errors or warnings in this structure.
167%
168*/
cristyecc2c142010-01-17 22:25:46 +0000169static MagickBooleanType CropToFitImage(Image **image,
170 const MagickRealType x_shear,const MagickRealType y_shear,
171 const MagickRealType width,const MagickRealType height,
172 const MagickBooleanType rotate,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000173{
174 Image
175 *crop_image;
176
177 PointInfo
178 extent[4],
179 min,
180 max;
181
182 RectangleInfo
183 geometry,
184 page;
185
cristybb503372010-05-27 20:51:26 +0000186 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000187 i;
188
189 /*
190 Calculate the rotated image size.
191 */
192 extent[0].x=(double) (-width/2.0);
193 extent[0].y=(double) (-height/2.0);
194 extent[1].x=(double) width/2.0;
195 extent[1].y=(double) (-height/2.0);
196 extent[2].x=(double) (-width/2.0);
197 extent[2].y=(double) height/2.0;
198 extent[3].x=(double) width/2.0;
199 extent[3].y=(double) height/2.0;
200 for (i=0; i < 4; i++)
201 {
202 extent[i].x+=x_shear*extent[i].y;
203 extent[i].y+=y_shear*extent[i].x;
204 if (rotate != MagickFalse)
205 extent[i].x+=x_shear*extent[i].y;
206 extent[i].x+=(double) (*image)->columns/2.0;
207 extent[i].y+=(double) (*image)->rows/2.0;
208 }
209 min=extent[0];
210 max=extent[0];
211 for (i=1; i < 4; i++)
212 {
213 if (min.x > extent[i].x)
214 min.x=extent[i].x;
215 if (min.y > extent[i].y)
216 min.y=extent[i].y;
217 if (max.x < extent[i].x)
218 max.x=extent[i].x;
219 if (max.y < extent[i].y)
220 max.y=extent[i].y;
221 }
cristybb503372010-05-27 20:51:26 +0000222 geometry.x=(ssize_t) ceil(min.x-0.5);
223 geometry.y=(ssize_t) ceil(min.y-0.5);
224 geometry.width=(size_t) floor(max.x-min.x+0.5);
225 geometry.height=(size_t) floor(max.y-min.y+0.5);
cristy3ed852e2009-09-05 21:47:34 +0000226 page=(*image)->page;
227 (void) ParseAbsoluteGeometry("0x0+0+0",&(*image)->page);
228 crop_image=CropImage(*image,&geometry,exception);
cristyecc2c142010-01-17 22:25:46 +0000229 if (crop_image == (Image *) NULL)
230 return(MagickFalse);
231 crop_image->page=page;
232 *image=DestroyImage(*image);
233 *image=crop_image;
234 return(MagickTrue);
cristy3ed852e2009-09-05 21:47:34 +0000235}
236
237/*
238%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
239% %
240% %
241% %
242% D e s k e w I m a g e %
243% %
244% %
245% %
246%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
247%
248% DeskewImage() removes skew from the image. Skew is an artifact that
249% occurs in scanned images because of the camera being misaligned,
250% imperfections in the scanning or surface, or simply because the paper was
251% not placed completely flat when scanned.
252%
253% The format of the DeskewImage method is:
254%
255% Image *DeskewImage(const Image *image,const double threshold,
256% ExceptionInfo *exception)
257%
258% A description of each parameter follows:
259%
260% o image: the image.
261%
262% o threshold: separate background from foreground.
263%
264% o exception: return any errors or warnings in this structure.
265%
266*/
267
268typedef struct _RadonInfo
269{
270 CacheType
271 type;
272
cristybb503372010-05-27 20:51:26 +0000273 size_t
cristy3ed852e2009-09-05 21:47:34 +0000274 width,
275 height;
276
277 MagickSizeType
278 length;
279
280 MagickBooleanType
281 mapped;
282
283 char
284 path[MaxTextExtent];
285
286 int
287 file;
288
289 unsigned short
290 *cells;
291} RadonInfo;
292
293static RadonInfo *DestroyRadonInfo(RadonInfo *radon_info)
294{
295 assert(radon_info != (RadonInfo *) NULL);
296 switch (radon_info->type)
297 {
298 case MemoryCache:
299 {
300 if (radon_info->mapped == MagickFalse)
301 radon_info->cells=(unsigned short *) RelinquishMagickMemory(
302 radon_info->cells);
303 else
304 radon_info->cells=(unsigned short *) UnmapBlob(radon_info->cells,
305 (size_t) radon_info->length);
306 RelinquishMagickResource(MemoryResource,radon_info->length);
307 break;
308 }
309 case MapCache:
310 {
311 radon_info->cells=(unsigned short *) UnmapBlob(radon_info->cells,(size_t)
312 radon_info->length);
313 RelinquishMagickResource(MapResource,radon_info->length);
314 }
315 case DiskCache:
316 {
317 if (radon_info->file != -1)
318 (void) close(radon_info->file);
319 (void) RelinquishUniqueFileResource(radon_info->path);
320 RelinquishMagickResource(DiskResource,radon_info->length);
321 break;
322 }
323 default:
324 break;
325 }
326 return((RadonInfo *) RelinquishMagickMemory(radon_info));
327}
328
329static MagickBooleanType ResetRadonCells(RadonInfo *radon_info)
330{
cristybb503372010-05-27 20:51:26 +0000331 ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000332 y;
333
cristybb503372010-05-27 20:51:26 +0000334 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000335 x;
336
337 ssize_t
338 count;
339
340 unsigned short
341 value;
342
343 if (radon_info->type != DiskCache)
344 {
345 (void) ResetMagickMemory(radon_info->cells,0,(size_t) radon_info->length);
346 return(MagickTrue);
347 }
348 value=0;
349 (void) MagickSeek(radon_info->file,0,SEEK_SET);
cristybb503372010-05-27 20:51:26 +0000350 for (y=0; y < (ssize_t) radon_info->height; y++)
cristy3ed852e2009-09-05 21:47:34 +0000351 {
cristybb503372010-05-27 20:51:26 +0000352 for (x=0; x < (ssize_t) radon_info->width; x++)
cristy3ed852e2009-09-05 21:47:34 +0000353 {
354 count=write(radon_info->file,&value,sizeof(*radon_info->cells));
355 if (count != (ssize_t) sizeof(*radon_info->cells))
356 break;
357 }
cristybb503372010-05-27 20:51:26 +0000358 if (x < (ssize_t) radon_info->width)
cristy3ed852e2009-09-05 21:47:34 +0000359 break;
360 }
cristybb503372010-05-27 20:51:26 +0000361 return(y < (ssize_t) radon_info->height ? MagickFalse : MagickTrue);
cristy3ed852e2009-09-05 21:47:34 +0000362}
363
cristybb503372010-05-27 20:51:26 +0000364static RadonInfo *AcquireRadonInfo(const Image *image,const size_t width,
365 const size_t height,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000366{
367 MagickBooleanType
368 status;
369
370 RadonInfo
371 *radon_info;
372
cristy73bd4a52010-10-05 11:24:23 +0000373 radon_info=(RadonInfo *) AcquireMagickMemory(sizeof(*radon_info));
cristy3ed852e2009-09-05 21:47:34 +0000374 if (radon_info == (RadonInfo *) NULL)
375 return((RadonInfo *) NULL);
376 (void) ResetMagickMemory(radon_info,0,sizeof(*radon_info));
377 radon_info->width=width;
378 radon_info->height=height;
379 radon_info->length=(MagickSizeType) width*height*sizeof(*radon_info->cells);
380 radon_info->type=MemoryCache;
381 status=AcquireMagickResource(AreaResource,radon_info->length);
382 if ((status != MagickFalse) &&
383 (radon_info->length == (MagickSizeType) ((size_t) radon_info->length)))
384 {
385 status=AcquireMagickResource(MemoryResource,radon_info->length);
386 if (status != MagickFalse)
387 {
388 radon_info->mapped=MagickFalse;
389 radon_info->cells=(unsigned short *) AcquireMagickMemory((size_t)
390 radon_info->length);
391 if (radon_info->cells == (unsigned short *) NULL)
392 {
393 radon_info->mapped=MagickTrue;
394 radon_info->cells=(unsigned short *) MapBlob(-1,IOMode,0,(size_t)
395 radon_info->length);
396 }
397 if (radon_info->cells == (unsigned short *) NULL)
398 RelinquishMagickResource(MemoryResource,radon_info->length);
399 }
400 }
401 radon_info->file=(-1);
402 if (radon_info->cells == (unsigned short *) NULL)
403 {
404 status=AcquireMagickResource(DiskResource,radon_info->length);
405 if (status == MagickFalse)
406 {
407 (void) ThrowMagickException(exception,GetMagickModule(),CacheError,
408 "CacheResourcesExhausted","`%s'",image->filename);
409 return(DestroyRadonInfo(radon_info));
410 }
411 radon_info->type=DiskCache;
412 (void) AcquireMagickResource(MemoryResource,radon_info->length);
413 radon_info->file=AcquireUniqueFileResource(radon_info->path);
414 if (radon_info->file == -1)
415 return(DestroyRadonInfo(radon_info));
416 status=AcquireMagickResource(MapResource,radon_info->length);
417 if (status != MagickFalse)
418 {
419 status=ResetRadonCells(radon_info);
420 if (status != MagickFalse)
421 {
422 radon_info->cells=(unsigned short *) MapBlob(radon_info->file,
423 IOMode,0,(size_t) radon_info->length);
424 if (radon_info->cells != (unsigned short *) NULL)
425 radon_info->type=MapCache;
426 else
427 RelinquishMagickResource(MapResource,radon_info->length);
428 }
429 }
430 }
431 return(radon_info);
432}
433
434static inline size_t MagickMin(const size_t x,const size_t y)
435{
436 if (x < y)
437 return(x);
438 return(y);
439}
440
441static inline ssize_t ReadRadonCell(const RadonInfo *radon_info,
442 const off_t offset,const size_t length,unsigned char *buffer)
443{
444 register ssize_t
445 i;
446
447 ssize_t
448 count;
449
450#if !defined(MAGICKCORE_HAVE_PPREAD)
cristyb5d5f722009-11-04 03:03:49 +0000451#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +0000452 #pragma omp critical (MagickCore_ReadRadonCell)
453#endif
454 {
455 i=(-1);
456 if (MagickSeek(radon_info->file,offset,SEEK_SET) >= 0)
457 {
458#endif
459 count=0;
460 for (i=0; i < (ssize_t) length; i+=count)
461 {
462#if !defined(MAGICKCORE_HAVE_PPREAD)
463 count=read(radon_info->file,buffer+i,MagickMin(length-i,(size_t)
464 SSIZE_MAX));
465#else
466 count=pread(radon_info->file,buffer+i,MagickMin(length-i,(size_t)
467 SSIZE_MAX),(off_t) (offset+i));
468#endif
469 if (count > 0)
470 continue;
471 count=0;
472 if (errno != EINTR)
473 {
474 i=(-1);
475 break;
476 }
477 }
478#if !defined(MAGICKCORE_HAVE_PPREAD)
479 }
480 }
481#endif
482 return(i);
483}
484
485static inline ssize_t WriteRadonCell(const RadonInfo *radon_info,
486 const off_t offset,const size_t length,const unsigned char *buffer)
487{
488 register ssize_t
489 i;
490
491 ssize_t
492 count;
493
494#if !defined(MAGICKCORE_HAVE_PWRITE)
cristyb5d5f722009-11-04 03:03:49 +0000495#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +0000496 #pragma omp critical (MagickCore_WriteRadonCell)
497#endif
498 {
499 if (MagickSeek(radon_info->file,offset,SEEK_SET) >= 0)
500 {
501#endif
502 count=0;
503 for (i=0; i < (ssize_t) length; i+=count)
504 {
505#if !defined(MAGICKCORE_HAVE_PWRITE)
506 count=write(radon_info->file,buffer+i,MagickMin(length-i,(size_t)
507 SSIZE_MAX));
508#else
509 count=pwrite(radon_info->file,buffer+i,MagickMin(length-i,(size_t)
510 SSIZE_MAX),(off_t) (offset+i));
511#endif
512 if (count > 0)
513 continue;
514 count=0;
515 if (errno != EINTR)
516 {
517 i=(-1);
518 break;
519 }
520 }
521#if !defined(MAGICKCORE_HAVE_PWRITE)
522 }
523 }
524#endif
525 return(i);
526}
527
528static inline unsigned short GetRadonCell(const RadonInfo *radon_info,
cristybb503372010-05-27 20:51:26 +0000529 const ssize_t x,const ssize_t y)
cristy3ed852e2009-09-05 21:47:34 +0000530{
531 off_t
532 i;
533
534 unsigned short
535 value;
536
537 i=(off_t) radon_info->height*x+y;
538 if ((i < 0) ||
539 ((MagickSizeType) (i*sizeof(*radon_info->cells)) >= radon_info->length))
540 return(0);
541 if (radon_info->type != DiskCache)
542 return(radon_info->cells[i]);
543 value=0;
544 (void) ReadRadonCell(radon_info,i*sizeof(*radon_info->cells),
545 sizeof(*radon_info->cells),(unsigned char *) &value);
546 return(value);
547}
548
549static inline MagickBooleanType SetRadonCell(const RadonInfo *radon_info,
cristybb503372010-05-27 20:51:26 +0000550 const ssize_t x,const ssize_t y,const unsigned short value)
cristy3ed852e2009-09-05 21:47:34 +0000551{
552 off_t
553 i;
554
555 ssize_t
556 count;
557
558 i=(off_t) radon_info->height*x+y;
559 if ((i < 0) ||
560 ((MagickSizeType) (i*sizeof(*radon_info->cells)) >= radon_info->length))
561 return(MagickFalse);
562 if (radon_info->type != DiskCache)
563 {
564 radon_info->cells[i]=value;
565 return(MagickTrue);
566 }
567 count=WriteRadonCell(radon_info,i*sizeof(*radon_info->cells),
cristy44807bb2009-09-19 18:28:06 +0000568 sizeof(*radon_info->cells),(const unsigned char *) &value);
cristy3ed852e2009-09-05 21:47:34 +0000569 if (count != (ssize_t) sizeof(*radon_info->cells))
570 return(MagickFalse);
571 return(MagickTrue);
572}
573
574static void RadonProjection(RadonInfo *source_cells,
cristybb503372010-05-27 20:51:26 +0000575 RadonInfo *destination_cells,const ssize_t sign,size_t *projection)
cristy3ed852e2009-09-05 21:47:34 +0000576{
577 RadonInfo
578 *swap;
579
cristybb503372010-05-27 20:51:26 +0000580 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000581 x;
582
583 register RadonInfo
584 *p,
585 *q;
586
cristybb503372010-05-27 20:51:26 +0000587 size_t
cristy3ed852e2009-09-05 21:47:34 +0000588 step;
589
590 p=source_cells;
591 q=destination_cells;
592 for (step=1; step < p->width; step*=2)
593 {
cristyeaedf062010-05-29 22:36:02 +0000594 for (x=0; x < (ssize_t) p->width; x+=2*(ssize_t) step)
cristy3ed852e2009-09-05 21:47:34 +0000595 {
cristybb503372010-05-27 20:51:26 +0000596 ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000597 y;
598
cristybb503372010-05-27 20:51:26 +0000599 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000600 i;
601
602 unsigned short
603 cell;
604
cristybb503372010-05-27 20:51:26 +0000605 for (i=0; i < (ssize_t) step; i++)
cristy3ed852e2009-09-05 21:47:34 +0000606 {
cristybb503372010-05-27 20:51:26 +0000607 for (y=0; y < (ssize_t) (p->height-i-1); y++)
cristy3ed852e2009-09-05 21:47:34 +0000608 {
609 cell=GetRadonCell(p,x+i,y);
cristyeaedf062010-05-29 22:36:02 +0000610 (void) SetRadonCell(q,x+2*i,y,cell+GetRadonCell(p,x+i+(ssize_t)
611 step,y+i));
612 (void) SetRadonCell(q,x+2*i+1,y,cell+GetRadonCell(p,x+i+(ssize_t)
613 step,y+i+1));
cristy3ed852e2009-09-05 21:47:34 +0000614 }
cristybb503372010-05-27 20:51:26 +0000615 for ( ; y < (ssize_t) (p->height-i); y++)
cristy3ed852e2009-09-05 21:47:34 +0000616 {
617 cell=GetRadonCell(p,x+i,y);
cristyeaedf062010-05-29 22:36:02 +0000618 (void) SetRadonCell(q,x+2*i,y,cell+GetRadonCell(p,x+i+(ssize_t) step,
619 y+i));
cristy3ed852e2009-09-05 21:47:34 +0000620 (void) SetRadonCell(q,x+2*i+1,y,cell);
621 }
cristybb503372010-05-27 20:51:26 +0000622 for ( ; y < (ssize_t) p->height; y++)
cristy3ed852e2009-09-05 21:47:34 +0000623 {
624 cell=GetRadonCell(p,x+i,y);
625 (void) SetRadonCell(q,x+2*i,y,cell);
626 (void) SetRadonCell(q,x+2*i+1,y,cell);
627 }
628 }
629 }
630 swap=p;
631 p=q;
632 q=swap;
633 }
cristyb5d5f722009-11-04 03:03:49 +0000634#if defined(MAGICKCORE_OPENMP_SUPPORT)
635 #pragma omp parallel for schedule(dynamic,4)
cristy3ed852e2009-09-05 21:47:34 +0000636#endif
cristybb503372010-05-27 20:51:26 +0000637 for (x=0; x < (ssize_t) p->width; x++)
cristy3ed852e2009-09-05 21:47:34 +0000638 {
cristybb503372010-05-27 20:51:26 +0000639 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000640 y;
641
cristybb503372010-05-27 20:51:26 +0000642 size_t
cristy3ed852e2009-09-05 21:47:34 +0000643 sum;
644
645 sum=0;
cristybb503372010-05-27 20:51:26 +0000646 for (y=0; y < (ssize_t) (p->height-1); y++)
cristy3ed852e2009-09-05 21:47:34 +0000647 {
cristybb503372010-05-27 20:51:26 +0000648 ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000649 delta;
650
cristybb503372010-05-27 20:51:26 +0000651 delta=GetRadonCell(p,x,y)-(ssize_t) GetRadonCell(p,x,y+1);
cristy3ed852e2009-09-05 21:47:34 +0000652 sum+=delta*delta;
653 }
654 projection[p->width+sign*x-1]=sum;
655 }
656}
657
658static MagickBooleanType RadonTransform(const Image *image,
cristybb503372010-05-27 20:51:26 +0000659 const double threshold,size_t *projection,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000660{
661 CacheView
662 *image_view;
663
cristybb503372010-05-27 20:51:26 +0000664 ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000665 y;
666
667 MagickBooleanType
668 status;
669
670 RadonInfo
671 *destination_cells,
672 *source_cells;
673
cristybb503372010-05-27 20:51:26 +0000674 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000675 i;
676
677 unsigned char
678 byte;
679
cristybb503372010-05-27 20:51:26 +0000680 size_t
cristy3ed852e2009-09-05 21:47:34 +0000681 count,
682 width;
683
684 unsigned short
685 bits[256];
686
687 for (width=1; width < ((image->columns+7)/8); width<<=1) ;
688 source_cells=AcquireRadonInfo(image,width,image->rows,exception);
689 destination_cells=AcquireRadonInfo(image,width,image->rows,exception);
690 if ((source_cells == (RadonInfo *) NULL) ||
691 (destination_cells == (RadonInfo *) NULL))
692 {
693 if (destination_cells != (RadonInfo *) NULL)
694 destination_cells=DestroyRadonInfo(destination_cells);
695 if (source_cells != (RadonInfo *) NULL)
696 source_cells=DestroyRadonInfo(source_cells);
697 return(MagickFalse);
698 }
699 if (ResetRadonCells(source_cells) == MagickFalse)
700 {
701 destination_cells=DestroyRadonInfo(destination_cells);
702 source_cells=DestroyRadonInfo(source_cells);
703 return(MagickFalse);
704 }
705 for (i=0; i < 256; i++)
706 {
707 byte=(unsigned char) i;
708 for (count=0; byte != 0; byte>>=1)
709 count+=byte & 0x01;
710 bits[i]=(unsigned short) count;
711 }
712 status=MagickTrue;
713 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +0000714#if defined(MAGICKCORE_OPENMP_SUPPORT)
715 #pragma omp parallel for schedule(dynamic,4) shared(status)
cristy3ed852e2009-09-05 21:47:34 +0000716#endif
cristybb503372010-05-27 20:51:26 +0000717 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000718 {
719 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +0000720 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +0000721
cristybb503372010-05-27 20:51:26 +0000722 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000723 i,
724 x;
725
cristybb503372010-05-27 20:51:26 +0000726 size_t
cristy3ed852e2009-09-05 21:47:34 +0000727 bit,
728 byte;
729
730 if (status == MagickFalse)
731 continue;
732 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
733 if (p == (const PixelPacket *) NULL)
734 {
735 status=MagickFalse;
736 continue;
737 }
738 bit=0;
739 byte=0;
cristybb503372010-05-27 20:51:26 +0000740 i=(ssize_t) (image->columns+7)/8;
741 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000742 {
743 byte<<=1;
744 if (((MagickRealType) p->red < threshold) ||
745 ((MagickRealType) p->green < threshold) ||
746 ((MagickRealType) p->blue < threshold))
747 byte|=0x01;
748 bit++;
749 if (bit == 8)
750 {
751 (void) SetRadonCell(source_cells,--i,y,bits[byte]);
752 bit=0;
753 byte=0;
754 }
755 p++;
756 }
757 if (bit != 0)
758 {
759 byte<<=(8-bit);
760 (void) SetRadonCell(source_cells,--i,y,bits[byte]);
761 }
762 }
763 RadonProjection(source_cells,destination_cells,-1,projection);
764 (void) ResetRadonCells(source_cells);
cristyb5d5f722009-11-04 03:03:49 +0000765#if defined(MAGICKCORE_OPENMP_SUPPORT)
766 #pragma omp parallel for schedule(dynamic,4) shared(status)
cristy3ed852e2009-09-05 21:47:34 +0000767#endif
cristybb503372010-05-27 20:51:26 +0000768 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000769 {
770 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +0000771 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +0000772
cristybb503372010-05-27 20:51:26 +0000773 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000774 i,
775 x;
776
cristybb503372010-05-27 20:51:26 +0000777 size_t
cristy3ed852e2009-09-05 21:47:34 +0000778 bit,
779 byte;
780
781 if (status == MagickFalse)
782 continue;
783 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
784 if (p == (const PixelPacket *) NULL)
785 {
786 status=MagickFalse;
787 continue;
788 }
789 bit=0;
790 byte=0;
791 i=0;
cristybb503372010-05-27 20:51:26 +0000792 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000793 {
794 byte<<=1;
795 if (((MagickRealType) p->red < threshold) ||
796 ((MagickRealType) p->green < threshold) ||
797 ((MagickRealType) p->blue < threshold))
798 byte|=0x01;
799 bit++;
800 if (bit == 8)
801 {
802 (void) SetRadonCell(source_cells,i++,y,bits[byte]);
803 bit=0;
804 byte=0;
805 }
806 p++;
807 }
808 if (bit != 0)
809 {
810 byte<<=(8-bit);
811 (void) SetRadonCell(source_cells,i++,y,bits[byte]);
812 }
813 }
814 RadonProjection(source_cells,destination_cells,1,projection);
815 image_view=DestroyCacheView(image_view);
816 destination_cells=DestroyRadonInfo(destination_cells);
817 source_cells=DestroyRadonInfo(source_cells);
818 return(MagickTrue);
819}
820
cristybb503372010-05-27 20:51:26 +0000821static void GetImageBackgroundColor(Image *image,const ssize_t offset,
cristy3ed852e2009-09-05 21:47:34 +0000822 ExceptionInfo *exception)
823{
824 CacheView
825 *image_view;
826
cristybb503372010-05-27 20:51:26 +0000827 ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000828 y;
829
830 MagickPixelPacket
831 background;
832
833 MagickRealType
834 count;
835
836 /*
837 Compute average background color.
838 */
839 if (offset <= 0)
840 return;
841 GetMagickPixelPacket(image,&background);
842 count=0.0;
843 image_view=AcquireCacheView(image);
cristybb503372010-05-27 20:51:26 +0000844 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000845 {
846 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +0000847 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +0000848
cristybb503372010-05-27 20:51:26 +0000849 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000850 x;
851
cristybb503372010-05-27 20:51:26 +0000852 if ((y >= offset) && (y < ((ssize_t) image->rows-offset)))
cristy3ed852e2009-09-05 21:47:34 +0000853 continue;
854 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
855 if (p == (const PixelPacket *) NULL)
856 continue;
cristybb503372010-05-27 20:51:26 +0000857 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000858 {
cristybb503372010-05-27 20:51:26 +0000859 if ((x >= offset) && (x < ((ssize_t) image->columns-offset)))
cristy3ed852e2009-09-05 21:47:34 +0000860 continue;
cristyce70c172010-01-07 17:15:30 +0000861 background.red+=QuantumScale*GetRedPixelComponent(p);
862 background.green+=QuantumScale*GetGreenPixelComponent(p);
863 background.blue+=QuantumScale*GetBluePixelComponent(p);
864 background.opacity+=QuantumScale*GetOpacityPixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +0000865 count++;
866 p++;
867 }
868 }
869 image_view=DestroyCacheView(image_view);
cristyce70c172010-01-07 17:15:30 +0000870 image->background_color.red=ClampToQuantum((MagickRealType) QuantumRange*
cristy3ed852e2009-09-05 21:47:34 +0000871 background.red/count);
cristyce70c172010-01-07 17:15:30 +0000872 image->background_color.green=ClampToQuantum((MagickRealType) QuantumRange*
cristy3ed852e2009-09-05 21:47:34 +0000873 background.green/count);
cristyce70c172010-01-07 17:15:30 +0000874 image->background_color.blue=ClampToQuantum((MagickRealType) QuantumRange*
cristy3ed852e2009-09-05 21:47:34 +0000875 background.blue/count);
cristyce70c172010-01-07 17:15:30 +0000876 image->background_color.opacity=ClampToQuantum((MagickRealType) QuantumRange*
cristy3ed852e2009-09-05 21:47:34 +0000877 background.opacity/count);
878}
879
880MagickExport Image *DeskewImage(const Image *image,const double threshold,
881 ExceptionInfo *exception)
882{
883 AffineMatrix
884 affine_matrix;
885
886 const char
887 *artifact;
888
889 double
890 degrees;
891
892 Image
893 *clone_image,
894 *crop_image,
895 *deskew_image,
896 *median_image;
897
cristybb503372010-05-27 20:51:26 +0000898 ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000899 skew;
900
901 MagickBooleanType
902 status;
903
904 RectangleInfo
905 geometry;
906
cristybb503372010-05-27 20:51:26 +0000907 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000908 i;
909
cristybb503372010-05-27 20:51:26 +0000910 size_t
cristy3ed852e2009-09-05 21:47:34 +0000911 max_projection,
912 *projection,
913 width;
914
915 /*
916 Compute deskew angle.
917 */
918 for (width=1; width < ((image->columns+7)/8); width<<=1) ;
cristybb503372010-05-27 20:51:26 +0000919 projection=(size_t *) AcquireQuantumMemory((size_t) (2*width-1),
cristy3ed852e2009-09-05 21:47:34 +0000920 sizeof(*projection));
cristybb503372010-05-27 20:51:26 +0000921 if (projection == (size_t *) NULL)
cristy3ed852e2009-09-05 21:47:34 +0000922 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
923 status=RadonTransform(image,threshold,projection,exception);
924 if (status == MagickFalse)
925 {
cristybb503372010-05-27 20:51:26 +0000926 projection=(size_t *) RelinquishMagickMemory(projection);
cristy3ed852e2009-09-05 21:47:34 +0000927 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
928 }
929 max_projection=0;
930 skew=0;
cristybb503372010-05-27 20:51:26 +0000931 for (i=0; i < (ssize_t) (2*width-1); i++)
cristy3ed852e2009-09-05 21:47:34 +0000932 {
933 if (projection[i] > max_projection)
934 {
cristybb503372010-05-27 20:51:26 +0000935 skew=i-(ssize_t) width+1;
cristy3ed852e2009-09-05 21:47:34 +0000936 max_projection=projection[i];
937 }
938 }
cristybb503372010-05-27 20:51:26 +0000939 projection=(size_t *) RelinquishMagickMemory(projection);
cristy3ed852e2009-09-05 21:47:34 +0000940 /*
941 Deskew image.
942 */
943 clone_image=CloneImage(image,0,0,MagickTrue,exception);
944 if (clone_image == (Image *) NULL)
945 return((Image *) NULL);
946 (void) SetImageVirtualPixelMethod(clone_image,BackgroundVirtualPixelMethod);
947 degrees=RadiansToDegrees(-atan((double) skew/width/8));
948 if (image->debug != MagickFalse)
cristy8cd5b312010-01-07 01:10:24 +0000949 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000950 " Deskew angle: %g",degrees);
cristy3ed852e2009-09-05 21:47:34 +0000951 affine_matrix.sx=cos(DegreesToRadians(fmod((double) degrees,360.0)));
952 affine_matrix.rx=sin(DegreesToRadians(fmod((double) degrees,360.0)));
953 affine_matrix.ry=(-sin(DegreesToRadians(fmod((double) degrees,360.0))));
954 affine_matrix.sy=cos(DegreesToRadians(fmod((double) degrees,360.0)));
955 affine_matrix.tx=0.0;
956 affine_matrix.ty=0.0;
957 artifact=GetImageArtifact(image,"deskew:auto-crop");
958 if (artifact == (const char *) NULL)
959 {
960 deskew_image=AffineTransformImage(clone_image,&affine_matrix,exception);
961 clone_image=DestroyImage(clone_image);
962 return(deskew_image);
963 }
964 /*
965 Auto-crop image.
966 */
cristyba978e12010-09-12 20:26:50 +0000967 GetImageBackgroundColor(clone_image,(ssize_t) StringToLong(artifact),
968 exception);
cristy3ed852e2009-09-05 21:47:34 +0000969 deskew_image=AffineTransformImage(clone_image,&affine_matrix,exception);
970 clone_image=DestroyImage(clone_image);
971 if (deskew_image == (Image *) NULL)
972 return((Image *) NULL);
973 median_image=MedianFilterImage(deskew_image,0.0,exception);
974 if (median_image == (Image *) NULL)
975 {
976 deskew_image=DestroyImage(deskew_image);
977 return((Image *) NULL);
978 }
979 geometry=GetImageBoundingBox(median_image,exception);
980 median_image=DestroyImage(median_image);
981 if (image->debug != MagickFalse)
982 (void) LogMagickEvent(TransformEvent,GetMagickModule()," Deskew geometry: "
cristy6d8abba2010-06-03 01:10:47 +0000983 "%.20gx%.20g%+.20g%+.20g",(double) geometry.width,(double)
cristye8c25f92010-06-03 00:53:06 +0000984 geometry.height,(double) geometry.x,(double) geometry.y);
cristy3ed852e2009-09-05 21:47:34 +0000985 crop_image=CropImage(deskew_image,&geometry,exception);
986 deskew_image=DestroyImage(deskew_image);
987 return(crop_image);
988}
989
990/*
991%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
992% %
993% %
994% %
995+ I n t e g r a l R o t a t e I m a g e %
996% %
997% %
998% %
999%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1000%
cristy3dfa16c2010-05-17 19:34:48 +00001001% IntegralRotateImage() rotates the image an integral of 90 degrees. It
cristy3ed852e2009-09-05 21:47:34 +00001002% allocates the memory necessary for the new Image structure and returns a
1003% pointer to the rotated image.
1004%
1005% The format of the IntegralRotateImage method is:
1006%
cristybb503372010-05-27 20:51:26 +00001007% Image *IntegralRotateImage(const Image *image,size_t rotations,
cristy3ed852e2009-09-05 21:47:34 +00001008% ExceptionInfo *exception)
1009%
1010% A description of each parameter follows.
1011%
1012% o image: the image.
1013%
1014% o rotations: Specifies the number of 90 degree rotations.
1015%
1016*/
cristybb503372010-05-27 20:51:26 +00001017static Image *IntegralRotateImage(const Image *image,size_t rotations,
cristy3ed852e2009-09-05 21:47:34 +00001018 ExceptionInfo *exception)
1019{
cristy3ed852e2009-09-05 21:47:34 +00001020#define RotateImageTag "Rotate/Image"
1021
1022 CacheView
1023 *image_view,
1024 *rotate_view;
1025
1026 Image
1027 *rotate_image;
1028
cristy3ed852e2009-09-05 21:47:34 +00001029 MagickBooleanType
1030 status;
1031
cristy5f959472010-05-27 22:19:46 +00001032 MagickOffsetType
1033 progress;
1034
cristy3ed852e2009-09-05 21:47:34 +00001035 RectangleInfo
1036 page;
1037
cristy5f959472010-05-27 22:19:46 +00001038 ssize_t
1039 y;
1040
cristy3ed852e2009-09-05 21:47:34 +00001041 /*
1042 Initialize rotated image attributes.
1043 */
1044 assert(image != (Image *) NULL);
1045 page=image->page;
1046 rotations%=4;
cristyb32b90a2009-09-07 21:45:48 +00001047 if (rotations == 0)
1048 return(CloneImage(image,0,0,MagickTrue,exception));
cristy3ed852e2009-09-05 21:47:34 +00001049 if ((rotations == 1) || (rotations == 3))
1050 rotate_image=CloneImage(image,image->rows,image->columns,MagickTrue,
1051 exception);
1052 else
1053 rotate_image=CloneImage(image,image->columns,image->rows,MagickTrue,
1054 exception);
1055 if (rotate_image == (Image *) NULL)
1056 return((Image *) NULL);
1057 /*
1058 Integral rotate the image.
1059 */
1060 status=MagickTrue;
1061 progress=0;
1062 image_view=AcquireCacheView(image);
1063 rotate_view=AcquireCacheView(rotate_image);
1064 switch (rotations)
1065 {
1066 case 0:
1067 {
1068 /*
1069 Rotate 0 degrees.
1070 */
cristy3ed852e2009-09-05 21:47:34 +00001071 break;
1072 }
1073 case 1:
1074 {
cristybb503372010-05-27 20:51:26 +00001075 ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001076 tile_y;
1077
cristybb503372010-05-27 20:51:26 +00001078 size_t
cristyb32b90a2009-09-07 21:45:48 +00001079 tile_height,
1080 tile_width;
1081
cristy3ed852e2009-09-05 21:47:34 +00001082 /*
1083 Rotate 90 degrees.
1084 */
cristyb32b90a2009-09-07 21:45:48 +00001085 GetPixelCacheTileSize(image,&tile_width,&tile_height);
cristy59e0d3b2010-06-07 13:12:38 +00001086#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy48012352010-10-21 16:24:59 +00001087 #pragma omp parallel for schedule(static,1) shared(progress, status) omp_throttle(1)
cristydaa97692009-09-13 02:10:35 +00001088#endif
cristyeaedf062010-05-29 22:36:02 +00001089 for (tile_y=0; tile_y < (ssize_t) image->rows; tile_y+=(ssize_t) tile_height)
cristy3ed852e2009-09-05 21:47:34 +00001090 {
cristybb503372010-05-27 20:51:26 +00001091 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001092 tile_x;
1093
1094 if (status == MagickFalse)
1095 continue;
cristyeaedf062010-05-29 22:36:02 +00001096 for (tile_x=0; tile_x < (ssize_t) image->columns; tile_x+=(ssize_t) tile_width)
cristy3ed852e2009-09-05 21:47:34 +00001097 {
1098 MagickBooleanType
1099 sync;
1100
1101 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00001102 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00001103
1104 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001105 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00001106
1107 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00001108 *restrict rotate_indexes;
cristy3ed852e2009-09-05 21:47:34 +00001109
cristybb503372010-05-27 20:51:26 +00001110 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001111 y;
1112
1113 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001114 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001115
cristybb503372010-05-27 20:51:26 +00001116 size_t
cristyb32b90a2009-09-07 21:45:48 +00001117 height,
1118 width;
cristy3ed852e2009-09-05 21:47:34 +00001119
cristyb32b90a2009-09-07 21:45:48 +00001120 width=tile_width;
cristybb503372010-05-27 20:51:26 +00001121 if ((tile_x+(ssize_t) tile_width) > (ssize_t) image->columns)
1122 width=(size_t) (tile_width-(tile_x+tile_width-
cristy3ed852e2009-09-05 21:47:34 +00001123 image->columns));
cristyb32b90a2009-09-07 21:45:48 +00001124 height=tile_height;
cristybb503372010-05-27 20:51:26 +00001125 if ((tile_y+(ssize_t) tile_height) > (ssize_t) image->rows)
1126 height=(size_t) (tile_height-(tile_y+tile_height-
cristy3ed852e2009-09-05 21:47:34 +00001127 image->rows));
cristydaa97692009-09-13 02:10:35 +00001128 p=GetCacheViewVirtualPixels(image_view,tile_x,tile_y,width,height,
1129 exception);
cristy3ed852e2009-09-05 21:47:34 +00001130 if (p == (const PixelPacket *) NULL)
1131 {
1132 status=MagickFalse;
1133 break;
1134 }
1135 indexes=GetCacheViewVirtualIndexQueue(image_view);
cristybb503372010-05-27 20:51:26 +00001136 for (y=0; y < (ssize_t) width; y++)
cristy3ed852e2009-09-05 21:47:34 +00001137 {
1138 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001139 *restrict tile_pixels;
cristy3ed852e2009-09-05 21:47:34 +00001140
cristybb503372010-05-27 20:51:26 +00001141 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001142 x;
1143
cristybb503372010-05-27 20:51:26 +00001144 q=QueueCacheViewAuthenticPixels(rotate_view,(ssize_t)
cristyeaedf062010-05-29 22:36:02 +00001145 (rotate_image->columns-(tile_y+height)),y+tile_x,height,
cristy3ed852e2009-09-05 21:47:34 +00001146 1,exception);
1147 if (q == (PixelPacket *) NULL)
1148 {
1149 status=MagickFalse;
1150 break;
1151 }
cristyb32b90a2009-09-07 21:45:48 +00001152 tile_pixels=p+(height-1)*width+y;
cristybb503372010-05-27 20:51:26 +00001153 for (x=0; x < (ssize_t) height; x++)
cristy3ed852e2009-09-05 21:47:34 +00001154 {
1155 *q++=(*tile_pixels);
cristyb32b90a2009-09-07 21:45:48 +00001156 tile_pixels-=width;
cristy3ed852e2009-09-05 21:47:34 +00001157 }
1158 rotate_indexes=GetCacheViewAuthenticIndexQueue(rotate_view);
1159 if ((indexes != (IndexPacket *) NULL) &&
1160 (rotate_indexes != (IndexPacket *) NULL))
1161 {
1162 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00001163 *restrict tile_indexes;
cristy3ed852e2009-09-05 21:47:34 +00001164
cristyb32b90a2009-09-07 21:45:48 +00001165 tile_indexes=indexes+(height-1)*width+y;
cristybb503372010-05-27 20:51:26 +00001166 for (x=0; x < (ssize_t) height; x++)
cristy3ed852e2009-09-05 21:47:34 +00001167 {
1168 *rotate_indexes++=(*tile_indexes);
cristyb32b90a2009-09-07 21:45:48 +00001169 tile_indexes-=width;
cristy3ed852e2009-09-05 21:47:34 +00001170 }
1171 }
1172 sync=SyncCacheViewAuthenticPixels(rotate_view,exception);
1173 if (sync == MagickFalse)
1174 status=MagickFalse;
1175 }
1176 }
1177 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1178 {
1179 MagickBooleanType
1180 proceed;
1181
cristyb32b90a2009-09-07 21:45:48 +00001182 proceed=SetImageProgress(image,RotateImageTag,progress+=tile_height,
cristy3ed852e2009-09-05 21:47:34 +00001183 image->rows);
1184 if (proceed == MagickFalse)
1185 status=MagickFalse;
1186 }
1187 }
cristyecc2c142010-01-17 22:25:46 +00001188 (void) SetImageProgress(image,RotateImageTag,(MagickOffsetType)
1189 image->rows-1,image->rows);
cristy3ed852e2009-09-05 21:47:34 +00001190 Swap(page.width,page.height);
1191 Swap(page.x,page.y);
1192 if (page.width != 0)
cristybb503372010-05-27 20:51:26 +00001193 page.x=(ssize_t) (page.width-rotate_image->columns-page.x);
cristy3ed852e2009-09-05 21:47:34 +00001194 break;
1195 }
1196 case 2:
1197 {
1198 /*
1199 Rotate 180 degrees.
1200 */
cristy59e0d3b2010-06-07 13:12:38 +00001201#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy09d81172010-10-21 16:15:05 +00001202 #pragma omp parallel for schedule(static,8) shared(progress,status) omp_throttle(1)
cristydaa97692009-09-13 02:10:35 +00001203#endif
cristybb503372010-05-27 20:51:26 +00001204 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001205 {
1206 MagickBooleanType
1207 sync;
1208
1209 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00001210 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00001211
1212 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001213 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00001214
1215 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00001216 *restrict rotate_indexes;
cristy3ed852e2009-09-05 21:47:34 +00001217
cristybb503372010-05-27 20:51:26 +00001218 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001219 x;
1220
1221 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001222 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001223
1224 if (status == MagickFalse)
1225 continue;
1226 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,
1227 exception);
cristybb503372010-05-27 20:51:26 +00001228 q=QueueCacheViewAuthenticPixels(rotate_view,0,(ssize_t) (image->rows-
cristy3ed852e2009-09-05 21:47:34 +00001229 y-1),image->columns,1,exception);
1230 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
1231 {
1232 status=MagickFalse;
1233 continue;
1234 }
1235 indexes=GetCacheViewVirtualIndexQueue(image_view);
1236 rotate_indexes=GetCacheViewAuthenticIndexQueue(rotate_view);
1237 q+=image->columns;
cristybb503372010-05-27 20:51:26 +00001238 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001239 *--q=(*p++);
1240 if ((indexes != (IndexPacket *) NULL) &&
1241 (rotate_indexes != (IndexPacket *) NULL))
cristybb503372010-05-27 20:51:26 +00001242 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001243 rotate_indexes[image->columns-x-1]=indexes[x];
1244 sync=SyncCacheViewAuthenticPixels(rotate_view,exception);
1245 if (sync == MagickFalse)
1246 status=MagickFalse;
1247 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1248 {
1249 MagickBooleanType
1250 proceed;
1251
1252 proceed=SetImageProgress(image,RotateImageTag,progress++,
1253 image->rows);
1254 if (proceed == MagickFalse)
1255 status=MagickFalse;
1256 }
1257 }
1258 if (page.width != 0)
cristybb503372010-05-27 20:51:26 +00001259 page.x=(ssize_t) (page.width-rotate_image->columns-page.x);
cristy3ed852e2009-09-05 21:47:34 +00001260 if (page.height != 0)
cristybb503372010-05-27 20:51:26 +00001261 page.y=(ssize_t) (page.height-rotate_image->rows-page.y);
cristy3ed852e2009-09-05 21:47:34 +00001262 break;
1263 }
1264 case 3:
1265 {
cristybb503372010-05-27 20:51:26 +00001266 ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001267 tile_y;
1268
cristybb503372010-05-27 20:51:26 +00001269 size_t
cristyb32b90a2009-09-07 21:45:48 +00001270 tile_height,
1271 tile_width;
1272
cristy3ed852e2009-09-05 21:47:34 +00001273 /*
1274 Rotate 270 degrees.
1275 */
cristyb32b90a2009-09-07 21:45:48 +00001276 GetPixelCacheTileSize(image,&tile_width,&tile_height);
cristy59e0d3b2010-06-07 13:12:38 +00001277#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy09d81172010-10-21 16:15:05 +00001278 #pragma omp parallel for schedule(static,1) shared(progress,status) omp_throttle(1)
cristydaa97692009-09-13 02:10:35 +00001279#endif
cristyeaedf062010-05-29 22:36:02 +00001280 for (tile_y=0; tile_y < (ssize_t) image->rows; tile_y+=(ssize_t) tile_height)
cristy3ed852e2009-09-05 21:47:34 +00001281 {
cristybb503372010-05-27 20:51:26 +00001282 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001283 tile_x;
1284
1285 if (status == MagickFalse)
1286 continue;
cristyeaedf062010-05-29 22:36:02 +00001287 for (tile_x=0; tile_x < (ssize_t) image->columns; tile_x+=(ssize_t) tile_width)
cristy3ed852e2009-09-05 21:47:34 +00001288 {
1289 MagickBooleanType
1290 sync;
1291
1292 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00001293 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00001294
1295 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001296 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00001297
1298 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00001299 *restrict rotate_indexes;
cristy3ed852e2009-09-05 21:47:34 +00001300
cristybb503372010-05-27 20:51:26 +00001301 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001302 y;
1303
1304 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001305 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001306
cristybb503372010-05-27 20:51:26 +00001307 size_t
cristyb32b90a2009-09-07 21:45:48 +00001308 height,
1309 width;
cristy3ed852e2009-09-05 21:47:34 +00001310
cristyb32b90a2009-09-07 21:45:48 +00001311 width=tile_width;
cristybb503372010-05-27 20:51:26 +00001312 if ((tile_x+(ssize_t) tile_width) > (ssize_t) image->columns)
1313 width=(size_t) (tile_width-(tile_x+tile_width-
cristy3ed852e2009-09-05 21:47:34 +00001314 image->columns));
cristyb32b90a2009-09-07 21:45:48 +00001315 height=tile_height;
cristybb503372010-05-27 20:51:26 +00001316 if ((tile_y+(ssize_t) tile_height) > (ssize_t) image->rows)
1317 height=(size_t) (tile_height-(tile_y+tile_height-
cristy3ed852e2009-09-05 21:47:34 +00001318 image->rows));
cristyb32b90a2009-09-07 21:45:48 +00001319 p=GetCacheViewVirtualPixels(image_view,tile_x,tile_y,width,
1320 height,exception);
cristy3ed852e2009-09-05 21:47:34 +00001321 if (p == (const PixelPacket *) NULL)
1322 {
1323 status=MagickFalse;
1324 break;
1325 }
1326 indexes=GetCacheViewVirtualIndexQueue(image_view);
cristybb503372010-05-27 20:51:26 +00001327 for (y=0; y < (ssize_t) width; y++)
cristy3ed852e2009-09-05 21:47:34 +00001328 {
1329 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001330 *restrict tile_pixels;
cristy3ed852e2009-09-05 21:47:34 +00001331
cristybb503372010-05-27 20:51:26 +00001332 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001333 x;
1334
cristybb503372010-05-27 20:51:26 +00001335 q=QueueCacheViewAuthenticPixels(rotate_view,tile_y,(ssize_t)
cristyeaedf062010-05-29 22:36:02 +00001336 (y+rotate_image->rows-(tile_x+width)),height,1,exception);
cristy3ed852e2009-09-05 21:47:34 +00001337 if (q == (PixelPacket *) NULL)
1338 {
1339 status=MagickFalse;
1340 break;
1341 }
cristyb32b90a2009-09-07 21:45:48 +00001342 tile_pixels=p+(width-1)-y;
cristybb503372010-05-27 20:51:26 +00001343 for (x=0; x < (ssize_t) height; x++)
cristy3ed852e2009-09-05 21:47:34 +00001344 {
1345 *q++=(*tile_pixels);
cristyb32b90a2009-09-07 21:45:48 +00001346 tile_pixels+=width;
cristy3ed852e2009-09-05 21:47:34 +00001347 }
1348 rotate_indexes=GetCacheViewAuthenticIndexQueue(rotate_view);
1349 if ((indexes != (IndexPacket *) NULL) &&
1350 (rotate_indexes != (IndexPacket *) NULL))
1351 {
1352 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00001353 *restrict tile_indexes;
cristy3ed852e2009-09-05 21:47:34 +00001354
cristyb32b90a2009-09-07 21:45:48 +00001355 tile_indexes=indexes+(width-1)-y;
cristybb503372010-05-27 20:51:26 +00001356 for (x=0; x < (ssize_t) height; x++)
cristy3ed852e2009-09-05 21:47:34 +00001357 {
1358 *rotate_indexes++=(*tile_indexes);
cristyb32b90a2009-09-07 21:45:48 +00001359 tile_indexes+=width;
cristy3ed852e2009-09-05 21:47:34 +00001360 }
1361 }
1362 sync=SyncCacheViewAuthenticPixels(rotate_view,exception);
1363 if (sync == MagickFalse)
1364 status=MagickFalse;
1365 }
1366 }
1367 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1368 {
1369 MagickBooleanType
1370 proceed;
1371
cristyb32b90a2009-09-07 21:45:48 +00001372 proceed=SetImageProgress(image,RotateImageTag,progress+=tile_height,
cristy3ed852e2009-09-05 21:47:34 +00001373 image->rows);
1374 if (proceed == MagickFalse)
1375 status=MagickFalse;
1376 }
1377 }
cristyecc2c142010-01-17 22:25:46 +00001378 (void) SetImageProgress(image,RotateImageTag,(MagickOffsetType)
1379 image->rows-1,image->rows);
cristy3ed852e2009-09-05 21:47:34 +00001380 Swap(page.width,page.height);
1381 Swap(page.x,page.y);
1382 if (page.height != 0)
cristybb503372010-05-27 20:51:26 +00001383 page.y=(ssize_t) (page.height-rotate_image->rows-page.y);
cristy3ed852e2009-09-05 21:47:34 +00001384 break;
1385 }
1386 }
1387 rotate_view=DestroyCacheView(rotate_view);
1388 image_view=DestroyCacheView(image_view);
1389 rotate_image->type=image->type;
1390 rotate_image->page=page;
1391 if (status == MagickFalse)
1392 rotate_image=DestroyImage(rotate_image);
1393 return(rotate_image);
1394}
1395
1396/*
1397%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1398% %
1399% %
1400% %
1401+ X S h e a r I m a g e %
1402% %
1403% %
1404% %
1405%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1406%
1407% XShearImage() shears the image in the X direction with a shear angle of
1408% 'degrees'. Positive angles shear counter-clockwise (right-hand rule), and
1409% negative angles shear clockwise. Angles are measured relative to a vertical
1410% Y-axis. X shears will widen an image creating 'empty' triangles on the left
1411% and right sides of the source image.
1412%
1413% The format of the XShearImage method is:
1414%
1415% MagickBooleanType XShearImage(Image *image,const MagickRealType degrees,
cristybb503372010-05-27 20:51:26 +00001416% const size_t width,const size_t height,
1417% const ssize_t x_offset,const ssize_t y_offset,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001418%
1419% A description of each parameter follows.
1420%
1421% o image: the image.
1422%
cristycee97112010-05-28 00:44:52 +00001423% o degrees: A MagickRealType representing the shearing angle along the X
cristy3ed852e2009-09-05 21:47:34 +00001424% axis.
1425%
1426% o width, height, x_offset, y_offset: Defines a region of the image
1427% to shear.
1428%
cristyecc2c142010-01-17 22:25:46 +00001429% o exception: return any errors or warnings in this structure.
1430%
cristy3ed852e2009-09-05 21:47:34 +00001431*/
1432static MagickBooleanType XShearImage(Image *image,const MagickRealType degrees,
cristybb503372010-05-27 20:51:26 +00001433 const size_t width,const size_t height,const ssize_t x_offset,
1434 const ssize_t y_offset,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001435{
1436#define XShearImageTag "XShear/Image"
1437
1438 typedef enum
1439 {
1440 LEFT,
1441 RIGHT
1442 } ShearDirection;
1443
1444 CacheView
1445 *image_view;
1446
cristy3ed852e2009-09-05 21:47:34 +00001447 MagickBooleanType
1448 status;
1449
cristy5f959472010-05-27 22:19:46 +00001450 MagickOffsetType
1451 progress;
1452
cristy3ed852e2009-09-05 21:47:34 +00001453 MagickPixelPacket
1454 background;
1455
cristy5f959472010-05-27 22:19:46 +00001456 ssize_t
1457 y;
1458
cristy3ed852e2009-09-05 21:47:34 +00001459 assert(image != (Image *) NULL);
1460 assert(image->signature == MagickSignature);
1461 if (image->debug != MagickFalse)
1462 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1463 GetMagickPixelPacket(image,&background);
1464 SetMagickPixelPacket(image,&image->background_color,(IndexPacket *) NULL,
1465 &background);
1466 if (image->colorspace == CMYKColorspace)
1467 ConvertRGBToCMYK(&background);
1468 /*
1469 XShear image.
1470 */
1471 status=MagickTrue;
1472 progress=0;
cristy3ed852e2009-09-05 21:47:34 +00001473 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00001474#if defined(MAGICKCORE_OPENMP_SUPPORT)
1475 #pragma omp parallel for schedule(dynamic,4) shared(progress, status)
cristy3ed852e2009-09-05 21:47:34 +00001476#endif
cristybb503372010-05-27 20:51:26 +00001477 for (y=0; y < (ssize_t) height; y++)
cristy3ed852e2009-09-05 21:47:34 +00001478 {
cristybb503372010-05-27 20:51:26 +00001479 ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001480 step;
1481
1482 MagickPixelPacket
1483 pixel,
1484 source,
1485 destination;
1486
1487 MagickRealType
1488 area,
1489 displacement;
1490
cristybb503372010-05-27 20:51:26 +00001491 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001492 i;
1493
1494 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00001495 *restrict indexes,
1496 *restrict shear_indexes;
cristy3ed852e2009-09-05 21:47:34 +00001497
1498 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001499 *restrict p,
1500 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001501
1502 ShearDirection
1503 direction;
1504
1505 if (status == MagickFalse)
1506 continue;
1507 p=GetCacheViewAuthenticPixels(image_view,0,y_offset+y,image->columns,1,
1508 exception);
1509 if (p == (PixelPacket *) NULL)
1510 {
1511 status=MagickFalse;
1512 continue;
1513 }
1514 indexes=GetCacheViewAuthenticIndexQueue(image_view);
1515 p+=x_offset;
1516 indexes+=x_offset;
1517 displacement=degrees*(MagickRealType) (y-height/2.0);
1518 if (displacement == 0.0)
1519 continue;
1520 if (displacement > 0.0)
1521 direction=RIGHT;
1522 else
1523 {
1524 displacement*=(-1.0);
1525 direction=LEFT;
1526 }
cristybb503372010-05-27 20:51:26 +00001527 step=(ssize_t) floor((double) displacement);
cristy3ed852e2009-09-05 21:47:34 +00001528 area=(MagickRealType) (displacement-step);
1529 step++;
1530 pixel=background;
1531 GetMagickPixelPacket(image,&source);
1532 GetMagickPixelPacket(image,&destination);
1533 switch (direction)
1534 {
1535 case LEFT:
1536 {
1537 /*
1538 Transfer pixels left-to-right.
1539 */
1540 if (step > x_offset)
1541 break;
1542 q=p-step;
1543 shear_indexes=indexes-step;
cristybb503372010-05-27 20:51:26 +00001544 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00001545 {
1546 if ((x_offset+i) < step)
1547 {
1548 SetMagickPixelPacket(image,++p,++indexes,&pixel);
1549 q++;
1550 shear_indexes++;
1551 continue;
1552 }
1553 SetMagickPixelPacket(image,p,indexes,&source);
1554 MagickPixelCompositeAreaBlend(&pixel,(MagickRealType) pixel.opacity,
1555 &source,(MagickRealType) p->opacity,area,&destination);
1556 SetPixelPacket(image,&destination,q++,shear_indexes++);
1557 SetMagickPixelPacket(image,p++,indexes++,&pixel);
1558 }
1559 MagickPixelCompositeAreaBlend(&pixel,(MagickRealType) pixel.opacity,
1560 &background,(MagickRealType) background.opacity,area,&destination);
1561 SetPixelPacket(image,&destination,q++,shear_indexes++);
1562 for (i=0; i < (step-1); i++)
1563 SetPixelPacket(image,&background,q++,shear_indexes++);
1564 break;
1565 }
1566 case RIGHT:
1567 {
1568 /*
1569 Transfer pixels right-to-left.
1570 */
1571 p+=width;
1572 indexes+=width;
1573 q=p+step;
1574 shear_indexes=indexes+step;
cristybb503372010-05-27 20:51:26 +00001575 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00001576 {
1577 p--;
1578 indexes--;
1579 q--;
1580 shear_indexes--;
cristybb503372010-05-27 20:51:26 +00001581 if ((size_t) (x_offset+width+step-i) >= image->columns)
cristy3ed852e2009-09-05 21:47:34 +00001582 continue;
1583 SetMagickPixelPacket(image,p,indexes,&source);
1584 MagickPixelCompositeAreaBlend(&pixel,(MagickRealType) pixel.opacity,
1585 &source,(MagickRealType) p->opacity,area,&destination);
1586 SetPixelPacket(image,&destination,q,shear_indexes);
1587 SetMagickPixelPacket(image,p,indexes,&pixel);
1588 }
1589 MagickPixelCompositeAreaBlend(&pixel,(MagickRealType) pixel.opacity,
1590 &background,(MagickRealType) background.opacity,area,&destination);
1591 SetPixelPacket(image,&destination,--q,--shear_indexes);
1592 for (i=0; i < (step-1); i++)
1593 SetPixelPacket(image,&background,--q,--shear_indexes);
1594 break;
1595 }
1596 }
1597 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
1598 status=MagickFalse;
1599 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1600 {
1601 MagickBooleanType
1602 proceed;
1603
cristyb5d5f722009-11-04 03:03:49 +00001604#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00001605 #pragma omp critical (MagickCore_XShearImage)
1606#endif
1607 proceed=SetImageProgress(image,XShearImageTag,progress++,height);
1608 if (proceed == MagickFalse)
1609 status=MagickFalse;
1610 }
1611 }
1612 image_view=DestroyCacheView(image_view);
1613 return(status);
1614}
1615
1616/*
1617%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1618% %
1619% %
1620% %
1621+ Y S h e a r I m a g e %
1622% %
1623% %
1624% %
1625%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1626%
1627% YShearImage shears the image in the Y direction with a shear angle of
1628% 'degrees'. Positive angles shear counter-clockwise (right-hand rule), and
1629% negative angles shear clockwise. Angles are measured relative to a
1630% horizontal X-axis. Y shears will increase the height of an image creating
1631% 'empty' triangles on the top and bottom of the source image.
1632%
1633% The format of the YShearImage method is:
1634%
1635% MagickBooleanType YShearImage(Image *image,const MagickRealType degrees,
cristybb503372010-05-27 20:51:26 +00001636% const size_t width,const size_t height,
1637% const ssize_t x_offset,const ssize_t y_offset,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001638%
1639% A description of each parameter follows.
1640%
1641% o image: the image.
1642%
cristycee97112010-05-28 00:44:52 +00001643% o degrees: A MagickRealType representing the shearing angle along the Y
cristy3ed852e2009-09-05 21:47:34 +00001644% axis.
1645%
1646% o width, height, x_offset, y_offset: Defines a region of the image
1647% to shear.
1648%
cristyecc2c142010-01-17 22:25:46 +00001649% o exception: return any errors or warnings in this structure.
1650%
cristy3ed852e2009-09-05 21:47:34 +00001651*/
1652static MagickBooleanType YShearImage(Image *image,const MagickRealType degrees,
cristybb503372010-05-27 20:51:26 +00001653 const size_t width,const size_t height,const ssize_t x_offset,
1654 const ssize_t y_offset,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001655{
1656#define YShearImageTag "YShear/Image"
1657
1658 typedef enum
1659 {
1660 UP,
1661 DOWN
1662 } ShearDirection;
1663
1664 CacheView
1665 *image_view;
1666
cristy3ed852e2009-09-05 21:47:34 +00001667 MagickBooleanType
1668 status;
1669
cristy5f959472010-05-27 22:19:46 +00001670 MagickOffsetType
1671 progress;
1672
cristy3ed852e2009-09-05 21:47:34 +00001673 MagickPixelPacket
1674 background;
1675
cristy5f959472010-05-27 22:19:46 +00001676 ssize_t
1677 x;
1678
cristy3ed852e2009-09-05 21:47:34 +00001679 assert(image != (Image *) NULL);
1680 assert(image->signature == MagickSignature);
1681 if (image->debug != MagickFalse)
1682 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1683 GetMagickPixelPacket(image,&background);
1684 SetMagickPixelPacket(image,&image->background_color,(IndexPacket *) NULL,
1685 &background);
1686 if (image->colorspace == CMYKColorspace)
1687 ConvertRGBToCMYK(&background);
1688 /*
1689 Y Shear image.
1690 */
1691 status=MagickTrue;
1692 progress=0;
cristy3ed852e2009-09-05 21:47:34 +00001693 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00001694#if defined(MAGICKCORE_OPENMP_SUPPORT)
1695 #pragma omp parallel for schedule(dynamic,4) shared(progress, status)
cristy3ed852e2009-09-05 21:47:34 +00001696#endif
cristybb503372010-05-27 20:51:26 +00001697 for (x=0; x < (ssize_t) width; x++)
cristy3ed852e2009-09-05 21:47:34 +00001698 {
cristybb503372010-05-27 20:51:26 +00001699 ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001700 step;
1701
1702 MagickPixelPacket
1703 pixel,
1704 source,
1705 destination;
1706
1707 MagickRealType
1708 area,
1709 displacement;
1710
1711 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00001712 *restrict indexes,
1713 *restrict shear_indexes;
cristy3ed852e2009-09-05 21:47:34 +00001714
cristybb503372010-05-27 20:51:26 +00001715 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001716 i;
1717
1718 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001719 *restrict p,
1720 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001721
1722 ShearDirection
1723 direction;
1724
1725 if (status == MagickFalse)
1726 continue;
1727 p=GetCacheViewAuthenticPixels(image_view,x_offset+x,0,1,image->rows,
1728 exception);
1729 if (p == (PixelPacket *) NULL)
1730 {
1731 status=MagickFalse;
1732 continue;
1733 }
1734 indexes=GetCacheViewAuthenticIndexQueue(image_view);
1735 p+=y_offset;
1736 indexes+=y_offset;
1737 displacement=degrees*(MagickRealType) (x-width/2.0);
1738 if (displacement == 0.0)
1739 continue;
1740 if (displacement > 0.0)
1741 direction=DOWN;
1742 else
1743 {
1744 displacement*=(-1.0);
1745 direction=UP;
1746 }
cristybb503372010-05-27 20:51:26 +00001747 step=(ssize_t) floor((double) displacement);
cristy3ed852e2009-09-05 21:47:34 +00001748 area=(MagickRealType) (displacement-step);
1749 step++;
1750 pixel=background;
1751 GetMagickPixelPacket(image,&source);
1752 GetMagickPixelPacket(image,&destination);
1753 switch (direction)
1754 {
1755 case UP:
1756 {
1757 /*
1758 Transfer pixels top-to-bottom.
1759 */
1760 if (step > y_offset)
1761 break;
1762 q=p-step;
1763 shear_indexes=indexes-step;
cristybb503372010-05-27 20:51:26 +00001764 for (i=0; i < (ssize_t) height; i++)
cristy3ed852e2009-09-05 21:47:34 +00001765 {
1766 if ((y_offset+i) < step)
1767 {
1768 SetMagickPixelPacket(image,++p,++indexes,&pixel);
1769 q++;
1770 shear_indexes++;
1771 continue;
1772 }
1773 SetMagickPixelPacket(image,p,indexes,&source);
1774 MagickPixelCompositeAreaBlend(&pixel,(MagickRealType) pixel.opacity,
1775 &source,(MagickRealType) p->opacity,area,&destination);
1776 SetPixelPacket(image,&destination,q++,shear_indexes++);
1777 SetMagickPixelPacket(image,p++,indexes++,&pixel);
1778 }
1779 MagickPixelCompositeAreaBlend(&pixel,(MagickRealType) pixel.opacity,
1780 &background,(MagickRealType) background.opacity,area,&destination);
1781 SetPixelPacket(image,&destination,q++,shear_indexes++);
1782 for (i=0; i < (step-1); i++)
1783 SetPixelPacket(image,&background,q++,shear_indexes++);
1784 break;
1785 }
1786 case DOWN:
1787 {
1788 /*
1789 Transfer pixels bottom-to-top.
1790 */
1791 p+=height;
1792 indexes+=height;
1793 q=p+step;
1794 shear_indexes=indexes+step;
cristybb503372010-05-27 20:51:26 +00001795 for (i=0; i < (ssize_t) height; i++)
cristy3ed852e2009-09-05 21:47:34 +00001796 {
1797 p--;
1798 indexes--;
1799 q--;
1800 shear_indexes--;
cristybb503372010-05-27 20:51:26 +00001801 if ((size_t) (y_offset+height+step-i) >= image->rows)
cristy3ed852e2009-09-05 21:47:34 +00001802 continue;
1803 SetMagickPixelPacket(image,p,indexes,&source);
1804 MagickPixelCompositeAreaBlend(&pixel,(MagickRealType) pixel.opacity,
1805 &source,(MagickRealType) p->opacity,area,&destination);
1806 SetPixelPacket(image,&destination,q,shear_indexes);
1807 SetMagickPixelPacket(image,p,indexes,&pixel);
1808 }
1809 MagickPixelCompositeAreaBlend(&pixel,(MagickRealType) pixel.opacity,
1810 &background,(MagickRealType) background.opacity,area,&destination);
1811 SetPixelPacket(image,&destination,--q,--shear_indexes);
1812 for (i=0; i < (step-1); i++)
1813 SetPixelPacket(image,&background,--q,--shear_indexes);
1814 break;
1815 }
1816 }
1817 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
1818 status=MagickFalse;
1819 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1820 {
1821 MagickBooleanType
1822 proceed;
1823
cristyb5d5f722009-11-04 03:03:49 +00001824#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00001825 #pragma omp critical (MagickCore_YShearImage)
1826#endif
1827 proceed=SetImageProgress(image,YShearImageTag,progress++,image->rows);
1828 if (proceed == MagickFalse)
1829 status=MagickFalse;
1830 }
1831 }
1832 image_view=DestroyCacheView(image_view);
1833 return(status);
1834}
1835
1836/*
1837%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1838% %
1839% %
1840% %
1841% R o t a t e I m a g e %
1842% %
1843% %
1844% %
1845%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1846%
1847% RotateImage() creates a new image that is a rotated copy of an existing
1848% one. Positive angles rotate counter-clockwise (right-hand rule), while
1849% negative angles rotate clockwise. Rotated images are usually larger than
1850% the originals and have 'empty' triangular corners. X axis. Empty
1851% triangles left over from shearing the image are filled with the background
1852% color defined by member 'background_color' of the image. RotateImage
1853% allocates the memory necessary for the new Image structure and returns a
1854% pointer to the new image.
1855%
1856% RotateImage() is based on the paper "A Fast Algorithm for General
1857% Raster Rotatation" by Alan W. Paeth. RotateImage is adapted from a similar
1858% method based on the Paeth paper written by Michael Halle of the Spatial
1859% Imaging Group, MIT Media Lab.
1860%
1861% The format of the RotateImage method is:
1862%
1863% Image *RotateImage(const Image *image,const double degrees,
1864% ExceptionInfo *exception)
1865%
1866% A description of each parameter follows.
1867%
1868% o image: the image.
1869%
1870% o degrees: Specifies the number of degrees to rotate the image.
1871%
1872% o exception: return any errors or warnings in this structure.
1873%
1874*/
1875MagickExport Image *RotateImage(const Image *image,const double degrees,
1876 ExceptionInfo *exception)
1877{
1878 Image
1879 *integral_image,
1880 *rotate_image;
1881
cristybb503372010-05-27 20:51:26 +00001882 ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001883 x_offset,
1884 y_offset;
1885
cristyecc2c142010-01-17 22:25:46 +00001886 MagickBooleanType
1887 status;
1888
cristy3ed852e2009-09-05 21:47:34 +00001889 MagickRealType
1890 angle;
1891
1892 PointInfo
1893 shear;
1894
1895 RectangleInfo
1896 border_info;
1897
cristybb503372010-05-27 20:51:26 +00001898 size_t
cristy3ed852e2009-09-05 21:47:34 +00001899 height,
1900 rotations,
1901 width,
1902 y_width;
1903
1904 /*
1905 Adjust rotation angle.
1906 */
1907 assert(image != (Image *) NULL);
1908 assert(image->signature == MagickSignature);
1909 if (image->debug != MagickFalse)
1910 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1911 assert(exception != (ExceptionInfo *) NULL);
1912 assert(exception->signature == MagickSignature);
1913 angle=degrees;
1914 while (angle < -45.0)
1915 angle+=360.0;
1916 for (rotations=0; angle > 45.0; rotations++)
1917 angle-=90.0;
1918 rotations%=4;
1919 /*
1920 Calculate shear equations.
1921 */
1922 integral_image=IntegralRotateImage(image,rotations,exception);
1923 if (integral_image == (Image *) NULL)
1924 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
1925 shear.x=(-tan((double) DegreesToRadians(angle)/2.0));
1926 shear.y=sin((double) DegreesToRadians(angle));
1927 if ((shear.x == 0.0) && (shear.y == 0.0))
1928 return(integral_image);
1929 if (SetImageStorageClass(integral_image,DirectClass) == MagickFalse)
1930 {
1931 InheritException(exception,&integral_image->exception);
1932 integral_image=DestroyImage(integral_image);
1933 return(integral_image);
1934 }
1935 if (integral_image->matte == MagickFalse)
1936 (void) SetImageAlphaChannel(integral_image,OpaqueAlphaChannel);
1937 /*
1938 Compute image size.
1939 */
1940 width=image->columns;
1941 height=image->rows;
1942 if ((rotations == 1) || (rotations == 3))
1943 {
1944 width=image->rows;
1945 height=image->columns;
1946 }
cristybb503372010-05-27 20:51:26 +00001947 y_width=width+(ssize_t) floor(fabs(shear.x)*height+0.5);
cristycee97112010-05-28 00:44:52 +00001948 x_offset=(ssize_t) ceil((double) width+((fabs(shear.y)*height)-width)/2.0-
1949 0.5);
1950 y_offset=(ssize_t) ceil((double) height+((fabs(shear.y)*y_width)-height)/2.0-
1951 0.5);
cristy3ed852e2009-09-05 21:47:34 +00001952 /*
1953 Surround image with a border.
1954 */
1955 integral_image->border_color=integral_image->background_color;
1956 integral_image->compose=CopyCompositeOp;
cristybb503372010-05-27 20:51:26 +00001957 border_info.width=(size_t) x_offset;
1958 border_info.height=(size_t) y_offset;
cristy3ed852e2009-09-05 21:47:34 +00001959 rotate_image=BorderImage(integral_image,&border_info,exception);
1960 integral_image=DestroyImage(integral_image);
1961 if (rotate_image == (Image *) NULL)
1962 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
1963 /*
1964 Rotate the image.
1965 */
cristyeaedf062010-05-29 22:36:02 +00001966 status=XShearImage(rotate_image,shear.x,width,height,x_offset,(ssize_t)
1967 (rotate_image->rows-height)/2,exception);
cristyecc2c142010-01-17 22:25:46 +00001968 if (status == MagickFalse)
1969 {
1970 rotate_image=DestroyImage(rotate_image);
1971 return((Image *) NULL);
1972 }
cristyeaedf062010-05-29 22:36:02 +00001973 status=YShearImage(rotate_image,shear.y,y_width,height,(ssize_t)
1974 (rotate_image->columns-y_width)/2,y_offset,exception);
cristyecc2c142010-01-17 22:25:46 +00001975 if (status == MagickFalse)
1976 {
1977 rotate_image=DestroyImage(rotate_image);
1978 return((Image *) NULL);
1979 }
cristyeaedf062010-05-29 22:36:02 +00001980 status=XShearImage(rotate_image,shear.x,y_width,rotate_image->rows,(ssize_t)
1981 (rotate_image->columns-y_width)/2,0,exception);
cristyecc2c142010-01-17 22:25:46 +00001982 if (status == MagickFalse)
1983 {
1984 rotate_image=DestroyImage(rotate_image);
1985 return((Image *) NULL);
1986 }
1987 status=CropToFitImage(&rotate_image,shear.x,shear.y,(MagickRealType) width,
cristy3ed852e2009-09-05 21:47:34 +00001988 (MagickRealType) height,MagickTrue,exception);
cristyecc2c142010-01-17 22:25:46 +00001989 if (status == MagickFalse)
1990 {
1991 rotate_image=DestroyImage(rotate_image);
1992 return((Image *) NULL);
1993 }
cristy3ed852e2009-09-05 21:47:34 +00001994 rotate_image->compose=image->compose;
1995 rotate_image->page.width=0;
1996 rotate_image->page.height=0;
1997 return(rotate_image);
1998}
1999
2000/*
2001%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2002% %
2003% %
2004% %
2005% S h e a r I m a g e %
2006% %
2007% %
2008% %
2009%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2010%
2011% ShearImage() creates a new image that is a shear_image copy of an existing
cristycee97112010-05-28 00:44:52 +00002012% one. Shearing slides one edge of an image along the X or Y axis, creating
2013% a parallelogram. An X direction shear slides an edge along the X axis,
2014% while a Y direction shear slides an edge along the Y axis. The amount of
cristy3ed852e2009-09-05 21:47:34 +00002015% the shear is controlled by a shear angle. For X direction shears, x_shear
2016% is measured relative to the Y axis, and similarly, for Y direction shears
2017% y_shear is measured relative to the X axis. Empty triangles left over from
2018% shearing the image are filled with the background color defined by member
2019% 'background_color' of the image.. ShearImage() allocates the memory
2020% necessary for the new Image structure and returns a pointer to the new image.
2021%
2022% ShearImage() is based on the paper "A Fast Algorithm for General Raster
2023% Rotatation" by Alan W. Paeth.
2024%
2025% The format of the ShearImage method is:
2026%
2027% Image *ShearImage(const Image *image,const double x_shear,
2028% const double y_shear,ExceptionInfo *exception)
2029%
2030% A description of each parameter follows.
2031%
2032% o image: the image.
2033%
2034% o x_shear, y_shear: Specifies the number of degrees to shear the image.
2035%
2036% o exception: return any errors or warnings in this structure.
2037%
2038*/
2039MagickExport Image *ShearImage(const Image *image,const double x_shear,
2040 const double y_shear,ExceptionInfo *exception)
2041{
2042 Image
2043 *integral_image,
2044 *shear_image;
2045
cristybb503372010-05-27 20:51:26 +00002046 ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002047 x_offset,
2048 y_offset;
2049
cristyecc2c142010-01-17 22:25:46 +00002050 MagickBooleanType
2051 status;
2052
cristy3ed852e2009-09-05 21:47:34 +00002053 PointInfo
2054 shear;
2055
2056 RectangleInfo
2057 border_info;
2058
cristybb503372010-05-27 20:51:26 +00002059 size_t
cristy3ed852e2009-09-05 21:47:34 +00002060 y_width;
2061
2062 assert(image != (Image *) NULL);
2063 assert(image->signature == MagickSignature);
2064 if (image->debug != MagickFalse)
2065 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2066 assert(exception != (ExceptionInfo *) NULL);
2067 assert(exception->signature == MagickSignature);
2068 if ((x_shear != 0.0) && (fmod(x_shear,90.0) == 0.0))
2069 ThrowImageException(ImageError,"AngleIsDiscontinuous");
2070 if ((y_shear != 0.0) && (fmod(y_shear,90.0) == 0.0))
2071 ThrowImageException(ImageError,"AngleIsDiscontinuous");
2072 /*
2073 Initialize shear angle.
2074 */
2075 integral_image=CloneImage(image,0,0,MagickTrue,exception);
2076 if (integral_image == (Image *) NULL)
2077 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
2078 shear.x=(-tan(DegreesToRadians(fmod(x_shear,360.0))));
2079 shear.y=tan(DegreesToRadians(fmod(y_shear,360.0)));
2080 if ((shear.x == 0.0) && (shear.y == 0.0))
2081 return(integral_image);
2082 if (SetImageStorageClass(integral_image,DirectClass) == MagickFalse)
2083 {
2084 InheritException(exception,&integral_image->exception);
2085 integral_image=DestroyImage(integral_image);
2086 return(integral_image);
2087 }
2088 if (integral_image->matte == MagickFalse)
2089 (void) SetImageAlphaChannel(integral_image,OpaqueAlphaChannel);
2090 /*
2091 Compute image size.
2092 */
cristybb503372010-05-27 20:51:26 +00002093 y_width=image->columns+(ssize_t) floor(fabs(shear.x)*image->rows+0.5);
cristycee97112010-05-28 00:44:52 +00002094 x_offset=(ssize_t) ceil((double) image->columns+((fabs(shear.x)*image->rows)-
cristy06609ee2010-03-17 20:21:27 +00002095 image->columns)/2.0-0.5);
cristycee97112010-05-28 00:44:52 +00002096 y_offset=(ssize_t) ceil((double) image->rows+((fabs(shear.y)*y_width)-
2097 image->rows)/2.0-0.5);
cristy3ed852e2009-09-05 21:47:34 +00002098 /*
2099 Surround image with border.
2100 */
2101 integral_image->border_color=integral_image->background_color;
2102 integral_image->compose=CopyCompositeOp;
cristybb503372010-05-27 20:51:26 +00002103 border_info.width=(size_t) x_offset;
2104 border_info.height=(size_t) y_offset;
cristy3ed852e2009-09-05 21:47:34 +00002105 shear_image=BorderImage(integral_image,&border_info,exception);
cristyecc2c142010-01-17 22:25:46 +00002106 integral_image=DestroyImage(integral_image);
cristy3ed852e2009-09-05 21:47:34 +00002107 if (shear_image == (Image *) NULL)
2108 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristy3ed852e2009-09-05 21:47:34 +00002109 /*
2110 Shear the image.
2111 */
2112 if (shear_image->matte == MagickFalse)
2113 (void) SetImageAlphaChannel(shear_image,OpaqueAlphaChannel);
cristyecc2c142010-01-17 22:25:46 +00002114 status=XShearImage(shear_image,shear.x,image->columns,image->rows,x_offset,
cristyeaedf062010-05-29 22:36:02 +00002115 (ssize_t) (shear_image->rows-image->rows)/2,exception);
cristyecc2c142010-01-17 22:25:46 +00002116 if (status == MagickFalse)
2117 {
2118 shear_image=DestroyImage(shear_image);
2119 return((Image *) NULL);
2120 }
cristyeaedf062010-05-29 22:36:02 +00002121 status=YShearImage(shear_image,shear.y,y_width,image->rows,(ssize_t)
2122 (shear_image->columns-y_width)/2,y_offset,exception);
cristyecc2c142010-01-17 22:25:46 +00002123 if (status == MagickFalse)
2124 {
2125 shear_image=DestroyImage(shear_image);
2126 return((Image *) NULL);
2127 }
2128 status=CropToFitImage(&shear_image,shear.x,shear.y,(MagickRealType)
2129 image->columns,(MagickRealType) image->rows,MagickFalse,exception);
2130 if (status == MagickFalse)
2131 {
2132 shear_image=DestroyImage(shear_image);
2133 return((Image *) NULL);
2134 }
cristy3ed852e2009-09-05 21:47:34 +00002135 shear_image->compose=image->compose;
2136 shear_image->page.width=0;
2137 shear_image->page.height=0;
2138 return(shear_image);
2139}