绘图相关的知识

591 阅读3分钟

绘图

概述

项目中有一个画板的功能,当时做的过程中遇到了内存增加的问题,最后通过修改绘图的方式解决了问题,在此记录一下解决问题的过程和带来的思考。

绘图实现的方式

绘图采用的是UIBezierPath + drawRect 的方法。具体操作是创建贝塞尔曲线对象在touchesBegan中获取曲线对象的起点,在touchesMoved中添加线到另一个点,然后调用setNeedsDisplay方法,该方法会自动调用drawRect方法进行绘图。详细代码如下

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    
    UITouch *touch = touches.anyObject;
    CGPoint movePoint = [touch locationInView:touch.view];
    self.path = [DrawBezierPath bezierPath];
    [self.path moveToPoint:movePoint];
    [self.pathArr addObject:self.path];
    
}

- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    
    UITouch *touch = touches.anyObject;
    CGPoint addPoint = [touch locationInView:touch.view];
    [self.path addLineToPoint:addPoint];
    [self setNeedsDisplay];
}

- (void)drawRect:(CGRect)rect{
    
    for (DrawBezierPath *path in self.pathArr) {
        
        [path.pathColor setStroke];
        [path strokeWithBlendMode:kCGBlendModeNormal alpha:1.0];
        
        [path stroke];
    }
}

观察代码可知道for循环遍历执行了多次,setNeedsDisplay一执行就会调用drawRect方法。这样每次画新的图的时候都要把之前所有的图在重绘一次,你画的越多,重绘的就越多。这应该就是内存增加的原因。

为什么要调用setNeedsDisplay?

在自定义的View上进行划线时调用setNeedsDisplay方法会让系统调用drawRect方法来绘图

为什么调用drawRect会增加内存?

屏幕上显示的内容基本上都是UIView,而UIView之所以能显示在屏幕上,是因为它的内部有一个CALayer图层。当UIView显示在屏幕上时会调用drawRect方法进行绘图,并且内容绘制在自己的图层上,绘图完成后系统会将图层拷贝到屏幕上,这就完成了UIView的显示。也就是说CALayerUIView具有了显示功能,而CALayer内部有一个 contents 属性(寄宿图),在给这个属性赋值的时候并不会增加额外的内存,但是一旦实现了drawRect方法,图层就会创建一个上下文,这个上下文的内存计算公式为:图层高(分辨率) * 图层宽 * 4个字节 ,图层每次重绘的时候都需要重新抹掉内存然后重新分配。这就是导致内存增加的原因。

使用CAShapeLayer+UIBezierPath的方式绘图

CAShapeLayer 是什么

CAShapeLayer是一个通过矢量图形而不是bitmap来绘制的图层子类。你指定诸如颜色和线宽等属性,用CGPath来定义想要绘制的图形,最后CAShapeLayer就自动渲染出来了。简单点就是UIBezierPath用来指定绘制图形路径,而CAShapeLayer就是根据路径来绘图。

CAShapeLayer 的优势

  • CAShapeLayer属于QuartzCore框架,drawRect属于Core Graphics框架,绘制图形时CAShapeLayer不需要像普通CALayer一样创建一个寄宿图形,所以无论有多大,都不会占用太多的内存。
  • 渲染快速,CAShapeLayer使用了硬件加速。

CAShapeLayer 如何使用

因为都使用了贝塞尔曲线,所以和drawRect有点类似,不同的是不需要调用方法,只要把CGPath 赋给CAShapeLayerpath 就行了。具体代码为

- (instancetype)initWithFrame:(CGRect)frame{
    
    if (self = [super initWithFrame:frame]) {
        
        [self.layer addSublayer:self.shapeLayer];
    }
    return self;
}

- (CAShapeLayer *)shapeLayer{
    
    if (!_shapeLayer) {
        
        _shapeLayer = [CAShapeLayer layer];
        _shapeLayer.frame = self.bounds;
        _shapeLayer.lineWidth = 2.f;
        _shapeLayer.strokeColor = [UIColor redColor].CGColor;
        _shapeLayer.fillColor = [UIColor clearColor].CGColor;
    }
    
    return _shapeLayer;
}

- (UIBezierPath *)shapePath{
    
    if (!_shapePath) {
        
        _shapePath = [UIBezierPath bezierPath];
    }
    return _shapePath;
}


- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    
    UITouch *touch = [touches anyObject];
    CGPoint point = [touch locationInView:self];
    [self.shapePath moveToPoint:point];
}

- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    
    UITouch *touch = [touches anyObject];
    CGPoint currentPoint = [touch locationInView:self];
    [self.shapePath addLineToPoint:currentPoint];
    self.shapeLayer.path = self.shapePath.CGPath;
}

总结

绘图有两种处理的方式:CPU(中央处理器)和GPU(图形处理器)。对于图像处理,通常用硬件会更快,因为GPU使用图像对高度并行浮点运算做了优化。什么专业的人做什么专业的事,这样效率更高,CAShapeLayer就是如此。

参考

什么时候调用setNeedsDisplay

CPU VS GPU

CALayer的contents属性

CAShapeLayer

软件绘图