canvas實作圖片塗鴉功能(附程式碼)
這篇文章帶給大家的內容是關於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中文網其他相關文章!

熱AI工具

Undresser.AI Undress
人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover
用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

Video Face Swap
使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱門文章

熱工具

記事本++7.3.1
好用且免費的程式碼編輯器

SublimeText3漢化版
中文版,非常好用

禪工作室 13.0.1
強大的PHP整合開發環境

Dreamweaver CS6
視覺化網頁開發工具

SublimeText3 Mac版
神級程式碼編輯軟體(SublimeText3)

探索Canvas框架:了解常用的Canvas框架有哪些,需要具體程式碼範例引言:Canvas是HTML5中提供的一個繪圖API,透過它我們可以實現豐富的圖形和動畫效果。為了提高繪圖的效率和便利性,許多開發者開發了不同的Canvas框架。本文將介紹一些常用的Canvas框架,並提供具體程式碼範例,以幫助讀者更深入地了解這些框架的使用方法。一、EaselJS框架Ea

JavaScript教學:如何取得HTTP狀態碼,需要具體程式碼範例前言:在Web開發中,經常會涉及到與伺服器進行資料互動的場景。在與伺服器進行通訊時,我們經常需要取得傳回的HTTP狀態碼來判斷操作是否成功,並根據不同的狀態碼來進行對應的處理。本篇文章將教你如何使用JavaScript來取得HTTP狀態碼,並提供一些實用的程式碼範例。使用XMLHttpRequest

JavaScript中的HTTP狀態碼取得方法簡介:在進行前端開發中,我們常常需要處理與後端介面的交互,而HTTP狀態碼就是其中非常重要的一部分。了解並取得HTTP狀態碼有助於我們更好地處理介面傳回的資料。本文將介紹使用JavaScript取得HTTP狀態碼的方法,並提供具體程式碼範例。一、什麼是HTTP狀態碼HTTP狀態碼是指當瀏覽器向伺服器發起請求時,服務

了解canvas在遊戲開發中的威力與應用概述:隨著網路科技的快速發展,網頁遊戲越來越受到廣大玩家的喜愛。而作為網頁遊戲開發中重要的一環,canvas技術在遊戲開發中逐漸嶄露頭角,展現出強大的威力與應用。本文將介紹canvas在遊戲開發中的潛力,並透過具體的程式碼範例來展示其應用。一、canvas技術簡介canvas是HTML5中新增的元素,它允許我們使用

Canvas程式碼可以寫在HTML檔案的<body>標籤內部,通常作為HTML文件的一部分,Canvas程式碼中的核心是取得並操作Canvas元素的上下文,透過document.getElementById('myCanvas')取得到Canvas元素的引用,然後使用getContext('2d')取得2D繪圖上下文。

Vue.js適合中小型項目和快速迭代,React適用於大型複雜應用。 1)Vue.js易於上手,適用於團隊經驗不足或項目規模較小的情況。 2)React的生態系統更豐富,適合有高性能需求和復雜功能需求的項目。

Vue.js不難學,特別是對於有JavaScript基礎的開發者。 1)其漸進式設計和響應式系統簡化了開發過程。 2)組件化開發讓代碼管理更高效。 3)使用示例展示了基本和高級用法。 4)常見錯誤可以通過VueDevtools調試。 5)性能優化和最佳實踐如使用v-if/v-show和key屬性可提升應用效率。

Vue.js主要用於前端開發。 1)它是一個輕量級且靈活的JavaScript框架,專注於構建用戶界面和單頁面應用。 2)Vue.js的核心是其響應式數據系統,數據變化時視圖自動更新。 3)它支持組件化開發,UI可拆分為獨立、可複用的組件。
