chromium/content/test/data/media/canvas_capture_color.html

<!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>