美文网首页
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