119 lines
3.4 KiB
JavaScript
119 lines
3.4 KiB
JavaScript
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;
|
||
}
|
||
});
|
||
}
|
||
} |