做一个属于自己的照片编辑器
2015-09-16 02:03
465 查看
使用过UIImagePickerController的童鞋们都知道,iOS在从相册或者摄像头后选择照片后可以编辑(选择照片的一部分剪切下来),只需要将UIImagePickerController的allowsEditing属性设为YES即可。哇,so easy!
真的如此简单吗?你如果这样想就可以不要继续看下去了。
用过的童鞋应该都会这么认为(至少我是这么认为)系统的那个渣渣真是他妈的弱爆了,主要体现在如下两点:
1、只能裁剪正方形;
2、照片边缘部分可能无法移动到裁剪区域。
哦,怎么办,产品狗就要给他弄一个长方形,甚至裁剪一个不规则形状。
哥前两天就遇到了这么狗血的事情,怎么办?哥百度、谷歌、github都了个遍,好像没找到有什么卵用的东西,于是乎只能自己动手写一个了。
好了,进入正题。如何实现一个可以剪切任意形状的图片编辑器?
需要考虑如下两个问题:
1、用户如何操作,以及界面的实现
2、如何裁剪。
首先解决第一个问题:
系统的图片编辑器是选中框不能移动,可以通过缩放和移动图片,从而确定需要选中的图片区域,此方法的优点是图片可以缩放,小图片可以放大来看清楚。但说真的,我不习惯这种操作。
我采取的方式是,将图片放在确定的位置,通过缩放和移动选中框而确定选中的图片区域。
实现如下:为了实现不规则形状选择时,未选中部分也有半透明阴影,通过在遮挡视图上绘制而成:
遮挡视图.h
遮挡视图.m
遮挡视图已经解决,嘿,为什么把选中部分的形状绘制在中心?因为这样的话,在移动选择区域时只需要移动整个遮挡视图,而无需每次都计算绘制形状的位置,为了保证无论如何移动,遮挡视图都会覆盖整个父视图,因此,该遮挡视图的大小必选是父视图大小的9倍。
接下来看控制器代码:
.h
.m
上面完成了第一个问题,下面来解决裁剪的问题,在MGCEditImageViewController的实现中添加如下代码:
添加一个UIImage的类别,实现如下裁剪代码:
好了,到此,已经实现了一个可以裁剪任何比例图片的编辑器了,在裁剪圆形图片了,代码还未上传到托管服务器,需要源码的伙伴可以联系我:770322699@qq.com。
demo中,为了扩展强,裁剪圆形图片仍然裁剪的矩形,在显示时通过设置mask显示圆形,若需要裁剪圆形,在裁剪时添加圆形path即可。
下面是效果图:
真的如此简单吗?你如果这样想就可以不要继续看下去了。
用过的童鞋应该都会这么认为(至少我是这么认为)系统的那个渣渣真是他妈的弱爆了,主要体现在如下两点:
1、只能裁剪正方形;
2、照片边缘部分可能无法移动到裁剪区域。
哦,怎么办,产品狗就要给他弄一个长方形,甚至裁剪一个不规则形状。
哥前两天就遇到了这么狗血的事情,怎么办?哥百度、谷歌、github都了个遍,好像没找到有什么卵用的东西,于是乎只能自己动手写一个了。
好了,进入正题。如何实现一个可以剪切任意形状的图片编辑器?
需要考虑如下两个问题:
1、用户如何操作,以及界面的实现
2、如何裁剪。
首先解决第一个问题:
系统的图片编辑器是选中框不能移动,可以通过缩放和移动图片,从而确定需要选中的图片区域,此方法的优点是图片可以缩放,小图片可以放大来看清楚。但说真的,我不习惯这种操作。
我采取的方式是,将图片放在确定的位置,通过缩放和移动选中框而确定选中的图片区域。
实现如下:为了实现不规则形状选择时,未选中部分也有半透明阴影,通过在遮挡视图上绘制而成:
遮挡视图.h
#import <UIKit/UIKit.h> typedef NS_ENUM(NSInteger, MGCEditSelectImageViewShapeStyle) { MGCEditSelectImageViewShapeStyle_rect, MGCEditSelectImageViewShapeStyle_circle, }; // 遮挡视图(目前只支持矩形和圆形,其它形状可以类似实现) @interface MGCEditSelectImageView : UIView @property (nonatomic, readonly) CGFloat width; @property (nonatomic, readonly) CGFloat height; @property (nonatomic, readonly) MGCEditSelectImageViewShapeStyle style; /** * 画形状 * * @param width 宽度、长半径 * @param height 高度、短半径 * @param style 形状类型,当画矩形时,若width或者height中的一个为0,那么画正方形,当画椭圆时,若width或者height为0时,画圆 */ - (void)drawShapeWithWidth:(CGFloat)width height:(CGFloat)height shapeStyle:(MGCEditSelectImageViewShapeStyle)style; @end
遮挡视图.m
#import "MGCEditSelectImageView.h" @interface MGCEditSelectImageView () @property (nonatomic, assign) CGFloat width; @property (nonatomic, assign) CGFloat height; @property (nonatomic, assign) MGCEditSelectImageViewShapeStyle style; @end @implementation MGCEditSelectImageView // Only override drawRect: if you perform custom drawing. // An empty implementation adversely affects performance during animation. - (void)drawRect:(CGRect)rect { CGContextRef context = UIGraphicsGetCurrentContext(); if (self.superview) { // 绘制边框, CGContextAddRect(context, CGRectMake(-self.frame.origin.x - 1, -self.frame.origin.y - 1, self.superview.frame.size.width + 2, self.superview.frame.size.height + 2)); }else{ CGContextAddRect(context, self.bounds); } [self drawShapeContext:context]; // 设置触笔颜色 [[UIColor whiteColor] setStroke]; // 将未选中部分蛇者为阴影半透明颜色 [[[UIColor blackColor] colorWithAlphaComponent:0.6] setFill]; CGContextDrawPath(context, kCGPathEOFillStroke); } #pragma mark - pravate // 绘制形状 - (void)drawShapeContext:(CGContextRef)context { if (self.width || self.height) { if (self.width <= 0 && self.height > 0) { self.width = self.height; }else if (self.height <= 0 && self.width > 0){ self.height = self.width; } CGFloat x = (self.frame.size.width - self.width) / 2; CGFloat y = (self.frame.size.height - self.height) / 2; CGRect rect = CGRectMake(x, y, self.width, self.height); if (self.style == MGCEditSelectImageViewShapeStyle_rect) { // 绘制矩形 CGContextAddRect(context, rect); }else 13de8 if (self.style == MGCEditSelectImageViewShapeStyle_circle){ // 绘制圆形 CGContextAddEllipseInRect(context, rect); } } } #pragma mark - public - (void)drawShapeWithWidth:(CGFloat)width height:(CGFloat)height shapeStyle:(MGCEditSelectImageViewShapeStyle)style { self.width = width; self.height = height; self.style = style; [self setNeedsDisplay]; } @end
遮挡视图已经解决,嘿,为什么把选中部分的形状绘制在中心?因为这样的话,在移动选择区域时只需要移动整个遮挡视图,而无需每次都计算绘制形状的位置,为了保证无论如何移动,遮挡视图都会覆盖整个父视图,因此,该遮挡视图的大小必选是父视图大小的9倍。
接下来看控制器代码:
.h
#import <UIKit/UIKit.h> #import "MGCEditSelectImageView.h" @class MGCEditImageViewController; @protocol MGCEditImageViewControllerDelegate <NSObject> // 编辑完成 - (void)editDidFinsh:(MGCEditImageViewController *)controller originalImage:(UIImage *)originalImage editImage:(UIImage *)editImage; // 编辑取消 - (void)editCancel:(MGCEditImageViewController *)controller origiinalImage:(UIImage *)originalImage; @end @interface MGCEditImageViewController : UIViewController @property (nonatomic, strong) UIImage *image; @property (nonatomic, assign) MGCEditSelectImageViewShapeStyle editStyle; // @property (nonatomic, assign) CGFloat ratioW_Y; // 宽高比 // 默认为1 @property (nonatomic, assign) CGFloat suitableWidth; // 最适合的宽度,或者直径 @property (nonatomic, weak) id<MGCEditImageViewControllerDelegate>delegate; @end
.m
#import "MGCEditImageViewController.h" #import "UIImage+addition.h" @interface MGCEditImageViewController () @property (nonatomic, strong) UIImageView *imageView; // 图片视图 @property (nonatomic, strong) MGCEditSelectImageView *selecterView; // 选择视图 @property (nonatomic, strong) UIView *bottomBar; @property (nonatomic, strong) UIButton *cancelButton; @property (nonatomic, strong) UIButton *selectButton; @property (nonatomic, assign) CGFloat selectViewScale; // 默认为1 @property (nonatomic, assign) CGPoint panStarPoint; @property (nonatomic, assign) CGFloat pinchLastScale; @end @implementation MGCEditImageViewController - (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view. self.view.backgroundColor = [UIColor blackColor]; [self.view addSubview:self.imageView]; [self.view addSubview:self.bottomBar]; [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"|-0-[_bottomBar]-0-|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(_bottomBar)]]; [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[_bottomBar(44)]-0-|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(_bottomBar)]]; UITapGestureRecognizer *tapGes = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(doubleClickSelectView:)]; tapGes.numberOfTapsRequired = 2; [self.view addGestureRecognizer:tapGes]; UIPinchGestureRecognizer *pinchGes = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(zoomSelectView:)]; [self.view addGestureRecognizer:pinchGes]; UIPanGestureRecognizer *panGes = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(moveSelectView:)]; [self.view addGestureRecognizer:panGes]; } - (instancetype)init { if (self = [super init]) { self.selectViewScale = 1; self.ratioW_Y = 1; self.editStyle = MGCEditSelectImageViewShapeStyle_rect; self.panStarPoint = CGPointZero; } return self; } - (void)didReceiveMemoryWarning { [super didReceiveMemoryWarning]; // Dispose of any resources that can be recreated. } - (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; [self.navigationController setNavigationBarHidden:YES animated:YES]; } - (void)viewWillDisappear:(BOOL)animated { [self.navigationController setNavigationBarHidden:NO animated:YES]; [[UIApplication sharedApplication] setStatusBarHidden:NO]; [super viewWillDisappear:animated]; } - (void)viewWillLayoutSubviews { [super viewWillLayoutSubviews]; // 设置图片视图的位置和大小 [self updateImageViewFram]; // 更新选择视图的位置 [self updateSelectViewFramWithCenter:CGPointMake(self.view.frame.size.width / 2, self.view.frame.size.height / 2)]; // 指定选中区域的大小和图片视图大小的比例 self.selectViewScale = [self suitableScale]; // 根据selectViewScale从新绘制选中区域 [self updateSelectView]; } #pragma mark - get and set // 图片视图 - (UIImageView *)imageView { if (!_imageView) { _imageView = [[UIImageView alloc] init]; _imageView.backgroundColor = [UIColor clearColor]; } return _imageView; } // 选择视图(遮挡视图) - (MGCEditSelectImageView *)selecterView { if (!_selecterView) { _selecterView = [[MGCEditSelectImageView alloc] init]; _selecterView.backgroundColor = [UIColor clearColor]; _selecterView.userInteractionEnabled = NO; _selecterView.translatesAutoresizingMaskIntoConstraints = NO; } return _selecterView; } - (void)setImage:(UIImage *)image { _image = image; self.imageView.image = image; // 更新图片视图的位置 [self updateImageViewFram]; } // 取消按钮 - (UIButton *)cancelButton { if (!_cancelButton) { _cancelButton = [self barButtonWithTitle:@"取消"]; [_cancelButton addTarget:self action:@selector(cancel:) forControlEvents:UIControlEventTouchUpInside]; } return _cancelButton; } // 确定按钮 - (UIButton *)selectButton { if (!_selectButton) { _selectButton = [self barButtonWithTitle:@"使用照片"]; [_selectButton addTarget:self action:@selector(confirm:) forControlEvents:UIControlEventTouchUpInside]; } return _selectButton; } // 底部工具栏 - (UIView *)bottomBar { if (!_bottomBar) { _bottomBar = [[UIView alloc] init]; _bottomBar.backgroundColor = [UIColor clearColor]; _bottomBar.translatesAutoresizingMaskIntoConstraints = NO; [_bottomBar addSubview:self.cancelButton]; [_bottomBar addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"|-20-[_cancelButton]" options:0 metrics:nil views:NSDictionaryOfVariableBindings(_cancelButton)]]; [_bottomBar addConstraint:[NSLayoutConstraint constraintWithItem:self.cancelButton attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:_bottomBar attribute:NSLayoutAttributeCenterY multiplier:1 constant:0]]; [_bottomBar addSubview:self.selectButton]; [_bottomBar addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"[_selectButton]-20-|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(_selectButton)]]; [_bottomBar addConstraint:[NSLayoutConstraint constraintWithItem:self.selectButton attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:_bottomBar attribute:NSLayoutAttributeCenterY multiplier:1 constant:0]]; } return _bottomBar; } #pragma mark - private - (UIButton *)barButtonWithTitle:(NSString *)title { UIButton *button = [UIButton buttonWithType:UIButtonTypeSystem]; button.backgroundColor = [UIColor clearColor]; [button setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal]; [button setTitle:title forState:UIControlStateNormal]; button.translatesAutoresizingMaskIntoConstraints = NO; return button; } // 图片和self.view的比例 - (CGFloat)ratioImageToView { CGFloat scaleX = self.image.size.width / self.view.frame.size.width; CGFloat scaleY = self.image.size.height / self.view.frame.size.height; return MAX(scaleX, scaleY); } // 更新图片视图视图 - (void)updateImageViewFram { // 获取图片和self.view的正确显示比例 CGFloat maxScale = [self ratioImageToView]; CGFloat width = self.image.size.width / maxScale; CGFloat height = self.image.size.height / maxScale; self.imageView.frame = CGRectMake(0, 0, width, height); self.imageView.center = CGPointMake(self.view.frame.size.width / 2, self.view.frame.size.height / 2); } // 更新选择视图位置和大小 - (void)updateSelectViewFramWithCenter:(CGPoint)center { // 大小设置为父视图的9倍,保证始终覆盖父视图 self.selecterView.frame = CGRectMake(0, 0, self.view.frame.size.width * 3, self.view.frame.size.height * 3); // 不需要考虑左右或者上下都超出的情况,因为形状的大小有selectViewScale控制 if (center.x - self.selecterView.width / 2 < self.imageView.frame.origin.x) { center.x = self.imageView.frame.origin.x + self.selecterView.width / 2; }else if (center.x + self.selecterView.width / 2 > CGRectGetMaxX(self.imageView.frame)){ center.x = CGRectGetMaxX(self.imageView.frame) - self.selecterView.width / 2; } if (center.y - self.selecterView.height / 2 < self.imageView.frame.origin.y) { center.y = self.imageView.frame.origin.y + self.selecterView.height / 2; }else if (center.y + self.selecterView.height / 2 > CGRectGetMaxY(self.imageView.frame)){ center.y = CGRectGetMaxY(self.imageView.frame) - self.selecterView.height / 2; } self.selecterView.center = center; } - (void)updateSelectView { if (self.selectViewScale > 1) { self.selectViewScale = 1; }else if (self.selectViewScale < 0.01){ self.selectViewScale = 0.01; } CGFloat imageViewRadioW_Y = self.imageView.frame.size.width / self.imageView.frame.size.height; CGFloat width = 0; CGFloat height = 0; if (imageViewRadioW_Y > self.ratioW_Y) { height = self.imageView.frame.size.height * self.selectViewScale; width = height * self.ratioW_Y; }else{ width = self.imageView.frame.size.width * self.selectViewScale; height = width / self.ratioW_Y; } [self.selecterView drawShapeWithWidth:width height:height shapeStyle:self.editStyle]; } // 根据给出的最合适的宽度和宽高比,计算最合适的选择形状的和图片视图的比例 - (CGFloat)suitableScale { if (self.suitableWidth > 0) { CGFloat suitableHeight = self.suitableWidth / self.ratioW_Y; return MIN(MAX(suitableHeight / self.imageView.frame.size.height, self.suitableWidth / self.imageView.frame.size.width), 1) ; }else{ return 1; } } #pragma mark - action // 双击试,回复最合适的大小,并置于中心 - (void)doubleClickSelectView:(UITapGestureRecognizer *)sender { self.selectViewScale = [self suitableScale]; [self updateSelectView]; self.selecterView.center = self.view.center; } // 缩放选择区域 - (void)zoomSelectView:(UIPinchGestureRecognizer *)sender { if (self.pinchLastScale == 0) { self.pinchLastScale = sender.scale; return; } sender.scale = sender.scale - self.pinchLastScale + 1; CGFloat scale = self.selectViewScale * sender.scale; self.selectViewScale = scale > 1 ? 1 : scale; [self updateSelectView]; [self updateSelectViewFramWithCenter:self.selecterView.center]; self.pinchLastScale = sender.scale; } // 移动选择区域,实则移动整个选择视图 - (void)moveSelectView:(UIPanGestureRecognizer *)sender { CGPoint startCenter = self.selecterView.center; CGRect shapeFramToSeleView = [self.selecterView convertRect:CGRectMake(self.selecterView.frame.size.width / 2 - self.selecterView.width / 2, self.selecterView.frame.size.height / 2 - self.selecterView.height / 2, self.selecterView.width, self.selecterView.height) toView:self.view]; BOOL startPointInShapeRect = self.panStarPoint.x >= shapeFramToSeleView.origin.x && self.panStarPoint.y >= shapeFramToSeleView.origin.y && self.panStarPoint.x <= CGRectGetMaxX(shapeFramToSeleView) && self.panStarPoint.y <= CGRectGetMaxY(shapeFramToSeleView); CGPoint gesCenter = [sender locationInView:sender.view]; if (sender.state == UIGestureRecognizerStateChanged) { if (startPointInShapeRect) { CGPoint center = gesCenter; [self updateSelectViewFramWithCenter:center]; self.panStarPoint = gesCenter; } }else if (sender.state == UIGestureRecognizerStateEnded){ self.panStarPoint = CGPointZero; }else if (sender.state == UIGestureRecognizerStateBegan){ self.panStarPoint = gesCenter; } if (self.selecterView.center.x != startCenter.x || self.selecterView.center.y != startCenter.y) { [self updateSelectView]; } } - (void)confirm:(UIButton *)sender { if (self.delegate && [self.delegate respondsToSelector:@selector(editDidFinsh:originalImage:editImage:)]) { [self.delegate editDidFinsh:self originalImage:self.image editImage:[self editImage]]; } } - (void)cancel:(UIButton *)sender { if (self.delegate && [self.delegate respondsToSelector:@selector(editCancel:origiinalImage:)]) { [self.delegate editCancel:self origiinalImage:self.image]; } }
上面完成了第一个问题,下面来解决裁剪的问题,在MGCEditImageViewController的实现中添加如下代码:
- (UIImage *)editImage { CGFloat ratioImageToView = [self ratioImageToView]; CGRect selectToImageView = [self.selecterView convertRect:CGRectMake(self.selecterView.frame.size.width / 2 - self.selecterView.width / 2, self.selecterView.frame.size.height / 2 - self.selecterView.height / 2, self.selecterView.width, self.selecterView.height) toView:self.imageView]; CGFloat ratio = ratioImageToView; CGRect resultRect = CGRectMake(selectToImageView.origin.x * ratio, selectToImageView.origin.y * ratio, selectToImageView.size.width * ratio, selectToImageView.size.height * ratio); return [self.image cutFromRect:resultRect]; }
添加一个UIImage的类别,实现如下裁剪代码:
// 裁剪图片 - (UIImage *)cutFromRect:(CGRect)rect { UIGraphicsBeginImageContext(CGSizeMake(self.size.width, self.size.height)); CGContextRef context = UIGraphicsGetCurrentContext(); CGContextAddRect(context, rect); CGContextClip(context); [self drawInRect:CGRectMake(0, 0, self.size.width, self.size.height)]; UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); UIGraphicsBeginImageContext(rect.size); context = UIGraphicsGetCurrentContext(); [newImage drawInRect:CGRectMake(-rect.origin.x, -rect.origin.y, self.size.width, self.size.height)]; newImage = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); return newImage; }
好了,到此,已经实现了一个可以裁剪任何比例图片的编辑器了,在裁剪圆形图片了,代码还未上传到托管服务器,需要源码的伙伴可以联系我:770322699@qq.com。
demo中,为了扩展强,裁剪圆形图片仍然裁剪的矩形,在显示时通过设置mask显示圆形,若需要裁剪圆形,在裁剪时添加圆形path即可。
下面是效果图:
相关文章推荐
- 师父照片集
- 基于jQuery+HttpHandler实现图片裁剪效果代码(适用于论坛, SNS)
- JQuery Jcrop 实现图片裁剪的插件
- PHPThumb图片处理实例
- Java图片处理 (文字水印、图片水印、缩放、补白)代码实例
- PHP图片处理之使用imagecopyresampled函数裁剪图片例子
- 常用的php图片处理类(水印、等比缩放、固定高宽)分享
- js+jquery实现图片裁剪功能
- php结合imgareaselect实现图片裁剪
- android获取照片的快照 思路及实现方法
- PHP图片处理之图片背景、画布操作
- PHP图片处理之使用imagecopyresampled函数实现图片缩放例子
- PHP实现图片裁剪、添加水印效果代码
- 使用JavaScript+canvas实现图片裁剪
- Java如何实现图片裁剪预览功能
- 摘自织梦CMS中的图片处理类
- 开源中国 OsChina Android 客户端源码分析(6)拍照、图库、裁剪
- 10个最佳的PHP图像操作库
- PHP图片的裁剪与缩放
- PHP图像处理类库MagickWand用法实例分析