半小时也不一定能学会智能防挡弹幕

474 阅读3分钟

经常刷腾讯视频、爱奇艺、Bilibili的小伙伴,会发现,这些网站都提供了一种"智能防挡弹幕"的功能,效果如下图所示。

image.png

那这些视频网站是如何实现的呢?

答案就是依靠 Mask

什么是 Mask ?

Mask是遮罩/蒙版。Mask 属性允许使用者通过遮罩或者裁切特定区域的图片的方式来隐藏一个元素的部分或者全部可见区域。

f5p5f-8v0wn.gif

接下来主要介绍遮罩/蒙版的原理、实现方式、弹幕应用。

遮罩/蒙版原理

image.png

  • 遮罩: 有色则可见(任何颜色),无色则不可见,半透明则半透明
  • 蒙版: 黑色则不可见,白色则可见,非黑色白色则半透明
可见不可见半透明实现方式
遮罩rgb(0 ~ 255, 0 ~ 255, 0 ~ 255)rgba(0 ~ 255, 0 ~ 255, 0 ~ 255, 0 ~ 1)css (mask 属性)canvas (globalCompositeOperation 设置)
蒙版rgb(255, 255, 255)rgb(0, 0, 0)rgb(0 ~ 255, 0 ~ 255, 0 ~ 255)svg (mask 元素)

其实两者概念上非常容易混淆,但实际最终效果是一样的,仅需记住哪个是无色不可见,哪个是黑色不可见。

遮罩

image.png

canvas globalCompositeOperation 属性设置或返回如何将一个源(新的)图像绘制到目标(已有的)的图像上。

源图像 = 您打算放置到画布上的绘图。

目标图像 = 您已经放置在画布上的绘图。

image.png

(function(){
  var imagecanvas = document.createElement('canvas');
  var imagecontext = imagecanvas.getContext('2d');
  /* uncomment do see the canvas to debug
  document.body.appendChild(imagecanvas);
  */
  window.addEventListener('load', function(){
    [].forEach.call(document.querySelectorAll('.mask'), function(img){
      var newImg = document.createElement('img');
      newImg.src = img.src;
      
      newImg.onload = function() {
        var width  = newImg.width;
        var height = newImg.height;
        var mask = document.createElement('img');
        mask.src = img.getAttribute('data-mask');
        mask.onload = function() {
          imagecanvas.width  = width;
          imagecanvas.height = height;
          imagecontext.drawImage(mask, 0, 0, width, height);
          // source-in 重叠部分显示
          imagecontext.globalCompositeOperation = 'source-in';
          imagecontext.drawImage(img, 0, 0);
          img.src = imagecanvas.toDataURL();
        }
      }
    });
  }, false);
})();

Canvas Text Mask Demo

蒙版

Svg 可以用 mask 来实现 蒙版

<svg width="300" height="250" viewBox="0 0 300 250" xmlns="http://www.w3.org/2000/svg">
    <defs>
        <rect id="rect" width="100" height="100" fill="green" />
        
        <!-- 白色的三角形蒙版 -->
        <path id="triangle" d="M50 0 L100 100 L0 100 Z" />
        
        <mask id="mask">
            <use xlink:href="#triangle" fill="rgb(255,255,255)" />
        </mask>
    </defs>
    <use xlink:href="#rect" x="10" y="10" mask="url(#mask)" />
    <!-- 本来是方形,显示出来是绿色的三角形 -->
</svg>

image.png

如果 mask 没有设置 fill,或者设置 fill="none" ( 表示无色 ),则不显示;

如果有色但非黑白色,则按色值计算透明度;

SVG Text Mask Demo

弹幕应用

前面,我们通过一些案例,简单说明了遮罩蒙版的作用和概念,相信大家有所理解。

接下来我们将用Canvas globalCompositeOperation(遮罩) 实现一个简易版"智能防挡弹幕"。

页面容器布局
<!-- 弹幕容器 -->
<div class="barrage-container">
    <!-- 弹幕列表 -->
    <div class="barrage-item">哈哈哈哈</div>
    <div class="barrage-item">不错不错</div>
    <div class="barrage-item">v5v5v5v5</div>
</div>
<!-- 遮罩容器 -->
<canvas id="canvas"></canvas>
基于AI人脸分割模型人脸坐标实现

image.png

function main() {
    // 人脸坐标字符串 0:空白 1:人脸
    // 256 x 144 的图像规格
    const str = `00000000000000111111111111111100011111111111100......11100000000000000111111100000`;
    // 人脸识别 width x height
    const WIDTH = 256;
    const HEIGHT = 144;
    function getCanvas(width, height) {
        const canvas = document.getElementById('canvas');
        canvas.width = width;
        canvas.height = height;
        return canvas;
    }
    function doMasking(canvas, pixels) {
        const ctx = canvas.getContext('2d');
        for (let x = 0; x < WIDTH; x++) {
            for (let y = 0; y < HEIGHT; y++) {
                const pixelIndex = y * WIDTH + x;
                // 如果数据有问题, 抛错
                if (pixelIndex >= pixels.length) {
                    throw new Error('原始数据有问题');
                }
                const shouldMask = pixels[pixelIndex] === 0;
                if (shouldMask) {
                    ctx.fillRect(x, y, 1, 1);
                }
            }
        }
        // ==== 添加遮罩 ====
        ctx.globalCompositeOperation = 'source-in';
        ctx.fillStyle = 'green';
        ctx.fillRect(0, 0, 256, 144);
    }
    // ==== 主体逻辑 ====
    const pixels = str
        .split('')
        .filter(i => i !== '\n')
        .map(i => ~~i);
    const canvas = getCanvas(WIDTH, HEIGHT);
    doMasking(canvas, pixels);
    // ==== 输出图像 ====
    const container = document.querySelector('.barrage-container');
    const dataUrl = canvas.toDataURL('image/png');
    container.style['-webkit-mask-image'] = `url(${dataUrl})`;
    container.style['mask-image'] = `url(${dataUrl})`;
}
main();
页面最终效果

image.png

要明确的是,使用 mask,不是将弹幕部分给遮挡住,而是利用 mask,指定弹幕容器之下,哪些部分正常展示,哪些部分透明隐藏。

f5p5f-8v0wn.gif

相关文档