canvas数据标注功能简单实现:矩形、圆形
背景说明
基于UI同学的设计,在市面上找不到刚刚好的数据标注工具,遂决定自行开发。目前需求是实现图片的矩形、圆形标注,并获取标注的坐标信息,使用canvas可以比较方便的实现该功能。

主要功能
选中图形,进行拖动
使用事件监听,mousedown确认鼠标按下坐标是否在图形内,mousemove提供坐标来更新图形坐标。
选中小圆点,进行缩放
使用事件监听,mousedown确认鼠标按下坐标是否在小圆点内,mousemove提供坐标来更新图形宽高(或半径)。
基础方法
// 获取canvas元素
const canvas = document.getElemnetById("canvas") as HtmlCanvasElement;
// 获取canvas上下文
const ctx = canvas.getContent("2d");
// 画矩形
ctx.beginPath();ctx.rect(x, y, w, h); // 参数:左上角坐标x,y 宽高whctx.fillStyle = color1; // 填充色
ctx.strokeStyle = color2; // 线条色
ctx.lineWidth = width1; // 线条宽ctx.fill(); // 填充
ctx.stroke(); // 画线ctx.closePath();
// 画圆形
ctx.beginPath();ctx.arc(x, y, r, 0, Math.PI * 2); // 参数:中心点坐标x,y 半径2 从0角度绘制到Math.PI*2角度ctx.fillStyle = color1;
ctx.strokeStyle = color2;
ctx.lineWidth = width1;ctx.fill();
ctx.stroke();ctx.closePath();
主要方法
新增图形
定义图形类,以矩形为例
export class Rectangle {x!: number;y!: number;w!: number;h!: number;fillColor!: string;strokeColor!: string;strokeWidth!: number;selectedDotIndex = -1;uuid!: string;isInsideDotFlag = false;dotR = 7;dotLineWidth = 2;dotFillColor = '#3D7FFF';dotStrokeColor = '#FFFFFF';dotConnectLineStorkeColor = '#3D7FFF';dotConnectLineWidth = 1;// 最小11x11minSize = 11;constructor(x: number, y: number, w: number, h: number, fillColor: string, storkeColor: string, storkeWidth: number) {this.x = x;this.y = y;this.w = w;this.h = h;this.fillColor = fillColor;this.strokeColor = storkeColor;this.strokeWidth = storkeWidth;this.uuid = this.generateUUID();}// 小圆点坐标get dots() {return [[this.x, this.y],[this.x + this.w / 2, this.y],[this.x + this.w, this.y],[this.x + this.w, this.y + this.h / 2],[this.x + this.w, this.y + this.h],[this.x + this.w / 2, this.y + this.h],[this.x, this.y + this.h],[this.x, this.y + this.h / 2]];}// 是否在小圆点内isInsideDot(x: number, y: number) {for (let index = 0; index < this.dots.length; index++) {const [dx, dy] = this.dots[index];if (this.distanceBetweenTwoPoints(x, y, dx, dy) < this.dotR) {this.selectedDotIndex = index;return true;}}this.selectedDotIndex = -1;return false;}// 是否在图形内isInsideShape(x: number, y: number) {this.isInsideDotFlag = this.isInsideDot(x, y);if (this.isInsideDotFlag) { // 在小圆点内也算在图形内return true;}return x >= this.x && x <= (this.x + this.w) && y >= this.y && y <= (this.y + this.h);}// 更新小圆点updateDot(mouseX: number, mouseY: number) {const handleIndex = this.selectedDotIndex;switch (handleIndex) {case 0: // 左上角this.w = this.x + this.w - mouseX;this.h = this.y + this.h - mouseY;this.x = mouseX;this.y = mouseY;break;case 1: // 上边中点this.h = this.y + this.h - mouseY;this.y = mouseY;break;case 2: // 右上角this.w = mouseX - this.x;this.h = this.y + this.h - mouseY;this.y = mouseY;break;case 3: // 右边中点this.w = mouseX - this.x;break;case 4: // 右下角this.w = mouseX - this.x;this.h = mouseY - this.y;break;case 5: // 下边中点this.h = mouseY - this.y;break;case 6: // 左下角this.w = this.x + this.w - mouseX;this.h = mouseY - this.y;this.x = mouseX;break;case 7: // 左边中点this.w = this.x + this.w - mouseX;this.x = mouseX;break;default:break;}this.w = Math.max(this.w, this.minSize);this.h = Math.max(this.h, this.minSize);}// 更新坐标updateXy(mouseX: number, mouseY: number) {this.x = mouseX - this.w / 2;this.y = mouseY - this.h / 2;}// 画小圆点drawDots(ctx: CanvasRenderingContext2D) {for (let index = 0; index < this.dots.length; index++) {const [x1, y1] = this.dots[index];const [x2, y2] = this.dots[(index + 1) % this.dots.length];// 点ctx.beginPath();ctx.arc(x1, y1, this.dotR, 0, Math.PI * 2);ctx.fillStyle = this.dotFillColor;ctx.strokeStyle = this.dotStrokeColor;ctx.lineWidth = this.dotLineWidth;ctx.fill();ctx.stroke();ctx.closePath();// 线ctx.beginPath();ctx.moveTo(x1, y1);ctx.lineTo(x2, y2);ctx.strokeStyle = this.dotConnectLineStorkeColor;ctx.lineWidth = this.dotConnectLineWidth;ctx.fill();ctx.stroke();ctx.closePath();}}// 画图draw(ctx: CanvasRenderingContext2D) {ctx.beginPath();ctx.rect(this.x, this.y, this.w, this.h);ctx.fillStyle = this.fillColor;ctx.strokeStyle = this.strokeColor;ctx.lineWidth = this.strokeWidth;ctx.fill();ctx.stroke();ctx.closePath();}// 获取uuidgenerateUUID() {var random = Math.random().toString(36).substring(2);var timestamp = new Date().getTime().toString(36);return random + timestamp;}// 计算2点之间的距离distanceBetweenTwoPoints(x1: number, y1: number, x2: number, y2: number) {const xDiff = x1 - x2;const yDiff = y1 - y2;return Math.sqrt(Math.pow(xDiff, 2) + Math.pow(yDiff, 2));}// 获取左上、右下坐标getPoints() {return [this.x, this.y, this.x + this.w, this.y + this.h];}
}
添加图形:把图形对象加入列表,方便管理
const rect = new Rectangle(this.startX, this.startY, 120, 120, this.hexToRgba(color, 0.3), color, 3);
this.dataList.unshift({name: '矩形',type: 'Rectangle',color,shape: rect
});
绘制图形:根据列表进行绘制
reDraw() {// 清空画布this.ctxFront.clearRect(0, 0, this.canvasFrontEle.width, this.canvasFrontEle.height);for (let index = 0; index < this.dataList.length; index++) {const shape = this.dataList[index].shape;shape.draw(this.ctxFront);// 选中的图形绘制小圆点if (this.selectedShape && shape.uuid === this.selectedShape.uuid) {shape.drawDots(this.ctxFront);}}
}
事件监听
// 监听canvas事件
listenCanvas() {// 事件监听// 鼠标按下this.canvasFrontEle.onmousedown = (e) => {const rect = this.canvasFrontEle.getBoundingClientRect();const clickX = e.clientX - rect.left;const clickY = e.clientY - rect.top;for (let index = 0; index < this.dataList.length; index++) {const shape = this.dataList[index].shape;if (shape.isInsideShape(clickX, clickY)) {this.selectedShape = shape;break;} else if (index === this.dataList.length - 1) {this.selectedShape = null;}}if (this.selectedShape) { // 选中图形if (this.selectedShape.isInsideDotFlag) { // 在顶点window.onmousemove = (e) => {const moveX = e.clientX - rect.left;const moveY = e.clientY - rect.top;this.selectedShape!.updateDot(moveX, moveY);this.reDraw();};} else { // 移动window.onmousemove = (e) => {const moveX = e.clientX - rect.left;const moveY = e.clientY - rect.top;this.selectedShape!.updateXy(moveX, moveY);this.reDraw();};}}this.reDraw();window.onmouseup = (e) => {window.onmousemove = null;window.onmouseup = null;}};
}
相关文章:
canvas数据标注功能简单实现:矩形、圆形
背景说明 基于UI同学的设计,在市面上找不到刚刚好的数据标注工具,遂决定自行开发。目前需求是实现图片的矩形、圆形标注,并获取标注的坐标信息,使用canvas可以比较方便的实现该功能。 主要功能 选中图形,进行拖动 使…...
Python 魔术方法深度解析:__getattr__ 与 __getattribute__
一、核心概念与差异解析 1. __getattr__ 的定位与特性 触发时机: 当访问对象中 **不存在的属性** 时自动触发,是 Python 属性访问链中的最后一道防线。 核心能力: 动态生成缺失属性实现优雅的错误处理构建链式调用接口(如 R…...
【机器学习】机器学习工程实战-第2章 项目开始前
上一章:第1章 概述 文章目录 2.1 机器学习项目的优先级排序2.1.1 机器学习的影响2.1.2 机器学习的成本 2.2 估计机器学习项目的复杂度2.2.1 未知因素2.2.2 简化问题2.2.3 非线性进展 2.3 确定机器学习项目的目标2.3.1 模型能做什么2.3.2 成功模型的属性 2.4 构建机…...
【UI设计】一些好用的免费图标素材网站
阿里巴巴矢量图标库https://www.iconfont.cn/国内最大的矢量图标库之一,拥有 800 万 图标资源。特色功能包括团队协作、多端适配、定制化编辑等,适合企业级项目、电商设计、中文产品开发等场景。IconParkhttps://iconpark.oceanengine.com/home字节跳动…...
Visual Studio(VS)的 Release 配置中生成程序数据库(PDB)文件
最近工作中的一个测试工具在测试多台设备上使用过程中闪退,存了dump,但因为是release版本,没有pdb,无法根据dump定位代码哪块出了问题,很苦恼,查了下怎么加pdb生成,记录一下。以下是具体的设置步…...
ubuntu 解挂载时提示 “umount: /home/xx/Applications/yy: target is busy.”
问题如题所示,我挂载一个squanfs文件系统到指定目录,当我使用完后,准备解挂载时,提示umount: /home/xx/Applications/yy: target is busy.,具体的如图所示, 这种提示通常是表明这个路径的内容正在被某些进…...
一条不太简单的TEX学习之路
目录 rule raisebox \includegraphics newenviro 、\vspace \stretch \setlength 解释: 总结: 、\linespread newcommand \par 小四 \small simple 、mutiput画网格 解释: 图案解释: xetex pdelatex etc index 报…...
Matplotlib完全指南:数据可视化从入门到实战
目录 引言 一、环境配置与基础概念 1.1 安装Matplotlib 1.2 导入惯例 1.3 两种绘图模式 二、基础图形绘制 2.1 折线图(Line Plot) 2.2 柱状图(Bar Chart) 三、高级图表类型 3.1 散点图(Scatter Plotÿ…...
在大数据开发中ETL是指什么?
hello宝子们...我们是艾斯视觉擅长ui设计和前端数字孪生、大数据、三维建模、三维动画10年经验!希望我的分享能帮助到您!如需帮助可以评论关注私信我们一起探讨!致敬感谢感恩! 在数字经济时代,数据已成为企业最核心的资产。然而,分散在业务系统、日志文件…...
OAuth 2.0认证
文章目录 1. 引言1.1 系列文章说明1.2 OAuth 2.0 的起源与演变1.3 应用场景概览 2. OAuth 2.0 核心概念2.1 角色划分2.2 核心术语解析 3. 四种授权模式详解3.1 授权码模式(Authorization Code Grant)3.1.1 完整流程解析3.1.2 PKCE 扩展(防止授…...
【Linux 下的 bash 无法正常解析, Windows 的 CRLF 换行符问题导致的】
文章目录 报错原因:解决办法:方法一:用 dos2unix 修复方法二:手动转换换行符方法三:VSCode 或其他编辑器手动改 总结 这个错误很常见,原因是你的 wait_for_gpu.sh 脚本 文件格式不对,具体来说…...
Kubernetes的Replica Set和ReplicaController有什么区别
ReplicaSet 和 ReplicationController 是 Kubernetes 中用于管理应用程序副本的两种资源,它们有类似的功能,但 ReplicaSet 是 ReplicationController 的增强版本。 以下是它们的主要区别: 1. 功能的演进 ReplicationController 是 Kubernete…...
WSL 导入完整系统包教程
作者: DWDROME 配置环境: OS: Ubuntu 20.04.6 LTS on Windows 11 x86_64Kernel: 5.15.167.4-microsoft-standard-WSL2ros-noetic 🧭WSL 导入完整系统包教程 ✅ 一、准备导出文件 假设你已有一个 .tar 的完整系统包(如从 WSL 或 L…...
[Lc_2 二叉树dfs] 布尔二叉树的值 | 根节点到叶节点数字之和 | 二叉树剪枝
目录 1.计算布尔二叉树的值 题解 2.求根节点到叶节点数字之和 3. 二叉树剪枝 题解 1.计算布尔二叉树的值 链接:2331. 计算布尔二叉树的值 给你一棵 完整二叉树 的根,这棵树有以下特征: 叶子节点 要么值为 0 要么值为 1 ,其…...
SOFABoot-07-版本查看
前言 大家好,我是老马。 sofastack 其实出来很久了,第一次应该是在 2022 年左右开始关注,但是一直没有深入研究。 最近想学习一下 SOFA 对于生态的设计和思考。 sofaboot 系列 SOFABoot-00-sofaboot 概览 SOFABoot-01-蚂蚁金服开源的 s…...
蓝桥杯 之 第27场月赛总结
文章目录 习题1.抓猪拿国一2.蓝桥字符3.蓝桥大使4.拳头对决 习题 比赛地址 1.抓猪拿国一 十分简单的签到题 print(sum(list(range(17))))2.蓝桥字符 常见的字符匹配的问题,是一个二维dp的问题,转化为对应的动态规划求解 力扣的相似题目 可以关注灵神…...
第十六章:Specialization and Overloading_《C++ Templates》notes
Specialization and Overloading 一、模板特化与重载的核心概念二、代码实战与测试用例三、关键知识点总结四、进阶技巧五、实践建议多选题设计题代码测试说明 一、模板特化与重载的核心概念 函数模板重载 (Function Template Overloading) // 基础模板 template<typename…...
可视化动态表单动态表单界的天花板--Formily(阿里开源)
文章目录 1、Formily表单介绍2、安装依赖2.1、安装内核库2.2、 安装 UI 桥接库2.3、Formily 支持多种 UI 组件生态: 3、表单设计器3.1、核心理念3.2、安装3.3、示例源码 4、场景案例-登录注册4.1、Markup Schema 案例4.2、JSON Schema 案例4.3、纯 JSX 案例 1、Form…...
Amdahl 定律
Amdahl 定律是用来表示,当提高系统某部分性能时对整个系统的影响,其公式如下: a表示我们提升部分初始耗时比例,k是我们的提升倍率,通过这个公式我们可以轻松的得知对每一部分的提醒,对整个系统带来的影响…...
rust学习笔记19-泛型
Rust 的泛型(Generics)允许编写可复用的代码,通过抽象类型或行为来避免重复逻辑。 1. 泛型的基本使用 函数泛型 在函数中定义泛型参数,支持不同类型的数据操作: fn max<T: PartialOrd>(a: T, b: T) -> T …...
Linux系统之美:环境变量的概念以及基本操作
本节重点 理解环境变量的基本概念学会在指令和代码操作上查询更改环境变量环境变量表的基本概念父子进程间环境变量的继承与隔离 一、引入 1.1 自定义命令(我们的exe) 我们以往的Linux编程经验告诉我们,我们在对一段代码编译形成可执行文件后…...
数学爱好者写的编程系列文章
作为一个数学爱好者,我大学读的专业却不是数学专业,而是跟计算机有关的专业。原本我对编程一窍不通,平时上课也是在看数学文献,作业基本靠同学,考试及格就行。不过后来因为毕业的压力,我还是拥抱编程了&…...
pnpm 报错 Error: Cannot find matching keyid 解决
1. 查看corepack版本,升级至0.31.0 npm i -g corepack0.31.0 这里注意环境变量,可能升级后还是指向旧版本,可以选择更新环境变量或者删除原指向的corepack命令 2. 更新pnpm corepack install -g pnpmlatest 问题解决。...
dcat-admin已完成项目部署注意事项
必须 composer update 更新项目php artisan admin:publish 发布dcatadmin的静态资源手动创建目录(如果没有) storage/appstorage/framework/cachestorage/framework/sessionsstorage/framework/views 需检查 php不要禁用以下函数 putenvsymlinkproc_…...
Ubuntu实时读取音乐软件的音频流
文章目录 一. 前言二. 开发环境三. 具体操作四. 实际效果 一. 前言 起因是这样的,我需要在Ubuntu中,实时读取正在播放音乐的音频流,然后对音频进行相关的处理。本来打算使用的PipewireHelvum的方式实现,好处是可以直接利用Helvum…...
大语言模型进化论:从文本理解到多模态认知的革命之路
一、Transformer:认知革命的基石 ### 1.1 自注意力机制:神经网络的"量子纠缠" python # 自注意力核心公式实现 def self_attention(Q, K, V, maskNone): d_k Q.size(-1) scores torch.matmul(Q, K.transpose(-2, -1)) / math.sqrt(…...
《Operating System Concepts》阅读笔记:p460-p4470
《Operating System Concepts》学习第 36 天,p460-p4470 总结,总计 11 页。 一、技术总结 无。 二、英语总结(生词:3) 1.lifespan (1)lifespan: life span(“the period of time that sth exists or happens”) c. 也写作 life-span, …...
Postgresql 删除数据库报错
1、删除数据库时,报错存在其他会话连接 ## 错误现象,存在其他的会话连接正在使用数据库 ERROR: database "cs" is being accessed by other users DETAIL: There is 1 other session using the database.2、解决方法 ## 终止被删除数据库下…...
Fiddler抓包工具最快入门
目录 前言 了解HTTP网络知识 简单了解网络访问过程 简单了解HTTP网络传输协议 工作过程 HTTP请求: Fildder工具使用教程 抓包的概念 一、什么是抓包 二、为什么要抓包 三、抓包的原理(图解) Fiddler工具 安装 使用 Fiddler查看…...
编译器与中间表示:LLVM与GCC、G++、Clang的关系详解
编译器与中间表示:LLVM与GCC、G、Clang的关系详解 引言 编译器是软件开发中不可或缺的工具,它负责将高级语言(如C/C、Java等)转换为机器语言,使计算机能够理解和执行程序。中间表示(Intermediate Represe…...
