自制用于单片机的点阵字库 您所在的位置:网站首页 喷字的字模怎么制作 自制用于单片机的点阵字库

自制用于单片机的点阵字库

2024-06-21 03:12| 来源: 网络整理| 查看: 265

背景

最近在玩esp32。在使用了屏幕后就想着显示中文。我的屏幕使用tff_espi这个库,它本身也提供了自定义字体的功能。网络上存在一些教程,教人使用它库本身带了一个processing的工程,可以用来把字体转换为vlw格式,这也是processing这个ide默认使用的字体格式。有些教程之后再将vlw转换为h文件,也有人说库本身也可以直接使用vlw。不过随着库工具更新,目前这个工程是直接导出h文件的。

由于各种原因(主要是不太想再安装一个ide),我决定自己实现一个软字库。恰好看到这个项目和这个项目,给了我一点启发。首先完全可以将位图信息保存在文件中;且来估算一下,如果字符大小为16*16,一个字可以用32Bytes存储,就算是unicode中两万余个汉字,全部存下来也只需625KB;如果字符为20*20,则需要约1MB。对于esp32至少4M的flash而言,非常足够了。这样,在单片机中,只需要获取每一个字的unicode编码,然后从文件中查找字模显示就行了。而且unicode编码相对比较连续,这甚至只需要一个简单的映射。

创建字库

我们的字库会收录以下字符,对于中文和日文应该很友好。

0x00~0x7F / 0-127 ascii码 共128个0x2010~0x205E / 8208-8286 通用标点 共79个0x3000~0x30FF / 12288-12543 中日标点和日本假名 共256个0x4E00~0x9FA5 / 19968-40869 东亚通用汉字 共20902个0xFF00~0xFFEF / 65280-65519 中日全宽或半宽字符 共240个

为了方便,使用python制作字模库,制作16像素或20像素。总共21605个字符,16像素时字库675KB,20像素时1MB。

在python中,利用PIL库的能力加载字体,然后将文字按照指定大小画在画布上,最后处理画布,得到字模。这里仅仅为了展示,使用得意黑字体,感谢大佬的付出。PIL处理文字时,可能存在一些小问题,譬如文字并不是恰好在我们预想的大小范围,因此可能需要调整。像 #5 downoffset = -4,需要一个竖直方向的偏移。类似得意黑的比较细长的字体,文字高度可能超过预期,则需要在#8 处导入字体时微调fontsize的参数。

# python创建字库 from PIL import Image, ImageFont, ImageDraw import numpy as np fontsize = 20 # 字符的大小/像素 downoffset = -4 # 如果fontsize是16的话这里就是-5 im = Image.new('P',(fontsize,fontsize),(0,0,0)) font = ImageFont.truetype('./SmileySans.ttf', fontsize-3, encoding='unic') # 上面的fontsize需要根据情况微调 draw = ImageDraw.Draw(im) with open('./smiley'+str(fontsize)+'.font','wb') as f: # 写入字库,重复的劳动,想要添加字符的话复制粘贴for循环即可 for a in range(0x00,0x80): draw.rectangle([(0,0),(fontsize,fontsize)], fill = (0,0,0)) draw.text((0, downoffset), chr(a), fill=(255, 255, 255), font=font, stroke_width=0) temp = np.packbits(np.array(im).flatten()).tobytes() f.write(temp) for a in range(0x2010,0x205F): draw.rectangle([(0,0),(fontsize,fontsize)], fill = (0,0,0)) draw.text((0, downoffset), chr(a), fill=(255, 255, 255), font=font, stroke_width=0) temp = np.packbits(np.array(im).flatten()).tobytes() f.write(temp) for a in range(0x3000,0x3100): draw.rectangle([(0,0),(fontsize,fontsize)], fill = (0,0,0)) draw.text((0, downoffset), chr(a), fill=(255, 255, 255), font=font, stroke_width=0) temp = np.packbits(np.array(im).flatten()).tobytes() f.write(temp) for a in range(0x4E00,0x9FA6): draw.rectangle([(0,0),(fontsize,fontsize)], fill = (0,0,0)) draw.text((0, downoffset), chr(a), fill=(255, 255, 255), font=font, stroke_width=0) temp = np.packbits(np.array(im).flatten()).tobytes() f.write(temp) for a in range(0xFF00,0xFFF0): draw.rectangle([(0,0),(fontsize,fontsize)], fill = (0,0,0)) draw.text((0, downoffset), chr(a), fill=(255, 255, 255), font=font, stroke_width=0) temp = np.packbits(np.array(im).flatten()).tobytes() f.write(temp)

同时简单使用python在控制台输出看一下。

效果展示 20像素 ████ ████████████████████ ████ ████ ████ ████ ██████████████████████ ████████████████ ████ ██ ████████████████ ██ ██ ████████████████ ██ ██ ██ ████ ████ ████ ██ ██████ ██ ██ ████ ██ ██ ████████

如此,字库文件“xxx.font”就创建好了,之后上传到单片机的flash即可

单片机内使用字库 处理字符串为unicode码数组(实现为链表)unic* unichead_from_str(char* test_str)从文件中查找字模,得到字模数组(链表) glyph* get_glyph_from_unic(unic* strhead)顺次输出到屏幕 void tft_chn(tft* tft,int x,int y,int color,char *print_str)此函数内部调用unichead_from_str()和get_glyph_from_unic()

以下是使用arduino平台测试的代码,使用中仅需要调用tft_chn()函数即可。这里我只实现了单行文字输出。

// test.ino #include #include TFT_eSPI tft = TFT_eSPI(); String fontFilePath = "/smiley16.font"; const int fontsize = 16; // unicode码结构体 typedef struct unic_node { struct unic_node *next; int val; } unic; // 字模结构体 typedef struct glyph_node { struct glyph_node *next; uint8_t val[2 * fontsize + 1]; } glyph; // 用于通过unicode码计算偏移量 int unicode_to_offset(int unicode) { if (unicode < 0x80) { return unicode; } else if (unicode >= 0x2010 && unicode = 0x3000 && unicode = 0x4E00 && unicode = 0xFF00 && unicode val)*fontsize*fontsize/8, SeekSet); file.read(temp_glyph_node->val, fontsize * fontsize / 8); temp_glyph_node->next = fonthead; fonthead = temp_glyph_node; temphead = temphead->next; } file.close(); } glyph *gly_conv = glyph_convert(fonthead); return gly_conv; } //最主要的函数,用于在屏幕上绘制文字 // 注意,这个只能显示一行文字,且没有实现半角字符 void tft_chn(TFT_eSPI &tftoutput, int x, int y, int color, char *printstr) { unic *temp_unic = unichead_from_str(printstr); glyph *temp_glyph = get_glyph_from_unic(temp_unic); delstr(temp_unic); glyph *temp_gly_head = temp_glyph; int now_x = x; int now_y = y; while(temp_gly_head !=NULL ){ tft_chn_one(tftoutput,now_x,now_y,color,temp_gly_head); temp_gly_head = temp_gly_head->next; now_x += fontsize+2; } delgly(temp_glyph); return; } // 输出单字符 void tft_chn_one(TFT_eSPI &tftoutput, int x, int y, int color, glyph *printstr) { char strbindata; int now_row = 0;// add on y int now_col = 0;// add on x for (int i = 0; i < fontsize * fontsize / 8; i++) { char temp = printstr->val[i]; for (int j = 0; j < 8; j++) { if ((temp >> (7 - j)) & 0x1) { tftoutput.drawPixel(x+now_col,y+now_row,color); } now_col++; if (now_col >= fontsize) { now_row++; now_col = 0; } } } return; } // 从字符串转为unicode整数的链表 unic *unichead_from_str(char *test_str) { unic *strhead = NULL; int state = 0; //表达状态,0为解析完成没有更多 int byte_number = 0; int unicodeindex = 0; for (int i = 0; i < strlen(test_str); i++) { if (state == 0) { //新一轮解析 int daistr = (int)(test_str[i] & 0xff); //待解析字符 if (daistr < 0x80) { //单字节编码,马上进入下一轮 unic *newnode = (unic *)malloc(sizeof(unic)); newnode->val = (int)(test_str[i] & 0xff); newnode->next = strhead; strhead = newnode; } else if ((daistr & 0xE0) == 0xC0) { //二字节编码 state = 1; byte_number = 2; unicodeindex = daistr & 0x1F; } else if ((daistr & 0xF0) == 0xE0) { //三字节编码 state = 1; byte_number = 3; unicodeindex = daistr & 0x0F; } else if ((daistr & 0xF8) == 0xF0) { //四字节编码 state = 1; byte_number = 4; unicodeindex = daistr & 0x07; } else { //非法 continue; } } else { //已经解析第一个,解析剩下的 byte_number--; int daistr = (int)(test_str[i] & 0xff); //待解析字符 if ((daistr & 0xC0) != 0x80) { //非法 state = 0; continue; } unicodeindex = (unicodeindex next = strhead; strhead = newnode; } } } unic *conv_str = unic_convert(strhead); return conv_str; } // 反转链表,内部用 unic *unic_convert(unic *unichead) { unic *newhead = NULL; unic *temp = NULL; if (unichead == NULL || unichead->next == NULL) { return newhead; } while (unichead != NULL) { temp = unichead; unichead = unichead->next; temp->next = newhead; newhead = temp; } return newhead; } glyph *glyph_convert(glyph *unichead) { glyph *newhead = NULL; glyph *temp = NULL; if (unichead == NULL || unichead->next == NULL) { return newhead; } while (unichead != NULL) { temp = unichead; unichead = unichead->next; temp->next = newhead; newhead = temp; } return newhead; } // 释放链表 void delstr(unic *strhead) { unic *temp; while (strhead != NULL) { temp = strhead; strhead = temp->next; free(temp); } return; } void delgly(glyph *strhead) { glyph *temp; while (strhead != NULL) { temp = strhead; strhead = temp->next; free(temp); } return; } void setup() { //测试 Serial.begin(9600); LittleFS.begin(); tft.begin(); tft.setRotation(1); tft.fillScreen(TFT_BLACK); String ss = "你好,世界!"; char *hello = (char*)ss.c_str(); tft_chn(tft,80,112,TFT_YELLOW,hello); LittleFS.end(); } void loop() { }

该测试代码在240*240 ips 屏幕上显示示意图

 

240*240 ips 屏幕上显示测试代码

参考资料

GitHub - StarCompute/tftziku: 这是一个通过单片机在各种屏幕上显示中文的解决方案atelierAnchorGitHub - pengfexue2/printPlay: Python print 点阵字/ Python print to form Chinese characters


【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有