Skip to content

Commit

Permalink
Don't use temporary file for SVG data when rendering with Inkscape
Browse files Browse the repository at this point in the history
This was already suggested in voc#22. In addition to potentially being a
little more efficient, this also avoids the problem of files referenced
from the SVG not being found, since the SVG is now rendered while in the
artwork directory, so relative paths inside the SVG are still correct.

Please note that the pipe functionality of Inkscape requires a relatively
new version of Inkscape, i.e. the version from Debian Buster is not
sufficient (the Buster backport from Debian should be though).

Unfortunately, the same does not work when using ImageMagick, since it
seems like they use different delegates/libraries to render the SVG based
on how it is passed, i.e. when passed as file, it got rendered with
Inkscape on my machine, when passing it as blob, it seemed to be some
internal library or another delegate which did not seem to support the
same feature set as Inkscape, which resulted in inferior output. Therefore,
a temporary file is still used for ImageMagick. However, the issue of
included images that could be solved for Inkscape with these changes
still persists, since at least when using the Inkscape delegate,
ImageMagick seems to create a temporary symbolic link in /tmp, which
prevents that the Inkscape delegate finds included images.
  • Loading branch information
mstock committed Aug 27, 2022
1 parent 082a4f3 commit 7dd51d0
Show file tree
Hide file tree
Showing 2 changed files with 17 additions and 26 deletions.
34 changes: 16 additions & 18 deletions renderlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from lxml import etree
from urllib.request import urlopen
from wand.image import Image
from tempfile import NamedTemporaryFile

# Frames per second. Increasing this renders more frames, the avconf-statements would still need modifications
fps = 25
Expand Down Expand Up @@ -83,26 +84,29 @@ def ensureFilesRemoved(pattern):
for f in glob.glob(pattern):
os.unlink(f)

def renderFrame(infile, task, outfile):
def renderFrame(svg, task, outfile):
width = 1920
height = 1080
outfile = os.path.abspath(outfile)
if args.imagemagick:
# invoke imagemagick to convert the generated svg-file into a png inside the .frames-directory
with Image(filename=infile) as img:
with img.convert('png') as converted:
converted.save(filename=outfile)
with NamedTemporaryFile(dir=task.workdir, suffix='.svg') as svgfile:
svgfile.write(svg.svgstr.encode('utf-8'))
svgfile.flush()
with Image(filename=svgfile.name) as img:
with img.convert('png') as converted:
converted.save(filename=outfile)
elif args.resvg:
# invoke inkscape to convert the generated svg-file into a png inside the .frames-directory
cmd = 'resvg --background white --width={1} --height={2} "{4}" "{3}" 2>&1 >/dev/null'.format(task.workdir, width, height, outfile, infile)
errorReturn = subprocess.check_output(cmd, shell=True, universal_newlines=True, stderr=subprocess.STDOUT, cwd=task.workdir)
if errorReturn != '':
print("resvg exited with error\n" + errorReturn)
# sys.exit(42)

else:
# invoke inkscape to convert the generated svg-file into a png inside the .frames-directory
cmd = 'inkscape --export-background=white --export-background-opacity=0 --export-width={1} --export-height={2} --export-filename="{3}" "{4}" --pipe 2>&1 >/dev/null'.format(task.workdir, width, height, os.path.abspath(outfile), os.path.abspath(infile))
errorReturn = subprocess.check_output(cmd, shell=True, universal_newlines=True, stderr=subprocess.STDOUT, cwd=task.workdir)
cmd = 'inkscape --export-background=white --export-background-opacity=0 --export-width={1} --export-height={2} --export-filename="{3}" --pipe 2>&1 >/dev/null'.format(task.workdir, width, height, outfile)
errorReturn = subprocess.check_output(cmd, shell=True, universal_newlines=True, input=svg.svgstr, stderr=subprocess.STDOUT, cwd=task.workdir)
if errorReturn != '':
print("inkscape exited with error\n" + errorReturn)
# sys.exit(42)
Expand Down Expand Up @@ -135,26 +139,20 @@ def cachedRenderFrame(frame, frameNr, task, cache):
elif not skip_rendering:
cache[frame] = frameNr

svgfile = '{0}/.frames/{1:04d}.svg'.format(task.workdir, frameNr)

with SVGTemplate(task, svgfile) as svg:
outfile = '{0}/.frames/{1:04d}.png'.format(task.workdir, frameNr)
with SVGTemplate(task) as svg:
svg.replacetext()
svg.transform(frame)
svg.write()

outfile = '{0}/.frames/{1:04d}.png'.format(task.workdir, frameNr)
renderFrame(svgfile, task, outfile)
renderFrame(svg, task, outfile)

# increment frame-number
frameNr += 1


def rendertask_image(task):
svgfile = '{0}/image.svg'.format(task.workdir)
with SVGTemplate(task, svgfile) as svg:
with SVGTemplate(task) as svg:
svg.replacetext()
svg.write()
renderFrame(svgfile, task, task.outfile)
renderFrame(svg, task, task.outfile)

def rendertask_video(task):
# iterate through the animation sequence frame by frame
Expand Down
9 changes: 1 addition & 8 deletions svgtemplate.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,21 +12,14 @@
cssutils.log.setLevel(logging.FATAL)

class SVGTemplate:
def __init__(self, task, outfile):
def __init__(self, task):
self.task = task
self.outfile = outfile

def __enter__(self):
with builtins.open(os.path.join(self.task.workdir, self.task.infile), 'r') as fp:
self.svgstr = fp.read()
return self

def write(self):
# open the output-file (named ".gen.svg" in the workdir)
with builtins.open(self.outfile, 'w') as fp:
# write the generated svg-text into the output-file
fp.write(self.svgstr)

def replacetext(self):
for key in self.task.parameters.keys():
self.svgstr = self.svgstr.replace(key, xmlescape(str(self.task.parameters[key])))
Expand Down

0 comments on commit 7dd51d0

Please sign in to comment.