style: format code biome
Local Build and Deploy / deploy (push) Failing after 46s

This commit is contained in:
openhands committed 2026-07-13 21:57:41 +02:00
1 parent 8efd032cc6
commit df38dccbf1
735 files changed
+128321 -120870

No files matched your search

+39 -37
View File
@@ -3,44 +3,46 @@ import { encodePng } from "@/lib/services/swf/nitro-builder";
import { cropRgba, decodePng } from "./png-decode";
describe("png-decode", () => {
it("round-trips an RGBA image through encodePng → decodePng", () => {
const w = 3;
const h = 2;
const rgba = Buffer.from([
// row 0
255, 0, 0, 255, 0, 255, 0, 255, 0, 0, 255, 255,
// row 1
10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120,
]);
const png = encodePng(w, h, rgba);
const dec = decodePng(png);
expect(dec.width).toBe(w);
expect(dec.height).toBe(h);
expect(Buffer.compare(dec.rgba, rgba)).toBe(0);
});
it("round-trips an RGBA image through encodePng → decodePng", () => {
const w = 3;
const h = 2;
const rgba = Buffer.from([
// row 0
255, 0, 0, 255, 0, 255, 0, 255, 0, 0, 255, 255,
// row 1
10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120,
]);
const png = encodePng(w, h, rgba);
const dec = decodePng(png);
expect(dec.width).toBe(w);
expect(dec.height).toBe(h);
expect(Buffer.compare(dec.rgba, rgba)).toBe(0);
});
it("decodes a larger gradient losslessly", () => {
const w = 16;
const h = 16;
const rgba = Buffer.alloc(w * h * 4);
for (let i = 0; i < w * h; i++) {
rgba[i * 4] = i % 256;
rgba[i * 4 + 1] = (i * 3) % 256;
rgba[i * 4 + 2] = (i * 7) % 256;
rgba[i * 4 + 3] = 255;
}
const dec = decodePng(encodePng(w, h, rgba));
expect(Buffer.compare(dec.rgba, rgba)).toBe(0);
});
it("decodes a larger gradient losslessly", () => {
const w = 16;
const h = 16;
const rgba = Buffer.alloc(w * h * 4);
for (let i = 0; i < w * h; i++) {
rgba[i * 4] = i % 256;
rgba[i * 4 + 1] = (i * 3) % 256;
rgba[i * 4 + 2] = (i * 7) % 256;
rgba[i * 4 + 3] = 255;
}
const dec = decodePng(encodePng(w, h, rgba));
expect(Buffer.compare(dec.rgba, rgba)).toBe(0);
});
it("crops a sub-region", () => {
// 2x2 sheet, crop the bottom-right 1x1 pixel
const sheet = Buffer.from([1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4]);
const cropped = cropRgba(sheet, 2, 1, 1, 1, 1);
expect([...cropped]).toEqual([4, 4, 4, 4]);
});
it("crops a sub-region", () => {
// 2x2 sheet, crop the bottom-right 1x1 pixel
const sheet = Buffer.from([1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4]);
const cropped = cropRgba(sheet, 2, 1, 1, 1, 1);
expect([...cropped]).toEqual([4, 4, 4, 4]);
});
it("rejects non-PNG input", () => {
expect(() => decodePng(Buffer.from([1, 2, 3, 4, 5, 6, 7, 8]))).toThrow("not a PNG");
});
it("rejects non-PNG input", () => {
expect(() => decodePng(Buffer.from([1, 2, 3, 4, 5, 6, 7, 8]))).toThrow(
"not a PNG",
);
});
});
+126 -105
View File
@@ -1,49 +1,55 @@
import { inflateSync } from "node:zlib";
export interface DecodedPng {
width: number;
height: number;
/** RGBA8888, row-major, length = width * height * 4 */
rgba: Buffer;
width: number;
height: number;
/** RGBA8888, row-major, length = width * height * 4 */
rgba: Buffer;
}
function paeth(a: number, b: number, c: number): number {
const p = a + b - c;
const pa = Math.abs(p - a);
const pb = Math.abs(p - b);
const pc = Math.abs(p - c);
if (pa <= pb && pa <= pc) return a;
if (pb <= pc) return b;
return c;
const p = a + b - c;
const pa = Math.abs(p - a);
const pb = Math.abs(p - b);
const pc = Math.abs(p - c);
if (pa <= pb && pa <= pc) return a;
if (pb <= pc) return b;
return c;
}
function unfilter(filter: number, cur: Buffer, prev: Buffer, bpp: number): void {
const n = cur.length;
switch (filter) {
case 0:
break;
case 1:
for (let i = 0; i < n; i++) cur[i] = (cur[i] + (i >= bpp ? cur[i - bpp] : 0)) & 0xff;
break;
case 2:
for (let i = 0; i < n; i++) cur[i] = (cur[i] + prev[i]) & 0xff;
break;
case 3:
for (let i = 0; i < n; i++) {
const a = i >= bpp ? cur[i - bpp] : 0;
cur[i] = (cur[i] + ((a + prev[i]) >> 1)) & 0xff;
}
break;
case 4:
for (let i = 0; i < n; i++) {
const a = i >= bpp ? cur[i - bpp] : 0;
const c = i >= bpp ? prev[i - bpp] : 0;
cur[i] = (cur[i] + paeth(a, prev[i], c)) & 0xff;
}
break;
default:
throw new Error(`unknown PNG filter ${filter}`);
}
function unfilter(
filter: number,
cur: Buffer,
prev: Buffer,
bpp: number,
): void {
const n = cur.length;
switch (filter) {
case 0:
break;
case 1:
for (let i = 0; i < n; i++)
cur[i] = (cur[i] + (i >= bpp ? cur[i - bpp] : 0)) & 0xff;
break;
case 2:
for (let i = 0; i < n; i++) cur[i] = (cur[i] + prev[i]) & 0xff;
break;
case 3:
for (let i = 0; i < n; i++) {
const a = i >= bpp ? cur[i - bpp] : 0;
cur[i] = (cur[i] + ((a + prev[i]) >> 1)) & 0xff;
}
break;
case 4:
for (let i = 0; i < n; i++) {
const a = i >= bpp ? cur[i - bpp] : 0;
const c = i >= bpp ? prev[i - bpp] : 0;
cur[i] = (cur[i] + paeth(a, prev[i], c)) & 0xff;
}
break;
default:
throw new Error(`unknown PNG filter ${filter}`);
}
}
/**
@@ -52,77 +58,92 @@ function unfilter(filter: number, cur: Buffer, prev: Buffer, bpp: number): void
* Returns RGBA pixels. Throws on unsupported formats.
*/
export function decodePng(buf: Buffer): DecodedPng {
if (buf.length < 8 || buf[0] !== 0x89 || buf[1] !== 0x50 || buf[2] !== 0x4e || buf[3] !== 0x47) {
throw new Error("not a PNG");
}
let off = 8;
let width = 0;
let height = 0;
let bitDepth = 0;
let colorType = 0;
let interlace = 0;
const idat: Buffer[] = [];
while (off + 8 <= buf.length) {
const len = buf.readUInt32BE(off);
const type = buf.toString("ascii", off + 4, off + 8);
const data = buf.subarray(off + 8, off + 8 + len);
off += 12 + len; // length(4) + type(4) + data + crc(4)
if (type === "IHDR") {
width = data.readUInt32BE(0);
height = data.readUInt32BE(4);
bitDepth = data[8];
colorType = data[9];
interlace = data[12];
} else if (type === "IDAT") {
idat.push(data);
} else if (type === "IEND") {
break;
}
}
if (bitDepth !== 8) throw new Error(`unsupported PNG bit depth ${bitDepth}`);
if (interlace !== 0) throw new Error("interlaced PNG not supported");
const channels = colorType === 6 ? 4 : colorType === 2 ? 3 : colorType === 0 ? 1 : -1;
if (channels === -1) throw new Error(`unsupported PNG color type ${colorType}`);
if (
buf.length < 8 ||
buf[0] !== 0x89 ||
buf[1] !== 0x50 ||
buf[2] !== 0x4e ||
buf[3] !== 0x47
) {
throw new Error("not a PNG");
}
let off = 8;
let width = 0;
let height = 0;
let bitDepth = 0;
let colorType = 0;
let interlace = 0;
const idat: Buffer[] = [];
while (off + 8 <= buf.length) {
const len = buf.readUInt32BE(off);
const type = buf.toString("ascii", off + 4, off + 8);
const data = buf.subarray(off + 8, off + 8 + len);
off += 12 + len; // length(4) + type(4) + data + crc(4)
if (type === "IHDR") {
width = data.readUInt32BE(0);
height = data.readUInt32BE(4);
bitDepth = data[8];
colorType = data[9];
interlace = data[12];
} else if (type === "IDAT") {
idat.push(data);
} else if (type === "IEND") {
break;
}
}
if (bitDepth !== 8) throw new Error(`unsupported PNG bit depth ${bitDepth}`);
if (interlace !== 0) throw new Error("interlaced PNG not supported");
const channels =
colorType === 6 ? 4 : colorType === 2 ? 3 : colorType === 0 ? 1 : -1;
if (channels === -1)
throw new Error(`unsupported PNG color type ${colorType}`);
const raw = inflateSync(Buffer.concat(idat));
const stride = width * channels;
const planar = Buffer.alloc(height * stride);
let prev = Buffer.alloc(stride);
let rp = 0;
for (let y = 0; y < height; y++) {
const filter = raw[rp++];
const cur = planar.subarray(y * stride, y * stride + stride);
raw.copy(cur, 0, rp, rp + stride);
rp += stride;
unfilter(filter, cur, prev, channels);
prev = cur;
}
const raw = inflateSync(Buffer.concat(idat));
const stride = width * channels;
const planar = Buffer.alloc(height * stride);
let prev = Buffer.alloc(stride);
let rp = 0;
for (let y = 0; y < height; y++) {
const filter = raw[rp++];
const cur = planar.subarray(y * stride, y * stride + stride);
raw.copy(cur, 0, rp, rp + stride);
rp += stride;
unfilter(filter, cur, prev, channels);
prev = cur;
}
if (channels === 4) return { width, height, rgba: planar };
if (channels === 4) return { width, height, rgba: planar };
const rgba = Buffer.alloc(width * height * 4);
for (let i = 0, j = 0; i < width * height; i++) {
if (channels === 3) {
rgba[j++] = planar[i * 3];
rgba[j++] = planar[i * 3 + 1];
rgba[j++] = planar[i * 3 + 2];
} else {
const g = planar[i];
rgba[j++] = g;
rgba[j++] = g;
rgba[j++] = g;
}
rgba[j++] = 255;
}
return { width, height, rgba };
const rgba = Buffer.alloc(width * height * 4);
for (let i = 0, j = 0; i < width * height; i++) {
if (channels === 3) {
rgba[j++] = planar[i * 3];
rgba[j++] = planar[i * 3 + 1];
rgba[j++] = planar[i * 3 + 2];
} else {
const g = planar[i];
rgba[j++] = g;
rgba[j++] = g;
rgba[j++] = g;
}
rgba[j++] = 255;
}
return { width, height, rgba };
}
/** Crop a w×h RGBA region at (x,y) out of a sheetW-wide RGBA buffer. */
export function cropRgba(rgba: Buffer, sheetW: number, x: number, y: number, w: number, h: number): Buffer {
const out = Buffer.alloc(w * h * 4);
for (let row = 0; row < h; row++) {
const src = ((y + row) * sheetW + x) * 4;
rgba.copy(out, row * w * 4, src, src + w * 4);
}
return out;
export function cropRgba(
rgba: Buffer,
sheetW: number,
x: number,
y: number,
w: number,
h: number,
): Buffer {
const out = Buffer.alloc(w * h * 4);
for (let row = 0; row < h; row++) {
const src = ((y + row) * sheetW + x) * 4;
rgba.copy(out, row * w * 4, src, src + w * 4);
}
return out;
}