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           '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   
    231 
    232