Home | History | Annotate | Download | only in media
      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   
    221 
    222