img-maker.vue 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888
  1. <template>
  2. <div>
  3. <div style="margin-bottom: 10px">
  4. <el-button plain type="primary" class="shape-border" @click="drawTypeChange('rectangle')" :disabled="!state.dragMode">标注模式</el-button>
  5. <el-button plain type="primary" class="shape-border" @click="enableDragMode" :disabled="state.dragMode">移动图片</el-button>
  6. <el-button plain type="primary" style="margin-left: 30px" class="shape-border" @click="selectLastObject" :disabled="!!state.activeTarget">选择最后一个标注(w)</el-button>
  7. <el-button
  8. plain
  9. type="primary"
  10. class="shape-border"
  11. @click="rotate(8)"
  12. :disabled="!state.activeTarget"
  13. >
  14. 顺时针旋转(r)
  15. </el-button>
  16. <el-button plain type="primary" class="shape-border" @click="rotate(-8)" :disabled="!state.activeTarget">逆时针旋转(e)</el-button>
  17. <el-button plain type="primary" style="margin-left: 30px" class="shape-border" @click="changeZoom(1.1)">放大图片</el-button>
  18. <el-button plain type="primary" class="shape-border" @click="changeZoom(0.9)">缩小图片</el-button>
  19. <!-- <el-button plain type="primary" class="shape-border" @click="drawPolygon('polygon')">多边形</el-button> -->
  20. </div>
  21. <canvas id="canvas" :width="cWidth" :height="cHeight"></canvas>
  22. </div>
  23. </template>
  24. <script lang="ts" setup>
  25. import { fabric } from 'fabric'
  26. import { reactive, watch, onMounted } from 'vue'
  27. import { ElMessage } from 'element-plus'
  28. // import { sortPoints } from '@/utils/fabric'
  29. const props = defineProps({
  30. src: {
  31. type: String,
  32. default: ''
  33. },
  34. area: {
  35. type: String,
  36. default: ''
  37. },
  38. width: {
  39. type: Number,
  40. default: 1920
  41. },
  42. height: {
  43. type: Number,
  44. default: 1080
  45. },
  46. cWidth: {
  47. type: Number,
  48. default: 960
  49. },
  50. cHeight: {
  51. type: Number,
  52. default: 540
  53. },
  54. classDef: {
  55. type: Object
  56. },
  57. jsonData: {
  58. type: Array
  59. }
  60. })
  61. const state = reactive({
  62. loading: true,
  63. radio: 1,
  64. realRadioX: 0.5,
  65. realRadioY: 0.5,
  66. imgPoint: { x: 0, y: 0 },
  67. realPoint: { x: 0, y: 0 },
  68. canvas: {} as any,
  69. mouseFrom: { x: 0, y: 0 } as canvasPoint,
  70. mouseTo: { x: 0, y: 0 } as canvasPoint,
  71. dragMode: false,
  72. isDragging: false,
  73. lastPosX: 0,
  74. lastPosY: 0,
  75. // drawType: 'rectangle' as string, //当前绘制图像的种类
  76. drawType: null as any, //当前绘制图像的种类
  77. drawWidth: 5, //笔触宽度
  78. color: '#E34F51', //画笔颜色
  79. drawingObject: null as any, //当前绘制对象
  80. activeTarget: null as any, // 当前点击活跃对象
  81. moveCount: 1, //绘制移动计数器
  82. doDrawing: false as boolean, // 绘制状态
  83. rectPath: '' as string, //矩形绘制路径
  84. //polygon 相关参数
  85. polygonMode: false as boolean,
  86. pointArray: [] as canvasPoint[],
  87. lineArray: [] as canvasPoint[],
  88. activeShape: false as any,
  89. activeLine: '' as any,
  90. line: {} as canvasPoint
  91. })
  92. watch(
  93. () => props.classDef,
  94. value => {
  95. // console.log(value)
  96. state.color = value && value.color ? value.color : '#E34F51'
  97. }
  98. )
  99. watch(
  100. () => state.drawType,
  101. value => {
  102. // console.log(value)
  103. state.canvas.selection = !value
  104. }
  105. )
  106. watch(
  107. () => props.width,
  108. value => {
  109. state.canvas.setWidth(value)
  110. }
  111. )
  112. watch(
  113. () => props.height,
  114. value => {
  115. state.canvas.setHeight(value)
  116. }
  117. )
  118. const enableDragMode = () => {
  119. state.dragMode = true
  120. state.activeTarget = null
  121. }
  122. const changeZoom = multi => {
  123. // 放大
  124. state.radio = state.canvas.getZoom() * multi
  125. state.canvas.setZoom(state.radio)
  126. // state.canvas.setViewport(state.canvas.getCenter()); // 更新视口
  127. state.canvas.renderAll()
  128. }
  129. const loadInit = () => {
  130. drawTypeChange('rectangle')
  131. state.color = props.classDef && props.classDef.color ? props.classDef.color : '#E34F51'
  132. document.addEventListener('keydown', function (e) {
  133. if (e.key === 'w' || e.key === 'W') {
  134. selectLastObject()
  135. return
  136. }
  137. if (state.activeTarget) {
  138. if (e.key === 'r' || e.key === 'R') {
  139. // 旋转矩形对象
  140. state.activeTarget.rotate(state.activeTarget.angle + 5)
  141. state.canvas.renderAll()
  142. } else if (e.key === 'e' || e.key === 'E') {
  143. // 旋转矩形对象
  144. state.activeTarget.rotate(state.activeTarget.angle - 5)
  145. state.canvas.renderAll()
  146. }
  147. }
  148. })
  149. if (props.src == '') {
  150. return
  151. }
  152. state.loading = true
  153. state.canvas = new fabric.Canvas('canvas', {})
  154. state.canvas.selectionColor = 'rgba(0,0,0,0.05)'
  155. state.canvas.on('mouse:down', mousedown)
  156. state.canvas.on('mouse:move', mousemove)
  157. state.canvas.on('mouse:up', mouseup)
  158. // // 添加鼠标滚轮缩放功能
  159. // state.canvas.on('mouse:wheel', event => {
  160. // const delta = event.delta
  161. // const zoom = state.canvas.getZoom()
  162. // const point = new fabric.Point(event.x, event.y)
  163. //
  164. // if (delta > 0) {
  165. // // 放大
  166. // state.canvas.zoomToPoint(point, zoom * 1.1)
  167. // // state.radio *= 1.1
  168. // } else {
  169. // // 缩小
  170. // state.canvas.zoomToPoint(point, zoom * 0.9)
  171. // // state.radio *= 0.9
  172. // }
  173. // state.canvas.renderAll()
  174. // event.e.preventDefault()
  175. // event.e.stopPropagation()
  176. // })
  177. // nice try!
  178. // console.log(props.jsonData)
  179. // if (props.jsonData && props.jsonData.length > 0) {
  180. // // console.log('load')
  181. // state.canvas.loadFromJSON(props.jsonData, () => {
  182. // let objects = state.canvas.getObjects()
  183. // // console.log(objects)
  184. // objects[0].selectable = false
  185. // for (let i = 1; i < objects.length; i++) {
  186. // objects[i].selectable = true
  187. // }
  188. // state.canvas.renderAll()
  189. // })
  190. // // console.log(state.canvas)
  191. // state.loading = false
  192. // return
  193. // }
  194. let imgElement = new Image()
  195. imgElement.src = props.src
  196. imgElement.onload = () => {
  197. // 区域大小/图片原始大小 缩放比例
  198. state.radio =
  199. props.cWidth / imgElement.width > props.cHeight / imgElement.height ? props.cHeight / imgElement.height : props.cWidth / imgElement.width
  200. // console.log('state.radio', state.radio)
  201. // 屏幕分辨率/图片原始大小
  202. state.realRadioX = props.width / imgElement.width
  203. state.realRadioY = props.height / imgElement.height
  204. state.imgPoint.x = Math.floor(imgElement.width / 2)
  205. state.imgPoint.y = Math.floor(imgElement.height / 2)
  206. state.realPoint.x = Math.floor(props.width / 2)
  207. state.realPoint.y = Math.floor(props.height / 2)
  208. let imgInstance = new fabric.Image(imgElement, {
  209. selectable: false,
  210. width: imgElement.width,
  211. height: imgElement.height,
  212. scaleX: state.radio,
  213. scaleY: state.radio
  214. })
  215. state.canvas.add(imgInstance)
  216. if (props.jsonData && props.jsonData.length > 0) {
  217. for (let i = 0; i < props.jsonData.length; i++) {
  218. let config = props.jsonData[i]
  219. let obj = new fabric.Path(config.pathString, config)
  220. state.canvas.add(obj)
  221. }
  222. state.canvas.renderAll()
  223. }
  224. state.radio = state.canvas.getZoom()
  225. state.loading = false
  226. // console.log(state.realRadioX)
  227. // console.log(state.realRadioY)
  228. }
  229. }
  230. // const drawImage = () => {
  231. // if (props.area === '') {
  232. // clearAll()
  233. // return
  234. // }
  235. // // console.log(props.area)
  236. // let points = props.area.split(',').map(item => {
  237. // let areas = item.split(';')
  238. // let data = areas.map((ars, index) => {
  239. // let arp = 0
  240. // let ar = Number(ars)
  241. // if (index % 2 == 0) {
  242. // let dx = Math.abs(state.realPoint.x > ar ? state.realPoint.x - ar : state.realPoint.x + ar) / state.realRadioX
  243. // let rdx = Math.abs(state.imgPoint.x - dx)
  244. // arp = rdx
  245. // } else {
  246. // let dy = Math.abs(state.realPoint.y > ar ? state.realPoint.y - ar : state.realPoint.y + ar) / state.realRadioY
  247. // let rdy = Math.abs(state.imgPoint.y - dy)
  248. // arp = rdy
  249. // }
  250. // return Number(arp) * state.radio
  251. // })
  252. // return data
  253. // })
  254. // points.forEach(point => {
  255. // drawImageObj(point)
  256. // })
  257. // }
  258. // const drawImageObj = data => {
  259. // let path = 'M '
  260. // // debugger
  261. // let points = [] as any
  262. // let len = data.length / 2
  263. // for (let i = 0; i < len; i++) {
  264. // let idx = i * 2
  265. // points.push({ x: data[idx], y: data[idx + 1] })
  266. // path += `${data[idx]} ${data[idx + 1]} L `
  267. // }
  268. // let canvasObject = null as any
  269. // if (points[0]?.y === points[1]?.y && points[2]?.y === points[3]?.y && points[0]?.x === points[3]?.x && points[1]?.x - points[2]?.x) {
  270. // path = path.replace(/L\s$/g, 'z')
  271. // canvasObject = new fabric.Path(path, {
  272. // left: data[0],
  273. // top: data[1],
  274. // stroke: state.color,
  275. // selectable: false,
  276. // strokeWidth: state.drawWidth,
  277. // fill: 'rgba(255, 255, 255, 0)',
  278. // hasControls: false
  279. // })
  280. // } else {
  281. // canvasObject = new fabric.Polygon(points, {
  282. // stroke: state.color,
  283. // strokeWidth: state.drawWidth,
  284. // fill: 'rgba(255, 255, 255, 0)',
  285. // opacity: 1,
  286. // hasBorders: false,
  287. // hasControls: false,
  288. // evented: false
  289. // })
  290. // }
  291. // canvasObject['points'] = points
  292. // state.canvas.add(canvasObject)
  293. // }
  294. const selectLastObject = () => {
  295. let objects = state.canvas.getObjects()
  296. state.activeTarget = objects[objects.length - 1]
  297. state.activeTarget.set('hasRotatingPoint', true)
  298. state.canvas.setActiveObject(state.activeTarget)
  299. state.canvas.renderAll()
  300. }
  301. const rotate = val => {
  302. if (!state.activeTarget) {
  303. ElMessage.error('请先选择旋转对象!')
  304. } else {
  305. state.activeTarget.rotate(state.activeTarget.angle + val)
  306. state.canvas.renderAll()
  307. }
  308. }
  309. const drawTypeChange = e => {
  310. state.dragMode = false
  311. state.activeTarget = null
  312. state.drawType = e
  313. state.canvas.skipTargetFind = !!e
  314. if (e == 'pen') {
  315. // isDrawingMode为true 才可以自由绘画
  316. state.canvas.isDrawingMode = true
  317. } else {
  318. state.canvas.isDrawingMode = false
  319. }
  320. }
  321. // 鼠标按下时触发
  322. const mousedown = e => {
  323. if (state.dragMode) {
  324. state.isDragging = true
  325. state.lastPosX = e.e.clientX - state.canvas.getElement().offsetLeft
  326. state.lastPosY = e.e.clientY - state.canvas.getElement().offsetTop
  327. return
  328. }
  329. // console.log(state.canvas.getObjects()[1])
  330. // console.log(e)
  331. const target = e.target
  332. // console.log(target)
  333. if (target) {
  334. if (target.type === 'rect') {
  335. state.activeTarget = target
  336. state.activeTarget.set('hasRotatingPoint', true)
  337. state.canvas.setActiveObject(state.activeTarget)
  338. return
  339. } else {
  340. state.activeTarget = null
  341. // state.canvas.discardActiveObject()
  342. }
  343. }
  344. // console.log(state.lastPosX, state.lastPosY)
  345. // console.log(state.canvas.viewportTransform)
  346. // 记录鼠标按下时的坐标
  347. let xy = e.pointer || transformMouse(e.e.offsetX, e.e.offsetY)
  348. // console.log(xy)
  349. state.mouseFrom.x = (xy.x - state.canvas.viewportTransform[4]) / state.radio
  350. state.mouseFrom.y = (xy.y - state.canvas.viewportTransform[5]) / state.radio
  351. state.doDrawing = true
  352. // console.log(state.drawType)
  353. // 绘制多边形
  354. if (state.drawType == 'polygon') {
  355. state.canvas.skipTargetFind = false
  356. try {
  357. // 此段为判断是否闭合多边形,点击红点时闭合多边形
  358. if (state.pointArray.length > 1) {
  359. // e.target.id == this.pointArray[0].id 表示点击了初始红点
  360. if (e.target && e.target.id == state.pointArray[0].id) {
  361. generatePolygon()
  362. }
  363. }
  364. //未点击红点则继续作画
  365. if (state.polygonMode) {
  366. addPoint(e)
  367. }
  368. } catch (error) {
  369. console.log(error)
  370. }
  371. }
  372. }
  373. // 鼠标松开执行
  374. const mouseup = e => {
  375. if (state.dragMode) {
  376. state.isDragging = false
  377. return
  378. }
  379. if (state.activeTarget) {
  380. state.activeTarget.setCoords()
  381. state.canvas.requestRenderAll()
  382. return
  383. }
  384. let xy = e.pointer || transformMouse(e.e.offsetX, e.e.offsetY)
  385. state.mouseTo.x = (xy.x - state.canvas.viewportTransform[4]) / state.radio
  386. state.mouseTo.y = (xy.y - state.canvas.viewportTransform[5]) / state.radio
  387. state.drawingObject = null
  388. state.moveCount = 1
  389. if (state.drawType != 'polygon' && state.drawType != 'line') {
  390. state.doDrawing = false
  391. }
  392. // 设置只允许绘制一个
  393. // let canvasObj = state.canvas.getObjects();
  394. // if(canvasObj.length >2){
  395. // state.canvas.remove(canvasObj[1])
  396. // }
  397. }
  398. //鼠标移动过程中已经完成了绘制
  399. const mousemove = e => {
  400. if (state.dragMode) {
  401. if (state.isDragging) {
  402. const deltaMove = {
  403. x: e.e.clientX - state.canvas.getElement().offsetLeft - state.lastPosX,
  404. y: e.e.clientY - state.canvas.getElement().offsetTop - state.lastPosY
  405. }
  406. state.lastPosX = e.e.clientX - state.canvas.getElement().offsetLeft
  407. state.lastPosY = e.e.clientY - state.canvas.getElement().offsetTop
  408. state.canvas.relativePan(new fabric.Point(deltaMove.x, deltaMove.y))
  409. // state.canvas.viewportTransform[4] += deltaMove.x
  410. // state.canvas.viewportTransform[5] += deltaMove.y
  411. // state.canvas.getObjects().forEach(obj => {
  412. // if (obj.type !== 'rect') {
  413. // return
  414. // }
  415. // obj.left = (obj.left - state.canvas.viewportTransform[4]) / state.radio;
  416. // obj.top = (obj.top - state.canvas.viewportTransform[5]) / state.radio;
  417. // obj.setCoords();
  418. // obj.setOptions({
  419. // selectable: true,
  420. // evented: true
  421. // });
  422. // });
  423. state.canvas.requestRenderAll();
  424. }
  425. return
  426. }
  427. if (state.activeTarget) {
  428. // const pointer = state.canvas.getPointer(e.e)
  429. // state.activeTarget.set({
  430. // left: pointer.x,
  431. // top: pointer.y
  432. // });
  433. state.canvas.requestRenderAll()
  434. return
  435. }
  436. if (state.moveCount % 2 && !state.doDrawing) {
  437. //减少绘制频率
  438. return
  439. }
  440. state.moveCount++
  441. let xy = e.pointer || transformMouse(e.e.offsetX, e.e.offsetY)
  442. if (xy.y >= 0 && xy.x <= props.cWidth && xy.y >= 0 && xy.y <= props.cHeight) {
  443. state.mouseTo.x = (xy.x - state.canvas.viewportTransform[4]) / state.radio
  444. state.mouseTo.y = (xy.y - state.canvas.viewportTransform[5]) / state.radio
  445. // 矩形
  446. if (state.drawType == 'rectangle') {
  447. if (state.mouseFrom.x < state.mouseTo.x && state.mouseFrom.y < state.mouseTo.y) {
  448. drawing()
  449. } else {
  450. // clearAll();
  451. }
  452. }
  453. if (state.drawType == 'polygon') {
  454. if (state.activeLine && state.activeLine.class == 'line') {
  455. let pointer = state.canvas.getPointer(e.e)
  456. state.activeLine.set({ x2: pointer.x, y2: pointer.y })
  457. let points = state.activeShape.get('points')
  458. points[state.pointArray.length] = {
  459. x: pointer.x,
  460. y: pointer.y,
  461. zIndex: 1
  462. }
  463. state.activeShape.set({
  464. points: points
  465. })
  466. state.canvas.renderAll()
  467. }
  468. state.canvas.renderAll()
  469. }
  470. } else {
  471. // clearAll();
  472. }
  473. }
  474. // 绘制矩形
  475. const drawing = () => {
  476. if (state.drawingObject) {
  477. state.canvas.remove(state.drawingObject)
  478. }
  479. let canvasObject = null
  480. let left = state.mouseFrom.x,
  481. top = state.mouseFrom.y,
  482. mouseFrom = state.mouseFrom,
  483. mouseTo = state.mouseTo
  484. let path =
  485. 'M ' +
  486. mouseFrom.x +
  487. ' ' +
  488. mouseFrom.y +
  489. ' L ' +
  490. mouseTo.x +
  491. ' ' +
  492. mouseFrom.y +
  493. ' L ' +
  494. mouseTo.x +
  495. ' ' +
  496. mouseTo.y +
  497. ' L ' +
  498. mouseFrom.x +
  499. ' ' +
  500. mouseTo.y +
  501. ' L ' +
  502. mouseFrom.x +
  503. ' ' +
  504. mouseFrom.y +
  505. ' z'
  506. state.rectPath = path
  507. canvasObject = new fabric.Rect({
  508. left: left,
  509. top: top,
  510. width: Math.abs(mouseTo.x - left),
  511. height: Math.abs(mouseTo.y - top),
  512. stroke: state.color,
  513. strokeWidth: state.drawWidth,
  514. fill: 'rgba(255, 255, 255, 0)',
  515. centeredRotation: true,
  516. angle: 0,
  517. label: props.classDef.label
  518. })
  519. // canvasObject = new fabric.Path(path, {
  520. // left: left,
  521. // top: top,
  522. // stroke: state.color,
  523. // selectable: false,
  524. // strokeWidth: state.drawWidth,
  525. // fill: 'rgba(255, 255, 255, 0)',
  526. // hasControls: false
  527. // })
  528. // console.log(canvasObject)
  529. canvasObject.on('selected', event => {
  530. // console.log("selected")
  531. state.activeTarget = event.target
  532. })
  533. if (canvasObject) {
  534. state.canvas.add(canvasObject)
  535. state.drawingObject = canvasObject
  536. }
  537. }
  538. // 绘制多边形开始,绘制多边形和其他图形不一样,需要单独处理
  539. // const drawPolygon = type => {
  540. // state.drawType = type
  541. // state.polygonMode = true
  542. // //这里画的多边形,由顶点与线组成
  543. // state.pointArray = [] // 顶点集合
  544. // state.lineArray = [] //线集合
  545. // state.canvas.isDrawingMode = false
  546. // }
  547. const addPoint = e => {
  548. let random = Math.floor(Math.random() * 10000)
  549. let id = new Date().getTime() + random
  550. let circle = new fabric.Circle({
  551. radius: 5,
  552. fill: '#ffffff',
  553. stroke: '#333333',
  554. strokeWidth: 0.5,
  555. left: (e.pointer.x || e.e.layerX) / state.canvas.getZoom(),
  556. top: (e.pointer.y || e.e.layerY) / state.canvas.getZoom(),
  557. selectable: false,
  558. hasBorders: false,
  559. hasControls: false,
  560. originX: 'center',
  561. originY: 'center',
  562. id: id,
  563. objectCaching: false
  564. })
  565. if (state.pointArray.length == 0) {
  566. circle.set({
  567. fill: '#00FFFF'
  568. })
  569. }
  570. let points = [
  571. (e.pointer.x || e.e.layerX) / state.canvas.getZoom(),
  572. (e.pointer.y || e.e.layerY) / state.canvas.getZoom(),
  573. (e.pointer.x || e.e.layerX) / state.canvas.getZoom(),
  574. (e.pointer.y || e.e.layerY) / state.canvas.getZoom()
  575. ]
  576. state.line = new fabric.Line(points, {
  577. strokeWidth: 2,
  578. fill: '#999999',
  579. stroke: '#999999',
  580. class: 'line',
  581. originX: 'center',
  582. originY: 'center',
  583. selectable: false,
  584. hasBorders: false,
  585. hasControls: false,
  586. evented: false,
  587. objectCaching: false
  588. })
  589. if (state.activeShape) {
  590. let pos = state.canvas.getPointer(e.e)
  591. let points = state.activeShape.get('points')
  592. points.push({
  593. x: pos.x,
  594. y: pos.y
  595. })
  596. let polygon = new fabric.Polygon(points, {
  597. stroke: '#333333',
  598. strokeWidth: 1,
  599. fill: '#cccccc',
  600. opacity: 0.3,
  601. selectable: false,
  602. hasBorders: false,
  603. hasControls: false,
  604. evented: false,
  605. objectCaching: false
  606. })
  607. state.canvas.remove(state.activeShape)
  608. state.canvas.add(polygon)
  609. state.activeShape = polygon
  610. state.canvas.renderAll()
  611. } else {
  612. let polyPoint = [
  613. {
  614. x: (e.pointer.x || e.e.layerX) / state.canvas.getZoom(),
  615. y: (e.pointer.y || e.e.layerY) / state.canvas.getZoom()
  616. }
  617. ]
  618. let polygon = new fabric.Polygon(polyPoint, {
  619. stroke: '#333333',
  620. strokeWidth: 1,
  621. fill: '#cccccc',
  622. opacity: 0.3,
  623. selectable: false,
  624. hasBorders: false,
  625. hasControls: false,
  626. evented: false,
  627. objectCaching: false
  628. })
  629. state.activeShape = polygon
  630. state.canvas.add(polygon)
  631. }
  632. state.activeLine = state.line
  633. state.pointArray.push(circle)
  634. state.lineArray.push(state.line)
  635. state.canvas.add(state.line)
  636. state.canvas.add(circle)
  637. }
  638. // 绘制不规则
  639. const generatePolygon = () => {
  640. // let points = clearPolygonLines()
  641. // let polygon = new fabric.Polygon(sortPoints(points), {
  642. // stroke: state.color,
  643. // strokeWidth: state.drawWidth,
  644. // fill: 'rgba(255, 255, 255, 0)',
  645. // opacity: 1,
  646. // hasBorders: false,
  647. // hasControls: false,
  648. // evented: false
  649. // })
  650. // state.canvas.add(polygon)
  651. let points = [{}]
  652. // console.log('state.pointArray', state.pointArray)
  653. for (let point in state.pointArray) {
  654. point = state.pointArray[point]
  655. // console.log(point.left, point.top)
  656. }
  657. state.pointArray.map(point => {
  658. points.push({
  659. x: point.left,
  660. y: point.top
  661. })
  662. state.canvas.remove(point)
  663. })
  664. state.lineArray.map(line => {
  665. state.canvas.remove(line)
  666. })
  667. state.canvas.remove(state.activeShape).remove(state.activeLine)
  668. let polygon = new fabric.Polygon(points, {
  669. stroke: state.color,
  670. strokeWidth: state.drawWidth,
  671. fill: 'rgba(255, 255, 255, 0)',
  672. opacity: 1,
  673. hasBorders: true,
  674. hasControls: false
  675. })
  676. state.canvas.add(polygon)
  677. resetPolygon()
  678. }
  679. // 坐标转换
  680. const transformMouse = (mouseX, mouseY) => {
  681. return { x: mouseX / 1, y: mouseY / 1 }
  682. }
  683. // 重置不规则四边形
  684. const resetPolygon = () => {
  685. state.activeLine = null
  686. state.activeShape = null
  687. state.polygonMode = false
  688. state.doDrawing = false
  689. state.drawType = null
  690. }
  691. // 清除绘制四边形的四个坐标点
  692. const clearPolygonLines = () => {
  693. let points = [{}]
  694. state.pointArray.forEach(point => {
  695. points.push({
  696. x: point.left,
  697. y: point.top
  698. })
  699. state.canvas.remove(point)
  700. })
  701. state.lineArray.forEach(line => {
  702. state.canvas.remove(line)
  703. })
  704. state.canvas.remove(state.activeShape).remove(state.activeLine)
  705. return points
  706. }
  707. // 撤销最后一次的操作
  708. const clearObjLast = () => {
  709. let canvasObjs = state.canvas.getObjects()
  710. let len = canvasObjs.length
  711. if (len > 1) {
  712. state.canvas.remove(canvasObjs[len - 1])
  713. }
  714. }
  715. // 全部清除
  716. const clearAll = () => {
  717. state.canvas.getObjects().forEach((element, index) => {
  718. if (index > 0) {
  719. state.canvas.remove(element)
  720. }
  721. })
  722. clearPolygonLines()
  723. resetPolygon()
  724. state.drawType = 'rectangle'
  725. }
  726. const getPoint = pi => {
  727. return Math.floor(pi / state.radio)
  728. }
  729. const getRealPoint = poi => {
  730. let dx = Math.abs(state.imgPoint.x > poi.x ? state.imgPoint.x - poi.x : state.imgPoint.x + poi.x) * state.realRadioX
  731. let dy = Math.abs(state.imgPoint.y > poi.y ? state.imgPoint.y - poi.y : state.imgPoint.y + poi.y) * state.realRadioY
  732. let rdx = Math.abs(state.realPoint.x - dx)
  733. let rdy = Math.abs(state.realPoint.y - dy)
  734. let minX = Math.min(Math.floor(rdx), props.width)
  735. let minY = Math.min(Math.floor(rdy), props.height)
  736. return { x: minX, y: minY }
  737. }
  738. // 生成真实分辨率图片的坐标点
  739. const getData = () => {
  740. let datas = [] as any
  741. let marks = ['tl', 'tr', 'br', 'bl']
  742. // if (state.lineArray.length > 0 && state.lineArray.length < 4) {
  743. // clearPolygonLines()
  744. // }
  745. datas.push(JSON.stringify(state.canvas.toJSON(['label'])))
  746. datas.push(state.canvas.toDataURL('image/png'))
  747. state.canvas.getObjects().forEach((item, index) => {
  748. // console.log(item, index)
  749. if (index > 0) {
  750. let aCoords = item.aCoords
  751. let point = {
  752. nodes: [],
  753. angle: item.angle,
  754. label: item.label
  755. }
  756. if (item.points) {
  757. item.points.forEach((item, idx) => {
  758. // if (aCoords[marks[idx]]) {
  759. // aCoords[marks[idx]].x = item.x
  760. // aCoords[marks[idx]].y = item.y
  761. // }
  762. // if (idx > 0) {
  763. point[idx] = getRealPoint({
  764. x: item?.x,
  765. y: item?.y
  766. })
  767. // }
  768. })
  769. } else {
  770. marks.forEach(mark => {
  771. let poi = {
  772. x: getPoint(aCoords[mark].x),
  773. y: getPoint(aCoords[mark].y)
  774. }
  775. // console.log(poi)
  776. poi = getRealPoint(poi)
  777. // console.log(poi)
  778. poi.x = poi.x / state.realRadioX / 1920
  779. poi.y = poi.y / state.realRadioY / 1080
  780. // console.log(poi)
  781. point['nodes'].push(poi)
  782. // point['nodes'].push(getRealPoint(poi))
  783. })
  784. }
  785. // if (item.points && item.points.length === 2) {
  786. // delete point['br']
  787. // delete point['bl']
  788. // }
  789. datas.push(point)
  790. // console.log(point)
  791. }
  792. })
  793. // console.log()
  794. return datas
  795. }
  796. // 获取归一化数据比例
  797. const normalization = () => {
  798. return {
  799. realRadioX: state.realRadioX,
  800. realRadioY: state.realRadioY
  801. }
  802. }
  803. defineExpose({ drawType: state.drawType, clearObjLast, clearAll, getData, normalization })
  804. onMounted(() => {
  805. loadInit()
  806. })
  807. </script>
  808. <style lang="scss" scoped>
  809. canvas {
  810. border: 1px dashed black;
  811. }
  812. .loading-box {
  813. display: flex;
  814. flex-direction: column;
  815. align-items: center;
  816. justify-content: center;
  817. width: 960px;
  818. height: 540px;
  819. font-size: 14px;
  820. color: #ea5413;
  821. }
  822. .draw-btn-group {
  823. display: flex;
  824. align-items: center;
  825. justify-content: flex-start;
  826. width: 960px;
  827. margin-top: 10px;
  828. .active {
  829. .draw-rect {
  830. background: #ff00ff;
  831. border-color: #ff00ff;
  832. }
  833. }
  834. .shape-box {
  835. width: 120px;
  836. text-align: left;
  837. }
  838. .shape-border {
  839. display: block;
  840. width: 80px;
  841. height: 30px;
  842. margin-right: 30px;
  843. font-size: 12px;
  844. text-align: center;
  845. }
  846. .shape-border-ti {
  847. transform: skewX(-45deg);
  848. }
  849. .draw-icon {
  850. display: inline-block;
  851. width: 80px;
  852. height: 30px;
  853. }
  854. .draw-rect {
  855. width: 80px;
  856. border-color: #333333;
  857. border-style: solid;
  858. border-width: 1px;
  859. }
  860. .draw-line {
  861. position: relative;
  862. top: -14px;
  863. border-bottom: 2px solid #00ffff;
  864. }
  865. }
  866. </style>