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

小程序实现摄像头拍照 + 水印绘制

文章标题

  • 01 功能说明
  • 02 使用方式 & 效果图
    • 2.1 基础用法
    • 2.2 拍照 + 底部定点水印 + 预览
    • 2.3 拍照 + 整体背景水印 + 预览
  • 03 全部代码
    • 3.1 页面布局 html
    • 3.2 业务核心 js
    • 3.3 基础样式 css

01 功能说明

需求:小程序端需要调用前置摄像头进行拍照,并且将拍好的照片添加水印后返回。下面的代码支持 底部定点水印整体背景水印

技术栈uniappvue

迭代:后期还可以继续 扩展多方位的定点水印 和 支持绘制多句话的背景水印。

02 使用方式 & 效果图

文件路径:"@/components/CameraSnap.vue"

2.1 基础用法

// (1)仅拍照 + 预览
<CameraSnap />// (4)仅给图片添加水印 + 预览
<CameraSnap photoSrc="xxx" :mark-list="['今天天气很好','2023-01-01 00:00:00']" 
/>

2.2 拍照 + 底部定点水印 + 预览

使用方式:

<CameraSnap :mark-list="['今天天气很好','2023-01-01 00:00:00']" textSize="24" useTextMask 
/>

效果如下:
拍照_底部定点水印_预览

2.3 拍照 + 整体背景水印 + 预览

使用方式:

// 若不设置 markType,则默认为 底部定点水印
// 目前背景水印只会取 markList 的第一项来绘制背景水印
<CameraSnap markType="background" :mark-list="['今天天气很好']" textColor="rgba(255,255,255,0.5)"  
/>

效果如下:
拍照_整体背景水印_预览

03 全部代码

uni-app camera 的官方文档:https://uniapp.dcloud.net.cn/component/camera.html#camera

3.1 页面布局 html

<template><view class="camera-wrapper"><!-- 拍照 --><template v-if="!snapSrc"><!-- 相机 --><camera device-position="front" flash="off" @error="handleError" class="image-size"><view class="photo-btn" @click="handleTakePhoto">拍照</view></camera><!-- 水印 --><canvas canvas-id="photoMarkCanvas" id="photoMarkCanvas" class="mark-canvas":style="{width: canvasWidth+'px',height: canvasHeight+'px'}" /></template><!-- 预览 --><template v-else><view class="re-photo-btn" @click="handleRephotograph">重拍</view><image class="image-size" :src="snapSrc"></image></template></view>
</template>

3.2 业务核心 js

<script>export default {name: 'CameraSnap',props: {// 照片地址(若传递了照片地址,则默认为预览该照片或添加水印后预览)photoSrc: {type: String,default: ""},// 水印类型markType: {type: String,default: "fixed", // 定点水印 fixed,背景水印 background},// 水印文本列表(支持多行)markList: {type: Array,default: () => []},textColor: {type: String,default: "#FFFFFF"},textSize: {type: Number,default: 32},// 定点水印的遮罩(为了让水印更清楚)useTextMask: {type: Boolean,default: true}},data() {return {snapSrc: "",canvasWidth: "",canvasHeight: "",}},watch: {photoSrc: {handler: function(newValue, oldValue) {if (newValue) {this.getWaterMarkImgPath(newValue)}},immediate: true}},methods: {handleTakePhoto() {const ctx = uni.createCameraContext();ctx.takePhoto({quality: 'high',success: (res) => {const imgPath = res.tempImagePathif (this.markList.length) {this.getWaterMarkImgPath(imgPath)} else {this.snapSrc = imgPath;console.log("default", this.snapSrc)this.$emit('complete', imgPath)}}});},handleRephotograph() {this.snapSrc = ""},handleError(err) {uni.showModal({title: '警告',content: '若不授权使用摄像头,将无法使用拍照功能!',cancelText: '不授权',confirmText: '授权',success: (res) => {if (res.confirm) {// 允许打开授权页面,调起客户端小程序设置界面,返回用户设置的操作结果uni.openSetting({success: (res) => {res.authSetting = { "scope.camera": true }},})} else if (res.cancel) {// 拒绝打开授权页面uni.showToast({ title: '您已拒绝授权,无法进行拍照', icon: 'error', duration: 2500 });}}})},setWaterMark(context, image) {const listLength = this.markList?.lengthswitch (this.markType) {case 'fixed':const spacing = 4 // 行间距const paddingTopBottom = 20 // 整体上下间距// 默认每行的高度 = 字体高度 + 向下间隔const lineHeight = this.textSize + spacingconst allLineHeight = lineHeight * listLength// 矩形遮罩的 Y 坐标const maskRectY = image.height - allLineHeight// 绘制遮罩层if (this.useTextMask) {context.setFillStyle('rgba(0,0,0,0.4)');context.fillRect(0, maskRectY - paddingTopBottom, image.width, allLineHeight + paddingTopBottom)}// 文本与 x 轴之间的间隔const textX = 10// 文本一行的最大宽度(减去 20 是为了一行的左右留间隙)const maxWidth = image.width - 20context.setFillStyle(this.textColor)context.setFontSize(this.textSize)this.markList.forEach((item, index) => {// 因为文本的 Y 坐标是指文本基线的 Y 轴坐标,所以要获取文本顶部的 Y 坐标const textY = maskRectY - paddingTopBottom / 2 + this.textSize + lineHeight * indexcontext.fillText(item, textX, textY, maxWidth);})break;case 'background':context.translate(0, 0);context.rotate(30 * Math.PI / 180);context.setFillStyle(this.textColor)context.setFontSize(this.textSize)const colSize = parseInt(image.height / 6)const rowSize = parseInt(image.width / 2)let x = -rowSizelet y = -colSize// 循环绘制 5 行 6 列 的文字for (let i = 1; i <= 6; i++) {for (let j = 1; j <= 5; j++) {context.fillText(this.markList[0], x, y, rowSize)// 每个水印间隔 20x += rowSize + 20}y += colSizex = -rowSize}break;}context.save();},getWaterMarkImgPath(src) {const _this = thisuni.getImageInfo({src,success: (image) => {this.canvasWidth = image.widththis.canvasHeight = image.heightconst context = uni.createCanvasContext("photoMarkCanvas", this)context.drawImage(src, 0, 0, image.width, image.height)// 设置水印this.setWaterMark(context, image)// 若还需其他操作,可在操作之后叠加保存:context.restore()// 将画布上的图保存为图片context.draw(false, () => {setTimeout(() => {uni.canvasToTempFilePath({destWidth: image.width,destHeight: image.height,canvasId: 'photoMarkCanvas',fileType: 'jpg',success: function(res) {_this.snapSrc = res.tempFilePathconsole.log("water", _this.snapSrc)_this.$emit('complete', _this.snapSrc)}},_this);}, 200)});}})},}}
</script>

3.3 基础样式 css

<style lang="scss" scoped>.camera-wrapper {position: relative;}.mark-canvas {position: absolute;/* 将画布移出展示区域 */top: -200vh;left: -200vw;}.image-size {width: 100%;height: 100vh;}.photo-btn {position: absolute;bottom: 100rpx;left: 50%;transform: translateX(-50%);width: 140rpx;height: 140rpx;line-height: 140rpx;text-align: center;background-color: #000000;border-radius: 50%;border: 10rpx solid #ffffff;color: #fff;}.re-photo-btn {position: absolute;bottom: 80rpx;right: 40rpx;padding: 10rpx 20rpx;background-color: #000000;border-radius: 10%;border: 6rpx solid #ffffff;color: #fff}
</style>

相关文章:

小程序实现摄像头拍照 + 水印绘制

文章标题 01 功能说明02 使用方式 & 效果图2.1 基础用法2.2 拍照 底部定点水印 预览2.3 拍照 整体背景水印 预览 03 全部代码3.1 页面布局 html3.2 业务核心 js3.3 基础样式 css 01 功能说明 需求&#xff1a;小程序端需要调用前置摄像头进行拍照&#xff0c;并且将拍…...

SpringMVC:从入门到精通,7篇系列篇带你全面掌握--三.使用SpringMVC完成增删改查

&#x1f973;&#x1f973;Welcome Huihuis Code World ! !&#x1f973;&#x1f973; 接下来看看由辉辉所写的关于SpringMVC的相关操作吧 目录 &#x1f973;&#x1f973;Welcome Huihuis Code World ! !&#x1f973;&#x1f973; 效果演示 一.导入项目的相关依赖 二.…...

ABAP GN_DELIVERY_CREATE 报错 VL 561

GN_DELIVERY_CREATE 去创建内向交货单的时候。 报错 VL 561 Essential transfer parameters are missing in record 表示一些必输字段没输入 诸如一些&#xff0c;物料号。单位。等一些字段 输入之后即可 DATA: ls_return TYPE bapireturn.DATA: lt_return TYPE STANDARD T…...

AWS-数据库迁移工具DMS-场景:单账号跨区域迁移RDS for Mysql

参考文档&#xff1a; 分为几个环节&#xff1a; 要使用 AWS DMS 迁移至 Amazon RDS 数据库实例&#xff1a; 1.创建复制实例 有坑内存必须8g或者以上&#xff0c;我测试空库 都提示内存不足 2.创建目标和源终端节点 目标空库也得自己创建哈 3.刷新源终端节点架构 4.创建迁…...

【漏洞复现】E-office文件包含漏洞

漏洞描述 Weaver E-Office是中国泛微科技(Weaver)公司的一个协同办公系统。泛微 E-Office 是一款标准化的协同 OA 办公软件,实行通用化产品设计,充分贴合企业管理需求,本着简洁易用、高效智能的原则,为企业快速打造移动化、无纸化、数字化的办公平台。 该漏洞是由于存在…...

Linux 系统常用命令总结

目录 提示一、文件和目录操作二、文件查看和编辑三、文件权限管理四、文件压缩和解压缩五、查找文件六、系统信息和状态七、用户和权限管理八、网络相关操作九、包管理十、进程管理十一、时间和日期十二、系统关机和重启十三、文件传输十四、其他常用命令 提示 [ ]&#xff1a…...

【数据结构】树的基础入门

文章目录 什么是树树的常见术语树的表示树的应用 什么是树 相信大家刚学数据结构的时候最先接触的就是顺序表,栈,队列等线性结构. 而树则是一种非线性存储结构,存储的是具有“一对多”关系的数据元素的集合 非线性 体现在它是由n个有限结点(可以是零个结点)组成一个具有层次关…...

【多线程】Thread的常用方法

Thread的常用方法 1.构造器 Thread提供的常见构造器说明public Thread(String name)可以为当前线程指定名称public Thread(Runnable target)封装Runnable对象成为线程对象public Thread(Runnable target,String name)封装Runnable对象成为线程对象&#xff0c;并指定线程名称…...

windows 下docker安装宝塔镜像 宝塔docker获取镜像

1. docker 安装宝塔 打开链接&#xff1a;https://www.docker.com/get-started&#xff0c;找对应的版本下载docker&#xff0c;安装docker打开百度云盘&#xff1a;链接&#xff1a;https://pan.baidu.com/s/1DGIjpKkNDAmy4roaKGLA_w 提取码&#xff1a;u8bi 2. 设置镜像 点…...

【FusionInsight 迁移】HBase从C50迁移到6.5.1(01)迁移概述

【FusionInsight 迁移】HBase从C50迁移到6.5.1&#xff08;01&#xff09;迁移概述 HBase从C50迁移到6.5.1&#xff08;01&#xff09;迁移概述迁移范围迁移前的准备HDFS文件检查确认HBase迁移目录确保数据落盘停止老集群HBase服务停止新集群HBase服务 HBase从C50迁移到6.5.1&a…...

ETCD集群搭建(实践可用)

概述 etcd 是兼具一致性和高可用性的键值数据库&#xff0c;可以作为保存 Kubernetes 所有集群数据的后台数据库。 - 官方网址&#xff1a; Documentation versions | etcd 准备cfssl证书生成工具 cfssl是一个开源的证书管理工具&#xff0c;使用json文件生成证书. 在任意一…...

基于stm32f103rct6的呼吸灯实现

一、PWM 我们可以通过改变灯的有效电压占空比来实现呼吸灯效果。其中我们要用到PWM&#xff08;脉宽调制&#xff09;&#xff0c;通过pwm我们可以来改变高电平的占空比 占空比&#xff1a;在一个周期中&#xff0c;高电平所占整个周期的百分比 具体如图&#xff1a; 当我们用…...

关于火绒邮件监控引起的扫描任意IP会有25和110端口反馈

之前测试过公司的外网IP&#xff0c;因为之前一直很注意对外映射的端口&#xff0c;都限制了可以访问的IP地址和端口&#xff0c;所以之前扫描的时候是一个端口都扫描不出来的。最近闲的无事&#xff0c;想着再扫描试试&#xff0c;结果发现居然开放了25和110端口&#xff0c;我…...

物联网应用中蓝牙模块怎么选?_蓝牙模块厂家

在蓝牙模块选型前期&#xff0c;一定要了解应用场景以及需要实现的功能&#xff08;应用框图&#xff09;&#xff0c;以及功能实现过程中所能提供调用的接口&#xff08;主从设备&#xff0c;功能&#xff09;&#xff0c;考虑模块供电&#xff0c;尺寸&#xff0c;接收灵敏度…...

Mysql远程登录报错:Host ‘192.168.137.1‘ is not allowed to connect to this MySQL server

连接失败是因为数据库没有对指定的ip的服务器地址的连接进行授权&#xff0c;许哦一需要先进行授权。 1. 改表 先登录登录数据库&#xff1a;mysql -u root -p mysql>use mysql;mysql>update user set host % where user root;mysql>FLUSH PRIVILEGES; 2.授权 …...

vue去掉循环数组中的最后一组的某个样式style/class

vue去掉循环数组中的最后一组的某个样式style/class 需求&#xff1a;要实现这样的排列 现状 发现&#xff0c;最后一个格子并没有跟下面绿色线对齐。 最后发现 是因为 每个格子都给了 margin-right&#xff1a;36px&#xff0c;影响到了最后一个格子 所以要 将最后一个格子的…...

Vue2面试题100问

Vue2面试题100问 Vue2面试题100问1.简述一下你对Vue的理解2.声明式和命令式编程概念的理解3.Vue 有哪些基本特征4.vue之防止页面加载时看到花括号解决方案有哪几种&#xff1f;5.Vue中v-for与v-if能否一起使用&#xff1f;6.vue中v-if与v-show的区别以及使用场景7.v-on可以监听…...

开机启动应用

windows 建立快捷方式 winr 输入shell:startup 将快捷方式复制进来 就可以了 如果你有ccleaner&#xff0c;也可以看到...

RK3588平台产测之ArmSoM-W3 DDR压力测试

1. 简介 RK3588从入门到精通 ArmSoM团队在产品量产之前都会对产品做几次专业化的功能测试以及性能压力测试&#xff0c;以此来保证产品的质量以及稳定性 优秀的产品都要进行多次全方位的功能测试以及性能压力测试才能够经得起市场的检验 2. 环境介绍 硬件环境&#xff1a; …...

springboot初试elasticsearch

引入依赖 elasticsearch的依赖版本与你elasticsearch要一致 <dependency><groupId>org.elasticsearch.client</groupId><artifactId>elasticsearch-rest-high-level-client</artifactId> </dependency> 索引库的操作 创建索引库 impo…...

MPNet:旋转机械轻量化故障诊断模型详解python代码复现

目录 一、问题背景与挑战 二、MPNet核心架构 2.1 多分支特征融合模块(MBFM) 2.2 残差注意力金字塔模块(RAPM) 2.2.1 空间金字塔注意力(SPA) 2.2.2 金字塔残差块(PRBlock) 2.3 分类器设计 三、关键技术突破 3.1 多尺度特征融合 3.2 轻量化设计策略 3.3 抗噪声…...

C++初阶-list的底层

目录 1.std::list实现的所有代码 2.list的简单介绍 2.1实现list的类 2.2_list_iterator的实现 2.2.1_list_iterator实现的原因和好处 2.2.2_list_iterator实现 2.3_list_node的实现 2.3.1. 避免递归的模板依赖 2.3.2. 内存布局一致性 2.3.3. 类型安全的替代方案 2.3.…...

Day131 | 灵神 | 回溯算法 | 子集型 子集

Day131 | 灵神 | 回溯算法 | 子集型 子集 78.子集 78. 子集 - 力扣&#xff08;LeetCode&#xff09; 思路&#xff1a; 笔者写过很多次这道题了&#xff0c;不想写题解了&#xff0c;大家看灵神讲解吧 回溯算法套路①子集型回溯【基础算法精讲 14】_哔哩哔哩_bilibili 完…...

条件运算符

C中的三目运算符&#xff08;也称条件运算符&#xff0c;英文&#xff1a;ternary operator&#xff09;是一种简洁的条件选择语句&#xff0c;语法如下&#xff1a; 条件表达式 ? 表达式1 : 表达式2• 如果“条件表达式”为true&#xff0c;则整个表达式的结果为“表达式1”…...

【2025年】解决Burpsuite抓不到https包的问题

环境&#xff1a;windows11 burpsuite:2025.5 在抓取https网站时&#xff0c;burpsuite抓取不到https数据包&#xff0c;只显示&#xff1a; 解决该问题只需如下三个步骤&#xff1a; 1、浏览器中访问 http://burp 2、下载 CA certificate 证书 3、在设置--隐私与安全--…...

2025 后端自学UNIAPP【项目实战:旅游项目】6、我的收藏页面

代码框架视图 1、先添加一个获取收藏景点的列表请求 【在文件my_api.js文件中添加】 // 引入公共的请求封装 import http from ./my_http.js// 登录接口&#xff08;适配服务端返回 Token&#xff09; export const login async (code, avatar) > {const res await http…...

OPENCV形态学基础之二腐蚀

一.腐蚀的原理 (图1) 数学表达式&#xff1a;dst(x,y) erode(src(x,y)) min(x,y)src(xx,yy) 腐蚀也是图像形态学的基本功能之一&#xff0c;腐蚀跟膨胀属于反向操作&#xff0c;膨胀是把图像图像变大&#xff0c;而腐蚀就是把图像变小。腐蚀后的图像变小变暗淡。 腐蚀…...

Hive 存储格式深度解析:从 TextFile 到 ORC,如何选对数据存储方案?

在大数据处理领域&#xff0c;Hive 作为 Hadoop 生态中重要的数据仓库工具&#xff0c;其存储格式的选择直接影响数据存储成本、查询效率和计算资源消耗。面对 TextFile、SequenceFile、Parquet、RCFile、ORC 等多种存储格式&#xff0c;很多开发者常常陷入选择困境。本文将从底…...

android RelativeLayout布局

<?xml version"1.0" encoding"utf-8"?> <RelativeLayout xmlns:android"http://schemas.android.com/apk/res/android"android:layout_width"match_parent"android:layout_height"match_parent"android:gravity&…...

系统掌握PyTorch:图解张量、Autograd、DataLoader、nn.Module与实战模型

本文较长&#xff0c;建议点赞收藏&#xff0c;以免遗失。更多AI大模型应用开发学习视频及资料&#xff0c;尽在聚客AI学院。 本文通过代码驱动的方式&#xff0c;系统讲解PyTorch核心概念和实战技巧&#xff0c;涵盖张量操作、自动微分、数据加载、模型构建和训练全流程&#…...