textRegionCropper.js 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127
  1. // server/utils/textRegionCropper.js
  2. import sharp from 'sharp';
  3. class TextRegionCropper {
  4. constructor() {
  5. // 可以在这里添加配置参数
  6. }
  7. async cropTextRegion(imageBuffer, box, regionIndex) {
  8. try {
  9. const metadata = await sharp(imageBuffer).metadata();
  10. const imgWidth = metadata.width;
  11. const imgHeight = metadata.height;
  12. const left = Math.min(box.x1, box.x2, box.x3, box.x4);
  13. const top = Math.min(box.y1, box.y2, box.y3, box.y4);
  14. const right = Math.max(box.x1, box.x2, box.x3, box.x4);
  15. const bottom = Math.max(box.y1, box.y2, box.y3, box.y4);
  16. const originalWidth = right - left;
  17. const originalHeight = bottom - top;
  18. // 减少扩展,避免引入过多背景
  19. const widthExpand = 10;
  20. const heightExpand = 10;
  21. const newWidth = originalWidth + widthExpand;
  22. const newHeight = originalHeight + heightExpand;
  23. const centerX = (left + right) / 2;
  24. const centerY = (top + bottom) / 2;
  25. const expandedLeft = Math.max(0, centerX - newWidth / 2);
  26. const expandedTop = Math.max(0, centerY - newHeight / 2);
  27. const expandedRight = Math.min(imgWidth - 1, centerX + newWidth / 2);
  28. const expandedBottom = Math.min(imgHeight - 1, centerY + newHeight / 2);
  29. const finalWidth = expandedRight - expandedLeft;
  30. const finalHeight = expandedBottom - expandedTop;
  31. if (finalWidth <= 0 || finalHeight <= 0) {
  32. console.log(`❌ 区域 ${regionIndex}: 无效的裁剪区域`);
  33. return null;
  34. }
  35. let adjustedLeft = expandedLeft;
  36. let adjustedTop = expandedTop;
  37. let adjustedWidth = finalWidth;
  38. let adjustedHeight = finalHeight;
  39. if (expandedLeft < 0) {
  40. adjustedLeft = 0;
  41. adjustedWidth = expandedRight;
  42. }
  43. if (expandedTop < 0) {
  44. adjustedTop = 0;
  45. adjustedHeight = expandedBottom;
  46. }
  47. if (expandedRight > imgWidth) {
  48. adjustedWidth = imgWidth - adjustedLeft;
  49. }
  50. if (expandedBottom > imgHeight) {
  51. adjustedHeight = imgHeight - adjustedTop;
  52. }
  53. const croppedBuffer = await sharp(imageBuffer)
  54. .extract({
  55. left: Math.floor(adjustedLeft),
  56. top: Math.floor(adjustedTop),
  57. width: Math.floor(adjustedWidth),
  58. height: Math.floor(adjustedHeight)
  59. })
  60. .png()
  61. .toBuffer();
  62. console.log(`✂️ 区域 ${regionIndex}: 裁剪 ${Math.floor(adjustedWidth)}x${Math.floor(adjustedHeight)}`);
  63. return {
  64. buffer: croppedBuffer,
  65. boxInfo: {
  66. original: { left, top, right, bottom, width: originalWidth, height: originalHeight },
  67. expanded: {
  68. left: adjustedLeft,
  69. top: adjustedTop,
  70. right: adjustedLeft + adjustedWidth,
  71. bottom: adjustedTop + adjustedHeight,
  72. width: adjustedWidth,
  73. height: adjustedHeight
  74. }
  75. }
  76. };
  77. } catch (error) {
  78. console.error(`❌ 区域 ${regionIndex}: 裁剪失败`, error);
  79. return null;
  80. }
  81. }
  82. async rotateImage(imageBuffer, degrees) {
  83. return await sharp(imageBuffer)
  84. .rotate(degrees)
  85. .png()
  86. .toBuffer();
  87. }
  88. calculateExpansion(originalWidth, originalHeight, expansionFactor = 1.2) {
  89. return {
  90. width: originalWidth * expansionFactor,
  91. height: originalHeight * expansionFactor
  92. };
  93. }
  94. validateCropRegion(left, top, width, height, imgWidth, imgHeight) {
  95. if (width <= 0 || height <= 0) {
  96. return false;
  97. }
  98. if (left < 0 || top < 0) {
  99. return false;
  100. }
  101. if (left + width > imgWidth || top + height > imgHeight) {
  102. return false;
  103. }
  104. return true;
  105. }
  106. }
  107. export default TextRegionCropper;