canvas实现图片涂鸦功能(附代码)

互联网 18-11-16
本篇文章给大家带来的内容是关于canvas实现图片涂鸦功能(附代码),有一定的参考价值,有需要的朋友可以参考一下,希望对你有所帮助。

需求

  1. 需要对图片进行标注,导出图片。

  2. 需要标注N多图片最后同时保存。

  3. 需要根据多边形区域数据(区域、颜色、名称)标注。

对应方案

  1. 用canvas实现涂鸦、圆形、矩形的绘制,最终生成图片base64编码用于上传

  2. 大量图片批量上传很耗时间,为了提高用户体验,改为只实现圆形、矩形绘制,最终保存成坐标,下次显示时根据坐标再绘制。

  3. 多边形区域的显示是根据坐标点绘制,名称显示的位置为多边形质心。

代码

<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实现图片涂鸦功能(附代码)的详细内容,更多内容请关注技术你好其它相关文章!

来源链接:
免责声明:
1.资讯内容不构成投资建议,投资者应独立决策并自行承担风险
2.本文版权归属原作所有,仅代表作者本人观点,不代表本站的观点或立场
标签: JavaScript
上一篇:php获取远程图片并下载保存到本地的方法分析 下一篇:用canvas实现简单的下雪效果(附代码)

相关资讯