当前位置: 首页 > news >正文

实现视频实时马赛克

步骤 1:定义马赛克相关类型与变量

在代码中声明马赛克工具的类型、状态变量(如尺寸、移动状态),并在绘图动作接口中支持马赛克类型。

对应代码

// 定义绘图工具类型,包含马赛克
type DrawingTool = 'pen' | 'rectangle' | 'circle' | 'arrow' | 'eraser' | 'text' | 'line' | 'mosaic';// 马赛克相关变量
const mosaicSize = ref(100); // 马赛克尺寸(默认100px)
let isMovingMosaic = ref(false); // 是否正在移动马赛克
let currentMosaicIndex = ref(-1); // 当前选中的马赛克索引// 绘图动作接口(支持马赛克工具)
interface DrawingAction {tool: DrawingTool;points: Point[]; // 存储中心点坐标(百分比)color: string; // 马赛克固定颜色#50B19Dwidth: number; // 存储马赛克尺寸text?: string;
}

步骤 2:添加马赛克工具按钮与尺寸控制器

在工具栏中添加马赛克按钮,点击后激活工具,并显示尺寸调整滑块。

对应代码

<!-- 模板中的马赛克工具按钮 -->
<XmBtn icon-text="马赛克" @click="selectTool('mosaic')" :class="{ active: activeTool === 'mosaic' }"><template #icon><span class="iconfont icon-mosaic"></span></template>
</XmBtn><!-- 马赛克尺寸调整器(仅在选中马赛克工具时显示) -->
<div v-if="activeTool === 'mosaic'" class="mosaic-size-control"><el-sliderv-model="mosaicSize":min="30":max="500":step="10":show-input="true"style="width: 140px"tooltip="always"></el-slider>
</div>

步骤 3:实现马赛克绘制逻辑(创建与预览)

处理鼠标事件,在画布上点击并拖动时创建马赛克,实时预览其位置和尺寸。

对应代码

// 开始绘图(马赛克工具逻辑)
const startDrawing = (e: MouseEvent) => {if (!props.isInitiator || !canvasContext || !canvasRef.value) return;const rect = canvasRef.value.getBoundingClientRect();// 计算点击位置相对于画布的百分比坐标const xPercent = (e.clientX - rect.left) / rect.width;const yPercent = (e.clientY - rect.top) / rect.height;// 马赛克工具逻辑if (activeTool.value === 'mosaic') {// 检查是否点击了已有的马赛克(用于移动)const clickedIndex = findClickedAction(xPercent, yPercent);if (clickedIndex !== -1 && drawingHistory.value[clickedIndex].tool === 'mosaic') {isMovingMosaic.value = true;currentMosaicIndex.value = clickedIndex;startPoint = { x: xPercent, y: yPercent };return;}// 创建新的马赛克isDrawing.value = true;startPoint = { x: xPercent, y: yPercent };currentAction = {tool: 'mosaic',points: [{ x: xPercent, y: yPercent }], // 中心点坐标color: '#50B19D', // 固定马赛克颜色width: mosaicSize.value // 用width存储尺寸};return;}
};// 绘图过程(实时更新马赛克位置)
const draw = (e: MouseEvent) => {if (activeTool.value !== 'mosaic' || !isDrawing.value || !currentAction || !canvasRef.value) return;const rect = canvasRef.value.getBoundingClientRect();const xPercent = (e.clientX - rect.left) / rect.width;const yPercent = (e.clientY - rect.top) / rect.height;// 更新当前马赛克的中心点坐标和尺寸currentAction.points = [{ x: xPercent, y: yPercent }];currentAction.width = mosaicSize.value;redrawCanvas(); // 实时重绘预览
};// 停止绘图(保存马赛克到历史记录)
const stopDrawing = () => {if (activeTool.value === 'mosaic' && isDrawing.value && currentAction) {isDrawing.value = false;drawingHistory.value.push(currentAction); // 保存到历史记录sendDrawingAction({ type: 'draw', data: currentAction }); // 同步到其他用户currentAction = null;startPoint = null;return;}
};

 

步骤 4:实现马赛克的绘制渲染(画布显示)

通过临时画布生成马赛克图案(块状效果),并绘制到主画布,同时添加边框和角落标记增强可视性。

对应代码

// 绘制单个标注动作(包含马赛克)
const drawAction = (action: DrawingAction) => {if (!canvasContext || !canvasRef.value) return;const { tool, points, color, width } = action;const canvas = canvasRef.value;// 转换百分比坐标为画布实际像素坐标const actualPoints = points.map(p => ({x: p.x * canvas.width,y: p.y * canvas.height}));// 根据工具类型绘制,此处处理马赛克switch (tool) {case 'mosaic':if (actualPoints.length) {drawMosaic(actualPoints[0], width); // 调用马赛克绘制方法}break;// 其他工具绘制逻辑...}
};// 绘制马赛克(核心渲染逻辑)
const drawMosaic = (position: Point, size: number) => {if (!canvasContext || !canvasRef.value) return;const canvas = canvasRef.value;// 基于参考尺寸计算实际显示大小(适配画布缩放)const mosaicSize = size * (canvas.width / props.referenceWidth);const cellSize = 10; // 马赛克块大小(固定10px,保持块状感)// 创建临时画布生成马赛克图案const tempCanvas = document.createElement('canvas');tempCanvas.width = mosaicSize;tempCanvas.height = mosaicSize;const tempCtx = tempCanvas.getContext('2d');if (!tempCtx) return;// 绘制马赛克块(主色#50B19D,带浅色边框)tempCtx.fillStyle = '#50B19D'; // 固定主色tempCtx.strokeStyle = 'rgba(255, 255, 255, 0.2)'; // 块间边框tempCtx.lineWidth = 1;// 绘制网格状马赛克块for (let y = 0; y < mosaicSize; y += cellSize) {for (let x = 0; x < mosaicSize; x += cellSize) {tempCtx.fillRect(x, y, cellSize, cellSize); // 填充块tempCtx.strokeRect(x, y, cellSize, cellSize); // 块边框}}// 将临时画布绘制到主画布(居中显示)canvasContext.drawImage(tempCanvas,position.x - mosaicSize / 2, // 左上角x(居中)position.y - mosaicSize / 2, // 左上角y(居中)mosaicSize,mosaicSize);// 绘制外边框(突出马赛克范围)canvasContext.strokeStyle = 'rgba(80, 177, 157, 0.8)'; // 同色系半透明边框canvasContext.lineWidth = 2;canvasContext.strokeRect(position.x - mosaicSize / 2, position.y - mosaicSize / 2, mosaicSize, mosaicSize);// 绘制角落标记(增强边界识别)const cornerSize = 8;canvasContext.fillStyle = '#50B19D';canvasContext.fillRect(position.x - mosaicSize / 2, position.y - mosaicSize / 2, cornerSize, cornerSize); // 左上角canvasContext.fillRect(position.x + mosaicSize / 2 - cornerSize, position.y - mosaicSize / 2, cornerSize, cornerSize); // 右上角canvasContext.fillRect(position.x - mosaicSize / 2, position.y + mosaicSize / 2 - cornerSize, cornerSize, cornerSize); // 左下角canvasContext.fillRect(position.x + mosaicSize / 2 - cornerSize, position.y + mosaicSize / 2 - cornerSize, cornerSize, cornerSize); // 右下角
};

步骤 5:实现马赛克的编辑功能(移动与删除)

支持点击选中马赛克并拖动移动,以及通过橡皮擦工具删除。

对应代码

// 移动马赛克(在draw方法中处理)
const draw = (e: MouseEvent) => {// ...其他逻辑// 移动马赛克逻辑if (isMovingMosaic.value && currentMosaicIndex.value !== -1 && startPoint) {const rect = canvasRef.value!.getBoundingClientRect();const xPercent = (e.clientX - rect.left) / rect.width;const yPercent = (e.clientY - rect.top) / rect.height;// 计算移动偏移量const dx = xPercent - startPoint.x;const dy = yPercent - startPoint.y;// 更新马赛克位置const mosaic = drawingHistory.value[currentMosaicIndex.value];mosaic.points[0].x += dx;mosaic.points[0].y += dy;// 限制在画布范围内(0-1之间)mosaic.points[0].x = Math.max(0, Math.min(1, mosaic.points[0].x));mosaic.points[0].y = Math.max(0, Math.min(1, mosaic.points[0].y));// 更新起始点用于下一次计算startPoint = { x: xPercent, y: yPercent };redrawCanvas(); // 重绘return;}
};// 停止移动马赛克(在stopDrawing中处理)
const stopDrawing = () => {if (isMovingMosaic.value) {isMovingMosaic.value = false;if (currentMosaicIndex.value !== -1) {// 同步移动后的马赛克数据sendDrawingAction({type: 'update',index: currentMosaicIndex.value,data: drawingHistory.value[currentMosaicIndex.value]});currentMosaicIndex.value = -1;}startPoint = null;return;}
};// 橡皮擦删除马赛克(在startDrawing中处理)
const startDrawing = (e: MouseEvent) => {// ...其他逻辑// 橡皮擦逻辑(删除点击的标注,包括马赛克)if (activeTool.value === 'eraser') {const clickedIndex = findClickedAction(xPercent, yPercent);if (clickedIndex !== -1) {drawingHistory.value.splice(clickedIndex, 1); // 从历史中删除sendDrawingAction({ type: 'remove', index: clickedIndex }); // 同步删除redrawCanvas();}return;}
};

步骤 6:实现马赛克的网络同步

通过 WebSocket 将马赛克的创建、移动、删除动作同步到其他用户。

对应代码

// 发送绘图动作到服务器
const sendDrawingAction = (message: SocketMessage) => {if (props.socket && props.isInitiator) {props.socket.sendJson({incidentType: 'annotation',annotationType: message.type, // 'draw'/'update'/'remove'data: message.data, // 马赛克数据index: message.index, // 索引(用于删除/更新)userId: props.userId,creater: props.creater});}
};// 处理接收的同步数据
const handleDrawingData = (data: any) => {if (data.annotationType === 'draw' && data.data.tool === 'mosaic') {drawingHistory.value.push(data.data); // 添加新马赛克redrawCanvas();} else if (data.annotationType === 'update' && data.data.tool === 'mosaic') {drawingHistory.value[data.index] = data.data; // 更新移动后的马赛克redrawCanvas();} else if (data.annotationType === 'remove') {drawingHistory.value.splice(data.index, 1); // 删除马赛克redrawCanvas();}
};

步骤 7:截图时保留马赛克效果

截图功能中单独处理马赛克绘制,确保导出的图片包含马赛克。

对应代码

// 截图时绘制马赛克
const drawActionToCanvas = (action: DrawingAction, ctx: CanvasRenderingContext2D, canvas: HTMLCanvasElement) => {switch (action.tool) {case 'mosaic':if (action.points.length) {drawMosaicToCanvas(action.points[0], action.width, ctx, canvas);}break;// 其他工具...}
};// 截图中的马赛克绘制方法
const drawMosaicToCanvas = (position: Point, size: number, ctx: CanvasRenderingContext2D, canvas: HTMLCanvasElement) => {// 逻辑与drawMosaic类似,但基于原始参考尺寸绘制const tempCanvas = document.createElement('canvas');tempCanvas.width = size;tempCanvas.height = size;const tempCtx = tempCanvas.getContext('2d');if (!tempCtx) return;// 绘制马赛克块(同主画布逻辑)tempCtx.fillStyle = '#50B19D';tempCtx.strokeStyle = 'rgba(255, 255, 255, 0.2)';const cellSize = 10;for (let y = 0; y < size; y += cellSize) {for (let x = 0; x < size; x += cellSize) {tempCtx.fillRect(x, y, cellSize, cellSize);tempCtx.strokeRect(x, y, cellSize, cellSize);}}// 绘制外边框和角落标记tempCtx.strokeStyle = 'rgba(80, 177, 157, 0.8)';tempCtx.lineWidth = 2;tempCtx.strokeRect(0, 0, size, size);const cornerSize = 8;tempCtx.fillRect(0, 0, cornerSize, cornerSize);// ...其他三个角落// 绘制到截图画布ctx.drawImage(tempCanvas, position.x - size / 2, position.y - size / 2, size, size);
};

总结

马赛克功能的实现核心是:

  1. 通过临时画布生成块状图案,确保视觉效果一致;
  2. 采用百分比坐标存储位置,适配不同尺寸的画布;
  3. 支持实时编辑(移动、调整尺寸)和网络同步,满足协作需求;
  4. 截图时单独处理绘制逻辑,确保导出内容完整。

 

http://www.xdnf.cn/news/1208701.html

相关文章:

  • P1098 [NOIP 2007 提高组] 字符串的展开
  • python案例:基于python 神经网络cnn和LDA主题分析的旅游景点满意度分析
  • 小程序中事件对象的属性与方法
  • 微算法科技(NASDAQ:MLGO)应用区块链联邦学习(BlockFL)架构,实现数据的安全传输
  • Django自带的加密算法
  • 3D游戏引擎的“眼睛“:相机系统深度揭秘与技术实现
  • 14、distance_object_model_3d算子
  • 如何用命令行快速提取PPT中的所有图片?
  • 线程崩溃是否导致进程崩溃
  • 【嵌入式电机控制#18】有刷直流串级控制
  • MySQL图解索引篇
  • 大模型技术对部分岗位的影响
  • Apache Ignite 的分布式原子类型(Atomic Types)
  • 在CSS中,如果你想设置一个元素的高度(height)与其宽度(width)相匹配,但又希望宽度使用百分比来定义,你可以通过几种方式来实现。
  • 试用SAP BTP 02C:试用SAP HANA Schemas HDI Containers
  • VSCode使用Code Runner运行C/C++输出[Done] exited with code=0 in xxx seconds
  • SpringBoot整合RocketMQ(rocketmq-client.jar)
  • C++ AI流处理核心算法实战
  • MOGA(多目标遗传算法)求解 ZDT1 双目标优化问题
  • 沪铝本周想法
  • 智能编队重构职场生态:Agentic AI 协同时代来临
  • 基于Blazor进销存管理系统
  • 对College数据进行多模型预测(R语言)
  • thingsboard 自定义动作JS编程
  • 【高阶版】R语言空间分析、模拟预测与可视化高级应用
  • 【C++算法】82.BFS解决FloodFill算法_被围绕的区域
  • Java抽Oracle数据时编码问题
  • SpringBoot整合RocketMQ(阿里云ONS)
  • CentOS安装ffmpeg并转码视频为mp4
  • 【腾讯云】EdgeOne免费版实现网站加速与安全防护