什么是 Canvas?
HTML5 的 canvas 元素使用 JavaScript 在网页上绘制图像。
画布是一个矩形区域,您可以控制其每一像素。
canvas 拥有多种绘制路径、矩形、圆形、字符以及添加图像的方法。
Canvas图片操作
canvas图像操作主要以下api:drawImage,createImageData, getImageData, putImageData ;
方法
描述
drawImage
在画布上绘制图像、画布或视频
createImageData
创建新的、空白的 ImageData 对象
getImageData
返回 ImageData 对象,该对象为画布上指定的矩形复制像素数据
putImageData
把图像数据(从指定的 ImageData 对象)放回画布上
在以上基础上,我们可以在canvas元素中绘制我们的图片, 假设我们的图片为image.jpg
const img = new Image() // 声明新的Image对象
img.src = "./img/photo.jpg"
const canvas = document.querySelector("#myCanvas"); // 获取canvas元素
const ctx = canvas.getContext("2d");
img.onload = function() {
// 根据image大小,指定canvas大小
canvas.width = img.width;
canvas.height = img.height;
// 将图片绘制到canvas中
ctx.drawImage(img, 0, 0, canvas.width,canvas.height);
}
这样我们就将图片绘制到canvas中了
canvas滤镜
这里我们主要是对imageData进行操作
getImageData() 方法返回 ImageData 对象,该对象拷贝了画布指定矩形的像素数据。
对于 ImageData 对象中的每个像素,都存在着四方面的信息,即 RGBA 值:
R - 红色 (0-255)
G - 绿色 (0-255)
B - 蓝色 (0-255)
A - alpha 通道 (0-255; 0 是透明的,255 是完全可见的)
color/alpha 以数组形式存在,并存储于 ImageData 对象的 data 属性中。
接下来让我们先搭建一个基本项目框架,这里我使用的是create-react-app生成的项目
import React, { useState, useLayoutEffect, useRef } from 'react';
function Home() {
const [size, setSize] = useState({ width: 0, height: 0 });
const canvasRef = useRef(null);
const imgRef = useRef(null);
const imageLoad = () => {
const width = imgRef.current.width;
const height = imgRef.current.height;
setSize({ width, height });
canvasRef.current.width = width;
canvasRef.current.height = height;
queryImageData();
};
useLayoutEffect(() => {
const { width, height } = size;
const ctx = canvasRef.current.getContext('2d');
//图片加载完成后 将图片绘制到canvas中
ctx.drawImage(imgRef.current, 0, 0, imgRef.current.naturalWidth, imgRef.current.naturalHeight, 0, 0, width, height);
}, [size]);
return (
<div className='home-view'>
<div className='home-content'>
<div className='content'>
<img onLoad={imageLoad} ref={imgRef} src={img} alt='little' />
</div>
<div className='content'>
<canvas ref={canvasRef} id='canvas' />
</div>
</div>
</div>
);
}
// css我就不贴了 基本没内容
当图片加载完成后, 我们将图片放入canvas中,这里我们未对图片做任何处理;
接下来就是对图片添加滤镜
为图片添加滤镜就是对imageData中data进行操作,然后将改变后的数据再次放入到canvas中展现
接下来我们添加操作按钮在页面底部
import React, { useState, useLayoutEffect, useRef } from 'react';
import CanvasRect from '../../utils/draw';
import photoFilter from '../../utils/filter';
import img from '../../static/img/4a5e48e736d12f2ef27d70b84fc2d56284356824.jpg';
import './index.less';
const buttonList = [
{ text: '正常', code: 'normal' },
{ text: '灰度滤镜', code: 'hdlj' },
{ text: '黑白滤镜', code: 'hblj' },
{ text: '反向滤镜', code: 'fxlj' },
{ text: '去色滤镜', code: 'qslj' },
{ text: '单色滤镜', code: 'dslj' },
{ text: '怀旧滤镜', code: 'hjlj' },
{ text: '熔铸滤镜', code: 'rzlj' },
{ text: '冰冻滤镜', code: 'bdlj' },
{ text: '连环画滤镜', code: 'lhhlj' },
{ text: '暗调滤镜', code: 'adlj' },
{ text: '高斯模糊滤镜', code: 'gsmmlj' },
];
function Home() {
const [size, setSize] = useState({ width: 0, height: 0 });
const canvasRef = useRef(null);
const imgRef = useRef(null);
const [type, setType] = useState(-1);
const imageLoad = () => {
const width = imgRef.current.width;
const height = imgRef.current.height;
setSize({ width, height });
canvasRef.current.width = width;
canvasRef.current.height = height;
queryImageData();
};
useLayoutEffect(() => {
const { width, height } = size;
const ctx = canvasRef.current.getContext('2d');
ctx.drawImage(imgRef.current, 0, 0, imgRef.current.naturalWidth, imgRef.current.naturalHeight, 0, 0, width, height);
}, [size]);
const frameCanvas = () => {
const img = imgRef.current;
const width = img.width;
const height = img.height;
const naturalWidth = img.naturalWidth;
const naturalHeight = img.naturalHeight;
let canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height;
let context = canvas.getContext('2d');
return [canvas, context, width, height, naturalWidth, naturalHeight];
};
const queryImageData = () => {
const img = imgRef.current;
const [, context, width, height, naturalWidth, naturalHeight] = frameCanvas();
context.drawImage(img, 0, 0, naturalWidth, naturalHeight, 0, 0, width, height);
const imgData = context.getImageData(0, 0, width, height);
return imgData;
};
const change = async (type) => {
const { width, height } = size;
await setType(type);
const func = photoFilter[type];
const baseImageData = queryImageData(); // 获取原始图片imageData
const imageData = func ? func(baseImageData) : baseImageData; // 通过滤镜改变图片
const img = filterImage(imageData); // 得到新图片
const ctx = canvasRef.current.getContext('2d');
new CanvasRect(img, ctx); // 将图片放入至canvas
ctx.clearRect(0, 0, width, height);
};
const filterImage = (imageData) => {
const [canvas, context] = frameCanvas();
context.putImageData(imageData, 0, 0);
const src = canvas.toDataURL();
return src;
};
return (
<div className='home-view'>
<div className='home-content'>
<div className='content'>
<img onLoad={imageLoad} ref={imgRef} src={img} alt='little' />
</div>
<div className='content'>
<canvas ref={canvasRef} id='canvas' />
</div>
</div>
<div className='home-footer'>
{buttonList.map((v) => {
return (
<div key={v.code} onClick={() => change(v.code)} className={`footer-button ${v.code === type ? 'active' : ''}`}>
{v.text}
</div>
);
})}
</div>
</div>
);
}
photoFilter中就是滤镜的一些算法,CanvasRect是将图片放入到canvas中
灰度滤镜
对每个像素的R, G, B数值取平均值
function (imgData) {
let data = imgData.data;
for (var i = 0; i < data.length; i += 4) {
var grey = (data[i] + data[i + 1] + data[i + 2]) / 3;
data[i] = data[i + 1] = data[i + 2] = grey;
}
return imgData;
}
黑白滤镜
对每个像素的R, G, B 三个值平均值大于125则设为255 反之设为0
function (imgData) {
let data = imgData.data;
for (var i = 0; i < data.length; i += 4) {
var avg = (data[i] + data[i + 1] + data[i + 2]) / 3;
data[i] = data[i + 1] = data[i + 2] = avg >= 125 ? 255 : 0;
}
return imgData;
}
反向滤镜
对每个像素的R, G, B 取反
function (imgData) {
let data = imgData.data;
for (var i = 0; i < data.length; i += 4) {
data[i] = 255 - data[i];
data[i + 1] = 255 - data[i + 1];
data[i + 2] = 255 - data[i + 2];
}
return imgData;
}
去色滤镜
将R, G, A 同时设置为三个值中最大和最小和的二分之一
function (imgData) {
let data = imgData.data;
for (let i = 0; i < data.length; i++) {
let avg = Math.floor((Math.min(data[i], data[i + 1], data[i + 2]) + Math.max(data[i], data[i + 1], data[i + 2])) / 2);
data[i] = data[i + 1] = data[i + 2] = avg;
}
return imgData;
}
单色滤镜
保留R, G, B中任意一个数值 将其他两个设置为0
function (imgData) {
let data = imgData.data;
for (let i = 0; i < data.length; i++) {
data[i * 4 + 2] = 0;
data[i * 4 + 1] = 0;
}
return imgData;
}
怀旧滤镜
function (imgData) {
let data = imgData.data;
for (let i = 0; i < data.length; i++) {
let r = data[i * 4],
g = data[i * 4 + 1],
b = data[i * 4 + 2];
let newR = 0.393 * r + 0.769 * g + 0.189 * b;
let newG = 0.349 * r + 0.686 * g + 0.168 * b;
let newB = 0.272 * r + 0.534 * g + 0.131 * b;
let rgbArr = [newR, newG, newB].map((e) => {
return e < 0 ? 0 : e > 255 ? 255 : e;
});
[data[i * 4], data[i * 4 + 1], data[i * 4 + 2]] = rgbArr;
}
return imgData;
}
熔铸滤镜
function (imgData) {
let data = imgData.data;
for (let i = 0; i < data.length; i++) {
let r = data[i * 4],
g = data[i * 4 + 1],
b = data[i * 4 + 2];
let newR = (r * 128) / (g + b + 1);
let newG = (g * 128) / (r + b + 1);
let newB = (b * 128) / (g + r + 1);
let rgbArr = [newR, newG, newB].map((e) => {
return e < 0 ? e * -1 : e;
});
[data[i * 4], data[i * 4 + 1], data[i * 4 + 2]] = rgbArr;
}
return imgData;
}
冰冻滤镜
function (imgData) {
let data = imgData.data;
for (let i = 0; i < data.length; i++) {
let r = data[i * 4],
g = data[i * 4 + 1],
b = data[i * 4 + 2];
let newR = ((r - g - b) * 3) / 2;
let newG = ((g - r - b) * 3) / 2;
let newB = ((b - g - r) * 3) / 2;
let rgbArr = [newR, newG, newB].map((e) => {
return e < 0 ? e * -1 : e;
});
[data[i * 4], data[i * 4 + 1], data[i * 4 + 2]] = rgbArr;
}
return imgData;
}
连环画滤镜
function (imgData) {
let data = imgData.data;
for (let i = 0; i < data.length; i++) {
let r = data[i * 4],
g = data[i * 4 + 1],
b = data[i * 4 + 2];
let newR = (Math.abs(g - b + g + r) * r) / 256;
let newG = (Math.abs(b - g + b + r) * r) / 256;
let newB = (Math.abs(b - g + b + r) * g) / 256;
let rgbArr = [newR, newG, newB].map((e) => {
return e < 0 ? 0 : e > 255 ? 255 : e;
});
[data[i * 4], data[i * 4 + 1], data[i * 4 + 2]] = rgbArr;
}
return imgData;
}
暗调滤镜
function (imgData) {
let data = imgData.data;
for (let i = 0; i < data.length; i++) {
let r = data[i * 4],
g = data[i * 4 + 1],
b = data[i * 4 + 2];
let newR = (r * r) / 255;
let newG = (g * g) / 255;
let newB = (b * b) / 255;
let rgbArr = [newR, newG, newB].map((e) => {
return e < 0 ? 0 : e > 255 ? 255 : e;
});
[data[i * 4], data[i * 4 + 1], data[i * 4 + 2]] = rgbArr;
}
return imgData;
}
高斯模糊滤镜
function (imgData, radius = 5, sigma = radius / 3) {
let handleEdge = (i, x, w) => {
var m = x + i;
if (m < 0) {
m = -m;
} else if (m >= w) {
m = w + i - x;
}
return m;
};
var pixes = imgData.data,
height = imgData.height,
width = imgData.width;
var gaussEdge = radius * 2 + 1;
var gaussMatrix = [],
gaussSum = 0,
a = 1 / (2 * sigma * sigma * Math.PI),
b = -a * Math.PI;
for (var i = -radius; i <= radius; i++) {
for (var j = -radius; j <= radius; j++) {
var gxy = a * Math.exp((i * i + j * j) * b);
gaussMatrix.push(gxy);
gaussSum += gxy;
}
}
var gaussNum = (radius + 1) * (radius + 1);
for (let i = 0; i < gaussNum; i++) {
gaussMatrix[i] /= gaussSum;
}
for (let x = 0; x < width; x++) {
for (let y = 0; y < height; y++) {
let r = 0,
g = 0,
b = 0;
for (let i = -radius; i <= radius; i++) {
let m = handleEdge(i, x, width);
for (let j = -radius; j <= radius; j++) {
let mm = handleEdge(j, y, height);
let currentPixId = (mm * width + m) * 4;
let jj = j + radius;
let ii = i + radius;
r += pixes[currentPixId] * gaussMatrix[jj * gaussEdge + ii];
g += pixes[currentPixId + 1] * gaussMatrix[jj * gaussEdge + ii];
b += pixes[currentPixId + 2] * gaussMatrix[jj * gaussEdge + ii];
}
}
let pixId = (y * width + x) * 4;
pixes[pixId] = ~~r;
pixes[pixId + 1] = ~~g;
pixes[pixId + 2] = ~~b;
}
}
return imgData;
}
。。。 暂时就这些吧,以后再补充一下, 第一次发文,没过多的文字,希望大家不要介意,希望大家可以学到一点点好玩的东西。