欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

iOS 裁剪工具

程序员文章站 2022-03-21 17:40:02
下载 "demo和工具下载链接SPClipTool" 使用说明 需求 图片裁剪,效果如下图,支持图片拖拽,缩放,裁剪框*变换大小。 思路 两个UIImageView,一个做背景,并加上蒙版效果,另外一个通过蒙版控制显示区域,并且保证两个UIImageView平移和缩放的时候完全重叠。最后使用一个U ......

下载

demo和工具下载链接spcliptool

使用说明

[[spcliptool sharecliptool] sp_cliporiginimage:pickerimage complete:^(uiimage * _nonnull image) {
    // 获取到裁剪后的image 后续操作
}];

需求

图片裁剪,效果如下图,支持图片拖拽,缩放,裁剪框*变换大小。

iOS  裁剪工具

思路

两个uiimageview,一个做背景,并加上蒙版效果,另外一个通过蒙版控制显示区域,并且保证两个uiimageview平移和缩放的时候完全重叠。最后使用一个uiview来做交互,绘制三分网格线(专业术语我不知道叫啥,截图时一个参照,2/3 ≈0.667 接近黄金比0.618)。

注意

  • 坐标系转换问题。
  • mask灵活使用问题。
  • 手势的处理和三分网格线绘制的时候,计算线条宽度和长度需要特别注意。

  • 为了增强用户体验,在裁剪框边缘交互设计的时候,注意额外增加用户的可操控范围。

实现

  • 初始化两个uiimageview,一个做背景图(backgroudimageview),一个用来显示裁剪区域(clipimageview),拖拽手势加到了clipimageview。
- (void)setupimageview {
    // backgroudimageview 
    uiimageview *backgroudimageview = [[uiimageview alloc] initwithframe:self.view.bounds];
    backgroudimageview.contentmode = uiviewcontentmodescaleaspectfit;
    backgroudimageview.image = self.originimage;
    [self.view addsubview:backgroudimageview];
    self.backgroudimageview = backgroudimageview;
    backgroudimageview.layer.mask = [[calayer alloc] init];
    backgroudimageview.layer.mask.frame = backgroudimageview.bounds;
    backgroudimageview.layer.mask.backgroundcolor = [uicolor colorwithwhite:1 alpha:0.5].cgcolor;
    
    // clipimageview
    uiimageview *clipimageview = [[uiimageview alloc] initwithframe:backgroudimageview.frame];
    clipimageview.userinteractionenabled = yes;
    clipimageview.image = backgroudimageview.image;
    clipimageview.contentmode = backgroudimageview.contentmode;
    [self.view addsubview:clipimageview];
    self.clipimageview = clipimageview;
    clipimageview.layer.mask = [[calayer alloc] init];
    clipimageview.layer.mask.backgroundcolor = [uicolor whitecolor].cgcolor;
    clipimageview.layer.mask.bordercolor = [uicolor whitecolor].cgcolor;
    clipimageview.layer.mask.borderwidth = 1;
    [clipimageview.layer.mask removeallanimations];
    
    uipangesturerecognizer *pangesture = [[uipangesturerecognizer alloc] initwithtarget:self action:@selector(imagepan:)];
    [clipimageview addgesturerecognizer:pangesture];
}
  • 初始化用于裁剪交互的spclipview
- (void)setupclipview {
    spclipview *clipview = [[spclipview alloc] init];
    clipview.backgroundcolor = [uicolor clearcolor];
    // 打开下面两行注释,可以查看真实clipview的大小。
//    clipview.layer.bordercolor = [uicolor whitecolor].cgcolor;
//    clipview.layer.borderwidth = 1;
    [self.view addsubview:clipview];
    self.clipview = clipview;
    // 获取真实frame
    dispatch_after(dispatch_time(dispatch_time_now, (int64_t)(0.1 * nsec_per_sec)), dispatch_get_main_queue(), ^{
        clipview.frame = cgrectmake(0, 0, self.view.width / 1.5, self.view.height / 1.5);
          clipview.center = self.view.center;
        self.backgroudimageview.frame = self.view.bounds;
        self.clipimageview.frame = self.backgroudimageview.frame;
        [self dealmask];
    });

    uipangesturerecognizer *pangesture = [[uipangesturerecognizer alloc] initwithtarget:self action:@selector(clippan:)];
    [clipview addgesturerecognizer:pangesture];
    
    uipinchgesturerecognizer *pinchgesture = [[uipinchgesturerecognizer alloc] initwithtarget:self action:@selector(pinchgestureaction:)];
    [self.view addgesturerecognizer:pinchgesture];
}
  • 手势处理
#pragma mark- uipangesturerecognizer
- (void)clippan:(uipangesturerecognizer *)pangesture {
    cgpoint point = [pangesture translationinview:self.clipview];
    self.clipview.origin = [self.clipview convertpoint:point toview:self.view];
    [self expandclipview:pangesture];
    [self dealguideline:pangesture];
    [self dealmask];
    [pangesture settranslation:cgpointmake(0, 0) inview:self.view];
}

- (void)imagepan:(uipangesturerecognizer *)pangesture {
    cgpoint point = [pangesture translationinview:self.clipimageview];
    self.clipimageview.origin = [self.clipimageview convertpoint:point toview:self.view];
    self.backgroudimageview.center = self.clipimageview.center;
    [self dealguideline:pangesture];
    [self dealmask];
    [pangesture settranslation:cgpointmake(0, 0) inview:self.view];
}

#pragma mark- uipinchgesturerecognizer
- (void)pinchgestureaction:(uipinchgesturerecognizer *)pinchgesture {
    switch (pinchgesture.state) {
        case uigesturerecognizerstatebegan: {
            if (lastscale <= minscale) {
                lastscale = minscale;
            }else if (lastscale >= maxscale) {
                lastscale = maxscale;
            }
            self.clipimageviewcenter = self.clipimageview.center;
            self.clipview.showguideline = yes;
        }
        case uigesturerecognizerstatechanged: {
            cgfloat currentscale = lastscale + pinchgesture.scale - 1;
            if (currentscale > minscale && currentscale < maxscale) {
                [self dealviewscale:currentscale];
            }
        }
            break;
        case uigesturerecognizerstateended:
            lastscale += (pinchgesture.scale - 1);
            self.clipview.showguideline = no;
            [self.clipview setneedsdisplay];
        default:
            break;
    }
}

#pragma mark- action
- (void)dealviewscale:(cgfloat)currentscale {
    self.clipimageview.width = currentscale * self.view.width;
    self.clipimageview.height = currentscale * self.view.height;
    self.clipimageview.center = self.clipimageviewcenter;
    self.backgroudimageview.frame = self.clipimageview.frame;
    self.backgroudimageview.layer.mask.frame = self.backgroudimageview.bounds;
    [self.backgroudimageview.layer.mask removeallanimations];
    [self dealmask];
}

- (void)expandclipview:(uipangesturerecognizer *)pangesture {
    cgpoint point = [pangesture translationinview:self.clipimageview];
    cgfloat margin = 60;
    cgfloat minvalue = margin;
    if (pangesture.numberoftouches) {
        cgpoint location = [pangesture locationoftouch:0 inview:pangesture.view];
        if (location.x < margin) {
            self.clipview.width = max(self.clipview.width -= point.x, minvalue);
        }
        if ((self.clipview.width - location.x) < margin) {
            self.clipview.frame = cgrectmake(self.clipview.x - point.x, self.clipview.y, self.clipview.width + point.x, self.clipview.height);
        }
        if (location.y < margin) {
            self.clipview.height = max(self.clipview.height -= point.y, minvalue);
        }
        if ((self.clipview.height - location.y) < margin) {
            self.clipview.frame = cgrectmake(self.clipview.x , self.clipview.y - point.y, self.clipview.width, self.clipview.height + point.y);
        }
    }
}

- (void)dealguideline:(uipangesturerecognizer *)pangesture  {
    switch (pangesture.state) {
        case uigesturerecognizerstatebegan:
            self.clipview.showguideline = yes;
            break;
        case uigesturerecognizerstateended:
            self.clipview.showguideline = no;
            break;
        default:
            break;
    }
}

- (void)dealmask {
    // 额外增加拖拉区域 增强边缘手势体验
    cgfloat margin = 30;
    cgrect rect = [self.view convertrect:self.clipview.frame toview:self.clipimageview];
    self.clipimageview.layer.mask.frame = cgrectmake(rect.origin.x + margin, rect.origin.y + margin, rect.size.width - 2 * margin, rect.size.height - 2 * margin);
    [self.clipview setneedsdisplay];
    [self.clipimageview.layer.mask removeallanimations];
}
  • 图片裁剪
- (void)clipimage {
    
    cgsize size = self.view.bounds.size;
    uigraphicsbeginimagecontextwithoptions(size, no, [uiscreen mainscreen].scale);
    [self.view drawviewhierarchyinrect:self.view.bounds afterscreenupdates:no];
    uiimage *image = uigraphicsgetimagefromcurrentimagecontext();
    cgimageref cgimage = [image cgimage];
    cgrect rect = [self.clipimageview convertrect:self.clipimageview.layer.mask.frame toview:self.view];
    
    // 边框线条宽度值
    cgfloat borderw = 1;
    cgimageref cgclipimage = cgimagecreatewithimageinrect(cgimage, cgrectmake((rect.origin.x + borderw / 2) * image.scale, (rect.origin.y + borderw / 2) * image.scale, (rect.size.width - borderw) * image.scale, (rect.size.height - borderw) * image.scale));
    uigraphicsendimagecontext();
    if (self.complete) {
        self.complete([uiimage imagewithcgimage:cgclipimage]);

    }
    [self dismissviewcontrolleranimated:yes completion:nil];
}
  • 裁剪区域绘制

    在这里,裁剪区域的矩形框我并没有直接采用clipview的fram大小,而是在其内部绘制了一个矩形框,为了让用户在调节边缘的时候更灵活,不然只有当手指在边框内部边缘才能触发调节边框大小的事件。如下图,可以看到clipview真实的大小(外框)。

iOS  裁剪工具

@implementation spclipview

- (void)drawrect:(cgrect)rect {
    // drawing code
    cgcontextref currentcontext = uigraphicsgetcurrentcontext();
    cgcontextsetstrokecolorwithcolor(currentcontext, [uicolor whitecolor].cgcolor);
    cgcontextsetlinewidth(currentcontext, 1);
    // 额外增加拖拉区域 增强边缘手势体验,该值应该和上文- (void)dealmask;方法中的margin一致
    cgfloat margin = 30;
  
    // 绘制矩形框
    cgcontextaddrect(currentcontext, cgrectmake(margin, margin, self.width - 2 * margin, self.height - 2 * margin));
    cgcontextstrokepath(currentcontext);
    
    // 绘制三分线
    cgfloat maskw = self.width - 2 * margin;
    cgfloat maskh = self.height - 2 * margin;
    cgcontextsetlinewidth(currentcontext, 0.5);
    if (self.showguideline) {
        cgcontextsetstrokecolorwithcolor(currentcontext, [uicolor whitecolor].cgcolor);
    }else {
        cgcontextsetstrokecolorwithcolor(currentcontext, [uicolor clearcolor].cgcolor);
    }
    cgcontextmovetopoint(currentcontext, margin, maskh / 3 + margin);
    cgcontextaddlinetopoint(currentcontext, self.width - margin, maskh / 3 + margin);
    cgcontextmovetopoint(currentcontext, margin, 2 / 3.0 * maskh + margin);
    cgcontextaddlinetopoint(currentcontext, self.width - margin, 2 / 3.0 * maskh + margin);
    
    cgcontextmovetopoint(currentcontext, maskw / 3 + margin, margin);
    cgcontextaddlinetopoint(currentcontext, maskw  / 3+ margin, self.height - margin);
    cgcontextmovetopoint(currentcontext, 2 / 3.0 * maskw + margin, margin);
    cgcontextaddlinetopoint(currentcontext, 2 / 3.0 * maskw + margin, self.height - margin);
    
    cgcontextstrokepath(currentcontext);
    
    // 绘制四角
    cgfloat cornerl = 15;
    cgfloat cornerlw = 2;
    // 实际的长度
    cgfloat cornerrl = cornerl + cornerlw;
    cgpoint originh = cgpointmake(margin - cornerlw, margin - cornerlw / 2);
    cgpoint originv = cgpointmake(margin - cornerlw / 2, margin - cornerlw);
    cgcontextsetstrokecolorwithcolor(currentcontext, [uicolor whitecolor].cgcolor);
    cgcontextsetlinewidth(currentcontext, cornerlw);
    
    // 左上
    cgcontextmovetopoint(currentcontext, originh.x, originh.y);
    cgcontextaddlinetopoint(currentcontext, originh.x + cornerrl, originh.y);
    cgcontextmovetopoint(currentcontext, originv.x, originv.y);
    cgcontextaddlinetopoint(currentcontext, originv.x, originv.y + cornerrl);
    
    // 左下
    cgcontextmovetopoint(currentcontext, originh.x, originh.y + maskh + cornerlw);
    cgcontextaddlinetopoint(currentcontext, originh.x + cornerrl, originh.y + maskh + cornerlw);
    cgcontextmovetopoint(currentcontext, originv.x, originv.y + maskh + 2 * cornerlw);
    cgcontextaddlinetopoint(currentcontext, originv.x, originv.y + maskh + 2 * cornerlw - cornerrl);
    
    // 右上
    cgcontextmovetopoint(currentcontext, originh.x + maskw + 2 * cornerlw, originh.y);
    cgcontextaddlinetopoint(currentcontext, originh.x + maskw + 2 * cornerlw - cornerrl, originh.y);
    cgcontextmovetopoint(currentcontext, originv.x + maskw + cornerlw, originv.y);
    cgcontextaddlinetopoint(currentcontext, originv.x + maskw + cornerlw, originv.y + cornerrl);
    
    // 右下
    cgcontextmovetopoint(currentcontext, originh.x + maskw + 2 * cornerlw, originh.y + maskh + cornerlw);
    cgcontextaddlinetopoint(currentcontext, originh.x + maskw + 2 * cornerlw - cornerrl, originh.y + maskh + cornerlw);
    cgcontextmovetopoint(currentcontext, originv.x + maskw + cornerlw, originv.y + maskh + 2 * cornerlw);
    cgcontextaddlinetopoint(currentcontext, originv.x + maskw + cornerlw, originv.y + maskh + 2 * cornerlw - cornerrl);

    cgcontextstrokepath(currentcontext);
}

这里一定要注意线条的宽度,线条是有宽度的,绘制路径位于线条的中心位置。

iOS  裁剪工具