普通图片标注工具


// fabric-annotation.service.ts
import { Injectable } from '@angular/core';
import { fabric } from 'fabric';
import { v4 as uuidv4 } from 'uuid';
// GeoJSON相关接口
export interface GeoJSONFeature {
  type: 'Feature';
  properties: {
    [key: string]: any;
  };
  geometry: {
    type: 'Polygon' | 'Point' | 'LineString' | 'MultiPolygon';
    coordinates: any[] | any[][] | any[][][];
  };
}

export interface GeoJSONFeatureCollection {
  type: 'FeatureCollection';
  name?: string;
  features: GeoJSONFeature[];
}


export interface AnnotationShape {
  id: number;
  type: string;
  originalCoords: any;
  vertices: Array<{x: number, y: number, inBounds: boolean}>;
}

export interface ImagePosition {
  x: number;
  y: number;
  scale: number;
  width: number;
  height: number;
}

export interface CanvasCoords {
  x: number;
  y: number;
  inBounds: boolean;
  imgWidth: number;
  imgHeight: number;
}

// export interface SavedShape {
//   type: string;
//   left: number;
//   top: number;
//   width?: number;
//   height?: number;
//   radius?: number;
//   points?: Array<{x: number, y: number}>;
//   fill: string;
//   stroke: string;
//   strokeWidth: number;
//   angle: number;
//   scaleX: number;
//   scaleY: number;
//   originX: string;
//   originY: string;
//   flipX: boolean;
//   flipY: boolean;
//   opacity: number;
//   strokeDashArray?: number[];
//   // 自定义属性
//   customType?: string;
//   absolutePoints?: Array<{x: number, y: number}>;
// }

@Injectable({
  providedIn: 'root',
})
export class FabricAnnotationService {
  private canvas: any = null;
  private currentTool: string = 'move';
  private isDrawing: boolean = false;
  private startPoint: any = null;
  private tempShape: any = null;
  private isDragging: boolean = false;
  private lastPosX: number = 0;
  private lastPosY: number = 0;

  // 斜矩形绘制相关
  private isDrawingSkewedRect: boolean = false;
  private skewedRectStartPoint: any = null;
  private tempSkewedLine: any = null;
  private tempSkewedRect: any = null;

  // 多边形绘制相关
  private isDrawingPolygon: boolean = false;
  private currentPolygonPoints: Array<{ x: number; y: number }> = [];
  private tempPolygon: any = null;
  private isNearFirstPoint: boolean = false;

  // 当前画笔颜色
  private currentColor: string = '#ff0000';

  // 存储背景图片对象
  private backgroundImage: any = null;
  //当前元素
  currentElement:any = null;
  constructor() {}

  /**
   * 初始化画布
   */
  initCanvas(canvasId: string): any {
    this.canvas = new fabric.Canvas(canvasId);
    this.setupEventListeners();
    return this.canvas;
  }

  /**
   * 设置画布背景图片
   */
  setBackgroundImage(imageUrl: string): Promise<void> {
    return new Promise((resolve, reject) => {
      if (!this.canvas) {
        reject(new Error('Canvas not initialized'));
        return;
      }

      fabric.Image.fromURL(
        imageUrl,
        (img: any) => {
          img.set({
            left: 0,
            top: 0,
            selectable: false,
            evented: false,
          });
          this.backgroundImage = img;
          this.canvas?.setBackgroundImage(img, this.canvas.renderAll.bind(this.canvas));
          resolve();
        },
        { crossOrigin: 'anonymous' }
      );
    });
  }

  /**
   * 设置当前工具
   */
  setCurrentTool(tool: 'move' | 'rect' | 'square' | 'skewedRect' | 'circle' | 'polygon'): void {
    this.currentTool = tool;
    if (!this.canvas) return;

    switch (tool) {
      case 'move':
        this.canvas.selection = true;
        this.canvas.defaultCursor = 'grab';
        break;
      case 'rect':
      case 'square':
      case 'skewedRect':
      case 'circle':
      case 'polygon':
        this.canvas.selection = false;
        this.canvas.defaultCursor = 'crosshair';
        break;
    }
  }

  /**
   * 设置画笔颜色
   */
  setBrushColor(color: string): void {
    this.currentColor = color;
  }

  /**
   * 获取当前工具
   */
  getCurrentTool(): string {
    return this.currentTool;
  }

  /**
   * 清空画布
   */
  clearCanvas(): void {
    if (!this.canvas) return;

    // 移除所有对象(包括临时形状)
    this.canvas.getObjects().forEach((obj: any) => {
      if (obj !== this.backgroundImage) {
        this.canvas?.remove(obj);
      }
    });

    // 重置视图
    this.canvas.setViewportTransform([1, 0, 0, 1, 0, 0]);
    this.canvas.setZoom(1);

    // 重置所有绘制状态
    this.resetAllDrawingStates();
  }
  /**
   *  重置视图
   */
  resetAllDrawing(){
    
     this.canvas.setViewportTransform([1, 0, 0, 1, 0, 0]);
     this.canvas.setZoom(1);
  }
  /**
   * 获取图片在当前视图中的实际位置
   */
  getImageActualPosition(): ImagePosition | null {
    if (!this.backgroundImage || !this.canvas) return null;

    const vpt: any = this.canvas.viewportTransform;
    if (!vpt) return { x: 0, y: 0, scale: 1, width: 0, height: 0 };

    const actualX = this.backgroundImage.left * vpt[0] + vpt[4];
    const actualY = this.backgroundImage.top * vpt[3] + vpt[5];

    return {
      x: actualX,
      y: actualY,
      scale: vpt[0],
      width: this.backgroundImage.width * vpt[0],
      height: this.backgroundImage.height * vpt[3],
    };
  }

  /**
   * 将画布坐标转换为相对于图片原始左上角的坐标
   */
  canvasToImageCoords(canvasX: number, canvasY: number): CanvasCoords | null {
    if (!this.backgroundImage || !this.canvas) return null;

    const vpt: any = this.canvas.viewportTransform;
    if (!vpt) return null;

    const originalX = (canvasX - vpt[4]) / vpt[0];
    const originalY = (canvasY - vpt[5]) / vpt[3];

    const relativeX = originalX;
    const relativeY = originalY;

    const imgWidth = this.backgroundImage.width || 800;
    const imgHeight = this.backgroundImage.height || 600;

    const inBounds = relativeX >= 0 && relativeX <= imgWidth && relativeY >= 0 && relativeY <= imgHeight;

    return {
      x: relativeX,
      y: relativeY,
      inBounds: inBounds,
      imgWidth: imgWidth,
      imgHeight: imgHeight,
    };
  }

  /**
   * 计算形状相对于图片原始左上角的坐标
   */
  getShapeImageCoords(shape: any): any {
    if (shape.type === 'polygon' || shape.type === 'polyline' || shape.type === 'skewed-rect') {
      const points = shape.points || [];
      const vertices = points.map((point: any) => {
        const coords = this.canvasToImageCoords(shape.left + point.x, shape.top + point.y);
        return coords;
      });

      return {
        vertices: vertices,
        type: shape.type,
        angle: shape.angle || 0,
      };
    } else if (shape.type === 'circle') {
      const center = this.canvasToImageCoords(shape.left, shape.top);
      if (!center) return null;

      const radius = shape.radius;

      return {
        center: center,
        radius: radius,
        type: 'circle',
        angle: shape.angle || 0,
      };
    } else {
      const topLeft = this.canvasToImageCoords(shape.left, shape.top);
      const topRight = this.canvasToImageCoords(shape.left + (shape.width || 0), shape.top);
      const bottomLeft = this.canvasToImageCoords(shape.left, shape.top + (shape.height || 0));
      const bottomRight = this.canvasToImageCoords(shape.left + (shape.width || 0), shape.top + (shape.height || 0));
      const centerX = this.canvasToImageCoords(
        shape.left + (shape.width || 0) / 2,
        shape.top + (shape.height || 0) / 2
      );

      if (!topLeft || !topRight || !bottomLeft || !bottomRight || !centerX) {
        return null;
      }

      return {
        topLeft: topLeft,
        topRight: topRight,
        bottomLeft: bottomLeft,
        bottomRight: bottomRight,
        width: shape.width || 0,
        height: shape.height || 0,
        centerX: centerX,
        type: shape.type,
        angle: shape.angle || 0,
      };
    }
  }

  /**
   * 获取所有图形的顶点坐标(相对于图片原始左上角)
   */
  getAllShapeVertices(): AnnotationShape[] {
    if (!this.canvas) return [];

    const objects = this.canvas.getObjects();
    const shapes = objects.filter(
      (obj: any) =>
        obj.type === 'rect' ||
        obj.type === 'circle' ||
        obj.type === 'polygon' ||
        obj.type === 'polyline' ||
        obj.type === 'skewed-rect'
    );

    const result: AnnotationShape[] = [];

    shapes.forEach((shape: any, index: number) => {
      const coords = this.getShapeImageCoords(shape);
      if (!coords) return;

      const shapeData: AnnotationShape = {
        id: index + 1,
        type: shape.type,
        originalCoords: coords,
        vertices: [],
      };

      if (shape.type === 'polygon' || shape.type === 'polyline' || shape.type === 'skewed-rect') {
        shapeData.vertices = coords.vertices;
      } else if (shape.type === 'circle') {
        const center = coords.center;
        if (!center) return;
        const radius = coords.radius;
        const angleRad = (shape.angle * Math.PI) / 180;

        shapeData.vertices = [];
        for (let i = 0; i < 8; i++) {
          const angle = (i * 45 * Math.PI) / 180 + angleRad;
          const x = center.x + radius * Math.cos(angle);
          const y = center.y + radius * Math.sin(angle);
          shapeData.vertices.push({ x: x, y: y, inBounds: center.inBounds });
        }
      } else {
        const centerX = coords.centerX;
        if (!centerX) return;

        const width = coords.width;
        const height = coords.height;
        const angleRad = ((shape.angle || 0) * Math.PI) / 180;

        const halfWidth = width / 2;
        const halfHeight = height / 2;

        const corners = [
          { x: -halfWidth, y: -halfHeight },
          { x: halfWidth, y: -halfHeight },
          { x: halfWidth, y: halfHeight },
          { x: -halfWidth, y: halfHeight },
        ];

        shapeData.vertices = corners.map((corner) => {
          const rotatedX = corner.x * Math.cos(angleRad) - corner.y * Math.sin(angleRad);
          const rotatedY = corner.x * Math.sin(angleRad) + corner.y * Math.cos(angleRad);

          const absX = centerX.x + rotatedX;
          const absY = centerX.y + rotatedY;

          const inBounds = absX >= 0 && absX <= coords.imgWidth && absY >= 0 && absY <= coords.imgHeight;

          return {
            x: absX,
            y: absY,
            inBounds: inBounds,
          };
        });
      }

      result.push(shapeData);
    });

    return result;
  }
  /**
   * 从统一格式创建图形
   */
  private createShapeFromUnified(feature: any): void {
    if (!this.canvas) return;

    let obj: any;
    const shape = feature.properties;
    if (shape.type === 'circle') {
      // 创建圆形
      obj = new fabric.Circle({
        left: shape.left,
        top: shape.top,
        radius: shape.radius || 50,
        fill: shape.fill,
        stroke: shape.stroke,
        strokeWidth: shape.strokeWidth,
        angle: shape.angle || 0,
        scaleX: shape.scaleX || 1,
        scaleY: shape.scaleY || 1,
        originX: shape.originX || 'left',
        originY: shape.originY || 'top',
        flipX: shape.flipX || false,
        flipY: shape.flipY || false,
        opacity: shape.opacity || 1,
        strokeDashArray: shape.strokeDashArray,
        selectable: true,
        hasControls: true,
        hasRotatingPoint: true,
        lockUniScaling: false,
        type: 'circle',
      });
    } else {
      // 创建多边形(包括原来的矩形)
      obj = new fabric.Polygon(shape.points || [], {
        left: shape.left,
        top: shape.top,
        fill: shape.fill,
        stroke: shape.stroke,
        strokeWidth: shape.strokeWidth,
        angle: shape.angle || 0,
        scaleX: shape.scaleX || 1,
        scaleY: shape.scaleY || 1,
        originX: shape.originX || 'left',
        originY: shape.originY || 'top',
        flipX: shape.flipX || false,
        flipY: shape.flipY || false,
        opacity: shape.opacity || 1,
        strokeDashArray: shape.strokeDashArray,
        selectable: true,
        hasControls: true,
        hasRotatingPoint: true,
        lockUniScaling: false,
        type: shape.customType || 'polygon', // 恢复原始类型
      });
    }

    if (obj) {
      obj.id = feature.id;
      this.canvas.add(obj);
    }
  }

  /**
   * 将所有图形转换为统一格式(多边形格式)
   */
  shapesToUnified(colorObj) {
    if (!this.canvas) return [];
    
    const objects = this.canvas.getObjects();
    const shapes: any[] = [];
    const features: any = [];
    objects.forEach((obj: any) => {
      
      if (obj !== this.backgroundImage) {
        // 不保存背景图片
        const shapeData: any = {
          
          type: 'polygon', // 统一使用polygon类型
          left: parseInt(obj.left),
          top: parseInt(obj.top),
          fill: obj.fill,
          stroke: obj.stroke,
          strokeWidth: obj.strokeWidth,
          angle: obj.angle || 0,
          scaleX: obj.scaleX || 1,
          scaleY: obj.scaleY || 1,
          originX: obj.originX || 'left',
          originY: obj.originY || 'top',
          flipX: obj.flipX || false,
          flipY: obj.flipY || false,
          opacity: obj.opacity || 1,
          strokeDashArray: obj.strokeDashArray || undefined,
          ...colorObj
        };
        const feature = {id: obj.id,type: "Feature", properties:{}, "geometry": { type: "Polygon", coordinates:<any>[]}};
        // 根据原始对象类型处理
        if (obj.type === 'rect' || obj.type === 'square') {
          // 将矩形转换为多边形顶点
          const width = obj.width * obj.scaleX;
          const height = obj.height * obj.scaleY;
          const angleRad = ((obj.angle || 0) * Math.PI) / 180;

          // 计算四个顶点相对于中心点的坐标
          // const halfWidth = width / 2;
          // const halfHeight = height / 2;

          const corners = [
            { x: 0, y: 0 }, // 左上
            { x: width, y: 0 }, // 右上
            { x: width, y: height }, // 右下
            { x: 0, y: height }, // 左下
          ];

          // 应用旋转
          const rotatedCorners = corners.map((corner) => {
            const rotatedX = corner.x * Math.cos(angleRad) - corner.y * Math.sin(angleRad);
            const rotatedY = corner.x * Math.sin(angleRad) + corner.y * Math.cos(angleRad);
            return {
              x: rotatedX,
              y: rotatedY,
            };
          });
          const absolutePoints = rotatedCorners.map(corner => ({
            x: obj.left + corner.x,
            y: obj.top + corner.y
          }));
          shapeData.points = rotatedCorners;
          
          shapeData.customType = obj.type; // 保留原始类型信息
          // shapeData.absolutePoints = absolutePoints;
          feature.properties = shapeData;
          feature.geometry.coordinates = [absolutePoints.map(v=>[parseInt(v.x),parseInt(v.y)])];

        } else if (obj.type === 'circle') {
          shapeData.type = 'circle';
          shapeData.radius = obj.radius;
          shapeData.customType = 'circle';
          feature.properties = shapeData;
          // feature.geometry.coordinates = absolutePoints;
        } else if (obj.type === 'polygon' || obj.type === 'skewed-rect') {
          shapeData.points = obj.points ? obj.points.map((p: any) => ({ x: parseInt(p.x), y: parseInt(p.y) })) : [];
          // shapeData.absolutePoints = shapeData.points;
          shapeData.customType = obj.type;
          feature.properties = shapeData;
          if(shapeData.points) {
            feature.geometry.coordinates = [shapeData.points.map(v=>[parseInt(v.x),parseInt(v.y)])];
          }
          
        }

        shapes.push(shapeData);
        features.push(feature);
      }
    });
    return {
      type: "FeatureCollection",
      features:features
    } 
    // return shapes;
  }

  /**
   * 从统一格式恢复图形
   */
  loadFromUnified(shapes: any): void {
    if (!this.canvas) return;

    // 清空当前画布(不包括背景图片)
    this.canvas.getObjects().forEach((obj: any) => {
      if (obj !== this.backgroundImage) {
        this.canvas?.remove(obj);
      }
    });

    // 重新创建图形
    shapes.forEach((shape) => {
      this.createShapeFromUnified(shape);
    });

    this.canvas.renderAll();
  }
  /**
   * 保存所有图形到本地存储(统一格式)
   */
  saveShapesToLocalStorage(key: string = 'fabric_unified_shapes'): void {
    const shapes = this.shapesToUnified({});
    localStorage.setItem(key, JSON.stringify(shapes));
  }

  /**
   * 从本地存储加载图形(统一格式)
   */
  loadShapesFromLocalStorage(key: string = 'fabric_unified_shapes'): void {
    const shapesStr = localStorage.getItem(key);
    if (!shapesStr) return;

    try {
      // const shapes: SavedShape[] = JSON.parse(shapesStr);
      const geojson = JSON.parse(shapesStr);
      const features:any[] = geojson.features;
      this.loadFromUnified(features);
    } catch (error) {
      console.error('Failed to load shapes from localStorage:', error);
    }
  }

  /**
   * 保存所有图形到本地存储
   */
  // saveShapesToLocalStorage(key: string = 'fabric_shapes'): void {
  //   if (!this.canvas) return;

  //   const objects = this.canvas.getObjects();
  //   const shapes: SavedShape[] = [];

  //   objects.forEach((obj: any) => {
  //     if (obj !== this.backgroundImage) { // 不保存背景图片
  //       const shapeData: SavedShape = {
  //         type: obj.type,
  //         left: obj.left,
  //         top: obj.top,
  //         fill: obj.fill,
  //         stroke: obj.stroke,
  //         strokeWidth: obj.strokeWidth,
  //         angle: obj.angle || 0,
  //         scaleX: obj.scaleX || 1,
  //         scaleY: obj.scaleY || 1,
  //         originX: obj.originX || 'left',
  //         originY: obj.originY || 'top',
  //         flipX: obj.flipX || false,
  //         flipY: obj.flipY || false,
  //         opacity: obj.opacity || 1,
  //         strokeDashArray: obj.strokeDashArray || undefined
  //       };

  //       // 根据对象类型添加特定属性
  //       if (obj.type === 'rect' || obj.type === 'circle') {
  //         shapeData.width = obj.width;
  //         shapeData.height = obj.height;
  //       }

  //       if (obj.type === 'circle') {
  //         shapeData.radius = obj.radius;
  //       }

  //       if (obj.type === 'polygon' || obj.type === 'polyline' || obj.type === 'skewed-rect') {
  //         // 对于斜矩形,我们保存原始点坐标而不是变换后的坐标
  //         if (obj.type === 'skewed-rect') {
  //           // 保存原始点(相对于左上角的点)
  //           const originalPoints = obj.points.map((p: any) => ({
  //             x: p.x,
  //             y: p.y
  //           }));
  //           shapeData.points = originalPoints;
  //           shapeData.customType = 'skewed-rect';
  //         } else {
  //           shapeData.points = obj.points ? [...obj.points] : [];
  //         }
  //       }

  //       shapes.push(shapeData);
  //     }
  //   });

  //   localStorage.setItem(key, JSON.stringify(shapes));
  // }

  /**
   * 从本地存储恢复图形
   */
  // loadShapesFromLocalStorage(key: string = 'fabric_shapes'): void {
  //   if (!this.canvas) return;

  //   const savedShapes = localStorage.getItem(key);
  //   if (!savedShapes) return;

  //   try {
  //     const shapes: SavedShape[] = JSON.parse(savedShapes);

  //     // 先清空当前画布(不包括背景图片)
  //     this.canvas.getObjects().forEach((obj: any) => {
  //       if (obj !== this.backgroundImage) {
  //         this.canvas?.remove(obj);
  //       }
  //     });

  //     // 重新创建图形
  //     shapes.forEach((shape: SavedShape) => {
  //       this.createShapeFromData(shape);
  //     });

  //     this.canvas.renderAll();
  //   } catch (error) {
  //     console.error('Failed to load shapes from localStorage:', error);
  //   }
  // }

  /**
   * 从数据创建图形
   */
  private createShapeFromData(shape: any): void {
    if (!this.canvas) return;

    let obj: any;

    switch (shape.type) {
      case 'rect':
        obj = new fabric.Rect({
          left: shape.left,
          top: shape.top,
          width: shape.width,
          height: shape.height,
          fill: shape.fill,
          stroke: shape.stroke,
          strokeWidth: shape.strokeWidth,
          angle: shape.angle,
          scaleX: shape.scaleX,
          scaleY: shape.scaleY,
          originX: shape.originX,
          originY: shape.originY,
          flipX: shape.flipX,
          flipY: shape.flipY,
          opacity: shape.opacity,
          selectable: true,
          hasControls: true,
          hasRotatingPoint: true,
          lockUniScaling: false,
        });
        break;

      case 'circle':
        obj = new fabric.Circle({
          left: shape.left,
          top: shape.top,
          radius: shape.radius,
          fill: shape.fill,
          stroke: shape.stroke,
          strokeWidth: shape.strokeWidth,
          angle: shape.angle,
          scaleX: shape.scaleX,
          scaleY: shape.scaleY,
          originX: shape.originX,
          originY: shape.originY,
          flipX: shape.flipX,
          flipY: shape.flipY,
          opacity: shape.opacity,
          selectable: true,
          hasControls: true,
          hasRotatingPoint: true,
          lockUniScaling: false,
        });
        break;

      case 'polygon':
      case 'polyline':
        obj = new fabric.Polygon(shape.points || [], {
          left: shape.left,
          top: shape.top,
          fill: shape.fill,
          stroke: shape.stroke,
          strokeWidth: shape.strokeWidth,
          angle: shape.angle,
          scaleX: shape.scaleX,
          scaleY: shape.scaleY,
          originX: shape.originX,
          originY: shape.originY,
          flipX: shape.flipX,
          flipY: shape.flipY,
          opacity: shape.opacity,
          selectable: true,
          hasControls: true,
          hasRotatingPoint: true,
          lockUniScaling: false,
        });
        break;

      case 'skewed-rect':
        // 为斜矩形创建多边形,但保留自定义类型
        obj = new fabric.Polygon(shape.points || [], {
          left: shape.left,
          top: shape.top,
          fill: shape.fill,
          stroke: shape.stroke,
          strokeWidth: shape.strokeWidth,
          angle: shape.angle,
          scaleX: shape.scaleX,
          scaleY: shape.scaleY,
          originX: shape.originX,
          originY: shape.originY,
          flipX: shape.flipX,
          flipY: shape.flipY,
          opacity: shape.opacity,
          selectable: true,
          hasControls: true,
          hasRotatingPoint: true,
          lockUniScaling: false,
        });
        // 设置自定义类型以便识别
        (obj as any).type = 'skewed-rect';
        break;

      default:
        console.warn(`Unknown shape type: ${shape.type}`);
        return;
    }
   
    this.canvas.add(obj);
  }

  /**
   * 完成多边形绘制
   */
  finishPolygon(): void {
    if (!this.isDrawingPolygon || this.currentPolygonPoints.length < 3) {
      if (this.currentPolygonPoints.length < 3) {
        alert('多边形至少需要3个顶点!');
      }
      return;
    }

    this.isDrawingPolygon = false;

    if (this.tempPolygon) {
      this.canvas?.remove(this.tempPolygon);
      this.tempPolygon = null;
    }

    const finalPolygon = new fabric.Polygon(this.currentPolygonPoints, {
      fill: `rgba(${parseInt(this.currentColor.slice(1, 3), 16)}, ${parseInt(
        this.currentColor.slice(3, 5),
        16
      )}, ${parseInt(this.currentColor.slice(5, 7), 16)}, 0.1)`,
      stroke: this.currentColor,
      strokeWidth: 2,
      selectable: true,
      hasControls: true,
      hasRotatingPoint: true,
      lockUniScaling: false,
    });
    (finalPolygon as any).id = uuidv4();//多边形增加id
    this.canvas?.add(finalPolygon);
    this.currentPolygonPoints = [];
    this.isNearFirstPoint = false;
  }

  /**
   * 取消多边形绘制
   */
  cancelPolygon(): void {
    this.isDrawingPolygon = false;

    if (this.tempPolygon) {
      this.canvas?.remove(this.tempPolygon);
      this.tempPolygon = null;
    }

    this.currentPolygonPoints = [];
    this.isNearFirstPoint = false;
  }

  /**
   * 重置多边形状态
   */
  private resetPolygonState(): void {
    this.isDrawingPolygon = false;
    this.currentPolygonPoints = [];
    if (this.tempPolygon) {
      this.canvas?.remove(this.tempPolygon);
      this.tempPolygon = null;
    }
    this.isNearFirstPoint = false;
  }

  /**
   * 重置斜矩形状态
   */
  private resetSkewedRectState(): void {
    this.isDrawingSkewedRect = false;
    this.skewedRectStartPoint = null;
    if (this.tempSkewedLine) {
      this.canvas?.remove(this.tempSkewedLine);
      this.tempSkewedLine = null;
    }
    if (this.tempSkewedRect) {
      this.canvas?.remove(this.tempSkewedRect);
      this.tempSkewedRect = null;
    }
  }

  /**
   * 重置所有绘制状态
   */
  private resetAllDrawingStates(): void {
    this.isDrawing = false;
    if (this.tempShape) {
      this.canvas?.remove(this.tempShape);
      this.tempShape = null;
    }
    this.resetPolygonState();
    this.resetSkewedRectState();
  }

  /**
   * 设置事件监听器
   */
  private setupEventListeners(): void {
    if (!this.canvas) return;

    // 鼠标滚轮缩放
    this.canvas.on('mouse:wheel', (opt: any) => {
      if (this.currentTool !== 'move') {
        opt.e.preventDefault();
        return;
      }

      const delta = opt.e.deltaY;
      let zoom = this.canvas?.getZoom() || 1;
      zoom = zoom + delta * -0.001;
      zoom = Math.min(Math.max(0.1, zoom), 5);

      const point = new fabric.Point(opt.e.offsetX, opt.e.offsetY);
      this.canvas?.zoomToPoint(point, zoom);
      opt.e.preventDefault();
      opt.e.stopPropagation();
    });

    // 鼠标按下事件
    this.canvas.on('mouse:down', (opt: any) => {
      if (this.currentTool === 'move') {
        this.isDragging = true;
        this.lastPosX = opt.e.clientX;
        this.lastPosY = opt.e.clientY;
      } else if (this.currentTool === 'skewedRect') {
        const target = this.canvas?.findTarget(opt.e);

        if (target && ['rect', 'circle', 'polygon', 'polyline', 'skewed-rect'].includes(target.type)) {
          this.canvas?.setActiveObject(target);
          return;
        }

        this.isDrawingSkewedRect = true;
        this.skewedRectStartPoint = this.canvas?.getPointer(opt.e) || null;

        if (this.skewedRectStartPoint) {
          this.tempSkewedLine = new fabric.Line(
            [
              this.skewedRectStartPoint.x,
              this.skewedRectStartPoint.y,
              this.skewedRectStartPoint.x,
              this.skewedRectStartPoint.y,
            ],
            {
              stroke: this.currentColor,
              strokeWidth: 1,
              strokeDashArray: [5, 5],
              selectable: false,
              evented: false,
            }
          );

          this.canvas?.add(this.tempSkewedLine);
        }
      } else if (this.currentTool === 'polygon') {
        const target = this.canvas?.findTarget(opt.e);

        if (target && ['rect', 'circle', 'polygon', 'polyline', 'skewed-rect'].includes(target.type)) {
          this.canvas?.setActiveObject(target);
          return;
        }

        if (this.isNearFirstPoint && this.currentPolygonPoints.length >= 3) {
          this.finishPolygon();
          return;
        }

        if (!this.isDrawingPolygon) {
          this.isDrawingPolygon = true;
          this.currentPolygonPoints = [];
        }

        const pointer = this.canvas?.getPointer(opt.e);
        if (pointer) {
          this.currentPolygonPoints.push({
            x: pointer.x,
            y: pointer.y,
          });
        }

        if (this.currentPolygonPoints.length >= 2) {
          if (this.tempPolygon) {
            this.canvas?.remove(this.tempPolygon);
          }

          this.tempPolygon = new fabric.Polyline(this.currentPolygonPoints, {
            fill: `rgba(${parseInt(this.currentColor.slice(1, 3), 16)}, ${parseInt(
              this.currentColor.slice(3, 5),
              16
            )}, ${parseInt(this.currentColor.slice(5, 7), 16)}, 0.1)`,
            stroke: this.currentColor,
            strokeWidth: 2,
            selectable: false,
            evented: false,
          });

          this.canvas?.add(this.tempPolygon);
          this.canvas?.renderAll();
        }
      } else if (['rect', 'square', 'circle'].includes(this.currentTool)) {
        const target = this.canvas?.findTarget(opt.e);

        if (target && ['rect', 'circle', 'polygon', 'polyline', 'skewed-rect'].includes(target.type)) {
          this.canvas?.setActiveObject(target);
          return;
        }

        this.isDrawing = true;
        this.startPoint = this.canvas?.getPointer(opt.e) || null;

        if (this.currentTool === 'circle') {
          const radius = 1;
          this.tempShape = new fabric.Circle({
            left: this.startPoint?.x || 0,
            top: this.startPoint?.y || 0,
            radius: radius,
            fill: `rgba(${parseInt(this.currentColor.slice(1, 3), 16)}, ${parseInt(
              this.currentColor.slice(3, 5),
              16
            )}, ${parseInt(this.currentColor.slice(5, 7), 16)}, 0.2)`,
            stroke: this.currentColor,
            strokeWidth: 2,
            selectable: false,
          });
        } else {
          this.tempShape = new fabric.Rect({
            left: this.startPoint?.x || 0,
            top: this.startPoint?.y || 0,
            width: 1,
            height: 1,
            fill: `rgba(${parseInt(this.currentColor.slice(1, 3), 16)}, ${parseInt(
              this.currentColor.slice(3, 5),
              16
            )}, ${parseInt(this.currentColor.slice(5, 7), 16)}, 0.2)`,
            stroke: this.currentColor,
            strokeWidth: 2,
            selectable: false,
          });
        }
        (this.tempShape as any).id = uuidv4();//斜矩形增加id
        this.canvas?.add(this.tempShape);
      }
    });

    // 鼠标移动事件
    this.canvas.on('mouse:move', (opt: any) => {
       // 检查是否有选中的对象
       const activeObject = this.canvas?.getActiveObject();
      if (this.isDragging && this.currentTool === 'move') {
        if (!activeObject || activeObject === this.backgroundImage) {
          const e = opt.e;
          const vpt: any = this.canvas?.viewportTransform;
          if (vpt) {
            vpt[4] += e.clientX - this.lastPosX;
            vpt[5] += e.clientY - this.lastPosY;
            this.lastPosX = e.clientX;
            this.lastPosY = e.clientY;
            this.canvas?.requestRenderAll();
          }
        }
        
      } else if (this.isDrawingSkewedRect && this.currentTool === 'skewedRect' && this.skewedRectStartPoint) {
        const pointer = this.canvas?.getPointer(opt.e);
        if (pointer && this.tempSkewedLine) {
          this.tempSkewedLine.set({
            x2: pointer.x,
            y2: pointer.y,
          });
          this.tempSkewedLine.setCoords();
          this.canvas?.renderAll();
        }

        if (pointer && this.skewedRectStartPoint) {
          const dx = pointer.x - this.skewedRectStartPoint.x;
          const dy = pointer.y - this.skewedRectStartPoint.y;
          const length = Math.sqrt(dx * dx + dy * dy);

          if (length > 0) {
            const perpX = -dy / length;
            const perpY = dx / length;

            if (this.tempSkewedRect) {
              this.canvas?.remove(this.tempSkewedRect);
              this.tempSkewedRect = null;
            }

            const height = length / 3;
            const width = length;

            const points = [
              { x: this.skewedRectStartPoint.x, y: this.skewedRectStartPoint.y },
              { x: pointer.x, y: pointer.y },
              { x: pointer.x + perpX * height, y: pointer.y + perpY * height },
              { x: this.skewedRectStartPoint.x + perpX * height, y: this.skewedRectStartPoint.y + perpY * height },
            ];

            this.tempSkewedRect = new fabric.Polygon(points, {
              fill: `rgba(${parseInt(this.currentColor.slice(1, 3), 16)}, ${parseInt(
                this.currentColor.slice(3, 5),
                16
              )}, ${parseInt(this.currentColor.slice(5, 7), 16)}, 0.2)`,
              stroke: this.currentColor,
              strokeWidth: 2,
              selectable: false,
              evented: false,
              type: 'skewed-rect', // 确保类型为斜矩形
            });

            this.canvas?.add(this.tempSkewedRect);
            this.canvas?.renderAll();
          }
        }
      } else if (this.isDrawingPolygon && this.currentTool === 'polygon') {
        const pointer = this.canvas?.getPointer(opt.e);
        if (!pointer) return;

        const updatedPoints = [...this.currentPolygonPoints];

        this.isNearFirstPoint = false;
        if (this.currentPolygonPoints.length >= 3) {
          const firstPoint = this.currentPolygonPoints[0];
          const distance = Math.sqrt(Math.pow(pointer.x - firstPoint.x, 2) + Math.pow(pointer.y - firstPoint.y, 2));

          if (distance < 5) {
            this.isNearFirstPoint = true;
            this.canvas.defaultCursor = 'pointer';
          } else {
            this.isNearFirstPoint = false;
            this.canvas.defaultCursor = 'crosshair';
          }
        } else {
          this.canvas.defaultCursor = 'crosshair';
        }

        if (this.currentPolygonPoints.length > 0) {
          updatedPoints.push({
            x: pointer.x,
            y: pointer.y,
          });
        }

        if (this.tempPolygon) {
          this.canvas?.remove(this.tempPolygon);
        }

        if (this.isNearFirstPoint && this.currentPolygonPoints.length >= 3) {
          this.tempPolygon = new fabric.Polyline(updatedPoints, {
            fill: `rgba(${parseInt(this.currentColor.slice(1, 3), 16)}, ${parseInt(
              this.currentColor.slice(3, 5),
              16
            )}, ${parseInt(this.currentColor.slice(5, 7), 16)}, 0.3)`,
            stroke: this.currentColor === '#ff0000' ? '#ff6666' : this.currentColor,
            strokeWidth: 3,
            selectable: false,
            evented: false,
          });
        } else {
          this.tempPolygon = new fabric.Polyline(updatedPoints, {
            fill: `rgba(${parseInt(this.currentColor.slice(1, 3), 16)}, ${parseInt(
              this.currentColor.slice(3, 5),
              16
            )}, ${parseInt(this.currentColor.slice(5, 7), 16)}, 0.1)`,
            stroke: this.currentColor,
            strokeWidth: 2,
            selectable: false,
            evented: false,
          });
        }

        this.canvas?.add(this.tempPolygon);
        this.canvas?.renderAll();
      } else if (this.isDrawing && this.tempShape) {
        const pointer = this.canvas?.getPointer(opt.e);
        if (!pointer || !this.startPoint) return;

        if (this.currentTool === 'circle') {
          const radius = Math.sqrt(
            Math.pow(pointer.x - this.startPoint.x, 2) + Math.pow(pointer.y - this.startPoint.y, 2)
          );

          (this.tempShape as any).set({
            radius: radius,
          });
        } else {
          let width = pointer.x - this.startPoint.x;
          let height = pointer.y - this.startPoint.y;

          if (this.currentTool === 'square') {
            const absWidth = Math.abs(width);
            const absHeight = Math.abs(height);
            const size = Math.min(absWidth, absHeight);

            width = width >= 0 ? size : -size;
            height = height >= 0 ? size : -size;
          }

          const left = width < 0 ? pointer.x : this.startPoint.x;
          const top = height < 0 ? pointer.y : this.startPoint.y;

          this.tempShape.set({
            left: left,
            top: top,
            width: Math.abs(width),
            height: Math.abs(height),
          });
        }

        this.tempShape.setCoords();
        this.canvas?.renderAll();
      }
    });

    // 鼠标抬起事件
    this.canvas.on('mouse:up', () => {
      if (this.isDragging && this.currentTool === 'move') {
        this.isDragging = false;
      } else if (this.isDrawing && this.tempShape) {
        this.isDrawing = false;

        let isTooSmall = false;

        if (this.currentTool === 'circle') {
          isTooSmall = (this.tempShape as any).radius < 5;
        } else {
          isTooSmall = Math.abs(this.tempShape.width || 0) < 5 || Math.abs(this.tempShape.height || 0) < 5;
        }

        if (isTooSmall) {
          this.canvas?.remove(this.tempShape);
        } else {
          this.tempShape.set({
            selectable: true,
            hasControls: true,
            hasRotatingPoint: true,
            lockUniScaling: false,
            fill: `rgba(${parseInt(this.currentColor.slice(1, 3), 16)}, ${parseInt(
              this.currentColor.slice(3, 5),
              16
            )}, ${parseInt(this.currentColor.slice(5, 7), 16)}, 0.1)`,
            stroke: this.currentColor,
          });

          this.canvas?.setActiveObject(this.tempShape);
        }

        this.tempShape = null;
      } else if (this.isDrawingSkewedRect && this.currentTool === 'skewedRect') {
        this.isDrawingSkewedRect = false;

        if (this.tempSkewedLine) {
          this.canvas?.remove(this.tempSkewedLine);
          this.tempSkewedLine = null;
        }

        if (this.tempSkewedRect) {
          // 保存原始点坐标而不是变换后的坐标
          const originalPoints = this.tempSkewedRect.points.map((p: any) => ({
            x: p.x,
            y: p.y,
          }));

          const finalSkewedRect = new fabric.Polygon(originalPoints, {
            left: this.tempSkewedRect.left,
            top: this.tempSkewedRect.top,
            fill: `rgba(${parseInt(this.currentColor.slice(1, 3), 16)}, ${parseInt(
              this.currentColor.slice(3, 5),
              16
            )}, ${parseInt(this.currentColor.slice(5, 7), 16)}, 0.2)`,
            stroke: this.currentColor,
            strokeWidth: 2,
            selectable: true,
            hasControls: true,
            hasRotatingPoint: true,
            lockUniScaling: false,
            type: 'skewed-rect',
          });
          (finalSkewedRect as any).id = uuidv4();//斜矩形增加id
          this.canvas?.add(finalSkewedRect);
          this.canvas?.setActiveObject(finalSkewedRect);

          this.canvas?.remove(this.tempSkewedRect);
          this.tempSkewedRect = null;
        }
      } else if (
        this.isDrawingPolygon &&
        this.currentTool === 'polygon' &&
        this.isNearFirstPoint &&
        this.currentPolygonPoints.length >= 3
      ) {
        this.finishPolygon();
      }
    });

    // 鼠标右键事件
    this.canvas.on('mouse:down', (opt: any) => {
      if (opt.e.button === 2 && this.currentTool === 'polygon' && this.isDrawingPolygon) {
        if (this.currentPolygonPoints.length > 0) {
          this.currentPolygonPoints.pop();

          if (this.tempPolygon) {
            this.canvas?.remove(this.tempPolygon);
          }

          if (this.currentPolygonPoints.length >= 2) {
            this.tempPolygon = new fabric.Polyline(this.currentPolygonPoints, {
              fill: `rgba(${parseInt(this.currentColor.slice(1, 3), 16)}, ${parseInt(
                this.currentColor.slice(3, 5),
                16
              )}, ${parseInt(this.currentColor.slice(5, 7), 16)}, 0.1)`,
              stroke: this.currentColor,
              strokeWidth: 2,
              selectable: false,
              evented: false,
            });

            this.canvas?.add(this.tempPolygon);
          } else if (this.currentPolygonPoints.length === 1) {
            this.tempPolygon = new fabric.Polyline(this.currentPolygonPoints, {
              fill: `rgba(${parseInt(this.currentColor.slice(1, 3), 16)}, ${parseInt(
                this.currentColor.slice(3, 5),
                16
              )}, ${parseInt(this.currentColor.slice(5, 7), 16)}, 0.1)`,
              stroke: this.currentColor,
              strokeWidth: 2,
              selectable: false,
              evented: false,
            });

            this.canvas?.add(this.tempPolygon);
          } else {
            this.tempPolygon = null;
          }

          this.canvas?.renderAll();
        }

        opt.e.preventDefault();
        return false;
      }
    });

    // 鼠标双击重置视图
    this.canvas.on('mouse:dblclick', () => {
      if (this.currentTool !== 'move') return;

      this.canvas?.setViewportTransform([1, 0, 0, 1, 0, 0]);
      this.canvas?.setZoom(1);
    });
  }

  /**
   * 从GeoJSON数据导入多边形
   */
  loadFromGeoJSON(geojson: any): void {
    if (!this.canvas) return;

    // 清空当前画布(不包括背景图片)
    this.canvas.getObjects().forEach((obj: any) => {
      if (obj !== this.backgroundImage) {
        this.canvas?.remove(obj);
      }
    });

    // 遍历GeoJSON中的所有要素
    geojson.features.forEach((feature) => {
      if (feature.geometry.type === 'Polygon') {
        this.createPolygonFromGeoJSON(feature);
      }
    });

    this.canvas.renderAll();
  }

  /**
   * 从GeoJSON要素创建多边形
   */
  private createPolygonFromGeoJSON(feature: GeoJSONFeature): void {
    if (!this.canvas || feature.geometry.type !== 'Polygon') return;

    // 获取坐标数组
    const coordinates = feature.geometry.coordinates[0]; // 取外环

    // 移除闭合点(最后一个点通常与第一个点相同)
    const coordsWithoutClosing = coordinates.slice(0, -1);

    // 转换坐标:GeoJSON坐标通常是 [x, y, z] 格式
    const points = coordsWithoutClosing.map((coord) => ({
      x: coord[0], // x坐标
      y: -coord[1], // y坐标需要翻转(GeoJSON的y轴方向与Canvas相反)
    }));

    // 计算边界框以确定中心点
    let minX = Infinity,
      minY = Infinity,
      maxX = -Infinity,
      maxY = -Infinity;
    points.forEach((point) => {
      if (point.x < minX) minX = point.x;
      if (point.x > maxX) maxX = point.x;
      if (point.y < minY) minY = point.y;
      if (point.y > maxY) maxY = point.y;
    });

    const centerX = minX;
    const centerY = minY;
    let color = '#FF0000'; // 默认红色
    // 创建多边形
    const polygon = new fabric.Polygon(points, {
      left: centerX,
      top: centerY,
      fill: `rgba(${parseInt(color.slice(1, 3), 16)}, ${parseInt(color.slice(3, 5), 16)}, ${parseInt(
        color.slice(5, 7),
        16
      )}, 0.3)`,
      stroke: color,
      strokeWidth: 2,
      selectable: true,
      hasControls: true,
      hasRotatingPoint: true,
      lockUniScaling: false,
      type: 'polygon'
    });

    this.canvas.add(polygon);
  }
  getAllItems(){
    return this.canvas.getObjects();
  }
  getLastItem(){
    const objects = this.canvas.getObjects();
    if(Array.isArray(objects) && objects.length > 0) {
      return objects[objects.length - 1];
    }
  }

//=====================================有关交互部分===================================================//
  searchActiveObject(){
    const activeObject = this.canvas?.getActiveObject();
    if (!activeObject || activeObject === this.backgroundImage) {
      return null;
    }else{
      return activeObject;
    }
  }
  /**
   * 根据ID查找元素
   */
  findElementById(id: string): fabric.Object | undefined {
    if (!this.canvas) return undefined;
    
    const objects = this.canvas.getObjects();
    return objects.find(obj => (obj as any).id === id);
  }

  /**
   * 点击列表项时定位到元素
   */
  locateElementById(id: string): void {
    const element = this.findElementById(id);
    // debugger;
    if (!element || !this.canvas) return;

    // 高亮显示元素
    // this.highlightElement(element);
     // 选中元素
    this.selectElement(element);
  }
    /**
   * 选中元素
   */
    selectElement(element: fabric.Object): void {
      if (!this.canvas) return;
  
      // 清除当前选中状态
      this.canvas.discardActiveObject();
      
      // 设置元素为可选中状态(如果尚未设置)
      element.set({
        selectable: true,
        evented: true
      });
      
      // 选中元素
      this.canvas.setActiveObject(element);
      
      // 重新渲染以显示选中状态
      this.canvas.renderAll();
    }
  /**
   * 高亮显示元素
   */
  highlightElement(element: fabric.Object): void {
    // 暂时改变元素样式
    const originalFill = element.fill;
    const originalStroke = element.stroke;
    // const originalStrokeWidth = (element as any).strokeWidth;
    
    element.set({
      fill: 'rgba(255, 255, 0, 0.5)', // 黄色半透明填充
      stroke: '#FFFF00',              // 黄色边框
      // strokeWidth: (originalStrokeWidth || 2) + 2 // 增加边框宽度
    });
    
    this.canvas?.renderAll();
    
    // 2秒后恢复原状
    setTimeout(() => {
      element.set({
        fill: originalFill,
        stroke: originalStroke,
        // strokeWidth: originalStrokeWidth
      });
      this.canvas?.renderAll();
    }, 1000);
  }

  /**
   * 删除元素
   */
  deleteElementById(id: string): boolean {
    const element = this.findElementById(id);
    if (!element || !this.canvas) return false;

    this.canvas.remove(element);
    // this.elementInfoMap.delete(id);
    return true;
  }

  /**
   * 切换元素可见性
   */
  toggleElementVisibility(id: string): boolean {
    const element = this.findElementById(id);
    if (!element) return false;

    const newVisibility = !element.visible;
    element.set({ visible: newVisibility });
    
    // 更新元素信息
    
    
    this.canvas?.renderAll();
    return newVisibility;
  }

  // /**
  //  * 更新元素信息
  //  */
  // updateElementInfo(id: string, updates: Partial<ElementInfo>): void {
  //   const info = this.elementInfoMap.get(id);
  //   if (info) {
  //     Object.assign(info, updates);
  //     this.elementInfoMap.set(id, info);
  //   }
  // }

  // /**
  //  * 获取元素信息
  //  */
  // getElementInfo(id: string): ElementInfo | undefined {
  //   return this.elementInfoMap.get(id);
  // }


//=====================================有关交互部分===================================================//
}




©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容