iOS 裁剪工具
程序员文章站
2022-06-23 22:46:40
下载 "demo和工具下载链接SPClipTool" 使用说明 需求 图片裁剪,效果如下图,支持图片拖拽,缩放,裁剪框*变换大小。 思路 两个UIImageView,一个做背景,并加上蒙版效果,另外一个通过蒙版控制显示区域,并且保证两个UIImageView平移和缩放的时候完全重叠。最后使用一个U ......
下载
使用说明
[[spcliptool sharecliptool] sp_cliporiginimage:pickerimage complete:^(uiimage * _nonnull image) { // 获取到裁剪后的image 后续操作 }];
需求
图片裁剪,效果如下图,支持图片拖拽,缩放,裁剪框*变换大小。
思路
两个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真实的大小(外框)。
@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); }
这里一定要注意线条的宽度,线条是有宽度的,绘制路径位于线条的中心位置。
上一篇: 原来你也不敢扔