2024-11-22 09:20:25 +08:00
|
|
|
<template>
|
2026-01-12 13:30:10 +08:00
|
|
|
<div class="test" ref="testRef">
|
2026-01-13 14:41:20 +08:00
|
|
|
<div class="canvas-container">
|
2026-01-12 13:30:10 +08:00
|
|
|
<canvas id="canvas"></canvas>
|
2026-01-13 14:41:20 +08:00
|
|
|
</div>
|
2026-01-12 13:30:10 +08:00
|
|
|
</div>
|
2024-11-22 09:20:25 +08:00
|
|
|
</template>
|
|
|
|
|
|
2026-01-07 13:02:08 +08:00
|
|
|
<script lang="ts" setup>
|
2026-01-12 13:30:10 +08:00
|
|
|
import { fabric } from "fabric-with-all";
|
|
|
|
|
import { ref, watch, onMounted } from "vue";
|
|
|
|
|
const imageUrl = "/src/assets/images/canvas/xiangaofenge.png";
|
|
|
|
|
const testRef = ref(null);
|
|
|
|
|
|
|
|
|
|
var canvas = null;
|
|
|
|
|
onMounted(() => {
|
|
|
|
|
canvas = new fabric.Canvas("canvas");
|
|
|
|
|
canvas.setWidth(800);
|
|
|
|
|
canvas.setHeight(600);
|
|
|
|
|
// fabric.Image.fromURL(imageUrl, (img) => {
|
|
|
|
|
// console.log(img.getElement());
|
|
|
|
|
// img.set({
|
|
|
|
|
// scaleX: 0.5,
|
|
|
|
|
// scaleY: 0.5,
|
|
|
|
|
// });
|
|
|
|
|
// canvas.add(img);
|
|
|
|
|
// });
|
|
|
|
|
const image = new Image();
|
|
|
|
|
image.src = imageUrl;
|
|
|
|
|
image.onload = () => {
|
|
|
|
|
const canvas1 = document.createElement("canvas");
|
|
|
|
|
const width = image.width / 2;
|
|
|
|
|
const height = image.height / 2;
|
|
|
|
|
canvas1.width = width;
|
|
|
|
|
canvas1.height = height;
|
|
|
|
|
const ctx1 = canvas1.getContext("2d");
|
|
|
|
|
ctx1.drawImage(image, 0, 0, width, height);
|
2026-01-13 14:41:20 +08:00
|
|
|
const arr = traceImageContour(canvas1);
|
|
|
|
|
const str = arr.map((v) => `${v.x} ${v.y}`).join(" L ");
|
|
|
|
|
const path = new fabric.Path(`M ${str} z`);
|
|
|
|
|
path.set({
|
|
|
|
|
fill: "rgba(127, 255, 127, 0.3)",
|
|
|
|
|
stroke: "#2AA81B",
|
|
|
|
|
strokeWidth: 2,
|
|
|
|
|
strokeDashArray: [8, 4],
|
|
|
|
|
strokeLineCap: "round",// 折线端点样式
|
|
|
|
|
strokeLineJoin: "bevel", // 折线连接样式
|
|
|
|
|
strokeUniform: true, // 保持描边宽度不随缩放改变
|
|
|
|
|
});
|
|
|
|
|
canvas.add(path);
|
2026-01-12 13:30:10 +08:00
|
|
|
};
|
|
|
|
|
});
|
2026-01-13 14:41:20 +08:00
|
|
|
// 边界追踪
|
|
|
|
|
function traceImageContour(canvas) {
|
|
|
|
|
const ctx = canvas.getContext("2d");
|
|
|
|
|
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;
|
2026-01-12 13:30:10 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-01-13 14:41:20 +08:00
|
|
|
|
|
|
|
|
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;
|
2026-01-12 13:30:10 +08:00
|
|
|
}
|
2024-11-22 09:20:25 +08:00
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<style lang='less' scoped>
|
2026-01-12 13:30:10 +08:00
|
|
|
.test {
|
|
|
|
|
.canvas-container {
|
|
|
|
|
display: inline-block;
|
|
|
|
|
border: 1px solid #000;
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-11-22 09:20:25 +08:00
|
|
|
</style>
|