]> git.cworth.org Git - apitrace/blob - scripts/snapdiff.py
Import PIL modules from PIL package.
[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 from PIL import Image
40 from PIL import ImageChops
41 from PIL import ImageEnhance
42
43
44 thumb_size = 320, 320
45
46
47 class Comparer:
48     '''Image comparer.'''
49
50     def __init__(self, ref_image, src_image, alpha = False):
51         self.ref_im = Image.open(ref_image)
52         self.src_im = Image.open(src_image)
53
54         # Ignore
55         if not alpha:
56             self.ref_im = self.ref_im.convert('RGB')
57             self.src_im = self.src_im.convert('RGB')
58
59         self.diff = ImageChops.difference(self.src_im, self.ref_im)
60
61     def write_diff(self, diff_image, fuzz = 0.05):
62         # make a difference image similar to ImageMagick's compare utility
63         mask = ImageEnhance.Brightness(self.diff).enhance(1.0/fuzz)
64         mask = mask.convert('L')
65
66         lowlight = Image.new('RGB', self.src_im.size, (0xff, 0xff, 0xff))
67         highlight = Image.new('RGB', self.src_im.size, (0xf1, 0x00, 0x1e))
68         diff_im = Image.composite(highlight, lowlight, mask)
69
70         diff_im = Image.blend(self.src_im, diff_im, 0xcc/255.0)
71         diff_im.save(diff_image)
72
73     def precision(self):
74         # See also http://effbot.org/zone/pil-comparing-images.htm
75         h = self.diff.histogram()
76         square_error = 0
77         for i in range(1, 256):
78             square_error += sum(h[i : 3*256: 256])*i*i
79         rel_error = float(square_error*2 + 1) / float(self.diff.size[0]*self.diff.size[1]*3*255*255*2)
80         bits = -math.log(rel_error)/math.log(2.0)
81         return bits
82
83     def ae(self):
84         # Compute absolute error
85         # TODO: this is approximate due to the grayscale conversion
86         h = self.diff.convert('L').histogram()
87         ae = sum(h[int(255 * fuzz) + 1 : 256])
88         return ae
89
90
91 def surface(html, image):
92     if True:
93         name, ext = os.path.splitext(image)
94         thumb = name + '.thumb' + ext
95         if os.path.exists(image) \
96            and (not os.path.exists(thumb) \
97                 or os.path.getmtime(thumb) < os.path.getmtime(image)):
98             im = Image.open(image)
99             im.thumbnail(thumb_size)
100             im.save(thumb)
101     else:
102         thumb = image
103     html.write('        <td><a href="%s"><img src="%s"/></a></td>\n' % (image, thumb))
104
105
106 def is_image(path):
107     return \
108         path.endswith('.png') \
109         and not path.endswith('.diff.png') \
110         and not path.endswith('.thumb.png')
111
112
113 def find_images(prefix):
114     prefix = os.path.abspath(prefix)
115     if os.path.isdir(prefix):
116         prefix_dir = prefix
117     else:
118         prefix_dir = os.path.dirname(prefix)
119
120     images = []
121     for dirname, dirnames, filenames in os.walk(prefix_dir, followlinks=True):
122         for filename in filenames:
123             filepath = os.path.join(dirname, filename)
124             if filepath.startswith(prefix) and is_image(filepath):
125                 images.append(filepath[len(prefix):])
126
127     return images
128
129
130 def main():
131     global options
132
133     optparser = optparse.OptionParser(
134         usage="\n\t%prog [options] <ref_prefix> <src_prefix>",
135         version="%%prog")
136     optparser.add_option(
137         '-o', '--output', metavar='FILE',
138         type="string", dest="output", default='index.html',
139         help="output filename [default: %default]")
140     optparser.add_option(
141         '-f', '--fuzz',
142         type="float", dest="fuzz", default=0.05,
143         help="fuzz ratio [default: %default]")
144     optparser.add_option(
145         '--overwrite',
146         action="store_true", dest="overwrite", default=False,
147         help="overwrite")
148
149     (options, args) = optparser.parse_args(sys.argv[1:])
150
151     if len(args) != 2:
152         optparser.error('incorrect number of arguments')
153
154     ref_prefix = args[0]
155     src_prefix = args[1]
156
157     ref_images = find_images(ref_prefix)
158     src_images = find_images(src_prefix)
159     images = list(set(ref_images).intersection(set(src_images)))
160     images.sort()
161
162     if options.output:
163         html = open(options.output, 'wt')
164     else:
165         html = sys.stdout
166     html.write('<html>\n')
167     html.write('  <body>\n')
168     html.write('    <table border="1">\n')
169     html.write('      <tr><th>%s</th><th>%s</th><th>&Delta;</th></tr>\n' % (ref_prefix, src_prefix))
170     for image in images:
171         ref_image = ref_prefix + image
172         src_image = src_prefix + image
173         root, ext = os.path.splitext(src_image)
174         delta_image = "%s.diff.png" % (root, )
175         if os.path.exists(ref_image) and os.path.exists(src_image):
176             if options.overwrite \
177                or not os.path.exists(delta_image) \
178                or (os.path.getmtime(delta_image) < os.path.getmtime(ref_image) \
179                    and os.path.getmtime(delta_image) < os.path.getmtime(src_image)):
180
181                 comparer = Comparer(ref_image, src_image)
182                 comparer.write_diff(delta_image, fuzz=options.fuzz)
183
184             html.write('      <tr>\n')
185             surface(html, ref_image)
186             surface(html, src_image)
187             surface(html, delta_image)
188             html.write('      </tr>\n')
189             html.flush()
190     html.write('    </table>\n')
191     html.write('  </body>\n')
192     html.write('</html>\n')
193
194
195 if __name__ == '__main__':
196     main()