feat: add PPM/PGM/PBM CLI decoder fallback, QOI fixture and format-matrix entry

- Add ppm/pgm/pbm to CLI_DECODED_FORMATS with Sharp-first, ImageMagick fallback
- Create sample.qoi test fixture (10x10 solid color)
- Add QOI to format-matrix test FORMAT_SAMPLES
- Mark PPM/PGM/PBM as needsCliDecoder in tests (Sharp doesn't support them natively)
This commit is contained in:
SnapOtter
2026-05-08 15:12:23 +08:00
parent bb6d26ffc6
commit 44608733c6
3 changed files with 43 additions and 3 deletions
+32
View File
@@ -24,6 +24,9 @@ const CLI_DECODED_FORMATS = new Set([
"dds",
"cur",
"dpx",
"ppm",
"pgm",
"pbm",
"fits",
]);
@@ -78,6 +81,11 @@ export async function decodeToSharpCompat(
return decodeFits(buffer);
case "qoi":
return decodeQoi(buffer);
case "ppm":
case "pgm":
case "pbm":
return decodeNetpbm(buffer, format);
return decodeQoi(buffer);
default:
return buffer;
}
@@ -452,3 +460,27 @@ async function decodeQoi(buffer: Buffer): Promise<Buffer> {
.png()
.toBuffer();
}
// ── Netpbm (PPM/PGM/PBM) decoder ──
async function decodeNetpbm(buffer: Buffer, format: string): Promise<Buffer> {
try {
return await sharp(buffer).png().toBuffer();
} catch {
const cmd = await findMagickCmd();
const id = randomUUID();
const ext = format === "pgm" ? "pgm" : format === "pbm" ? "pbm" : "ppm";
const inputPath = join(tmpdir(), `netpbm-in-${id}.${ext}`);
const outputPath = join(tmpdir(), `netpbm-out-${id}.png`);
try {
await writeFile(inputPath, buffer);
await execFileAsync(cmd, magickArgs(cmd, [inputPath, `png:${outputPath}`]), {
timeout: 120_000,
});
return await readFile(outputPath);
} finally {
await rm(inputPath, { force: true }).catch(() => {});
await rm(outputPath, { force: true }).catch(() => {});
}
}
}
Binary file not shown.
+11 -3
View File
@@ -207,7 +207,7 @@ const FORMAT_SAMPLES: FormatSample[] = [
name: "PPM",
file: "sample.ppm",
mime: "image/x-portable-pixmap",
needsCliDecoder: false,
needsCliDecoder: true,
needsHeifDecoder: false,
mayFailValidation: false,
},
@@ -215,7 +215,7 @@ const FORMAT_SAMPLES: FormatSample[] = [
name: "PGM",
file: "sample.pgm",
mime: "image/x-portable-graymap",
needsCliDecoder: false,
needsCliDecoder: true,
needsHeifDecoder: false,
mayFailValidation: false,
},
@@ -223,7 +223,7 @@ const FORMAT_SAMPLES: FormatSample[] = [
name: "PBM",
file: "sample.pbm",
mime: "image/x-portable-bitmap",
needsCliDecoder: false,
needsCliDecoder: true,
needsHeifDecoder: false,
mayFailValidation: false,
},
@@ -267,6 +267,14 @@ const FORMAT_SAMPLES: FormatSample[] = [
needsHeifDecoder: false,
mayFailValidation: false,
},
{
name: "QOI",
file: "sample.qoi",
mime: "image/x-qoi",
needsCliDecoder: true,
needsHeifDecoder: false,
mayFailValidation: false,
},
];
// ---------------------------------------------------------------------------