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
@@ -45,7 +45,7 @@ export async function generateScale32(input: Buffer): Promise<{
|
||||
count: number;
|
||||
skipped: number;
|
||||
}> {
|
||||
const { json, png } = parseNitroBundle(input);
|
||||
const { json, texture, textureFormat } = parseNitroBundle(input);
|
||||
const assets = object(json.assets),
|
||||
sheet = object(json.spritesheet),
|
||||
frames = object(sheet.frames);
|
||||
@@ -67,7 +67,7 @@ export async function generateScale32(input: Buffer): Promise<{
|
||||
if (!names.length)
|
||||
throw Error("No supported scale 64 furniture sprites were found");
|
||||
if (names.length > 10000) throw Error("Too many sprites to generate safely");
|
||||
const dimensions = await sharp(png).metadata();
|
||||
const dimensions = await sharp(texture).metadata();
|
||||
if (!dimensions.width || !dimensions.height) throw Error("Invalid texture");
|
||||
const images: {
|
||||
id: number;
|
||||
@@ -103,7 +103,7 @@ export async function generateScale32(input: Buffer): Promise<{
|
||||
}
|
||||
const frame = resolved.frame,
|
||||
rect = object(frame.frame);
|
||||
let sprite = await sharp(png)
|
||||
let sprite = await sharp(texture)
|
||||
.extract({
|
||||
left: Number(rect.x),
|
||||
top: Number(rect.y),
|
||||
@@ -149,25 +149,35 @@ export async function generateScale32(input: Buffer): Promise<{
|
||||
});
|
||||
}
|
||||
if (!images.length) throw Error("No independent texture frames were found");
|
||||
const atlas = buildSpritesheet(images);
|
||||
const atlas = await buildSpritesheet(images, { format: textureFormat });
|
||||
const width = Math.max(dimensions.width, atlas.width),
|
||||
height = dimensions.height + atlas.height;
|
||||
if (width * height > 64_000_000)
|
||||
throw Error("Combined texture exceeds the texture budget");
|
||||
const combined = await sharp({
|
||||
// The merged sheet must keep the source bundle's texture format so an
|
||||
// existing PNG bundle is not silently converted (or vice versa).
|
||||
const composited = sharp({
|
||||
create: {
|
||||
width,
|
||||
height,
|
||||
channels: 4,
|
||||
background: { r: 0, g: 0, b: 0, alpha: 0 },
|
||||
},
|
||||
})
|
||||
.composite([
|
||||
{ input: png, left: 0, top: 0 },
|
||||
{ input: atlas.png, left: 0, top: dimensions.height },
|
||||
])
|
||||
.png()
|
||||
.toBuffer();
|
||||
}).composite([
|
||||
{ input: texture, left: 0, top: 0 },
|
||||
{ input: atlas.texture, left: 0, top: dimensions.height },
|
||||
]);
|
||||
const combined =
|
||||
textureFormat === "webp"
|
||||
? await composited
|
||||
.webp({
|
||||
lossless: true,
|
||||
nearLossless: false,
|
||||
smartSubsample: false,
|
||||
effort: 6,
|
||||
})
|
||||
.toBuffer()
|
||||
: await composited.png().toBuffer();
|
||||
const generated = new Map<string, boolean>();
|
||||
for (const name of names)
|
||||
if (!skipped.has(name)) generated.set(scaleName(name), true);
|
||||
@@ -198,7 +208,13 @@ export async function generateScale32(input: Buffer): Promise<{
|
||||
json.spritesheet = {
|
||||
...sheet,
|
||||
frames,
|
||||
meta: { ...object(sheet.meta), size: { w: width, h: height } },
|
||||
meta: {
|
||||
...object(sheet.meta),
|
||||
// Keep the client-facing image pointer aligned with the member
|
||||
// `createNitroBundle` is about to write.
|
||||
image: `${String(json.name || "furniture")}.${textureFormat}`,
|
||||
size: { w: width, h: height },
|
||||
},
|
||||
};
|
||||
json.catalogStudio = {
|
||||
...object(json.catalogStudio),
|
||||
|
||||
Reference in new issue
Block a user