QQ缩略图和大图不同实现


背景

最近在QQ群里看见别人发一张图,缩略图看着是一个样子,但打开大图以后却发现是另一张图,仔细观察这两张图发现缩略图是白色背景,大图是黑色背景,结合缩略图所在的对话框是白色的,打开大图的UI是黑色的,很容易猜到应该是依靠透明度实现的功能,因此研究了一下怎么用代码通过将两张图片混合来构造出满足要求的图片。

思路

首先,混合的图片必须是灰度图(黑白图),理由之后再讲。灰度图的每个像素都是一个0到255的整数,分别表示从纯黑到纯白的颜色。设想要构造出的灰度图每个像素颜色为c,每个像素都有一个透明度,是一个0到1的小数,设为a。当我们将一张半透明的图片覆盖在背景上时,得到的图片每个像素的颜色既不是这个像素的本来颜色,也不是背景的颜色,而是介于这两种颜色之间的颜色,即将这两个颜色按一定比例进行混合,这个混合比例就是透明度,也称为Alpha,因此这一过程称为Alpha混合。如果设背景颜色为b,混合得到的颜色为n,则有公式: \[ n=c\times a+b\times(1-a) \] 那么,我们就可以列两个方程了: \[ n_{黑}=c\times a+0\times(1-a)=c\times a \\ n_{白}=c\times a+255\times(1-a) \] 这样,我们给出两张灰度图,它们就是我们想看到的n黑和n白,只要解出c和a,就可以构造出想要的图片了。但是这里有一个问题,因为255×(1-a)始终是大于0的,所以n白将始终大于n黑,换言之,想在白色背景下显示的图片的每个像素值必须大于想在黑色背景下显示的图片对应位置的像素值。为了满足这个需求,同时保证同一图片各个像素值的相对大小,可以将黑色背景下显示的图片的像素颜色从0-255映射到0-127,把白色背景下显示的图片的像素颜色从0-255映射到128-255,这样就可以了。映射可以这样计算: \[ n_{黑新}=n_{黑原}/2 \\ n_{白新}=128+n_{白原}/2 \] 为什么混合的图片不能是彩色图呢,彩色图的每个像素都是一个三维向量,分别表示红色的强度,蓝色的强度和绿色的强度。代入到方程中会发现n白和n黑已经变成三维向量了,但它们的差值仍是一个标量,也就是说,白色背景下显示的图片的每个像素值和黑色背景下显示的图片对应位置的像素值的红色、蓝色、绿色强度差值完全相同。如果用HSV色域来解释,就是必须保证两张图片的每个像素的色相和饱和度完全相等,只有亮度不一样,而这肯定不能保证,因此混合的图片不能是彩色图。

代码

import cv2
import numpy as np

imgb = cv2.imread('test1.png', 0)
imgw = cv2.imread('test2.png', 0)
imgb = imgb // 2
imgw = imgw // 2 + 128
a = 255 - (imgw - imgb) # 因为实际图片格式中Alpha是一个0-255的整数,所以这里计算的实际是上面公式中的a*255的值。
c = np.uint8(imgb / (a / 255 + 1e-3)) # 加1e-3防止当Alpha等于0的时候除0错误
imgn = cv2.cvtColor(c, cv2.COLOR_GRAY2BGRA)
imgn[:, :, 3] = a
cv2.imwrite('testo.png', imgn)