当前位置: 首页 > news >正文

JavaScript - 实现套索工具的demo

最近实习想要完成图生图的项目,想要套索工具来建立蒙版,下面是我的两个demo。包括自由套索工具和多边形套索工具。

自由套索工具:

<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>PS套索工具模拟</title><script src="https://cdn.tailwindcss.com"></script><link href="https://cdn.jsdelivr.net/npm/font-awesome@4.7.0/css/font-awesome.min.css" rel="stylesheet"><script>tailwind.config = {theme: {extend: {colors: {primary: '#3b82f6',secondary: '#1e40af',neutral: '#f3f4f6',},fontFamily: {inter: ['Inter', 'sans-serif'],},}}}</script><style type="text/tailwindcss">@layer utilities {.content-auto {content-visibility: auto;}.tool-active {@apply bg-primary text-white;}.canvas-container {@apply relative w-full h-[600px] bg-gray-100 overflow-hidden;}.toolbar {@apply flex flex-col bg-gray-800 text-white p-2 space-y-3 rounded-lg shadow-lg;}.tool-button {@apply p-2 rounded hover:bg-gray-700 transition-all duration-200 flex items-center justify-center;}.canvas-wrapper {@apply flex justify-center items-center w-full h-full;}.mode-button {@apply px-2 py-1 text-xs rounded transition-all duration-200 text-center;}.mode-active {@apply bg-primary text-white;}}</style>
</head>
<body class="bg-gray-900 font-inter text-gray-100 min-h-screen flex flex-col"><header class="bg-gray-800 shadow-md py-4 px-6 flex justify-between items-center"><div class="flex items-center space-x-2"><i class="fa fa-paint-brush text-primary text-2xl"></i><h1 class="text-xl font-bold">套索工具模拟</h1></div><div class="flex items-center space-x-4"><button id="saveBtn" class="bg-primary hover:bg-secondary text-white px-4 py-2 rounded transition-all duration-200 flex items-center"><i class="fa fa-save mr-2"></i>保存选区</button><button id="uploadBtn" class="bg-primary hover:bg-secondary text-white px-4 py-2 rounded transition-all duration-200 flex items-center"><i class="fa fa-upload mr-2"></i>上传图片</button></div></header><main class="flex-1 p-6 flex flex-col md:flex-row gap-6"><div class="w-full md:w-64"><div class="toolbar"><h3 class="text-center font-semibold text-sm uppercase tracking-wider mb-2">工具</h3><div class="space-y-1"><button id="lassoTool" class="tool-button tool-active" title="套索工具 (L)"><i class="fa fa-pencil"></i></button><button id="polygonTool" class="tool-button" title="多边形套索工具 (P)"><i class="fa fa-pencil-square-o"></i></button></div><!-- 添加选区模式选项 --><div class="mt-6"><h3 class="text-center font-semibold text-sm uppercase tracking-wider mb-2">选区模式</h3><div class="grid grid-cols-2 gap-1"><button id="newSelection" class="mode-button mode-active" title="新建选区"><i class="fa fa-square-o"></i></button><button id="addSelection" class="mode-button" title="添加到选区 (Shift)"><i class="fa fa-plus"></i></button></div></div><!-- 选区信息 --><div class="mt-6 p-2 bg-gray-700 rounded"><h3 class="text-center font-semibold text-sm uppercase tracking-wider mb-2">选区信息</h3><div class="text-xs space-y-1"><p>状态: <span id="selectionStatus">无选区</span></p><p>模式: <span id="currentMode">新建</span></p><p>点数量: <span id="pointCount">0</span></p></div></div></div></div><div class="flex-1"><div class="canvas-container rounded-lg shadow-xl"><div class="canvas-wrapper"><canvas id="imageCanvas" class="border-2 border-gray-300 rounded shadow-lg"></canvas></div></div></div></main><script>document.addEventListener('DOMContentLoaded', function() {// 获取Canvas元素和绘图上下文const canvas = document.getElementById('imageCanvas');const ctx = canvas.getContext('2d');// 设置Canvas尺寸canvas.width = 800;canvas.height = 500;// 工具状态let isDrawing = false;let currentPoints = []; // 当前正在绘制的点let savedSelections = []; // 已保存的选区集合,每个元素是一个独立的选区点数组let selectionMode = 'new'; // 选区模式: new, add, subtractlet backgroundImage = null;let currentTool = 'lasso'; // 默认使用套索工具// 生成示例图像(网格背景)function drawGrid() {const gridSize = 20;ctx.fillStyle = '#ffffff';ctx.fillRect(0, 0, canvas.width, canvas.height);ctx.strokeStyle = '#eeeeee';ctx.lineWidth = 1;// 绘制垂直线for (let x = 0; x < canvas.width; x += gridSize) {ctx.beginPath();ctx.moveTo(x, 0);ctx.lineTo(x, canvas.height);ctx.stroke();}// 绘制水平线for (let y = 0; y < canvas.height; y += gridSize) {ctx.beginPath();ctx.moveTo(0, y);ctx.lineTo(canvas.width, y);ctx.stroke();}}// 绘制背景图片function drawBackgroundImage() {if (backgroundImage) {ctx.drawImage(backgroundImage, 0, 0, canvas.width, canvas.height);} else {drawGrid();}}// 绘制已保存的选区function drawSavedSelection() {for (const selection of savedSelections) {if (selection.length < 3) continue;ctx.save();ctx.beginPath();ctx.moveTo(selection[0].x, selection[0].y);for (let i = 1; i < selection.length; i++) {ctx.lineTo(selection[i].x, selection[i].y);}ctx.closePath();// 填充半透明蓝色ctx.fillStyle = 'rgba(59, 130, 246, 0.2)';ctx.fill();// 绘制选区边界(虚线)ctx.strokeStyle = '#3b82f6';ctx.lineWidth = 2;ctx.setLineDash([5, 5]);ctx.stroke();ctx.restore();}}// 绘制当前正在绘制的选区function drawCurrentSelection() {if (currentPoints.length < 2) return;ctx.save();ctx.beginPath();ctx.moveTo(currentPoints[0].x, currentPoints[0].y);for (let i = 1; i < currentPoints.length; i++) {ctx.lineTo(currentPoints[i].x, currentPoints[i].y);}// 多边形工具需要手动闭合路径if (currentTool === 'polygon' && currentPoints.length >= 3) {ctx.closePath();}// 填充和描边逻辑if (currentTool === 'polygon' || isClosedPath()) {ctx.fillStyle = selectionMode === 'add' ? 'rgba(34, 197, 94, 0.2)' : 'rgba(59, 130, 246, 0.2)';ctx.fill();}ctx.strokeStyle = selectionMode === 'add' ? '#22c55e' : '#3b82f6';ctx.lineWidth = 2;ctx.setLineDash([5, 5]);ctx.stroke();ctx.restore();}// 检查路径是否闭合(首尾相连)function isClosedPath() {if (currentPoints.length < 3) return false;const firstPoint = currentPoints[0];const lastPoint = currentPoints[currentPoints.length - 1];const distance = Math.hypot(lastPoint.x - firstPoint.x, lastPoint.y - firstPoint.y);return distance < 10; // 如果首尾距离小于10像素,认为闭合}// 清除当前正在绘制的选区function clearCurrentSelection() {currentPoints = [];updateSelectionInfo();}// 清除所有选区function clearAllSelections() {currentPoints = [];savedSelections = [];redrawCanvas();updateSelectionInfo();}// 重绘Canvasfunction redrawCanvas() {drawBackgroundImage();drawSavedSelection();drawCurrentSelection();}// 更新选区信息显示function updateSelectionInfo() {document.getElementById('pointCount').textContent = currentPoints.length;// 更新状态if (savedSelections.length > 0) {document.getElementById('selectionStatus').textContent = '已创建';} else {document.getElementById('selectionStatus').textContent = '无选区';}// 更新模式显示switch(selectionMode) {case 'new':document.getElementById('currentMode').textContent = '新建';break;case 'add':document.getElementById('currentMode').textContent = '添加';break;}}// 应用选区操作function applySelection() {if (currentPoints.length < 3) {return false;}// 移除最后一个点(如果是多边形工具,双击时已经闭合)if (currentTool === 'lasso' && !isClosedPath()) {return false;}switch(selectionMode) {case 'new':savedSelections = [[...currentPoints]];break;case 'add':savedSelections.push([...currentPoints]);break;}currentPoints = [];return true;}// 判断点是否在多边形内部(用于减法操作)function isPointInPolygon(point, polygon) {let inside = false;for (let i = 0, j = polygon.length - 1; i < polygon.length; j = i++) {const xi = polygon[i].x, yi = polygon[i].y;const xj = polygon[j].x, yj = polygon[j].y;const intersect = ((yi > point.y) !== (yj > point.y))&& (point.x < (xj - xi) * (point.y - yi) / (yj - yi) + xi);if (intersect) inside = !inside;}return inside;}// 修改保存选区的函数document.getElementById('saveBtn').addEventListener('click', function() {if (savedSelections.length === 0) {alert('请先绘制一个闭合的选区');return;}// 创建选区图像数据const tempCanvas = document.createElement('canvas');tempCanvas.width = canvas.width;tempCanvas.height = canvas.height;const tempCtx = tempCanvas.getContext('2d');// 绘制背景if (backgroundImage) {tempCtx.drawImage(backgroundImage, 0, 0, canvas.width, canvas.height);} else {// 绘制网格const gridSize = 20;tempCtx.fillStyle = '#ffffff';tempCtx.fillRect(0, 0, canvas.width, canvas.height);tempCtx.strokeStyle = '#eeeeee';tempCtx.lineWidth = 1;for (let x = 0; x < canvas.width; x += gridSize) {tempCtx.beginPath();tempCtx.moveTo(x, 0);tempCtx.lineTo(x, canvas.height);tempCtx.stroke();}for (let y = 0; y < canvas.height; y += gridSize) {tempCtx.beginPath();tempCtx.moveTo(0, y);tempCtx.lineTo(canvas.width, y);tempCtx.stroke();}}// 创建一个掩码Canvas来绘制所有选区const maskCanvas = document.createElement('canvas');maskCanvas.width = canvas.width;maskCanvas.height = canvas.height;const maskCtx = maskCanvas.getContext('2d');// 清除掩码CanvasmaskCtx.clearRect(0, 0, maskCanvas.width, maskCanvas.height);// 在掩码Canvas上绘制所有选区for (const selection of savedSelections) {maskCtx.beginPath();maskCtx.moveTo(selection[0].x, selection[0].y);for (let i = 1; i < selection.length; i++) {maskCtx.lineTo(selection[i].x, selection[i].y);}maskCtx.closePath();maskCtx.fillStyle = 'black';maskCtx.fill();}// 将背景应用掩码tempCtx.globalCompositeOperation = 'destination-in';tempCtx.drawImage(maskCanvas, 0, 0);// 创建下载链接const link = document.createElement('a');link.download = 'selection.png';link.href = tempCanvas.toDataURL('image/png');link.click();});// 上传图片document.getElementById('uploadBtn').addEventListener('click', function() {const input = document.createElement('input');input.type = 'file';input.accept = 'image/*';input.onchange = function(e) {const file = e.target.files[0];if (file) {const reader = new FileReader();reader.onload = function(e) {const img = new Image();img.onload = function() {backgroundImage = img;redrawCanvas();};img.src = e.target.result;};reader.readAsDataURL(file);}};input.click();});// 鼠标按下事件canvas.addEventListener('mousedown', function(e) {const rect = canvas.getBoundingClientRect();const x = e.clientX - rect.left;const y = e.clientY - rect.top;if (currentTool === 'lasso') {isDrawing = true;currentPoints = [{x, y}];} else if (currentTool === 'polygon') {currentPoints.push({x, y});redrawCanvas();updateSelectionInfo();}});// 鼠标双击事件(用于闭合多边形)canvas.addEventListener('dblclick', function() {if (currentTool === 'polygon' && currentPoints.length >= 3) {applySelection();redrawCanvas();}});// 鼠标移动事件canvas.addEventListener('mousemove', function(e) {if (!isDrawing) return;const rect = canvas.getBoundingClientRect();const x = e.clientX - rect.left;const y = e.clientY - rect.top;// 限制点的数量,避免性能问题if (currentPoints.length < 500) {// 只在距离上一个点足够远时添加新点const lastPoint = currentPoints[currentPoints.length - 1];const distance = Math.hypot(x - lastPoint.x, y - lastPoint.y);if (distance > 2) { // 距离大于2像素才添加新点currentPoints.push({x, y});redrawCanvas();updateSelectionInfo();}}});// 鼠标释放事件canvas.addEventListener('mouseup', function() {if (!isDrawing) return;isDrawing = false;// 如果路径闭合,应用选区操作if (applySelection()) {redrawCanvas();}updateSelectionInfo();});// 选区模式按钮事件document.getElementById('newSelection').addEventListener('click', function() {selectionMode = 'new';setActiveModeButton('newSelection');updateSelectionInfo();});document.getElementById('addSelection').addEventListener('click', function() {selectionMode = 'add';setActiveModeButton('addSelection');updateSelectionInfo();});// 工具按钮事件document.getElementById('polygonTool').addEventListener('click', function() {currentTool = 'polygon';setActiveToolButton('polygonTool');isDrawing = false; // 重置绘图状态currentPoints = []; // 清空当前点redrawCanvas();updateSelectionInfo();});// 设置激活的模式按钮样式function setActiveModeButton(id) {document.querySelectorAll('.mode-button').forEach(btn => {btn.classList.remove('mode-active');});document.getElementById(id).classList.add('mode-active');}// 设置激活的工具按钮样式function setActiveToolButton(id) {document.querySelectorAll('.tool-button').forEach(btn => {btn.classList.remove('tool-active');});document.getElementById(id).classList.add('tool-active');}// 初始化drawGrid();updateSelectionInfo();});</script>
</body>
</html>

多边形套索工具:

<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>PS套索工具模拟</title><script src="https://cdn.tailwindcss.com"></script><link href="https://cdn.jsdelivr.net/npm/font-awesome@4.7.0/css/font-awesome.min.css" rel="stylesheet"><script>tailwind.config = {theme: {extend: {colors: {primary: '#3b82f6',secondary: '#1e40af',neutral: '#f3f4f6',},fontFamily: {inter: ['Inter', 'sans-serif'],},}}}</script><style type="text/tailwindcss">@layer utilities {.content-auto {content-visibility: auto;}.tool-active {@apply bg-primary text-white;}.canvas-container {@apply relative w-full h-[600px] bg-gray-100 overflow-hidden;}.toolbar {@apply flex flex-col bg-gray-800 text-white p-2 space-y-3 rounded-lg shadow-lg;}.tool-button {@apply p-2 rounded hover:bg-gray-700 transition-all duration-200 flex items-center justify-center;}.canvas-wrapper {@apply flex justify-center items-center w-full h-full;}.mode-button {@apply px-2 py-1 text-xs rounded transition-all duration-200 text-center;}.mode-active {@apply bg-primary text-white;}}</style>
</head>
<body class="bg-gray-900 font-inter text-gray-100 min-h-screen flex flex-col"><header class="bg-gray-800 shadow-md py-4 px-6 flex justify-between items-center"><div class="flex items-center space-x-2"><i class="fa fa-paint-brush text-primary text-2xl"></i><h1 class="text-xl font-bold">套索工具模拟</h1></div><div class="flex items-center space-x-4"><button id="saveBtn" class="bg-primary hover:bg-secondary text-white px-4 py-2 rounded transition-all duration-200 flex items-center"><i class="fa fa-save mr-2"></i>保存选区</button><button id="uploadBtn" class="bg-primary hover:bg-secondary text-white px-4 py-2 rounded transition-all duration-200 flex items-center"><i class="fa fa-upload mr-2"></i>上传图片</button></div></header><main class="flex-1 p-6 flex flex-col md:flex-row gap-6"><div class="w-full md:w-64"><div class="toolbar"><h3 class="text-center font-semibold text-sm uppercase tracking-wider mb-2">工具</h3><div class="space-y-1"><button id="polygonTool" class="tool-button tool-active" title="多边形套索工具 (P)"><i class="fa fa-pencil-square-o"></i></button></div><!-- 添加选区模式选项 --><div class="mt-6"><h3 class="text-center font-semibold text-sm uppercase tracking-wider mb-2">选区模式</h3><div class="grid grid-cols-2 gap-1"><button id="newSelection" class="mode-button mode-active" title="新建选区"><i class="fa fa-square-o"></i></button><button id="addSelection" class="mode-button" title="添加到选区 (Shift)"><i class="fa fa-plus"></i></button></div></div><!-- 选区信息 --><div class="mt-6 p-2 bg-gray-700 rounded"><h3 class="text-center font-semibold text-sm uppercase tracking-wider mb-2">选区信息</h3><div class="text-xs space-y-1"><p>状态: <span id="selectionStatus">无选区</span></p><p>模式: <span id="currentMode">新建</span></p><p>点数量: <span id="pointCount">0</span></p></div></div></div></div><div class="flex-1"><div class="canvas-container rounded-lg shadow-xl"><div class="canvas-wrapper"><canvas id="imageCanvas" class="border-2 border-gray-300 rounded shadow-lg"></canvas></div></div></div></main><script>document.addEventListener('DOMContentLoaded', function() {// 获取Canvas元素和绘图上下文const canvas = document.getElementById('imageCanvas');const ctx = canvas.getContext('2d');// 设置Canvas尺寸canvas.width = 800;canvas.height = 500;// 工具状态let isDrawing = false;let currentPoints = []; // 当前正在绘制的点let savedSelections = []; // 已保存的选区集合,每个元素是一个独立的选区点数组let selectionMode = 'new'; // 选区模式: new, add, subtractlet backgroundImage = null;let currentTool = 'polygon'; // 只保留多边形套索工具// 生成示例图像(网格背景)function drawGrid() {const gridSize = 20;ctx.fillStyle = '#ffffff';ctx.fillRect(0, 0, canvas.width, canvas.height);ctx.strokeStyle = '#eeeeee';ctx.lineWidth = 1;// 绘制垂直线for (let x = 0; x < canvas.width; x += gridSize) {ctx.beginPath();ctx.moveTo(x, 0);ctx.lineTo(x, canvas.height);ctx.stroke();}// 绘制水平线for (let y = 0; y < canvas.height; y += gridSize) {ctx.beginPath();ctx.moveTo(0, y);ctx.lineTo(canvas.width, y);ctx.stroke();}}// 绘制背景图片function drawBackgroundImage() {if (backgroundImage) {ctx.drawImage(backgroundImage, 0, 0, canvas.width, canvas.height);} else {drawGrid();}}// 绘制已保存的选区function drawSavedSelection() {for (const selection of savedSelections) {if (selection.length < 3) continue;ctx.save();ctx.beginPath();ctx.moveTo(selection[0].x, selection[0].y);for (let i = 1; i < selection.length; i++) {ctx.lineTo(selection[i].x, selection[i].y);}ctx.closePath();// 填充半透明蓝色ctx.fillStyle = 'rgba(59, 130, 246, 0.2)';ctx.fill();// 绘制选区边界(虚线)ctx.strokeStyle = '#3b82f6';ctx.lineWidth = 2;ctx.setLineDash([5, 5]);ctx.stroke();ctx.restore();}}// 绘制当前正在绘制的选区function drawCurrentSelection() {// 首点高亮显示(无论有几个点)if (currentPoints.length > 0) {ctx.save();ctx.beginPath();ctx.arc(currentPoints[0].x, currentPoints[0].y, 6, 0, 2 * Math.PI);ctx.fillStyle = '#ef4444'; // 红色高亮ctx.fill();ctx.lineWidth = 2;ctx.strokeStyle = '#fff';ctx.stroke();ctx.restore();}if (currentPoints.length < 2) return;ctx.save();ctx.beginPath();ctx.moveTo(currentPoints[0].x, currentPoints[0].y);for (let i = 1; i < currentPoints.length; i++) {ctx.lineTo(currentPoints[i].x, currentPoints[i].y);}// 多边形工具需要手动闭合路径if (currentPoints.length >= 3) {ctx.closePath();}// 填充和描边逻辑if (currentPoints.length >= 3) {ctx.fillStyle = selectionMode === 'add' ? 'rgba(34, 197, 94, 0.2)' : 'rgba(59, 130, 246, 0.2)';ctx.fill();}ctx.strokeStyle = selectionMode === 'add' ? '#22c55e' : '#3b82f6';ctx.lineWidth = 2;ctx.setLineDash([5, 5]);ctx.stroke();ctx.restore();}// 检查路径是否闭合(首尾相连)function isClosedPath() {return currentPoints.length >= 3;}// 清除当前正在绘制的选区function clearCurrentSelection() {currentPoints = [];updateSelectionInfo();}// 清除所有选区function clearAllSelections() {currentPoints = [];savedSelections = [];redrawCanvas();updateSelectionInfo();}// 重绘Canvasfunction redrawCanvas() {drawBackgroundImage();drawSavedSelection();drawCurrentSelection();}// 更新选区信息显示function updateSelectionInfo() {document.getElementById('pointCount').textContent = currentPoints.length;// 更新状态if (savedSelections.length > 0) {document.getElementById('selectionStatus').textContent = '已创建';} else {document.getElementById('selectionStatus').textContent = '无选区';}// 更新模式显示switch(selectionMode) {case 'new':document.getElementById('currentMode').textContent = '新建';break;case 'add':document.getElementById('currentMode').textContent = '添加';break;}}// 应用选区操作function applySelection() {if (currentPoints.length < 3) {return false;}// 只要有三个点以上就允许闭合switch(selectionMode) {case 'new':savedSelections = [[...currentPoints]];break;case 'add':savedSelections.push([...currentPoints]);break;}currentPoints = [];return true;}// 判断点是否在多边形内部(用于减法操作)function isPointInPolygon(point, polygon) {let inside = false;for (let i = 0, j = polygon.length - 1; i < polygon.length; j = i++) {const xi = polygon[i].x, yi = polygon[i].y;const xj = polygon[j].x, yj = polygon[j].y;const intersect = ((yi > point.y) !== (yj > point.y))&& (point.x < (xj - xi) * (point.y - yi) / (yj - yi) + xi);if (intersect) inside = !inside;}return inside;}// 修改保存选区的函数document.getElementById('saveBtn').addEventListener('click', function() {if (savedSelections.length === 0) {alert('请先绘制一个闭合的选区');return;}// 创建选区图像数据const tempCanvas = document.createElement('canvas');tempCanvas.width = canvas.width;tempCanvas.height = canvas.height;const tempCtx = tempCanvas.getContext('2d');// 绘制背景if (backgroundImage) {tempCtx.drawImage(backgroundImage, 0, 0, canvas.width, canvas.height);} else {// 绘制网格const gridSize = 20;tempCtx.fillStyle = '#ffffff';tempCtx.fillRect(0, 0, canvas.width, canvas.height);tempCtx.strokeStyle = '#eeeeee';tempCtx.lineWidth = 1;for (let x = 0; x < canvas.width; x += gridSize) {tempCtx.beginPath();tempCtx.moveTo(x, 0);tempCtx.lineTo(x, canvas.height);tempCtx.stroke();}for (let y = 0; y < canvas.height; y += gridSize) {tempCtx.beginPath();tempCtx.moveTo(0, y);tempCtx.lineTo(canvas.width, y);tempCtx.stroke();}}// 创建一个掩码Canvas来绘制所有选区const maskCanvas = document.createElement('canvas');maskCanvas.width = canvas.width;maskCanvas.height = canvas.height;const maskCtx = maskCanvas.getContext('2d');// 清除掩码CanvasmaskCtx.clearRect(0, 0, maskCanvas.width, maskCanvas.height);// 在掩码Canvas上绘制所有选区for (const selection of savedSelections) {maskCtx.beginPath();maskCtx.moveTo(selection[0].x, selection[0].y);for (let i = 1; i < selection.length; i++) {maskCtx.lineTo(selection[i].x, selection[i].y);}maskCtx.closePath();maskCtx.fillStyle = 'black';maskCtx.fill();}// 将背景应用掩码tempCtx.globalCompositeOperation = 'destination-in';tempCtx.drawImage(maskCanvas, 0, 0);// 创建下载链接const link = document.createElement('a');link.download = 'selection.png';link.href = tempCanvas.toDataURL('image/png');link.click();});// 上传图片document.getElementById('uploadBtn').addEventListener('click', function() {const input = document.createElement('input');input.type = 'file';input.accept = 'image/*';input.onchange = function(e) {const file = e.target.files[0];if (file) {const reader = new FileReader();reader.onload = function(e) {const img = new Image();img.onload = function() {backgroundImage = img;redrawCanvas();};img.src = e.target.result;};reader.readAsDataURL(file);}};input.click();});// 鼠标按下事件canvas.addEventListener('mousedown', function(e) {const rect = canvas.getBoundingClientRect();const x = e.clientX - rect.left;const y = e.clientY - rect.top;currentPoints.push({x, y});redrawCanvas();updateSelectionInfo();});// 鼠标双击事件(用于闭合多边形)canvas.addEventListener('dblclick', function() {if (currentPoints.length >= 3) {applySelection();redrawCanvas();}});// 鼠标移动事件// 移除lasso相关逻辑,不再处理mousemove// 鼠标释放事件// 移除lasso相关逻辑,不再处理mouseup// 选区模式按钮事件document.getElementById('newSelection').addEventListener('click', function() {selectionMode = 'new';setActiveModeButton('newSelection');updateSelectionInfo();});document.getElementById('addSelection').addEventListener('click', function() {selectionMode = 'add';setActiveModeButton('addSelection');updateSelectionInfo();});// 工具按钮事件// 只保留polygonTool按钮,且始终激活document.getElementById('polygonTool').addEventListener('click', function() {// This button is always active, so no need to set active class here});// 设置激活的模式按钮样式function setActiveModeButton(id) {document.querySelectorAll('.mode-button').forEach(btn => {btn.classList.remove('mode-active');});document.getElementById(id).classList.add('mode-active');}// 设置激活的工具按钮样式function setActiveToolButton(id) {document.querySelectorAll('.tool-button').forEach(btn => {btn.classList.remove('tool-active');});document.getElementById(id).classList.add('tool-active');}// 初始化drawGrid();updateSelectionInfo();});</script>
</body>
</html>

http://www.dtcms.com/a/301069.html

相关文章:

  • 机器学习鸢尾花案例
  • 面试150 只出现一次的数字Ⅱ
  • 相关数电知识
  • 【51单片机和数码管仿真显示问题共阴共阳代码】2022-9-24
  • Web3与元宇宙:构建下一代互联网的数字文明
  • Petalinux生成文件的关系
  • Flutter 生命周期介绍
  • 2507C++,结构化存储与复合文件
  • JavaWeb(苍穹外卖)--学习笔记13(微信小程序开发,缓存菜品,Spring Cache)
  • epoll_event 事件类型详解
  • Python折线图
  • Spring 核心流程
  • 问津集 #2:High Compression and Fast Search on Semi-Structured Logs
  • 网络基础19:OSPF多区域实验
  • 小黑课堂计算机二级 WPS Office题库安装包2.52_Win中文_计算机二级考试_安装教程
  • C++算法竞赛篇(五)循环嵌套题型讲解
  • java开闭原则 open-closed principle
  • 商品中心—1.B端建品和C端缓存
  • 内网服务器实现从公网穿透
  • NVMe高速传输之摆脱XDMA设计16:队列管理模块设计(上)
  • Python 列表推导式与生成器表达式
  • 激光SLAM技术综述(2025版)
  • Python入门构建网页
  • Linux驱动20 --- FFMPEG视频API
  • 基于Django的天气数据可视化分析预测系统
  • Coze:字节跳动AI开发平台功能和架构解析
  • 第五章 中央处理器(CPU)知识体系与考法总结
  • 虚拟机ubuntu20.04共享安装文件夹
  • ubuntu 部署 coze-loop
  • C语言函数递归详解