最近做图片文字识别项目中有对图片裁剪的需求,本来使用的是TKImageView做裁剪功能,但产品说需要对图片自由裁剪,可以在TKImageView的基础上进行修改,但太耗时,于是只能自己写一个图片裁剪的工具,其中主要的想法还是借鉴TKImageView,感谢大神!
先上效果图
主要功能:
1.四个顶点自由拖动
2.四个中心点按水平或垂直拖动
Let's do IT!
1.图片蒙层和裁剪区域线条设置
/// 设置蒙层路径
- (void)resetMaskPath {
CAShapeLayer *maskLayer = (CAShapeLayer *)self.maskView.layer.mask;
if (!maskLayer) {
maskLayer = [CAShapeLayer layer];
self.maskView.layer.mask = maskLayer;
}
UIBezierPath *clearPath = [UIBezierPath bezierPath];
[clearPath moveToPoint:self.topLeftAreaView.center];
[clearPath addLineToPoint:self.topCenterAreaView.center];
[clearPath addLineToPoint:self.topRightAreaView.center];
[clearPath addLineToPoint:self.rightCenterAreaView.center];
[clearPath addLineToPoint:self.bottomRightAreaView.center];
[clearPath addLineToPoint:self.bottomCenterAreaView.center];
[clearPath addLineToPoint:self.bottomLeftAreaView.center];
[clearPath addLineToPoint:self.leftCenterAreaView.center];
[clearPath closePath];
UIBezierPath *maskPath = [[UIBezierPath bezierPathWithRect:self.maskView.bounds] bezierPathByReversingPath];
[maskPath appendPath:clearPath];
maskLayer.path = maskPath.CGPath;
self.lineLayer.path = clearPath.CGPath;
}
2.拖动四个顶点的方法
/// 拖动顶点裁剪范围
- (void)movePoint:(UIPanGestureRecognizer *)pan {
AreaView *view = (AreaView *)pan.view;
if (pan.state == UIGestureRecognizerStateChanged) {
CGPoint location = [pan locationInView:self.areaView];
CGFloat x = location.x;
CGFloat y = location.y;
// 将裁剪范围限制在图片范围内
if (!CGRectContainsPoint(self.areaView.frame, location)) {
if (location.x < CGRectGetMinX(self.areaView.frame)) {
x = CGRectGetMinX(self.areaView.frame);
}
if (location.x > CGRectGetMaxX(self.areaView.frame)) {
x = CGRectGetMaxX(self.areaView.frame);
}
if (location.y < CGRectGetMinX(self.areaView.frame)) {
y = CGRectGetMinY(self.areaView.frame);
}
if (y > CGRectGetMaxY(self.areaView.frame)) {
y = CGRectGetMaxY(self.areaView.frame);
}
}
if (view == self.topLeftAreaView) {
x = MIN(x, self.topCenterAreaView.center.x - self.minClipWidthAndHeight/2);
y = MIN(y, self.leftCenterAreaView.center.y - self.minClipWidthAndHeight/2);
} else if (view == self.bottomLeftAreaView) {
x = MIN(x, self.topCenterAreaView.center.x - self.minClipWidthAndHeight/2);
y = MAX(y, self.leftCenterAreaView.center.y + self.minClipWidthAndHeight/2);
} else if (view == self.topRightAreaView) {
x = MAX(x, self.topCenterAreaView.center.x + self.minClipWidthAndHeight/2);
y = MIN(y, self.rightCenterAreaView.center.y - self.minClipWidthAndHeight/2);
} else if (view == self.bottomRightAreaView) {
x = MAX(x, self.bottomCenterAreaView.center.x + self.minClipWidthAndHeight/2);
y = MAX(y, self.rightCenterAreaView.center.y + self.minClipWidthAndHeight/2);
}
view.center = CGPointMake(x, y);
[self resetMaskPath];
}
}
3.拖动中心点方法
/// 拖动中心点裁剪范围
- (void)moveCenterPoint:(UIPanGestureRecognizer *)pan {
CenterAreaView *view = (CenterAreaView *)pan.view;
if (pan.state == UIGestureRecognizerStateChanged) {
CGPoint point = [pan locationInView:self.areaView];
if (view == self.leftCenterAreaView ||
view == self.rightCenterAreaView) {
CGFloat x = point.x;
if (x < CGRectGetMinX(self.areaView.frame)) {
x = CGRectGetMinX(self.areaView.frame);
}
if (x > CGRectGetMaxX(self.areaView.frame)) {
x = CGRectGetMaxX(self.areaView.frame);
}
if (view == self.leftCenterAreaView) {
x = MIN(x, self.topCenterAreaView.center.x - self.minClipWidthAndHeight/2);
} else {
x = MAX(x, self.topCenterAreaView.center.x + self.minClipWidthAndHeight/2);
}
CGPoint center = view.center;
CGPoint center1 = view.relationView1.center;
CGPoint center2 = view.relationView2.center;
view.center = CGPointMake(x, center.y);
view.relationView1.center = CGPointMake(x, center1.y);
view.relationView2.center = CGPointMake(x, center2.y);
} else {
CGFloat y = point.y;
if (y < CGRectGetMinY(self.areaView.frame)) {
y = CGRectGetMinY(self.areaView.frame);
}
if (y > CGRectGetMaxY(self.areaView.frame)) {
y = CGRectGetMaxY(self.areaView.frame);
}
if (view == self.topCenterAreaView) {
y = MIN(y, self.leftCenterAreaView.center.y - self.minClipWidthAndHeight/2);
} else {
y = MAX(y, self.leftCenterAreaView.center.y + self.minClipWidthAndHeight/2);
}
CGPoint center = view.center;
CGPoint center1 = view.relationView1.center;
CGPoint center2 = view.relationView2.center;
view.center = CGPointMake(center.x, y);
view.relationView1.center = CGPointMake(center1.x, y);
view.relationView2.center = CGPointMake(center2.x, y);
}
[self resetMaskPath];
}
}
4.获取裁剪后图片(主要是根据图片大小转换贝塞尔曲线)
/// 获取裁剪后的图片
- (UIImage *)currentImage {
CGFloat hScale = self.originImage.size.width/self.imageView.bounds.size.width;
CGFloat vScale = self.originImage.size.height/self.imageView.bounds.size.height;
/*
将imageview的路径转换为图片的路径
这样可以使图片在切割时不用缩放,防止图片失真
*/
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:CGPointMake(self.topLeftAreaView.center.x*hScale, self.topLeftAreaView.center.y*vScale)];
[path addLineToPoint:CGPointMake(self.topCenterAreaView.center.x*hScale, self.topCenterAreaView.center.y*vScale)];
[path addLineToPoint:CGPointMake(self.topRightAreaView.center.x*hScale, self.topRightAreaView.center.y*vScale)];
[path addLineToPoint:CGPointMake(self.rightCenterAreaView.center.x*hScale, self.rightCenterAreaView.center.y*vScale)];
[path addLineToPoint:CGPointMake(self.bottomRightAreaView.center.x*hScale, self.bottomRightAreaView.center.y*vScale)];
[path addLineToPoint:CGPointMake(self.bottomCenterAreaView.center.x*hScale, self.bottomCenterAreaView.center.y*vScale)];
[path addLineToPoint:CGPointMake(self.bottomLeftAreaView.center.x*hScale, self.bottomLeftAreaView.center.y*vScale)];
[path addLineToPoint:CGPointMake(self.leftCenterAreaView.center.x*hScale, self.leftCenterAreaView.center.y*vScale)];
[path closePath];
return [self.originImage clipWithPath:[path copy]];
}
5.使用贝塞尔曲线对图片进行裁剪,方法如下
注:从相册获取文中效果图的图片裁剪后出现上下颠倒的状态,这是因为图片的imageOrientation属性不是UIImageOrientationUp,所以拿到图片后需要先判断图片是不是UIImageOrientationUp状态,如果不是需要做相应处理:1.将图片的状态修改为UIImageOrientationUp状态;2.将图片旋转到UIImageOrientationUp状态;我采用的是第一种方案(因为代码少😹)
// 根据path切割图片
- (UIImage*)clipWithPath:(UIBezierPath*)path {
@autoreleasepool {
UIImage * image = [self copy];
// 解决图片不是朝上的问题 - 重置为UIImageOrientationUp
if (image.imageOrientation != UIImageOrientationUp) {
UIGraphicsBeginImageContextWithOptions(image.size, NO, image.scale);
[image drawInRect:(CGRect){0, 0, image.size}];
image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
}
//开始绘制图片
UIGraphicsBeginImageContextWithOptions(image.size, NO, image.scale);
CGContextRef contextRef = UIGraphicsGetCurrentContext();
UIBezierPath * clipPath = path;
CGContextAddPath(contextRef, clipPath.CGPath);
CGContextClosePath(contextRef);
CGContextClip(contextRef);
//坐标系转换
CGContextTranslateCTM(contextRef, 0, image.size.height);
CGContextScaleCTM(contextRef, image.scale, -image.scale);
CGRect drawRect = CGRectMake(0, 0, image.size.width, image.size.height);
CGContextDrawImage(contextRef, drawRect, [image CGImage]);
UIImage *destImg = UIGraphicsGetImageFromCurrentImageContext();
//结束绘画
UIGraphicsEndImageContext();
//转成png格式 会保留透明
NSData * data = UIImageJPEGRepresentation(destImg, .5);
UIImage * dImage = [UIImage imageWithData:data];
return dImage;
}
}
注意:每次调用UIGraphicsBeginImageContextWithOptions方法获取上下文,图片编辑结束后一定不要忘记调用UIGraphicsEndImageContext方法关闭上下文,否则内存会不断增加,直至溢出!
遇到的问题:
相机或相册获取图片过大在运行时内存会瞬间提高很多(60M左右,可能会更大),我的解决方案是在获取图片时对图片进行了裁剪(对图片质量要求高的不适用)
附方法:具体裁剪的大小根据自己的需求设置
- (UIImage *)clipImage:(UIImage *)image {
if (image.size.width > 500 || image.size.height > 500) {
CGFloat scale = image.size.height/image.size.width;
CGFloat width = image.size.width;
if (image.size.width > 500) {
width = 500;
}
CGSize size = CGSizeMake(width, width*scale);
UIGraphicsBeginImageContextWithOptions(size, NO, image.scale);
[image drawInRect:(CGRect){0, 0, size}];
image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
}
return image;
}
可扩展的功能:(有兴趣的可以尝试一下)
1.图片放大、缩小
2.整体拖动裁剪区域
更新:
1、优化锚点拖动 - 只有四个顶点可以自由移动,中间的锚点只能上下或左右移动,这样切的图片不会太不规则(其实是因为产品要求的),且中间锚点一直处于中间状态。
2、可移动裁剪区域
3、支持图片缩放手势,但有个问题就是放大图片后无法拖动裁剪区域