您的位置:首页 > 其它

做一个属于自己的照片编辑器

2015-09-16 02:03 465 查看
使用过UIImagePickerController的童鞋们都知道,iOS在从相册或者摄像头后选择照片后可以编辑(选择照片的一部分剪切下来),只需要将UIImagePickerController的allowsEditing属性设为YES即可。哇,so easy!

真的如此简单吗?你如果这样想就可以不要继续看下去了。

用过的童鞋应该都会这么认为(至少我是这么认为)系统的那个渣渣真是他妈的弱爆了,主要体现在如下两点:

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即可。

下面是效果图:





内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息