127 行
4.3 KiB
JavaScript
127 行
4.3 KiB
JavaScript
|
|
// 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;
|