美文网首页
iOS-图像绘制技术-CGPath、CoreGraphics

iOS-图像绘制技术-CGPath、CoreGraphics

作者: 流云_henry | 来源:发表于2020-07-07 17:11 被阅读0次

    在iOS开发中,视图的绘制无处不在,例如使用UILable组件显示文本标签,使用UIImageView组件显示图片视图等。在日常开发中我们使用的大多数视图组件都是基于UIKit框架的,其实关于图像绘制,CoreGraphics是一个更加底层、也更加强大的图形绘制框架。

    1、CGPath路径绘制

    CoreGraphics核心图形框架相较于UIKit框架更加偏向底层。在OC中,CoreGraphics中的方法都是采用的C语言风格进行编写的,同时他并不支持OC的自动引用计数功能,在使用这个框架时,开发者要手动对内存进行管理。
    CGPath可以理解为图形的路径,在OC工程中,他是系统定义的一个内部结构体,开发者不可以直接使用。可以使用CGPathRef和CGMutablePathRef别名作为对CGPath的引用。
    下面使用代码来演示自定义绘制的视图,绘制一个闭合的圆角矩形。

    - (void)drawRect:(CGRect)rect {
            //获取当前绘图上下文
            CGContextRef contentextRef = UIGraphicsGetCurrentContext();
            CGPoint center = CGPointMake(rect.size.width/2.0, rect.size.height/2.0);
            //创建圆角矩形路径,30-横向的圆角尺寸 10-纵向的圆角尺寸
            CGPathRef pathRef = CGPathCreateWithRoundedRect(CGRectMake(center.x - 50, center.y - 50, 50*2, 50*2), 30, 10, nil);
            //将路线虚化
            CGFloat floats[] = {10,5};
            //创建虚线路径-path:虚线的逻辑,phase:从lengths数组的第几部分开始绘制虚线,lengths:表示每段虚线的长度,例如传入的是{10,5}则表示虚线先绘制长度为10的实现,再绘制长度为5的空白,如此循环;count:设置为lengths数组的长度
        
        //    CGPathCreateCopyByDashingPath(<#CGPathRef  _Nullable path#>, <#const CGAffineTransform * _Nullable transform#>, <#CGFloat phase#>, <#const CGFloat * _Nullable lengths#>, <#size_t count#>)
            pathRef = CGPathCreateCopyByDashingPath(pathRef, nil, 0, floats, 2);
            //设置绘制颜色
            [[UIColor blackColor] setStroke];
            //将路径添加到绘图上下文中
            CGContextAddPath(contentextRef, pathRef);
            //进行绘制
            CGContextDrawPath(contentextRef, kCGPathStroke);
            //释放内存
            CGPathRelease(pathRef);
    }
    

    效果图如下:


    image.png

    CGPath除了上面的简单的绘制方法外,还有如下的更多复杂的方法:

      //将路径移动到一个点作为起始
    CG_EXTERN void CGPathMoveToPoint(CGMutablePathRef cg_nullable path,
        const CGAffineTransform * __nullable m, CGFloat x, CGFloat y)
        CG_AVAILABLE_STARTING(10.2, 2.0);
    
    //将路径移到某个点画出一条线
    CG_EXTERN void CGPathAddLineToPoint(CGMutablePathRef cg_nullable path,
        const CGAffineTransform * __nullable m, CGFloat x, CGFloat y)
        CG_AVAILABLE_STARTING(10.2, 2.0);
    
    //向路径中添加一段二次贝济埃曲线
    /*
    cpx:控制点的X坐标
    cpy:控制点的Y坐标
    */
    CG_EXTERN void CGPathAddQuadCurveToPoint(CGMutablePathRef cg_nullable path,
        const CGAffineTransform *__nullable m, CGFloat cpx, CGFloat cpy,
        CGFloat x, CGFloat y)
        CG_AVAILABLE_STARTING(10.2, 2.0);
    //添加一段三次贝济埃曲线
    CG_EXTERN void CGPathAddCurveToPoint(CGMutablePathRef cg_nullable path,
        const CGAffineTransform * __nullable m, CGFloat cp1x, CGFloat cp1y,
        CGFloat cp2x, CGFloat cp2y, CGFloat x, CGFloat y)
        CG_AVAILABLE_STARTING(10.2, 2.0);
    
    //闭合路径,在调用这个方法后,路径最后的端点讲和起点闭合
    CG_EXTERN void CGPathCloseSubpath(CGMutablePathRef cg_nullable path)
        CG_AVAILABLE_STARTING(10.2, 2.0);
    
    //向路径中追加一个矩形
    CG_EXTERN void CGPathAddRect(CGMutablePathRef cg_nullable path,
        const CGAffineTransform * __nullable m, CGRect rect)
        CG_AVAILABLE_STARTING(10.2, 2.0);
    
    //向路径中追加一组矩形
    CG_EXTERN void CGPathAddRects(CGMutablePathRef cg_nullable path,
        const CGAffineTransform * __nullable m, const CGRect * __nullable rects,
        size_t count)
        CG_AVAILABLE_STARTING(10.2, 2.0);
    
    //向路径中追加一条线
    CG_EXTERN void CGPathAddLines(CGMutablePathRef cg_nullable path,
        const CGAffineTransform * __nullable m, const CGPoint * __nullable points,
        size_t count)
        CG_AVAILABLE_STARTING(10.2, 2.0);
    
    //添加椭圆
    CG_EXTERN void CGPathAddEllipseInRect(CGMutablePathRef cg_nullable path,
        const CGAffineTransform * __nullable m, CGRect rect)
        CG_AVAILABLE_STARTING(10.4, 2.0);
    
    //向路径中追加一组圆弧
    /*
    X:圆心x坐标
    y:圆心y坐标
    radius:弧线半径
    startAngle:起始角度
    delta:圆弧绘制的长度为弧度制,2π位整个圆
    */
    CG_EXTERN void CGPathAddRelativeArc(CGMutablePathRef cg_nullable path,
        const CGAffineTransform * __nullable matrix, CGFloat x, CGFloat y,
        CGFloat radius, CGFloat startAngle, CGFloat delta)
        CG_AVAILABLE_STARTING(10.7, 5.0);
    
    //向路径中追加一组圆弧
    /*
    X:圆心x坐标
    y:圆心y坐标
    radius:弧线半径
    startAngle:起始角度
    endAngle:终止角度
    clockwise:是否沿顺时针方向绘制
    */
    CG_EXTERN void CGPathAddArc(CGMutablePathRef cg_nullable path,
        const CGAffineTransform * __nullable m,
        CGFloat x, CGFloat y, CGFloat radius, CGFloat startAngle, CGFloat endAngle,
        bool clockwise)
        CG_AVAILABLE_STARTING(10.2, 2.0);
    
    //向路径中追加一段圆弧,弧线是以(x1,y1)到(x2,y2)为切线的弧线
    CG_EXTERN void CGPathAddArcToPoint(CGMutablePathRef cg_nullable path,
        const CGAffineTransform * __nullable m, CGFloat x1, CGFloat y1,
        CGFloat x2, CGFloat y2, CGFloat radius)
        CG_AVAILABLE_STARTING(10.2, 2.0);
    
    //向路径中追加一段路径
    CG_EXTERN void CGPathAddPath(CGMutablePathRef cg_nullable path1,
        const CGAffineTransform * __nullable m, CGPathRef cg_nullable path2)
        CG_AVAILABLE_STARTING(10.2, 2.0);
    

    例如我们用下面的代码来绘制更具复杂的图形:

    - (void)drawRect:(CGRect)rect {
        //获取当前绘图上下文
        CGContextRef contextRef = UIGraphicsGetCurrentContext();
        CGPoint center = CGPointMake(rect.size.width/2.0, rect.size.height/2.0);
        //创建可变路径
        CGMutablePathRef pathRef = CGPathCreateMutable();
        //创建起始点
        CGPathMoveToPoint(pathRef, nil, center.x, center.y-50);
        //将路径移到某个点画出一条线
        CGPathAddLineToPoint(pathRef, nil, center.x+100, center.y-20);
    //    //向路径中添加一段二次贝济埃曲线
        CGPathAddQuadCurveToPoint(pathRef, nil, 0, 0, center.x+100, center.y - 100);
    //    //向路径中追加一组圆弧
        CGPathAddRelativeArc(pathRef, nil, 100, 100, 50, 0, M_PI);
        // 闭合路径
        CGPathCloseSubpath(pathRef);
        [[UIColor blackColor] setStroke];
        //将路径添加到绘图上下文中
        CGContextAddPath(contextRef, pathRef);
        //进行绘制
        CGContextDrawPath(contextRef, kCGPathStroke);
        //释放内存
        CGPathRelease(pathRef);
    }
    

    效果如下图:


    image.png

    下面为一些CGPath相关的工具方法:

    //判断某个路径是否为空
    bool CGPathIsEmpty(CGPathRef cg_nullable path);
    
    //判断某个路径是否为某个矩形
    bool CGPathIsRect(CGPathRef cg_nullable path, CGRect * __nullable rect);
    
    //获取某个路径当前绘制的所在的点
    CGPoint CGPathGetCurrentPoint(CGPathRef cg_nullable path);
    
    //获取某个路径包含的所有点的尺寸
    CGRect CGPathGetBoundingBox(CGPathRef cg_nullable path);
    
    //获取某个路径的尺寸
    CGRect CGPathGetPathBoundingBox(CGPathRef cg_nullable path);
    
    //判断路径是否包含某个点
    bool CGPathContainsPoint(CGPathRef cg_nullable path,
        const CGAffineTransform * __nullable m, CGPoint point, bool eoFill);
    

    2、理解图形上下文

    GraphicsContext对于开发者来说是完全透明的,我们不需要关系他的实现,也不需要关心其绘制方式,而只需要将要绘制的内容船体给图形上下文,由图形上下文来讲内容绘制到对应的目标上。这里需要注意的是,绘制的顺序十分重要!如果后绘制的内容和先绘制的内容有位置冲突,则后绘制的内容会覆盖先绘制的内容。
    特定的上下文用于将内容绘制到特定的输出源上,在CoreGraphics中提供如下几种图形上下文:

    *位图图形上下文:用于将RGB图像、GMYK图像或黑白图像会知道一个位图对象中。
    *PDF图形上下文:可以帮助开发者创建PDF文件,将内容绘制进PDF文件中。与位图上下文最大的区别在于其PDF数据可以保存多页数据。
    *窗口上下文:用于iOS系统中的窗口绘制。
    *图层上下文:用于将内容绘制在Layer图层上。
    *打印上下文:当使用Mac打印功能时,此上下文用于将内容绘制在打印输出源上。

    3、关于层聚合

    在正常情况下,当使用CoreGraphics框架中的方法进行图形绘制时,每一闭合的图形都是一个独立的层,如果在绘制时添加了阴影效果,则可以很明显的看到图形的分层情况:

    - (void)drawRect:(CGRect)rect {
        float width = rect.size.width/2.0;
        CGPoint center = CGPointMake(width, rect.size.height/2.0);
        CGSize myShadowOffset = CGSizeMake(10, -20);
        //获取上下文
        CGContextRef myContext = UIGraphicsGetCurrentContext();
        CGContextSetShadow(myContext, myShadowOffset, 10);
        //绘制三个圆
        CGContextSetRGBFillColor(myContext, 0, 1, 0, 1);
        CGContextFillEllipseInRect(myContext, CGRectMake(center.x-width/2, center.y-width/4*3, width, width));
        
        CGContextSetRGBFillColor(myContext, 0, 0, 1, 1);
        CGContextFillEllipseInRect(myContext, CGRectMake(center.x-width/4, center.y-width/3, width, width));
        
        CGContextSetRGBFillColor(myContext, 1, 0, 0, 1);
        CGContextFillEllipseInRect(myContext, CGRectMake(center.x-width/4*3, center.y-width/4, width, width));
    }
    

    效果如下图:


    image.png

    从图中可以发现,所绘制的3个圆并非位于同一层级上。那么如果我们有这样的需求:在园内的阴影不应该存在,只有圆外的阴影才可以有。这样的需求应该是很普遍的。在CoreGraphics中也提供了进行图形聚合绘制的方法:

    - (void)drawRect:(CGRect)rect {
            float width = rect.size.width/2.0;
        CGPoint center = CGPointMake(width, rect.size.height/2.0);
        CGSize myShadowOffset = CGSizeMake(10, -20);
        //获取上下文
        CGContextRef myContext = UIGraphicsGetCurrentContext();
        CGContextSetShadow(myContext, myShadowOffset, 10);
        
        CGContextBeginTransparencyLayer(myContext, NULL);
        //开启图形聚合绘制
        //后续的绘制代码都将绘制到统一的图层上
        //绘制三个圆
        CGContextSetRGBFillColor(myContext, 0, 1, 0, 1);
        CGContextFillEllipseInRect(myContext, CGRectMake(center.x-width/2, center.y-width/4*3, width, width));
        
        CGContextSetRGBFillColor(myContext, 0, 0, 1, 1);
        CGContextFillEllipseInRect(myContext, CGRectMake(center.x-width/4, center.y-width/3, width, width));
        
        CGContextSetRGBFillColor(myContext, 1, 0, 0, 1);
        CGContextFillEllipseInRect(myContext, CGRectMake(center.x-width/4*3, center.y-width/4, width, width));
        //结束聚合绘制
        CGContextEndTransparencyLayer(myContext);
    }
    

    效果图如下:


    image.png

    相关文章

      网友评论

          本文标题:iOS-图像绘制技术-CGPath、CoreGraphics

          本文链接:https://www.haomeiwen.com/subject/ekgaqktx.html