img-maker.vue 24 KB

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