| <!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', |
| 'yuv420p_hi10p.mp4' |
| ]; |
| |
| 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) { |
| var actual = imgData[(x + y * stride) * 4 + i]; |
| if (Math.abs(actual - expected) > allowedError) { |
| log('Color didn\'t match at (' + x + ', ' + y + '). Expected: ' + |
| expected + ', actual: ' + actual); |
| 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> |