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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342