Android开发之图像处理那点事——变换

继上一篇《Android开发之图像处理那点事——滤镜》,这次我们来讲一下图片的变换操作,本篇主要是介绍常见的图像变换4种操作(平移、缩放、旋转、错切)

对于图像的颜色处理,Andriod系统给我们提供一个很方便的颜色矩阵类ColorMatrix,同样的,Android系统也给图像的变换处理提供了矩阵类Matrix,相比颜色矩阵的4 * 5,变换矩阵来得更加简单,它是由一个3 * 3的数字矩阵组成,我们来了解一下变换原理。

变换矩阵

在上一篇文章中,我们讲到像素点的颜色是由RGBA1组成的,分别代表红、绿、蓝、透明度各通道值还有颜色偏移量,在变换矩阵中也是类似的,像素点的位置是由矩阵C(X、Y、L)组成的,分别代表像素点在X、Y轴的位置还有偏移量。

变换矩阵

根据矩阵的乘法,我们可以得出下面的等式:

X1 = a * X + b * Y + c
Y1 = d * X + e * Y + f
L  = g * X + h * Y + i

当a=1,b和c=0,可以知道X1=1X+0+0=1X,
当e=1,d和f=0,可以知道Y1=0+1Y+0=1Y
当i=1,g和h=0,可以知道1=0+0+1=1
由此我们可以得到变换矩阵的单位矩阵:

单位矩阵

在日常开发中,我们涉及到的图像变换大致有平移、缩放、旋转、错切(很少),这些操作就是对这3 * 3的矩阵做数值上的调整,和上一篇讲颜色矩阵变化是类似的,不清楚的朋友,具体可以参考上一篇文章的讲解,下面我们快速过一下流程。

平移操作

像素点的平移其实就是对像素点的x和y坐标进行移动,如下图,从点p1移动到点p,假设p1(x1,y1)分别平移了x0和y0距离得到p(x,y),

平移操作

得到的公式就是:

x = x1 + x0
y = y1 + y0

矩阵所表现出来的形式:

平移操作

缩放操作

对于单个像素点是不存在缩放操作的,但如果是一系列的像素点,我们就可以根据x和y轴做一定比例的缩放,假设缩放比例为K1,得到的公式是:

x = K1 * x0
y = k1 * y0

矩阵所表现出来的形式:

缩放操作

旋转操作

像素点的旋转是围绕一个点旋转一定的角度得到新的像素点位置,为了便于理解,这里带上一张图:

旋转操作

如上图以原点为中心旋转,点(x0,y0)旋转了β°角度到了p(x,y),假设斜边为r,根据三角函数我们可以知道:

x0 = r * cosα
y0 = r * sinα

那么x和y就是x0和y0旋转了α角度后,再旋转了β角度,根据三角函数可以推出:

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轴坐标不变,X轴坐标按照比例进行平移,且平移的大小与该点到Y轴的距离成成正比,计算公式为:

x = x0 + k1 * y0
y = y0

矩阵变现形式为:

水平错切

垂直错切让所有像素点的X轴坐标不变,Y轴坐标按照比例进行平移,且平移的大小与该点到X轴的距离成成正比,计算公式为:

x = x0 
y = y0+ k2 * x0

矩阵变现形式为:

垂直错切

两个方向都错切,公式为:

x = x0 + k1 * y0
y = k2 * x0 * y0
水平/垂直错切

根据以上的矩阵表现方式,我们可以推出图像的变换规律为:

矩阵变换规律

Matrix

了解了各种变换原理后,我们来看下Matrix类,这是Android系统给我们提供的图像变换矩阵类,打开源码,我们可以看到这9个常量:

    public static final int MSCALE_X = 0;   //!< use with getValues/setValues
    public static final int MSKEW_X  = 1;   //!< use with getValues/setValues
    public static final int MTRANS_X = 2;   //!< use with getValues/setValues
    public static final int MSKEW_Y  = 3;   //!< use with getValues/setValues
    public static final int MSCALE_Y = 4;   //!< use with getValues/setValues
    public static final int MTRANS_Y = 5;   //!< use with getValues/setValues
    public static final int MPERSP_0 = 6;   //!< use with getValues/setValues
    public static final int MPERSP_1 = 7;   //!< use with getValues/setValues
    public static final int MPERSP_2 = 8;   //!< use with getValues/setValues

我们把它按3 * 3的矩阵形式排开,可以得到:

矩阵

对比下刚才上面我们推导的矩阵表现形式,怎么样,是不是更有感觉了,我们可以得到如下信息:
MTRANS_X、MTRANS_Y 决定了平移(Translate)
MSCALE_X、MSCALE_Y 决定了缩放( Scale)
MSCALE_X、MSKEW_X、MSCALE_Y、MSKEW_Y 决定了旋转( Rotate)
MSKEW_X、MSKEW_Y 决定了错切( Skew)
其实图像的变换操作无非就是对这个3 * 3的矩阵进行值的变化,而Matrix类只是帮我们封装好了这些操作,让我们无需关心细节,更加专注业务的实现,我们可以在Matrix中找到各操作对应的调用方法:

矩阵的操作方法

简单的看个小Demo:

上面分别对图像进行了平移、缩放、旋转、错切操作,贴一下核心代码:

            Matrix matrix = new Matrix();
            matrix.setTranslate(50, 50);//x,y坐标平移50像素
            canvas.drawBitmap(mBitmap, matrix, null);
            Matrix matrix = new Matrix();
            matrix.setScale(2, 2);//x,y坐标放大原来2倍
            canvas.drawBitmap(mBitmap, matrix, null);
            Matrix matrix = new Matrix();
            matrix.setRotate(20);//x,y坐标旋转20度
            canvas.drawBitmap(mBitmap, matrix, null);
            Matrix matrix = new Matrix();
            matrix.setSkew(2, 2);//x,y坐标按比例错切平移2
            canvas.drawBitmap(mBitmap, matrix, null);

当然这些操作也是可以组合起来的,Matrix里提供了一系列的postXXX,preXXX方法:

矩阵的操作方法

其实就是线性代数中矩阵的左乘和右乘,由于矩阵的乘法是不满足乘法交换律的,所以变换操作的执行顺序是对结果有影响的,继续来个小Demo:

先平移,再放大,再旋转

实现代码:

            Matrix matrix = new Matrix();
            matrix.setTranslate(200, 200);
            matrix.postScale(2, 2);
            matrix.postRotate(20);
            canvas.drawBitmap(mBitmap, matrix, null);
先平移,再旋转,再放大

实现代码:

            Matrix matrix = new Matrix();
            matrix.setTranslate(200, 200);
            matrix.preScale(2, 2);
            matrix.preRotate(20);
            canvas.drawBitmap(mBitmap, matrix, null);

简单粗暴可以这样理解:Matrix操作是一系列的任务存放在一个队列里,pre是把当前任务插入队列前,post是插入队列后,set则是清空队列所有任务,再入队。

这些看似很简单很基础的东西却恰恰是最重要的,它可以创造出很多东西,比如图片的自由手势缩放,微博上面的自定义贴纸,美图秀秀里的相框拼接等效果都离不开它。

源码下载:

这里附上源码地址(欢迎Star,欢迎Fork):BeautyImageDemo

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

推荐阅读更多精彩内容