Embed html videos with correct background color

The problem
How to integrate a video with a solid background color seamlessly into a website with the same background color.

We came to realize that on most screens there was a color deviation and we couldn’t globally match both colors. After trying several encodings and compressions of our .mp4 video, we could still see a slight difference in background color compared to the CSS color.

Color deviation html5 Web Video
Color deviation of html5 video vs. canvas workaround

The usual solution would be to use a Codec which supports alpha channel (webm) or use an additional alpha mask to make the mp4 video transparent. But in this case we needed full browser compatibility, low processing power and couldn’t ask our client to generate alpha mask videos for each video he would upload on his CMS.

The solution

Some research showed that the problem comes from each users GPU. Those can be set to support the full RGB range (0 – 255) or a limited range (16-235). This limited range is causing this shift in color interpretation and makes it impossible to match a videos color to a CSS value globally.

Instead of playing the video directly in the video tag, we render the video on a canvas (there the color shift gets applied), pick a sample color of the first frame and adjust the background of the website accordingly. Now the video fits seamlessly into the website. Heres a demo:

Careful: As this approach changes the background color of your website slightly, we recommend trying to get the videos color as close as possible the original css value as possible to keep it unnoticed.

The Code

function drawingLoop(){
  requestId = window.requestAnimationFrame(drawingLoop)
  ctx.drawImage(vid, 0, 0, vidWidth, vidHeight, 0, 0, canvas.width, canvas.height);

function setVideoBgColor(vid, backgroundElement) {
    // draw first four pixel of video to a canvas
    // then get pixel color from that canvas
    var canvas = document.createElement("canvas");
    canvas.width = 8;
    canvas.height = 8;

    var ctx = canvas.getContext("2d");
    ctx.drawImage(vid, 0, 0, 8, 8);

    var p = ctx.getImageData(0, 0, 8, 8).data;
    //dont take the first but fourth pixel [r g b a]
    backgroundElement.style.backgroundColor = "rgb(" + p[60] + "," + p[61] + "," + p[62] + ")";

The initial idea comes from Feng (stack), but we rewrote it to fit our needs. Instead of choosing the very first pixel of the frame to pick the color, we’re taking the 16th, which seems to be a bit saver.