You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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>

问题原因排查

  1. 语法错误:代码里的*font = ImageFont.truetype(...)多了一个*,这会导致变量赋值错误,直接把*去掉即可。
  2. 字体文件访问问题: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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.12 08:10:46