当前位置: 首页 > 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采集…...

浅谈 React Hooks

React Hooks 是 React 16.8 引入的一组 API&#xff0c;用于在函数组件中使用 state 和其他 React 特性&#xff08;例如生命周期方法、context 等&#xff09;。Hooks 通过简洁的函数接口&#xff0c;解决了状态与 UI 的高度解耦&#xff0c;通过函数式编程范式实现更灵活 Rea…...

地震勘探——干扰波识别、井中地震时距曲线特点

目录 干扰波识别反射波地震勘探的干扰波 井中地震时距曲线特点 干扰波识别 有效波&#xff1a;可以用来解决所提出的地质任务的波&#xff1b;干扰波&#xff1a;所有妨碍辨认、追踪有效波的其他波。 地震勘探中&#xff0c;有效波和干扰波是相对的。例如&#xff0c;在反射波…...

python打卡day49

知识点回顾&#xff1a; 通道注意力模块复习空间注意力模块CBAM的定义 作业&#xff1a;尝试对今天的模型检查参数数目&#xff0c;并用tensorboard查看训练过程 import torch import torch.nn as nn# 定义通道注意力 class ChannelAttention(nn.Module):def __init__(self,…...

[ICLR 2022]How Much Can CLIP Benefit Vision-and-Language Tasks?

论文网址&#xff1a;pdf 英文是纯手打的&#xff01;论文原文的summarizing and paraphrasing。可能会出现难以避免的拼写错误和语法错误&#xff0c;若有发现欢迎评论指正&#xff01;文章偏向于笔记&#xff0c;谨慎食用 目录 1. 心得 2. 论文逐段精读 2.1. Abstract 2…...

Java-41 深入浅出 Spring - 声明式事务的支持 事务配置 XML模式 XML+注解模式

点一下关注吧&#xff01;&#xff01;&#xff01;非常感谢&#xff01;&#xff01;持续更新&#xff01;&#xff01;&#xff01; &#x1f680; AI篇持续更新中&#xff01;&#xff08;长期更新&#xff09; 目前2025年06月05日更新到&#xff1a; AI炼丹日志-28 - Aud…...

DIY|Mac 搭建 ESP-IDF 开发环境及编译小智 AI

前一阵子在百度 AI 开发者大会上&#xff0c;看到基于小智 AI DIY 玩具的演示&#xff0c;感觉有点意思&#xff0c;想着自己也来试试。 如果只是想烧录现成的固件&#xff0c;乐鑫官方除了提供了 Windows 版本的 Flash 下载工具 之外&#xff0c;还提供了基于网页版的 ESP LA…...

【单片机期末】单片机系统设计

主要内容&#xff1a;系统状态机&#xff0c;系统时基&#xff0c;系统需求分析&#xff0c;系统构建&#xff0c;系统状态流图 一、题目要求 二、绘制系统状态流图 题目&#xff1a;根据上述描述绘制系统状态流图&#xff0c;注明状态转移条件及方向。 三、利用定时器产生时…...

Python如何给视频添加音频和字幕

在Python中&#xff0c;给视频添加音频和字幕可以使用电影文件处理库MoviePy和字幕处理库Subtitles。下面将详细介绍如何使用这些库来实现视频的音频和字幕添加&#xff0c;包括必要的代码示例和详细解释。 环境准备 在开始之前&#xff0c;需要安装以下Python库&#xff1a;…...

力扣-35.搜索插入位置

题目描述 给定一个排序数组和一个目标值&#xff0c;在数组中找到目标值&#xff0c;并返回其索引。如果目标值不存在于数组中&#xff0c;返回它将会被按顺序插入的位置。 请必须使用时间复杂度为 O(log n) 的算法。 class Solution {public int searchInsert(int[] nums, …...

Python ROS2【机器人中间件框架】 简介

销量过万TEEIS德国护膝夏天用薄款 优惠券冠生园 百花蜂蜜428g 挤压瓶纯蜂蜜巨奇严选 鞋子除臭剂360ml 多芬身体磨砂膏280g健70%-75%酒精消毒棉片湿巾1418cm 80片/袋3袋大包清洁食品用消毒 优惠券AIMORNY52朵红玫瑰永生香皂花同城配送非鲜花七夕情人节生日礼物送女友 热卖妙洁棉…...