PyScript中Pillow的ImageFont.truetype函数无法使用的问题排查
问题:Pyodide环境中Pillow的ImageFont.truetype无法正常工作
我要实现上传图片并添加文字的功能,但卡在了ImageFont.truetype函数上。已知Pillow 4.2.0+就支持这个函数,当前Pyodide环境的Pillow版本是9.1.1,理论上没问题,求排查原因和解决办法。
原代码如下:
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" /> <script defer src="https://pyscript.net/latest/pyscript.js"></script> <py-config type="json"> { "autoclose_loader": true, "runtimes": [{ "src": "https://cdn.jsdelivr.net/pyodide/v0.21.2/full/pyodide.js", "name": "pyodide-0.21.3", "lang": "python" }], "fetch": [{ "packages": ["Pillow"] }] } </py-config> <py-env> - Pillow </py-env> <label for="Upload a PNG image"></label><input type="file" id="file-upload-pillow"> <div id="output_upload_pillow"></div> <py-script> from js import document, console, Uint8Array, window, File from pyodide.ffi import create_proxy import io from PIL import Image, ImageFilter, ImageFont, ImageDraw async def _upload_change_and_show(e): file_list = e.target.files first_item = file_list.item(0) array_buf = Uint8Array.new(await first_item.arrayBuffer()) bytes_list = bytearray(array_buf) my_bytes = io.BytesIO(bytes_list) my_image = Image.open(my_bytes) console.log(f"{my_image.format= } {my_image.width= } {my_image.height= }") *font = ImageFont.truetype(font="text-font/Roboto-Black.ttf", size=20) draw = ImageDraw.Draw(my_image) draw.text((0, 0),"Sample Text",(255,255,255), font=font) my_stream = io.BytesIO() my_image.save(my_stream, format="PNG") image_file = File.new([Uint8Array.new(my_stream.getvalue())], "new_image_file.png", {type: "image/png"}) new_image = document.createElement('img') new_image.src = window.URL.createObjectURL(image_file) document.getElementById("output_upload_pillow").appendChild(new_image) upload_file = create_proxy(_upload_change_and_show) document.getElementById("file-upload-pillow").addEventListener("change", upload_file) </py-script>
问题原因排查
- 语法错误:代码里的
*font = ImageFont.truetype(...)多了一个*,这会导致变量赋值错误,直接把*去掉即可。 - 字体文件访问问题:Pyodide运行在浏览器沙箱里,无法直接读取本地相对路径的字体文件(比如
text-font/Roboto-Black.ttf),必须把字体文件加载到内存中才能使用。
解决办法
方案1:通过fetch加载字体文件到内存
修改代码,先通过fetch获取字体文件的二进制内容,再用ImageFont.truetype读取内存中的字体数据:
修正后的完整代码
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" /> <script defer src="https://pyscript.net/latest/pyscript.js"></script> <py-config type="json"> { "autoclose_loader": true, "runtimes": [{ "src": "https://cdn.jsdelivr.net/pyodide/v0.21.2/full/pyodide.js", "name": "pyodide-0.21.3", "lang": "python" }] } </py-config> <py-env> - Pillow </py-env> <label for="Upload a PNG image">上传PNG图片</label><input type="file" id="file-upload-pillow" accept="image/png"> <div id="output_upload_pillow"></div> <py-script> from js import document, console, Uint8Array, window, File, fetch from pyodide.ffi import create_proxy import io from PIL import Image, ImageFont, ImageDraw async def _upload_change_and_show(e): file_list = e.target.files first_item = file_list.item(0) # 读取上传的图片 array_buf = Uint8Array.new(await first_item.arrayBuffer()) bytes_list = bytearray(array_buf) my_bytes = io.BytesIO(bytes_list) my_image = Image.open(my_bytes) console.log(f"{my_image.format= } {my_image.width= } {my_image.height= }") # 加载字体文件(替换为你的字体实际路径) font_response = await fetch("Roboto-Black.ttf") font_buf = Uint8Array.new(await font_response.arrayBuffer()) font_bytes = io.BytesIO(bytearray(font_buf)) # 创建字体对象(去掉多余的*) font = ImageFont.truetype(font_bytes, size=20) draw = ImageDraw.Draw(my_image) draw.text((0, 0), "Sample Text", (255,255,255), font=font) # 保存修改后的图片并显示 my_stream = io.BytesIO() my_image.save(my_stream, format="PNG") image_file = File.new([Uint8Array.new(my_stream.getvalue())], "new_image_file.png", {"type": "image/png"}) new_image = document.createElement('img') new_image.src = window.URL.createObjectURL(image_file) document.getElementById("output_upload_pillow").appendChild(new_image) upload_file = create_proxy(_upload_change_and_show) document.getElementById("file-upload-pillow").addEventListener("change", upload_file) </py-script>
方案2:将字体转为Base64嵌入代码
如果不想依赖外部字体文件,可以把字体文件转为Base64编码,直接嵌入到代码中:
import base64 # 替换为你的字体Base64内容 font_base64 = "BASE64_ENCODED_FONT_CONTENT" font_bytes = io.BytesIO(base64.b64decode(font_base64)) font = ImageFont.truetype(font_bytes, size=20)
内容的提问来源于stack exchange,提问作者Dinosaur
相关产品推荐
相关产品推荐

