blob: 62ab2354e98378c08c81a56e4edc2e36fb686118 [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 %
cristyde984cd2013-12-01 14:49:27 +000016% Cristy %
cristy3ed852e2009-09-05 21:47:34 +000017% July 1992 %
18% %
19% %
cristyfe676ee2013-11-18 13:03:38 +000020% Copyright 1999-2014 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
dirk93b02b72013-11-16 16:03:36 +0000446 i=0;
cristy3ed852e2009-09-05 21:47:34 +0000447#if !defined(MAGICKCORE_HAVE_PWRITE)
cristyb5d5f722009-11-04 03:03:49 +0000448#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +0000449 #pragma omp critical (MagickCore_WriteRadonCell)
450#endif
451 {
cristy7f317702011-02-18 20:40:28 +0000452 if (lseek(radon_info->file,offset,SEEK_SET) >= 0)
cristy3ed852e2009-09-05 21:47:34 +0000453 {
454#endif
455 count=0;
456 for (i=0; i < (ssize_t) length; i+=count)
457 {
458#if !defined(MAGICKCORE_HAVE_PWRITE)
459 count=write(radon_info->file,buffer+i,MagickMin(length-i,(size_t)
460 SSIZE_MAX));
461#else
462 count=pwrite(radon_info->file,buffer+i,MagickMin(length-i,(size_t)
cristy2c38b272011-02-18 23:25:33 +0000463 SSIZE_MAX),offset+i);
cristy3ed852e2009-09-05 21:47:34 +0000464#endif
465 if (count > 0)
466 continue;
467 count=0;
468 if (errno != EINTR)
469 {
470 i=(-1);
471 break;
472 }
473 }
474#if !defined(MAGICKCORE_HAVE_PWRITE)
475 }
476 }
477#endif
478 return(i);
479}
480
481static inline unsigned short GetRadonCell(const RadonInfo *radon_info,
cristybb503372010-05-27 20:51:26 +0000482 const ssize_t x,const ssize_t y)
cristy3ed852e2009-09-05 21:47:34 +0000483{
cristy2c38b272011-02-18 23:25:33 +0000484 MagickOffsetType
cristy3ed852e2009-09-05 21:47:34 +0000485 i;
486
487 unsigned short
488 value;
489
cristy2c38b272011-02-18 23:25:33 +0000490 i=(MagickOffsetType) radon_info->height*x+y;
cristy3ed852e2009-09-05 21:47:34 +0000491 if ((i < 0) ||
492 ((MagickSizeType) (i*sizeof(*radon_info->cells)) >= radon_info->length))
493 return(0);
494 if (radon_info->type != DiskCache)
495 return(radon_info->cells[i]);
496 value=0;
497 (void) ReadRadonCell(radon_info,i*sizeof(*radon_info->cells),
498 sizeof(*radon_info->cells),(unsigned char *) &value);
499 return(value);
500}
501
502static inline MagickBooleanType SetRadonCell(const RadonInfo *radon_info,
cristybb503372010-05-27 20:51:26 +0000503 const ssize_t x,const ssize_t y,const unsigned short value)
cristy3ed852e2009-09-05 21:47:34 +0000504{
cristy2c38b272011-02-18 23:25:33 +0000505 MagickOffsetType
cristy3ed852e2009-09-05 21:47:34 +0000506 i;
507
508 ssize_t
509 count;
510
cristy2c38b272011-02-18 23:25:33 +0000511 i=(MagickOffsetType) radon_info->height*x+y;
cristy3ed852e2009-09-05 21:47:34 +0000512 if ((i < 0) ||
513 ((MagickSizeType) (i*sizeof(*radon_info->cells)) >= radon_info->length))
514 return(MagickFalse);
515 if (radon_info->type != DiskCache)
516 {
517 radon_info->cells[i]=value;
518 return(MagickTrue);
519 }
520 count=WriteRadonCell(radon_info,i*sizeof(*radon_info->cells),
cristy44807bb2009-09-19 18:28:06 +0000521 sizeof(*radon_info->cells),(const unsigned char *) &value);
cristy3ed852e2009-09-05 21:47:34 +0000522 if (count != (ssize_t) sizeof(*radon_info->cells))
523 return(MagickFalse);
524 return(MagickTrue);
525}
526
cristy4ee2b0c2012-05-15 00:30:35 +0000527static void RadonProjection(const Image *image,RadonInfo *source_cells,
cristybb503372010-05-27 20:51:26 +0000528 RadonInfo *destination_cells,const ssize_t sign,size_t *projection)
cristy3ed852e2009-09-05 21:47:34 +0000529{
530 RadonInfo
531 *swap;
532
cristybb503372010-05-27 20:51:26 +0000533 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000534 x;
535
536 register RadonInfo
537 *p,
538 *q;
539
cristybb503372010-05-27 20:51:26 +0000540 size_t
cristy3ed852e2009-09-05 21:47:34 +0000541 step;
542
cristyb0de93f2013-05-03 13:39:25 +0000543 assert(image != (Image *) NULL);
cristy3ed852e2009-09-05 21:47:34 +0000544 p=source_cells;
545 q=destination_cells;
546 for (step=1; step < p->width; step*=2)
547 {
cristyeaedf062010-05-29 22:36:02 +0000548 for (x=0; x < (ssize_t) p->width; x+=2*(ssize_t) step)
cristy3ed852e2009-09-05 21:47:34 +0000549 {
cristybb503372010-05-27 20:51:26 +0000550 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000551 i;
552
cristyd40deb62011-03-09 00:52:27 +0000553 ssize_t
554 y;
555
cristy3ed852e2009-09-05 21:47:34 +0000556 unsigned short
557 cell;
558
cristybb503372010-05-27 20:51:26 +0000559 for (i=0; i < (ssize_t) step; i++)
cristy3ed852e2009-09-05 21:47:34 +0000560 {
cristybb503372010-05-27 20:51:26 +0000561 for (y=0; y < (ssize_t) (p->height-i-1); y++)
cristy3ed852e2009-09-05 21:47:34 +0000562 {
563 cell=GetRadonCell(p,x+i,y);
cristyeaedf062010-05-29 22:36:02 +0000564 (void) SetRadonCell(q,x+2*i,y,cell+GetRadonCell(p,x+i+(ssize_t)
565 step,y+i));
566 (void) SetRadonCell(q,x+2*i+1,y,cell+GetRadonCell(p,x+i+(ssize_t)
567 step,y+i+1));
cristy3ed852e2009-09-05 21:47:34 +0000568 }
cristybb503372010-05-27 20:51:26 +0000569 for ( ; y < (ssize_t) (p->height-i); y++)
cristy3ed852e2009-09-05 21:47:34 +0000570 {
571 cell=GetRadonCell(p,x+i,y);
cristyeaedf062010-05-29 22:36:02 +0000572 (void) SetRadonCell(q,x+2*i,y,cell+GetRadonCell(p,x+i+(ssize_t) step,
573 y+i));
cristy3ed852e2009-09-05 21:47:34 +0000574 (void) SetRadonCell(q,x+2*i+1,y,cell);
575 }
cristybb503372010-05-27 20:51:26 +0000576 for ( ; y < (ssize_t) p->height; y++)
cristy3ed852e2009-09-05 21:47:34 +0000577 {
578 cell=GetRadonCell(p,x+i,y);
579 (void) SetRadonCell(q,x+2*i,y,cell);
580 (void) SetRadonCell(q,x+2*i+1,y,cell);
581 }
582 }
583 }
584 swap=p;
585 p=q;
586 q=swap;
587 }
cristyb5d5f722009-11-04 03:03:49 +0000588#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +0000589 #pragma omp parallel for schedule(static,4) \
cristycb7dfcc2013-01-06 18:34:59 +0000590 magick_threads(image,image,1,1)
cristy3ed852e2009-09-05 21:47:34 +0000591#endif
cristybb503372010-05-27 20:51:26 +0000592 for (x=0; x < (ssize_t) p->width; x++)
cristy3ed852e2009-09-05 21:47:34 +0000593 {
cristybb503372010-05-27 20:51:26 +0000594 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000595 y;
596
cristybb503372010-05-27 20:51:26 +0000597 size_t
cristy3ed852e2009-09-05 21:47:34 +0000598 sum;
599
600 sum=0;
cristybb503372010-05-27 20:51:26 +0000601 for (y=0; y < (ssize_t) (p->height-1); y++)
cristy3ed852e2009-09-05 21:47:34 +0000602 {
cristybb503372010-05-27 20:51:26 +0000603 ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000604 delta;
605
cristybb503372010-05-27 20:51:26 +0000606 delta=GetRadonCell(p,x,y)-(ssize_t) GetRadonCell(p,x,y+1);
cristy3ed852e2009-09-05 21:47:34 +0000607 sum+=delta*delta;
608 }
609 projection[p->width+sign*x-1]=sum;
610 }
611}
612
613static MagickBooleanType RadonTransform(const Image *image,
cristybb503372010-05-27 20:51:26 +0000614 const double threshold,size_t *projection,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000615{
616 CacheView
617 *image_view;
618
cristy3ed852e2009-09-05 21:47:34 +0000619 MagickBooleanType
620 status;
621
622 RadonInfo
623 *destination_cells,
624 *source_cells;
625
cristybb503372010-05-27 20:51:26 +0000626 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000627 i;
628
cristybb503372010-05-27 20:51:26 +0000629 size_t
cristy3ed852e2009-09-05 21:47:34 +0000630 count,
631 width;
632
cristyd40deb62011-03-09 00:52:27 +0000633 ssize_t
634 y;
635
636 unsigned char
637 byte;
638
cristy3ed852e2009-09-05 21:47:34 +0000639 unsigned short
640 bits[256];
641
642 for (width=1; width < ((image->columns+7)/8); width<<=1) ;
643 source_cells=AcquireRadonInfo(image,width,image->rows,exception);
644 destination_cells=AcquireRadonInfo(image,width,image->rows,exception);
645 if ((source_cells == (RadonInfo *) NULL) ||
646 (destination_cells == (RadonInfo *) NULL))
647 {
648 if (destination_cells != (RadonInfo *) NULL)
649 destination_cells=DestroyRadonInfo(destination_cells);
650 if (source_cells != (RadonInfo *) NULL)
651 source_cells=DestroyRadonInfo(source_cells);
652 return(MagickFalse);
653 }
654 if (ResetRadonCells(source_cells) == MagickFalse)
655 {
656 destination_cells=DestroyRadonInfo(destination_cells);
657 source_cells=DestroyRadonInfo(source_cells);
658 return(MagickFalse);
659 }
660 for (i=0; i < 256; i++)
661 {
662 byte=(unsigned char) i;
663 for (count=0; byte != 0; byte>>=1)
664 count+=byte & 0x01;
665 bits[i]=(unsigned short) count;
666 }
667 status=MagickTrue;
cristy46ff2672012-12-14 15:32:26 +0000668 image_view=AcquireVirtualCacheView(image,exception);
cristyb5d5f722009-11-04 03:03:49 +0000669#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +0000670 #pragma omp parallel for schedule(static,4) shared(status) \
cristy5e6b2592012-12-19 14:08:11 +0000671 magick_threads(image,image,image->rows,1)
cristy3ed852e2009-09-05 21:47:34 +0000672#endif
cristybb503372010-05-27 20:51:26 +0000673 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000674 {
cristy4c08aed2011-07-01 19:47:50 +0000675 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +0000676 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +0000677
cristybb503372010-05-27 20:51:26 +0000678 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000679 i,
680 x;
681
cristybb503372010-05-27 20:51:26 +0000682 size_t
cristy3ed852e2009-09-05 21:47:34 +0000683 bit,
684 byte;
685
686 if (status == MagickFalse)
687 continue;
688 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +0000689 if (p == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +0000690 {
691 status=MagickFalse;
692 continue;
693 }
694 bit=0;
695 byte=0;
cristybb503372010-05-27 20:51:26 +0000696 i=(ssize_t) (image->columns+7)/8;
697 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000698 {
699 byte<<=1;
cristyf13c5942012-08-08 23:50:11 +0000700 if (GetPixelIntensity(image,p) < threshold)
cristy3ed852e2009-09-05 21:47:34 +0000701 byte|=0x01;
702 bit++;
703 if (bit == 8)
704 {
705 (void) SetRadonCell(source_cells,--i,y,bits[byte]);
706 bit=0;
707 byte=0;
708 }
cristyed231572011-07-14 02:18:59 +0000709 p+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +0000710 }
711 if (bit != 0)
712 {
713 byte<<=(8-bit);
714 (void) SetRadonCell(source_cells,--i,y,bits[byte]);
715 }
716 }
cristy4ee2b0c2012-05-15 00:30:35 +0000717 RadonProjection(image,source_cells,destination_cells,-1,projection);
cristy3ed852e2009-09-05 21:47:34 +0000718 (void) ResetRadonCells(source_cells);
cristyb5d5f722009-11-04 03:03:49 +0000719#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +0000720 #pragma omp parallel for schedule(static,4) shared(status) \
cristycb7dfcc2013-01-06 18:34:59 +0000721 magick_threads(image,image,1,1)
cristy3ed852e2009-09-05 21:47:34 +0000722#endif
cristybb503372010-05-27 20:51:26 +0000723 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000724 {
cristy4c08aed2011-07-01 19:47:50 +0000725 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +0000726 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +0000727
cristybb503372010-05-27 20:51:26 +0000728 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000729 i,
730 x;
731
cristybb503372010-05-27 20:51:26 +0000732 size_t
cristy3ed852e2009-09-05 21:47:34 +0000733 bit,
734 byte;
735
736 if (status == MagickFalse)
737 continue;
738 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +0000739 if (p == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +0000740 {
741 status=MagickFalse;
742 continue;
743 }
744 bit=0;
745 byte=0;
746 i=0;
cristybb503372010-05-27 20:51:26 +0000747 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000748 {
749 byte<<=1;
cristyf13c5942012-08-08 23:50:11 +0000750 if (GetPixelIntensity(image,p) < threshold)
cristy3ed852e2009-09-05 21:47:34 +0000751 byte|=0x01;
752 bit++;
753 if (bit == 8)
754 {
755 (void) SetRadonCell(source_cells,i++,y,bits[byte]);
756 bit=0;
757 byte=0;
758 }
cristyed231572011-07-14 02:18:59 +0000759 p+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +0000760 }
761 if (bit != 0)
762 {
763 byte<<=(8-bit);
764 (void) SetRadonCell(source_cells,i++,y,bits[byte]);
765 }
766 }
cristy4ee2b0c2012-05-15 00:30:35 +0000767 RadonProjection(image,source_cells,destination_cells,1,projection);
cristy3ed852e2009-09-05 21:47:34 +0000768 image_view=DestroyCacheView(image_view);
769 destination_cells=DestroyRadonInfo(destination_cells);
770 source_cells=DestroyRadonInfo(source_cells);
771 return(MagickTrue);
772}
773
cristybb503372010-05-27 20:51:26 +0000774static void GetImageBackgroundColor(Image *image,const ssize_t offset,
cristy3ed852e2009-09-05 21:47:34 +0000775 ExceptionInfo *exception)
776{
777 CacheView
778 *image_view;
779
cristy4c08aed2011-07-01 19:47:50 +0000780 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +0000781 background;
782
cristya19f1d72012-08-07 18:24:38 +0000783 double
cristy3ed852e2009-09-05 21:47:34 +0000784 count;
785
cristyd40deb62011-03-09 00:52:27 +0000786 ssize_t
787 y;
788
cristy3ed852e2009-09-05 21:47:34 +0000789 /*
790 Compute average background color.
791 */
792 if (offset <= 0)
793 return;
cristy4c08aed2011-07-01 19:47:50 +0000794 GetPixelInfo(image,&background);
cristy3ed852e2009-09-05 21:47:34 +0000795 count=0.0;
cristy46ff2672012-12-14 15:32:26 +0000796 image_view=AcquireVirtualCacheView(image,exception);
cristybb503372010-05-27 20:51:26 +0000797 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000798 {
cristy4c08aed2011-07-01 19:47:50 +0000799 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +0000800 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +0000801
cristybb503372010-05-27 20:51:26 +0000802 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000803 x;
804
cristybb503372010-05-27 20:51:26 +0000805 if ((y >= offset) && (y < ((ssize_t) image->rows-offset)))
cristy3ed852e2009-09-05 21:47:34 +0000806 continue;
807 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +0000808 if (p == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +0000809 continue;
cristybb503372010-05-27 20:51:26 +0000810 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000811 {
cristybb503372010-05-27 20:51:26 +0000812 if ((x >= offset) && (x < ((ssize_t) image->columns-offset)))
cristy3ed852e2009-09-05 21:47:34 +0000813 continue;
cristy4c08aed2011-07-01 19:47:50 +0000814 background.red+=QuantumScale*GetPixelRed(image,p);
815 background.green+=QuantumScale*GetPixelGreen(image,p);
816 background.blue+=QuantumScale*GetPixelBlue(image,p);
cristy26295322011-09-22 00:50:36 +0000817 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
818 background.alpha+=QuantumScale*GetPixelAlpha(image,p);
cristy3ed852e2009-09-05 21:47:34 +0000819 count++;
cristyed231572011-07-14 02:18:59 +0000820 p+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +0000821 }
822 }
823 image_view=DestroyCacheView(image_view);
cristy8cd03c32012-07-07 18:57:59 +0000824 image->background_color.red=(double) ClampToQuantum(QuantumRange*
825 background.red/count);
826 image->background_color.green=(double) ClampToQuantum(QuantumRange*
827 background.green/count);
828 image->background_color.blue=(double) ClampToQuantum(QuantumRange*
829 background.blue/count);
cristy26295322011-09-22 00:50:36 +0000830 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy8cd03c32012-07-07 18:57:59 +0000831 image->background_color.alpha=(double) ClampToQuantum(QuantumRange*
832 background.alpha/count);
cristy3ed852e2009-09-05 21:47:34 +0000833}
834
835MagickExport Image *DeskewImage(const Image *image,const double threshold,
836 ExceptionInfo *exception)
837{
838 AffineMatrix
839 affine_matrix;
840
841 const char
842 *artifact;
843
844 double
845 degrees;
846
847 Image
848 *clone_image,
849 *crop_image,
850 *deskew_image,
851 *median_image;
852
cristy3ed852e2009-09-05 21:47:34 +0000853 MagickBooleanType
854 status;
855
856 RectangleInfo
857 geometry;
858
cristybb503372010-05-27 20:51:26 +0000859 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000860 i;
861
cristybb503372010-05-27 20:51:26 +0000862 size_t
cristy3ed852e2009-09-05 21:47:34 +0000863 max_projection,
864 *projection,
865 width;
866
cristyd40deb62011-03-09 00:52:27 +0000867 ssize_t
868 skew;
869
cristy3ed852e2009-09-05 21:47:34 +0000870 /*
871 Compute deskew angle.
872 */
873 for (width=1; width < ((image->columns+7)/8); width<<=1) ;
cristybb503372010-05-27 20:51:26 +0000874 projection=(size_t *) AcquireQuantumMemory((size_t) (2*width-1),
cristy3ed852e2009-09-05 21:47:34 +0000875 sizeof(*projection));
cristybb503372010-05-27 20:51:26 +0000876 if (projection == (size_t *) NULL)
cristy3ed852e2009-09-05 21:47:34 +0000877 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
878 status=RadonTransform(image,threshold,projection,exception);
879 if (status == MagickFalse)
880 {
cristybb503372010-05-27 20:51:26 +0000881 projection=(size_t *) RelinquishMagickMemory(projection);
cristy3ed852e2009-09-05 21:47:34 +0000882 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
883 }
884 max_projection=0;
885 skew=0;
cristybb503372010-05-27 20:51:26 +0000886 for (i=0; i < (ssize_t) (2*width-1); i++)
cristy3ed852e2009-09-05 21:47:34 +0000887 {
888 if (projection[i] > max_projection)
889 {
cristybb503372010-05-27 20:51:26 +0000890 skew=i-(ssize_t) width+1;
cristy3ed852e2009-09-05 21:47:34 +0000891 max_projection=projection[i];
892 }
893 }
cristybb503372010-05-27 20:51:26 +0000894 projection=(size_t *) RelinquishMagickMemory(projection);
anthony553bfcc2013-04-10 01:27:43 +0000895 degrees=RadiansToDegrees(-atan((double) skew/width/8));
896 if (image->debug != MagickFalse)
897 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
898 " Deskew angle: %g",degrees);
cristy3ed852e2009-09-05 21:47:34 +0000899 /*
900 Deskew image.
901 */
902 clone_image=CloneImage(image,0,0,MagickTrue,exception);
903 if (clone_image == (Image *) NULL)
904 return((Image *) NULL);
anthony8a95e802013-04-10 05:31:57 +0000905 {
906 char
907 angle[MaxTextExtent];
908
909 (void) FormatLocaleString(angle,MaxTextExtent,"%.20g",degrees);
910 (void) SetImageArtifact(clone_image,"deskew:angle",angle);
911 }
cristy387430f2012-02-07 13:09:46 +0000912 (void) SetImageVirtualPixelMethod(clone_image,BackgroundVirtualPixelMethod,
913 exception);
cristy3ed852e2009-09-05 21:47:34 +0000914 affine_matrix.sx=cos(DegreesToRadians(fmod((double) degrees,360.0)));
915 affine_matrix.rx=sin(DegreesToRadians(fmod((double) degrees,360.0)));
916 affine_matrix.ry=(-sin(DegreesToRadians(fmod((double) degrees,360.0))));
917 affine_matrix.sy=cos(DegreesToRadians(fmod((double) degrees,360.0)));
918 affine_matrix.tx=0.0;
919 affine_matrix.ty=0.0;
920 artifact=GetImageArtifact(image,"deskew:auto-crop");
921 if (artifact == (const char *) NULL)
922 {
923 deskew_image=AffineTransformImage(clone_image,&affine_matrix,exception);
924 clone_image=DestroyImage(clone_image);
925 return(deskew_image);
926 }
927 /*
928 Auto-crop image.
929 */
cristyba978e12010-09-12 20:26:50 +0000930 GetImageBackgroundColor(clone_image,(ssize_t) StringToLong(artifact),
931 exception);
cristy3ed852e2009-09-05 21:47:34 +0000932 deskew_image=AffineTransformImage(clone_image,&affine_matrix,exception);
933 clone_image=DestroyImage(clone_image);
934 if (deskew_image == (Image *) NULL)
935 return((Image *) NULL);
cristy95c38342011-03-18 22:39:51 +0000936 median_image=StatisticImage(deskew_image,MedianStatistic,3,3,exception);
cristy3ed852e2009-09-05 21:47:34 +0000937 if (median_image == (Image *) NULL)
938 {
939 deskew_image=DestroyImage(deskew_image);
940 return((Image *) NULL);
941 }
942 geometry=GetImageBoundingBox(median_image,exception);
943 median_image=DestroyImage(median_image);
944 if (image->debug != MagickFalse)
945 (void) LogMagickEvent(TransformEvent,GetMagickModule()," Deskew geometry: "
cristy6d8abba2010-06-03 01:10:47 +0000946 "%.20gx%.20g%+.20g%+.20g",(double) geometry.width,(double)
cristye8c25f92010-06-03 00:53:06 +0000947 geometry.height,(double) geometry.x,(double) geometry.y);
cristy3ed852e2009-09-05 21:47:34 +0000948 crop_image=CropImage(deskew_image,&geometry,exception);
949 deskew_image=DestroyImage(deskew_image);
950 return(crop_image);
951}
952
953/*
954%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
955% %
956% %
957% %
cristy987feef2011-11-17 12:24:56 +0000958% 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 +0000959% %
960% %
961% %
962%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
963%
cristy3dfa16c2010-05-17 19:34:48 +0000964% IntegralRotateImage() rotates the image an integral of 90 degrees. It
cristy3ed852e2009-09-05 21:47:34 +0000965% allocates the memory necessary for the new Image structure and returns a
966% pointer to the rotated image.
967%
968% The format of the IntegralRotateImage method is:
969%
cristybb503372010-05-27 20:51:26 +0000970% Image *IntegralRotateImage(const Image *image,size_t rotations,
cristy3ed852e2009-09-05 21:47:34 +0000971% ExceptionInfo *exception)
972%
973% A description of each parameter follows.
974%
975% o image: the image.
976%
977% o rotations: Specifies the number of 90 degree rotations.
978%
979*/
cristy987feef2011-11-17 12:24:56 +0000980MagickExport Image *IntegralRotateImage(const Image *image,size_t rotations,
cristy3ed852e2009-09-05 21:47:34 +0000981 ExceptionInfo *exception)
982{
cristy3ed852e2009-09-05 21:47:34 +0000983#define RotateImageTag "Rotate/Image"
984
985 CacheView
986 *image_view,
987 *rotate_view;
988
989 Image
990 *rotate_image;
991
cristy3ed852e2009-09-05 21:47:34 +0000992 MagickBooleanType
993 status;
994
cristy5f959472010-05-27 22:19:46 +0000995 MagickOffsetType
996 progress;
997
cristy3ed852e2009-09-05 21:47:34 +0000998 RectangleInfo
999 page;
1000
cristy5f959472010-05-27 22:19:46 +00001001 ssize_t
1002 y;
1003
cristy3ed852e2009-09-05 21:47:34 +00001004 /*
1005 Initialize rotated image attributes.
1006 */
1007 assert(image != (Image *) NULL);
1008 page=image->page;
1009 rotations%=4;
cristyb32b90a2009-09-07 21:45:48 +00001010 if (rotations == 0)
1011 return(CloneImage(image,0,0,MagickTrue,exception));
cristy3ed852e2009-09-05 21:47:34 +00001012 if ((rotations == 1) || (rotations == 3))
1013 rotate_image=CloneImage(image,image->rows,image->columns,MagickTrue,
1014 exception);
1015 else
1016 rotate_image=CloneImage(image,image->columns,image->rows,MagickTrue,
1017 exception);
1018 if (rotate_image == (Image *) NULL)
1019 return((Image *) NULL);
1020 /*
1021 Integral rotate the image.
1022 */
1023 status=MagickTrue;
1024 progress=0;
cristy46ff2672012-12-14 15:32:26 +00001025 image_view=AcquireVirtualCacheView(image,exception);
1026 rotate_view=AcquireAuthenticCacheView(rotate_image,exception);
cristy3ed852e2009-09-05 21:47:34 +00001027 switch (rotations)
1028 {
1029 case 0:
1030 {
1031 /*
1032 Rotate 0 degrees.
1033 */
cristy3ed852e2009-09-05 21:47:34 +00001034 break;
1035 }
1036 case 1:
1037 {
cristybb503372010-05-27 20:51:26 +00001038 size_t
cristyb32b90a2009-09-07 21:45:48 +00001039 tile_height,
1040 tile_width;
1041
cristyd40deb62011-03-09 00:52:27 +00001042 ssize_t
1043 tile_y;
1044
cristy3ed852e2009-09-05 21:47:34 +00001045 /*
1046 Rotate 90 degrees.
1047 */
cristyb32b90a2009-09-07 21:45:48 +00001048 GetPixelCacheTileSize(image,&tile_width,&tile_height);
cristy8b8148b2012-10-07 00:41:15 +00001049 tile_width=image->columns;
cristy26b64912012-12-16 18:20:09 +00001050#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy64c10cd2013-01-06 18:40:43 +00001051 #pragma omp parallel for schedule(static,4) shared(status) \
cristy3e1fa372013-01-06 18:07:11 +00001052 magick_threads(image,image,1,1)
cristy9a5a52f2012-10-09 14:40:31 +00001053#endif
cristyad11aac2011-09-25 21:29:16 +00001054 for (tile_y=0; tile_y < (ssize_t) image->rows; tile_y+=(ssize_t) tile_height)
cristy3ed852e2009-09-05 21:47:34 +00001055 {
cristybb503372010-05-27 20:51:26 +00001056 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001057 tile_x;
1058
1059 if (status == MagickFalse)
1060 continue;
cristy26295322011-09-22 00:50:36 +00001061 tile_x=0;
1062 for ( ; tile_x < (ssize_t) image->columns; tile_x+=(ssize_t) tile_width)
cristy3ed852e2009-09-05 21:47:34 +00001063 {
1064 MagickBooleanType
1065 sync;
1066
cristy4c08aed2011-07-01 19:47:50 +00001067 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00001068 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00001069
cristy4c08aed2011-07-01 19:47:50 +00001070 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00001071 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001072
cristya26211e2011-10-09 01:24:57 +00001073 register ssize_t
1074 y;
1075
cristybb503372010-05-27 20:51:26 +00001076 size_t
cristyb32b90a2009-09-07 21:45:48 +00001077 height,
1078 width;
cristy3ed852e2009-09-05 21:47:34 +00001079
cristyb32b90a2009-09-07 21:45:48 +00001080 width=tile_width;
cristybb503372010-05-27 20:51:26 +00001081 if ((tile_x+(ssize_t) tile_width) > (ssize_t) image->columns)
cristya45d2ec2011-08-24 13:17:19 +00001082 width=(size_t) (tile_width-(tile_x+tile_width-image->columns));
cristyb32b90a2009-09-07 21:45:48 +00001083 height=tile_height;
cristybb503372010-05-27 20:51:26 +00001084 if ((tile_y+(ssize_t) tile_height) > (ssize_t) image->rows)
cristya45d2ec2011-08-24 13:17:19 +00001085 height=(size_t) (tile_height-(tile_y+tile_height-image->rows));
cristydaa97692009-09-13 02:10:35 +00001086 p=GetCacheViewVirtualPixels(image_view,tile_x,tile_y,width,height,
1087 exception);
cristy4c08aed2011-07-01 19:47:50 +00001088 if (p == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001089 {
1090 status=MagickFalse;
1091 break;
1092 }
cristybb503372010-05-27 20:51:26 +00001093 for (y=0; y < (ssize_t) width; y++)
cristy3ed852e2009-09-05 21:47:34 +00001094 {
cristy4c08aed2011-07-01 19:47:50 +00001095 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00001096 *restrict tile_pixels;
cristy3ed852e2009-09-05 21:47:34 +00001097
cristybb503372010-05-27 20:51:26 +00001098 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001099 x;
1100
cristy27709ef2011-09-18 02:53:53 +00001101 if (status == MagickFalse)
1102 continue;
cristybb503372010-05-27 20:51:26 +00001103 q=QueueCacheViewAuthenticPixels(rotate_view,(ssize_t)
cristy92611572011-07-07 16:02:42 +00001104 (rotate_image->columns-(tile_y+height)),y+tile_x,height,1,
1105 exception);
cristyacd2ed22011-08-30 01:44:23 +00001106 if (q == (Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001107 {
1108 status=MagickFalse;
cristy27709ef2011-09-18 02:53:53 +00001109 continue;
cristy3ed852e2009-09-05 21:47:34 +00001110 }
cristyed231572011-07-14 02:18:59 +00001111 tile_pixels=p+((height-1)*width+y)*GetPixelChannels(image);
cristybb503372010-05-27 20:51:26 +00001112 for (x=0; x < (ssize_t) height; x++)
cristy3ed852e2009-09-05 21:47:34 +00001113 {
cristya45d2ec2011-08-24 13:17:19 +00001114 register ssize_t
1115 i;
1116
cristy883fde12013-04-08 00:50:13 +00001117 if (GetPixelReadMask(image,tile_pixels) == 0)
cristy10a6c612012-01-29 21:41:05 +00001118 {
1119 tile_pixels-=width*GetPixelChannels(image);
1120 q+=GetPixelChannels(rotate_image);
1121 continue;
1122 }
cristya45d2ec2011-08-24 13:17:19 +00001123 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1124 {
cristy5a23c552013-02-13 14:34:28 +00001125 PixelChannel channel=GetPixelChannelChannel(image,i);
1126 PixelTrait traits=GetPixelChannelTraits(image,channel);
1127 PixelTrait rotate_traits=GetPixelChannelTraits(rotate_image,
1128 channel);
cristy010d7d12011-08-31 01:02:48 +00001129 if ((traits == UndefinedPixelTrait) ||
1130 (rotate_traits == UndefinedPixelTrait))
cristya45d2ec2011-08-24 13:17:19 +00001131 continue;
cristy0beccfa2011-09-25 20:47:53 +00001132 SetPixelChannel(rotate_image,channel,tile_pixels[i],q);
cristya45d2ec2011-08-24 13:17:19 +00001133 }
cristyed231572011-07-14 02:18:59 +00001134 tile_pixels-=width*GetPixelChannels(image);
1135 q+=GetPixelChannels(rotate_image);
cristy3ed852e2009-09-05 21:47:34 +00001136 }
cristy3ed852e2009-09-05 21:47:34 +00001137 sync=SyncCacheViewAuthenticPixels(rotate_view,exception);
1138 if (sync == MagickFalse)
1139 status=MagickFalse;
1140 }
1141 }
1142 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1143 {
1144 MagickBooleanType
1145 proceed;
1146
cristy26b64912012-12-16 18:20:09 +00001147#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy9a5a52f2012-10-09 14:40:31 +00001148 #pragma omp critical (MagickCore_IntegralRotateImage)
1149#endif
cristyb32b90a2009-09-07 21:45:48 +00001150 proceed=SetImageProgress(image,RotateImageTag,progress+=tile_height,
cristy3ed852e2009-09-05 21:47:34 +00001151 image->rows);
1152 if (proceed == MagickFalse)
1153 status=MagickFalse;
1154 }
1155 }
cristyecc2c142010-01-17 22:25:46 +00001156 (void) SetImageProgress(image,RotateImageTag,(MagickOffsetType)
1157 image->rows-1,image->rows);
cristy3ed852e2009-09-05 21:47:34 +00001158 Swap(page.width,page.height);
1159 Swap(page.x,page.y);
1160 if (page.width != 0)
cristybb503372010-05-27 20:51:26 +00001161 page.x=(ssize_t) (page.width-rotate_image->columns-page.x);
cristy3ed852e2009-09-05 21:47:34 +00001162 break;
1163 }
1164 case 2:
1165 {
1166 /*
1167 Rotate 180 degrees.
1168 */
cristy5f890612012-12-16 23:34:05 +00001169#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy64c10cd2013-01-06 18:40:43 +00001170 #pragma omp parallel for schedule(static,4) shared(status) \
cristyd6432472013-01-06 16:56:13 +00001171 magick_threads(image,image,1,1)
cristy5f890612012-12-16 23:34:05 +00001172#endif
cristybb503372010-05-27 20:51:26 +00001173 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001174 {
1175 MagickBooleanType
1176 sync;
1177
cristy4c08aed2011-07-01 19:47:50 +00001178 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00001179 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00001180
cristy4c08aed2011-07-01 19:47:50 +00001181 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00001182 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001183
cristy2be50602011-10-09 01:28:13 +00001184 register ssize_t
1185 x;
1186
cristy3ed852e2009-09-05 21:47:34 +00001187 if (status == MagickFalse)
1188 continue;
cristya45d2ec2011-08-24 13:17:19 +00001189 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
1190 q=QueueCacheViewAuthenticPixels(rotate_view,0,(ssize_t) (image->rows-y-
1191 1),image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00001192 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00001193 {
1194 status=MagickFalse;
1195 continue;
1196 }
cristyed231572011-07-14 02:18:59 +00001197 q+=GetPixelChannels(rotate_image)*image->columns;
cristybb503372010-05-27 20:51:26 +00001198 for (x=0; x < (ssize_t) image->columns; x++)
cristy4c08aed2011-07-01 19:47:50 +00001199 {
cristya45d2ec2011-08-24 13:17:19 +00001200 register ssize_t
1201 i;
1202
cristyed231572011-07-14 02:18:59 +00001203 q-=GetPixelChannels(rotate_image);
cristy883fde12013-04-08 00:50:13 +00001204 if (GetPixelReadMask(image,p) == 0)
cristy10a6c612012-01-29 21:41:05 +00001205 {
1206 p+=GetPixelChannels(image);
1207 continue;
1208 }
cristya45d2ec2011-08-24 13:17:19 +00001209 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1210 {
cristy5a23c552013-02-13 14:34:28 +00001211 PixelChannel channel=GetPixelChannelChannel(image,i);
1212 PixelTrait traits=GetPixelChannelTraits(image,channel);
1213 PixelTrait rotate_traits=GetPixelChannelTraits(rotate_image,
1214 channel);
cristy010d7d12011-08-31 01:02:48 +00001215 if ((traits == UndefinedPixelTrait) ||
1216 (rotate_traits == UndefinedPixelTrait))
cristya45d2ec2011-08-24 13:17:19 +00001217 continue;
cristy0beccfa2011-09-25 20:47:53 +00001218 SetPixelChannel(rotate_image,channel,p[i],q);
cristya45d2ec2011-08-24 13:17:19 +00001219 }
cristyed231572011-07-14 02:18:59 +00001220 p+=GetPixelChannels(image);
cristy4c08aed2011-07-01 19:47:50 +00001221 }
cristy3ed852e2009-09-05 21:47:34 +00001222 sync=SyncCacheViewAuthenticPixels(rotate_view,exception);
1223 if (sync == MagickFalse)
1224 status=MagickFalse;
1225 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1226 {
1227 MagickBooleanType
1228 proceed;
1229
cristy5f890612012-12-16 23:34:05 +00001230#if defined(MAGICKCORE_OPENMP_SUPPORT)
1231 #pragma omp critical (MagickCore_IntegralRotateImage)
1232#endif
cristy0729beb2011-09-25 23:29:32 +00001233 proceed=SetImageProgress(image,RotateImageTag,progress++,
cristy21e03412011-09-25 22:39:35 +00001234 image->rows);
1235 if (proceed == MagickFalse)
1236 status=MagickFalse;
1237 }
1238 }
1239 (void) SetImageProgress(image,RotateImageTag,(MagickOffsetType)
1240 image->rows-1,image->rows);
1241 Swap(page.width,page.height);
1242 Swap(page.x,page.y);
1243 if (page.width != 0)
1244 page.x=(ssize_t) (page.width-rotate_image->columns-page.x);
1245 break;
1246 }
cristy3ed852e2009-09-05 21:47:34 +00001247 case 3:
1248 {
cristybb503372010-05-27 20:51:26 +00001249 size_t
cristyb32b90a2009-09-07 21:45:48 +00001250 tile_height,
1251 tile_width;
1252
cristyd40deb62011-03-09 00:52:27 +00001253 ssize_t
1254 tile_y;
1255
cristy3ed852e2009-09-05 21:47:34 +00001256 /*
1257 Rotate 270 degrees.
1258 */
cristyb32b90a2009-09-07 21:45:48 +00001259 GetPixelCacheTileSize(image,&tile_width,&tile_height);
cristy8b8148b2012-10-07 00:41:15 +00001260 tile_width=image->columns;
cristy26b64912012-12-16 18:20:09 +00001261#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy64c10cd2013-01-06 18:40:43 +00001262 #pragma omp parallel for schedule(static,4) shared(status) \
cristycb7dfcc2013-01-06 18:34:59 +00001263 magick_threads(image,image,1,1)
cristy9a5a52f2012-10-09 14:40:31 +00001264#endif
cristyad11aac2011-09-25 21:29:16 +00001265 for (tile_y=0; tile_y < (ssize_t) image->rows; tile_y+=(ssize_t) tile_height)
cristy3ed852e2009-09-05 21:47:34 +00001266 {
cristybb503372010-05-27 20:51:26 +00001267 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001268 tile_x;
1269
1270 if (status == MagickFalse)
1271 continue;
cristy26295322011-09-22 00:50:36 +00001272 tile_x=0;
1273 for ( ; tile_x < (ssize_t) image->columns; tile_x+=(ssize_t) tile_width)
cristy3ed852e2009-09-05 21:47:34 +00001274 {
1275 MagickBooleanType
1276 sync;
1277
cristy4c08aed2011-07-01 19:47:50 +00001278 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00001279 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00001280
cristy4c08aed2011-07-01 19:47:50 +00001281 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00001282 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001283
cristy2be50602011-10-09 01:28:13 +00001284 register ssize_t
1285 y;
1286
cristybb503372010-05-27 20:51:26 +00001287 size_t
cristyb32b90a2009-09-07 21:45:48 +00001288 height,
1289 width;
cristy3ed852e2009-09-05 21:47:34 +00001290
cristyb32b90a2009-09-07 21:45:48 +00001291 width=tile_width;
cristybb503372010-05-27 20:51:26 +00001292 if ((tile_x+(ssize_t) tile_width) > (ssize_t) image->columns)
cristya45d2ec2011-08-24 13:17:19 +00001293 width=(size_t) (tile_width-(tile_x+tile_width-image->columns));
cristyb32b90a2009-09-07 21:45:48 +00001294 height=tile_height;
cristybb503372010-05-27 20:51:26 +00001295 if ((tile_y+(ssize_t) tile_height) > (ssize_t) image->rows)
cristya45d2ec2011-08-24 13:17:19 +00001296 height=(size_t) (tile_height-(tile_y+tile_height-image->rows));
1297 p=GetCacheViewVirtualPixels(image_view,tile_x,tile_y,width,height,
1298 exception);
cristy4c08aed2011-07-01 19:47:50 +00001299 if (p == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001300 {
1301 status=MagickFalse;
1302 break;
1303 }
cristybb503372010-05-27 20:51:26 +00001304 for (y=0; y < (ssize_t) width; y++)
cristy3ed852e2009-09-05 21:47:34 +00001305 {
cristy4c08aed2011-07-01 19:47:50 +00001306 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00001307 *restrict tile_pixels;
cristy3ed852e2009-09-05 21:47:34 +00001308
cristybb503372010-05-27 20:51:26 +00001309 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001310 x;
1311
cristy27709ef2011-09-18 02:53:53 +00001312 if (status == MagickFalse)
1313 continue;
cristya45d2ec2011-08-24 13:17:19 +00001314 q=QueueCacheViewAuthenticPixels(rotate_view,tile_y,(ssize_t) (y+
1315 rotate_image->rows-(tile_x+width)),height,1,exception);
cristyacd2ed22011-08-30 01:44:23 +00001316 if (q == (Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001317 {
1318 status=MagickFalse;
cristy27709ef2011-09-18 02:53:53 +00001319 continue;
cristy3ed852e2009-09-05 21:47:34 +00001320 }
cristyed231572011-07-14 02:18:59 +00001321 tile_pixels=p+((width-1)-y)*GetPixelChannels(image);
cristybb503372010-05-27 20:51:26 +00001322 for (x=0; x < (ssize_t) height; x++)
cristy3ed852e2009-09-05 21:47:34 +00001323 {
cristya45d2ec2011-08-24 13:17:19 +00001324 register ssize_t
1325 i;
1326
cristy883fde12013-04-08 00:50:13 +00001327 if (GetPixelReadMask(image,tile_pixels) == 0)
cristy10a6c612012-01-29 21:41:05 +00001328 {
1329 tile_pixels+=width*GetPixelChannels(image);
1330 q+=GetPixelChannels(rotate_image);
1331 continue;
1332 }
cristya45d2ec2011-08-24 13:17:19 +00001333 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1334 {
cristy5a23c552013-02-13 14:34:28 +00001335 PixelChannel channel=GetPixelChannelChannel(image,i);
1336 PixelTrait traits=GetPixelChannelTraits(image,channel);
1337 PixelTrait rotate_traits=GetPixelChannelTraits(rotate_image,
1338 channel);
cristy010d7d12011-08-31 01:02:48 +00001339 if ((traits == UndefinedPixelTrait) ||
1340 (rotate_traits == UndefinedPixelTrait))
cristya45d2ec2011-08-24 13:17:19 +00001341 continue;
cristy0beccfa2011-09-25 20:47:53 +00001342 SetPixelChannel(rotate_image,channel,tile_pixels[i],q);
cristya45d2ec2011-08-24 13:17:19 +00001343 }
cristyed231572011-07-14 02:18:59 +00001344 tile_pixels+=width*GetPixelChannels(image);
1345 q+=GetPixelChannels(rotate_image);
cristy3ed852e2009-09-05 21:47:34 +00001346 }
cristy26b64912012-12-16 18:20:09 +00001347#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy9a5a52f2012-10-09 14:40:31 +00001348 #pragma omp critical (MagickCore_IntegralRotateImage)
1349#endif
cristy3ed852e2009-09-05 21:47:34 +00001350 sync=SyncCacheViewAuthenticPixels(rotate_view,exception);
1351 if (sync == MagickFalse)
1352 status=MagickFalse;
1353 }
1354 }
1355 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1356 {
1357 MagickBooleanType
1358 proceed;
1359
cristy21e03412011-09-25 22:39:35 +00001360 proceed=SetImageProgress(image,RotateImageTag,progress+=tile_height,
1361 image->rows);
1362 if (proceed == MagickFalse)
1363 status=MagickFalse;
1364 }
1365 }
1366 (void) SetImageProgress(image,RotateImageTag,(MagickOffsetType)
1367 image->rows-1,image->rows);
1368 Swap(page.width,page.height);
1369 Swap(page.x,page.y);
1370 if (page.width != 0)
1371 page.x=(ssize_t) (page.width-rotate_image->columns-page.x);
1372 break;
1373 }
cristy3ed852e2009-09-05 21:47:34 +00001374 }
1375 rotate_view=DestroyCacheView(rotate_view);
1376 image_view=DestroyCacheView(image_view);
1377 rotate_image->type=image->type;
1378 rotate_image->page=page;
1379 if (status == MagickFalse)
1380 rotate_image=DestroyImage(rotate_image);
1381 return(rotate_image);
1382}
1383
1384/*
1385%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1386% %
1387% %
1388% %
1389+ X S h e a r I m a g e %
1390% %
1391% %
1392% %
1393%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1394%
1395% XShearImage() shears the image in the X direction with a shear angle of
1396% 'degrees'. Positive angles shear counter-clockwise (right-hand rule), and
1397% negative angles shear clockwise. Angles are measured relative to a vertical
1398% Y-axis. X shears will widen an image creating 'empty' triangles on the left
1399% and right sides of the source image.
1400%
1401% The format of the XShearImage method is:
1402%
cristya19f1d72012-08-07 18:24:38 +00001403% MagickBooleanType XShearImage(Image *image,const double degrees,
cristybb503372010-05-27 20:51:26 +00001404% const size_t width,const size_t height,
1405% const ssize_t x_offset,const ssize_t y_offset,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001406%
1407% A description of each parameter follows.
1408%
1409% o image: the image.
1410%
cristya19f1d72012-08-07 18:24:38 +00001411% o degrees: A double representing the shearing angle along the X
cristy3ed852e2009-09-05 21:47:34 +00001412% axis.
1413%
1414% o width, height, x_offset, y_offset: Defines a region of the image
1415% to shear.
1416%
cristyecc2c142010-01-17 22:25:46 +00001417% o exception: return any errors or warnings in this structure.
1418%
cristy3ed852e2009-09-05 21:47:34 +00001419*/
cristya19f1d72012-08-07 18:24:38 +00001420static MagickBooleanType XShearImage(Image *image,const double degrees,
cristybb503372010-05-27 20:51:26 +00001421 const size_t width,const size_t height,const ssize_t x_offset,
1422 const ssize_t y_offset,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001423{
1424#define XShearImageTag "XShear/Image"
1425
1426 typedef enum
1427 {
1428 LEFT,
1429 RIGHT
1430 } ShearDirection;
1431
1432 CacheView
1433 *image_view;
1434
cristy3ed852e2009-09-05 21:47:34 +00001435 MagickBooleanType
1436 status;
1437
cristy5f959472010-05-27 22:19:46 +00001438 MagickOffsetType
1439 progress;
1440
cristy4c08aed2011-07-01 19:47:50 +00001441 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00001442 background;
1443
cristy5f959472010-05-27 22:19:46 +00001444 ssize_t
1445 y;
1446
cristy9d8c8ce2011-10-25 16:13:52 +00001447 /*
1448 X shear image.
1449 */
cristy3ed852e2009-09-05 21:47:34 +00001450 assert(image != (Image *) NULL);
1451 assert(image->signature == MagickSignature);
1452 if (image->debug != MagickFalse)
1453 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
cristy3ed852e2009-09-05 21:47:34 +00001454 status=MagickTrue;
cristy9d8c8ce2011-10-25 16:13:52 +00001455 background=image->background_color;
cristy3ed852e2009-09-05 21:47:34 +00001456 progress=0;
cristy46ff2672012-12-14 15:32:26 +00001457 image_view=AcquireAuthenticCacheView(image,exception);
cristyb5d5f722009-11-04 03:03:49 +00001458#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +00001459 #pragma omp parallel for schedule(static,4) shared(progress,status) \
cristy5e6b2592012-12-19 14:08:11 +00001460 magick_threads(image,image,height,1)
cristy3ed852e2009-09-05 21:47:34 +00001461#endif
cristybb503372010-05-27 20:51:26 +00001462 for (y=0; y < (ssize_t) height; y++)
cristy3ed852e2009-09-05 21:47:34 +00001463 {
cristy4c08aed2011-07-01 19:47:50 +00001464 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00001465 pixel,
1466 source,
1467 destination;
1468
cristya19f1d72012-08-07 18:24:38 +00001469 double
cristy3ed852e2009-09-05 21:47:34 +00001470 area,
1471 displacement;
1472
cristy4c08aed2011-07-01 19:47:50 +00001473 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00001474 *restrict p,
1475 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001476
cristyd40deb62011-03-09 00:52:27 +00001477 register ssize_t
1478 i;
1479
cristy3ed852e2009-09-05 21:47:34 +00001480 ShearDirection
1481 direction;
1482
cristyd40deb62011-03-09 00:52:27 +00001483 ssize_t
1484 step;
1485
cristy3ed852e2009-09-05 21:47:34 +00001486 if (status == MagickFalse)
1487 continue;
1488 p=GetCacheViewAuthenticPixels(image_view,0,y_offset+y,image->columns,1,
1489 exception);
cristy4c08aed2011-07-01 19:47:50 +00001490 if (p == (Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001491 {
1492 status=MagickFalse;
1493 continue;
1494 }
cristyed231572011-07-14 02:18:59 +00001495 p+=x_offset*GetPixelChannels(image);
cristya19f1d72012-08-07 18:24:38 +00001496 displacement=degrees*(double) (y-height/2.0);
cristy3ed852e2009-09-05 21:47:34 +00001497 if (displacement == 0.0)
1498 continue;
1499 if (displacement > 0.0)
1500 direction=RIGHT;
1501 else
1502 {
1503 displacement*=(-1.0);
1504 direction=LEFT;
1505 }
cristybb503372010-05-27 20:51:26 +00001506 step=(ssize_t) floor((double) displacement);
cristya19f1d72012-08-07 18:24:38 +00001507 area=(double) (displacement-step);
cristy3ed852e2009-09-05 21:47:34 +00001508 step++;
1509 pixel=background;
cristy4c08aed2011-07-01 19:47:50 +00001510 GetPixelInfo(image,&source);
1511 GetPixelInfo(image,&destination);
cristy3ed852e2009-09-05 21:47:34 +00001512 switch (direction)
1513 {
1514 case LEFT:
1515 {
1516 /*
1517 Transfer pixels left-to-right.
1518 */
1519 if (step > x_offset)
1520 break;
cristyed231572011-07-14 02:18:59 +00001521 q=p-step*GetPixelChannels(image);
cristybb503372010-05-27 20:51:26 +00001522 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00001523 {
1524 if ((x_offset+i) < step)
1525 {
cristyed231572011-07-14 02:18:59 +00001526 p+=GetPixelChannels(image);
cristy803640d2011-11-17 02:11:32 +00001527 GetPixelInfoPixel(image,p,&pixel);
cristyed231572011-07-14 02:18:59 +00001528 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00001529 continue;
1530 }
cristy803640d2011-11-17 02:11:32 +00001531 GetPixelInfoPixel(image,p,&source);
cristya19f1d72012-08-07 18:24:38 +00001532 CompositePixelInfoAreaBlend(&pixel,(double) pixel.alpha,
1533 &source,(double) GetPixelAlpha(image,p),area,&destination);
cristy803640d2011-11-17 02:11:32 +00001534 SetPixelInfoPixel(image,&destination,q);
1535 GetPixelInfoPixel(image,p,&pixel);
cristyed231572011-07-14 02:18:59 +00001536 p+=GetPixelChannels(image);
1537 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00001538 }
cristya19f1d72012-08-07 18:24:38 +00001539 CompositePixelInfoAreaBlend(&pixel,(double) pixel.alpha,
1540 &background,(double) background.alpha,area,&destination);
cristy803640d2011-11-17 02:11:32 +00001541 SetPixelInfoPixel(image,&destination,q);
cristyed231572011-07-14 02:18:59 +00001542 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00001543 for (i=0; i < (step-1); i++)
cristy4c08aed2011-07-01 19:47:50 +00001544 {
cristy803640d2011-11-17 02:11:32 +00001545 SetPixelInfoPixel(image,&background,q);
cristyed231572011-07-14 02:18:59 +00001546 q+=GetPixelChannels(image);
cristy4c08aed2011-07-01 19:47:50 +00001547 }
cristy3ed852e2009-09-05 21:47:34 +00001548 break;
1549 }
1550 case RIGHT:
1551 {
1552 /*
1553 Transfer pixels right-to-left.
1554 */
cristyed231572011-07-14 02:18:59 +00001555 p+=width*GetPixelChannels(image);
1556 q=p+step*GetPixelChannels(image);
cristybb503372010-05-27 20:51:26 +00001557 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00001558 {
cristyed231572011-07-14 02:18:59 +00001559 p-=GetPixelChannels(image);
1560 q-=GetPixelChannels(image);
cristy5285ef62014-02-26 13:55:31 +00001561 if ((size_t) (x_offset+width+step-i) > image->columns)
cristy3ed852e2009-09-05 21:47:34 +00001562 continue;
cristy803640d2011-11-17 02:11:32 +00001563 GetPixelInfoPixel(image,p,&source);
cristya19f1d72012-08-07 18:24:38 +00001564 CompositePixelInfoAreaBlend(&pixel,(double) pixel.alpha,
1565 &source,(double) GetPixelAlpha(image,p),area,&destination);
cristy803640d2011-11-17 02:11:32 +00001566 SetPixelInfoPixel(image,&destination,q);
1567 GetPixelInfoPixel(image,p,&pixel);
cristy3ed852e2009-09-05 21:47:34 +00001568 }
cristya19f1d72012-08-07 18:24:38 +00001569 CompositePixelInfoAreaBlend(&pixel,(double) pixel.alpha,
1570 &background,(double) background.alpha,area,&destination);
cristyed231572011-07-14 02:18:59 +00001571 q-=GetPixelChannels(image);
cristy803640d2011-11-17 02:11:32 +00001572 SetPixelInfoPixel(image,&destination,q);
cristy3ed852e2009-09-05 21:47:34 +00001573 for (i=0; i < (step-1); i++)
cristy4c08aed2011-07-01 19:47:50 +00001574 {
cristyed231572011-07-14 02:18:59 +00001575 q-=GetPixelChannels(image);
cristy803640d2011-11-17 02:11:32 +00001576 SetPixelInfoPixel(image,&background,q);
cristy4c08aed2011-07-01 19:47:50 +00001577 }
cristy3ed852e2009-09-05 21:47:34 +00001578 break;
1579 }
1580 }
1581 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
1582 status=MagickFalse;
1583 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1584 {
1585 MagickBooleanType
1586 proceed;
1587
cristyb5d5f722009-11-04 03:03:49 +00001588#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy7b650b52011-10-09 01:13:39 +00001589 #pragma omp critical (MagickCore_XShearImage)
cristy3ed852e2009-09-05 21:47:34 +00001590#endif
1591 proceed=SetImageProgress(image,XShearImageTag,progress++,height);
1592 if (proceed == MagickFalse)
1593 status=MagickFalse;
1594 }
1595 }
1596 image_view=DestroyCacheView(image_view);
1597 return(status);
1598}
1599
1600/*
1601%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1602% %
1603% %
1604% %
1605+ Y S h e a r I m a g e %
1606% %
1607% %
1608% %
1609%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1610%
1611% YShearImage shears the image in the Y direction with a shear angle of
1612% 'degrees'. Positive angles shear counter-clockwise (right-hand rule), and
1613% negative angles shear clockwise. Angles are measured relative to a
1614% horizontal X-axis. Y shears will increase the height of an image creating
1615% 'empty' triangles on the top and bottom of the source image.
1616%
1617% The format of the YShearImage method is:
1618%
cristya19f1d72012-08-07 18:24:38 +00001619% MagickBooleanType YShearImage(Image *image,const double degrees,
cristybb503372010-05-27 20:51:26 +00001620% const size_t width,const size_t height,
1621% const ssize_t x_offset,const ssize_t y_offset,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001622%
1623% A description of each parameter follows.
1624%
1625% o image: the image.
1626%
cristya19f1d72012-08-07 18:24:38 +00001627% o degrees: A double representing the shearing angle along the Y
cristy3ed852e2009-09-05 21:47:34 +00001628% axis.
1629%
1630% o width, height, x_offset, y_offset: Defines a region of the image
1631% to shear.
1632%
cristyecc2c142010-01-17 22:25:46 +00001633% o exception: return any errors or warnings in this structure.
1634%
cristy3ed852e2009-09-05 21:47:34 +00001635*/
cristya19f1d72012-08-07 18:24:38 +00001636static MagickBooleanType YShearImage(Image *image,const double degrees,
cristybb503372010-05-27 20:51:26 +00001637 const size_t width,const size_t height,const ssize_t x_offset,
1638 const ssize_t y_offset,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001639{
1640#define YShearImageTag "YShear/Image"
1641
1642 typedef enum
1643 {
1644 UP,
1645 DOWN
1646 } ShearDirection;
1647
1648 CacheView
1649 *image_view;
1650
cristy3ed852e2009-09-05 21:47:34 +00001651 MagickBooleanType
1652 status;
1653
cristy5f959472010-05-27 22:19:46 +00001654 MagickOffsetType
1655 progress;
1656
cristy4c08aed2011-07-01 19:47:50 +00001657 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00001658 background;
1659
cristy5f959472010-05-27 22:19:46 +00001660 ssize_t
1661 x;
1662
cristy9d8c8ce2011-10-25 16:13:52 +00001663 /*
1664 Y Shear image.
1665 */
cristy3ed852e2009-09-05 21:47:34 +00001666 assert(image != (Image *) NULL);
1667 assert(image->signature == MagickSignature);
1668 if (image->debug != MagickFalse)
1669 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
cristy3ed852e2009-09-05 21:47:34 +00001670 status=MagickTrue;
1671 progress=0;
cristy9d8c8ce2011-10-25 16:13:52 +00001672 background=image->background_color;
cristy46ff2672012-12-14 15:32:26 +00001673 image_view=AcquireAuthenticCacheView(image,exception);
cristyb5d5f722009-11-04 03:03:49 +00001674#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +00001675 #pragma omp parallel for schedule(static,4) shared(progress,status) \
cristy5e6b2592012-12-19 14:08:11 +00001676 magick_threads(image,image,width,1)
cristy3ed852e2009-09-05 21:47:34 +00001677#endif
cristybb503372010-05-27 20:51:26 +00001678 for (x=0; x < (ssize_t) width; x++)
cristy3ed852e2009-09-05 21:47:34 +00001679 {
cristybb503372010-05-27 20:51:26 +00001680 ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001681 step;
1682
cristya19f1d72012-08-07 18:24:38 +00001683 double
cristy3ed852e2009-09-05 21:47:34 +00001684 area,
1685 displacement;
1686
cristy4c08aed2011-07-01 19:47:50 +00001687 PixelInfo
1688 pixel,
1689 source,
1690 destination;
1691
1692 register Quantum
1693 *restrict p,
1694 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001695
cristybb503372010-05-27 20:51:26 +00001696 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001697 i;
1698
cristy3ed852e2009-09-05 21:47:34 +00001699 ShearDirection
1700 direction;
1701
1702 if (status == MagickFalse)
1703 continue;
1704 p=GetCacheViewAuthenticPixels(image_view,x_offset+x,0,1,image->rows,
1705 exception);
cristy4c08aed2011-07-01 19:47:50 +00001706 if (p == (Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001707 {
1708 status=MagickFalse;
1709 continue;
1710 }
cristyed231572011-07-14 02:18:59 +00001711 p+=y_offset*GetPixelChannels(image);
cristya19f1d72012-08-07 18:24:38 +00001712 displacement=degrees*(double) (x-width/2.0);
cristy3ed852e2009-09-05 21:47:34 +00001713 if (displacement == 0.0)
1714 continue;
1715 if (displacement > 0.0)
1716 direction=DOWN;
1717 else
1718 {
1719 displacement*=(-1.0);
1720 direction=UP;
1721 }
cristybb503372010-05-27 20:51:26 +00001722 step=(ssize_t) floor((double) displacement);
cristya19f1d72012-08-07 18:24:38 +00001723 area=(double) (displacement-step);
cristy3ed852e2009-09-05 21:47:34 +00001724 step++;
1725 pixel=background;
cristy4c08aed2011-07-01 19:47:50 +00001726 GetPixelInfo(image,&source);
1727 GetPixelInfo(image,&destination);
cristy3ed852e2009-09-05 21:47:34 +00001728 switch (direction)
1729 {
1730 case UP:
1731 {
1732 /*
1733 Transfer pixels top-to-bottom.
1734 */
1735 if (step > y_offset)
1736 break;
cristyed231572011-07-14 02:18:59 +00001737 q=p-step*GetPixelChannels(image);
cristybb503372010-05-27 20:51:26 +00001738 for (i=0; i < (ssize_t) height; i++)
cristy3ed852e2009-09-05 21:47:34 +00001739 {
1740 if ((y_offset+i) < step)
1741 {
cristyed231572011-07-14 02:18:59 +00001742 p+=GetPixelChannels(image);
cristy803640d2011-11-17 02:11:32 +00001743 GetPixelInfoPixel(image,p,&pixel);
cristyed231572011-07-14 02:18:59 +00001744 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00001745 continue;
1746 }
cristy803640d2011-11-17 02:11:32 +00001747 GetPixelInfoPixel(image,p,&source);
cristya19f1d72012-08-07 18:24:38 +00001748 CompositePixelInfoAreaBlend(&pixel,(double) pixel.alpha,
1749 &source,(double) GetPixelAlpha(image,p),area,
cristy4c08aed2011-07-01 19:47:50 +00001750 &destination);
cristy803640d2011-11-17 02:11:32 +00001751 SetPixelInfoPixel(image,&destination,q);
1752 GetPixelInfoPixel(image,p,&pixel);
cristyed231572011-07-14 02:18:59 +00001753 p+=GetPixelChannels(image);
1754 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00001755 }
cristya19f1d72012-08-07 18:24:38 +00001756 CompositePixelInfoAreaBlend(&pixel,(double) pixel.alpha,
1757 &background,(double) background.alpha,area,&destination);
cristy803640d2011-11-17 02:11:32 +00001758 SetPixelInfoPixel(image,&destination,q);
cristyed231572011-07-14 02:18:59 +00001759 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00001760 for (i=0; i < (step-1); i++)
cristy4c08aed2011-07-01 19:47:50 +00001761 {
cristy803640d2011-11-17 02:11:32 +00001762 SetPixelInfoPixel(image,&background,q);
cristyed231572011-07-14 02:18:59 +00001763 q+=GetPixelChannels(image);
cristy4c08aed2011-07-01 19:47:50 +00001764 }
cristy3ed852e2009-09-05 21:47:34 +00001765 break;
1766 }
1767 case DOWN:
1768 {
1769 /*
1770 Transfer pixels bottom-to-top.
1771 */
cristyed231572011-07-14 02:18:59 +00001772 p+=height*GetPixelChannels(image);
1773 q=p+step*GetPixelChannels(image);
cristybb503372010-05-27 20:51:26 +00001774 for (i=0; i < (ssize_t) height; i++)
cristy3ed852e2009-09-05 21:47:34 +00001775 {
cristyed231572011-07-14 02:18:59 +00001776 p-=GetPixelChannels(image);
1777 q-=GetPixelChannels(image);
cristy5285ef62014-02-26 13:55:31 +00001778 if ((size_t) (y_offset+height+step-i) > image->rows)
cristy3ed852e2009-09-05 21:47:34 +00001779 continue;
cristy803640d2011-11-17 02:11:32 +00001780 GetPixelInfoPixel(image,p,&source);
cristya19f1d72012-08-07 18:24:38 +00001781 CompositePixelInfoAreaBlend(&pixel,(double) pixel.alpha,
1782 &source,(double) GetPixelAlpha(image,p),area,
cristy4c08aed2011-07-01 19:47:50 +00001783 &destination);
cristy803640d2011-11-17 02:11:32 +00001784 SetPixelInfoPixel(image,&destination,q);
1785 GetPixelInfoPixel(image,p,&pixel);
cristy3ed852e2009-09-05 21:47:34 +00001786 }
cristya19f1d72012-08-07 18:24:38 +00001787 CompositePixelInfoAreaBlend(&pixel,(double) pixel.alpha,
1788 &background,(double) background.alpha,area,&destination);
cristyed231572011-07-14 02:18:59 +00001789 q-=GetPixelChannels(image);
cristy803640d2011-11-17 02:11:32 +00001790 SetPixelInfoPixel(image,&destination,q);
cristy3ed852e2009-09-05 21:47:34 +00001791 for (i=0; i < (step-1); i++)
cristy4c08aed2011-07-01 19:47:50 +00001792 {
cristyed231572011-07-14 02:18:59 +00001793 q-=GetPixelChannels(image);
cristy803640d2011-11-17 02:11:32 +00001794 SetPixelInfoPixel(image,&background,q);
cristy4c08aed2011-07-01 19:47:50 +00001795 }
cristy3ed852e2009-09-05 21:47:34 +00001796 break;
1797 }
1798 }
1799 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
1800 status=MagickFalse;
1801 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1802 {
1803 MagickBooleanType
1804 proceed;
1805
cristyb5d5f722009-11-04 03:03:49 +00001806#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy7b650b52011-10-09 01:13:39 +00001807 #pragma omp critical (MagickCore_YShearImage)
cristy3ed852e2009-09-05 21:47:34 +00001808#endif
1809 proceed=SetImageProgress(image,YShearImageTag,progress++,image->rows);
1810 if (proceed == MagickFalse)
1811 status=MagickFalse;
1812 }
1813 }
1814 image_view=DestroyCacheView(image_view);
1815 return(status);
1816}
1817
1818/*
1819%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1820% %
1821% %
1822% %
cristy3ed852e2009-09-05 21:47:34 +00001823% S h e a r I m a g e %
1824% %
1825% %
1826% %
1827%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1828%
1829% ShearImage() creates a new image that is a shear_image copy of an existing
cristycee97112010-05-28 00:44:52 +00001830% one. Shearing slides one edge of an image along the X or Y axis, creating
1831% a parallelogram. An X direction shear slides an edge along the X axis,
1832% while a Y direction shear slides an edge along the Y axis. The amount of
cristy3ed852e2009-09-05 21:47:34 +00001833% the shear is controlled by a shear angle. For X direction shears, x_shear
1834% is measured relative to the Y axis, and similarly, for Y direction shears
1835% y_shear is measured relative to the X axis. Empty triangles left over from
1836% shearing the image are filled with the background color defined by member
1837% 'background_color' of the image.. ShearImage() allocates the memory
1838% necessary for the new Image structure and returns a pointer to the new image.
1839%
1840% ShearImage() is based on the paper "A Fast Algorithm for General Raster
1841% Rotatation" by Alan W. Paeth.
1842%
1843% The format of the ShearImage method is:
1844%
1845% Image *ShearImage(const Image *image,const double x_shear,
1846% const double y_shear,ExceptionInfo *exception)
1847%
1848% A description of each parameter follows.
1849%
1850% o image: the image.
1851%
1852% o x_shear, y_shear: Specifies the number of degrees to shear the image.
1853%
1854% o exception: return any errors or warnings in this structure.
1855%
1856*/
1857MagickExport Image *ShearImage(const Image *image,const double x_shear,
1858 const double y_shear,ExceptionInfo *exception)
1859{
1860 Image
1861 *integral_image,
1862 *shear_image;
1863
cristyecc2c142010-01-17 22:25:46 +00001864 MagickBooleanType
1865 status;
1866
cristy3ed852e2009-09-05 21:47:34 +00001867 PointInfo
1868 shear;
1869
1870 RectangleInfo
cristy5285ef62014-02-26 13:55:31 +00001871 border_info,
1872 bounds;
cristy3ed852e2009-09-05 21:47:34 +00001873
1874 assert(image != (Image *) NULL);
1875 assert(image->signature == MagickSignature);
1876 if (image->debug != MagickFalse)
1877 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1878 assert(exception != (ExceptionInfo *) NULL);
1879 assert(exception->signature == MagickSignature);
1880 if ((x_shear != 0.0) && (fmod(x_shear,90.0) == 0.0))
1881 ThrowImageException(ImageError,"AngleIsDiscontinuous");
1882 if ((y_shear != 0.0) && (fmod(y_shear,90.0) == 0.0))
1883 ThrowImageException(ImageError,"AngleIsDiscontinuous");
1884 /*
1885 Initialize shear angle.
1886 */
1887 integral_image=CloneImage(image,0,0,MagickTrue,exception);
1888 if (integral_image == (Image *) NULL)
1889 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
1890 shear.x=(-tan(DegreesToRadians(fmod(x_shear,360.0))));
1891 shear.y=tan(DegreesToRadians(fmod(y_shear,360.0)));
1892 if ((shear.x == 0.0) && (shear.y == 0.0))
1893 return(integral_image);
cristy574cc262011-08-05 01:23:58 +00001894 if (SetImageStorageClass(integral_image,DirectClass,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00001895 {
cristy3ed852e2009-09-05 21:47:34 +00001896 integral_image=DestroyImage(integral_image);
1897 return(integral_image);
1898 }
cristy8a46d822012-08-28 23:32:39 +00001899 if (integral_image->alpha_trait != BlendPixelTrait)
cristy63240882011-08-05 19:05:27 +00001900 (void) SetImageAlphaChannel(integral_image,OpaqueAlphaChannel,exception);
cristy3ed852e2009-09-05 21:47:34 +00001901 /*
1902 Compute image size.
1903 */
cristy5285ef62014-02-26 13:55:31 +00001904 bounds.width=image->columns+(ssize_t) floor(fabs(shear.x)*image->rows+0.5);
1905 bounds.x=(ssize_t) ceil((double) image->columns+((fabs(shear.x)*image->rows)-
cristy06609ee2010-03-17 20:21:27 +00001906 image->columns)/2.0-0.5);
cristy5285ef62014-02-26 13:55:31 +00001907 bounds.y=(ssize_t) ceil((double) image->rows+((fabs(shear.y)*bounds.width)-
cristycee97112010-05-28 00:44:52 +00001908 image->rows)/2.0-0.5);
cristy3ed852e2009-09-05 21:47:34 +00001909 /*
1910 Surround image with border.
1911 */
1912 integral_image->border_color=integral_image->background_color;
1913 integral_image->compose=CopyCompositeOp;
cristy5285ef62014-02-26 13:55:31 +00001914 border_info.width=(size_t) bounds.x;
1915 border_info.height=(size_t) bounds.y;
cristy633f0c62011-09-15 13:27:36 +00001916 shear_image=BorderImage(integral_image,&border_info,image->compose,exception);
cristyecc2c142010-01-17 22:25:46 +00001917 integral_image=DestroyImage(integral_image);
cristy3ed852e2009-09-05 21:47:34 +00001918 if (shear_image == (Image *) NULL)
1919 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristy3ed852e2009-09-05 21:47:34 +00001920 /*
1921 Shear the image.
1922 */
cristy8a46d822012-08-28 23:32:39 +00001923 if (shear_image->alpha_trait != BlendPixelTrait)
cristy63240882011-08-05 19:05:27 +00001924 (void) SetImageAlphaChannel(shear_image,OpaqueAlphaChannel,exception);
cristy5285ef62014-02-26 13:55:31 +00001925 status=XShearImage(shear_image,shear.x,image->columns,image->rows,bounds.x,
cristyeaedf062010-05-29 22:36:02 +00001926 (ssize_t) (shear_image->rows-image->rows)/2,exception);
cristyecc2c142010-01-17 22:25:46 +00001927 if (status == MagickFalse)
1928 {
1929 shear_image=DestroyImage(shear_image);
1930 return((Image *) NULL);
1931 }
cristy5285ef62014-02-26 13:55:31 +00001932 status=YShearImage(shear_image,shear.y,bounds.width,image->rows,(ssize_t)
1933 (shear_image->columns-bounds.width)/2,bounds.y,exception);
cristyecc2c142010-01-17 22:25:46 +00001934 if (status == MagickFalse)
1935 {
1936 shear_image=DestroyImage(shear_image);
1937 return((Image *) NULL);
1938 }
cristy5285ef62014-02-26 13:55:31 +00001939 status=CropToFitImage(&shear_image,shear.x,shear.y,(MagickRealType)
1940 image->columns,(MagickRealType) image->rows,MagickFalse,exception);
cristy3ed852e2009-09-05 21:47:34 +00001941 shear_image->compose=image->compose;
1942 shear_image->page.width=0;
1943 shear_image->page.height=0;
cristy1c2f48d2012-12-14 01:20:55 +00001944 if (status == MagickFalse)
1945 shear_image=DestroyImage(shear_image);
cristy3ed852e2009-09-05 21:47:34 +00001946 return(shear_image);
1947}
cristyc7c81142011-11-07 12:14:23 +00001948
1949/*
1950%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1951% %
1952% %
1953% %
1954% S h e a r R o t a t e I m a g e %
1955% %
1956% %
1957% %
1958%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1959%
1960% ShearRotateImage() creates a new image that is a rotated copy of an existing
1961% one. Positive angles rotate counter-clockwise (right-hand rule), while
1962% negative angles rotate clockwise. Rotated images are usually larger than
1963% the originals and have 'empty' triangular corners. X axis. Empty
1964% triangles left over from shearing the image are filled with the background
1965% color defined by member 'background_color' of the image. ShearRotateImage
1966% allocates the memory necessary for the new Image structure and returns a
1967% pointer to the new image.
1968%
1969% ShearRotateImage() is based on the paper "A Fast Algorithm for General
1970% Raster Rotatation" by Alan W. Paeth. ShearRotateImage is adapted from a
1971% similar method based on the Paeth paper written by Michael Halle of the
1972% Spatial Imaging Group, MIT Media Lab.
1973%
1974% The format of the ShearRotateImage method is:
1975%
1976% Image *ShearRotateImage(const Image *image,const double degrees,
1977% ExceptionInfo *exception)
1978%
1979% A description of each parameter follows.
1980%
1981% o image: the image.
1982%
1983% o degrees: Specifies the number of degrees to rotate the image.
1984%
1985% o exception: return any errors or warnings in this structure.
1986%
1987*/
1988MagickExport Image *ShearRotateImage(const Image *image,const double degrees,
1989 ExceptionInfo *exception)
1990{
1991 Image
1992 *integral_image,
1993 *rotate_image;
1994
1995 MagickBooleanType
1996 status;
1997
cristy5285ef62014-02-26 13:55:31 +00001998 MagickRealType
cristyc7c81142011-11-07 12:14:23 +00001999 angle;
2000
2001 PointInfo
2002 shear;
2003
2004 RectangleInfo
cristy5285ef62014-02-26 13:55:31 +00002005 border_info,
2006 bounds;
cristyc7c81142011-11-07 12:14:23 +00002007
2008 size_t
2009 height,
2010 rotations,
cristy5285ef62014-02-26 13:55:31 +00002011 shear_width,
2012 width;
cristyc7c81142011-11-07 12:14:23 +00002013
2014 /*
2015 Adjust rotation angle.
2016 */
2017 assert(image != (Image *) NULL);
2018 assert(image->signature == MagickSignature);
2019 if (image->debug != MagickFalse)
2020 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2021 assert(exception != (ExceptionInfo *) NULL);
2022 assert(exception->signature == MagickSignature);
2023 angle=degrees;
2024 while (angle < -45.0)
2025 angle+=360.0;
2026 for (rotations=0; angle > 45.0; rotations++)
2027 angle-=90.0;
2028 rotations%=4;
2029 /*
2030 Calculate shear equations.
2031 */
2032 integral_image=IntegralRotateImage(image,rotations,exception);
2033 if (integral_image == (Image *) NULL)
2034 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
2035 shear.x=(-tan((double) DegreesToRadians(angle)/2.0));
2036 shear.y=sin((double) DegreesToRadians(angle));
2037 if ((shear.x == 0.0) && (shear.y == 0.0))
2038 return(integral_image);
2039 if (SetImageStorageClass(integral_image,DirectClass,exception) == MagickFalse)
2040 {
2041 integral_image=DestroyImage(integral_image);
2042 return(integral_image);
2043 }
cristy8a46d822012-08-28 23:32:39 +00002044 if (integral_image->alpha_trait != BlendPixelTrait)
cristyc7c81142011-11-07 12:14:23 +00002045 (void) SetImageAlphaChannel(integral_image,OpaqueAlphaChannel,exception);
2046 /*
cristy5285ef62014-02-26 13:55:31 +00002047 Compute maximum bounds for 3 shear operations.
cristyc7c81142011-11-07 12:14:23 +00002048 */
cristy5285ef62014-02-26 13:55:31 +00002049 width=integral_image->columns;
2050 height=integral_image->rows;
2051 bounds.width=(size_t) floor(fabs((double) height*shear.x)+width+0.5);
2052 bounds.height=(size_t) floor(fabs((double) bounds.width*shear.y)+height+0.5);
2053 shear_width=(size_t) floor(fabs((double) bounds.height*shear.x)+
2054 bounds.width+0.5);
2055 bounds.x=(ssize_t) floor((double) ((shear_width > bounds.width) ? width :
2056 bounds.width-shear_width+2)/2.0+0.5);
2057 bounds.y=(ssize_t) floor(((double) bounds.height-height+2)/2.0+0.5);
cristyc7c81142011-11-07 12:14:23 +00002058 /*
2059 Surround image with a border.
2060 */
2061 integral_image->border_color=integral_image->background_color;
2062 integral_image->compose=CopyCompositeOp;
cristy5285ef62014-02-26 13:55:31 +00002063 border_info.width=(size_t) bounds.x;
2064 border_info.height=(size_t) bounds.y;
cristyc7c81142011-11-07 12:14:23 +00002065 rotate_image=BorderImage(integral_image,&border_info,image->compose,
2066 exception);
2067 integral_image=DestroyImage(integral_image);
2068 if (rotate_image == (Image *) NULL)
2069 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
2070 /*
2071 Rotate the image.
2072 */
cristy5285ef62014-02-26 13:55:31 +00002073 status=XShearImage(rotate_image,shear.x,width,height,bounds.x,(ssize_t)
cristyc7c81142011-11-07 12:14:23 +00002074 (rotate_image->rows-height)/2,exception);
2075 if (status == MagickFalse)
2076 {
2077 rotate_image=DestroyImage(rotate_image);
2078 return((Image *) NULL);
2079 }
cristy5285ef62014-02-26 13:55:31 +00002080 status=YShearImage(rotate_image,shear.y,bounds.width,height,(ssize_t)
2081 (rotate_image->columns-bounds.width)/2,bounds.y,exception);
cristyc7c81142011-11-07 12:14:23 +00002082 if (status == MagickFalse)
2083 {
2084 rotate_image=DestroyImage(rotate_image);
2085 return((Image *) NULL);
2086 }
cristy5285ef62014-02-26 13:55:31 +00002087 status=XShearImage(rotate_image,shear.x,bounds.width,bounds.height,(ssize_t)
2088 (rotate_image->columns-bounds.width)/2,(ssize_t) (rotate_image->rows-
2089 bounds.height)/2,exception);
cristyc7c81142011-11-07 12:14:23 +00002090 if (status == MagickFalse)
2091 {
2092 rotate_image=DestroyImage(rotate_image);
2093 return((Image *) NULL);
2094 }
cristy5285ef62014-02-26 13:55:31 +00002095 status=CropToFitImage(&rotate_image,shear.x,shear.y,(MagickRealType) width,
2096 (MagickRealType) height,MagickTrue,exception);
cristyc7c81142011-11-07 12:14:23 +00002097 rotate_image->compose=image->compose;
2098 rotate_image->page.width=0;
2099 rotate_image->page.height=0;
cristy1c2f48d2012-12-14 01:20:55 +00002100 if (status == MagickFalse)
2101 rotate_image=DestroyImage(rotate_image);
cristyc7c81142011-11-07 12:14:23 +00002102 return(rotate_image);
2103}