需求:需要根据背景图片和用户的微信的头像和昵称,生成一张海报图,并保存到手机相册。
首先在画布上绘制海报背景图片,然后再绘制头像、昵称和挑战成功的信息。
代码如下:
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开始已经停止维护了。所以这个接口不再继续使用。
创建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不能同时混用。