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