feat: add sharp-backed image rendering

This commit is contained in:
2026-09-08 17:49:39 -04:00
parent 1f5db91a6a
commit d964b4f0ac
2 changed files with 35 additions and 0 deletions
@@ -0,0 +1,21 @@
import sharp from 'sharp';
export function createSharpImageRenderer() {
return async function renderDerivative({ data, maxWidth }) {
if (!Buffer.isBuffer(data)) {
throw new TypeError('Image data must be provided as a Buffer.');
}
if (!Number.isInteger(maxWidth) || maxWidth <= 0) {
throw new TypeError('maxWidth must be a positive integer.');
}
const image = sharp(data);
const metadata = await image.metadata();
const targetWidth = metadata.width && metadata.width > maxWidth ? maxWidth : metadata.width ?? maxWidth;
const resized = targetWidth === metadata.width ? image : image.resize({ width: targetWidth, withoutEnlargement: true });
return resized.webp().toBuffer();
};
}
@@ -2,6 +2,7 @@ import assert from 'node:assert/strict';
import test from 'node:test'; import test from 'node:test';
import { createImageDerivativeProcessor } from '../../src/infrastructure/images/image-derivative-processor.js'; import { createImageDerivativeProcessor } from '../../src/infrastructure/images/image-derivative-processor.js';
import { createSharpImageRenderer } from '../../src/infrastructure/images/sharp-image-renderer.js';
test('renders and stores thumbnail, medium, and large derivatives in order', async () => { test('renders and stores thumbnail, medium, and large derivatives in order', async () => {
const renderedWidths = []; const renderedWidths = [];
@@ -36,4 +37,17 @@ test('requires a renderer implementation', () => {
() => createImageDerivativeProcessor({ imageStorage: {} }), () => createImageDerivativeProcessor({ imageStorage: {} }),
/renderer is required/, /renderer is required/,
); );
});
test('creates a sharp-backed WebP renderer that resizes to the requested maximum width', async () => {
const renderer = createSharpImageRenderer();
const data = Buffer.from(
'<svg xmlns="http://www.w3.org/2000/svg" width="1024" height="1024" viewBox="0 0 1024 1024"><rect width="1024" height="1024" fill="#ff0000"/></svg>',
);
const output = await renderer({ data, maxWidth: 320 });
assert.ok(output instanceof Buffer);
assert.ok(output.length > 0);
assert.equal(output.subarray(0, 4).toString('ascii'), 'RIFF');
assert.equal(output.subarray(8, 12).toString('ascii'), 'WEBP');
}); });