fix(svg-to-raster): use encodeHeic for HEIF output, fix preview decoding

Sharp cannot encode HEVC directly. Use encodeHeic() (same as convert tool)
to go through heif-enc. Also decode HEIF before Sharp for preview generation.
Hoist NON_PREVIEWABLE set to module scope.
This commit is contained in:
Siddharth Kumar Sah
2026-04-13 12:38:10 +08:00
parent 078b344660
commit 3713cf91fe
+11 -5
View File
@@ -4,9 +4,12 @@ import { basename, join } from "node:path";
import type { FastifyInstance } from "fastify"; import type { FastifyInstance } from "fastify";
import sharp from "sharp"; import sharp from "sharp";
import { z } from "zod"; import { z } from "zod";
import { decodeHeic, encodeHeic } from "../../lib/heic-converter.js";
import { sanitizeSvg } from "../../lib/svg-sanitize.js"; import { sanitizeSvg } from "../../lib/svg-sanitize.js";
import { createWorkspace } from "../../lib/workspace.js"; import { createWorkspace } from "../../lib/workspace.js";
const NON_PREVIEWABLE = new Set(["tiff", "heif"]);
const settingsSchema = z.object({ const settingsSchema = z.object({
width: z.number().min(1).max(16384).optional(), width: z.number().min(1).max(16384).optional(),
height: z.number().min(1).max(16384).optional(), height: z.number().min(1).max(16384).optional(),
@@ -113,10 +116,13 @@ export function registerSvgToRaster(app: FastifyInstance) {
buffer = await image.gif().toBuffer(); buffer = await image.gif().toBuffer();
ext = "gif"; ext = "gif";
break; break;
case "heif": case "heif": {
buffer = await image.heif({ quality: settings.quality }).toBuffer(); // Sharp cannot encode HEVC. Convert to PNG first, then use heif-enc.
const pngBuffer = await image.png().toBuffer();
buffer = await encodeHeic(pngBuffer, settings.quality);
ext = "heif"; ext = "heif";
break; break;
}
default: default:
buffer = await image.png().toBuffer(); buffer = await image.png().toBuffer();
ext = "png"; ext = "png";
@@ -129,12 +135,12 @@ export function registerSvgToRaster(app: FastifyInstance) {
const outputPath = join(workspacePath, "output", outFilename); const outputPath = join(workspacePath, "output", outFilename);
await writeFile(outputPath, buffer); await writeFile(outputPath, buffer);
// Generate browser-previewable thumbnail for non-browser formats
const NON_PREVIEWABLE = new Set(["tiff", "heif"]);
let previewUrl: string | undefined; let previewUrl: string | undefined;
if (NON_PREVIEWABLE.has(ext)) { if (NON_PREVIEWABLE.has(ext)) {
try { try {
const previewBuffer = await sharp(buffer) // Sharp can't decode HEVC-encoded HEIF; decode first
const previewInput = ext === "heif" ? await decodeHeic(buffer) : buffer;
const previewBuffer = await sharp(previewInput)
.resize(1200, 1200, { fit: "inside" }) .resize(1200, 1200, { fit: "inside" })
.webp({ quality: 80 }) .webp({ quality: 80 })
.toBuffer(); .toBuffer();