前言 📝
当你在微信小程序中制作照片时,你可能需要添加水印,以便在分享时保护你的照片,并标识出你的品牌或网站。本文将介绍如何在uniapp中添加照片水印(微信小程序大同小异)。操作 canvas
相关的 api 使用的是微信最新提供的 (一路过来踩了好多坑…)
1. 我的环境
微信开发者工具: 1.06.2402040
调试基础库:3.4.1
2. 选择图片和上传图片
由于我之前并不知道有添加水印的需求,我就在原来封装好的选择图片和上传图片的基础上更改(如果这两个方法和我的不一样,也不会影响到水印的方法,只是需要略微调整。我将上传的代码例举出来就是为了大家结合上下文,方便理解)。先来看一下之前封装的选择图片和上传图片的代码。
2.1 选择图片或者视频
uniapp的chooseMedia
API传送门
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| type mediaType = 'image' | 'video' | 'mix' export function useChooseMix( type: mediaType, options?: UniNamespace.ChooseMediaOption ): Promise<UniApp.MediaFile[]> { return new Promise((resolve, reject) => { uni.chooseMedia({ count: 9, mediaType: [type], maxDuration: 60, sourceType: ['album', 'camera'], sizeType: ['original'], ...options, success: (res) => { resolve(res.tempFiles) }, fail: (err) => { uni.showToast({ title: '取消上传', icon: 'none' }) reject(err) } }) }) }
|
1 2 3 4 5 6
| const handleClick = async () => { const res = await useChooseMix()
}
|
2.2 上传图片或视频
uniapp的uploadFile
API传送门
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75
| export function useUploadFile(files: any[], fileType: number = 601): Promise<any> { const store = useLogin() if (files.length === 0) return Promise.resolve([]) const filePath: any = [] files.forEach((item) => { if (item.path) { return filePath.push(item.path) } return filePath.push(item.tempFilePath) }) files = filePath
return new Promise((resolve, reject) => { let index = 0 const result: any[] = [] let count = 0 uni.showLoading({ title: '加载中', mask: true })
function _request() { const i = index const url = files[i] index++ uni.uploadFile({ url: `${BASE_URL}/api/xxx/uploadFile`, filePath: url, name: 'file', header: { 'content-type': 'multipart/form-data', Authorization: store.token }, formData: { fileType: fileType }, success: (res) => { if (typeof res.data === 'string') { result[i] = JSON.parse(res.data) } else { result[i] = res.data } }, fail: (err) => { result[i] = err uni.showToast({ title: '上传失败', icon: 'none' }) }, complete: () => { count++ if (count >= files.length) { resolve(result) uni.hideLoading() } } }) }
for (let i = 0; i < files.length; i++) { _request() } }) }
|
1 2 3 4 5
|
const tempRes = await useChooseMix('image')
const res = await useUploadFile(tempRes)
|
3. 添加水印
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
|
export async function useAddWatermark(fileInfoRef: any[], chooseImageRef: any): Promise<any> { const result: any[] = [] for (let i = 0; i < fileInfoRef.length; i++) { if (fileInfoRef[i]?.fileType.includes('video')) { result.push(fileInfoRef[i]) continue } const res = await chooseImageRef?.getCanvas( fileInfoRef[i], parseTime(+new Date(), '{y}-{m}-{d} {h}:{i}') ) result.push(res) } return result }
|
下面这个组件是我封装的一个上传图片和视频的组件,当然添加水印所需要的canvas也在这个页面,简单介绍一个这个组件使用,,我们在内部调用了useChooseMix
选择图片,选择完图片之后,会通过v-model
将选择的数据提供给父组件fileInfoRef
。所以在父组件中我们就可以得到所有在微信小程序中选择的图片。
1 2 3 4 5 6 7
| <!-- fileInfoRef就是通过微信选择图片api返回的内容 --> <ChooseImage style="width: 100%" v-model="fileInfoRef" ref="chooseImageRef" upload-type="image" ></ChooseImage>
|
uniapp的createSelectorQuery
API传送门
微信小程序的getImageInfo
API传送门
微信小程序的canvasToTempFilePath
API传送们
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218
| <script setup lang="ts"> // 框架 // 组件 // 方法/类型 import { useChooseMix } from '@/utils/useUploadFile' import { handleImageScale } from '@/pagesGround/fun/useImageScale'
const props = withDefaults( defineProps<{ modelValue: any[] // 代表该组件选择的文件类型 uploadType: 'image' | 'video' | 'mix' chooseOptions: UniNamespace.ChooseMediaOption disabled?: boolean }>(), { modelValue: () => [], uploadType: 'image', chooseOptions: () => ({}), disabled: false } )
const instance = getCurrentInstance()
const picPaths = computed({ get() { return props.modelValue }, set(value) { emits('update:modelValue', value) } })
const emits = defineEmits(['update:modelValue'])
const picFlag = ref(false)
// 点击选择图片 const handleClick = async () => { const res = await useChooseMix(props.uploadType, props.chooseOptions) picFlag.value = true let picContent: any[] = [] res.forEach((item) => { picContent.push(item) })
// 重新赋值-需要先解构之前得moduleValue,因为可能只删除了一张图片,还有其余得图片,不能直接 picPaths.value = picContent picPaths.value = [...props.modelValue, ...picContent] }
// 关闭某一个图片/视频 const handleClose = (path: string) => { picFlag.value = false // 重新赋值 picPaths.value = props.modelValue.filter((item) => item.tempFilePath !== path) }
// 添加水印代码 // 添加水印代码 // 添加水印代码 const canvasId = ref('canvasId') function getCanvas(fileItem: any, context: string) { return new Promise((parResolve) => { var mycenter = 0 //文字左右居中显示 var myheight = 0 //文字高度 const query = uni.createSelectorQuery() query .in(instance) .select('#' + canvasId.value) .fields({ node: true, size: true }, (res) => {}) .exec((res) => { const canvas = res[0].node const ctx = canvas.getContext('2d') new Promise(function (resolve) { // 绘制背景图片 wx.getImageInfo({ src: fileItem.tempFilePath, success(res) { var width = res.width var height = res.height mycenter = width / 2 myheight = height / 2 canvas.width = width canvas.height = height const img = canvas.createImage() img.src = res.path img.onload = () => { ctx.drawImage(img, 0, 0, width, height) resolve(true) } } }) }) .then(() => { ctx.font = '700 100px Arial' ctx.textAlign = 'center' ctx.fillStyle = 'white' ctx.fillText(context, mycenter, myheight - 50) })
.then(function () { transferCanvasToImage(canvas, fileItem, parResolve) }) .catch((err) => {}) }) }) }
//canvas转为图片 function transferCanvasToImage(canvas: any, fileItem: any, parResolve: any) { wx.canvasToTempFilePath({ canvas: canvas, success(res) { // canvasImg.value = res.tempFilePath parResolve({ ...fileItem, tempFilePath: res.tempFilePath }) } }) }
defineExpose({ getCanvas }) </script>
<template> <view class="pic-item__container"> <text class="iconfont icon-camera camera item" @click="handleClick" v-if="!props.disabled" ></text>
<view class="item" v-for="(item, index) in picPaths" :key="index"> <image @click="handleImageScale(picPaths, index)" v-if="item.fileType.includes('image')" :src="item.tempFilePath" mode="scaleToFill" />
<video v-if="item.fileType.includes('video')" :src="item.tempFilePath" style="width: 100%; height: 100%" :poster="item.tinyImageUrl" ></video>
<view class="visbale" @click="handleClose(item.tempFilePath)" v-if="!props.disabled"> <text class="iconfont icon-x close"></text> </view>
</view>
<!-- 画水印 --> <view style="width: 0rpx; height: 0rpx; overflow: hidden"> <canvas id="canvasId" type="2d" style="position: fixed; left: 9999px"></canvas> </view>
</view> </template>
<style scoped lang="scss"> .pic-item__container { min-height: 200rpx; @include flex(flex-start, center); flex-wrap: wrap; .camera { font-size: 100rpx; color: $custom-primary; }
.item { width: 199rpx; height: 199rpx; margin-bottom: $mr10; margin-right: 17rpx; @include flex(center, center); position: relative;
image { width: 100%; height: 100%; } .visbale { width: 28rpx; height: 28rpx; position: absolute; top: 0; right: 0; background-color: rgba(0, 0, 0, 0.7); border-radius: 0 0 0 24rpx; color: $custom-text-color; .close { position: absolute; right: 1rpx; color: $custom-inverse; font-size: 24rpx; } } } } </style>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| const handleSubmit = async () => { if (fileInfoRef.value.length === 0) { return uni.showToast({ title: '请选择设备图片', icon: 'none', duration: 2000 }) }
const result = await useAddWatermark(fileInfoRef.value, chooseImageRef.value) const res = await useUploadFile(result)
}
|