使用过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. DW做一个点击切换照片的功能

    首先引入JQ的插件 下面那些是我的页面那些布局跟照片 最下面的那些代码是js的那些点击切换的代码 这些css基础代码就不说了就是给一下那定位 5.这些就是效果图了点击那些文字切换照片,这个第二张我点击 ...

  2. # 独立开发变现周刊(第57期):开发一个免费照片编辑器,一个人每月收入10万美元...

    分享独立开发.产品变现相关内容,每周五发布. 目录 1.Raindrop: 一站式书签管理工具 2.NotionPet: 嵌入式小组件库 3.Gummysearch: 在Reddit上发现目标用户的机 ...

  3. Win10 系统无法用自带的照片编辑器查看照片的原因

    小编前几天遇到一个关于Win10系统照片编辑器的问题. 电脑里面的一张图片,用自带的照片编辑器打开,系统弹出如下界面: 解决方法: 1.打开系统注册变:win键+R,输入regedit,打开注册表 2 ...

  4. macos文本编辑器_适用于macOS的最佳免费照片编辑器

    macos文本编辑器 If you're a Mac-using professional photographer, you're probably already paying $10 a mon ...

  5. 做一个“多人在线编辑器”,你会怎么开始

    看似只是一个简单的问题,但是其中却隐含了非常多的知识,对于"多人在线编辑器"这么一个产品来说,如果让你来负责设计并开发,你会怎么去开始一步步展开工作,其中主要考察的并不是让你迅速的 ...

  6. 【跟我一起学Unity3D】做一个2D的90坦克大战之地图编辑器

    从10月20号到现在,Unity3D也学了10天了,对于Unity3D也有了一个大致的了解,有必要做一个小游戏来检测一下自己的学习成果了.经过两天的努力,终于总算是做出来了一个可以玩的坦克大战了.首先 ...

  7. 用Python做一个简陋的文本编辑器

    Hello,大家好,我是Gary! 想必大家都用过Windows系统自带的"记事本"应用吧?它是一个编辑txt文件的应用. 当然,咱们可不能只局限于记事本,咱们也可以自己做一个! ...

  8. 如何做一个高级的文本编辑器 textarea,拥有快捷键操作

    如何做一个高级的文本编辑器 textarea,拥有快捷键操作 最近想做一个高级点的 textarea ,支持 JetBrains 系列软件的快捷键,比如: CTRL+ D 复制当前行. Tab 在前面 ...

  9. wxFormBuilder摸索--小白上手--做一个编辑器

    前言 wxFormbuilder的文章非常少,也没有一个官方指南,非常坑爹.不过网络上有很多大神,参考了一下,然后自己再试试,页算是能摸索出用法.wxFormbuilder只能设计布局,事件只能链接一 ...

最新文章

  1. 趣谈网络协议笔记-二(第十六讲上)
  2. AVB中将公钥转换成字符数组头文件的实现
  3. 【今日CS 视觉论文速览】Fri, 21 Dec 2018
  4. laravel生成php代码,laravel代码生成器
  5. Python函数中单独一个星号或斜线作为形参的含义
  6. “编程能力差,90%会输在这点上!”谷歌开发:方法不对,努力也白费
  7. python入侵个人电脑的步骤图解台式_入侵渗透专用的python小脚本脚本安全 -电脑资料...
  8. Google退出中国,我同意了。
  9. 不定期备考小tips[常微][2] #20210528
  10. [GW-CTF2019] babyvm
  11. hadoop 8088端口网页无法打开的原因分析
  12. SQLI DUMB SERIES-6
  13. ros学习——gmapping建图
  14. resure挽救笔记本系统和一些相关的操作记录
  15. 部分HTTPS网站无法访问的可能原因
  16. Paperreading之五  Stacked Hourglass Networks(SHN)和源码阅读(PyTorch版本)
  17. 苹果手机消息先发给服务器,iPhone接收微信信息延迟,可能是这3个问题导致的,赶紧自查一下...
  18. 基于WEB的小型酒店管理系统的设计与实现
  19. ArcGIS操作系列9- Arcmap 线分段
  20. http://www.open-open.com/lib/view/open1322977704296.html

热门文章

  1. vim配置python命令自动补全
  2. 嵌入式硬件系统电磁兼容设计
  3. IDA memory dump
  4. CRICQSCRI2012MCRIGAI显色性计算软件工具
  5. 将word直接转成chm的软件
  6. python多重背包问题_01-背包、完全背包、多重背包及其相关应用
  7. 代码生成器插件开发---代码生成项目框架的创建(2)
  8. 抽象类,接口, final,选择排序
  9. 【合集】高等数学:繁星随想录
  10. 一篇文章全面了解光分路器、PLC分路器、拉锥分路器