blob: a80ad8fb1a0bcf5fc9d13b1b9dbd0b4e94dff4d2 [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% %
cristy45ef08f2012-12-07 13:13:34 +000020% Copyright 1999-2013 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%
cristyc7c81142011-11-07 12:14:23 +000036% The XShearImage() and YShearImage() methods are based on the paper "A Fast
cristy7f81c002011-11-07 01:08:58 +000037% Algorithm for General Raster Rotatation" by Alan W. Paeth, Graphics
cristyc7c81142011-11-07 12:14:23 +000038% Interface '86 (Vancouver). ShearRotateImage() is adapted from a similar
39% method based on the Paeth paper written by Michael Halle of the Spatial
40% Imaging Group, MIT Media Lab.
cristy3ed852e2009-09-05 21:47:34 +000041%
42*/
43
44/*
45 Include declarations.
46*/
cristy4c08aed2011-07-01 19:47:50 +000047#include "MagickCore/studio.h"
48#include "MagickCore/artifact.h"
49#include "MagickCore/attribute.h"
50#include "MagickCore/blob-private.h"
51#include "MagickCore/cache-private.h"
cristy6a2180c2013-05-27 10:28:36 +000052#include "MagickCore/channel.h"
cristy4c08aed2011-07-01 19:47:50 +000053#include "MagickCore/color-private.h"
54#include "MagickCore/colorspace-private.h"
55#include "MagickCore/composite.h"
56#include "MagickCore/composite-private.h"
57#include "MagickCore/decorate.h"
58#include "MagickCore/distort.h"
59#include "MagickCore/draw.h"
60#include "MagickCore/exception.h"
61#include "MagickCore/exception-private.h"
62#include "MagickCore/gem.h"
63#include "MagickCore/geometry.h"
64#include "MagickCore/image.h"
65#include "MagickCore/image-private.h"
66#include "MagickCore/memory_.h"
67#include "MagickCore/list.h"
68#include "MagickCore/monitor.h"
69#include "MagickCore/monitor-private.h"
cristy2c5fc272012-02-22 01:27:46 +000070#include "MagickCore/nt-base-private.h"
cristy4c08aed2011-07-01 19:47:50 +000071#include "MagickCore/pixel-accessor.h"
72#include "MagickCore/quantum.h"
73#include "MagickCore/resource_.h"
74#include "MagickCore/shear.h"
75#include "MagickCore/statistic.h"
76#include "MagickCore/string_.h"
77#include "MagickCore/string-private.h"
78#include "MagickCore/thread-private.h"
79#include "MagickCore/threshold.h"
80#include "MagickCore/transform.h"
cristy3ed852e2009-09-05 21:47:34 +000081
82/*
83%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
84% %
85% %
86% %
cristy3ed852e2009-09-05 21:47:34 +000087+ C r o p T o F i t I m a g e %
88% %
89% %
90% %
91%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
92%
93% CropToFitImage() crops the sheared image as determined by the bounding box
94% as defined by width and height and shearing angles.
95%
96% The format of the CropToFitImage method is:
97%
cristyecc2c142010-01-17 22:25:46 +000098% MagickBooleanType CropToFitImage(Image **image,
cristya19f1d72012-08-07 18:24:38 +000099% const double x_shear,const double x_shear,
100% const double width,const double height,
cristyecc2c142010-01-17 22:25:46 +0000101% const MagickBooleanType rotate,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000102%
103% A description of each parameter follows.
104%
105% o image: the image.
106%
107% o x_shear, y_shear, width, height: Defines a region of the image to crop.
108%
109% o exception: return any errors or warnings in this structure.
110%
111*/
cristyecc2c142010-01-17 22:25:46 +0000112static MagickBooleanType CropToFitImage(Image **image,
cristya19f1d72012-08-07 18:24:38 +0000113 const double x_shear,const double y_shear,
114 const double width,const double height,
cristyecc2c142010-01-17 22:25:46 +0000115 const MagickBooleanType rotate,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000116{
117 Image
118 *crop_image;
119
120 PointInfo
121 extent[4],
122 min,
123 max;
124
125 RectangleInfo
126 geometry,
127 page;
128
cristybb503372010-05-27 20:51:26 +0000129 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000130 i;
131
132 /*
133 Calculate the rotated image size.
134 */
135 extent[0].x=(double) (-width/2.0);
136 extent[0].y=(double) (-height/2.0);
137 extent[1].x=(double) width/2.0;
138 extent[1].y=(double) (-height/2.0);
139 extent[2].x=(double) (-width/2.0);
140 extent[2].y=(double) height/2.0;
141 extent[3].x=(double) width/2.0;
142 extent[3].y=(double) height/2.0;
143 for (i=0; i < 4; i++)
144 {
145 extent[i].x+=x_shear*extent[i].y;
146 extent[i].y+=y_shear*extent[i].x;
147 if (rotate != MagickFalse)
148 extent[i].x+=x_shear*extent[i].y;
149 extent[i].x+=(double) (*image)->columns/2.0;
150 extent[i].y+=(double) (*image)->rows/2.0;
151 }
152 min=extent[0];
153 max=extent[0];
154 for (i=1; i < 4; i++)
155 {
156 if (min.x > extent[i].x)
157 min.x=extent[i].x;
158 if (min.y > extent[i].y)
159 min.y=extent[i].y;
160 if (max.x < extent[i].x)
161 max.x=extent[i].x;
162 if (max.y < extent[i].y)
163 max.y=extent[i].y;
164 }
cristybb503372010-05-27 20:51:26 +0000165 geometry.x=(ssize_t) ceil(min.x-0.5);
166 geometry.y=(ssize_t) ceil(min.y-0.5);
167 geometry.width=(size_t) floor(max.x-min.x+0.5);
168 geometry.height=(size_t) floor(max.y-min.y+0.5);
cristy3ed852e2009-09-05 21:47:34 +0000169 page=(*image)->page;
170 (void) ParseAbsoluteGeometry("0x0+0+0",&(*image)->page);
171 crop_image=CropImage(*image,&geometry,exception);
cristyecc2c142010-01-17 22:25:46 +0000172 if (crop_image == (Image *) NULL)
173 return(MagickFalse);
174 crop_image->page=page;
175 *image=DestroyImage(*image);
176 *image=crop_image;
177 return(MagickTrue);
cristy3ed852e2009-09-05 21:47:34 +0000178}
179
180/*
181%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
182% %
183% %
184% %
185% D e s k e w I m a g e %
186% %
187% %
188% %
189%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
190%
191% DeskewImage() removes skew from the image. Skew is an artifact that
192% occurs in scanned images because of the camera being misaligned,
193% imperfections in the scanning or surface, or simply because the paper was
194% not placed completely flat when scanned.
195%
anthony8a95e802013-04-10 05:31:57 +0000196% The result will be auto-croped if the artifact "deskew:auto-crop" is
197% defined, while the amount the image is to be deskewed, in degrees is also
198% saved as the artifact "deskew:angle".
199%
anthonye92872a2013-04-30 01:49:12 +0000200% If the artifact "deskew:auto-crop" is given the image will be automatically
201% cropped of the excess background. The value is the border width of all
202% pixels around the edge that will be used to determine an average border
203% color for the automatic trim.
anthony8a95e802013-04-10 05:31:57 +0000204%
cristy3ed852e2009-09-05 21:47:34 +0000205% The format of the DeskewImage method is:
206%
207% Image *DeskewImage(const Image *image,const double threshold,
208% ExceptionInfo *exception)
209%
210% A description of each parameter follows:
211%
212% o image: the image.
213%
214% o threshold: separate background from foreground.
215%
216% o exception: return any errors or warnings in this structure.
217%
218*/
219
220typedef struct _RadonInfo
221{
222 CacheType
223 type;
224
cristybb503372010-05-27 20:51:26 +0000225 size_t
cristy3ed852e2009-09-05 21:47:34 +0000226 width,
227 height;
228
229 MagickSizeType
230 length;
231
232 MagickBooleanType
233 mapped;
234
235 char
236 path[MaxTextExtent];
237
238 int
239 file;
240
241 unsigned short
242 *cells;
243} RadonInfo;
244
245static RadonInfo *DestroyRadonInfo(RadonInfo *radon_info)
246{
247 assert(radon_info != (RadonInfo *) NULL);
248 switch (radon_info->type)
249 {
250 case MemoryCache:
251 {
252 if (radon_info->mapped == MagickFalse)
253 radon_info->cells=(unsigned short *) RelinquishMagickMemory(
254 radon_info->cells);
255 else
cristybdbf3f42013-11-15 18:07:59 +0000256 {
257 (void) UnmapBlob(radon_info->cells,(size_t) radon_info->length);
258 radon_info->cells=(unsigned short *) NULL;
259 }
cristy3ed852e2009-09-05 21:47:34 +0000260 RelinquishMagickResource(MemoryResource,radon_info->length);
261 break;
262 }
263 case MapCache:
264 {
cristybdbf3f42013-11-15 18:07:59 +0000265 (void) UnmapBlob(radon_info->cells,(size_t) radon_info->length);
266 radon_info->cells=(unsigned short *) NULL;
cristy3ed852e2009-09-05 21:47:34 +0000267 RelinquishMagickResource(MapResource,radon_info->length);
268 }
269 case DiskCache:
270 {
271 if (radon_info->file != -1)
272 (void) close(radon_info->file);
273 (void) RelinquishUniqueFileResource(radon_info->path);
274 RelinquishMagickResource(DiskResource,radon_info->length);
275 break;
276 }
277 default:
278 break;
279 }
280 return((RadonInfo *) RelinquishMagickMemory(radon_info));
281}
282
283static MagickBooleanType ResetRadonCells(RadonInfo *radon_info)
284{
cristybb503372010-05-27 20:51:26 +0000285 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000286 x;
287
288 ssize_t
cristyd40deb62011-03-09 00:52:27 +0000289 count,
290 y;
cristy3ed852e2009-09-05 21:47:34 +0000291
292 unsigned short
293 value;
294
295 if (radon_info->type != DiskCache)
296 {
297 (void) ResetMagickMemory(radon_info->cells,0,(size_t) radon_info->length);
298 return(MagickTrue);
299 }
300 value=0;
cristy7f317702011-02-18 20:40:28 +0000301 (void) lseek(radon_info->file,0,SEEK_SET);
cristybb503372010-05-27 20:51:26 +0000302 for (y=0; y < (ssize_t) radon_info->height; y++)
cristy3ed852e2009-09-05 21:47:34 +0000303 {
cristybb503372010-05-27 20:51:26 +0000304 for (x=0; x < (ssize_t) radon_info->width; x++)
cristy3ed852e2009-09-05 21:47:34 +0000305 {
306 count=write(radon_info->file,&value,sizeof(*radon_info->cells));
307 if (count != (ssize_t) sizeof(*radon_info->cells))
308 break;
309 }
cristybb503372010-05-27 20:51:26 +0000310 if (x < (ssize_t) radon_info->width)
cristy3ed852e2009-09-05 21:47:34 +0000311 break;
312 }
cristybb503372010-05-27 20:51:26 +0000313 return(y < (ssize_t) radon_info->height ? MagickFalse : MagickTrue);
cristy3ed852e2009-09-05 21:47:34 +0000314}
315
cristybb503372010-05-27 20:51:26 +0000316static RadonInfo *AcquireRadonInfo(const Image *image,const size_t width,
317 const size_t height,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000318{
319 MagickBooleanType
320 status;
321
322 RadonInfo
323 *radon_info;
324
cristy73bd4a52010-10-05 11:24:23 +0000325 radon_info=(RadonInfo *) AcquireMagickMemory(sizeof(*radon_info));
cristy3ed852e2009-09-05 21:47:34 +0000326 if (radon_info == (RadonInfo *) NULL)
327 return((RadonInfo *) NULL);
328 (void) ResetMagickMemory(radon_info,0,sizeof(*radon_info));
329 radon_info->width=width;
330 radon_info->height=height;
331 radon_info->length=(MagickSizeType) width*height*sizeof(*radon_info->cells);
332 radon_info->type=MemoryCache;
333 status=AcquireMagickResource(AreaResource,radon_info->length);
334 if ((status != MagickFalse) &&
335 (radon_info->length == (MagickSizeType) ((size_t) radon_info->length)))
336 {
337 status=AcquireMagickResource(MemoryResource,radon_info->length);
338 if (status != MagickFalse)
339 {
340 radon_info->mapped=MagickFalse;
341 radon_info->cells=(unsigned short *) AcquireMagickMemory((size_t)
342 radon_info->length);
343 if (radon_info->cells == (unsigned short *) NULL)
344 {
345 radon_info->mapped=MagickTrue;
346 radon_info->cells=(unsigned short *) MapBlob(-1,IOMode,0,(size_t)
347 radon_info->length);
348 }
349 if (radon_info->cells == (unsigned short *) NULL)
350 RelinquishMagickResource(MemoryResource,radon_info->length);
351 }
352 }
353 radon_info->file=(-1);
354 if (radon_info->cells == (unsigned short *) NULL)
355 {
356 status=AcquireMagickResource(DiskResource,radon_info->length);
357 if (status == MagickFalse)
358 {
359 (void) ThrowMagickException(exception,GetMagickModule(),CacheError,
cristyefe601c2013-01-05 17:51:12 +0000360 "CacheResourcesExhausted","`%s'",image->filename);
cristy3ed852e2009-09-05 21:47:34 +0000361 return(DestroyRadonInfo(radon_info));
362 }
363 radon_info->type=DiskCache;
364 (void) AcquireMagickResource(MemoryResource,radon_info->length);
365 radon_info->file=AcquireUniqueFileResource(radon_info->path);
366 if (radon_info->file == -1)
367 return(DestroyRadonInfo(radon_info));
368 status=AcquireMagickResource(MapResource,radon_info->length);
369 if (status != MagickFalse)
370 {
371 status=ResetRadonCells(radon_info);
372 if (status != MagickFalse)
373 {
374 radon_info->cells=(unsigned short *) MapBlob(radon_info->file,
375 IOMode,0,(size_t) radon_info->length);
376 if (radon_info->cells != (unsigned short *) NULL)
377 radon_info->type=MapCache;
378 else
379 RelinquishMagickResource(MapResource,radon_info->length);
380 }
381 }
382 }
383 return(radon_info);
384}
385
386static inline size_t MagickMin(const size_t x,const size_t y)
387{
388 if (x < y)
389 return(x);
390 return(y);
391}
392
393static inline ssize_t ReadRadonCell(const RadonInfo *radon_info,
cristy2c38b272011-02-18 23:25:33 +0000394 const MagickOffsetType offset,const size_t length,unsigned char *buffer)
cristy3ed852e2009-09-05 21:47:34 +0000395{
396 register ssize_t
397 i;
398
399 ssize_t
400 count;
401
402#if !defined(MAGICKCORE_HAVE_PPREAD)
cristyb5d5f722009-11-04 03:03:49 +0000403#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +0000404 #pragma omp critical (MagickCore_ReadRadonCell)
405#endif
406 {
407 i=(-1);
cristy7f317702011-02-18 20:40:28 +0000408 if (lseek(radon_info->file,offset,SEEK_SET) >= 0)
cristy3ed852e2009-09-05 21:47:34 +0000409 {
410#endif
411 count=0;
412 for (i=0; i < (ssize_t) length; i+=count)
413 {
414#if !defined(MAGICKCORE_HAVE_PPREAD)
415 count=read(radon_info->file,buffer+i,MagickMin(length-i,(size_t)
416 SSIZE_MAX));
417#else
418 count=pread(radon_info->file,buffer+i,MagickMin(length-i,(size_t)
cristy2c38b272011-02-18 23:25:33 +0000419 SSIZE_MAX),offset+i);
cristy3ed852e2009-09-05 21:47:34 +0000420#endif
421 if (count > 0)
422 continue;
423 count=0;
424 if (errno != EINTR)
425 {
426 i=(-1);
427 break;
428 }
429 }
430#if !defined(MAGICKCORE_HAVE_PPREAD)
431 }
432 }
433#endif
434 return(i);
435}
436
437static inline ssize_t WriteRadonCell(const RadonInfo *radon_info,
cristy2c38b272011-02-18 23:25:33 +0000438 const MagickOffsetType offset,const size_t length,const unsigned char *buffer)
cristy3ed852e2009-09-05 21:47:34 +0000439{
440 register ssize_t
441 i;
442
443 ssize_t
444 count;
445
446#if !defined(MAGICKCORE_HAVE_PWRITE)
cristyb5d5f722009-11-04 03:03:49 +0000447#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +0000448 #pragma omp critical (MagickCore_WriteRadonCell)
449#endif
450 {
cristy7f317702011-02-18 20:40:28 +0000451 if (lseek(radon_info->file,offset,SEEK_SET) >= 0)
cristy3ed852e2009-09-05 21:47:34 +0000452 {
453#endif
454 count=0;
455 for (i=0; i < (ssize_t) length; i+=count)
456 {
457#if !defined(MAGICKCORE_HAVE_PWRITE)
458 count=write(radon_info->file,buffer+i,MagickMin(length-i,(size_t)
459 SSIZE_MAX));
460#else
461 count=pwrite(radon_info->file,buffer+i,MagickMin(length-i,(size_t)
cristy2c38b272011-02-18 23:25:33 +0000462 SSIZE_MAX),offset+i);
cristy3ed852e2009-09-05 21:47:34 +0000463#endif
464 if (count > 0)
465 continue;
466 count=0;
467 if (errno != EINTR)
468 {
469 i=(-1);
470 break;
471 }
472 }
473#if !defined(MAGICKCORE_HAVE_PWRITE)
474 }
475 }
476#endif
477 return(i);
478}
479
480static inline unsigned short GetRadonCell(const RadonInfo *radon_info,
cristybb503372010-05-27 20:51:26 +0000481 const ssize_t x,const ssize_t y)
cristy3ed852e2009-09-05 21:47:34 +0000482{
cristy2c38b272011-02-18 23:25:33 +0000483 MagickOffsetType
cristy3ed852e2009-09-05 21:47:34 +0000484 i;
485
486 unsigned short
487 value;
488
cristy2c38b272011-02-18 23:25:33 +0000489 i=(MagickOffsetType) radon_info->height*x+y;
cristy3ed852e2009-09-05 21:47:34 +0000490 if ((i < 0) ||
491 ((MagickSizeType) (i*sizeof(*radon_info->cells)) >= radon_info->length))
492 return(0);
493 if (radon_info->type != DiskCache)
494 return(radon_info->cells[i]);
495 value=0;
496 (void) ReadRadonCell(radon_info,i*sizeof(*radon_info->cells),
497 sizeof(*radon_info->cells),(unsigned char *) &value);
498 return(value);
499}
500
501static inline MagickBooleanType SetRadonCell(const RadonInfo *radon_info,
cristybb503372010-05-27 20:51:26 +0000502 const ssize_t x,const ssize_t y,const unsigned short value)
cristy3ed852e2009-09-05 21:47:34 +0000503{
cristy2c38b272011-02-18 23:25:33 +0000504 MagickOffsetType
cristy3ed852e2009-09-05 21:47:34 +0000505 i;
506
507 ssize_t
508 count;
509
cristy2c38b272011-02-18 23:25:33 +0000510 i=(MagickOffsetType) radon_info->height*x+y;
cristy3ed852e2009-09-05 21:47:34 +0000511 if ((i < 0) ||
512 ((MagickSizeType) (i*sizeof(*radon_info->cells)) >= radon_info->length))
513 return(MagickFalse);
514 if (radon_info->type != DiskCache)
515 {
516 radon_info->cells[i]=value;
517 return(MagickTrue);
518 }
519 count=WriteRadonCell(radon_info,i*sizeof(*radon_info->cells),
cristy44807bb2009-09-19 18:28:06 +0000520 sizeof(*radon_info->cells),(const unsigned char *) &value);
cristy3ed852e2009-09-05 21:47:34 +0000521 if (count != (ssize_t) sizeof(*radon_info->cells))
522 return(MagickFalse);
523 return(MagickTrue);
524}
525
cristy4ee2b0c2012-05-15 00:30:35 +0000526static void RadonProjection(const Image *image,RadonInfo *source_cells,
cristybb503372010-05-27 20:51:26 +0000527 RadonInfo *destination_cells,const ssize_t sign,size_t *projection)
cristy3ed852e2009-09-05 21:47:34 +0000528{
529 RadonInfo
530 *swap;
531
cristybb503372010-05-27 20:51:26 +0000532 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000533 x;
534
535 register RadonInfo
536 *p,
537 *q;
538
cristybb503372010-05-27 20:51:26 +0000539 size_t
cristy3ed852e2009-09-05 21:47:34 +0000540 step;
541
cristyb0de93f2013-05-03 13:39:25 +0000542 assert(image != (Image *) NULL);
cristy3ed852e2009-09-05 21:47:34 +0000543 p=source_cells;
544 q=destination_cells;
545 for (step=1; step < p->width; step*=2)
546 {
cristyeaedf062010-05-29 22:36:02 +0000547 for (x=0; x < (ssize_t) p->width; x+=2*(ssize_t) step)
cristy3ed852e2009-09-05 21:47:34 +0000548 {
cristybb503372010-05-27 20:51:26 +0000549 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000550 i;
551
cristyd40deb62011-03-09 00:52:27 +0000552 ssize_t
553 y;
554
cristy3ed852e2009-09-05 21:47:34 +0000555 unsigned short
556 cell;
557
cristybb503372010-05-27 20:51:26 +0000558 for (i=0; i < (ssize_t) step; i++)
cristy3ed852e2009-09-05 21:47:34 +0000559 {
cristybb503372010-05-27 20:51:26 +0000560 for (y=0; y < (ssize_t) (p->height-i-1); y++)
cristy3ed852e2009-09-05 21:47:34 +0000561 {
562 cell=GetRadonCell(p,x+i,y);
cristyeaedf062010-05-29 22:36:02 +0000563 (void) SetRadonCell(q,x+2*i,y,cell+GetRadonCell(p,x+i+(ssize_t)
564 step,y+i));
565 (void) SetRadonCell(q,x+2*i+1,y,cell+GetRadonCell(p,x+i+(ssize_t)
566 step,y+i+1));
cristy3ed852e2009-09-05 21:47:34 +0000567 }
cristybb503372010-05-27 20:51:26 +0000568 for ( ; y < (ssize_t) (p->height-i); y++)
cristy3ed852e2009-09-05 21:47:34 +0000569 {
570 cell=GetRadonCell(p,x+i,y);
cristyeaedf062010-05-29 22:36:02 +0000571 (void) SetRadonCell(q,x+2*i,y,cell+GetRadonCell(p,x+i+(ssize_t) step,
572 y+i));
cristy3ed852e2009-09-05 21:47:34 +0000573 (void) SetRadonCell(q,x+2*i+1,y,cell);
574 }
cristybb503372010-05-27 20:51:26 +0000575 for ( ; y < (ssize_t) p->height; y++)
cristy3ed852e2009-09-05 21:47:34 +0000576 {
577 cell=GetRadonCell(p,x+i,y);
578 (void) SetRadonCell(q,x+2*i,y,cell);
579 (void) SetRadonCell(q,x+2*i+1,y,cell);
580 }
581 }
582 }
583 swap=p;
584 p=q;
585 q=swap;
586 }
cristyb5d5f722009-11-04 03:03:49 +0000587#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +0000588 #pragma omp parallel for schedule(static,4) \
cristycb7dfcc2013-01-06 18:34:59 +0000589 magick_threads(image,image,1,1)
cristy3ed852e2009-09-05 21:47:34 +0000590#endif
cristybb503372010-05-27 20:51:26 +0000591 for (x=0; x < (ssize_t) p->width; x++)
cristy3ed852e2009-09-05 21:47:34 +0000592 {
cristybb503372010-05-27 20:51:26 +0000593 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000594 y;
595
cristybb503372010-05-27 20:51:26 +0000596 size_t
cristy3ed852e2009-09-05 21:47:34 +0000597 sum;
598
599 sum=0;
cristybb503372010-05-27 20:51:26 +0000600 for (y=0; y < (ssize_t) (p->height-1); y++)
cristy3ed852e2009-09-05 21:47:34 +0000601 {
cristybb503372010-05-27 20:51:26 +0000602 ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000603 delta;
604
cristybb503372010-05-27 20:51:26 +0000605 delta=GetRadonCell(p,x,y)-(ssize_t) GetRadonCell(p,x,y+1);
cristy3ed852e2009-09-05 21:47:34 +0000606 sum+=delta*delta;
607 }
608 projection[p->width+sign*x-1]=sum;
609 }
610}
611
612static MagickBooleanType RadonTransform(const Image *image,
cristybb503372010-05-27 20:51:26 +0000613 const double threshold,size_t *projection,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000614{
615 CacheView
616 *image_view;
617
cristy3ed852e2009-09-05 21:47:34 +0000618 MagickBooleanType
619 status;
620
621 RadonInfo
622 *destination_cells,
623 *source_cells;
624
cristybb503372010-05-27 20:51:26 +0000625 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000626 i;
627
cristybb503372010-05-27 20:51:26 +0000628 size_t
cristy3ed852e2009-09-05 21:47:34 +0000629 count,
630 width;
631
cristyd40deb62011-03-09 00:52:27 +0000632 ssize_t
633 y;
634
635 unsigned char
636 byte;
637
cristy3ed852e2009-09-05 21:47:34 +0000638 unsigned short
639 bits[256];
640
641 for (width=1; width < ((image->columns+7)/8); width<<=1) ;
642 source_cells=AcquireRadonInfo(image,width,image->rows,exception);
643 destination_cells=AcquireRadonInfo(image,width,image->rows,exception);
644 if ((source_cells == (RadonInfo *) NULL) ||
645 (destination_cells == (RadonInfo *) NULL))
646 {
647 if (destination_cells != (RadonInfo *) NULL)
648 destination_cells=DestroyRadonInfo(destination_cells);
649 if (source_cells != (RadonInfo *) NULL)
650 source_cells=DestroyRadonInfo(source_cells);
651 return(MagickFalse);
652 }
653 if (ResetRadonCells(source_cells) == MagickFalse)
654 {
655 destination_cells=DestroyRadonInfo(destination_cells);
656 source_cells=DestroyRadonInfo(source_cells);
657 return(MagickFalse);
658 }
659 for (i=0; i < 256; i++)
660 {
661 byte=(unsigned char) i;
662 for (count=0; byte != 0; byte>>=1)
663 count+=byte & 0x01;
664 bits[i]=(unsigned short) count;
665 }
666 status=MagickTrue;
cristy46ff2672012-12-14 15:32:26 +0000667 image_view=AcquireVirtualCacheView(image,exception);
cristyb5d5f722009-11-04 03:03:49 +0000668#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +0000669 #pragma omp parallel for schedule(static,4) shared(status) \
cristy5e6b2592012-12-19 14:08:11 +0000670 magick_threads(image,image,image->rows,1)
cristy3ed852e2009-09-05 21:47:34 +0000671#endif
cristybb503372010-05-27 20:51:26 +0000672 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000673 {
cristy4c08aed2011-07-01 19:47:50 +0000674 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +0000675 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +0000676
cristybb503372010-05-27 20:51:26 +0000677 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000678 i,
679 x;
680
cristybb503372010-05-27 20:51:26 +0000681 size_t
cristy3ed852e2009-09-05 21:47:34 +0000682 bit,
683 byte;
684
685 if (status == MagickFalse)
686 continue;
687 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +0000688 if (p == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +0000689 {
690 status=MagickFalse;
691 continue;
692 }
693 bit=0;
694 byte=0;
cristybb503372010-05-27 20:51:26 +0000695 i=(ssize_t) (image->columns+7)/8;
696 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000697 {
698 byte<<=1;
cristyf13c5942012-08-08 23:50:11 +0000699 if (GetPixelIntensity(image,p) < threshold)
cristy3ed852e2009-09-05 21:47:34 +0000700 byte|=0x01;
701 bit++;
702 if (bit == 8)
703 {
704 (void) SetRadonCell(source_cells,--i,y,bits[byte]);
705 bit=0;
706 byte=0;
707 }
cristyed231572011-07-14 02:18:59 +0000708 p+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +0000709 }
710 if (bit != 0)
711 {
712 byte<<=(8-bit);
713 (void) SetRadonCell(source_cells,--i,y,bits[byte]);
714 }
715 }
cristy4ee2b0c2012-05-15 00:30:35 +0000716 RadonProjection(image,source_cells,destination_cells,-1,projection);
cristy3ed852e2009-09-05 21:47:34 +0000717 (void) ResetRadonCells(source_cells);
cristyb5d5f722009-11-04 03:03:49 +0000718#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +0000719 #pragma omp parallel for schedule(static,4) shared(status) \
cristycb7dfcc2013-01-06 18:34:59 +0000720 magick_threads(image,image,1,1)
cristy3ed852e2009-09-05 21:47:34 +0000721#endif
cristybb503372010-05-27 20:51:26 +0000722 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000723 {
cristy4c08aed2011-07-01 19:47:50 +0000724 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +0000725 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +0000726
cristybb503372010-05-27 20:51:26 +0000727 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000728 i,
729 x;
730
cristybb503372010-05-27 20:51:26 +0000731 size_t
cristy3ed852e2009-09-05 21:47:34 +0000732 bit,
733 byte;
734
735 if (status == MagickFalse)
736 continue;
737 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +0000738 if (p == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +0000739 {
740 status=MagickFalse;
741 continue;
742 }
743 bit=0;
744 byte=0;
745 i=0;
cristybb503372010-05-27 20:51:26 +0000746 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000747 {
748 byte<<=1;
cristyf13c5942012-08-08 23:50:11 +0000749 if (GetPixelIntensity(image,p) < threshold)
cristy3ed852e2009-09-05 21:47:34 +0000750 byte|=0x01;
751 bit++;
752 if (bit == 8)
753 {
754 (void) SetRadonCell(source_cells,i++,y,bits[byte]);
755 bit=0;
756 byte=0;
757 }
cristyed231572011-07-14 02:18:59 +0000758 p+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +0000759 }
760 if (bit != 0)
761 {
762 byte<<=(8-bit);
763 (void) SetRadonCell(source_cells,i++,y,bits[byte]);
764 }
765 }
cristy4ee2b0c2012-05-15 00:30:35 +0000766 RadonProjection(image,source_cells,destination_cells,1,projection);
cristy3ed852e2009-09-05 21:47:34 +0000767 image_view=DestroyCacheView(image_view);
768 destination_cells=DestroyRadonInfo(destination_cells);
769 source_cells=DestroyRadonInfo(source_cells);
770 return(MagickTrue);
771}
772
cristybb503372010-05-27 20:51:26 +0000773static void GetImageBackgroundColor(Image *image,const ssize_t offset,
cristy3ed852e2009-09-05 21:47:34 +0000774 ExceptionInfo *exception)
775{
776 CacheView
777 *image_view;
778
cristy4c08aed2011-07-01 19:47:50 +0000779 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +0000780 background;
781
cristya19f1d72012-08-07 18:24:38 +0000782 double
cristy3ed852e2009-09-05 21:47:34 +0000783 count;
784
cristyd40deb62011-03-09 00:52:27 +0000785 ssize_t
786 y;
787
cristy3ed852e2009-09-05 21:47:34 +0000788 /*
789 Compute average background color.
790 */
791 if (offset <= 0)
792 return;
cristy4c08aed2011-07-01 19:47:50 +0000793 GetPixelInfo(image,&background);
cristy3ed852e2009-09-05 21:47:34 +0000794 count=0.0;
cristy46ff2672012-12-14 15:32:26 +0000795 image_view=AcquireVirtualCacheView(image,exception);
cristybb503372010-05-27 20:51:26 +0000796 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000797 {
cristy4c08aed2011-07-01 19:47:50 +0000798 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +0000799 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +0000800
cristybb503372010-05-27 20:51:26 +0000801 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000802 x;
803
cristybb503372010-05-27 20:51:26 +0000804 if ((y >= offset) && (y < ((ssize_t) image->rows-offset)))
cristy3ed852e2009-09-05 21:47:34 +0000805 continue;
806 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +0000807 if (p == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +0000808 continue;
cristybb503372010-05-27 20:51:26 +0000809 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000810 {
cristybb503372010-05-27 20:51:26 +0000811 if ((x >= offset) && (x < ((ssize_t) image->columns-offset)))
cristy3ed852e2009-09-05 21:47:34 +0000812 continue;
cristy4c08aed2011-07-01 19:47:50 +0000813 background.red+=QuantumScale*GetPixelRed(image,p);
814 background.green+=QuantumScale*GetPixelGreen(image,p);
815 background.blue+=QuantumScale*GetPixelBlue(image,p);
cristy26295322011-09-22 00:50:36 +0000816 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
817 background.alpha+=QuantumScale*GetPixelAlpha(image,p);
cristy3ed852e2009-09-05 21:47:34 +0000818 count++;
cristyed231572011-07-14 02:18:59 +0000819 p+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +0000820 }
821 }
822 image_view=DestroyCacheView(image_view);
cristy8cd03c32012-07-07 18:57:59 +0000823 image->background_color.red=(double) ClampToQuantum(QuantumRange*
824 background.red/count);
825 image->background_color.green=(double) ClampToQuantum(QuantumRange*
826 background.green/count);
827 image->background_color.blue=(double) ClampToQuantum(QuantumRange*
828 background.blue/count);
cristy26295322011-09-22 00:50:36 +0000829 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy8cd03c32012-07-07 18:57:59 +0000830 image->background_color.alpha=(double) ClampToQuantum(QuantumRange*
831 background.alpha/count);
cristy3ed852e2009-09-05 21:47:34 +0000832}
833
834MagickExport Image *DeskewImage(const Image *image,const double threshold,
835 ExceptionInfo *exception)
836{
837 AffineMatrix
838 affine_matrix;
839
840 const char
841 *artifact;
842
843 double
844 degrees;
845
846 Image
847 *clone_image,
848 *crop_image,
849 *deskew_image,
850 *median_image;
851
cristy3ed852e2009-09-05 21:47:34 +0000852 MagickBooleanType
853 status;
854
855 RectangleInfo
856 geometry;
857
cristybb503372010-05-27 20:51:26 +0000858 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000859 i;
860
cristybb503372010-05-27 20:51:26 +0000861 size_t
cristy3ed852e2009-09-05 21:47:34 +0000862 max_projection,
863 *projection,
864 width;
865
cristyd40deb62011-03-09 00:52:27 +0000866 ssize_t
867 skew;
868
cristy3ed852e2009-09-05 21:47:34 +0000869 /*
870 Compute deskew angle.
871 */
872 for (width=1; width < ((image->columns+7)/8); width<<=1) ;
cristybb503372010-05-27 20:51:26 +0000873 projection=(size_t *) AcquireQuantumMemory((size_t) (2*width-1),
cristy3ed852e2009-09-05 21:47:34 +0000874 sizeof(*projection));
cristybb503372010-05-27 20:51:26 +0000875 if (projection == (size_t *) NULL)
cristy3ed852e2009-09-05 21:47:34 +0000876 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
877 status=RadonTransform(image,threshold,projection,exception);
878 if (status == MagickFalse)
879 {
cristybb503372010-05-27 20:51:26 +0000880 projection=(size_t *) RelinquishMagickMemory(projection);
cristy3ed852e2009-09-05 21:47:34 +0000881 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
882 }
883 max_projection=0;
884 skew=0;
cristybb503372010-05-27 20:51:26 +0000885 for (i=0; i < (ssize_t) (2*width-1); i++)
cristy3ed852e2009-09-05 21:47:34 +0000886 {
887 if (projection[i] > max_projection)
888 {
cristybb503372010-05-27 20:51:26 +0000889 skew=i-(ssize_t) width+1;
cristy3ed852e2009-09-05 21:47:34 +0000890 max_projection=projection[i];
891 }
892 }
cristybb503372010-05-27 20:51:26 +0000893 projection=(size_t *) RelinquishMagickMemory(projection);
anthony553bfcc2013-04-10 01:27:43 +0000894 degrees=RadiansToDegrees(-atan((double) skew/width/8));
895 if (image->debug != MagickFalse)
896 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
897 " Deskew angle: %g",degrees);
cristy3ed852e2009-09-05 21:47:34 +0000898 /*
899 Deskew image.
900 */
901 clone_image=CloneImage(image,0,0,MagickTrue,exception);
902 if (clone_image == (Image *) NULL)
903 return((Image *) NULL);
anthony8a95e802013-04-10 05:31:57 +0000904 {
905 char
906 angle[MaxTextExtent];
907
908 (void) FormatLocaleString(angle,MaxTextExtent,"%.20g",degrees);
909 (void) SetImageArtifact(clone_image,"deskew:angle",angle);
910 }
cristy387430f2012-02-07 13:09:46 +0000911 (void) SetImageVirtualPixelMethod(clone_image,BackgroundVirtualPixelMethod,
912 exception);
cristy3ed852e2009-09-05 21:47:34 +0000913 affine_matrix.sx=cos(DegreesToRadians(fmod((double) degrees,360.0)));
914 affine_matrix.rx=sin(DegreesToRadians(fmod((double) degrees,360.0)));
915 affine_matrix.ry=(-sin(DegreesToRadians(fmod((double) degrees,360.0))));
916 affine_matrix.sy=cos(DegreesToRadians(fmod((double) degrees,360.0)));
917 affine_matrix.tx=0.0;
918 affine_matrix.ty=0.0;
919 artifact=GetImageArtifact(image,"deskew:auto-crop");
920 if (artifact == (const char *) NULL)
921 {
922 deskew_image=AffineTransformImage(clone_image,&affine_matrix,exception);
923 clone_image=DestroyImage(clone_image);
924 return(deskew_image);
925 }
926 /*
927 Auto-crop image.
928 */
cristyba978e12010-09-12 20:26:50 +0000929 GetImageBackgroundColor(clone_image,(ssize_t) StringToLong(artifact),
930 exception);
cristy3ed852e2009-09-05 21:47:34 +0000931 deskew_image=AffineTransformImage(clone_image,&affine_matrix,exception);
932 clone_image=DestroyImage(clone_image);
933 if (deskew_image == (Image *) NULL)
934 return((Image *) NULL);
cristy95c38342011-03-18 22:39:51 +0000935 median_image=StatisticImage(deskew_image,MedianStatistic,3,3,exception);
cristy3ed852e2009-09-05 21:47:34 +0000936 if (median_image == (Image *) NULL)
937 {
938 deskew_image=DestroyImage(deskew_image);
939 return((Image *) NULL);
940 }
941 geometry=GetImageBoundingBox(median_image,exception);
942 median_image=DestroyImage(median_image);
943 if (image->debug != MagickFalse)
944 (void) LogMagickEvent(TransformEvent,GetMagickModule()," Deskew geometry: "
cristy6d8abba2010-06-03 01:10:47 +0000945 "%.20gx%.20g%+.20g%+.20g",(double) geometry.width,(double)
cristye8c25f92010-06-03 00:53:06 +0000946 geometry.height,(double) geometry.x,(double) geometry.y);
cristy3ed852e2009-09-05 21:47:34 +0000947 crop_image=CropImage(deskew_image,&geometry,exception);
948 deskew_image=DestroyImage(deskew_image);
949 return(crop_image);
950}
951
952/*
953%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
954% %
955% %
956% %
cristy987feef2011-11-17 12:24:56 +0000957% I n t e g r a l R o t a t e I m a g e %
cristy3ed852e2009-09-05 21:47:34 +0000958% %
959% %
960% %
961%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
962%
cristy3dfa16c2010-05-17 19:34:48 +0000963% IntegralRotateImage() rotates the image an integral of 90 degrees. It
cristy3ed852e2009-09-05 21:47:34 +0000964% allocates the memory necessary for the new Image structure and returns a
965% pointer to the rotated image.
966%
967% The format of the IntegralRotateImage method is:
968%
cristybb503372010-05-27 20:51:26 +0000969% Image *IntegralRotateImage(const Image *image,size_t rotations,
cristy3ed852e2009-09-05 21:47:34 +0000970% ExceptionInfo *exception)
971%
972% A description of each parameter follows.
973%
974% o image: the image.
975%
976% o rotations: Specifies the number of 90 degree rotations.
977%
978*/
cristy987feef2011-11-17 12:24:56 +0000979MagickExport Image *IntegralRotateImage(const Image *image,size_t rotations,
cristy3ed852e2009-09-05 21:47:34 +0000980 ExceptionInfo *exception)
981{
cristy3ed852e2009-09-05 21:47:34 +0000982#define RotateImageTag "Rotate/Image"
983
984 CacheView
985 *image_view,
986 *rotate_view;
987
988 Image
989 *rotate_image;
990
cristy3ed852e2009-09-05 21:47:34 +0000991 MagickBooleanType
992 status;
993
cristy5f959472010-05-27 22:19:46 +0000994 MagickOffsetType
995 progress;
996
cristy3ed852e2009-09-05 21:47:34 +0000997 RectangleInfo
998 page;
999
cristy5f959472010-05-27 22:19:46 +00001000 ssize_t
1001 y;
1002
cristy3ed852e2009-09-05 21:47:34 +00001003 /*
1004 Initialize rotated image attributes.
1005 */
1006 assert(image != (Image *) NULL);
1007 page=image->page;
1008 rotations%=4;
cristyb32b90a2009-09-07 21:45:48 +00001009 if (rotations == 0)
1010 return(CloneImage(image,0,0,MagickTrue,exception));
cristy3ed852e2009-09-05 21:47:34 +00001011 if ((rotations == 1) || (rotations == 3))
1012 rotate_image=CloneImage(image,image->rows,image->columns,MagickTrue,
1013 exception);
1014 else
1015 rotate_image=CloneImage(image,image->columns,image->rows,MagickTrue,
1016 exception);
1017 if (rotate_image == (Image *) NULL)
1018 return((Image *) NULL);
1019 /*
1020 Integral rotate the image.
1021 */
1022 status=MagickTrue;
1023 progress=0;
cristy46ff2672012-12-14 15:32:26 +00001024 image_view=AcquireVirtualCacheView(image,exception);
1025 rotate_view=AcquireAuthenticCacheView(rotate_image,exception);
cristy3ed852e2009-09-05 21:47:34 +00001026 switch (rotations)
1027 {
1028 case 0:
1029 {
1030 /*
1031 Rotate 0 degrees.
1032 */
cristy3ed852e2009-09-05 21:47:34 +00001033 break;
1034 }
1035 case 1:
1036 {
cristybb503372010-05-27 20:51:26 +00001037 size_t
cristyb32b90a2009-09-07 21:45:48 +00001038 tile_height,
1039 tile_width;
1040
cristyd40deb62011-03-09 00:52:27 +00001041 ssize_t
1042 tile_y;
1043
cristy3ed852e2009-09-05 21:47:34 +00001044 /*
1045 Rotate 90 degrees.
1046 */
cristyb32b90a2009-09-07 21:45:48 +00001047 GetPixelCacheTileSize(image,&tile_width,&tile_height);
cristy8b8148b2012-10-07 00:41:15 +00001048 tile_width=image->columns;
cristy26b64912012-12-16 18:20:09 +00001049#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy64c10cd2013-01-06 18:40:43 +00001050 #pragma omp parallel for schedule(static,4) shared(status) \
cristy3e1fa372013-01-06 18:07:11 +00001051 magick_threads(image,image,1,1)
cristy9a5a52f2012-10-09 14:40:31 +00001052#endif
cristyad11aac2011-09-25 21:29:16 +00001053 for (tile_y=0; tile_y < (ssize_t) image->rows; tile_y+=(ssize_t) tile_height)
cristy3ed852e2009-09-05 21:47:34 +00001054 {
cristybb503372010-05-27 20:51:26 +00001055 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001056 tile_x;
1057
1058 if (status == MagickFalse)
1059 continue;
cristy26295322011-09-22 00:50:36 +00001060 tile_x=0;
1061 for ( ; tile_x < (ssize_t) image->columns; tile_x+=(ssize_t) tile_width)
cristy3ed852e2009-09-05 21:47:34 +00001062 {
1063 MagickBooleanType
1064 sync;
1065
cristy4c08aed2011-07-01 19:47:50 +00001066 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00001067 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00001068
cristy4c08aed2011-07-01 19:47:50 +00001069 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00001070 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001071
cristya26211e2011-10-09 01:24:57 +00001072 register ssize_t
1073 y;
1074
cristybb503372010-05-27 20:51:26 +00001075 size_t
cristyb32b90a2009-09-07 21:45:48 +00001076 height,
1077 width;
cristy3ed852e2009-09-05 21:47:34 +00001078
cristyb32b90a2009-09-07 21:45:48 +00001079 width=tile_width;
cristybb503372010-05-27 20:51:26 +00001080 if ((tile_x+(ssize_t) tile_width) > (ssize_t) image->columns)
cristya45d2ec2011-08-24 13:17:19 +00001081 width=(size_t) (tile_width-(tile_x+tile_width-image->columns));
cristyb32b90a2009-09-07 21:45:48 +00001082 height=tile_height;
cristybb503372010-05-27 20:51:26 +00001083 if ((tile_y+(ssize_t) tile_height) > (ssize_t) image->rows)
cristya45d2ec2011-08-24 13:17:19 +00001084 height=(size_t) (tile_height-(tile_y+tile_height-image->rows));
cristydaa97692009-09-13 02:10:35 +00001085 p=GetCacheViewVirtualPixels(image_view,tile_x,tile_y,width,height,
1086 exception);
cristy4c08aed2011-07-01 19:47:50 +00001087 if (p == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001088 {
1089 status=MagickFalse;
1090 break;
1091 }
cristybb503372010-05-27 20:51:26 +00001092 for (y=0; y < (ssize_t) width; y++)
cristy3ed852e2009-09-05 21:47:34 +00001093 {
cristy4c08aed2011-07-01 19:47:50 +00001094 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00001095 *restrict tile_pixels;
cristy3ed852e2009-09-05 21:47:34 +00001096
cristybb503372010-05-27 20:51:26 +00001097 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001098 x;
1099
cristy27709ef2011-09-18 02:53:53 +00001100 if (status == MagickFalse)
1101 continue;
cristybb503372010-05-27 20:51:26 +00001102 q=QueueCacheViewAuthenticPixels(rotate_view,(ssize_t)
cristy92611572011-07-07 16:02:42 +00001103 (rotate_image->columns-(tile_y+height)),y+tile_x,height,1,
1104 exception);
cristyacd2ed22011-08-30 01:44:23 +00001105 if (q == (Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001106 {
1107 status=MagickFalse;
cristy27709ef2011-09-18 02:53:53 +00001108 continue;
cristy3ed852e2009-09-05 21:47:34 +00001109 }
cristyed231572011-07-14 02:18:59 +00001110 tile_pixels=p+((height-1)*width+y)*GetPixelChannels(image);
cristybb503372010-05-27 20:51:26 +00001111 for (x=0; x < (ssize_t) height; x++)
cristy3ed852e2009-09-05 21:47:34 +00001112 {
cristya45d2ec2011-08-24 13:17:19 +00001113 register ssize_t
1114 i;
1115
cristy883fde12013-04-08 00:50:13 +00001116 if (GetPixelReadMask(image,tile_pixels) == 0)
cristy10a6c612012-01-29 21:41:05 +00001117 {
1118 tile_pixels-=width*GetPixelChannels(image);
1119 q+=GetPixelChannels(rotate_image);
1120 continue;
1121 }
cristya45d2ec2011-08-24 13:17:19 +00001122 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1123 {
cristy5a23c552013-02-13 14:34:28 +00001124 PixelChannel channel=GetPixelChannelChannel(image,i);
1125 PixelTrait traits=GetPixelChannelTraits(image,channel);
1126 PixelTrait rotate_traits=GetPixelChannelTraits(rotate_image,
1127 channel);
cristy010d7d12011-08-31 01:02:48 +00001128 if ((traits == UndefinedPixelTrait) ||
1129 (rotate_traits == UndefinedPixelTrait))
cristya45d2ec2011-08-24 13:17:19 +00001130 continue;
cristy0beccfa2011-09-25 20:47:53 +00001131 SetPixelChannel(rotate_image,channel,tile_pixels[i],q);
cristya45d2ec2011-08-24 13:17:19 +00001132 }
cristyed231572011-07-14 02:18:59 +00001133 tile_pixels-=width*GetPixelChannels(image);
1134 q+=GetPixelChannels(rotate_image);
cristy3ed852e2009-09-05 21:47:34 +00001135 }
cristy3ed852e2009-09-05 21:47:34 +00001136 sync=SyncCacheViewAuthenticPixels(rotate_view,exception);
1137 if (sync == MagickFalse)
1138 status=MagickFalse;
1139 }
1140 }
1141 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1142 {
1143 MagickBooleanType
1144 proceed;
1145
cristy26b64912012-12-16 18:20:09 +00001146#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy9a5a52f2012-10-09 14:40:31 +00001147 #pragma omp critical (MagickCore_IntegralRotateImage)
1148#endif
cristyb32b90a2009-09-07 21:45:48 +00001149 proceed=SetImageProgress(image,RotateImageTag,progress+=tile_height,
cristy3ed852e2009-09-05 21:47:34 +00001150 image->rows);
1151 if (proceed == MagickFalse)
1152 status=MagickFalse;
1153 }
1154 }
cristyecc2c142010-01-17 22:25:46 +00001155 (void) SetImageProgress(image,RotateImageTag,(MagickOffsetType)
1156 image->rows-1,image->rows);
cristy3ed852e2009-09-05 21:47:34 +00001157 Swap(page.width,page.height);
1158 Swap(page.x,page.y);
1159 if (page.width != 0)
cristybb503372010-05-27 20:51:26 +00001160 page.x=(ssize_t) (page.width-rotate_image->columns-page.x);
cristy3ed852e2009-09-05 21:47:34 +00001161 break;
1162 }
1163 case 2:
1164 {
1165 /*
1166 Rotate 180 degrees.
1167 */
cristy5f890612012-12-16 23:34:05 +00001168#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy64c10cd2013-01-06 18:40:43 +00001169 #pragma omp parallel for schedule(static,4) shared(status) \
cristyd6432472013-01-06 16:56:13 +00001170 magick_threads(image,image,1,1)
cristy5f890612012-12-16 23:34:05 +00001171#endif
cristybb503372010-05-27 20:51:26 +00001172 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001173 {
1174 MagickBooleanType
1175 sync;
1176
cristy4c08aed2011-07-01 19:47:50 +00001177 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00001178 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00001179
cristy4c08aed2011-07-01 19:47:50 +00001180 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00001181 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001182
cristy2be50602011-10-09 01:28:13 +00001183 register ssize_t
1184 x;
1185
cristy3ed852e2009-09-05 21:47:34 +00001186 if (status == MagickFalse)
1187 continue;
cristya45d2ec2011-08-24 13:17:19 +00001188 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
1189 q=QueueCacheViewAuthenticPixels(rotate_view,0,(ssize_t) (image->rows-y-
1190 1),image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00001191 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00001192 {
1193 status=MagickFalse;
1194 continue;
1195 }
cristyed231572011-07-14 02:18:59 +00001196 q+=GetPixelChannels(rotate_image)*image->columns;
cristybb503372010-05-27 20:51:26 +00001197 for (x=0; x < (ssize_t) image->columns; x++)
cristy4c08aed2011-07-01 19:47:50 +00001198 {
cristya45d2ec2011-08-24 13:17:19 +00001199 register ssize_t
1200 i;
1201
cristyed231572011-07-14 02:18:59 +00001202 q-=GetPixelChannels(rotate_image);
cristy883fde12013-04-08 00:50:13 +00001203 if (GetPixelReadMask(image,p) == 0)
cristy10a6c612012-01-29 21:41:05 +00001204 {
1205 p+=GetPixelChannels(image);
1206 continue;
1207 }
cristya45d2ec2011-08-24 13:17:19 +00001208 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1209 {
cristy5a23c552013-02-13 14:34:28 +00001210 PixelChannel channel=GetPixelChannelChannel(image,i);
1211 PixelTrait traits=GetPixelChannelTraits(image,channel);
1212 PixelTrait rotate_traits=GetPixelChannelTraits(rotate_image,
1213 channel);
cristy010d7d12011-08-31 01:02:48 +00001214 if ((traits == UndefinedPixelTrait) ||
1215 (rotate_traits == UndefinedPixelTrait))
cristya45d2ec2011-08-24 13:17:19 +00001216 continue;
cristy0beccfa2011-09-25 20:47:53 +00001217 SetPixelChannel(rotate_image,channel,p[i],q);
cristya45d2ec2011-08-24 13:17:19 +00001218 }
cristyed231572011-07-14 02:18:59 +00001219 p+=GetPixelChannels(image);
cristy4c08aed2011-07-01 19:47:50 +00001220 }
cristy3ed852e2009-09-05 21:47:34 +00001221 sync=SyncCacheViewAuthenticPixels(rotate_view,exception);
1222 if (sync == MagickFalse)
1223 status=MagickFalse;
1224 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1225 {
1226 MagickBooleanType
1227 proceed;
1228
cristy5f890612012-12-16 23:34:05 +00001229#if defined(MAGICKCORE_OPENMP_SUPPORT)
1230 #pragma omp critical (MagickCore_IntegralRotateImage)
1231#endif
cristy0729beb2011-09-25 23:29:32 +00001232 proceed=SetImageProgress(image,RotateImageTag,progress++,
cristy21e03412011-09-25 22:39:35 +00001233 image->rows);
1234 if (proceed == MagickFalse)
1235 status=MagickFalse;
1236 }
1237 }
1238 (void) SetImageProgress(image,RotateImageTag,(MagickOffsetType)
1239 image->rows-1,image->rows);
1240 Swap(page.width,page.height);
1241 Swap(page.x,page.y);
1242 if (page.width != 0)
1243 page.x=(ssize_t) (page.width-rotate_image->columns-page.x);
1244 break;
1245 }
cristy3ed852e2009-09-05 21:47:34 +00001246 case 3:
1247 {
cristybb503372010-05-27 20:51:26 +00001248 size_t
cristyb32b90a2009-09-07 21:45:48 +00001249 tile_height,
1250 tile_width;
1251
cristyd40deb62011-03-09 00:52:27 +00001252 ssize_t
1253 tile_y;
1254
cristy3ed852e2009-09-05 21:47:34 +00001255 /*
1256 Rotate 270 degrees.
1257 */
cristyb32b90a2009-09-07 21:45:48 +00001258 GetPixelCacheTileSize(image,&tile_width,&tile_height);
cristy8b8148b2012-10-07 00:41:15 +00001259 tile_width=image->columns;
cristy26b64912012-12-16 18:20:09 +00001260#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy64c10cd2013-01-06 18:40:43 +00001261 #pragma omp parallel for schedule(static,4) shared(status) \
cristycb7dfcc2013-01-06 18:34:59 +00001262 magick_threads(image,image,1,1)
cristy9a5a52f2012-10-09 14:40:31 +00001263#endif
cristyad11aac2011-09-25 21:29:16 +00001264 for (tile_y=0; tile_y < (ssize_t) image->rows; tile_y+=(ssize_t) tile_height)
cristy3ed852e2009-09-05 21:47:34 +00001265 {
cristybb503372010-05-27 20:51:26 +00001266 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001267 tile_x;
1268
1269 if (status == MagickFalse)
1270 continue;
cristy26295322011-09-22 00:50:36 +00001271 tile_x=0;
1272 for ( ; tile_x < (ssize_t) image->columns; tile_x+=(ssize_t) tile_width)
cristy3ed852e2009-09-05 21:47:34 +00001273 {
1274 MagickBooleanType
1275 sync;
1276
cristy4c08aed2011-07-01 19:47:50 +00001277 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00001278 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00001279
cristy4c08aed2011-07-01 19:47:50 +00001280 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00001281 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001282
cristy2be50602011-10-09 01:28:13 +00001283 register ssize_t
1284 y;
1285
cristybb503372010-05-27 20:51:26 +00001286 size_t
cristyb32b90a2009-09-07 21:45:48 +00001287 height,
1288 width;
cristy3ed852e2009-09-05 21:47:34 +00001289
cristyb32b90a2009-09-07 21:45:48 +00001290 width=tile_width;
cristybb503372010-05-27 20:51:26 +00001291 if ((tile_x+(ssize_t) tile_width) > (ssize_t) image->columns)
cristya45d2ec2011-08-24 13:17:19 +00001292 width=(size_t) (tile_width-(tile_x+tile_width-image->columns));
cristyb32b90a2009-09-07 21:45:48 +00001293 height=tile_height;
cristybb503372010-05-27 20:51:26 +00001294 if ((tile_y+(ssize_t) tile_height) > (ssize_t) image->rows)
cristya45d2ec2011-08-24 13:17:19 +00001295 height=(size_t) (tile_height-(tile_y+tile_height-image->rows));
1296 p=GetCacheViewVirtualPixels(image_view,tile_x,tile_y,width,height,
1297 exception);
cristy4c08aed2011-07-01 19:47:50 +00001298 if (p == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001299 {
1300 status=MagickFalse;
1301 break;
1302 }
cristybb503372010-05-27 20:51:26 +00001303 for (y=0; y < (ssize_t) width; y++)
cristy3ed852e2009-09-05 21:47:34 +00001304 {
cristy4c08aed2011-07-01 19:47:50 +00001305 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00001306 *restrict tile_pixels;
cristy3ed852e2009-09-05 21:47:34 +00001307
cristybb503372010-05-27 20:51:26 +00001308 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001309 x;
1310
cristy27709ef2011-09-18 02:53:53 +00001311 if (status == MagickFalse)
1312 continue;
cristya45d2ec2011-08-24 13:17:19 +00001313 q=QueueCacheViewAuthenticPixels(rotate_view,tile_y,(ssize_t) (y+
1314 rotate_image->rows-(tile_x+width)),height,1,exception);
cristyacd2ed22011-08-30 01:44:23 +00001315 if (q == (Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001316 {
1317 status=MagickFalse;
cristy27709ef2011-09-18 02:53:53 +00001318 continue;
cristy3ed852e2009-09-05 21:47:34 +00001319 }
cristyed231572011-07-14 02:18:59 +00001320 tile_pixels=p+((width-1)-y)*GetPixelChannels(image);
cristybb503372010-05-27 20:51:26 +00001321 for (x=0; x < (ssize_t) height; x++)
cristy3ed852e2009-09-05 21:47:34 +00001322 {
cristya45d2ec2011-08-24 13:17:19 +00001323 register ssize_t
1324 i;
1325
cristy883fde12013-04-08 00:50:13 +00001326 if (GetPixelReadMask(image,tile_pixels) == 0)
cristy10a6c612012-01-29 21:41:05 +00001327 {
1328 tile_pixels+=width*GetPixelChannels(image);
1329 q+=GetPixelChannels(rotate_image);
1330 continue;
1331 }
cristya45d2ec2011-08-24 13:17:19 +00001332 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1333 {
cristy5a23c552013-02-13 14:34:28 +00001334 PixelChannel channel=GetPixelChannelChannel(image,i);
1335 PixelTrait traits=GetPixelChannelTraits(image,channel);
1336 PixelTrait rotate_traits=GetPixelChannelTraits(rotate_image,
1337 channel);
cristy010d7d12011-08-31 01:02:48 +00001338 if ((traits == UndefinedPixelTrait) ||
1339 (rotate_traits == UndefinedPixelTrait))
cristya45d2ec2011-08-24 13:17:19 +00001340 continue;
cristy0beccfa2011-09-25 20:47:53 +00001341 SetPixelChannel(rotate_image,channel,tile_pixels[i],q);
cristya45d2ec2011-08-24 13:17:19 +00001342 }
cristyed231572011-07-14 02:18:59 +00001343 tile_pixels+=width*GetPixelChannels(image);
1344 q+=GetPixelChannels(rotate_image);
cristy3ed852e2009-09-05 21:47:34 +00001345 }
cristy26b64912012-12-16 18:20:09 +00001346#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy9a5a52f2012-10-09 14:40:31 +00001347 #pragma omp critical (MagickCore_IntegralRotateImage)
1348#endif
cristy3ed852e2009-09-05 21:47:34 +00001349 sync=SyncCacheViewAuthenticPixels(rotate_view,exception);
1350 if (sync == MagickFalse)
1351 status=MagickFalse;
1352 }
1353 }
1354 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1355 {
1356 MagickBooleanType
1357 proceed;
1358
cristy21e03412011-09-25 22:39:35 +00001359 proceed=SetImageProgress(image,RotateImageTag,progress+=tile_height,
1360 image->rows);
1361 if (proceed == MagickFalse)
1362 status=MagickFalse;
1363 }
1364 }
1365 (void) SetImageProgress(image,RotateImageTag,(MagickOffsetType)
1366 image->rows-1,image->rows);
1367 Swap(page.width,page.height);
1368 Swap(page.x,page.y);
1369 if (page.width != 0)
1370 page.x=(ssize_t) (page.width-rotate_image->columns-page.x);
1371 break;
1372 }
cristy3ed852e2009-09-05 21:47:34 +00001373 }
1374 rotate_view=DestroyCacheView(rotate_view);
1375 image_view=DestroyCacheView(image_view);
1376 rotate_image->type=image->type;
1377 rotate_image->page=page;
1378 if (status == MagickFalse)
1379 rotate_image=DestroyImage(rotate_image);
1380 return(rotate_image);
1381}
1382
1383/*
1384%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1385% %
1386% %
1387% %
1388+ X S h e a r I m a g e %
1389% %
1390% %
1391% %
1392%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1393%
1394% XShearImage() shears the image in the X direction with a shear angle of
1395% 'degrees'. Positive angles shear counter-clockwise (right-hand rule), and
1396% negative angles shear clockwise. Angles are measured relative to a vertical
1397% Y-axis. X shears will widen an image creating 'empty' triangles on the left
1398% and right sides of the source image.
1399%
1400% The format of the XShearImage method is:
1401%
cristya19f1d72012-08-07 18:24:38 +00001402% MagickBooleanType XShearImage(Image *image,const double degrees,
cristybb503372010-05-27 20:51:26 +00001403% const size_t width,const size_t height,
1404% const ssize_t x_offset,const ssize_t y_offset,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001405%
1406% A description of each parameter follows.
1407%
1408% o image: the image.
1409%
cristya19f1d72012-08-07 18:24:38 +00001410% o degrees: A double representing the shearing angle along the X
cristy3ed852e2009-09-05 21:47:34 +00001411% axis.
1412%
1413% o width, height, x_offset, y_offset: Defines a region of the image
1414% to shear.
1415%
cristyecc2c142010-01-17 22:25:46 +00001416% o exception: return any errors or warnings in this structure.
1417%
cristy3ed852e2009-09-05 21:47:34 +00001418*/
cristya19f1d72012-08-07 18:24:38 +00001419static MagickBooleanType XShearImage(Image *image,const double degrees,
cristybb503372010-05-27 20:51:26 +00001420 const size_t width,const size_t height,const ssize_t x_offset,
1421 const ssize_t y_offset,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001422{
1423#define XShearImageTag "XShear/Image"
1424
1425 typedef enum
1426 {
1427 LEFT,
1428 RIGHT
1429 } ShearDirection;
1430
1431 CacheView
1432 *image_view;
1433
cristy3ed852e2009-09-05 21:47:34 +00001434 MagickBooleanType
1435 status;
1436
cristy5f959472010-05-27 22:19:46 +00001437 MagickOffsetType
1438 progress;
1439
cristy4c08aed2011-07-01 19:47:50 +00001440 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00001441 background;
1442
cristy5f959472010-05-27 22:19:46 +00001443 ssize_t
1444 y;
1445
cristy9d8c8ce2011-10-25 16:13:52 +00001446 /*
1447 X shear image.
1448 */
cristy3ed852e2009-09-05 21:47:34 +00001449 assert(image != (Image *) NULL);
1450 assert(image->signature == MagickSignature);
1451 if (image->debug != MagickFalse)
1452 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
cristy3ed852e2009-09-05 21:47:34 +00001453 status=MagickTrue;
cristy9d8c8ce2011-10-25 16:13:52 +00001454 background=image->background_color;
cristy3ed852e2009-09-05 21:47:34 +00001455 progress=0;
cristy46ff2672012-12-14 15:32:26 +00001456 image_view=AcquireAuthenticCacheView(image,exception);
cristyb5d5f722009-11-04 03:03:49 +00001457#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +00001458 #pragma omp parallel for schedule(static,4) shared(progress,status) \
cristy5e6b2592012-12-19 14:08:11 +00001459 magick_threads(image,image,height,1)
cristy3ed852e2009-09-05 21:47:34 +00001460#endif
cristybb503372010-05-27 20:51:26 +00001461 for (y=0; y < (ssize_t) height; y++)
cristy3ed852e2009-09-05 21:47:34 +00001462 {
cristy4c08aed2011-07-01 19:47:50 +00001463 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00001464 pixel,
1465 source,
1466 destination;
1467
cristya19f1d72012-08-07 18:24:38 +00001468 double
cristy3ed852e2009-09-05 21:47:34 +00001469 area,
1470 displacement;
1471
cristy4c08aed2011-07-01 19:47:50 +00001472 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00001473 *restrict p,
1474 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001475
cristyd40deb62011-03-09 00:52:27 +00001476 register ssize_t
1477 i;
1478
cristy3ed852e2009-09-05 21:47:34 +00001479 ShearDirection
1480 direction;
1481
cristyd40deb62011-03-09 00:52:27 +00001482 ssize_t
1483 step;
1484
cristy3ed852e2009-09-05 21:47:34 +00001485 if (status == MagickFalse)
1486 continue;
1487 p=GetCacheViewAuthenticPixels(image_view,0,y_offset+y,image->columns,1,
1488 exception);
cristy4c08aed2011-07-01 19:47:50 +00001489 if (p == (Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001490 {
1491 status=MagickFalse;
1492 continue;
1493 }
cristyed231572011-07-14 02:18:59 +00001494 p+=x_offset*GetPixelChannels(image);
cristya19f1d72012-08-07 18:24:38 +00001495 displacement=degrees*(double) (y-height/2.0);
cristy3ed852e2009-09-05 21:47:34 +00001496 if (displacement == 0.0)
1497 continue;
1498 if (displacement > 0.0)
1499 direction=RIGHT;
1500 else
1501 {
1502 displacement*=(-1.0);
1503 direction=LEFT;
1504 }
cristybb503372010-05-27 20:51:26 +00001505 step=(ssize_t) floor((double) displacement);
cristya19f1d72012-08-07 18:24:38 +00001506 area=(double) (displacement-step);
cristy3ed852e2009-09-05 21:47:34 +00001507 step++;
1508 pixel=background;
cristy4c08aed2011-07-01 19:47:50 +00001509 GetPixelInfo(image,&source);
1510 GetPixelInfo(image,&destination);
cristy3ed852e2009-09-05 21:47:34 +00001511 switch (direction)
1512 {
1513 case LEFT:
1514 {
1515 /*
1516 Transfer pixels left-to-right.
1517 */
1518 if (step > x_offset)
1519 break;
cristyed231572011-07-14 02:18:59 +00001520 q=p-step*GetPixelChannels(image);
cristybb503372010-05-27 20:51:26 +00001521 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00001522 {
1523 if ((x_offset+i) < step)
1524 {
cristyed231572011-07-14 02:18:59 +00001525 p+=GetPixelChannels(image);
cristy803640d2011-11-17 02:11:32 +00001526 GetPixelInfoPixel(image,p,&pixel);
cristyed231572011-07-14 02:18:59 +00001527 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00001528 continue;
1529 }
cristy803640d2011-11-17 02:11:32 +00001530 GetPixelInfoPixel(image,p,&source);
cristya19f1d72012-08-07 18:24:38 +00001531 CompositePixelInfoAreaBlend(&pixel,(double) pixel.alpha,
1532 &source,(double) GetPixelAlpha(image,p),area,&destination);
cristy803640d2011-11-17 02:11:32 +00001533 SetPixelInfoPixel(image,&destination,q);
1534 GetPixelInfoPixel(image,p,&pixel);
cristyed231572011-07-14 02:18:59 +00001535 p+=GetPixelChannels(image);
1536 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00001537 }
cristya19f1d72012-08-07 18:24:38 +00001538 CompositePixelInfoAreaBlend(&pixel,(double) pixel.alpha,
1539 &background,(double) background.alpha,area,&destination);
cristy803640d2011-11-17 02:11:32 +00001540 SetPixelInfoPixel(image,&destination,q);
cristyed231572011-07-14 02:18:59 +00001541 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00001542 for (i=0; i < (step-1); i++)
cristy4c08aed2011-07-01 19:47:50 +00001543 {
cristy803640d2011-11-17 02:11:32 +00001544 SetPixelInfoPixel(image,&background,q);
cristyed231572011-07-14 02:18:59 +00001545 q+=GetPixelChannels(image);
cristy4c08aed2011-07-01 19:47:50 +00001546 }
cristy3ed852e2009-09-05 21:47:34 +00001547 break;
1548 }
1549 case RIGHT:
1550 {
1551 /*
1552 Transfer pixels right-to-left.
1553 */
cristyed231572011-07-14 02:18:59 +00001554 p+=width*GetPixelChannels(image);
1555 q=p+step*GetPixelChannels(image);
cristybb503372010-05-27 20:51:26 +00001556 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00001557 {
cristyed231572011-07-14 02:18:59 +00001558 p-=GetPixelChannels(image);
1559 q-=GetPixelChannels(image);
cristybb503372010-05-27 20:51:26 +00001560 if ((size_t) (x_offset+width+step-i) >= image->columns)
cristy3ed852e2009-09-05 21:47:34 +00001561 continue;
cristy803640d2011-11-17 02:11:32 +00001562 GetPixelInfoPixel(image,p,&source);
cristya19f1d72012-08-07 18:24:38 +00001563 CompositePixelInfoAreaBlend(&pixel,(double) pixel.alpha,
1564 &source,(double) GetPixelAlpha(image,p),area,&destination);
cristy803640d2011-11-17 02:11:32 +00001565 SetPixelInfoPixel(image,&destination,q);
1566 GetPixelInfoPixel(image,p,&pixel);
cristy3ed852e2009-09-05 21:47:34 +00001567 }
cristya19f1d72012-08-07 18:24:38 +00001568 CompositePixelInfoAreaBlend(&pixel,(double) pixel.alpha,
1569 &background,(double) background.alpha,area,&destination);
cristyed231572011-07-14 02:18:59 +00001570 q-=GetPixelChannels(image);
cristy803640d2011-11-17 02:11:32 +00001571 SetPixelInfoPixel(image,&destination,q);
cristy3ed852e2009-09-05 21:47:34 +00001572 for (i=0; i < (step-1); i++)
cristy4c08aed2011-07-01 19:47:50 +00001573 {
cristyed231572011-07-14 02:18:59 +00001574 q-=GetPixelChannels(image);
cristy803640d2011-11-17 02:11:32 +00001575 SetPixelInfoPixel(image,&background,q);
cristy4c08aed2011-07-01 19:47:50 +00001576 }
cristy3ed852e2009-09-05 21:47:34 +00001577 break;
1578 }
1579 }
1580 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
1581 status=MagickFalse;
1582 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1583 {
1584 MagickBooleanType
1585 proceed;
1586
cristyb5d5f722009-11-04 03:03:49 +00001587#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy7b650b52011-10-09 01:13:39 +00001588 #pragma omp critical (MagickCore_XShearImage)
cristy3ed852e2009-09-05 21:47:34 +00001589#endif
1590 proceed=SetImageProgress(image,XShearImageTag,progress++,height);
1591 if (proceed == MagickFalse)
1592 status=MagickFalse;
1593 }
1594 }
1595 image_view=DestroyCacheView(image_view);
1596 return(status);
1597}
1598
1599/*
1600%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1601% %
1602% %
1603% %
1604+ Y S h e a r I m a g e %
1605% %
1606% %
1607% %
1608%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1609%
1610% YShearImage shears the image in the Y direction with a shear angle of
1611% 'degrees'. Positive angles shear counter-clockwise (right-hand rule), and
1612% negative angles shear clockwise. Angles are measured relative to a
1613% horizontal X-axis. Y shears will increase the height of an image creating
1614% 'empty' triangles on the top and bottom of the source image.
1615%
1616% The format of the YShearImage method is:
1617%
cristya19f1d72012-08-07 18:24:38 +00001618% MagickBooleanType YShearImage(Image *image,const double degrees,
cristybb503372010-05-27 20:51:26 +00001619% const size_t width,const size_t height,
1620% const ssize_t x_offset,const ssize_t y_offset,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001621%
1622% A description of each parameter follows.
1623%
1624% o image: the image.
1625%
cristya19f1d72012-08-07 18:24:38 +00001626% o degrees: A double representing the shearing angle along the Y
cristy3ed852e2009-09-05 21:47:34 +00001627% axis.
1628%
1629% o width, height, x_offset, y_offset: Defines a region of the image
1630% to shear.
1631%
cristyecc2c142010-01-17 22:25:46 +00001632% o exception: return any errors or warnings in this structure.
1633%
cristy3ed852e2009-09-05 21:47:34 +00001634*/
cristya19f1d72012-08-07 18:24:38 +00001635static MagickBooleanType YShearImage(Image *image,const double degrees,
cristybb503372010-05-27 20:51:26 +00001636 const size_t width,const size_t height,const ssize_t x_offset,
1637 const ssize_t y_offset,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001638{
1639#define YShearImageTag "YShear/Image"
1640
1641 typedef enum
1642 {
1643 UP,
1644 DOWN
1645 } ShearDirection;
1646
1647 CacheView
1648 *image_view;
1649
cristy3ed852e2009-09-05 21:47:34 +00001650 MagickBooleanType
1651 status;
1652
cristy5f959472010-05-27 22:19:46 +00001653 MagickOffsetType
1654 progress;
1655
cristy4c08aed2011-07-01 19:47:50 +00001656 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00001657 background;
1658
cristy5f959472010-05-27 22:19:46 +00001659 ssize_t
1660 x;
1661
cristy9d8c8ce2011-10-25 16:13:52 +00001662 /*
1663 Y Shear image.
1664 */
cristy3ed852e2009-09-05 21:47:34 +00001665 assert(image != (Image *) NULL);
1666 assert(image->signature == MagickSignature);
1667 if (image->debug != MagickFalse)
1668 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
cristy3ed852e2009-09-05 21:47:34 +00001669 status=MagickTrue;
1670 progress=0;
cristy9d8c8ce2011-10-25 16:13:52 +00001671 background=image->background_color;
cristy46ff2672012-12-14 15:32:26 +00001672 image_view=AcquireAuthenticCacheView(image,exception);
cristyb5d5f722009-11-04 03:03:49 +00001673#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +00001674 #pragma omp parallel for schedule(static,4) shared(progress,status) \
cristy5e6b2592012-12-19 14:08:11 +00001675 magick_threads(image,image,width,1)
cristy3ed852e2009-09-05 21:47:34 +00001676#endif
cristybb503372010-05-27 20:51:26 +00001677 for (x=0; x < (ssize_t) width; x++)
cristy3ed852e2009-09-05 21:47:34 +00001678 {
cristybb503372010-05-27 20:51:26 +00001679 ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001680 step;
1681
cristya19f1d72012-08-07 18:24:38 +00001682 double
cristy3ed852e2009-09-05 21:47:34 +00001683 area,
1684 displacement;
1685
cristy4c08aed2011-07-01 19:47:50 +00001686 PixelInfo
1687 pixel,
1688 source,
1689 destination;
1690
1691 register Quantum
1692 *restrict p,
1693 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001694
cristybb503372010-05-27 20:51:26 +00001695 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001696 i;
1697
cristy3ed852e2009-09-05 21:47:34 +00001698 ShearDirection
1699 direction;
1700
1701 if (status == MagickFalse)
1702 continue;
1703 p=GetCacheViewAuthenticPixels(image_view,x_offset+x,0,1,image->rows,
1704 exception);
cristy4c08aed2011-07-01 19:47:50 +00001705 if (p == (Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001706 {
1707 status=MagickFalse;
1708 continue;
1709 }
cristyed231572011-07-14 02:18:59 +00001710 p+=y_offset*GetPixelChannels(image);
cristya19f1d72012-08-07 18:24:38 +00001711 displacement=degrees*(double) (x-width/2.0);
cristy3ed852e2009-09-05 21:47:34 +00001712 if (displacement == 0.0)
1713 continue;
1714 if (displacement > 0.0)
1715 direction=DOWN;
1716 else
1717 {
1718 displacement*=(-1.0);
1719 direction=UP;
1720 }
cristybb503372010-05-27 20:51:26 +00001721 step=(ssize_t) floor((double) displacement);
cristya19f1d72012-08-07 18:24:38 +00001722 area=(double) (displacement-step);
cristy3ed852e2009-09-05 21:47:34 +00001723 step++;
1724 pixel=background;
cristy4c08aed2011-07-01 19:47:50 +00001725 GetPixelInfo(image,&source);
1726 GetPixelInfo(image,&destination);
cristy3ed852e2009-09-05 21:47:34 +00001727 switch (direction)
1728 {
1729 case UP:
1730 {
1731 /*
1732 Transfer pixels top-to-bottom.
1733 */
1734 if (step > y_offset)
1735 break;
cristyed231572011-07-14 02:18:59 +00001736 q=p-step*GetPixelChannels(image);
cristybb503372010-05-27 20:51:26 +00001737 for (i=0; i < (ssize_t) height; i++)
cristy3ed852e2009-09-05 21:47:34 +00001738 {
1739 if ((y_offset+i) < step)
1740 {
cristyed231572011-07-14 02:18:59 +00001741 p+=GetPixelChannels(image);
cristy803640d2011-11-17 02:11:32 +00001742 GetPixelInfoPixel(image,p,&pixel);
cristyed231572011-07-14 02:18:59 +00001743 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00001744 continue;
1745 }
cristy803640d2011-11-17 02:11:32 +00001746 GetPixelInfoPixel(image,p,&source);
cristya19f1d72012-08-07 18:24:38 +00001747 CompositePixelInfoAreaBlend(&pixel,(double) pixel.alpha,
1748 &source,(double) GetPixelAlpha(image,p),area,
cristy4c08aed2011-07-01 19:47:50 +00001749 &destination);
cristy803640d2011-11-17 02:11:32 +00001750 SetPixelInfoPixel(image,&destination,q);
1751 GetPixelInfoPixel(image,p,&pixel);
cristyed231572011-07-14 02:18:59 +00001752 p+=GetPixelChannels(image);
1753 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00001754 }
cristya19f1d72012-08-07 18:24:38 +00001755 CompositePixelInfoAreaBlend(&pixel,(double) pixel.alpha,
1756 &background,(double) background.alpha,area,&destination);
cristy803640d2011-11-17 02:11:32 +00001757 SetPixelInfoPixel(image,&destination,q);
cristyed231572011-07-14 02:18:59 +00001758 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00001759 for (i=0; i < (step-1); i++)
cristy4c08aed2011-07-01 19:47:50 +00001760 {
cristy803640d2011-11-17 02:11:32 +00001761 SetPixelInfoPixel(image,&background,q);
cristyed231572011-07-14 02:18:59 +00001762 q+=GetPixelChannels(image);
cristy4c08aed2011-07-01 19:47:50 +00001763 }
cristy3ed852e2009-09-05 21:47:34 +00001764 break;
1765 }
1766 case DOWN:
1767 {
1768 /*
1769 Transfer pixels bottom-to-top.
1770 */
cristyed231572011-07-14 02:18:59 +00001771 p+=height*GetPixelChannels(image);
1772 q=p+step*GetPixelChannels(image);
cristybb503372010-05-27 20:51:26 +00001773 for (i=0; i < (ssize_t) height; i++)
cristy3ed852e2009-09-05 21:47:34 +00001774 {
cristyed231572011-07-14 02:18:59 +00001775 p-=GetPixelChannels(image);
1776 q-=GetPixelChannels(image);
cristybb503372010-05-27 20:51:26 +00001777 if ((size_t) (y_offset+height+step-i) >= image->rows)
cristy3ed852e2009-09-05 21:47:34 +00001778 continue;
cristy803640d2011-11-17 02:11:32 +00001779 GetPixelInfoPixel(image,p,&source);
cristya19f1d72012-08-07 18:24:38 +00001780 CompositePixelInfoAreaBlend(&pixel,(double) pixel.alpha,
1781 &source,(double) GetPixelAlpha(image,p),area,
cristy4c08aed2011-07-01 19:47:50 +00001782 &destination);
cristy803640d2011-11-17 02:11:32 +00001783 SetPixelInfoPixel(image,&destination,q);
1784 GetPixelInfoPixel(image,p,&pixel);
cristy3ed852e2009-09-05 21:47:34 +00001785 }
cristya19f1d72012-08-07 18:24:38 +00001786 CompositePixelInfoAreaBlend(&pixel,(double) pixel.alpha,
1787 &background,(double) background.alpha,area,&destination);
cristyed231572011-07-14 02:18:59 +00001788 q-=GetPixelChannels(image);
cristy803640d2011-11-17 02:11:32 +00001789 SetPixelInfoPixel(image,&destination,q);
cristy3ed852e2009-09-05 21:47:34 +00001790 for (i=0; i < (step-1); i++)
cristy4c08aed2011-07-01 19:47:50 +00001791 {
cristyed231572011-07-14 02:18:59 +00001792 q-=GetPixelChannels(image);
cristy803640d2011-11-17 02:11:32 +00001793 SetPixelInfoPixel(image,&background,q);
cristy4c08aed2011-07-01 19:47:50 +00001794 }
cristy3ed852e2009-09-05 21:47:34 +00001795 break;
1796 }
1797 }
1798 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
1799 status=MagickFalse;
1800 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1801 {
1802 MagickBooleanType
1803 proceed;
1804
cristyb5d5f722009-11-04 03:03:49 +00001805#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy7b650b52011-10-09 01:13:39 +00001806 #pragma omp critical (MagickCore_YShearImage)
cristy3ed852e2009-09-05 21:47:34 +00001807#endif
1808 proceed=SetImageProgress(image,YShearImageTag,progress++,image->rows);
1809 if (proceed == MagickFalse)
1810 status=MagickFalse;
1811 }
1812 }
1813 image_view=DestroyCacheView(image_view);
1814 return(status);
1815}
1816
1817/*
1818%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1819% %
1820% %
1821% %
cristy3ed852e2009-09-05 21:47:34 +00001822% S h e a r I m a g e %
1823% %
1824% %
1825% %
1826%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1827%
1828% ShearImage() creates a new image that is a shear_image copy of an existing
cristycee97112010-05-28 00:44:52 +00001829% one. Shearing slides one edge of an image along the X or Y axis, creating
1830% a parallelogram. An X direction shear slides an edge along the X axis,
1831% while a Y direction shear slides an edge along the Y axis. The amount of
cristy3ed852e2009-09-05 21:47:34 +00001832% the shear is controlled by a shear angle. For X direction shears, x_shear
1833% is measured relative to the Y axis, and similarly, for Y direction shears
1834% y_shear is measured relative to the X axis. Empty triangles left over from
1835% shearing the image are filled with the background color defined by member
1836% 'background_color' of the image.. ShearImage() allocates the memory
1837% necessary for the new Image structure and returns a pointer to the new image.
1838%
1839% ShearImage() is based on the paper "A Fast Algorithm for General Raster
1840% Rotatation" by Alan W. Paeth.
1841%
1842% The format of the ShearImage method is:
1843%
1844% Image *ShearImage(const Image *image,const double x_shear,
1845% const double y_shear,ExceptionInfo *exception)
1846%
1847% A description of each parameter follows.
1848%
1849% o image: the image.
1850%
1851% o x_shear, y_shear: Specifies the number of degrees to shear the image.
1852%
1853% o exception: return any errors or warnings in this structure.
1854%
1855*/
1856MagickExport Image *ShearImage(const Image *image,const double x_shear,
1857 const double y_shear,ExceptionInfo *exception)
1858{
1859 Image
1860 *integral_image,
1861 *shear_image;
1862
cristybb503372010-05-27 20:51:26 +00001863 ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001864 x_offset,
1865 y_offset;
1866
cristyecc2c142010-01-17 22:25:46 +00001867 MagickBooleanType
1868 status;
1869
cristy3ed852e2009-09-05 21:47:34 +00001870 PointInfo
1871 shear;
1872
1873 RectangleInfo
1874 border_info;
1875
cristybb503372010-05-27 20:51:26 +00001876 size_t
cristy3ed852e2009-09-05 21:47:34 +00001877 y_width;
1878
1879 assert(image != (Image *) NULL);
1880 assert(image->signature == MagickSignature);
1881 if (image->debug != MagickFalse)
1882 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1883 assert(exception != (ExceptionInfo *) NULL);
1884 assert(exception->signature == MagickSignature);
1885 if ((x_shear != 0.0) && (fmod(x_shear,90.0) == 0.0))
1886 ThrowImageException(ImageError,"AngleIsDiscontinuous");
1887 if ((y_shear != 0.0) && (fmod(y_shear,90.0) == 0.0))
1888 ThrowImageException(ImageError,"AngleIsDiscontinuous");
1889 /*
1890 Initialize shear angle.
1891 */
1892 integral_image=CloneImage(image,0,0,MagickTrue,exception);
1893 if (integral_image == (Image *) NULL)
1894 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
1895 shear.x=(-tan(DegreesToRadians(fmod(x_shear,360.0))));
1896 shear.y=tan(DegreesToRadians(fmod(y_shear,360.0)));
1897 if ((shear.x == 0.0) && (shear.y == 0.0))
1898 return(integral_image);
cristy574cc262011-08-05 01:23:58 +00001899 if (SetImageStorageClass(integral_image,DirectClass,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00001900 {
cristy3ed852e2009-09-05 21:47:34 +00001901 integral_image=DestroyImage(integral_image);
1902 return(integral_image);
1903 }
cristy8a46d822012-08-28 23:32:39 +00001904 if (integral_image->alpha_trait != BlendPixelTrait)
cristy63240882011-08-05 19:05:27 +00001905 (void) SetImageAlphaChannel(integral_image,OpaqueAlphaChannel,exception);
cristy3ed852e2009-09-05 21:47:34 +00001906 /*
1907 Compute image size.
1908 */
cristybb503372010-05-27 20:51:26 +00001909 y_width=image->columns+(ssize_t) floor(fabs(shear.x)*image->rows+0.5);
cristycee97112010-05-28 00:44:52 +00001910 x_offset=(ssize_t) ceil((double) image->columns+((fabs(shear.x)*image->rows)-
cristy06609ee2010-03-17 20:21:27 +00001911 image->columns)/2.0-0.5);
cristycee97112010-05-28 00:44:52 +00001912 y_offset=(ssize_t) ceil((double) image->rows+((fabs(shear.y)*y_width)-
1913 image->rows)/2.0-0.5);
cristy3ed852e2009-09-05 21:47:34 +00001914 /*
1915 Surround image with border.
1916 */
1917 integral_image->border_color=integral_image->background_color;
1918 integral_image->compose=CopyCompositeOp;
cristybb503372010-05-27 20:51:26 +00001919 border_info.width=(size_t) x_offset;
1920 border_info.height=(size_t) y_offset;
cristy633f0c62011-09-15 13:27:36 +00001921 shear_image=BorderImage(integral_image,&border_info,image->compose,exception);
cristyecc2c142010-01-17 22:25:46 +00001922 integral_image=DestroyImage(integral_image);
cristy3ed852e2009-09-05 21:47:34 +00001923 if (shear_image == (Image *) NULL)
1924 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristy3ed852e2009-09-05 21:47:34 +00001925 /*
1926 Shear the image.
1927 */
cristy8a46d822012-08-28 23:32:39 +00001928 if (shear_image->alpha_trait != BlendPixelTrait)
cristy63240882011-08-05 19:05:27 +00001929 (void) SetImageAlphaChannel(shear_image,OpaqueAlphaChannel,exception);
cristyecc2c142010-01-17 22:25:46 +00001930 status=XShearImage(shear_image,shear.x,image->columns,image->rows,x_offset,
cristyeaedf062010-05-29 22:36:02 +00001931 (ssize_t) (shear_image->rows-image->rows)/2,exception);
cristyecc2c142010-01-17 22:25:46 +00001932 if (status == MagickFalse)
1933 {
1934 shear_image=DestroyImage(shear_image);
1935 return((Image *) NULL);
1936 }
cristyeaedf062010-05-29 22:36:02 +00001937 status=YShearImage(shear_image,shear.y,y_width,image->rows,(ssize_t)
1938 (shear_image->columns-y_width)/2,y_offset,exception);
cristyecc2c142010-01-17 22:25:46 +00001939 if (status == MagickFalse)
1940 {
1941 shear_image=DestroyImage(shear_image);
1942 return((Image *) NULL);
1943 }
cristya19f1d72012-08-07 18:24:38 +00001944 status=CropToFitImage(&shear_image,shear.x,shear.y,(double)
1945 image->columns,(double) image->rows,MagickFalse,exception);
cristy3ed852e2009-09-05 21:47:34 +00001946 shear_image->compose=image->compose;
1947 shear_image->page.width=0;
1948 shear_image->page.height=0;
cristy1c2f48d2012-12-14 01:20:55 +00001949 if (status == MagickFalse)
1950 shear_image=DestroyImage(shear_image);
cristy3ed852e2009-09-05 21:47:34 +00001951 return(shear_image);
1952}
cristyc7c81142011-11-07 12:14:23 +00001953
1954/*
1955%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1956% %
1957% %
1958% %
1959% S h e a r R o t a t e I m a g e %
1960% %
1961% %
1962% %
1963%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1964%
1965% ShearRotateImage() creates a new image that is a rotated copy of an existing
1966% one. Positive angles rotate counter-clockwise (right-hand rule), while
1967% negative angles rotate clockwise. Rotated images are usually larger than
1968% the originals and have 'empty' triangular corners. X axis. Empty
1969% triangles left over from shearing the image are filled with the background
1970% color defined by member 'background_color' of the image. ShearRotateImage
1971% allocates the memory necessary for the new Image structure and returns a
1972% pointer to the new image.
1973%
1974% ShearRotateImage() is based on the paper "A Fast Algorithm for General
1975% Raster Rotatation" by Alan W. Paeth. ShearRotateImage is adapted from a
1976% similar method based on the Paeth paper written by Michael Halle of the
1977% Spatial Imaging Group, MIT Media Lab.
1978%
1979% The format of the ShearRotateImage method is:
1980%
1981% Image *ShearRotateImage(const Image *image,const double degrees,
1982% ExceptionInfo *exception)
1983%
1984% A description of each parameter follows.
1985%
1986% o image: the image.
1987%
1988% o degrees: Specifies the number of degrees to rotate the image.
1989%
1990% o exception: return any errors or warnings in this structure.
1991%
1992*/
1993MagickExport Image *ShearRotateImage(const Image *image,const double degrees,
1994 ExceptionInfo *exception)
1995{
1996 Image
1997 *integral_image,
1998 *rotate_image;
1999
2000 MagickBooleanType
2001 status;
2002
cristya19f1d72012-08-07 18:24:38 +00002003 double
cristyc7c81142011-11-07 12:14:23 +00002004 angle;
2005
2006 PointInfo
2007 shear;
2008
2009 RectangleInfo
2010 border_info;
2011
2012 size_t
2013 height,
2014 rotations,
2015 width,
2016 y_width;
2017
2018 ssize_t
2019 x_offset,
2020 y_offset;
2021
2022 /*
2023 Adjust rotation angle.
2024 */
2025 assert(image != (Image *) NULL);
2026 assert(image->signature == MagickSignature);
2027 if (image->debug != MagickFalse)
2028 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2029 assert(exception != (ExceptionInfo *) NULL);
2030 assert(exception->signature == MagickSignature);
2031 angle=degrees;
2032 while (angle < -45.0)
2033 angle+=360.0;
2034 for (rotations=0; angle > 45.0; rotations++)
2035 angle-=90.0;
2036 rotations%=4;
2037 /*
2038 Calculate shear equations.
2039 */
2040 integral_image=IntegralRotateImage(image,rotations,exception);
2041 if (integral_image == (Image *) NULL)
2042 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
2043 shear.x=(-tan((double) DegreesToRadians(angle)/2.0));
2044 shear.y=sin((double) DegreesToRadians(angle));
2045 if ((shear.x == 0.0) && (shear.y == 0.0))
2046 return(integral_image);
2047 if (SetImageStorageClass(integral_image,DirectClass,exception) == MagickFalse)
2048 {
2049 integral_image=DestroyImage(integral_image);
2050 return(integral_image);
2051 }
cristy8a46d822012-08-28 23:32:39 +00002052 if (integral_image->alpha_trait != BlendPixelTrait)
cristyc7c81142011-11-07 12:14:23 +00002053 (void) SetImageAlphaChannel(integral_image,OpaqueAlphaChannel,exception);
2054 /*
2055 Compute image size.
2056 */
2057 width=image->columns;
2058 height=image->rows;
2059 if ((rotations == 1) || (rotations == 3))
2060 {
2061 width=image->rows;
2062 height=image->columns;
2063 }
2064 y_width=width+(ssize_t) floor(fabs(shear.x)*height+0.5);
2065 x_offset=(ssize_t) ceil((double) width+((fabs(shear.y)*height)-width)/2.0-
2066 0.5);
2067 y_offset=(ssize_t) ceil((double) height+((fabs(shear.y)*y_width)-height)/2.0-
2068 0.5);
2069 /*
2070 Surround image with a border.
2071 */
2072 integral_image->border_color=integral_image->background_color;
2073 integral_image->compose=CopyCompositeOp;
2074 border_info.width=(size_t) x_offset;
2075 border_info.height=(size_t) y_offset;
2076 rotate_image=BorderImage(integral_image,&border_info,image->compose,
2077 exception);
2078 integral_image=DestroyImage(integral_image);
2079 if (rotate_image == (Image *) NULL)
2080 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
2081 /*
2082 Rotate the image.
2083 */
2084 status=XShearImage(rotate_image,shear.x,width,height,x_offset,(ssize_t)
2085 (rotate_image->rows-height)/2,exception);
2086 if (status == MagickFalse)
2087 {
2088 rotate_image=DestroyImage(rotate_image);
2089 return((Image *) NULL);
2090 }
2091 status=YShearImage(rotate_image,shear.y,y_width,height,(ssize_t)
2092 (rotate_image->columns-y_width)/2,y_offset,exception);
2093 if (status == MagickFalse)
2094 {
2095 rotate_image=DestroyImage(rotate_image);
2096 return((Image *) NULL);
2097 }
2098 status=XShearImage(rotate_image,shear.x,y_width,rotate_image->rows,(ssize_t)
2099 (rotate_image->columns-y_width)/2,0,exception);
2100 if (status == MagickFalse)
2101 {
2102 rotate_image=DestroyImage(rotate_image);
2103 return((Image *) NULL);
2104 }
cristya19f1d72012-08-07 18:24:38 +00002105 status=CropToFitImage(&rotate_image,shear.x,shear.y,(double) width,
2106 (double) height,MagickTrue,exception);
cristyc7c81142011-11-07 12:14:23 +00002107 rotate_image->compose=image->compose;
2108 rotate_image->page.width=0;
2109 rotate_image->page.height=0;
cristy1c2f48d2012-12-14 01:20:55 +00002110 if (status == MagickFalse)
2111 rotate_image=DestroyImage(rotate_image);
cristyc7c81142011-11-07 12:14:23 +00002112 return(rotate_image);
2113}