游客无体验权限,请先登录。
授权微博登录
授权Github登录
···

取色实时画面

基于MediaDevice这一Web API捕获摄像头拍摄的实时画面,将视频画面即时渲染至Canvas画布上,基于Airglass.js处理与取色相关的交互操作。

渲染视频捕获画面

调用MediaDevices API的getUserMedia()方法,用户代理会自动处理用户是否授权开发者访问音视频输入流,开发者只需编写授权成功和授权失败后的处理函数。当授权成功后,开发者就能在处理函数中拿到stream输入流。将输入流赋值给HTML5中的视频标签 <video></video>对应的DOM元素的srcObject属性,然后再调用它的 play() 方法即可实时播放从用户摄像头捕获的画面。

let videoEl = document.querySelector('#video');

navigator.mediaDevices.getUserMedia({
  audio: true,
  video: true
})
.then(stream => {
  videoEl.srcObject = stream;
  videoEl.play();
});

Canvas API的 drawImage() 方法的第一个参数,不单单能传递一个Image实例,还能传递一个Video实例。开发者想要操作实时画面中的像素,就需要想办法将视频画面实时地渲染到Canvas画布中。捕获到的视频画面的尺寸受各式各样的硬件设备配置参数的影响,Canvas画布的尺寸又需要适配用户的显示设备,所以,将视频画面渲染到Canvas上就需要一套算法适配各式各样的视频画面尺寸。

(function render(){
  requestAnimationFrame(render);
  if (!videoEl.videoWidth || !videoEl.videoHeight) return;
  let x = 0;
  let y = 0;
  let width = videoEl.videoWidth;
  let height = videoEl.videoHeight;
  let videoRatio = videoEl.videoWidth / videoEl.videoHeight;
  let agRatio = ag.width / ag.height;
  if (agRatio > videoRatio) {
    width = ag.width;
    height = parseInt(width / videoRatio);
    if (height > ag.height) {
      y = parseInt((ag.height - height) / 2);
    }
  } else {
    height = ag.height;
    width = parseInt(videoRatio * height);
    if (width > ag.width) {
      x = parseInt((ag.width - width) / 2);
    }
  }
  realtimeCtx.drawImage(videoEl, 0, 0, videoEl.videoWidth, videoEl.videoHeight, x, y, width, height);
})();

取色器组件

得益于Airglass.js对发生在Canvas画布上的丰富的事件类型的高效处理和友好反馈,比如TouchStart、TouchMove、TouchEnd等事件,开发者可以轻松实现拖拽取色器的能力。Canvas API提供的 getImageData() 方法,让开发者能获取单个像素或一块区域所有像素的颜色信息。

开发者有能力继承 airglass.Renderable 类实现自定义的可渲染组件,比如自定义的取色器组件。由于取色器组件需要响应用户触发的的TouchStart和TouchMove事件,所以取色器组件必须包含 path 属性,用以存储当前取色器发生交互的路径区域。

const ColorSelector = airglass.extend(airglass.Renderable, {
  _constructor(params) {
    this.x = params && params.x || 0;
    this.y = params && params.y || 0;
    this.size = params && params.size || 16;
    this.sourceCtx = params.sourceCtx;
    this.path = new Path2D;
    this.r = 0;
    this.g = 0;
    this.b = 0;
  },
  draw(ctx) {
    let imgData = this.sourceCtx.getImageData(this.x, this.y, 1, 1);
    this.r = imgData.data[0];
    this.g = imgData.data[1];
    this.b = imgData.data[2];
    this.path = new Path2D;
    this.path.arc(this.x, this.y, this.size, 0, Math.PI * 2, true);
    ctx.save();
    ctx.fillStyle = `rgb(${this.r}, ${this.g}, ${this.b})`;
    ctx.strokeStyle = '#fff';
    ctx.lineWidth = 3;
    ctx.fill(this.path);
    ctx.stroke(this.path);
    ctx.restore();
  }
});

常见Canvas图像处理算法

首先提取整个Canvas画布的像素数据,之后循环遍历每一个像素的颜色值,其中每4个数字为一组,这4个数字分表代表R(红色)、G(绿色)、B(蓝色)和A(不透明度),每一组数字表示一个像素的颜色,这4个数字的取值范围在0~255之间。修改每个像素的颜色值时,并不是凭空设置,而是在每个像素的原有颜色值的基础上,经过一些计算,得到每个像素最终的颜色值。Canvas API的 putImageData() 方法让开发者能将修改完颜色后的像素渲染到Canvas画布中。

  1. 负片效果,又称底片效果、反相效果、反色效果。因为R、G、B三个颜色通道的数值取值范围在0~255之间,因此,用每个像素的3个颜色通道的最大值255减去该通过的原始值,就能得到每个像素反色后应该设置的3个颜色值。比如某个像素的红色通道值为5,说明这个像素的红色很少,用255减去5得到250,最终这个像素的红色通道值变成了250,那么这个像素的红色看起来就很多。所以负片效果的本质就是将每个像素原本很浓的颜色变淡,将原本很淡的颜色变浓,从宏观来看,整幅图像就有了底片的效果。

const imgData = realtimeCtx.getImageData(0, 0, ag.width, ag.height);
for (let i = 0; i < imgData.data.length; i += 4) {
  let r = imgData.data[i];
  let g = imgData.data[i + 1];
  let b = imgData.data[i + 2];
  let a = imgData.data[i + 3];
  imgData.data[i] = 255 - r;
  imgData.data[i + 1] = 255 - g;
  imgData.data[i + 2] = 255 - b;
  imgData.data[i + 3] = 255;
}
  1. 灰度效果。灰度并不是将整幅图像的所有像素的RGB通道都设置为同一个0~255之间的数值,这会让整幅图像变成灰色。真正的灰度效果是让每一个像素的RGB通道值“齐头并进”——计算并得出同一个值,而这个值在各个像素之间却不尽相同。

const imgData = realtimeCtx.getImageData(0, 0, ag.width, ag.height);
for (let i = 0; i < imgData.data.length; i += 4) {
  let red = imgData.data[i];
  let green = imgData.data[i + 1];
  let blue = imgData.data[i + 2];
  let gray = 0.3 * red + 0.59 * green + 0.11 * blue;
  imgData.data[i] = gray;
  imgData.data[i + 1] = gray;
  imgData.data[i + 2] = gray;
}
  1. 单色效果。制造单色效果,只需把R、G、B这3个颜色通道中的任意2个关闭即可。以红色通道为例,比如将红色通道设置为255,就是将红色通道完全打开,就像一个很通畅的管道,纯红色全量通过,反映到每一个像素上,图像偏红。若给它设置为0,就是将红色通道完全关闭,就像一个完全堵塞住的管道,红色全部无法通过,反映到每一个像素上,图像一点红色都没有。若设置为0~255之间的值,值越接近255,红色越纯越亮。未必一定要将3个通道中的某两个通过关闭来营造纯红、纯绿、纯蓝这三种单色效果,如果想制造橙色、青色、紫色的单色效果,就需要RGB三原色共同参与,恰当调和。

const imgData = realtimeCtx.getImageData(0, 0, ag.width, ag.height);
for (let i = 0; i < imgData.data.length; i += 4) {
  let red = imgData.data[i];
  let green = imgData.data[i + 1];
  let blue = imgData.data[i + 2];
  imgData.data[i] = red;
  imgData.data[i + 1] = 0;
  imgData.data[i + 2] = 0;
}
  1. 非黑即白效果。

const imgData = realtimeCtx.getImageData(0, 0, ag.width, ag.height);
for (let i = 0; i < imgData.data.length; i += 4) {
  let red = imgData.data[i];
  let green = imgData.data[i + 1];
  let blue = imgData.data[i + 2];
  let gray = 0.6 * red + 0.59 * green + 0.11 * blue;
  let black;
  gray > 100 ? black = 255 : black = 0;
  imgData.data[i] = black;
  imgData.data[i + 1] = black;
  imgData.data[i + 2] = black;
}
视频加载中...
《取色实时画面》使用指南
雷达扫描动效

基于Airglass.js

我是我的作品

个人简历

一直在路上

心上到路上的“旅行”

待办事项

由原生JS实现的视图组件

项目管理平台

基于Vue.js

甘特图在线编辑

基于Airglass.js

Blender3D模型渲染

基于Three.js渲染

元胞自动机

基于Airglass.js

JS原型链可视化

基于Airglass.js

重构图像在线标注

基于Airglass.js

图像在线标注工具

基于原生Canvas实现

极坐标地图

Canvas缓动动画实验

类星系关系可视化

Canvas交互事件触发

趣玩周易64卦

国学小项目

日程计划组件

基于React.js

家谱可视化

基于Airglass.js

Airglass.js官方API

我的Canvas库使用参考