// server/utils/textRegionCropper.js import sharp from 'sharp'; class TextRegionCropper { constructor() { // 可以在这里添加配置参数 } async cropTextRegion(imageBuffer, box, regionIndex) { try { const metadata = await sharp(imageBuffer).metadata(); const imgWidth = metadata.width; const imgHeight = metadata.height; const left = Math.min(box.x1, box.x2, box.x3, box.x4); const top = Math.min(box.y1, box.y2, box.y3, box.y4); const right = Math.max(box.x1, box.x2, box.x3, box.x4); const bottom = Math.max(box.y1, box.y2, box.y3, box.y4); const originalWidth = right - left; const originalHeight = bottom - top; // 减少扩展,避免引入过多背景 const widthExpand = 10; const heightExpand = 10; const newWidth = originalWidth + widthExpand; const newHeight = originalHeight + heightExpand; const centerX = (left + right) / 2; const centerY = (top + bottom) / 2; const expandedLeft = Math.max(0, centerX - newWidth / 2); const expandedTop = Math.max(0, centerY - newHeight / 2); const expandedRight = Math.min(imgWidth - 1, centerX + newWidth / 2); const expandedBottom = Math.min(imgHeight - 1, centerY + newHeight / 2); const finalWidth = expandedRight - expandedLeft; const finalHeight = expandedBottom - expandedTop; if (finalWidth <= 0 || finalHeight <= 0) { console.log(`❌ 区域 ${regionIndex}: 无效的裁剪区域`); return null; } let adjustedLeft = expandedLeft; let adjustedTop = expandedTop; let adjustedWidth = finalWidth; let adjustedHeight = finalHeight; if (expandedLeft < 0) { adjustedLeft = 0; adjustedWidth = expandedRight; } if (expandedTop < 0) { adjustedTop = 0; adjustedHeight = expandedBottom; } if (expandedRight > imgWidth) { adjustedWidth = imgWidth - adjustedLeft; } if (expandedBottom > imgHeight) { adjustedHeight = imgHeight - adjustedTop; } const croppedBuffer = await sharp(imageBuffer) .extract({ left: Math.floor(adjustedLeft), top: Math.floor(adjustedTop), width: Math.floor(adjustedWidth), height: Math.floor(adjustedHeight) }) .png() .toBuffer(); console.log(`✂️ 区域 ${regionIndex}: 裁剪 ${Math.floor(adjustedWidth)}x${Math.floor(adjustedHeight)}`); return { buffer: croppedBuffer, boxInfo: { original: { left, top, right, bottom, width: originalWidth, height: originalHeight }, expanded: { left: adjustedLeft, top: adjustedTop, right: adjustedLeft + adjustedWidth, bottom: adjustedTop + adjustedHeight, width: adjustedWidth, height: adjustedHeight } } }; } catch (error) { console.error(`❌ 区域 ${regionIndex}: 裁剪失败`, error); return null; } } async rotateImage(imageBuffer, degrees) { return await sharp(imageBuffer) .rotate(degrees) .png() .toBuffer(); } calculateExpansion(originalWidth, originalHeight, expansionFactor = 1.2) { return { width: originalWidth * expansionFactor, height: originalHeight * expansionFactor }; } validateCropRegion(left, top, width, height, imgWidth, imgHeight) { if (width <= 0 || height <= 0) { return false; } if (left < 0 || top < 0) { return false; } if (left + width > imgWidth || top + height > imgHeight) { return false; } return true; } } export default TextRegionCropper;