From 6d318949a6dae45dbdac87b9853874e55b215179 Mon Sep 17 00:00:00 2001 From: Vincent Petithory Date: Sat, 22 Sep 2012 21:51:03 +0200 Subject: Add a tool to visualize map differences between revisions --- tools/map-diff.py | 188 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 188 insertions(+) create mode 100755 tools/map-diff.py (limited to 'tools') diff --git a/tools/map-diff.py b/tools/map-diff.py new file mode 100755 index 00000000..b872d47f --- /dev/null +++ b/tools/map-diff.py @@ -0,0 +1,188 @@ +#!/usr/bin/env python +#-*- coding:utf-8 -*- + +import sys +import os +import subprocess +import re +import tempfile + +class MapDiff(object): + + @staticmethod + def check_programs(): + """ + Checks the require programs are available + """ + def which(program): + import os + def is_exe(fpath): + return os.path.isfile(fpath) and os.access(fpath, os.X_OK) + fpath, fname = os.path.split(program) + if fpath: + if is_exe(program): + return program + else: + for path in os.environ["PATH"].split(os.pathsep): + exe_file = os.path.join(path, program) + if is_exe(exe_file): + return exe_file + return None + + platform_programs = MapDiff.PROGRAMS.get(sys.platform, MapDiff.PROGRAMS.get('default')) + for program in platform_programs.values(): + if not which(program): + raise Exception('The required "%s" program is missing from your PATH.' % program) + + MAP_RE = re.compile(r'^\d{3}-\d{1}(\.tmx)?$') + PROGRAMS = { + 'default': { + 'tmxrasterizer': 'tmxrasterizer', + 'im_convert': 'convert', + 'im_display': 'display', + 'git': 'git', + }, + 'win32': { + 'tmxrasterizer': 'tmxrasterizer.exe', + 'im_convert': 'convert.exe', + 'im_display': 'display.exe', + 'git': 'git.exe', + }, + } + + def __init__(self): + self.platform_programs = MapDiff.PROGRAMS.get(sys.platform, MapDiff.PROGRAMS.get('default')) + + def _diffmaps(self, tmx1, tmx2, tmxdiffpath): + tmxraster1 = self._rastermap(tmx1) + tmxraster2 = self._rastermap(tmx2) + tmxf, tmxdiff = tempfile.mkstemp(suffix='.png') + subprocess.check_call([ + self.platform_programs.get('im_convert'), tmxraster1, tmxraster2, + '-compose', 'Difference', + '-auto-level', + '-composite', + tmxraster2, + '-compose', 'Screen', + '-composite', + tmxdiffpath + ]) + os.unlink(tmxdiff) + os.unlink(tmxraster1) + os.unlink(tmxraster2) + sys.stdout.write((u'Map diff written to %s\n' % tmxdiffpath).encode('utf-8')) + subprocess.check_call([self.platform_programs.get('im_display'), tmxdiffpath]) + + def _rastermap(self, tmx): + tmxf, tmxraster = tempfile.mkstemp(suffix='.png') + subprocess.check_call([self.platform_programs.get('tmxrasterizer'), tmx, tmxraster]) + if os.stat(tmxraster).st_size == 0: + # the image couldnt be rendered. The most probable reason is + # that the map was too big (e.g 024-4, 500x500 tiles) + raise Exception('Map too large to be rendered.') + return tmxraster + + +class MapGitRevDiff(MapDiff): + + def __init__(self, map_name): + super(MapGitRevDiff, self).__init__() + self.map_name = map_name + + def diff(self): + if not MapDiff.MAP_RE.match(self.map_name): + sys.stderr.write(u'Invalid map name: %s.\n' % self.map_name) + return 1 + if not self.map_name.endswith(u'.tmx'): + self.map_name = self.map_name+u'.tmx' + self.tmx_path = os.path.join(u'..', u'maps', self.map_name) + self.map_number = os.path.splitext(os.path.basename(self.map_name))[0] + p = subprocess.Popen([self.platform_programs.get('git'), '--no-pager', 'log', '-n', '2', '--oneline', '--follow', self.tmx_path], stdout=subprocess.PIPE) + log = p.communicate()[0].splitlines() + if not len(log) == 2: + raise Exception('This map has only one version') + c1 = log[0].split(' ')[0] + c2 = log[1].split(' ')[0] + + # We have the 2 revs to compare. Let's extract the related tmx file + p1 = self._mktmx_from_rev(c1) + p2 = self._mktmx_from_rev(c2) + difftmxpath = '%s_%s-%s.png' % (self.map_number, c1, c2) + self._diffmaps(p1, p2, difftmxpath) + os.unlink(p1) + os.unlink(p2) + + def _mktmx_from_rev(self, rev): + p = subprocess.Popen([self.platform_programs.get('git'), '--no-pager', 'show', '%s:%s' % (rev, self.tmx_path)], stdout=subprocess.PIPE) + contents = p.communicate()[0] + revtmx = '%s-%s.tmx' % (self.map_number, rev) + f = open(revtmx, 'w') + f.write(contents) + f.close() + return revtmx + + +class MapFileDiff(MapDiff): + + def __init__(self, map1, map2): + super(MapFileDiff, self).__init__() + self.map1 = map1 + self.map2 = map2 + + def diff(self): + b1 = os.path.splitext(os.path.basename(self.map1))[0] + b2 = os.path.splitext(os.path.basename(self.map2))[0] + difftmxpath = '%s__%s.png' % (b1, b2) + self._diffmaps(self.map1, self.map2, difftmxpath) + + +def usage(): + sys.stderr.write(u'''Usage: %s MAP_NAME + %s CHANGED_TMX REFERENCE_TMX + + Example: + $ ./map-diff.py 007-1 + will highlight the changes between the current 007-1 map and its previous version + + $ ./map-diff.py changes-made-by-someone-007-1.tmx ../maps-007-1.tmx + will highlight the changes between the two tmx maps. + Note that these 2 tmx to compare have to satisfy their dependancies, e.g tilesets. + Hence they should be in a sibling directory of the client-data/maps folder. + \n''' % (sys.argv[0], sys.argv[0])) + +def main(): + if not len(sys.argv) > 1: + usage() + return 127 + if not os.path.basename(os.path.dirname(os.getcwdu())) == u'client-data': + sys.stderr.write(u'This script must be run from client-data/tools.\n') + return 1 + try: + MapDiff.check_programs() + except Exception as e: + sys.stderr.write(u'%s\n' % e) + return 126 + if len(sys.argv) == 2: + map_name = sys.argv[1] + mapdiff = MapGitRevDiff(map_name) + try: + mapdiff.diff() + except Exception as e: + sys.stderr.write(u'\x1b[31m\x1b[1mError while generating the diff for map %s: %s\x1b[0m\n' % (map_name, e)) + return 1 + else: + return 0 + else: + map1 = sys.argv[1] + map2 = sys.argv[2] + mapdiff = MapFileDiff(map1, map2) + try: + mapdiff.diff() + except Exception as e: + sys.stderr.write(u'\x1b[31m\x1b[1mError while generating the diff for %s and %s: %s\x1b[0m\n' % (map1, map2, e)) + return 1 + else: + return 0 + +if __name__ == '__main__': + sys.exit(main()) -- cgit v1.2.3-60-g2f50