refactor(attachment-upload): switch image resizer to lossless PNG for vision analysis

This commit is contained in:
MythEclipse
2026-06-03 19:25:55 +07:00
parent ac678e8a88
commit dc78faa65d
@@ -4,10 +4,10 @@ import { createChildLogger } from "@bete/shared/logger";
const log = createChildLogger("imageResizer"); const log = createChildLogger("imageResizer");
/** /**
* Resize an image buffer for optimal vision LLM analysis. * Prepare an image buffer for optimal vision LLM analysis.
* *
* - Resizes to maxDim x maxDim maintaining aspect ratio * - Resizes to maxDim x maxDim maintaining aspect ratio (only if larger)
* - Converts to JPEG at quality 85 for size reduction * - Converts to PNG (lossless) to preserve full image detail
* - Falls back to original buffer if sharp fails * - Falls back to original buffer if sharp fails
* *
* @param buf - Raw image buffer * @param buf - Raw image buffer
@@ -22,31 +22,33 @@ export async function resizeImageForVision(
const metadata = await sharp(buf).metadata(); const metadata = await sharp(buf).metadata();
const inputFormat = metadata.format ?? "jpeg"; const inputFormat = metadata.format ?? "jpeg";
// Skip resize if already smaller than maxDim // Skip resize entirely if already within max dimension
if ((metadata.width ?? 0) <= maxDim && (metadata.height ?? 0) <= maxDim) { if ((metadata.width ?? 0) <= maxDim && (metadata.height ?? 0) <= maxDim) {
return { data: buf, mimeType: `image/${inputFormat}` }; return { data: buf, mimeType: `image/${inputFormat}` };
} }
// Resize dimension only — convert to PNG lossless to preserve detail
const resized = await sharp(buf) const resized = await sharp(buf)
.resize(maxDim, maxDim, { .resize(maxDim, maxDim, {
fit: "inside", fit: "inside",
withoutEnlargement: true, withoutEnlargement: true,
}) })
.jpeg({ quality: 85 }) .png()
.toBuffer(); .toBuffer();
log.debug( log.debug(
{ {
originalSize: buf.length, originalSize: buf.length,
originalFormat: inputFormat,
resizedSize: resized.length, resizedSize: resized.length,
reductionPct: Math.round( reductionPct: Math.round(
((buf.length - resized.length) / buf.length) * 100, ((buf.length - resized.length) / buf.length) * 100,
), ),
}, },
"Image resized for vision analysis", "Image resized for vision analysis (lossless PNG)",
); );
return { data: resized, mimeType: "image/jpeg" }; return { data: resized, mimeType: "image/png" };
} catch (error) { } catch (error) {
log.warn( log.warn(
{ error: error instanceof Error ? error.message : String(error) }, { error: error instanceof Error ? error.message : String(error) },