iOS圖片自由裁剪

最近做圖片文字識(shí)別項(xiàng)目中有對(duì)圖片裁剪的需求斥废,本來使用的是TKImageView做裁剪功能饱亿,但產(chǎn)品說需要對(duì)圖片自由裁剪蚜退,可以在TKImageView的基礎(chǔ)上進(jìn)行修改闰靴,但太耗時(shí),于是只能自己寫一個(gè)圖片裁剪的工具钻注,其中主要的想法還是借鑒TKImageView蚂且,感謝大神!

先上效果圖

選中裁剪區(qū)域

完成裁剪

主要功能:
1.四個(gè)頂點(diǎn)自由拖動(dòng)
2.四個(gè)中心點(diǎn)按水平或垂直拖動(dòng)

Let's do IT!

1.圖片蒙層和裁剪區(qū)域線條設(shè)置

/// 設(shè)置蒙層路徑
- (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.拖動(dòng)四個(gè)頂點(diǎn)的方法

/// 拖動(dòng)頂點(diǎn)裁剪范圍
- (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;
        // 將裁剪范圍限制在圖片范圍內(nèi)
        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.拖動(dòng)中心點(diǎn)方法

/// 拖動(dòng)中心點(diǎn)裁剪范圍
- (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.獲取裁剪后圖片(主要是根據(jù)圖片大小轉(zhuǎn)換貝塞爾曲線)

/// 獲取裁剪后的圖片
- (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的路徑轉(zhuǎn)換為圖片的路徑
     這樣可以使圖片在切割時(shí)不用縮放幅恋,防止圖片失真
     */
    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.使用貝塞爾曲線對(duì)圖片進(jìn)行裁剪杏死,方法如下

注:從相冊(cè)獲取文中效果圖的圖片裁剪后出現(xiàn)上下顛倒的狀態(tài),這是因?yàn)閳D片的imageOrientation屬性不是UIImageOrientationUp捆交,所以拿到圖片后需要先判斷圖片是不是UIImageOrientationUp狀態(tài)淑翼,如果不是需要做相應(yīng)處理:1.將圖片的狀態(tài)修改為UIImageOrientationUp狀態(tài);2.將圖片旋轉(zhuǎn)到UIImageOrientationUp狀態(tài)品追;我采用的是第一種方案(因?yàn)榇a少??)

// 根據(jù)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);
        
        //坐標(biāo)系轉(zhuǎn)換
        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();
        
        //結(jié)束繪畫
        UIGraphicsEndImageContext();
        
        //轉(zhuǎn)成png格式 會(huì)保留透明
        NSData * data = UIImageJPEGRepresentation(destImg, .5);
        UIImage * dImage = [UIImage imageWithData:data];
        
        return dImage;
    }
}

注意:每次調(diào)用UIGraphicsBeginImageContextWithOptions方法獲取上下文玄括,圖片編輯結(jié)束后一定不要忘記調(diào)用UIGraphicsEndImageContext方法關(guān)閉上下文,否則內(nèi)存會(huì)不斷增加肉瓦,直至溢出遭京!

遇到的問題:
相機(jī)或相冊(cè)獲取圖片過大在運(yùn)行時(shí)內(nèi)存會(huì)瞬間提高很多(60M左右,可能會(huì)更大)风宁,我的解決方案是在獲取圖片時(shí)對(duì)圖片進(jìn)行了裁剪(對(duì)圖片質(zhì)量要求高的不適用)

附方法:具體裁剪的大小根據(jù)自己的需求設(shè)置

- (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;
}

可擴(kuò)展的功能:(有興趣的可以嘗試一下)
1.圖片放大、縮小
2.整體拖動(dòng)裁剪區(qū)域

更新:

1蛹疯、優(yōu)化錨點(diǎn)拖動(dòng) - 只有四個(gè)頂點(diǎn)可以自由移動(dòng)戒财,中間的錨點(diǎn)只能上下或左右移動(dòng),這樣切的圖片不會(huì)太不規(guī)則(其實(shí)是因?yàn)楫a(chǎn)品要求的)捺弦,且中間錨點(diǎn)一直處于中間狀態(tài)饮寞。
2、可移動(dòng)裁剪區(qū)域
3列吼、支持圖片縮放手勢(shì)幽崩,但有個(gè)問題就是放大圖片后無法拖動(dòng)裁剪區(qū)域

Just do IT!

The End!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市寞钥,隨后出現(xiàn)的幾起案子慌申,更是在濱河造成了極大的恐慌,老刑警劉巖理郑,帶你破解...
    沈念sama閱讀 207,113評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蹄溉,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡您炉,警方通過查閱死者的電腦和手機(jī)柒爵,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來赚爵,“玉大人棉胀,你說我怎么就攤上這事法瑟。” “怎么了唁奢?”我有些...
    開封第一講書人閱讀 153,340評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵霎挟,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我驮瞧,道長(zhǎng)氓扛,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,449評(píng)論 1 279
  • 正文 為了忘掉前任论笔,我火速辦了婚禮采郎,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘狂魔。我一直安慰自己蒜埋,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評(píng)論 5 374
  • 文/花漫 我一把揭開白布最楷。 她就那樣靜靜地躺著整份,像睡著了一般。 火紅的嫁衣襯著肌膚如雪籽孙。 梳的紋絲不亂的頭發(fā)上烈评,一...
    開封第一講書人閱讀 49,166評(píng)論 1 284
  • 那天,我揣著相機(jī)與錄音犯建,去河邊找鬼讲冠。 笑死,一個(gè)胖子當(dāng)著我的面吹牛适瓦,可吹牛的內(nèi)容都是我干的竿开。 我是一名探鬼主播,決...
    沈念sama閱讀 38,442評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼玻熙,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼否彩!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起嗦随,我...
    開封第一講書人閱讀 37,105評(píng)論 0 261
  • 序言:老撾萬榮一對(duì)情侶失蹤列荔,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后枚尼,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體肌毅,經(jīng)...
    沈念sama閱讀 43,601評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評(píng)論 2 325
  • 正文 我和宋清朗相戀三年姑原,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了悬而。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,161評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡锭汛,死狀恐怖笨奠,靈堂內(nèi)的尸體忽然破棺而出袭蝗,到底是詐尸還是另有隱情,我是刑警寧澤般婆,帶...
    沈念sama閱讀 33,792評(píng)論 4 323
  • 正文 年R本政府宣布到腥,位于F島的核電站,受9級(jí)特大地震影響蔚袍,放射性物質(zhì)發(fā)生泄漏乡范。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評(píng)論 3 307
  • 文/蒙蒙 一啤咽、第九天 我趴在偏房一處隱蔽的房頂上張望晋辆。 院中可真熱鬧,春花似錦宇整、人聲如沸瓶佳。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽霸饲。三九已至,卻和暖如春臂拓,著一層夾襖步出監(jiān)牢的瞬間厚脉,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評(píng)論 1 261
  • 我被黑心中介騙來泰國打工胶惰, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留傻工,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,618評(píng)論 2 355
  • 正文 我出身青樓童番,卻偏偏與公主長(zhǎng)得像精钮,于是被迫代替她去往敵國和親威鹿。 傳聞我的和親對(duì)象是個(gè)殘疾皇子剃斧,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評(píng)論 2 344