
本文详解如何在 PyScript 环境中安全读取用户上传的图像文件,利用 Pillow 进行预处理,并调用 linedraw 风格算法生成可直接渲染的 SVG 线稿,最终动态插入页面展示。
本文详解如何在 pyscript 环境中安全读取用户上传的图像文件,利用 pillow 进行预处理,并调用 `linedraw` 风格算法生成可直接渲染的 svg 线稿,最终动态插入页面展示。
在浏览器端使用 PyScript 实现图像矢量化(如绘图仪线稿生成),关键在于绕过传统文件系统限制,通过 JavaScript 的 FileReader 或更现代的 arrayBuffer() 接口将二进制图像数据桥接到 Python 环境,并借助 Pillow 完成灰度转换、对比度增强等预处理,再通过自定义矢量化逻辑生成 SVG 字符串——而非写入真实磁盘路径(PyScript 的虚拟文件系统不支持持久化写入到浏览器本地文件系统)。
以下为可直接运行的完整实现要点:
✅ 正确的数据流设计
原始代码中尝试直接将 dataURL 传入 image_to_svg() 是错误的:PIL.Image.open() 无法解析 base64 编码的 dataURL 字符串(如 "data:image/png;base64,...")。正确做法是:
- 使用 file.arrayBuffer() 获取原始字节;
- 通过 Uint8Array.new() 转为 JS 数组;
- 封装为 bytearray → io.BytesIO → Image.open()。
from js import document, Uint8Array
from pyodide import create_proxy
import io
from PIL import Image, ImageOps
async def _upload_change_and_show(e):
file_list = e.target.files
if not file_list.length:
return
first_file = file_list.item(0)
# ✅ 安全读取二进制数据
array_buf = await first_file.arrayBuffer()
uint8_array = Uint8Array.new(array_buf)
img_bytes = io.BytesIO(bytearray(uint8_array))
# ✅ 加载为 PIL 图像并预处理
img = Image.open(img_bytes).convert("L")
img = ImageOps.autocontrast(img, cutoff=5, preserve_tone=True)✅ SVG 生成与内联渲染(无需保存物理文件)
PyScript 的 open("plotter.svg", "w") 实际写入的是内存中的虚拟文件系统(/home/pyodide/),无法被浏览器直接加载为 。正确方式是:
- 调用 makesvg(lines) 得到 SVG 字符串;
- 创建 Blob URL 并设置为
的 src,或更推荐:直接插入 (避免 MIME 类型问题)。
# ✅ 方案一:动态插入 SVG 元素(推荐,无跨域/CORS风险)
svg_str = makesvg(lines) # 返回纯 SVG 字符串
svg_el = document.createElement('div')
svg_el.innerHTML = svg_str
document.getElementById("output_upload_pillow").appendChild(svg_el)
# ✅ 方案二:若坚持用 <img alt="使用 PyScript 实现图像上传与矢量化(SVG 线稿生成)完整教程" >,需构造 Blob URL(注意 MIME 类型应为 'image/svg+xml')
blob = window.Blob.new([svg_str], {type: 'image/svg+xml'})
url = window.URL.createObjectURL(blob)
img_el = document.createElement('img')
img_el.src = url
document.getElementById("output_upload_pillow").appendChild(img_el)⚠️ 注意事项与常见陷阱
- 依赖项精简:cv2 在 PyScript 中极难编译成功(WebAssembly 限制),建议始终启用 no_cv=True 分支,改用 Pillow + NumPy 实现轮廓检测(如 getcontours() 应基于 ImageFilter.FIND_EDGES 或 Sobel 滤波模拟);
- 分辨率控制:resize_image(img, resolution) 必须保持宽高比缩放,避免畸变,推荐使用 img.thumbnail((resolution, resolution), Image.LANCZOS);
- 性能提示:复杂图像(>1024px)在主线程执行矢量化可能卡顿,可包裹 await asyncio.sleep(0) 让出控制权;
- 调试技巧:用 console.log() 输出中间结果(如 img.width, len(lines)),配合浏览器 DevTools 的 Console 面板快速定位流程断点。
✅ 最小可行 makesvg() 示例(兼容 PyScript)
def makesvg(lines):
# 避免 width/height 动态计算误差(防止 SVG 渲染空白)
max_x = max((p[0] for l in lines for p in l), default=100)
max_y = max((p[1] for l in lines for p in l), default=100)
viewBox = f"0 0 {int(max_x * 0.6)} {int(max_y * 0.6)}"
out = f'<svg xmlns="http://www.w3.org/2000/svg" viewBox="{viewBox}" stroke="black" stroke-width="1.2" fill="none">'
for line in lines:
points = " ".join(f"{int(p[0]*0.5)},{int(p[1]*0.5)}" for p in line)
out += f'<polyline points="{points}" />'
out += '</svg>'
return out通过以上结构化实现,你将获得一个零依赖、纯前端、响应迅速的图像线稿生成器——所有计算在用户浏览器中完成,无需后端服务,完全符合现代 Web 安全模型。


















