| OLD | NEW |
| (Empty) |
| 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 </script> | |
| 230 </body> | |
| 231 </html> | |
| OLD | NEW |