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
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:
1 parent
420210ffa0
commit
17de94d984
22 files changed
+1118
-125
No files matched your search
@@ -6,6 +6,7 @@ import {
|
||||
convertSwfToNitro,
|
||||
extractIconFromSwf,
|
||||
} from "./index";
|
||||
import type { NitroTextureFormat } from "./nitro-builder";
|
||||
|
||||
// Offloads the synchronous, CPU-heavy SWF→Nitro conversion (and icon
|
||||
// extraction) to a small pool of worker threads so the main Node event loop
|
||||
@@ -89,28 +90,39 @@ function pump() {
|
||||
|
||||
const CONVERSION_TIMEOUT_MS = 60_000;
|
||||
|
||||
function run(req: Omit<WorkerRequest, "id">): Promise<WorkerResponse> {
|
||||
// `Omit` does not distribute over a union, which would erase `textureFormat`
|
||||
// from the convert variant.
|
||||
type DistributiveOmit<T, K extends keyof never> = T extends unknown
|
||||
? Omit<T, K>
|
||||
: never;
|
||||
|
||||
function run(
|
||||
req: DistributiveOmit<WorkerRequest, "id">,
|
||||
): Promise<WorkerResponse> {
|
||||
if (!ensurePool()) {
|
||||
try {
|
||||
if (req.type === "convert") {
|
||||
// No pool available: run on the main thread. Icon extraction stays
|
||||
// synchronous; the conversion is async because it encodes a spritesheet.
|
||||
if (req.type === "icon") {
|
||||
try {
|
||||
return Promise.resolve({
|
||||
id: 0,
|
||||
ok: true,
|
||||
result: convertSwfToNitro(req.buffer, req.classname),
|
||||
icon: extractIconFromSwf(req.buffer, req.classname),
|
||||
});
|
||||
} catch (e) {
|
||||
return Promise.resolve({
|
||||
id: 0,
|
||||
ok: false,
|
||||
error: (e as Error).message,
|
||||
});
|
||||
}
|
||||
return Promise.resolve({
|
||||
id: 0,
|
||||
ok: true,
|
||||
icon: extractIconFromSwf(req.buffer, req.classname),
|
||||
});
|
||||
} catch (e) {
|
||||
return Promise.resolve({
|
||||
id: 0,
|
||||
ok: false,
|
||||
error: (e as Error).message,
|
||||
});
|
||||
}
|
||||
return convertSwfToNitro(req.buffer, req.classname, {
|
||||
textureFormat: req.textureFormat,
|
||||
}).then(
|
||||
(result) => ({ id: 0, ok: true, result }) as WorkerResponse,
|
||||
(e: Error) => ({ id: 0, ok: false, error: e.message }) as WorkerResponse,
|
||||
);
|
||||
}
|
||||
return new Promise<WorkerResponse>((resolve, reject) => {
|
||||
const job: Job = {
|
||||
@@ -153,13 +165,19 @@ function run(req: Omit<WorkerRequest, "id">): Promise<WorkerResponse> {
|
||||
export async function convertSwfToNitroAsync(
|
||||
buffer: Buffer,
|
||||
classname: string,
|
||||
options?: { textureFormat?: NitroTextureFormat },
|
||||
): Promise<ConversionResult> {
|
||||
const res = await run({ type: "convert", buffer, classname }).catch(
|
||||
() => null,
|
||||
);
|
||||
const res = await run({
|
||||
type: "convert",
|
||||
buffer,
|
||||
classname,
|
||||
textureFormat: options?.textureFormat,
|
||||
}).catch(() => null);
|
||||
if (res?.ok && res.result) return res.result;
|
||||
// Worker failed for this item — fall back to the main thread.
|
||||
return convertSwfToNitro(buffer, classname);
|
||||
return convertSwfToNitro(buffer, classname, {
|
||||
textureFormat: options?.textureFormat,
|
||||
});
|
||||
}
|
||||
|
||||
export async function extractIconFromSwfAsync(
|
||||
|
||||
Reference in new issue
Block a user