注: 从个人博客园移植而来
环境: windows7, python2.7
简介
偶然在网上看到热心网友使用python讲微信头像进行了组字,感觉很有意思,就做下研究。
感谢,原文参考: Python玩微信头像组字
需求的相关工具:
- python第三方库
image
pip install image # 图像处理
- HZK16字库的下载
百度网盘: https://pan.baidu.com/s/1XTBXSeKY3jAH1N7KxAYhRg
提取码:wjt0
- 本应该要安装python的第三方库
itchat
的
pip install itchat # 开源的微信个人接口
# 腾讯在2019年7月份关闭了网页版登录接口
# itchat的相关接口已经不能够正常使用了,原有使用的主要接口有:
# 自动登录微信网页,会生成一个二维码图片,手机扫码即可登入
itchat.auto_login()
# 获取微信好友信息列表,从而获取头像信息
friendList = itchat.get_friends(update=True)
实现的原理:
通过itchat
获取微信好友头像图片,将设定的文字按照HZK16
字库转换为矩阵信息,然后在每个矩阵点上放置2X2张图片,最后通过Image
生成出来。
虽然itchat
不可使用了,但是我们可以使用本地的图片进行模拟效果.
HZK16简介
它是符合GB2312国家标准的16×16点阵字库,每个汉字需要**256(16×16)**个点组成。
其GB2312-80支持的汉字有6763个,符号682个;其中一级汉字有 3755个,按声序排列,二级汉字有3008个,按偏旁部首排列。
通常情况下中文汉字,在UTF-8格式下占用字节为2个;在GBK,GB2312格式下占用字节3个。因此GB2312的HZK16下的中文汉字占用2个字节。其编码范围:0xA1A1~0xFEFE
,A1-A9
为符号区,B0-F7
为汉字区。
前面说到GB2312格式下汉字占2个字节,前一个字节为该汉字的区号,每个区中记录94个汉字;后一个字节为该字的位号。用于记录汉字在该区中的位置。
因此要找到一个汉字在HZK16
字库中的位置就必须得到它的区码和位码。
- 区码:
汉字的第一个字节 - 0xA0,因为汉字编码是从0xA0区开始的,所以文件最前面就是从0xA0区开始,要算出相对区码
- 位码:
汉字的第二个字节 - 0xA0
通过区码和位码我们就可以得到汉字在HZK16中的绝对偏移位置:
'''
* 区码或者位码减1,是由于数组从0开始,而区号位号是以1开始
* (94*(区号-1)+位号-1)是一个汉字字模占用的字节数
* 乘以32是因为一个汉字由32个字节存储(16*16/8)
'''
offset = (94*(区码-1)+(位码-1))*32
案例:
下载HZK16文件后,放置到指定的目录中,其目录结构为:
res中放置着一张75X75的png图片,可放置多个。代码为:
# -*- coding:UTF-8 -*-
#!/usr/bin/env python
import os
import math
import binascii
from PIL import Image
# 用于解决错误:UnicodeEncodeError: 'ascii' codec encode characters in position...
# 原因在于调用ascii编码处理字符流时,若字符流不属于ascii范围内就会报错
import sys
reload(sys)
sys.setdefaultencoding('utf-8')
# 每张头像裁剪后尺寸,建议图片不要太大,最好宽高一致
HEAD_CLIPSIZE = 75
# 每行列头像数目,即每点:2*2,可修改为3,即3*3
HEAD_NUM = 2
RECT_WIDTH = 16 # 矩阵点宽度 16
RECT_HEIGHT = 16 # 矩阵点高度 16
BYTE_COUNT_PER_FONT = 2*RECT_HEIGHT # 占用字节 32
# 将文字转换为点阵
def char2bit(textStr):
KEYS = [0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01]
target = []
global count
count = 0
# 遍历文字
for x in range(len(textStr)):
text = textStr[x]
# 初始化16*16点阵位置
rect_list = [] * RECT_WIDTH
for i in range(RECT_HEIGHT):
rect_list.append([] * RECT_WIDTH)
# 获取GB2312编码字符
gb2312 = text.encode('gb2312')
hex_str = binascii.b2a_hex(gb2312)
result = str(hex_str)
# 获取汉字第一个字节,区码
area = eval('0x' + result[:2]) - 0xA0
# 获取汉字第二个字节,位码
index = eval('0x' + result[2:]) - 0xA0
# 获取汉字在字库中的绝对偏移值
offset = (94 * (area-1) + (index-1)) * BYTE_COUNT_PER_FONT
font_rect = None
# 读取HZK16字库文件
with open("HZK16", "rb") as f:
# 获取目标汉字偏移位置
f.seek(offset)
# 从数据中读取32字节数据
font_rect = f.read(BYTE_COUNT_PER_FONT)
for k in range(len(font_rect)/2):
row_list = rect_list[k]
for j in range(2):
for i in range(8):
asc = binascii.b2a_hex(font_rect[k * 2 + j])
asc = asc = eval('0x' + asc)
flag = asc & KEYS[i]
row_list.append(flag)
output = []
_str = ''
for row in rect_list:
for i in row:
if i:
output.append('1')
_str += '0'
count+=1
else:
output.append('0')
_str += '.'
print(_str)
_str = ''
target.append(''.join(output))
return target
def head2char(index, outlist):
# 获取资源列表
imgList = []
workspace = os.getcwd()
respath = os.path.join(workspace, 'res')
for root, dirs, files in os.walk(respath):
for filename in files:
imgList.append(os.path.join(root, filename))
# 图片数目
imgCount = len(imgList)
#变量n用于循环遍历头像图片,即当所需图片大于头像总数时,循环使用头像图片
n = 0
for item in outlist:
# 创建新图片
canvasWidth = RECT_WIDTH * HEAD_NUM * HEAD_CLIPSIZE
canvasHeight = RECT_HEIGHT * HEAD_NUM * HEAD_CLIPSIZE
canvas = Image.new('RGB', (canvasWidth, canvasHeight), '#E0EEE0')
# 遍历 RECT_WIDTH * RECT_HEIGHT 矩阵
for i in range(RECT_WIDTH * RECT_HEIGHT):
#点阵信息为1,即代表此处要显示头像来组字
if item[i] != '1':
continue
# 每个点使用放置几个矩阵,比如2*2,3*3
for count in range(pow(HEAD_NUM, 2)):
# 获取图片索引
imgIndex = (n + count) % imgCount
# 读取图片
headImg = Image.open(imgList[imgIndex])
# 重置图片大小
headImg = headImg.resize((HEAD_CLIPSIZE, HEAD_CLIPSIZE), Image.ANTIALIAS)
# 拼接图片
posx = ((i % RECT_WIDTH) * HEAD_NUM + (count%HEAD_NUM)) * HEAD_CLIPSIZE
posy = ((i // RECT_HEIGHT) * HEAD_NUM + (count//HEAD_NUM)) * HEAD_CLIPSIZE
canvas.paste(headImg, (posx, posy))
#调整n以读取后续图片
n = (n+4) % imgCount
# 保存图片 quality代表图片质量,1-100
canvas.save('result_{0}.jpg'.format(index), quality=100)
# 将gbk转换为unicode格式
def transGbk2Unicode(str_v):
str_s = str_v.replace(r'%', r'\x')
res = eval(repr(str_s).replace('\\\\', '\\'))
return res.decode('gb2312')
if __name__=="__main__":
inputStr = u'请输入您想要生成的文字(ENTER结束):'
# 输入内容,将中文从unicode转换为gbk,防止乱码
content = raw_input(inputStr.encode('gbk'))
# 将gbk转换为unicode,以方便遍历时能够遍历每个文字或字母
content = transGbk2Unicode(content)
print(u'注意:指定文字每个仅能生成一个')
# 循环遍历
index = 0
for _str in content:
print(u'生成汉字:' + _str)
#将字转化为汉字库的点阵数据
outlist = char2bit(_str)
#将头像图片按点阵拼接成单字图片
head2char(index, outlist)
index += 1
print(u'生成成功!!!')
执行结果:
参考:
HZK16的原理
python实现点阵字体读取与转换