用Python生成不同背景下显示内容不同的图片

今天在QQ群里看到这样一张图片,在手机上显示的时候,缩略图和点开之后看到的大图是完全不同的两个人。以前也在很多地方看到过点开之前和点开之后显示内容不同的图片,一直不知道其中的原理,于是想探究一番。

由于我不是很喜欢原图,这里用我喜欢的另一张图来说明。

首先把图片保存到电脑中,发现图片是png格式,而不是更常见的jpg。用Photoshop打开图片,效果如下:
amazing2

这些灰白相间的格子嘛,在Photoshop中表示透明。这说明图片中的大部分是透明的!那为什么在QQ中显示的不是这个样子呢?因为图片的后面有背景颜色!透明图片的特点就是在不同背景下显示的效果可以不一样。这也可以说明为什么图片是png格式,因为png格式可以有Alpha通道,而jpg没有。

在Photoshop中直接验证这个想法:

在原图的图层下面新建一图层,填充为白色,效果如下:
amazing3
amazing4

如果下面的图层填充为黑色,效果如下:
amazing5
amazing6

果然是背景颜色的原因。QQ、QQ空间等软件(包括网页版,甚至微博、贴吧、twitter)默认预览时的背景是白色的,而点开后查看图片的背景通常是黑色的,所以点开后图片显示出了不同的内容。

那么应该如何制作这种图片呢?我从Alpha通道的原理开始分析——

这种图片都是灰度图片,假设某个像素位置的灰度是\(C\),Alpha通道(即不透明度)是\(A\),在黑色背景下显示的灰度是\(C_1\),在白色背景下显示的灰度是\(C_2\),其中每个数值的取值范围都是\([0,1]\),则我们有

$$\begin{equation} \begin{cases}
C_1 = C \cdot A + 0 \cdot \left( 1 – A \right) \\
C_2 = C \cdot A + 1 \cdot \left( 1 – A \right)
\end{cases} \end{equation}$$

由此解出\(C\)和\(A\),即得到由两张原始图片颜色计算叠加图片颜色的公式

$$\begin{equation} \begin{cases}
C = \frac {C_1} {1 + C_1 – C_2} \\
A = 1 + C_1 – C_2
\end{cases} \end{equation}$$

由\(1-C_2 \geq 0\)和\(C_1 \geq 0\)可知\(C\)一定在\([0,1]\)范围内。(如果分子分母同时为零就取任意值,对应的情况为黑色背景下为纯黑、白色背景下为纯白,此时让Alpha为零即可。)

可是\(A\)不一定在\([0,1]\)范围内,只有当\(1 + C_1 – C_2 \leq 1\)即\(C_1 \leq C_2\)时才可以。也就是说黑背景下图片的每个像素都要比白背景下图片对应的像素暗,否则Alpha值被抹平为1,这个像素点就不能在纯黑和纯白背景下准确还原两张图了。这就说明,黑背景下显示的图应该尽量选择一张暗色图,白背景下显示的图应该尽量选择一张明亮的图。

但是我们这样只能产生灰度图片,并不能把这种技术用于彩色图片。原因就是在处理彩色图片的RGB三个通道时,得到的Alpha值可能不相同,这样就无法同时准确还原三个通道的颜色了。

推导出数学公式后,我就用Python的PIL库写了一个生成这种图片的程序。

程序力求简洁,有如下约定:

  • 两张原始图片分辨率相同
  • 想要在黑色背景下显示的图足够暗
  • 想要在白色背景下显示的图足够亮
  • 输出的图片只有灰度,没有颜色

代码如下:

import sys
from PIL import Image

dark=Image.open(sys.argv[1],'r').convert('LA').split()[0]
bright=Image.open(sys.argv[2],'r').convert('LA').split()[0]
assert dark.size==bright.size

def conv(c1,c2):
c=round(255*c1/(255+c1-c2)) if 255+c1-c2!=0 else 0
alpha=255+c1-c2 if 255+c1-c2<=255 else 255
global distortion
if 255+c1-c2>255: distortion+=1
return (c,alpha)

distortion=0
newdata=list(map(conv,dark.getdata(),bright.getdata()))
print('distortion:%.2f%%' % (distortion/len(newdata)*100))

img = Image.new('LA',dark.size)
img.putdata(newdata)

img.save(sys.argv[3], "PNG")

程序使用方法: python3 gen.py dark.jpg bright.jpg new.png

程序输出的distortion是失真(即算出的Alpha大于1而被抹平为1)的像素的百分比。

在网上随便找了两张图片,一张暗色调,一张亮色调,用这段程序生成png格式的图片后发到QQ,确实有点开前和点开后完全不同的效果。

点开前:
amazing7

点开后:
amazing8

《用Python生成不同背景下显示内容不同的图片》有4个想法

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注