Android 自定义View学习(八)——Matrix知识学习

Matrix主要用于对图像的图形处理。前面学习的ColorMatirx主要是图像色彩的处理

学习资料

十分感谢 : )


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初始化矩阵

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,高为200dponDraw()方法中,canvas绘制底色为黄色,又绘制了原始了的bitmapbitmap的大小是没有控件大的,屏幕右侧留下了一块黄色的区域。此时并没有用到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)bitmapx,y轴上移动了100px

代入到平移的公式中:

平移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个步骤:

  1. 将坐标原点移动到任意点O
  2. 使用上面的以坐标系原点为中心的旋转方法进行旋转
  3. 将坐标原点还原

主要就是考虑任意点与原点的坐标错。然而使用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个方法可以指定旋转中心Ofloat 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方法会重置矩阵中的所有值,而prepost不会


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,得到一个新的矩阵NN * c

后乘的过程:
a左乘b,计算就是a * b,得到一个新的矩阵NN * c

总结:
pre或者post方法前进行设置了哪个矩阵M,矩阵MM之前所有的矩阵的运算得到的新矩阵NN就看做当前矩阵,前乘或者后乘就是相对于这个当前矩阵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. 方式1和方式2结果是否相同? 相同
  2. 方式1和方式3结果是否相同? 不同

有图,有真相

3种方式的差别

在看问题前,先了解这样一个矩阵的知识点,有助于理解问题:

有3个矩阵ABC,相乘,N = A * B * C
从左向右顺序计算,第1步,X = A * B,然后N = X * C
从右向左倒序计算,第1步,X = B * C,然后N = A * X

这两种计算方式是一样的。

网上有人说,图形处理时,矩阵的运算是从右向左计算的,这也就是为啥有pre可以理解为先进行计算的一说,但个人感觉,从左开始和从右开始计算是一样的。但从右开始计算更容易理解吧

之所以说矩阵不满足乘法的交换规律,是说A * BB * A

N = A * B * C,从左开始计算和从右开始计算结果一样的前提就是,要按照矩阵排列时的顺序来进行计算

N = A * B,但N * CC * N

下面看问题


问题1:

  • 方式1的矩阵的形式:
方式1矩阵
  • 方式2的矩阵的形式:
方式2

new Matrix()或者mMatrix.reset()得到的就是一个原始矩阵

两个矩阵最终形式其实就是一个矩阵,差别可以通过括号的位置理解,括号内的就是先计算的。方式1是可以看作从右面开始计算,方式2可以看做从左面开始计算。但前面说了,计算的顺序并会不影响最终的结果


问题2:

方式3的矩阵形式与方式1,2不一样:

方式3矩阵

方式3中与方式1,2的差别就是旋转和平移两个矩阵交换了位置,而矩阵不满足乘法的交换律,所以方式1和方式3,最终结果就不同


8.其他的方法

Matrix中,方法主要4大类,占据了绝大部分,setprepostmap开头的方法

8.1 setPolyToPoly()

这个方法非常强大,通过改变参数,除了可以实现平移,旋转,缩放,错切,还可以实现透视

这个方法主要是利用确定矩形4个顶点,根据4个顶点坐标的变化来对bitmap进行变换

setPolyToPoly方法

最终的效果主要由srcdst两个数组进行控制,两个数组控制4个顶点的坐标,srcIndex,dstIndex分别是srcdst的第一个值的角标,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[] srcfloat[] 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:

ScaleToFit

FILL: 可能会变换矩形的长宽比,保证变换和目标矩阵长宽一致。
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种基本变换操作的方法

本人很菜,有错误请指出

感谢学习资料中的大神前辈们

共勉 : )

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,117评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,963评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,897评论 0 240
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,805评论 0 203
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,208评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,535评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,797评论 2 311
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,493评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,215评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,477评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,988评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,325评论 2 252
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,971评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,055评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,807评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,544评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,455评论 2 266

推荐阅读更多精彩内容