矩阵基础知识
基础知识见原文:baike.baidu.com/item/%E7%9F…
矩阵定义
加法 减法 转置
乘法
在Flutter中,矩阵的操作
平移
参看:baike.baidu.com/item/%E5%B9…
平移,是指在同一平面内,将一个图形上的所有点都按照某个直线方向做相同距离的移动,这样的图形运动叫做图形的平移运动,简称平移。
平移不改变图形的形状和大小。图形经过平移,对应线段相等,对应角相等,对应点所连的线段相等。 它是等距同构,是仿射空间中仿射变换的一种。它可以视为将同一个向量加到每点上,或将坐标系统的中心移动所得的结果。即是说,若是一个已知的向量,是空间中一点,平移。
图片平移的方向,不限于是水平。
齐次坐标
平移矩阵
设某点[x,y],向x方向移动 dx, 向y方向移动 dy ,移动后坐标为[x+dx,y+dy]。
怎么实现呢?
使用矩阵乘法,而下图中的矩阵,即为平移转换矩阵。
再看下三维的:
缩放
设某点[x,y,z],在x轴缩放 dx, 在y轴缩放dy,在z轴缩放dz ,缩放后坐标为[xdx,ydy,z*dz]。 怎么实现呢?
那么缩放矩阵就是:
旋转
在x向右为正,y向上为正的坐标系中,设某点[x,y],逆时针选择b度,求旋转后的坐标[X,Y]?
首先建立坐标系,其中a是[x,y]与x轴初始角度,b是逆时针旋转的角度。
设R为半径,那么:R=x/cosa,R=y/sina, sina=y/R,cosa=x/R
通过图可看到,X = R * cos(a+b), Y = R * sin(a+b)
X = R * cos(a+b)
X = (x / cosa ) * (cosa * cosb - sina*sinb)
X = x * cosb - x *(sina/cosa) * sinb
引入:sina=y/R,cosa=x/R
X = x * cosb -y * sinb
同理: Y = R * sin(a+b)
Y = (y / sina) * (sina * cosb +cosa * sinb)
Y = y*cosb + y * (cosa/sina) * sinb
引入:sina=y/R,cosa=x/R
Y = ycosb +xsinb
通过推导,得出[X,Y]=[x * cosb -y * sinb,ycosb +xsinb].
那么怎么通过矩阵来处理呢?
使用矩阵乘法:
逆时针旋转矩阵为:
同理可得顺时针旋转矩阵为:
综上,平移、缩放和旋转,都是矩阵乘操作,只是不同操作矩阵结构不同。
注意,在flutter中,x向右为正,y向下为正,因此上面的逆时针与顺时针矩阵正好相反。
实际例子
Canvas.transform
canvas坐标系是xyz的,因此矩阵是4阶矩阵。
平移
利用矩阵,向x方向平移50,如下.
平移矩阵:
Float64List.fromList(
[ 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, dx, dy, dz, 1 ])
向哪个方向平移,就增加哪个的值。也可以同时向xy平移。注意在屏幕这里z是无效果的。
示例代码:
void _transform(Canvas canvas, Size size) {
Path path = Path();
path.lineTo(60, 60);
path.lineTo(-60, 60);
path.lineTo(60, -60);
path.lineTo(-60, -160);
path.close();
canvas.drawPath(path, _paint);
Float64List matrix = Float64List.fromList(
[
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
50, 0, 0, 1 // 注意这里的50
]);
canvas.transform(matrix);
canvas.drawPath(
path,
_paint
..style = PaintingStyle.stroke
..strokeWidth = 2);
}
缩放
在x轴放大2倍,如下:
缩放矩阵:
Float64List.fromList(
[ sx, 0, 0, 0, 0, sy, 0, 0, 0, 0, sz, 0, 0, 0, 0, 1 ])
想要在哪个轴缩放,就修改哪个的值,不要修改sx sy sz之外的值,一但修改会是其他效果了。
示例代码:
void _transformScale(Canvas canvas, Size size) {
Path path = Path();
path.lineTo(60, 60);
path.lineTo(-60, 60);
path.lineTo(60, -60);
path.lineTo(-60, -160);
path.close();
canvas.drawPath(path, _paint);
Float64List matrix = Float64List.fromList(
[
2, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1
]);
canvas.transform(matrix);
canvas.drawPath(
path,
_paint
..style = PaintingStyle.stroke
..strokeWidth = 2);
}
旋转
canvas提供的rotate方法其实都是围绕z轴的,但是transform可以随意定制。
我们用矩阵实现旋转。
当一个轴的箭头指向我们自己时,我们定义顺时针和逆时针。
围绕z轴
围绕z轴旋转,xy的值都变化
- 顺时针
Float64List.fromList(
[
cos(angle), sin(angle), 0, 0,
-sin(angle), cos(angle), 0, 0,
0, 0, 1, 0,
0, 0, 0, 1
])
- 逆时针
Float64List.fromList(
[
cos(angle), -sin(angle), 0, 0,
sin(angle), cos(angle), 0, 0,
0, 0, 1, 0,
0, 0, 0, 1
])
一个围绕z轴顺时针的例子:
void _transformRotate(Canvas canvas, Size size) {
Path path = Path();
path.lineTo(60, 60);
path.lineTo(-60, 60);
path.lineTo(60, -60);
path.lineTo(-60, -160);
path.close();
canvas.drawPath(path, _paint);
double angle = 0.5;
// 绕z轴顺时针旋转
Float64List matrix = Float64List.fromList(
[
cos(angle), sin(angle), 0, 0,
-sin(angle), cos(angle), 0, 0,
0, 0, 1, 0,
0, 0, 0, 1
]);
// 绕z轴逆时针旋转
Float64List matrix1 = Float64List.fromList(
[
cos(angle), -sin(angle), 0, 0,
sin(angle), cos(angle), 0, 0,
0, 0, 1, 0,
0, 0, 0, 1
]);
canvas.transform(matrix);
// canvas.rotate(pi/2);
canvas.drawPath(
path,
_paint
..style = PaintingStyle.stroke
..strokeWidth = 2);
}
围绕x轴
围绕x轴旋转,yz的值都变化,但是在平面上,z轴变化时看不出来的,只能看出来y的变化
- 顺时针
Float64List.fromList(
[
1, 0, 0, 0,
0, cos(angle), sin(angle), 0,
0, -sin(angle), cos(angle), 0,
0, 0, 0, 1
])
- 逆时针
Float64List.fromList(
[
1, 0, 0, 0,
0, cos(angle), -sin(angle), 0,
0, sin(angle), cos(angle), 0,
0, 0, 0, 1
])
一个围绕x周,顺时针的示例:
void _transformRotateX(Canvas canvas, Size size) {
Path path = Path();
path.lineTo(60, 60);
path.lineTo(-60, 60);
path.lineTo(60, -60);
path.lineTo(-60, -160);
path.close();
canvas.drawPath(path, _paint);
double angle = pi/4;
// 绕x轴顺时针旋转
Float64List matrix = Float64List.fromList(
[
1, 0, 0, 0,
0, cos(angle), sin(angle), 0,
0, -sin(angle), cos(angle), 0,
0, 0, 0, 1
]);
// 绕x轴逆时针旋转
Float64List matrix1 = Float64List.fromList(
[
1, 0, 0, 0,
0, cos(angle), -sin(angle), 0,
0, sin(angle), cos(angle), 0,
0, 0, 0, 1
]);
canvas.transform(matrix);
// canvas.rotate(pi/2);
canvas.drawPath(
path,
_paint
..style = PaintingStyle.stroke
..strokeWidth = 2);
}
围绕y轴
围绕y轴旋转,xz的值发生变化,但是平面上z轴变化体现不出来,只能看到x的变化
- 顺时针
Float64List.fromList(
[
cos(angle), 0, sin(angle), 0,
0, 1, 0, 0,
-sin(angle), 0, cos(angle), 0,
0, 0, 0, 1
])
- 逆时针
Float64List.fromList(
[
cos(angle), 0, -sin(angle), 0,
0, 1, 0, 0,
sin(angle), 0, cos(angle), 0,
0, 0, 0, 1
])
一个围绕y轴顺时针的示例:
void _transformRotateY(Canvas canvas, Size size) {
Path path = Path();
path.lineTo(60, 60);
path.lineTo(-60, 60);
path.lineTo(60, -60);
path.lineTo(-60, -160);
path.close();
canvas.drawPath(path, _paint);
double angle = 1;
// 绕y轴顺时针旋转
Float64List matrix = Float64List.fromList(
[
cos(angle), 0, sin(angle), 0,
0, 1, 0, 0,
-sin(angle), 0, cos(angle), 0,
0, 0, 0, 1
]);
// 绕y轴逆时针旋转
Float64List matrix1 = Float64List.fromList(
[
cos(angle), 0, -sin(angle), 0,
0, 1, 0, 0,
sin(angle), 0, cos(angle), 0,
0, 0, 0, 1
]);
canvas.transform(matrix);
// canvas.rotate(pi/2);
canvas.drawPath(
path,
_paint
..style = PaintingStyle.stroke
..strokeWidth = 2);
}
Paint的Shader
ImageShader
作用和上面一样,都是平移、旋转、缩放或其他形状的操作。针对的是shader这个纹理。
void _testImageShaderMatrix(Canvas canvas, Size size) {
Paint _paint = Paint();
_paint.isAntiAlias = true;
_paint.shader = ImageShader(_src, TileMode.mirror,
// TileMode.clamp,
TileMode.repeated,
// TileMode.mirror,
Float64List.fromList([
0.5, 0, 0, 0.001,
0, 0.5, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1,
]));
canvas.drawCircle(Offset.zero, 200, _paint);
}
Gradient
都是对shader这个纹理的调整,平移、旋转、缩放或其他形状操作。
linear
如对x缩放:
void _testGradientLinear(Canvas canvas, Size size) {
Paint _paint = Paint();
_paint.isAntiAlias = true;
_paint.style = PaintingStyle.stroke;
_paint.strokeWidth = 30;
_paint.shader = ui.Gradient.linear(
Offset(-100, 0),
Offset(100, 0),
[Colors.red, Colors.blue, Colors.green],
[0.2, 0.4, 0.6],
// TileMode.clamp,
// TileMode.repeated,
TileMode.mirror,
Float64List.fromList([
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1,
]),
);
canvas.drawLine(Offset(-150, 0), Offset(150, 0), _paint);
_paint.shader = ui.Gradient.linear(
Offset(-100, 0),
Offset(100, 0),
[Colors.red, Colors.blue, Colors.green],
[0.2, 0.4, 0.6],
// TileMode.clamp,
// TileMode.repeated,
TileMode.mirror,
Float64List.fromList([
0.3, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1,
]),
);
canvas.drawLine(Offset(-150, 40), Offset(150, 40), _paint);
}
radial
画两个圆,第二个对x轴缩放:
void _testGradientRadial(Canvas canvas, Size size) {
Paint _paint = Paint();
_paint.isAntiAlias = true;
_paint.style = PaintingStyle.fill;
_paint.strokeWidth = 1;
_paint.shader = ui.Gradient.radial(
Offset(0, -150),
80,
[Colors.red, Colors.blue, Colors.green],
[0.2, 0.4, 0.8],
// TileMode.clamp,
TileMode.repeated,
Float64List.fromList([
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1,
])
// TileMode.mirror,
);
canvas.drawCircle(Offset(0, -150), 150, _paint);
_paint.shader = ui.Gradient.radial(
Offset(0, 150),
80,
[Colors.red, Colors.blue, Colors.green],
[0.2, 0.4, 0.8],
// TileMode.clamp,
TileMode.repeated,
Float64List.fromList([
0.3, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1,
])
// TileMode.mirror,
);
canvas.drawCircle(Offset(0, 150), 150, _paint);
}
sweep
第二个圆,对y做缩放:
void _testGradientSweep(Canvas canvas, Size size) {
Paint _paint = Paint();
_paint.isAntiAlias = true;
_paint.style = PaintingStyle.fill;
_paint.strokeWidth = 1;
_paint.shader = ui.Gradient.sweep(
Offset(0, -150),
[Colors.red, Colors.blue, Colors.green],
[0.3, 0.5, 1],
// TileMode.clamp,
TileMode.repeated,
// TileMode.mirror,
0,
pi/2,
Float64List.fromList([
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1,
]),
);
canvas.drawCircle(Offset(0, -150), 150, _paint);
_paint.shader = ui.Gradient.sweep(
Offset(0, 150),
[Colors.red, Colors.blue, Colors.green],
[0.3, 0.5, 1],
// TileMode.clamp,
TileMode.repeated,
// TileMode.mirror,
0,
pi/2,
Float64List.fromList([
1, 0, 0, 0,
0, 0.5, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1,
]),
);
canvas.drawCircle(Offset(0, 150), 150, _paint);
}
总之Gradient的三个构造方法中,矩阵都是对shader的操作。
Paint 的 ColorFilter
colorFilter 是对颜色的操作。
- mode(),指定一个颜色,然后使用混合模式对dst进行融合
- matirx(),自定义矩阵,对dst的每个颜色通道(像素点)进行乘操作,从而改变dst的颜色
- linearToSrgbGamma(), 构造一个将sRGB gamma曲线应用于RGB的颜色过滤器频道
- srgbToLinearGamma(),创建应用sRGB gamma曲线反转的颜色过滤器到RGB通道
- 灰度颜色过滤器 照片颜色变给白
paint.colorFilter = ColorFilter.matrix(<double>[
0.2126, 0.7152, 0.0722, 0, 0,
0.2126, 0.7152, 0.0722, 0, 0,
0.2126, 0.7152, 0.0722, 0, 0,
0, 0, 0, 1, 0,
]);
- 暗褐色色调的颜色 照片颜色变给老久 泛黄
paint.colorFilter = ColorFilter.matrix(<double>[
0.393, 0.769, 0.189, 0, 0,
0.349, 0.686, 0.168, 0, 0,
0.272, 0.534, 0.131, 0, 0,
0, 0, 0, 1, 0,
]);
- 反转颜色矩阵
paint.colorFilter = ColorFilter.matrix(<double>[
-1, 0, 0, 0, 255,
0, -1, 0, 0, 255,
0, 0, -1, 0, 255,
0, 0, 0, 1, 0,
]);
- 去除红色
paint.colorFilter = ColorFilter.matrix(<double>[
0, 0, 0, 0, 0,
0, 1, 0, 0, 0,
0, 0, 1, 0, 0,
0, 0, 0, 1, 0,
]);
5.改变透明度
paint.colorFilter = ColorFilter.matrix(<double>[
1, 0, 0, 0, 0,
0, 1, 0, 0, 0,
0, 0, 1, 0, 0,
0, 0, 0, 0.5, 0,
]);
6.泛白效果 构造一个将sRGB gamma曲线应用于RGB的颜色过滤器频道
paint.colorFilter = ColorFilter.linearToSrgbGamma();
7.泛白的反效果,有点清纯黑,创建应用sRGB gamma曲线反转的颜色过滤器到RGB通道。
paint.colorFilter = ColorFilter.srgbToLinearGamma();
示例代码:
void _testColorFilter(Canvas canvas, Size size) {
Paint paint = Paint();
// 灰度颜色过滤器 照片颜色变给白
paint.colorFilter = ColorFilter.matrix(<double>[
0.2126, 0.7152, 0.0722, 0, 0,
0.2126, 0.7152, 0.0722, 0, 0,
0.2126, 0.7152, 0.0722, 0, 0,
0, 0, 0, 1, 0,
]);
// 暗褐色色调的颜色 照片颜色变给老久 泛黄
paint.colorFilter = ColorFilter.matrix(<double>[
0.393, 0.769, 0.189, 0, 0,
0.349, 0.686, 0.168, 0, 0,
0.272, 0.534, 0.131, 0, 0,
0, 0, 0, 1, 0,
]);
// 反转颜色矩阵
paint.colorFilter = ColorFilter.matrix(<double>[
-1, 0, 0, 0, 255,
0, -1, 0, 0, 255,
0, 0, -1, 0, 255,
0, 0, 0, 1, 0,
]);
// 去除红色
paint.colorFilter = ColorFilter.matrix(<double>[
0, 0, 0, 0, 0,
0, 1, 0, 0, 0,
0, 0, 1, 0, 0,
0, 0, 0, 1, 0,
]);
// 改变透明度
paint.colorFilter = ColorFilter.matrix(<double>[
1, 0, 0, 0, 0,
0, 1, 0, 0, 0,
0, 0, 1, 0, 0,
0, 0, 0, 0.5, 0,
]);
// 泛白效果 构造一个将sRGB gamma曲线应用于RGB的颜色过滤器频道
paint.colorFilter = ColorFilter.linearToSrgbGamma();
// 泛白的反效果,有点清纯黑,创建应用sRGB gamma曲线反转的颜色过滤器到RGB通道。
paint.colorFilter = ColorFilter.srgbToLinearGamma();
canvas.drawImage(
_src, Offset(-_src.width/2,-_src.height/2), paint);
}