现象
以下是保存画布到文件的函数:
export function exportcanvas(canvas) {
wx.canvasToTempFilePath({ //将canvas生成图片
canvas: canvas,
x: 0,
y: 0,
width: canvas.width,
height: canvas.height,
destWidth: canvas.width,
destHeight: canvas.height,
success: function (res) {
wx.saveImageToPhotosAlbum({ //保存图片到相册
filePath: res.tempFilePath,
success: function () {
wx.showToast({
title: "生成图片成功!",
duration: 1800
});
}
})
},
fail: function (res) {
console.log(res)
}
});
}
在手机端一切正常,但是Windows端出现问题:只截取了原图的一部分进行保存,如下图所示:
为了不占空间我对上图的高度进行了裁剪。但从宽度上明显看出缺了一部分。此外,残缺的图的长宽比和canvas一致(因此后文只提宽度不提高度)。导出的图片的宽度和设置的宽度也不一样,大了不少。
实验
这个问题n年了微信官方还是没解决。
一开始看到“微信开放社区”内有人的解决办法是除以pixelRatio。但是没有用。于是我把屏幕信息打印了出来:screenWidth=414,pixelRatio=1.4479。
原图的宽度是800像素。然后脑抽以为ctx也有width属性(实际上没有),于是把destWidth和destHeight设置为了ctx.width和ctx.height(为undefined,实际上相当于没传),结果发现也能导出,图片内容和之前一样(相同的残缺),只是图片宽度为599,而之前有1158。我从手机上获取了正常导出的图片,发现本次PC端导出的图片截取的部分,就是正常图片的前599像素(从宽度上来看)。换了别的图片(宽度均超过599),得到的也都是截取前599像素的图片。这个发现告诉我:截取的内容大小相对于canvas固定,都是599像素。
我还是把destWidth和destHeight换成了canvas.width和canvas.height。但是输入不同尺寸的图,导出的大小也不一样,但都比我设置的尺寸大。那有什么共性呢?于是我发现1158=800*1.4479,即:导出的实际宽度=设定宽度*pixelRatio。要让导出的和设定的一样,只要提前除以pixelRatio即可。这样输出就解决了。
我试着改变截取的宽度和高度,小了确实在截取,大了(即使超过画布大小)则锁定截取599像素。那不同的电脑输出的是否相同呢?如果相同,只要缩放画布内容即可;如果不同,那关系是什么?结果是:不同。另一台电脑上,只截取517像素。于是发现了关系:414(屏幕宽度)*1.4479(像素比)=599(实际截取的宽度),这就是实际可以导出的画布的最大宽度。
所以导出前要把内容缩放到这个“最大宽度”内。但是微信小程序不能凭空创建画布,offscreencanvas又是各种bug,所以选择了较为崎岖的一条路:导出为ImageData,转换为我的图片的数据结构,进行缩放,然后转回ImageData,绘制到屏幕上。当然可以直接drawImage在左上角画一个小图,但是这样周围有一圈不好看。
解决
需要注意的是有个延时,因为发现putImageData不会立即更新画布内容。jsPic是我的图片库。代码如下:
async function download() {
let cW = c.width;
let cH = c.height;
let dstW = c.width;
let dstH = c.height;
// PC系统要单独处理
if (wx.getDeviceInfo().system.toLowerCase().startsWith("windows")) {
const win = wx.getWindowInfo();
const pixR = win.pixelRatio;
const realW = win.screenWidth * pixR; // 实际截取的宽度
// 保证输出的尺寸正确
dstW /= pixR;
dstH /= pixR;
// 保证输入的尺寸正确 对原图进行缩放
const imgdata = ctx.getImageData(0, 0, c.width, c.height);
const jspic = (new jsPic().fromImageData(imgdata, 'RGB')).resize(realW);
data2canvas(jspic.toImageData(), ctx, c);
// 不延时不会及时反馈
await new Promise((resolve) => setTimeout(resolve, 100));
}
wx.canvasToTempFilePath({ //将canvas生成图片
canvas: c,
x: 0,
y: 0,
width: cW,
height: cH,
destWidth: dstW,
destHeight: dstH,
success: function (res) {
wx.saveImageToPhotosAlbum({ //保存图片到相册
filePath: res.tempFilePath,
success: function () {
wx.showToast({
title: "生成图片成功!",
duration: 1800
});
}
})
},
fail: function (res) {
console.log(res)
}
});
}
function data2canvas(imgdata, ctx, c) {
c.width = imgdata.width;
c.height = imgdata.height;
ctx.putImageData(imgdata, 0, 0);
}
嗯,成一坨屎山了。谢谢你,微信。