QR code 生成器

0

Comments

前几天在找 Android 应用的时候,又见到了 QR code。说到 QR code,记得2006年,我还在维基活跃的时候,有一个人在维基的 QQ 群上发了这个的东西,然后大家就觉得很好玩。很早的事情了。寒假回福州的时候,在乌山公园,不少地方也有这个二维码,是移动设置的“物联网试验区”。

二维码着实是一个很有趣的东西。据我去过日本的同学讲,这个东西在日本已经十分普及了。在维基上查了一下,QR code 是开放的,有专利但没有被行使。而且识别算法似乎已经十分稳定成熟了。Android 手机里面安装一个 Barcode Scanner 就可以利用摄像头扫描二维码了,非常方便~

二维码可以干嘛呢?它可以储存网址、储存电话、储存名片信息,扫描一下就可以读取出来,将人从人工的复制方式中解脱出来~

于是我便也做了一个 CGI 的 QR code 生成器。

这个生成器用了开源的 libqrencode 和 libmagick 这两个库,都可以直接在 Ubuntu 里面 apt-get 下来,第一个是生成二维码数据,第二个是输出为图片。使用二进制 CGI 还涉及了我可爱的服务器商的支持问题等等……

生成器源代码

核心程序没什么好说的,其实就是查文档的问题而已。参考了一下 libqrencode 和 libmagick 的文档就可以了。

直接贴代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <qrencode.h>
#include <magick/api.h>
 
#define DEFAULT_TEXT "http:/""/upsuper.org/"
 
int main(int argc, char **argv) {
    int i, j;
    int scale;
    QRecLevel level;
    char *text;
 
    // 设置默认参数
    scale = 8;
    level = QR_ECLEVEL_Q;
    text = (char *)malloc(strlen(DEFAULT_TEXT) + 1);
    strcpy(text, DEFAULT_TEXT);
 
    // 读取参数
    const char *orig;
    char *query_string;
    char *p;
    int len;
    orig = getenv("QUERY_STRING");
    len = strlen(orig);
    query_string = (char *)malloc(len + 2);
    *query_string = '&';
    strcpy(query_string + 1, orig);
    for (p = query_string; *p; ++p) {
        if (*p != '&')
            continue;
        if (strncasecmp(p + 1, "scale=", 6) == 0) {
            // 读取缩放级
            scale = atol(p + 7);
        } else if (strncasecmp(p + 1, "text=", 5) == 0) {
            // 读取文本
            char *p_last, *p_src, *p_dest;
            for (p_last = p + 6; *p_last && *p_last != '&'; ++p_last);
            text = (char *)realloc((void *)text, p_last - p - 6 + 1);
            // urldecode
            for (p_src = p + 6, p_dest = text;
                    p_src < p_last;
                    ++p_src, ++p_dest) {
                if (*p_src == '+') {
                    *p_dest = ' ';
                } else if (*p_src == '%') {
                    int code;
                    if (sscanf(p_src + 1, "%2x", &code) != 1)
                        code = '?';
                    *p_dest = code;
                    p_src += 2;
                } else {
                    *p_dest = *p_src;
                }
            }
            *p_dest = '\0';
        } else if (strncasecmp(p + 1, "level=", 6) == 0) {
            // 读取纠错级别
            switch (*(p + 7)) {
                case 'l': case 'L': level = QR_ECLEVEL_L; break;
                case 'm': case 'M': level = QR_ECLEVEL_M; break;
                case 'q': case 'Q': level = QR_ECLEVEL_Q; break;
                case 'h': case 'H': level = QR_ECLEVEL_H; break;
            }
        }
    }
 
    // 生成 QR code
    QRcode *qrcode;
    int width;
    qrcode = QRcode_encodeString8bit(text, 0, level);
    width = qrcode->width;
    // 初始化 Image Magick
    ImageInfo *image_info;
    ExceptionInfo exception;
    InitializeMagick(*argv);
    GetExceptionInfo(&exception);
    // 初始化图片
    Image *image, *resized_image;
    image_info = CloneImageInfo((ImageInfo *)NULL);
    image = AllocateImage(image_info);
    image->columns = image->rows = width;
    // 生成图片
    PixelPacket *pixel_packet;
    int t_bit;
    for (i = 0; i < width; ++i) {
        pixel_packet = SetImagePixels(image, 0, i, width, 1);
        if (! pixel_packet)
            break;
        for (j = 0; j < width; ++j, ++pixel_packet) {
            t_bit = ! (qrcode->data[i * width + j] & 1);
            pixel_packet->red   = MaxRGB * t_bit;
            pixel_packet->green = MaxRGB * t_bit;
            pixel_packet->blue  = MaxRGB * t_bit;
        }
        if (! SyncImagePixels(image))
            break;
    }
    QRcode_free(qrcode);
    // 处理图片
    resized_image = ScaleImage(image,
            width * scale, width * scale, &exception);
    DestroyImage(image);
    CompressImageColormap(resized_image);
    // 输出图片
    puts("Content-Type: image/png");
    puts("");
    strcpy(resized_image->filename, "png:-");
    WriteImage(image_info, resized_image);
    // 清理
    DestroyImage(resized_image);
    DestroyImageInfo(image_info);
    DestroyExceptionInfo(&exception);
    DestroyMagick();
 
    return 0;
}

话说最近喜欢用 C,C++ 什么的最讨厌了……

另外写了个 Makefile:

1
2
3
4
5
LDFLAGS:=-lqrencode `Magick-config --ldflags --libs`
CFLAGS:=`Magick-config --cflags`
 
all: qrcodetest
qrcodetest: qrcodetest.c

下面就是把这个 cgi 部署到服务器的问题了……

部署到服务器

我的这个空间商吧,我猜他装了 libmagick 的库,不过肯定没装开发库,也就是 libmagick-dev 这种东西,这样就编译不了了,比较麻烦。探究了一下发现安装了 gcc~很是高兴啊~而且 cgi 支持 bash 的。

这样就好玩了~我在我的访问不到的根目录下建立了一个 usr 文件夹,就已这个作为存在的核心了~

我写了 bash cgi 程序直接从服务器上获取源代码包,在服务器上解压编译 ImageMagick,也就是下面这段代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#!/bin/bash
 
export PATH=$PATH:/xxxx/upsuper.org/usr/bin
export CPATH=/xxxx/upsuper.org/usr/include
export LIBRARY_PATH=/xxxx/upsuper.org/usr/lib
 
echo Content-Type: text/plain
echo
wget http://downloads.sourceforge.net/project/imagemagick/ImageMagick/00-6.6.1/ImageMagick-6.6.1-5.tar.bz2
mkdir imagick
mv ImageMagick-6.6.1-5.tar.bz2 imagick
cd imagick
tar -jxvf ImageMagick-6.6.1-5.tar.bz2
cd imagick/ImageMagick-6.6.1-5
./configure --prefix=/xxxx/upsuper.org/usr
make
make install
make clean
make distclean

至于 qrencode,也不过就是把里面的一些东西改动一下就可以了~由于 make 的优美特性,即使中间被中断也可以断点继续,很好很好~

最后编译好以后,再把我的 cgi 代码传上去,编译,通过了。把生成的文件拷贝出来加上 .cgi,却不能运行……

弄了个 bash 脚本上去加 ldd,发现 libqrencode.so 找不到,libmagick 看起来确实是有预装,在 /usr/lib 里面就轻易找到了。不甘心啊,这个东西应该是有办法解决的……

研究了一下,想起那个环境变量 LD_LIBRARY_PATH。我在 bash 的 cgi 里面加上

1
export LD_LIBRARY_PATH=/xxxx/upsuper.org/usr/lib

之后,ldd 里面所有库都可以找到了~真好

然后我就想啊,我们可不可以让他自动设置 LD_LIBRARY_PATH 这个环境变量呢?我就查了一下,发现在 .htaccess 里面加入 SetEnv 是可以的。

于是我就

1
SetEnv LD_LIBRARY_PATH /xxxx/upsuper.org/usr/lib

结果不行。弄了个 bash cgi:

1
2
3
4
5
#!/bin/bash
 
echo Content-Type: text/plain
echo
env

发现这个东西没有被加入环境变量当中。郁闷……在网上查了半天,没有发现任何人遇到过这种情况。我就想,不会吧,我这么倒霉?

然后我试了一下网上普遍喜欢用 SetEnv 的样例:

1
SetEnv TZ Asia/Chongqing

再打开刚才的查环境变量专用 cgi,发现出现了!

这就奇怪了。这说明 Apache 的 env 模块非常正常,但设置的东西却是选择性的进入 cgi。我猜一定是有什么白名单之类的东西。

于是我就跑去问服务商的技术客服……(又要用英语……)

话说这个主机商的技术客服看起来十分繁忙,我每次去都要等很久很久才有人应答。不知道是我的英语描述水平的问题,还是对方无法理解这种高深的问题,他直接告诉我这是我自己代码的问题,跟他没关系……然后居然就直接关断了聊天,我靠,这什么态度!我就囧了……又连了一次,我非常耐心的跟那个客服解释了一遍我想要的效果,他终于明白了!而且非常耐心的帮我解决问题。最后他告诉我,因为服务器使用了 suEXEC 这个模块,对进入 cgi 的环境变量做了一个白名单,所以不行……

唉,不行就不行呗,早知这样,我也不是没有办法的……

在 suexec 的代码里查到了环境变量白名单,发现所有 HTTP_ 和 SSL_ 开头的都是白名单范围的。于是我就写下了这样一个 .htaccess:

1
2
3
4
5
6
7
RewriteEngine On
 
RewriteCond %{REQUEST_URI} \.cgi$
RewriteRule ^(.*)$ /setenv.sh?cgi=$1 [QSA]
 
RewriteCond %{QUERY_STRING} ^cgi=([^&]+)
RewriteRule . - [E=HTTP_CGI:%1]

与之配套的 setenv.sh:

1
2
3
4
5
6
7
8
#!/bin/bash
 
export PATH=$PATH:/xxxx/upsuper.org/usr/bin
export CPATH=/xxxx/upsuper.org/usr/include
export LIBRARY_PATH=/xxxx/upsuper.org/usr/lib
export LD_LIBRARY_PATH=/xxxx/upsuper.org/usr/lib
 
./$HTTP_CGI

终于可以了……

这个论原理也很简单,就是把对 cgi 的请求全部转化到一个专门的设置环境变量的 bash 脚本里,然后让它调用相应的 cgi。对了,这个 setenv.sh 必须有执行权限才行……

这一切终于结束了,然后这个生成器也可以用了~

我主页的 QR code

最后是一个小小的东西:

QR code 生成器




参考资料

Leave a Reply