美文网首页Android进阶自定义view相关view
Android 自定义View学习(八)——Matrix知识学习

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

作者: 英勇青铜5 | 来源:发表于2016-09-18 19:46 被阅读3685次

    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种基本变换操作的方法

    本人很菜,有错误请指出

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

    共勉 : )

    相关文章

      网友评论

      • android0226:写代码,变成考数学了。楼主很厉害了 感觉
        英勇青铜5: @android0226 矩阵这里,本身就是大量的线性代数,不过大部分时候都还好,涉及到的不难也不复杂。这里有一篇特别不错的博客,一步一步,用代码实现数学计算强烈推荐 http://www.jianshu.com/p/3dd3d1524851
      • 鱼丸粗面X:“a,b,c,d 共同控制旋转变换”这句话作者写错了,应该是a,b,d,e 共同控制旋转变换 :smile:
        英勇青铜5:@野生代码搬运工 在上一篇中,3.3 translate 平移画布,我记得我测试的结果是,使用canvas平移之后,坐标系的原点位置会根据平移而改变,matrix的平移并不会对坐标系造成改变。 这里你也验证一下吧,我感觉我当时搞错了。
        鱼丸粗面X:@英勇青铜5 哈哈,不谢,因为你写的好,我才会看得这么认真。另外文中有一句“根据底色可以明显看出,matrix的平移对canvas的坐标系不会造成影响,不像canvas.traslate()方法"。我用canvas.traslate()方法平移了,没看出有什么明显区别,不知道你说的区别是指什么?
        英勇青铜5:@野生代码搬运工 已修改 多谢指出错误
      • alighters:👍。。
        英勇青铜5:@alighters :smile:

      本文标题:Android 自定义View学习(八)——Matrix知识学习

      本文链接:https://www.haomeiwen.com/subject/hkjgettx.html