<!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实现抠图,画笔,水印等功能的更多相关文章

  1. 【javascript】html5中使用canvas编写头像上传截取功能

    [javascript]html5中使用canvas编写头像上传截取功能 本人对canvas很是喜欢,于是想仿照新浪微博头像上传功能(前端使用canvas) 本程序目前在谷歌浏览器和火狐浏览器测试可用 ...

  2. 导出HTML5 Canvas图片并上传服务器功能

    这篇文章主要介绍了导出HTML5 Canvas图片并上传服务器功能,文中通过实例代码给大家介绍了HTML5 Canvas转化成图片后上传服务器,代码简单易懂非常不错,具有一定的参考借鉴价值,需要的朋友 ...

  3. canvas实现平铺水印

    欲实现的水印平铺的效果图如下: 从图上看,应该做到以下几点: 文字在X和Y方向上进行平铺: 文字进行了一定的角度的旋转: 水印作为背景,其z-index位置应位于页面内容底部, 即不能覆盖页面主内容: ...

  4. (H5)canvas实现裁剪图片和马赛克功能,以及又拍云上传图片

    1.核心功能 此组件功能包含: 图片裁剪(裁剪框拖动,裁剪框改变大小): 图片马赛克(绘制马赛克,清除马赛克): 图片预览.图片还原(返回原图.返回处理图): 图片上传(获取签名.上传图片). 2.核 ...

  5. canvas与html5实现视频截图功能

    这段时间一直在研究canvas,突发奇想想做一个可以截屏视频的功能,然后把图片拉去做表情包,哈哈哈哈哈哈~~ 制作方法: 1.在页面中加载视频 在使用canvas制作这个截图功能时,首先必须保证页面上 ...

  6. 本图片处理类功能非常之强大可以实现几乎所有WEB开发中对图像的处理功能都集成了,包括有缩放图像、切割图像、图像类型转换、彩色转黑白、文字水印、图片水印等功能

    import java.awt.AlphaComposite; import java.awt.Color; import java.awt.Font; import java.awt.Graphic ...

  7. canvas给图片加水印

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  8. Android 在Canvas中实现画笔效果(一)--钢笔

    如题: 公司要求做一个涂鸦板,要有钢笔.毛笔等画笔效果,网上搜了很多,可是效果不怎么好,决定自己研究下.废话不多说,进入正题. 首先,赛贝尔曲线弄明白了,在画曲线的过程中就是一条条的向量. 第二,曲线 ...

  9. tp5文件上传实现缩略图+水印的功能(参考)

    public function AddNews(){ $data = Request::instance()->param(); //接收文件 $file = request()->fil ...

  10. canvas刮刮乐和画笔

    这周有点迷茫,不知道干嘛了,一天天就过去了!我在博客右侧公告栏加了qq交流,各位有好的主题,或者有趣的技术,欢迎交流!今天突发奇想,就写了2个h5 canvas的demo玩玩! demo一:刮刮乐 舍 ...

随机推荐

  1. js常用函数-02 _关于$(function () { })的运行时机

    js常用函数-02 _关于$(function () { })的运行时机 代码模式: $(function() { //执行操作 }); 查找到的结果: (function() {}),即 $(doc ...

  2. Rust的Reborrow机制

    最近,在使用Rust时遇到了Reborrow的概念,记录下来以备以后参考. 1. 起因 起因准备对数据进行Min-Max标准化处理,也就是将一系列数据映射到一个新的范围. 首先,需要遍历数据,找出其中 ...

  3. RAC:无训练持续扩展,基于检索的目标检测器 | ECCV'24

    来源:晓飞的算法工程笔记 公众号,转载请注明出处 论文: Online Learning via Memory: Retrieval-Augmented Detector Adaptation 论文地 ...

  4. 题解:CF1015D Walking Between Houses

    题解:CF1015D Walking Between Houses 算法 模拟,分类讨论 分析 首先,设每步走的距离为 \(t_i\),我们发现 \(t_i\) 应是满足 \(1\le t_i\le ...

  5. pyenv-win-master\pyenv-win\libexec\pyenv-install.vbs(161, 5) Microsoft VBScript 运行时错误: 文件未找到

    Windows 10 运行 pyenv install 3.11.2 提示 pyenv-win-master\pyenv-win\libexec\pyenv-install.vbs(161, 5) M ...

  6. 2023NOIP A层联测20 T3 点餐

    2023NOIP A层联测20 点餐 题目很好,可惜考试没想到. 思路 可以按照 \(b\) 从小到大排序,固定选择个数 \(k\),枚举选择的盘子 \(x\) 的 \(b\) 最大,最优解肯定是贪心 ...

  7. 3、oracle内存讲解

    oracle数据库实例(instance) 数据库打开以后,会生成一个内存结构和一堆进程 内存和进程:就是oracle的实例instance oracle数据库实例结构: 用户是通过连接实例来访问数据 ...

  8. Java 并发编程实战学习笔记——CountDownLatch的使用

    public class CountDownLatch extends Object 一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待. 用给定的计数 初始化 Co ...

  9. 基于golang的swagger

    Swagger 相关的工具集会根据 OpenAPI 规范去生成各式各类的与接口相关联的内容,常见的流程是编写注解 =>调用生成库->生成标准描述文件 =>生成/导入到对应的 Swag ...

  10. 【懒狗必备】用bat命令解放双手

    背景说明 每天上班,都需要打开本地的一些服务,比如redis.zk等. 作为懒狗,需要会利用工具. 于是我写了一个bat脚本,幼儿园水平: chcp 65001 title 一键启动本地环境脚本 st ...