核心的 Canvas 繪圖引擎,實現複雜的形狀遮罩系統,包含圓形、圓角矩形的精確繪製和圖片縮放算法。
Core Canvas drawing engine implementing complex shape masking system with precise circle and rounded rectangle drawing and image scaling algorithms.
/**
* 在指定的 Canvas 上繪製帶形狀遮罩的圖片
* Draw image with shape mask on specified canvas
* @param {CanvasRenderingContext2D} ctx - Canvas 2D 上下文 (Canvas 2D context)
* @param {HTMLImageElement} img - 要繪製的圖片 (Image to draw)
* @param {number} size - 目標尺寸 (Target size)
*/
drawImageWithShape(ctx, img, size) {
// 設置 Canvas 尺寸 (Set canvas size)
ctx.canvas.width = size;
ctx.canvas.height = size;
// 清空畫布 (Clear canvas)
ctx.clearRect(0, 0, size, size);
// 設置背景 (Set background)
if (!this.transparentBg) {
ctx.fillStyle = this.currentBgColor;
ctx.fillRect(0, 0, size, size);
}
// 創建裁切路徑 (Create clipping path)
ctx.save();
switch (this.currentShape) {
case 'square':
// 方形:使用整個畫布 (Square: use entire canvas)
ctx.rect(0, 0, size, size);
break;
case 'rounded':
// 圓角矩形 (Rounded rectangle)
const radius = (this.currentRadius / 100) * (size / 2);
this.drawRoundedRect(ctx, 0, 0, size, size, radius);
break;
case 'circle':
// 圓形 (Circle)
ctx.beginPath();
ctx.arc(size / 2, size / 2, size / 2, 0, 2 * Math.PI);
break;
}
// 應用裁切 (Apply clipping)
ctx.clip();
// 計算圖片縮放和位置 (Calculate image scaling and position)
const { width: drawWidth, height: drawHeight, x: drawX, y: drawY } =
this.calculateImageDimensions(img, size);
// 繪製圖片 (Draw image)
ctx.drawImage(img, drawX, drawY, drawWidth, drawHeight);
ctx.restore();
}
/**
* 繪製圓角矩形路徑
* Draw rounded rectangle path
* @param {CanvasRenderingContext2D} ctx - Canvas 2D 上下文
* @param {number} x - X 座標
* @param {number} y - Y 座標
* @param {number} width - 寬度
* @param {number} height - 高度
* @param {number} radius - 圓角半徑
*/
drawRoundedRect(ctx, x, y, width, height, radius) {
ctx.beginPath();
ctx.moveTo(x + radius, y);
ctx.lineTo(x + width - radius, y);
ctx.quadraticCurveTo(x + width, y, x + width, y + radius);
ctx.lineTo(x + width, y + height - radius);
ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);
ctx.lineTo(x + radius, y + height);
ctx.quadraticCurveTo(x, y + height, x, y + height - radius);
ctx.lineTo(x, y + radius);
ctx.quadraticCurveTo(x, y, x + radius, y);
ctx.closePath();
}
/**
* 計算圖片在目標尺寸中的縮放和位置
* Calculate image scaling and position within target size
* @param {HTMLImageElement} img - 圖片元素 (Image element)
* @param {number} targetSize - 目標尺寸 (Target size)
* @returns {Object} 包含 width, height, x, y 的物件 (Object containing width, height, x, y)
*/
calculateImageDimensions(img, targetSize) {
const imgAspectRatio = img.width / img.height;
let drawWidth, drawHeight;
// 計算縮放比例以填滿整個區域 (Calculate scale to fill entire area)
if (imgAspectRatio > 1) {
// 寬圖:以高度為準 (Wide image: base on height)
drawHeight = targetSize;
drawWidth = targetSize * imgAspectRatio;
} else {
// 高圖:以寬度為準 (Tall image: base on width)
drawWidth = targetSize;
drawHeight = targetSize / imgAspectRatio;
}
// 計算居中位置 (Calculate centered position)
const drawX = (targetSize - drawWidth) / 2;
const drawY = (targetSize - drawHeight) / 2;
return { width: drawWidth, height: drawHeight, x: drawX, y: drawY };
}
/**
* 生成所有尺寸的預覽
* Generate previews for all sizes
*/
generatePreviews() {
if (!this.currentImage) return;
// 生成主預覽(200x200)(Generate main preview 200x200)
this.generateMainPreview();
// 生成多尺寸預覽 (Generate multi-size previews)
this.generateMultiSizePreviews();
}
/**
* 生成主預覽
* Generate main preview
*/
generateMainPreview() {
const canvas = document.querySelector('.main-preview-canvas');
if (!canvas) return;
const ctx = canvas.getContext('2d');
this.drawImageWithShape(ctx, this.currentImage, 200);
}
/**
* 生成多尺寸預覽
* Generate multi-size previews
*/
generateMultiSizePreviews() {
const container = document.querySelector('.size-previews');
if (!container) return;
// 清空現有預覽 (Clear existing previews)
container.innerHTML = '';
// 為每個尺寸生成預覽 (Generate preview for each size)
this.sizes.forEach(size => {
const previewItem = document.createElement('div');
previewItem.className = 'size-preview-item';
const canvas = document.createElement('canvas');
canvas.width = Math.min(size, 64); // 預覽最大顯示 64px
canvas.height = Math.min(size, 64);
canvas.className = 'size-preview-canvas';
canvas.dataset.size = size;
const label = document.createElement('span');
label.className = 'size-label';
label.textContent = `${size}×${size}`;
const ctx = canvas.getContext('2d');
this.drawImageWithShape(ctx, this.currentImage, Math.min(size, 64));
previewItem.appendChild(canvas);
previewItem.appendChild(label);
container.appendChild(previewItem);
});
}