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

使用过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 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即可。
下面是效果图:

效果图1

效果图2

效果图3

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值