现象
在使用 MediaCodec 解码视频获取到纹理时,它会给出一个 cropRect 来裁剪多余的绿色像素。当拿着这个纹理和对应的 cropRect 去上屏的时候,发现在边缘的地方有一像素绿边。
如图所示,解码出的纹理大小是 1920*1088
,有 8 像素的绿边;裁剪后大小是 1920*1080
,有 1 像素绿边。
解码图片
裁剪后
纹素和像素的映射关系
一开始怀疑是纹素和像素的坐标系不一致的问题,对纹理坐标减了 0.5,发现还是有绿边。然后还找到坐标系不一致的问题只存在于 D3D9,后续的 D3D10 修改了坐标系的对应关系,而且 OpenGL 的坐标系一直没这个问题。
收缩 0.5 纹素
在OpenGL ES Texture Coordinates Slightly Off上看到说只有当采样的点在纹素中心,才返回准确的颜色,否则就是插值出来的。也就是当采样的点在纹素中心和边界之间时,可能就会采到超出边界的颜色。
查 Android 源码
同时也发现使用SurfaceTexture.getTransformMatrix
得到的 matrix 时,画面是正常的,所以去查看了 Android 的源码,想知道这个 matrix 是怎么生成的。
生成的逻辑就是下面这段代码,可以看到注释说为了防止双线性采样超过裁剪边缘,普通纹理需要收缩 0.5 纹素,YUV420的要收缩 1.0 纹素。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 |
|
再看一遍SurfaceTexture.getTransformMatrix)发现也有说明。
双线性插值(Bilinear Filtering)
双线性插值会取临近 4 个像素的加权平均值。
上面的情况我们在传递的是图片边缘的 UV 坐标,那么由于双线性采样,它就会采到下面绿色的像素;如果我们传递的 UV 坐标收缩 0.5px,那么边缘外面的像素权重会是 0,就采不到绿色。
链接
OpenGL ES Texture Coordinates Slightly Off
SurfaceTexture::computeTransformMatrix
SurfaceTexture.getTransformMatrix)
图形学底层探秘 - 纹理采样、环绕、过滤与Mipmap的那些事
Directly Mapping Texels to Pixels (Direct3D 9)