KiBot/kibot/PcbDraw/convert.py

97 lines
3.7 KiB
Python

# Author: Jan Mrázek
# License: MIT
import platform
import subprocess
import textwrap
import os
from typing import Union
from tempfile import TemporaryDirectory
# from PIL import Image
from lxml.etree import _ElementTree # type: ignore
# Converting SVG to bitmap is a hard problem. We used Wand (and thus
# imagemagick) to do the conversion. However, imagemagick is really hard to
# configure properly and it breaks often. Therefore, we provide a custom module
# that has several conversion strategies that reflect the platform. We also try
# to provide descriptive enough message so the user can detect what is wrong.
if platform.system() == "Windows":
from .convert_windows import detectInkscape
else:
from .convert_unix import detectInkscape, rsvgSvgToPng
def inkscapeSvgToPng(inputFilename: str, outputFilename: str, dpi: int) -> None:
"""
A strategy to convert an SVG file into a PNG file using Inkscape
"""
command = [detectInkscape(), "--export-type=png", f"--export-dpi={dpi}",
f"--export-filename={outputFilename}", inputFilename]
def reportError(message: str) -> None:
raise RuntimeError(f"Cannot convert {inputFilename} to {outputFilename}. Inkscape failed with:\n"
+ textwrap.indent(message, " "))
try:
r = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
output = r.stdout.decode("utf-8") + "\n" + r.stderr.decode("utf-8")
# Inkscape doesn't respect error codes
if "Can't open file" in output:
reportError(output)
except subprocess.CalledProcessError as e:
output = e.stdout.decode("utf-8") + "\n" + e.stderr.decode("utf-8")
reportError(output)
def svgToPng(inputFilename: str, outputFilename: str, dpi: int=300) -> None:
"""
Convert SVG file into a PNG file based on platform-dependent strategies
"""
if platform.system() == "Windows":
strategies = {
"Inkscape": inkscapeSvgToPng
}
else:
strategies = {
"RSVG": rsvgSvgToPng, # We prefer it over Inkscape as it is much faster
"Inkscape": inkscapeSvgToPng
}
errors = {}
for name, strategy in strategies.items():
try:
strategy(inputFilename, outputFilename, dpi)
return
except Exception as e:
errors[name] = str(e)
message = "Cannot convert PNG to SVG; all strategies failed:\n"
for name, error in errors.items():
m = f"- Strategy '{name}' failed with: {textwrap.indent(error, ' ')}\n"
message += textwrap.indent(m, " ")
raise RuntimeError(message)
def save(image: _ElementTree, filename: str, dpi: int=600, format: str=None) -> None:
"""
Given an SVG tree or an image, save to a filename. The format is deduced
from the extension.
"""
ftype = os.path.splitext(filename)[1][1:].lower() if format is None else format
# if isinstance(image, Image.Image):
# if ftype not in ["jpg", "jpeg", "png", "bmp"]:
# raise TypeError(f"Cannot save bitmap image into {ftype}")
# image.save(filename)
# return
if isinstance(image, _ElementTree):
if ftype == "svg":
image.write(filename)
return
with TemporaryDirectory() as d:
svg_filename = os.path.join(d, "image.svg")
if ftype == "png":
png_filename = filename
else:
png_filename = os.path.join(d, "image.png")
image.write(svg_filename)
svgToPng(svg_filename, png_filename, dpi=dpi)
if ftype == "png":
return
# Image.open(png_filename).convert("RGB").save(filename)
# return
raise TypeError(f"Unknown image type: {type(image)}")