IOS的UIImagePickerController可以让用户通过相机或者相册获取想要的图片,并且通过设置allowsEditing属性允许用户在选择了图片以后对图片进行裁剪。不过在某些时候会出现正方形的裁剪框没有适配图片的情况,如下图:
这时候裁剪得到的是一张长方形图片,并且图片尺寸与UIImagePickerController设置的maxWidth和maxHeight尺寸并不符合。例如一个高和宽比例为1:2的图片,设置裁剪的maxWidth和maxHeight均为100,裁剪框的范围类似于上面右边的图片,上下留空左右框住图片的边界。最终获取到的裁剪结果为一张宽为100高为200的长方形图片。
现在需要一个图片裁剪器,能够自适应图片最窄边的裁剪,保证最终得到的图片为正方形。综合网上查询结果,决定做一个类似WP7系统自带的裁剪方案:一个固定的透明框在屏幕中央,周围留黑色遮罩,允许用户随意缩放和移动图片,不过图片的边界不会超出屏幕中的透明框。
首先考虑裁剪框,需要固定在屏幕中间不动,并且周围是透明的遮罩,决定采用UIImageView显示一张图片在屏幕的最顶层。裁剪器在我这里实际的用途为裁剪头像,定义的标准为两种,720X720的高清头像和300X300的普通头像。因此透明框的大小我选择了300X300,在屏幕中居中。
为了实现图片的滑动和缩放,选择UIScrollView作为容器来装显示用户图片的ImageView。为了保证图片边界不超出裁剪框范围,需要根据图片的长宽来定义ScrollView的ContentSize,并适应缩放。代码中我重写了SourceImage的Set方法,在Set方法中适配好图片显示和ContentSize的大小。并且在缩放倍数上作限制,如果图片本身的最短边就不足720则禁止缩放,如果超过720则最大允许缩放到裁剪框内画面的实际大小为720大小。
ContentView要根据image的大小调整,保证图片不会超出裁剪框。ContentView过大和过小都会影响裁剪。
1 - (void)setSourceImage:(UIImage *)image{ 2 if (sourceImage) { 3 [sourceImage release]; 4 sourceImage = nil; 5 } 6 sourceImage = [image retain]; 7 [_imageview setImage:self.sourceImage]; 8 CGFloat wh = sourceImage.size.width/sourceImage.size.height; 9 CGSize displaySize; 10 if (wh > 1) {//宽图 11 _imageContainer.maximumZoomScale = ((sourceImage.size.height / DEF_CUTSIZE > 1)&&(sourceImage.size.height / DEF_CUTSIZE)*(DEF_CUTSIZE/DEF_HDSIZE) > 1) ? (sourceImage.size.height / DEF_CUTSIZE)*(DEF_CUTSIZE/720) : 1;//设置放大倍数 12 isImgAvailable = (sourceImage.size.height*2 < DEF_CUTSIZE) ? NO : YES;//检查图片是否可用 13 displaySize = CGSizeMake(sourceImage.size.width*(DEF_CUTSIZE/sourceImage.size.height), DEF_CUTSIZE); 14 }else{//高图 15 _imageContainer.maximumZoomScale = ((sourceImage.size.width / DEF_CUTSIZE > 1)&&(sourceImage.size.width / DEF_CUTSIZE)*(DEF_CUTSIZE/DEF_HDSIZE) > 1) ? (sourceImage.size.width / DEF_CUTSIZE)*(DEF_CUTSIZE/720) : 1;//设置放大倍数 16 isImgAvailable = (sourceImage.size.width*2 < DEF_CUTSIZE) ? NO : YES;//检查图片是否可用 17 displaySize = CGSizeMake(DEF_CUTSIZE, sourceImage.size.height*(DEF_CUTSIZE/sourceImage.size.width)); 18 } 19 _imageview.frame = CGRectMake(0, 0, displaySize.width, displaySize.height); 20 _imageContainer.contentSize = _imageview.frame.size; 21 _imageContainer.contentInset = UIEdgeInsetsMake((SCREEN_HEIGHT - DEF_CUTSIZE)/2, (SCREEN_WIDTH - DEF_CUTSIZE)/2, (SCREEN_HEIGHT - DEF_CUTSIZE)/2, (SCREEN_WIDTH - DEF_CUTSIZE)/2); 22 23 //让图片居中显示 24 _imageContainer.contentOffset = (wh>1) ? CGPointMake((displaySize.width - SCREEN_WIDTH)/2, _imageContainer.contentOffset.y) : CGPointMake(_imageContainer.contentOffset.x, (displaySize.height - SCREEN_HEIGHT)/2); 25 }
图片的拖动和缩放做好以后,剩下的就是裁剪了。裁剪方法是从网上抄来的代码,全网都在转载不知具体出处了。定义好裁剪区域的大小和起始坐标就可以得到裁剪完成的图片了。
1 CGPoint point = CGPointMake(_imageContainer.contentOffset.x + (SCREEN_WIDTH - DEF_CUTSIZE)/2, _imageContainer.contentOffset.y + (SCREEN_HEIGHT - DEF_CUTSIZE)/2); 2 CGRect imageRect = CGRectMake(point.x * (self.sourceImage.size.width / _imageview.frame.size.width), point.y * (self.sourceImage.size.height / _imageview.frame.size.height), DEF_CUTSIZE * (self.sourceImage.size.width / _imageview.frame.size.width), DEF_CUTSIZE * (self.sourceImage.size.height / _imageview.frame.size.height)); 3 subImage = [self getImageFromImage:self.sourceImage subImageSize:imageRect.size subImageRect:imageRect]; 4 5 6 //图片裁剪 7 -(UIImage *)getImageFromImage:(UIImage*) superImage subImageSize:(CGSize)subImageSize subImageRect:(CGRect)subImageRect { 8 // CGSize subImageSize = CGSizeMake(WIDTH, HEIGHT); //定义裁剪的区域相对于原图片的位置 9 // CGRect subImageRect = CGRectMake(START_X, START_Y, WIDTH, HEIGHT); 10 CGImageRef imageRef = superImage.CGImage; 11 CGImageRef subImageRef = CGImageCreateWithImageInRect(imageRef, subImageRect); 12 UIGraphicsBeginImageContext(subImageSize); 13 CGContextRef context = UIGraphicsGetCurrentContext(); 14 CGContextDrawImage(context, subImageRect, subImageRef); 15 UIImage* returnImage = [UIImage imageWithCGImage:subImageRef]; 16 UIGraphicsEndImageContext(); //返回裁剪的部分图像 17 return returnImage; 18 }
最后添加一个动画效果,模仿Path软件中看图片的动画,将裁剪框周围的遮罩渐变为全黑,只保留裁剪好的图片,最后裁剪好的图片逐渐缩小到显示头像的地方。为了实现这个效果,在裁剪器中添加一个CGRect属性,生成裁剪器时设置好返回头像位置的坐标和大小,采用UIView的animateWithDuration方法实现动画效果即可。
在裁剪好了以后,头像位置显示的为300X300的小头像,因此增加一个查看720X720高清头像的方法。模仿Path查看图片的渐变动画效果,页面中头像以外的其他元素渐变至全黑,同时将头像放大到屏幕大小。实现方法为用一个新的Controller来显示头像变大变小的动画和展示大图,原页面的小头像仅响应点击。在presentViewController到新的Controller时会挡住原页面,为了实现半透明的渐变效果,需要设置原页面Controler的modalPresentionStyle属性为UIModalPresentationCurrentContext。新的Controller将实现进入和退出动画,在大图显示的时候点击屏幕执行退出动画。
1 // 2 // AvatarHDViewController.h 3 // CutPicTest 4 // 5 // Created by liulu on 12-12-21. 6 // Copyright (c) 2012年 liulu. All rights reserved. 7 // 8 9 #import "AvatarHDViewController.h" 10 #import "AppDelegate.h" 11 #import <QuartzCore/QuartzCore.h> 12 13 #define SCREEN_WIDTH 320 14 #define SCREEN_HEIGHT 480 15 16 @interface AvatarHDViewController () 17 18 @end 19 20 @implementation AvatarHDViewController 21 @synthesize avatarImg; 22 @synthesize beginRect; 23 @synthesize delegate; 24 25 - (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil 26 { 27 self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; 28 if (self) { 29 // Custom initialization 30 isShowHDImg = NO; 31 self.view.backgroundColor = [UIColor clearColor]; 32 33 _viewBg = [[UIView alloc]initWithFrame:CGRectMake(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT)]; 34 [self.view addSubview:_viewBg]; 35 [self.view sendSubviewToBack:_viewBg]; 36 _viewBg.backgroundColor = [UIColor blackColor]; 37 _viewBg.alpha = 0; 38 } 39 return self; 40 } 41 42 - (void)viewDidLoad 43 { 44 [super viewDidLoad]; 45 // Do any additional setup after loading the view. 46 _avatarImgV = [[UIImageView alloc]init]; 47 [self.view addSubview:_avatarImgV]; 48 [_avatarImgV.layer setMasksToBounds:YES]; 49 // [_avatarImgV.layer setCornerRadius:6.0]; 50 51 _avatarImgV.contentMode = UIViewContentModeScaleAspectFill; 52 53 } 54 55 -(void)viewDidAppear:(BOOL)animated{ 56 [super viewDidAppear:animated]; 57 [self enterAnimation]; 58 } 59 60 - (void)dealloc{ 61 [_avatarImgV release]; 62 [super dealloc]; 63 } 64 65 #pragma mark - 66 #pragma mark set 67 - (void)setAvatarImg:(UIImage *)img{ 68 avatarImg = img; 69 [_avatarImgV setImage:self.avatarImg]; 70 } 71 72 - (void)setBeginRect:(CGRect)rect{ 73 beginRect = rect; 74 _avatarImgV.frame = self.beginRect; 75 } 76 77 #pragma mark - 78 #pragma mark Animation 79 - (void)enterAnimation{ 80 // [UIView animateWithDuration:0.2 animations:^{ 81 // _viewBg.alpha = 1; 82 // }completion:^(BOOL finished){ 83 // if (finished) { 84 [UIView animateWithDuration:0.5 animations:^{ 85 _avatarImgV.frame = CGRectMake(0, (SCREEN_HEIGHT - SCREEN_WIDTH)/2, SCREEN_WIDTH, SCREEN_WIDTH); 86 _viewBg.alpha = 1; 87 }completion:^(BOOL finished){ 88 if (finished) { 89 //添加手势 90 if (!_recognizer) { 91 _recognizer = [[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(handleSwipeFromDownToUp)]; 92 } 93 [_recognizer setNumberOfTapsRequired:1]; 94 [_recognizer setNumberOfTouchesRequired:1]; 95 [self.view addGestureRecognizer:_recognizer]; 96 } 97 }]; 98 // } 99 // }]; 100 } 101 102 - (void)exitAnimation{ 103 // [UIView animateWithDuration:0.4 animations:^{ 104 // _avatarImgV.frame = self.beginRect; 105 // }completion:^(BOOL finished){ 106 // if (finished) { 107 [UIView animateWithDuration:0.5 animations:^{ 108 _viewBg.alpha = 0; 109 _avatarImgV.frame = self.beginRect; 110 }completion:^(BOOL finished){ 111 if (self.delegate&&[self.delegate respondsToSelector:@selector(hiddenHDUserImg)]) { 112 [self.delegate hiddenHDUserImg]; 113 } 114 }]; 115 // } 116 // }]; 117 } 118 119 - (void)handleSwipeFromDownToUp{ 120 //移除手势 121 for (UITapGestureRecognizer* recognizer in self.view.gestureRecognizers) { 122 if (recognizer==_recognizer) { 123 [self.view removeGestureRecognizer:recognizer]; 124 } 125 } 126 [self exitAnimation]; 127 } 128 129 - (void)didReceiveMemoryWarning 130 { 131 [super didReceiveMemoryWarning]; 132 // Dispose of any resources that can be recreated. 133 } 134 135 @end
最终效果:
因为设计的时候考虑不足,在真机上拍照以后可能出现裁剪得到的图片与裁剪框中不同的问题,这是因为ios的相机并没有根据拍照时的重力方向来将图片实际旋转,而是采用了写入图片EXIF信息的方式确保图片显示方向正确。因此在裁剪图片时还需要根据从相册获取到的UIImage对象的imageOrientation来重新计算正确的裁剪坐标和区域才能得到正确的图像。
最终我的这个裁剪器还是没有在实际当中使用,原因是为了适配高清图片,在图片最小边不足720时我直接禁止用户放大了,导致用户体验非常不好。而应用在设置头像的场景中时很多时候对于一张照片用户确实就只想截取其中的某个区域作为头像,而用户很少会在意头像是否是绝对的高清。并且300的尺寸和720的尺寸在大部分手机屏幕上实际上看起来差别并不大。设计时更应该全面考虑实际应用情况,720的分辨率只应该作为高清头像的一个上限标准,而不该强制用户使用720分辨率头像。
作者:liulu
出处:http://www.cnblogs.com/liulunet/
谢谢支持