uniapp 微信小程序 两张图片合成一张图片,并下载 canvas , uni.uploadFile,uni.saveImageToPhotosAlbum,uni.downloadFile

标题: uniapp 微信小程序 两张图片合成一张图片,并下载 canvas , uni.uploadFile,uni.saveImageToPhotosAlbum,uni.downloadFile

示例图片,差不多就这个样子
uniapp 微信小程序 两张图片合成一张图片,并下载 canvas , uni.uploadFile,uni.saveImageToPhotosAlbum,uni.downloadFile_第1张图片
注意:
1. 先合成一张图
2. 图片 用自己项目中的 上传文件 接口转一下 路径
3. uni.downloadFile 再下载

uniapp 微信小程序 两张图片合成一张图片,并下载 canvas , uni.uploadFile,uni.saveImageToPhotosAlbum,uni.downloadFile

Poster.vue 组件

<template>
	<view class="canvas">
		<canvas canvas-id="myCanvas" :style="{width: width+'px',height: height+'px'}"></canvas>
	</view>
</template>
<!-- 
list参数说明:
 图片渲染:
 type: 'image',
 x: X轴位置,
 y: Y轴位置,
 path: 图片路径,
 width: 图片宽度,
 height: 图片高度,
 rotate: 旋转角度
 shape: 形状,默认无,可选值:circle 圆形
 area: {x,y,width,height}  // 绘制范围,超出该范围会被剪裁掉 该属性与shape暂时无法同时使用,area存在时,shape失效
 
 文字渲染:
 type: 'text',
 x: X轴位置,
 y: Y轴位置,
 text: 文本内容,
 size: 字体大小,
 textBaseline: 基线 默认top  可选值:'top''bottom''middle''normal'
 color: 颜色
 
 多行文字渲染:
 type: 'textarea',
 x: X轴位置,
 y: Y轴位置,
 width:换行的宽度
 height: 高度,溢出会展示“...lineSpace: 行间距
 text: 文本内容,
 size: 字体大小,
 textBaseline: 基线 默认top  可选值:'top''bottom''middle''normal'
 color: 颜色
 -->
<script>
	export default {
		name: "Poster",
		props: {
			// 绘制队列
			list: {
				type: Array,
				required: true
			},
			width: {
				type: Number,
				required: true
			},
			height: {
				type: Number,
				required: true
			},
			backgroundColor: {
				type: String,
				default: 'rgba(0,0,0,0)'
			}
		},
		emit: ['on-success', 'on-error'],
		data() {
			return {
				posterUrl: '',
				ctx: null, //画布上下文
				counter: -1, //计数器
				drawPathQueue: [], //画图路径队列
			};
		},
		computed: {
			// 渲染列表中图片的数量
			imgCount() {
				return this.list.filter(item => item.type == 'image').length;
			},
			successImageCount() {
				return this.drawPathQueue.filter(item => item.type == 'image' && item.success).length;
			}
		},
		watch: {
			drawPathQueue: {
				handler(newVal, oldVal) {
					// 总图片数量不为零 并且 加载图片数量与总图片数量不相等,不进行绘制
					// 没图片或图片都加载完成了,开始绘制
					console.log(this.imgCount, this.successImageCount)
					if (!this.imgCount && newVal != this.imgCount) {
						return;
					}
					// 绘制单行文字
					const fillText = (textOptions) => {
						this.ctx.setFillStyle(textOptions.color)
						this.ctx.setFontSize(textOptions.size)
						this.ctx.setTextBaseline(textOptions.textBaseline || 'top')
						this.ctx.fillText(textOptions.text, textOptions.x, textOptions.y)
					}
					// 绘制段落
					const fillParagraph = (textOptions) => {
						this.ctx.setFontSize(textOptions.size)
						let tempOptions = JSON.parse(JSON.stringify(textOptions));
						// 如果没有指定行间距则设置默认值
						tempOptions.lineSpace = tempOptions.lineSpace ? tempOptions.lineSpace : 10;
						// 获取字符串
						let str = textOptions.text;
						// 计算指定高度可以输出的最大行数
						let lineCount = Math.floor((tempOptions.height || this.height + tempOptions.lineSpace) / (
							tempOptions.size +
							tempOptions.lineSpace))
						// 初始化单行宽度
						let lineWidth = 0;
						let lastSubStrIndex = 0; //每次开始截取的字符串的索引

						// 构建一个打印数组
						let strArr = str.split("");
						let drawArr = [];
						let text = "";
						while (strArr.length) {
							let word = strArr.shift()
							text += word;
							let textWidth = this.ctx.measureText(text).width;
							if (textWidth > textOptions.width) {
								// 因为超出宽度 所以要截取掉最后一个字符
								text = text.substr(0, text.length - 1)
								drawArr.push(text)
								text = "";
								// 最后一个字还给strArr
								strArr.unshift(word)
							} else if (!strArr.length) {
								drawArr.push(text)
							}
						}

						if (drawArr.length > lineCount) {
							// 超出最大行数
							drawArr.length = lineCount;
							let pointWidth = this.ctx.measureText('...').width;
							let wordWidth = 0;
							let wordArr = drawArr[drawArr.length - 1].split("");
							let words = '';
							while (pointWidth > wordWidth) {
								words += wordArr.pop();
								wordWidth = this.ctx.measureText(words).width
							}
							drawArr[drawArr.length - 1] = wordArr.join('') + '...';
						}
						// 打印
						// 记录初始y值
						const y = tempOptions.y;
						for (let i = 0; i < drawArr.length; i++) {
							tempOptions.y = y + tempOptions.size * i + tempOptions.lineSpace * i; // y的位置
							tempOptions.text = drawArr[i]; // 绘制的文本
							fillText(tempOptions)
						}
					}
					// 绘制背景
					this.ctx.setFillStyle(this.backgroundColor);
					this.ctx.fillRect(0, 0, this.width, this.height);
					/* 所有元素入队则开始绘制 */
					if (this.drawPathQueue.length === this.list.length) {
						try {
							// console.log('生成的队列:' + JSON.stringify(this.drawPathQueue));
							console.log('开始绘制...')
							for (let i = 0; i < this.drawPathQueue.length; i++) {
								for (let j = 0; j < this.drawPathQueue.length; j++) {
									let current = this.drawPathQueue[j]
									/* 按顺序绘制 */
									if (current.index === i) {
										/* 文本绘制 */
										if (current.type === 'text') {
											console.log('绘制文本:' + current.text);
											fillText(current)
											this.counter--
										}
										/* 多行文本 */
										if (current.type === 'textarea') {
											console.log('绘制段落:' + current.text);
											fillParagraph(current)
											this.counter--
										}
										/* 图片绘制 */
										if (current.type === 'image') {
											console.log('绘制图片:' + current.path);
											if (current.area) {
												// 绘制绘图区域
												this.ctx.save()
												this.ctx.beginPath(); //开始绘制
												this.ctx.rect(current.area.x, current.area.y, current.area.width, current
													.area
													.height)
												this.ctx.clip();
												// 设置旋转中心
												let offsetX = current.x + Number(current.width) / 2;
												let offsetY = current.y + Number(current.height) / 2;
												this.ctx.translate(offsetX, offsetY)
												let degrees = current.rotate ? Number(current.rotate) % 360 : 0;
												this.ctx.rotate(degrees * Math.PI / 180)
												this.ctx.drawImage(current.path, current.x - offsetX, current.y - offsetY,
													current.width, current.height)
												this.ctx.closePath();
												this.ctx.restore(); // 恢复之前保存的上下文
											} else if (current.shape == 'circle') {
												this.ctx.save(); // 保存上下文,绘制后恢复
												this.ctx.beginPath(); //开始绘制
												//先画个圆   前两个参数确定了圆心 (x,y) 坐标  第三个参数是圆的半径  四参数是绘图方向  默认是false,即顺时针
												let width = (current.width / 2 + current.x);
												let height = (current.height / 2 + current.y);
												let r = current.width / 2;
												this.ctx.arc(width, height, r, 0, Math.PI * 2);
												//画好了圆 剪切  原始画布中剪切任意形状和尺寸。一旦剪切了某个区域,则所有之后的绘图都会被限制在被剪切的区域内 这也是我们要save上下文的原因
												this.ctx.clip();
												// 设置旋转中心
												let offsetX = current.x + Number(current.width) / 2;
												let offsetY = current.y + Number(current.height) / 2;
												this.ctx.translate(offsetX, offsetY)
												let degrees = current.rotate ? Number(current.rotate) % 360 : 0;
												this.ctx.rotate(degrees * Math.PI / 180)
												this.ctx.drawImage(current.path, current.x - offsetX, current.y - offsetY,
													current.width, current.height)
												this.ctx.closePath();
												this.ctx.restore(); // 恢复之前保存的上下文
											} else {
												this.ctx.drawImage(current.path, current.x, current.y, current.width,
													current
													.height)
											}
											this.counter--
										}
									}
								}
							}
						} catch (err) {
							console.log(err)
							this.$emit('on-error', err)
						}
					}
				},
				deep: true
			},
			counter(newVal, oldVal) {
				if (newVal === 0) {
					/* draw完不能立刻转存,需要等待一段时间 */
					// draw回调的正确用法。。。。 一定要仔细看文档啊
					this.ctx.draw(false, () => {
						console.log('final counter', this.counter);
						uni.canvasToTempFilePath({
							canvasId: 'myCanvas',
							success: (res) => {
								console.log('in canvasToTempFilePath');
								// 在H5平台下,tempFilePath 为 base64
								console.log('图片已生成:', res.tempFilePath)
								this.posterUrl = res.tempFilePath;
								this.$emit('on-success', res.tempFilePath)
							},
							fail: (res) => {
								console.log(res)
							}
						}, this)
					})
				}
			}
		},
		mounted() {
			this.ctx = uni.createCanvasContext('myCanvas', this)
			// this.generateImg()
			console.log('mounted')
		},

		methods: {
			create() {
				this.generateImg()
			},
			generateImg() {
				console.log('generateimg')
				this.counter = this.list.length
				this.drawPathQueue = []
				/* 将图片路径取出放入绘图队列 */
				for (let i = 0; i < this.list.length; i++) {
					console.log('for', i)
					let current = this.list[i]
					current.index = i
					/* 如果是文本直接放入队列 */
					if (current.type === 'text' || current.type === 'textarea') {
						this.drawPathQueue.push(current)
						continue
					}
					/* 图片需获取本地缓存path放入队列 */
					uni.getImageInfo({
						src: current.path,
						success: (res) => {
							current.path = res.path;
							current.success = true; // 表示加载完成
							this.drawPathQueue.push(current)
						},
						fail: (err) => {
							console.error(err)
						}
					})
				}
			},
			saveImg() {
				console.log('保存')
				uni.canvasToTempFilePath({
					canvasId: 'myCanvas',
					success: (res) => {
						console.log(res,'是看是快快快是')
						// 在H5平台下,tempFilePath 为 base64
						uni.saveImageToPhotosAlbum({
							filePath: res.tempFilePath,
							success: (resres) => {
								console.log('save success',resres);
							}
						});
					}
				})
			}
		}
	}
</script>

<style lang="scss" scoped>
	.canvas {
		position: fixed;
		top: 100rpx;
		left: 750rpx;
	}
</style>

页面使用

<template>
	<view class="haibaobox">
		<image :src="poster"  class="bgimg"></image>
		<poster ref="poster" :list="list" background-color="#FFF" :width="750" :height="1334" @on-success="posterSuccess" @on-error="posterError"></poster>							
		<view class="commonBtn u-m-t-32" @click="saveImgFun">保存图片</view>
	</view>
</template>

<script>
	import Poster from '@/components/Poster'
	import baseUrl from "@/api/baseUrl.js"
	import {
	  dtParamInfo,
		userInfo
	 } from "@/api/index/index.js"
	export default {
		components: {Poster},
		data() {
			return {
				commonInfo:{},  // 获取用户信息,二维码
				src:'',  // 获取背景图片
				
				poster: '', // 展示在 页面的图片
				list: [],
				uploadImg:'',//保存到手机的图片路径
			}
		},
		onLoad(option) {},
		onShow() {
			this.getUserInfo();
		},
		methods:{
			// 用户详情
			getUserInfo(){
				let data = {
					userId: uni.getStorageSync('id')
				}
				userInfo(data).then(res => {
					this.commonInfo = res.data;
					this.getimg();
				});
			},
			getimg(){
				dtParamInfo({id:3}).then(res => {
					this.src = res.data.content;
					this.start();
				});
			},
			posterError(err) {
				console.log(err,'错误=======')
			},
			posterSuccess(url) {
				// 生成成功,会把临时路径在这里返回
				let that = this;
				this.poster = url;
				console.log(url,'成功======')
				// 生成的图片 用 自己项目中的上传文件接口 转一下图片
				// 不然 下载图片的时候 会报错 downloadfile fail url scheme is invalid
				
				uni.uploadFile({
					url: baseUrl + 'upload',
					filePath: that.poster,
					name: 'file',
					formData: {
						file: that.poster
					},
					header: {},
					success: r => {
						let imgData = JSON.parse(r.data)
						this.uploadImg = imgData.url; // 下载的图片
					}
				});
			},
			start() {
				// 赋值需要渲染的信息
				this.list = [{
						type: 'image',
						// path替换成你自己的图片,注意需要在小程序开发设置中配置域名
						path: this.src,
						x: 0,
						y: 0,
						width: 750,
						height: 1260
					},
					{
						type: 'image',
						path: this.commonInfo.invitationCode,
						x: (750 - 272) / 2,
						y: 800,
						width: 272,
						height: 272
					}
				];
				// // 生成图片
				this.$nextTick(() => {
					// 要放在$nextTick()里,不然会空白
					this.$refs.poster.create();
				})
			},
			// 保存图片
			saveImgFun(){
				const that = this;
				uni.downloadFile({
					url: that.uploadImg,
					success: res => {
						if (res.statusCode === 200) {
							uni.saveImageToPhotosAlbum({
								filePath: res.tempFilePath,
								success: function() {
									uni.showToast({
										title: '保存成功',
										duration: 2000
									})
								},
								fail: function() {
									uni.showToast({
										title: '保存失败',
										duration: 2000
									})
									 
								}
							})
						} else {
							uni.showToast({
								title: '保存失败',
								duration: 2000
							})
						}
					}
				});
				
			}
			
		},
	}
</script>

<style lang='scss' scoped>
	.haibaobox{
		.bgimg{
			position: relative;
			width:750rpx;
			height:85vh;
		}
		.maimg{
			position: absolute;
			left:0;
			right: 0;
			bottom: 260rpx;
			width: 272rpx;
			height: 272rpx;
			margin: 0 auto;
		}
	}
/* 按钮 */
.commonBtn{
	margin: 0 auto;
	width: 686rpx;
	height: 88rpx;
	background: #8D43AA;
	border-radius: 44rpx;
	font-size: 34rpx;
	color: #FFFFFF;
	line-height: 88rpx;
	text-align: center;
	
}
</style>

你可能感兴趣的:(uni-app,微信小程序)