需求是,小程序里面需要给用户上传的图片配上文字,最下面再打上我们自己的 logo。
步骤基本是
- 先创建一个 Canvas
<Canvas
type="2d"
id="posterCanvas"
style={{
width: `${posterWidth}px`,
height: `${posterHeight}px`,
position: "fixed",
left: "-9999px",
top: "-9999px",
}}
/>
- 然后是绘制 Canvas
依次把上传的图片缩放到 Canvas 指定的大小区域。注意 Canvas 是以左上为起点(0, 0),然后开始绘制。
至于图片的清晰度,需要const dpr = Taro.getSystemInfoSync().pixelRatio; 获取到设备的像素比,然后 scale 出来,否则绘制上去的图片清晰度有问题。
依次绘制图片,描述和 logo。
const padding = 32;
const designWidth = 750;
const posterWidth = designWidth - padding * 2;
const posterHeight = 1010;
const imageHeight = 760;
const logoUrl = "https://xxx.com/public/logo.png";
const drawPoster = async () => {
return new Promise<void>((resolve, reject) => {
Taro.createSelectorQuery()
.select("#posterCanvas")
.fields({ node: true, size: true })
.exec(async (res) => {
if (!res[0] || !res[0].node) {
reject(new Error("Canvas not found"));
return;
}
const canvas = res[0].node;
const ctx = canvas.getContext("2d");
const width = posterWidth;
const height = posterHeight;
const dpr = Taro.getSystemInfoSync().pixelRatio;
// Set canvas dimensions
canvas.width = width * dpr;
canvas.height = height * dpr;
ctx.scale(dpr, dpr);
// Draw background
ctx.fillStyle = "#ffffff";
ctx.fillRect(0, 0, width, height);
try {
// Draw Photo
if (photos.length > 0 && photos[0].path) {
const photoSrc = photos[0].path;
const photoInfo = await Taro.getImageInfo({ src: photoSrc });
const photoImg = canvas.createImage();
photoImg.src = photoInfo.path;
await new Promise((resolveImg) => {
photoImg.onload = resolveImg;
photoImg.onerror = reject;
});
const targetWidth = posterWidth - padding * 2;
const targetHeight = imageHeight;
const startX = (width - targetWidth) / 2;
const startY = padding;
// Draw Image (Aspect Fit)
const imgRatio = photoInfo.width / photoInfo.height;
const targetRatio = targetWidth / targetHeight;
let drawWidth, drawHeight, drawX, drawY;
if (imgRatio > targetRatio) {
// Image is wider than target
drawWidth = targetWidth;
drawHeight = targetWidth / imgRatio;
drawX = startX;
drawY = startY + (targetHeight - drawHeight) / 2;
} else {
// Image is taller than target
drawHeight = targetHeight;
drawWidth = targetHeight * imgRatio;
drawX = startX + (targetWidth - drawWidth) / 2;
drawY = startY;
}
ctx.drawImage(photoImg, drawX, drawY, drawWidth, drawHeight);
}
// Draw Description
if (photos.length > 0 && photos[0].description) {
ctx.font = "24px sans-serif"; // Adjust size as needed
ctx.fillStyle = "rgba(0, 0, 0, 0.44)";
ctx.textBaseline = "top";
const text = photos[0].description;
const textX = padding;
const textY = imageHeight + padding * 2;
const maxWidth = posterWidth - padding * 2;
const lineHeight = 40;
// Simple text wrapping
const words = text.split("");
let line = "";
let y = textY;
for (let n = 0; n < words.length; n++) {
const testLine = line + words[n];
const metrics = ctx.measureText(testLine);
const testWidth = metrics.width;
if (testWidth > maxWidth && n > 0) {
ctx.fillText(line, textX, y);
line = words[n];
y += lineHeight;
} else {
line = testLine;
}
}
ctx.fillText(line, textX, y);
}
// Draw Logo
const logoInfo = await Taro.getImageInfo({ src: logoUrl });
const logoImg = canvas.createImage();
logoImg.src = logoInfo.path;
await new Promise((resolveLogo) => {
logoImg.onload = resolveLogo;
logoImg.onerror = reject;
});
const logoWidth = 225;
const logoHeight = 43;
const logoX = (width - logoWidth) / 2;
const logoY = height - 32 - logoHeight;
ctx.drawImage(logoImg, logoX, logoY, logoWidth, logoHeight);
resolve();
} catch (error) {
reject(error);
}
});
});
};
- 处理下载
下载需要把 Canvas 先导成临时文件,然后再调用saveImageToPhotosAlbum即可。
const saveCanvasToAlbum = () => {
return new Promise<void>((resolve, reject) => {
// Get canvas node again to convert to temp file path
Taro.createSelectorQuery()
.select("#posterCanvas")
.fields({ node: true, size: true })
.exec(async (res) => {
if (res[0] && res[0].node) {
const canvas = res[0].node;
const dpr = Taro.getSystemInfoSync().pixelRatio;
Taro.canvasToTempFilePath({
canvas,
destWidth: posterWidth * dpr,
destHeight: posterHeight * dpr,
success: (res) => {
Taro.saveImageToPhotosAlbum({
filePath: res.tempFilePath,
success: () => {
Taro.hideLoading();
Taro.showToast({
title: "已下载",
icon: "success",
});
resolve();
},
fail: (err) => {
Taro.hideLoading();
console.error("Save to album failed:", err);
// Handle permission denied specifically if needed
if (err.errMsg.includes("auth deny")) {
Taro.showToast({
title: "授权失败",
icon: "none",
});
} else {
Taro.showToast({
title: "保存失败",
icon: "none",
});
}
reject(err);
},
});
},
fail: (err) => {
Taro.hideLoading();
console.error("Canvas to temp file failed:", err);
Taro.showToast({
title: "生成失败",
icon: "none",
});
reject(err);
},
});
} else {
reject(new Error("Canvas not found"));
}
});
});
};
- 分享
同理,如果需要分享图片,调用showShareImageMenu即可。