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), }, }; }