CGContext的变换过程详解

4,737 阅读3分钟

0. 序言

今天透过OpenGL ES的角度,终于想明白了CGContext的变换过程。

多年的老便秘,终于有救了。

这里记录一下。如果有同样问题的小伙伴,可以看一下。

1. Core Graphics

iOS支持两套图形API族:Core Graphics/Quartz 和 OpenGL ES。

  • OpenGL ES 是跨平台的图形API,属于OpenGL的一个简化版本。

  • Core Graphics Framework是基于Quartz的高级绘图引擎。它提供低级别、轻量级的2D渲染,具有无与伦比的输出保真度。使用此框架可以处理基于路径的绘制、转换、颜色管理、屏幕外渲染、模式、渐变和阴影、图像数据管理、图像创建和图像遮罩,以及PDF文档创建、显示和解析。

虽然两套API不同,但底层的原理其实是相通的。

我们可以用Core Graphics进行图形的绘制,也可以用它来解码图片(可以查看 SDWebImageYYImageGPUImage 中的具体实现)。

2. CGContext的变换

在绘制图片时,我们可能会对 CGContextRef 的实例对象进行各种变换,以达到我们的绘制效果。

例如,现在我们需要绘制一张旋转了180°的图片,我们可能会对 CGContextRef 的实例对象进行如下变换:

CGContextScaleCTM(context, -1.0, -1.0);
CGContextTranslateCTM(context, -image.size.width, -image.size.height);

或者

CGContextRotateCTM(context, M_PI);
CGContextTranslateCTM(context, -image.size.width, -image.size.height);

或者

CGContextTranslateCTM(context, image.size.width / 2.0, image.size.height / 2.0);
CGContextRotateCTM(context, M_PI);
CGContextTranslateCTM(context, -image.size.width / 2.0, -image.size.height / 2.0);

以前看了很多博客、很多图示,感觉讲得都不是很明白。

特别是有些文章说,这个变换其实是变换的坐标系,并图示了坐标系的变化,感觉也挺难懂的。

其实, 不要把这个上下文理解成一张画布,然后旋转、移动变换这个画布

点进API描述,我们看到 变换的其实是当前图像的变换矩阵

/* Rotate the current graphics state's transformation matrix (the CTM) by `angle' radians. */
CG_EXTERN void CGContextRotateCTM(CGContextRef cg_nullable c, CGFloat angle) CG_AVAILABLE_STARTING(10.0, 2.0);

我们绘制图像的顶点坐标,会经过我们这里设置的各种变换,最终被确定并绘制出来。

本质和在OpenGL ES中,对图像的顶点坐标进行矩阵变换是一样的。

3. 图解CGContext的变换

我们还是以翻转图片为例进行讲解。

我们经常在进行翻转、旋转变换后,需要进行平移,但常常苦恼于如何选择到底向x轴和y轴的正方向或是负方向进行移动。

其实,和OpenGL的矩阵变换类似, CGContext的变换如果从后往前读,更符合我们的理解。

下面我们来试一下。

3.1 变换1

CGContextScaleCTM(context, -1.0, -1.0);
CGContextTranslateCTM(context, -image.size.width, -image.size.height);

上面的代码反过来读就是:

  • 先向x轴的负方向移动image.size.width,向y轴的负方向移动image.size.height

  • 再关于原点对称

3.2 变换2

CGContextRotateCTM(context, M_PI);
CGContextTranslateCTM(context, -image.size.width, -image.size.height);

上面的代码反过来读就是:

  • 先向x轴的负方向移动image.size.width,向y轴的负方向移动image.size.height
  • 再绕原点旋转180°

3.3 变换3

CGContextTranslateCTM(context, image.size.width / 2.0, image.size.height / 2.0);
CGContextRotateCTM(context, M_PI);
CGContextTranslateCTM(context, -image.size.width / 2.0, -image.size.height / 2.0);

上面的代码反过来读就是:

  • 先向x轴的负方向移动image.size.width/2.0,向y轴的负方向移动image.size.height/2.0
  • 再绕原点旋转180°
  • 然后向x轴的正方向移动image.size.width/2.0,向y轴的正方向移动image.size.height/2.0

4. 题外话

如果只是想翻转一个图片,其实还有一个简单的办法。

- (void)viewDidLoad {
    [super viewDidLoad];
    UIImage *image = [UIImage imageNamed:@"qiyu"];
    UIImage *rotatedImage = [UIImage imageWithCGImage:image.CGImage scale:image.scale orientation:UIImageOrientationDown];
    UIImageView *imageView = [[UIImageView alloc] initWithImage:rotatedImage];
    imageView.center = self.view.center;
    [self.view addSubview:imageView];
}


如果觉得本文对你有所帮助,给我点个赞吧~ 👍🏻