blob: 8230b6baf1e561c14997340ac5dcfee8934ffe1d [file] [log] [blame]
<!DOCTYPE html>
<html>
<head>
<style>
body {
color: white;
background-color: black;
}
</style>
</head>
<body onload="main()">
<div id="buttons"></div>
<table>
<tr>
<td>Image</td>
<td id="video_header"></td>
<td>Absolute Diff</td>
<td>Different Pixels</td>
</tr>
<tr>
<td><img src="blackwhite.png"></div>
<td><video autoplay></video></div>
<td><canvas id="diff"></canvas></td>
<td><canvas id="mask"></canvas></td>
</tr>
</div>
<p id="result"></p>
<script>
function log(str) {
document.getElementById('result').textContent = str;
console.log(str);
}
function loadVideo(name) {
var videoElem = document.querySelector('video');
videoElem.src = 'blackwhite_' + name;
document.getElementById('video_header').textContent = name;
videoElem.addEventListener('ended', onVideoEnded);
}
function onVideoEnded(e) {
document.title = verifyVideo() ? 'ENDED' : 'FAILED';
}
function onVideoError(e) {
document.title = 'ERROR';
document.getElementById('diff').style.visibility = 'hidden';
document.getElementById('mask').style.visibility = 'hidden';
log('Error playing video: ' + e.target.error.code + '.');
}
function main() {
// Programatically create buttons for each clip for manual testing.
var buttonsElem = document.getElementById('buttons');
function createButton(name) {
var buttonElem = document.createElement('button');
buttonElem.textContent = name;
buttonElem.addEventListener('click', function() {
loadVideo(name);
});
buttonsElem.appendChild(buttonElem);
}
var VIDEOS = [
'yuv420p.ogv',
'yuv422p.ogv',
'yuv444p.ogv',
'yuv420p.webm',
'yuv444p.webm',
'yuv420p.mp4',
'yuv420p_rec709.mp4',
'yuvj420p.mp4',
'yuv422p.mp4',
'yuv444p.mp4',
'yuv420p.avi'
];
for (var i = 0; i < VIDEOS.length; ++i) {
createButton(VIDEOS[i]);
}
// Video event handlers.
var videoElem = document.querySelector('video');
videoElem.addEventListener('error', onVideoError);
// Check if a query parameter was provided for automated tests.
if (window.location.search.length > 1) {
loadVideo(window.location.search.substr(1));
} else {
// If we're not an automated test, compute some pretty diffs.
document.querySelector('video').addEventListener('ended',
computeDiffs);
}
}
function getCanvasPixels(canvas) {
try {
return canvas.getContext('2d')
.getImageData(0, 0, canvas.width, canvas.height)
.data;
} catch(e) {
var message = 'ERROR: ' + e;
if (e.name == 'SecurityError') {
message += ' Couldn\'t get image pixels, try running with ' +
'--allow-file-access-from-files.';
}
log(message);
}
}
function verifyVideo() {
var videoElem = document.querySelector('video');
var offscreen = document.createElement('canvas');
offscreen.width = videoElem.videoWidth;
offscreen.height = videoElem.videoHeight;
offscreen.getContext('2d')
.drawImage(videoElem, 0, 0, offscreen.width, offscreen.height);
videoData = getCanvasPixels(offscreen);
if (!videoData)
return false;
// Check the color of a givel pixel |x,y| in |imgData| against an
// expected value, |expected|, with up to |allowedError| difference.
function checkColor(imgData, x, y, stride, expected, allowedError) {
for (var i = 0; i < 3; ++i) {
if (Math.abs(imgData[(x + y * stride) * 4 + i] - expected) >
allowedError) {
return false;
}
}
return true;
}
// Check one pixel in each quadrant (in the upper left, away from
// boundaries and the text, to avoid compression artifacts).
// Also allow a small error, for the same reason.
// TODO(mtomasz): Once code.google.com/p/libyuv/issues/detail?id=324 is
// fixed, the allowedError should be decreased to 1.
var allowedError = 2;
return checkColor(videoData, 30, 30, videoElem.videoWidth, 0xff,
allowedError) &&
checkColor(videoData, 150, 30, videoElem.videoWidth, 0x00,
allowedError) &&
checkColor(videoData, 30, 150, videoElem.videoWidth, 0x10,
allowedError) &&
checkColor(videoData, 150, 150, videoElem.videoWidth, 0xef,
allowedError);
}
// Compute a standard diff image, plus a high-contrast mask that shows
// each differing pixel more visibly.
function computeDiffs() {
var diffElem = document.getElementById('diff');
var maskElem = document.getElementById('mask');
var videoElem = document.querySelector('video');
var imgElem = document.querySelector('img');
var width = imgElem.width;
var height = imgElem.height;
if (videoElem.videoWidth != width || videoElem.videoHeight != height) {
log('ERROR: video dimensions don\'t match reference image ' +
'dimensions');
return;
}
// Make an offscreen canvas to dump reference image pixels into.
var offscreen = document.createElement('canvas');
offscreen.width = width;
offscreen.height = height;
offscreen.getContext('2d').drawImage(imgElem, 0, 0, width, height);
imgData = getCanvasPixels(offscreen);
if (!imgData)
return;
// Scale and clear diff canvases.
diffElem.width = maskElem.width = width;
diffElem.height = maskElem.height = height;
var diffCtx = diffElem.getContext('2d');
var maskCtx = maskElem.getContext('2d');
maskCtx.clearRect(0, 0, width, height);
diffCtx.clearRect(0, 0, width, height);
// Copy video pixels into diff.
diffCtx.drawImage(videoElem, 0, 0, width, height);
var diffIData = diffCtx.getImageData(0, 0, width, height);
var diffData = diffIData.data;
var maskIData = maskCtx.getImageData(0, 0, width, height);
var maskData = maskIData.data;
// Make diffs and collect stats.
var meanSquaredError = 0;
for (var i = 0; i < imgData.length; i += 4) {
var difference = 0;
for (var j = 0; j < 3; ++j) {
diffData[i + j] = Math.abs(diffData[i + j] - imgData[i + j]);
meanSquaredError += diffData[i + j] * diffData[i + j];
if (diffData[i + j] != 0) {
difference += diffData[i + j];
}
}
if (difference > 0) {
if (difference <= 3) {
// If we're only off by a bit per channel or so, use darker red.
maskData[i] = 128;
} else {
// Bright red to indicate a different pixel.
maskData[i] = 255;
}
maskData[i+3] = 255;
}
}
meanSquaredError /= width * height;
log('Mean squared error: ' + meanSquaredError);
diffCtx.putImageData(diffIData, 0, 0);
maskCtx.putImageData(maskIData, 0, 0);
document.getElementById('diff').style.visibility = 'visible';
document.getElementById('mask').style.visibility = 'visible';
}
</script>
</body>
</html>