本文详解如何在 pyscript 环境中安全读取用户上传的图像文件,利用 pillow 预处理并调用 linedraw 风格算法提取轮廓与交叉线,最终生成可直接渲染的 svg 字符串并动态插入页面展示。
本文详解如何在 pyscript 环境中安全读取用户上传的图像文件,利用 pillow 预处理并调用 linedraw 风格算法提取轮廓与交叉线,最终生成可直接渲染的 svg 字符串并动态插入页面展示。
在 PyScript 中实现图像上传→矢量化→SVG 渲染的全流程,需特别注意浏览器沙箱限制:PyScript 运行于 WebAssembly 环境,无法直接访问本地文件系统,也不能通过 open("plotter.svg", "w") 写入真实磁盘文件(该操作仅作用于内存虚拟文件系统)。因此,关键在于——将生成的 SVG 内容作为字符串,而非文件路径,注入 DOM。
以下为完整、可运行的解决方案核心逻辑:
✅ 正确的数据流链路
- 用户选择图像文件()
- 使用 Uint8Array + arrayBuffer() 安全读取二进制数据
- 用 io.BytesIO 封装为字节流,交由 PIL.Image.open() 解析
- 执行灰度转换、自动对比度增强等预处理(提升线条提取质量)
- 调用自定义 getcontours() / hatch() 等 linedraw 函数生成坐标点列表 lines
- 用 makesvg(lines) 将点序列转为标准 SVG 字符串(注意:无需写磁盘)
- 创建
元素,设置其 src 为 data:image/svg+xml;base64,... 编码的 SVG 字符串(最可靠方式)
? 关键修复点(原代码问题)
- ❌ image_to_svg(imageFile, ...) 中 imageFile 是 data: URL 字符串(如 "data:image/png;base64,..."),但 PIL.Image.open() 无法直接解析该格式,必须先解码或转为字节流。
- ❌ f = open("plotter.svg", "w") 写入的是虚拟 FS,
会 404 —— 浏览器无法访问 PyScript 的虚拟文件系统。
- ✅ 正确做法:svg_str = makesvg(lines) → b64 = base64.b64encode(svg_str.encode()).decode() → img.src = f"data:image/svg+xml;base64,{b64}"
? 推荐实现(精简可运行版)
from js import document, Uint8Array, window
from pyodide import create_proxy
import io, base64
from PIL import Image, ImageOps
def makesvg(lines):
out = '<svg xmlns="http://www.w3.org/2000/svg" width="800" height="600" viewbox="0 0 800 600">'
for l in lines:
points = " ".join(f"{int(p[0])},{int(p[1])}" for p in l)
out += f'<polyline points="{points}" stroke="black" stroke-width="1.5" fill="none"></polyline>'
out += '</svg>'
return out
# 模拟 linedraw 核心逻辑(实际请集成 evildmp 版本)
def getcontours(img, level=2):
# 示例:简单边缘检测(生产环境替换为 cv2.Canny 或 linedraw.contour)
img = img.filter(ImageFilter.FIND_EDGES)
img = img.point(lambda x: 255 if x > 30 else 0, mode='1')
# 此处应返回 [[(x1,y1), (x2,y2), ...], [...]] 格式线条列表
return [[(100,100), (300,100), (300,300), (100,300)]] # 占位矩形
def on_file_change(e):
files = e.target.files
if not files.length: return
file = files.item(0)
array_buf = Uint8Array.new(await file.arrayBuffer())
img_bytes = io.BytesIO(bytes(array_buf))
try:
pil_img = Image.open(img_bytes).convert("L")
pil_img = ImageOps.autocontrast(pil_img, cutoff=5)
# 矢量化(此处调用你的 linedraw 函数)
lines = getcontours(pil_img.resize((512, 512))) # 统一分辨率
# 生成 SVG 字符串并 Base64 编码
svg_content = makesvg(lines)
b64_svg = base64.b64encode(svg_content.encode()).decode()
data_url = f"data:image/svg+xml;base64,{b64_svg}"
# 动态插入 SVG 预览
output_div = document.getElementById("output_upload_pillow")
output_div.innerHTML = f'<div style="margin-top:1rem;">
<h3>Generated Line Art:</h3>
<img src="%7Bdata_url%7D" style="max-width:90%">
</div>'
except Exception as err:
console.error("Processing failed:", err)
document.getElementById("output_upload_pillow").innerHTML = f"<p style="color:red">Error: {err}</p>"
# 绑定事件
upload_el = document.getElementById("file-upload-pillow")
upload_el.addEventListener("change", create_proxy(on_file_change))
⚠️ 注意事项
-
依赖声明:确保
中包含 Pillow(PyScript 0.25+ 已内置,无需额外安装 OpenCV);若需 cv2,需显式添加 - opencv-python(体积较大,建议优先用 Pillow 实现基础功能)。 -
SVG 渲染兼容性:
标签支持 data: URL 的 SVG,但部分旧浏览器可能受限;更稳妥的方式是使用 + innerHTML = svg_content 直接挂载 SVG DOM。
- 性能提示:复杂图像的矢量化(尤其 hatch)在纯 Python 中较慢,建议对上传图像预缩放(如 img.resize((512,512)))再处理。
- 调试技巧:用 console.log() 输出中间变量(如 pil_img.size, len(lines)),配合浏览器 DevTools 的 Console 快速定位异常。
通过以上结构化实现,你即可在零后端、纯前端的 PyScript 环境中,稳定完成图像→线条→SVG 的端到端转化,并即时可视化结果,完美适配绘图仪工作流。











