Files
faceFamilySource/src/libs/haibao.js
xiaoaojiao f59feef223 todolist
2025-09-14 22:18:52 +08:00

119 lines
3.4 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

export default class Haibao {
constructor(width, height, color = '#fff') {
this.canvas = document.createElement('canvas');
this.ctx = this.canvas.getContext('2d', { alpha: true });
this.canvas.width = width;
this.canvas.height = height;
// this.ctx.fillStyle = color;
this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height)
this.zIndex = 0
this.imagePromises = []
this.images = []; // 存储待合成的图片信息
}
/**
* 添加图片到合成队列现在返回Promise
* @param {string|HTMLImageElement} image 图片URL或Image对象
* @param {number} x 绘制X坐标
* @param {number} y 绘制Y坐标
* @returns {Promise} 图片加载完成的Promise
*/
add (image, x, y, width = 'auto', height = 'auto', index) {
this.zIndex++
const zIndex = index ? index : this.zIndex
const loadPromise = this._createLoadPromise(image).then(img => {
this.images.push({ img, x, y, width, height, zIndex });
});
this.imagePromises.push(loadPromise);
return loadPromise;
}
text (text, x, y, opts) {
const { font = '16px sans-serif', color = '#000', align = 'center' } = opts || {};
this.ctx.font = font;
this.ctx.fillStyle = color;
const drawText = (text, x, y) => {
this.ctx.fillText(text, x, y);
};
const totalWidth = this.ctx.measureText(text).width;
if (align === 'center') {
x -= totalWidth / 2;
}
drawText(text, x, y);
return this
}
async draw (mask = 'source-over') {
await Promise.all(this.imagePromises);
// 绘制所有图片
this.images.sort((a, b) => a.zIndex - b.zIndex)
this.images.forEach(({ img, x, y, width, height, zIndex }, idx) => {
const w = width === 'auto' ? img.width : width
const h = height === 'auto' ? img.height : height
this.ctx.drawImage(img, x, y, w, h);
if (idx === 0) {
this.ctx.globalCompositeOperation = mask
} else {
this.ctx.globalCompositeOperation = 'source-over'
}
});
return this;
}
/**
* 生成合成后的图片返回Promise
* @returns {Promise<string>} 合成后的Base64图片数据
*/
async generate ({ mimeType = 'image/jpeg', quality = .8 }) {
const validTypes = ['image/png', 'image/jpeg', 'image/webp'];
mimeType = validTypes.includes(mimeType) ? mimeType : 'image/png';
quality = Math.min(1, Math.max(0, Number(quality))) || .8;
const b64 = this.canvas.toDataURL(mimeType, quality)
this.images.length = 0;
this.imagePromises.length = 0;
return b64
}
// 创建加载Promise私有方法
_createLoadPromise (image) {
if (typeof image === 'string') {
return this._loadImageFromUrl(image);
} else if (image instanceof HTMLImageElement) {
return this._handleExistingImage(image);
}
return Promise.reject(new Error('Invalid image type'));
}
// 从URL加载图片
_loadImageFromUrl (url) {
return new Promise((resolve, reject) => {
const img = new Image();
img.crossOrigin = 'anonymous';
img.src = url;
img.onload = () => resolve(img);
img.onerror = reject;
});
}
// 处理已存在的Image对象
_handleExistingImage (img) {
return new Promise((resolve, reject) => {
if (img.complete && img.naturalHeight !== 0) {
resolve(img);
} else {
img.onload = () => resolve(img);
img.onerror = reject;
}
});
}
}