CGContext 总结二

本文详细介绍了CGContext中的transform用法,包括缩放、旋转和平移,强调了transform是对坐标系的操作。同时,讨论了状态栈的概念,裁剪的运用,以及CGPath路径绘制的细节。通过示例展示了如何使用CGPath和UIBezierPath创建笑脸,解释了fillMode的两种规则:非零环绕数和奇偶原则,并分析了它们的区别。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

CGContext 总结二

CGContext中的transform

1.1 用法

CGContextScaleCTM(ref, 1.0, -1.0)表示缩放,为负数时,整个翻转;
CGContextRotateCTM(ref, 180 * M_PI/180)表示旋转,rotate的方向,为正时,旋转方向为从x轴到y轴,负则相反;
CGContextTranslateCTM(ref, 0, -400)表示平移;
也可结合CGAffineTransform:

transform = CGAffineTransformMakeTranslation(300, 0.0);
transform = CGAffineTransformRotate(transform, M_PI / 2.0);
CGContextConcatCTM(ref, transform);

效果与前者一致。需注意的是,以上transform都是对context坐标系的操作,即坐标原点会跟着变动。

1.2 示例

//right 图片向右旋转
CGContextScaleCTM(ref, -1, 1);
CGContextTranslateCTM(ref, -300, 0);//300代表宽
transform = CGAffineTransformMakeTranslation(300, 0.0);
transform = CGAffineTransformRotate(transform, M_PI / 2.0);
CGContextConcatCTM(ref, transform);

//left 图片向左旋转
CGContextScaleCTM(ref, -1, 1);
CGContextTranslateCTM(ref, -300, 0);
transform = CGAffineTransformMakeTranslation(0.0, 400);//400代表高
transform = CGAffineTransformRotate(transform, -90 * M_PI/180);
//transform = CGAffineTransformMakeTranslation(0.0, 400);
CGContextConcatCTM(ref, transform);

以上都是基于context坐标系原点在左上角:UIGraphicsBeginImageContext(CGSizeMake(300, 400)),且采用CGContextDrawImage(ref, CGRectMake(0, 0, 300, 400), newImage.CGImage);;还有另外一种方式实现图片旋转,即UIImage的drawInRect:方法,此方法则不用对context进行操作:

UIGraphicsBeginImageContext(CGSizeMake(300, 400));
UIImage *originImage = [UIImage imageNamed:@"D.jpeg"];
UIImage *newImage = [UIImage imageWithCGImage:originImage.CGImage scale:1.0 orientation:UIImageOrientationRight];//向右旋转,向左则UIImageOrientationLeft
[originImage drawInRect:CGRectMake(0, 0, 300, 400)];

向右旋转

向左旋转

2 状态栈

Graphics Context包含一个绘图状态栈。当Quartz创建一个Graphics Context时,栈为空。当保存图形状态时,Quartz将当前图形状态的一个副本压入栈中。当还原图形状态时,Quartz将栈顶的图形状态出栈。出栈的状态成为当前图形状态。
可使用函数CGContextSaveGState来保存图形状态,CGContextRestoreGState来还原图形状态。具体绘图状态包括:
Current transformation matrix (CTM):当前转换矩阵
Clipping area:裁剪区域
Line: 线
Accuracy of curve estimation (flatness):曲线平滑度
Anti-aliasing setting:反锯齿设置
Color: 颜色
Alpha value (transparency):透明度
Rendering intent:渲染目标
Color space: 颜色空间
Text: 文本
Blend mode:混合模式

3 裁剪

需要用到context中的裁剪时,需要先裁剪context后再划线或image

NSInteger w = 300, h = 400;
CGContextBeginPath (ref1);
CGContextAddArc (ref1, w/2, h/2, ((w>h) ? h : w)/2, 0, 2*M_PI, 0);
CGContextClosePath (ref1);
CGContextClip (ref1);
//drawLine 或 drawImage

4 CGPath 路径绘制

直接在context上划线很方便,但对于比较复杂的绘制操作,如果想复用的话就比较麻烦。对此,可以先将你想画的线绘制在路径上,然后再将路径添加到context上,最后将路径stroke或fill出来。须注意的是,最后要将路径添加到context中,否则画不出;
CGPathCreateMutable 建立一个路径,画完后要把它加到context上,才能展示出来 CGContextAddPath
下面函数一一对应

CGPathCreateMutable --> CGContextBeginPath
CGPathMoveToPoint --> CGContextMoveToPoint
CGPathAddLineToPoint --> CGContextAddLineToPoint
CGPathAddCurveToPoint --> CGContextAddCurveToPoint
CGPathAddEllipseInRect --> CGContextAddEllipseInRect
CGPathAddArc --> CGContextAddArc
CGPathAddRect --> CGContextAddRect
CGPathCloseSubpath --> CGContextClosePath

附上三种实现笑脸的Demo(转自https://www.mgenware.com/blog/?p=493

1.采用CGPath

- (void)viewDidLoad
{
    [super viewDidLoad];

    //开始图像绘图
    UIGraphicsBeginImageContext(self.view.bounds.size);
    //获取当前CGContextRef
    CGContextRef gc = UIGraphicsGetCurrentContext();

    //创建用于转移坐标的Transform,这样我们不用按照实际显示做坐标计算
    CGAffineTransform transform = CGAffineTransformMakeTranslation(50, 50);
    //创建CGMutablePathRef
    CGMutablePathRef path = CGPathCreateMutable();
    //左眼
    CGPathAddEllipseInRect(path, &transform, CGRectMake(0, 0, 20, 20));
    //右眼
    CGPathAddEllipseInRect(path, &transform, CGRectMake(80, 0, 20, 20));
    //笑
    CGPathMoveToPoint(path, &transform, 100, 50);
    CGPathAddArc(path, &transform, 50, 50, 50, 0, M_PI, NO);
    //将CGMutablePathRef添加到当前Context内
    CGContextAddPath(gc, path);
    //设置绘图属性
    [[UIColor blueColor] setStroke];
    CGContextSetLineWidth(gc, 2);
    //执行绘画
    CGContextStrokePath(gc);

    //从Context中获取图像,并显示在界面上
    UIImage *img = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();

    UIImageView *imgView = [[UIImageView alloc] initWithImage:img];
    [self.view addSubview:imgView];
}

2.采用CGContext

- (void)viewDidLoad
{
    [super viewDidLoad];

    //开始图像绘图
    UIGraphicsBeginImageContext(self.view.bounds.size);
    //获取当前CGContextRef
    CGContextRef gc = UIGraphicsGetCurrentContext();

    //使用CGContextTranslateCTM函数来转移坐标的Transform,这样我们不用按照实际显示做坐标计算
    CGContextTranslateCTM(gc, 50, 50);
    //左眼
    CGContextAddEllipseInRect(gc, CGRectMake(0, 0, 20, 20));
    //右眼
    CGContextAddEllipseInRect(gc, CGRectMake(80, 0, 20, 20));
    //笑
    CGContextMoveToPoint(gc, 100, 50);
    CGContextAddArc(gc, 50, 50, 50, 0, M_PI, NO);
    //设置绘图属性
    [[UIColor blueColor] setStroke];
    CGContextSetLineWidth(gc, 2);
    //执行绘画
    CGContextStrokePath(gc);

    //从Context中获取图像,并显示在界面上
    UIImage *img = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    UIImageView *imgView = [[UIImageView alloc] initWithImage:img];
    [self.view addSubview:imgView];
}

3.采用UIBezierPath
UIBezierPath包装了Quartz的相关API,自己存在于UIKit中,因此不是基于C的API,而是基于Objective-C对象的。那么一个非常重要的点是由于离开了Quartz绘图,所以不需要考虑Y轴翻转的问题,在画弧的时候,clockwise参数是和现实一样的,如果需要顺时针就传YES,而不是像Quartz环境下传NO的。其次椭圆的创建需使用bezierPathWithOvalInRect方法,这里名字是Oral而不是Quartz中的Ellipse。最后注意UIBezierPath的applyTransform方法需要最后调用。

- (void)viewDidLoad
{
    [super viewDidLoad];

    //开始图像绘图
    UIGraphicsBeginImageContext(self.view.bounds.size);

    //创建UIBezierPath
    UIBezierPath *path = [UIBezierPath bezierPath];
    //左眼
    [path appendPath:[UIBezierPath bezierPathWithOvalInRect:CGRectMake(0, 0, 20, 20)]];
    //右眼
    [path appendPath:[UIBezierPath bezierPathWithOvalInRect:CGRectMake(80, 0, 20, 20)]];
    //笑
    [path moveToPoint:CGPointMake(100, 50)];
    //注意这里clockwise参数是YES而不是NO,因为这里不知Quartz,不需要考虑Y轴翻转的问题
    [path addArcWithCenter:CGPointMake(50, 50) radius:50 startAngle:0 endAngle:M_PI clockwise:YES];
    //使用applyTransform函数来转移坐标的Transform,这样我们不用按照实际显示做坐标计算
    [path applyTransform:CGAffineTransformMakeTranslation(50, 50)];
    //设置绘画属性
    [[UIColor blueColor] setStroke];
    [path setLineWidth:2];
    //执行绘画
    [path stroke];

    //从Context中获取图像,并显示在界面上
    UIImage *img = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    UIImageView *imgView = [[UIImageView alloc] initWithImage:img];
    [self.view addSubview:imgView];
}

5 fillMode

fillPath有两种方式,默认的为nonzero winding number rule非缠绕数原则,根据子路径穿过线(为渲染的点到画布外的一条线)的方向计数,从左到右穿过+1,反之减一(初始值0),当最终非零时填充,否则不填充;另一种为even-odd(奇偶原则),统计最终值为奇数时填充,偶数时不填充,CGContextEOFillPath(CGContextRef __nullable c)

判断点p是否填充,从点p向外做一条射线(可以任意方向),默认非零环绕数规则,多边形的边从左到右经过射线时环数加1,多边形的边从右往左经过射线时环数减1,最后环数不为0,则填充,否则不填充。
1. 奇-偶规则(Odd-even Rule):奇数表示在多边形内,偶数表示在多边形外
从任意位置p作一条射线,若与该射线相交的多边形边的数目为奇数,则填充,否则不填充;
2. 非零环绕数规则(Nonzero Winding Number Rule):若环绕数为0则填充,否则不填充。

将环绕数初始化为零。再从任意位置p作一条射线。当从p点沿射线方向移动时,对在每个方向上穿过射线的边计数,每当多边形的边从左到右穿过射线时,环绕数加1,从右到左时,环绕数减1。处理完多边形的所有相关边之后,若环绕数为非零,则填充,否则不填充。

当然,非零绕数规则和奇偶规则会判断出现矛盾的情况,如下图所示,左侧表示用奇偶规则判断绕环数为2 ,表示在多边形外,所以没有填充。右侧图用非零绕环规则判断出绕数为2,非0表示在多边形内部,所以填充。

另外一个例子,如下

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值