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;