神奇的PNG缩略图魔术

2014.10.24 14:04 Fri| 44次阅读 technology| 源码

有位朋友向我显摆了一个小技巧,某个网站有张图片,缩略图看着是图 A,当鼠标悬浮于缩略图上/或点击缩略图时,却显示成另外一张图像 B。

是不是有点小神奇呢?我在本文尝试解释这一「魔术」的原理,并且重新实现这个特效。

(编注:那个网站需梯子,请直接把下图保存到本地,然后你再打开看看)

行为

某些 png 文件渲染器显示一张图片,而其他渲染器可能会显示为完全不同的另外一张图片。一张图片总是暗的,一张是亮的。

例子:

能显示亮色图的东西

缩略图渲染器(Facebook等)
苹果 png 渲染器
Windows png 渲染器

能显示暗色图的东西

Firefox(以及使用 libpng 的任何扩展)
Google Chrome

这可以带来有意思的组合:

直接外链 Facebook 上的图片,缩略图是图 A,但是当链接被点击时就完全不同。
可以用来检测用户浏览器。(Chrome、Firefox 或Safari)
图片在浏览器中显示一种结果,当下载到用户(受害者)的电脑中是另外一样。
经典的图片板块缩略图。

挑战与胜利

我开始苦苦思索这个效果是如何实现的,以便我可以复制。通往启迪的道路包括了很多错误的弯路,比如认为图片被转换为GIF。但是我最终发现了真相。

我发现了秘密之后,用 Ruby 写了一个名叫双视觉(doubleVision)命令行工具,这样任何人都可以生成神奇的缩略图。

双视觉(doubleVision)是一个可执行的Ruby gem,源码我放在 Github 上了。

输出的图片看起来像这样:

试试把它下到你电脑上看看。酷不酷呢?

它是如何工作的

PNG 的说明中包含了一个元数据属性,允许你渲染图片时带上指定的 Gamma 值。这个属性是用来保证图片在所有的电脑上看起来一样。这是非常正常的图像处理过程,叫做伽马修正。

PNG 的说明中定义了伽马块(块存储了伽马值)来改变图片的输出,像这样:

light_out = image_sample^(1 / gamma)

这个公式根据伽马值的倒数作为指数调整图像的值。如果伽马值像通常一样在 1 附近,这个函数就没有明显影响。在这个过程中,对于一个像素来说最低亮度是0,最高的为1。

如果我们把 PNG 的伽马属性设为很低的值,使得指数值很高(因为是倒数),所有更暗的像素变得更黑,所有更亮的像素被映射到正常的频谱。

指数伽马映射

我们可以对非常低的伽马属性值反过来映射(我用 0.023),把PNG图像中所有像素映射到非常亮的颜色。如果我们接下来把PNG的伽马值设为0.023,图像会看来正常些,除了在截断图像高值引入的误差附近。

问题是,并不是所有的渲染器度支持伽马属性。如果我们在一个不支持伽马属性的渲染器中试着查看图片,它会显示得太亮而无法辨认。

我们可以滥用这个方法来创建一个神奇的缩略图,利用两种同尺寸的图片创建一个原始维度两倍的新图片。一张图片利用之前提到的反向伽马滤波处理,使得所有像素非常亮,另一个没有很亮的像素因此变暗。然后,把两张图放在网格中相互环绕(参见下图)。最后把图存为伽马值为 0.023 的PNG文件。

当图片在支持伽马的渲染器(像Firefox/Chrome)中显示时,浅色像素变得相当的暗,但是可见颜色和正常的像素变成了一个暗像素的网格。当图片在不支持伽马的渲染器(像 Apple/Microsoft 渲染)中显示时,未转换的图片看起来被一个白色像素的网格围绕。

安装和使用

你可以安装双视觉(doubleVision)gem 执行下面的命令:

$ gem install doubleVision

然后像这样运行程序:

doubleVision withgamma.png withoutgamma.png out.png
显式地用你自己的文件名进行替换。

它会把图片组合成一个图(out.png),用支持伽马的渲染器查看时显示 withgamma.png (例如,Firefox中),不支持伽马的显示 withoutgamma.png (例如,作为缩略图)。

更多详细的说明看 Github 上的 README。

其他例子:(图片另存到本地后再看看哈~)


生成自: