Coverage Report

Created: 2026-06-30 07:12

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