canvas实现图片矩形截图,矩形旋转后的截图,旋转后的截图摆正显示

一、效果图

在这里插入图片描述

二、主要代码

获取矩形框中地方的截图数据
1、先获取矩形四点在画布上的实际坐标值;
2、计算矩形此时实际的宽和高,便于设置后期临时矩形的宽和高;
3、可能矩形旋转了一定的角度,我们新建一个临时的画布tempCanvas,然后向反方向摆正,使矩形能正常的显示在临时画布中;
4、因为摆正了矩形,此时需要计算矩形的left和top,方便画图;
5、在创建一个用于画截图的canvas,canvas的宽高就是前面我们画出来的宽高‘
6、然后利用
rotatedCtx.drawImage(
tempCanvas,
left,
top,
width,
height,
0,
0,
width,
height
);
在临时画布中截取矩形框中的部分
7、ctx.drawImage的参数代表的意思请详见:

https://developer.mozilla.org/zh-CN/docs/Web/API/Canvas_API/Tutorial/Using_images

    getCanvasImageData(id) {
      const viewportTransform = this.fabricCanvas.viewportTransform;
      const zoom = this.fabricCanvas.getZoom();
      let selectedCoords = null;
      let rotationAngle = 0;
      let left = 0;
      let top = 0;
       //根据画布矩阵和缩放信息,获取到矩形实际在画布上的坐标点
      this.fabricCanvas.getObjects().forEach(rect => {
        if (rect.id == id) {
          console.log("rect", rect);
          const coords = [];
          const points = rect.get("aCoords");
          // 将内部坐标转换为实际画布坐标
          coords.push(this.getACoords(points["tl"], viewportTransform, zoom));
          coords.push(this.getACoords(points["tr"], viewportTransform, zoom));
          coords.push(this.getACoords(points["br"], viewportTransform, zoom));
          coords.push(this.getACoords(points["bl"], viewportTransform, zoom));
          selectedCoords = coords;
          rotationAngle = rect.angle;
          // left = rect.left;
          // top = rect.top;
        }
      });

      if (selectedCoords) {
        //2、根据rotationAngle和selectedCoords,计算rect此时的宽高,此时矩形有可能被旋转,有可能被拉伸,所以需要重新计算矩形的宽高
        const width = Math.sqrt(
          Math.pow(selectedCoords[1][0] - selectedCoords[0][0], 2) +
            Math.pow(selectedCoords[1][1] - selectedCoords[0][1], 2)
        );
        const height = Math.sqrt(
          Math.pow(selectedCoords[3][0] - selectedCoords[0][0], 2) +
            Math.pow(selectedCoords[3][1] - selectedCoords[0][1], 2)
        );

        // 计算矩形中心点
        const centerX = (selectedCoords[0][0] + selectedCoords[2][0]) / 2;
        const centerY = (selectedCoords[0][1] + selectedCoords[2][1]) / 2;

        // 新建一个 canvas 元素
        const tempCanvas = document.createElement("canvas");
        const tempCtx = tempCanvas.getContext("2d");
        tempCanvas.height = this.canvasProp.height;
        tempCanvas.width = this.canvasProp.width;
        // 将原点移动到矩形中心点
        tempCtx.translate(centerX, centerY);
        // 按中心点旋转画布
        tempCtx.rotate(-(rotationAngle * (Math.PI / 180)));
        // 将原点移回
        tempCtx.translate(-centerX, -centerY);
        // 将 fabricCanvas 的内容绘制到临时 canvas 上,并进行裁剪
        tempCtx.save();
        tempCtx.beginPath();
        tempCtx.moveTo(selectedCoords[0][0], selectedCoords[0][1]);
        tempCtx.lineTo(selectedCoords[1][0], selectedCoords[1][1]);
        tempCtx.lineTo(selectedCoords[2][0], selectedCoords[2][1]);
        tempCtx.lineTo(selectedCoords[3][0], selectedCoords[3][1]);
        tempCtx.closePath();
        tempCtx.clip();

        // 获取 fabricCanvas 的数据并绘制到临时 canvas 上
        tempCtx.drawImage(this.bgImg, 0, 0);
        // 恢复上下文状态
        tempCtx.restore();

        // return tempCtx.getImageData(
        //   0,
        //   0,
        //   tempCanvas.width,
        //   tempCanvas.height
        // );

        // 重新计算旋转后的矩形的 left 和 top
        const rotatedCoords = selectedCoords.map(coord => {
          const x = coord[0] - centerX;
          const y = coord[1] - centerY;
          const rotatedX =
            x * Math.cos((rotationAngle * Math.PI) / 180) +
            y * Math.sin((rotationAngle * Math.PI) / 180);
          const rotatedY =
            -x * Math.sin((rotationAngle * Math.PI) / 180) +
            y * Math.cos((rotationAngle * Math.PI) / 180);
          return [rotatedX + centerX, rotatedY + centerY];
        });

        const left = Math.min(
          rotatedCoords[0][0],
          rotatedCoords[1][0],
          rotatedCoords[2][0],
          rotatedCoords[3][0]
        );
        const top = Math.min(
          rotatedCoords[0][1],
          rotatedCoords[1][1],
          rotatedCoords[2][1],
          rotatedCoords[3][1]
        );

        const rotatedCanvas = document.createElement("canvas");
        rotatedCanvas.width = width;
        rotatedCanvas.height = height;
        const rotatedCtx = rotatedCanvas.getContext("2d");

        // 将临时canvas的图像绘制到旋转后的canvas上
        rotatedCtx.drawImage(
          tempCanvas,
          left,
          top,
          width,
          height,
          0,
          0,
          width,
          height
        );

        // 返回旋转后的图像数据
        return rotatedCtx.getImageData(
          0,
          0,
          rotatedCanvas.width,
          rotatedCanvas.height
        );
      } else {
        return null;
      }
    },

三、组件代码

<template>
  <div v-if="canvasProp.width != 0">
    <div
      class="canvas-wrap"
      :style="{
        width: canvasProp.width + 'px',
        height: canvasProp.height + 'px'
      }"
    >
      <canvas
        :width="canvasProp.width"
        :height="canvasProp.height"
        ref="canvas"
        :style="{
          width: canvasProp.width + 'px',
          height: canvasProp.height + 'px'
        }"
        id="canvasId"
      ></canvas>
    </div>
    <div
      class="muane"
      :style="{
        width: canvasProp.width - 2 + 'px'
      }"
    >
      <span @click="zoomBig">
        <i class="el-icon-zoom-in" style="color: #000;font-size: 16px;"></i>
      </span>
      <span @click="zoomSmall">
        <i class="el-icon-zoom-out" style="color: #000;font-size: 16px;"></i>
      </span>
      <span @click="panChange">
        <i
          v-if="!isPan"
          class="icon iconfont-saber icon-saber-shouzhang mIcon"
          style="color: #000;font-size: 15px !important;"
        ></i>
        <i
          v-else
          class="icon iconfont-saber icon-saber-24gl-pointer mIcon"
          style="color: #000;font-size: 16px !important;"
        ></i>
      </span>
    </div>

    <!-- 隐藏的 canvas -->
    <canvas
      ref="hiddenCanvas"
      :width="canvasProp.width"
      :height="canvasProp.height"
      style="display: none;"
    ></canvas>
  </div>
</template>
<script>
let backgroundImage = null;
export default {
  name: "images-tags",
  props: {
    // 矩形标注的数据
    tagsData: {
      type: Array,
      default: () => {
        return [
          {
            label: "基表数据",
            color: "#0000ff",
            type: "rectangle",
            width: 150,
            height: 50,
            rotate: 0,
            isInit: true,
            startX: 185,
            startY: 235
          },
          {
            label: "数据点2",
            color: "#0000ff",
            type: "rectangle",
            width: 150,
            height: 50,
            rotate: 0,
            isInit: false,
            startX: 100,
            startY: 100
          }
        ];
      }
    },
    // 图片路径
    images: {
      type: String,
      default: ""
    }
  },
  data() {
    return {
      bgImg: null,
      ctx: null,
      fabricCanvas: null,
      isPan: false,
      isDragging: false,
      canvasSizeScale: 1,
      lastPosX: 0,
      lastPosY: 0,
      canvasProp: {
        width: 0, // canvas的宽度
        height: 0, // canvas的高度
        translateX: 0,
        translateY: 0
      }
    };
  },
  mounted() {
    this.loadImageAndSetCanvas();
    //window.addEventListener("keydown", this.handleKeyDown);
  },
  beforeDestroy() {
    //window.removeEventListener("keydown", this.handleKeyDown);
    this.fabricCanvas.off("object:modified", this.modifyRect);
  },
  methods: {
    // 添加新的方法
    getCanvasImageData(id) {
      console.log("getCanvasImageData");
      const viewportTransform = this.fabricCanvas.viewportTransform;
      const zoom = this.fabricCanvas.getZoom();
      let selectedCoords = null;
      let rotationAngle = 0;
      

      this.fabricCanvas.getObjects().forEach(rect => {
        if (rect.id == id) {
          console.log("rect", rect);
          const coords = [];
          const points = rect.get("aCoords");
          // 将内部坐标转换为实际画布坐标
          coords.push(this.getACoords(points["tl"], viewportTransform, zoom));
          coords.push(this.getACoords(points["tr"], viewportTransform, zoom));
          coords.push(this.getACoords(points["br"], viewportTransform, zoom));
          coords.push(this.getACoords(points["bl"], viewportTransform, zoom));
          selectedCoords = coords;
          rotationAngle = rect.angle;
        }
      });

      if (selectedCoords) {
        //2、根据rotationAngle和selectedCoords,计算rect此时的宽高
        const width = Math.sqrt(
          Math.pow(selectedCoords[1][0] - selectedCoords[0][0], 2) +
            Math.pow(selectedCoords[1][1] - selectedCoords[0][1], 2)
        );
        const height = Math.sqrt(
          Math.pow(selectedCoords[3][0] - selectedCoords[0][0], 2) +
            Math.pow(selectedCoords[3][1] - selectedCoords[0][1], 2)
        );

        // 计算矩形中心点
        const centerX = (selectedCoords[0][0] + selectedCoords[2][0]) / 2;
        const centerY = (selectedCoords[0][1] + selectedCoords[2][1]) / 2;

        // 新建一个 canvas 元素
        const tempCanvas = document.createElement("canvas");
        const tempCtx = tempCanvas.getContext("2d");
        tempCanvas.height = this.canvasProp.height;
        tempCanvas.width = this.canvasProp.width;
        // 将原点移动到矩形中心点
        tempCtx.translate(centerX, centerY);
        // 按中心点旋转画布
        tempCtx.rotate(-(rotationAngle * (Math.PI / 180)));
        // 将原点移回
        tempCtx.translate(-centerX, -centerY);
        // 将 fabricCanvas 的内容绘制到临时 canvas 上,并进行裁剪
        tempCtx.save();
        tempCtx.beginPath();
        tempCtx.moveTo(selectedCoords[0][0], selectedCoords[0][1]);
        tempCtx.lineTo(selectedCoords[1][0], selectedCoords[1][1]);
        tempCtx.lineTo(selectedCoords[2][0], selectedCoords[2][1]);
        tempCtx.lineTo(selectedCoords[3][0], selectedCoords[3][1]);
        tempCtx.closePath();
        tempCtx.clip();

        // 获取 fabricCanvas 的数据并绘制到临时 canvas 上
        tempCtx.drawImage(this.bgImg, 0, 0);
        // 恢复上下文状态
        tempCtx.restore();

        // return tempCtx.getImageData(
        //   0,
        //   0,
        //   tempCanvas.width,
        //   tempCanvas.height
        // );

        // 重新计算旋转后的矩形的 left 和 top
        const rotatedCoords = selectedCoords.map(coord => {
          const x = coord[0] - centerX;
          const y = coord[1] - centerY;
          const rotatedX =
            x * Math.cos((rotationAngle * Math.PI) / 180) +
            y * Math.sin((rotationAngle * Math.PI) / 180);
          const rotatedY =
            -x * Math.sin((rotationAngle * Math.PI) / 180) +
            y * Math.cos((rotationAngle * Math.PI) / 180);
          return [rotatedX + centerX, rotatedY + centerY];
        });

        const left = Math.min(
          rotatedCoords[0][0],
          rotatedCoords[1][0],
          rotatedCoords[2][0],
          rotatedCoords[3][0]
        );
        const top = Math.min(
          rotatedCoords[0][1],
          rotatedCoords[1][1],
          rotatedCoords[2][1],
          rotatedCoords[3][1]
        );

          // 检查并调整宽度和高度,确保不超出画布边界,否则ios无法正常显示
        let adjustedWidth = width;
        let adjustedHeight = height;

        //超过右边界
        if (left + width > tempCanvas.width) {
          adjustedWidth = tempCanvas.width - left;
        }
        //超过左边界
        if (left<0) {
          adjustedWidth = width+left;
          if(adjustedWidth>tempCanvas.width){
            adjustedWidth = tempCanvas.width;
          }
        }
        //如果横框宽度
       
        //超过下边界
        if (top + height > tempCanvas.height) {
          adjustedHeight = tempCanvas.height - top;
        }
        //超过上边界
        if(top<0){
          adjustedHeight = height+top;
          if(adjustedHeight>tempCanvas.height){
            adjustedHeight = tempCanvas.height;
          }
        }



        const rotatedCanvas = document.createElement("canvas");
        rotatedCanvas.width = adjustedWidth;
        rotatedCanvas.height = adjustedHeight;
        const rotatedCtx = rotatedCanvas.getContext("2d");
        

        // 将临时canvas的图像绘制到旋转后的canvas上
        rotatedCtx.drawImage(
          tempCanvas,
          left<0 ? 0 : left,
          top<0? 0 : top,
          adjustedWidth,
          adjustedHeight,
          0,
          0,
          adjustedWidth,
          adjustedHeight
        );
    

        // 返回旋转后的图像数据
        return rotatedCtx.getImageData(
          0,
          0,
          rotatedCanvas.width,
          rotatedCanvas.height
        );
      } else {
        return null;
      }
    },

    convertToGrayScale(imageData) {
      const data = imageData.data;
      for (let i = 0; i < data.length; i += 4) {
        const avg = 0.3 * data[i] + 0.59 * data[i + 1] + 0.11 * data[i + 2];
        data[i] = avg;
        data[i + 1] = avg;
        data[i + 2] = avg;
      }
      return imageData;
    },
    modifyRect(rect) {
      console.log(rect, this);
      let selectedDataPoint = this.$parent.selectedDataPoint;
      let id = rect.target.id;
      for (let i = 0; i < selectedDataPoint.length; i++) {
        let item = selectedDataPoint[i];
        if (item.id == id) {
          this.applyGrayScale(id, item.ledThreshold);
          break;
        }
      }
    },
    //绘制灰色图片
    applyGrayScale(id, threshold) {
      // 获取选中区域的图像数据
      const imageData = this.getCanvasImageData(id);
      //对图像数据进行灰度处理
      const grayImageData = this.convertToGrayScale(imageData);
      this.$emit("getGrayCtxSize", {
        id: id,
        width: grayImageData.width,
        height: grayImageData.height
      });
      // 找到灰度canvas并放入灰度图片数据

      const grayCtx = this.$parent.$refs["grayCanvas-" + id][0].getContext(
        "2d"
      );

      this.$nextTick(() => {
        grayCtx.putImageData(grayImageData, 0, 0);
        this.applyThreshold(
          id,
          threshold,
          grayImageData.width,
          grayImageData.height
        );
      });
    },
    applyThreshold(id, threshold, width, height) {
      console.log("width", width);
      if (!width) return;

      if (!threshold) {
        threshold = 128;
      }
      const grayCtx = this.$parent.$refs["grayCanvas-" + id][0].getContext(
        "2d"
      );
      const grayImageData = grayCtx.getImageData(0, 0, width, height);
      const data = grayImageData.data;
      for (let i = 0; i < data.length; i += 4) {
        const avg = data[i];
        const value = avg > threshold ? 255 : 0;
        data[i] = value;
        data[i + 1] = value;
        data[i + 2] = value;
      }
      const binaryCtx = this.$parent.$refs["binaryCanvas-" + id][0].getContext(
        "2d"
      );
      grayCtx.width = binaryCtx.width;
      grayCtx.height = binaryCtx.height;

      binaryCtx.putImageData(grayImageData, 0, 0);
    },
    //切换操作切换
    panChange() {
      this.isPan = !this.isPan;
      if (this.isPan) {
        this.enablePan();
      } else {
        this.disablePan();
      }
    },
    enablePan() {
      this.fabricCanvas.discardActiveObject(); //取消当前的选中状态
      //禁止页面的选择
      this.fabricCanvas.forEachObject(function(obj) {
        obj.selectable = false; // 禁用对象选择
        obj.evented = false; // 禁用对象事件
      });
      // 设置 canvas 的鼠标样式为 'move'
      this.fabricCanvas.defaultCursor = "grab";
      this.fabricCanvas.hoverCursor = "grab";
      // 添加事件监听
      this.fabricCanvas.on("mouse:down", this.onMouseDown);
      this.fabricCanvas.on("mouse:move", this.onMouseMove);
      this.fabricCanvas.on("mouse:up", this.onMouseUp);
      this.fabricCanvas.on("mouse:out", this.onMouseUp);

      this.fabricCanvas.renderAll(); // 重绘画布以显示更改
    },
    disablePan() {
      //禁止页面的选择
      this.fabricCanvas.forEachObject(function(obj) {
        obj.selectable = true; // 禁用对象选择
        obj.evented = true; // 禁用对象事件
      });
      // 恢复 canvas 的默认鼠标样式
      this.fabricCanvas.defaultCursor = "default";
      this.fabricCanvas.hoverCursor = "default";

      // 移除事件监听
      this.fabricCanvas.off("mouse:down", this.onMouseDown);
      this.fabricCanvas.off("mouse:move", this.onMouseMove);
      this.fabricCanvas.off("mouse:up", this.onMouseUp);
      this.fabricCanvas.off("mouse:out", this.onMouseUp);

      this.fabricCanvas.renderAll(); // 重绘画布以显示更改
    },
    onMouseDown(event) {
      this.isDragging = true;
      this.lastPosX = event.e.clientX;
      this.lastPosY = event.e.clientY;
      this.fabricCanvas.defaultCursor = "grabbing";
    },
    // onMouseMove(event) {
    //   if (this.isDragging) {
    //     const e = event.e;
    //     const vpt = this.fabricCanvas.viewportTransform;
    //     vpt[4] += e.clientX - this.lastPosX;
    //     vpt[5] += e.clientY - this.lastPosY;
    //     this.fabricCanvas.requestRenderAll();
    //     this.lastPosX = e.clientX;
    //     this.lastPosY = e.clientY;
    //   }
    // },
    onMouseMove(event) {
      if (this.isDragging) {
        const e = event.e;
        const vpt = this.fabricCanvas.viewportTransform.slice(); // 创建副本,避免直接修改
        const zoom = this.fabricCanvas.getZoom();

        // 计算平移的距离
        const moveX = e.clientX - this.lastPosX;
        const moveY = e.clientY - this.lastPosY;

        // 获取画布和容器的宽高
        const canvasWidth = this.fabricCanvas.getWidth();
        const canvasHeight = this.fabricCanvas.getHeight();
        const containerWidth = this.canvasProp.width;
        const containerHeight = this.canvasProp.height;

        // 计算缩放后的宽高
        const scaledWidth = canvasWidth * zoom;
        const scaledHeight = canvasHeight * zoom;

        // 计算边界
        const leftBoundary = Math.min(0, containerWidth - scaledWidth);
        const topBoundary = Math.min(0, containerHeight - scaledHeight);

        // 应用平移限制
        vpt[4] = Math.max(leftBoundary, Math.min(0, vpt[4] + moveX));
        vpt[5] = Math.max(topBoundary, Math.min(0, vpt[5] + moveY));

        // 更新画布视口变换
        this.fabricCanvas.setViewportTransform(vpt);
        this.lastPosX = e.clientX;
        this.lastPosY = e.clientY;
      }
    },
    onMouseUp() {
      this.isDragging = false;
      this.fabricCanvas.defaultCursor = "grab";
    },
    zoomBig() {
      const currentZoom = this.fabricCanvas.getZoom();
      if (currentZoom < 3) {
        this.scaleCanvas(currentZoom + 0.1);
      }
    },
    zoomSmall() {
      const currentZoom = this.fabricCanvas.getZoom();
      if (currentZoom > 1) {
        this.scaleCanvas(currentZoom - 0.1);
      }
    },
    scaleCanvas(scale) {
      const center = this.getCanvasCenter();
      this.fabricCanvas.zoomToPoint({ x: center.x, y: center.y }, scale);
    },
    //获取画布的中心点
    getCanvasCenter() {
      const canvasCenter = {
        x: this.fabricCanvas.width / 2,
        y: this.fabricCanvas.height / 2
      };
      return canvasCenter;
    },

    handleKeyDown(event) {
      console.log("event.key", event.key);
      const step = 5; // 每次移动的步长
      switch (event.key) {
        case "ArrowUp":
          this.canvasProp.translateY -= step;
          break;
        case "ArrowDown":
          this.canvasProp.translateY += step;
          break;
        case "ArrowLeft":
          this.canvasProp.translateX -= step;
          break;
        case "ArrowRight":
          this.canvasProp.translateX += step;
          break;
      }
      this.panCanvas(this.canvasProp.translateX, this.canvasProp.translateY);
    },
    loadFromJSON(json) {
      //  this.fabricCanvas.clear();
      //背景图替换为当前的背景图
      let newjson = JSON.parse(json);

      console.log("加载的json", newjson);
      newjson.backgroundImage = backgroundImage;
      //this.fabricCanvas.loadFromJSON(newjson);
      // 反序列化对象
      this.fabricCanvas.loadFromJSON(newjson);
    },
    getCanvasJson() {
      console.log("在线获取json", this.fabricCanvas.toJSON(["id"]));
      return JSON.stringify(this.fabricCanvas.toJSON(["id"]));
    },

    hexToRgba(hex, alpha) {
      const bigint = parseInt(hex.replace("#", ""), 16);
      const r = (bigint >> 16) & 255;
      const g = (bigint >> 8) & 255;
      const b = bigint & 255;
      return `rgba(${r},${g},${b},${alpha})`;
    },
    // ...其他方法
    panCanvas(translateX, translateY) {
      // 获取当前的 viewportTransform
      const viewportTransform = this.fabricCanvas.viewportTransform.slice(); // 创建一个副本,以免直接修改原始数组

      // 更新平移值
      viewportTransform[4] = translateX;
      viewportTransform[5] = translateY;

      // 设置新的 viewportTransform
      this.fabricCanvas.setViewportTransform(viewportTransform);
    },
    loadImageAndSetCanvas() {
      if (!this.images) return;
      const img = new Image();
      img.src = this.images;
      img.onload = () => {
        console.log("图片加载完毕", img);
        this.canvasProp.width = img.width * this.canvasSizeScale;
        this.canvasProp.height = img.height * this.canvasSizeScale;
        this.bgImg = img;

        this.$nextTick(() => {
          this.fabricCanvas = new fabric.Canvas("canvasId");

          // 设置canvas大小
          this.fabricCanvas.setWidth(this.canvasProp.width);
          this.fabricCanvas.setHeight(this.canvasProp.height);

          this.$refs.hiddenCanvas.width = img.width;
          this.$refs.hiddenCanvas.height = img.height;

          this.ctx = this.$refs.hiddenCanvas.getContext("2d");
          this.ctx.drawImage(img, 0, 0, img.width, img.height);

          // 创建 Fabric 图片对象
          const fabricImage = new fabric.Image(img, {
            left: 0,
            top: 0,
            width: img.width * this.canvasSizeScale, // 你可以根据需要调整宽度
            height: img.height * this.canvasSizeScale, // 高度调整为原来的1.5倍
            selectable: false // 防止背景图片被选择
          });

          // 设置背景图片
          let backgroundImageObj = this.fabricCanvas.setBackgroundImage(
            fabricImage
          );

          backgroundImage = backgroundImageObj.backgroundImage;

          this.fabricCanvas.on("object:modified", this.modifyRect);

          this.$emit("fabricEnd");
          //this.drawTags(this.tagsData[0]);
        });
      };
    },
    clear() {
      this.fabricCanvas.getObjects().forEach(obj => {
        if (obj !== this.fabricCanvas.backgroundImage) {
          this.fabricCanvas.remove(obj);
        }
      });
    },
    getGroupById(id) {
      let result = null;
      this.fabricCanvas.getObjects().forEach(obj => {
        console.log(obj);
        if (obj.id === id) {
          result = obj;
        }
      });
      return result;
    },
    remove(item) {
      console.log("id", item.id);

      let result = this.getGroupById(item.id);

      console.log("result", result);

      if (result) {
        this.fabricCanvas.remove(result);
      }
    },
    drawTags(item, callback = () => {}) {
      // 创建一个矩形
      const rect = new fabric.Rect({
        fill: this.hexToRgba(item.color, 0.2), // 填充颜色,透明度为 0.2
        width: item.width, // 矩形的宽度
        height: item.height, // 矩形的高度
        angle: 0, // 旋转角度
        stroke: item.color,
        strokeWidth: 2 // 边框宽度
      });

      // 创建第一个文字对象
      const text1 = new fabric.Text(item.label, {
        fontFamily: "Arial",
        fontSize: 20,
        fill: item.color // 文字颜色
        // stroke: "#ffffff", // 文字边框颜色
        // strokeWidth: 0.5 // 文字边框宽度
      });

      // 计算文字位置以确保其在矩形的正中央
      text1.set({
        left: rect.left + rect.width / 2,
        top: rect.top + rect.height / 2,
        originX: "center",
        originY: "center"
      });

      // 使用fabric.Group将矩形和文字组合在一起
      const group = new fabric.Group([rect, text1], {
        left: item.isInit
          ? this.canvasProp.width / 2 - item.width / 2
          : item.startX, // 矩形的左上角 x 坐标
        top: item.isInit
          ? this.canvasProp.height / 2 - item.height / 2
          : item.startY, // 矩形的左上角 y 坐标
        cornerColor: item.color, // 控制点的颜色
        cornerSize: 10, // 控制点的大小
        borderWidth: 0, // 选中时的边框宽度
        transparentCorners: true,
        angle: item.rotate, // 组合对象的旋转角度
        id: item.id
      });
      // 将组合添加到画布上
      console.log("添加到画布上", group);
      this.fabricCanvas.add(group);
      callback();
    },
    saveData() {
      this.getPointData();
    },
    getACoords(point, viewportTransform, zoom) {
      return [Math.round(point.x), Math.round(point.y)];
    },
    getPointData() {
      let result = {};
      this.fabricCanvas.getObjects().forEach(rect => {
        console.log("rect==", rect);
        const coords = [];
        const points = rect.get("aCoords"); // 获取矩形的绝对坐标

        console.log("points", points);

        const viewportTransform = this.fabricCanvas.viewportTransform;
        const zoom = this.fabricCanvas.getZoom();
    

        // 将内部坐标转换为实际画布坐标
        coords.push(this.getACoords(points["tl"], viewportTransform, zoom));
        coords.push(this.getACoords(points["tr"], viewportTransform, zoom));
        coords.push(this.getACoords(points["br"], viewportTransform, zoom));
        coords.push(this.getACoords(points["bl"], viewportTransform, zoom));
        console.log("coords", coords);

        result[rect.id] = coords;
      });
      console.log("result", result);
      return result;
    }
  }
};
</script>
<style lang="scss" scoped>
.muane {
  height: 36px;
  background: #fafafa;
  display: flex;
  align-items: center;
  padding: 0px 1px;
  border-left: 1px solid rgb(229, 229, 229);
  border-right: 1px solid rgb(229, 229, 229);
  border-bottom: 1px solid rgb(229, 229, 229);
  span {
    width: 50%;
    text-align: center;
  }
}
.canvas-wrap {
  border-left: 1px solid rgb(229, 229, 229);
  border-right: 1px solid rgb(229, 229, 229);
  border-top: 1px solid rgb(229, 229, 229);
}
</style>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/870752.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

mkv怎么改成mp4?3种mkv转mp4格式方法的介绍

mkv怎么改成mp4&#xff1f;将MKV格式视频转换为MP4格式&#xff0c;能显著提升兼容性&#xff0c;让视频在更多设备、平台上流畅播放。无论是智能手机、平板电脑、智能电视还是网页浏览器&#xff0c;MP4格式都具备广泛的支持&#xff0c;从而扩大视频的传播范围和受众群体。这…

【Hadoop】核心组件深度剖析:HDFS、YARN与MapReduce的奥秘

&#x1f407;明明跟你说过&#xff1a;个人主页 &#x1f3c5;个人专栏&#xff1a;《大数据前沿&#xff1a;技术与应用并进》&#x1f3c5; &#x1f516;行路有良友&#xff0c;便是天堂&#x1f516; 目录 一、引言 1、Hadoop简介 2、Hadoop生态系统概览 二、Hadoo…

人工智能和机器学习 3(复旦大学计算机科学与技术实践工作站)python机器学习、Pytorch库入门、d2l学习+<机器学习、神经网络————原理、理论>

前言 安装 — 动手学深度学习 2.0.0 documentation (d2l.ai)https://zh.d2l.ai/chapter_installation/index.html 安装 我们需要配置一个环境来运行 Python、Jupyter Notebook、相关库以及运行本书所需的代码&#xff0c;以快速入门并获得动手学习经验。 安装 Miniconda 最…

建造者模式 和 外观模式

这两种模式很像, 都是将一个复杂的流程统一用一个方法进行包装, 方便外界使用. 建造者模式更像是 外观模式的一种特里, 只对一个类的复杂初始化流程进行包装 建造者模式 简介: 就是一个类的构造方法可能很复杂, 由于系统的限制等原因, 可能很多初始化逻辑不能放在构造函数里,…

Redis大显身手:实时用户活跃排行榜

文章目录 场景说明方案设计数据结构 Redis使用方案排行榜实现更新用户活跃积分幂等策略榜单评分更新触发活跃度更新排行榜查询 技术派项目源码地址 : Gitee :技术派 - https://gitee.com/itwanger/paicodingGithub :技术派 - https://github.com/itwanger/paicoding 效果如图 …

在阿里云上部署 Docker并通过 Docker 安装 Dify

目录 一、在服务器上安装docker和docker compose 1.1 首先关闭防火墙 1.2 安装docker依赖包 1.3 设置阿里云镜像源并安装docker-ce社区版 1.4 开启docker服务并设置开机自启动 1.5 查看docker版本信息 1.6 设置镜像加速 1.7 将docker compose环境复制到系统的bin目录下…

DM8守护集群部署、数据同步验证、主备切换

1. 环境描述 实例详情 端口详情 2. 部署步骤 2.1 数据准备 2.1.1主库初始化 [dmdbaray1 ~]$ cd /dmdba/dmdbms/bin [dmdbaray1 bin]$ ./dminit path/dmdba/data PAGE_SIZE32 EXTENT_SIZE32 CASE_SENSITIVEy CHARSET1 DB_NAMEGRP1_RT_01 INSTANCE_NAMEGRP1_RT_01 PORT_NU…

C++——入门基础(上)

目录 一、C参考文档 二、C在工作领域的应用 三、C学习书籍 四、C的第一个程序 五、命名空间 &#xff08;1&#xff09;namespace的定义 (2)命名空间的使用 六、C的输入和输出 七、缺省函数 八、函数重载 九、写在最后 一、C参考文档 &#xff08;1&#xff09;虽…

第46课 Scratch入门篇:狙击望远镜

无限画中画 故事背景: 手拿一把狙击枪,第一次按下空格键的时候瞄准镜放大一倍,再按一次再放大一倍。开枪设计,瞬间击毁! 程序原理: 1、瞄准的物品放大,其实是角色的变化,我们把背景设置成角色,原始的角色是 480360,第一次放大的图为 14401080,放大了 3 倍。第二级…

【Java 并发编程】(二) 从对象内存布局开始聊 synchronized

对象的内存布局 首先抛出一个经典面试题: 一个 Object 对象占多大? 这里我用工具打印了出来, 发现是 “16bytes”, 也就是 16B; 为什么? 请继续往下看; 普通对象(除了数组), 由markword, 类型指针, 实例数据(就是对象里的成员), 对齐填充(整个对象大小要能被8B整数, 方便6…

思科OSPF动态路由配置8

#路由协议实现# #任务八OSPF动态路由配置8# 开放式最短路径优先&#xff08;Open Shortest Path First,OSPF&#xff09;协议是目前网络中应用最广泛的动态路由协议之一。它也属于内部网关路由协议&#xff0c;能够适应各种规模的网络环境&#xff0c;是典型的链路状态路由协…

ZooKeeper 集群的详细部署

ZooKeeper 集群部署 一、ZooKeeper 简介1.1 什么是 ZooKeeper1.2 ZooKeeper 特点 二 ZooKeeper 的架构和设计4.1 ZooKeeper 数据模型4.1.1 Znode 节点特性 三、ZooKeeper 的集群安装前准备工作3.1 需要的准备工作3.2 Linux 系统 3 个节点准备3.2.1 克隆3.2.2 配置另外两台服务器…

【RabbitMQ】 相关概念 + 工作模式

本文将介绍一些MQ中常见的概念&#xff0c;同时也会简单实现一下RabbitMQ的工作流程。 MQ概念 Message Queue消息队列。是用来存储消息的队列&#xff0c;多用于分布式系统之间的通信。 系统间调用通常有&#xff1a;同步通信和异步通信。MQ就是在异步通信的时候使用的。 同…

高考志愿智能推荐系统-计算机毕设Java|springboot实战项目

&#x1f34a;作者&#xff1a;计算机毕设匠心工作室 &#x1f34a;简介&#xff1a;毕业后就一直专业从事计算机软件程序开发&#xff0c;至今也有8年工作经验。擅长Java、Python、微信小程序、安卓、大数据、PHP、.NET|C#、Golang等。 擅长&#xff1a;按照需求定制化开发项目…

第三方软件测评中心分享:软件系统测试内容和作用

近年来&#xff0c;随着信息技术的迅猛发展&#xff0c;软件系统的应用范围不断扩大。保证软件质量的关键措施之一就是软件系统测试。软件系统测试是指在软件开发生命周期中&#xff0c;通过一系列特定的测试活动来验证和确认软件系统的性能、功能及安全性&#xff0c;确保软件…

优优嗨聚集团:餐饮合作新未来引领美食产业新风尚

在快速变化的21世纪&#xff0c;餐饮行业作为民生消费的重要组成部分&#xff0c;正经历着前所未有的变革与挑战。随着消费者需求的多元化、个性化以及科技的不断进步&#xff0c;餐饮合作的新模式正悄然兴起&#xff0c;为行业带来了前所未有的发展机遇与活力。本文将探讨餐饮…

【Redis】Redis 数据类型与结构—(二)

Redis 数据类型与结构 一、值的数据类型二、键值对数据结构三、集合数据操作效率 一、值的数据类型 Redis “快”取决于两方面&#xff0c;一方面&#xff0c;它是内存数据库&#xff0c;另一方面&#xff0c;则是高效的数据结构。 Redis 键值对中值的数据类型&#xff0c;也…

网页版IntelliJ IDEA部署

在服务器部署网页 IntelliJ IDEA 引言 大家好&#xff0c;我是小阳&#xff0c;今天要为大家带来一个黑科技——如何在云端部署和使用WEB版的IntelliJ IDEA&#xff0c;让你在任何地方都可以随心所欲地进行Java开发。这个方法特别适合那些用着老旧Windows电脑&#xff0c;部署…

MySQL集群+Keepalived实现高可用部署

Mysql高可用集群-双主双活-myqlkeeplived 一、特殊情况 常见案例&#xff1a;当生产环境中&#xff0c;当应用服务使用了mysql-1连接信息&#xff0c;在升级打包过程中或者有高频的数据持续写入【对数据一致性要求比较高的场景】&#xff0c;这种情况下&#xff0c;数据库连接…

Springboot 整合 Swagger3(springdoc-openapi)

使用springdoc-openapi这个库来生成swagger的api文档 官方Github仓库&#xff1a; https://github.com/springdoc/springdoc-openapi 官网地址&#xff1a;https://springdoc.org 目录题 1. 引入依赖2. 拦截器设置3. 访问接口页面3.1 添加配置项&#xff0c;使得访问路径变短…