闪耀的二维码 之二

在做完 闪耀的二维码 之一 之后,发现好像二维码大多适合于 “静态扫”,是一张 “静态图”。我们用的可是显示器啊,是可以播放动画的。那么如何把二维码做成动态的呢?

思路

首先,我们要有两张图:一个 QR,一个背景动画。

其次,我们要把 QR 叠加到动画上。

QR 背景 预期结果
QR Background Rsult

计算宽高

我的想法是:全局补白居中。因为实现比较简单……

宽度和高度取两个图片的 max 就可以了。

当然,还有一个办法也是不错的:QR 放最左边居中,动图放右下角,QR 和动图可以有重叠。

补白

padding_width=(width-image_width)>>1padding_height=(height-image_height)>>1 来计算补白宽度。

新建一个数组,将原图放入图片中心。

掩模

这里解释一下为什么要掩模。

我们拿到的 QR 一般都是点阵图,RGB 的点阵图,如果直接 bia 到一张图片上的话,是没有透明效果的。在 Widows 画图里面,“移动” 工具默认也是背景不透明的。为了让下面的图像显示出来,我们需要一个 RGBA 的点阵图,或者是设置一个 “透明色”(例如,常用的玫红色、蓝色)。遇到 “透明色” 就不进行叠加。

我们可以使用遍历整个图片的方法将所有的透明区域标记出来,放到一个 mask 数组里面。或者使用一些 numpy 的函数(我不会用……)

叠加

叠加仅仅是个叠加么?

叠加有两种做法:第一种是数学做法,做一个加号就好了,矩阵运算,速度快,巴拉巴拉。第二种是点做法,一个一个像素来,如果不是 “透明像素”,那么上层像素值覆盖下层像素值(就像刷油漆)。后者在计算机图形学里面是一种很基础的算法。

像我这种懒人,肯定是使用加号了。但是直接加出来出现了下面的奇怪结果:

失败的例子

后来一想:对啊,图片怎么能直接加呢…… 值会超过 255 的,就死白了。

那怎么办?

先在原图中把非透明像素的区域减掉,然后再把 QR 非透明像素的区域加上去。掩模矩阵是一个 01 矩阵,是可以直接数乘的,那这项工作就变得简单了:image * (not mask) + QR * mask

我们就可以得到正确的图片啦。

示例

伪透明

我们发现上面生成的图片中,QR 的颜色过重,导致下层图片被完全覆盖,不好。有没有办法将上层图片变得 “透明” 一点呢?

继续用到计算机图形学里面学到的东西:反锯齿的公式还记得不?前景 * alpha + 背景 *(1-alpha)。试了一下,效果还不错。

代码

# -*- coding: utf-8 -*-

import skimage.io
import skimage.color
import imageio
import numpy as np


def make_qr_gif(filename_qr, filename_bg, filename_out, mask_color=[255, 255, 255], alpha=1.0, fps=10):
    image_qr = skimage.io.imread(filename_qr)
    # 为了防止输入图片为灰度图
    if image_qr.ndim == 2:
        image_qr = skimage.color.gray2rgb(image_qr)
    (height_qr, width_qr, _) = image_qr.shape
    image_qr = skimage.img_as_ubyte(image_qr)
    if len(image_qr[0][0]) == 4:
        image_qr = image_qr[:, :, 0:3]

    image_bg = skimage.io.imread(filename_bg)
    # 为了防止输入图片为静态图
    if image_bg.ndim == 3:
        iamge_bg = np.array([iamge_bg])
    image_bg = skimage.img_as_ubyte(image_bg)
    (_, height_bg, width_bg, __) = image_bg.shape
    if len(image_bg[0][0]) == 4:
        image_bg = image_bg[:, :, 0:3]

    # 获取大小
    width = max(width_qr, width_bg)
    height = max(height_qr, height_bg)
    # 补边宽度
    padd_bg_heigh = (height - height_bg) >> 1
    padd_bg_width = (width - width_bg) >> 1
    padd_qr_height = (height - height_qr) >> 1
    padd_qr_width = (width - width_qr) >> 1

    # 把 QR 图居中
    image_qr_center = np.zeros((height, width, 3))
    for i in range(3):
        image_qr_center[i].fill(mask_color[i])
    image_qr_center[padd_qr_height:padd_qr_height + height_qr, padd_qr_width:padd_qr_width + width_qr, :] = image_qr

    # 根据 QR 图做一个 mask
    mask = np.ones((height, width, 3), np.bool)
    for x in range(height):
        for y in range(width):
            if (image_qr_center[x, y, :] == mask_color[:]).all():
                mask[x, y, :].fill(False)

    # 动画序列
    sequence = list()

    for bg in image_bg:
        one_frame = np.zeros((height, width, 3))
        one_frame.fill(255)
        # 每一帧都居中
        one_frame[padd_bg_heigh:padd_bg_heigh + height_bg, padd_bg_width:padd_bg_width + width_bg, :] = bg[::]
        # 叠加上 QR 图
        one_frame = one_frame * np.invert(mask) + image_qr_center * mask * alpha + one_frame * mask * (1 - alpha)
        # 加入动画序列
        sequence.append(one_frame)

    imageio.mimsave(filename_out, sequence, fps=fps)


if __name__ == '__main__':
    make_qr_gif(filename_qr='qr.png',
                filename_bg='background.gif',
                filename_out='result.gif',
                alpha=0.6
                )

效果演示

呆呆最有爱了,是吧~

示例

示例

广告:单身汪求解放。

留下评论