canvas实现抠图,画笔,水印等功能
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<style>
html,
body {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
padding: 0;
margin: 0;
}
.canvas-box {
display: flex;
position: relative;
}
#canvas {
box-shadow: 0 0 0 1px #ccc;
}
.option {
padding-left: 12px;
}
.item {
display: flex;
align-items: center;
margin-bottom: 6px;
}
.btn-box {
width: 300px;
display: flex;
align-items: center;
justify-content: center;
flex-wrap: wrap;
}
.cursor {
width: 20px;
height: 20px;
position: absolute;
border-radius: 50%;
transform: translate(-50%, -50%);
box-shadow: 0 0 0 2px #000 inset;
background-color: transparent;
pointer-events: none;
opacity: 0;
z-index: 99;
}
</style>
</head>
<body>
<div class="canvas-box">
<div class="cursor"></div>
<canvas id="canvas" style="background-color: rgba(0, 0, 0, 0)"></canvas>
</div>
<div class="option">
<div class="item">
画笔大小(2~50):
<input type="number" name="pencil" id="" min="2" max="50" value="20" />
</div>
<div class="item">
橡皮大小(2~50):
<input type="number" name="eraser" id="" min="2" max="50" value="20" />
</div>
<div class="item">
保存图片质量(0.4~1):
<input
type="number"
name="quality"
id=""
min="0.4"
max="1"
value="0.8"
step="0.1"
/>
</div>
<div class="item">
保存格式:
<input type="radio" data-type="imgType" name="png" id="" checked />png
<input type="radio" data-type="imgType" name="jpeg" id="" />jpeg
<input type="radio" data-type="imgType" name="webp" id="" />webp
</div>
<div class="item">
画布随上传图片缩放:
<input type="radio" data-type="imgRule" name="long" checked />长边
<input type="radio" data-type="imgRule" name="short" />短边
<input type="radio" data-type="imgRule" name="custom" />自适应
</div>
<div class="item">
水印文案:<input
type="text"
name="waterMark"
value="大吉大利,今晚吃鸡"
/>
</div>
<div class="item">
水印文案颜色:<input type="color" name="waterMarkColor" />
</div>
<div class="item">
水印透明度(0.1~1):
<input
type="number"
name="waterMarkOpacity"
id=""
min="0.1"
max="1"
value="0.15"
step="0.01"
/>
</div>
<div class="item">
水印文字大小(10~60):
<input
type="number"
name="fontSize"
id=""
min="12"
max="60"
value="14"
/>
</div>
<div class="item">
水印旋转角度(-360~360):
<input
type="number"
name="rotate"
id=""
min="-360"
max="360"
value="-25"
/>
</div>
<div class="item">
水印水平间距(50~500):
<input type="number" name="x" id="" min="20" max="500" value="100" />
</div>
<div class="item">
水印垂直间距(50~500):
<input type="number" name="y" id="" min="20" max="500" value="100" />
</div>
<div class="item">
水印X轴偏移量(50~500):
<input
type="number"
name="offsetX"
id=""
min="50"
max="300"
value="50"
/>
</div>
<div class="item">
水印Y轴偏移量(50~500):
<input
type="number"
name="offsetY"
id=""
min="50"
max="300"
value="50"
/>
</div>
<div class="item">
水印文案最大宽度(50~500):
<input
type="number"
name="maxWidth"
id=""
min="50"
max="300"
value="200"
/>
</div>
<div class="btn-box">
<button name="usePencil">使用画笔</button>
<button name="useEraser">使用橡皮</button>
<input type="color" name="color" id="" />
<button name="reset">清空画布</button>
<button name="undo">撤销</button>
<button name="redo">恢复</button>
<button name="save">保存</button>
<button name="upload">上传图片</button>
<button name="waterMark">使用水印</button>
<button name="cutout">抠图</button>
</div>
</div>
<input
type="file"
name="file"
accept="image/jpeg,image/png,image/webp"
style="display: none"
/>
<script>
const container = document.querySelector(".canvas-box");
const cursorDom = document.querySelector(".cursor");
const fileDom = document.querySelector('input[type="file"]');
const canvas = document.querySelector("#canvas");
const ctx = canvas.getContext("2d", { willReadFrequently: true });
const cursor = document.querySelector(".cursor");
const usePencil = document.querySelector('button[name="usePencil"]');
const useEraser = document.querySelector('button[name="useEraser"]');
const pencilRange = document.querySelector('input[name="pencil"]');
const eraserRange = document.querySelector('input[name="eraser"]');
const qualityRange = document.querySelector('input[name="quality"]');
const colorPick = document.querySelector('input[name="color"]');
const cutout = document.querySelector('button[name="cutout"]');
const waterMarkColorPick = document.querySelector(
'input[name="waterMarkColor"]'
);
const waterMarkOpacityRange = document.querySelector(
'input[name="waterMarkOpacity"]'
);
const imgTypeRadios = document.querySelectorAll(
'input[data-type="imgType"]'
);
const imgRuleRadios = document.querySelectorAll(
'input[data-type="imgRule"]'
);
const fontSizeRange = document.querySelector('input[name="fontSize"]');
const xRange = document.querySelector('input[name="x"]');
const yRange = document.querySelector('input[name="y"]');
const rotateRange = document.querySelector('input[name="rotate"]');
const offsetRangeX = document.querySelector('input[name="offsetX"]');
const offsetRangeY = document.querySelector('input[name="offsetY"]');
const maxWidthRange = document.querySelector('input[name="maxWidth"]');
const reset = document.querySelector('button[name="reset"]');
const undo = document.querySelector('button[name="undo"]');
const redo = document.querySelector('button[name="redo"]');
const save = document.querySelector('button[name="save"]');
const upload = document.querySelector('button[name="upload"]');
const waterMark = document.querySelector('button[name="waterMark"]');
const canvasDefaultSize = 600;
canvas.width = canvasDefaultSize;
canvas.height = canvasDefaultSize;
let pencil = 20;
let eraser = 20;
let quality = 0.8;
let imgType = "png";
let imgRule = "long";
let isPencil = true;
let isEraser = false;
let isDrawingLine = false;
let colors = "#000";
let historyIdx = 0;
let history = [];
let canvasArea = [0, 0, canvas.width, canvas.height];
let text = "大吉大利,今晚吃鸡";
let rotate = -25;
let maxWidth = 200;
let offsetX = 50;
let offsetY = 50;
let gap = [100, 100];
let isCutout=false
const setCursorSize = (size) => {
cursor.style.width = size + "px";
cursor.style.height = size + "px";
};
cutout.onclick=(e)=>{
isCutout=!isCutout
if(isCutout){
cutout.innerHTML='取消抠图'
}else{
cutout.innerHTML='抠图'
}
}
pencilRange.oninput = (e) => {
if (!e.target.value) e.target.value = 20;
pencil = e.target.valueAsNumber;
isPencil && setCursorSize(pencil);
};
eraserRange.oninput = (e) => {
if (!e.target.value) e.target.value = 20;
eraser = e.target.valueAsNumber;
isEraser && setCursorSize(eraser);
};
qualityRange.oninput = (e) => {
if (!e.target.value) e.target.value = 0.8;
quality = e.target.valueAsNumber;
};
fontSizeRange.oninput = (e) => {
if (!e.target.value) e.target.value = 14;
ctx.font = `500 ${e.target.valueAsNumber}px sans-serif`;
};
xRange.oninput = (e) => {
if (!e.target.value) e.target.value = 100;
gap[0] = e.target.valueAsNumber;
};
yRange.oninput = (e) => {
if (!e.target.value) e.target.value = 100;
gap[1] = e.target.valueAsNumber;
};
rotateRange.oninput = (e) => {
if (Number.isNaN(e.target.valueAsNumber)) e.target.value = -25;
rotate = e.target.valueAsNumber;
};
offsetRangeX.oninput = (e) => {
if (Number.isNaN(e.target.valueAsNumber)) e.target.value = 50;
offsetX = e.target.valueAsNumber;
};
offsetRangeY.oninput = (e) => {
if (Number.isNaN(e.target.valueAsNumber)) e.target.value = 50;
offsetY = e.target.valueAsNumber;
};
maxWidthRange.oninput = (e) => {
if (Number.isNaN(e.target.valueAsNumber)) e.target.value = 200;
maxWidth = e.target.valueAsNumber;
};
waterMarkOpacityRange.oninput = (e) => {
const [r, g, b] = hex2Rgb(waterMarkColorPick.value);
ctx.fillStyle = `rgba(${r}, ${g}, ${b}, ${waterMarkOpacityRange.valueAsNumber})`;
};
fileDom.oninput = (e) => {
const fileReader = new FileReader();
fileReader.readAsDataURL(e.target.files[0]);
fileReader.onload = (e) => {
const img = document.createElement("img");
img.src = e.target.result;
img.onload = () => {
let wh = [img.width, img.height];
let canvasWh = [canvasDefaultSize, canvasDefaultSize];
if (imgRule === "long") {
const ratio = canvasDefaultSize / Math.max(...wh);
wh = [img.width * ratio, img.height * ratio];
} else if (imgRule === "short") {
const ratio = canvasDefaultSize / Math.min(...wh);
wh = [img.width * ratio, img.height * ratio];
} else {
canvasWh = [img.width, img.height];
}
ctx.clearRect(...canvasArea);
canvasArea = [0, 0, ...canvasWh];
canvas.width = canvasWh[0];
canvas.height = canvasWh[1];
ctx.drawImage(img, 0, 0, ...wh);
};
};
};
imgTypeRadios.forEach((el) => {
el.onclick = () => {
imgTypeRadios.forEach((el2) => (el2.checked = false));
el.checked = true;
imgType = el.name;
};
});
imgRuleRadios.forEach((el) => {
el.onclick = () => {
imgRuleRadios.forEach((el2) => (el2.checked = false));
el.checked = true;
imgRule = el.name;
};
});
colorPick.oninput = (e) => (colors = e.target.value);
waterMarkColorPick.oninput = (e) => {
const [r, g, b] = hex2Rgb(e.target.value);
ctx.fillStyle = `rgba(${r}, ${g}, ${b}, ${waterMarkOpacityRange.valueAsNumber})`;
};
// 撤销
undo.onclick = () => {
historyIdx--;
if (historyIdx <= -1) {
historyIdx = -1;
ctx.clearRect(...canvasArea);
} else {
ctx.putImageData(history[historyIdx], 0, 0);
}
};
// 恢复
redo.onclick = () => {
historyIdx++;
if (historyIdx > history.length - 1) historyIdx = history.length - 1;
else ctx.putImageData(history[historyIdx], 0, 0);
};
save.onclick = () => {
const a = document.createElement("a");
a.href = canvas.toDataURL(`image/${imgType}`, quality);
a.download = `save_${Date.now()}`;
document.body.append(a);
a.click();
a.remove();
};
upload.onclick = () => {
fileDom.value = "";
fileDom.click();
};
usePencil.onclick = () => {
isPencil = true;
isEraser = false;
setCursorSize(pencil);
};
useEraser.onclick = () => {
isPencil = false;
isEraser = true;
setCursorSize(eraser);
};
reset.onclick = () => {
history = [];
historyIdx = -1;
ctx.clearRect(...canvasArea);
canvas.width = canvasDefaultSize;
canvas.height = canvasDefaultSize;
canvasArea = [0, 0, canvas.width, canvas.height];
const [r, g, b] = hex2Rgb(waterMarkColorPick.value);
waterMarkOpacityRange.oninput();
ctx.font = `500 ${fontSizeRange.valueAsNumber}px sans-serif`;
};
waterMark.onclick = () => {
let [x, y] = gap;
let [, , w, h] = canvasArea;
const xLine = Math.ceil((w - offsetX) / x);
const yLine = Math.ceil((h - offsetY) / y);
waterMarkOpacityRange.oninput();
ctx.font = `500 ${fontSizeRange.valueAsNumber}px sans-serif`;
for (let i = 0; i <= xLine; i++) {
const x0 = x * i + offsetX;
for (let j = 0; j <= yLine; j++) {
drawWaterMark(x0, y * j + offsetY);
}
}
};
const drawWaterMark = (x, y) => {
ctx.save();
ctx.translate(x, y);
ctx.rotate((rotate / 180) * Math.PI);
ctx.fillText(text, -ctx.measureText(text).width / 2, 0, maxWidth);
ctx.restore();
};
canvas.onmousedown = (e) => {
// 只允许左键
if (e.button) return;
if (!isPencil && !isEraser) return false;
isDrawingLine = true;
ctx.beginPath();
ctx.globalCompositeOperation = isEraser
? "destination-out"
: "source-over";
ctx.strokeStyle = isEraser ? "#fff" : colors;
if(isCutout){
ctx.globalCompositeOperation = 'destination-out';
ctx.strokeStyle=undefined
}
ctx.lineCap = "round";
ctx.lineJoin = "round";
ctx.lineWidth = isEraser ? eraser : pencil;
ctx.moveTo(e.offsetX, e.offsetY);
ctx.lineTo(e.offsetX, e.offsetY);
ctx.stroke();
canvas.onmousemove = composeCanvasMousemove;
};
canvas.onmouseup = () => {
if (!isPencil && !isEraser) return false;
isDrawingLine = false;
canvas.onmousemove = null;
// 使用了橡皮时,必须存在历史记录,否则不做任何事
if (isEraser && !history.length) return false;
addOneHistory(lineToAlpha());
};
container.onmouseenter = (e) => {
if (!container.contains(e.toElement)) return;
if (isPencil || isEraser) {
// 离开画布时,若正在使用划线功能,重新开启一个路径
if (isDrawingLine) {
ctx.beginPath();
ctx.moveTo(e.offsetX, e.offsetY);
ctx.lineTo(e.offsetX, e.offsetY);
ctx.stroke();
}
canvas.style.cursor = "none";
cursorDom.style.opacity = "1";
cursorDom.style.left = e.offsetX + "px";
cursorDom.style.top = e.offsetY + "px";
cursorDom.style.boxShadow = `0 0 0 2px ${
isPencil ? colors : "#ccc"
} inset`;
} else canvas.style.cursor = "default";
};
container.onmousemove = (e) => {
if (isPencil || isEraser) {
cursorDom.style.opacity = "1";
cursorDom.style.left = e.offsetX + "px";
cursorDom.style.top = e.offsetY + "px";
return false;
}
};
container.onmouseout = (e) => {
if (!container.contains(e.fromElement)) return;
cursorDom.style.opacity = "0";
};
const composeCanvasMousemove = (e) => {
ctx.lineTo(e.offsetX, e.offsetY);
ctx.stroke();
};
const lineToAlpha = () => {
const imageData = ctx.getImageData(...canvasArea);
return imageData;
};
// 添加一项历史记录
const addOneHistory = (json) => {
if (history.length - 1 !== historyIdx) {
history = history.slice(0, historyIdx + 1);
}
historyIdx = history.push(json) - 1;
};
// hex 2 rgb
const hex2Rgb = (hex) => {
const red = hex.substring(1, 3);
const green = hex.substring(3, 5);
const blue = hex.substring(5, 7);
return [parseInt(red, 16), parseInt(green, 16), parseInt(blue, 16)];
};
// 鼠标抬起时,发现不处于画布内,调用画笔结束
const handleLeaveCanvas = (e) => {
if (!isDrawingLine) return;
if (!container.contains(e.target)) canvas.onmouseup();
};
window.addEventListener("mouseup", handleLeaveCanvas);
setCursorSize(pencil);
</script>
</body>
</html>
canvas实现抠图,画笔,水印等功能的更多相关文章
- 【javascript】html5中使用canvas编写头像上传截取功能
[javascript]html5中使用canvas编写头像上传截取功能 本人对canvas很是喜欢,于是想仿照新浪微博头像上传功能(前端使用canvas) 本程序目前在谷歌浏览器和火狐浏览器测试可用 ...
- 导出HTML5 Canvas图片并上传服务器功能
这篇文章主要介绍了导出HTML5 Canvas图片并上传服务器功能,文中通过实例代码给大家介绍了HTML5 Canvas转化成图片后上传服务器,代码简单易懂非常不错,具有一定的参考借鉴价值,需要的朋友 ...
- canvas实现平铺水印
欲实现的水印平铺的效果图如下: 从图上看,应该做到以下几点: 文字在X和Y方向上进行平铺: 文字进行了一定的角度的旋转: 水印作为背景,其z-index位置应位于页面内容底部, 即不能覆盖页面主内容: ...
- (H5)canvas实现裁剪图片和马赛克功能,以及又拍云上传图片
1.核心功能 此组件功能包含: 图片裁剪(裁剪框拖动,裁剪框改变大小): 图片马赛克(绘制马赛克,清除马赛克): 图片预览.图片还原(返回原图.返回处理图): 图片上传(获取签名.上传图片). 2.核 ...
- canvas与html5实现视频截图功能
这段时间一直在研究canvas,突发奇想想做一个可以截屏视频的功能,然后把图片拉去做表情包,哈哈哈哈哈哈~~ 制作方法: 1.在页面中加载视频 在使用canvas制作这个截图功能时,首先必须保证页面上 ...
- 本图片处理类功能非常之强大可以实现几乎所有WEB开发中对图像的处理功能都集成了,包括有缩放图像、切割图像、图像类型转换、彩色转黑白、文字水印、图片水印等功能
import java.awt.AlphaComposite; import java.awt.Color; import java.awt.Font; import java.awt.Graphic ...
- canvas给图片加水印
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...
- Android 在Canvas中实现画笔效果(一)--钢笔
如题: 公司要求做一个涂鸦板,要有钢笔.毛笔等画笔效果,网上搜了很多,可是效果不怎么好,决定自己研究下.废话不多说,进入正题. 首先,赛贝尔曲线弄明白了,在画曲线的过程中就是一条条的向量. 第二,曲线 ...
- tp5文件上传实现缩略图+水印的功能(参考)
public function AddNews(){ $data = Request::instance()->param(); //接收文件 $file = request()->fil ...
- canvas刮刮乐和画笔
这周有点迷茫,不知道干嘛了,一天天就过去了!我在博客右侧公告栏加了qq交流,各位有好的主题,或者有趣的技术,欢迎交流!今天突发奇想,就写了2个h5 canvas的demo玩玩! demo一:刮刮乐 舍 ...
随机推荐
- 小程序框架开发笔记-wepy
WePY 一.前置 本地使用版本V1.7.3最新版本V2.x 二.使用 export class App extends wepy.app {} 小程序入口,App基类 wepy ...
- Git操作【常用操作命令】
Git操作指令 1. git init 初始化一个git 仓库: 2. git add test.txt 添加一个文件到仓库,可以添加多个,一空格隔开: 3. git commit -m " ...
- SQL Server创建用户只能访问指定数据库和视图
我们在给数据库用户赋予权限时,有时候不想让该用户看到太多过程表和过程视图,这时就需要限定用户的访问权限 第一步:创建用户 创建数据库连接后,进入安全性--登录名,单击右键,新建登录名,并设置默认数据库 ...
- C240731B
B 游戏类问题 先假设一瓶毒药都不用, 先把治疗的贡献加进答案里面,这样治疗.毒药.攻击的贡献分别是独立的. 如果 \(i\) 位置本来是治疗, 那么用毒药多扣的血是 \(a[i]=(p+r) \ti ...
- 3.6 Linux命令基本格式
本节开始,我们不会再见到图形界面了,因为对服务器来讲,图形界面会占用更多的系统资源,而且会安装更多的服务.开放更多的端口,这对服务器的稳定性和安全性都有负面影响.其实,服务器是一个连显示器都没有的家伙 ...
- linux基本指令总结
拖了好久的linux学习,终于开始啦 环境终于没问题了 边学边总结 一.常用指令 1.1 关机与开机 poweroff 马上关机 reboot 马上重启 1.2 目录文件操作命令 cd / 切换到根目 ...
- 接口自动化测试框架【python+requests+pytest+excel/yaml+allure+jenkins】
一.在整个框架中需要用到哪些东西? 1.python环境安装 https://www.python.org/downloads/windows/ 下载解压后直接安装,选择 Add python to ...
- phpstorm之代码质量工具
在进行php开发的时候, 经常由于编码上的不规范导致了隐藏的bug,这里介绍代码质量工具 PHP CodeSniffer: phpcs [安装] composer require squizla ...
- Linux之密码生成工具pwgen
linux中生成随机字符串,可以使用pwgen 安装) ubuntu: apt-get install pwgen Centos: yum install pwgen 语法及参数) pwgen [ O ...
- Lambda【1】-- List相关Lambda表达式使用(上篇)
Lambda在jdk1.8里面已经很好用了,在这里不讲底层的实现,只有简单的用法,会继续补全. 首先一个list我们要使用lambda的话,需要使用它的stream()方法,获取流,才能使用后续的方法 ...