项目近期有一个需求,是在小程序使用相机拍照时,页面缩略图和上传服务器(或保存本地)的都是带水印的,水印文案是当前的时间和当前所处的地点。
前期使用了微信小程序的wx.chooseImage相机拍照,再使用拍摄后的图片添加水印,上传或者保存本地,这样做发现,每保存一次,相册里面都添加了两张图片,测试后发现,wx.chooseImage相机拍照默认保存到相册了,这样的话就会有问题,后期就改成了自己调用相机上传图片,发现是可以实现的,代码如下:
camera.wxml:
<view class="cameraWrapper" hidden="{
{ markPhoto }}">
<view class='camera'>
<camera wx:if="{
{isAuth}}" device-position="back" flash="off" binderror="error"></camera>
</view>
<view class="btn-group">
<mp-icon class="iconBtn returnBtn" icon="previous" color="#666" size="34" bindtap="returnCarmera"></mp-icon>
<button class="takePhoto" type="primary" plain="{
{true}}" bindtap="camera"><text></text></button>
</view>
<!-- 添加水印 -->
<view class="canvas-cont">
<canvas canvas-id='firstCanvas' style="width: {
{
w}}px;height: {
{
h}}px;"></canvas>
</view>
</view>
<!-- 拍摄后生成水印图片 -->
<view class="camera-cont" hidden="{
{ !markPhoto }}">
<view class="preview-image-cont">
<image class="preview-image" src="{
{markPhoto}}" mode="aspectFit"></image>
</view>
<view class="btn-group btn-group-padding" style="{
{
markPhoto=='' ? 'display:none' : ''}}">
<mp-icon class="iconBtn" icon="previous" color="#666" size="44" bindtap="againBtn"></mp-icon>
<icon class="icon-box-img" type="success" size="60" bindtap="saveBtn"></icon>
</view>
</view>
camera.js:
const util = require('../../../utils/util')
const app = getApp()
Page({
data: {
isAuth: false,
markPhoto: null,
prePage: null // 从哪个页面进入
},
onLoad: function (options) {
this.setData({
prePage: options.page })
wx.showLoading({
title: "正在加载中...", mask: true })
app.globalData.pageName = this // 将globalData的页面指向自己
const that = this
wx.getSetting({
success: res => {
if (res.authSetting['scope.camera']) {
// 用户已经授权
wx.hideLoading()
that.setData({
isAuth: true })
} else {
// 用户还没有授权,向用户发起授权请求
wx.authorize({
scope: 'scope.camera',
success() {
// 用户同意授权
wx.hideLoading()
that.setData({
isAuth: true })
},
fail() {
// 用户不同意授权
that.openSetting().then(res => {
wx.hideLoading()
that.setData({
isAuth: true })
})
}
})
}
},
fail: res => {
wx.hideLoading()
console.log('获取用户授权信息失败')
}
})
},
// 打开授权设置界面
openSetting: function () {
const that = this
let promise = new Promise((resolve, reject) => {
wx.showModal({
title: '授权',
content: '请先授权获取摄像头权限',
success(res) {
if (res.confirm) {
wx.openSetting({
success(res) {
if (res.authSetting['scope.camera']) {
// 用户打开了授权开关
resolve(true)
} else {
// 用户没有打开授权开关, 继续打开设置页面
that.openSetting().then(res => {
resolve(true) })
}
},
fail(res) {
console.log(res)
}
})
} else if (res.cancel) {
that.openSetting().then(res => {
resolve(true) })
}
}
})
})
return promise;
},
// 服务站端-水印相机-调用摄像头拍照
camera: function () {
const that = this
if (app.globalData.address) {
const ctx = wx.createCameraContext()
ctx.takePhoto({
quality: 'normal',
success: (res) => {
// console.log('res拍照', res)
wx.showLoading({
title: "正在加载图片...", mask: true })
that.addMark(res.tempImagePath)
},
fail (error) {
wx.showToast({
title: error.errMsg, icon: 'none', duration: 2000 })
setTimeout( () => {
wx.navigateBack() }, 2000)
}
})
} else {
wx.showModal({
title: '提示', content: '请先授权获取当前地理位置',
success (res) {
if (res.confirm) {
app.getLocation('again')
} else if (res.cancel) {
wx.navigateBack()
}
}
})
}
},
// 获取图片信息
addMark: function (file) {
const that = this
wx.getImageInfo({
src: file,
success(res2) {
that.getCanvasImg(res2)
}
})
},
// 服务站端-相机-canvas添加水印
getCanvasImg: function (imgInfo) {
wx.showLoading({
title: "图片努力生成中...", mask: true })
const that = this
const today = util.formatTime(new Date())
const addressTxt = app.globalData.address
let {
path, width, height } = imgInfo
that.setData({
w: width, h: height }) // 720 1206
// 创建canvas
const ctx = wx.createCanvasContext('firstCanvas', that)
ctx.drawImage(path, 0, 0, width, height) // 先画出图片 地址,在canvas上X轴的位置,在canvas上y轴的位置,图片的宽度,图片的高度
let fontSize = 30
let rectY = height - 140
let rectH = 140
let imgWidth = 100
let timeY = height - 80
let addressY = height - 40
let logoX = width - 120
let logoY = height - 120
let txtMaxWidth = width - imgWidth - 80
if (addressTxt.length > 20) {
rectY = height - 180
rectH = 180
imgWidth = 140
timeY = height - 120
addressY = height - 76
logoX = width - 160
logoY = height - 160
txtMaxWidth = width - imgWidth - 80
}
ctx.setFontSize(fontSize) //注意:设置文字大小必须放在填充文字之前,否则不生效
ctx.setFillStyle('rgba(0, 0, 0, .3)')
ctx.fillRect(0, rectY, width, rectH)
ctx.drawImage('/assets/images/logo.png', logoX, logoY, imgWidth, imgWidth)
ctx.setFillStyle('rgba(255, 255, 255, 1)')
ctx.fillText(today, 30, timeY)
if (addressTxt.length < 20) {
ctx.fillText(addressTxt, 30, addressY)
} else {
var chr = addressTxt.split("");//这个方法是将一个字符串分割成字符串数组
var temp = "";
var row = [];
for (var a = 0; a < chr.length; a++) {
if (ctx.measureText(temp).width < txtMaxWidth) {
temp += chr[a];
} else {
a--; //这里添加了a-- 是为了防止字符丢失,效果图中有对比
row.push(temp);
temp = "";
}
}
row.push(temp);
// 如果数组长度大于2 则截取前两个
if (row.length > 2) {
var rowCut = row.slice(0, 2);
var rowPart = rowCut[1];
var test = "";
var empty = [];
for (var a = 0; a < rowPart.length; a++) {
if (ctx.measureText(test).width < 460) {
test += rowPart[a];
} else {
break;
}
}
empty.push(test);
var group = empty[0] + "..." // 这里只显示两行,超出的用...表示
rowCut.splice(1, 1, group);
row = rowCut;
}
for (var b = 0; b < row.length; b++) {
ctx.fillText(row[b], 30, addressY + b * 40, txtMaxWidth);
}
}
ctx.draw(false, (() => {
setTimeout( () => {
// 生成图片把当前画布指定区域的内容导出生成指定大小的图片。在 draw() 回调里调用该方法才能保证图片导出成功
wx.canvasToTempFilePath({
quality: 0.5,
fileType: 'jpg',
canvasId: 'firstCanvas',
success: function (res) {
wx.hideLoading()
that.setData({
'markPhoto': res.tempFilePath })
},
fail: function(error) {
wx.hideLoading()
wx.showToast({
title: error.errMsg, icon: 'none', duration: 2000 })
}
}, that)
}, 100)
})())
},
// 重拍
againBtn: function () {
this.setData({
'markPhoto': null })
},
// 保存图片到相册
saveBtn: function () {
const that = this
if (that.data.prePage == 'complete') {
let pages = getCurrentPages() // 获取当前页面
let prePage = pages[pages.length - 2] // 获取上一页面
prePage.setData({
'markPhoto': that.data.markPhoto //给上一页面的变量赋值
})
prePage.uploadMark(that.data.markPhoto) // 调用上一页面的方法(加载数据)
wx.navigateBack({
delta: 1 }) // 返回上一页面
} else {
wx.saveImageToPhotosAlbum({
// 保存图片到系统相册
filePath: that.data.markPhoto,
success(res) {
that.setData({
'markPhoto': null })
}
})
}
},
// 相机返回
returnCarmera: function () {
wx.navigateBack()
}
})
camera.json:
{
"component": true,
"usingComponents": {
"mp-icon": "weui-miniprogram/icon/icon"
},
"enablePullDownRefresh": true
}
camera.wxss:
/* 模拟相机 */
.cameraWrapper {
width: 100vw;
height: 100vh;
display: flex;
flex-direction: column;
}
.camera {
flex: 1;
width: 100%;
position: relative;
}
.camera camera {
width: 100vw;
height: calc(100vh - 200rpx);
box-sizing: border-box;
}
.btn-group {
position: relative;
height: 200rpx;
background-color: #fff;
display: flex;
align-items: center;
justify-content: space-around;
}
.btn-group button.takePhoto:not([size='mini']) {
position: relative;
width: 120rpx;
height: 120rpx;
border-radius: 50%;
border-width: 4rpx;
}
.btn-group button.takePhoto:not([size='mini']) text {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
width: 90rpx;
height: 90rpx;
border-radius: 50%;
margin: auto;
background-color: #07c160;
}
.btn-group .iconBtn {
border: 4rpx solid #666;
border-radius: 50%;
}
.btn-group .returnBtn {
position: absolute;
bottom: 19%;
left: 14%;
}
.cameraWrapper .canvas-cont {
width: 0px;
height: 0px;
position: fixed;
left: 90000000px;
z-index: -999;
overflow: hidden;
}
/* 拍摄后生成水印图片 */
.camera-cont {
width: 100vw;
height: 100vh;
}
.camera-cont .preview-image-cont {
height: calc(100% - 200rpx);
width: 100%;
display: flex;
align-items: center;
justify-content: center;
}
.camera-cont .preview-image {
width: 100%;
height: 100%;
}
.camera-cont .btn-group-padding {
padding: 0 12%;
}
.camera-cont .icon-box-img {
margin-top: -4rpx;
}
let pages = getCurrentPages() // 获取当前页面
let prePage = pages[pages.length - 2] // 获取上一页面
prePage.setData({‘markPhoto’: that.data.markPhoto}) //给上一页面的变量赋值
prePage.uploadMark(that.data.markPhoto) // 调用上一页面的方法(加载数据)
– prePage.uploadMark()是调用上一个页面的方法
// 页面调用子组件的方法
uploadMark: function (markPhoto) {
this.selectComponent(‘#uploadMarkId’).handleUpload(markPhoto)
},
uploadMarkChoose: function (pictureSource) {
this.selectComponent(‘#uploadMarkId’).chooseImage(pictureSource)
}