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 |