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

本篇文章给大家带来的内容是关于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缓存的问题,在图片路径后面拼个随机码就可以解决。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值