React中使用AILabel.js实现图片标注功能,包括图片缩放,平移,下载等功能

本文详细介绍了如何在React应用中使用AILabel.js库进行图片标注,包括图片缩放、平移功能以及导出图片,提供了完整的代码示例和关键步骤。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

React中使用AILabel.js实现图片标注功能,包括图片缩放,平移,下载等功能

1.安装AILabel.js

npm install ailabel

2.文件里引入ailabel

import AILabel from "ailabel";

3.直接上例子完整代码

由于本人写这篇文章的时候,官方文档地址已经失效,地址如下http://ailabel.com.cn/public/ailabel/api/index.html,如需查看具体api的小伙伴可网上搜索,有其他人写的博客涉及到具体api,本文不作具体描述。

话不多说,直接上代码,以下是react中使用AiLabel实现图片矩形标注,平移,缩放的具体的例子。

import React, { useState, useEffect } from "react";

import AILabel from "ailabel";

import image1 from "./apple.jpg";
import image2 from "./tiger.png";
import image3 from "./biaozhu.jpg";

const imgs = [{ url: image1 }, { url: image2 }, { url: image3 }];

const AiLabelDemo = () => {
  let drawingStyle = {}; // 绘制过程中样式
  let gMap; // 标注容器
  let gImageLayer; // 图片绘制
  let gFirstFeatureLayer; // 实例化

  const [imgSize, setImgSize] = useState(); // 记录图片的大小
  const [index, setIndex] = useState(1);

  useEffect(() => {
    let size;
    (async () => {
      size = await getImageSizeByUrl(imgs[index].url); // 获取缩放比例以后的图片
      setImgSize({ ...size });
    })();
  }, [index]); // eslint-disable-line

  useEffect(() => {
    if (imgSize) {
      initgMap(imgSize);
    }
  }, [imgSize]); // eslint-disable-line

  // 根据url获取图片大小
  const getImageSizeByUrl = (url) => {
    return new Promise(function (resolve, reject) {
      let image = new Image();
      image.src = url;
      image.onload = function () {
        let scale = 1;
        const container = document.querySelector("#map");
        let containerWidth =
          container.clientWidth ||
          (container.style.width
            ? container.style.width.replace(/px/, "")
            : "");
        let containerHeight =
          container.clientHeight ||
          (container.style.height
            ? container.style.height.replace(/px/, "")
            : "");
        if (image.width > image.height) {
          // 宽图计算
          scale = containerWidth / image.width;
        } else {
          // 长图计算
          scale = containerHeight / image.height;
        }
        console.log(scale, "scale"); //根据标注区域获取图片展示大小
        resolve({
          width: image.width * scale,
          height: image.height * scale,
          scale,
          src: url,
        });
      };
      image.onerror = function () {
        reject(new Error("error"));
      };
    });
  };
  // 初始化容器
  const initgMap = (size) => {
    //初始化标注容器
    gMap = new AILabel.Map("map", {
      center: { x: size.width / 2, y: size.height / 2 }, // 为了让图片居中
      zoom: 600,
      mode: "PAN", // 绘制线段
      refreshDelayWhenZooming: true, // 缩放时是否允许刷新延时,性能更优
      zoomWhenDrawing: false,
      panWhenDrawing: false,
      zoomWheelRatio: 5, // 控制滑轮缩放缩率[0, 10), 值越小,则缩放越快,反之越慢
      withHotKeys: true, // 关闭快捷键
    });

    //添加图片层
    gImageLayer = new AILabel.Layer.Image(
      "first-layer-image", // id
      {
        src: size.src,
        width: size.width,
        height: size.height,
        crossOrigin: false, // 如果跨域图片,需要设置为true
        position: {
          // 左上角相对中心点偏移量
          x: 0,
          y: 0,
        },
      }, // imageInfo
      { name: "第一个图片图层" }, // props
      { zIndex: 5 } // style
    );
    //  添加到gMap对象
    gMap.addLayer(gImageLayer);

    //添加实例化,矢量图层
    gFirstFeatureLayer = new AILabel.Layer.Feature(
      "first-layer-feature", // id
      { name: "第一个矢量图层" }, // props
      { zIndex: 10 } // style
    );
    //  添加到gMap对象
    gMap.addLayer(gFirstFeatureLayer);

    //监听
    gMap.events.on("drawDone", (type, data) => {
      console.log("--type, data--", type, data);
      if (type === "RECT") {
        const rectFeature = new AILabel.Feature.Rect(
          `${+new Date()}`, // id
          data, // shape
          { name: "矢量图形" }, // props
          drawingStyle // style
        );
        gFirstFeatureLayer.addFeature(rectFeature);
      }
    });

    //双击选中
    gMap.events.on("featureSelected", (feature) => {
      console.log("--map featureSelected--", feature);
      gMap.setActiveFeature(feature);
      if (feature.type !== "POINT") {
        addDeleteIcon(feature);
      }
    });
    // 单击空白取消编辑
    gMap.events.on("featureUnselected", () => {
      // 取消featureSelected
      RemoveAllMarkers();
      gMap.setActiveFeature(null);
    });
    //更新完
    gMap.events.on("featureUpdated", (feature, shape) => {
      // 更新或者移动需要重新设置删除图标
      RemoveAllMarkers();
      feature.updateShape(shape);
      if (feature.type !== "POINT") {
        addDeleteIcon(feature);
      }
    });
    //右键 目前只针对"点"双击选中右键触发
    gMap.events.on("featureDeleted", ({ id: featureId }) => {
      console.log("右键删除");
      gFirstFeatureLayer.removeFeatureById(featureId);
    });

    // // 图片层相关事件监听
    gImageLayer.events.on("loadStart", (a, b) => {
      console.log("--loadStart--", a, b);
    });
    gImageLayer.events.on("loadEnd", (a, b) => {
      console.log("--loadEnd--", a, b);
    });
    gImageLayer.events.on("loadError", (a, b) => {
      console.log("--loadError--", a, b);
    });
  };

  //移除所有标注
  const RemoveAllMarkers = () => {
    gMap.markerLayer.removeAllMarkers();
  };
  // 增加删除图标
  const addDeleteIcon = (feature) => {
    const { shape, id } = feature;
    // 添加delete-icon
    let x =
      (shape?.x || shape?.cx || shape?.start?.x || shape?.points[0].x) +
      (shape?.width || shape?.r || 0);
    let y =
      (shape?.y || shape?.cy || shape?.start?.y || shape?.points[0].y) - 15;

    const gFirstMarker = new AILabel.Marker( //Marker标注层为系统内置图层
      id, // id
      {
        src: "https://s1.ax1x.com/2022/06/20/XvFRbT.png",
        position: { x, y }, // 矩形右上角 根据图形动态调整
        offset: {
          x: 0,
          y: 0,
        },
      }, // markerInfo
      { name: "delete" } // props
    );
    gFirstMarker.events.on("click", (marker) => {
      // 首先删除当前marker
      gMap.markerLayer.removeMarkerById(marker.id);
      // 删除对应feature
      gFirstFeatureLayer.removeFeatureById(feature.id);
    });
    gMap.markerLayer.addMarker(gFirstMarker);
  };

  // 放大
  const zoomIn = () => {
    gMap.zoomIn();
  };

  //缩小
  const zoomOut = () => {
    gMap.zoomOut();
  };

  // 平移或者画矩形
  const setMode = (mode) => {
    gMap.setMode(mode);
    // 后续对应模式处理
    switch (gMap.mode) {
      case "PAN": {
        break;
      }
      case "RECT": {
        drawingStyle = {
          strokeStyle: "#ffffff",
          lineWidth: 1,
          fillStyle: "#00f",
          globalAlpha: 0.3,
          fill: true,
        };
        gMap.setDrawingStyle(drawingStyle);
        break;
      }
      default:
        break;
    }
  };

  // 导出图片上护具
  const exportImage = async (type) => {
    const imagedata = await gMap.exportLayersToImage(
      { x: 0, y: 0, width: imgSize.width, height: imgSize.height },
      { type, format: "image/jpeg" }
    );
    const imageDom = new Image();
    if (type === "base64") {
      // 导出base64格式
      imageDom.src = imagedata;
    } else {
      // 导出blob格式
      const url = URL.createObjectURL(imagedata);
      imageDom.src = url;
      imageDom.onload = () => {
        URL.revokeObjectURL(url);
      };
    }

    let aLink = document.createElement("a");
    aLink.style.display = "none";
    aLink.href = imageDom.src;
    aLink.download = "export.png";
    // 触发点击-然后移除
    document.body.appendChild(aLink);
    aLink.click();
    document.body.removeChild(aLink);
  };

  // 获取所有features
  const getFeatures = () => {
    const allFeatures = gFirstFeatureLayer.getAllFeatures();
    console.log("--allFeatures--", allFeatures);
  };

  // 实例销毁
  const destroy = () => {
    gMap.destroy();
  };

  //重置
  const reset = () => {
    destroy();
    initgMap(imgSize);
  };

  //上一张
  const before = () => {
    if (gMap) destroy();
    setIndex((index) => {
      return index - 1;
    });
  };

  //下一张
  const next = () => {
    if (gMap) destroy();
    setIndex((index) => {
      return index + 1;
    });
  };

  window.onresize = function () {
    gMap && gMap.resize();
  };

  return (
    <>
      <div
        id="map"
        style={{
          overflow: "hidden",
          position: "relative",
          height: "500px",
          width: "500px",
          border: "1px dashed #ccc",
        }}
      ></div>
      <div className="button-wrap" style={{ marginTop: 20, textAlign: "left" }}>
        <button className="btn btn-default" onClick={() => setMode("PAN")}>
          平移
        </button>
        <button className="btn btn-default" onClick={() => setMode("RECT")}>
          矩形
        </button>
        <button className="btn btn-default" onClick={() => getFeatures()}>
          获取标注数据
        </button>
        <button
          className="btn btn-default"
          onClick={() => exportImage("base64")}
        >
          导出base64图片
        </button>
        <button className="btn btn-default" onClick={() => exportImage("blob")}>
          导出blob图片
        </button>
        <button className="btn btn-default" onClick={() => reset()}>
          图片归位
        </button>
        <button className="zoom-icon-plus" onClick={() => zoomIn()}>
          放大
        </button>
        <button className="zoom-icon-minus" onClick={() => zoomOut()}>
          缩小
        </button>
        <button disabled={index === 0} onClick={() => before()}>
          上一张
        </button>
        <button disabled={index === imgs.length - 1} onClick={() => next()}>
          下一张
        </button>
      </div>
    </>
  );
};

export default AiLabelDemo;

4.代码实现效果如下:

在这里插入图片描述

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值