feat(nitro): convert imported SWF bundles to WebP Lossless
Gitea Actions Runner Test / test-job (push) Successful in 1s
CI / check (push) Successful in 31s
CI / tests-unit (push) Successful in 1m59s
CI / tests-integration (push) Successful in 2m19s
CI / tests-ui (push) Successful in 2m56s
CI / preflight (push) Skipped
CI / deploy (push) Successful in 2m18s

Newly converted .nitro bundles now store their spritesheet as WebP VP8L
instead of PNG, so imports land much smaller without changing a single
pixel. The texture member and spritesheet.meta.image are both labelled
from the actual bytes, never from a caller's assumption.

- encode through sharp with lossless and exact, so colour hidden under
  alpha 0 survives; this mirrors ImageSharp's TransparentColorMode.Preserve
- detect PNG/WebP by magic bytes and reject anything the client cannot
  render, on create, download and upload paths
- keep the source format when deriving size-32 sheets, scaling composites
  and editing metadata, so existing bundles are never silently rewritten
- report fidelity in the studio: the compression panel re-encodes with the
  same options the importer uses, so it cannot drift and invent false
  warnings, and shows PNG/WebP size estimates

convertSwfToNitro and buildSpritesheet are now async, so the worker, the
main-thread fallback and every import call site await them. PNG stays
supported for existing bundles and icon sidecars are untouched.
This commit is contained in:
openhands committed 2026-09-27 15:42:21 +02:00
1 parent 420210ffa0
commit 17de94d984
22 files changed
+1118 -125

No files matched your search

+18 -5
View File
@@ -34,7 +34,8 @@ export function nitroHasSize32(json: Record<string, unknown>): boolean {
export async function addSize32ToBundle(
buffer: Buffer,
): Promise<Buffer | null> {
const { json, png, jsonFileName } = parseNitroBundle(buffer);
const { json, texture, textureFormat, jsonFileName } =
parseNitroBundle(buffer);
if (nitroHasSize32(json)) return null;
const name = (json.name as string) || jsonFileName.replace(/\.json$/, "");
@@ -68,7 +69,7 @@ export async function addSize32ToBundle(
for (const [frameName, frame] of Object.entries(frames)) {
const f = frame.frame;
if (!f || f.w <= 0 || f.h <= 0) continue;
const { data, info } = await sharp(png)
const { data, info } = await sharp(texture)
.extract({ left: f.x, top: f.y, width: f.w, height: f.h })
.raw()
.ensureAlpha()
@@ -95,7 +96,7 @@ export async function addSize32ToBundle(
const f = frame.frame;
if (f.w <= 0 || f.h <= 0) continue;
const src = await sharp(png)
const src = await sharp(texture)
.extract({ left: f.x, top: f.y, width: f.w, height: f.h })
.raw()
.ensureAlpha()
@@ -131,7 +132,12 @@ export async function addSize32ToBundle(
if (Object.keys(newAssets).length === 0) return null;
const { png: newPng, frames: newFrames } = buildSpritesheet(images);
// Preserve the source bundle's texture format: regenerating a scale must
// not silently downgrade a WebP bundle back to PNG.
const { texture: newTexture, frames: newFrames } = await buildSpritesheet(
images,
{ format: textureFormat },
);
// Update existing frame coordinates (sheet was rebuilt).
for (const [frameName, frame] of Object.entries(frames)) {
@@ -171,5 +177,12 @@ export async function addSize32ToBundle(
vis.push({ ...sourceVis, size: 32 });
}
return createNitroBundle(json, newPng, name);
// Keep the client-facing image pointer aligned with the member
// `createNitroBundle` is about to write.
const sheet = (json.spritesheet ?? {}) as Record<string, unknown>;
const meta = (sheet.meta ?? {}) as Record<string, unknown>;
sheet.meta = { ...meta, image: `${name}.${textureFormat}` };
json.spritesheet = sheet;
return createNitroBundle(json, newTexture, name);
}