1
    2
    3
    4
    5
    6
    7
    8
    9
   10
   11
   12
   13
   14
   15
   16
   17
   18
   19
   20
   21
   22
   23
   24
   25
   26
   27
   28
   29
   30
   31
   32
   33
   34
   35
   36
   37
   38
   39
   40
   41
   42
   43
   44
   45
   46
   47
   48
   49
   50
   51
   52
   53
   54
   55
   56
   57
   58
   59
   60
   61
   62
   63
   64
   65
   66
   67
   68
   69
   70
   71
   72
   73
   74
   75
   76
   77
   78
   79
   80
   81
   82
   83
   84
   85
   86
   87
   88
   89
   90
   91
   92
   93
   94
   95
   96
   97
   98
   99
  100
  101
  102
  103
  104
  105
  106
  107
  108
  109
  110
  111
  112
  113
  114
  115
  116
  117
  118
  119
  120
  121
  122
  123
  124
  125
  126
  127
  128
  129
  130
  131
  132
  133
  134
  135
  136
  137
  138
  139
  140
  141
  142
  143
  144
  145
  146
  147
  148
  149
  150
  151
  152
  153
  154
  155
  156
  157
  158
  159
  160
  161
  162
  163
  164
  165
  166
  167
  168
  169
  170
  171
  172
  173
  174
  175
  176
  177
  178
  179
  180
  181
  182

content / test / data / media / canvas_capture_color.html [blame]

<!DOCTYPE html>
<html>
<head>
<title>Media Capture from DOM Elements (canvas) Browser Test</title>
</head>
<body>
  <div>Canvas capture Color Test.</div>
  <canvas id='canvas-2d' width=10 height=10></canvas>
  <video id='canvas-2d-local-view' autoplay></video>
  <canvas id='canvas-webgl' width=10 height=10></canvas>
  <video id='canvas-webgl-local-view' autoplay></video>
  <canvas id='local-view-canvas' width=10 height=10></canvas>
<script type="text/javascript" src="mediarecorder_test_utils.js"></script>
<script>

'use strict';

const RED = [255, 0, 0, 1];
const GREEN = [0, 255, 0, 1];
const BLUE = [0, 0, 255, 1];
const RED_WITH_ALPHA = [255, 0, 0, 0.2];
const GREEN_WITH_ALPHA = [0, 255, 0, 0.5];
const BLUE_WITH_ALPHA = [0, 0, 255, 0.9];
const MAX_ALPHA = 255;

// The RGBA to YUV conversion is not perfectly reversible, so it is expected
// that there will be some color info lost when converting RGBA to YUV and then
// later YUV to RGBA.
const TOLERANCE = 10;

// This function draws a colored rectangle on the canvas.
function draw(canvasId, contextType, colorRgba, alphaContext) {
  return new Promise(function(resolve, reject) {
    // Wrapping the update in requestAnimationFrame is required for this to be
    // a regression test for crbug.com/702446. requestAnimationFrame exposes
    // this use case to a potential race between the frame capture and the
    // frame clear that is caused by the {preserveDrawingBuffer: false} option
    // on webgl contexts.
    requestAnimationFrame(function() {
      var context = canvasId.getContext(contextType, {alpha : alphaContext});
      if (contextType == '2d') {
        context.clearRect(0, 0, canvasId.clientWidth, canvasId.clientHeight);
        context.fillStyle = 'rgba(' + colorRgba.join() + ')';
        context.fillRect(0, 0, canvasId.clientWidth, canvasId.clientHeight);
      } else {
        context.clearColor(colorRgba[0] / 255, colorRgba[1] / 255, colorRgba[2] / 255, colorRgba[3]);
        context.clear(context.COLOR_BUFFER_BIT);
      }
      resolve();
    });
  });
}

// This function gets all the pixels from the snapshot canvas.
// The snapshot canvas represents a snapshot of the video frame.
function getPixels(videoId, canvasId) {
  var context = canvasId.getContext('2d');
  context.clearRect(0, 0, canvasId.clientWidth, canvasId.clientHeight);
  context.drawImage(videoId, 0, 0);
  return context.getImageData(0, 0 , canvasId.clientWidth,
      canvasId.clientHeight).data;
}

// This generator yields one pixel [R, G, B, A] from the supplied data.
function* getPixelGenerator(pixelData) {
  for (var i = 0; i < pixelData.length; i += 4) {
    yield pixelData.slice(i, i + 4);
  }
}

// This function checks the color correctness of the whole video frame.
function isVideoColor(videoId, canvasId, colorRgba, alphaContext) {
  var pixelIterator = getPixelGenerator(getPixels(videoId, canvasId));
  for (var pixelRgba of pixelIterator) {
    if (!isPixelColor(pixelRgba, colorRgba, alphaContext))
      return false;
  }
  return true;
}

// This function checks the rgba color of a single pixel in the video.
function isPixelColor(pixel, color, alphaContext) {
  var expectedColor = color.slice(0);
  // The css rgba() function takes an alpha channel (a) such as 0 <= a <= 1,
  // but context.getImageData() returns array of rgba data with alpha
  // channel (a') such as  0 <= a' <= 255.
  var checkLength = alphaContext ? color.length : color.length - 1;
  expectedColor[expectedColor.length - 1] *= MAX_ALPHA;
  for (var i = 0; i < checkLength; i++) {
    // When |alphaContext| is true we have an alpha channel; premultiply the RGB
    // channel values that are defined in this file unpremultiplied.
    if (alphaContext)
      expectedColor[i] *= expectedColor[-1];

    if (Math.abs(pixel[i] - expectedColor[i]) > TOLERANCE) {
      console.log('Expected ' + expectedColor + ', got ' + pixel);
      return false;
    }
  }
  return true;
}

function connectStream(contextType) {
  var canvas = document.getElementById('canvas-' + contextType);
  var video = document.getElementById('canvas-' + contextType + '-local-view');
  var stream = canvas.captureStream();
  video.srcObject = stream;
  video.load();
}

// This function runs the canvas capture rgba color checks.
async function testCanvas2DCaptureColors(alphaContext) {
  connectStream('2d');

  await doCanvasCaptureAndCheckRgba('2d', RED, alphaContext)
  await doCanvasCaptureAndCheckRgba('2d', GREEN, alphaContext);
  await doCanvasCaptureAndCheckRgba('2d', BLUE, alphaContext);
  await doCanvasCaptureAndCheckRgba('2d', RED_WITH_ALPHA, alphaContext);
  await doCanvasCaptureAndCheckRgba('2d', GREEN_WITH_ALPHA, alphaContext);
  await doCanvasCaptureAndCheckRgba('2d', BLUE_WITH_ALPHA, alphaContext);
}

async function testCanvasWebGLCaptureOpaqueColors(alphaContext) {
  connectStream('webgl');

  await doCanvasCaptureAndCheckRgba('webgl', RED, alphaContext)
  await doCanvasCaptureAndCheckRgba('webgl', GREEN, alphaContext);
  await doCanvasCaptureAndCheckRgba('webgl', BLUE, alphaContext);
}

async function testCanvasWebGLCaptureAlphaColors(alphaContext) {
  connectStream('webgl');

  await doCanvasCaptureAndCheckRgba('webgl', RED_WITH_ALPHA, alphaContext)
  await doCanvasCaptureAndCheckRgba('webgl', GREEN_WITH_ALPHA, alphaContext);
  await doCanvasCaptureAndCheckRgba('webgl', BLUE_WITH_ALPHA, alphaContext);
}

// Forces a context loss between captured frames and checks if capture continues
// as expected.
async function testCanvas2DContextLoss(alphaContext) {
  var contextType = '2d';
  connectStream(contextType);
  var canvas = document.getElementById('canvas-' + contextType);

  await doCanvasCaptureAndCheckRgba(contextType, RED, alphaContext)
  internals.forceLoseCanvasContext(canvas, "2d")
  // For the canvas to realize its Graphics context was lost we must try
  // to use the contents of the canvas.
  var imageData = canvas.getContext(contextType).getImageData(0, 0, 1, 1);
  await doCanvasCaptureAndCheckRgba(contextType, BLUE, alphaContext);
}

// This function fills a canvas with one rgba color and canvas-captures it
// to a video element. We then snapshot the video element to
// another canvas and checks the color is what we expect.
function doCanvasCaptureAndCheckRgba(contextType, colorRgba, alphaContext) {
  return new Promise(function(resolve, reject) {
    var canvas = document.getElementById('canvas-' + contextType);
    var video = document.getElementById('canvas-' + contextType + '-local-view');
    var snapshotCanvas = document.getElementById('local-view-canvas');

    draw(canvas, contextType, colorRgba, alphaContext)
        .then(function() {
          return waitFor('Verify the canvas color is as expected',
              function() {
                return isVideoColor(video, snapshotCanvas, colorRgba,
                  alphaContext);
              });
        })
        .catch(function(err) {
          reject(err);
        })
        .then(function() {
          resolve();
        });
  });
}

</script>
</body>
</html>