专栏首页多云转晴canvas 像素操作

canvas 像素操作

在 canvas 中可以使用 context.drawImage(image,dx,dy) 方法将图片绘制在 canvas 上。将图片绘制上去后,还可以使用 context.getImageData(sx, sy, sw, sh) 方法获取 canvas 区域隐含的像素数据,该方法返回一个 ImageData 对象,里面包含的是 canvas 像素信息。

getImageData 函数参数

这个函数有四个参数,都是必选的。 格式:context.getImageData(sx, sy, sw, sh)。 其中:

  • sx:将要被提取的图像数据矩形区域的左上角 x 坐标;
  • sy:将要被提取的图像数据矩形区域的左上角 y 坐标;
  • sw:将要被提取的图像数据矩形区域的宽度;
  • sy:将要被提取的图像数据矩形区域的高度;
// 获取整个 canvas 画布上的像素信息
var imageData = context.getImageData(0,0,canvas.width,canvas.height);

console.log(imageData);

需要注意的是,如果是使用 image 对象动态生成 img 图片,然后使用 drawImagegetImageData 方法时,chrome 后可能会报图片跨域错误。

const cvs = document.querySelector("#myCvs");
const ctx = cvs.getContext("2d");

var image = new Image(cvs.height);
image.src = "./05.jpg";

// 图片加载好后在获得像素,不然获得不了
image.onload = function(){
    ctx.drawImage(image,0,0);
    var imageData = ctx.getImageData(0,0,cvs.width,cvs.height);

    console.log(imageData);
}

报错信息:

Failed to execute 'getImageData' on 'CanvasRenderingContext2D': The canvas has been tainted by cross-origin data.
    at Image.image.onload

解决办法是写一个本地服务器,把 HTML 文件部署到服务器上。

另一个办法是建立一个文件夹(比如 src 目录),把图片和 HTML 文件(HTML 文件命名为 index.html)放入其中,运行 npx serve ./src 命令。

serve 模块

打开 http:localhost:5000 就不会报错了。serve 模块是一个 npm 包,一个开箱即用的服务端模块。

ImageData 对象中有三个属性:

  • width:canvas 的宽度;
  • height:canvas 的高度;
  • data:指定区域的像素数据;

imageData.data 中的像素数据是一个一维正整数数组(Uint8ClampedArray 类型的数组,即:无符号整型),一个像素信息包含 RGB 三原色信息和透明度。data 数组数据每四个为一组,分别表示 RGB 通道和透明度。这四种值取值都在 0-255 之间。

putImageData() 方法

该方法可以将 imageData 对象绘制到 canvas 上。我们可以用 getImageData 将获取到的 imageData 数据处理后再使用 putImageData 方法重新绘制到 canvas 中。

该方法的参数:ctx.putImageData(imagedata, dx, dy);

  • dx:源图像数据在目标画布中的 x 轴方向的偏移量;
  • dy:源图像数据在目标画布中的 y 轴方向的偏移量;

除这两个参数之外还有四个可选属性,这里不做介绍,可以参考 MDN 文档: https://developer.mozilla.org/zh-CN/docs/Web/API/CanvasRenderingContext2D/putImageData[1]

简单的 canvas 的像素操作

在 CSS 颜色值里,可以使用六位十六进制法表示颜色值,比如:#000000 表示纯黑色,还可以使用 rgb 通道表示法表示一个颜色,格式:rgb(red,green,blue)。当值是 rgb(255,255,255) 是就是纯白色,rgb(255,0,0) 表示红色。rgb 通道的取值在 0-255 之间。在 CSS 当中,还定义了 rgba 颜色值,多出来的 a 表示透明度,只不过取值在 0-1 之间,0 表示透明度为 100%(而在 canvas 的像素中,透明度同样是 0-255 之间)。

下面就介绍几个简单且常见的像素处理结果。原始图片均以下面的彩图为例:

绫小路

灰度处理

使用上面的两个 API 就可以随意操作像素数据了。比如下面的例子,将一个彩色的图像变成灰色的图像:

<body>

    <img src="./img/05.jpg" height="423" width="423" />
    <canvas id="myCvs" height="423" width="423"></canvas>

    <button id="btn">gray</button>

    <script>

        const cvs = document.querySelector("#myCvs");
        const ctx = cvs.getContext("2d");
        const btn = document.querySelector("#btn");

        var image = new Image(cvs.height);
        image.src = "./05.jpg";

        image.onload = function(){
            ctx.drawImage(image,0,0);
        }

        btn.onclick = function(){
            // 获取像素信息
            var imageData = ctx.getImageData(0,0,cvs.width,cvs.height);
            var newImageData = gray(imageData);
            ctx.putImageData(newImageData,0,0);
        }

        // 将图像变成黑白图
        function gray(imageData){
            var data = imageData.data;
            var len = data.length;

            for(let i = 0;i < len;i += 4){
                var avg = 0;
                for(let j = 0;j < 3;j ++){
                    // RGB 颜色求平均值
                    avg += (data[i + j] / 3);
                }
                // 把平均值赋给 RGB
                data[i] = data[i + 1] = data[i + 2] = avg;
            }
            return imageData;
        }

    </script>
</body>

当点击按钮后,彩色图片就会变成灰色。黑白图的原理就是取 RGB 通道的均值,再把均值赋值给 RGB 三个通道。处理结果:

灰度图

需要注意的是,imageData.data 中的数据类型都是无符号整型,做平均运算时很可能会出现小数,不过 JavaScript 会自动进行取整操作,当然你也可以使用 Math.floor 或者 Math.ceil 方法进行取整操作。

纯黑白图

灰度处理后,颜色并不是纯白色或者黑色,而是介于黑与白之间。要想将一张图片处理成“纯”黑白的图片可以这样做:

  • 得到 RGB 通道的颜色平均值;
  • 如果平均值是小于 128(256 / 2),那么 RGB 都取 0(黑色);
  • 如果平均值大于等于 128,那么 RGB 都取值 255(白色);

所以,代码是这样的:

function gray(imageData){
    var data = imageData.data;
    var len = data.length;

    for(let i = 0;i < len;i += 4){
        var avg = 0;
        for(let j = 0;j < 3;j ++){
            avg += (data[i + j] / 3);
        }
        //!判断
        data[i] = data[i + 1] = data[i + 2] = avg < 128 ? 0 : 255;
    }
    return imageData;
}

处理结果:

纯黑白

通过上面也可以实现只有红色通道的图片,原理是只将平均值赋给红色通道,其他通道变成 0。

// 红色蒙版

var redMask = function(imageData){
    var data = imageData.data;
    var len = data.length;
    for (let i = 0; i < len; i += 4) {
        let sum = 0;
        for (let j = 0; j < 3; j++) {
            sum += data[i + j];
        }
        var avg = sum / 3;
        data[i] = avg; // 红色是平均值
        // 绿色和蓝色都设为零
        data[i + 1] = data[i + 2] = 0;
    }
    return imageData;
}

效果:

红色蒙版

可以试着将蓝色、绿色或者透明度设成均值,把别的通道置 0 看看图像变化。

透明度变换

透明度处理使用的是第四个值,方法是将透明度乘以一个加权值,这个加权值在 0-1 之间:

// decimal 取值应在 0-1 之间
var transparency = function(imageData,decimal){
    var data = imageData.data;
    for(let i = 0;i < data.length;i ++){
        // 第三个通道加权
        data[i + 3] *= decimal;
    }
    return imageData;
}

色彩反转

色彩反转的思路是:获得每个像素的 RGB 通道的值,用 255 减去该值,再把算出的结果赋给对应的 RGB 通道。

var colorReversal = function(imageData){
    var data = imageData.data;
    var len = imageData.length;
    for(let i = 0;i < len;i += 4){
        data[i] = 255 - data[i];
        data[i + 1] = 255 - data[i + 1]
        data[i + 2] = 255 - data[i + 2];
    }
    return imageData;
}

处理结果:

色彩反转

复古处理

图片复古处理可以让图片看着有“历史感”,原理是将 RGB 每个通道赋值为三个通道的加权值之和(0-1 之间),

for (let i = 0; i < len; i += 4) {
    let r = data[i],
        g = data[i + 1],
        b = data[i + 2];
    // 加权操作
    data[i] = r * 0.39 + g * 0.76 + b * 0.18;
    data[i + 1] = r * 0.35 + g * 0.68 + b * 0.16;
    data[i + 2] = r * 0.27 + g * 0.35 + b * 0.13;
}

ctx.putImageData(imageData, 0, 0);

效果:

复古

CSS3 中的滤镜

CSS3 中新增了滤镜属性:filter

filter 的取值可以有许多种,比如 blur(px) 可以给图像设置高斯模糊;hue-rotate(deg) 可以给图像应用色相旋转;opacity(%) 可以转化图像的透明程度;saturate(%) 可以转换图像饱和度;详细的介绍可以 参考 MDN 文档[2]

canvas 像素处理有个缺点,就是每次改变图像像素时,不能实时更新,如果要做一个滑动色彩变换,可以使用 CSS3 提供的 filter。思路是:使用 input 标签的 range 类型。range 类型属性有四个:min:该值不得小于 min. 默认值为 0;max:该值将不大于 max. 默认值为 100;step:该值表示滑动步数,预设值为 1。value 表示当前的步数,默认是长度的一半。

<body>
    <img src="./05.jpg" />
    <input value="0" id="range" type="range" min="0" max="100" step="1" />

    <script>
        const img = document.querySelector("img");
        const range = document.querySelector("#range");

        range.oninput = function(){
            // value 就是滑块滑动的距离
            img.style.filter = `blur(${this.value}px)`;
        }
    </script>
</body>

当滑动滑块时图片高斯模糊会平滑改变。当然,也可以使用 change 事件,当鼠标放开时才触发事件。CSS3 中的 filter 属性是很强大的,不足的就是浏览器兼容性现在还不太好。

canvas 视频处理

canvas 中的 drawImage 方法的第一个参数不仅可以传入图片对象,还可以传入 video 对象。

// 获取 video 元素
var video = document.getElementsByTagName("video")[0];
// 将视频绘制到 canvas 上
context.drawImage(video,0,0,cvs.width,cvs.height);

绘制到 canvas 上后只是视频的第一帧图,想要让 canvas 像视频一样播放图片帧,就需要使用 requestAnimationFrame 方法。

首先需要一个 video 标签和 canvas 标签。

<body>
    <video src="./xxx.mp4" muted controls height="423" width="423"></video>
    <canvas id="myCvs" height="423" width="423"></canvas>
</body>

然后用 JS 获取元素进行操作:

const video = document.querySelector("video");
const cvs = document.querySelector("#myCvs");
const ctx = cvs.getContext("2d");

// 处理像素
function greenMask(imageData) {
    var data = imageData.data;
    var len = data.length;
    for (let i = 0; i < len; i += 4) {
        let sum = 0;
        for (let j = 0; j < 3; j++) {
            sum += data[i + j];
        }
        var avg = sum / 3;
        data[i + 2] = avg;
        data[i + 1] = data[i] = 0;
    }
    return imageData;
}

// 该事件是当媒体资源的第一帧加载完成时被触发。
video.onloadeddata = function () {
    // 然后运行 play 函数
    window.requestAnimationFrame(play);
}

// play 函数:
function play(imageData) {
    window.requestAnimationFrame(play);
    // 先绘制图片
    ctx.drawImage(video, 0, 0,cvs.width,cvs.height);

    // 然后获取像素
    var imageData = ctx.getImageData(0,0,cvs.width,cvs.height);
    // 处理像素,然后重新绘制到 canvas 上
    var newImageData = redMask(imageData);
    ctx.putImageData(newImageData,0,0);
}

当然,CSS3 中的 filter 也可以用于 canvas 上:

const video = document.querySelector("video");
const cvs = document.querySelector("#myCvs");
const ctx = cvs.getContext("2d");

// 定义后,canvas 图像会呈现高斯模糊效果
cvs.style.filter = "blur(8px)";

video.onloadeddata = function () {
    window.requestAnimationFrame(play);
}
function play(){
    window.requestAnimationFrame(play);
    ctx.drawImage(video, 0, 0,cvs.width,cvs.height);
    // 就不需要再获取像素然后重新绘制了
}

当获取像素并能进行操作时,可以说几乎任何图像处理操作都可以通过 canvas 完成,可见 canvas 的强大之处,当然,canvas 的强大不只局限于基本的像素操作,图片合成、视频合成以及游戏动画等也是 canvas 的能够胜任的,学好 canvas 就如同打开了一扇新的大门。canvas 像素操作就说到这里。

参考资料

[1]

https://developer.mozilla.org/zh-CN/docs/Web/API/CanvasRenderingContext2D/putImageData: https://developer.mozilla.org/zh-CN/docs/Web/API/CanvasRenderingContext2D/putImageData

[2]

参考 MDN 文档: https://developer.mozilla.org/zh-CN/docs/Web/CSS/filter

本文分享自微信公众号 - Neptune丶(Neptune_mh_0110),作者:多云转晴丶

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2019-12-29

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Ubuntu 环境下 gitlab 安装说明

    然后点击 Ubuntu 的安装过程.进去之后,按照第一步说的运行命令. 这里 使用的 Ubuntu 版本 是 18.04。

    多云转晴
  • JavaScript 严格模式

    严格模式是 ECMAScript5 (ES5)发布的语言新特性。使用严格模式可以限制 JavaScript 的一些语言特性,使用严格模式可以去除在书写代码时的一...

    多云转晴
  • 说说几个 API 和应用案例

    除了 classList.contains 方法之外,还有一个 node.contains 方法,这个方法返回的是一个布尔值,来表示传入的节点是否为该节点的后代...

    多云转晴
  • keras_bert文本多分类

    用户1750490
  • WordPress展示最近更新过的文章,并用邮件通知评论过的用户

    杨逸轩
  • shellcheck 帮助你写出更好的脚本

    首先,可以帮助你提前发现并修复简单的语法错误,节约时间。每次都需要运行才发现写错了一个小地方,确实非常浪费时间。 其次,可以针对你当前不够完善不够健壮的写法,...

    zqb_all
  • 【业界】谷歌利用机器学习删除了Google Play中的70万个垃圾应用,同比2016年增加了70%

    1月30日,Google分享了Google Play努力保护Android用户的细节,他们的工程师、政策专家、产品经理和运营专业人士都在监视商店是否存在误导性和...

    AiTechYun
  • HTC G14解锁S-OFF、刷机、获取ROOT权限

    前期准备工作: 1、安装连接电脑的ADB 驱动,这是连接电脑必要的驱动(如果已安装过或自动安装了,可忽略!) 下载: HTCDriver3.0.0.008_x...

    阳光岛主
  • 分类模型的评价指标(三)

    假设我现在有一个二分类任务,是分析100封邮件是否是垃圾邮件,其中不是垃圾邮件有65封,是垃圾邮件有35封.模型最终给邮件的结论只有两个:是垃圾邮件与 不是垃圾...

    用户5745385
  • 4.2.1指令寻址和数据寻址

    寻址方式是指寻找指令或操作数有效地址的方式,也就是指确定本条指令的数据地址,以及下一条将要执行的指令地址的方式。

    week

扫码关注云+社区

领取腾讯云代金券

玩转腾讯云 有奖征文活动