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