Spaces:
Running
Running
File size: 5,259 Bytes
880cb16 f2690e7 880cb16 f2690e7 880cb16 f2690e7 880cb16 f2690e7 f97f2ac f2690e7 f97f2ac f2690e7 880cb16 f2690e7 880cb16 f2690e7 880cb16 f2690e7 880cb16 f2690e7 f97f2ac f2690e7 f97f2ac f2690e7 880cb16 f97f2ac 880cb16 f2690e7 880cb16 f2690e7 f97f2ac f2690e7 880cb16 f2690e7 880cb16 f2690e7 880cb16 f2690e7 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 |
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
SVG → PNG 批量转换(rsvg-convert ▸ Inkscape ▸ CairoSVG 三层保险版)
作者: ChatGPT 示例
"""
import shutil, subprocess, tempfile, zipfile, gradio as gr
from pathlib import Path
from typing import List, Tuple
import cairosvg # 兜底
DEFAULT_DPI = 300 # 默认分辨率
# ───────────────────────── 工具检测 ─────────────────────────
def which(cmd: str) -> str | None:
"""返回可执行文件完整路径,找不到则 None"""
return shutil.which(cmd)
RSVG = which("rsvg-convert")
INKSCAPE = which("inkscape")
# ──────────────────────── 渲染核心函数 ───────────────────────
def svg_to_png(svg: Path, png: Path, dpi: int = DEFAULT_DPI):
"""
依次尝试 rsvg-convert → Inkscape → CairoSVG。
任一成功即返回;全部失败则抛 RuntimeError。
"""
errs = []
# 1) rsvg-convert(最快最稳,支持大部分特性)
if RSVG:
cmd = [RSVG, "-d", str(dpi), "-p", str(dpi), "-f", "png",
"-o", str(png), str(svg)]
try:
subprocess.run(cmd, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
if png.stat().st_size > 2048: # 简单判空
return
except subprocess.CalledProcessError as e:
errs.append(f"[rsvg-convert] {e.stderr.decode(errors='ignore')}")
# 2) Inkscape CLI(加 export-area-drawing 防止空白)
if INKSCAPE:
cmd_new = [INKSCAPE, str(svg),
"--export-type=png",
f"--export-filename={png}",
f"--export-dpi={dpi}",
"--export-area-drawing"] # 关键参数
try:
subprocess.run(cmd_new, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
if png.stat().st_size > 2048:
return
except subprocess.CalledProcessError as e_new:
# 极老版本 Inkscape (<1.0) 回退旧参数
cmd_old = [INKSCAPE, str(svg),
f"--export-png={png}",
f"-d={dpi}",
"--export-area-drawing"]
try:
subprocess.run(cmd_old, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
if png.stat().st_size > 2048:
return
except subprocess.CalledProcessError as e_old:
errs.append(f"[Inkscape] {e_new.stderr.decode(errors='ignore')}\n"
f"[Inkscape-old] {e_old.stderr.decode(errors='ignore')}")
# 3) CairoSVG 兜底
try:
cairosvg.svg2png(url=str(svg), write_to=str(png), dpi=dpi)
if png.stat().st_size > 2048:
return
except Exception as cs_err:
errs.append(f"[CairoSVG] {cs_err}")
raise RuntimeError("三种渲染器均失败:\n" + "\n".join(errs))
# ───────────────────────── Gradio 回调 ───────────────────────
def batch_convert(files: List[gr.File], dpi: int = DEFAULT_DPI
) -> Tuple[List[Tuple[str, str]], str]:
if not files:
raise gr.Error("请先上传至少一个 SVG 文件!")
tmp = Path(tempfile.mkdtemp(prefix="svg2png_"))
out_dir = tmp / "png"; out_dir.mkdir()
gallery, errs = [], []
for f in files:
svg = Path(f.name)
out = out_dir / f"{svg.stem}.png"
try:
svg_to_png(svg, out, dpi)
gallery.append((out.name, str(out)))
except Exception as e:
errs.append(f"{svg.name} 转换失败:{e}")
# 打包 zip
zip_path = tmp / "result.zip"
with zipfile.ZipFile(zip_path, "w", zipfile.ZIP_DEFLATED) as zf:
for p in out_dir.iterdir():
zf.write(p, p.name)
if errs:
# 收敛为单条 Warning,避免打断流程
gr.Warning("\n".join(errs))
return gallery, str(zip_path)
# ────────────────────────── Gradio UI ────────────────────────
with gr.Blocks(title="SVG → PNG 在线转换", theme="soft") as demo:
gr.Markdown("""
# 🖼️ SVG → PNG 转换器
- **rsvg-convert ▸ Inkscape ▸ CairoSVG** 三层保险
- 支持批量上传,DPI 可调,默认导出实际绘图区域
""")
with gr.Row():
uploader = gr.File(file_count="multiple", file_types=[".svg"],
label="上传 SVG 文件(可多选)")
dpi_slider = gr.Slider(72, 600, value=DEFAULT_DPI, step=1,
label="输出 DPI", info="分辨率越高越清晰")
btn = gr.Button("🚀 开始转换")
gallery = gr.Gallery(label="PNG 预览")
zip_out = gr.File(label="下载全部 PNG (ZIP)")
btn.click(batch_convert, [uploader, dpi_slider], [gallery, zip_out],
api_name="convert", queue=True)
if __name__ == "__main__":
demo.launch(share=False) # 本地调试可改 True
|