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>