微信小程序,使用canvas画图生成海报并保存手机相册

需求:需要根据背景图片和用户的微信的头像和昵称,生成一张海报图,并保存到手机相册。

微信小程序,使用canvas画图生成海报并保存手机相册_第1张图片

首先在画布上绘制海报背景图片,然后再绘制头像、昵称和挑战成功的信息。

代码如下:

       getImageInfo() {
            return new Promise((resolve) => {
                uni.getImageInfo({
                    src: this.sharePosterImage,
                    success: (image) => {
                        console.log(image, "海报图");
                        this.$set(this, "canvasWidth", image.width + "px");
                        this.$set(this, "canvasHeight", image.height + "px");
                        resolve(true);
                    },
                });
            });
        },


        // 获取用户详情
        initUserData() {
            const _this = this;
            uni.getUserProfile({
                desc: "用于完善会员资料",
                success: (res) => {
                    uni.authorize({
                        scope: "scope.writePhotosAlbum",
                        success: async () => {
                            wx.showLoading({
                                title: "加载中",
                            });
                            this.headImgUrl = res.userInfo.avatarUrl;
                            this.headName = res.userInfo.nickName;
                            await _this.getImageInfo();
                            const posterUrl = await _this.initShareImage();
                            _this.posterImgUrl = posterUrl;
                            _this.posterShow = true;
                            wx.hideLoading();
                            uni.saveImageToPhotosAlbum({
                                filePath: posterUrl,
                                success() {
                                    _this.shareBtnShake = false;
                                    uni.hideToast();
                                    _this.successToast = true;
                                },
                                fail() {
                                    _this.shareBtnShake = false;
                                    uni.showToast({
                                        title: "保存失败",
                                        icon: "error",
                                    });
                                },
                            });
                        },
                        fail: () => {
                            wx.hideLoading();
                            _this.shareBtnShake = false;
                            uni.showModal({
                                title: "提示",
                                content: "您未授权OPPO客服小程序保存图片或视频到相册,是否现在去设置?",
                                showCancel: true,
                                success: ({ confirm }) => {
                                    if (confirm) {
                                        _this.$authUtil.openSetting();
                                    }
                                },
                            });
                        },
                    });
                },
                fail: (error) => {
                    _this.shareBtnShake = false;
                    console.log(error);
                },
            });
        },
        initShareImage() {
            return new Promise((resolve) => {
                this.$nextTick(async () => {
                    const vwRate = this.canvasWidth.replace("px", "");
                    const vhRate = this.canvasHeight.replace("px", "");
                    // 初始化
                    await this.$refs.rCanvas.init({
                        scale: "1",
                        canvas_id: "rCanvas",
                        canvas_width: this.canvasWidth.replace("px", ""),
                        canvas_height: this.canvasHeight.replace("px", ""),
                        hidden: "true",
                    });
                    // 画图
                    await this.$refs.rCanvas
                        .drawImage({
                            url: this.sharePosterImage,
                            x: 0,
                            y: 0,
                            w: this.canvasWidth.replace("px", ""),
                            h: this.canvasHeight.replace("px", ""),
                        })
                        .catch(() => {});
                    // 画头像
                    await this.$refs.rCanvas
                        .drawImage({
                            url: this.headImgUrl,
                            x: (18 / 360) * vwRate,
                            y: (64 / 779) * vhRate,
                            w: (40 / 360) * vwRate,
                            h: (40 / 360) * vwRate,
                            border_color: "#FFFFFF",
                            is_radius: true,
                        })
                        .catch(() => {});
                    // 画昵称
                    await this.$refs.rCanvas
                        .drawText({
                            text: this.headName,
                            x: (74 / 360) * vwRate,
                            y: (76 / 779) * vhRate,
                            font_color: "#000000",
                            font_size: (12 / 360) * vwRate,
                            font_weight: "400",
                            line_height: (17 / 360) * vwRate,
                            text_align: "left",
                            font_family: "PingFang SC",
                        })
                        .catch(() => {});
                    // 画获奖矩形
                    await this.$refs.rCanvas
                        .fillRoundRect({
                            x: (74 / 360) * vwRate,
                            y: (85 / 779) * vhRate,
                            w: (268 / 360) * vwRate,
                            h: (65 / 779) * vhRate,
                            radius: (12 / 779) * vhRate,
                            fill_color: "#FFFFFF",
                        })
                        .catch(() => {});
                    await this.$refs.rCanvas
                        .fillRoundRect({
                            x: (132 / 360) * vwRate,
                            y: (98 / 779) * vhRate,
                            w: (48 / 360) * vwRate,
                            h: (20 / 779) * vhRate,
                            radius: (4 / 779) * vhRate,
                            fill_color: "#FDECEE",
                        })
                        .catch(() => {});
                    // 画文字
                    await this.$refs.rCanvas
                        .drawText({
                            text: `我是第`,
                            x: (86 / 360) * vwRate,
                            y: (114 / 779) * vhRate,
                            font_color: "#000000",
                            font_size: (14 / 360) * vwRate,
                            font_weight: "400",
                            line_height: (20 / 360) * vwRate,
                            text_align: "left",
                            font_family: "PingFang SC",
                        })
                        .catch(() => {});
                    const numberX = 156 - 4 * this.numWinning.toString().length;
                    await this.$refs.rCanvas
                        .drawText({
                            text: this.numWinning,
                            x: (numberX / 360) * vwRate,
                            y: (114 / 779) * vhRate,
                            font_color: "#EC3E50",
                            font_size: (14 / 360) * vwRate,
                            font_weight: "400",
                            line_height: (20 / 360) * vwRate,
                            text_align: "left",
                            font_family: "PingFang SC",
                        })
                        .catch(() => {});
                    await this.$refs.rCanvas
                        .drawText({
                            text: `位挑战成功,免费领取`,
                            x: (184 / 360) * vwRate,
                            y: (114 / 779) * vhRate,
                            font_color: "#000000",
                            font_size: (14 / 360) * vwRate,
                            font_weight: "400",
                            line_height: (20 / 360) * vwRate,
                            text_align: "left",
                            font_family: "PingFang SC",
                        })
                        .catch(() => {});
                    await this.$refs.rCanvas
                        .drawText({
                            text: `了奖品。`,
                            x: (86 / 360) * vwRate,
                            y: (134 / 779) * vhRate,
                            font_color: "#000000",
                            font_size: (14 / 360) * vwRate,
                            font_weight: "400",
                            line_height: (20 / 360) * vwRate,
                            text_align: "left",
                            font_family: "PingFang SC",
                        })
                        .catch(() => {});

                    // 生成海报
                    await this.$refs.rCanvas.draw((res) => {
                        resolve(res.tempFilePath);
                    });
                });
            });
        },

 rCanvas组件封装的都是canvas 2d绘图方法:

// -----------------------------rCanvas.vue内容








// ------------------------------r-canvas.js内容
export default{
	data(){
		return{
			system_info:{}, //system info
			canvas_width:0, //canvas width px
			canvas_height:0, //canvas height px
			ctx:null, //canvas object
			canvas_id:null, //canvas id
			hidden:false,//Whether to hide canvas
			scale:1,//canvas scale
			r_canvas_scale:1,
			if_ctx:true
		}
	},
	methods:{
		/**
		 * save r-canvas.vue object
		 * @param {Object} that
		 */
		// saveThis(that){
		// 	rCanvasThis = that
		// },
		/**
		 * Draw round rect text
		 * @param {Object} config
		 * @param {Number} config.x x坐标
		 * @param {Number} config.y y坐标
		 * @param {Number} config.w 宽度
		 * @param {Number} config.h 高度
		 * @param {Number} config.radius 圆角弧度
		 * @param {String} config.fill_color 矩形颜色
		 */
		fillRoundRect(config) {
			return new Promise((resolve,reject)=>{
				let x = this.compatibilitySize(parseFloat(config.x)*this.scale)
				let y = this.compatibilitySize(parseFloat(config.y)*this.scale)
				let w = this.compatibilitySize(parseFloat(config.w)*this.scale)
				let h = this.compatibilitySize(parseFloat(config.h)*this.scale)
				let radius = config.radius?parseFloat(config.radius)*this.scale:10*this.scale
				
				let fill_color = config.fill_color || "black"
				// The diameter of the circle must be less than the width and height of the rectangle
				if (2 * radius > w || 2 * radius > h) { 
					reject("The diameter of the circle must be less than the width and height of the rectangle")
					return false; 
				}
				this.ctx.save();
				this.ctx.translate(x, y);
				//  
				this.drawRoundRectPath({
					w: w, 
					h: h, 
					radius: radius
				});
				this.ctx.fillStyle = fill_color
				this.ctx.fill();
				this.ctx.restore();
				resolve()
			})
		},
        /**
         * (x1, y1) 第1个点的坐标
         * (x2, y2)	第2个点的坐标
         * (x3, y3)	第3个点的坐标
         * fill_color: 填充颜色
         */
         drawTriRect(config) {
            return new Promise((resolve) => {
                this.ctx.beginPath();
                this.ctx.moveTo(config.x1, config.y1);
                this.ctx.lineTo(config.x2, config.y2);
                this.ctx.lineTo(config.x3, config.y3);
                this.ctx.fillStyle = config.fill_color;
                this.ctx.closePath();
                this.ctx.fill();
                resolve();
            });
        },

		/**
		 * Draws the sides of a rounded rectangle
		 * @param {Object} config
		 * @param {Number} config.w 宽度
		 * @param {Number} config.h 高度
		 * @param {Number} config.radius 圆角弧度
		 */
		drawRoundRectPath(config) {
			this.ctx.beginPath(0);
			this.ctx.arc(config.w - config.radius, config.h - config.radius, config.radius, 0, Math.PI / 2);
			this.ctx.lineTo(config.radius, config.h);
			this.ctx.arc(config.radius, config.h - config.radius, config.radius, Math.PI / 2, Math.PI);
			this.ctx.lineTo(0, config.radius);
			this.ctx.arc(config.radius, config.radius, config.radius, Math.PI, Math.PI * 3 / 2);
			this.ctx.lineTo(config.w - config.radius, 0);
			this.ctx.arc(config.w - config.radius, config.radius, config.radius, Math.PI * 3 / 2, Math.PI * 2);
			this.ctx.lineTo(config.w, config.h - config.radius);
			this.ctx.closePath();
		},
		/**
		 * Draw special Text,line wrapping is not supported
		 * @param {Object} config
		 * @param {String} config.text 文字
		 * @param {Number} config.x x坐标
		 * @param {Number} config.y y坐标
		 * @param {String} config.font_color 文字颜色
		 * @param {String} config.font_family 文字字体
		 * @param {Number} config.font_size 文字大小(px)
		 */
		drawSpecialText(config_arr){
			return new Promise(async (resolve,reject)=>{
				if(config_arr && config_arr.length>0){
					for(let i in config_arr){
						if(i != 0){
							for(let a in config_arr){
								if(a < i){
									// 先处理font-size才能知道字体宽度
									let font_size = config_arr[a].font_size?parseFloat(config_arr[a].font_size)*this.scale:20*this.scale
									this.ctx.setFontSize(this.compatibilitySize(font_size))
									config_arr[i].x = parseFloat(config_arr[i].x)*this.scale + this.ctx.measureText(config_arr[a].text).width
								}else{
									break;
								}
							}
						}
						await this.drawText(config_arr[i])
					}
					resolve()
				}else{
					reject("The length of config arr is less than 0")
					return;
				}
				
			})
		},
		/**
		 * array delete empty
		 * @param {Object} arr
		 */
		arrDeleteEmpty(arr){
			let newArr = []
			for(let i in arr){
				if(arr[i]){
					newArr.push(arr[i])
				}
			}
			return newArr
		},
		/**
		 * Draw Text,support line
		 * @param {Object} config
		 * @param {String} config.text 文字
		 * @param {Number} config.max_width 文字最大宽度(大于宽度自动换行)
		 * @param {Number} config.line_height 文字上下行间距
		 * @param {Number} config.x x坐标
		 * @param {Number} config.y y坐标
		 * @param {String} config.font_color 文字颜色
		 * @param {String} config.font_family 文字字体 默认值:Arial
		 * @param {String} config.text_align 文字对齐方式(left/center/right)
		 * @param {Number} config.font_size 文字大小(px)
		 * @param {Boolean} config.line_through_height 中划线大小
		 * @param {Boolean} config.line_through_color 中划线颜色
		 * @param {String} config.font_style 规定文字样式
		 * @param {String} config.font_variant 规定字体变体
		 * @param {String} config.font_weight 规定字体粗细
		 * @param {String} config.line_through_cap 线末端类型
		 * @param {String} config.line_clamp 最大行数
		 * @param {String} config.line_clamp_hint 超过line_clamp后,尾部显示的自定义标识 如 ...
		 * @param {String} config.is_line_break 是否开启换行符换行
		 * 
		 */
		drawText(config,configuration = {}){
			
			configuration['line_num'] = configuration.line_num?configuration.line_num:0
			configuration['text_width'] = configuration.text_width?configuration.text_width:0
			
			return new Promise(async (resolve,reject)=>{
				
				if(config.text){
					
					let draw_width = 0,draw_height = 0,draw_x = config.x,draw_y = config.y
					let font_size = config.font_size?(parseFloat(config.font_size)*this.scale):(20*this.scale)
					let font_color = config.font_color || "#000"
					let font_family = config.font_family || "Arial"
					let line_height = config.line_height || config.font_size || 20
					let text_align = config.text_align || "left"
					let font_weight = config.font_weight || "normal"
					let font_variant = config.font_variant || "normal"
					let font_style = config.font_style || "normal"
					let line_clamp_hint = config.line_clamp_hint || '...'
					let lineBreakJoinText = ""
					let max_width = config.max_width?parseFloat(config.max_width)*this.scale:0
					// checkout is line break
					if(config.is_line_break){
						let splitTextArr = config.text.split(/[\n]/g)
						if(splitTextArr && splitTextArr.length > 0){
							let newSplitTextArr = this.arrDeleteEmpty(splitTextArr)
							if(newSplitTextArr && newSplitTextArr.length > 0){
								lineBreakJoinText = newSplitTextArr.slice(1).join("\n")
								config.text = newSplitTextArr[0]
							}else{
								reject("Text cannot be empty:103")
								return
							}
						}else{
							reject("Text cannot be empty:102")
							return
						}
					}
					
					this.ctx.setFillStyle(font_color) // color
					this.ctx.textAlign = text_align;
					this.ctx.font = `${font_style} ${font_variant} ${font_weight} ${parseInt(font_size)}px ${font_family}`
					if(configuration.text_width >= this.ctx.measureText(config.text).width){
						draw_width = configuration.text_width
					}else if(max_width > 0){
						draw_width = max_width < this.ctx.measureText(config.text).width ? this.resetCompatibilitySize(max_width) : this.resetCompatibilitySize(this.ctx.measureText(config.text).width)
					}else{
						draw_width = this.resetCompatibilitySize(this.ctx.measureText(config.text).width)
					}
					configuration.text_width = draw_width
					if( max_width && this.compatibilitySize(this.ctx.measureText(config.text).width) > this.compatibilitySize(max_width)){
						let current_text = ""
						let text_arr = config.text.split("")
						for(let i in text_arr){
							if( this.compatibilitySize(this.ctx.measureText(current_text+text_arr[i]).width) > this.compatibilitySize(max_width) ){
								// Hyphenation that is greater than the drawable width continues to draw
								if(config.line_clamp && parseInt(config.line_clamp) == 1){
									// Subtracting the current_text tail width from the line_clamp_hint width
									let current_text_arr = current_text.split('')
									let json_current_text = ''
									while(true){
										current_text_arr = current_text_arr.slice(1)
										json_current_text = current_text_arr.join('')
										if(this.compatibilitySize(this.ctx.measureText(json_current_text).width) <= this.compatibilitySize(this.ctx.measureText(line_clamp_hint).width)){
											current_text = current_text.replace(json_current_text,'')
											break;
										}
									}
									configuration.line_num += 1
									this.ctx.setFontSize(parseInt(this.compatibilitySize(font_size))) // font size
									this.ctx.fillText(current_text + line_clamp_hint, this.compatibilitySize(parseFloat(config.x)*this.scale), this.compatibilitySize(parseFloat(config.y)*this.scale));
								}else{
									configuration.line_num += 1
									this.ctx.setFontSize(parseInt(this.compatibilitySize(font_size))) // font size
									this.ctx.fillText(current_text, this.compatibilitySize(parseFloat(config.x)*this.scale), this.compatibilitySize(parseFloat(config.y)*this.scale));
									config.text = text_arr.slice(i).join("")
									config.y = config.y + line_height
									if(config.line_clamp){
										config.line_clamp = parseInt(config.line_clamp) - 1
									}
									await this.drawText(config,configuration)
								}
								
								break;
							}else{
								current_text = current_text+text_arr[i]
							}
						}
					}else{
						if(config.line_through_height){
							let x = parseFloat(config.x)*this.scale
							let w
							let y = parseFloat(config.y)*this.scale - (font_size / 2.6) 
							if(text_align == "left"){
								w = this.ctx.measureText(config.text).width/1.1 + parseFloat(config.x)*this.scale
							}else if(text_align == "right"){
								w = parseFloat(config.x)*this.scale - this.ctx.measureText(config.text).width/1.1
							}else if(text_align == "center"){
								x = parseFloat(config.x)*this.scale - this.ctx.measureText(config.text).width / 1.1 / 2
								w = parseFloat(config.x)*this.scale + this.ctx.measureText(config.text).width / 1.1 / 2
							}
							this.drawLineTo({
								x:x,
								y:y,
								w:w,
								h:y,
								line_width:config.line_through_height,
								line_color:config.line_through_color,
								line_cap:config.line_through_cap
							})
						}
						configuration.line_num += 1
						this.ctx.setFontSize(parseInt(this.compatibilitySize(font_size))) // font size
						this.ctx.fillText(config.text, this.compatibilitySize(parseFloat(config.x)*this.scale), this.compatibilitySize(parseFloat(config.y)*this.scale));
						if(config.line_clamp){
							config.line_clamp = parseInt(config.line_clamp) - 1
						}
					}
					if(lineBreakJoinText){
						await this.drawText({...config,text:lineBreakJoinText,y:config.y + line_height},configuration)
					}
					draw_height = config.font_size * configuration.line_num
					draw_width = configuration.text_width
					resolve({draw_width,draw_height,draw_x,draw_y})
				}else{
					reject("Text cannot be empty:101")
				}
			})
		},
		/**
		 * Draw Line
		 * @param {Object} config
		 * @param {Object} config.x x坐标
		 * @param {Object} config.y y坐标
		 * @param {Object} config.w 线的宽度
		 * @param {Object} config.h 线的高度
		 * @param {Object} config.line_width 线的宽度
		 * @param {Object} config.line_color 线条颜色
		 */
		drawLineTo(config){
			let x = this.compatibilitySize(config.x)
			let y = this.compatibilitySize(config.y)
			let w = this.compatibilitySize(config.w)
			let h = this.compatibilitySize(config.h)
			let line_width = config.line_width?parseFloat(config.line_width)*this.scale:1*this.scale
			let line_color = config.line_color || "black"
			let line_cap = config.line_cap || "butt"
			this.ctx.beginPath()
			this.ctx.lineCap = line_cap
			this.ctx.lineWidth = line_width
			this.ctx.strokeStyle = line_color
			this.ctx.moveTo(x,y)
			this.ctx.lineTo(w,h)
			this.ctx.stroke()
		},
		/** 
		 * Compatibility px
		 * @param {Object} size
		 */
		compatibilitySize(size) {
		  let canvasSize = (parseFloat(size) / 750) * this.system_info.windowWidth
		  canvasSize = parseFloat(canvasSize * 2)
		  return canvasSize
		},
		/**
		 * Restore compatibility px
		 * @param {Object} size
		 */
		resetCompatibilitySize(size) {
		  let canvasSize = (parseFloat(size/2)/this.system_info.windowWidth) * 750
		  return canvasSize
		},
		/**
		 * Init canvas
		 */
		init(config){
			return new Promise(async (resolve,reject)=>{
				if(!config.canvas_id){
					reject("Canvas ID cannot be empty, please refer to the usage example")
					return;
				}
				this.hidden = config.hidden
				this.canvas_id = config.canvas_id
				let system_info = await uni.getSystemInfoSync()
				this.system_info = system_info
				this.scale = config.scale&&parseFloat(config.scale)>0?parseInt(config.scale):1
				this.canvas_width = (config.canvas_width ? this.compatibilitySize(config.canvas_width) : system_info.windowWidth) * this.scale
				this.canvas_height = (config.canvas_height ? this.compatibilitySize(config.canvas_height) : system_info.windowHeight) * this.scale,
				this.r_canvas_scale = 1/this.scale
				this.ctx = uni.createCanvasContext(this.canvas_id,this)
				this.setCanvasConfig({
					global_alpha:config.global_alpha?parseFloat(config.global_alpha):1,
					backgroundColor:config.background_color?config.background_color:"#fff"
				})
				resolve()
			})
		},
		/**
		 * clear canvas all path
		 */
		clearCanvas(){
			return new Promise(async (resolve,reject)=>{
				if(!this.ctx){
					reject("canvas is not initialized:101")
					return
				}else{
					this.ctx.clearRect(0,0,parseFloat(this.canvas_width)*this.scale,parseFloat(this.canvas_height)*this.scale)
					await this.draw()
					resolve()
				}
			})
		},
		/**
		 * Set canvas config
		 * @param {Object} config
		 */
		setCanvasConfig(config){
			this.ctx.globalAlpha = config.global_alpha
			this.ctx.fillStyle = config.backgroundColor
			this.ctx.fillRect(0, 0, parseFloat(this.canvas_width)*this.scale, parseFloat(this.canvas_height)*this.scale)
		},
		/**
		 * Draw to filepath
		 */
		draw(callback){
			return new Promise((resolve,reject)=>{
				let stop = setTimeout(()=>{
					this.ctx.draw(false,setTimeout(()=>{
					    uni.canvasToTempFilePath({
					    	canvasId: this.canvas_id,
					    	quality: 1,
					    	success: (res)=>{
					    		console.log('res99999',res)
					    		resolve(res)
					    		callback && callback(res)
					    	},
					    	fail:(err)=>{
					    		reject(JSON.stringify(err)|| "Failed to generate poster:101")
					    	}
					    },this)
					},300))
					clearTimeout(stop)
				},300)
			})
		},
		/**
		 * draw rect
		 * @param {Number} config.x x坐标
		 * @param {Number} config.y y坐标
		 * @param {Number} config.w 图形宽度(px)
		 * @param {Number} config.h 图形高度(px)
		 * @param {Number} config.color 图形颜色
		 * @param {Number} config.is_radius 是否开启圆图(1.1.6及以下版本废弃,请使用border_radius)
		 * 
		 */
		drawRect(config){
			return new Promise(async (resolve,reject)=>{
				let color = config.color || 'white'
				config['color'] = color
				this.ctx.fillStyle = color;
				if(config.is_radius || config.border_radius){
					this.setNativeBorderRadius(config)
					this.ctx.fill()
				}else{
					this.ctx.fillRect(this.compatibilitySize(parseFloat(config.x)*this.scale),this.compatibilitySize(parseFloat(config.y)*this.scale),this.compatibilitySize(parseFloat(config.w)*this.scale),this.compatibilitySize(parseFloat(config.h)*this.scale))
				}
				resolve()
			})
		},
		/**
		 * Draw image
		 * @param {Object} config
		 * @param {String} config.url 图片链接
		 * @param {Number} config.x x坐标
		 * @param {Number} config.y y坐标
		 * @param {Number} config.w 图片宽度(px)
		 * @param {Number} config.h 图片高度(px)
		 * @param {Number} config.border_width 边大小
		 * @param {Number} config.border_color 边颜色
		 * @param {Number} config.is_radius 是否开启圆图(1.1.6及以下版本废弃,请使用border_radius)
		 * @param {Number} config.border_radius 圆角弧度
		 */
		drawImage(config){
			return new Promise(async (resolve,reject)=>{
				if(config.url){
					let type = 0 // 1、network image  2、native image  3、base64 image
					let image_url
					let reg = /^https?/ig;
					if(reg.test(config.url)){
						type = 1
					}else{
						if((config.url.indexOf("data:image/png;base64") != -1) || config.url.indexOf("data:image/jpeg;base64") != -1){
							type = 3
						}else{
							type = 2
						}
					}
					if(type == 1){
						// network image
						await this.downLoadNetworkFile(config.url).then(res=>{ // two function
							image_url = res
						}).catch(err=>{
							reject(err)
							return;
						})
					}else if(type == 2){
						// native image
						const imageInfoResult = await uni.getImageInfo({
							src: config.url
						});
						try{
							if(imageInfoResult.length <= 1){
								reject(imageInfoResult[0].errMsg + ':404')
								return
							}
						}catch(e){
							reject(e+':500')
							return
						}
						let base64 = await this.urlToBase64({url:imageInfoResult[1].path})
						// #ifdef MP-WEIXIN
						await this.base64ToNative({url:base64}).then(res=>{
							image_url = res
						}).catch(err=>{
							reject(JSON.stringify(err)+":501")
							return;
						})
						// #endif
						// #ifndef MP-WEIXIN
						image_url = base64
						// #endif
						
					}else if(type == 3){
						// #ifdef MP-WEIXIN
						await this.base64ToNative({url:config.url}).then(res=>{
							image_url = res
						}).catch(err=>{
							reject(JSON.stringify(err)+":500")
							return;
						})
						// #endif
						// #ifndef MP-WEIXIN
						image_url = config.url
						// #endif
					}else{
						reject("Other Type Errors:101")
						return
					}
					if(config.border_width){
						let border_radius = 0
						if(config.border_radius){
							let multiple = config.w / config.border_radius
							border_radius = (parseFloat(config.w) + parseFloat(config.border_width)) / multiple
						}
						// drawRect
						await this.drawRect({
							x:parseFloat(config.x) - parseFloat(config.border_width)/2,
							y:parseFloat(config.y) - parseFloat(config.border_width)/2,
							w:parseFloat(config.w) + parseFloat(config.border_width),
							h:parseFloat(config.h) + parseFloat(config.border_width),
							color:config.border_color,
							border_radius:border_radius,
							border_width:config.border_width,
							is_radius:config.is_radius
						})
					}
					
					if(config.border_radius){
						this.setNativeBorderRadius(config)
					}else if(config.is_radius){
						//已废弃 is_radius
						this.ctx.save();
            this.ctx.beginPath();
						this.ctx.arc(this.compatibilitySize(parseFloat(config.x)*this.scale+parseFloat(config.w)*this.scale/2), this.compatibilitySize(parseFloat(config.y)*this.scale+parseFloat(config.h)*this.scale/2), this.compatibilitySize(parseFloat(config.w)*this.scale/2), 0, 2 * Math.PI, false)
            this.ctx.strokeStyle = "#FFFFFF"; // 设置绘制圆形边框的颜色
            this.ctx.clip();
						this.ctx.stroke();
					}
					
					await this.ctx.drawImage(image_url,this.compatibilitySize(parseFloat(config.x)*this.scale),this.compatibilitySize(parseFloat(config.y)*this.scale),this.compatibilitySize(parseFloat(config.w)*this.scale),this.compatibilitySize(parseFloat(config.h)*this.scale))
					this.ctx.restore() //Restore previously saved drawing context
					resolve()
				}else{
					let err_msg = "Links cannot be empty:101"
					reject(err_msg)
				}
			})
		},
		/**
		 * base64 to native available path
		 * @param {Object} config
		 */
		base64ToNative(config){
			return new Promise((resolve,reject)=>{
				let fileName = new Date().getTime()
				var filePath = `${wx.env.USER_DATA_PATH}/${fileName}_rCanvas.png`
				wx.getFileSystemManager().writeFile({
					filePath: filePath,
					data: config.url.replace(/^data:\S+\/\S+;base64,/, ''),
					encoding: 'base64',
					success: function() {
						resolve(filePath)
					},
					fail: function(error) {
						reject(error)
					}
				})
			})
		},
		/**
		 * native url to base64
		 * @param {Object} config
		 */
		urlToBase64(config){
			return new Promise(async (resolve,reject)=>{
				if (typeof window != 'undefined') {
					await this.downLoadNetworkFile(config.url).then(res=>{ // two function
						resolve(res)
					}).catch(err=>{
						reject(err)
					})
				}else if (typeof plus != 'undefined') {
					plus.io.resolveLocalFileSystemURL(config.url,(obj)=>{
						obj.file((file)=>{
							let fileReader = new plus.io.FileReader()
							fileReader.onload = (res)=>{
								resolve(res.target.result)
							}
							fileReader.onerror = (err)=>{
								reject(err)
							}
							fileReader.readAsDataURL(file)
						}, (err)=>{
							reject(err)
						})
					},(err)=>{
						reject(err)
					})
				}else if(typeof wx != 'undefined'){
					wx.getFileSystemManager().readFile({
						filePath: config.url,
						encoding: 'base64',
						success: function(res) {
							resolve('data:image/png;base64,' + res.data)
						},
						fail: function(error) {
							reject(error)
						}
					})
				}
			})
		},
		setNativeBorderRadius(config){
			let border_radius = config.border_radius?(parseFloat(config.border_radius)*this.scale):(20*this.scale)
			if ((parseFloat(config.w)*this.scale) < 2 * border_radius) border_radius = (parseFloat(config.w)*this.scale) / 2;
			if ((parseFloat(config.h)*this.scale) < 2 * border_radius) border_radius = (parseFloat(config.h)*this.scale) / 2;
			this.ctx.beginPath();
			this.ctx.moveTo(this.compatibilitySize((parseFloat(config.x)*this.scale) + border_radius), this.compatibilitySize((parseFloat(config.y)*this.scale)));
			this.ctx.arcTo(this.compatibilitySize((parseFloat(config.x)*this.scale) + (parseFloat(config.w)*this.scale)), this.compatibilitySize((parseFloat(config.y)*this.scale)), this.compatibilitySize((parseFloat(config.x)*this.scale) + (parseFloat(config.w)*this.scale)), this.compatibilitySize((parseFloat(config.y)*this.scale) + (parseFloat(config.h)*this.scale)), this.compatibilitySize(border_radius));
			this.ctx.arcTo(this.compatibilitySize((parseFloat(config.x)*this.scale) + (parseFloat(config.w)*this.scale)), this.compatibilitySize((parseFloat(config.y)*this.scale) + (parseFloat(config.h)*this.scale)), this.compatibilitySize((parseFloat(config.x)*this.scale)), this.compatibilitySize((parseFloat(config.y)*this.scale) + (parseFloat(config.h)*this.scale)), this.compatibilitySize(border_radius));
			this.ctx.arcTo((this.compatibilitySize(parseFloat(config.x)*this.scale)), this.compatibilitySize((parseFloat(config.y)*this.scale) + (parseFloat(config.h)*this.scale)), this.compatibilitySize((parseFloat(config.x)*this.scale)), this.compatibilitySize((parseFloat(config.y)*this.scale)), this.compatibilitySize(border_radius));
			this.ctx.arcTo(this.compatibilitySize((parseFloat(config.x)*this.scale)), this.compatibilitySize((parseFloat(config.y)*this.scale)), this.compatibilitySize((parseFloat(config.x)*this.scale) + (parseFloat(config.w)*this.scale)), this.compatibilitySize((parseFloat(config.y)*this.scale)), this.compatibilitySize(border_radius));
			this.ctx.closePath();
			this.ctx.strokeStyle = config.color || config.border_color; // 设置绘制边框的颜色
			this.ctx.stroke();
			this.ctx.save()
			this.ctx.clip();
		},
		/**
		 * Download network file
		 * @param {Object} url : download url
		 */
		downLoadNetworkFile(url){
			return new Promise((resolve,reject)=>{
				uni.downloadFile({
					url,
					success:(res)=>{
						if(res.statusCode == 200){
							resolve(res.tempFilePath)
						}else{
							reject("Download Image Fail:102")
						}
					},
					fail:(err)=>{
						reject("Download Image Fail:101")
					}
				})
			})
		},
		/**
		 * Save image to natice
		 * @param {Object} filePath : native imageUrl
		 */
		saveImage(filePath){
			return new Promise((resolve,reject)=>{
				if(!filePath){
					reject("FilePath cannot be null:101")
					return;
				}
				
				// #ifdef H5
					var createA = document.createElement("a");
					createA.download = filePath;
					createA.href = filePath;
					document.body.appendChild(createA);
					createA.click();
					createA.remove();
					resolve()
				// #endif
				
				// #ifndef H5
				uni.saveImageToPhotosAlbum({
					filePath: filePath,
					success:(res)=>{
						resolve(res)
					}, 
					fail:(err)=>{
						reject(err)
					}
				})
				// #endif
			})
		}
	}
}


 其中踩的坑需要总结一下:

1.创建 canvas 的绘图上下文CanvasContext 对象

在微信开发文档中的方法是wx.createCanvasContext,但是注意: 从基础库2.9.0开始已经停止维护了。所以这个接口不再继续使用。

微信小程序,使用canvas画图生成海报并保存手机相册_第2张图片

创建canvasContext对象需要使用一下方法:

                    const query = wx.createSelectorQuery();
                    query
                        .select("#shareImage") // canvas的id
                        .fields({
                            node: true,
                            size: true,
                        })
                        .exec((res) => {
                            let canvas = res[0].node;
                            const ctx = canvas.getContext("2d");
                         });

2.下载网络图片生成背景图时需要注意使用canvas.createImage()创建图片对象,并且在image.onload()中绘制图片ctx.drawImage()

 const bgImg = canvas.createImage();
 bgImg.src = image.path;
 bgImg.onload = () => {
       ctx.drawImage(bgImg, 0, 0, image.width, image.height);
 }

3.这个真是个大坑啊.......canvas.createImage()在个别机型(Iphone12)上创建的是空对象,但是在微信开发者工具或者别的机型上就是可以正常使用的。所以解决方法就是使用旧版API,同时旧版api和新版canvas 2d不能同时混用。

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