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

【uniapp】图片添加canvas水印

目录

  • 需求&背景
  • 实现
    • 地理位置
    • 添加水印
  • ios补充

需求&背景

需求:拍照后给图片添加水印, 水印包含经纬度、用户信息、公司logo等信息。
效果图:
在这里插入图片描述

方案:使用canvas添加水印。
具体实现:上传图片组件是项目里现有的,主要还是使用uni.chooseImage,这里不做赘述。
在上传图片组件中增加一个参数判断是否添加水印,在获取到图片、上传到后端之前对图片进行加工。

实现

在模板中添加canvas。
template:

<template><view class="md-upload"><!-- 其他组件上传图片逻辑 --><canvas v-if="waterMarkParams.display" canvas-id="waterMarkCanvas" :style="canvasStyle" /></view>
</template>

props:

props: {//其他props参数。。。waterMask: { // 是否添加水印type: Boolean,default: () => false}},

其他的一些数据

//这个放data
waterMarkParams: {display: false, // 控制 canvas 创建与销毁canvasWidth: 300, // 默认宽度canvasHeight: 225 // 默认高度
},
latitude: "",
longitude: "",
address: "",
orgFullName: "",
userName: "",
currentTime: ""//这个放computed
canvasStyle() {
return {position: 'fixed', // 移除到屏幕外left: '9999px',width: this.waterMarkParams.canvasWidth + 'px',height: this.waterMarkParams.canvasHeight + 'px'};
}

地理位置

这里有一个小问题,尝试了很多方法都没办法在success回调中去绘制canvas,退而其次选择在mounted里去获得地理位置,不知道有没有更好的写法欢迎指正。
注:如果要获取地理位置,type只能选择gcj02,而且这个type只适用于app,用h5开发调试会显示不出来。

mounted() {uni.getLocation({type: 'gcj02',geocode: true,success: (res) => {//经纬度this.longitude = res.longitude; this.latitude = res.latitude;//拼接地址this.address = res.address.province + res.address.city + res.address.district + res.address.street + res.address.streetNum + res.address.poiName;}})},

添加水印

入参的src是uni.chooseImage的success回调里返回的path

// 添加水印async getWaterMark(src) {//绘图方法startfunction fillText(context, x, y, content, font, fontStyle, textAlign) {context.save();context.font = font;context.fillStyle = fontStyle;context.textAlign = textAlign;context.fillText(content, x, y);}function fillCircle(context, x, y, r, fillStyle) {context.save();context.beginPath();context.arc(x, y, r, 0, 2 * Math.PI);context.fillStyle = fillStyle;context.fill();context.closePath();}function fillRect(context, x, y, width, height, fillStyle) {context.save();context.fillStyle = fillStyle;context.fillRect(x, y, width, height);}//绘图方法endlet that = this;that.waterMarkParams.display = true;this.currentTime = moment().format('YYYY-MM-DD HH:mm:ss'); //获取当前时间if(!this.orgFullName){ //获取运营商信息await this.getOrgFullName();}this.userName = uni.getStorageSync("userInfo").userTitle; //获取用户信息return new Promise((resolve, reject) => {// 获取图片信息,配置 canvas 尺寸uni.getImageInfo({src,success: (res) => {try {console.log("成功获取图片信息",res);// 修复部分手机(如红米9)手机屏幕比较窄拍摄出来的图片水印压缩着覆盖的问题that.waterMarkParams.canvasWidth = Math.max(res.width, 886);that.waterMarkParams.canvasHeight = res.height;// 等待 canvas 元素创建that.$nextTick(async () => {let context = uni.createCanvasContext('waterMarkCanvas', that);const { canvasWidth, canvasHeight } = that.waterMarkParams;// 绘制前清空画布context.clearRect(0, 0, canvasWidth, canvasHeight);// 将图片src放到cancas内,宽高必须为图片大小context.drawImage(src, 0, 0, canvasWidth, canvasHeight, canvasWidth, canvasHeight);const fangweiY = 320;// 保证水印能完整显示出来 x是画布宽度两倍 y是画布高度减去防伪码位置和字体大小const [x, y] = [canvasWidth / 2, canvasHeight - (fangweiY + 100)];// 坐标原点移动到画布左下角context.translate(0, canvasHeight);const icon_site = require("./site.png");const icon_camera = require("./camera.png");const icon_icon = require("./icon.png");context.drawImage(icon_site, 40,-360,30,46);fillText(context, 80, -326, this.siteName, '500 40px "Microsoft YaHei"', 'white', 'left');fillRect(context, 40, -290, 10, 260, "#FF0000");fillText(context, 70, -250, "时间:" + this.currentTime, '30px "Microsoft YaHei"', 'white', 'left');fillText(context, 70, -190, "运维商:" + this.orgFullName, '30px "Microsoft YaHei"', 'white', 'left');fillText(context, 70, -130, "经纬度:" + that.longitude + ", " + that.latitude, '30px "Microsoft YaHei"', 'white', 'left');if(that.address.length > 15){ //地址过长时截断显示fillText(context, 70, -70, "地址:" + that.address.slice(0,15), '30px "Microsoft YaHei"', 'white', 'left');fillText(context, 70, -40, "" + that.address.slice(15), '30px "Microsoft YaHei"', 'white', 'left');}else {fillText(context, 70, -70, "地址:" + that.address, '30px "Microsoft YaHei"', 'white', 'left');}//移动到右下角let textLength = that.userName.length * 30;context.translate(canvasWidth, 0);if(that.address.length > 15){context.drawImage(icon_icon, -180, -130,72,35);fillText(context, -40, -100, "光伏", '30px "Microsoft YaHei"', 'white', 'right');context.drawImage(icon_camera, -1 * textLength - 130, -70,38,34);fillText(context, -40, -40, that.userName + "  拍摄", '30px "Microsoft YaHei"', 'white', 'right');}else{context.drawImage(icon_icon, -180, -160,72,35);fillText(context, -40, -130, "光伏", '30px "Microsoft YaHei"', 'white', 'right');context.drawImage(icon_camera, -1 * textLength - 130, -100,38,34);fillText(context, -40, -70, that.userName + "  拍摄", '30px "Microsoft YaHei"', 'white', 'right');}// 一定要加上一个定时器否则进入到页面第一次可能会无法正常拍照,后几次才正常setTimeout(() => {// 本次绘画完重开开始绘画,并且在绘画完毕之后再保存图片,不然页面可能会出现白屏等情况context.draw(false, () => {console.log('!!!!!开始绘画', canvasWidth, canvasHeight);uni.canvasToTempFilePath({canvasId: 'waterMarkCanvas',fileType: 'jpg',width: canvasWidth,height: canvasHeight,destWidth: canvasWidth,destHeight: canvasHeight,success: ({ tempFilePath }) => {console.log('绘制成功');that.waterMarkParams.display = false;resolve(tempFilePath);},fail: (err) => {reject(err);console.log(err);}},that);});}, 1000);console.log("完成绘制");});} catch (error) {console.log(error);}},fail: (err) => {reject(err);console.log(err);}});});},

最后用这个方法返回的url替换原本上传时的url,就是添加了水印的图片

let waterUrl = await this.getWaterMark(lists[i].url);
lists[i].filePath = waterUrl;

ios补充

如果ios出现了图片空白,把方法里的定时器时间加长(我从1000加到了2500),uni.chooseImage也选择压缩不要选择原图

相关文章:

【uniapp】图片添加canvas水印

目录 需求&背景实现地理位置添加水印 ios补充 需求&背景 需求&#xff1a;拍照后给图片添加水印, 水印包含经纬度、用户信息、公司logo等信息。 效果图&#xff1a; 方案&#xff1a;使用canvas添加水印。 具体实现&#xff1a;上传图片组件是项目里现有的&#xff…...

ElementUI 级联选择器el-cascader启用选择任意一级选项,选中后关闭下拉框

1、启用选择任意一级选项 在 el-cascader 标签上加上配置项&#xff1a; :props"{ checkStrictly: true }"例如&#xff1a; <el-cascaderref"selectedArrRef"v-model"selectedArr":options"optionsList":props"{ checkStri…...

【音视频】ffplay常用命令

一、 ffplay常用命令 -x width&#xff1a;强制显示宽度-y height&#xff1a;强制显示高度 强制以 640*360的宽高显示 ffplay 2.mp4 -x 640 -y 360 效果如下 -fs 全屏显示 ffplay -fs 2.mp4效果如下&#xff1a; -an 禁用音频&#xff08;不播放声音&#xff09;-vn 禁…...

5人3小时复刻Manus?开源OpenManus项目全解剖,我的DeepSeek股票报告这样诞生

大家好,我是大 F,深耕AI算法十余年,互联网大厂技术岗。分享AI算法干货、技术心得。 更多文章可关注《大模型理论和实战》、《DeepSeek技术解析和实战》,一起探索技术的无限可能! OpenManus是什么 1. 项目背景 OpenManus 是由 MetaGPT 核心团队仅用 3 小时复刻而成的开源…...

【Python运维】用Python自动化AWS资源管理:利用boto3实现高效管理S3桶和EC2实例

《Python OpenCV从菜鸟到高手》带你进入图像处理与计算机视觉的大门! 解锁Python编程的无限可能:《奇妙的Python》带你漫游代码世界 随着云计算的普及,AWS(Amazon Web Services)已经成为许多企业和开发者首选的云平台。为了提高工作效率,自动化管理AWS资源成为了一个热…...

django各种mixin用法

在 Django 中,Mixin 是一种用于扩展类功能的设计模式。通过 Mixin,可以在不修改原有类的情况下,为其添加新的方法或属性。Django 中的 Mixin 广泛应用于视图(View)、表单(Form)、模型(Model)等组件中。以下是 Django 中常见 Mixin 的用法和示例: 一、视图(View)中的…...

Java 大视界 -- Java 大数据在智能教育考试评估与学情分析中的应用(112)

&#x1f496;亲爱的朋友们&#xff0c;热烈欢迎来到 青云交的博客&#xff01;能与诸位在此相逢&#xff0c;我倍感荣幸。在这飞速更迭的时代&#xff0c;我们都渴望一方心灵净土&#xff0c;而 我的博客 正是这样温暖的所在。这里为你呈上趣味与实用兼具的知识&#xff0c;也…...

Manus AI : Agent 元年开启.pdf

Manus AI : Agent 元年开启.pdf 是由华泰证券出品的一份调研报告&#xff0c;共计23页。报告详细介绍了Manus AI 及 Agent&#xff0c;主要包括Manus AI 的功能、优势、技术能力&#xff0c;Agent 的概念、架构、应用场景&#xff0c;以及 AI Agent 的类型和相关案例&#xff0…...

【计算机网络】计算机网络的性能指标——时延、时延带宽积、往返时延、信道利用率

计算机网络的性能指标 导读 大家好&#xff0c;很高兴又和大家见面啦&#xff01;&#xff01;&#xff01; 在上一篇内容中我们介绍了计算机网络的三个性能指标——速率、带宽和吞吐量。用大白话来说就是&#xff1a;网速、最高网速和实时网速。 相信大家看到这三个词应该就…...

FreeRTOS第15篇:FreeRTOS链表实现细节03_List_t与ListItem_t的奥秘

文/指尖动听知识库-星愿 文章为付费内容,商业行为,禁止私自转载及抄袭,违者必究!!! 文章专栏:深入FreeRTOS内核:从原理到实战的嵌入式开发指南 1 FreeRTOS列表的核心数据结构 FreeRTOS的列表实现由两个关键结构体组成:List_t(列表)和ListItem_t(列表项)。它们共同…...

git 添加额外的远程仓库 URL

要使用 git branch -a 查看 net-next 远程仓库中的所有分支&#xff0c;请按照以下步骤操作&#xff1a; 步骤 1: 确保已添加 net-next 远程仓库 如果尚未添加 net-next 远程仓库&#xff0c;请运行以下命令&#xff1a; git remote add net-next git://git.kernel.org/pub/s…...

不同类型光谱相机的技术差异比较

一、波段数量与连续性 ‌多光谱相机‌ 波段数&#xff1a;通常4-9个离散波段&#xff0c;光谱范围集中于400-1000nm‌。 数据特征&#xff1a;光谱呈阶梯状&#xff0c;无法连续覆盖&#xff0c;适用于中等精度需求场景&#xff08;如植被分类&#xff09;‌。 ‌高光谱相机…...

Swift系列01-Swift语言基本原理与设计哲学

本文将深入探讨Swift的核心原理、设计理念以及与Objective-C的对比 1. Swift与Objective-C的架构差异分析 Swift和Objective-C尽管可以无缝协作&#xff0c;但它们的架构设计存在本质差异。 1.1语言范式 Objective-C是一种动态语言&#xff0c;建立在C语言之上并添加了Smal…...

《OpenCV》——dlib(人脸应用实例)

文章目录 dlib库dlib库——人脸应用实例——表情识别dlib库——人脸应用实例——疲劳检测 dlib库 dlib库的基础用法介绍可以参考这篇文章&#xff1a;https://blog.csdn.net/lou0720/article/details/145968062?spm1011.2415.3001.5331&#xff0c;故此这篇文章只介绍dlib的人…...

以太网通讯

接口开发笔记-WebApi-CSDN博客 以太网常用通讯协议 1、modbus tcp using EasyModbus; using System;class Program {static void Main(string[] args){// 创建Modbus客户端实例ModbusClient modbusClient new ModbusClient("192.168.1.100"); // IP地址modbusCli…...

UDP学习笔记(一)为什么UDP需要先将数据转换为字节数组

UDP 发送数据时需要先将数据转换为字节数组再发送&#xff0c;主要是因为计算机网络传输的最基本单位是“字节”&#xff08;Byte&#xff09;。让我们从以下几个方面来深入理解这个设计选择&#xff1a; 1. 计算机网络只能传输“字节” 在网络通信中&#xff0c;无论是 TCP 还…...

数据分析/数据科学常见SQL题目:连续登录用户、留存率、最大观看人数

文章目录 1. SQL的执行顺序是什么&#xff1f;on和join谁先执行&#xff0c;为什么&#xff1f;on和where的区别&#xff1f;2. 已知表user,字段id, date&#xff0c;求新用户的次日留存率3. 已知表user&#xff0c;字段id&#xff0c;date&#xff0c;求每个日期新用户的次日留…...

【Conda】Windows安装conda/Anaconda环境

安装conda并配置powershell 访问该网址&#xff0c;下载安装即可&#xff1a; Anaconda下载 安装完成后&#xff0c;打开Anaconda&#xff0c;并访问Powershell Prompt 弹出Windows Terminal&#xff0c;并正常进入Conda 【非必须】如果不是通过Windows Terminal打开&#x…...

olmOCR:高效精准的 PDF 文本提取工具

在日常的工作和学习中&#xff0c;是否经常被 PDF 文本提取问题困扰&#xff1f;例如&#xff1a; 想从学术论文 PDF 中提取关键信息&#xff0c;却发现传统 OCR 工具识别不准确或文本格式混乱&#xff1f;需要快速提取商务合同 PDF 中的条款内容&#xff0c;却因工具不给力而…...

数字投屏叫号器-发射端python窗口定制

窗口 本系列前章介绍&#xff0c;叫号器的显示端&#xff0c;完成了视频音频的形成和传输的介绍。本章节开始定制小窗口。 最终实现&#xff0c;处于桌面最前端&#xff0c;发送指令&#xff0c;集合前篇即可完成&#xff1a; 处理本地text.txt更新&#xff0c;随之被rtsp采集…...

[具身智能-189]:ROS2的Node通信机制,为硬件的仿真平台与模型算法的分离以及他们之间标准化的通信提供了保障,在嵌入式系统,特别是具身智能开发中,解决“软硬耦合”这一顽疾。

ROS 2 的节点通信机制&#xff0c;本质上就是为了解决“软硬耦合”这一顽疾而生的。 它通过去中心化的架构和标准化的中间件&#xff08;DDS&#xff09;&#xff0c;让仿真平台&#xff08;如 Gazebo、Isaac Sim&#xff09;和模型算法&#xff08;如导航、感知&#xff09;能…...

实时行情系统设计:从协议选择到高可用架构,再到数据源选型

一、核心问题及解决方案&#xff08;按踩坑频率排序&#xff09; 问题 1&#xff1a;误删他人持有锁——最基础也最易犯的漏洞 成因&#xff1a;释放锁时未做身份校验&#xff0c;直接执行 DEL 命令删除键。典型场景&#xff1a;服务 A 持有锁后&#xff0c;业务逻辑耗时超过锁…...

从Prompt到Agent:收藏这份LLM应用落地演进指南,小白程序员必备!

本文介绍了LLM应用落地的演进过程&#xff0c;从最初的Prompt工程阶段&#xff0c;到Chain编排阶段&#xff0c;再到最新的Agent阶段。文章详细阐述了每个阶段的原理、优缺点及应用实例&#xff0c;并提供了基于Golang的Agent实现示例。通过学习本文&#xff0c;读者可以了解LL…...

从零开始用Typora写技术文档:完整配置指南与高效排版秘籍

从零开始用Typora打造专业技术文档&#xff1a;配置、排版与效率全攻略 在技术写作领域&#xff0c;文档的呈现质量往往直接影响知识传递的效果。Typora作为一款轻量级Markdown编辑器&#xff0c;凭借其即时渲染、简洁界面和强大的扩展功能&#xff0c;已成为众多开发者和技术作…...

cool-admin(midway版)前端错误处理:全局错误边界与日志上报

cool-admin(midway版)前端错误处理&#xff1a;全局错误边界与日志上报 【免费下载链接】cool-admin-midway &#x1f525; cool-admin(midway版)一个很酷的后台权限管理框架&#xff0c;模块化、插件化、CRUD极速开发&#xff0c;永久开源免费&#xff0c;基于midway.js 3.x、…...

不止基础管理!国产 CRM 软件如何用数据分析赋能客户与销售工作

引言2026年国内企业数字化转型已进入深水区&#xff0c;CRM早已脱离了单纯的客户信息台账工具属性&#xff0c;数据分析能力成为衡量CRM产品价值的核心指标——从线索获客成本核算到跟单转化率优化&#xff0c;从客户复购价值挖掘到全链路风险管控&#xff0c;高质量的数据分析…...

让ai当你的git导师:用快马开发智能github问答与代码生成助手

最近在尝试学习GitHub的使用时&#xff0c;发现很多操作命令记不住&#xff0c;尤其是遇到合并冲突或者需要回退版本的时候&#xff0c;总是要反复查文档。于是我想&#xff0c;能不能做一个AI助手来帮忙&#xff1f;经过在InsCode(快马)平台上的一番折腾&#xff0c;还真做出了…...

壁仞科技上市后首次年报:2025年营收10亿 经调整亏损8.7亿

雷递网 雷建平 3月30日上海壁仞科技股份有限公司&#xff08;股份代号&#xff1a;6082&#xff09;今日发布截至2025年12月31日的财报。财报显示&#xff0c;壁仞科技2025年营收为10.35亿元&#xff0c;较上年同期的3.37亿元增长207.2%。壁仞科技2025年毛利为5.57亿元&#xf…...

深入解析 iOS 上 fixed 底栏与滚动容器的手势冲突:从 H5 修复到原生根治

在移动端 H5 开发中,我们时常遇到这样的场景:页面底部有一个固定定位(position: fixed)的按钮栏或底栏,上方是一个可滚动的长列表。在 iOS 设备上,当用户尝试从底部 fixed 区域起手向上滑动时,列表却纹丝不动,仿佛被“粘”住了。这个现象不是偶发 bug,而是 iOS 对 fix…...

springboot+vue基于web的学生宿舍预订分配管理系统的设计与实现

目录同行可拿货,招校园代理 ,本人源头供货商系统功能模块划分技术实现要点扩展性考虑项目技术支持源码获取详细视频演示 &#xff1a;文章底部获取博主联系方式&#xff01;同行可合作同行可拿货,招校园代理 ,本人源头供货商 系统功能模块划分 后端&#xff08;SpringBoot&am…...