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

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

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

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

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

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

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

代码

: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)">

// 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()

}

}

}

.canvas{

cursor: crosshair;

}

必须传入的参数图片路径url: string绘图区域宽度width: string绘图区域高度height: string

选择传入的参数是否可以绘制,默认truecanDraw: boolean坐标点信息,不传入则不绘制info: string是否可绘制,默认truecanDraw: boolean绘图颜色,默认redlineColor: string绘图笔宽度,默认2lineWidth: number绘图笔类型,rec、circle,默认reclineType: string

可以调用的方法清空画布clean()返回坐标点信息getInfo()

特殊说明canvas对象不能获得坐标,是通过父元素坐标获取的,所以该组件的父元素以上的层级不能有太多的定位、嵌套,否则绘制坐标会偏移。

域名不同的图片可能存在跨域问题,看过很多资料没有太好的办法,最后项目中是用node服务做了一个图片转为base64的接口,再给canvas绘制解决的。并不一定适用于其他项目,如果有更好的办法解决欢迎分享。

导出坐标点数据只能导出规则图案的坐标点,因为随意涂鸦的坐标点太多时会崩溃的(虽然没试过具体到什么程度会崩溃),如果有高性能的实现方式欢迎分享。

如果涂鸦后保存再请求图片url出现请求不到的情况,是因为CDN缓存的问题,在图片路径后面拼个随机码就可以解决。

linux手写涂鸦代码,canvas实现图片涂鸦功能(附代码)相关推荐

  1. 用c语言编写图片马赛克代码代码,canvas实现图片马赛克的示例代码

    1. 原生canvas实现用到的API 1) getContext(contextID) ---返回一个用于在画布上绘图的环境 复制代码代码如下: Canvas.getContext('2d') // ...

  2. 【线程池】自行准备linux环境,带你手写线程池,只需仅仅150行代码|内存池|API|连接池|应用协议丨C/C++Linux服务器开发

    [线程池]自行准备linux环境,带你手写线程池,只需仅仅150行代码 视频讲解如下,点击观看: [线程池]自行准备linux环境,带你手写线程池,只需仅仅150行代码|内存池|API|连接池|应用协 ...

  3. 【线程池】自行准备linux环境,带你手写线程池,只需仅仅150行代码

    [线程池]自行准备linux环境,带你手写线程池,只需仅仅150行代码 视频讲解如下,点击观看: [线程池]自行准备linux环境,带你手写线程池,只需仅仅150行代码|内存池|API|连接池|应用协 ...

  4. php 涂鸦,canvas实现图片涂鸦功能(附代码)

    本篇文章给大家带来的内容是关于canvas实现图片涂鸦功能(附代码),有一定的参考价值,有需要的朋友可以参考一下,希望对你有所帮助. 需求需要对图片进行标注,导出图片. 需要标注N多图片最后同时保存. ...

  5. linux 进程间通信 dbus-glib【实例】详解一(附代码)(d-feet工具使用)

    linux 进程间通信 dbus-glib[实例]详解一(附代码)(d-feet工具使用) linux 进程间通信 dbus-glib[实例]详解二(上) 消息和消息总线(附代码) linux 进程间 ...

  6. 超级详细的手写webpack4配置来启动vue2项目(附配置作用)

    基础目录结构以及各个文件的作用 初始npm项目 npm init 一路回车,一律使用默认的npm项目配置 package.json修改scripts 如下: {"name": &q ...

  7. [微信小程序]聊天对话(文本,图片)的功能(完整代码附效果图)

    相关文章: 1.小程序聊天群,发送语音,文字,图片. 2.微信小程序集成腾讯IM,实现实时音视频通话,1V1聊天 3.云开发微信小程序聊天群 4.接入网易云信IM即时通讯的微信小程序聊天室 5.微信小 ...

  8. cocos creator实例--实现FlappyBird游戏的基本功能 | 附代码

    FlappyBird是早期的早IOS上的一款非常受欢迎的像素游戏. 游戏玩法: 点击屏幕控制小鸟上下飞行:小鸟在飞行的过程中,碰撞到管子就结束游戏,飞过一根管子分数加1: 游戏效果: 实现逻辑介绍: ...

  9. html5采集手写签名,前端canvas手写签名(含移动端)

    image.png 直接上代码 手写签名 html, body { margin: 0; padding: 0; } .saveimg { text-align: center; } .saveimg ...

最新文章

  1. 拒绝了我们的连接请求_职场上,我们该如何巧妙而优雅的拒绝同事忙的请求呢?...
  2. 哇塞,可以使用PyTorch实现目标检测与跟踪,这不有趣多了
  3. 在当当买了python怎么下载源代码-Python爬取当当网APP数据
  4. 每天一道LeetCode-----生命游戏
  5. Spring—注解开发
  6. Oracle体系结构之控制文件的多路复用技术
  7. Linux 网页交互、curl
  8. OBS录制的时候黑屏怎么办
  9. 怎么用Python爬取抖音小视频? 资深程序员都这样爬取的(附源码)
  10. (7)开机动画2D版,开机动画3D版
  11. 如何删除电脑上的$RECYCLE.BIN文件夹
  12. 燕千云 YQCloud 数智化业务服务管理平台发布1.11版本
  13. python中与six有关的whl_pandas - 在升级numpy,six和python-dateutil时,无法使用pip来安装pandas - 堆栈内存溢出...
  14. opencv-python 详解阈值分割
  15. 最近设计的一个无人机app的界面
  16. GO笔记之为什么要学习GO
  17. vcs 如何 dump sva
  18. 什么是纠删码(与纠错码的区别)|纠删码与副本对比|LDPC码
  19. 【speaking】TPO1-48口语手册(所有的文本词汇+短语)
  20. 将代码从OPENGL移植到OPENGL ES需要修改什么

热门文章

  1. 苹果手机上input的button颜色变蓝变绿
  2. Linux文件权限大全(chmod+chown)
  3. pycharm2017.1专业版破解
  4. Lambda表达式从入门到玩嗨儿~
  5. 【JavaScript—数组】详解js数组一篇文章吃透js-数组
  6. 机械好还是计算机,机械微波炉的优点?微波炉机械式好还是电脑式好?
  7. JS中通过id或者class获取文本内容
  8. c语言 结构体数组的赋值
  9. 我的宝贝女儿满月了!
  10. dyned怎么修改服务器,关于DYNED的经验技巧总结dyned刷分