| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114 |
- // server/utils/textRegionCropper.js
- import sharp from 'sharp';
- import fse from 'fs-extra';
- import * as path from 'path';
- class TextRegionCropper {
- constructor() {
- this.logger = {
- info: (msg, ...args) => console.log(`✂️ [裁剪] ${msg}`, ...args),
- debug: (msg, ...args) => console.log(`🐛 [裁剪] ${msg}`, ...args),
- error: (msg, ...args) => console.error(`❌ [裁剪] ${msg}`, ...args)
- };
- // 确保裁剪调试目录存在
- this.cropDebugDir = path.join(process.cwd(), 'temp', 'crop_debug');
- fse.ensureDirSync(this.cropDebugDir);
- }
- async cropTextRegion(imageBuffer, box, regionIndex) {
- const timestamp = Date.now();
- 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;
- // 四边各扩大5像素
- const expandPixels = 5;
- const expandedLeft = Math.max(0, left - expandPixels);
- const expandedTop = Math.max(0, top - expandPixels);
- const expandedRight = Math.min(imgWidth - 1, right + expandPixels);
- const expandedBottom = Math.min(imgHeight - 1, bottom + expandPixels);
- const expandedWidth = expandedRight - expandedLeft;
- const expandedHeight = expandedBottom - expandedTop;
- if (expandedWidth <= 0 || expandedHeight <= 0) {
- this.logger.debug(`区域 ${regionIndex}: 无效的裁剪区域`);
- return null;
- }
- const croppedBuffer = await sharp(imageBuffer)
- .extract({
- left: Math.floor(expandedLeft),
- top: Math.floor(expandedTop),
- width: Math.floor(expandedWidth),
- height: Math.floor(expandedHeight)
- })
- .png()
- .toBuffer();
- // 保存裁剪后的图像用于调试
- const cropPath = path.join(this.cropDebugDir, `crop-${regionIndex}-${timestamp}.png`);
- await fse.writeFile(cropPath, croppedBuffer);
- this.logger.debug(`区域 ${regionIndex}: 裁剪 ${Math.floor(expandedWidth)}x${Math.floor(expandedHeight)} -> ${cropPath}`);
- return {
- buffer: croppedBuffer,
- boxInfo: {
- original: { left, top, right, bottom, width: originalWidth, height: originalHeight },
- expanded: {
- left: expandedLeft,
- top: expandedTop,
- right: expandedRight,
- bottom: expandedBottom,
- width: expandedWidth,
- height: expandedHeight
- }
- }
- };
- } catch (error) {
- this.logger.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;
|