( imageBuffer: Buffer, maxBytes: number = IMAGE_TARGET_RAW_SIZE, originalMediaType?: string, )
| 511 | * This ensures images fit within context windows while maintaining format when possible. |
| 512 | */ |
| 513 | export async function compressImageBuffer( |
| 514 | imageBuffer: Buffer, |
| 515 | maxBytes: number = IMAGE_TARGET_RAW_SIZE, |
| 516 | originalMediaType?: string, |
| 517 | ): Promise<CompressedImageResult> { |
| 518 | // Extract format from originalMediaType if provided (e.g., "image/png" -> "png") |
| 519 | const fallbackFormat = originalMediaType?.split('/')[1] || 'jpeg' |
| 520 | const normalizedFallback = fallbackFormat === 'jpg' ? 'jpeg' : fallbackFormat |
| 521 | |
| 522 | try { |
| 523 | const sharp = await getImageProcessor() |
| 524 | const metadata = await sharp(imageBuffer).metadata() |
| 525 | const format = metadata.format || normalizedFallback |
| 526 | const originalSize = imageBuffer.length |
| 527 | |
| 528 | const context: ImageCompressionContext = { |
| 529 | imageBuffer, |
| 530 | metadata, |
| 531 | format, |
| 532 | maxBytes, |
| 533 | originalSize, |
| 534 | } |
| 535 | |
| 536 | // If image is already within size limit, return as-is without processing |
| 537 | if (originalSize <= maxBytes) { |
| 538 | return createCompressedImageResult(imageBuffer, format, originalSize) |
| 539 | } |
| 540 | |
| 541 | // Try progressive resizing with format preservation |
| 542 | const resizedResult = await tryProgressiveResizing(context, sharp) |
| 543 | if (resizedResult) { |
| 544 | return resizedResult |
| 545 | } |
| 546 | |
| 547 | // For PNG, try palette optimization |
| 548 | if (format === 'png') { |
| 549 | const palettizedResult = await tryPalettePNG(context, sharp) |
| 550 | if (palettizedResult) { |
| 551 | return palettizedResult |
| 552 | } |
| 553 | } |
| 554 | |
| 555 | // Try JPEG conversion with moderate compression |
| 556 | const jpegResult = await tryJPEGConversion(context, 50, sharp) |
| 557 | if (jpegResult) { |
| 558 | return jpegResult |
| 559 | } |
| 560 | |
| 561 | // Last resort: ultra-compressed JPEG |
| 562 | return await createUltraCompressedJPEG(context, sharp) |
| 563 | } catch (error) { |
| 564 | // Log the error and emit analytics event |
| 565 | logError(error as Error) |
| 566 | const errorType = classifyImageError(error) |
| 567 | const errorMsg = errorMessage(error) |
| 568 | logEvent('tengu_image_compress_failed', { |
| 569 | original_size_bytes: imageBuffer.length, |
| 570 | max_bytes: maxBytes, |
no test coverage detected