一文弄懂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. mysql8.0----mysqldump抛出:Unknown table 'COLUMN_STATISTICS' in information_schema (1109)

    问题:我尝试使用mysqldump时,得到以下错误: 复制 $> mysqldump --single-transaction --h  -u user -p db > db.sql my ...

  2. kubebuilder实战之八:知识点小记

    欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java.Docker.Kubernetes.DevOPS ...

  3. 分布式ID生成器及redis,etcd分布式锁

    分布式id生成器 有时我们需要能够生成类似MySQL自增ID这样不断增大,同时又不会重复的id.以支持业务中的高并发场景.比较典型的,电商促销时,短时间内会有大量的订单涌入到系统,比如每秒10w+.明 ...

  4. Selenium4 IDE初体验

    今天闲来无事,尝试了一番Selenium4的IDE,提供了录制和回放的功能.下面是对它的简单介绍. 安装 下载地址:https://www.selenium.dev/selenium-ide/ 在下载 ...

  5. SQLServer数据库之连接查询

    SQLServer数据库之连接查询 表的连接查询的几种方法介绍: inner join on内连接,left join on 左连接 , rigth join on 右连接, full join on ...

  6. Java基础(二)——内部类

    一.内部类 内部类(Inner Class)就是定义在一个类里面的类.与之对应,包含内部类的类被称为外部类.内部类可以用private修饰. 1.为什么要定义内部类?或者内部类的作用是什么? 内部类提 ...

  7. 【数据库上】 第四讲 E-R模型基础知识

    第四讲 E-R模型基础知识 一.数据库设计过程 数据库设计的关键阶段? 各个阶段设计的主要任务? 基础条件:清楚一个应用系统的功能需求与数据需求(直接与用户交互.数据流程图示例/UML类图等) 核心阶 ...

  8. WEB漏洞——XXE

    XXE漏洞又称XML外部实体注入(XML External Entity) 介绍XXE漏洞前先说一下什么是XML XML语言 XML用于标记电子文件使其具有结构性的标记语言,可以用来标记数据定义数据类 ...

  9. Intel® QAT加速卡之加密、哈希操作流程和示例

    Intel QAT 加密API介绍 文章主要讲述了Intel QAT 加密API接口的说明,以及多种应用场景下的使用方法. 文章目录 Intel QAT 加密API介绍 1. 概述 1.1 会话(se ...

  10. coreos 常见问题

    1.如果docker run的时候报如下错误: error creating overlay mount to /var/lib/docker/overlay2/... ... 则需要修改/run/s ...