97 lines
3.7 KiB
Python
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)}")
|