高级UI<第二十五篇>:图像处理之变换矩阵

图像的处理大致有两种:

  • ColorMatrix:颜色矩阵,5x4的矩阵。
  • Matrix:变换矩阵,3*3的矩阵。

本章主要讲解变换矩阵

(1)基本矩阵
[scale_x   skew_x   trans_x
 skew_y   scale_y   trans_y 
 persp_0  persp_1  persp_2]

其中,scale处理缩放变换,skew处理错切变换,trans处理平移变换,persp处理透视变换。

初始矩阵如下:

[1    0    0
 0    1    0
 0    0    1]
(2)图像缩放

直接贴一下代码

    Bitmap result = pool.get(toTransform.getWidth(), toTransform.getHeight(), Bitmap.Config.ARGB_8888);
    if(result == null){
        result = Bitmap.createBitmap(toTransform.getWidth(), toTransform.getHeight(), Bitmap.Config.ARGB_8888);
    }

    Canvas canvas = new Canvas(result);
    // 初始化Matrix对象
    Matrix matrix = new Matrix();
    // 根据传入的参数设置缩放比例
    matrix.setScale(scale_x, scale_y);
    Paint paint = new Paint();
    paint.setAntiAlias(true);
    // 根据缩放比例,把图片draw到Canvas上
    canvas.drawBitmap(toTransform, matrix,paint);

如果scale_x = 1,scale_y = 1,那么相当于没有缩放,原图效果如下:

图片.png

当我们将scale_x 和scale_y 都设置成0.5时,也就是将原图缩小一半,如题所示:

图片.png

满足以上效果,也可以直接设置矩阵

    Bitmap result = pool.get(toTransform.getWidth(), toTransform.getHeight(), Bitmap.Config.ARGB_8888);
    if(result == null){
        result = Bitmap.createBitmap(toTransform.getWidth(), toTransform.getHeight(), Bitmap.Config.ARGB_8888);
    }

    Canvas canvas = new Canvas(result);
    // 初始化Matrix对象
    Matrix matrix = new Matrix();
    // 根据传入的参数设置缩放比例
    float[] values = {
            0.5f,  0,   0,
              0,  0.5f, 0,
              0,   0,   1
    };
    matrix.setValues(values);
    Paint paint = new Paint();
    paint.setAntiAlias(true);
    // 根据缩放比例,把图片draw到Canvas上
    canvas.drawBitmap(toTransform, matrix,paint);

原理:

图形缩放原理.jpg

最终得出的图形旋转矩阵为:

[a     0     0
 0     b     0
 0     0     1]

a为x轴方向的缩放系数,b为y轴方向的缩放系数。

(2)平移操作

在当前画布上,x方向和y方向,各移动100距离。

    Bitmap result = pool.get(toTransform.getWidth(), toTransform.getHeight(), Bitmap.Config.ARGB_8888);
    if(result == null){
        result = Bitmap.createBitmap(toTransform.getWidth(), toTransform.getHeight(), Bitmap.Config.ARGB_8888);
    }

    Canvas canvas = new Canvas(result);
    // 初始化Matrix对象
    Matrix matrix = new Matrix();
    // 根据传入的参数设置缩放比例
    matrix.setTranslate(100, 100);
    Paint paint = new Paint();
    paint.setAntiAlias(true);
    // 根据缩放比例,把图片draw到Canvas上
    canvas.drawBitmap(toTransform, matrix,paint);

效果如下:

图片.png

使用矩阵数组,我们将图片缩小一倍之后再移动100距离。

    Bitmap result = pool.get(toTransform.getWidth(), toTransform.getHeight(), Bitmap.Config.ARGB_8888);
    if(result == null){
        result = Bitmap.createBitmap(toTransform.getWidth(), toTransform.getHeight(), Bitmap.Config.ARGB_8888);
    }

    Canvas canvas = new Canvas(result);
    // 初始化Matrix对象
    Matrix matrix = new Matrix();
    // 根据传入的参数设置缩放比例
    float[] values = {
            0.5f,  0,   100,
              0,  0.5f, 100,
              0,   0,   1
    };
    matrix.setValues(values);
    Paint paint = new Paint();
    paint.setAntiAlias(true);
    // 根据缩放比例,把图片draw到Canvas上
    canvas.drawBitmap(toTransform, matrix,paint);

效果如下:

图片.png

原理:

图形平移原理.jpg

最终得出的图形旋转矩阵为:

[1     0     a
 0     1     b
 0     0     1]

a为x轴方向的平移,b为y轴方向的平移。

(3)错切操作

定义: 错切是在某方向上,按照一定的比例对图形的每个点到某条平行于该方向的直线的有向距离做放缩得到的平面图形。

Matrix中有关错切的两个方法如下:

setSkew(float m, float n)
setSkew(float m, float n, float px, float py)

m为x轴上的错切因子,n为y轴上的错切因子,当前错切操作的错切坐标轴中心点为(px,py),如果没有指定错切坐标轴中心点,那么默认为(0,0)。

图片.png

如图所示,我们将图片按照坐标轴分成1,2,3,4几个部分,1和2为x轴的负方向,3和4为x轴的正方向,1和3为y轴的负方向,2和4为y轴的正方向。

假设错切因子是m、n,图片上所有的点用(x,y)表示,那么错切操作之后的点为

(x + my, y + nx)

也就是说,图片上的任一点都有一个从

(x,y)-->(x + my, y + nx)

转变的过程。

当m=0时,转变过程是

(x,y)-->(x , y + nx)

当n=0时,转变过程是

(x,y)-->(x + my, y)

所以图片不管怎么错切,x轴和y轴的点都不会变化。

执行以下代码

matrix.setSkew(0.1f, 0.1f, toTransform.getWidth()/2, toTransform.getHeight()/2);

错切后的效果如下:

图片.png

使用矩阵数组的代码如下(错切坐标轴的中心点是图片的左上角的点):

    Bitmap result = pool.get(toTransform.getWidth(), toTransform.getHeight(), Bitmap.Config.ARGB_8888);
    if(result == null){
        result = Bitmap.createBitmap(toTransform.getWidth(), toTransform.getHeight(), Bitmap.Config.ARGB_8888);
    }

    Canvas canvas = new Canvas(result);
    // 初始化Matrix对象
    Matrix matrix = new Matrix();
    // 根据传入的参数设置缩放比例
    float[] values = {
            1,    0.1f,   0,
            0.1f,  1,     0,
            0,     0,     1
    };
    matrix.setValues(values);
    Paint paint = new Paint();
    paint.setColor(Color.BLUE);
    paint.setStrokeWidth(5);
    paint.setAntiAlias(true);
    // 根据缩放比例,把图片draw到Canvas上
    canvas.drawBitmap(toTransform, matrix,paint);
    canvas.drawLine(0, toTransform.getHeight()/2, toTransform.getWidth(), toTransform.getHeight()/2, paint);
    canvas.drawLine(toTransform.getWidth()/2, 0, toTransform.getWidth()/2, toTransform.getHeight(), paint);

原理:

图形错切原理.jpg

其中m为x轴方向的错切因子,n为y轴方向的错切因子。

(4)旋转操作

假设中心点为图片的中央,在照中心点旋转顺时针旋转30度。

matrix.setRotate(30, toTransform.getWidth()/2, toTransform.getHeight()/2);

假设中心点为图片的中央,在中心点逆时针旋转30度。

matrix.setRotate(-30, toTransform.getWidth()/2, toTransform.getHeight()/2);

如图所示

顺时针

图片.png

逆时针

图片.png

旋转原理:

图形旋转原理.jpg

其中,θ表示图形旋转的角度,最终得出的图形旋转矩阵为:

[cosθ     -sinθ     0
 sinθ      cosθ     0
  0        0        1]
(5)逆矩阵操作

定义: 在线性代数中,给定一个 n 阶方阵方形矩阵 A,若存在一 n 阶方阵B,使得 AB=BA=In,其中In为 n阶单位矩阵,则称A可逆的,且BA逆矩阵,记作A-1

Android的Matrix也有对逆矩阵的封装

matrix.invert(matrix)

invert将矩阵逆转,假设逆转前的矩阵记作A,逆转后的矩阵记作B,单位矩阵记作*C,那么

C = A * B

或者

C = B * A

A和B互为可逆。

举例1: 平移操作的逆矩阵

向x轴方向移动100, 向y轴方向移动100

[1   0   100
 0   1   100
 0   0   1]

它的逆矩阵是

[1   0   -100
 0   1   -100
 0   0   1]

举例2: 缩放操作的逆矩阵

[0.5  0  0
 0    2  0
 0    0  1]

它的逆矩阵是

[2      0  0
 0    0.5  0
 0      0  1]

不举例了,总之两个互逆矩阵相乘为单位矩阵。(记住这个就可以了)

(6)isAffine

Android5.0新增接口,判断是否是仿射矩阵。

什么是仿射矩阵?

以上讲到的平移,缩放、旋转、错切、逆转都是仿射矩阵仿射矩阵最明显的特征就是3x3居中的第三行的数值都是[0 0 1]。

(7)isIdentity

判断是否为单位矩阵。

(8)reset

将矩阵重置为单位矩阵。

(9)前乘变换

preConcat、preRotate、preScale、preSkew、preTranslate

pre称之为前乘,也可以称之为右乘

    float[] A = {
            1,    0,   0,
            0,    1,   0,
            0,    0,   1
    };
    matrix.setValues(A);
    matrix.preConcat(B);

以上代码其实就是A * BB右乘A。(当然,也可以说A左乘B

(10)后乘变换

postConcat、postRotate、postScale、postSkew、postTranslate

    Matrix matrix = new Matrix();
    // 根据传入的参数设置缩放比例
    float[] A = {
            1,    0,   0,
            0,    1,   0,
            0,    0,   1
    };
    matrix.setValues(A);
    matrix.postConcat(B);

以上代码其实就是B * AB左乘A。(当然,也可以说A右乘B

(11)rectStaysRect

判断该矩阵是否可以将一个矩形依然变换为一个矩形。当矩阵是单位矩阵,或者只进行平移,缩放,以及旋转90度的倍数的时候,返回true。

(12)setRectToRect
setRectToRect(RectF src, RectF dst, ScaleToFit stf)

将rect变换成rect。

ScaleToFit 有如下四个值:

FILL: 可能会变换矩形的长宽比,保证变换和目标矩阵长宽一致。
START: 保持坐标变换前矩形的长宽比,并最大限度的填充变换后的矩形。至少有一边和目标矩形重叠。左上对齐。
CENTER: 保持坐标变换前矩形的长宽比,并最大限度的填充变换后的矩形。至少有一边和目标矩形重叠。
END: 保持坐标变换前矩形的长宽比,并最大限度的填充变换后的矩形。至少有一边和目标矩形重叠。右下对齐。

(13)setSinCos
setSinCos(float sinValue, float cosValue, float px, float py)
setSinCos(float sinValue, float cosValue)

这个常用于矩阵的旋转操作,之前我们讲过,图片旋转之后的矩阵为:

[cosθ     -sinθ     0
 sinθ      cosθ     0
  0        0        1]

假如需要旋转90度

matrix.setRotate(90);

或者

//sin90 = 1,cos90 = 0
matrix.setSinCos(1, 0);

以上两种写法都是可以的。

(14)setPolyToPoly
setPolyToPoly(float[] src, int srcIndex, float[] dst, int dstIndex, int pointCount)

src: 原始点
srcIndex:原始点的索引
dst: 目标点
dstIndex: 目标点的索引
pointCount: 点的数量取值范围是[0, 4]

  • 当pointCount为0时,则设置无效。
  • 当pointCount为1时,则控制一个点,可以将一个点平移到另一个点,相当于平移操作。
    float[] src = {0, 0};
    float[] dst = {100, 100};
    matrix.setPolyToPoly(src, 0, dst, 0 ,1);
图片.png
  • 当pointCount为2时,则控制两个点,两个点可以控制旋转和缩放操作,即以一点为中心点,将图片上的一点,移动到另一点。
图片.png

如上图所示,绿色线条为直角坐标,红色点分别是A、B、O。

现在要求将图片按照O旋转,从点A旋转到B,代码如下:

    float[] src = {toTransform.getWidth() / 2, toTransform.getHeight() / 2, toTransform.getWidth(), 0};
    float[] dst = {toTransform.getWidth() / 2, toTransform.getHeight() / 2, toTransform.getWidth() / 2, toTransform.getHeight()};
    matrix.setPolyToPoly(src, 0, dst, 0 ,2);

效果如下:

图片.png

我们已经将图片上的A点移动到了B点,完全符合预想中的效果。(旋转且缩放)

那么,再举一个例子,将B点移动到A点,O为中心点。

图片.png

代码如下:

    float[] src = {toTransform.getWidth() / 2, 0, toTransform.getWidth(), 0};
    float[] dst = {toTransform.getWidth() / 2, 0, toTransform.getWidth() * 3 / 4, 0};
    matrix.setPolyToPoly(src, 0, dst, 0 ,2);

效果如下:

图片.png
  • 当pointCount为3时,则控制三个点,三个点可以控制错切操作,即指定图片上的三点,一点固定,另外两点移动。

      float[] src = {0, 0, 0, toTransform.getHeight(), toTransform.getWidth(), toTransform.getHeight()};
      float[] dst = {0, 0, 200, toTransform.getHeight(), toTransform.getWidth() + 100, toTransform.getHeight()};
      matrix.setPolyToPoly(src, 0, dst, 0 ,3);
    

效果如下:

图片.png

这个比较好理解,固定一点,其他两点移动。数组里面有三个点,第一个点为固定点,后面两个点为移动点。

  • 当pointCount为4时,则控制四个点,四个点可以实现透视效果。

矩阵变换有两种:

仿射变换: 前面所说的平移,缩放、旋转、错切、逆转都是仿射矩阵,仿射矩阵最明显的特征就是3x3居中的第三行的数值都是[0 0 1]。(2D效果)
透视变换: 可以展示3D效果的透视图(3D效果)(影映射矩阵)

在PS中,如果图片不是很正,那么可以使用透视裁剪工具将图片矫正。

图片.png
图片.png

上图就是矫正前的图片。

在Android中,将正图转换成以上透视图就是从2D转3D的过程,这个过程叫做透视变换。

代码如下

    float[] src = {0, 0, 0, toTransform.getHeight(), toTransform.getWidth(), toTransform.getHeight(), toTransform.getWidth(), 0};
    int offset = 100;
    float[] dst = {0 + offset, 0, 0, toTransform.getHeight(), toTransform.getWidth(), toTransform.getHeight(), toTransform.getWidth()-offset, 0};
    matrix.setPolyToPoly(src, 0, dst, 0 ,4);

选定图四角上的四点,将上面两个点缩小,效果如下:

图片.png
(15)mapPoints
mapPoints(float[] dst, int dstIndex, float[] src, int srcIndex,int pointCount) 
mapPoints(float[] dst, float[] src) 
mapPoints(float[] pts)

映射点的值到指定的数组中,这个方法可以在矩阵变换以后,给出指定点的值。
dst:指定写入的数组
dstIndex:写入的起始索引,x,y两个坐标算作一对,索引的单位是对,也就是经过两个值才加1
src:指定要计算的点
srcIndex:要计算的点的索引
pointCount:需要计算的点的个数,每个点有两个值,x和y。

(16)mapRect
mapRect(RectF dst, RectF src)
mapRect(RectF rect)

返回值即是调用的rectStaysRect()。

(17)mapRadius
mapRadius(float radius)

返回一个圆圈半径的平均值,将matrix作用于一个指定radius半径的圆,随后返回的平均半径。

(18)mapVectors
mapVectors(float[] dst, int dstIndex, float[] src, int srcIndex,int vectorCount)     
mapVectors(float[] dst, float[] src) 
mapVectors(float[] vecs)

与上面的mapPoionts基本类似,这里是将一个矩阵作用于一个向量,由于向量的平移前后是相等的,所以这个方法不会对translate相关的方法产生反应,如果只是调用了translate相关的方法,那么得到的值和原本的一致。

[本章完...]

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

推荐阅读更多精彩内容