singe/util/traceText.py

59 lines
2.6 KiB
Python

# Reconstructs assets/SingeText.jpeg as assets/SingeText.svg: thresholds the logotype, traces it
# with potrace into Bezier outlines, and writes a tightly cropped SVG with a transparent background.
# Usage: python3 util/traceText.py assets/SingeText.jpeg assets/SingeText.svg [potrace] [check.svg]
# Needs Pillow and potrace (https://potrace.sourceforge.net). check.svg keeps the full page so it
# can be rendered over the original for comparison.
import os
import re
import subprocess
import sys
import tempfile
from PIL import Image, ImageFilter
src = sys.argv[1]
out = sys.argv[2]
potrace = sys.argv[3] if len(sys.argv) > 3 else 'potrace'
check = sys.argv[4] if len(sys.argv) > 4 else None
# Trace at 4x so the anti-aliased edges of the JPEG turn into smooth curves rather than steps.
scale = 4
BLUR = 3.5
grey = Image.open(src).convert('L')
w, h = grey.size
big = grey.resize((w * scale, h * scale), Image.LANCZOS)
# A light blur removes JPEG ringing along the edges, which would otherwise trace as tiny wobbles.
big = big.filter(ImageFilter.GaussianBlur(BLUR))
mono = big.point(lambda v: 255 if v >= 128 else 0).convert('1')
work = tempfile.mkdtemp()
pbm = os.path.join(work, 'text.pbm')
mono.save(pbm)
def trace(tight):
args = [potrace, pbm, '-s', '--flat', '-a', '1.0', '-O', '0.2', '-u', '1', '-t', str(4 * scale * scale), '-o', '-']
if tight:
args.append('--tight')
svg = subprocess.run(args, check=True, capture_output=True, text=True).stdout
width = float(re.search(r'width="([\d.]+)pt"', svg).group(1))
height = float(re.search(r'height="([\d.]+)pt"', svg).group(1))
transform = re.search(r'transform="([^"]+)"', svg).group(1)
path = re.search(r'<path[^>]*\bd="([^"]+)"', svg, re.S).group(1)
path = re.sub(r'\s+', ' ', path).strip()
return width, height, transform, path
def write(name, tight):
width, height, transform, path = trace(tight)
lines = ['<?xml version="1.0" encoding="UTF-8"?>',
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 %.2f %.2f" width="%.2f" height="%.2f">' % (width / scale, height / scale, width / scale, height / scale),
' <!-- Singe logotype, traced from SingeText.jpeg; transparent background. -->',
' <title>Singe</title>',
' <g transform="scale(%g) %s" fill="#000" stroke="none">' % (1.0 / scale, transform),
' <path d="%s"/>' % path,
' </g>',
'</svg>']
open(name, 'w').write('\n'.join(lines) + '\n')
print('%s: %.0fx%.0f, %d bytes' % (name, width / scale, height / scale, os.path.getsize(name)))
write(out, True)
if check:
write(check, False)