Android 自定义View学习(八)——Matrix知识学习
Matrix
主要用于对图像的图形处理。前面学习的ColorMatirx主要是图像色彩的处理
学习资料
- Android 群英传
- Android Matrix
- android matrix最全方法详解与进阶(完整篇)
十分感谢 : )
1.Martrix 变形矩阵
Matrix
是一个3 * 3
的矩阵,每个像素点表达了其坐标的X,Y
信息
处理每个像素点的计算方法
X1 = a * X + b * Y + c
Y1 = d * X + e * Y + f
L = g * X + h * Y + i
一般,g = h = 0 , i = 1
,这时L = g * X + h * Y + i
恒成立,也就是L = i = 1
Matrix
的初始化矩阵,对角线为1
,其余为0
Matrix
主要可以对图像做4种基本变换
- Translate 平移变换
- Rotate 旋转变换
- Scale 缩放变换
- Skew 错切变换
Matrix
类中的方法,主要也是和这四个变换相关,只是对计算过程做了封装
作用对象是Bitmap
而不是Canvas
2. Translate 平移变换
平移变换红点p1
平移到白点p
时,坐标值
x = x1 + x0
y = y1 + y0
矩阵的形式:
平移变换矩阵为了更好直观表现,先看原始效果
原始效果private void init() {
bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.test);
//画笔
paint = new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setColor(Color.parseColor("#FF4081"));
//矩阵
matrix = new Matrix();
matrix.setTranslate(100f,100f);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawColor(Color.YELLOW);
canvas.drawBitmap(bitmap,0,0,null);
}
在布局文件中,控件的宽为match_parent
,高为200dp
,onDraw()
方法中,canvas
绘制底色为黄色,又绘制了原始了的bitmap
。bitmap
的大小是没有控件大的,屏幕右侧留下了一块黄色的区域。此时并没有用到matrix
2.1 setTranslate()方法
Matrix
中提供了一个setTranslate()
方法,很容易就做到平移
简单修改代码
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawColor(Color.YELLOW);
canvas.drawBitmap(bitmap,matrix,null);
//在(100,100)处画一个圆
//用来辅助查看matrix作用后的坐标系
canvas.drawCircle(100,100,30,paint);
}
平移后
根据小圆可以看出,matrix
的平移对canvas
的坐标系不会造成影响,不像canvas.traslate()
方法。
matrix.setTranslate(100f,100f)
,bitmap
在x,y
轴上移动了100
个px
。
代入到平移的公式中:
平移100个像素最终
x = x1 + 100
y = y1 + 100
而超出了canvas
的部分,则不再显示
3. Rotate 旋转变换
旋转就是一个点围绕一个中心点旋转到新的位置
以原点的为旋转中心过程学习:
Rotate旋转图示白点p(x0,yo)
绕原点旋转β°
后,得到红点p(x,y)
斜边为r
,角度为α
,利用三角函数,得到
x0 = r * cosα
y0 = r * sinα
同理,可以得出
x = r * cos(α + β) = r * cosα * cosβ - r * sinα * sinβ = x0 * cosβ - y0 * sinβ
y = r * sin(α + β) = r * sinα * cosβ + r * cosα * sinβ = y0 * cosβ + x0 * sinβ
过程其实就是三角函数展开,矩阵的形式就是
旋转矩阵变换根据计算结果y = y0 * cosβ + x0 * sinβ
,需要注意sinβ
和cosβ
在矩阵的位置
上面的情况是以原点为旋转中心,任意点O
为旋转中心进行旋转变换,一般有3个步骤:
- 将坐标原点移动到任意点
O
- 使用上面的以坐标系原点为中心的旋转方法进行旋转
- 将坐标原点还原
主要就是考虑任意点与原点的坐标错。然而使用setRotate()
方法时,并不用考虑过多,都进行了封装
3.1 setRotate()方法
旋转方法有两个重载方法:
1. setRotate(float degrees)
2. setRotate(float degrees, float px, float py)
第一个方法简单使用,简单修改2.1中的代码
private void init() {
bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.test);
matrix = new Matrix();
matrix.setRotate(15);
}
围绕左上角旋转15度
默认以左上角为旋转中心,bitmap
的宽为r
进行旋转
第2个方法可以指定旋转中心O
,float px
就是O
点的X
轴坐标,float py
就是O
点的Y
轴坐标
matrix.setRotate(15,bitmap.getWidth()/2,bitmap.getHeight()/2);
以Bitmap中心为旋转中心
以bitmap
中心为旋转中心进行旋转15度
4.Scale 缩放变换
对于一个像素点来说,不存在缩放的概念,但一个图像是由很多个像素点组成,将每个点的坐标进行相同比例的缩放后,整个图像也就有了缩放的效果。
计算公式:
x = K1 * x0
y = k1 * y0
矩阵形式:
矩阵缩放变换k1 就是要缩放的比例,负值无效,bitmap
会不显示,0~1f
缩小,k1 > 1
为放大
4.1 setScale() 缩放方法
这个方法也有两个重载方法
1. setScale(float sx, float sy)
2. setScale(float sx, float sy, float px, float py)
根据学习setRotate()
方法,这个方法的两个重载方法比较好理解
第1个方法,简单使用
private void init() {
bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.test);
matrix = new Matrix();
matrix.setScale(0.5f,0.5f);
}
缩放二分之一
此时的缩放中心为bitmap
的坐上角
第2个方法,简单使用
matrix.setScale(0.5f,0.5f,bitmap.getWidth()/2,bitmap.getHeight()/2);
以Bitmap中心缩放
此时就是以Bitmap
的中心进行缩放,整个Bitmap
的边缘向中间靠拢
5. Skew 错切变换
错切变换skew
是一种比较特殊的线性变换,分为水平错切和垂直错切
5.1 水平错切
水平错切效果就是让所有像素点的Y
轴坐标不变,X
轴坐标按照比例进行平移,且平移的大小与该点到Y
轴的距离成成正比
在坐标系中的效果:
水平错切计算公式:
x = x0 + k1 * y0
y = y0
矩阵形式:
矩阵水平错切变换X
轴平移的值,是k1 * y0
5.2 垂直错切
垂直错切让所有像素点的X
轴坐标不变,Y
轴坐标按照比例进行平移,且平移的大小与该点到X
轴的距离成成正比
在坐标系中的效果:
垂直错切计算公式:
x = x0
y = y0+ k2 * x0
矩阵形式:
矩阵垂直错切变换5.3 两个方向都进行错切
当水平和垂直方向上都做错切变换时
计算公式:
x = x0 + k1 * y0
y = k2 * x0 * y0
矩阵形式:
水平和垂直都错切变换无论水平还垂直错切,最终的效果其实就是由矩形变作平行四边形
5.3 setSkew() 错切方法
这个方法也有两个重载
1. setSkew(float kx, float ky)
2. setSkew(float kx, float ky, float px, float py)
第2个方法后面两个参数也是为了指定错切的中心
5.3.1 setSkew(float kx, float ky)第一个方法
水平错切:
private void init() {
bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.test);
matrix = new Matrix();
matrix.setSkew(0.25f,0f);
}
水平错切效果
kx
就是k1
,负值,向左切;正值向右切
垂直错切:
private void init() {
bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.test);
matrix = new Matrix();
matrix.setSkew(0f,0.25f);
}
垂直错切效果
ky
就是k2
,负值,向上切;正值,向下切
bitmap
最右边的区域不是错切的效果,是因为bitmap
的宽没有canvas
的宽大,留下的空白区域
两个方向都错切:
matrix.setSkew(0.25f,0.25f);
两个方向错切
此时可以明显看出,错切的中心点为bitmap
的左上角
5.3.2 setSkew(float kx, float ky, float px, float py) 指定错切中心
简单使用:
matrix.setSkew(0.1f,0.1f,bitmap.getWidth()/2,bitmap.getHeight()/2);
指定错切中心点
以bitmap
的中心为错切中心点
这里目前并不是很理解指定中心点后错切对坐标系的影响
6.矩阵中的元素与四种变换效果的对应关系
矩阵- a和e 控制缩放变换
- b和d 控制错切变换
- c和f 控制平移变换
- a,b,d,e 共同控制旋转变换
第1行都是影响的X
轴,第2行影响的Y
轴
7.关于前乘和后乘
首先,矩阵的乘法不满足乘法的交换规律
在Matrix
类中,set
方法会重置矩阵中的所有值,而pre
和post
不会
7.1 简单对比
前乘的代码:
private void init() {
bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.test);
matrix = new Matrix();
matrix.setTranslate(100,100);
matrix.preRotate(15);
}
前乘效果
先进行平移,前乘旋转15°
后乘,简单修改代码:
matrix.postRotate(15);
后乘效果
两者差别,看右下角的区域比较明显
7.2 尝试分析
前乘旋转源码 旋转后乘源码前乘
就是M * R(degrees)
,后乘
就是R(degrees) * M
前乘
就对应线性代数矩阵运算的右乘
后乘
就对应线性代数矩阵运算的左乘
在矩阵运算中:
M
右乘A
,就是A * M
M
左乘A
,就是M * A
简单记法:右乘从右边乘进来,左乘从左边乘进来
7.1
中的矩阵:
在7.1
中共有3个矩阵,首先平移b
,旋转a
,像素c
在前乘或者后乘之前有一个setTranslate(100,100)
设置的矩阵b
,前乘后后乘也就是相对于b
来说
在7.1
前乘,计算过程就是:
a
右乘b
,计算就是b * a
,得到一个新的矩阵N
,N * c
后乘的过程:
a
左乘b
,计算就是a * b
,得到一个新的矩阵N
,N * c
总结:
在pre
或者post
方法前进行设置了哪个矩阵M
,矩阵M
与M
之前所有的矩阵的运算得到的新矩阵N
,N
就看做当前矩阵,前乘或者后乘就是相对于这个当前矩阵N
而言
7.3 补充 2016.09.30 09:09 <p>
根据总结,看下下面的两个小练习:
//方式1
matrix = new Matrix();
matrix.preRotate(30);
matrix.postTranslate(100f, 100f);
//方式2
matrix = new Matrix();
matrix.postTranslate(100f, 100f);
matrix.preRotate(30);
//方式3
matrix = new Matrix();
matrix.postRotate(30);
matrix.preTranslate(100f, 100f);
- 方式
1
和方式2
结果是否相同? 相同 - 方式
1
和方式3
结果是否相同? 不同
有图,有真相
3种方式的差别在看问题前,先了解这样一个矩阵的知识点,有助于理解问题:
有3个矩阵A
,B
,C
,相乘,N
= A * B * C
从左向右顺序计算,第1步,X
= A * B
,然后N
= X * C
从右向左倒序计算,第1步,X
= B * C
,然后N
= A * X
这两种计算方式是一样的。
网上有人说,图形处理时,矩阵的运算是从右向左计算的,这也就是为啥有pre
可以理解为先进行计算的一说,但个人感觉,从左开始和从右开始计算是一样的。但从右开始计算更容易理解吧
之所以说矩阵不满足乘法的交换规律,是说A * B
≠ B * A
N
= A * B * C
,从左开始计算和从右开始计算结果一样的前提就是,要按照矩阵排列时的顺序来进行计算
N
= A * B
,但N * C
≠ C * N
下面看问题
问题1:
- 方式
1
的矩阵的形式:
- 方式
2
的矩阵的形式:
new Matrix()
或者mMatrix.reset()
得到的就是一个原始矩阵
两个矩阵最终形式其实就是一个矩阵,差别可以通过括号的位置理解,括号内的就是先计算的。方式1
是可以看作从右面开始计算,方式2
可以看做从左面开始计算。但前面说了,计算的顺序并会不影响最终的结果
问题2:
方式3
的矩阵形式与方式1,2
不一样:
方式3
中与方式1,2
的差别就是旋转和平移两个矩阵交换了位置,而矩阵不满足乘法的交换律,所以方式1
和方式3
,最终结果就不同
8.其他的方法
Matrix
中,方法主要4大类,占据了绝大部分,set
,pre
,post
,map
开头的方法
8.1 setPolyToPoly()
这个方法非常强大,通过改变参数,除了可以实现平移,旋转,缩放,错切
,还可以实现透视
这个方法主要是利用确定矩形4个顶点,根据4个顶点坐标的变化来对bitmap
进行变换
最终的效果主要由src
和dst
两个数组进行控制,两个数组控制4个顶点的坐标,srcIndex,dstIndex
分别是src
和dst
的第一个值的角标,pointCount
是4个顶点中要使用的个数,最大为4,0表示不进行操作变换
透视效果,简单使用:
private void init() {
bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.test);
matrix = new Matrix();
float bWidth = bitmap.getWidth();
float bHeight = bitmap.getHeight();
float[] src = {0, 0, 0, bHeight, bWidth, bHeight,bWidth, 0};
float[] dst = {0 + 150,0, 0, bHeight, bWidth, bHeight, bWidth - 150, 0};
matrix.setPolyToPoly(src, 0, dst, 0, 4);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawColor(Color.YELLOW);
canvas.drawBitmap(bitmap,matrix,null);
}
透视效果
float[] src
和float[] dst
中的值一定是成对的出现,因为一个点的坐标由(x,y)
来确定,两两一对控制对应的一个顶点的坐标,最多有4对有效,超过的无效,因为再方法setPolyToPoly()
中,最后一个参数不能超过4
数组值和顶点坐标点的对应关系:
float[] dst = {f0, f1, f3, f3, f4, f5,f6, f7}
坐标和顶点的对应关系
为了方便看,将bitmap
放在了画布比较靠中心的位置
dst
可以看做是底板,最终要显示的效果;
src
可以看做是要截取的bitmap
的要显示的有效区域
控制不同的点的效果:
- 1个点,平移
- 2个点,缩放或者旋转
- 3个点,错切
- 4个点,透视
8.2 setRectToRect()
setRectToRect(RectF src, RectF dst, ScaleToFit stf)
第一个参数src
,截取资源Bitmap
的显示区域
第二个参数dst
,底板,显示的区域
第三个参数stf
,模式
谷歌api 给的Demo:
ScaleToFitFILL: 可能会变换矩形的长宽比,保证变换和目标矩阵长宽一致。
START:保持坐标变换前矩形的长宽比,并最大限度的填充变换后的矩形。至少有一边和目标矩形重叠。左上对齐。
CENTER: 保持坐标变换前矩形的长宽比,并最大限度的填充变换后的矩形。至少有一边和目标矩形重叠。
END:保持坐标变换前矩形的长宽比,并最大限度的填充变换后的矩形。至少有一边和目标矩形重叠。右下对齐。
图和文字说明从androidmatrix最全方法详解与进阶(完整篇)摘抄
简单使用:
private void init() {
bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher);
matrix = new Matrix();
int screenWidth = getResources().getDisplayMetrics().widthPixels;
int screenHeight = getResources().getDisplayMetrics().heightPixels;
float bWidth = bitmap.getWidth();
float bHeight = bitmap.getHeight();
RectF src = new RectF(0,0,bWidth/2,bHeight/2 );
RectF dst = new RectF(0,0,screenWidth,screenHeight);
matrix.setRectToRect(src,dst, Matrix.ScaleToFit.END);
}
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawColor(Color.YELLOW);
canvas.drawBitmap(bitmap,matrix,null);
}
Matrix.ScaleToFit.END
结合上面的图,也比较容易理解
8.3 其他的一些方法
方法名 | 作用 |
---|---|
reset() |
将矩阵恢复为初始化矩阵 |
boolean invert(Matrix inverse) |
反转当前矩阵 |
boolean isIdentity() |
是否为初始化矩阵 |
boolean isAffine() |
是否为仿射矩阵 |
boolean rectStaysRect() |
判断该矩阵是否可以将一个矩形依然变换为一个矩形。当矩阵是单位矩阵,或者只进行平移,缩放,以及旋转90度的倍数的时候,返回true |
仿射变换其实就是二维坐标到二维坐标的线性变换,保持二维图形的“平直性”(即变换后直线还是直线不会打弯,圆弧还是圆弧)和“平行性”(指保持二维图形间的相对位置关系不变,平行线还是平行线,而直线上点的位置顺序不变),可以通过一系列的原子变换的复合来实现,原子变换就包括:平移、缩放、翻转、旋转和错切。这里除了透视可以改变z轴以外,其他的变换基本都是上述的原子变换,所以,只要最后一行是0,0,1则是仿射矩阵。
其他的方法以后用到了再学习补充
9. 最后
本篇主要就学习4种基本变换操作的方法
本人很菜,有错误请指出
感谢学习资料中的大神前辈们
共勉 : )