mirror of https://github.com/Chlumsky/msdfgen.git
225 lines
8.3 KiB
Python
225 lines
8.3 KiB
Python
import argparse
|
|
import os
|
|
import re
|
|
import sys
|
|
import codecs
|
|
import tempfile
|
|
from subprocess import Popen, PIPE, call
|
|
|
|
|
|
def println(msg):
|
|
sys.stdout.write(msg)
|
|
sys.stdout.write('\n')
|
|
sys.stdout.flush()
|
|
return msg
|
|
|
|
|
|
class Runner:
|
|
pass_count = 0
|
|
fail_count = 0
|
|
stop_on_fail = False
|
|
results = []
|
|
|
|
def __init__(self):
|
|
pass
|
|
|
|
def add_pass(self, input_path, msg, out_path=None, render_path=None, diff_path=None):
|
|
return self.add_result(True, input_path, msg, out_path, render_path, diff_path)
|
|
|
|
def add_fail(self, input_path, msg, out_path=None, render_path=None, diff_path=None):
|
|
return self.add_result(False, input_path, msg, out_path, render_path, diff_path)
|
|
|
|
def add_result(self, passed, input_path, msg, out_path=None, render_path=None, diff_path=None):
|
|
if passed:
|
|
self.pass_count += 1
|
|
println("PASS \"%s\" %s" % (input_path, msg))
|
|
else:
|
|
self.fail_count += 1
|
|
println("FAIL \"%s\" %s" % (input_path, msg))
|
|
if self.stop_on_fail:
|
|
sys.exit(1)
|
|
|
|
self.results.append({
|
|
'pass': passed,
|
|
'in': input_path,
|
|
'out': out_path,
|
|
'render': render_path,
|
|
'diff': diff_path})
|
|
|
|
return passed
|
|
|
|
def get_outputs(self, key):
|
|
ret = []
|
|
for e in self.results:
|
|
if key in e:
|
|
ret.append(e[key])
|
|
return ret
|
|
|
|
|
|
def check_imagemagick():
|
|
p = Popen(["compare -version"],
|
|
stdin=PIPE, stdout=PIPE, stderr=PIPE, shell=True)
|
|
output, err = p.communicate("")
|
|
if p.returncode != 0:
|
|
println("Cannot find ImageMagick <http://www.imagemagick.org> on your PATH. It is required to run tests.")
|
|
sys.exit(1)
|
|
|
|
|
|
def test_svg(path, args, runner):
|
|
(root, ext) = os.path.splitext(path)
|
|
|
|
mode = args.mode
|
|
sz = args.sdf_size
|
|
rsz = str(args.render_size)
|
|
|
|
sdf_path = "%s.%s.png" % (root, mode)
|
|
render_path = "%s.%s-render.png" % (root, mode)
|
|
diff_path = "%s.%s-diff.png" % (root, mode)
|
|
|
|
try:
|
|
result = call([args.exe, mode, '-svg', path, '-o', sdf_path,
|
|
# '-angle', '4',
|
|
# '-pxrange', '2',
|
|
# '-range', '8',
|
|
# '-tolerance', '0.01',
|
|
# '-legacy', str(args.legacy),
|
|
'-scale', str(sz / float(args.render_size)), '-size', str(sz), str(sz),
|
|
# '-exportshape', 'shape.txt',
|
|
'-testrender', render_path, rsz, rsz
|
|
], shell=False)
|
|
if result != 0:
|
|
return runner.add_fail(path, "Unable to render %s" % mode)
|
|
except OSError as er:
|
|
return runner.add_fail(path, "Error running %s: %s" % (args.exe, er))
|
|
|
|
# Use imagemagick to rasterize the reference image and compare it.
|
|
# We use a very fuzzy comparison to try and account for differences in aliasing introduced around edges of
|
|
# an image, which comes from both the down/up rezzing introduced by going through the SDF process, but also
|
|
# minor differences with IM's SVG rasterizer. What we're trying to catch is full on pixel mismatches that
|
|
# indicate a shape was mis-handled (curve shooting off to the edge, fill rule violation, etc).
|
|
|
|
total_pixels = args.render_size * args.render_size
|
|
|
|
p = Popen(["compare -fuzz %f%% -metric AE \"%s\" \"%s\" \"%s\"" % (args.fuzz, path, render_path, diff_path)],
|
|
stdin=PIPE, stdout=PIPE, stderr=PIPE, shell=True)
|
|
output, err = p.communicate("")
|
|
passed = False
|
|
if p.returncode == 1:
|
|
# Ran successfully, but they are different. Let's parse the output and check the actual error metric.
|
|
match = re.match(r'^(\d+)$', err)
|
|
passed = False
|
|
if match:
|
|
try:
|
|
pixels = int(match.group(1))
|
|
e = float(pixels) / total_pixels * 100.0
|
|
if e > args.fail_threshold:
|
|
msg = "Error = %.3f %% (%d)" % (e, pixels)
|
|
else:
|
|
passed = True
|
|
msg = "OK = %.3f %% (%d)" % (e, pixels)
|
|
except ValueError:
|
|
msg = "(Unknown) Error metric = %s" % err
|
|
else:
|
|
msg = "(Unknown) Error metric = %s" % err
|
|
elif p.returncode != 0:
|
|
msg = "Error comparing to %s [%d]: %s" % (render_path, p.returncode, err)
|
|
else:
|
|
passed = True
|
|
msg = "Identical"
|
|
|
|
return runner.add_result(passed, path, msg, sdf_path, render_path, diff_path)
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description="Test MSDFGEN outputs")
|
|
|
|
parser.add_argument("--svg-dir",
|
|
help="Directory to scan for SVG files.")
|
|
parser.add_argument("--svg",
|
|
help="SVG file to test",
|
|
action='append')
|
|
parser.add_argument("--mode",
|
|
help="Algorithm: [sdf, psdf, msdf] (default=msdf)",
|
|
default="msdf")
|
|
parser.add_argument("--sdf_size",
|
|
help="Size for rendered (M) image. Default = 128",
|
|
default=128,
|
|
type=int)
|
|
parser.add_argument("--render_size",
|
|
help="Size for the rendered test image. Default = 512",
|
|
default=512,
|
|
type=int)
|
|
parser.add_argument("--exe",
|
|
help="Path to MSDFGEN executable",
|
|
default="msdfgen")
|
|
parser.add_argument("--fail-threshold",
|
|
help="Percentage of pixels that are allowed to be different without flagging a failure. (Default: 0.06%)",
|
|
default=0.06, # Found experimentally (~150.0 / (512 * 512))
|
|
type=float)
|
|
parser.add_argument("--fuzz",
|
|
help="Fuzz percentage to use when comparing",
|
|
default=99.5,
|
|
type=float)
|
|
parser.add_argument("--legacy",
|
|
help="Use legacy <Version> mode algorithm",
|
|
default='0')
|
|
parser.add_argument("--montage",
|
|
help="Generate montage image of failed results (diffs)",
|
|
default=False,
|
|
action='store_true')
|
|
parser.add_argument("--stop-on-fail",
|
|
help="Stop testing after the first fail",
|
|
default=False,
|
|
action='store_true')
|
|
|
|
args = parser.parse_args()
|
|
|
|
if not args.svg_dir and not args.svg:
|
|
parser.print_help()
|
|
sys.exit(1)
|
|
|
|
check_imagemagick()
|
|
|
|
runner = Runner()
|
|
runner.stop_on_fail = args.stop_on_fail
|
|
|
|
if args.svg_dir:
|
|
for root, dirs, files in os.walk(args.svg_dir):
|
|
for name in files:
|
|
if not name.endswith(".svg"):
|
|
continue
|
|
path = os.path.join(root, name)
|
|
test_svg(path, args, runner)
|
|
if args.svg:
|
|
for file in args.svg:
|
|
if file.startswith('@'):
|
|
# Use ImageMagick style @filename.txt for loading filenames to test.
|
|
for line in codecs.open(file[1:], 'r', 'utf-8'):
|
|
test_svg(line.rstrip('\n'), args, runner)
|
|
else:
|
|
test_svg(file, args, runner)
|
|
|
|
if args.montage:
|
|
# We keep the diff montage at actual size (we want to see details)
|
|
with tempfile.NamedTemporaryFile(delete=False) as temp_file:
|
|
temp_file.write("\n".join(runner.get_outputs('diff')))
|
|
temp_file.close()
|
|
montage_name = "montage-%s-%s-diff.png" % (args.mode, args.legacy)
|
|
call(["montage -geometry +1+1 @%s %s" % (temp_file.name, montage_name)], shell=True)
|
|
os.unlink(temp_file.name)
|
|
# print("Wrote: %s" % montage_name)
|
|
|
|
# The others, we can let IM do some resizing because it's really just for quick glances.
|
|
# call(["montage %s montage-%s-%s-out.png" %
|
|
# (" ".join(runner.get_outputs('out')), args.mode, args.legacy)], shell=True)
|
|
# call(["montage %s montage-%s-%s-render.png" %
|
|
# (" ".join(runner.get_outputs('render')), args.mode, args.legacy)], shell=True)
|
|
|
|
if runner.fail_count > 0:
|
|
sys.exit(1)
|
|
else:
|
|
sys.exit(0)
|
|
|
|
if __name__ == '__main__':
|
|
main()
|