Hbuilder 上的水印相机实现方案 (vue3 + vite + hbuilder)

效果

思路

  • 通过 live-pusher 这个视频推流的组件来获取摄像头
  • 拿到视频的一帧图片之后,跳转到正常的 vue 页面,通过 canvas 来处理图片+水印

源码

live-pusher 这个组件必须是 nvue

至于什么是 nvue,看这个官方文档吧 https://uniapp.dcloud.net.cn/tutorial/nvue-outline.html

javascript 复制代码
// index.nvue
<template>
	<view class="pengke-camera" :style="{ width: windowWidth, height: windowHeight }">
		<!-- live-pusher 显示实时画面 -->
		<live-pusher
			id="livePusher"
			class="livePusher"
			mode="FHD"
			device-position="back"
			:muted="true"
			:enable-camera="true"
			:enable-mic="false"
			:auto-focus="true"
			:zoom="false"
			:style="{ width: windowWidth, height: windowHeight }"
		></live-pusher>

		<!-- 拍照按钮 -->
		<view class="menu">
			<cover-image class="menu-snapshot" @tap="snapshot" src="./icon/snapshot.png"></cover-image>
		</view>
	</view>
</template>

<script>
export default {
	data() {
		return {
			windowWidth: '',
			windowHeight: '',
			livePusher: null
		}
	},
	onLoad() {
		this.initCamera()
	},
	onReady() {
		const pages = getCurrentPages()
		const currentPage = pages[pages.length - 1]
		this.livePusher = uni.createLivePusherContext('livePusher', currentPage)
		this.livePusher.startPreview()
	},
	methods: {
		initCamera() {
			uni.getSystemInfo({
				success: res => {
					this.windowWidth = res.windowWidth
					this.windowHeight = res.windowHeight
				}
			})
		},
		snapshot() {
			this.livePusher.snapshot({
				success: e => {
					const path = e.message.tempImagePath
					uni.navigateTo({
						url: `/pages/camera/preview?img=${encodeURIComponent(path)}`
					})
				},
				fail: err => {
					console.error('拍照失败', err)
				}
			})
		}
	}
}
</script>

<style lang="less">
.pengke-camera {
	position: relative;

	.livePusher {
		position: absolute;
		top: 0;
		left: 0;
		width: 100%;
		height: 100%;
	}

	.menu {
		position: absolute;
		bottom: 60rpx;
		left: 0;
		right: 0;
		display: flex;
		justify-content: center;
		align-items: center;
		z-index: 10;
		pointer-events: auto;

		.menu-snapshot {
			width: 130rpx;
			height: 130rpx;
			background-color: #fff;
			border-radius: 65rpx;
			box-shadow: 0 6rpx 16rpx rgba(0, 0, 0, 0.2);
		}
	}
}

</style>

预览文件 preview.vue

javascript 复制代码
// preview.vue
<template>
  <view class="preview-page">
    <!-- 可见 canvas 显示图 + 水印 -->
    <canvas
      canvas-id="watermarkCanvas"
      id="watermarkCanvas"
      class="watermark-canvas"
      :style="{ width: screenWidth + 'px', height: screenHeight + 'px' }"
    ></canvas>

    <!-- 操作按钮 -->
    <view class="preview-actions">
      <button class="action-btn cancel-btn" @click="goBack">取消</button>
      <button class="action-btn confirm-btn" @click="exportImage">确认</button>
    </view>
  </view>
</template>

<script setup>
import { ref, nextTick, onMounted } from 'vue'
import { onLoad } from '@dcloudio/uni-app'
import message from '@/utils/message'

const imgUrl = ref('')
const canvasWidth = ref(0)
const canvasHeight = ref(0)
const currentTime = ref('')
const locationText = ref('正在获取位置信息...')

const screenWidth = uni.getSystemInfoSync().windowWidth
const screenHeight = uni.getSystemInfoSync().windowHeight

onLoad((options) => {
  if (options.img) {
    imgUrl.value = decodeURIComponent(options.img)
    initImage()
  }
  updateTime()
  updateLocation()
})

function updateTime() {
  const now = new Date()
  currentTime.value = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')} ${String(now.getHours()).padStart(2, '0')}:${String(now.getMinutes()).padStart(2, '0')}:${String(now.getSeconds()).padStart(2, '0')}`
}

function updateLocation() {
  uni.getLocation({
    type: 'wgs84',
    success: (res) => {
      locationText.value = `经度: ${res.longitude}  纬度: ${res.latitude}`
      drawCanvas()
    },
    fail: () => {
      locationText.value = '位置信息获取失败'
      drawCanvas()
    }
  })
}

function initImage() {
  uni.getImageInfo({
    src: imgUrl.value,
    success(res) {
      canvasWidth.value = res.width
      canvasHeight.value = res.height
      drawCanvas()
    },
    fail(err) {
      console.error('图片信息获取失败:', err)
    }
  })
}

// 绘制预览 canvas
function drawCanvas() {
  if (!imgUrl.value || !canvasWidth.value || !canvasHeight.value) return

  nextTick(() => {
    const ctx = uni.createCanvasContext('watermarkCanvas')

    // 绘制原图
    ctx.drawImage(imgUrl.value, 0, 0, screenWidth, screenHeight)

    // 设置水印样式
    ctx.setFontSize(16)
    ctx.setFillStyle('white')
    ctx.setTextAlign('left')

    ctx.fillText(currentTime.value, 20, screenHeight - 160)
    ctx.setFontSize(16)
    ctx.fillText(locationText.value, 20, screenHeight - 120)

    ctx.draw()
  })
}

// 点击确认导出
function exportImage() {
  // 显示加载提示
  uni.showLoading({
    title: '导出中...',
    mask: true  // 防止点击遮罩层关闭
  })

  uni.canvasToTempFilePath({
    canvasId: 'watermarkCanvas',
    destWidth: canvasWidth.value,
    destHeight: canvasHeight.value,
    success: (res) => {
      // 隐藏加载提示
      uni.hideLoading()
      console.log('导出成功:', res.tempFilePath)
      message.success(`导出成功! 文件路径为 ${res.tempFilePath}`)
      uni.previewImage({ urls: [res.tempFilePath] })
    },
    fail: (err) => {
      // 隐藏加载提示
      uni.hideLoading()
      console.error('导出失败:', err)
      uni.showToast({ title: '导出失败', icon: 'none' })
    }
  })
}


function goBack() {
  uni.navigateBack()
}
</script>

<style scoped lang="scss">
.preview-page {
  width: 100vw;
  height: 100vh;
  position: relative;
  background: #000;
  overflow: hidden;
}

.watermark-canvas {
  position: absolute;
  left: 0;
  top: 0;
  z-index: 1;
}

.preview-actions {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 60rpx;
  display: flex;
  justify-content: center;
  gap: 40rpx;
  z-index: 10;
}

.action-btn {
  padding: 0 40rpx;
  height: 80rpx;
  line-height: 80rpx;
  border-radius: 40rpx;
  font-size: 28rpx;
  background: #fff;
  color: #333;
  border: none;
  opacity: 0.9;
}

.cancel-btn {
  background: #eee;
}

.confirm-btn {
  background: #19be6b;
  color: #fff;
}
</style>
相关推荐
3DVisionary4 小时前
蓝光三维扫描技术:高效精密测量相机镜头底座注塑件
数码相机·3d·3d扫描·蓝光三维扫描 注塑件检测·精密测量 相机镜头底座·全尺寸检测 高效检测·有限元分析 极端环境测试
吴梓穆5 小时前
UE5 相机裁剪面
数码相机·ue5
布吉岛呀~6 小时前
相机模型--CMOS和CCD的区别
数码相机
AndrewHZ8 小时前
【图像处理基石】什么是去马赛克算法?
图像处理·数码相机·算法·计算机视觉·isp算法·手机影像·去马赛克算法
qq_282195318 小时前
Cypress EZ-USB CX3 适配输出imx586相机
单片机·嵌入式硬件·数码相机
云钥科技10 小时前
云钥科技柔性上料振动蜘蛛手工作原理及应用范围详细介绍
数码相机
东风西巷10 小时前
谷歌相机最新版:专业摄影,一键掌握
数码相机·智能手机·软件需求
强化学习与机器人控制仿真1 天前
ROS & ROS2 机器人深度相机激光雷达多传感器标定工具箱
人工智能·深度学习·数码相机·计算机视觉·机器人·自动驾驶·视觉检测
沉到海底去吧Go1 天前
【图片识别分类】如何快速识别照片中的水印文字,对图片进行关键字分类,快速整理水印相机拍摄图片,基于WPF和腾讯OCR的技术实现
数码相机·ocr·wpf