]> git.cworth.org Git - apitrace/blob - scripts/snapdiff.py
Specify fuzz as a ratio instead of percentage.
[apitrace] / scripts / snapdiff.py
1 #!/usr/bin/env python
2 ##########################################################################
3 #
4 # Copyright 2011 Jose Fonseca
5 # Copyright 2008-2009 VMware, Inc.
6 # All Rights Reserved.
7 #
8 # Permission is hereby granted, free of charge, to any person obtaining a copy
9 # of this software and associated documentation files (the "Software"), to deal
10 # in the Software without restriction, including without limitation the rights
11 # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 # copies of the Software, and to permit persons to whom the Software is
13 # furnished to do so, subject to the following conditions:
14 #
15 # The above copyright notice and this permission notice shall be included in
16 # all copies or substantial portions of the Software.
17 #
18 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24 # THE SOFTWARE.
25 #
26 ##########################################################################/
27
28
29 '''Snapshot (image) comparison script.
30 '''
31
32
33 import sys
34 import os.path
35 import optparse
36 import math
37 import operator
38
39 import Image
40 import ImageChops
41 import ImageEnhance
42
43
44 thumb_size = 320, 320
45
46
47 def _compare(ref_image, src_image, delta_image):
48     import subprocess
49     p = subprocess.Popen([
50         'compare', 
51         '-metric', 'AE', 
52         '-fuzz', '%u%%' % (100*options.fuzz,),
53         '-dissimilarity-threshold', '1',
54         ref_image, src_image, delta_image
55     ], stderr=subprocess.PIPE)
56     _, stderr = p.communicate()
57     try:
58         return int(stderr)
59     except ValueError:
60         return 0xffffffff
61
62
63 def compare(ref_image, src_image, delta_image):
64     ref_im = Image.open(ref_image)
65     src_im = Image.open(src_image)
66
67     ref_im = ref_im.convert('RGB')
68     src_im = src_im.convert('RGB')
69
70     diff = ImageChops.difference(src_im, ref_im)
71
72     mask = ImageEnhance.Brightness(diff).enhance(1.0/options.fuzz)
73     mask = mask.convert('L')
74
75     lowlight = Image.new('RGB', src_im.size, (0xff, 0xff, 0xff))
76     highlight = Image.new('RGB', src_im.size, (0xf1, 0x00, 0x1e))
77     delta_im = Image.composite(highlight, lowlight, mask)
78
79     delta_im = Image.blend(src_im, delta_im, 0xcc/255.0)
80     delta_im.save(delta_image)
81
82     # See also http://effbot.org/zone/pil-comparing-images.htm
83     # TODO: this is approximate due to the grayscale conversion
84     h = diff.convert('L').histogram()
85     ae = sum(h[int(255 * options.fuzz) + 1 : 256])
86     return ae
87
88
89 def surface(html, image):
90     if True:
91         name, ext = os.path.splitext(image)
92         thumb = name + '.thumb' + ext
93         if os.path.exists(image) \
94            and (not os.path.exists(thumb) \
95                 or os.path.getmtime(thumb) < os.path.getmtime(image)):
96             im = Image.open(image)
97             im.thumbnail(thumb_size)
98             im.save(thumb)
99     else:
100         thumb = image
101     html.write('        <td><a href="%s"><img src="%s"/></a></td>\n' % (image, thumb))
102
103
104 def is_image(path):
105     return \
106         path.endswith('.png') \
107         and not path.endswith('.diff.png') \
108         and not path.endswith('.thumb.png')
109
110
111 def find_images(prefix):
112     prefix = os.path.abspath(prefix)
113     if os.path.isdir(prefix):
114         prefix_dir = prefix
115     else:
116         prefix_dir = os.path.dirname(prefix)
117
118     images = []
119     for dirname, dirnames, filenames in os.walk(prefix_dir, followlinks=True):
120         for filename in filenames:
121             filepath = os.path.join(dirname, filename)
122             if filepath.startswith(prefix) and is_image(filepath):
123                 images.append(filepath[len(prefix):])
124
125     return images
126
127
128 def main():
129     global options
130
131     optparser = optparse.OptionParser(
132         usage="\n\t%prog [options] <ref_prefix> <src_prefix>",
133         version="%%prog")
134     optparser.add_option(
135         '-o', '--output', metavar='FILE',
136         type="string", dest="output", default='index.html',
137         help="output filename [default: %default]")
138     optparser.add_option(
139         '-f', '--fuzz',
140         type="float", dest="fuzz", default=.05,
141         help="fuzz ratio [default: %default]")
142     optparser.add_option(
143         '--overwrite',
144         action="store_true", dest="overwrite", default=False,
145         help="overwrite")
146
147     (options, args) = optparser.parse_args(sys.argv[1:])
148
149     if len(args) != 2:
150         optparser.error('incorrect number of arguments')
151
152     ref_prefix = args[0]
153     src_prefix = args[1]
154
155     ref_images = find_images(ref_prefix)
156     src_images = find_images(src_prefix)
157     images = list(set(ref_images).intersection(set(src_images)))
158     images.sort()
159
160     if options.output:
161         html = open(options.output, 'wt')
162     else:
163         html = sys.stdout
164     html.write('<html>\n')
165     html.write('  <body>\n')
166     html.write('    <table border="1">\n')
167     html.write('      <tr><th>%s</th><th>%s</th><th>&Delta;</th></tr>\n' % (ref_prefix, src_prefix))
168     for image in images:
169         ref_image = ref_prefix + image
170         src_image = src_prefix + image
171         root, ext = os.path.splitext(src_image)
172         delta_image = "%s.diff.png" % (root, )
173         if os.path.exists(ref_image) and os.path.exists(src_image):
174             if options.overwrite \
175                or not os.path.exists(delta_image) \
176                or (os.path.getmtime(delta_image) < os.path.getmtime(ref_image) \
177                    and os.path.getmtime(delta_image) < os.path.getmtime(src_image)):
178                 compare(ref_image, src_image, delta_image)
179
180             html.write('      <tr>\n')
181             surface(html, ref_image)
182             surface(html, src_image)
183             surface(html, delta_image)
184             html.write('      </tr>\n')
185             html.flush()
186     html.write('    </table>\n')
187     html.write('  </body>\n')
188     html.write('</html>\n')
189
190
191 if __name__ == '__main__':
192     main()