refactor(attachment-upload): switch image resizer to lossless PNG for vision analysis
This commit is contained in:
@@ -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) },
|
||||||
|
|||||||
Reference in New Issue
Block a user