前言

本文是接续系列教程的extra1,主要是介绍颜色系统在canvas中的应用。
本来是与extra1一起成文的,因为segmentfault莫名其妙的字数限制bug只能分割放送了。

canvas操纵像素

你如果认为canvas只是画图工具,那接下来的操作会颠覆你的认知。canvas提供api可以获取画布上任何一个像素,并可以自由的操作他们。

获取像素

直接访问像素的功能由canvas上下文中的ImageData对象提供,它提供了以下一组方法,都会返回ImageData对象。

  • getImageData()接受x轴坐标、y轴坐标、宽度、高度四个参数,获取画布上这个矩形区域的像素数据;
  • createImageData()可凭空创建指定宽高的矩形区域,初始是黑色,也可以输入一个ImageData对象用于创建一个同样大小的区域,但注意不会复制像素数据
context.getImageData(x, y, width, height);
context.createImageData(width, height);
context.createImageData(anothorImageData);

获取到的ImageData对象中data属性是一个一维数组,乍看乱糟糟的,但细看你会发现其实这就是RGBA的颜色数据,也就是数组中每个四位就是一个像素的颜色数据,这里注意一下透明度A也是0~255,不是CSS里简化过的0~1

    • *

举个例子
现在假定在一个纯红色区域取一块2*2的矩形,我们得到的像素数据是:

let pixels = [255, 0, 0, 255, 255, 0, 0, 255,
255, 0, 0, 255, 255, 0, 0, 255]

他们与图像的对应关系是从左到右,从上到下,大概就像上面代码格式化这样,如图所示:

根据4对1的对应关系,我们很容易就能写出遍历的办法,offset就相当于指针,每次移动4位,代码如下:

for (let offset = 0, len = pixels.length; offset < len; offset += 4) {
r = pixels[offset];
g = pixels[offset + 1];
b = pixels[offset + 2];
a = pixels[offset + 3];
}

当需要访问特定坐标的像素时,可以使用如下公式,其中xpos是像素点在该区域的x坐标;ypos是像素点在该区域的y坐标,imagedata.width是指区域横向有多少像素。

let offset = (xpos + ypos * imagedata.width) * 4;
let r = pixels[offset];
let g = pixels[offset + 1];
let b = pixels[offset + 2];
let a = pixels[offset + 3];

绘制像素

可以将修改过的ImageData对象重新用上下文的putImageData()方法绘制到指定区域,该方法接受三个参数:ImageData对象、x轴坐标、y轴坐标。绘制指定的位置绘制ImageData对象的内容。直接看下面例子的核心代码。
先绘制铺满画布的色块,点击按钮触发change事件处理器可改变颜色,过程见注释。
完整例子:演示反色变化

【PS】对js了解不深的朋友可能会有疑问,遍历过程操作的是pixels,imageData怎么会改变呢?
这是因为js中对象都是地址传递的特点,也就是pixels = imageData.data操作只是将pixels变量的指向到imageData.data所指向的内存空间,所以操作pixels就是操作imageData.data。

window.onload = function () {
const canvas = document.getElementById('canvas');
const context = canvas.getContext('2d');
// 绘制色块,每个色块宽10像素,高等于画布高,铺满画布
for (let i = 0; i < canvas.width; i += 10) {
context.fillStyle = (i % 20 === 0) ? '#f00' : ((i % 30 === 0) ? '#0f0' : '#00f');
context.fillRect(i, 0, 10, canvas.height);
}
}; function change() {
const canvas = document.getElementById('canvas');
const context = canvas.getContext('2d');
// 获取整个画布的ImageData对象
const imageData = context.getImageData(0, 0, canvas.width, canvas.height);
// 取出颜色数据
const pixels = imageData.data;
// 遍历颜色数据求每个颜色的反色
for (let offset = 0, len = pixels.length; offset < len; offset += 4) {
pixels[offset] = 255 - pixels[offset];
pixels[offset + 1] = 255 - pixels[offset + 1];
pixels[offset + 2] = 255 - pixels[offset + 2];
// 这里没有操作透明度
}
// 将ImageData重新绘制到画布上
context.putImageData(imageData, 0, 0);
}

更多有趣的例子

canvas强大的像素操作可以给我们带来更多的可能,也许你会想开始做一个网页版的美图工具了吧(笑)。
这里还有一些有趣的demo可以玩玩:

综合案例

有关颜色的番外部分到这里就基本完结了,最后有个综合题,会应用这些技术。
将系列第二篇中的鼠标画图工具改造成鼠标喷漆工具,这里建议自己动手实践一下。
下面例子基本思路就是取得画布像素数据,每当鼠标点下并移动(执行onMouseMove)就随机改变鼠标周围一定范围的像素点的颜色。
完整案例:鼠标喷漆工具

window.onload = function () {
const canvas = document.getElementById('canvas');
const context = canvas.getContext('2d');
// 获得整个画布区域的ImageData对象
const imageData = context.getImageData(0, 0, canvas.width, canvas.height);
// 取出像素数据
const pixels = imageData.data;
// 设定笔刷大小
const brush_size = 25;
// 设定笔刷密度
const brush_density = 80;
// 笔刷的颜色变量
let brush_color; function onMouseMove() {
// 根据设定的笔刷密度生成随机像素点
for (let i = 0; i < brush_density; i++) {
// 随机像素点角度相对于鼠标的角度
const angle = Math.random() * Math.PI * 2;
// 根据设定的笔刷大小,随机像素点以鼠标为圆心的半径
const radius = Math.random() * brush_size;
// 计算出像素点的x轴相对坐标
const xpos = (mouse.x + Math.cos(angle) * radius) | 0;
// 计算出像素点的y轴相对坐标
const ypos = (mouse.y + Math.sin(angle) * radius) | 0;
// 算出该像素点在pixels中的偏移量
const offset = (xpos + ypos * imageData.width) * 4;
// 对这个像素点的颜色数据进行操作,将颜色分解成三基色
pixels[offset] = brush_color >> 16 & 0xff;
pixels[offset + 1] = brush_color >> 8 & 0xff;
pixels[offset + 2] = brush_color & 0xff;
pixels[offset + 3] = 255;
}
// 重新绘制区域
context.putImageData(imageData, 0, 0);
}
canvas.addEventListener('mousedown', () => {
// 随机一个颜色
brush_color = utils.parseColor(Math.random() * 0xffffff, true);
canvas.addEventListener('mousemove', onMouseMove, false);
}, false);
canvas.addEventListener('mouseup', () => {
canvas.removeEventListener('mousemove', onMouseMove, false);
}, false);
};

【30分钟学完】canvas动画|游戏基础(extra1-1):美图我也行的更多相关文章

  1. 【30分钟学完】canvas动画|游戏基础(extra1):颜色那些事

    前言 本篇主要讲解关于计算机颜色系统的概念,后续结合一些canvas的应用.因为是"你不知道也没关系"的边缘知识,所以作为本系列教程的扩展,没有兴趣的同学可以跳过. 开始我们万紫千 ...

  2. 【30分钟学完】canvas动画|游戏基础(2):从零开始画画

    前言 上篇主要是理论的概述,本篇会多些实践,来讲讲canvas的基础用法,并包含一些基础三角函数的应用,推荐没有canvas基础的朋友阅读,熟悉的朋友可以跳过. 本人能力有限,欢迎牛人共同讨论,批评指 ...

  3. 【30分钟学完】canvas动画|游戏基础(1):理论先行

    前言 本文虽说是基础教程,但这是相对动画/游戏领域来说,在前端领域算是中级教程了,不适合前端小白或萌新.阅读前请确保自己对前端三大件(JavaScript+CSS+HTML)的基础已经十分熟悉,而且有 ...

  4. 【30分钟学完】canvas动画|游戏基础(7):动量守恒与多物体碰撞

    前言 一路沿着本系列教程学习的朋友可能会发现,前面教程中都尽量避免提及质量的概念,很多运动概念也时刻提醒大家这不是真实的物体运动.因为真实的物体运动其实跟质量都是密不可分的,而且质量的引入自然必须提及 ...

  5. 【30分钟学完】canvas动画|游戏基础(4):边界与碰撞

    前言 本系列前几篇中常出现物体跑到画布外的情况,本篇就是为了解决这个问题. 阅读本篇前请先打好前面的基础. 本人能力有限,欢迎牛人共同讨论,批评指正. 越界检测 假定物体是个圆形,如图其圆心坐标即是物 ...

  6. 【30分钟学完】canvas动画|游戏基础(6):坐标旋转探究

    前言 本篇主要讲坐标旋转及其应用,这是编程动画必不可少的技术. 阅读本篇前请先打好前面的基础. 本人能力有限,欢迎牛人共同讨论,批评指正. 坐标旋转 模拟场景:已知一个中心点(centerX,cent ...

  7. 【30分钟学完】canvas动画|游戏基础(5):重力加速度与模拟摩擦力

    前言 解决运动和碰撞问题后,我们为了让运动环境更加自然,需要加入一些环境因子,比如常见的重力加速度和模拟摩擦力. 阅读本篇前请先打好前面的基础. 本人能力有限,欢迎牛人共同讨论,批评指正. 重力加速度 ...

  8. 30分钟学玩转RabbitMQ

    最近在学习RabbitMQ,在网上找了不少资料发现都特高端.动辄集群部署,分布式架构什么的,对于一个初学者实在不够友好.心想求人不如求自己,为什么不自己整理一套资料呢?于是<30分钟学玩转Rab ...

  9. 3分钟学完Python,直接从入门到精通

    作为帅气小编,我已经把python一些模块的甩在这儿了qwq,只要你拿到这些干货,包你玩转python,直接冲向"大佬"的段位,如果已经学了C或者C++或者说如果你需要你的一段关键 ...

随机推荐

  1. Delphi实现类的持久化保存(DFM格式)

    var inStream,outStream:TMemoryStream; begin inStream:=TMemoryStream.Create; outStream:=TMemoryStream ...

  2. 简单的servlet下载

    <servlet> <servlet-name>servletTest</servlet-name> <servlet-class>com.shangs ...

  3. nginx下配置vue前端项目

    server { listen 80; server_name _; root /opt/h5/index/; location / { index index.html index.htm inde ...

  4. #Java学习之路——基础阶段二(第十四篇)

    我的学习阶段是跟着CZBK黑马的双源课程,学习目标以及博客是为了审查自己的学习情况,毕竟看一遍,敲一遍,和自己归纳总结一遍有着很大的区别,在此期间我会参杂Java疯狂讲义(第四版)里面的内容. 前言: ...

  5. CentOS 7 卸载 mysql

    查看是否安装 mysql rpm -qa | grep -i mysql yum list install mysql* 卸载 yum方式 yum remove mysql mysql-server ...

  6. USACO4.1 Fence Loops【最小环&边->点转化】

    数据不是很大,如果要转换为正常的那种建图方式的话,可以给点进行标号,用一个二维数组存这两条边相交的那个点的标号,方便处理.一定要注意不要同一个点使用不同的编号也不要不同的点使用同一个编号(这不是废话嘛 ...

  7. ocelot集成consul服务发现

    首先下载consul 点击这里下载 转到解压文件夹目录输入cmd命令  consul agent -dev (有时候会卡住按一下方向键上) 在浏览器中输入http://localhost:8500/u ...

  8. C语言作业09

    问题 答案 这个作业属于那个课程 C语言程序设计 这个作业要求在哪里 https://i.cnblogs.com/EditPosts.aspx?opt=1 我在这个课程的目标是 在学好C语言编程的基础 ...

  9. [转帖]Ubuntu 对应内核版本

    带有相应Linux内核版本的Ubuntu版本列表 https://www.helplib.com/ubuntu/article_155943   问题: 是否有带有默认对应的Linux内核版本的Ubu ...

  10. postgresql 用 like 可以 复制结构包括主键约束

    create tabletablename ( like tablename INCLUDING INDEXES INCLUDING COMMENTS); PostgreSQL 动态表复制(CREAT ...