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 'yuv444p.webm', 74 'yuv420p.mp4', 75 'yuvj420p.mp4', 76 'yuv422p.mp4', 77 'yuv444p.mp4', 78 'yuv420p.avi' 79 ]; 80 81 for (var i = 0; i < VIDEOS.length; ++i) { 82 createButton(VIDEOS[i]); 83 } 84 85 // Video event handlers. 86 var videoElem = document.querySelector('video'); 87 videoElem.addEventListener('error', onVideoError); 88 89 // Check if a query parameter was provided for automated tests. 90 if (window.location.search.length > 1) { 91 loadVideo(window.location.search.substr(1)); 92 } else { 93 // If we're not an automated test, compute some pretty diffs. 94 document.querySelector('video').addEventListener('ended', 95 computeDiffs); 96 } 97 } 98 99 function getCanvasPixels(canvas) { 100 try { 101 return canvas.getContext('2d') 102 .getImageData(0, 0, canvas.width, canvas.height) 103 .data; 104 } catch(e) { 105 var message = 'ERROR: ' + e; 106 if (e.name == 'SecurityError') { 107 message += ' Couldn\'t get image pixels, try running with ' + 108 '--allow-file-access-from-files.'; 109 } 110 log(message); 111 } 112 } 113 114 function verifyVideo() { 115 var videoElem = document.querySelector('video'); 116 var offscreen = document.createElement('canvas'); 117 offscreen.width = videoElem.videoWidth; 118 offscreen.height = videoElem.videoHeight; 119 offscreen.getContext('2d') 120 .drawImage(videoElem, 0, 0, offscreen.width, offscreen.height); 121 122 videoData = getCanvasPixels(offscreen); 123 if (!videoData) 124 return false; 125 126 // Check the color of a givel pixel |x,y| in |imgData| against an 127 // expected value, |expected|, with up to |allowedError| difference. 128 function checkColor(imgData, x, y, stride, expected, allowedError) { 129 for (var i = 0; i < 3; ++i) { 130 if (Math.abs(imgData[(x + y * stride) * 4 + i] - expected) > 131 allowedError) { 132 return false; 133 } 134 } 135 return true; 136 } 137 138 // Check one pixel in each quadrant (in the upper left, away from 139 // boundaries and the text, to avoid compression artifacts). 140 // Also allow a small error, for the same reason. 141 142 // TODO(mtomasz): Once code.google.com/p/libyuv/issues/detail?id=324 is 143 // fixed, the allowedError should be decreased to 1. 144 var allowedError = 2; 145 146 return checkColor(videoData, 30, 30, videoElem.videoWidth, 0xff, 147 allowedError) && 148 checkColor(videoData, 150, 30, videoElem.videoWidth, 0x00, 149 allowedError) && 150 checkColor(videoData, 30, 150, videoElem.videoWidth, 0x10, 151 allowedError) && 152 checkColor(videoData, 150, 150, videoElem.videoWidth, 0xef, 153 allowedError); 154 } 155 156 // Compute a standard diff image, plus a high-contrast mask that shows 157 // each differing pixel more visibly. 158 function computeDiffs() { 159 var diffElem = document.getElementById('diff'); 160 var maskElem = document.getElementById('mask'); 161 var videoElem = document.querySelector('video'); 162 var imgElem = document.querySelector('img'); 163 164 var width = imgElem.width; 165 var height = imgElem.height; 166 167 if (videoElem.videoWidth != width || videoElem.videoHeight != height) { 168 log('ERROR: video dimensions don\'t match reference image ' + 169 'dimensions'); 170 return; 171 } 172 173 // Make an offscreen canvas to dump reference image pixels into. 174 var offscreen = document.createElement('canvas'); 175 offscreen.width = width; 176 offscreen.height = height; 177 178 offscreen.getContext('2d').drawImage(imgElem, 0, 0, width, height); 179 imgData = getCanvasPixels(offscreen); 180 if (!imgData) 181 return; 182 183 // Scale and clear diff canvases. 184 diffElem.width = maskElem.width = width; 185 diffElem.height = maskElem.height = height; 186 var diffCtx = diffElem.getContext('2d'); 187 var maskCtx = maskElem.getContext('2d'); 188 maskCtx.clearRect(0, 0, width, height); 189 diffCtx.clearRect(0, 0, width, height); 190 191 // Copy video pixels into diff. 192 diffCtx.drawImage(videoElem, 0, 0, width, height); 193 194 var diffIData = diffCtx.getImageData(0, 0, width, height); 195 var diffData = diffIData.data; 196 var maskIData = maskCtx.getImageData(0, 0, width, height); 197 var maskData = maskIData.data; 198 199 // Make diffs and collect stats. 200 var meanSquaredError = 0; 201 for (var i = 0; i < imgData.length; i += 4) { 202 var difference = 0; 203 for (var j = 0; j < 3; ++j) { 204 diffData[i + j] = Math.abs(diffData[i + j] - imgData[i + j]); 205 meanSquaredError += diffData[i + j] * diffData[i + j]; 206 if (diffData[i + j] != 0) { 207 difference += diffData[i + j]; 208 } 209 } 210 if (difference > 0) { 211 if (difference <= 3) { 212 // If we're only off by a bit per channel or so, use darker red. 213 maskData[i] = 128; 214 } else { 215 // Bright red to indicate a different pixel. 216 maskData[i] = 255; 217 } 218 maskData[i+3] = 255; 219 } 220 } 221 222 meanSquaredError /= width * height; 223 log('Mean squared error: ' + meanSquaredError); 224 diffCtx.putImageData(diffIData, 0, 0); 225 maskCtx.putImageData(maskIData, 0, 0); 226 document.getElementById('diff').style.visibility = 'visible'; 227 document.getElementById('mask').style.visibility = 'visible'; 228 } 229 230