YUV Alpha Blending
Alpha-Blending,是按照“Alpha”混合向量的值来混合源像素和目标像素的一种图像处理技术
Alpha混合向量表示图片的透明度,取值范围[0,255],0表示全透明,图片无法被看见,255表示原始的图像,无透明效果,取中间值为半透明状态
RGB Alpha Blending
- 首先将源像素和目标像素的R,G,B分量分别提取出来;
- 然后将源像素的R分量乘以alpha,目标像素的R分量乘以alpha的反值并相加两者的结果做为新像素的R值;G,B分量做同样的处理;
- 最后将新的R,G,B分量重新合成为一个新像素
混合算法:
// Alpha做了归一化处理
R3 = R1 * a + R2 * (1 - a)
G3 = G1 * a + G2 * (1 - a)
B3 = B1 * a + B2 * (1 - a)
YUV Alpha Blending
对于YUV数据,我们根据RGB到YUV的转化算法和RGB的Alpha Blending算法做推导
// RGB to YUV
Y = (( 66 * R + 129 * G + 25 * B + 128) >> 8) + 16
U = ((-38 * R - 74 * G + 112 * B + 128) >> 8) + 128
V = ((112 * R - 94 * G - 18 * B + 128) >> 8) + 128
以Y分量做演示
((Y1 - 16) << 8) * a + ((Y2 - 16) << 8) * (1 - a)
= (66 * R1 + 129 * G1 + 25 * B1 + 128) * a +
(66 * R2 + 129 * G2 + 25 * B2 + 128) * (1 - a)
= 66 * ((R1 * a - R2 * (1 - a)) +
129 * ((G1 * a - G2 * (1 - a)) +
25 * ((B1 * a - B2 * (1 - a)) + 128
= 66 * R3 + 129 * G3 + 25 * B3 + 128
= (Y3 - 16) << 8
即Y3 = Y1 * a + Y2 * (1 - a)
同理可以验证到U,V具有同样的表现形式f(x, y, a) = x * a + y * (1 - a)
YUV叠加水印
一张图片上添加水印的原理其实就是像素替换,在指定的水印区域内,用水印图片的像素值替换掉原图区域内的像素值。
一般的内容为白色的水印,比如手机相机的时间水印,水印的bitmap,背景为黑色,内容为白色,我们可以直接根据白色byte值为-21来做判断条件进行像素替换
但是在彩色图片作为水印或者水印内容为黑色的时候,上面这种hard code的解决方案就不适用了,此时我们就可以使用YUV Alpha Blending算法了
// Y像素替换,U,V分量同理
int a = alpha[waterMarkYIndex]; // [0, 255]
int dest = waterMarkYuv[waterMarkYIndex];
int source = sourceYuv[sourceYIndex];
sourceYuv[sourceYIndex] = (dest * a + source * (255 - a)) >> 8;
从水印的Bitmap中将YUV数据和Alpha数据提取出来,此处以提取NV21为例,
/**
* fetch nv21 data and alpha data from bitmap
* @param bitmap bitmap
* @param nv21 nv21 data
* @param alpha alpha data
*/
public static void fetchNv21(@NonNull Bitmap bitmap, @NonNull byte[] nv21, int[] alpha) {
int w = bitmap.getWidth();
int h = bitmap.getHeight();
int size = w * h;
int[] pixels = new int[size];
bitmap.getPixels(pixels, 0, w, 0, 0, w, h);
w &= ~1;
h &= ~1;
for (int i = 0; i < h; i++) {
for (int j = 0; j < w; j++) {
int yIndex = i * w + j;
int argb = pixels[yIndex];
int r = (argb >> 16) & 0xff;
int g = (argb >> 8) & 0xff;
int b = argb & 0xff;
int y = ((66 * r + 129 * g + 25 * b + 128) >> 8) + 16;
y = clamp(y, 16, 255);
nv21[yIndex] = (byte) y;
if (i % 2 == 0 && j % 2 == 0) {
int u = ((-38 * r - 74 * g + 112 * b + 128) >> 8) + 128;
int v = ((112 * r - 94 * g -18 * b + 128) >> 8) + 128;
u = clamp(u, 0, 255);
v = clamp(v, 0, 255);
nv21[size + i / 2 * w + j] = (byte) v;
nv21[size + i / 2 * w + j + 1] = (byte) u;
}
if (alpha != null) {
alpha[yIndex] = (argb >> 24) & 0xff;
}
}
}
}
从fetchNv21这个方法中可以看到YUV的数据长度为w * h * 3 / 2,而提取的Alpha数据长度只是Y分量的长度,也就是w * h。这样在运用YUV Alpha Blending算法的时候,混合Y分量,每一个Y都对应一个alpha,那么U,V分量的alpha值要怎么取呢?
对于YUV420的格式来说,每四个Y分量共用一个UV分量,而人眼对Y分量,也就是亮度敏感,而对UV分量,即色度不敏感。在进行YUV Alpha Blending,一对UV混合的时候,只需要使用共用这对UV的4个Y分量的第一个Y分量对应的alpha来作为混合因子就可以了
使用这篇文章的封面图作为背景,公众号头像作为水印,采用YUV Alpha Blending算法实现的水印效果如下: