Skip to content
vue
<template>
  <!-- 摄像头拍摄到的视频 -->
  <video autoplay ref="cameraRef" hidden></video>
  <!-- 替换摄像头背景后的视频 -->
  <video autoplay ref="cameraChromaRef"></video>
  <!-- 渲染摄像头拍摄到的视频 -->
  <canvas ref="canvasRef" hidden></canvas>
  <!-- 渲染替换摄像头背景后的视频 -->
  <canvas ref="canvas2Ref" hidden></canvas>
  <!-- 背景图片转为 imageData 的容器 -->
  <canvas ref="canvas3Ref" hidden></canvas>
</template>

<script lang="ts" setup>
import { useImageToImageData } from '@/hooks'
import { onMounted, ref } from 'vue'

const cameraRef = ref(null)
const cameraChromaRef = ref(null)
const canvasRef = ref(null)
const canvas2Ref = ref(null)
const canvas3Ref = ref(null)

const init = () => {
  const camera = cameraRef.value
  const cameraChroma = cameraChromaRef.value
  const canvas = canvasRef.value
  const canvas2 = canvas2Ref.value
  const context = canvas.getContext('2d')
  const context2 = canvas2.getContext('2d')

  try {
    const stream = navigator.mediaDevices.getUserMedia({
      video: true,
      audio: false
    })
    camera.srcObject = stream
  } catch (error) {
    console.error(error)
    return
  }

  // 需要给 video 元素设置 autoplay 属性或手动 camera.play() 才会触发回调,
  // 现代浏览器通常禁止自动播放无静音的视频,需要给 video 元素设置 muted 属性
  // 或 getUserMedia不采集音频
  camera.addEventListener('play', async () => {
    const { videoWidth, videoHeight } = camera
    canvas.width = canvas2.width = videoWidth
    canvas.height = canvas2.height = videoHeight
    const stream = canvas2.captureStream()
    cameraChroma.srcObject = stream
    const imageData = await getImageData()

    const timer = setInterval(() => {
      const { paused, ended } = camera

      if (paused || ended) {
        clearInterval(timer)
        return
      }

      updateBg(
        context,
        context2,
        camera,
        videoWidth,
        videoHeight,
        imageData.data
      )
    }, 50)
  })
}

const getImageData = async () => {
  const img = await new Promise<HTMLImageElement>(resolve => {
    const image = new Image()
    image.src = '/images/beach.jpg'
    image.onload = () => resolve(image)
  })

  return useImageToImageData(canvas3Ref.value, img)
}

const updateBg = (
  context,
  context2,
  camera,
  videoWidth,
  videoHeight,
  bgData
) => {
  context.drawImage(camera, 0, 0, videoWidth, videoHeight)
  const imageData = context.getImageData(0, 0, videoWidth, videoHeight)
  const { data } = imageData

  for (let i = 0, l = data.length; i < l; i += 4) {
    const r = data[i]
    const g = data[i + 1]
    const b = data[i + 2]

    // 偏白色的部分用背景替换
    if (r > 100 && g > 100 && b > 100) {
      data[i] = bgData[i]
      data[i + 1] = bgData[i + 1]
      data[i + 2] = bgData[i + 2]
    }
  }

  context2.putImageData(imageData, 0, 0)
}

onMounted(() => {
  // init()
})
</script>