TTF与图片之间的相互转换,使用python,potrace,fontforge

概述

TTF是字体文件格式,里面存储的是矢量化的字体信息。TTF与图片之间的相互转换简单描述如下:

  • 使用python中的PIL(pillow)图像库可以实现TTF转图片
  • 使用potrace可以将图片转为矢量文件svg,再进一步使用fontforge可以将svg转为TTF文件

在windows中,我们可以拿系统字体做一下演示。系统字体的路径为:C:\Windows\Fonts,这里我们用方正舒体作为示例,字体文件名是FZSTK.TTF

字体的浏览和修改需要使用专业的字体软件,这里使用免费的fontforge,界面略显简单粗糙,但是后续图片转TTF时候还要用,加之免费,所以选用该软件。
fontforge下载地址:https://fontforge.org/en-US/

ps:fontforge默认情况打开字体后,无论字体有没有实现,都会占一个格子,所以可能会出现大量空格子,给我们浏览字体带来不便(托滑动条都难找),因此可做如下设置,以紧凑的方式显示字体:菜单栏依次点击Encoding -> Compact (hide unused glyphs)

在这里插入图片描述

Unicode简介

unicode官网:https://home.unicode.org/

字体会关联一套编码系统,比如提到汉字编码我们常见的是GBK,但在做字体相关工作时候,更常用的是Unicode编码系统,Unicode更加通用,它为很多语言做了唯一编码,甚至还有一些表情。Unicode使用4个16进制的数字来作为字符的编码(索引),如4E00表示汉字0061表示小写字母a

常用汉字的编码范围是:4E00(一) - 9FA5(龥)

截止这篇博客写作时间(2024.09),unicode最新版本是16.0.0,包含了154,998个字符(数量相当多)。可以在这里查询Unicode最新版本情况:https://www.unicode.org/versions/latest/

ps:4位16进制最大可表示65536,所以为什么Unicode能编码15万+字符?暂且不深究了,对于本文来讲不关键。

TTF转图片

这部分我们需要使用两个关键性依赖库,pillow(PIL)和fonttools,其中pillow是必需的,fonttools可选但建议也装上,因为下面代码会依赖。

  • fonttools主要用来获取字体的一些信息,主要指Unicode信息。
  • pillow用来转图片

安装依赖:

pip install pillow fonttools

有个跟字体相关度较高的简单概念最好能提前了解一下,即字体的 cmap - Character to Glyph Index Mapping Table,它里面存储的是Unicode信息(已转为十进制数字)和对应的字符名称,在python中使用fonttools读取后以字典形式存放。字符名称是个字符串,一般只有西文字符(英文、北欧、拉丁等)会有有含义的名称,其他字符常用字符串形式的16进制Unicode码来作为名称。字符名称了解一下即可,不关键。关键的是Unicode数字,如下图中前面的数字才是关键的,后面字符串不用太在意。

在这里插入图片描述
在处理字体相关信息时,代码中会使用很多的try…except语句,并且except不指定错误类型,因为总有字体编码时候不那么规范,导致python里面的两个依赖库处理不了,错误类型有点不确定,所以就全接了,以避免程序崩掉。

代码如下:
示例使用的是TTF文件,实际上OTF文件也能用下面代码。

# -*- coding: utf-8 -*-
import os
from fontTools.ttLib import TTFont
from PIL import Image
from PIL import ImageDraw
from PIL import ImageFontdef get_cmap(font_file):"""Get unicode cmap - Character to Glyph Index Mapping Tablefont_file: path of font file"""try:font = TTFont(font_file)except:return Nonetry:cmap = font.getBestCmap()except:return Nonefont.close()return cmapdef get_decimal_unicode(font_file):"""Get unicode (decimal mode - radix=10) of font."""cmap = get_cmap(font_file)if cmap is None:return Nonetry:decimal_unicode = list(cmap.keys())except:decimal_unicode = Nonereturn decimal_unicodedef decimal_to_hex(decimal_unicode, prefix='uni'):"""Convert decimal unicode (radix=10) to hex unicode (radix=16, str type)"""def _regularize(single_decimal_unicode, prefix):# result of hex() contains prefix '0x', such as '0x61',# while font file usually use 'uni0061',# so support changing prefix and filling to width 4 with 0h = hex(single_decimal_unicode)single_hex_unicode = prefix + h[2:].zfill(4)return single_hex_unicodeis_single_code = Falseif not isinstance(decimal_unicode, (list, tuple)):decimal_unicode = [decimal_unicode]is_single_code = Truehex_unicode = [_regularize(x, prefix) for x in decimal_unicode]if is_single_code:hex_unicode = hex_unicode[0]return hex_unicodedef decimal_to_char(decimal_unicode):"""Convert decimal unicode (radix=10) to characters"""is_single_code = Falseif not isinstance(decimal_unicode, (list, tuple)):decimal_unicode = [decimal_unicode]is_single_code = Truechar = [chr(x) for x in decimal_unicode]if is_single_code:char = char[0]return chardef get_bbox_offset(bbox, image_size):"""Get offset (x, y) for moving bbox to the center of imagebbox: bounding box of character, containing [xmin, ymin, xmax, ymax]"""if not isinstance(image_size, (list, tuple)):image_size = (image_size, image_size)center_x = image_size[0] // 2center_y = image_size[1] // 2xmin, ymin, xmax, ymax = bboxbbox_xmid = (xmin + xmax) // 2bbox_ymid = (ymin + ymax) // 2offset_x = center_x - bbox_xmidoffset_y = center_y - bbox_ymidreturn offset_x, offset_ydef char_to_image(char, font_pil, image_size, bg_color=255, fg_color=0):"""Generate an image containing single character in a font.char: such as '中' , 'a' ...font_pil: result of PIL.ImageFont"""try:bbox = font_pil.getbbox(char)except:return Noneif not isinstance(image_size, (list, tuple)):image_size = (image_size, image_size)offset_x, offset_y = get_bbox_offset(bbox, image_size)offset = (offset_x, offset_y)# convert ttf/otf to bitmap image using PILimage = Image.new('L', image_size, bg_color)draw = ImageDraw.Draw(image)draw.text(offset, char, font=font_pil, fill=fg_color)return imagedef font2image(font_file,font_size,image_size,out_folder=None,decimal_unicode=None,name_mode='char',image_extension='jpg',bg_color=255,fg_color=0,is_skip=True):"""Generate images from a font.font_size: size of font when reading by PIL, type=floatimage_size: image_size should normally be larger than font_sizedecimal_unicode: if not None, only generate images of decimal_unicodename_mode: if not 'char', then will be like 'uni0061'is_skip: whether skip existed images"""if out_folder is None:out_folder = os.path.splitext(font_file)[0]os.makedirs(out_folder, exist_ok=True)font_pil = ImageFont.truetype(font_file, font_size)if not isinstance(image_size, (list, tuple)):image_size = (image_size, image_size)if decimal_unicode is None:decimal_unicode = get_decimal_unicode(font_file)for code in decimal_unicode:char = chr(code)# get output filenameif name_mode == 'char':filename = charelse:filename = decimal_to_hex(code)filename = os.path.join(out_folder, f'{filename}.{image_extension}')# skip existed imagesif is_skip and os.path.exists(filename):continueimage = char_to_image(char, font_pil, image_size, bg_color, fg_color)if image is None:continuetry:image.save(filename)except:passif __name__ == '__main__':font_file = r'FZSTK.TTF'font2image(font_file, 112, 128)

生成的结果如图所示:
在这里插入图片描述

图片转TTF

需要安装fontforge和potrace,fontforge在概述中已经提到过了,这里再罗列一下。

  • fontforge下载地址:https://fontforge.org/en-US/
  • potrace下载地址:https://potrace.sourceforge.net/

fontforge需要安装,potrace实际上不需要安装,解压即可。

图片转TTF需要分两步:

  • 图片转SVG
  • SVG转TTF

图片转SVG

需用到potrace,解压potrace,把potrace.exe的路径配置到下面python代码中。
然后改一改下面代码中的路径参数,执行即可。
生成的SVG可以用浏览器打开查看,如chrome。

代码如下:

# -*- coding: utf-8 -*-
import os
import subprocess
from PIL import ImageIMAGE_EXTENSIONS = ['jpg', 'png']
POTRACE_PATH = r'.\potrace-1.16.win64\potrace.exe'def clamp(x, xmin, xmax):return min(max(x, xmin), xmax)def get_files(path, extensions):files = []for name in os.listdir(path):fullname = os.path.join(path, name)if os.path.isfile(fullname):ext = os.path.splitext(name)[-1].lower()[1:]if (ext == '') or (ext in extensions):files.append(fullname)return filesdef get_folders(path):children_paths = os.listdir(path)folders = [os.path.join(path, x) for x in children_paths]folders = [x for x in folders if os.path.isdir(x)]return foldersdef remove_file(filename):if os.path.exists(filename):os.remove(filename)def image2pgm(image_file, dst_size, mode='L'):"""Convert ordinary image to resized pgm and return path of pgm_filemode: image color model, can be 'L' (grayscale) or 'RGB'"""if not isinstance(dst_size, (tuple, list)):dst_size = (dst_size, dst_size)image = Image.open(image_file).convert(mode)image = image.resize(dst_size)pgm_file = os.path.splitext(image_file)[0] + '.pgm'image.save(pgm_file)return pgm_filedef image2svg(image_root_folder, out_folder, dst_size, alphamax=1):"""Convert image to svg using `potrace`dst_size: resize image before calling potracealphamax: potrace parameter to control the roundness of curves.larger value lead to round curve, while smaller value lead to straight curve."""# pre-process parametersif not isinstance(dst_size, (tuple, list)):dst_size = (dst_size, dst_size)alphamax = clamp(alphamax, 0., 1.)folders = get_folders(image_root_folder)if len(folders) == 0:folders = [image_root_folder]for i, folder in enumerate(folders):print("processing %d / %d, %s" % (i + 1, len(folders), folder))out_subfolder = folder.replace(image_root_folder, out_folder)os.makedirs(out_subfolder, exist_ok=True)image_files = get_files(folder, IMAGE_EXTENSIONS)for image_file in image_files:# convert ordinary image to pgmpgm_file = image2pgm(image_file, dst_size)svg_file = os.path.splitext(image_file)[0] + '.svg'svg_file = svg_file.replace(image_root_folder, out_folder)# convert pgm to svg using potracesubprocess.run([POTRACE_PATH, pgm_file,'--alphamax', str(alphamax),'--svg','-o', svg_file])  # ignore_security_alertremove_file(pgm_file)if __name__ == '__main__':image_root_folder = r'.\FZSTK'out_folder = r'.\FZSTK_SVG'dst_size = 512alphamax = 1image2svg(image_root_folder, out_folder, dst_size, alphamax)

生成的svg示例如下,svg边缘受图片质量的影响,相比原始字符多少有些毛糙。
图片质量包括分辨率较小,128*128,另外由jpeg压缩带来的画质损失。
在这里插入图片描述

SVG转TTF

# -*- coding: utf-8 -*-
"""
This script can NOT run directly, use the following command in cmd:
`fontforge.exe -script xxx.py param1 param2 ...`
"""
import os
import sys
import fontforgedef get_files(path, extensions):files = []for name in os.listdir(path):fullname = os.path.join(path, name)if os.path.isfile(fullname):ext = os.path.splitext(name)[-1].lower()[1:]if (ext == '') or (ext in extensions):files.append(fullname)return filesdef svg2ttf(folder, out_file):if not os.path.isdir(folder):raise ValueError("%s is NOT a directory!" % folder)svg_files = get_files(folder, ['svg'])infos = []for svg_file in svg_files:char = os.path.splitext(svg_file)[0][-1]decimal_unicode = ord(char)hex_unicode = "uni" + hex(decimal_unicode)[2:].zfill(4)infos.append([char, decimal_unicode, hex_unicode, svg_file])# sort infos by decimal_unicode in ascend orderinfos.sort(key=lambda x: (x[1]))ff_font = fontforge.font()ff_font.fontname = "fontname"ff_font.fullname = "fullname"ff_font.familyname = "familyname"ff_font.encoding = "Unicode"for info in infos:char, dec_code, hex_code, svg_file = infoglyph = ff_font.createChar(dec_code)glyph.width = 1000glyph.importOutlines(svg_file)# Make the glyph lay on the baseline.ymin = glyph.boundingBox()[1]glyph.transform([1, 0, 0, 1, 0, -ymin])ff_font.generate(out_file)ff_font.close()if __name__ == '__main__':# the sys.argv[0] is python scripts itself,# sys.argv[1] is first param, sys.argv[2] is second param and so onfolder = sys.argv[1]if len(sys.argv) == 3:out_file = sys.argv[2]else:out_file = folder + '.ttf'svg2ttf(folder, out_file)

给上述脚本保存个文件名为:svg_to_ttf.py,需要给该脚本设置两个参数,第一个参数是SVG的文件夹路径,第二个参数是待输出的TTF的路径。

上述脚本在windows中不能直接执行,因为常规的python环境下 import fontforge会报错,找不到module。需要让fontforge.exe在脚本模式下去调用python脚本,才能使import fontforge正常生效,所以需要在cmd中使用如下命令:

.\FontForgeBuilds\bin\fontforge.exe -script svg_to_ttf.py FZSTK_SVG FZSTK_new.ttf

生成出来的字体如下,跟上面截图相比最大的区别在于字体的位置,在svg转ttf脚本中,我们把字体位置做了向下对齐,而非上下居中,这一点可以自行修改。
另外细节也会有损失,字体矢量化过程中矢量信息会增多,所以最终生成的字体文件也变大了不少。

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/145589.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

一天认识一个硬件之连接线

我们在日常工作生活中经常会用到许多连接线,比如视频线,USB线,但是他们的区别在哪里,可能太不清楚,今天就来给大家分享一下。 HDMI线 特点:HDMI线是一种全数字化视频和声音发送接口,可以发送未…

phpword读取word docx文档文本及图片转html格式

最近在做一个PHP读取word文档功能,搜索一圈后决定选择用phpword第三方组件。 composer安装phpWord composer require phpoffice/phpword如果你的文件是doc格式,直接另存为一个docx就行了;如果你的doc文档较多,可以下一个批量转…

Lingo求解器基本语法

Lingo是一款用于线性规划和整数规划的数学建模和求解软件,被广泛应用于运筹学、生产优化、供应链管理等领域。今天与大家一起来熟悉一下它的基本语法 Lingo基本语法 1、定义目标函数为MIN,MAX. 2、以一个分号“;”结尾。除SETS,ENDSETS,D…

煤矸石检测数据集(yolo)

yolo煤矸石检测 数据集 pt模型 界面, ✓3091张图片和txt标签,标签类别两类:“coal”、“rock”。 ✓适用于煤矸石识别,深度学习,机器学习,yolov5 yolov6 yolov7 yolov8 yolov9 yolov10,Python 煤…

Nvidia的高级研究科学家Jim Fan预计在未来两到三年内,机器人技术将取得重大进展

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗?订阅我们的简报,深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同,从行业内部的深度分析和实用指南中受益。不要错过这个机会,成为AI领…

Wacom 和 Splashtop 携手共赴 IBC 2024 展会,宣布向欧洲市场隆重推出 Wacom Bridge

2024年9月10日 荷兰阿姆斯特丹&德国杜塞尔多夫 Wacom 是数位笔技术的全球领袖,Splashtop 是高性能远程访问解决方案领域的先驱,双方宣布已在欧洲隆重推出 Wacom Bridge,目前 Splashtop Enterprise 和 Splashtop Business Access Perform…

【算法】BFS 系列之 多源 BFS

【ps】本篇有 4 道 leetcode OJ。 目录 一、算法简介 二、相关例题 1)01 矩阵 .1- 题目解析 .2- 代码编写 2)飞地的数量 .1- 题目解析 .2- 代码编写 3)地图中的最高点 .1- 题目解析 .2- 代码编写 4)地图分析 .1- 题…

英集芯IP5912:集成开关充电功能的低功耗8位POWER MCU芯片

英集芯IP5912是一款功能丰富的、集成了降压充电管理功能的8位MCU芯片,它内置了一个5V输入的同步降压充电DC-DC,功率管也是内置的,同时提供最大1.5A的充电电流。封装方式采用SOP16,方案应用时只需要很少的外围器件,就可…

Visual Studio 2022 - QT 环境中文字符乱码问题

Visual Studio 2022 - QT 环境中文字符乱码问题 一、Visual Studio 2022 - Qt 环境 在 QT 中使用中文字符串常会出现乱码现象&#xff0c;如下&#xff1a;以下提供了几个解决方法&#xff0c;仅供参考 QString str "百香果真是一直可爱的小猫咪"; qDebug() <…

大数据-142 - ClickHouse 集群 副本和分片 Distributed 附带案例演示

点一下关注吧&#xff01;&#xff01;&#xff01;非常感谢&#xff01;&#xff01;持续更新&#xff01;&#xff01;&#xff01; 目前已经更新到了&#xff1a; Hadoop&#xff08;已更完&#xff09;HDFS&#xff08;已更完&#xff09;MapReduce&#xff08;已更完&am…

Tcping:一款实用的端口存活检测工具

简介 tcping 是一个基于TCP协议的网络诊断工具,通过发送 TCP SYN/ACK包来检测目标主机的端口状态。 官网:tcping.exe - ping over a tcp connection 优点: (1)监听服务器端口状态:tcping 可以检测指定端口的状态,默认是80端口,也可以指定其他端口。 (2)显示ping返…

ESXI主机证书报错

VCENTER提示主机证书状态异常 没有虚拟机运行 查看状态发现即将过期&#xff08;但其实已经过期了&#xff09; 点击更新即可&#xff0c;当有虚拟机运行时&#xff0c;不能直接更新证书。

探索自闭症寄宿学校的专属教育模式

在自闭症儿童教育的探索之路上&#xff0c;寄宿学校作为一种特殊的教育形式&#xff0c;正逐渐展现出其独特的优势与价值。这些学校不仅为自闭症儿童提供了一个安全、稳定的生活环境&#xff0c;更通过专属的教育模式&#xff0c;帮助他们逐步克服障碍&#xff0c;实现潜能的最…

掌握数据中心虚拟化:关键挑战与解决方案

数据中心虚拟化是使用云软件平台将物理数据中心转变为数字数据中心的过程&#xff0c;使企业能够远程访问信息和应用程序。它包括在数据中心内创建物理基础设施的多个虚拟版本&#xff0c;通过将服务器、存储和网络等资源划分为虚拟实体来实现资源的高效利用。 虚拟化环境中的关…

关于 NLP 深度学习的核心流程

基本流程实现的先后顺序&#xff08;每一步都包含很多技术点&#xff09;&#xff1a; 选定语言模型结构 关于语言模型&#xff1a; 作用 判断那一句话相对更合理&#xff0c;相对不合理的会得到较底的分值&#xff1a; 挑选成句概率分值最高的评价指标&#xff1a;PPL&…

微服务架构中的负载均衡与服务注册中心(Nacos)

1. 负载均衡&#xff1a;解决实际业务问题 1.1 业务场景思考 想象一个电子商务平台的微服务架构。我们有一个订单服务和多个用户服务实例。当订单服务需要调用用户服务时&#xff0c;它如何选择具体调用哪一台用户服务器&#xff1f;这就是负载均衡要解决的核心问题。 1.2 常…

主流卷积神经网络CNN总结

ResNet&#xff08;2015&#xff09;残差神经网络 残差结构 ResNet50具体卷积结构图 ResNeXt&#xff08;2016&#xff09;加入了分组卷积的思想&#xff0c;将原ResNet网络中的block替换成由group分组的block&#xff0c;两者得到的feature map一致&#xff0c;只是参数量更少…

《微软飞行模拟2024》储存空间需求仅不到前作的1/5

根据微软在最新一期 Xbox Wire 中的介绍&#xff0c;将于今年11 月登陆 Xbox Series X|S 主机的《微软飞行模拟2024》将比前作占用空间小很多。 微软解释了为何新作的文件大小仅为30GB&#xff0c;约为前作的五分之一。简单来说&#xff0c;微软使用了云流媒体传输技术。公司还…

SEC重拳打击币安

美国SEC于上周四提交针对币安的拟议修订投诉&#xff0c;重点关注交易所的代币上市流程。 1.美国证券交易委员会对币安提出拟议的修订申诉。 2.SEC在驳回币安最初诉讼的动议中基本获胜&#xff0c;但在驳回动议的命令中&#xff0c;关于某些代币的若干问题仍未得到解答。 3.SEC…

木马加载器通用套路

木马加载器通用套路 加载器 木马有两种类型&#xff0c;第一种是自己写的木马&#xff0c;作为远控客户端&#xff0c;第二种是加载器 加载器就是把C2的shellcode加载到目标机器的过程。目前攻防场景下&#xff0c;主流手段是通过加载器的方式实现远控 既然是加载器&#xff…