IOS 高仿企业微信的图片标注功能

先上个图,有画箭头,画方框,画曲线,旋转,裁剪,上一步,下一步,清空画布,扩充图片,放大缩小,拖拽等,然后可以对画过的图形进行编辑功能

image.png

一.画板的整体实现

主要参考了这篇文章,https://www.jianshu.com/p/8c145884cf2c
然后采用了NSUndoManager+ Quartz2D 画图方法。底部一个UIImageview 用来显示图片,然后再来一个drawView当做画布。

    self.imageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 44, CGRectGetWidth(self.view.bounds), CGRectGetHeight(self.view.bounds) - 44)];
    self.imageView.image = self.image;
    self.imageView.contentMode = UIViewContentModeScaleAspectFit;
    [self.view addSubview:self.imageView];
    [self.imageView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.edges.equalTo(self.view);
    }];
    
    self.drawView = [EDDrawView new];
    self.drawView.backgroundColor = [UIColor clearColor];
    self.drawView.image = self.image;
    self.drawView.drawType = EDDrawViewToolType_Arrow;
    self.drawView.lineColor = getColorWithColorType(EDDrawPenColorType_Red);
    self.drawView.lineWidth = getLineWithPenWithType(EDDrawPenWidthType_W9);
    self.drawView.fontSize = getFontSizeWithPenWithType(EDDrawPenWidthType_W9);
    self.drawView.selectLineColor = [UIColor colorWithHex:0x8FC4FF];
    self.drawView.selectLineWidth = 2;
    self.drawView.delegate = self;
    [self.view addSubview:self.drawView];
    [self.drawView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.edges.equalTo(self.view);
    }];

为了实现画布的放大缩小、拖拽功能加上手势操作,通过手势改变背景图 和画布的CGAffineTransform

    CGAffineTransform move =  CGAffineTransformMakeTranslation(self.transformPoint.x, self.transformPoint.y);
    CGAffineTransform scale =  CGAffineTransformMakeScale(self.scale, self.scale);
    CGAffineTransform transform = CGAffineTransformConcat(move, scale);
    [self.layer setAffineTransform:transform];
    if ([self.delegate respondsToSelector:@selector(didDrawTransformUpdate:)]) {
        [self.delegate didDrawTransformUpdate:transform];
    }

二.画图操作

画图功能是算是这个功能模块里面的难点,主要监听touchesBegan,touchesMoved,touchesEnded这三个代理方法,然后记录关键点实现。
1.画箭头

    CGContextTranslateCTM(context, _startPoint.x, _startPoint.y);
    
    CGPoint newEndPont = CGPointMake(_endPoint.x - _startPoint.x, _endPoint.y - _startPoint.y);
    CGFloat length = sqrt( pow(fabs(newEndPont.x), 2) + pow(fabs(newEndPont.y), 2) );
    if (length < 10) {
        return;
    }
    CGFloat angle = 0.0;
    if (newEndPont.x > 0 && newEndPont.y >= 0) {
        angle = atan(fabs(newEndPont.y / newEndPont.x * 1.0));
    }else if (newEndPont.x <= 0 && newEndPont.y > 0){
        angle = atan(fabs(newEndPont.x / newEndPont.y * 1.0)) + M_PI_2;
    }else if (newEndPont.x < 0 && newEndPont.y <= 0){
        angle = atan(fabs(newEndPont.y / newEndPont.x * 1.0)) + M_PI;
    }else if (newEndPont.x >= 0 && newEndPont.y < 0){
        angle = atan(fabs(newEndPont.x / newEndPont.y * 1.0)) + M_PI + M_PI_2;
    }
    CGContextRotateCTM(context, angle);
    if (_path) {
        CGPathRelease(_path);
    }
    _path = CGPathCreateMutable();
    CGContextBeginPath(context);
    CGPathMoveToPoint(_path, NULL, length, 0);
    CGPathAddLineToPoint(_path, NULL, length - 10 * 0.25 - self.lineWidth * 2.5, -6 * 0.25 - self.lineWidth * 0.6 * 2.5);
    CGPathAddLineToPoint(_path, NULL,length - 9 * 0.25 - self.lineWidth * 0.9 * 2.5, -3 * 0.25 - self.lineWidth * 0.3 * 2.5);
    CGPathAddLineToPoint(_path, NULL, 0, -2);
    CGPathAddLineToPoint(_path, NULL, 0, 2);
    CGPathAddLineToPoint(_path, NULL,length - 9 * 0.25 - self.lineWidth * 0.9 * 2.5, 3 * 0.25 + self.lineWidth * 0.3 * 2.5);
    CGPathAddLineToPoint(_path, NULL,length - 10 * 0.25 - self.lineWidth * 2.5, 6 * 0.25 + self.lineWidth * 0.6 * 2.5);
    CGPathCloseSubpath(_path);
    CGContextAddPath(context, _path);
    CGContextSetFillColorWithColor(context, self.color.CGColor);
    CGContextFillPath(context);

画箭头如果想通过2个点,计算出7个关键点的坐标实际上是非常复杂的,这里采用的是先通过2个点画一个水平向右的箭头,然后通过2个点计算出角度旋转画布。
2.画方框

    CGPathMoveToPoint(_path, NULL, _startPoint.x, _endPoint.y);
    CGPathAddLineToPoint(_path, NULL, _startPoint.x, _startPoint.y);
    CGPathAddLineToPoint(_path, NULL, _endPoint.x, _startPoint.y);
    CGPathAddLineToPoint(_path, NULL, _endPoint.x, _endPoint.y);
    CGPathCloseSubpath(_path);
    CGContextSetLineJoin(context, kCGLineJoinRound);
    CGContextAddPath(context, _path);
    
    CGContextSetStrokeColorWithColor(context, self.color.CGColor);
    CGContextSetLineWidth(context, self.lineWidth);

这个比较简单,两个点可以很容易的计算出4个关键点的坐标
3.画曲线

    _path = CGPathCreateMutable();

    [self.pointArray enumerateObjectsUsingBlock:^(NSValue *pointValue, NSUInteger idx, BOOL * _Nonnull stop) {
        CGPoint point = [pointValue CGPointValue];
        if (idx == 0) {
            CGPathMoveToPoint(_path, NULL, point.x, point.y);
        }else{
            
            CGPoint prePoint = [self.pointArray[idx - 1] CGPointValue];
            CGPoint currentPoint = point;
            CGPoint midPoint = CGPointMake((prePoint.x + currentPoint.x) * 0.5, (prePoint.y + currentPoint.y) * 0.5);
            CGPathAddQuadCurveToPoint(_path, NULL, midPoint.x, midPoint.y, currentPoint.x, currentPoint.y);
        }
        
    }];
    CGContextSetLineCap(context, kCGLineCapRound);
    CGContextSetLineJoin(context, kCGLineJoinRound);
    CGContextAddPath(context, _path);
    CGContextSetStrokeColorWithColor(context, self.color.CGColor);
    CGContextSetLineWidth(context, self.lineWidth);
    CGContextStrokePath(context);

曲线里面记录了经过的每一个点,然后任意两点通过贝塞尔曲线连接,这样画的曲线就比较圆润。
4.画文字

    CGRect rect =  CGRectMake(_startPoint.x, _startPoint.y, _textBoundingSize.width, _textBoundingSize.height);

    CGContextTranslateCTM(context, _startPoint.x, _startPoint.y);
    CGContextTranslateCTM(context, 0, _textBoundingSize.height);
    CGContextScaleCTM(context, 1, -1);
    
    
    CGContextSetShouldSmoothFonts(context, YES);

    _path = CGPathCreateMutable();
    CGPathAddRect(_path, NULL, (CGRect){CGPointMake(4, -6),rect.size});
    NSAttributedString *contentString = [[NSAttributedString alloc] initWithString:_content attributes:@{NSForegroundColorAttributeName:self.color,NSFontAttributeName:self.fontSize}];

    {
        NSMutableAttributedString *attString = [[NSMutableAttributedString alloc] initWithAttributedString:contentString];
        [attString addAttributes:@{NSForegroundColorAttributeName:[UIColor whiteColor],
                                   NSStrokeColorAttributeName:[UIColor whiteColor],
                                   NSStrokeWidthAttributeName:@(20),
                                   
        } range:NSMakeRange(0, attString.length)];
        
        
        
        CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString((CFTypeRef)attString);
        CTFrameRef frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, attString.length), _path, NULL);
        CTFrameDraw(frame, context);
    }
    
    {
        CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString((CFTypeRef)contentString);
        CTFrameRef frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, contentString.length), _path, NULL);
        CTFrameDraw(frame, context);
    }

画文字有点复杂,首先要搞清楚屏幕坐标映射关系和矩阵变换,可以看看这篇文章,https://www.jianshu.com/p/5ab7ed3f8958

Kapture 2020-06-17 at 19.56.53.gif

然后就是这种文字的描边效果实际上画两遍文字实现的。拖拽文字的边框实现文字的自由排版目前做的不是很好。

三.图形的编辑操作

Kapture 2020-06-17 at 20.02.26.gif

每个图形要实现一个边缘检测方法,用来判断当前点是否在图形内部。如果是点击了图形,图形变为选中状态,这时手势的操作就是针对该图形的操作,通过改变坐标重新绘制图形即可。

四.图片的扩充功能

Kapture 2020-06-17 at 20.09.48.gif

正常情况下,画图只能在图片上进行,如果图形超出图片的大小,就要进行裁剪。这里采用的扩充图片的方式,每次画完一个图形就检测是否超出图片边缘的限制,超出了就计算超出的最远点,然后图片就被扩冲到该最远点。

五.图片的导出功能

目前图片导出是通过截屏方式,然后裁剪出画图区域。这种方式生成的图片大小好像比较固定,质量不高。比较好的做法是先画上原图,然后把画过的图形依次画到原图上。

    CGFloat scale = [UIScreen mainScreen].scale;
    UIGraphicsBeginImageContextWithOptions(self.drawView.frame.size, NO, scale);
    CGContextRef context = UIGraphicsGetCurrentContext();
    [self.imageView.layer renderInContext:context];
    [self.drawView.layer renderInContext:context];
    UIImage *snap = UIGraphicsGetImageFromCurrentImageContext();
    
    UIGraphicsEndImageContext();
    
    UIImage *retImage = [snap imageByCropToRect:cutRect];

最后放上链接,感觉可以,麻烦点颗星星。

https://github.com/udoubi/EDMarkDemo/tree/master

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

友情链接更多精彩内容