神奇canvas 带你实现魔法摄像头

简介: 神奇canvas 带你实现魔法摄像头

背景

我们用手机的摄像头自拍,很容易实现 简单的自拍效果,如复古、黑白等等。其实我们使用web端的JavaScript也是可以实现的。接下来就带领小伙伴实现一个魔法摄像头。并且提供了截图下载功能。

魔鬼风格

276K(3OAY]5JE1CCI][(VYF.png

复古风格

305K]Q0H]G8`9`E_A}I0Y(4.png

关键技术

  1. canvas 它可以用于动画、游戏画面、数据可视化、图片编辑以及实时视频处理等方面。
  2. video 用于在 HTML 或者 XHTML 文档中嵌入媒体播放器
  3. navigator.mediaDevices.getUserMedia 用来将摄像头视频转成文件流
  4. requestAnimationFrame 你希望执行一个动画,并且要求浏览器在下次重绘之前调用指定的回调函数更新动画
  5. 小程序 canvas 生成海报 一次搞掂

主要业务流程

  1. 调用摄像头加载画面到video上
  2. 使用canvas将video视频逐帧画到canvas上
  3. 实现canvas滤镜效果
  4. 点击截图

调用摄像头加载画面到video上

3W$G$7@O~N)_X{`Y}7@JJBX.png

}T)PC0RS$(F90G%XDYHWB_J.png

<!DOCTYPE html>
<html>
<head>
  <title>Canvas Demo</title>
</head>
<body>
  <video id="videoElement" autoplay></video>
  <canvas id="canvasElement"></canvas>
  <script>
    // 获取视频元素和画布元素
    const video = document.getElementById('videoElement');
    // 检查浏览器是否支持 getUserMedia API
    if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
      // 请求访问摄像头
      navigator.mediaDevices.getUserMedia({ video: true })
        .then(function (stream) {
          // 将视频流绑定到视频元素上
          video.srcObject = stream;
          // 开始绘制视频画面到画布上
          requestAnimationFrame(drawFrame);
        })
        .catch(function (error) {
          console.error('无法访问摄像头:', error);
        });
    } else {
      console.error('浏览器不支持 getUserMedia API');
    }
  </script>
</body>
</html>

将video视频逐帧画到canvas上

COHQ$OBBF1`9Z7E%NEZ]~CA.png

R}A62]`%%U)E~9E]O8{EPZ4.png

<!DOCTYPE html>
<html>
<head>
  <title>Canvas Demo</title>
</head>
<body>
  <video id="videoElement" autoplay></video>
  <canvas id="canvasElement"></canvas>
  <script>
    // 获取视频元素和画布元素
    const video = document.getElementById('videoElement');
    const canvas = document.getElementById('canvasElement');
    const ctx = canvas.getContext('2d');
    // 当视频元素加载完成后执行
    video.addEventListener('loadedmetadata', function () {
      // 设置画布大小与视频尺寸相同
      canvas.width = video.videoWidth;
      canvas.height = video.videoHeight;
    });
    // 在每一帧绘制视频画面到画布上 一秒描绘60次
    function drawFrame() {
      ctx.drawImage(video, 0, 0, canvas.width, canvas.height);// 将视频画在画布上
      requestAnimationFrame(drawFrame);
    }
    // 检查浏览器是否支持 getUserMedia API
    if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
      // 请求访问摄像头
      navigator.mediaDevices.getUserMedia({ video: true })
        .then(function (stream) {
          // 将视频流绑定到视频元素上
          video.srcObject = stream;
          // 开始绘制视频画面到画布上
          requestAnimationFrame(drawFrame);
        })
        .catch(function (error) {
          console.error('无法访问摄像头:', error);
        });
    } else {
      console.error('浏览器不支持 getUserMedia API');
    }
  </script>
</body>
</html>

实现canvas滤镜效果

以下代码没有进行过多封装,后续会出一篇使用面向对象和设计模式的续集来优化代码

本次案例实现的滤镜效果主要有 反转 黑白 亮度 复古 红色 绿色 蓝色 透明 马赛克 渐变

在canvas中,可以通过 getImageData 获取到当前画布上所有的像素点,它以4个点为一组,表示画布上当前坐标点的 R G B A (红、绿、蓝、透明度)。我们要实现的滤镜效果,几乎都是直接对该像素点进行操作。如 黑白效果 将每个像素的RGB值转换为灰度值(R、G、B三个分量取平均值)

GM0_786JR2~OY{@C}J3`L%P.png

<!DOCTYPE html>
<html>
<head>
  <title>Canvas Demo</title>
</head>
<body>
  <video id="videoElement" autoplay></video>
  <canvas id="canvasElement"></canvas>
  <script>
    // 获取视频元素和画布元素
    const video = document.getElementById('videoElement');
    const canvas = document.getElementById('canvasElement');
    const ctx = canvas.getContext('2d');
    const buttons = document.querySelectorAll("button[data-type]");
    const takePhoto = document.querySelector("#takePhoto")
    let drawType = ""
    // 当视频元素加载完成后执行
    video.addEventListener('loadedmetadata', function () {
      // 设置画布大小与视频尺寸相同
      canvas.width = video.videoWidth;
      canvas.height = video.videoHeight;
    });
    // 在每一帧绘制视频画面到画布上
    function drawFrame() {
      ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
      let imageObj = ctx.getImageData(0, 0, canvas.width, canvas.height);
        //  黑白效果
      for (let i = 0; i < imageObj.data.length; i += 4) {
        const average = (imageObj.data[i + 0] + imageObj.data[i + 1] + imageObj.data[i + 2] + imageObj.data[i + 3]) / 3;
        imageObj.data[i + 0] = average;//红
        imageObj.data[i + 1] = average; //绿
        imageObj.data[i + 2] = average; //蓝
      }
      ctx.putImageData(imageObj, 0, 0)
      requestAnimationFrame(drawFrame);
    }
    // 检查浏览器是否支持 getUserMedia API
    if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
      // 请求访问摄像头
      navigator.mediaDevices.getUserMedia({ video: true })
        .then(function (stream) {
          // 将视频流绑定到视频元素上
          video.srcObject = stream;
          // 开始绘制视频画面到画布上
          requestAnimationFrame(drawFrame);
        })
        .catch(function (error) {
          console.error('无法访问摄像头:', error);
        });
    } else {
      console.error('浏览器不支持 getUserMedia API');
    }
  </script>
</body>
</html>

所有滤镜效果总结如下

  1. 反转效果:
  • 原理:通过将每个像素的RGB值取反来实现反转效果。
  • 实现方式:使用getImageData获取图像数据,然后遍历每个像素,将每个像素的RGB值取反,再使用putImageData将修改后的数据绘制回Canvas。
  1. 黑白效果:
  • 原理:将每个像素的RGB值转换为灰度值,使图像变为黑白。
  • 实现方式:使用getImageData获取图像数据,然后遍历每个像素,将每个像素的RGB值转换为灰度值(R、G、B三个分量取平均值),再使用putImageData将修改后的数据绘制回Canvas。
  1. 亮度效果:
  • 原理:调整每个像素的亮度值,使图像变亮或变暗。
  • 实现方式:使用getImageData获取图像数据,然后遍历每个像素,调整每个像素的亮度值,再使用putImageData将修改后的数据绘制回Canvas。
  1. 复古效果:
  • 原理:通过调整每个像素的色调、饱和度和亮度,使图像呈现复古效果。
  • 实现方式:使用getImageData获取图像数据,然后遍历每个像素,调整每个像素的色调、饱和度和亮度,再使用putImageData将修改后的数据绘制回Canvas。
  1. 红色、绿色、蓝色效果:
  • 原理:增加或减少每个像素的红色、绿色、蓝色分量的值,使图像呈现相应颜色的效果。
  • 实现方式:使用getImageData获取图像数据,然后遍历每个像素,增加或减少每个像素的红色、绿色、蓝色分量的值,再使用putImageData将修改后的数据绘制回Canvas。
  1. 透明效果:
  • 原理:调整每个像素的透明度值,使图像呈现透明效果。
  • 实现方式:使用getImageData获取图像数据,然后遍历每个像素,调整每个像素的透明度值,再使用putImageData将修改后的数据绘制回Canvas。
  1. 马赛克效果:
  • 原理:将图像分割为小块,每个小块的像素值设置为该小块内像素的平均值,从而实现马赛克效果。
  • 实现方式:使用getImageData获取图像数据,然后将图像分割为小块,计算每个小块内像素的平均值,再将该小块内所有像素的值设置为该平均值,最后使用putImageData将修改后的数据绘制回Canvas。
  1. 马赛克效果
  • 由于实际操作过程中,上述马赛克效果处理性能比较底下,这里用来一个取巧的效果来实现。就是先用canvas将画面画小,然后再将画面缩放来实现一个模糊效果,间接实现马赛克效果
  1. 渐变滤镜效果:
  • 原理:通过在图像上应用渐变效果,使图像呈现渐变色的效果。
  • 实现方式:使用createLinearGradientcreateRadialGradient创建渐变对象,然后使用渐变对象作为填充样式,绘制图像到Canvas上。

X(89VC4H~L5~YMQZ0O_P~GO.png

<!DOCTYPE html>
<html>
<head>
  <title>Canvas Demo</title>
  <style>
    button {
      border-radius: 10px;
      display: inline-flex;
      align-items: center;
      justify-content: center;
      cursor: pointer;
      overflow: hidden;
      user-select: none;
      outline: none;
      border: none;
      padding: 16px;
      background-color: #1d93ab;
      color: #fff;
    }
    button:focus {
      background-color: #e88f21
    }
  </style>
</head>
<body>
  <div>
    <button data-type="gray">反转</button>
    <button data-type="blackwhite">黑白</button>
    <button data-type="brightness">亮度</button>
    <button data-type="sepia">复古</button>
    <button data-type="redMask">红色</button>
    <button data-type="greenMask">绿色</button>
    <button data-type="blueMask">蓝色</button>
    <button data-type="opacity">透明</button>
    <button data-type="mosaic">马赛克</button>
    <button data-type="linearGradient">渐变</button>
  </div>
  <video id="videoElement" autoplay></video>
  <canvas id="canvasElement"></canvas>
  <script>
    // 获取视频元素和画布元素
    const video = document.getElementById('videoElement');
    const canvas = document.getElementById('canvasElement');
    const ctx = canvas.getContext('2d');
    const buttons = document.querySelectorAll("button[data-type]");
    let drawType = ""
    // 当视频元素加载完成后执行
    video.addEventListener('loadedmetadata', function () {
      // 设置画布大小与视频尺寸相同
      canvas.width = video.videoWidth;
      canvas.height = video.videoHeight;
    });
    // 在每一帧绘制视频画面到画布上
    function drawFrame() {
      ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
      let imageObj = ctx.getImageData(0, 0, canvas.width, canvas.height);
      if (drawType === "gray") {
        // 反转
        for (let i = 0; i < imageObj.data.length; i += 4) {
          imageObj.data[i + 0] = 255 - imageObj.data[i + 0];
          imageObj.data[i + 1] = 255 - imageObj.data[i + 1];
          imageObj.data[i + 2] = 255 - imageObj.data[i + 2];
        }
        ctx.putImageData(imageObj, 0, 0)
      }
      if (drawType === "blackwhite") {
        // 黑白
        for (let i = 0; i < imageObj.data.length; i += 4) {
          const average = (imageObj.data[i + 0] + imageObj.data[i + 1] + imageObj.data[i + 2] + imageObj.data[i + 3]) / 3;
          imageObj.data[i + 0] = average;//红
          imageObj.data[i + 1] = average; //绿
          imageObj.data[i + 2] = average; //蓝
        }
        ctx.putImageData(imageObj, 0, 0)
      }
      if (drawType === "brightness") {
        // 亮度
        for (let i = 0; i < imageObj.data.length; i += 4) {
          const a = 50;
          imageObj.data[i + 0] += a;
          imageObj.data[i + 1] += a;
          imageObj.data[i + 2] += a;
        }
        ctx.putImageData(imageObj, 0, 0)
      }
      if (drawType === "sepia") {
        // 复古
        for (let i = 0; i < imageObj.data.length; i += 4) {
          const r = imageObj.data[i + 0];
          const g = imageObj.data[i + 1];
          const b = imageObj.data[i + 2];
          imageObj.data[i + 0] = r * 0.39 + g * 0.76 + b * 0.18;
          imageObj.data[i + 1] = r * 0.35 + g * 0.68 + b * 0.16;
          imageObj.data[i + 2] = r * 0.27 + g * 0.53 + b * 0.13;
        }
        ctx.putImageData(imageObj, 0, 0)
      }
      if (drawType === "redMask") {
        // 红色
        for (let i = 0; i < imageObj.data.length; i += 4) {
          const r = imageObj.data[i + 0]
          const g = imageObj.data[i + 1]
          const b = imageObj.data[i + 2]
          const average = (r + g + b) / 3
          imageObj.data[i + 0] = average
          imageObj.data[i + 1] = 0
          imageObj.data[i + 2] = 0
        }
        ctx.putImageData(imageObj, 0, 0)
      }
      if (drawType === "greenMask") {
        // 绿色
        for (let i = 0; i < imageObj.data.length; i += 4) {
          const r = imageObj.data[i + 0]
          const g = imageObj.data[i + 1]
          const b = imageObj.data[i + 2]
          const average = (r + g + b) / 3
          imageObj.data[i + 0] = 0
          imageObj.data[i + 1] = average
          imageObj.data[i + 2] = 0
        }
        ctx.putImageData(imageObj, 0, 0)
      }
      if (drawType === "blueMask") {
        // 蓝色
        for (let i = 0; i < imageObj.data.length; i += 4) {
          const r = imageObj.data[i + 0]
          const g = imageObj.data[i + 1]
          const b = imageObj.data[i + 2]
          const average = (r + g + b) / 3
          imageObj.data[i + 0] = 0
          imageObj.data[i + 1] = 0
          imageObj.data[i + 2] = average
        }
        ctx.putImageData(imageObj, 0, 0)
      }
      if (drawType === "opacity") {
        // 透明
        for (let i = 0; i < imageObj.data.length; i += 4) {
          imageObj.data[i + 3] = imageObj.data[i + 3] * 0.3;
        }
        ctx.putImageData(imageObj, 0, 0)
      }
      if (drawType === "linearGradient") {
        // 渐变
        const data = imageObj.data;
        // 遍历每个像素
        for (let i = 0; i < data.length; i += 4) {
          const x = (i / 4) % canvas.width; // 当前像素的 x 坐标
          const y = Math.floor(i / (4 * canvas.width)); // 当前像素的 y 坐标
          // 计算当前像素的颜色值
          const r = x / canvas.width * 255; // 红色分量
          const g = y / canvas.height * 255; // 绿色分量
          const b = 128; // 蓝色分量
          const a = 100; // 不透明度
          // 设置当前像素的颜色值
          data[i] = r; // 红色分量
          data[i + 1] = g; // 绿色分量
          data[i + 2] = b; // 蓝色分量
          data[i + 3] = a; // 不透明度
        }
        ctx.putImageData(imageObj, 0, 0)
      }
      if (drawType === "mosaic") {
        // 马赛克
        ctx.imageSmoothingEnabled = false; // 禁用图像平滑处理
        const tileSize = 10; // 马赛克块的大小
        // 缩小马赛克块
        ctx.drawImage(canvas, 0, 0, canvas.width, canvas.height, 0, 0, canvas.width / tileSize, canvas.height / tileSize);
        // 放大回原来的大小
        ctx.drawImage(canvas, 0, 0, canvas.width / tileSize, canvas.height / tileSize, 0, 0, canvas.width, canvas.height);
      }
      requestAnimationFrame(drawFrame);
      // setTimeout(drawFrame, 1000);
    }
    // 检查浏览器是否支持 getUserMedia API
    if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
      // 请求访问摄像头
      navigator.mediaDevices.getUserMedia({ video: true })
        .then(function (stream) {
          // 将视频流绑定到视频元素上
          video.srcObject = stream;
          // 开始绘制视频画面到画布上
          requestAnimationFrame(drawFrame);
        })
        .catch(function (error) {
          console.error('无法访问摄像头:', error);
        });
    } else {
      console.error('浏览器不支持 getUserMedia API');
    }
    buttons.forEach(button => {
      button.addEventListener("click", function (e) {
        drawType = e.target.dataset.type;
      })
    })
  </script>
</body>
</html>

点击截图

_VE}`~C(7JKR3R~KE[S{4N3.png

流程

  1. 将Canvas 的 toDataURL 方法将内容转换为数据 URL。
  2. 创建一个 <a> 元素,并将数据 URL 赋值给其 href 属性。
  3. 设置 <a> 元素的 download 属性为要保存的文件名。
  4. 使用 JavaScript 模拟点击 <a> 元素来触发下载。


const takePhoto = document.querySelector("#takePhoto")// 截图 按钮
takePhoto.addEventListener('click', function (e) {
  // 绘制原始 Canvas 的内容到新的 Canvas 上
  ctx.drawImage(canvas, 0, 0, canvas.width, canvas.height);
  // 将内容转换为数据 URL
  const dataURL = canvas.toDataURL();
  // 创建一个 <a> 元素并设置属性
  const link = document.createElement('a');
  link.href = dataURL;
  link.download = 'screenshot.png'; // 设置要保存的文件名
  // 模拟点击 <a> 元素来触发下载
  link.click();
})


完整代码

<!DOCTYPE html>
<html>
<head>
  <title>Canvas Demo</title>
  <style>
    button {
      border-radius: 10px;
      display: inline-flex;
      align-items: center;
      justify-content: center;
      cursor: pointer;
      overflow: hidden;
      user-select: none;
      outline: none;
      border: none;
      padding: 16px;
      background-color: #1d93ab;
      color: #fff;
    }
    button:focus {
      background-color: #e88f21
    }
  </style>
</head>
<body>
  <div>
    <button data-type="gray">反转</button>
    <button data-type="blackwhite">黑白</button>
    <button data-type="brightness">亮度</button>
    <button data-type="sepia">复古</button>
    <button data-type="redMask">红色</button>
    <button data-type="greenMask">绿色</button>
    <button data-type="blueMask">蓝色</button>
    <button data-type="opacity">透明</button>
    <button data-type="mosaic">马赛克</button>
    <button data-type="linearGradient">渐变</button>
    <button id="takePhoto">拍摄</button>
  </div>
  <video id="videoElement" autoplay></video>
  <canvas id="canvasElement"></canvas>
  <script>
    // 获取视频元素和画布元素
    const video = document.getElementById('videoElement');
    const canvas = document.getElementById('canvasElement');
    const ctx = canvas.getContext('2d');
    const buttons = document.querySelectorAll("button[data-type]");
    const takePhoto = document.querySelector("#takePhoto")// 截图 按钮
    let drawType = ""
    // 当视频元素加载完成后执行
    video.addEventListener('loadedmetadata', function () {
      // 设置画布大小与视频尺寸相同
      canvas.width = video.videoWidth;
      canvas.height = video.videoHeight;
    });
    // 在每一帧绘制视频画面到画布上
    function drawFrame() {
      ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
      let imageObj = ctx.getImageData(0, 0, canvas.width, canvas.height);
      if (drawType === "gray") {
        // 反转
        for (let i = 0; i < imageObj.data.length; i += 4) {
          imageObj.data[i + 0] = 255 - imageObj.data[i + 0];
          imageObj.data[i + 1] = 255 - imageObj.data[i + 1];
          imageObj.data[i + 2] = 255 - imageObj.data[i + 2];
        }
        ctx.putImageData(imageObj, 0, 0)
      }
      if (drawType === "blackwhite") {
        // 黑白
        for (let i = 0; i < imageObj.data.length; i += 4) {
          const average = (imageObj.data[i + 0] + imageObj.data[i + 1] + imageObj.data[i + 2] + imageObj.data[i + 3]) / 3;
          imageObj.data[i + 0] = average;//红
          imageObj.data[i + 1] = average; //绿
          imageObj.data[i + 2] = average; //蓝
        }
        ctx.putImageData(imageObj, 0, 0)
      }
      if (drawType === "brightness") {
        // 亮度
        for (let i = 0; i < imageObj.data.length; i += 4) {
          const a = 50;
          imageObj.data[i + 0] += a;
          imageObj.data[i + 1] += a;
          imageObj.data[i + 2] += a;
        }
        ctx.putImageData(imageObj, 0, 0)
      }
      if (drawType === "sepia") {
        // 复古
        for (let i = 0; i < imageObj.data.length; i += 4) {
          const r = imageObj.data[i + 0];
          const g = imageObj.data[i + 1];
          const b = imageObj.data[i + 2];
          imageObj.data[i + 0] = r * 0.39 + g * 0.76 + b * 0.18;
          imageObj.data[i + 1] = r * 0.35 + g * 0.68 + b * 0.16;
          imageObj.data[i + 2] = r * 0.27 + g * 0.53 + b * 0.13;
        }
        ctx.putImageData(imageObj, 0, 0)
      }
      if (drawType === "redMask") {
        // 红色
        for (let i = 0; i < imageObj.data.length; i += 4) {
          const r = imageObj.data[i + 0]
          const g = imageObj.data[i + 1]
          const b = imageObj.data[i + 2]
          const average = (r + g + b) / 3
          imageObj.data[i + 0] = average
          imageObj.data[i + 1] = 0
          imageObj.data[i + 2] = 0
        }
        ctx.putImageData(imageObj, 0, 0)
      }
      if (drawType === "greenMask") {
        // 绿色
        for (let i = 0; i < imageObj.data.length; i += 4) {
          const r = imageObj.data[i + 0]
          const g = imageObj.data[i + 1]
          const b = imageObj.data[i + 2]
          const average = (r + g + b) / 3
          imageObj.data[i + 0] = 0
          imageObj.data[i + 1] = average
          imageObj.data[i + 2] = 0
        }
        ctx.putImageData(imageObj, 0, 0)
      }
      if (drawType === "blueMask") {
        // 蓝色
        for (let i = 0; i < imageObj.data.length; i += 4) {
          const r = imageObj.data[i + 0]
          const g = imageObj.data[i + 1]
          const b = imageObj.data[i + 2]
          const average = (r + g + b) / 3
          imageObj.data[i + 0] = 0
          imageObj.data[i + 1] = 0
          imageObj.data[i + 2] = average
        }
        ctx.putImageData(imageObj, 0, 0)
      }
      if (drawType === "opacity") {
        // 透明
        for (let i = 0; i < imageObj.data.length; i += 4) {
          imageObj.data[i + 3] = imageObj.data[i + 3] * 0.3;
        }
        ctx.putImageData(imageObj, 0, 0)
      }
      if (drawType === "linearGradient") {
        // 渐变
        const data = imageObj.data;
        // 遍历每个像素
        for (let i = 0; i < data.length; i += 4) {
          const x = (i / 4) % canvas.width; // 当前像素的 x 坐标
          const y = Math.floor(i / (4 * canvas.width)); // 当前像素的 y 坐标
          // 计算当前像素的颜色值
          const r = x / canvas.width * 255; // 红色分量
          const g = y / canvas.height * 255; // 绿色分量
          const b = 128; // 蓝色分量
          const a = 100; // 不透明度
          // 设置当前像素的颜色值
          data[i] = r; // 红色分量
          data[i + 1] = g; // 绿色分量
          data[i + 2] = b; // 蓝色分量
          data[i + 3] = a; // 不透明度
        }
        ctx.putImageData(imageObj, 0, 0)
      }
      if (drawType === "mosaic") {
        // 马赛克
        ctx.imageSmoothingEnabled = false; // 禁用图像平滑处理
        const tileSize = 10; // 马赛克块的大小
        // 缩小马赛克块
        ctx.drawImage(canvas, 0, 0, canvas.width, canvas.height, 0, 0, canvas.width / tileSize, canvas.height / tileSize);
        // 放大回原来的大小
        ctx.drawImage(canvas, 0, 0, canvas.width / tileSize, canvas.height / tileSize, 0, 0, canvas.width, canvas.height);
      }
      requestAnimationFrame(drawFrame);
      // setTimeout(drawFrame, 1000);
    }
    // 检查浏览器是否支持 getUserMedia API
    if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
      // 请求访问摄像头
      navigator.mediaDevices.getUserMedia({ video: true })
        .then(function (stream) {
          // 将视频流绑定到视频元素上
          video.srcObject = stream;
          // 开始绘制视频画面到画布上
          requestAnimationFrame(drawFrame);
        })
        .catch(function (error) {
          console.error('无法访问摄像头:', error);
        });
    } else {
      console.error('浏览器不支持 getUserMedia API');
    }
    buttons.forEach(button => {
      button.addEventListener("click", function (e) {
        drawType = e.target.dataset.type;
      })
    })
    takePhoto.addEventListener('click', function (e) {
      // 绘制原始 Canvas 的内容到新的 Canvas 上
      ctx.drawImage(canvas, 0, 0, canvas.width, canvas.height);
      // 将内容转换为数据 URL
      const dataURL = canvas.toDataURL();
      // 创建一个 <a> 元素并设置属性
      const link = document.createElement('a');
      link.href = dataURL;
      link.download = 'screenshot.png'; // 设置要保存的文件名
      // 模拟点击 <a> 元素来触发下载
      link.click();
    })
  </script>
</body>
</html>
目录
相关文章
|
前端开发 数据安全/隐私保护 容器
|
2月前
|
传感器 API Android开发
Android摄像头采集选Camera1还是Camera2?
Camera1与Camera2是Android平台上的两种摄像头API。Camera1(API1)在Android 5.0后被标记为过时,新项目应优先选用Camera2(API2)。Camera2提供了更精细的控制选项,如曝光时间、ISO感光度等;支持多摄像头管理;采用异步操作提高应用响应速度;并支持RAW图像捕获及实时图像处理。此外,它还具备更好的适配性和扩展性,适用于各类应用场景,如相机应用开发、视频通话和计算机视觉等。因此,在现代Android开发中推荐使用Camera2。
|
5月前
|
前端开发
canvas详解06-合成
canvas详解06-合成
58 2
|
5月前
|
存储 前端开发
canvas详解05-变形
canvas详解05-变形
68 2
|
5月前
|
前端开发 API
canvas详解03-绘制图像和视频
canvas详解03-绘制图像和视频
74 1
|
前端开发 JavaScript Shell
|
前端开发 JavaScript 容器
前端|利用<canvas>画布制作地球轨道
前端|利用<canvas>画布制作地球轨道
225 0
|
存储 前端开发 算法
canvas 处理图像(下)
canvas 处理图像(下)
canvas 处理图像(下)
|
存储 Web App开发 编解码
分享一个canvas录屏方案
对比Web开发和原生开发,通常APP被说到的优势在于两点:1.交互体验更流畅。2.硬件支持更友好。随着HTML5的发展,我们可以通过浏览器做的事情会越来越多,之前只能原生开发做的事情通过浏览器也能实现
|
存储 移动开发 前端开发
canvas 处理图像(上)
canvas 处理图像(上)