一文弄懂CGAffineTransform和CTM

  • 一些概念

坐标空间(系):视图(View)坐标空间与绘制(draw)坐标空间
CTM:全称current transformation matrix,看名称 “当前变换矩阵” 也就是矩阵。
CGAffineTransform:是一个具体的矩阵数据值。CGAffineTransform是CTM的具体值。
  • 关于矩阵变换

相同CGAffineTransform作用于不同的坐标空间,其结果不一样。

移动:

视图空间 中心为原点,向右为x递增,向下y递增,CGAffineTransformMakeTranslation(-75, 25);  左移75,下移25
绘制空间 左下点为原点,向右为x递增,向上y递增,CGAffineTransformMakeTranslation(-75, 25);  左移75,上移25

视图空间示例:_demoView.transform = CGAffineTransformMakeTranslation(-75, 25);

绘制空间示例:
CGContextConcatCTM(ctx, CGAffineTransformMakeTranslation(-75, 25));
CGContextDrawImage(ctx, CGRectMake(0, 0, imageWidth, imageHeight), self.image.CGImage);


旋转:
视图空间 中心为原点,向右为x递增,向下y递增, transform = CGAffineTransformRotate(transform, -M_PI_2); 围绕中心点,逆时针旋转90度
绘制空间 左下点为原点,向右为x递增,向上y递增 transform = CGAffineTransformRotate(transform, -M_PI_2); 围绕左下角点,顺时针旋转90度

视图空间示例:_demoView.transform = CGAffineTransformRotate(transform, -M_PI_2);

绘制空间示例:
CGContextConcatCTM(ctx, CGAffineTransformRotate(transform, -M_PI_2););
CGContextDrawImage(ctx, CGRectMake(0, 0, imageWidth, imageHeight), self.image.CGImage);

缩放:
视图空间 默认以中心点为原点 transform = CGAffineTransformMakeScale(1, -1); 沿着中心X轴线竖直翻转
绘制空间 默认以左下角为原点 transform = CGAffineTransformMakeScale(1, -1); 沿着X轴横线竖直翻转

视图空间示例:_demoView.transform = CGAffineTransformMakeScale(1, -1);

绘制空间示例:
CGContextConcatCTM(ctx, CGAffineTransformMakeScale(1, -1));
CGContextDrawImage(ctx, CGRectMake(0, 0, imageWidth, imageHeight), self.image.CGImage);

如果我们现在想要把上面的图片逆时针旋转90度,可以使用CGAffineTransform组合的方式。这里提供两种转换方式,都是采用的平移、旋转,执行顺序不同导致,给的参数也不同。后续说明原因。
方法一:

//移动到屏幕右边
transform = CGAffineTransformTranslate(transform, imageHeight,0);
//逆时针旋转90度
transform = CGAffineTransformRotate(transform, M_PI_2);
//将transform作用于context
CGContextConcatCTM(ctx, transform);
CGContextDrawImage(ctx, CGRectMake(0, 0, imageWidth, imageHeight), self.image.CGImage);


方法二:
//逆时针旋转90度
transform = CGAffineTransformRotate(transform, M_PI_2);
//右移图片高度的距离
transform = CGAffineTransformTranslate(transform, 0,-imageHeight);
CGContextConcatCTM(ctx, transform);
CGContextDrawImage(ctx, CGRectMake(0, 0, imageWidth, imageHeight), self.image.CGImage);

执行结果如下
CGAffineTransformRotate组合坐标系问题
注意:所有的变换都会影响到坐标系产生新的坐标系。比如:旋转转换,坐标系也跟着旋转,按照X轴翻转缩放,坐标系也会翻转。
解释一下逆时针旋转图片 方法一 和 方法二
方法一比较容易理解,
1.右移图片宽度
2.左下角 为圆心,逆时针旋转90度。


重点说明下方法二
1.逆时针旋转90度
2.右移图片高度。

逆时针旋转大家容易理解,但是,右移图片高度为什么是transform = CGAffineTransformTranslate(transform, 0,-imageHeight);???
谜底是,逆时针旋转的时候,坐标系也跟着逆时针旋转90度,变成了右下角为原点,y左边递增,右边递减。x上方向递增,下方向递减。所以此时想要把图片向右边移动-imageHeight的距离,按照新的坐标系,就是往y轴的递减方向走。也就有了transform = CGAffineTransformTranslate(transform, 0,-imageHeight);

  • 关于CGContextDrawImage

DrawImage绘制什么情况是颠倒的,什么情况不是颠倒的,坐标系又是什么样的?

使用UIGraphicsGetCurrentContext()获取的上下文,CGContextDrawImage是颠倒的。想要正向的图片需要做CTM变换。
-(void)drawImage{
    
    CGFloat imageWidth = CGImageGetWidth(self.image.CGImage);
    CGFloat imageHeight = CGImageGetHeight(self.image.CGImage);
    
    UIGraphicsBeginImageContextWithOptions(CGSizeMake(imageWidth, imageHeight), 0, [UIScreen mainScreen].scale);
    CGContextRef context = UIGraphicsGetCurrentContext();

//,为了保证正向显示图片,需要先上移图片高度,再沿X轴翻转。
//    CGContextTranslateCTM(context, 0, imageHeight);
//    CGContextScaleCTM(context, 1, -1);
// 使用转换之后的坐标系绘制图片
    CGContextDrawImage(context, CGRectMake(0, 0, imageWidth, imageHeight), self.image.CGImage);
    
    UIImage *newImg = UIGraphicsGetImageFromCurrentImageContext();
    
    UIGraphicsEndImageContext();
    
    self.imageView.image = newImg;
}

 自己创建位图,再调用CGContextDrawImage,并不会出现上下颠倒的问题。
-(void)drawImage{
    
    CGFloat imageWidth = CGImageGetWidth(self.image.CGImage);
    CGFloat imageHeight = CGImageGetHeight(self.image.CGImage);
    
    //创建位图上下文
    CGContextRef ctx = CGBitmapContextCreate(NULL, imageHeight,imageWidth,
                                             CGImageGetBitsPerComponent(self.image.CGImage), 0,
                                             CGImageGetColorSpace(self.image.CGImage),
                                             CGImageGetBitmapInfo(self.image.CGImage));
    //这里drawImage是正的。
    CGContextDrawImage(ctx, CGRectMake(0, 0, imageWidth, imageHeight), self.image.CGImage);
    CGImageRef cgimg = CGBitmapContextCreateImage(ctx);
    UIImage *img = [UIImage imageWithCGImage:cgimg];
    CGContextRelease(ctx);
    CGImageRelease(cgimg);
    self.imageView.image = img;
    return ;
}

CGContextDrawImage的坐标系
默认是以左下角为坐标原点开始绘制,但是,But,通过一系列的CTM转换之后,最终的绘制坐标CGRectMake(0, 0, imageWidth, imageHeight)是根据新的坐标系计算的。比如逆时针旋转90度的例子,新坐标系 原点为右下角,y左边递增,右边递减。x上方向递增

CGContextDrawImage(ctx, CGRectMake(100, 100, imageWidth, imageHeight), self.image.CGImage);

关于CGRectApplyAffineTransform转换CGRect

  • 关于CGRectApplyAffineTransform转换CGRect

CGrectApplyAffineTrans对于平移、缩放、旋转的表现情况


CGRectApplyAffineTransform(CGRect rect, CGAffineTransform t) 在当前坐标系,对rect做仿射变换之后,得到的新rect。
//平移
    CGRect rc = CGRectMake(100, 0, 100, 200);
    //打印结果为(200, 100, 100, 200);
    CGRect newRC = CGRectApplyAffineTransform(rc, CGAffineTransformMakeTranslation(100, 100));

//缩放
    CGRect rc = CGRectMake(100, 0, 100, 200);
    //打印结果为 (200, 0, 200, 400); 所有值都乘了2
    CGRect newRC = CGRectApplyAffineTransform(rc, CGAffineTransformMakeScale(2, 2));


//旋转
    CGAffineTransform transform = CGAffineTransformMakeTranslation(0, 0);
    transform = CGAffineTransformRotate(transform, -M_PI_2);
    
    CGRect rc = CGRectMake(100, 0, 100, 200);
    //打印结果为(0, -200, 200, 100); 得到的是相对于(0,0)旋转90度的值
    CGRect newRC = CGRectApplyAffineTransform(rc, transform);
   

//平移 + 缩放
    CGAffineTransform transform = CGAffineTransformMakeTranslation(100, 100);
    transform = CGAffineTransformScale(transform, 2, 2);
    CGRect rc = CGRectMake(100, 0, 100, 200);
    //打印为(300, 100, 200, 400); 先计算了缩放,再计算平移得到此值
    CGRect newRC = CGRectApplyAffineTransform(rc, transform);

//平移+旋转
    CGAffineTransform transform = CGAffineTransformMakeTranslation(100, 100);
    transform = CGAffineTransformRotate(transform, -M_PI_2);
    
    CGRect rc = CGRectMake(100, 0, 100, 200);
    //打印为(100, -100, 200, 100);  先计算了旋转,再计算平移得到此值
    CGRect newRC = CGRectApplyAffineTransform(rc, transform);

//平移 + 旋转 + 缩放

    CGAffineTransform transform = CGAffineTransformMakeTranslation(100, 100);
    transform = CGAffineTransformRotate(transform, -M_PI_2);
    transform = CGAffineTransformScale(transform, 2, 2);
    CGRect rc = CGRectMake(100, 0, 100, 200);
    //打印为 (100, -300, 400, 200); 先计算了旋转/缩放,再计算平移得到此值
    CGRect newRC = CGRectApplyAffineTransform(rc, transform);

总结:
    平移:CGRectApplyAffineTransform对于平移转换是与实际变换结果一致的。
    旋转:以当前坐标系原点(0,0)进行旋转计算后的值。
    缩放:得到的结果为,rect中的各个值乘以缩放比例。
    组合变换:先计算旋转和缩放,最后计算平移

一文弄懂CGAffineTransform和CTM的更多相关文章

  1. 一文弄懂神经网络中的反向传播法——BackPropagation【转】

    本文转载自:https://www.cnblogs.com/charlotte77/p/5629865.html 一文弄懂神经网络中的反向传播法——BackPropagation   最近在看深度学习 ...

  2. 一文弄懂-Netty核心功能及线程模型

    目录 一. Netty是什么? 二. Netty 的使用场景 三. Netty通讯示例 1. Netty的maven依赖 2. 服务端代码 3. 客户端代码 四. Netty线程模型 五. Netty ...

  3. 一文弄懂-《Scalable IO In Java》

    目录 一. <Scalable IO In Java> 是什么? 二. IO架构的演变历程 1. Classic Service Designs 经典服务模型 2. Event-drive ...

  4. 一文弄懂-BIO,NIO,AIO

    目录 一文弄懂-BIO,NIO,AIO 1. BIO: 同步阻塞IO模型 2. NIO: 同步非阻塞IO模型(多路复用) 3.Epoll函数详解 4.Redis线程模型 5. AIO: 异步非阻塞IO ...

  5. 【TensorFlow】一文弄懂CNN中的padding参数

    在深度学习的图像识别领域中,我们经常使用卷积神经网络CNN来对图像进行特征提取,当我们使用TensorFlow搭建自己的CNN时,一般会使用TensorFlow中的卷积函数和池化函数来对图像进行卷积和 ...

  6. 一文弄懂Pytorch的DataLoader, DataSet, Sampler之间的关系

    以下内容都是针对Pytorch 1.0-1.1介绍. 很多文章都是从Dataset等对象自下往上进行介绍,但是对于初学者而言,其实这并不好理解,因为有的时候会不自觉地陷入到一些细枝末节中去,而不能把握 ...

  7. 一文弄懂js的执行上下文与执行上下文栈

    目录 执行上下文与执行上下文栈 变量提升与函数提升 变量提升 函数提升 变量提升与函数提升的优先级 变量提升的一道题目引出var关键字与let关键字各自的特性 执行上下文 全局执行上下文 函数(局部) ...

  8. 一文弄懂pytorch搭建网络流程+多分类评价指标

    讲在前面,本来想通过一个简单的多层感知机实验一下不同的优化方法的,结果写着写着就先研究起评价指标来了,之前也写过一篇:https://www.cnblogs.com/xiximayou/p/13700 ...

  9. 一文弄懂神经网络中的反向传播法——BackPropagation

    最近在看深度学习的东西,一开始看的吴恩达的UFLDL教程,有中文版就直接看了,后来发现有些地方总是不是很明确,又去看英文版,然后又找了些资料看,才发现,中文版的译者在翻译的时候会对省略的公式推导过程进 ...

随机推荐

  1. 三大操作系统对比使用之·MacOSX

    时间:2018-11-13 整理:byzqy 本篇是一篇个人对Mac系统使用习惯和应用推荐的分享,在此记录,以便后续使用查询! 打开终端: command+空格,调出"聚焦搜索(Spotli ...

  2. APP 兼容性测试之云测平台体验

    前言 兼容性测试主要通过人工或自动化的方式,在需要覆盖的终端设备上进行功能用例执行,查看软件性能.稳定性等是否正常. 对于需要覆盖的终端设备,大型互联网公司,像BAT,基本都有自己的测试实验室,拥有大 ...

  3. Java并发之AQS原理解读(一)

    前言 本文简要介绍AQS以及其中两个重要概念:state和Node. AQS 抽象队列同步器AQS是java.util.concurrent.locks包下比较核心的类之一,包括AbstractQue ...

  4. MySQL修改配置文件 避免中文乱码

    MySQL修改配置文件 避免中文乱码 MySQL安装后默认的服务器字符集是拉丁文,也就是说默认 character_set_server = latin1 ,这是造成 MySQL 中文乱码的主要原因之 ...

  5. Stream流用于按照对象中某一属性来对集合去重+简单数据类型集合的去重

    上次对Stream流来进行分组的文章很多人看,想看的可以来这: Stream流来进行集合分组 这次小编又带来Stream的去重,话不多数,直接上代码: 这是对简单数据类型的去重 //字符串集合进行简单 ...

  6. RT-Thread 4.0 + STM32F407 学习笔记1

    RT Thread 4.0提供了新的BSP框架 新 BSP 框架的主要特性如下: 提供多系列 BSP 模板,大大降低新 BSP 的添加难度: 每个 BSP 都配有齐全的驱动文件,开发者可以方便地使用所 ...

  7. Linux串口调试详解

    测试平台 宿主机平台:Ubuntu 16.04.6 目标机:iMX6ULL 目标机内核:Linux 4.1.15 目标机添加串口设备 一般嵌入式主板的默认镜像可能只配置了调试串口,并用于 consol ...

  8. Java并发之Synchronized机制详解

    带着问题阅读 1.Synchronized如何使用,加锁的粒度分别是什么 2.Synchronized的实现机制是什么 3.Synchronized是公平锁吗 4.Java对Synchronized做 ...

  9. Xilinx约束学习笔记(三)—— 时序概念

    3. 时序概念 发现对于时序基础的介绍这一块,Intel 的文档竟然要比 Xilinx 的详细,因此引用了很多 Intel 的文档内容. 3.1 术语 发送沿(launch edge),指用来发送数据 ...

  10. [转]SpringBoot系列——花里胡哨的banner.txt

    Creating ASCII Text Banners from the Linux Command Line In Ubuntu, Debian, Linux Mint etc. $ sudo ap ...