uniapp-app-安卓/IOS-图片水印处理

一、APP的数据需求
1、调用手机相机(需求是拍照、当然选取相册也是可以的,只是需求要求),拍照后将所需信息和二维码图片作为水印放置在图片上,然后具有水印的图片,可以通过接口参数传给后端,进行后续逻辑的处理。
2、可以上传多个照片上传,但是水印必须是一个一个绘制,
3、可以作为单独的组件,可以在任何需要的功能页面直接引入。
二、大致方案
1、使用u-upload(uniapp本身提供)作为图片上传的组件
2、将图片和水印内容,重新绘制在canvas上,在所有内容绘制完成后,调用uni.canvasToTempFilePath,将canvas转化为文件格式,方便后续作为参数,想后端进行参数传递。三、撰写水印组件(water-mark-upload.vue)

<template>
	<view style="width: 100%;">
		<!-- 上传组件 -->
		<!-- "u-upload"可以通过$refs获取u-upload实例,可以获取u-upload,上传的图片 -->
		<!-- "uplpadAction"如果是自动上传,就需要配置上传地址 -->
		<!-- "sourceType"配置上选择图片的来源,album-从相册选图,camera-使用相机-->
		<!-- "fileList"上传的图片数据-->
		<!-- "autoUpload"是否自动上传-->
		<!-- "beforeUpload"上传前处理图片文件,判断文件大小、文件数据-->
		<!-- "onChooseComplete"上传前处理图片文件,判断文件大小、文件数据-->
		<!-- "remove"删除上传的图片-->
		<u-upload ref="uUpload" :action="uplpadAction" :source-type="sourceType" :file-list="fileList" max-count="10"
			:auto-upload="autoUpload" :before-upload="beforeUpload" @on-choose-complete="onChooseComplete" @on-remove="remove" />
		<!--  Canvas画布,用于添加水印 -->
		<!--  "isCanvasShow" 是否展示canvas -->
		<!--  "canvasWidth、canvasHeight" 根据上传图片的宽高,来设置canvas的宽高 -->
		<canvas canvas-id="watermarkCanvas" v-if="isCanvasShow" :style="{
        position: 'absolute',
        top: '-9999px',
        left: '-9999px',
        width: canvasWidth + 'px',
        height: canvasHeight + 'px',
      }"></canvas>
	</view>
</template>
<script>
	//处理时间格式
	import dayjs from "dayjs";
	//原来使用这个插件,用url生产二维码,但是uniapp打包后会有问题,选择用url作为参数,让后端生成二维码,不建议使
	// import QRCode from "qrcode";
	// 后端接口,生成二维码用,如有需求自行实现
	import {
		FindFamQRCode
	} from "@/src/api/apiContent/allStationWorks.js";

	export default {
		name: "water-mark-upload",
		props: {
			//电站的经度-水印内容-需求需要,如果不需要可以穿空
			currentLongitude: {
				type: Number,
				default: () => {
					return ''
				}
			},
			//电站的纬度-水印内容-需求需要,如果不需要可以穿空
			currentLatitude: {
				type: Number,
				default: () => {
					return ''
				}
			},
			//电站的地址-水印内容-需求需要,如果不需要可以穿空
			currentAddress: {
				type: String,
				default: () => {
					return ''
				}
			},
			//上传组件自动上传时候,需要传入的
			uplpadAction: {
				type: String,
				default: () => {
					return ''
				}
			},
			// 电站的一些基本信息-根据实际需求来定
			stationInfo: {
				type: Object,
				default: () => {
					return {};
				}
			},
			//是否自动上传
			autoUpload: {
				type: Boolean,
				default: false
			},
			//文件列表
			fileList: {
				type: Array,
				default: () => {
					return [];
				}
			},
			// 选择图片的来源,album-从相册选图,camera-使用相机
			sourceType: {
				type: Array,
				default: () => {
					return ["camera"];
				}
			},
			// 是否需要添加水印,控制自动上传
			isWatermark: {
				type: Boolean,
				default: true
			}
		},
		onShow() {
			const THAT = this;
		},
		data() {
			return {
				// 状态栏高度,H5中,此值为0,因为H5不可操作状态栏
				statusBarHeight: uni.getSystemInfoSync().statusBarHeight, // 导航栏内容区域高度,不包括状态栏高度在内
				navbarHeight: 44,
				isCanvasShow: false, // 是否显示画布
				currentTime: "", // 当前时间
				// currentAddress: "陕西省西安市大寨路中航华府", // 当前地址
				canvasWidth: 300, // 画布宽度
				canvasHeight: 225 // 画布高度
			};
		},
		methods: {
			beforeUpload(index, lists) {
				const THAT = this;
				//if (index > 9) {
				//	this.$refs.uToast.show({
				//		title: '最多上传10张图片'
				//	})
				//	return false;
				// }
				// if (lists[index].size > 5 * 1024) {
				// 	this.$refs.uToast.show({
				// 		title: '图片不能超过5M'
				// 	})
				// }
			},
			//删除图片
			remove(index, lists, name){
				const THAT = this;
				//触发父组件对上传文件进行删除
				THAT.$emit('remove',index, lists, name)
			},
			// 当选择图片后触发的事件
			async onChooseComplete(list, name) {
				if (this.isWatermark) {
					try {
						const index = list.length - 1;
						//对图片进行水印处理
						await this.drawWatermark(list, index); // 添加水印
					} catch (error) {
						console.error("水印处理失败", error);
					}
				}
			},
			// 处理水印并手动上传
			async drawWatermark(list, index) {
				let THAT = this;
				try {
					const url = list[index].url;
					const size = list[index].file.size;

					// 获取当前时间:年-月-日 时:分:秒 星期
					THAT.currentTime = dayjs(new Date()).format("YYYY-MM-DD HH:mm:ss");
					// 设置mask: true,避免水印加载过程中用户执行其他操作
					uni.showLoading({
						title: "加载水印中",
						mask: true
					});
					//获取图片的宽高和path信息
					const {
						width,
						height,
						path
					} = await THAT.getImageInfo(url); // 获取图片信息
					//宽高比
					const ratio = width / height
					//高德提供的根据这个url生成二维码,扫码可以调用高德地图进行导航
					const codeUrl =
						`http://uri.amap.com/navigation?from=${THAT.currentLongitude},${THAT.currentLatitude},${THAT.currentAddress}&to=${THAT.stationInfo.longitude},${THAT.stationInfo.latitude},${THAT.stationInfo.address}&mode=car&policy=1&src=mypage&callnative=0`
					//获取二维码地址
					const QRData = await FindFamQRCode({
						url: codeUrl
					})
					let qrString = ''
					console.log(QRData.code)
					if (QRData.code === 0 && QRData.data) {
						qrString = QRData.data
					}
					// 设置画布宽高
					THAT.canvasWidth = width;
					THAT.canvasHeight = height;
					// #ifdef APP-PLUS
					console.log(plus.os.name)
					//对ios单独处理,ios图片水印,如果使用原来图片宽高会出现,水印异常问题,只会出现图片的一个角,图片被放大
					if (plus.os.name == "iOS") {
						const maxWidth = uni.upx2px(960);
						const maxHeight = uni.upx2px(720);
						/**
						 * 规则:1.照片宽度大于最大宽度,以最大宽度为准
						 * 规则:2.照片高度大于最大高度,以最大高度为准
						 * 规则:3.照片高度或照片宽度任意一个超出最值范围,以最大高度和宽度为准
						 */
						if (width > maxWidth) {
							THAT.canvasWidth = maxWidth;
							THAT.canvasHeight = THAT.canvasWidth / ratio;
						} else if (height > maxHeight) {
							THAT.canvasHeight = maxHeight;
							THAT.canvasWidth = THAT.canvasHeight * ratio;
						} else {
							THAT.canvasWidth = width;
							THAT.canvasHeight = height;
						}
						console.log('THAT.canvasWidth',THAT.canvasWidth)
						console.log('THAT.canvasHeight',THAT.canvasHeight)
					}
					// #endif
					THAT.isCanvasShow = true;

					// 等待 canvas 元素创建
					THAT.$nextTick(() => {
						let ctx = uni.createCanvasContext("watermarkCanvas", THAT);
						console.log(ctx)
						// 清除画布
						ctx.clearRect(0, 0, THAT.canvasWidth, THAT.canvasHeight);

						// 绘制整个图片,将原始图片绘制在canvas上面
						ctx.drawImage(path, 0, 0, THAT.canvasWidth, THAT.canvasHeight);

						// 绘制竖线水印、时间、创建人等
						// 判断是浏览器还是终端安卓 或者IOS
						// 说明是浏览器
						if(navigator){
							THAT.drawOtherWatermarks(ctx, THAT.canvasWidth, THAT.canvasHeight,'Internet');
							// 绘制二维码图片
							if (qrString) {
								const yHeight =
									THAT.canvasWidth > THAT.canvasHeight ? THAT.canvasHeight - 380 + 25 : THAT
									.canvasHeight - 380 + 25;
								if (THAT.canvasWidth > THAT.canvasHeight) {
									ctx.drawImage(qrString, THAT.canvasWidth - 300, yHeight, 260, 260);
								} else {
									ctx.drawImage(qrString, THAT.canvasWidth - 330, yHeight, 285, 285);
								}
							}
						}else{
							if (plus.os.name == "Android"){
								THAT.drawOtherWatermarks(ctx, THAT.canvasWidth, THAT.canvasHeight,'Android');
								// 绘制二维码图片
								if (qrString) {
									const yHeight =
										THAT.canvasWidth > THAT.canvasHeight ? THAT.canvasHeight - 380 + 25 : THAT
										.canvasHeight - 380 + 25;
									if (THAT.canvasWidth > THAT.canvasHeight) {
										ctx.drawImage(qrString, THAT.canvasWidth - 300, yHeight, 260, 260);
									} else {
										ctx.drawImage(qrString, THAT.canvasWidth - 330, yHeight, 285, 285);
									}
								}
							}else if(plus.os.name == "iOS") {
								THAT.drawOtherWatermarks(ctx, THAT.canvasWidth, THAT.canvasHeight,'iOS');
								// 绘制二维码图片
								if (qrString) {
									const yHeight =
										THAT.canvasWidth > THAT.canvasHeight ? THAT.canvasHeight - 190 + 12 : THAT
										.canvasHeight - 190 + 12;
									if (THAT.canvasWidth > THAT.canvasHeight) {
										ctx.drawImage(qrString, THAT.canvasWidth - 150, yHeight, 130, 130);
									} else {
										ctx.drawImage(qrString, THAT.canvasWidth - 150, yHeight, 130, 130);
									}
								}
							}
						}

						// 绘制结束后生成临时文件并上传
						setTimeout(() => {
							ctx.draw(false, () => {
								uni.canvasToTempFilePath({
										canvasId: "watermarkCanvas",
										quality: 0.6, // 图片质量,范围0-1,1为最高质量
										width: THAT.canvasWidth, // 画布宽度
										height: THAT.canvasHeight, // 画布高度
										destWidth: THAT
											.canvasWidth, // 输出图片宽度(默认为 width * 屏幕像素密度)
										destHeight: THAT
											.canvasHeight, // 输出图片高度(默认为 height * 屏幕像素密度)
										success: data => {
											console.log('水印成功')

											// 隐藏画布
											THAT.isCanvasShow = false;
											// 替换原始图片为带水印图片
											THAT.$refs.uUpload.lists[index] = {
												size: size,
												thumb: data.tempFilePath,
												type: "png",
												url: data.tempFilePath
											};
											// 手动上传图片
											// this.$refs.uUpload.upload();
											THAT.$nextTick(() => {
												THAT.$emit(
													'before-file-list-change',
													THAT.$refs.uUpload
													.lists)
											})
											uni.hideLoading();
										},
										fail: err => {
											THAT.$nextTick(() => {
												THAT.$emit(
													'before-file-list-change',
													THAT.$refs.uUpload
													.lists)
											})
											uni.hideLoading();
											console.error(
												"canvasToTempFilePath failed", err);
										}
									},
									THAT
								); // 注意这里要加this(确保 canvas 与当前页面或者组件正确关联)否则报错:"canvasToTempFilePath: fail canvas is empty"
							});
						}, 1000)
					});
				} catch (error) {
					console.error("drawWatermark error:", error);
					uni.hideLoading();
				}
			},
			// 绘制其他水印(竖线、时间等)里面出现的数字就是设置水印在图片上面位置,可以根据实际情况设定
			drawOtherWatermarks(ctx, width, height , deviceType) {
				const THAT = this;
				const currentTime = "时间:" + THAT.currentTime;
				const stationAddress = `地点:${
        THAT.stationInfo.address ? THAT.stationInfo.address : "-"
      }`;
				const stationOwer = `业主:${
        THAT.stationInfo.owner ? THAT.stationInfo.owner : "-"
      }`;
				const jw = `经纬度:${
        THAT.stationInfo.longitude ? THAT.stationInfo.longitude : "-"
      }°E  ${THAT.stationInfo.latitude ? THAT.stationInfo.latitude : "-"}°N`;
				const operationName = `运维商:${
        THAT.stationInfo.operationName ? THAT.stationInfo.operationName : "-"
      }`;
				// 通过width、height对比判断照片:横屏\竖屏,调整绘制的 👤 创建人名称 水印的高度
				// 绘制背景色
				if(deviceType === 'Internet' || deviceType === 'Android'){
					const nowYHeight = width > height ? height - 380 : height - 380;
					if (stationAddress.length > 14) {
						THAT.fillGrayBakcground(ctx, 40, nowYHeight, width - 70, 350, {
							color: "rgba(0, 0, 0, 0.4)",
							opacity: "0.4"
						});
						// 时间水印
						THAT.fillText(ctx, currentTime, 60, nowYHeight + 60, {
							font: 'bold 30px "PingFang SC"',
							color: "#fff"
						});
						const addressLength = stationAddress.length;
						// 地址水印01
						THAT.fillText(ctx, stationAddress.slice(0, 14), 60, nowYHeight + 110, {
							font: 'bold 30px "PingFang SC"',
							color: "#fff"
						});
						// 地址水印02
						THAT.fillText(ctx, stationAddress.slice(14, addressLength), 60, nowYHeight + 160, {
							font: 'bold 30px "PingFang SC"',
							color: "#fff"
						});
						// 业主信息
						THAT.fillText(ctx, stationOwer, 60, nowYHeight + 210, {
							font: 'bold 30px "PingFang SC"',
							color: "#fff"
						});
	
						// 经纬度
						THAT.fillText(ctx, jw, 60, nowYHeight + 260, {
							font: 'bold 30px "PingFang SC"',
							color: "#fff"
						});
	
						// 运维商
						THAT.fillText(ctx, operationName, 60, nowYHeight + 310, {
							font: 'bold 30px "PingFang SC"',
							color: "#fff"
						});
					} else {
						THAT.fillGrayBakcground(ctx, 40, nowYHeight, width - 70, 300, {
							color: "rgba(0, 0, 0, 0.4)",
							opacity: "0.4"
						});
	
						// 时间水印
						THAT.fillText(ctx, currentTime, 60, nowYHeight + 60, {
							font: 'bold 30px "PingFang SC"',
							color: "#fff"
						});
						// 地址水印
						THAT.fillText(ctx, stationAddress, 60, nowYHeight + 110, {
							font: 'bold 30px "PingFang SC"',
							color: "#fff"
						});
						// 业主信息
						THAT.fillText(ctx, stationOwer, 60, nowYHeight + 160, {
							font: 'bold 30px "PingFang SC"',
							color: "#fff"
						});
	
						// 经纬度
						THAT.fillText(ctx, jw, 60, nowYHeight + 210, {
							font: 'bold 30px "PingFang SC"',
							color: "#fff"
						});
	
						// 运维商
						THAT.fillText(ctx, operationName, 60, nowYHeight + 260, {
							font: 'bold 30px "PingFang SC"',
							color: "#fff"
						});
					}
				}else if(deviceType === 'iOS'){
					const nowYHeight = width > height ? height - 190 : height - 190;
					if (stationAddress.length > 14) {
						THAT.fillGrayBakcground(ctx, 20, nowYHeight, width - 30, 166, {
							color: "rgba(0, 0, 0, 0.4)",
							opacity: "0.4"
						});
						// 时间水印
						THAT.fillText(ctx, currentTime, 30, nowYHeight + 30, {
							font: 'bold 16px "PingFang SC"',
							color: "#fff"
						});
						const addressLength = stationAddress.length;
						// 地址水印01
						THAT.fillText(ctx, stationAddress.slice(0, 14), 30, nowYHeight + 55, {
							font: 'bold 16px "PingFang SC"',
							color: "#fff"
						});
						// 地址水印02
						THAT.fillText(ctx, stationAddress.slice(14, addressLength), 30, nowYHeight + 80, {
							font: 'bold 16px "PingFang SC"',
							color: "#fff"
						});
						// 业主信息
						THAT.fillText(ctx, stationOwer, 30, nowYHeight + 105, {
							font: 'bold 16px "PingFang SC"',
							color: "#fff"
						});
	
						// 经纬度
						THAT.fillText(ctx, jw, 30, nowYHeight + 130, {
							font: 'bold 16px "PingFang SC"',
							color: "#fff"
						});
	
						// 运维商
						THAT.fillText(ctx, operationName, 30, nowYHeight + 155, {
							font: 'bold 16px "PingFang SC"',
							color: "#fff"
						});
					} else {
						THAT.fillGrayBakcground(ctx, 20, nowYHeight, width - 30, 150, {
							color: "rgba(0, 0, 0, 0.4)",
							opacity: "0.4"
						});
	
						// 时间水印
						THAT.fillText(ctx, currentTime, 30, nowYHeight + 30, {
							font: 'bold 16px "PingFang SC"',
							color: "#fff"
						});
						// 地址水印
						THAT.fillText(ctx, stationAddress, 30, nowYHeight + 55, {
							font: 'bold 16px "PingFang SC"',
							color: "#fff"
						});
						// 业主信息
						THAT.fillText(ctx, stationOwer, 30, nowYHeight + 80, {
							font: 'bold 16px "PingFang SC"',
							color: "#fff"
						});
	
						// 经纬度
						THAT.fillText(ctx, jw, 30, nowYHeight + 105, {
							font: 'bold 16px "PingFang SC"',
							color: "#fff"
						});
	
						// 运维商
						THAT.fillText(ctx, operationName, 30, nowYHeight + 130, {
							font: 'bold 16px "PingFang SC"',
							color: "#fff"
						});
					}
				}
			},
			// 灰色绘制背景
			fillGrayBakcground(cxt, x, y, width, height, style) {
				cxt.save();
				//背景色
				cxt.fillStyle = style.color;
				cxt.fillRect(x, y, width, height);
				cxt.restore();
			},
			// 绘制文字
			fillText(cxt, content, x, y, style) {
				cxt.save();
				cxt.font = style.font;
				cxt.fillStyle = style.color;
				cxt.fillText(content, x, y);
				cxt.restore();
			},

			// 绘制矩形竖线水印
			fillRectangle(cxt, x, y, width, height, style) {
				cxt.save();
				cxt.fillStyle = style;
				cxt.fillRect(x, y, width, height);
				cxt.restore();
			},

			// 获取图片信息封装为 Promise,便于异步处理
			getImageInfo(src) {
				return new Promise((resolve, reject) => {
					uni.getImageInfo({
						src: src,
						success: resolve,
						fail: reject
					});
				});
			}
		}
	};
</script>

四、外部父组件调用

	<water-mark-upload 
		:station-info="stationInfo" 
		uplpad-action="" 
		:auto-upload="false"
		current-longitude="currentLongitude" 
		:current-latitude="currentLatitude" 
		:current-address="currentAddress" 
		:fileList="fileListBefore"
		@remove="removeBeforeList"
		@before-file-list-change="fileListBeforeChange" />

五、最终效果
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值