1 <!DOCTYPE html> 2 <html> 3 <head> 4 <style> 5 body { 6 color: white; 7 background-color: black; 8 } 9 </style> 10 </head> 11 <body onload="main()"> 12 <div id="buttons"></div> 13 <table> 14 <tr> 15 <td>Image</td> 16 <td id="video_header"></td> 17 <td>Absolute Diff</td> 18 <td>Different Pixels</td> 19 </tr> 20 <tr> 21 <td><img src="blackwhite.png"></div> 22 <td><video autoplay></video></div> 23 <td><canvas id="diff"></canvas></td> 24 <td><canvas id="mask"></canvas></td> 25 </tr> 26 </div> 27 28 <p id="result"></p> 29 30 <script> 31 function log(str) { 32 document.getElementById('result').textContent = str; 33 console.log(str); 34 } 35 36 function loadVideo(name) { 37 var videoElem = document.querySelector('video'); 38 videoElem.src = 'blackwhite_' + name; 39 40 document.getElementById('video_header').textContent = name; 41 videoElem.addEventListener('ended', onVideoEnded); 42 } 43 44 function onVideoEnded(e) { 45 document.title = verifyVideo() ? 'ENDED' : 'FAILED'; 46 } 47 48 function onVideoError(e) { 49 document.title = 'ERROR'; 50 document.getElementById('diff').style.visibility = 'hidden'; 51 document.getElementById('mask').style.visibility = 'hidden'; 52 log('Error playing video: ' + e.target.error.code + '.'); 53 } 54 55 function main() { 56 // Programatically create buttons for each clip for manual testing. 57 var buttonsElem = document.getElementById('buttons'); 58 59 function createButton(name) { 60 var buttonElem = document.createElement('button'); 61 buttonElem.textContent = name; 62 buttonElem.addEventListener('click', function() { 63 loadVideo(name); 64 }); 65 buttonsElem.appendChild(buttonElem); 66 } 67 68 var VIDEOS = [ 69 'yuv420p.ogv', 70 'yuv422p.ogv', 71 'yuv444p.ogv', 72 'yuv420p.webm', 73 'yuv420p.mp4', 74 'yuvj420p.mp4', 75 'yuv422p.mp4', 76 'yuv444p.mp4', 77 'yuv420p.avi' 78 ]; 79 80 for (var i = 0; i < VIDEOS.length; ++i) { 81 createButton(VIDEOS[i]); 82 } 83 84 // Video event handlers. 85 var videoElem = document.querySelector('video'); 86 videoElem.addEventListener('error', onVideoError); 87 88 // Check if a query parameter was provided for automated tests. 89 if (window.location.search.length > 1) { 90 loadVideo(window.location.search.substr(1)); 91 } else { 92 // If we're not an automated test, compute some pretty diffs. 93 document.querySelector('video').addEventListener('ended', 94 computeDiffs); 95 } 96 } 97 98 function getCanvasPixels(canvas) { 99 try { 100 return canvas.getContext('2d') 101 .getImageData(0, 0, canvas.width, canvas.height) 102 .data; 103 } catch(e) { 104 var message = 'ERROR: ' + e; 105 if (e.name == 'SecurityError') { 106 message += ' Couldn\'t get image pixels, try running with ' + 107 '--allow-file-access-from-files.'; 108 } 109 log(message); 110 } 111 } 112 113 function verifyVideo() { 114 var videoElem = document.querySelector('video'); 115 var offscreen = document.createElement('canvas'); 116 offscreen.width = videoElem.videoWidth; 117 offscreen.height = videoElem.videoHeight; 118 offscreen.getContext('2d') 119 .drawImage(videoElem, 0, 0, offscreen.width, offscreen.height); 120 121 videoData = getCanvasPixels(offscreen); 122 if (!videoData) 123 return false; 124 125 // Check the color of a givel pixel |x,y| in |imgData| against an 126 // expected value, |expected|, with up to |allowedError| difference. 127 function checkColor(imgData, x, y, stride, expected, allowedError) { 128 for (var i = 0; i < 3; ++i) { 129 if (Math.abs(imgData[(x + y * stride) * 4 + i] - expected) > 130 allowedError) { 131 return false; 132 } 133 } 134 return true; 135 } 136 137 // Check one pixel in each quadrant (in the upper left, away from 138 // boundaries and the text, to avoid compression artifacts). 139 // Also allow an error of 1, for the same reason. 140 return checkColor(videoData, 30, 30, videoElem.videoWidth, 0xff, 1) && 141 checkColor(videoData, 150, 30, videoElem.videoWidth, 0x00, 1) && 142 checkColor(videoData, 30, 150, videoElem.videoWidth, 0x10, 1) && 143 checkColor(videoData, 150, 150, videoElem.videoWidth, 0xef, 1); 144 } 145 146 // Compute a standard diff image, plus a high-contrast mask that shows 147 // each differing pixel more visibly. 148 function computeDiffs() { 149 var diffElem = document.getElementById('diff'); 150 var maskElem = document.getElementById('mask'); 151 var videoElem = document.querySelector('video'); 152 var imgElem = document.querySelector('img'); 153 154 var width = imgElem.width; 155 var height = imgElem.height; 156 157 if (videoElem.videoWidth != width || videoElem.videoHeight != height) { 158 log('ERROR: video dimensions don\'t match reference image ' + 159 'dimensions'); 160 return; 161 } 162 163 // Make an offscreen canvas to dump reference image pixels into. 164 var offscreen = document.createElement('canvas'); 165 offscreen.width = width; 166 offscreen.height = height; 167 168 offscreen.getContext('2d').drawImage(imgElem, 0, 0, width, height); 169 imgData = getCanvasPixels(offscreen); 170 if (!imgData) 171 return; 172 173 // Scale and clear diff canvases. 174 diffElem.width = maskElem.width = width; 175 diffElem.height = maskElem.height = height; 176 var diffCtx = diffElem.getContext('2d'); 177 var maskCtx = maskElem.getContext('2d'); 178 maskCtx.clearRect(0, 0, width, height); 179 diffCtx.clearRect(0, 0, width, height); 180 181 // Copy video pixels into diff. 182 diffCtx.drawImage(videoElem, 0, 0, width, height); 183 184 var diffIData = diffCtx.getImageData(0, 0, width, height); 185 var diffData = diffIData.data; 186 var maskIData = maskCtx.getImageData(0, 0, width, height); 187 var maskData = maskIData.data; 188 189 // Make diffs and collect stats. 190 var meanSquaredError = 0; 191 for (var i = 0; i < imgData.length; i += 4) { 192 var difference = 0; 193 for (var j = 0; j < 3; ++j) { 194 diffData[i + j] = Math.abs(diffData[i + j] - imgData[i + j]); 195 meanSquaredError += diffData[i + j] * diffData[i + j]; 196 if (diffData[i + j] != 0) { 197 difference += diffData[i + j]; 198 } 199 } 200 if (difference > 0) { 201 if (difference <= 3) { 202 // If we're only off by a bit per channel or so, use darker red. 203 maskData[i] = 128; 204 } else { 205 // Bright red to indicate a different pixel. 206 maskData[i] = 255; 207 } 208 maskData[i+3] = 255; 209 } 210 } 211 212 meanSquaredError /= width * height; 213 log('Mean squared error: ' + meanSquaredError); 214 diffCtx.putImageData(diffIData, 0, 0); 215 maskCtx.putImageData(maskIData, 0, 0); 216 document.getElementById('diff').style.visibility = 'visible'; 217 document.getElementById('mask').style.visibility = 'visible'; 218 } 219 220