Flutter绘制-06-矩阵专项

920 阅读8分钟

查看目录-->

矩阵基础知识

基础知识见原文:baike.baidu.com/item/%E7%9F…

矩阵定义

image.png

加法 减法 转置

image.png

image.png

乘法

image.png

在Flutter中,矩阵的操作

平移

参看:baike.baidu.com/item/%E5%B9…

平移,是指在同一平面内,将一个图形上的所有点都按照某个直线方向做相同距离的移动,这样的图形运动叫做图形的平移运动,简称平移。

平移不改变图形的形状和大小。图形经过平移,对应线段相等,对应角相等,对应点所连的线段相等。 它是等距同构,是仿射空间中仿射变换的一种。它可以视为将同一个向量加到每点上,或将坐标系统的中心移动所得的结果。即是说,若是一个已知的向量,是空间中一点,平移。

图片平移的方向,不限于是水平。

齐次坐标

image.png

平移矩阵

设某点[x,y],向x方向移动 dx, 向y方向移动 dy ,移动后坐标为[x+dx,y+dy]。

怎么实现呢?

image.png

使用矩阵乘法,而下图中的矩阵,即为平移转换矩阵。

image.png

再看下三维的:

image.png

缩放

设某点[x,y,z],在x轴缩放 dx, 在y轴缩放dy,在z轴缩放dz ,缩放后坐标为[xdx,ydy,z*dz]。 怎么实现呢?

image.png

那么缩放矩阵就是:

image.png

旋转

在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

image.png

通过图可看到,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].

那么怎么通过矩阵来处理呢?

使用矩阵乘法:

image.png

逆时针旋转矩阵为:

image.png

同理可得顺时针旋转矩阵为:

image.png

综上,平移、缩放和旋转,都是矩阵乘操作,只是不同操作矩阵结构不同。

注意,在flutter中,x向右为正,y向下为正,因此上面的逆时针与顺时针矩阵正好相反。

实际例子

Canvas.transform

canvas坐标系是xyz的,因此矩阵是4阶矩阵。

平移

利用矩阵,向x方向平移50,如下. image.png

平移矩阵:

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倍,如下:

image.png

缩放矩阵:

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轴顺时针的例子:

image.png

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周,顺时针的示例: image.png

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轴顺时针的示例:

image.png

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这个纹理。

image.png

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缩放:

image.png

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轴缩放:

image.png

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做缩放: image.png

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通道
  1. 灰度颜色过滤器 照片颜色变给白
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,
         ]);

image.png

  1. 暗褐色色调的颜色 照片颜色变给老久 泛黄
    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,
     ]);

image.png

  1. 反转颜色矩阵
    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,
    ]);

image.png

  1. 去除红色
    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,
     ]);

image.png 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,
    ]);

image.png

6.泛白效果 构造一个将sRGB gamma曲线应用于RGB的颜色过滤器频道

    paint.colorFilter = ColorFilter.linearToSrgbGamma();

image.png

7.泛白的反效果,有点清纯黑,创建应用sRGB gamma曲线反转的颜色过滤器到RGB通道。

    paint.colorFilter = ColorFilter.srgbToLinearGamma();

image.png

示例代码:

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);
  }