canvas/pages/index/transform-canvas.vue

325 lines
8.8 KiB
Vue
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.

<!-- 子组件 transform-canvas.vue -->
<template>
<view>
<canvas
id="myCanvas"
canvas-id="myCanvas"
type="2d"
:style="{
width: `${canvasDisplayWidth}px`,
height: `${canvasDisplayHeight}px`,
border: '1px solid #eee'
}"
></canvas>
</view>
</template>
<script>
export default {
props: {
width: Number, // 画布实际宽度(像素)
height: Number, // 画布实际高度(像素)
imgUrl: String, // 图片URL
matrix: { // 变换矩阵
type: Object,
default: () => ({
a: 1, b: 0, c: 0, d: 1, tx: 0, ty: 0
})
},
areaData:{}
},
data() {
return {
canvasContext: null,
image: null,
dpr: 1,
canvasDisplayWidth: 800, // 默认显示尺寸
canvasDisplayHeight: 600,
canvasActualWidth: 800, // 实际像素尺寸
canvasActualHeight: 600
};
},
watch: {
imgUrl: {
handler(newUrl) {
if (newUrl) this.loadImage(newUrl);
},
immediate: true
},
width: {
handler() {
this.updateCanvasSize();
},
immediate: true
},
height: {
handler() {
this.updateCanvasSize();
},
immediate: true
},
matrix: {
deep: true,
immediate: true, // 添加立即触发
handler() {
this.$nextTick(this.redraw); // 确保在下一个tick重绘
}
},
},
mounted() {
this.initCanvas();
},
methods: {
// 添加点击检测方法
checkHitArea(x, y) {
if (!this.areaData) return null;
// 转换坐标到原始画布空间
const inverted = this.matrix.invertPoint(x, y);
console.log('checkHitArea',inverted)
// 遍历所有区域检测
for (const area of this.areaData) {
if (this.pointInPolygon(inverted.x, inverted.y, area.polygon)) {
return area.areacode;
}
}
return null;
},
// 射线法判断点是否在多边形内
// pointInPolygon(x, y, polygon) {
// // console.log('pointInPolygon',x, y, polygon)
// // 确保多边形有足够的点构成
// if (!polygon || polygon.length < 6) return false;
// let inside = false;
// const length = polygon.length;
// // 不需要遍历最后一个点,因为它是闭合点(与第一个点相同)
// for (let i = 0, j = length - 2; i < length; j = i, i += 2) {
// const xi = polygon[i], yi = polygon[i + 1];
// const xj = polygon[j], yj = polygon[j + 1];
// // 检查点是否在边的y值范围内
// const intersect = ((yi > y) !== (yj > y));
// // 检查点是否在边的左侧
// if (intersect) {
// const slope = (xj - xi) / (yj - yi); // 边的斜率
// const intersectX = xi + (y - yi) * slope;
// if (x < intersectX) {
// inside = !inside;
// }
// }
// }
// return inside;
// },
// 在 transform-canvas.vue 中替换 pointInPolygon 方法
pointInPolygon(x, y, polygon) {
// 确保多边形的点数是偶数且至少有3个顶点
if (!polygon || polygon.length < 6 || polygon.length % 2 !== 0) {
return false;
}
let inside = false;
// 转换多边形点数为顶点数
const vertexCount = polygon.length / 2;
let j = vertexCount - 1;
for (let i = 0; i < vertexCount; j = i++) {
// 获取当前顶点和上一个顶点
const xi = polygon[i * 2];
const yi = polygon[i * 2 + 1];
const xj = polygon[j * 2];
const yj = polygon[j * 2 + 1];
// 检查点是否在边界上 - 提高精度避免浮点数误差
const onLine = this.pointOnLineSegment(x, y, xi, yi, xj, yj);
if (onLine) return true;
// 检查点是否在两个顶点之间(射线法)
if ((yi < y && yj >= y) || (yj < y && yi >= y)) {
// 计算射线与边界的交点
const slope = (xj - xi) / (yj - yi);
const intersectX = xi + (y - yi) * slope;
// 如果交点在点右侧,标记为穿越
if (x < intersectX) {
inside = !inside;
}
}
}
return inside;
},
// 添加点是否在线段上的检查
pointOnLineSegment(x, y, x1, y1, x2, y2) {
// 线段的长度
const dx = x2 - x1;
const dy = y2 - y1;
const segmentLength = Math.sqrt(dx * dx + dy * dy);
// 点到线段起点的向量
const vx = x - x1;
const vy = y - y1;
// 向量在方向上的投影
const dotProduct = vx * dx + vy * dy;
// 计算参数t (0-1之间表示在线段上)
const t = dotProduct / (segmentLength * segmentLength);
// 点在线段上
if (t >= 0 && t <= 1) {
// 计算点在线段上的位置
const projX = x1 + t * dx;
const projY = y1 + t * dy;
// 检查点与投影点的距离容差5px
const distance = Math.sqrt((x - projX) ** 2 + (y - projY) ** 2);
return distance < 5;
}
return false;
},
initCanvas() {
const query = uni.createSelectorQuery().in(this);
query.select('#myCanvas')
.fields({ node: true, size: true })
.exec(async (res) => {
if (!res[0]) return;
this.canvas = res[0].node;
this.ctx = res[0].node.getContext('2d');
// 获取设备像素比
this.dpr = uni.getSystemInfoSync().pixelRatio;
// 更新画布尺寸
this.updateCanvasSize();
// 如果已有图片URL加载图片
if (this.imgUrl) {
await this.loadImage(this.imgUrl);
}
});
},
updateCanvasSize() {
// 显示尺寸使用prop传入的width/height
this.canvasDisplayWidth = this.width;
this.canvasDisplayHeight = this.height;
// 实际像素尺寸考虑DPR
this.canvasActualWidth = this.width * this.dpr;
this.canvasActualHeight = this.height * this.dpr;
if (this.canvas) {
this.canvas.width = this.canvasActualWidth;
this.canvas.height = this.canvasActualHeight;
this.ctx.scale(this.dpr, this.dpr);
this.redraw();
}
},
async loadImage(src) {
try {
// 等待canvas初始化
if (!this.canvas) {
await this.initCanvas();
}
this.image = await new Promise((resolve, reject) => {
if (!this.canvas) {
// reject(new Error("Canvas not initialized"));
return;
}
const image = this.canvas.createImage();
image.src = src;
image.onload = () => resolve(image);
image.onerror = reject;
});
this.redraw();
} catch (e) {
console.error("图片加载失败:", e);
this.image = null;
}
},
// async loadImage(src) {
// try {
// this.image = await new Promise((resolve, reject) => {
// const image = this.canvas.createImage();
// image.src = src;
// image.onload = () => resolve(image);
// image.onerror = reject;
// });
// this.redraw();
// } catch (e) {
// console.error("图片加载失败:", e);
// this.image = null;
// this.redraw(); // 加载失败时也需要重绘画布
// }
// },
redraw() {
if (!this.ctx) return;
// 保存当前状态
this.ctx.save();
// 重置为初始状态
this.ctx.resetTransform();
this.ctx.clearRect(0, 0, this.canvasActualWidth, this.canvasActualHeight);
// 应用设备像素比缩放
this.ctx.scale(this.dpr, this.dpr);
// 应用变换矩阵
const { a, b, c, d, tx, ty } = this.matrix;
this.ctx.setTransform(a, b, c, d, tx, ty);
// 绘制图片(或错误信息)
if (this.image) {
this.ctx.drawImage(
this.image,
0,
0,
this.canvasDisplayWidth,
this.canvasDisplayHeight
);
}
// this.ctx.restore();
// 恢复上下文状态
if (this.areaData && this.areaData.length > 0) {
this.areaData.forEach(area => {
// if (!area.polygon || area.polygon.length < 6) return;
this.ctx.beginPath();
for (let i = 0; i < area.polygon.length; i += 2) {
// 使用标准API
if (i === 0) {
this.ctx.moveTo(area.polygon[i], area.polygon[i + 1]);
} else {
this.ctx.lineTo(area.polygon[i], area.polygon[i + 1]);
}
}
this.ctx.closePath();
this.ctx.strokeStyle = 'rgba(255, 0, 0, 0.7)';
this.ctx.lineWidth = 2;
this.ctx.stroke();
this.ctx.fillStyle = 'rgba(0, 255, 0, 0.2)';
this.ctx.fill();
});
}
}
}
}
</script>