使用canvas实现魔法摄像头的示例代码

 更新时间:2023年08月07日 08:48:33   作者:万少  
我们用手机的摄像头自拍,很容易实现简单的自拍效果,如复古、黑白等等,其实我们使用web端的JavaScript也是可以实现的,接下来就带领小伙伴实现一个魔法摄像头,并且提供了截图下载功能,需要的朋友可以参考下

脚本之家 / 编程助手:解决程序员“几乎”所有问题!
脚本之家官方知识库 → 点击立即使用

背景

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

魔鬼风格

image-20230806210316856

复古风格

image-20230806211755401

关键技术

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

主要业务流程

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

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

image-20230806213348829

image-20230806213703274

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
<!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上

image-20230806220903756

image-20230806214258903

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
<!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三个分量取平均值)

image-20230806215151375

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

2023-8-62202.gif

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
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
<!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>

点击截图

image-20230806221624692

流程

  1. 将Canvas 的 toDataURL 方法将内容转换为数据 URL。
  2. 创建一个 <a> 元素,并将数据 URL 赋值给其 href 属性。
  3. 设置 <a> 元素的 download 属性为要保存的文件名。
  4. 使用 JavaScript 模拟点击 <a> 元素来触发下载。
1
2
3
4
5
6
7
8
9
10
11
12
13
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();
})

完整代码

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
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
<!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>

以上就是使用canvas实现魔法摄像头的示例代码的详细内容,更多关于canvas 魔法摄像头的资料请关注脚本之家其它相关文章!

蓄力AI

微信公众号搜索 “ 脚本之家 ” ,选择关注

程序猿的那些事、送书等活动等着你

原文链接:https://juejin.cn/post/7264125562393788473

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若内容造成侵权/违法违规/事实不符,请将相关资料发送至 reterry123@163.com 进行投诉反馈,一经查实,立即处理!

相关文章

  • 有关js的变量作用域和this指针的讨论

    有关js的变量作用域和this指针的讨论

    在ECMAScript中,只有两种执行环境,全局环境和函数环境,每个函数都是一个执行环境,包括嵌套函数。换句话说,其他情况下即使变量声明在一对大括号中,在括号外部仍然可以访问这些变量
    2010-12-12
  • 浅谈js中的attributes和Attribute的用法与区别

    浅谈js中的attributes和Attribute的用法与区别

    这篇文章主要介绍了浅谈js中的attributes和Attribute的用法与区别,attributes可以获取一个对象中的一个属性,attributes 属性返回指定节点属性的集合,文中通过示例代码介绍的非常详细,需要的朋友们下面随着小编来一起学习学习吧
    2020-07-07
  • javascript中attachEvent用法实例分析

    javascript中attachEvent用法实例分析

    这篇文章主要介绍了javascript中attachEvent用法,实例分析了javascript中事件绑定的相关技巧,需要的朋友可以参考下
    2015-05-05
  • JavaScript中的document.querySelector()方法使用详解

    JavaScript中的document.querySelector()方法使用详解

    HTML的DOM querySelector()方法可以不需要额外的jQuery等支持,也可以方便的获取DOM元素,语法跟jQuery类似,这篇文章主要给大家介绍了关于JavaScript中document.querySelector()方法使用的相关资料,需要的朋友可以参考下
    2024-06-06
  • 让你的网站可编辑的实现js代码

    让你的网站可编辑的实现js代码

    可以让你编辑浏览器中看到网页可编辑的实现代码。
    2009-10-10
  • JavaScript callback回调函数用法实例分析

    JavaScript callback回调函数用法实例分析

    这篇文章主要介绍了JavaScript callback回调函数用法,结合实例形式分析了callback回调函数的概念、功能、应用场景及相关使用技巧,需要的朋友可以参考下
    2018-05-05
  • JavaScript解八皇后问题的方法总结

    JavaScript解八皇后问题的方法总结

    在国际象棋的8*8棋盘上如何摆放8个皇后使任一皇后无法吃掉其他皇后的问题便是最初的八皇后问题,此后也被不断扩展而作为经典的算法题目,这里我们就来看一下JavaScript解八皇后问题的方法总结
    2016-06-06
  • js限制文本框只能输入数字方法小结

    js限制文本框只能输入数字方法小结

    这篇文章主要分享下js代码限制文本框中只能输入数字的多个实例,学习下js控制文本框中输入数字的方法,需要的朋友可以参考下
    2014-06-06
  • 详解BootStrap中Affix控件的使用及保持布局的美观的方法

    详解BootStrap中Affix控件的使用及保持布局的美观的方法

    Affix是BootStrap中的一个很有用的控件,他能够监视浏览器的滚动条的位置并让你的导航始终都在页面的可视区域。本文重点给大家介绍BootStrap中Affix控件的使用及保持布局的美观的方法,感兴趣的朋友一起看看吧
    2016-07-07
  • 用js实现层随着内容大小动态渐变改变 推荐

    用js实现层随着内容大小动态渐变改变 推荐

    以前做谷歌的小工具时,api里提供了一个很有用的函数,那就是在程序运行时可以使层动态随内容大小而变化,而且是平滑变换,在一些jquery的lightbox里也普遍有这种效果,看起来很酷的样子。
    2009-12-12

最新评论