本篇文章给大家带来的内容是关于canvas实现图片涂鸦功能(附代码),有一定的参考价值,有需要的朋友可以参考一下,希望对你有所帮助。
需求
需要对图片进行标注,导出图片。
需要标注N多图片最后同时保存。
需要根据多边形区域数据(区域、颜色、名称)标注。
对应方案
用canvas实现涂鸦、圆形、矩形的绘制,最终生成图片base64编码用于上传
大量图片批量上传很耗时间,为了提高用户体验,改为只实现圆形、矩形绘制,最终保存成坐标,下次显示时根据坐标再绘制。
多边形区域的显示是根据坐标点绘制,名称显示的位置为多边形质心。
代码
<template> <div> <canvas :id="radom" :class="{canDraw: 'canvas'}" :width="width" :height="height" :style="{'width':`${width}px`,'height':`${height}px`}" @mousedown="canvasDown($event)" @mouseup="canvasUp($event)" @mousemove="canvasMove($event)" @touchstart="canvasDown($event)" @touchend="canvasUp($event)" @touchmove="canvasMove($event)"> </canvas> </div> </template> <script> // import proxy from './proxy.js' const uuid = require('node-uuid') export default { props: { canDraw: { // 图片路径 type: Boolean, default: true }, url: { // 图片路径 type: String }, info: { // 位置点信息 type: Array }, width: { // 绘图区域宽度 type: String }, height: { // 绘图区域高度 type: String }, lineColor: { // 画笔颜色 type: String, default: 'red' }, lineWidth: { // 画笔宽度 type: Number, default: 2 }, lineType: { // 画笔类型 type: String, default: 'circle' } }, watch: { info (val) { if (val) { this.initDraw() } } }, data () { return { // 同一页面多次渲染时,用于区分元素的id radom: uuid.v4(), // canvas对象 context: {}, // 是否处于绘制状态 canvasMoveUse: false, // 绘制矩形和椭圆时用来保存起始点信息 beginRec: { x: '', y: '', imageData: '' }, // 储存坐标信息 drawInfo: [], // 背景图片缓存 img: new Image() } }, mounted () { this.initDraw() }, methods: { // 初始化绘制信息 initDraw () { // 初始化画布 const canvas = document.getElementById(this.radom) this.context = canvas.getContext('2d') // 初始化背景图片 this.img.setAttribute('crossOrigin', 'Anonymous') this.img.src = this.url this.img.onerror = () => { var timeStamp = +new Date() this.img.src = this.url + '?' + timeStamp } this.img.onload = () => { this.clean() } // proxy.getBase64({imgUrl: this.url}).then((res) => { // if (res.code * 1 === 0) { // this.img.src = 'data:image/jpeg;base64,'+res.data // this.img.onload = () => { // this.clean() // } // } // }) // 初始化画笔 this.context.lineWidth = this.lineWidth this.context.strokeStyle = this.lineColor }, // 鼠标按下 canvasDown (e) { if (this.canDraw) { this.canvasMoveUse = true // client是基于整个页面的坐标,offset是cavas距离pictureDetail顶部以及左边的距离 const canvasX = e.clientX - e.target.parentNode.offsetLeft const canvasY = e.clientY - e.target.parentNode.offsetTop // 记录起始点和起始状态 this.beginRec.x = canvasX this.beginRec.y = canvasY this.beginRec.imageData = this.context.getImageData(0, 0, this.width, this.height) // 存储本次绘制坐标信息 this.drawInfo.push({ x: canvasX / this.width, y: canvasY / this.height, type: this.lineType }) } }, Area (p0,p1,p2) { let area = 0.0 ; area = p0.x * p1.y + p1.x * p2.y + p2.x * p0.y - p1.x * p0.y - p2.x * p1.y - p0.x * p2.y; return area / 2 ; }, // 计算多边形质心 getPolygonAreaCenter (points) { let sum_x = 0; let sum_y = 0; let sum_area = 0; let p1 = points[1]; for (var i = 2; i < points.length; i++) { let p2 = points[i]; let area = this.Area(points[0],p1,p2) ; sum_area += area ; sum_x += (points[0].x + p1.x + p2.x) * area; sum_y += (points[0].y + p1.y + p2.y) * area; p1 = p2 ; } return { x: sum_x / sum_area / 3, y: sum_y / sum_area / 3 } }, // 根据坐标信息绘制图形 drawWithInfo () { this.info.forEach(item => { this.context.beginPath() if (!item.type) { // 设置颜色 this.context.strokeStyle = item.regionColor this.context.fillStyle = item.regionColor // 绘制多边形的边 if (typeof item.region === 'string') { item.region = JSON.parse(item.region) } item.region.forEach(point => { this.context.lineTo(point.x * this.width, point.y * this.height) }) this.context.closePath() // 在多边形质心标注文字 let point = this.getPolygonAreaCenter(item.region) this.context.fillText(item.areaName, point.x * this.width, point.y * this.height) } else if (item.type === 'rec') { this.context.rect(item.x * this.width, item.y * this.height, item.w * this.width, item.h * this.height) } else if (item.type === 'circle') { this.drawEllipse(this.context, (item.x + item.a) * this.width, (item.y + item.b) * this.height, item.a > 0 ? item.a * this.width : -item.a * this.width, item.b > 0 ? item.b * this.height : -item.b * this.height) } this.context.stroke() }) }, // 鼠标移动时绘制 canvasMove (e) { if (this.canvasMoveUse && this.canDraw) { // client是基于整个页面的坐标,offset是cavas距离pictureDetail顶部以及左边的距离 let canvasX = e.clientX - e.target.parentNode.offsetLeft let canvasY = e.clientY - e.target.parentNode.offsetTop if (this.lineType === 'rec') { // 绘制矩形时恢复起始点状态再重新绘制 this.context.putImageData(this.beginRec.imageData, 0, 0) this.context.beginPath() this.context.rect(this.beginRec.x, this.beginRec.y, canvasX - this.beginRec.x, canvasY - this.beginRec.y) let info = this.drawInfo[this.drawInfo.length - 1] info.w = canvasX / this.width - info.x info.h = canvasY / this.height - info.y } else if (this.lineType === 'circle') { // 绘制椭圆时恢复起始点状态再重新绘制 this.context.putImageData(this.beginRec.imageData, 0, 0) this.context.beginPath() let a = (canvasX - this.beginRec.x) / 2 let b = (canvasY - this.beginRec.y) / 2 this.drawEllipse(this.context, this.beginRec.x + a, this.beginRec.y + b, a > 0 ? a : -a, b > 0 ? b : -b) let info = this.drawInfo[this.drawInfo.length - 1] info.a = a / this.width info.b = b / this.height } this.context.stroke() } }, // 绘制椭圆 drawEllipse (context, x, y, a, b) { context.save() var r = (a > b) ? a : b var ratioX = a / r var ratioY = b / r context.scale(ratioX, ratioY) context.beginPath() context.arc(x / ratioX, y / ratioY, r, 0, 2 * Math.PI, false) context.closePath() context.restore() }, // 鼠标抬起 canvasUp (e) { if (this.canDraw) { this.canvasMoveUse = false } }, // 获取坐标信息 getInfo () { return this.drawInfo }, // 清空画布 clean () { this.context.drawImage(this.img, 0, 0, this.width, this.height) this.drawInfo = [] if (this.info && this.info.length !== 0) this.drawWithInfo() } } } </script> <style scoped> .canvas{ cursor: crosshair; } </style>
必须传入的参数
图片路径
url: string
绘图区域宽度
width: string
绘图区域高度
height: string
选择传入的参数
是否可以绘制,默认true
canDraw: boolean
坐标点信息,不传入则不绘制
info: string
是否可绘制,默认true
canDraw: boolean
绘图颜色,默认red
lineColor: string
绘图笔宽度,默认2
lineWidth: number
绘图笔类型,rec、circle,默认rec
lineType: string
可以调用的方法
清空画布
clean()
返回坐标点信息
getInfo()
特殊说明
canvas对象不能获得坐标,是通过父元素坐标获取的,所以该组件的父元素以上的层级不能有太多的定位、嵌套,否则绘制坐标会偏移。
域名不同的图片可能存在跨域问题,看过很多资料没有太好的办法,最后项目中是用node服务做了一个图片转为base64的接口,再给canvas绘制解决的。并不一定适用于其他项目,如果有更好的办法解决欢迎分享。
导出坐标点数据只能导出规则图案的坐标点,因为随意涂鸦的坐标点太多时会崩溃的(虽然没试过具体到什么程度会崩溃),如果有高性能的实现方式欢迎分享。
如果涂鸦后保存再请求图片url出现请求不到的情况,是因为CDN缓存的问题,在图片路径后面拼个随机码就可以解决。
以上是canvas实现图片涂鸦功能(附代码)的详细内容。更多信息请关注PHP中文网其他相关文章!

HTML5代码由标签、元素和属性组成:1.标签定义内容类型,用尖括号包围,如。2.元素由开始标签、内容和结束标签组成,如内容。3.属性在开始标签中定义键值对,增强功能,如。这些是构建网页结构的基本单位。

HTML5是构建现代网页的关键技术,提供了许多新元素和功能。1.HTML5引入了语义化元素如、、等,增强了网页结构和SEO。2.支持多媒体元素和,无需插件即可嵌入媒体。3.表单增强了新输入类型和验证属性,简化了验证过程。4.提供了离线和本地存储功能,提升了网页性能和用户体验。

H5代码的最佳实践包括:1.使用正确的DOCTYPE声明和字符编码;2.采用语义化标签;3.减少HTTP请求;4.使用异步加载;5.优化图像。这些实践能提升网页的效率、可维护性和用户体验。

Web标准和技术从HTML4、CSS2和简单的JavaScript演变至今,经历了显着的发展。 1)HTML5引入了Canvas、WebStorage等API,增强了Web应用的复杂性和互动性。 2)CSS3增加了动画和过渡功能,使页面效果更加丰富。 3)JavaScript通过Node.js和ES6的现代化语法,如箭头函数和类,提升了开发效率和代码可读性,这些变化推动了Web应用的性能优化和最佳实践的发展。

H5不仅仅是HTML5的简称,它代表了一个更广泛的现代网页开发技术生态:1.H5包括HTML5、CSS3、JavaScript及相关API和技术;2.它提供更丰富、互动、流畅的用户体验,能在多设备上无缝运行;3.使用H5技术栈可以创建响应式网页和复杂交互功能。

H5与HTML5指的是同一个东西,即HTML5。HTML5是HTML的第五个版本,带来了语义化标签、多媒体支持、画布与图形、离线存储与本地存储等新功能,提升了网页的表现力和交互性。

H5referstoHTML5,apivotaltechnologyinwebdevelopment.1)HTML5introducesnewelementsandAPIsforrich,dynamicwebapplications.2)Itsupportsmultimediawithoutplugins,enhancinguserexperienceacrossdevices.3)SemanticelementsimprovecontentstructureandSEO.4)H5'srespo

H5开发需要掌握的工具和框架包括Vue.js、React和Webpack。1.Vue.js适用于构建用户界面,支持组件化开发。2.React通过虚拟DOM优化页面渲染,适合复杂应用。3.Webpack用于模块打包,优化资源加载。


热AI工具

Undresser.AI Undress
人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover
用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool
免费脱衣服图片

Clothoff.io
AI脱衣机

AI Hentai Generator
免费生成ai无尽的。

热门文章

热工具

VSCode Windows 64位 下载
微软推出的免费、功能强大的一款IDE编辑器

螳螂BT
Mantis是一个易于部署的基于Web的缺陷跟踪工具,用于帮助产品缺陷跟踪。它需要PHP、MySQL和一个Web服务器。请查看我们的演示和托管服务。

ZendStudio 13.5.1 Mac
功能强大的PHP集成开发环境

Dreamweaver Mac版
视觉化网页开发工具

MinGW - 适用于 Windows 的极简 GNU
这个项目正在迁移到osdn.net/projects/mingw的过程中,你可以继续在那里关注我们。MinGW:GNU编译器集合(GCC)的本地Windows移植版本,可自由分发的导入库和用于构建本地Windows应用程序的头文件;包括对MSVC运行时的扩展,以支持C99功能。MinGW的所有软件都可以在64位Windows平台上运行。