- 通过canvas配合JSMpeg来渲染视频
- 给canvas绑定按下,移动、抬起事件实现路径绘画
- 获取坐标后roi如何计算,如果过程中屏幕发生改变、如何重新计算roi坐标
路径绘画代码
js
<canvas
//contextmenu.prevent 这里我是点击鼠标右击结束绘画,当数量大于8后禁用绘画
id="mycanvas"
ref="mycanvas"
@mousedown="canvasDown($event)"
@mousemove="canvasMove($event)"
@mouseup="canvasUp($event)"
@contextmenu.prevent="all_coordinatesArr.length < 8 ? doubleclick() : ''"
>浏览器不持之canvas
</canvas>
<script setup>
const isdraw = ref(false); //是否在画图形
const ctx = ref(null); //canvas对象
const coordinates = ref([]); //一个多边形的坐标信息
const endtip = ref(false); //是否结束一个多边形的绘制
const all_coordinates = ref([]); //所有多边形的信息
const all_coordinatesArr = ref([]); //初始坐标点
const isSwitch = ref(false); //控制是否可以画图
//鼠标按下事件
const canvasDown = (e) => {
//是否开启绘画
if (isSwitch.value) {
// 判断是否点击的为鼠标左键
if (e.button === 0) {
//判断是否结束本地绘画
if (endtip.value) {
//当前点击坐标小于10后可以结束本次绘画
if (all_coordinates.value.length < 10) {
endtip.value = false; //清空,重新画
} else {
// $message.warning("最多可绘制十个闭合的凸多边形区域");
return;
}
}
//获取鼠标按下的坐标,放入本次坐标数组中
var x = e.offsetX;
var y = e.offsetY;
var insertFlag = true;
//判断这次绘画的线条是否有坐标相交
coordinates.value.forEach((item, index) => {
if (item.cor_x == x && item.cor_y == y) {
insertFlag = false;
}
});
if (insertFlag) {
coordinates.value.push({ cor_x: x, cor_y: y });
// 判断相交
var lineList = [];
coordinates.value.forEach((item, index) => {
if (index < coordinates.value.length - 1) {
lineList.push([
item.cor_x,
item.cor_y,
coordinates.value[index + 1].cor_x,
coordinates.value[index + 1].cor_y,
]);
}
});
lineList.forEach((item, index) => {
if (index > 0 && index < lineList.length - 1) {
var flag = judgeIntersect(
lineList[lineList.length - 1][0],
lineList[lineList.length - 1][1],
lineList[lineList.length - 1][2],
lineList[lineList.length - 1][3],
lineList[index - 1][0],
lineList[index - 1][1],
lineList[index - 1][2],
lineList[index - 1][3]
);
if (flag) {
coordinates.value.pop();
}
}
});
}
if (coordinates.value.length > 19) {
// $message.warning("每个区域最多可绘制20个点坐标");
doubleclick();
drawcircles();
return;
}
drawcircle();
isdraw.value = true; //正在画多边形
}
}
};
const canvasMove = (e) => {
//没开始画或者结束画之后不进行操作
ctx.value.strokeStyle = "rgba(24, 144, 255, 1)";
if (coordinates.value.length == 0 || !isdraw.value || endtip.value) {
return;
}
var x = e.offsetX;
var y = e.offsetY;
//获取上一个点
var last_x = coordinates.value[coordinates.value.length - 1].cor_x;
var last_y = coordinates.value[coordinates.value.length - 1].cor_y;
ctx.value.clearRect(0, 0, canWidth.value, canheight.value); //清空画布
drawline(); //把之前的点连线
drawcircle(); //画之前的点
if (all_coordinates.value.length != 0) {
//不止一个多边形,把多边形们画出来
drawlines();
drawcircles();
fillarea();
}
//获取鼠标移动时的点,画线,实现线段跟踪效果。
ctx.value.beginPath();
ctx.value.moveTo(last_x, last_y);
ctx.value.lineTo(x, y); //追踪鼠标
ctx.value.stroke(); //绘制已定义的路径 追踪鼠标
ctx.value.closePath();
};
const canvasUp = (e) => {
ctx.value.clearRect(0, 0, canWidth.value, canheight.value); //清空画布
drawline(); //把之前的点连线
drawcircle(); //画之前的点
ctx.value.strokeStyle = "rgba(24, 144, 255, 1)"; //图形边框颜色
if (all_coordinates.value.length != 0) {
//不止一个多边形,把多边形们画出来
drawlines();
drawcircles();
fillarea();
}
};
//画线-把当前绘制的多边形之前的坐标线段绘制出来
const drawline = () => {
for (var i = 0; i < coordinates.value.length - 1; i++) {
ctx.value.beginPath();
var x0 = coordinates.value[i].cor_x;
var y0 = coordinates.value[i].cor_y;
var x1 = coordinates.value[i + 1].cor_x;
var y1 = coordinates.value[i + 1].cor_y;
ctx.value.moveTo(x0, y0);
ctx.value.lineTo(x1, y1);
ctx.value.stroke();
ctx.value.closePath();
}
};
//画点-把当前绘制的多边形之前的端点画圆
const drawcircle = () => {
ctx.value.fillStyle = "rgba(0, 133, 221, 1)";
for (var i = 0; i < coordinates.value.length; i++) {
var x = coordinates.value[i].cor_x;
var y = coordinates.value[i].cor_y;
ctx.value.beginPath(); //起始一条路径,或重置当前路径
ctx.value.moveTo(x, y); //把路径移动到画布中的指定点(x,y)开始坐标
ctx.value.arc(x, y, 5, 0, Math.PI * 2); //点
ctx.value.fill(); //填充点
ctx.value.closePath(); //创建从当前点回到起始点的路径
}
};
// 线条
const drawlines = () => {
//把所有多边形画出来
for (var i = 0; i < all_coordinates.value.length; i++) {
var cors = all_coordinates.value[i];
//前后坐标连线
for (var j = 0; j < cors.length - 1; j++) {
ctx.value.beginPath();
var x0 = cors[j].cor_x;
var y0 = cors[j].cor_y;
var y1 = cors[j + 1].cor_y;
ctx.value.moveTo(x0, y0);
ctx.value.lineTo(x1, y1);
ctx.value.stroke();
ctx.value.closePath();
}
//最后一个与第一个连线
var begin_x = cors[0].cor_x;
var begin_y = cors[0].cor_y;
var end_x = cors[cors.length - 1].cor_x;
var end_y = cors[cors.length - 1].cor_y;
ctx.value.beginPath();
ctx.value.moveTo(begin_x, begin_y);
ctx.value.lineTo(end_x, end_y);
ctx.value.stroke();
ctx.value.closePath();
}
};
const drawcircles = () => {
//为所有的多边形端点画圆
ctx.value.fillStyle = "rgba(24, 144, 255, 1)";
for (var i = 0; i < all_coordinates.value.length; i++) {
var cors = all_coordinates.value[i];
for (var j = 0; j < cors.length; j++) {
var x = cors[j].cor_x;
var y = cors[j].cor_y;
ctx.value.beginPath();
ctx.value.moveTo(x, y);
ctx.value.arc(x, y, 5, 0, Math.PI * 2);
ctx.value.fill();
ctx.value.closePath();
}
}
};
// 颜色填充
const fillarea = () => {
ctx.value.fillStyle = "rgba(24, 144, 255, 0.30)";
for (var i = 0; i < all_coordinates.value.length; i++) {
var cors = all_coordinates.value[i];
var x0 = cors[0].cor_x;
var y0 = cors[0].cor_y;
ctx.value.beginPath();
ctx.value.moveTo(x0, y0);
for (var j = 1; j < cors.length; j++) {
var x = cors[j].cor_x;
var y = cors[j].cor_y;
ctx.value.lineTo(x, y);
}
ctx.value.fill();
ctx.value.closePath();
}
};
// 右击按钮时---------------------------------------
const doubleclick = () => {
//双击画布,在最后一个点的时候双击,自动连线第一个点
if (coordinates.value.length != 0 && coordinates.value.length >= 3) { //必须大于3个点位
//连接起始点
var x0 = coordinates.value[0].cor_x;
var y0 = coordinates.value[0].cor_y;
var x1 = coordinates.value[coordinates.value.length - 1].cor_x;
var y1 = coordinates.value[coordinates.value.length - 1].cor_y;
ctx.value.beginPath();
ctx.value.moveTo(x0, y0);
ctx.value.lineTo(x1, y1);
ctx.value.stroke();
ctx.value.closePath();
isdraw.value = false;
endtip.value = true;
//每次的点添加到数组中
all_coordinates.value.push(coordinates.value);
// 设置坐标内部背景色透明
ctx.value.fillStyle = "rgba(24, 144, 255, 0.30)";
var bx = coordinates.value[0].cor_x;
var by = coordinates.value[0].cor_y;
ctx.value.moveTo(bx, by);
for (var k = 1; k < coordinates.value.length; k++) {
var x = coordinates.value[k].cor_x;
var y = coordinates.value[k].cor_y;
ctx.value.lineTo(x, y);
}
ctx.value.fill();
ctx.value.closePath();
all_coordinatesArr.value = calculatePolygonCoords(all_coordinates.value);
const result = calculatePolygonCoords(all_coordinates.value).map((innerArray) =>
innerArray.map((subArray) => subArray.map((value) => parseFloat(value)))
);
coordinates.value = [];
isSwitch.value = false;
}
};
// 计算多边形坐标 转我 归一化坐标 方便保存、传输
function calculatePolygonCoords(coords) {
const result = coords.map((arr) =>
arr.map((obj) => [
//Decimal.js 插件 解决精度丢失问题 pnpm i decimal.js
new Decimal(obj.cor_x / canWidth.value).toFixed(5),
new Decimal(obj.cor_y / canheight.value).toFixed(5),
])
);
return result;
//格式
// "POLYGON((0.11336 0.19512, 0.15376 0.64165, 0.54433 0.18574))",
}
// 判断直线是否相交
const judgeIntersect = (x1, y1, x2, y2, x3, y3, x4, y4) => {
if (
!(
Math.min(x1, x2) <= Math.max(x3, x4) &&
Math.min(y3, y4) <= Math.max(y1, y2) &&
Math.min(x3, x4) <= Math.max(x1, x2) &&
Math.min(y1, y2) <= Math.max(y3, y4)
)
)
return false;
var u, v, w, z;
u = (x3 - x1) * (y2 - y1) - (x2 - x1) * (y3 - y1);
v = (x4 - x1) * (y2 - y1) - (x2 - x1) * (y4 - y1);
w = (x1 - x3) * (y4 - y3) - (x4 - x3) * (y1 - y3);
z = (x2 - x3) * (y4 - y3) - (x4 - x3) * (y2 - y3);
return u * v <= 0.00000001 && w * z <= 0.00000001;
};
</script>
进行自适应、坐标计算
js
const canWidth = ref(null);
const canheight = ref(null);
const screenWidth = ref("");
// 监听屏幕变化
const watchScreen = () => {
screenWidth.value = document.body.clientWidth;
window.onresize = () => {
//屏幕尺寸变化就重新赋值
return (() => {
screenWidth.value = document.body.clientWidth;
})();
};
};
watch(
() => screenWidth.value,
(newVal, old) => {
canvasGetWid();
}
);
// 自适应方法
const canvasGetWid = () => {
//初始化画布对象
const canvas = document.querySelector("#mycanvas"); // canvas本身
const fileDIV = document.querySelector("#fileDIV"); // 这是canvas父级盒子
ctx.value = canvas.getContext("2d"); //getContext() 方法返回一个用于在画布上绘图的环境,2d二维绘图
// 获取元素的内容宽高 不包括 外边距margin
canvas.width = fileDIV.offsetWidth;
canvas.height = fileDIV.offsetHeight;
// 记录画板的宽高
canWidth.value = canvas.width;
canheight.value = canvas.height;
// 根据屏幕变化重新计算坐标
if (all_coordinatesArr.value) {
all_coordinates.value = switchChange(all_coordinatesArr.value);
}
canvasUp();
};
//计算坐标
const switchChange = (arr) => {
const result = arr.map((subArr) => {
return subArr.map((item) => {
// 这里使用float转为小数类型
// 再使用Int转为整数 因为计算后的坐标如果出现小数点会出现一些偏差 保证整数最好
const x = parseInt(parseFloat(item[0]) * canWidth.value);
const y = parseInt(parseFloat(item[1]) * canheight.value);
return { cor_x: x, cor_y: y };
});
});
return result;
};
视频播放
js
//播放视频
const startVideo = (rtsp) => {
let url = window.global.BASE_URL_PROD_URL;
const canvas = document.querySelector("#mycanvas");
loading.value = true;
new JSMpeg.Player(
"ws://localhost:9999/rtsp?url=" +
btoa("rtsp://admin:1234qwer@192.168.3.000:554/Streaming/Channels/201"),
{
canvas: document.getElementById("canvas"),
}
);
videoShow.value = false;
loading.value = false;
}, 1000);
};
这里使用的是rtsp视频流
前端如果要转码的花有两种方式
- 通过ffmpeg进行转码,再通过node转换为ws接口 前端页面再进行连接 一个实时流就需要一个窗口
- 使用rtsp2Web转码,是一个node插件 方便简洁