1 /*
2 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3 % %
4 % %
5 % %
6 % SSSSS H H EEEEE AAA RRRR %
7 % SS H H E A A R R %
8 % SSS HHHHH EEE AAAAA RRRR %
9 % SS H H E A A R R %
10 % SSSSS H H EEEEE A A R R %
11 % %
12 % %
13 % MagickCore Methods to Shear or Rotate an Image by an Arbitrary Angle %
14 % %
15 % Software Design %
16 % Cristy %
17 % July 1992 %
18 % %
19 % %
20 % Copyright 1999-2016 ImageMagick Studio LLC, a non-profit organization %
21 % dedicated to making software imaging solutions freely available. %
22 % %
23 % You may not use this file except in compliance with the License. You may %
24 % obtain a copy of the License at %
25 % %
26 % http://www.imagemagick.org/script/license.php %
27 % %
28 % Unless required by applicable law or agreed to in writing, software %
29 % distributed under the License is distributed on an "AS IS" BASIS, %
30 % WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %
31 % See the License for the specific language governing permissions and %
32 % limitations under the License. %
33 % %
34 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
35 %
36 % The XShearImage() and YShearImage() methods are based on the paper "A Fast
37 % Algorithm for General Raster Rotatation" by Alan W. Paeth, Graphics
38 % Interface '86 (Vancouver). ShearRotateImage() is adapted from a similar
39 % method based on the Paeth paper written by Michael Halle of the Spatial
40 % Imaging Group, MIT Media Lab.
41 %
42 */
43
44 /*
45 Include declarations.
46 */
47 #include "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"
52 #include "MagickCore/channel.h"
53 #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/matrix.h"
67 #include "MagickCore/memory_.h"
68 #include "MagickCore/list.h"
69 #include "MagickCore/monitor.h"
70 #include "MagickCore/monitor-private.h"
71 #include "MagickCore/nt-base-private.h"
72 #include "MagickCore/pixel-accessor.h"
73 #include "MagickCore/quantum.h"
74 #include "MagickCore/resource_.h"
75 #include "MagickCore/shear.h"
76 #include "MagickCore/statistic.h"
77 #include "MagickCore/string_.h"
78 #include "MagickCore/string-private.h"
79 #include "MagickCore/thread-private.h"
80 #include "MagickCore/threshold.h"
81 #include "MagickCore/transform.h"
82
83 /*
84 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
85 % %
86 % %
87 % %
88 + C r o p T o F i t I m a g e %
89 % %
90 % %
91 % %
92 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
93 %
94 % CropToFitImage() crops the sheared image as determined by the bounding box
95 % as defined by width and height and shearing angles.
96 %
97 % The format of the CropToFitImage method is:
98 %
99 % MagickBooleanType CropToFitImage(Image **image,
100 % const double x_shear,const double x_shear,
101 % const double width,const double height,
102 % const MagickBooleanType rotate,ExceptionInfo *exception)
103 %
104 % A description of each parameter follows.
105 %
106 % o image: the image.
107 %
108 % o x_shear, y_shear, width, height: Defines a region of the image to crop.
109 %
110 % o exception: return any errors or warnings in this structure.
111 %
112 */
CropToFitImage(Image ** image,const double x_shear,const double y_shear,const double width,const double height,const MagickBooleanType rotate,ExceptionInfo * exception)113 static MagickBooleanType CropToFitImage(Image **image,
114 const double x_shear,const double y_shear,
115 const double width,const double height,
116 const MagickBooleanType rotate,ExceptionInfo *exception)
117 {
118 Image
119 *crop_image;
120
121 PointInfo
122 extent[4],
123 min,
124 max;
125
126 RectangleInfo
127 geometry,
128 page;
129
130 register ssize_t
131 i;
132
133 /*
134 Calculate the rotated image size.
135 */
136 extent[0].x=(double) (-width/2.0);
137 extent[0].y=(double) (-height/2.0);
138 extent[1].x=(double) width/2.0;
139 extent[1].y=(double) (-height/2.0);
140 extent[2].x=(double) (-width/2.0);
141 extent[2].y=(double) height/2.0;
142 extent[3].x=(double) width/2.0;
143 extent[3].y=(double) height/2.0;
144 for (i=0; i < 4; i++)
145 {
146 extent[i].x+=x_shear*extent[i].y;
147 extent[i].y+=y_shear*extent[i].x;
148 if (rotate != MagickFalse)
149 extent[i].x+=x_shear*extent[i].y;
150 extent[i].x+=(double) (*image)->columns/2.0;
151 extent[i].y+=(double) (*image)->rows/2.0;
152 }
153 min=extent[0];
154 max=extent[0];
155 for (i=1; i < 4; i++)
156 {
157 if (min.x > extent[i].x)
158 min.x=extent[i].x;
159 if (min.y > extent[i].y)
160 min.y=extent[i].y;
161 if (max.x < extent[i].x)
162 max.x=extent[i].x;
163 if (max.y < extent[i].y)
164 max.y=extent[i].y;
165 }
166 geometry.x=(ssize_t) ceil(min.x-0.5);
167 geometry.y=(ssize_t) ceil(min.y-0.5);
168 geometry.width=(size_t) floor(max.x-min.x+0.5);
169 geometry.height=(size_t) floor(max.y-min.y+0.5);
170 page=(*image)->page;
171 (void) ParseAbsoluteGeometry("0x0+0+0",&(*image)->page);
172 crop_image=CropImage(*image,&geometry,exception);
173 if (crop_image == (Image *) NULL)
174 return(MagickFalse);
175 crop_image->page=page;
176 *image=DestroyImage(*image);
177 *image=crop_image;
178 return(MagickTrue);
179 }
180
181 /*
182 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
183 % %
184 % %
185 % %
186 % D e s k e w I m a g e %
187 % %
188 % %
189 % %
190 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
191 %
192 % DeskewImage() removes skew from the image. Skew is an artifact that
193 % occurs in scanned images because of the camera being misaligned,
194 % imperfections in the scanning or surface, or simply because the paper was
195 % not placed completely flat when scanned.
196 %
197 % The result will be auto-croped if the artifact "deskew:auto-crop" is
198 % defined, while the amount the image is to be deskewed, in degrees is also
199 % saved as the artifact "deskew:angle".
200 %
201 % If the artifact "deskew:auto-crop" is given the image will be automatically
202 % cropped of the excess background. The value is the border width of all
203 % pixels around the edge that will be used to determine an average border
204 % color for the automatic trim.
205 %
206 % The format of the DeskewImage method is:
207 %
208 % Image *DeskewImage(const Image *image,const double threshold,
209 % ExceptionInfo *exception)
210 %
211 % A description of each parameter follows:
212 %
213 % o image: the image.
214 %
215 % o threshold: separate background from foreground.
216 %
217 % o exception: return any errors or warnings in this structure.
218 %
219 */
220
RadonProjection(const Image * image,MatrixInfo * source_matrixs,MatrixInfo * destination_matrixs,const ssize_t sign,size_t * projection)221 static void RadonProjection(const Image *image,MatrixInfo *source_matrixs,
222 MatrixInfo *destination_matrixs,const ssize_t sign,size_t *projection)
223 {
224 MatrixInfo
225 *swap;
226
227 register MatrixInfo
228 *p,
229 *q;
230
231 register ssize_t
232 x;
233
234 size_t
235 step;
236
237 p=source_matrixs;
238 q=destination_matrixs;
239 for (step=1; step < GetMatrixColumns(p); step*=2)
240 {
241 for (x=0; x < (ssize_t) GetMatrixColumns(p); x+=2*(ssize_t) step)
242 {
243 register ssize_t
244 i;
245
246 ssize_t
247 y;
248
249 unsigned short
250 element,
251 neighbor;
252
253 for (i=0; i < (ssize_t) step; i++)
254 {
255 for (y=0; y < (ssize_t) (GetMatrixRows(p)-i-1); y++)
256 {
257 if (GetMatrixElement(p,x+i,y,&element) == MagickFalse)
258 continue;
259 if (GetMatrixElement(p,x+i+step,y+i,&neighbor) == MagickFalse)
260 continue;
261 neighbor+=element;
262 if (SetMatrixElement(q,x+2*i,y,&neighbor) == MagickFalse)
263 continue;
264 if (GetMatrixElement(p,x+i+step,y+i+1,&neighbor) == MagickFalse)
265 continue;
266 neighbor+=element;
267 if (SetMatrixElement(q,x+2*i+1,y,&neighbor) == MagickFalse)
268 continue;
269 }
270 for ( ; y < (ssize_t) (GetMatrixRows(p)-i); y++)
271 {
272 if (GetMatrixElement(p,x+i,y,&element) == MagickFalse)
273 continue;
274 if (GetMatrixElement(p,x+i+step,y+i,&neighbor) == MagickFalse)
275 continue;
276 neighbor+=element;
277 if (SetMatrixElement(q,x+2*i,y,&neighbor) == MagickFalse)
278 continue;
279 if (SetMatrixElement(q,x+2*i+1,y,&element) == MagickFalse)
280 continue;
281 }
282 for ( ; y < (ssize_t) GetMatrixRows(p); y++)
283 {
284 if (GetMatrixElement(p,x+i,y,&element) == MagickFalse)
285 continue;
286 if (SetMatrixElement(q,x+2*i,y,&element) == MagickFalse)
287 continue;
288 if (SetMatrixElement(q,x+2*i+1,y,&element) == MagickFalse)
289 continue;
290 }
291 }
292 }
293 swap=p;
294 p=q;
295 q=swap;
296 }
297 #if defined(MAGICKCORE_OPENMP_SUPPORT)
298 #pragma omp parallel for schedule(static,4) \
299 magick_threads(image,image,1,1)
300 #endif
301 for (x=0; x < (ssize_t) GetMatrixColumns(p); x++)
302 {
303 register ssize_t
304 y;
305
306 size_t
307 sum;
308
309 sum=0;
310 for (y=0; y < (ssize_t) (GetMatrixRows(p)-1); y++)
311 {
312 ssize_t
313 delta;
314
315 unsigned short
316 element,
317 neighbor;
318
319 if (GetMatrixElement(p,x,y,&element) == MagickFalse)
320 continue;
321 if (GetMatrixElement(p,x,y+1,&neighbor) == MagickFalse)
322 continue;
323 delta=(ssize_t) element-(ssize_t) neighbor;
324 sum+=delta*delta;
325 }
326 projection[GetMatrixColumns(p)+sign*x-1]=sum;
327 }
328 }
329
RadonTransform(const Image * image,const double threshold,size_t * projection,ExceptionInfo * exception)330 static MagickBooleanType RadonTransform(const Image *image,
331 const double threshold,size_t *projection,ExceptionInfo *exception)
332 {
333 CacheView
334 *image_view;
335
336 MatrixInfo
337 *destination_matrixs,
338 *source_matrixs;
339
340 MagickBooleanType
341 status;
342
343 size_t
344 count,
345 width;
346
347 ssize_t
348 j,
349 y;
350
351 unsigned char
352 c;
353
354 unsigned short
355 bits[256];
356
357 for (width=1; width < ((image->columns+7)/8); width<<=1) ;
358 source_matrixs=AcquireMatrixInfo(width,image->rows,sizeof(unsigned short),
359 exception);
360 destination_matrixs=AcquireMatrixInfo(width,image->rows,sizeof(unsigned short),
361 exception);
362 if ((source_matrixs == (MatrixInfo *) NULL) ||
363 (destination_matrixs == (MatrixInfo *) NULL))
364 {
365 if (destination_matrixs != (MatrixInfo *) NULL)
366 destination_matrixs=DestroyMatrixInfo(destination_matrixs);
367 if (source_matrixs != (MatrixInfo *) NULL)
368 source_matrixs=DestroyMatrixInfo(source_matrixs);
369 return(MagickFalse);
370 }
371 if (NullMatrix(source_matrixs) == MagickFalse)
372 {
373 destination_matrixs=DestroyMatrixInfo(destination_matrixs);
374 source_matrixs=DestroyMatrixInfo(source_matrixs);
375 return(MagickFalse);
376 }
377 for (j=0; j < 256; j++)
378 {
379 c=(unsigned char) j;
380 for (count=0; c != 0; c>>=1)
381 count+=c & 0x01;
382 bits[j]=(unsigned short) count;
383 }
384 status=MagickTrue;
385 image_view=AcquireVirtualCacheView(image,exception);
386 #if defined(MAGICKCORE_OPENMP_SUPPORT)
387 #pragma omp parallel for schedule(static,4) shared(status) \
388 magick_threads(image,image,1,1)
389 #endif
390 for (y=0; y < (ssize_t) image->rows; y++)
391 {
392 register const Quantum
393 *magick_restrict p;
394
395 register ssize_t
396 i,
397 x;
398
399 size_t
400 bit,
401 byte;
402
403 unsigned short
404 value;
405
406 if (status == MagickFalse)
407 continue;
408 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
409 if (p == (const Quantum *) NULL)
410 {
411 status=MagickFalse;
412 continue;
413 }
414 bit=0;
415 byte=0;
416 i=(ssize_t) (image->columns+7)/8;
417 for (x=0; x < (ssize_t) image->columns; x++)
418 {
419 byte<<=1;
420 if (((MagickRealType) GetPixelRed(image,p) < threshold) ||
421 ((MagickRealType) GetPixelGreen(image,p) < threshold) ||
422 ((MagickRealType) GetPixelBlue(image,p) < threshold))
423 byte|=0x01;
424 bit++;
425 if (bit == 8)
426 {
427 value=bits[byte];
428 (void) SetMatrixElement(source_matrixs,--i,y,&value);
429 bit=0;
430 byte=0;
431 }
432 p+=GetPixelChannels(image);
433 }
434 if (bit != 0)
435 {
436 byte<<=(8-bit);
437 value=bits[byte];
438 (void) SetMatrixElement(source_matrixs,--i,y,&value);
439 }
440 }
441 RadonProjection(image,source_matrixs,destination_matrixs,-1,projection);
442 (void) NullMatrix(source_matrixs);
443 #if defined(MAGICKCORE_OPENMP_SUPPORT)
444 #pragma omp parallel for schedule(static,4) shared(status) \
445 magick_threads(image,image,image->rows,1)
446 #endif
447 for (y=0; y < (ssize_t) image->rows; y++)
448 {
449 register const Quantum
450 *magick_restrict p;
451
452 register ssize_t
453 i,
454 x;
455
456 size_t
457 bit,
458 byte;
459
460 unsigned short
461 value;
462
463 if (status == MagickFalse)
464 continue;
465 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
466 if (p == (const Quantum *) NULL)
467 {
468 status=MagickFalse;
469 continue;
470 }
471 bit=0;
472 byte=0;
473 i=0;
474 for (x=0; x < (ssize_t) image->columns; x++)
475 {
476 byte<<=1;
477 if (((MagickRealType) GetPixelRed(image,p) < threshold) ||
478 ((MagickRealType) GetPixelGreen(image,p) < threshold) ||
479 ((MagickRealType) GetPixelBlue(image,p) < threshold))
480 byte|=0x01;
481 bit++;
482 if (bit == 8)
483 {
484 value=bits[byte];
485 (void) SetMatrixElement(source_matrixs,i++,y,&value);
486 bit=0;
487 byte=0;
488 }
489 p+=GetPixelChannels(image);
490 }
491 if (bit != 0)
492 {
493 byte<<=(8-bit);
494 value=bits[byte];
495 (void) SetMatrixElement(source_matrixs,i++,y,&value);
496 }
497 }
498 RadonProjection(image,source_matrixs,destination_matrixs,1,projection);
499 image_view=DestroyCacheView(image_view);
500 destination_matrixs=DestroyMatrixInfo(destination_matrixs);
501 source_matrixs=DestroyMatrixInfo(source_matrixs);
502 return(MagickTrue);
503 }
504
GetImageBackgroundColor(Image * image,const ssize_t offset,ExceptionInfo * exception)505 static void GetImageBackgroundColor(Image *image,const ssize_t offset,
506 ExceptionInfo *exception)
507 {
508 CacheView
509 *image_view;
510
511 PixelInfo
512 background;
513
514 double
515 count;
516
517 ssize_t
518 y;
519
520 /*
521 Compute average background color.
522 */
523 if (offset <= 0)
524 return;
525 GetPixelInfo(image,&background);
526 count=0.0;
527 image_view=AcquireVirtualCacheView(image,exception);
528 for (y=0; y < (ssize_t) image->rows; y++)
529 {
530 register const Quantum
531 *magick_restrict p;
532
533 register ssize_t
534 x;
535
536 if ((y >= offset) && (y < ((ssize_t) image->rows-offset)))
537 continue;
538 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
539 if (p == (const Quantum *) NULL)
540 continue;
541 for (x=0; x < (ssize_t) image->columns; x++)
542 {
543 if ((x >= offset) && (x < ((ssize_t) image->columns-offset)))
544 continue;
545 background.red+=QuantumScale*GetPixelRed(image,p);
546 background.green+=QuantumScale*GetPixelGreen(image,p);
547 background.blue+=QuantumScale*GetPixelBlue(image,p);
548 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
549 background.alpha+=QuantumScale*GetPixelAlpha(image,p);
550 count++;
551 p+=GetPixelChannels(image);
552 }
553 }
554 image_view=DestroyCacheView(image_view);
555 image->background_color.red=(double) ClampToQuantum(QuantumRange*
556 background.red/count);
557 image->background_color.green=(double) ClampToQuantum(QuantumRange*
558 background.green/count);
559 image->background_color.blue=(double) ClampToQuantum(QuantumRange*
560 background.blue/count);
561 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
562 image->background_color.alpha=(double) ClampToQuantum(QuantumRange*
563 background.alpha/count);
564 }
565
DeskewImage(const Image * image,const double threshold,ExceptionInfo * exception)566 MagickExport Image *DeskewImage(const Image *image,const double threshold,
567 ExceptionInfo *exception)
568 {
569 AffineMatrix
570 affine_matrix;
571
572 const char
573 *artifact;
574
575 double
576 degrees;
577
578 Image
579 *clone_image,
580 *crop_image,
581 *deskew_image,
582 *median_image;
583
584 MagickBooleanType
585 status;
586
587 RectangleInfo
588 geometry;
589
590 register ssize_t
591 i;
592
593 size_t
594 max_projection,
595 *projection,
596 width;
597
598 ssize_t
599 skew;
600
601 /*
602 Compute deskew angle.
603 */
604 for (width=1; width < ((image->columns+7)/8); width<<=1) ;
605 projection=(size_t *) AcquireQuantumMemory((size_t) (2*width-1),
606 sizeof(*projection));
607 if (projection == (size_t *) NULL)
608 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
609 status=RadonTransform(image,threshold,projection,exception);
610 if (status == MagickFalse)
611 {
612 projection=(size_t *) RelinquishMagickMemory(projection);
613 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
614 }
615 max_projection=0;
616 skew=0;
617 for (i=0; i < (ssize_t) (2*width-1); i++)
618 {
619 if (projection[i] > max_projection)
620 {
621 skew=i-(ssize_t) width+1;
622 max_projection=projection[i];
623 }
624 }
625 projection=(size_t *) RelinquishMagickMemory(projection);
626 degrees=RadiansToDegrees(-atan((double) skew/width/8));
627 if (image->debug != MagickFalse)
628 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
629 " Deskew angle: %g",degrees);
630 /*
631 Deskew image.
632 */
633 clone_image=CloneImage(image,0,0,MagickTrue,exception);
634 if (clone_image == (Image *) NULL)
635 return((Image *) NULL);
636 {
637 char
638 angle[MagickPathExtent];
639
640 (void) FormatLocaleString(angle,MagickPathExtent,"%.20g",degrees);
641 (void) SetImageArtifact(clone_image,"deskew:angle",angle);
642 }
643 (void) SetImageVirtualPixelMethod(clone_image,BackgroundVirtualPixelMethod,
644 exception);
645 affine_matrix.sx=cos(DegreesToRadians(fmod((double) degrees,360.0)));
646 affine_matrix.rx=sin(DegreesToRadians(fmod((double) degrees,360.0)));
647 affine_matrix.ry=(-sin(DegreesToRadians(fmod((double) degrees,360.0))));
648 affine_matrix.sy=cos(DegreesToRadians(fmod((double) degrees,360.0)));
649 affine_matrix.tx=0.0;
650 affine_matrix.ty=0.0;
651 artifact=GetImageArtifact(image,"deskew:auto-crop");
652 if (IsStringTrue(artifact) == MagickFalse)
653 {
654 deskew_image=AffineTransformImage(clone_image,&affine_matrix,exception);
655 clone_image=DestroyImage(clone_image);
656 return(deskew_image);
657 }
658 /*
659 Auto-crop image.
660 */
661 GetImageBackgroundColor(clone_image,(ssize_t) StringToLong(artifact),
662 exception);
663 deskew_image=AffineTransformImage(clone_image,&affine_matrix,exception);
664 clone_image=DestroyImage(clone_image);
665 if (deskew_image == (Image *) NULL)
666 return((Image *) NULL);
667 median_image=StatisticImage(deskew_image,MedianStatistic,3,3,exception);
668 if (median_image == (Image *) NULL)
669 {
670 deskew_image=DestroyImage(deskew_image);
671 return((Image *) NULL);
672 }
673 geometry=GetImageBoundingBox(median_image,exception);
674 median_image=DestroyImage(median_image);
675 if (image->debug != MagickFalse)
676 (void) LogMagickEvent(TransformEvent,GetMagickModule()," Deskew geometry: "
677 "%.20gx%.20g%+.20g%+.20g",(double) geometry.width,(double)
678 geometry.height,(double) geometry.x,(double) geometry.y);
679 crop_image=CropImage(deskew_image,&geometry,exception);
680 deskew_image=DestroyImage(deskew_image);
681 return(crop_image);
682 }
683
684 /*
685 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
686 % %
687 % %
688 % %
689 % I n t e g r a l R o t a t e I m a g e %
690 % %
691 % %
692 % %
693 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
694 %
695 % IntegralRotateImage() rotates the image an integral of 90 degrees. It
696 % allocates the memory necessary for the new Image structure and returns a
697 % pointer to the rotated image.
698 %
699 % The format of the IntegralRotateImage method is:
700 %
701 % Image *IntegralRotateImage(const Image *image,size_t rotations,
702 % ExceptionInfo *exception)
703 %
704 % A description of each parameter follows.
705 %
706 % o image: the image.
707 %
708 % o rotations: Specifies the number of 90 degree rotations.
709 %
710 */
IntegralRotateImage(const Image * image,size_t rotations,ExceptionInfo * exception)711 MagickExport Image *IntegralRotateImage(const Image *image,size_t rotations,
712 ExceptionInfo *exception)
713 {
714 #define RotateImageTag "Rotate/Image"
715
716 CacheView
717 *image_view,
718 *rotate_view;
719
720 Image
721 *rotate_image;
722
723 MagickBooleanType
724 status;
725
726 MagickOffsetType
727 progress;
728
729 RectangleInfo
730 page;
731
732 /*
733 Initialize rotated image attributes.
734 */
735 assert(image != (Image *) NULL);
736 page=image->page;
737 rotations%=4;
738 if (rotations == 0)
739 return(CloneImage(image,0,0,MagickTrue,exception));
740 if ((rotations == 1) || (rotations == 3))
741 rotate_image=CloneImage(image,image->rows,image->columns,MagickTrue,
742 exception);
743 else
744 rotate_image=CloneImage(image,image->columns,image->rows,MagickTrue,
745 exception);
746 if (rotate_image == (Image *) NULL)
747 return((Image *) NULL);
748 /*
749 Integral rotate the image.
750 */
751 status=MagickTrue;
752 progress=0;
753 image_view=AcquireVirtualCacheView(image,exception);
754 rotate_view=AcquireAuthenticCacheView(rotate_image,exception);
755 switch (rotations)
756 {
757 case 1:
758 {
759 size_t
760 tile_height,
761 tile_width;
762
763 ssize_t
764 tile_y;
765
766 /*
767 Rotate 90 degrees.
768 */
769 GetPixelCacheTileSize(image,&tile_width,&tile_height);
770 tile_width=image->columns;
771 #if defined(MAGICKCORE_OPENMP_SUPPORT)
772 #pragma omp parallel for schedule(static,4) shared(status) \
773 magick_threads(image,image,1,1)
774 #endif
775 for (tile_y=0; tile_y < (ssize_t) image->rows; tile_y+=(ssize_t) tile_height)
776 {
777 register ssize_t
778 tile_x;
779
780 if (status == MagickFalse)
781 continue;
782 tile_x=0;
783 for ( ; tile_x < (ssize_t) image->columns; tile_x+=(ssize_t) tile_width)
784 {
785 MagickBooleanType
786 sync;
787
788 register const Quantum
789 *magick_restrict p;
790
791 register Quantum
792 *magick_restrict q;
793
794 register ssize_t
795 y;
796
797 size_t
798 height,
799 width;
800
801 width=tile_width;
802 if ((tile_x+(ssize_t) tile_width) > (ssize_t) image->columns)
803 width=(size_t) (tile_width-(tile_x+tile_width-image->columns));
804 height=tile_height;
805 if ((tile_y+(ssize_t) tile_height) > (ssize_t) image->rows)
806 height=(size_t) (tile_height-(tile_y+tile_height-image->rows));
807 p=GetCacheViewVirtualPixels(image_view,tile_x,tile_y,width,height,
808 exception);
809 if (p == (const Quantum *) NULL)
810 {
811 status=MagickFalse;
812 break;
813 }
814 for (y=0; y < (ssize_t) width; y++)
815 {
816 register const Quantum
817 *magick_restrict tile_pixels;
818
819 register ssize_t
820 x;
821
822 if (status == MagickFalse)
823 continue;
824 q=QueueCacheViewAuthenticPixels(rotate_view,(ssize_t)
825 (rotate_image->columns-(tile_y+height)),y+tile_x,height,1,
826 exception);
827 if (q == (Quantum *) NULL)
828 {
829 status=MagickFalse;
830 continue;
831 }
832 tile_pixels=p+((height-1)*width+y)*GetPixelChannels(image);
833 for (x=0; x < (ssize_t) height; x++)
834 {
835 register ssize_t
836 i;
837
838 if (GetPixelReadMask(image,tile_pixels) == 0)
839 {
840 tile_pixels-=width*GetPixelChannels(image);
841 q+=GetPixelChannels(rotate_image);
842 continue;
843 }
844 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
845 {
846 PixelChannel channel=GetPixelChannelChannel(image,i);
847 PixelTrait traits=GetPixelChannelTraits(image,channel);
848 PixelTrait rotate_traits=GetPixelChannelTraits(rotate_image,
849 channel);
850 if ((traits == UndefinedPixelTrait) ||
851 (rotate_traits == UndefinedPixelTrait))
852 continue;
853 SetPixelChannel(rotate_image,channel,tile_pixels[i],q);
854 }
855 tile_pixels-=width*GetPixelChannels(image);
856 q+=GetPixelChannels(rotate_image);
857 }
858 sync=SyncCacheViewAuthenticPixels(rotate_view,exception);
859 if (sync == MagickFalse)
860 status=MagickFalse;
861 }
862 }
863 if (image->progress_monitor != (MagickProgressMonitor) NULL)
864 {
865 MagickBooleanType
866 proceed;
867
868 #if defined(MAGICKCORE_OPENMP_SUPPORT)
869 #pragma omp critical (MagickCore_IntegralRotateImage)
870 #endif
871 proceed=SetImageProgress(image,RotateImageTag,progress+=tile_height,
872 image->rows);
873 if (proceed == MagickFalse)
874 status=MagickFalse;
875 }
876 }
877 (void) SetImageProgress(image,RotateImageTag,(MagickOffsetType)
878 image->rows-1,image->rows);
879 Swap(page.width,page.height);
880 Swap(page.x,page.y);
881 if (page.width != 0)
882 page.x=(ssize_t) (page.width-rotate_image->columns-page.x);
883 break;
884 }
885 case 2:
886 {
887 register ssize_t
888 y;
889
890 /*
891 Rotate 180 degrees.
892 */
893 #if defined(MAGICKCORE_OPENMP_SUPPORT)
894 #pragma omp parallel for schedule(static,4) shared(status) \
895 magick_threads(image,image,1,1)
896 #endif
897 for (y=0; y < (ssize_t) image->rows; y++)
898 {
899 MagickBooleanType
900 sync;
901
902 register const Quantum
903 *magick_restrict p;
904
905 register Quantum
906 *magick_restrict q;
907
908 register ssize_t
909 x;
910
911 if (status == MagickFalse)
912 continue;
913 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
914 q=QueueCacheViewAuthenticPixels(rotate_view,0,(ssize_t) (image->rows-y-
915 1),image->columns,1,exception);
916 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
917 {
918 status=MagickFalse;
919 continue;
920 }
921 q+=GetPixelChannels(rotate_image)*image->columns;
922 for (x=0; x < (ssize_t) image->columns; x++)
923 {
924 register ssize_t
925 i;
926
927 q-=GetPixelChannels(rotate_image);
928 if (GetPixelReadMask(image,p) == 0)
929 {
930 p+=GetPixelChannels(image);
931 continue;
932 }
933 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
934 {
935 PixelChannel channel=GetPixelChannelChannel(image,i);
936 PixelTrait traits=GetPixelChannelTraits(image,channel);
937 PixelTrait rotate_traits=GetPixelChannelTraits(rotate_image,
938 channel);
939 if ((traits == UndefinedPixelTrait) ||
940 (rotate_traits == UndefinedPixelTrait))
941 continue;
942 SetPixelChannel(rotate_image,channel,p[i],q);
943 }
944 p+=GetPixelChannels(image);
945 }
946 sync=SyncCacheViewAuthenticPixels(rotate_view,exception);
947 if (sync == MagickFalse)
948 status=MagickFalse;
949 if (image->progress_monitor != (MagickProgressMonitor) NULL)
950 {
951 MagickBooleanType
952 proceed;
953
954 #if defined(MAGICKCORE_OPENMP_SUPPORT)
955 #pragma omp critical (MagickCore_IntegralRotateImage)
956 #endif
957 proceed=SetImageProgress(image,RotateImageTag,progress++,
958 image->rows);
959 if (proceed == MagickFalse)
960 status=MagickFalse;
961 }
962 }
963 (void) SetImageProgress(image,RotateImageTag,(MagickOffsetType)
964 image->rows-1,image->rows);
965 Swap(page.width,page.height);
966 Swap(page.x,page.y);
967 if (page.width != 0)
968 page.x=(ssize_t) (page.width-rotate_image->columns-page.x);
969 break;
970 }
971 case 3:
972 {
973 size_t
974 tile_height,
975 tile_width;
976
977 ssize_t
978 tile_y;
979
980 /*
981 Rotate 270 degrees.
982 */
983 GetPixelCacheTileSize(image,&tile_width,&tile_height);
984 tile_width=image->columns;
985 #if defined(MAGICKCORE_OPENMP_SUPPORT)
986 #pragma omp parallel for schedule(static,4) shared(status) \
987 magick_threads(image,image,1,1)
988 #endif
989 for (tile_y=0; tile_y < (ssize_t) image->rows; tile_y+=(ssize_t) tile_height)
990 {
991 register ssize_t
992 tile_x;
993
994 if (status == MagickFalse)
995 continue;
996 tile_x=0;
997 for ( ; tile_x < (ssize_t) image->columns; tile_x+=(ssize_t) tile_width)
998 {
999 MagickBooleanType
1000 sync;
1001
1002 register const Quantum
1003 *magick_restrict p;
1004
1005 register Quantum
1006 *magick_restrict q;
1007
1008 register ssize_t
1009 y;
1010
1011 size_t
1012 height,
1013 width;
1014
1015 width=tile_width;
1016 if ((tile_x+(ssize_t) tile_width) > (ssize_t) image->columns)
1017 width=(size_t) (tile_width-(tile_x+tile_width-image->columns));
1018 height=tile_height;
1019 if ((tile_y+(ssize_t) tile_height) > (ssize_t) image->rows)
1020 height=(size_t) (tile_height-(tile_y+tile_height-image->rows));
1021 p=GetCacheViewVirtualPixels(image_view,tile_x,tile_y,width,height,
1022 exception);
1023 if (p == (const Quantum *) NULL)
1024 {
1025 status=MagickFalse;
1026 break;
1027 }
1028 for (y=0; y < (ssize_t) width; y++)
1029 {
1030 register const Quantum
1031 *magick_restrict tile_pixels;
1032
1033 register ssize_t
1034 x;
1035
1036 if (status == MagickFalse)
1037 continue;
1038 q=QueueCacheViewAuthenticPixels(rotate_view,tile_y,(ssize_t) (y+
1039 rotate_image->rows-(tile_x+width)),height,1,exception);
1040 if (q == (Quantum *) NULL)
1041 {
1042 status=MagickFalse;
1043 continue;
1044 }
1045 tile_pixels=p+((width-1)-y)*GetPixelChannels(image);
1046 for (x=0; x < (ssize_t) height; x++)
1047 {
1048 register ssize_t
1049 i;
1050
1051 if (GetPixelReadMask(image,tile_pixels) == 0)
1052 {
1053 tile_pixels+=width*GetPixelChannels(image);
1054 q+=GetPixelChannels(rotate_image);
1055 continue;
1056 }
1057 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1058 {
1059 PixelChannel channel=GetPixelChannelChannel(image,i);
1060 PixelTrait traits=GetPixelChannelTraits(image,channel);
1061 PixelTrait rotate_traits=GetPixelChannelTraits(rotate_image,
1062 channel);
1063 if ((traits == UndefinedPixelTrait) ||
1064 (rotate_traits == UndefinedPixelTrait))
1065 continue;
1066 SetPixelChannel(rotate_image,channel,tile_pixels[i],q);
1067 }
1068 tile_pixels+=width*GetPixelChannels(image);
1069 q+=GetPixelChannels(rotate_image);
1070 }
1071 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1072 #pragma omp critical (MagickCore_IntegralRotateImage)
1073 #endif
1074 sync=SyncCacheViewAuthenticPixels(rotate_view,exception);
1075 if (sync == MagickFalse)
1076 status=MagickFalse;
1077 }
1078 }
1079 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1080 {
1081 MagickBooleanType
1082 proceed;
1083
1084 proceed=SetImageProgress(image,RotateImageTag,progress+=tile_height,
1085 image->rows);
1086 if (proceed == MagickFalse)
1087 status=MagickFalse;
1088 }
1089 }
1090 (void) SetImageProgress(image,RotateImageTag,(MagickOffsetType)
1091 image->rows-1,image->rows);
1092 Swap(page.width,page.height);
1093 Swap(page.x,page.y);
1094 if (page.width != 0)
1095 page.x=(ssize_t) (page.width-rotate_image->columns-page.x);
1096 break;
1097 }
1098 default:
1099 break;
1100 }
1101 rotate_view=DestroyCacheView(rotate_view);
1102 image_view=DestroyCacheView(image_view);
1103 rotate_image->type=image->type;
1104 rotate_image->page=page;
1105 if (status == MagickFalse)
1106 rotate_image=DestroyImage(rotate_image);
1107 return(rotate_image);
1108 }
1109
1110 /*
1111 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1112 % %
1113 % %
1114 % %
1115 + X S h e a r I m a g e %
1116 % %
1117 % %
1118 % %
1119 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1120 %
1121 % XShearImage() shears the image in the X direction with a shear angle of
1122 % 'degrees'. Positive angles shear counter-clockwise (right-hand rule), and
1123 % negative angles shear clockwise. Angles are measured relative to a vertical
1124 % Y-axis. X shears will widen an image creating 'empty' triangles on the left
1125 % and right sides of the source image.
1126 %
1127 % The format of the XShearImage method is:
1128 %
1129 % MagickBooleanType XShearImage(Image *image,const double degrees,
1130 % const size_t width,const size_t height,
1131 % const ssize_t x_offset,const ssize_t y_offset,ExceptionInfo *exception)
1132 %
1133 % A description of each parameter follows.
1134 %
1135 % o image: the image.
1136 %
1137 % o degrees: A double representing the shearing angle along the X
1138 % axis.
1139 %
1140 % o width, height, x_offset, y_offset: Defines a region of the image
1141 % to shear.
1142 %
1143 % o exception: return any errors or warnings in this structure.
1144 %
1145 */
XShearImage(Image * image,const double degrees,const size_t width,const size_t height,const ssize_t x_offset,const ssize_t y_offset,ExceptionInfo * exception)1146 static MagickBooleanType XShearImage(Image *image,const double degrees,
1147 const size_t width,const size_t height,const ssize_t x_offset,
1148 const ssize_t y_offset,ExceptionInfo *exception)
1149 {
1150 #define XShearImageTag "XShear/Image"
1151
1152 typedef enum
1153 {
1154 LEFT,
1155 RIGHT
1156 } ShearDirection;
1157
1158 CacheView
1159 *image_view;
1160
1161 MagickBooleanType
1162 status;
1163
1164 MagickOffsetType
1165 progress;
1166
1167 PixelInfo
1168 background;
1169
1170 ssize_t
1171 y;
1172
1173 /*
1174 X shear image.
1175 */
1176 assert(image != (Image *) NULL);
1177 assert(image->signature == MagickCoreSignature);
1178 if (image->debug != MagickFalse)
1179 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1180 status=MagickTrue;
1181 background=image->background_color;
1182 progress=0;
1183 image_view=AcquireAuthenticCacheView(image,exception);
1184 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1185 #pragma omp parallel for schedule(static,4) shared(progress,status) \
1186 magick_threads(image,image,height,1)
1187 #endif
1188 for (y=0; y < (ssize_t) height; y++)
1189 {
1190 PixelInfo
1191 pixel,
1192 source,
1193 destination;
1194
1195 double
1196 area,
1197 displacement;
1198
1199 register Quantum
1200 *magick_restrict p,
1201 *magick_restrict q;
1202
1203 register ssize_t
1204 i;
1205
1206 ShearDirection
1207 direction;
1208
1209 ssize_t
1210 step;
1211
1212 if (status == MagickFalse)
1213 continue;
1214 p=GetCacheViewAuthenticPixels(image_view,0,y_offset+y,image->columns,1,
1215 exception);
1216 if (p == (Quantum *) NULL)
1217 {
1218 status=MagickFalse;
1219 continue;
1220 }
1221 p+=x_offset*GetPixelChannels(image);
1222 displacement=degrees*(double) (y-height/2.0);
1223 if (displacement == 0.0)
1224 continue;
1225 if (displacement > 0.0)
1226 direction=RIGHT;
1227 else
1228 {
1229 displacement*=(-1.0);
1230 direction=LEFT;
1231 }
1232 step=(ssize_t) floor((double) displacement);
1233 area=(double) (displacement-step);
1234 step++;
1235 pixel=background;
1236 GetPixelInfo(image,&source);
1237 GetPixelInfo(image,&destination);
1238 switch (direction)
1239 {
1240 case LEFT:
1241 {
1242 /*
1243 Transfer pixels left-to-right.
1244 */
1245 if (step > x_offset)
1246 break;
1247 q=p-step*GetPixelChannels(image);
1248 for (i=0; i < (ssize_t) width; i++)
1249 {
1250 if ((x_offset+i) < step)
1251 {
1252 p+=GetPixelChannels(image);
1253 GetPixelInfoPixel(image,p,&pixel);
1254 q+=GetPixelChannels(image);
1255 continue;
1256 }
1257 GetPixelInfoPixel(image,p,&source);
1258 CompositePixelInfoAreaBlend(&pixel,(double) pixel.alpha,
1259 &source,(double) GetPixelAlpha(image,p),area,&destination);
1260 SetPixelViaPixelInfo(image,&destination,q);
1261 GetPixelInfoPixel(image,p,&pixel);
1262 p+=GetPixelChannels(image);
1263 q+=GetPixelChannels(image);
1264 }
1265 CompositePixelInfoAreaBlend(&pixel,(double) pixel.alpha,
1266 &background,(double) background.alpha,area,&destination);
1267 SetPixelViaPixelInfo(image,&destination,q);
1268 q+=GetPixelChannels(image);
1269 for (i=0; i < (step-1); i++)
1270 {
1271 SetPixelViaPixelInfo(image,&background,q);
1272 q+=GetPixelChannels(image);
1273 }
1274 break;
1275 }
1276 case RIGHT:
1277 {
1278 /*
1279 Transfer pixels right-to-left.
1280 */
1281 p+=width*GetPixelChannels(image);
1282 q=p+step*GetPixelChannels(image);
1283 for (i=0; i < (ssize_t) width; i++)
1284 {
1285 p-=GetPixelChannels(image);
1286 q-=GetPixelChannels(image);
1287 if ((size_t) (x_offset+width+step-i) > image->columns)
1288 continue;
1289 GetPixelInfoPixel(image,p,&source);
1290 CompositePixelInfoAreaBlend(&pixel,(double) pixel.alpha,
1291 &source,(double) GetPixelAlpha(image,p),area,&destination);
1292 SetPixelViaPixelInfo(image,&destination,q);
1293 GetPixelInfoPixel(image,p,&pixel);
1294 }
1295 CompositePixelInfoAreaBlend(&pixel,(double) pixel.alpha,
1296 &background,(double) background.alpha,area,&destination);
1297 q-=GetPixelChannels(image);
1298 SetPixelViaPixelInfo(image,&destination,q);
1299 for (i=0; i < (step-1); i++)
1300 {
1301 q-=GetPixelChannels(image);
1302 SetPixelViaPixelInfo(image,&background,q);
1303 }
1304 break;
1305 }
1306 }
1307 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
1308 status=MagickFalse;
1309 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1310 {
1311 MagickBooleanType
1312 proceed;
1313
1314 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1315 #pragma omp critical (MagickCore_XShearImage)
1316 #endif
1317 proceed=SetImageProgress(image,XShearImageTag,progress++,height);
1318 if (proceed == MagickFalse)
1319 status=MagickFalse;
1320 }
1321 }
1322 image_view=DestroyCacheView(image_view);
1323 return(status);
1324 }
1325
1326 /*
1327 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1328 % %
1329 % %
1330 % %
1331 + Y S h e a r I m a g e %
1332 % %
1333 % %
1334 % %
1335 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1336 %
1337 % YShearImage shears the image in the Y direction with a shear angle of
1338 % 'degrees'. Positive angles shear counter-clockwise (right-hand rule), and
1339 % negative angles shear clockwise. Angles are measured relative to a
1340 % horizontal X-axis. Y shears will increase the height of an image creating
1341 % 'empty' triangles on the top and bottom of the source image.
1342 %
1343 % The format of the YShearImage method is:
1344 %
1345 % MagickBooleanType YShearImage(Image *image,const double degrees,
1346 % const size_t width,const size_t height,
1347 % const ssize_t x_offset,const ssize_t y_offset,ExceptionInfo *exception)
1348 %
1349 % A description of each parameter follows.
1350 %
1351 % o image: the image.
1352 %
1353 % o degrees: A double representing the shearing angle along the Y
1354 % axis.
1355 %
1356 % o width, height, x_offset, y_offset: Defines a region of the image
1357 % to shear.
1358 %
1359 % o exception: return any errors or warnings in this structure.
1360 %
1361 */
YShearImage(Image * image,const double degrees,const size_t width,const size_t height,const ssize_t x_offset,const ssize_t y_offset,ExceptionInfo * exception)1362 static MagickBooleanType YShearImage(Image *image,const double degrees,
1363 const size_t width,const size_t height,const ssize_t x_offset,
1364 const ssize_t y_offset,ExceptionInfo *exception)
1365 {
1366 #define YShearImageTag "YShear/Image"
1367
1368 typedef enum
1369 {
1370 UP,
1371 DOWN
1372 } ShearDirection;
1373
1374 CacheView
1375 *image_view;
1376
1377 MagickBooleanType
1378 status;
1379
1380 MagickOffsetType
1381 progress;
1382
1383 PixelInfo
1384 background;
1385
1386 ssize_t
1387 x;
1388
1389 /*
1390 Y Shear image.
1391 */
1392 assert(image != (Image *) NULL);
1393 assert(image->signature == MagickCoreSignature);
1394 if (image->debug != MagickFalse)
1395 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1396 status=MagickTrue;
1397 progress=0;
1398 background=image->background_color;
1399 image_view=AcquireAuthenticCacheView(image,exception);
1400 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1401 #pragma omp parallel for schedule(static,4) shared(progress,status) \
1402 magick_threads(image,image,width,1)
1403 #endif
1404 for (x=0; x < (ssize_t) width; x++)
1405 {
1406 ssize_t
1407 step;
1408
1409 double
1410 area,
1411 displacement;
1412
1413 PixelInfo
1414 pixel,
1415 source,
1416 destination;
1417
1418 register Quantum
1419 *magick_restrict p,
1420 *magick_restrict q;
1421
1422 register ssize_t
1423 i;
1424
1425 ShearDirection
1426 direction;
1427
1428 if (status == MagickFalse)
1429 continue;
1430 p=GetCacheViewAuthenticPixels(image_view,x_offset+x,0,1,image->rows,
1431 exception);
1432 if (p == (Quantum *) NULL)
1433 {
1434 status=MagickFalse;
1435 continue;
1436 }
1437 p+=y_offset*GetPixelChannels(image);
1438 displacement=degrees*(double) (x-width/2.0);
1439 if (displacement == 0.0)
1440 continue;
1441 if (displacement > 0.0)
1442 direction=DOWN;
1443 else
1444 {
1445 displacement*=(-1.0);
1446 direction=UP;
1447 }
1448 step=(ssize_t) floor((double) displacement);
1449 area=(double) (displacement-step);
1450 step++;
1451 pixel=background;
1452 GetPixelInfo(image,&source);
1453 GetPixelInfo(image,&destination);
1454 switch (direction)
1455 {
1456 case UP:
1457 {
1458 /*
1459 Transfer pixels top-to-bottom.
1460 */
1461 if (step > y_offset)
1462 break;
1463 q=p-step*GetPixelChannels(image);
1464 for (i=0; i < (ssize_t) height; i++)
1465 {
1466 if ((y_offset+i) < step)
1467 {
1468 p+=GetPixelChannels(image);
1469 GetPixelInfoPixel(image,p,&pixel);
1470 q+=GetPixelChannels(image);
1471 continue;
1472 }
1473 GetPixelInfoPixel(image,p,&source);
1474 CompositePixelInfoAreaBlend(&pixel,(double) pixel.alpha,
1475 &source,(double) GetPixelAlpha(image,p),area,
1476 &destination);
1477 SetPixelViaPixelInfo(image,&destination,q);
1478 GetPixelInfoPixel(image,p,&pixel);
1479 p+=GetPixelChannels(image);
1480 q+=GetPixelChannels(image);
1481 }
1482 CompositePixelInfoAreaBlend(&pixel,(double) pixel.alpha,
1483 &background,(double) background.alpha,area,&destination);
1484 SetPixelViaPixelInfo(image,&destination,q);
1485 q+=GetPixelChannels(image);
1486 for (i=0; i < (step-1); i++)
1487 {
1488 SetPixelViaPixelInfo(image,&background,q);
1489 q+=GetPixelChannels(image);
1490 }
1491 break;
1492 }
1493 case DOWN:
1494 {
1495 /*
1496 Transfer pixels bottom-to-top.
1497 */
1498 p+=height*GetPixelChannels(image);
1499 q=p+step*GetPixelChannels(image);
1500 for (i=0; i < (ssize_t) height; i++)
1501 {
1502 p-=GetPixelChannels(image);
1503 q-=GetPixelChannels(image);
1504 if ((size_t) (y_offset+height+step-i) > image->rows)
1505 continue;
1506 GetPixelInfoPixel(image,p,&source);
1507 CompositePixelInfoAreaBlend(&pixel,(double) pixel.alpha,
1508 &source,(double) GetPixelAlpha(image,p),area,
1509 &destination);
1510 SetPixelViaPixelInfo(image,&destination,q);
1511 GetPixelInfoPixel(image,p,&pixel);
1512 }
1513 CompositePixelInfoAreaBlend(&pixel,(double) pixel.alpha,
1514 &background,(double) background.alpha,area,&destination);
1515 q-=GetPixelChannels(image);
1516 SetPixelViaPixelInfo(image,&destination,q);
1517 for (i=0; i < (step-1); i++)
1518 {
1519 q-=GetPixelChannels(image);
1520 SetPixelViaPixelInfo(image,&background,q);
1521 }
1522 break;
1523 }
1524 }
1525 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
1526 status=MagickFalse;
1527 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1528 {
1529 MagickBooleanType
1530 proceed;
1531
1532 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1533 #pragma omp critical (MagickCore_YShearImage)
1534 #endif
1535 proceed=SetImageProgress(image,YShearImageTag,progress++,image->rows);
1536 if (proceed == MagickFalse)
1537 status=MagickFalse;
1538 }
1539 }
1540 image_view=DestroyCacheView(image_view);
1541 return(status);
1542 }
1543
1544 /*
1545 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1546 % %
1547 % %
1548 % %
1549 % S h e a r I m a g e %
1550 % %
1551 % %
1552 % %
1553 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1554 %
1555 % ShearImage() creates a new image that is a shear_image copy of an existing
1556 % one. Shearing slides one edge of an image along the X or Y axis, creating
1557 % a parallelogram. An X direction shear slides an edge along the X axis,
1558 % while a Y direction shear slides an edge along the Y axis. The amount of
1559 % the shear is controlled by a shear angle. For X direction shears, x_shear
1560 % is measured relative to the Y axis, and similarly, for Y direction shears
1561 % y_shear is measured relative to the X axis. Empty triangles left over from
1562 % shearing the image are filled with the background color defined by member
1563 % 'background_color' of the image.. ShearImage() allocates the memory
1564 % necessary for the new Image structure and returns a pointer to the new image.
1565 %
1566 % ShearImage() is based on the paper "A Fast Algorithm for General Raster
1567 % Rotatation" by Alan W. Paeth.
1568 %
1569 % The format of the ShearImage method is:
1570 %
1571 % Image *ShearImage(const Image *image,const double x_shear,
1572 % const double y_shear,ExceptionInfo *exception)
1573 %
1574 % A description of each parameter follows.
1575 %
1576 % o image: the image.
1577 %
1578 % o x_shear, y_shear: Specifies the number of degrees to shear the image.
1579 %
1580 % o exception: return any errors or warnings in this structure.
1581 %
1582 */
ShearImage(const Image * image,const double x_shear,const double y_shear,ExceptionInfo * exception)1583 MagickExport Image *ShearImage(const Image *image,const double x_shear,
1584 const double y_shear,ExceptionInfo *exception)
1585 {
1586 Image
1587 *integral_image,
1588 *shear_image;
1589
1590 MagickBooleanType
1591 status;
1592
1593 PointInfo
1594 shear;
1595
1596 RectangleInfo
1597 border_info,
1598 bounds;
1599
1600 assert(image != (Image *) NULL);
1601 assert(image->signature == MagickCoreSignature);
1602 if (image->debug != MagickFalse)
1603 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1604 assert(exception != (ExceptionInfo *) NULL);
1605 assert(exception->signature == MagickCoreSignature);
1606 if ((x_shear != 0.0) && (fmod(x_shear,90.0) == 0.0))
1607 ThrowImageException(ImageError,"AngleIsDiscontinuous");
1608 if ((y_shear != 0.0) && (fmod(y_shear,90.0) == 0.0))
1609 ThrowImageException(ImageError,"AngleIsDiscontinuous");
1610 /*
1611 Initialize shear angle.
1612 */
1613 integral_image=CloneImage(image,0,0,MagickTrue,exception);
1614 if (integral_image == (Image *) NULL)
1615 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
1616 shear.x=(-tan(DegreesToRadians(fmod(x_shear,360.0))));
1617 shear.y=tan(DegreesToRadians(fmod(y_shear,360.0)));
1618 if ((shear.x == 0.0) && (shear.y == 0.0))
1619 return(integral_image);
1620 if (SetImageStorageClass(integral_image,DirectClass,exception) == MagickFalse)
1621 {
1622 integral_image=DestroyImage(integral_image);
1623 return(integral_image);
1624 }
1625 if (integral_image->alpha_trait == UndefinedPixelTrait)
1626 (void) SetImageAlphaChannel(integral_image,OpaqueAlphaChannel,exception);
1627 /*
1628 Compute image size.
1629 */
1630 bounds.width=image->columns+(ssize_t) floor(fabs(shear.x)*image->rows+0.5);
1631 bounds.x=(ssize_t) ceil((double) image->columns+((fabs(shear.x)*image->rows)-
1632 image->columns)/2.0-0.5);
1633 bounds.y=(ssize_t) ceil((double) image->rows+((fabs(shear.y)*bounds.width)-
1634 image->rows)/2.0-0.5);
1635 /*
1636 Surround image with border.
1637 */
1638 integral_image->border_color=integral_image->background_color;
1639 integral_image->compose=CopyCompositeOp;
1640 border_info.width=(size_t) bounds.x;
1641 border_info.height=(size_t) bounds.y;
1642 shear_image=BorderImage(integral_image,&border_info,image->compose,exception);
1643 integral_image=DestroyImage(integral_image);
1644 if (shear_image == (Image *) NULL)
1645 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
1646 /*
1647 Shear the image.
1648 */
1649 if (shear_image->alpha_trait == UndefinedPixelTrait)
1650 (void) SetImageAlphaChannel(shear_image,OpaqueAlphaChannel,exception);
1651 status=XShearImage(shear_image,shear.x,image->columns,image->rows,bounds.x,
1652 (ssize_t) (shear_image->rows-image->rows)/2,exception);
1653 if (status == MagickFalse)
1654 {
1655 shear_image=DestroyImage(shear_image);
1656 return((Image *) NULL);
1657 }
1658 status=YShearImage(shear_image,shear.y,bounds.width,image->rows,(ssize_t)
1659 (shear_image->columns-bounds.width)/2,bounds.y,exception);
1660 if (status == MagickFalse)
1661 {
1662 shear_image=DestroyImage(shear_image);
1663 return((Image *) NULL);
1664 }
1665 status=CropToFitImage(&shear_image,shear.x,shear.y,(MagickRealType)
1666 image->columns,(MagickRealType) image->rows,MagickFalse,exception);
1667 shear_image->alpha_trait=image->alpha_trait;
1668 shear_image->compose=image->compose;
1669 shear_image->page.width=0;
1670 shear_image->page.height=0;
1671 if (status == MagickFalse)
1672 shear_image=DestroyImage(shear_image);
1673 return(shear_image);
1674 }
1675
1676 /*
1677 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1678 % %
1679 % %
1680 % %
1681 % S h e a r R o t a t e I m a g e %
1682 % %
1683 % %
1684 % %
1685 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1686 %
1687 % ShearRotateImage() creates a new image that is a rotated copy of an existing
1688 % one. Positive angles rotate counter-clockwise (right-hand rule), while
1689 % negative angles rotate clockwise. Rotated images are usually larger than
1690 % the originals and have 'empty' triangular corners. X axis. Empty
1691 % triangles left over from shearing the image are filled with the background
1692 % color defined by member 'background_color' of the image. ShearRotateImage
1693 % allocates the memory necessary for the new Image structure and returns a
1694 % pointer to the new image.
1695 %
1696 % ShearRotateImage() is based on the paper "A Fast Algorithm for General
1697 % Raster Rotatation" by Alan W. Paeth. ShearRotateImage is adapted from a
1698 % similar method based on the Paeth paper written by Michael Halle of the
1699 % Spatial Imaging Group, MIT Media Lab.
1700 %
1701 % The format of the ShearRotateImage method is:
1702 %
1703 % Image *ShearRotateImage(const Image *image,const double degrees,
1704 % ExceptionInfo *exception)
1705 %
1706 % A description of each parameter follows.
1707 %
1708 % o image: the image.
1709 %
1710 % o degrees: Specifies the number of degrees to rotate the image.
1711 %
1712 % o exception: return any errors or warnings in this structure.
1713 %
1714 */
ShearRotateImage(const Image * image,const double degrees,ExceptionInfo * exception)1715 MagickExport Image *ShearRotateImage(const Image *image,const double degrees,
1716 ExceptionInfo *exception)
1717 {
1718 Image
1719 *integral_image,
1720 *rotate_image;
1721
1722 MagickBooleanType
1723 status;
1724
1725 MagickRealType
1726 angle;
1727
1728 PointInfo
1729 shear;
1730
1731 RectangleInfo
1732 border_info,
1733 bounds;
1734
1735 size_t
1736 height,
1737 rotations,
1738 shear_width,
1739 width;
1740
1741 /*
1742 Adjust rotation angle.
1743 */
1744 assert(image != (Image *) NULL);
1745 assert(image->signature == MagickCoreSignature);
1746 if (image->debug != MagickFalse)
1747 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1748 assert(exception != (ExceptionInfo *) NULL);
1749 assert(exception->signature == MagickCoreSignature);
1750 angle=degrees;
1751 while (angle < -45.0)
1752 angle+=360.0;
1753 for (rotations=0; angle > 45.0; rotations++)
1754 angle-=90.0;
1755 rotations%=4;
1756 /*
1757 Calculate shear equations.
1758 */
1759 integral_image=IntegralRotateImage(image,rotations,exception);
1760 if (integral_image == (Image *) NULL)
1761 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
1762 shear.x=(-tan((double) DegreesToRadians(angle)/2.0));
1763 shear.y=sin((double) DegreesToRadians(angle));
1764 if ((shear.x == 0.0) && (shear.y == 0.0))
1765 return(integral_image);
1766 if (SetImageStorageClass(integral_image,DirectClass,exception) == MagickFalse)
1767 {
1768 integral_image=DestroyImage(integral_image);
1769 return(integral_image);
1770 }
1771 if (integral_image->alpha_trait == UndefinedPixelTrait)
1772 (void) SetImageAlphaChannel(integral_image,OpaqueAlphaChannel,exception);
1773 /*
1774 Compute maximum bounds for 3 shear operations.
1775 */
1776 width=integral_image->columns;
1777 height=integral_image->rows;
1778 bounds.width=(size_t) floor(fabs((double) height*shear.x)+width+0.5);
1779 bounds.height=(size_t) floor(fabs((double) bounds.width*shear.y)+height+0.5);
1780 shear_width=(size_t) floor(fabs((double) bounds.height*shear.x)+
1781 bounds.width+0.5);
1782 bounds.x=(ssize_t) floor((double) ((shear_width > bounds.width) ? width :
1783 bounds.width-shear_width+2)/2.0+0.5);
1784 bounds.y=(ssize_t) floor(((double) bounds.height-height+2)/2.0+0.5);
1785 /*
1786 Surround image with a border.
1787 */
1788 integral_image->border_color=integral_image->background_color;
1789 integral_image->compose=CopyCompositeOp;
1790 border_info.width=(size_t) bounds.x;
1791 border_info.height=(size_t) bounds.y;
1792 rotate_image=BorderImage(integral_image,&border_info,image->compose,
1793 exception);
1794 integral_image=DestroyImage(integral_image);
1795 if (rotate_image == (Image *) NULL)
1796 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
1797 /*
1798 Rotate the image.
1799 */
1800 status=XShearImage(rotate_image,shear.x,width,height,bounds.x,(ssize_t)
1801 (rotate_image->rows-height)/2,exception);
1802 if (status == MagickFalse)
1803 {
1804 rotate_image=DestroyImage(rotate_image);
1805 return((Image *) NULL);
1806 }
1807 status=YShearImage(rotate_image,shear.y,bounds.width,height,(ssize_t)
1808 (rotate_image->columns-bounds.width)/2,bounds.y,exception);
1809 if (status == MagickFalse)
1810 {
1811 rotate_image=DestroyImage(rotate_image);
1812 return((Image *) NULL);
1813 }
1814 status=XShearImage(rotate_image,shear.x,bounds.width,bounds.height,(ssize_t)
1815 (rotate_image->columns-bounds.width)/2,(ssize_t) (rotate_image->rows-
1816 bounds.height)/2,exception);
1817 if (status == MagickFalse)
1818 {
1819 rotate_image=DestroyImage(rotate_image);
1820 return((Image *) NULL);
1821 }
1822 status=CropToFitImage(&rotate_image,shear.x,shear.y,(MagickRealType) width,
1823 (MagickRealType) height,MagickTrue,exception);
1824 rotate_image->alpha_trait=image->alpha_trait;
1825 rotate_image->compose=image->compose;
1826 rotate_image->page.width=0;
1827 rotate_image->page.height=0;
1828 if (status == MagickFalse)
1829 rotate_image=DestroyImage(rotate_image);
1830 return(rotate_image);
1831 }
1832