canvas如何實現(xiàn)圖片涂鴉功能

小編給大家分享一下canvas如何實現(xiàn)圖片涂鴉功能,相信大部分人都還不怎么了解,因此分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后大有收獲,下面讓我們一起去了解一下吧!

創(chuàng)新互聯(lián)公司專注于中大型企業(yè)的網(wǎng)站建設(shè)、成都網(wǎng)站建設(shè)和網(wǎng)站改版、網(wǎng)站營銷服務(wù),追求商業(yè)策劃與數(shù)據(jù)分析、創(chuàng)意藝術(shù)與技術(shù)開發(fā)的融合,累計客戶上千多家,服務(wù)滿意度達97%。幫助廣大客戶順利對接上互聯(lián)網(wǎng)浪潮,準確優(yōu)選出符合自己需要的互聯(lián)網(wǎng)運用,我們將一直專注高端網(wǎng)站設(shè)計和互聯(lián)網(wǎng)程序開發(fā),在前進的路上,與客戶一起成長!

需求

  1. 需要對圖片進行標(biāo)注,導(dǎo)出圖片。

  2. 需要標(biāo)注N多圖片最后同時保存。

  3. 需要根據(jù)多邊形區(qū)域數(shù)據(jù)(區(qū)域、顏色、名稱)標(biāo)注。

對應(yīng)方案

  1. 用canvas實現(xiàn)涂鴉、圓形、矩形的繪制,最終生成圖片base64編碼用于上傳

  2. 大量圖片批量上傳很耗時間,為了提高用戶體驗,改為只實現(xiàn)圓形、矩形繪制,最終保存成坐標(biāo),下次顯示時根據(jù)坐標(biāo)再繪制。

  3. 多邊形區(qū)域的顯示是根據(jù)坐標(biāo)點繪制,名稱顯示的位置為多邊形質(zhì)心。

代碼

<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: { // 繪圖區(qū)域?qū)挾?
        type: String
      },
      height: { // 繪圖區(qū)域高度
        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 {
        // 同一頁面多次渲染時,用于區(qū)分元素的id
        radom: uuid.v4(),
        // canvas對象
        context: {},
        // 是否處于繪制狀態(tài)
        canvasMoveUse: false,
        // 繪制矩形和橢圓時用來保存起始點信息
        beginRec: {
          x: '',
          y: '',
          imageData: ''
        },
        // 儲存坐標(biāo)信息
        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
      },
      // 鼠標(biāo)按下
      canvasDown (e) {
        if (this.canDraw) {
          this.canvasMoveUse = true
          // client是基于整個頁面的坐標(biāo),offset是cavas距離pictureDetail頂部以及左邊的距離
          const canvasX = e.clientX - e.target.parentNode.offsetLeft
          const canvasY = e.clientY - e.target.parentNode.offsetTop
          // 記錄起始點和起始狀態(tài)
          this.beginRec.x = canvasX
          this.beginRec.y = canvasY
          this.beginRec.imageData = this.context.getImageData(0, 0, this.width, this.height)
          // 存儲本次繪制坐標(biāo)信息
          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 ;
      },
      // 計算多邊形質(zhì)心
      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
        }
      },
      // 根據(jù)坐標(biāo)信息繪制圖形
      drawWithInfo () {
        this.info.forEach(item => {
          this.context.beginPath()
          if (!item.type) {
            // 設(shè)置顏色
            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()
            // 在多邊形質(zhì)心標(biāo)注文字
            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()
        })
      },
      // 鼠標(biāo)移動時繪制
      canvasMove (e) {
        if (this.canvasMoveUse && this.canDraw) {
          // client是基于整個頁面的坐標(biāo),offset是cavas距離pictureDetail頂部以及左邊的距離
          let canvasX = e.clientX - e.target.parentNode.offsetLeft
          let canvasY = e.clientY - e.target.parentNode.offsetTop
          if (this.lineType === 'rec') { // 繪制矩形時恢復(fù)起始點狀態(tài)再重新繪制
            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') { // 繪制橢圓時恢復(fù)起始點狀態(tài)再重新繪制
            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()
      },
      // 鼠標(biāo)抬起
      canvasUp (e) {
        if (this.canDraw) {
          this.canvasMoveUse = false
        }
      },
      // 獲取坐標(biāo)信息
      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>

必須傳入的參數(shù)

  • 圖片路徑

url: string
  • 繪圖區(qū)域?qū)挾?/p>

width: string
  • 繪圖區(qū)域高度

height: string

選擇傳入的參數(shù)

  • 是否可以繪制,默認true

canDraw: boolean
  • 坐標(biāo)點信息,不傳入則不繪制

info: string
  • 是否可繪制,默認true

canDraw: boolean
  • 繪圖顏色,默認red

lineColor: string
  • 繪圖筆寬度,默認2

lineWidth: number
  • 繪圖筆類型,rec、circle,默認rec

lineType: string

可以調(diào)用的方法

  • 清空畫布

clean()
  • 返回坐標(biāo)點信息

getInfo()

特殊說明

  • canvas對象不能獲得坐標(biāo),是通過父元素坐標(biāo)獲取的,所以該組件的父元素以上的層級不能有太多的定位、嵌套,否則繪制坐標(biāo)會偏移。

  • 域名不同的圖片可能存在跨域問題,看過很多資料沒有太好的辦法,最后項目中是用node服務(wù)做了一個圖片轉(zhuǎn)為base64的接口,再給canvas繪制解決的。并不一定適用于其他項目,如果有更好的辦法解決歡迎分享。

  • 導(dǎo)出坐標(biāo)點數(shù)據(jù)只能導(dǎo)出規(guī)則圖案的坐標(biāo)點,因為隨意涂鴉的坐標(biāo)點太多時會崩潰的(雖然沒試過具體到什么程度會崩潰),如果有高性能的實現(xiàn)方式歡迎分享。

  • 如果涂鴉后保存再請求圖片url出現(xiàn)請求不到的情況,是因為cdn緩存的問題,在圖片路徑后面拼個隨機碼就可以解決。

以上是canvas如何實現(xiàn)圖片涂鴉功能的所有內(nèi)容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內(nèi)容對大家有所幫助,如果還想學(xué)習(xí)更多知識,歡迎關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道!

分享文章:canvas如何實現(xiàn)圖片涂鴉功能
標(biāo)題鏈接:http://muchs.cn/article20/pipsco.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供、電子商務(wù)、網(wǎng)站營銷網(wǎng)站設(shè)計、網(wǎng)站建設(shè)、品牌網(wǎng)站建設(shè)

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)

h5響應(yīng)式網(wǎng)站建設(shè)