国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

利用fabric繪畫矩形和多邊形

這篇具有很好參考價值的文章主要介紹了利用fabric繪畫矩形和多邊形。希望對大家有所幫助。如果存在錯誤或未考慮完全的地方,請大家不吝賜教,您也可以點擊"舉報違法"按鈕提交疑問。

需求在一張圖片上標(biāo)注矩形和多邊形,支持回顯;
fabric版本:4.6.0;
Fabric.js 是一個功能強(qiáng)大且操作簡單的 Javascript HTML5 canvas 工具庫。
官方文檔
參考鏈接

利用fabric繪畫矩形和多邊形

組件代碼drawer.vue
createUuid 是為了讓每一個圖形有自己的id;方便用于獲取用戶點擊的那個圖形等操作;
defaultRectStyle、defaultPolygonStyle是自己定義的一些默認(rèn)的樣式,比如顏色,邊框大小等;

<template>
  <div
    class="drawer"
    :style="`width: ${width}px; height: ${height}px`"
  >
    <div
      v-show="editable"
      class="canvans"
    >
      <canvas ref="canvas" />
    </div>
    <div class="container">
      <slot />
    </div>
  </div>
</template>
<script lang="ts">
import Vue from 'vue';
import { Component, Prop } from 'vue-property-decorator';
import { fabric } from 'fabric';
import { createUuid } from '@/utils/uuid';

export const enum DrawType {
  Pointer = '',
  Polygon = 'polygon',
  Rect = 'rectangle',
  Line = 'Line',
}

const defaultRectStyle: OthersConfigModel = {
  stroke: 'rgb(0, 232, 8)',
  strokeWidth: 1,
  fill: '',
  opacity: 0.8,
  cornerColor: 'rgb(0, 232, 8)',
  cornerSize: 4,
  selectionLineWidth: 0,
  hasBorders: false
};

const defaultPolygonStyle: OthersConfigModel = {
  stroke: 'rgb(255, 30, 0)',
  strokeWidth: 1,
  fill: '',
  opacity: 0.8,
  cornerColor: 'rgb(255, 30, 0)',
  cornerSize: 4,
  cornerStyle: 'circle',
  selectionLineWidth: 0
};

const defaultCircleStyle: OthersConfigModel = {
  fill: '#FFE700'
  // selectionLineWidth: 0
};

export interface MousePosition {
  x: number;
  y: number;
}

export interface Shape {
  type: DrawType | string;
  points: Array<MousePosition | number> | number[][];
  content?: number|string;
  others?: OthersConfigModel;
}

export interface RectModel {
  left: number;
  top: number;
  width: number;
  height: number;
  scaleX?: number;
  scaleY?: number;
}

export interface OthersModel{
  stroke: string;
  opacity: number;
  uuid: string | null;
  strokeWidth: number;
  fill: string;
  cornerColor: string;
  cornerStyle: string;
  cornerSize: number;
  radius: number;
  selectionLineWidth: number;
  clickIndex: number;
  hasBorders: boolean;
  hasControls: boolean; //  不顯示邊框點
  selectable: boolean; // 禁止選中當(dāng)前元素
  lockMovementX: boolean; // 禁止元素移動
  lockMovementY: boolean;
  lockScalingX: boolean; // 禁止元素縮
  lockScalingY: boolean;
  standard_item_id: number;
  visible: boolean; // 設(shè)置元素不可見
}

export type OthersConfigModel = Partial<OthersModel>

@Component()
export default class Drawer extends Vue {
  @Prop({
    type: Number,
    required: false,
    default: 780
  })
  private width!: number;

  @Prop({
    type: Number,
    required: false,
    default: 580
  })
  private height!: number;

  @Prop({ type: Boolean, default: true })
  private editable!: boolean;

  // @PropSync('shapes', { type: Array })
  // private bindShapes!: Array<Shape>;

  @Prop({
    type: Object,
    required: false,
    default: () => {}
  })
  private rectStyle!: {};

  @Prop({
    type: Object,
    required: false,
    default: () => {}
  })
  private polygonStyle!: {};

  private canvas: any = null;
  private drawType = '';
  private drawingObject: any = null;
  private drawingShape: any[] = [];

  private operateAttribute = ['lockMovementX', 'lockMovementY', 'lockScalingX', 'lockScalingY']; // 畫框的時候不能放大縮小移動;
  private canvasObjects: number[] | null = null; // 用戶點擊置于頂層/底層 導(dǎo)致順序變化 ids變化 index也變化

  // 改變時候觸發(fā)
  private async onChange () {
    // 防止用戶將不想要的也傳出去
    this.$emit('onChange', this.canvas.getObjects().filter(x => x.uuid));
  }

  // 將點位比例縮小 從后臺傳來渲染
  public narrowPolygon (narrowPolygon: MousePosition[], others?: OthersConfigModel) {
    const polygon = this.drawPolygon(narrowPolygon, others);
    polygon.centerPoints = polygon.getCenterPoint(); // 獲取中心點
    this.canvas.add(polygon);
  }

  public narrowRect (points: number[], others?: OthersConfigModel) {
    this.canvas.add(this.drawRect(points, others));
  }

  public narrowCircle (narrowCircle: MousePosition, others?: OthersConfigModel) {
    this.canvas.add(this.drawCircle(narrowCircle, others));
  }

  // 展示組 矩形+文字
  public showRectGroup (points: number[], text: string, others?: OthersConfigModel) {
    const rect = this.drawRect(points, others);
    const content = this.showContent(rect, text);
    const group = this.getGroup(rect, content);
    this.canvas.add(group);
  }

  // 展示組 多邊形+文字
  public showPolygonGroup (narrowPolygon: MousePosition[], text: string, others?: OthersConfigModel) {
    const polygon = this.drawPolygon(narrowPolygon, others);
    polygon.centerPoints = polygon.getCenterPoint(); // 獲取中心點
    const content = this.showContent(polygon, text);
    const group = this.getGroup(polygon, content);
    this.canvas.add(group);
  }

  // 刪除選中
  public remove (object: any) {
    this.canvas.remove(object);
  }

  // 刪除選中
  public removeItemByIndex (itemIndex: number) {
    (this.canvasObjects ?? this.canvas.getObjects()).forEach((item, index) => {
      if (!item.uuid) {
        itemIndex += 1;
        return;
      }
      if (index === itemIndex) {
        this.remove(item);
      }
    });
    const spliceObject = this.canvasObjects?.splice(itemIndex, 1);
  }

  public removeAll () {
    this.canvas.getObjects().forEach(item => {
      this.canvas.remove(item);
    });
  }

  // 設(shè)置為當(dāng)前活躍
  public async setActiveObjectByIndex (itemIndex: number) {
    (this.canvasObjects ?? this.canvas.getObjects()).forEach((item, index) => {
      if (!item.uuid) {
        itemIndex += 1;
        return;
      }
      if (index === itemIndex) {
        this.canvas.setActiveObject(item);
      }
    });
  }

  public setVisible (visible: boolean) {
    // 元素不可見  //  隱藏點
    (this.canvasObjects ?? this.canvas.getObjects()).forEach(item => {
      item.visible = visible;
      item.setControlsVisibility({
        mt: visible,
        mb: visible,
        ml: visible,
        mr: visible,
        bl: visible,
        br: visible,
        tl: visible,
        tr: visible
      });
    });
    this.canvas.renderAll();
  }

  public resetObjects () {
    this.canvasObjects = null;
  }

  private init () {
    this.canvas = new fabric.Canvas(this.$refs.canvas, {
      // isDrawingMode: this.editable,
    });
    this.canvas.on('mouse:down', this.mouseDown);
    this.canvas.on('mouse:move', this.mouseMove);
    this.canvas.on('mouse:up', this.mouseUp);
    this.canvas.on('mouse:dblclick', this.onPolygonDrawEnd);
    this.canvas.on('mouse:over', this.mouseOver);
    this.canvas.on('mouse:out', this.mouseOut);
    this.canvas.on('selection:updated', e => {
      this.edit(e.target);
    });
    this.canvas.on('selection:created', e => {
      this.edit(e.target);
    });
    this.canvas.on('object:modified', this.onChange);
    this.canvas.on('object:moving', this.mouseMoving); // 阻止對象移動到畫布外面
  }

  public setDrawType (type) {
    // 外部調(diào)用此方法,切換畫圖模式
    this.drawType = type;
  }

  private transformMouse (e): MousePosition {
    return e.pointer;
  }

  private drawLine (position: MousePosition) {
    return new fabric.Line([position.x, position.y, position.x, position.y], {
      type: DrawType.Line,
      objectCaching: false,
      hasBorders: false,
      selectable: false,
      ...defaultPolygonStyle
    });
  }

  // 畫多邊形
  private drawPolygon (points: Array<MousePosition>, others?: OthersConfigModel) {
    return new fabric.Polygon(points, {
      type: DrawType.Polygon,
      uuid: createUuid(), // todo
      objectCaching: false,
      hasBorders: false,
      transparentCorners: false,
      selectionColor: 'rgba(0,0,0,0)',
      ...defaultPolygonStyle,
      ...this.polygonStyle,
      ...others
    });
  }

  // 畫四邊形
  private drawRect (points: Array<number>, others?: OthersConfigModel) {
    const rect = new fabric.Rect({
      type: DrawType.Rect,
      uuid: createUuid(), // todo
      left: points[0],
      top: points[1],
      width: points[2] || 0,
      height: points[3] || 0,
      objectCaching: false,
      transparentCorners: false,
      selectionColor: 'rgba(0,0,0,0)',
      lockRotation: true,
      ...defaultRectStyle,
      ...this.rectStyle,
      ...others
    });
    // eslint-disable-next-line spellcheck/spell-checker
    rect.setControlsVisibility({ mtr: false }); // 隱藏旋轉(zhuǎn)點
    return rect;
  }

  private drawCircle (position: MousePosition, others?: OthersConfigModel) {
    const circle = new fabric.Circle({
      radius: 4,
      uuid: createUuid(),
      top: position.y,
      left: position.x,
      ...defaultCircleStyle,
      ...others
    });
    // eslint-disable-next-line spellcheck/spell-checker
    circle.setControlsVisibility({ mtr: false }); // 隱藏旋轉(zhuǎn)點
    return circle;
  }

  private showContent (shape: any, text: string) {
    return new fabric.Text(text, {
      left: shape.left + 5,
      top: shape.top + 10,
      fill: 'white',
      fontSize: 14
    });
  }

  private getGroup (ellipse: any, text: string) {
    return new fabric.Group([ellipse, text], {
      uuid: createUuid(),
      objectCaching: false,
      transparentCorners: false,
      selectionColor: 'rgba(0,0,0,0)',
      hasControls: false,
      hasBorders: false,
      lockMovementX: true,
      lockMovementY: true,
      lockScalingX: true,
      lockScalingY: true
    });
  }

  // 鼠標(biāo)按下
  private mouseDown (e) {
    // 如果type不為line,則認(rèn)為正在編輯圖形,鼠標(biāo)點擊事件不觸發(fā)畫新圖形
    if (!this.drawType) {
      this.operateAttribute.map(item => {
        if (this.selectObject() && this.selectObject()[item]) {
          this.selectObject()[item] = !this.rectStyle ? this.selectObject()[item] : this.rectStyle[item];
        }
      });
      return;
    }
    // 防止在畫1的里面畫2的時候 影響1
    if (this.selectObject() && !this.drawingObject) {
      this.operateAttribute.map(item => {
        this.selectObject()[item] = true;
      });
    }
    this[`mouseDown${this.drawType}`](this.transformMouse(e));
  }

  // 鼠標(biāo)移動
  private mouseMove (e) {
    if (!this.drawType || !this.drawingObject) {
      return;
    }
    this[`mouseMove${this.drawType}`](this.transformMouse(e));
  }

  private mouseDownPolygon (position: MousePosition) {
    const line = this.drawLine(position);
    this.drawingShape.push(line);
    this.drawingObject = line;
    this.canvas.add(line);
  }

  private mouseDownRect (position: MousePosition) {
    if (this.drawingObject) {
      this.canvas.remove(this.drawingObject);
      this.canvas.add(this.drawingObject);
      this.drawEnd(this.drawingObject);
      return;
    }
    const rect = this.drawRect([position.x, position.y, 0, 0]);
    this.drawingObject = rect;
    this.canvas.add(rect);
  }

  private mouseMovePolygon (position: MousePosition) {
    if (this.drawingObject) {
      this.drawingObject.set({
        x2: position.x,
        y2: position.y
      });
      this.canvas.remove(this.drawingObject);
      this.canvas.add(this.drawingObject);
    }
  }

  private mouseMoveRect (position: MousePosition) {
    if (this.drawingObject) {
      this.drawingObject.set({
        width: position.x - this.drawingObject.get('left'),
        height: position.y - this.drawingObject.get('top')
      });
      this.canvas.renderAll();
    }
  }

  private mouseUpRect (position: MousePosition) {
    // 第一次抬起的時候是有對象的  第二次沒有才對  思路:第一個點的鼠標(biāo)松開的時候重新給draw一下
    if (this.drawingObject) {
      // 讓其可以拖拽 需要確保它有uuid
      if (this.selectObject() && this.selectObject().uuid) {
        this.operateAttribute.map(item => {
          this.selectObject()[item] = false;
        });
      }
      this.canvas.remove(this.drawingObject);
      const rect = this.drawRect([this.drawingObject.left, this.drawingObject.top, 0, 0]);
      this.drawingObject = rect;
      this.canvas.add(rect);
      // 設(shè)置一下width height 否則松開不動就會有問題
      this.drawingObject.set({
        width: position.x - this.drawingObject.get('left'),
        height: position.y - this.drawingObject.get('top')
      });
      this.canvas.renderAll();
    }
  }

  private mouseUp (e) {
    if (this[`mouseUp${this.drawType}`] && e.pointer) {
      this[`mouseUp${this.drawType}`](this.transformMouse(e));
    }
  }

  private mouseMoving (e) {
    const padding = -1.5; // 內(nèi)容距離畫布的空白寬度,主動設(shè)置
    const obj = e.target;
    if (obj.currentHeight > obj.canvas.height - padding * 2 || obj.currentWidth > obj.canvas.width - padding * 2) {
      return;
    }
    obj.setCoords();
    const objBoundingBox = obj.getBoundingRect();
    if (objBoundingBox.top < padding || objBoundingBox.left < padding) {
      obj.top = Math.max(obj.top, obj.top - objBoundingBox.top + padding);
      obj.left = Math.max(obj.left, obj.left - objBoundingBox.left + padding);
    }
    if (
      objBoundingBox.top + objBoundingBox.height > obj.canvas.height - padding ||
      objBoundingBox.left + objBoundingBox.width > obj.canvas.width - padding
    ) {
      obj.top = Math.min(obj.top, obj.canvas.height - objBoundingBox.height + obj.top - objBoundingBox.top - padding);
      obj.left = Math.min(obj.left, obj.canvas.width - objBoundingBox.width + obj.left - objBoundingBox.left - padding);
    }
  }

  private mouseOver (e: Event) {
    if (!e.target) {
      return;
    }
    this.$emit('handleMouseOver', e.target);
  }

  private mouseOut (e: Event) {
    if (!e.target) {
      return;
    }
    this.$emit('handleMouseOut', e.target);
  }

  private async onPolygonDrawEnd () {
    if (this.drawingObject) {
      const points = this.drawingShape.map(line => {
        return {
          x: line.get('x1'),
          y: line.get('y1')
        };
      });
      this.drawingShape.forEach(item => {
        this.canvas.remove(item);
      });
      const polygon = this.drawPolygon(points.splice(0, points.length - 1));
      polygon.centerPoints = polygon.getCenterPoint();
      this.drawEnd(polygon);
      this.canvas.add(polygon);
    } else {
      this.$emit('copyShape', this.selectObject());
    }
  }

  private async drawEnd (object) {
    this.drawingObject = null;
    this.drawingShape = [];
    if (this.canvasObjects) {
      this.canvasObjects.push(object); // 置于頂層/底層之后
    }
    this.edit(object);
    // 設(shè)置為當(dāng)前活躍
    this.canvas.setActiveObject(object);
    await this.$nextTick();
    this.onChange();
    this.$emit('change');
  }

  private edit (object) {
    if (object.type === DrawType.Polygon) {
      const lastControl = object.points.length - 1;
      object.controls = object.points.reduce((a, point, index) => {
        a['p' + index] = new fabric.Control({
          positionHandler: (dim, finalMatrix, fabricObject) => {
            const x = fabricObject.points[index].x - fabricObject.pathOffset.x;
            const y = fabricObject.points[index].y - fabricObject.pathOffset.y;
            return fabric.util.transformPoint(
              { x: x, y: y },
              fabric.util.multiplyTransformMatrices(
                fabricObject.canvas.viewportTransform,
                fabricObject.calcTransformMatrix()
              )
            );
          },
          actionHandler: this.anchorWrapper(index > 0 ? index - 1 : lastControl, this.actionHandler),
          actionName: 'modifyPolygon',
          pointIndex: index
        });
        return a;
      }, {});
    } else {
      object.cornerStyle = 'circle';
      object.controls = fabric.Object.prototype.controls;
    }
    const ids = (this.canvasObjects ?? this.canvas.getObjects()).map(item => item.uuid).filter(x => x);
    this.canvas.requestRenderAll();
    this.$emit('editIndex', object.clickIndex ?? ids.indexOf(object.uuid));
  }

  private anchorWrapper (anchorIndex, fn) {
    return function (eventData, transform, x, y) {
      const fabricObject = transform.target;
      const absolutePoint = fabric.util.transformPoint(
        {
          x: fabricObject.points[anchorIndex].x - fabricObject.pathOffset.x,
          y: fabricObject.points[anchorIndex].y - fabricObject.pathOffset.y
        },
        fabricObject.calcTransformMatrix()
      );
      const actionPerformed = fn(eventData, transform, x, y);
      const polygonBaseSize = fabricObject._getNonTransformedDimensions();
      const newX = (fabricObject.points[anchorIndex].x - fabricObject.pathOffset.x) / polygonBaseSize.x;
      const newY = (fabricObject.points[anchorIndex].y - fabricObject.pathOffset.y) / polygonBaseSize.y;
      fabricObject.setPositionByOrigin(absolutePoint, newX + 0.5, newY + 0.5);
      return actionPerformed;
    };
  }

  private actionHandler (eventData, transform, x, y) {
    const polygon = transform.target;
    const currentControl = polygon.controls[polygon.__corner];
    const mouseLocalPosition = polygon.toLocalPoint(new fabric.Point(x, y), 'center', 'center');
    const polygonBaseSize = polygon._getNonTransformedDimensions();
    const size = polygon._getTransformedDimensions(0, 0);
    const finalPointPosition = {
      x: (mouseLocalPosition.x * polygonBaseSize.x) / size.x + polygon.pathOffset.x,
      y: (mouseLocalPosition.y * polygonBaseSize.y) / size.y + polygon.pathOffset.y
    };
    polygon.points[currentControl.pointIndex] = finalPointPosition;
    return true;
  }

  public selectObject () {
    return this.canvas.getActiveObject();
  }

  public sendToBack () {
    if (!this.canvasObjects) {
      this.canvasObjects = this.canvas.getObjects();
    }
    this.canvas.sendToBack(this.selectObject());
  }

  public setTop () {
    if (!this.canvasObjects) {
      this.canvasObjects = this.canvas.getObjects();
    }
    this.canvas.bringToFront(this.selectObject());
  }

  public async onLoadStereo () {
    this.updateCanvasStyle(this.width, this.height);
  }

  public async updateCanvasStyle (width: number, height: number) {
    // 改變canvas 的寬高
    this.canvas.setWidth(width);
    this.canvas.setHeight(height);
  }

  private beforeDestroy () {
    this.canvas.removeListeners();
    this.removeAll();
  }

  private mounted () {
    this.init();
  }
}
</script>
<style lang="scss" scoped>
.drawer {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  user-select: none;
  width: 100%;
  height: calc(100% - 70px);
  .canvans,
  .container {
    position: absolute;
    left: 0;
    top: 0;
    width: 100%;
    height: 100%;
  }
}
</style>
<style lang="scss">
.canvas-container {
  z-index: 2;
  width: 100% !important; //  設(shè)置這幾個百分比用來解決窗口尺寸變化 框也變化
  height: 100% !important;
}
.lower-canvas,.upper-canvas  {
  width: 100% !important;
  height: 100% !important;
}
</style>

組件代碼ImageDrawer.vue

dropImage 可以拖拽圖片
使用組件的時候需要傳入shapes

<template>
  <div @mousedown.prevent="dropImage(arguments[0])">
    <drawer
      ref="drawer"
      v-bind="$attrs"
      :width="width"
      :height="height"
      v-on="$listeners"
      @onChange="onChange"
      @contextmenu.prevent.native="openMenu($event)"
    >
      <basic-img
        class="img"
        :style="`width: ${width}px; height: ${height}px;${maxWidth ? `maxWidth:${maxWidth}px` : ''}`"
        v-bind="$attrs"
        @load="imageLoaded"
      />
      <ul
        v-show="visible"
        :style="contextmenuStyle"
        class="contextmenu"
      >
        <li @click="setBack">
          置于底層
        </li>
        <li @click="setTop">
          置于頂層
        </li>
      </ul>
    </drawer>
  </div>
</template>

<script lang='ts'>
import Vue from 'vue';
import { Component, Ref, Prop, PropSync, Watch } from 'vue-property-decorator';
import Drawer, { Shape, DrawType, MousePosition, RectModel, OthersConfigModel } from './Drawer.vue';
import BasicImg from '@/components/img/BasicImg.vue';

export type ShapeModel = Shape

@Component({
  components: {
    Drawer,
    BasicImg
  }
})
export default class ImageDrawer extends Vue {
  @Ref()
  private readonly drawer !: Drawer

  @Prop({
    type: [String, Number],
    required: false,
    default: 780
  })
  private width!: string | number; // innerHeight

  @Prop({
    type: [String, Number],
    required: false,
    default: 0
  })
  private maxWidth!: string | number; // innerHeight

  @Prop({
    type: [String, Number],
    required: false,
    default: 580
  })
  private height!: string | number;

  @Prop({
    type: Boolean,
    required: false,
    default: false
  })
  private canDropImage!: boolean;

  @PropSync('shapes', { type: Array })
  private bindShapes!: Array<Shape>;

  @Prop({
    type: Array,
    required: false,
    default: () => []
  })
  private othersShapes!: Array<Shape>;

  private imgInfo: any={}; // 圖片信息
  private xRate=0;
  private yRate=0;

  private contextmenuStyle: any={}
  private visible=false;

  private odiv: any=null;
  private editAble=false;

  private onChange (object: any) {
    this.bindShapes = object.map(item => {
      let points: any = [];
      if (item.type === DrawType.Polygon && item.centerPoints) {
        const pointsX = item.getCenterPoint().x - item.centerPoints.x;
        const pointsY = item.getCenterPoint().y - item.centerPoints.y;
        points = item.points.map(item => {
          return {
            x: item.x + pointsX,
            y: item.y + pointsY
          };
        });
      }
      return {
        type: item.type,
        // 放大坐標(biāo)
        points: item.type === DrawType.Polygon ? this.enlargePolygon(points) : this.enlargeRect(item),
        styles: {
          ...item
        }
      };
    });
  }

  // 將點位比例放大  渲染計算之后給后臺
  private enlargePolygon (points: MousePosition[]) {
    return points.map(item => {
      return [item.x / this.xRate, item.y / this.yRate];
    });
  }

  private enlargeRect (item: RectModel) {
    item.width = item.width * (item.scaleX || 1);
    item.height = item.height * (item.scaleY || 1);
    // 解決從各個方向畫的問題
    const convertItem: RectModel = this.getConvertPoints(item);
    const { left, top, width, height } = convertItem;
    // 計算完成之后需要把寬度設(shè)回去 不然會影響頁面上的效果
    item.width = item.width / (item.scaleX || 1);
    item.height = item.height / (item.scaleY || 1);
    return [
      [
        left / this.xRate,
        top / this.yRate
      ],
      [
        (left + width) / this.xRate,
        (top + height) / this.yRate
      ]
    ];
  }

  private getConvertPoints (item: RectModel) {
    if (item.width < 0 && item.height < 0) {
      // 右下角畫
      item.left = item.left + item.width;
      item.top = item.top + item.height;
      item.width = -item.width;
      item.height = -item.height;
    } else if (item.width < 0) {
      // 右上角畫
      item.left = item.left + item.width;
      item.width = -item.width;
    } else if (item.height < 0) {
      // 左下角畫
      item.top = item.top + item.height;
      item.height = -item.height;
    }
    return item;
  }

  private async imageLoaded (e) {
    // 獲取圖片原始寬高 // 實際寬高
    this.imgInfo = e.target;
    await this.$nextTick();
    this.drawer && this.drawer.updateCanvasStyle(this.imgInfo.width, this.imgInfo.height);
    this.$emit('load', e);
    // 最終換算比例
    this.xRate = e.target.width / e.target.naturalWidth;
    this.yRate = e.target.height / e.target.naturalHeight;
    // 得到imgInfo
    await this.initShapes();
  }

  public async updateCanvasStyle (width: number, height: number, xRate?: number, yRate?: number) {
    await this.removeAll(); // 防止有些時候執(zhí)行多次
    this.drawer && this.drawer.updateCanvasStyle(width, height);
    this.xRate = xRate || this.xRate;
    this.yRate = yRate || this.yRate;
    await this.initShapes();
  }

  public async initShapes (selectFirst = true) {
    await this.removeAll();
    // if(this.bindShapes && !this.bindShapes.length && this.othersShapes && !this.othersShapes.length){
    //   return
    // }
    // 不同的type 縮小的比例不一樣  縮小比例
    await this.othersShapes.map(item => {
      if (item.type === DrawType.Rect) {
        this.narrowRect(item.points as number[], item.others as OthersConfigModel);
      } else if (item.type === DrawType.Polygon) {
        this.narrowPolygon(item.points as number[], item.others as OthersConfigModel);
      }
    });
    await this.bindShapes.map(item => {
      if (item.type === DrawType.Rect) {
        this.narrowRect(item.points as number[], item.others as OthersConfigModel, item.content as string);
      } else if (item.type === DrawType.Polygon) {
        this.narrowPolygon(item.points as number[], item.others as OthersConfigModel, item.content as string);
      }
    });
    this.drawer && this.drawer.resetObjects(); // 重新繪制的時候就reset
    if (selectFirst) {
      // init選擇為第一個
      this.setActiveObjectByIndex(0);
    }
  }

  // 將點位比例縮小 從后臺傳來渲染 content 3d點云處使用
  private narrowPolygon (points: number[], others?: OthersConfigModel, content?: string) {
    const pointsList = points.map(item => {
      return {
        x: item[0] * this.xRate,
        y: item[1] * this.yRate
      };
    });
    if (content) {
      this.drawer.showPolygonGroup(pointsList, content, others);
    } else {
      this.drawer && this.drawer.narrowPolygon(pointsList, others);
    }
  }

  private narrowRect (points: number[], others?: OthersConfigModel, content?: string) {
    const pointsList = [
      points[0][0] * this.xRate,
      points[0][1] * this.yRate,
      (points[1][0] - points[0][0]) * this.xRate,
      (points[1][1] - points[0][1]) * this.yRate
    ];
    if (content) {
      this.drawer.showRectGroup(pointsList, content, others);
    } else {
      this.drawer && this.drawer.narrowRect(pointsList, others);
    }
  }

  public narrowCircle (points: number[], others?: OthersConfigModel) {
    const point = {
      x: points[0] * this.xRate,
      y: points[1] * this.yRate
    };
    this.drawer.narrowCircle(point, others);
  }

  public removeAll () {
    this.drawer.removeAll();
  }

  public setDrawType (drawType: string) {
    this.editAble = drawType !== '';
    this.drawer.setDrawType(drawType);
  }

  public removeItemByIndex (index: number) {
    this.drawer.removeItemByIndex(index);
  }

  public setActiveObjectByIndex (index: number) {
    this.drawer && this.drawer.setActiveObjectByIndex(index);
  }

  public setVisible (visible: boolean) {
    this.drawer.setVisible(visible);
  }

  // 鼠標(biāo)右鍵
  private async openMenu (e) {
    this.contextmenuStyle = {
      left: e.offsetX + 'px',
      top: e.offsetY + 'px'
    };
    this.visible = true;
  }

  private setBack () {
    this.drawer.sendToBack();
  }

  private setTop () {
    this.drawer.setTop();
  }

  private closeMenu () {
    this.visible = false;
  }

  // 放大縮小
  public setTransform (multiples: number) {
    (this.$el as any).style.transform = `scale(${multiples})`;
  }

  // 拖拽
  public dropImage (e: any) {
    if (!e || this.editAble || !this.canDropImage) {
      return;
    }
    this.odiv = this.drawer.$el; // 獲取目標(biāo)元素
    // 算出鼠標(biāo)相對元素的位置
    const disX = e.clientX - this.odiv.offsetLeft;
    const disY = e.clientY - this.odiv.offsetTop;
    document.onmousemove = (e) => { // 鼠標(biāo)按下并移動的事件
      // 用鼠標(biāo)的位置減去鼠標(biāo)相對元素的位置,得到元素的位置
      const left = e.clientX - disX;
      const top = e.clientY - disY;
      // 移動當(dāng)前元素
      this.odiv.style.left = left + 'px';
      this.odiv.style.top = top + 'px';
    };
    document.onmouseup = (e) => {
      document.onmousemove = null;
      document.onmouseup = null;
      this.$emit('onmouseup', this.odiv.style.left, this.odiv.style.top);
    };
  }

  // 回到原始位置 一起拖拽
  public setDrawerPosition (left = 0, top = 0) {
    this.odiv = this.drawer.$el; // 獲取目標(biāo)元素
    if (this.odiv) {
      this.odiv.style.left = left;
      this.odiv.style.top = top;
    }
  }

  @Watch('visible')
  private visibleChange () {
    if (this.visible) { // 顯示的時候 添加一個點擊事件  用于隱藏右鍵的內(nèi)容
      window.addEventListener('click', this.closeMenu);
    } else {
      window.removeEventListener('click', this.closeMenu);
    }
  }
}
</script>

<style lang='scss' scoped>
@import '~@/css/variable';
.contextmenu {
  min-width: 86px;
  margin: 0;
  background: #eeeded;
  opacity: 0.9;
  z-index: 3000;
  position: absolute;
  list-style-type: none;
  padding: 5px;
  border-radius: 4px;
  font-size: 12px;
  color: #505050;
  border: 1px solid $color-border;
  box-shadow: 2px 2px 3px 0 rgba(0, 0, 0, 0.3);
}
.contextmenu li {
  margin: 0;
  padding: 2px 10px;
  border-radius: 2px;
  cursor: pointer;
}
.contextmenu li:hover {
  background: $color-button-disabled-hover;
  color: $color-basic;
}
::v-deep .el-image__placeholder {
  background: transparent;
}
</style>

fabric版本5.3.0
多邊形選中的時候 點位顯示有問題
參考官方例子

所以在多邊形這里需要單獨處理一下,我是在設(shè)置選中的時候 做的處理【需要說明這時候我是用的是vue3】,核心代碼如下文章來源地址http://www.zghlxwxcb.cn/news/detail-474579.html

// 根據(jù)id設(shè)置當(dāng)前活躍的框
const setActiveObjectById = (id: number) => {
  (canvasObjects.value ?? canvas.value.getObjects()).forEach((item: any) => {
    if (item.shapeId === id) {
      canvas.value?.setActiveObject(item);
      if (item.type === DrawType.Polygon) {
        item.controls = item.points.reduce(
          function (
            // eslint-disable-next-line spellcheck/spell-checker
            acc: { [x: string]: fabric.Control },
            point: any,
            index: number
          ) {
            // eslint-disable-next-line spellcheck/spell-checker
            acc['p' + index] = new fabric.Control({
              positionHandler: polygonPositionHandler,
              pointIndex: index
            } as any);
            // eslint-disable-next-line spellcheck/spell-checker
            return acc;
          } as any,
          {}
        );
      }
    }
  });
};





function polygonPositionHandler(
  this: { positionHandler: (dim: any, finalMatrix: any, fabricObject: any) => fabric.Point; pointIndex: number },
  dim: any,
  finalMatrix: any,
  fabricObject: any
) {
  var x = fabricObject.points[this.pointIndex].x - fabricObject.pathOffset.x,
    y = fabricObject.points[this.pointIndex].y - fabricObject.pathOffset.y;
  return fabric.util.transformPoint(
    { x: x, y: y } as any,
    fabric.util.multiplyTransformMatrices(fabricObject.canvas.viewportTransform, fabricObject.calcTransformMatrix())
  );
}

到了這里,關(guān)于利用fabric繪畫矩形和多邊形的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來自互聯(lián)網(wǎng)用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實不符,請點擊違法舉報進(jìn)行投訴反饋,一經(jīng)查實,立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費用

相關(guān)文章

  • OpenCV(10): 輪廓近似—多邊形擬合,邊界矩形與邊界圓形

    OpenCV(10): 輪廓近似—多邊形擬合,邊界矩形與邊界圓形

    輪廓近似(Contour Approximation)是指對輪廓進(jìn)行逼近或擬合,得到近似的輪廓。在圖像處理中,輪廓表示了圖像中物體的邊界,因此輪廓近似可以用來描述和識別物體的形狀。 多邊形擬合(Approximating Polygons)是將輪廓逼近成一個由直線段構(gòu)成的多邊形。常見的有最小包圍矩形

    2024年02月10日
    瀏覽(117)
  • Python OpenCV實現(xiàn)鼠標(biāo)繪制矩形框和多邊形

    Python OpenCV實現(xiàn)鼠標(biāo)繪制矩形框和多邊形

    目錄 Python OpenCV實現(xiàn)鼠標(biāo)繪制矩形框和多邊形 1. OpenCV鼠標(biāo)事件操作說明 (1)setMouseCallback函數(shù)說明 (2)回調(diào)函數(shù)onMouse說明 (3)event 具體說明: (4)flags 具體說明 2. OpenCV實現(xiàn)鼠標(biāo)繪制矩形框和多邊形框 (1)繪制矩形框 (2)繪制多邊形 (3)鍵盤控制 3. 完整的代碼 本篇將

    2024年02月06日
    瀏覽(131)
  • cesium學(xué)習(xí)記錄08-鼠標(biāo)繪制實體(點,線,矩形,圓,多邊形)

    cesium學(xué)習(xí)記錄08-鼠標(biāo)繪制實體(點,線,矩形,圓,多邊形)

    上一篇學(xué)習(xí)了創(chuàng)建實體的一些基礎(chǔ)知識,但有時還需要我們使用鼠標(biāo)進(jìn)行手動繪制,這一篇就來進(jìn)行鼠標(biāo)繪制實體的實現(xiàn)(點,線,矩形,圓,多邊形)。 (這里需要掌握三個知識點,一是上一篇中的創(chuàng)建實體、二是鼠標(biāo)事件、三是回調(diào)函數(shù)) 既然是鼠標(biāo)繪制,自然離不開

    2024年02月12日
    瀏覽(125)
  • OpenCV(三十四):輪廓外接最大、最小矩形和多邊形擬合

    OpenCV(三十四):輪廓外接最大、最小矩形和多邊形擬合

    目錄 1.輪廓外接最大矩形boundingRect() 2.輪廓外接最小矩形minAreaRect() 3.輪廓外接多邊形approxPolyDP() 1.輪廓外接最大矩形boundingRect() Rect cv::boundingRect ( InputArray array ) array:輸入的灰度圖像或者2D點集,數(shù)據(jù)類型為vectorPoint或者M(jìn)at。 示例代碼: ?輪廓外接最大矩形的結(jié)果: 2.輪廓外接

    2024年02月09日
    瀏覽(301)
  • OpenCV(十三):圖像中繪制直線、圓形、橢圓形、矩形、多邊形和文字

    OpenCV(十三):圖像中繪制直線、圓形、橢圓形、矩形、多邊形和文字

    目錄 1.繪制直線line() 2.繪制圓形circle() 3.繪制橢圓形ellipse()? 4.繪制矩形rectangle() 5.繪制多邊形?fillPoly()? 6.繪制文字putText() 7.例子 1.繪制直線line() img:繪制圓形的圖像 ptl:直線起始點在圖像中的坐標(biāo) pt2: 直線終點在圖像中的坐標(biāo) color:圓形的顏色,用三通道表示 thickness:輪廓的寬

    2024年02月09日
    瀏覽(97)
  • 使用c#實現(xiàn)一個簡單繪圖軟件(繪制矩形、多邊形,畫筆、橡皮擦功能)的完整教程

    使用c#實現(xiàn)一個簡單繪圖軟件(繪制矩形、多邊形,畫筆、橡皮擦功能)的完整教程

    c#是微軟提供的用于開發(fā)windows軟件的開發(fā)語言,可以使用其快速的實現(xiàn)windows桌面軟件的開發(fā)。這里基于C#開發(fā)了一款簡單的繪圖軟件,可以用于繪制矩形、多邊形的繪制(基于這些可以實現(xiàn)自己的數(shù)據(jù)標(biāo)注軟件),還實現(xiàn)了畫筆、橡皮擦功能。開發(fā)C#軟件通常使用Visual Studio軟

    2024年02月03日
    瀏覽(90)
  • Opencv(C++)筆記--繪制直線、矩形、橢圓、圓、填充多邊形、繪制字體和隨機(jī)產(chǎn)生坐標(biāo)點

    Opencv(C++)筆記--繪制直線、矩形、橢圓、圓、填充多邊形、繪制字體和隨機(jī)產(chǎn)生坐標(biāo)點

    目錄 1--cv::line()繪制直線 2--cv::Rect()繪制矩形 3--cv::ellipse()繪制橢圓 4--cv::circle()繪制圓 5--cv::fillPoly()填充多邊形 6--cv::putText()繪制字體 6--cv::RNG隨機(jī)產(chǎn)生坐標(biāo)點 使用 cv::Point p1 定義坐標(biāo)點; 使用 cv::line() 繪制直線,傳入的參數(shù)依次為:背景圖、兩個點的坐標(biāo)、直線的顏色、直線

    2024年02月14日
    瀏覽(117)
  • 計算機(jī)圖形學(xué)實驗——利用MFC對話框?qū)崿F(xiàn)多邊形繪制與填充(掃描線填充算法)附源碼

    計算機(jī)圖形學(xué)實驗——利用MFC對話框?qū)崿F(xiàn)多邊形繪制與填充(掃描線填充算法)附源碼

    內(nèi)容概括: 利用基于對話框的MFC項目 實現(xiàn)鼠標(biāo)點擊繪制多邊形 實現(xiàn)掃描線算法填充多邊形 源碼見Yushan-Ji/ComputerGraphics: ECNU2023秋 計算機(jī)圖形學(xué)課程實驗代碼 (github.com) 通過鼠標(biāo)交互輸入多邊形 對各種多邊形進(jìn)行填充,包括邊界自交的情況 利用 OnLButtonDown 和 OnRButtonDown 函數(shù),

    2024年02月04日
    瀏覽(175)
  • 如何判斷兩個多邊形是否相交?——多邊形相交判定算法詳解

    如何判斷兩個多邊形是否相交?——多邊形相交判定算法詳解 在計算機(jī)圖形學(xué)中,判斷兩個多邊形是否相交是一項很重要的任務(wù)。這涉及到各種應(yīng)用場景,如碰撞檢測、模擬物理效果等。在本篇文章中,我們將會介紹多邊形相交判定算法的相關(guān)知識和實現(xiàn)方式。 首先,我們

    2024年02月14日
    瀏覽(87)
  • 基于C++ 的OpenCV繪制多邊形,多邊形多條邊用不用的顏色繪制

    使用基于C++的OpenCV庫來繪制多邊形,并且為多邊形的不同邊使用不同的顏色,可以按照以下步驟進(jìn)行操作: 首先,確保你已經(jīng)安裝了OpenCV庫并配置好了你的開發(fā)環(huán)境。 導(dǎo)入必要的頭文件: 創(chuàng)建一個空白的圖像,然后繪制多邊形,并為每條邊選擇不同的顏色: 在這個示例中,

    2024年02月13日
    瀏覽(90)

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包