Add shared-scene 2D authoring, sprite animation and Rapier2D physics
CI / validate (push) Canceled after 0s
CI / validate (push) Canceled after 0s
This commit is contained in:
@@ -0,0 +1,84 @@
|
||||
import { base64 } from "./archive.ts";
|
||||
import { uid, type Asset } from "./schema.ts";
|
||||
import { sliceImage } from "./two-d.ts";
|
||||
/** Read image dimensions without DOM, so browser and MCP import share validation. */
|
||||
export function imageInfo(bytes: Uint8Array) {
|
||||
if (bytes.length > 25 * 1024 * 1024)
|
||||
throw Error("Изображение превышает 25 МБ");
|
||||
const d = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
||||
let width = 0,
|
||||
height = 0,
|
||||
mime = "",
|
||||
ext = "";
|
||||
if (
|
||||
bytes.length >= 24 &&
|
||||
d.getUint32(0) === 0x89504e47 &&
|
||||
d.getUint32(4) === 0x0d0a1a0a
|
||||
) {
|
||||
width = d.getUint32(16);
|
||||
height = d.getUint32(20);
|
||||
mime = "image/png";
|
||||
ext = "png";
|
||||
} else if (
|
||||
bytes.length >= 30 &&
|
||||
d.getUint32(0) === 0x52494646 &&
|
||||
d.getUint32(8) === 0x57454250
|
||||
) {
|
||||
const kind = d.getUint32(12);
|
||||
if (kind === 0x56503858) {
|
||||
width = 1 + bytes[24] + bytes[25] * 256 + bytes[26] * 65536;
|
||||
height = 1 + bytes[27] + bytes[28] * 256 + bytes[29] * 65536;
|
||||
} else if (kind === 0x5650384c && bytes[20] === 0x2f) {
|
||||
const bits = d.getUint32(21, true);
|
||||
width = (bits & 0x3fff) + 1;
|
||||
height = ((bits >>> 14) & 0x3fff) + 1;
|
||||
} else if (
|
||||
kind === 0x56503820 &&
|
||||
bytes[23] === 0x9d &&
|
||||
bytes[24] === 1 &&
|
||||
bytes[25] === 0x2a
|
||||
) {
|
||||
width = d.getUint16(26, true) & 0x3fff;
|
||||
height = d.getUint16(28, true) & 0x3fff;
|
||||
}
|
||||
mime = "image/webp";
|
||||
ext = "webp";
|
||||
} else if (bytes.length > 4 && d.getUint16(0) === 0xffd8) {
|
||||
let offset = 2;
|
||||
while (offset + 4 <= bytes.length) {
|
||||
if (bytes[offset++] !== 255) break;
|
||||
while (bytes[offset] === 255) offset++;
|
||||
const marker = bytes[offset++];
|
||||
if (marker === 0xda || marker === 0xd9) break;
|
||||
const length = d.getUint16(offset);
|
||||
if (length < 2 || offset + length > bytes.length) break;
|
||||
if ([0xc0, 0xc1, 0xc2].includes(marker) && length >= 7) {
|
||||
height = d.getUint16(offset + 3);
|
||||
width = d.getUint16(offset + 5);
|
||||
break;
|
||||
}
|
||||
offset += length;
|
||||
}
|
||||
mime = "image/jpeg";
|
||||
ext = "jpg";
|
||||
}
|
||||
if (!width || !height || width > 16384 || height > 16384)
|
||||
throw Error("Ожидалось PNG, JPEG или WebP размером до 16384×16384");
|
||||
return { width, height, mime, ext };
|
||||
}
|
||||
export function imageAsset(bytes: Uint8Array, name: string): Asset {
|
||||
const info = imageInfo(bytes);
|
||||
return {
|
||||
id: uid("image"),
|
||||
name: name.replace(/\.[^.]+$/, "") + "." + info.ext,
|
||||
kind: "image",
|
||||
uri: `data:${info.mime};base64,${base64(bytes)}`,
|
||||
image: {
|
||||
width: info.width,
|
||||
height: info.height,
|
||||
pixelsPerUnit: 100,
|
||||
filter: "nearest",
|
||||
frames: sliceImage(info.width, info.height, info.width, info.height),
|
||||
},
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user