TODO
选择头像主题类型: flag 🇨🇳 | graduation 🎓选择滤镜效果- 切割图片
自定义图像大小选择滤镜前,应该备份原图像数据, 只能有一次还原机会选择🎓风格下载图片有问题拍照上传bug 移动端上传文件- bug 设置 width(上传文件时,保存其数据)
- bug 滤镜, 每次选择滤镜时,都是从原头像上添加滤镜效果(再添加一个canvas。当做备份)
样式 open/close/take 按钮,需要优化视频的时候也可以添加滤镜效果- 重构代码
头像加国旗
演示
地址02 有视频滤镜:yylichao.gitee.io/avatar/
功能
- 头像加国旗
- 切换主题: 国庆主题、毕业主题
- 滤镜效果
- 视频滤镜
- 拍照上传
思路
头像加国旗
- 上传的图片
- 在画布中先画底图,然后画国旗
切换主题
- 点击主题的同时,下面选择的 flag或者 graduation
- 遮罩图像路径,是根据主题和选择的类型来计算的
滤镜效果
- 获取图像的数据 -> 算法处理 -> 生成新的图像数据
- 点击切换滤镜时,执行相应滤镜算法
视频滤镜
- video 获取图像数据
- 在 canvas 中画出来
实现
在画布中先画底图,然后画国旗
const drawHat = (context) => {
const type = e('.t-value-theme-type').dataset.type
const n = +e(`.t-select-${type}`).dataset[type]
const pre = type === 'flag' ? 'hat' : type
const imgUrl = `https://grammyli.com/t/avatar/img/${type}/${pre}${n}.png`
// log('imgUrl', imgUrl)
const img = new Image()
img.setAttribute('crossorigin', 'anonymous')
img.src = imgUrl
img.onload = () => {
context.drawImage(img, 0, 0, width, width)
}
}
const drawAvatar = () => {
if (!imageData) {
alert('大佬 上传图片')
return
}
const c = e('.t-canvas')
const context = c.getContext('2d')
// 清空画布
context.clearRect(0, 0, width, width)
context.putImageData(imageData, 0, 0)
drawHat(context)
}
视频滤镜
const imageFilter = () => {
const type = e(".t-select-filter").dataset.filter;
// 没有上传文件
if (!window.imageData) {
return;
}
const image = document.querySelector(".t-canvas");
const context = image.getContext("2d");
if (type === "notFilter") {
window.imageData = window.rawImageData;
context.putImageData(imageData, 0, 0);
return;
}
const filters = {
gray: grayOfColors, // 灰度
blackWhite: blackWhiteOfColors, // 黑白
negative: negativeOfColors, // 底片
comic: comicBooksOfColors, // 漫画
nostalgia: nostalgiaOfColors, // 怀旧
fusedCast: fusedCastOfColors,
};
// const imageData = context.getImageData(0, 0, 400, 400);
const { data } = window.imageData;
for (let i = 0; i < data.length; i += 4) {
const colors = data.slice(i, i + 4);
const newColors = filters[type](colors);
const [r, g, b, a] = newColors;
data[i] = r;
data[i + 1] = g;
data[i + 2] = b;
data[i + 3] = a;
}
context.putImageData(imageData, 0, 0);
};
案例: pixel -> new pixel
const comicBooksOfColors = (colors) => {
let r = colors[0];
let g = colors[1];
let b = colors[2];
let A = colors[3];
let R = (Math.abs(g - b + g + r) * r) / 256;
let G = (Math.abs(b - g + b + r) * r) / 256;
let B = (Math.abs(b - g + b + r) * g) / 256;
return [int(R), int(G), int(B), int(A)];
};
const nostalgiaOfColors = (colors) => {
let r = colors[0];
let g = colors[1];
let b = colors[2];
let A = colors[3];
let R = 0.393 * r + 0.769 * g + 0.189 * b
let G = 0.349 * r + 0.686 * g + 0.168 * b
let B = 0.272 * r + 0.534 * g + 0.131 * b
return [int(R), int(G), int(B), int(A)];
}
const fusedCastOfColors = (colors) => {
let r = colors[0];
let g = colors[1];
let b = colors[2];
let A = colors[3];
let R = r * 128 / (g + b + 1)
let G = g * 128 / (r + b + 1)
let B = b * 128 / (g + r + 1)
return [int(R), int(G), int(B), int(A)];
}
const grayOfColors = (colors) => {
let r = colors[0];
let g = colors[1];
let b = colors[2];
let gray = (r + g + b) / 3;
let R = gray;
let G = gray;
let B = gray;
let A = colors[3];
return [int(R), int(G), int(B), int(A)];
};
// 求RGB平均值Avg = (R + G + B) / 3,如果Avg >= 100,
// 则新的颜色值为=RG=B=255;
// 如果Avg < 100,则新的颜色值为R=G=B=0;255就是白色,0就是黑色
const blackWhiteOfColors = (colors) => {
let r = colors[0];
let g = colors[1];
let b = colors[2];
let gray = (r + g + b) / 3;
gray = gray >= 100 ? 255 : 0;
let R = gray;
let G = gray;
let B = gray;
let A = colors[3];
return [int(R), int(G), int(B), int(A)];
};
// 底片
// 算法原理:将当前像素点的RGB值分别与255之差后的值作为当前点的RGB值,即
// R = 255 – R;G = 255 – G;B = 255 – B;
const negativeOfColors = (colors) => {
let r = colors[0];
let g = colors[1];
let b = colors[2];
let R = 255 - r;
let G = 255 - g;
let B = 255 - b;
let A = colors[3];
return [int(R), int(G), int(B), int(A)];
};