iOS实现图片裁剪功能,基于TKImageView完善与讲解

最近做图片文字识别项目中有对图片裁剪的需求,本来使用的是TKImageView做裁剪功能,但产品说需要对图片自由裁剪,可以在TKImageView的基础上进行修改,但太耗时,于是只能自己写一个图片裁剪的工具,其中主要的想法还是借鉴TKImageView,感谢大神!

先上效果图

image
image

主要功能:
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、支持图片缩放手势,但有个问题就是放大图片后无法拖动裁剪区域

你可能感兴趣的:(iOS实现图片裁剪功能,基于TKImageView完善与讲解)