--- /dev/null
+Building from source
+====================
+
+
+Requirements common for all platforms:
+
+* Python (requires version 2.6)
+
+* CMake (tested with version 2.8)
+
+Requirements to build the GUI (optional):
+
+* Qt (tested with version 4.7)
+
+* QJSON (tested with version 0.7.1)
+
+
+Linux / Mac OS X
+----------------
+
+Build as:
+
+ cmake -H. -Bbuild
+ make -C build
+
+You can also build the 32bit GL wrapper on 64bit distro with a multilib gcc by
+doing:
+
+ cmake -H. -Bbuild32 -DCMAKE_C_FLAGS=-m32 -DCMAKE_CXX_FLAGS=-m32 -DCMAKE_EXE_LINKER_FLAGS=-m32
+ make -C build32 glxtrace
+
+
+Windows
+-------
+
+Additional requirements:
+
+* Microsoft Visual Studio (tested with 2008 version) or MinGW (tested with gcc version 4.4)
+
+* Microsoft DirectX SDK (tested with August 2007 release)
+
+To build with Visual Studio first invoke CMake GUI as:
+
+ cmake-gui -H. -B%cd%\build
+
+and press the _Configure_ button.
+
+It will try to detect most required/optional dependencies automatically. When
+not found automatically, you can manually specify the location of the
+dependencies from the GUI.
+
+If you are building with GUI support (i.e, with QT and QJSON), it should detect
+the official QT sdk automatically, but you will need to build QJSON yourself
+and also set the `QJSON_INCLUDE_DIR` and `QJSON_LIBRARIES` variables in the
+generated `CMakeCache.txt` when building apitrace and repeat the above
+sequence.
+
+After you've succesfully configured, you can start the build by opening the
+generated `build\apitrace.sln` solution file, or invoking `cmake` as:
+
+ cmake --build build --config MinSizeRel
+
+The steps to build 64bit version are similar, but choosing _Visual Studio 9
+2008 Win64_ instead of _Visual Studio 9 2008_.
+
+It's also possible to instruct `cmake` build Windows binaries on Linux with
+[MinGW cross compilers](http://www.cmake.org/Wiki/CmakeMingw).
+
+
* visualize trace files, and inspect state.
-Building from source
-====================
-
-
-Requirements common for all platforms:
-
-* Python (requires version 2.6)
-
-* CMake (tested with version 2.8)
-
-Requirements to build the GUI (optional):
-
-* Qt (tested with version 4.7)
-
-* QJSON (tested with version 0.7.1)
-
-
-Linux / Mac OS X
-----------------
-
-Build as:
-
- cmake -H. -Bbuild
- make -C build
-
-You can also build the 32bit GL wrapper on 64bit distro with a multilib gcc by
-doing:
-
- cmake -H. -Bbuild32 -DCMAKE_C_FLAGS=-m32 -DCMAKE_CXX_FLAGS=-m32 -DCMAKE_EXE_LINKER_FLAGS=-m32
- make -C build32 glxtrace
-
-
-Windows
--------
-
-Additional requirements:
-
-* Microsoft Visual Studio (tested with 2008 version) or MinGW (tested with gcc version 4.4)
-
-* Microsoft DirectX SDK (tested with August 2007 release)
-
-To build with Visual Studio first invoke CMake GUI as:
-
- cmake-gui -H. -B%cd%\build
-
-and press the _Configure_ button.
-
-It will try to detect most required/optional dependencies automatically. When
-not found automatically, you can manually specify the location of the
-dependencies from the GUI.
-
-If you are building with GUI support (i.e, with QT and QJSON), it should detect
-the official QT sdk automatically, but you will need to build QJSON yourself
-and also set the `QJSON_INCLUDE_DIR` and `QJSON_LIBRARIES` variables in the
-generated `CMakeCache.txt` when building apitrace and repeat the above
-sequence.
-
-After you've succesfully configured, you can start the build by opening the
-generated `build\apitrace.sln` solution file, or invoking `cmake` as:
-
- cmake --build build --config MinSizeRel
-
-The steps to build 64bit version are similar, but choosing _Visual Studio 9
-2008 Win64_ instead of _Visual Studio 9 2008_.
-
-It's also possible to instruct `cmake` build Windows binaries on Linux with
-[MinGW cross compilers](http://www.cmake.org/Wiki/CmakeMingw).
-
-
-Usage
-=====
+Basic usage
+===========
Linux
See the `ld.so` man page for more information about `LD_PRELOAD` and
`LD_LIBRARY_PATH` environment flags.
-You can make a video of the output by doing
-
- /path/to/glretrace -s - application.trace \
- | ffmpeg -r 30 -f image2pipe -vcodec ppm -i pipe: -vcodec mpeg4 -y output.mp4
-
Mac OS X
\path\to\glretrace application.trace
+Advanced command line usage
+===========================
+
+
+Dump GL state at a particular call
+----------------------------------
+
+You can get a dump of the bound GL state at call 12345 by doing:
+
+ /path/to/glretrace -D 12345 application.trace > 12345.json
+
+This is precisely the mechanism the GUI obtains its own state.
+
+You can compare two state dumps with the jsondiff.py script:
+
+ ./scripts/jsondiff.py 12345.json 67890.json
+
+
+Comparing two traces side by side
+---------------------------------
+
+ ./scripts/tracediff.sh trace1.trace trace2.trace
+
+This works only on Unices, and it will truncate the traces due to performance
+limitations.
+
+
+Recording a video with FFmpeg
+-----------------------------
+
+You can make a video of the output by doing
+
+ /path/to/glretrace -s - application.trace \
+ | ffmpeg -r 30 -f image2pipe -vcodec ppm -i pipe: -vcodec mpeg4 -y output.mp4
+
+
+Advanced usage for OpenGL implementors
+======================================
+
+There are several avanced usage examples meant for OpenGL implementors.
+
+
+Regression testing
+------------------
+
+These are the steps to create a regression testsuite around apitrace:
+
+* obtain a trace
+
+* obtain reference snapshots, by doing:
+
+ mkdir /path/to/snapshots/
+ /path/to/glretrace -s /path/to/reference/snapshots/ application.trace
+
+ on reference system.
+
+* prune the snapshots which are not interesting
+
+* to do a regression test, do:
+
+ /path/to/glretrace -c /path/to/reference/snapshots/ application.trace
+
+ Alternatively, for a HTML summary, use the snapdiff script:
+
+ /path/to/glretrace -s /path/to/current/snapshots/ application.trace
+ ./scripts/snapdiff.py --output summary.html /path/to/reference/snapshots/ /path/to/current/snapshots/
+
+
+Automated git-bisection
+-----------------------
+
+With tracecheck.py it is possible to automate git bisect and pinpoint the
+commit responsible for a regression.
+
+Below is an example of using tracecheck.py to bisect a regression in the
+Mesa-based Intel 965 driver. But the procedure could be applied to any GL
+driver hosted on a git repository.
+
+First, create a build script, named build-script.sh, containing:
+
+ #!/bin/sh
+ set -e
+ export PATH=/usr/lib/ccache:$PATH
+ export CFLAGS='-g'
+ export CXXFLAGS='-g'
+ ./autogen.sh --disable-egl --disable-gallium --disable-glut --disable-glu --disable-glw --with-dri-drivers=i965
+ make clean
+ make "$@"
+
+It is important that builds are both robust, and efficient. Due to broken
+dependency discovery in Mesa's makefile system, it was necessary invoke `make
+clean` in every iteration step. `ccache` should be installed to avoid
+recompiling unchanged source files.
+
+Then do:
+
+ cd /path/to/mesa
+ export LIBGL_DEBUG=verbose
+ export LD_LIBRARY_PATH=$PWD/lib
+ export LIBGL_DRIVERS_DIR=$PWD/lib
+ git bisect start \
+ 6491e9593d5cbc5644eb02593a2f562447efdcbb 71acbb54f49089b03d3498b6f88c1681d3f649ac \
+ -- src/mesa/drivers/dri/intel src/mesa/drivers/dri/i965/
+ git bisect run /path/to/tracecheck.py \
+ --precision-threshold 8.0 \
+ --build /path/to/build-script.sh \
+ --gl-renderer '.*Mesa.*Intel.*' \
+ --retrace=/path/to/glretrace \
+ -c /path/to/reference/snapshots/ \
+ topogun-1.06-orc-84k.trace
+
+The trace-check.py script will skip automatically when there are build
+failures.
+
+The `--gl-renderer` option will also cause a commit to be skipped if the
+`GL_RENDERER` is unexpected (e.g., when a software renderer or another GL
+driver is unintentianlly loaded due to missing symbol in the DRI driver, or
+another runtime fault).
+
+
+Side by side retracing
+----------------------
+
+In order to determine which draw call a regression first manifests one could
+generate snapshots for every draw call, using the -S option. That is, however,
+very inefficient for big traces with many draw calls.
+
+A faster approach is to run both the bad and a good GL driver side-by-side.
+The latter can be either a preivously known good build of the GL driver, or a
+reference software renderer.
+
+This can be achieved with retracediff.py script, which invokes glretrace with
+different environments, allowing to choose the desired GL driver by
+manipulating variables such as `LD_LIBRARY_PATH` or `LIBGL_DRIVERS_DIR`.
+
+For example:
+
+ ./scripts/retracediff.py \
+ --ref-env LD_LIBRARY_PATH=/path/to/reference/GL/implementation \
+ -r ./glretrace \
+ --diff-prefix=/path/to/output/diffs \
+ application.trace
+
+
+
Links
=====
* Plug memory leaks.
-* Allow to retrace with two libGL.so in parallel, and output differences in
- rendered frames / draw calls.
-
* D3D support.
* Visualize meshes in draw commands.
-Other:
+Other
+-----
* Side-by-side trace diffing; either as a separate tool on or the GUI.
-* Side-by-side state diffing.
-
* Ability to extract just a single frame from a trace, and all previous calls
that contributed to it:
if (snapshot_prefix) {
if (snapshot_prefix[0] == '-' && snapshot_prefix[1] == 0) {
- src->writePNM(std::cout);
+ char comment[21];
+ snprintf(comment, sizeof comment, "%u", call_no);
+ src->writePNM(std::cout, comment);
} else {
char filename[PATH_MAX];
snprintf(filename, sizeof filename, "%s%010u.png", snapshot_prefix, call_no);
bool writeBMP(const char *filename) const;
- void writePNM(std::ostream &os) const;
+ void writePNM(std::ostream &os, const char *comment = NULL) const;
- inline bool writePNM(const char *filename) const {
+ inline bool writePNM(const char *filename, const char *comment = NULL) const {
std::ofstream os(filename, std::ofstream::binary);
if (!os) {
return false;
}
- writePNM(os);
+ writePNM(os, comment);
return true;
}
#include <assert.h>
+#include <string.h>
#include <stdint.h>
#include "image.hpp"
/**
* http://en.wikipedia.org/wiki/Netpbm_format
+ * http://netpbm.sourceforge.net/doc/ppm.html
*/
void
-Image::writePNM(std::ostream &os) const {
+Image::writePNM(std::ostream &os, const char *comment) const {
assert(channels == 1 || channels >= 3);
os << (channels == 1 ? "P5" : "P6") << "\n";
+ if (comment) {
+ os << "#" << comment << "\n";
+ }
os << width << " " << height << "\n";
os << "255" << "\n";
os.write((const char *)row, width*channels);
}
} else {
- unsigned char pixel[3] = {0, 0, 0};
- for (row = start(); row != end(); row += stride()) {
- for (unsigned x = 0; x < width; ++x) {
- for (unsigned channel = 0; channel < channels; ++channel) {
- pixel[channel] = row[x*channels + channel];
+ unsigned char *tmp = new unsigned char[width*3];
+ if (channels == 4) {
+ for (row = start(); row != end(); row += stride()) {
+ const uint32_t *src = (const uint32_t *)row;
+ uint32_t *dst = (uint32_t *)tmp;
+ unsigned x;
+ for (x = 0; x + 4 <= width; x += 4) {
+ /*
+ * It's much faster to access dwords than bytes.
+ *
+ * FIXME: Big-endian version.
+ */
+
+ uint32_t rgba0 = *src++ & 0xffffff;
+ uint32_t rgba1 = *src++ & 0xffffff;
+ uint32_t rgba2 = *src++ & 0xffffff;
+ uint32_t rgba3 = *src++ & 0xffffff;
+ uint32_t rgb0 = rgba0
+ | (rgba1 << 24);
+ uint32_t rgb1 = (rgba1 >> 8)
+ | (rgba2 << 16);
+ uint32_t rgb2 = (rgba2 >> 16)
+ | (rgba3 << 8);
+ *dst++ = rgb0;
+ *dst++ = rgb1;
+ *dst++ = rgb2;
+ }
+ for (; x < width; ++x) {
+ tmp[x*3 + 0] = row[x*4 + 0];
+ tmp[x*3 + 1] = row[x*4 + 1];
+ tmp[x*3 + 2] = row[x*4 + 2];
+ }
+ os.write((const char *)tmp, width*3);
+ }
+ } else if (channels == 2) {
+ for (row = start(); row != end(); row += stride()) {
+ const unsigned char *src = row;
+ unsigned char *dst = tmp;
+ for (unsigned x = 0; x < width; ++x) {
+ *dst++ = *src++;
+ *dst++ = *src++;
+ *dst++ = 0;
}
- os.write((const char *)pixel, sizeof pixel);
+ os.write((const char *)tmp, width*3);
}
+ } else {
+ assert(0);
}
+ delete [] tmp;
}
}
+++ /dev/null
-This directory contains several utilitarian scripts for common development
-tasks using apitrace tools.
-
-See their code for more details about their usage.
--- /dev/null
+##########################################################################
+#
+# Copyright 2011 Jose Fonseca
+# Copyright 2008-2009 VMware, Inc.
+# All Rights Reserved.
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+#
+##########################################################################/
+
+
+import sys
+import platform
+
+
+class PlainHighlighter:
+ '''Plain formatter'''
+
+ black = None
+ red = None
+ green = None
+ yellow = None
+ blue = None
+ magenta = None
+ cyan = None
+ white = None
+
+ def __init__(self, stream):
+ self.stream = stream
+
+ def write(self, text):
+ self.stream.write(text)
+
+ def flush(self):
+ self.stream.flush()
+
+ def normal(self):
+ pass
+
+ def color(self, color):
+ pass
+
+ def bold(self):
+ pass
+
+ def italic(self):
+ pass
+
+
+class AnsiHighlighter(PlainHighlighter):
+ '''Highlighter for plain-text files which outputs ANSI escape codes. See
+ http://en.wikipedia.org/wiki/ANSI_escape_code for more information
+ concerning ANSI escape codes.
+ '''
+
+ _csi = '\33['
+
+ _normal = '0m'
+ _bold = '1m'
+ _italic = '3m'
+
+ black = 0
+ red = 1
+ green = 2
+ yellow = 3
+ blue = 4
+ magenta = 5
+ cyan = 6
+ white = 7
+
+ def __init__(self, stream):
+ PlainHighlighter.__init__(self, stream)
+ self.isatty = stream.isatty()
+
+ def _escape(self, code):
+ if self.isatty:
+ self.stream.write(self._csi + code)
+
+ def normal(self):
+ self._escape(self._normal)
+
+ def color(self, color):
+ self._escape(str(30 + color) + 'm')
+
+ def bold(self):
+ self._escape(self._bold)
+
+ def italic(self):
+ self._escape(self._italic)
+
+
+class WindowsConsoleHighlighter(PlainHighlighter):
+ '''Highlighter for the Windows Console. See
+ http://code.activestate.com/recipes/496901/ for more information.
+ '''
+
+ INVALID_HANDLE_VALUE = -1
+ STD_INPUT_HANDLE = -10
+ STD_OUTPUT_HANDLE = -11
+ STD_ERROR_HANDLE = -12
+
+ FOREGROUND_BLUE = 0x01
+ FOREGROUND_GREEN = 0x02
+ FOREGROUND_RED = 0x04
+ FOREGROUND_INTENSITY = 0x08
+ BACKGROUND_BLUE = 0x10
+ BACKGROUND_GREEN = 0x20
+ BACKGROUND_RED = 0x40
+ BACKGROUND_INTENSITY = 0x80
+
+ COMMON_LVB_LEADING_BYTE = 0x0100
+ COMMON_LVB_TRAILING_BYTE = 0x0200
+ COMMON_LVB_GRID_HORIZONTAL = 0x0400
+ COMMON_LVB_GRID_LVERTICAL = 0x0800
+ COMMON_LVB_GRID_RVERTICAL = 0x1000
+ COMMON_LVB_REVERSE_VIDEO = 0x4000
+ COMMON_LVB_UNDERSCORE = 0x8000
+
+ _normal = FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED
+ _bold = FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_INTENSITY
+ _italic = FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED
+
+ black = 0
+ red = FOREGROUND_RED
+ green = FOREGROUND_GREEN
+ blue = FOREGROUND_BLUE
+ white = FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED
+
+ def __init__(self, stream):
+ PlainHighlighter.__init__(self, stream)
+
+ if stream is sys.stdin:
+ nStdHandle = self.STD_INPUT_HANDLE
+ elif stream is sys.stdout:
+ nStdHandle = self.STD_OUTPUT_HANDLE
+ elif stream is sys.stderr:
+ nStdHandle = self.STD_ERROR_HANDLE
+ else:
+ nStdHandle = None
+
+ if nStdHandle is not None:
+ import ctypes
+ self._handle = ctypes.windll.kernel32.GetStdHandle(nStdHandle)
+ else:
+ self._handle = INVALID_HANDLE_VALUE
+
+ self._attribute = self.white
+
+ def _setAttribute(self, attr):
+ if self._handle != INVALID_HANDLE_VALUE:
+ import ctypes
+ ctypes.windll.kernel32.SetConsoleTextAttribute(self._handle, attr)
+ self._attribute = attr
+
+ def normal(self):
+ self._setAttribute(self._normal)
+
+ def color(self, color):
+ intensity = self._attribute & FOREGROUND_INTENSITY
+ self._setAttribute(color | intensity)
+
+ def bold(self):
+ self._setAttribute(self._attribute | FOREGROUND_INTENSITY)
+
+ def italic(self):
+ pass
+
+
+def Highlighter(stream = sys.stdout):
+ if platform.system() == 'Windows':
+ return WindowsConsoleHighlighter(stream)
+ else:
+ return AnsiHighlighter(stream)
+
+
+__all__ = [
+ 'Highlighter',
+]
import optparse
import os.path
-import re
-import shutil
import subprocess
import platform
import sys
-import tempfile
+
+from PIL import Image
from snapdiff import Comparer
+from highlight import Highlighter
import jsondiff
self.args = args
self.env = env
- def retrace(self, snapshot_dir):
+ def retrace(self):
cmd = [
options.retrace,
- '-s', snapshot_dir + os.path.sep,
+ '-s', '-',
'-S', options.snapshot_frequency,
] + self.args
p = subprocess.Popen(cmd, env=self.env, stdout=subprocess.PIPE, stderr=NULL)
p = subprocess.Popen(cmd, env=self.env, stdout=subprocess.PIPE, stderr=NULL)
state = jsondiff.load(p.stdout)
p.wait()
- return state
+ return state.get('parameters', {})
+
+ def diff_state(self, ref_call_no, src_call_no):
+ '''Compare the state between two calls.'''
+
+ ref_state = self.dump_state(ref_call_no)
+ src_state = self.dump_state(src_call_no)
+ sys.stdout.flush()
+ differ = jsondiff.Differ(sys.stdout)
+ differ.visit(ref_state, src_state)
+ sys.stdout.write('\n')
-def diff_state(setup, ref_call_no, src_call_no):
- ref_state = setup.dump_state(ref_call_no)
- src_state = setup.dump_state(src_call_no)
- sys.stdout.flush()
- differ = jsondiff.Differ(sys.stdout)
- differ.visit(ref_state, src_state)
- sys.stdout.write('\n')
+
+def read_pnm(stream):
+ '''Read a PNM from the stream, and return the image object, and the comment.'''
+
+ magic = stream.readline()
+ if not magic:
+ return None, None
+ assert magic.rstrip() == 'P6'
+ comment = ''
+ line = stream.readline()
+ while line.startswith('#'):
+ comment += line[1:]
+ line = stream.readline()
+ width, height = map(int, line.strip().split())
+ maximum = int(stream.readline().strip())
+ assert maximum == 255
+ data = stream.read(height * width * 3)
+ image = Image.frombuffer('RGB', (width, height), data, 'raw', 'RGB', 0, 1)
+ return image, comment
def parse_env(optparser, entries):
optparser.add_option(
'--ref-env', metavar='NAME=VALUE',
type='string', action='append', dest='ref_env', default=[],
- help='reference environment variable')
+ help='add variable to reference environment')
optparser.add_option(
'--src-env', metavar='NAME=VALUE',
type='string', action='append', dest='src_env', default=[],
- help='reference environment variable')
+ help='add variable to source environment')
optparser.add_option(
'--diff-prefix', metavar='PATH',
type='string', dest='diff_prefix', default='.',
- help='reference environment variable')
+ help='prefix for the difference images')
optparser.add_option(
'-t', '--threshold', metavar='BITS',
type="float", dest="threshold", default=12.0,
optparser.add_option(
'-S', '--snapshot-frequency', metavar='FREQUENCY',
type="string", dest="snapshot_frequency", default='draw',
- help="snapshot frequency [default: %default]")
+ help="snapshot frequency: frame, framebuffer, or draw [default: %default]")
(options, args) = optparser.parse_args(sys.argv[1:])
ref_env = parse_env(optparser, options.ref_env)
ref_setup = Setup(args, ref_env)
src_setup = Setup(args, src_env)
- image_re = re.compile('^Wrote (.*\.png)$')
+ highligher = Highlighter(sys.stdout)
+
+ highligher.write('call\tprecision\n')
- last_good = -1
last_bad = -1
- ref_snapshot_dir = tempfile.mkdtemp()
+ last_good = 0
+ ref_proc = ref_setup.retrace()
try:
- src_snapshot_dir = tempfile.mkdtemp()
+ src_proc = src_setup.retrace()
try:
- ref_proc = ref_setup.retrace(ref_snapshot_dir)
- try:
- src_proc = src_setup.retrace(src_snapshot_dir)
- try:
- for ref_line in ref_proc.stdout:
- # Get the reference image
- ref_line = ref_line.rstrip()
- mo = image_re.match(ref_line)
- if mo:
- ref_image = mo.group(1)
- for src_line in src_proc.stdout:
- # Get the source image
- src_line = src_line.rstrip()
- mo = image_re.match(src_line)
- if mo:
- src_image = mo.group(1)
-
- root, ext = os.path.splitext(os.path.basename(src_image))
- call_no = int(root)
-
- # Compare the two images
- comparer = Comparer(ref_image, src_image)
- precision = comparer.precision()
-
- sys.stdout.write('%u %f\n' % (call_no, precision))
-
- if precision < options.threshold:
- if options.diff_prefix:
- comparer.write_diff(os.path.join(options.diff_prefix, root + '.diff.png'))
- if last_bad < last_good:
- diff_state(src_setup, last_good, call_no)
- last_bad = call_no
- else:
- last_good = call_no
-
- sys.stdout.flush()
-
- os.unlink(src_image)
- break
- os.unlink(ref_image)
- finally:
- src_proc.terminate()
- finally:
- ref_proc.terminate()
+ while True:
+ # Get the reference image
+ ref_image, ref_comment = read_pnm(ref_proc.stdout)
+ if ref_image is None:
+ break
+
+ # Get the source image
+ src_image, src_comment = read_pnm(src_proc.stdout)
+ if src_image is None:
+ break
+
+ assert ref_comment == src_comment
+
+ call_no = int(ref_comment.strip())
+
+ # Compare the two images
+ comparer = Comparer(ref_image, src_image)
+ precision = comparer.precision()
+
+ mismatch = precision < options.threshold
+
+ if mismatch:
+ highligher.color(highligher.red)
+ highligher.bold()
+ highligher.write('%u\t%f\n' % (call_no, precision))
+ if mismatch:
+ highligher.normal()
+
+ if mismatch:
+ if options.diff_prefix:
+ prefix = os.path.join(options.diff_prefix, '%010u' % call_no)
+ prefix_dir = os.path.dirname(prefix)
+ if not os.path.isdir(prefix_dir):
+ os.makedirs(prefix_dir)
+ ref_image.save(prefix + '.ref.png')
+ src_image.save(prefix + '.src.png')
+ comparer.write_diff(prefix + '.diff.png')
+ if last_bad < last_good:
+ src_setup.diff_state(last_good, call_no)
+ last_bad = call_no
+ else:
+ last_good = call_no
+
+ highligher.flush()
finally:
- shutil.rmtree(ref_snapshot_dir)
+ src_proc.terminate()
finally:
- shutil.rmtree(src_snapshot_dir)
+ ref_proc.terminate()
if __name__ == '__main__':
'''Image comparer.'''
def __init__(self, ref_image, src_image, alpha = False):
- self.ref_im = Image.open(ref_image)
- self.src_im = Image.open(src_image)
+ if isinstance(ref_image, basestring):
+ self.ref_im = Image.open(ref_image)
+ else:
+ self.ref_im = ref_image
+
+ if isinstance(src_image, basestring):
+ self.src_im = Image.open(src_image)
+ else:
+ self.src_im = src_image
# Ignore
if not alpha: