graphics包Matrix类函数理解
二维图形变换的矩阵如下:
|ScaleX SkewX TransX|
|SkewY ScaleY TransY|
|Persp0 Persp1 Persp2|
ScaleX:x方向缩放倍率
ScaleY:y方向缩放倍率
TransX:x方向平移值
TransY:y方向平移值
SkewX:x方向错切值
SkewY:y方向错切值
Persp:齐次坐标的值,一般取值0或1。
graphics包中的Matrix类的方法调用native计算,jni调用了skia库中SkMatrix.cpp文件下的函数计算,各个函数实际上都是在对二维变换矩阵进行赋值:
void SkMatrix::setScale(SkScalar sx, SkScalar sy) { if (1 == sx && 1 == sy) { this->reset(); } else { fMat[kMScaleX] = sx; fMat[kMScaleY] = sy; fMat[kMPersp2] = 1; fMat[kMTransX] = fMat[kMTransY] = fMat[kMSkewX] = fMat[kMSkewY] = fMat[kMPersp0] = fMat[kMPersp1] = 0; this->setTypeMask(kScale_Mask | kRectStaysRect_Mask); } }
这是以原点为中心进行缩放的矩阵赋值。
void SkMatrix::setScale(SkScalar sx, SkScalar sy, SkScalar px, SkScalar py) { if (1 == sx && 1 == sy) { this->reset(); } else { fMat[kMScaleX] = sx; fMat[kMScaleY] = sy; fMat[kMTransX] = px - sx * px; fMat[kMTransY] = py - sy * py; fMat[kMPersp2] = 1; fMat[kMSkewX] = fMat[kMSkewY] = fMat[kMPersp0] = fMat[kMPersp1] = 0; this->setTypeMask(kScale_Mask | kTranslate_Mask | kRectStaysRect_Mask); } }
这是以指定坐标(px, py)为中心点进行缩放的矩阵赋值。与上一个函数对比可以发现,不仅进行了缩放还进行了平移,所以才可以表现为在指定点处缩放。
void SkMatrix::setTranslate(SkScalar dx, SkScalar dy) {
if (dx || dy) {
fMat[kMTransX] = dx;
fMat[kMTransY] = dy;
fMat[kMScaleX] = fMat[kMScaleY] = fMat[kMPersp2] = 1;
fMat[kMSkewX] = fMat[kMSkewY] =
fMat[kMPersp0] = fMat[kMPersp1] = 0;
this->setTypeMask(kTranslate_Mask | kRectStaysRect_Mask);
} else {
this->reset();
}
}
void SkMatrix::preTranslate(SkScalar dx, SkScalar dy) { if (!dx && !dy) { return; } if (this->hasPerspective()) { SkMatrix m; m.setTranslate(dx, dy); this->preConcat(m); } else { fMat[kMTransX] += sdot(fMat[kMScaleX], dx, fMat[kMSkewX], dy); fMat[kMTransY] += sdot(fMat[kMSkewY], dx, fMat[kMScaleY], dy); this->setTypeMask(kUnknown_Mask | kOnlyPerspectiveValid_Mask); } } void SkMatrix::postTranslate(SkScalar dx, SkScalar dy) { if (!dx && !dy) { return; } if (this->hasPerspective()) { SkMatrix m; m.setTranslate(dx, dy); this->postConcat(m); } else { fMat[kMTransX] += dx; fMat[kMTransY] += dy; this->setTypeMask(kUnknown_Mask | kOnlyPerspectiveValid_Mask); } } static inline SkScalar sdot(SkScalar a, SkScalar b, SkScalar c, SkScalar d) { return a * b + c * d; }
上面的这四个函数说明了set,pre和post的区别,假如有如下操作:
(1)
Matrix.scale(0.5, 0.5);
Matrix.preTranslate(100, 100);
(2)
Matrix.scale(0.5, 0.5);
Matrix.postTranslate(100, 100);
第一段代码会平移(100*0.5, 100*0.5),第二段代码会平移(100, 100),post不受之前的矩阵变换的影响。
而set则会清除所有值钱的矩阵设置。
其它函数的pre和post的意思都是如此。
比如preRotate():那么它的旋转中心点会受到之前矩阵变换的影响,而postRotate()不会。
preTranslate(100, 100);
preRotate(45):那么它的旋转中心点是(100, 100)
postRotate(45):旋转中心点是原点
图片的矩阵变换是对图片在屏幕上的每个坐标点进行变换。
比如矩形框Rect(100, 100, 600, 600),缩放scale(0.5f, 0.5f)之后变为Rect(50, 50, 300, 300)在屏幕上表现为矩形本身有缩小,但位置也有了偏移,这个时候不能简单的认为是矩形框保持矩形中心位置不变或左上角位置不变作缩放。
所以一般的绕中心缩放,倍数缩放操作之后还需要平移。因为中心坐标点位置不变,所以有关系式
先缩放后平移:
centerX * scaleX + translateX = centerX
centerY * scaleY + translateY = centerY
先平移后缩放:
(centerX + translateX) * scaleX = centerX
(centerY + translateY) * scaleY = centerY
再来理解下preScale(scaleX, scaleY, oriX, oriY),这个指定缩放原点的函数的意思。之前说图片的矩阵变化是对图片在屏幕上的每个坐标点进行变换,这里需要说明的是坐标是相对于坐标原点为(0, 0)而言的。而这里指定了坐标原点,那么所有的矩阵变换是相对于指定的这个坐标原点而言的。指定坐标原点对平移没有影响,但是对旋转和缩放是有影响的。比如RectF(0, 600, 0, 600),矩阵变换后:
preScale(0.5, 0.5) ——> RectF(0, 300, 0, 300)
preScale(0.5, 0.5, 900, 900) ——> RectF(450, 750, 450, 750):可以想象坐标原点移到了(900,900),原矩形上的所有点相对于这个原点的位置进行缩放得到新的矩形。
查看连接矩阵变换的skia库源码发现对于矩阵变换顺序如下:
pre(A)
post(B)
那么连接的矩阵变换为A*B
pre(A)
pre(B)
那么连接的矩阵变换为B*A
即pre的意思是改变矩阵变换的顺序
注意pre和post是相对于在这个操作之前的操作而言的pre(A)之前没有任何操作,所以此时pre和post和set无差别
另外:
scale缩放取值为-1时可以得到镜像矩阵
protected static Matrix getHorizontalMatrix(float width) { Matrix flipHorizontalMatrix = new Matrix(); flipHorizontalMatrix.setScale(-1, 1); flipHorizontalMatrix.postTranslate(width, 0); return flipHorizontalMatrix; }
上面的矩阵表示对原图进行水平镜像变换。