深度解析 Android Matrix 变换(二):组合变换 pre、post

前言

在上一篇文章中,我们讲解了 Canvas 中单个变换的原理和效果,即缩放、旋转和平移。但是单个旋转仅仅是基础,Canvas 变换最重要的是能够随意组合各种变换以实现想要的效果。在这种情况下,就需要了解如何组合变换,以及组合变换背后的矩阵是如何计算出来的。

阅读本文之前,建议先阅读:深度解析 Android Matrix 变换(一):缩放 scale、旋转 rotate、平移 translate

在这篇文章中,我就带领大家理解组合变换以及其背后的原理。通过这篇文章,大家肯定能随意组合变换以达到想要的效果。

从一个点开始

在二维平面中,表示一个点只是需要其 x,y 坐标,但是在表换过程中,并不是使用两个值的表示方式,而是用齐次坐标来表示。简单来说,就是 x,y 坐标再加上一个 1 来表示。当用列向量来表示这个点的坐标时,因为其具有三行,因此可以与各种变换的矩阵相乘,因为矩阵都是 3*3 的,可以与 3*1 的向量相乘。

我们用 Ms 表示缩放矩阵,Mr 表示旋转矩阵,Mt 表示平移矩阵,如果对于一个点 P0,我们对它依次应用旋转平移和缩放,那么变换后得到的点 P1 为:

P1 = Ms * Mt * Mr * P0

注意相乘的顺序,我们必须从又往左相乘,但是由于矩阵乘法满足结合律,因此这三个矩阵可以合并为一个与 P0 相乘。

在了解了如何组合矩阵之后我们就从代码层面理解如何构建想要的组合矩阵。

构建矩阵

在上篇文章中,我们介绍过,缩放旋转和平移都有对应的 post 和 pre 版本。在这一节中,我们来介绍这两个种类的方法的具体意义:

  1. Matrix.preScale(float sx, float sy);
  2. Matrix.postScale(float sx, float sy);
  3. Matrix.preRotate(float degrees);
  4. Matrix.postRotate(float degrees);
  5. Matrix.preTranslate(float dx, float dy);
  6. Matrix.postTranslate(float dx, float dy);

在上面的方法中,我们先过滤掉 preXXXpostXXX 的方法名和参数,因为这些方法实际上都是通过方法名和参数来构建一个矩阵,然后 pre 或者 post 这个矩阵。也就是说,上述的方法可以总结为两个方法:

  1. Matrix.preConcat(Matrix other)
  2. Matrix.postConcat(Matrix other)

那么这两个方法有什么区别呢?说简单一点就是 pre 是左乘,post 是右乘。举个例子:

  • A.preConcat(B) 表示 A * B 结果设置到 A 中;
  • A.preConcat(B) 表示 B * A 结果设置到 A 中;

变换顺序

在构建矩阵时,变换顺序是非常重要的。上面只是对变换时矩阵的乘法进行了讲解,但是在实际编码时,我们通常是按照 先缩放,再旋转,最后平移 的顺序来进行的。

对于一个变换,我们必须考虑按照这个顺序来完成,特别是平移需要最后完成。否则后续的旋转或缩放会影响平移变换,变换的结果可能完全不同,甚至不符合直觉。

而按照这个变换做的好处是:

  • 缩放发生在局部坐标系,不影响旋转或平移的方向。
  • 旋转发生在缩放之后,不会影响缩放比例。
  • 平移最后执行,不会受到缩放或旋转的干扰。

实例

最后我们组合一个变换,并通过动画的形式演示变换过程。

我们还是以上篇文章的图片为基础:

我们先对这个图片进行缩放(1.5,0.5),再旋转30度,最后移动(500, 200)。

根据上面对构建矩阵的描述,我们通过下面的代码来生成变换矩阵:

Matrix matrix = new Matrix();
matrix.postScale(1.5F, 0.5F);
matrix.postRotate(30);
matrix.postTranslate(500, 200);

在将这个 matrix 应用到 Canvas 后,可以看到变换后的结果:

其实,对于某一个结果,可以通过多种方式来构造变换矩阵。例如这个变换,我们还可以通过下面的矩阵生成:

Matrix matrix = new Matrix();
matrix.setRotate(30);
matrix.preScale(1.5F, 0.5F);
matrix.postTranslate(500, 200);

既然同一个变换有多种构建方式,那么应该用哪种构建矩阵的方式呢?这里还是建议用复杂的方式,至少,别人越是看不懂,越是显得你的重要性。

最后,通过动画,来演示一下这个组合变换是如何生效的:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/38681.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

c++之迭代器

一.迭代器的基本概念 1.什么是迭代器 迭代器是一种对象,它提供了一种访问容器中各个元素的方法,同时隐藏了容器内部的实现细节。简单来说,迭代器就像是一个指针,它可以指向容器中的某个元素,并且能够通过一些操作&am…

在 .NET 9.0 Web API 中实现 Scalar 接口文档及JWT集成

示例代码:https://download.csdn.net/download/hefeng_aspnet/90408075 介绍 随着 .NET 9 的发布,微软宣布他们将不再为任何 .NET API 项目提供默认的 Swagger gen UI。以前,当我们创建 .NET API 项目时,微软会自动添加 Swagger…

【操作系统笔记】操作系统的功能

上节课,我们学习了《什么是操作系统》。接下来,我们来看看操作系统有哪些功能? 这里讲的内容有两部分,一个是操作系统的目标,另外一个就是操作系统的功能。这两个细节可能会在考试的时候考到,但是最近好些年很少考到了。为了理解,我们还是一起来看一下。 操作系统的目标…

C/C++蓝桥杯算法真题打卡(Day7)

一、P8723 [蓝桥杯 2020 省 AB3] 乘法表 - 洛谷 算法代码&#xff1a; #include<bits/stdc.h> // 包含标准库中的所有头文件&#xff0c;通常用于竞赛编程中简化代码 using namespace std; // 使用标准命名空间&#xff0c;避免每次调用标准库函数时都要加std:: ty…

数据结构5(初):排序

目录 1、排序的概念以及常见的排序算法 1.1、排序的概念 1.2、常见的排序算法 2、常见排序算法的实现 2.1、插入排序 2.1.1、直接插入排序 2.1.2、希尔排序 2.2、选择排序 2.2.1、直接选择排序 2.2.2、堆排序 2.3、交换排序 2.3.1、冒泡排序 2.3.2、快速排序 2.3.…

VS2022中通过VCPKG安装的ceres之后调试ceres的例程设置

1.采用C20. vcpkg中设置: 2.增加预处理宏: GLOG_USE_GLOG_EXPORT 3.屏蔽sdl错误 在 项目-属性-C/C -命令行中添加 /sdl /w34996 #include "ceres/ceres.h" //#include <iostream> //#include<glog/logging.h>using ceres::AutoDiffCostFunction; usi…

Pydantic字段级校验:解锁@validator的12种应用

title: Pydantic字段级校验:解锁@validator的12种应用 date: 2025/3/23 updated: 2025/3/23 author: cmdragon excerpt: Pydantic校验系统支持通过pre验证器实现原始数据预处理,在类型转换前完成字符清洗等操作。格式验证涵盖正则表达式匹配与枚举值约束,确保护照编号等字…

函数递归和迭代

1.什么是递归&#xff1f; 在C语言中递归就是自己调用自己。 看一下简单函数的递归&#xff1a; 上面的代码实现演示一下函数的递归&#xff0c;最终是会陷入死循环的&#xff0c;栈溢出 。 1.1递归的思想&#xff1a; 把一个大型的问题一步一步的转换成一个个小的子问题来解…

发票查验/发票验真如何用Java实现接口调用

一、什么是发票查验&#xff1f;发票验真接口&#xff1f; 输入发票基本信息发票代码、发票号码、开票日期、校验码后6位、不含税金额、含税金额&#xff0c;核验发票真伪。 该接口也适用于机动车、二手车销售发票、航空运输电子客票、铁路电子客票等。 二、如何用Java实现接口…

AM32-MultiRotor-ESC项目固件编译和烧录方法介绍

AM32-MultiRotor-ESC项目固件编译和烧录方法介绍 &#x1f4cd;AM32-MultiRotor-ESC项目地址:https://github.com/AlkaMotors/AM32-MultiRotor-ESC-firmware&#x1f388;Updater with V8 Bootloader&#xff1a; https://github.com/AlkaMotors/F051_Bootloader_Updater&#…

HarmonyOS:@AnimatableExtend 装饰器自学指南

在最近的项目开发中&#xff0c;我遇到了需要实现复杂动画效果的需求。在探索解决方案的过程中&#xff0c;我发现了 AnimatableExtend 装饰器&#xff0c;它为实现动画效果提供了一种非常灵活且强大的方式。然而&#xff0c;在学习这个装饰器的过程中&#xff0c;我发现相关的…

Windows server 2022域控制服务器的配置

Windows server 2022介绍 一、核心特性与改进 安全核心服务器&#xff08;Secured-Core Server&#xff09; 硬件级安全&#xff1a;支持基于硬件的安全功能&#xff08;如TPM 2.0、Secure Boot、基于虚拟化的安全防护VBS&#xff09;&#xff0c;防止固件攻击。受信任的启动链…

C++语法之模板函数和模板类

模板函数是什么&#xff1f;就是不指定类型的函数&#xff0c;不指定类型如何写代码?所以得用到模板&#xff0c;可以先用模板代替&#xff0c;就好像方程式&#xff0c;先用x,y代替一样。 它的写法是这样&#xff0c;定义函数时&#xff0c;开头加一句:(其中的T就相当于x,y之…

时序分析笔记

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 目录 前言 一、周期约束 二、建立时间和保持时间 三、时序路径 四、时序模型 前言 约束文件笔记&#xff0c;傅里叶的猫的视频。 一、周期约束 时序约束就是告诉软件输…

六十天前端强化训练之第二十八天之Composition 函数完全指南

欢迎来到编程星辰海的博客讲解 看完可以给一个免费的三连吗&#xff0c;谢谢大佬&#xff01; 目录 一、核心概念解析 1.1 什么是 Composition 函数 1.2 为什么需要封装 1.3 设计原则 二、实战案例&#xff1a;鼠标跟踪器 2.1 未封装版本 2.2 封装后的 Composition 函数…

MySQL 锁机制详解

MySQL 锁机制详解 5.1 概述 锁是计算机协调多个进程或线程并发访问某一资源的机制。在数据库中&#xff0c;除传统的计算资源&#xff08;CPU、 RAM、I/O&#xff09;的争用以外&#xff0c;数据也是一种供许多用户共享的资源。如何保证数据并发访问的一致性、有 效性是所有数…

常见中间件漏洞攻略-Apache篇

漏洞名称&#xff1a;Apache HTTP Server 路径穿越漏洞-CVE-2021-41773 第一步&#xff1a;拉取环境、启动环境 #拉取环境 docker pull blueteamsteve/cve-2021-41773:no-cgidhttp://121.40.229.129:8080#启动环境 docker run -dit -p 8080:80 blueteamsteve/cve-2021-41773:n…

站群服务器是什么意思呢?

站群服务器是一种专门为托管和管理多个网站而设计的服务器&#xff0c;其核心特点是为每个网站分配独立的IP地址。这种服务器通常用于SEO优化、提高网站权重和排名&#xff0c;以及集中管理多个网站的需求。以下是站群服务器的详细解释&#xff1a; 一、站群服务器的定义 站群…

Excel 小黑第22套

对应大猫22 新建一行&#xff0c;输入第一个人名字&#xff0c; 填充 -快速填充 修改员工编号&#xff08;1—001&#xff09;&#xff1a;选中所有员工编号&#xff0c;开始 -数据组 -自定义数字格式 000 在所有空表格单元格中输入数字0&#xff1a;选中修改的表格范围&#…

多传感器融合 SLAM LVI-SAM

目录 LVI-SAM 简介 A. 系统概述 B. 视觉惯导系统 C.雷达惯导系统 LVI-SAM 安装编译 编译 LVI-SAM 常见问题 LVI-SAM 工程化建议 LVI-SAM 简介 源码地址:https://github.com/TixiaoShan/LVI-SAM 如无法下载,换用 gitee 版本:https://gitee.com/inf_lee/LVI-SAM 改进…