Files
FiDA_Front/src/components/Canvas/DepthCanvas/tools/canvasMethod.js
2026-03-26 10:53:01 +08:00

262 lines
6.7 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/** 克隆对象 */
export async function cloneObjects(objects = []) {
const arrs = []
for (const obj of objects) {
const clonedObj = await new Promise((resolve, reject) => {
obj.clone((v) => {
v.set({
left: obj.left,
top: obj.top,
width: obj.width,
height: obj.height,
scaleX: obj.scaleX,
scaleY: obj.scaleY,
angle: obj.angle,
})
resolve(v)
})
})
arrs.push(clonedObj)
}
return arrs
}
/** 获取组合对象边界 */
export async function getObjectsBoundingBox(objects = []) {
const box1 = { x: Infinity, y: Infinity }
const box2 = { x: -Infinity, y: -Infinity }
objects.forEach(obj => {
const rect = obj.getBoundingRect()
box1.x = Math.min(box1.x, rect.left)
box1.y = Math.min(box1.y, rect.top)
box2.x = Math.max(box2.x, rect.left + rect.width)
box2.y = Math.max(box2.y, rect.top + rect.height)
})
return {
left: box1.x,
top: box1.y,
width: box2.x - box1.x,
height: box2.y - box1.y,
}
}
/** 获取五角星数组 */
export function getStarArr(width = 0, height = 0) {
const arr = [
{ x: 0, y: -0.5 }, // 顶点0 (上)
{ x: 0.15, y: -0.15 }, // 顶点1 (内)
{ x: 0.50, y: -0.15 }, // 顶点2 (右上外)
{ x: 0.20, y: 0.10 }, // 顶点3 (内)
{ x: 0.30, y: 0.50 }, // 顶点4 (右下外)
{ x: 0.0, y: 0.25 }, // 顶点5 (内)
{ x: -0.30, y: 0.50 }, // 顶点6 (左下外)
{ x: -0.20, y: 0.10 }, // 顶点7 (内)
{ x: -0.50, y: -0.15 }, // 顶点8 (左上外)
{ x: -0.15, y: -0.15 } // 顶点9 (内)
]
return arr.map(item => ({
x: item.x * width,
y: item.y * height,
}))
}
/** 获取箭头路径 */
export function getArrowPath(width = 0, strokeWidth = 4) {
const height = strokeWidth * 4
const arr = [
["M", 0, height / 2],
["L", width, height / 2],
["M", width - height, 0],
["L", width, height / 2],
["L", width - height, height],
]
var path = ""
arr.forEach(item => {
path += item.join(" ") + " "
})
return path
}
/** 获取直线路径 */
export function getLinePath(width = 0, height = 0) {
const arr = [
["M", 0, height / 2],
["L", width, height / 2],
]
var path = ""
arr.forEach(item => {
path += item.join(" ") + " "
})
return path
}
/** 计算两点之间的距离 */
export function distance(x1, y1, x2, y2) {
const dx = x2 - x1;
const dy = y2 - y1;
return Math.sqrt(dx * dx + dy * dy);
}
/** 计算两点之间的角度(角度) */
export function angleBetweenPointsDegrees(x1, y1, x2, y2) {
const dx = x2 - x1;
const dy = y2 - y1;
// 计算弧度并转换为角度
const rad = Math.atan2(dy, dx);
const deg = rad * 180 / Math.PI;
return deg;
}
/**
* 获取对象黑白通道画布
* @param {fabric.Object} object - 要处理的 fabric 对象
* @param {ImageData} revData - 相反的ImageData白通道的相同位置是否为透明revData为白色为透明黑色为不透明
* @param {number} diff - 差值,默认 25
* @param {Object} rgba - 自定义 rgba 值,默认 { r: 255, g: 255, b: 255, a: 255 }
* @param {boolean} isMerge - 是否合并true=合并revDatafalse=反转revData
* @returns {HTMLCanvasElement|null} 包含黑白通道的画布,或 null 如果失败
*/
export function getObjectAlphaToCanvas(object, revData, diff = 30, rgba = { r: 255, g: 255, b: 255, a: 255 }, isMerge = false) {
const image = object.getElement();
if (image.nodeName !== "IMG" && image.nodeName !== "CANVAS") {
console.warn("对象不是图片");
return null;
}
const { width, height } = image;
if (!width || !height) {
console.warn("对象没有元素");
return null;
}
const canvas = document.createElement("canvas");
canvas.width = width;
canvas.height = height;
const ctx = canvas.getContext("2d", { willReadFrequently: true });
ctx.drawImage(image, 0, 0, width, height);
const data = ctx.getImageData(0, 0, width, height);
for (let i = 0; i < data.data.length; i += 4) {
const r = data.data[i + 0];
const g = data.data[i + 1];
const b = data.data[i + 2];
const a = data.data[i + 3];
const revR = revData?.data[i + 0] || 0;
const revG = revData?.data[i + 1] || 0;
const revB = revData?.data[i + 2] || 0;
const revA = revData?.data[i + 3] || 0;
let isHave = false;
if (r || g || b || a) {
if (revR > diff || revG > diff || revB > diff || revA > diff) {
isHave = false;
} else {
isHave = true;
}
}
if (isMerge && (revR || revG || revB || revA)) isHave = true;
if (isHave) {
data.data[i + 0] = rgba.r;
data.data[i + 1] = rgba.g;
data.data[i + 2] = rgba.b;
data.data[i + 3] = rgba.a;
} else {
data.data[i + 0] = 0;
data.data[i + 1] = 0;
data.data[i + 2] = 0;
data.data[i + 3] = 0;
}
}
ctx.clearRect(0, 0, width, height);
ctx.putImageData(data, 0, 0);
return canvas;
}
/**
* 图片边界跟踪算法(透明底)
* @param {HTMLCanvasElement} canvas - canvas元素
* @param {Number} scale - 缩放比例
* @returns {Array} 边界点数组 [{x, y}, ...]
*/
export function traceImageContour(canvas) {
const ctx = canvas.getContext("2d", { willReadFrequently: true });
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const data = imageData.data;
const width = canvas.width;
const height = canvas.height;
// 查找起始点(第一个不透明像素)
let startX = -1;
let startY = -1;
outer: for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
const index = (y * width + x) * 4;
if (data[index + 3] > 0) {
startX = x;
startY = y;
break outer;
}
}
}
if (startX === -1) return []; // 没有不透明像素
// Moore-Neighbor边界跟踪算法
const contour = [];
const visited = new Set();
const directions = [
[-1, 0],
[-1, -1],
[0, -1],
[1, -1],
[1, 0],
[1, 1],
[0, 1],
[-1, 1],
];
let currentX = startX;
let currentY = startY;
let backtrackDir = 4; // 起始方向:右
do {
const pointKey = `${currentX},${currentY}`;
if (!visited.has(pointKey)) {
contour.push({ x: currentX, y: currentY });
visited.add(pointKey);
}
// 从右方向开始顺时针查找
let found = false;
for (let i = 0; i < 8; i++) {
const dir = (backtrackDir + i) % 8;
const dx = directions[dir][0];
const dy = directions[dir][1];
const checkX = currentX + dx;
const checkY = currentY + dy;
if (
checkX >= 0 &&
checkX < width &&
checkY >= 0 &&
checkY < height
) {
const index = (checkY * width + checkX) * 4;
if (data[index + 3] > 0) {
currentX = checkX;
currentY = checkY;
backtrackDir = (dir + 5) % 8; // 下一个开始查找的方向
found = true;
break;
}
}
}
if (!found) break;
} while (
!(currentX === startX && currentY === startY) &&
visited.size < width * height
);
return contour;
}