uni-app实现水印相机(仅小程序端)

效果展示

点击“拍照”,拍照成功后在底部生成已经添加上水印的图片,点击图片查看图片

在这里插入图片描述

在这里插入图片描述

结构与样式

结构

<template>
	<view>
		<camera :device-position="device" :flash="flash" @error="error" :style="{ width: '100%',position: 'relative', height: getHeight + 'px' }">
			<cover-view class="topBox">
                <!-- 时间 -->
				<cover-view class="topItem">{{nowTime2}}</cover-view>
                <!-- 日期 -->
				<cover-view class="topItem">{{nowTime}}</cover-view>
                <!-- 地址 -->
				<cover-view class="topItem">{{address}}</cover-view>
			</cover-view>

            <!-- 旋转摄像头 -->
			<cover-image @click="xzBtn" class="xzImg" src="https://cdn.zhoukaiwen.com/xz.png"></cover-image>
            <!-- 打开/关闭 闪关灯 -->
			<cover-image @click="sgdBtn" class="sgdImg" :src="sgdUrl"></cover-image>
            <!-- 拍照 -->
			<cover-view class="cameraBtn" @click="takePhoto">
				<cover-view class="cameraBtn2"></cover-view>
			</cover-view>
          
            <!-- 底部预览生成图片 -->
			<cover-view class="bottomBg" v-if="imgList.length > 0">
				<cover-view>
					<cover-view @click="ViewImage(index)" class="imgBox" v-for="(item,index) in imgList" :key="index">
						<cover-image class="imgItem" :src="item.src" mode="aspectFill"></cover-image>
						<cover-view class="cu-tag" @tap.stop="DelImg" :data-index="index">
							<cover-image class="iconClose" src="https://cdn.zhoukaiwen.com/icon_close.png" mode="aspectFill"></cover-image>
						</cover-view>
					</cover-view>
				</cover-view>
			</cover-view>
		</camera>

        <!-- canvas元素,利用它的功能,实现添加水印 -->
		<view style="position: absolute;top: -999999px;">
          <view>
            <canvas :style="{ width: w, height: h }" canvas-id="firstCanvas"></canvas>
          </view>
		</view>

	</view>
</template>

为了让自定义的按钮如:旋转镜头、打开/关闭 闪光灯、拍摄等能够位于camera 组件上层,所以容器都采用 cover-viewcover-image

样式

	.topBox {
		width: 750rpx;
		box-sizing: border-box;
		padding: 30rpx;
		color: #EEEEEE;
		font-size: 34rpx;

		.topItem {
			width: 100%;
			white-space: pre-wrap;
			margin-bottom: 15rpx;
		}
	}

	.cameraBtn {
		width: 120rpx;
		height: 120rpx;
		line-height: 120rpx;
		border: 6rpx #FFFFFF solid;
		border-radius: 50%;
		padding: 8rpx;
		position: absolute;
		left: calc(50% - 60rpx);
		bottom: 210rpx;
	}

	.cameraBtn2 {
		width: 100%;
		height: 100%;
		border-radius: 50%;
		background-color: #FFFFFF;
		text-align: center;
		color: #007AFF;
	}

	.xzImg {
		width: 52rpx;
		height: auto;
		position: absolute;
		right: 44rpx;
		bottom: 580rpx;
	}

	.sgdImg {
		width: 40rpx;
		height: auto;
		position: absolute;
		right: 50rpx;
		bottom: 450rpx;
	}

	.bottomBtn {
		width: 100%;
		height: 150rpx;
		padding-bottom: 15rpx;
		position: absolute;
		bottom: 0;
		left: 0;
		text-align: center;
		display: flex;
		justify-content: space-between;

		.btn {
			width: 30%;
			height: 150rpx;
			font-size: 34rpx;
			color: #FFFFFF;
			line-height: 150rpx;
		}
	}

	.bottomBg {
		width: 100%;
		height: 170rpx;
		box-sizing: border-box;
		padding: 20rpx 30rpx 40rpx;
		position: absolute;
		bottom: 0;
		left: 0;
		background-color: rgba(0, 0, 0, .8);
		display: flex;
		justify-content: space-between;
		align-items: center;

		.imgBox {
			width: 110rpx;
			height: 110rpx;
			float: left;
			margin-right: 40rpx;
			position: relative;

			.cu-tag {
				position: absolute;
				right: 0;
				top: 0;
				border-bottom-left-radius: 2px;
				padding: 3px 5px;
				height: auto;
				background-color: rgba(0, 0, 0, 0.5);
				font-size: 10px;
				vertical-align: middle;
				font-family: Helvetica Neue, Helvetica, sans-serif;
				white-space: nowrap;
				color: #ffffff;
			}
		}

		.imgItem {
			width: 110rpx;
			height: 110rpx;
		}
	}

	.iconClose {
		width: 20rpx;
		height: 20rpx;
	}

绑定数据

data() {
  return {
    getHeight: '200', // camera 组件高度
    device: 'back', //前置或后置摄像头,值为front, back
    flash: 'off', // 闪光灯,值为auto, on, off
    nowTime: '', //日期
    nowTime2: '', //时间
    address: '', //当前地址信息
    sgdUrl: 'https://cdn.zhoukaiwen.com/sgd.png', // 闪光灯图片地址
    imgList: [ // 底部预览图片的列表
      // {
      // 	src: "https://cdn.zhoukaiwen.com/angular.jpg"
      // }
    ],
    w: '', // canvas 元素宽度
    h: '' // canvas 元素高度
  }
},

逻辑代码

需要提前下载腾讯地图微信小程序JavaScript SDK,实现定位功能

// 引入微信小程序JavaScript SDK
import QQMapWX from "@/common/qqmap-wx-jssdk";


onLoad() {
  const that = this;
  var qqmapsdk;
  // 获取系统信息,为 camera 组件设置高度
  uni.getSystemInfo({
    success: function(res) {
      that.getHeight = res.windowHeight;
    }
  });
  // 获取当前日期时间
  this.getTime();
  // 获取当前位置信息 
  uni.getLocation({
    type: 'wgs84',
    success: function(res) {
      console.log('当前位置的经度:' + res.longitude);
      console.log('当前位置的纬度:' + res.latitude);

      qqmapsdk = new QQMapWX({
        key: "xxxxxxxxxxxxxxxxxxx" //自己申请的腾讯地图key
      });
      // 根据经纬度反解析出地址名称
      qqmapsdk.reverseGeocoder({
        location: {
          latitude: res.latitude,
          longitude: res.longitude
        },
        success(addressRes) {
          that.address = addressRes.result.address;
        },
        fail(res) {}
      });
    }
  });

},
methods: {
  // 旋转摄像头
  xzBtn() {
    if (this.device == 'front') {
      this.device = 'back'
    } else {
      this.device = 'front'
    }
  },
  // 打开/关闭 闪光灯
  sgdBtn() {
    if (this.flash == 'off') {
      this.flash = 'on'
      this.sgdUrl = 'https://cdn.zhoukaiwen.com/sgd_on.png'
    } else {
      this.flash = 'off'
      this.sgdUrl = 'https://cdn.zhoukaiwen.com/sgd.png'
    }
  },
  // 删除水印照片
  DelImg(e) {
    uni.showModal({
      content: '确定要删除这张照片吗?',
      cancelText: '取消',
      confirmText: '确认',
      success: res => {
        if (res.confirm) {
          this.imgList.splice(e.currentTarget.dataset.index, 1)
        }
      }
    })
  },
  // 查看照片
  ViewImage(index) {
    const imgList = [this.imgList[index].src];
    uni.previewImage({
      urls: imgList
    });
  },
  // 点击拍照
  takePhoto() {
    var that = this;
    if (this.imgList.length < 3) {
      // 创建并返回 camera 组件的上下文 cameraContext 对象
      const ctx = uni.createCameraContext();
      // 拍照
      ctx.takePhoto({
        quality: 'high', // 图片质量高
        success: (res) => {
          var tempImagePath = res.tempImagePath; // 临时图片路径
          // 获取图片信息
          uni.getImageInfo({
            src: res.tempImagePath,
            success: ress => {
              that.w = ress.width / 3 + 'px'; // 设置 canvas 元素宽度
              that.h = ress.height / 3.01 + 'px'; // 设置 canvas 元素高度
              let ctx = uni.createCanvasContext('firstCanvas'); /** 创建画布 */
              //将图片绘制到cancas内
              ctx.drawImage(res.tempImagePath, 0, 0, ress.width / 3, ress.height / 3);
              
              ctx.setFontSize(10); // 设置字体大小为 10px
              ctx.setFillStyle('#FFFFFF'); // 设置颜色为白色
              
              let textToWidth = (ress.width / 3) * 0.03; // 绘制文本的左上角x坐标位置
              let textToHeight1 = (ress.height / 3) * 0.94; // 绘制文本的左上角y坐标位置
              let textToHeight2 = (ress.height / 3) * 0.98;
              
              // 绘制日期和时间
              ctx.fillText(that.nowTime + ' ' + that.nowTime2, textToWidth, textToHeight1);
              // 绘制地址
              ctx.fillText(that.address, textToWidth, textToHeight2);

              // 绘制完成后,在下一个事件循环将 canvas 内容导出为临时图片地址
              ctx.draw(false, () => {
                setTimeout(() => {
                  uni.canvasToTempFilePath({
                    canvasId: 'firstCanvas',
                    success: res1 => {
                      tempImagePath = res1.tempFilePath
                      this.imgList.push({
                        src: tempImagePath
                      })
                    }
                  });
                }, 1000);
              });
            }
          });
        }
      });
    } else {
      uni.showToast({
        title: '最大上传3张照片',
        duration: 2000,
        icon: 'none'
      });
    }
  },
  // 用户不允许使用摄像头时触发
  error(e) {
    console.log(e.detail);
  },
  // 获取日期时间
  getTime: function() {
    var date = new Date(),
        year = date.getFullYear(),
        month = date.getMonth() + 1,
        day = date.getDate(),
        hour = date.getHours() < 10 ? "0" + date.getHours() : date.getHours(),
        minute = date.getMinutes() < 10 ? "0" + date.getMinutes() : date.getMinutes(),
        second = date.getSeconds() < 10 ? "0" + date.getSeconds() : date.getSeconds();
    month >= 1 && month <= 9 ? (month = "0" + month) : "";
    day >= 0 && day <= 9 ? (day = "0" + day) : "";
    var timer = year + '年' + month + '月' + day + '日';
    var timer2 = hour + ':' + minute + ':' + second;
    this.nowTime = timer;
    this.nowTime2 = timer2;
  }
}

注意

本文章代码 90% 以上都是参考开源项目 前端铺子,如帮助到你,可以去大佬项目 gitee地址 点个 star

### 实现 uni-app 应用中照片添加水印功能 为了实现在 `uni-app` 中对手机拍摄的照片添加水印,可以采用 HTML5 的 Canvas API 来绘制图像并添加文字或图形作为水印。考虑到 iOS 设备对于大尺寸图片的支持有限制,因此需要先检测图片大小并对超出限定范围的图片进行等比例缩放处理[^1]。 #### 获取相机权限与拍照 通过调用 `uni.chooseImage()` 或者更推荐的方式是使用组件 `<camera>` 和 `<cover-view>` 结合来获取用户授权以及捕捉到的照片数据: ```html <template> <view class="content"> <!-- Camera Component --> <camera device-position="back" flash="off" @error="handleError"></camera> <button type="primary" bindtap="takePhoto">拍 照</button> <!-- Display the photo with watermark here --> <image :src="watermarkedImageUrl" mode="widthFix"></image> </view> </template> ``` 当用户点击按钮触发拍照事件后,在 JavaScript 方法里接收返回的数据流,并将其转换成 base64 编码字符串以便后续操作。 #### 图片预处理(压缩) 由于部分移动操作系统存在内存限制,所以有必要预先判断所选文件分辨率是否过高而采取相应措施——即按一定比率缩小原始图样直至满足条件为止。这一步骤能够有效防止因资源占用过多而导致程序崩溃的情况发生。 ```javascript methods: { takePhoto() { const ctx = wx.createCameraContext(); ctx.takePhoto({ quality: 'high', success:async (res) => { let tempFilePath = res.tempImagePath; // Check image size and resize if necessary before adding watermarks. await this.resizeIfNecessary(tempFilePath).then((resultPath)=>{ this.addWatermark(resultPath); }); } }) }, async resizeIfNecessary(filePath){ return new Promise((resolve, reject)=>{ uni.getImageInfo({ src: filePath, success:(info)=>{ const maxWidthHeight = Math.max(info.width, info.height); if(maxWidthHeight > 1365){ const ratio = 1365 / maxWidthHeight; uni.compressImage({ src:filePath, quality:80, width:parseInt(ratio * info.width), height:parseInt(ratio * info.height), success(resizedData)=>{ resolve(resizedData.tempFilePath)} }); }else{ resolve(filePath); } },fail:error=>reject(error) }); }); }, } ``` #### 使用 Canvas 绘制带水印的新图片 创建一个隐藏 canvas 元素用于合成最终效果。将调整好后的原图加载至该上下文中渲染出来之后再叠加一层透明度较低的文字层面上去形成完整的带有标记的作品。 ```javascript addWatermark(imageSrc) { return new Promise(async(resolve,reject)=>{ try{ const query = uni.createSelectorQuery().in(this); var canvasDom = null; function createCanvas(width,height){ const c = document.createElement('canvas'); c.width=width; c.height=height; return c; } // Get Image Info Again For Accurate Size After Compression const imgInfo =await new Promise((rs,rj)=> uni.getImageInfo({src:imageSrc},(data,err)=>err?rj(err):rs(data)) ); const {width:imgW,height:imgH}=imgInfo; // Create Offscreen Canvas And Draw Original Photo First const offScreenCtx=createCanvas(imgW,imgH).getContext('2d'); const img=new Image(); img.src=imageSrc; img.onload=()=>{ offScreenCtx.drawImage(img,0,0); // Add WaterMark Text Here With Desired Style Options offScreenCtx.font='bold 40px sans-serif'; offScreenCtx.fillStyle='#FFFFFF';// White Color As An Example offScreenCtx.globalAlpha=.7;// Set Transparency Level Between 0 To 1 // Calculate Position Based On Your Needs const textX=(imgW-offScreenCtx.measureText("Your Water Mark").width)/2; const textY=imgH-(imgH*0.05); offScreenCtx.fillText("Your Water Mark",textX,textY); // Convert The Final Result Back Into Base64 String Format Or Save It Directly Depending On Requirements this.watermarkedImageUrl = offScreenCtx.canvas.toDataURL('image/jpeg', .92); resolve(true); }; }catch(e){ console.error(e.message); reject(false); } }); } ``` 上述代码片段展示了如何利用 UniApp 提供的能力完成从捕获相片到最后生成含有所需标志版本的一系列流程。值得注意的是实际项目开发过程中可能还需要考虑更多细节比如不同平台间的兼容性差异等问题。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

鹏北海-RemHusband

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值