@forgeax/engine-image 0.1.4 → 0.1.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -7
- package/dist/.tsbuildinfo +1 -1
- package/dist/decode-image-from-file.d.ts.map +1 -1
- package/dist/decode-image-from-file.mjs +5 -135
- package/dist/decode-image-from-file.mjs.map +1 -1
- package/dist/hdr-decoder.mjs +1 -1
- package/dist/hdr-decoder.mjs.map +1 -1
- package/dist/image-importer.d.ts.map +1 -1
- package/dist/image-importer.mjs +20 -100
- package/dist/image-importer.mjs.map +1 -1
- package/dist/index.d.ts +1 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.mjs +2 -36
- package/dist/index.mjs.map +1 -1
- package/dist/ktx2-encode.d.ts +2 -5
- package/dist/ktx2-encode.d.ts.map +1 -1
- package/dist/ktx2-encode.mjs +15 -17
- package/dist/ktx2-encode.mjs.map +1 -1
- package/dist/parse-image.d.ts +0 -1
- package/dist/parse-image.d.ts.map +1 -1
- package/dist/parse-image.mjs +3 -80
- package/dist/parse-image.mjs.map +1 -1
- package/package.json +6 -6
- package/src/__tests__/image.unit.test.ts +28 -162
- package/src/decode-image-from-file.ts +3 -68
- package/src/errors.ts +1 -1
- package/src/image-decoder-browser.ts +1 -1
- package/src/image-importer.ts +3 -4
- package/src/index.ts +3 -5
- package/src/ktx2-encode.ts +17 -21
- package/src/parse-image.ts +3 -103
- package/dist/__tests__/ktx2-encode-mode-owner.test-d.d.ts +0 -2
- package/dist/__tests__/ktx2-encode-mode-owner.test-d.d.ts.map +0 -1
- package/dist/__tests__/runtime-decode.unit.test.d.ts +0 -2
- package/dist/__tests__/runtime-decode.unit.test.d.ts.map +0 -1
- package/dist/__tests__/tga.unit.test.d.ts +0 -2
- package/dist/__tests__/tga.unit.test.d.ts.map +0 -1
- package/dist/runtime/__tests__/asset-decoders.unit.test.d.ts +0 -2
- package/dist/runtime/__tests__/asset-decoders.unit.test.d.ts.map +0 -1
- package/dist/runtime/decode-image-bytes.d.ts +0 -7
- package/dist/runtime/decode-image-bytes.d.ts.map +0 -1
- package/dist/runtime/image-error.d.ts +0 -3
- package/dist/runtime/image-error.d.ts.map +0 -1
- package/src/__tests__/ktx2-encode-mode-owner.test-d.ts +0 -70
- package/src/__tests__/runtime-decode.unit.test.ts +0 -22
- package/src/__tests__/tga.unit.test.ts +0 -59
- package/src/runtime/__tests__/asset-decoders.unit.test.ts +0 -66
- package/src/runtime/decode-image-bytes.ts +0 -41
- package/src/runtime/image-error.ts +0 -8
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { stat, readFile } from 'fs/promises';
|
|
2
2
|
import { extname, dirname, join } from 'path';
|
|
3
|
-
import { validateMeta } from '@forgeax/engine-pack';
|
|
4
3
|
import { err, ok, IMAGE_ERROR_HINTS } from '@forgeax/engine-types';
|
|
5
4
|
import * as jpeg from 'jpeg-js';
|
|
6
5
|
import * as UPNG from 'upng-js';
|
|
@@ -8,7 +7,7 @@ import * as UPNG from 'upng-js';
|
|
|
8
7
|
// src/decode-image-from-file.ts
|
|
9
8
|
var IMAGE_ERROR_EXPECTED = {
|
|
10
9
|
"image-decode-failed": "PNG / JPG byte stream decodes successfully",
|
|
11
|
-
"image-format-unsupported": "mime is one of ['image/png', 'image/jpeg'
|
|
10
|
+
"image-format-unsupported": "mime is one of ['image/png', 'image/jpeg']; uploadTexture format <-> colorSpace family agrees",
|
|
12
11
|
"image-dimension-out-of-bounds": "width and height fall under device caps maxTextureDimension2D (or 16384 hard cap)",
|
|
13
12
|
"image-meta-missing": "<source>.meta.json sidecar (importer: 'image') exists in the same directory",
|
|
14
13
|
"image-hdr-decode-failed": "Radiance RGBE header is valid and pixel data decodes successfully",
|
|
@@ -245,7 +244,7 @@ function downscaleRgba(bytes, width, height, maxDimension) {
|
|
|
245
244
|
|
|
246
245
|
// src/parse-image.ts
|
|
247
246
|
var DEFAULT_MAX_DIMENSION = 16384;
|
|
248
|
-
var SUPPORTED_MIMES = ["image/png", "image/jpeg"
|
|
247
|
+
var SUPPORTED_MIMES = ["image/png", "image/jpeg"];
|
|
249
248
|
function parseImage(bytes, mime, opts = {}) {
|
|
250
249
|
if (!SUPPORTED_MIMES.includes(mime)) {
|
|
251
250
|
return err(
|
|
@@ -277,17 +276,12 @@ function parseImage(bytes, mime, opts = {}) {
|
|
|
277
276
|
);
|
|
278
277
|
}
|
|
279
278
|
rgba = new Uint8Array(first);
|
|
280
|
-
} else
|
|
279
|
+
} else {
|
|
281
280
|
const jpegMod = jpeg.default ?? jpeg;
|
|
282
281
|
const decoded = jpegMod.decode(bytes, { useTArray: true, formatAsRGBA: true });
|
|
283
282
|
width = decoded.width;
|
|
284
283
|
height = decoded.height;
|
|
285
284
|
rgba = decoded.data;
|
|
286
|
-
} else {
|
|
287
|
-
const decoded = decodeTga(bytes);
|
|
288
|
-
width = decoded.width;
|
|
289
|
-
height = decoded.height;
|
|
290
|
-
rgba = decoded.bytes;
|
|
291
285
|
}
|
|
292
286
|
} catch (e) {
|
|
293
287
|
return err(
|
|
@@ -324,85 +318,12 @@ function parseImage(bytes, mime, opts = {}) {
|
|
|
324
318
|
mipmap: opts.mipmap ?? true
|
|
325
319
|
});
|
|
326
320
|
}
|
|
327
|
-
function decodeTga(bytes) {
|
|
328
|
-
if (bytes.length < 18) throw new Error("TGA header is truncated");
|
|
329
|
-
const colorMapType = bytes[1];
|
|
330
|
-
const imageType = bytes[2];
|
|
331
|
-
const idLength = bytes[0] ?? 0;
|
|
332
|
-
const width = (bytes[12] ?? 0) | (bytes[13] ?? 0) << 8;
|
|
333
|
-
const height = (bytes[14] ?? 0) | (bytes[15] ?? 0) << 8;
|
|
334
|
-
const pixelDepth = bytes[16] ?? 0;
|
|
335
|
-
const descriptor = bytes[17] ?? 0;
|
|
336
|
-
if (colorMapType !== 0) throw new Error("color-mapped TGA is unsupported");
|
|
337
|
-
if (width < 1 || height < 1) throw new Error("TGA dimensions must be positive");
|
|
338
|
-
const isTrueColor = imageType === 2 || imageType === 10;
|
|
339
|
-
const isGray = imageType === 3 || imageType === 11;
|
|
340
|
-
if (!isTrueColor && !isGray) throw new Error(`TGA image type ${imageType} is unsupported`);
|
|
341
|
-
if (isTrueColor && pixelDepth !== 24 && pixelDepth !== 32) {
|
|
342
|
-
throw new Error(`TGA true-color depth ${pixelDepth} is unsupported`);
|
|
343
|
-
}
|
|
344
|
-
if (isGray && pixelDepth !== 8)
|
|
345
|
-
throw new Error(`TGA grayscale depth ${pixelDepth} is unsupported`);
|
|
346
|
-
const bytesPerPixel = pixelDepth / 8;
|
|
347
|
-
let offset = 18 + idLength;
|
|
348
|
-
const pixelCount = width * height;
|
|
349
|
-
const out = new Uint8Array(pixelCount * 4);
|
|
350
|
-
const topOrigin = (descriptor & 32) !== 0;
|
|
351
|
-
const rightOrigin = (descriptor & 16) !== 0;
|
|
352
|
-
let filePixel = 0;
|
|
353
|
-
const writePixel = (pixel) => {
|
|
354
|
-
if (filePixel >= pixelCount) throw new Error("TGA pixel data has too many pixels");
|
|
355
|
-
const fileX = filePixel % width;
|
|
356
|
-
const fileY = Math.floor(filePixel / width);
|
|
357
|
-
const x = rightOrigin ? width - 1 - fileX : fileX;
|
|
358
|
-
const y = topOrigin ? fileY : height - 1 - fileY;
|
|
359
|
-
const destination = (y * width + x) * 4;
|
|
360
|
-
if (isGray) {
|
|
361
|
-
const value = pixel[0] ?? 0;
|
|
362
|
-
out[destination] = value;
|
|
363
|
-
out[destination + 1] = value;
|
|
364
|
-
out[destination + 2] = value;
|
|
365
|
-
out[destination + 3] = 255;
|
|
366
|
-
} else {
|
|
367
|
-
out[destination] = pixel[2] ?? 0;
|
|
368
|
-
out[destination + 1] = pixel[1] ?? 0;
|
|
369
|
-
out[destination + 2] = pixel[0] ?? 0;
|
|
370
|
-
out[destination + 3] = bytesPerPixel === 4 ? pixel[3] ?? 255 : 255;
|
|
371
|
-
}
|
|
372
|
-
filePixel += 1;
|
|
373
|
-
};
|
|
374
|
-
const readPixel = () => {
|
|
375
|
-
if (offset + bytesPerPixel > bytes.length) throw new Error("TGA pixel data is truncated");
|
|
376
|
-
const pixel = bytes.slice(offset, offset + bytesPerPixel);
|
|
377
|
-
offset += bytesPerPixel;
|
|
378
|
-
return pixel;
|
|
379
|
-
};
|
|
380
|
-
if (imageType === 2 || imageType === 3) {
|
|
381
|
-
while (filePixel < pixelCount) writePixel(readPixel());
|
|
382
|
-
} else {
|
|
383
|
-
while (filePixel < pixelCount) {
|
|
384
|
-
if (offset >= bytes.length) throw new Error("TGA RLE packet header is truncated");
|
|
385
|
-
const packet = bytes[offset++] ?? 0;
|
|
386
|
-
const count = (packet & 127) + 1;
|
|
387
|
-
if (filePixel + count > pixelCount) throw new Error("TGA RLE packet exceeds image bounds");
|
|
388
|
-
if ((packet & 128) !== 0) {
|
|
389
|
-
const pixel = readPixel();
|
|
390
|
-
for (let i = 0; i < count; i++) writePixel(pixel);
|
|
391
|
-
} else {
|
|
392
|
-
for (let i = 0; i < count; i++) writePixel(readPixel());
|
|
393
|
-
}
|
|
394
|
-
}
|
|
395
|
-
}
|
|
396
|
-
if (filePixel !== pixelCount) throw new Error("TGA pixel count mismatch");
|
|
397
|
-
return { width, height, bytes: out };
|
|
398
|
-
}
|
|
399
321
|
|
|
400
322
|
// src/decode-image-from-file.ts
|
|
401
323
|
var EXT_TO_MIME = {
|
|
402
324
|
".png": "image/png",
|
|
403
325
|
".jpg": "image/jpeg",
|
|
404
326
|
".jpeg": "image/jpeg",
|
|
405
|
-
".tga": "image/x-tga",
|
|
406
327
|
".hdr": "image/vnd.radiance"
|
|
407
328
|
};
|
|
408
329
|
function deriveSidecarPath(sourcePath) {
|
|
@@ -410,37 +331,6 @@ function deriveSidecarPath(sourcePath) {
|
|
|
410
331
|
const base = sourcePath.slice(dir.length + 1);
|
|
411
332
|
return join(dir, `${base}.meta.json`);
|
|
412
333
|
}
|
|
413
|
-
function sidecarSchemaFailureReason() {
|
|
414
|
-
const firstError = validateMeta.errors?.[0];
|
|
415
|
-
if (firstError === void 0) return "sidecar schema validation failed";
|
|
416
|
-
const location = firstError.instancePath === "" ? "/" : firstError.instancePath;
|
|
417
|
-
return `sidecar schema validation failed: ${firstError.keyword} at ${location}`.slice(0, 256);
|
|
418
|
-
}
|
|
419
|
-
function imageSidecarFailureReason(sidecar, mime) {
|
|
420
|
-
const subAsset = sidecar.subAssets[0];
|
|
421
|
-
const settings = sidecar.importSettings;
|
|
422
|
-
const expectedKind = mime === "image/vnd.radiance" ? "equirect" : "texture";
|
|
423
|
-
if (sidecar.importer !== "image") return "image sidecar importer must be image";
|
|
424
|
-
if (sidecar.subAssets.length !== 1 || subAsset === void 0) {
|
|
425
|
-
return "image sidecar must declare one sub-asset";
|
|
426
|
-
}
|
|
427
|
-
if (subAsset.kind !== expectedKind || subAsset.sourceIndex !== 0) {
|
|
428
|
-
return `image sidecar sub-asset must be ${expectedKind} at sourceIndex 0`;
|
|
429
|
-
}
|
|
430
|
-
if (settings.colorSpace !== void 0 && !["srgb", "linear"].includes(settings.colorSpace)) {
|
|
431
|
-
return "image sidecar colorSpace must be srgb or linear";
|
|
432
|
-
}
|
|
433
|
-
if (settings.mipmap !== void 0 && !["auto", "none"].includes(settings.mipmap)) {
|
|
434
|
-
return "image sidecar mipmap must be auto or none";
|
|
435
|
-
}
|
|
436
|
-
if (settings.addressMode !== void 0 && !["repeat", "clamp-to-edge", "mirror-repeat"].includes(settings.addressMode)) {
|
|
437
|
-
return "image sidecar addressMode is unsupported";
|
|
438
|
-
}
|
|
439
|
-
if (settings.filterMode !== void 0 && !["nearest", "linear"].includes(settings.filterMode)) {
|
|
440
|
-
return "image sidecar filterMode is unsupported";
|
|
441
|
-
}
|
|
442
|
-
return void 0;
|
|
443
|
-
}
|
|
444
334
|
async function decodeImageFromFile(sourcePath) {
|
|
445
335
|
const ext = extname(sourcePath).toLowerCase();
|
|
446
336
|
const mime = EXT_TO_MIME[ext];
|
|
@@ -488,9 +378,9 @@ async function decodeImageFromFile(sourcePath) {
|
|
|
488
378
|
})
|
|
489
379
|
);
|
|
490
380
|
}
|
|
491
|
-
let
|
|
381
|
+
let sidecar;
|
|
492
382
|
try {
|
|
493
|
-
|
|
383
|
+
sidecar = JSON.parse(sidecarText);
|
|
494
384
|
} catch (e) {
|
|
495
385
|
return err(
|
|
496
386
|
imageError({
|
|
@@ -500,26 +390,6 @@ async function decodeImageFromFile(sourcePath) {
|
|
|
500
390
|
})
|
|
501
391
|
);
|
|
502
392
|
}
|
|
503
|
-
if (!validateMeta(parsedSidecar)) {
|
|
504
|
-
return err(
|
|
505
|
-
imageError({
|
|
506
|
-
code: "image-decode-failed",
|
|
507
|
-
reason: sidecarSchemaFailureReason(),
|
|
508
|
-
path: sidecarPath
|
|
509
|
-
})
|
|
510
|
-
);
|
|
511
|
-
}
|
|
512
|
-
const sidecar = parsedSidecar;
|
|
513
|
-
const imageSidecarFailure = imageSidecarFailureReason(sidecar, mime);
|
|
514
|
-
if (imageSidecarFailure !== void 0) {
|
|
515
|
-
return err(
|
|
516
|
-
imageError({
|
|
517
|
-
code: "image-decode-failed",
|
|
518
|
-
reason: imageSidecarFailure,
|
|
519
|
-
path: sidecarPath
|
|
520
|
-
})
|
|
521
|
-
);
|
|
522
|
-
}
|
|
523
393
|
const settings = sidecar.importSettings ?? {};
|
|
524
394
|
const colorSpace = settings.colorSpace ?? "srgb";
|
|
525
395
|
const mipmap = settings.mipmap ?? "auto";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/errors.ts","../src/hdr-decoder.ts","../src/resize-image.ts","../src/parse-image.ts","../src/decode-image-from-file.ts"],"names":[],"mappings":"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","file":"decode-image-from-file.mjs","sourcesContent":["import type { ImageErrorCode, ImageErrorDetailFor, ImageErrorFor } from '@forgeax/engine-types';\nimport { IMAGE_ERROR_HINTS } from '@forgeax/engine-types';\n\n// Per-code .expected string literals SSOT.\n// Mirrors ASSET_ERROR_HINTS / IMAGE_ERROR_HINTS shape (charter P5 consistent\n// abstraction). AI users surface .expected when a structured error needs to\n// describe the precondition that was violated.\nconst IMAGE_ERROR_EXPECTED: Readonly<Record<ImageErrorCode, string>> = {\n 'image-decode-failed': 'PNG / JPG byte stream decodes successfully',\n 'image-format-unsupported':\n \"mime is one of ['image/png', 'image/jpeg', 'image/x-tga']; uploadTexture format <-> colorSpace family agrees\",\n 'image-dimension-out-of-bounds':\n 'width and height fall under device caps maxTextureDimension2D (or 16384 hard cap)',\n 'image-meta-missing':\n \"<source>.meta.json sidecar (importer: 'image') exists in the same directory\",\n 'image-hdr-decode-failed': 'Radiance RGBE header is valid and pixel data decodes successfully',\n // feat-20260521-sprite-atlas-animation M1 T-03 — vite-plugin-image atlas\n // hook .expected literals (plan-strategy section 2 D-2 + AC-10 a/b/c).\n // ImageErrorImpl construction path is unchanged: the new atlas-* errors\n // flow through `new ImageErrorImpl({ code: 'atlas-...', ...detail })` and\n // pick the .expected string up from this Record at construction time\n // (charter P3 explicit failure SSOT — AI users surface .expected next to\n // .hint after switch (err.code) without parsing the message).\n 'atlas-empty-input': 'images.length >= 1',\n 'atlas-size-exceeded':\n 'image width x height <= maxAtlasSize^2 and each image fits in the atlas footprint',\n 'atlas-region-mismatch': 'sum(regions[i].w x regions[i].h) <= atlasWidth x atlasHeight',\n};\n\n/** Runtime implementation of the correlated `ImageErrorFor<C>` envelope. */\nexport class ImageErrorImpl<C extends ImageErrorCode = ImageErrorCode>\n extends Error\n implements ImageErrorFor<C>\n{\n readonly code: C;\n readonly expected: string;\n readonly hint: string;\n readonly detail: ImageErrorDetailFor<C>;\n\n constructor(detail: ImageErrorDetailFor<C>) {\n const code = detail.code;\n const expected = IMAGE_ERROR_EXPECTED[code];\n const hint = IMAGE_ERROR_HINTS[code];\n super(`[ImageError ${code}] expected: ${expected}; hint: ${hint}`);\n this.name = 'ImageError';\n this.code = code;\n this.expected = expected;\n this.hint = hint;\n this.detail = detail;\n }\n}\n\n/** Construct a structured image error while preserving its code/detail pair. */\nexport function imageError<C extends ImageErrorCode>(\n detail: ImageErrorDetailFor<C>,\n): ImageErrorFor<C> {\n return new ImageErrorImpl<C>(detail);\n}\n\nexport { IMAGE_ERROR_EXPECTED };\n","import type { ImageError } from '@forgeax/engine-types';\nimport { imageError } from './errors.js';\nimport type { Result } from './result.js';\nimport { err, ok } from './result.js';\n\n/**\n * Decoded HDR image POD.\n *\n * `data` is an interleaved RGBA Float32Array (4 floats per pixel, row-major).\n * Color space is always linear (RGBE is radiance).\n */\nexport interface HdrDecoded {\n readonly width: number;\n readonly height: number;\n readonly data: Float32Array;\n}\n\n// ASCII code points\nconst LF = 0x0a;\n\n/**\n * Decode a Radiance RGBE (.hdr) byte stream into linear RGBA float pixels.\n *\n * Steps per research F-6:\n * 1. Parse ASCII header: magic (#?RADIANCE), FORMAT=32-bit_rle_rgbe,\n * resolution line (-Y height +X width).\n * 2. Decode per-scanline new-RLE pixel data: 4 independent channel runs.\n * 3. Convert RGBE to float: if E==0 -> 0; else f=2^(E-136), R=(Rm+0.5)*f.\n * 4. Output as interleaved RGBA Float32Array (alpha=1.0).\n *\n * Old-RLE scanlines (prefix not matching 0x02,0x02,hi,lo with correct width)\n * return image-hdr-decode-failed.\n */\nexport function decodeHdr(bytes: Uint8Array): Result<HdrDecoded, ImageError> {\n let pos = 0;\n\n // Step 1a: magic -- must start with \"#?RADIANCE\" or \"#?RGBE\"\n if (bytes.length < 11) {\n return err(hdrDecodeError('file too short for Radiance HDR header'));\n }\n const magic = String.fromCharCode(...bytes.subarray(0, 11));\n if (magic !== '#?RADIANCE\\n' && magic !== '#?RGBE\\n') {\n return err(\n hdrDecodeError('missing or invalid Radiance HDR magic; expected #?RADIANCE or #?RGBE'),\n );\n }\n pos = 11;\n\n // Step 1b: key=value lines until empty line\n let formatFound = false;\n while (pos < bytes.length) {\n const lineEnd = findByte(bytes, LF, pos);\n if (lineEnd === -1) return err(hdrDecodeError('header truncated before empty line'));\n const line = asciiSubstring(bytes, pos, lineEnd);\n pos = lineEnd + 1;\n if (line === '') break;\n if (line.startsWith('FORMAT=')) {\n const val = line.slice(7);\n if (val === '32-bit_rle_rgbe') {\n formatFound = true;\n }\n }\n }\n if (!formatFound) {\n return err(hdrDecodeError('missing or unsupported FORMAT; expected FORMAT=32-bit_rle_rgbe'));\n }\n\n // Step 1c: resolution line (-Y height +X width)\n if (pos >= bytes.length) return err(hdrDecodeError('missing resolution line'));\n const resLineEnd = findByte(bytes, LF, pos);\n if (resLineEnd === -1) return err(hdrDecodeError('resolution line truncated'));\n const resLine = asciiSubstring(bytes, pos, resLineEnd);\n pos = resLineEnd + 1;\n const resMatch = /^-Y\\s+(\\d+)\\s+\\+X\\s+(\\d+)\\s*$/.exec(resLine);\n if (resMatch === null) {\n return err(\n hdrDecodeError(`unexpected resolution line format: \"${resLine}\"; expected \"-Y H +X W\"`),\n );\n }\n const height = Number(resMatch[1]);\n const width = Number(resMatch[2]);\n if (height === undefined || width === undefined || height <= 0 || width <= 0) {\n return err(hdrDecodeError(`invalid dimensions: ${width}x${height}`));\n }\n\n // Step 2: decode per-scanline new-RLE pixel data\n const pixelCount = width * height;\n const rgbe = new Uint8Array(pixelCount * 4);\n const scanlineBytes = width * 4;\n\n for (let y = 0; y < height; y++) {\n if (pos + 4 > bytes.length) {\n return err(hdrDecodeError(`truncated at scanline ${y}: expected 4-byte prefix`));\n }\n const p0 = bytes[pos];\n const p1 = bytes[pos + 1];\n const pHi = bytes[pos + 2];\n const pLo = bytes[pos + 3];\n if (p0 === undefined || p1 === undefined || pHi === undefined || pLo === undefined) {\n return err(hdrDecodeError(`truncated at scanline ${y}`));\n }\n // biome-ignore lint/style/noNonNullAssertion: checked above\n const prefixWidth = (pHi! << 8) | pLo!;\n\n if (p0 === 0x02 && p1 === 0x02 && prefixWidth === width && width >= 8 && width <= 32767) {\n // New-RLE: 4 channels independently RLE-encoded, then interleaved\n const decoded = decodeNewRleScanline(bytes, pos + 4, width);\n if (decoded === null) {\n return err(hdrDecodeError(`RLE decode failed at scanline ${y}`));\n }\n // Copy into rgbe interleaved buffer\n const base = y * scanlineBytes;\n const channels = [decoded[0], decoded[1], decoded[2], decoded[3]];\n for (let x = 0; x < width; x++) {\n for (let ch = 0; ch < 4; ch++) {\n // biome-ignore lint/style/noNonNullAssertion: checked at allocation\n rgbe[base + x * 4 + ch] = channels[ch]![x]!;\n }\n }\n // Advance pos past the 4-byte prefix + the total RLE bytes consumed\n pos += 4 + decoded.totalBytes;\n } else {\n // Old-RLE: not supported\n return err(\n hdrDecodeError(\n 'old-RLE format is not supported; only new-RLE (32-bit_rle_rgbe) is accepted',\n ),\n );\n }\n }\n\n // Step 3: RGBE to float\n const out = new Float32Array(pixelCount * 4);\n for (let i = 0; i < pixelCount; i++) {\n const base = i * 4;\n // biome-ignore lint/style/noNonNullAssertion: checked at allocation\n const r = rgbe[base]!;\n // biome-ignore lint/style/noNonNullAssertion: checked at allocation\n const g = rgbe[base + 1]!;\n // biome-ignore lint/style/noNonNullAssertion: checked at allocation\n const b = rgbe[base + 2]!;\n // biome-ignore lint/style/noNonNullAssertion: checked at allocation\n const e = rgbe[base + 3]!;\n out[base + 3] = 1.0;\n\n if (e === 0) {\n out[base] = 0;\n out[base + 1] = 0;\n out[base + 2] = 0;\n } else {\n // f = 2^(E - 136) = ldexp(1.0, E - 128 - 8)\n // Equivalent: ldexp(1.0 / 256.0, E - 128)\n // But simpler: const f = (1.0 / 256.0) * Math.pow(2, e - 128);\n // Using ldexp: Math.pow(2, e - 136)\n const f = 2 ** (e - 136);\n out[base] = (r + 0.5) * f;\n out[base + 1] = (g + 0.5) * f;\n out[base + 2] = (b + 0.5) * f;\n }\n }\n\n return ok({ width, height, data: out });\n}\n\ninterface DecodedScanline {\n /** 4 channels, each width bytes */\n readonly 0: Uint8Array;\n readonly 1: Uint8Array;\n readonly 2: Uint8Array;\n readonly 3: Uint8Array;\n readonly totalBytes: number;\n}\n\n/**\n * Decode one scanline of new-RLE data into 4 channel arrays.\n * Returns null on failure (data underrun).\n */\nfunction decodeNewRleScanline(\n bytes: Uint8Array,\n start: number,\n width: number,\n): DecodedScanline | null {\n const channels: Uint8Array[] = [];\n let cursor = start;\n\n for (let ch = 0; ch < 4; ch++) {\n const data = new Uint8Array(width);\n let x = 0;\n while (x < width) {\n if (cursor >= bytes.length) return null;\n // biome-ignore lint/style/noNonNullAssertion: checked above\n const code = bytes[cursor]!;\n cursor++;\n if (code > 128) {\n // RLE run: next byte repeated (code - 128) times\n const count = code - 128;\n if (cursor >= bytes.length || x + count > width) return null;\n // biome-ignore lint/style/noNonNullAssertion: checked above\n const val = bytes[cursor]!;\n cursor++;\n data.fill(val, x, x + count);\n x += count;\n } else {\n // Literal run: next `code` bytes copied verbatim\n if (code === 0) continue;\n if (cursor + code > bytes.length || x + code > width) return null;\n for (let k = 0; k < code; k++) {\n // biome-ignore lint/style/noNonNullAssertion: checked above\n data[x + k] = bytes[cursor + k]!;\n }\n cursor += code;\n x += code;\n }\n }\n channels.push(data);\n }\n\n const ch0 = channels[0];\n const ch1 = channels[1];\n const ch2 = channels[2];\n const ch3 = channels[3];\n if (ch0 === undefined || ch1 === undefined || ch2 === undefined || ch3 === undefined) {\n return null; // channels should always have 4 entries\n }\n return {\n 0: ch0,\n 1: ch1,\n 2: ch2,\n 3: ch3,\n totalBytes: cursor - start,\n };\n}\n\nfunction findByte(bytes: Uint8Array, target: number, start: number): number {\n for (let i = start; i < bytes.length; i++) {\n if (bytes[i] === target) return i;\n }\n return -1;\n}\n\nfunction asciiSubstring(bytes: Uint8Array, start: number, end: number): string {\n let s = '';\n for (let i = start; i < end; i++) {\n // biome-ignore lint/style/noNonNullAssertion: in bounds\n s += String.fromCharCode(bytes[i]!);\n }\n return s;\n}\n\nfunction hdrDecodeError(reason: string): ImageError {\n return imageError({\n code: 'image-hdr-decode-failed',\n reason,\n });\n}\n","/** Deterministically downscale tight-packed RGBA pixels with bilinear sampling. */\nexport function downscaleRgba(\n bytes: Uint8Array,\n width: number,\n height: number,\n maxDimension: number,\n): { readonly bytes: Uint8Array; readonly width: number; readonly height: number } {\n if (width <= maxDimension && height <= maxDimension) {\n return { bytes, width, height };\n }\n\n const scale = maxDimension / Math.max(width, height);\n const targetWidth = Math.max(1, Math.round(width * scale));\n const targetHeight = Math.max(1, Math.round(height * scale));\n const output = new Uint8Array(targetWidth * targetHeight * 4);\n const source = (x: number, y: number, channel: number): number =>\n bytes[(y * width + x) * 4 + channel] ?? 0;\n\n for (let y = 0; y < targetHeight; y += 1) {\n const sourceY = ((y + 0.5) * height) / targetHeight - 0.5;\n const y0 = Math.max(0, Math.floor(sourceY));\n const y1 = Math.min(height - 1, y0 + 1);\n const yWeight = Math.max(0, Math.min(1, sourceY - y0));\n for (let x = 0; x < targetWidth; x += 1) {\n const sourceX = ((x + 0.5) * width) / targetWidth - 0.5;\n const x0 = Math.max(0, Math.floor(sourceX));\n const x1 = Math.min(width - 1, x0 + 1);\n const xWeight = Math.max(0, Math.min(1, sourceX - x0));\n const outputOffset = (y * targetWidth + x) * 4;\n for (let channel = 0; channel < 4; channel += 1) {\n const top = source(x0, y0, channel) * (1 - xWeight) + source(x1, y0, channel) * xWeight;\n const bottom = source(x0, y1, channel) * (1 - xWeight) + source(x1, y1, channel) * xWeight;\n output[outputOffset + channel] = Math.round(top * (1 - yWeight) + bottom * yWeight);\n }\n }\n }\n\n return { bytes: output, width: targetWidth, height: targetHeight };\n}\n","import type { DecodedImage, ImageColorSpace, ImageError } from '@forgeax/engine-types';\nimport * as jpeg from 'jpeg-js';\n// Static imports of upng-js + jpeg-js. tsup external keeps both as runtime\n// require() calls so the browser bundle still tree-shakes them out when\n// only image-decoder-browser.ts (createImageBitmap) is reached at runtime.\n// Synchronous decode is required by the parseImage signature contract (see\n// plan-strategy section 3.3).\nimport * as UPNG from 'upng-js';\nimport { imageError } from './errors.js';\nimport { downscaleRgba } from './resize-image.js';\nimport type { Result } from './result.js';\nimport { err, ok } from './result.js';\n\nexport interface ParseImageOptions {\n /**\n * Maximum allowed dimension (width or height). Defaults to 16384 -- the\n * conservative WebGPU `maxTextureDimension2D` floor across desktop +\n * high-end mobile (research F-3 / spec). Test fixtures pass smaller\n * values to exercise the bounds-check code path with tiny fixtures.\n */\n readonly maxDimension?: number;\n\n /** Optional asset-owned cooked-payload target; source bytes remain unchanged. */\n readonly downscaleMaxDimension?: number;\n\n /**\n * Sidecar `colorSpace` carried over to the DecodedImage POD. Defaults to\n * 'srgb' (typical baseColor / albedo path; plan-strategy section 2.5).\n */\n readonly colorSpace?: ImageColorSpace;\n\n /**\n * Sidecar `mipmap` flag carried over to DecodedImage. Defaults to true\n * (auto-generate via runtime mipmap-generator; plan-strategy section 2.6).\n */\n readonly mipmap?: boolean;\n\n /**\n * Optional source path to embed in error.detail.path (decode-failed /\n * format-unsupported variants). decodeImageFromFile fills this in;\n * in-memory parseImage callers leave it empty.\n */\n readonly path?: string;\n}\n\nconst DEFAULT_MAX_DIMENSION = 16384;\n\nconst SUPPORTED_MIMES: readonly string[] = ['image/png', 'image/jpeg', 'image/x-tga'];\n\ninterface UpngDecoded {\n readonly width: number;\n readonly height: number;\n readonly data: Uint8Array;\n}\n\ninterface JpegDecoded {\n readonly width: number;\n readonly height: number;\n readonly data: Uint8Array;\n}\n\n/**\n * Pure synchronous image decoder. Translates raw byte stream + mime hint\n * into a tight-packed RGBA `DecodedImage` POD. Surfaces all four\n * ImageErrorCode members through the structured `Result<DecodedImage,\n * ImageError>` return (charter P3 explicit failure; AGENTS.md \"Return\n * Result, never throw\").\n *\n * Decoder selection follows the mime hint:\n * - `image/png` -> upng-js `UPNG.decode` + `UPNG.toRGBA8`\n * - `image/jpeg` -> jpeg-js `decode` with `formatAsRGBA: true`\n * - `image/x-tga` -> the built-in TGA true-color/grayscale decoder\n * - other -> `image-format-unsupported`\n *\n * Decoded bytes are always tight-packed RGBA (`bytes.length === width *\n * height * 4`). `colorSpace` and `mipmap` are carried from the supplied\n * `ParseImageOptions` (decodeImageFromFile passes the sidecar settings).\n */\nexport function parseImage(\n bytes: Uint8Array,\n mime: string,\n opts: ParseImageOptions = {},\n): Result<DecodedImage, ImageError> {\n if (!SUPPORTED_MIMES.includes(mime)) {\n return err(\n imageError({\n code: 'image-format-unsupported',\n actualMime: mime,\n ...(opts.path !== undefined ? { path: opts.path } : {}),\n }),\n );\n }\n\n let width = 0;\n let height = 0;\n let rgba: Uint8Array;\n\n try {\n if (mime === 'image/png') {\n const upngMod: {\n decode: (b: Uint8Array | ArrayBuffer) => UpngDecoded;\n toRGBA8: (i: UpngDecoded) => ArrayBuffer[];\n } = ((UPNG as unknown as { default?: typeof UPNG }).default ?? UPNG) as unknown as {\n decode: (b: Uint8Array | ArrayBuffer) => UpngDecoded;\n toRGBA8: (i: UpngDecoded) => ArrayBuffer[];\n };\n const decoded = upngMod.decode(bytes);\n width = decoded.width;\n height = decoded.height;\n const frames = upngMod.toRGBA8(decoded);\n const first = frames[0];\n if (first === undefined) {\n return err(\n imageError({\n code: 'image-decode-failed',\n reason: 'UPNG.toRGBA8 returned no frames',\n ...(opts.path !== undefined ? { path: opts.path } : {}),\n }),\n );\n }\n rgba = new Uint8Array(first);\n } else if (mime === 'image/jpeg') {\n // image/jpeg\n const jpegMod: {\n decode: (\n b: Uint8Array | ArrayBuffer,\n o?: { useTArray?: boolean; formatAsRGBA?: boolean },\n ) => JpegDecoded;\n } = ((jpeg as unknown as { default?: typeof jpeg }).default ?? jpeg) as unknown as {\n decode: (\n b: Uint8Array | ArrayBuffer,\n o?: { useTArray?: boolean; formatAsRGBA?: boolean },\n ) => JpegDecoded;\n };\n const decoded = jpegMod.decode(bytes, { useTArray: true, formatAsRGBA: true });\n width = decoded.width;\n height = decoded.height;\n rgba = decoded.data;\n } else {\n const decoded = decodeTga(bytes);\n width = decoded.width;\n height = decoded.height;\n rgba = decoded.bytes;\n }\n } catch (e) {\n return err(\n imageError({\n code: 'image-decode-failed',\n reason: e instanceof Error ? e.message : String(e),\n ...(opts.path !== undefined ? { path: opts.path } : {}),\n }),\n );\n }\n\n const downscaleLimit = opts.downscaleMaxDimension;\n if (downscaleLimit !== undefined && Number.isInteger(downscaleLimit) && downscaleLimit > 0) {\n const downscaled = downscaleRgba(rgba, width, height, downscaleLimit);\n rgba = downscaled.bytes;\n width = downscaled.width;\n height = downscaled.height;\n }\n\n const limit = opts.maxDimension ?? DEFAULT_MAX_DIMENSION;\n if (width > limit || height > limit) {\n return err(\n imageError({\n code: 'image-dimension-out-of-bounds',\n requested: { width, height },\n limit,\n }),\n );\n }\n\n return ok({\n bytes: rgba,\n width,\n height,\n mime: mime as 'image/png' | 'image/jpeg' | 'image/x-tga',\n colorSpace: opts.colorSpace ?? 'srgb',\n mipmap: opts.mipmap ?? true,\n });\n}\n\ninterface TgaDecoded {\n readonly width: number;\n readonly height: number;\n readonly bytes: Uint8Array;\n}\n\n/**\n * Decode the portable TGA subset used by common FBX exports.\n *\n * The decoder owns the format boundary instead of trusting the extension: it\n * validates the header, supports uncompressed and RLE true-colour (24/32-bit)\n * plus grayscale (8-bit), and applies the origin bits from the image\n * descriptor while producing a top-left RGBA POD.\n */\nfunction decodeTga(bytes: Uint8Array): TgaDecoded {\n if (bytes.length < 18) throw new Error('TGA header is truncated');\n\n const colorMapType = bytes[1];\n const imageType = bytes[2];\n const idLength = bytes[0] ?? 0;\n const width = (bytes[12] ?? 0) | ((bytes[13] ?? 0) << 8);\n const height = (bytes[14] ?? 0) | ((bytes[15] ?? 0) << 8);\n const pixelDepth = bytes[16] ?? 0;\n const descriptor = bytes[17] ?? 0;\n if (colorMapType !== 0) throw new Error('color-mapped TGA is unsupported');\n if (width < 1 || height < 1) throw new Error('TGA dimensions must be positive');\n\n const isTrueColor = imageType === 2 || imageType === 10;\n const isGray = imageType === 3 || imageType === 11;\n if (!isTrueColor && !isGray) throw new Error(`TGA image type ${imageType} is unsupported`);\n if (isTrueColor && pixelDepth !== 24 && pixelDepth !== 32) {\n throw new Error(`TGA true-color depth ${pixelDepth} is unsupported`);\n }\n if (isGray && pixelDepth !== 8)\n throw new Error(`TGA grayscale depth ${pixelDepth} is unsupported`);\n\n const bytesPerPixel = pixelDepth / 8;\n let offset = 18 + idLength;\n const pixelCount = width * height;\n const out = new Uint8Array(pixelCount * 4);\n const topOrigin = (descriptor & 0x20) !== 0;\n const rightOrigin = (descriptor & 0x10) !== 0;\n let filePixel = 0;\n\n const writePixel = (pixel: Uint8Array): void => {\n if (filePixel >= pixelCount) throw new Error('TGA pixel data has too many pixels');\n const fileX = filePixel % width;\n const fileY = Math.floor(filePixel / width);\n const x = rightOrigin ? width - 1 - fileX : fileX;\n const y = topOrigin ? fileY : height - 1 - fileY;\n const destination = (y * width + x) * 4;\n if (isGray) {\n const value = pixel[0] ?? 0;\n out[destination] = value;\n out[destination + 1] = value;\n out[destination + 2] = value;\n out[destination + 3] = 255;\n } else {\n out[destination] = pixel[2] ?? 0;\n out[destination + 1] = pixel[1] ?? 0;\n out[destination + 2] = pixel[0] ?? 0;\n out[destination + 3] = bytesPerPixel === 4 ? (pixel[3] ?? 255) : 255;\n }\n filePixel += 1;\n };\n\n const readPixel = (): Uint8Array => {\n if (offset + bytesPerPixel > bytes.length) throw new Error('TGA pixel data is truncated');\n const pixel = bytes.slice(offset, offset + bytesPerPixel);\n offset += bytesPerPixel;\n return pixel;\n };\n\n if (imageType === 2 || imageType === 3) {\n while (filePixel < pixelCount) writePixel(readPixel());\n } else {\n while (filePixel < pixelCount) {\n if (offset >= bytes.length) throw new Error('TGA RLE packet header is truncated');\n const packet = bytes[offset++] ?? 0;\n const count = (packet & 0x7f) + 1;\n if (filePixel + count > pixelCount) throw new Error('TGA RLE packet exceeds image bounds');\n if ((packet & 0x80) !== 0) {\n const pixel = readPixel();\n for (let i = 0; i < count; i++) writePixel(pixel);\n } else {\n for (let i = 0; i < count; i++) writePixel(readPixel());\n }\n }\n }\n\n if (filePixel !== pixelCount) throw new Error('TGA pixel count mismatch');\n return { width, height, bytes: out };\n}\n","import { readFile, stat } from 'node:fs/promises';\nimport { dirname, extname, join } from 'node:path';\n\nimport { validateMeta } from '@forgeax/engine-pack';\nimport type { DecodedImage, ImageError, ImageMeta } from '@forgeax/engine-types';\nimport { imageError } from './errors.js';\nimport { decodeHdr, type HdrDecoded } from './hdr-decoder.js';\nimport { parseImage } from './parse-image.js';\nimport type { Result } from './result.js';\nimport { err, ok } from './result.js';\n\n/**\n * Sidecar JSON shape consumed by decodeImageFromFile. The schema is the\n * external-asset-package $defs from `packages/pack/schema/meta.schema.json`\n * (research F-9). Only the fields required by the Node-side decoder are\n * enumerated here; importSettings remains free-form per plan-strategy R5.\n */\ninterface SidecarMeta {\n readonly schemaVersion: string;\n readonly kind: 'external-asset-package';\n readonly importer: 'image';\n readonly source: string;\n readonly importSettings: Partial<{\n colorSpace: ImageMeta['colorSpace'];\n mipmap: ImageMeta['mipmap'];\n addressMode: ImageMeta['addressMode'];\n filterMode: ImageMeta['filterMode'];\n }>;\n readonly subAssets: ReadonlyArray<{\n readonly guid: string;\n readonly sourceIndex: number;\n readonly kind: string;\n }>;\n}\n\n/**\n * Result envelope returned by decodeImageFromFile for PNG/JPG sources.\n * Carries both the raw DecodedImage POD (for direct uploadTexture consumption)\n * and the parsed ImageMeta POD.\n */\nexport interface DecodedImageWithMeta {\n readonly decoded: DecodedImage;\n readonly meta: ImageMeta;\n}\n\n/**\n * Result envelope returned by decodeImageFromFile for .hdr sources.\n * Carries the HDR-decoded float data ready for cubemap upload.\n */\nexport interface DecodedHdrWithMeta {\n readonly hdr: HdrDecoded;\n readonly meta: ImageMeta;\n}\n\nconst EXT_TO_MIME: Readonly<\n Record<string, 'image/png' | 'image/jpeg' | 'image/x-tga' | 'image/vnd.radiance'>\n> = {\n '.png': 'image/png',\n '.jpg': 'image/jpeg',\n '.jpeg': 'image/jpeg',\n '.tga': 'image/x-tga',\n '.hdr': 'image/vnd.radiance',\n};\n\nfunction deriveSidecarPath(sourcePath: string): string {\n const dir = dirname(sourcePath);\n // sidecar lives next to source as <source-with-ext>.meta.json\n // (feat-20260521 unify-sidecar-meta-dispatch-by-content; the importer field\n // in the JSON drives importer dispatch instead of filename suffix).\n const base = sourcePath.slice(dir.length + 1);\n return join(dir, `${base}.meta.json`);\n}\n\nfunction sidecarSchemaFailureReason(): string {\n const firstError = validateMeta.errors?.[0];\n if (firstError === undefined) return 'sidecar schema validation failed';\n const location = firstError.instancePath === '' ? '/' : firstError.instancePath;\n return `sidecar schema validation failed: ${firstError.keyword} at ${location}`.slice(0, 256);\n}\n\nfunction imageSidecarFailureReason(\n sidecar: SidecarMeta,\n mime: (typeof EXT_TO_MIME)[keyof typeof EXT_TO_MIME],\n): string | undefined {\n const subAsset = sidecar.subAssets[0];\n const settings = sidecar.importSettings;\n const expectedKind = mime === 'image/vnd.radiance' ? 'equirect' : 'texture';\n if (sidecar.importer !== 'image') return 'image sidecar importer must be image';\n if (sidecar.subAssets.length !== 1 || subAsset === undefined) {\n return 'image sidecar must declare one sub-asset';\n }\n if (subAsset.kind !== expectedKind || subAsset.sourceIndex !== 0) {\n return `image sidecar sub-asset must be ${expectedKind} at sourceIndex 0`;\n }\n if (settings.colorSpace !== undefined && !['srgb', 'linear'].includes(settings.colorSpace)) {\n return 'image sidecar colorSpace must be srgb or linear';\n }\n if (settings.mipmap !== undefined && !['auto', 'none'].includes(settings.mipmap)) {\n return 'image sidecar mipmap must be auto or none';\n }\n if (\n settings.addressMode !== undefined &&\n !['repeat', 'clamp-to-edge', 'mirror-repeat'].includes(settings.addressMode)\n ) {\n return 'image sidecar addressMode is unsupported';\n }\n if (settings.filterMode !== undefined && !['nearest', 'linear'].includes(settings.filterMode)) {\n return 'image sidecar filterMode is unsupported';\n }\n return undefined;\n}\n\n/**\n * Async file-system entry to the image importer (plan-strategy section 3.2\n * sequence A; AC-17 path (a) sidecar three-way fallback).\n *\n * Behaviour (left-to-right, fail-fast on first surfaced ImageError):\n * 1. Sniff extension -- not in `.png / .jpg / .jpeg` -> image-format-unsupported\n * 2. Stat source -- absent -> image-decode-failed with the source path\n * 3. Stat sibling `<source>.meta.json` (importer: 'image') -- absent -> image-meta-missing\n * 4. Read source bytes + parse sidecar JSON\n * 5. Hand off to `parseImage(bytes, mime, opts)` -- surfaces image-decode-failed\n * / image-format-unsupported / image-dimension-out-of-bounds\n * 6. Compose DecodedImage POD + ImageMeta POD return envelope.\n *\n * AC-17 path (a) lock: when the sidecar is absent, the returned error\n * carries `detail.sourcePath` + `detail.expectedSidecarPath` so AI users\n * read .hint and run `forgeax-engine-remote-asset import <path>` to\n * self-recover (charter P3 explicit failure + IDE jump-to-source).\n */\nexport async function decodeImageFromFile(\n sourcePath: string,\n): Promise<Result<DecodedImageWithMeta, ImageError>> {\n const ext = extname(sourcePath).toLowerCase();\n const mime = EXT_TO_MIME[ext];\n if (mime === undefined) {\n return err(\n imageError({\n code: 'image-format-unsupported',\n actualMime: `extension '${ext || '<none>'}'`,\n path: sourcePath,\n }),\n );\n }\n\n const sidecarPath = deriveSidecarPath(sourcePath);\n\n try {\n await stat(sourcePath);\n } catch (e) {\n return err(\n imageError({\n code: 'image-decode-failed',\n reason: `failed to read source: ${e instanceof Error ? e.message : String(e)}`,\n path: sourcePath,\n }),\n );\n }\n\n try {\n await stat(sidecarPath);\n } catch {\n return err(\n imageError({\n code: 'image-meta-missing',\n sourcePath,\n expectedSidecarPath: sidecarPath,\n }),\n );\n }\n\n let sidecarText: string;\n try {\n sidecarText = (await readFile(sidecarPath)).toString('utf8');\n } catch (e) {\n return err(\n imageError({\n code: 'image-decode-failed',\n reason: `failed to read sidecar: ${e instanceof Error ? e.message : String(e)}`,\n path: sidecarPath,\n }),\n );\n }\n\n let parsedSidecar: unknown;\n try {\n parsedSidecar = JSON.parse(sidecarText) as unknown;\n } catch (e) {\n return err(\n imageError({\n code: 'image-decode-failed',\n reason: `sidecar JSON parse failed: ${e instanceof Error ? e.message : String(e)}`,\n path: sidecarPath,\n }),\n );\n }\n\n if (!validateMeta(parsedSidecar)) {\n return err(\n imageError({\n code: 'image-decode-failed',\n reason: sidecarSchemaFailureReason(),\n path: sidecarPath,\n }),\n );\n }\n\n const sidecar = parsedSidecar as unknown as SidecarMeta;\n const imageSidecarFailure = imageSidecarFailureReason(sidecar, mime);\n if (imageSidecarFailure !== undefined) {\n return err(\n imageError({\n code: 'image-decode-failed',\n reason: imageSidecarFailure,\n path: sidecarPath,\n }),\n );\n }\n\n const settings = sidecar.importSettings ?? {};\n const colorSpace: ImageMeta['colorSpace'] = settings.colorSpace ?? 'srgb';\n const mipmap: ImageMeta['mipmap'] = settings.mipmap ?? 'auto';\n const addressMode: ImageMeta['addressMode'] = settings.addressMode ?? 'repeat';\n const filterMode: ImageMeta['filterMode'] = settings.filterMode ?? 'linear';\n const guid = sidecar.subAssets[0]?.guid ?? '';\n\n const meta: ImageMeta = {\n guid,\n colorSpace,\n mipmap,\n addressMode,\n filterMode,\n };\n\n let bytes: Uint8Array;\n try {\n const buf = await readFile(sourcePath);\n bytes = new Uint8Array(buf);\n } catch (e) {\n return err(\n imageError({\n code: 'image-decode-failed',\n reason: `failed to read source: ${e instanceof Error ? e.message : String(e)}`,\n path: sourcePath,\n }),\n );\n }\n\n // .hdr path: delegate to HDR decoder, enforce linear color space\n if (mime === 'image/vnd.radiance') {\n if (colorSpace !== 'linear') {\n return err(\n imageError({\n code: 'image-format-unsupported',\n actualMime: `colorSpace conflict: HDR requires linear, got ${colorSpace}`,\n path: sourcePath,\n }),\n );\n }\n const hdrRes = decodeHdr(bytes);\n if (!hdrRes.ok) return err(hdrRes.error);\n // DecodedImageWithMeta expects DecodedImage; for HDR we return the HDR\n // variant through a separate export path. Downstream consumers\n // distinguish by file extension.\n // For the DecodedImageWithMeta return type compatibility, we synthesise\n // a minimal DecodedImage POD carrying the byte representation.\n // This keeps the return type stable for existing PNG/JPG callers.\n // The HDR data is available through the separate decodeHdr export.\n const rgba8 = new Uint8Array(hdrRes.value.width * hdrRes.value.height * 4);\n // convert float to quantised 8-bit for the POD shape (lossy; consumers\n // should use decodeHdr directly for float precision)\n for (let i = 0; i < hdrRes.value.data.length; i++) {\n const v = hdrRes.value.data[i];\n const clamped = v !== undefined ? Math.max(0, Math.min(1, v as number)) : 0;\n rgba8[i] = Math.round(clamped * 255);\n }\n const decoded: DecodedImage = {\n bytes: rgba8,\n width: hdrRes.value.width,\n height: hdrRes.value.height,\n mime: 'image/jpeg',\n colorSpace: 'linear',\n mipmap: false,\n };\n return ok({ decoded, meta });\n }\n\n const r = parseImage(bytes, mime, {\n colorSpace,\n mipmap: mipmap === 'auto',\n path: sourcePath,\n });\n if (!r.ok) {\n return err(r.error);\n }\n\n return ok({ decoded: r.value, meta });\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/errors.ts","../src/hdr-decoder.ts","../src/resize-image.ts","../src/parse-image.ts","../src/decode-image-from-file.ts"],"names":[],"mappings":"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","file":"decode-image-from-file.mjs","sourcesContent":["import type { ImageErrorCode, ImageErrorDetailFor, ImageErrorFor } from '@forgeax/engine-types';\nimport { IMAGE_ERROR_HINTS } from '@forgeax/engine-types';\n\n// Per-code .expected string literals SSOT.\n// Mirrors ASSET_ERROR_HINTS / IMAGE_ERROR_HINTS shape (charter P5 consistent\n// abstraction). AI users surface .expected when a structured error needs to\n// describe the precondition that was violated.\nconst IMAGE_ERROR_EXPECTED: Readonly<Record<ImageErrorCode, string>> = {\n 'image-decode-failed': 'PNG / JPG byte stream decodes successfully',\n 'image-format-unsupported':\n \"mime is one of ['image/png', 'image/jpeg']; uploadTexture format <-> colorSpace family agrees\",\n 'image-dimension-out-of-bounds':\n 'width and height fall under device caps maxTextureDimension2D (or 16384 hard cap)',\n 'image-meta-missing':\n \"<source>.meta.json sidecar (importer: 'image') exists in the same directory\",\n 'image-hdr-decode-failed': 'Radiance RGBE header is valid and pixel data decodes successfully',\n // feat-20260521-sprite-atlas-animation M1 T-03 — vite-plugin-image atlas\n // hook .expected literals (plan-strategy section 2 D-2 + AC-10 a/b/c).\n // ImageErrorImpl construction path is unchanged: the new atlas-* errors\n // flow through `new ImageErrorImpl({ code: 'atlas-...', ...detail })` and\n // pick the .expected string up from this Record at construction time\n // (charter P3 explicit failure SSOT — AI users surface .expected next to\n // .hint after switch (err.code) without parsing the message).\n 'atlas-empty-input': 'images.length >= 1',\n 'atlas-size-exceeded':\n 'image width x height <= maxAtlasSize^2 and each image fits in the atlas footprint',\n 'atlas-region-mismatch': 'sum(regions[i].w x regions[i].h) <= atlasWidth x atlasHeight',\n};\n\n/** Runtime implementation of the correlated `ImageErrorFor<C>` envelope. */\nexport class ImageErrorImpl<C extends ImageErrorCode = ImageErrorCode>\n extends Error\n implements ImageErrorFor<C>\n{\n readonly code: C;\n readonly expected: string;\n readonly hint: string;\n readonly detail: ImageErrorDetailFor<C>;\n\n constructor(detail: ImageErrorDetailFor<C>) {\n const code = detail.code;\n const expected = IMAGE_ERROR_EXPECTED[code];\n const hint = IMAGE_ERROR_HINTS[code];\n super(`[ImageError ${code}] expected: ${expected}; hint: ${hint}`);\n this.name = 'ImageError';\n this.code = code;\n this.expected = expected;\n this.hint = hint;\n this.detail = detail;\n }\n}\n\n/** Construct a structured image error while preserving its code/detail pair. */\nexport function imageError<C extends ImageErrorCode>(\n detail: ImageErrorDetailFor<C>,\n): ImageErrorFor<C> {\n return new ImageErrorImpl<C>(detail);\n}\n\nexport { IMAGE_ERROR_EXPECTED };\n","import type { ImageError } from '@forgeax/engine-types';\nimport { imageError } from './errors.js';\nimport type { Result } from './result.js';\nimport { err, ok } from './result.js';\n\n/**\n * Decoded HDR image POD.\n *\n * `data` is an interleaved RGBA Float32Array (4 floats per pixel, row-major).\n * Color space is always linear (RGBE is radiance).\n */\nexport interface HdrDecoded {\n readonly width: number;\n readonly height: number;\n readonly data: Float32Array;\n}\n\n// ASCII code points\nconst LF = 0x0a;\n\n/**\n * Decode a Radiance RGBE (.hdr) byte stream into linear RGBA float pixels.\n *\n * Steps per research F-6:\n * 1. Parse ASCII header: magic (#?RADIANCE), FORMAT=32-bit_rle_rgbe,\n * resolution line (-Y height +X width).\n * 2. Decode per-scanline new-RLE pixel data: 4 independent channel runs.\n * 3. Convert RGBE to float: if E==0 -> 0; else f=2^(E-136), R=(Rm+0.5)*f.\n * 4. Output as interleaved RGBA Float32Array (alpha=1.0).\n *\n * Old-RLE scanlines (prefix not matching 0x02,0x02,hi,lo with correct width)\n * return image-hdr-decode-failed.\n */\nexport function decodeHdr(bytes: Uint8Array): Result<HdrDecoded, ImageError> {\n let pos = 0;\n\n // Step 1a: magic -- must start with \"#?RADIANCE\" or \"#?RGBE\"\n if (bytes.length < 11) {\n return err(hdrDecodeError('file too short for Radiance HDR header'));\n }\n const magic = String.fromCharCode(...bytes.subarray(0, 11));\n if (magic !== '#?RADIANCE\\n' && magic !== '#?RGBE\\n') {\n return err(\n hdrDecodeError('missing or invalid Radiance HDR magic; expected #?RADIANCE or #?RGBE'),\n );\n }\n pos = 11;\n\n // Step 1b: key=value lines until empty line\n let formatFound = false;\n while (pos < bytes.length) {\n const lineEnd = findByte(bytes, LF, pos);\n if (lineEnd === -1) return err(hdrDecodeError('header truncated before empty line'));\n const line = asciiSubstring(bytes, pos, lineEnd);\n pos = lineEnd + 1;\n if (line === '') break;\n if (line.startsWith('FORMAT=')) {\n const val = line.slice(7);\n if (val === '32-bit_rle_rgbe') {\n formatFound = true;\n }\n }\n }\n if (!formatFound) {\n return err(hdrDecodeError('missing or unsupported FORMAT; expected FORMAT=32-bit_rle_rgbe'));\n }\n\n // Step 1c: resolution line (-Y height +X width)\n if (pos >= bytes.length) return err(hdrDecodeError('missing resolution line'));\n const resLineEnd = findByte(bytes, LF, pos);\n if (resLineEnd === -1) return err(hdrDecodeError('resolution line truncated'));\n const resLine = asciiSubstring(bytes, pos, resLineEnd);\n pos = resLineEnd + 1;\n const resMatch = /^-Y\\s+(\\d+)\\s+\\+X\\s+(\\d+)\\s*$/.exec(resLine);\n if (resMatch === null) {\n return err(\n hdrDecodeError(`unexpected resolution line format: \"${resLine}\"; expected \"-Y H +X W\"`),\n );\n }\n const height = Number(resMatch[1]);\n const width = Number(resMatch[2]);\n if (height === undefined || width === undefined || height <= 0 || width <= 0) {\n return err(hdrDecodeError(`invalid dimensions: ${width}x${height}`));\n }\n\n // Step 2: decode per-scanline new-RLE pixel data\n const pixelCount = width * height;\n const rgbe = new Uint8Array(pixelCount * 4);\n const scanlineBytes = width * 4;\n\n for (let y = 0; y < height; y++) {\n if (pos + 4 > bytes.length) {\n return err(hdrDecodeError(`truncated at scanline ${y}: expected 4-byte prefix`));\n }\n const p0 = bytes[pos];\n const p1 = bytes[pos + 1];\n const pHi = bytes[pos + 2];\n const pLo = bytes[pos + 3];\n if (p0 === undefined || p1 === undefined || pHi === undefined || pLo === undefined) {\n return err(hdrDecodeError(`truncated at scanline ${y}`));\n }\n // biome-ignore lint/style/noNonNullAssertion: checked above\n const prefixWidth = (pHi! << 8) | pLo!;\n\n if (p0 === 0x02 && p1 === 0x02 && prefixWidth === width && width >= 8 && width <= 32767) {\n // New-RLE: 4 channels independently RLE-encoded, then interleaved\n const decoded = decodeNewRleScanline(bytes, pos + 4, width);\n if (decoded === null) {\n return err(hdrDecodeError(`RLE decode failed at scanline ${y}`));\n }\n // Copy into rgbe interleaved buffer\n const base = y * scanlineBytes;\n const channels = [decoded[0], decoded[1], decoded[2], decoded[3]];\n for (let x = 0; x < width; x++) {\n for (let ch = 0; ch < 4; ch++) {\n // biome-ignore lint/style/noNonNullAssertion: checked at allocation\n rgbe[base + x * 4 + ch] = channels[ch]![x]!;\n }\n }\n // Advance pos past the 4-byte prefix + the total RLE bytes consumed\n pos += 4 + decoded.totalBytes;\n } else {\n // Old-RLE: not supported\n return err(\n hdrDecodeError(\n 'old-RLE format is not supported; only new-RLE (32-bit_rle_rgbe) is accepted',\n ),\n );\n }\n }\n\n // Step 3: RGBE to float\n const out = new Float32Array(pixelCount * 4);\n for (let i = 0; i < pixelCount; i++) {\n const base = i * 4;\n // biome-ignore lint/style/noNonNullAssertion: checked at allocation\n const r = rgbe[base]!;\n // biome-ignore lint/style/noNonNullAssertion: checked at allocation\n const g = rgbe[base + 1]!;\n // biome-ignore lint/style/noNonNullAssertion: checked at allocation\n const b = rgbe[base + 2]!;\n // biome-ignore lint/style/noNonNullAssertion: checked at allocation\n const e = rgbe[base + 3]!;\n out[base + 3] = 1.0;\n\n if (e === 0) {\n out[base] = 0;\n out[base + 1] = 0;\n out[base + 2] = 0;\n } else {\n // f = 2^(E - 136) = ldexp(1.0, E - 128 - 8)\n // Equivalent: ldexp(1.0 / 256.0, E - 128)\n // But simpler: const f = (1.0 / 256.0) * Math.pow(2, e - 128);\n // Using ldexp: Math.pow(2, e - 136)\n const f = 2 ** (e - 136);\n out[base] = (r + 0.5) * f;\n out[base + 1] = (g + 0.5) * f;\n out[base + 2] = (b + 0.5) * f;\n }\n }\n\n return ok({ width, height, data: out });\n}\n\ninterface DecodedScanline {\n /** 4 channels, each width bytes */\n readonly 0: Uint8Array;\n readonly 1: Uint8Array;\n readonly 2: Uint8Array;\n readonly 3: Uint8Array;\n readonly totalBytes: number;\n}\n\n/**\n * Decode one scanline of new-RLE data into 4 channel arrays.\n * Returns null on failure (data underrun).\n */\nfunction decodeNewRleScanline(\n bytes: Uint8Array,\n start: number,\n width: number,\n): DecodedScanline | null {\n const channels: Uint8Array[] = [];\n let cursor = start;\n\n for (let ch = 0; ch < 4; ch++) {\n const data = new Uint8Array(width);\n let x = 0;\n while (x < width) {\n if (cursor >= bytes.length) return null;\n // biome-ignore lint/style/noNonNullAssertion: checked above\n const code = bytes[cursor]!;\n cursor++;\n if (code > 128) {\n // RLE run: next byte repeated (code - 128) times\n const count = code - 128;\n if (cursor >= bytes.length || x + count > width) return null;\n // biome-ignore lint/style/noNonNullAssertion: checked above\n const val = bytes[cursor]!;\n cursor++;\n data.fill(val, x, x + count);\n x += count;\n } else {\n // Literal run: next `code` bytes copied verbatim\n if (code === 0) continue;\n if (cursor + code > bytes.length || x + code > width) return null;\n for (let k = 0; k < code; k++) {\n // biome-ignore lint/style/noNonNullAssertion: checked above\n data[x + k] = bytes[cursor + k]!;\n }\n cursor += code;\n x += code;\n }\n }\n channels.push(data);\n }\n\n const ch0 = channels[0];\n const ch1 = channels[1];\n const ch2 = channels[2];\n const ch3 = channels[3];\n if (ch0 === undefined || ch1 === undefined || ch2 === undefined || ch3 === undefined) {\n return null; // channels should always have 4 entries\n }\n return {\n 0: ch0,\n 1: ch1,\n 2: ch2,\n 3: ch3,\n totalBytes: cursor - start,\n };\n}\n\nfunction findByte(bytes: Uint8Array, target: number, start: number): number {\n for (let i = start; i < bytes.length; i++) {\n if (bytes[i] === target) return i;\n }\n return -1;\n}\n\nfunction asciiSubstring(bytes: Uint8Array, start: number, end: number): string {\n let s = '';\n for (let i = start; i < end; i++) {\n // biome-ignore lint/style/noNonNullAssertion: in bounds\n s += String.fromCharCode(bytes[i]!);\n }\n return s;\n}\n\nfunction hdrDecodeError(reason: string): ImageError {\n return imageError({\n code: 'image-hdr-decode-failed',\n reason,\n });\n}\n","/** Deterministically downscale tight-packed RGBA pixels with bilinear sampling. */\nexport function downscaleRgba(\n bytes: Uint8Array,\n width: number,\n height: number,\n maxDimension: number,\n): { readonly bytes: Uint8Array; readonly width: number; readonly height: number } {\n if (width <= maxDimension && height <= maxDimension) {\n return { bytes, width, height };\n }\n\n const scale = maxDimension / Math.max(width, height);\n const targetWidth = Math.max(1, Math.round(width * scale));\n const targetHeight = Math.max(1, Math.round(height * scale));\n const output = new Uint8Array(targetWidth * targetHeight * 4);\n const source = (x: number, y: number, channel: number): number =>\n bytes[(y * width + x) * 4 + channel] ?? 0;\n\n for (let y = 0; y < targetHeight; y += 1) {\n const sourceY = ((y + 0.5) * height) / targetHeight - 0.5;\n const y0 = Math.max(0, Math.floor(sourceY));\n const y1 = Math.min(height - 1, y0 + 1);\n const yWeight = Math.max(0, Math.min(1, sourceY - y0));\n for (let x = 0; x < targetWidth; x += 1) {\n const sourceX = ((x + 0.5) * width) / targetWidth - 0.5;\n const x0 = Math.max(0, Math.floor(sourceX));\n const x1 = Math.min(width - 1, x0 + 1);\n const xWeight = Math.max(0, Math.min(1, sourceX - x0));\n const outputOffset = (y * targetWidth + x) * 4;\n for (let channel = 0; channel < 4; channel += 1) {\n const top = source(x0, y0, channel) * (1 - xWeight) + source(x1, y0, channel) * xWeight;\n const bottom = source(x0, y1, channel) * (1 - xWeight) + source(x1, y1, channel) * xWeight;\n output[outputOffset + channel] = Math.round(top * (1 - yWeight) + bottom * yWeight);\n }\n }\n }\n\n return { bytes: output, width: targetWidth, height: targetHeight };\n}\n","import type { DecodedImage, ImageColorSpace, ImageError } from '@forgeax/engine-types';\nimport * as jpeg from 'jpeg-js';\n// Static imports of upng-js + jpeg-js. tsup external keeps both as runtime\n// require() calls so the browser bundle still tree-shakes them out when\n// only image-decoder-browser.ts (createImageBitmap) is reached at runtime.\n// Synchronous decode is required by the parseImage signature contract (see\n// plan-strategy section 3.3).\nimport * as UPNG from 'upng-js';\nimport { imageError } from './errors.js';\nimport { downscaleRgba } from './resize-image.js';\nimport type { Result } from './result.js';\nimport { err, ok } from './result.js';\n\nexport interface ParseImageOptions {\n /**\n * Maximum allowed dimension (width or height). Defaults to 16384 -- the\n * conservative WebGPU `maxTextureDimension2D` floor across desktop +\n * high-end mobile (research F-3 / spec). Test fixtures pass smaller\n * values to exercise the bounds-check code path with tiny fixtures.\n */\n readonly maxDimension?: number;\n\n /** Optional asset-owned cooked-payload target; source bytes remain unchanged. */\n readonly downscaleMaxDimension?: number;\n\n /**\n * Sidecar `colorSpace` carried over to the DecodedImage POD. Defaults to\n * 'srgb' (typical baseColor / albedo path; plan-strategy section 2.5).\n */\n readonly colorSpace?: ImageColorSpace;\n\n /**\n * Sidecar `mipmap` flag carried over to DecodedImage. Defaults to true\n * (auto-generate via runtime mipmap-generator; plan-strategy section 2.6).\n */\n readonly mipmap?: boolean;\n\n /**\n * Optional source path to embed in error.detail.path (decode-failed /\n * format-unsupported variants). decodeImageFromFile fills this in;\n * in-memory parseImage callers leave it empty.\n */\n readonly path?: string;\n}\n\nconst DEFAULT_MAX_DIMENSION = 16384;\n\nconst SUPPORTED_MIMES: readonly string[] = ['image/png', 'image/jpeg'];\n\ninterface UpngDecoded {\n readonly width: number;\n readonly height: number;\n readonly data: Uint8Array;\n}\n\ninterface JpegDecoded {\n readonly width: number;\n readonly height: number;\n readonly data: Uint8Array;\n}\n\n/**\n * Pure synchronous image decoder. Translates raw byte stream + mime hint\n * into a tight-packed RGBA `DecodedImage` POD. Surfaces all four\n * ImageErrorCode members through the structured `Result<DecodedImage,\n * ImageError>` return (charter P3 explicit failure; AGENTS.md \"Return\n * Result, never throw\").\n *\n * Decoder selection follows the mime hint:\n * - `image/png` -> upng-js `UPNG.decode` + `UPNG.toRGBA8`\n * - `image/jpeg` -> jpeg-js `decode` with `formatAsRGBA: true`\n * - other -> `image-format-unsupported`\n *\n * Decoded bytes are always tight-packed RGBA (`bytes.length === width *\n * height * 4`). `colorSpace` and `mipmap` are carried from the supplied\n * `ParseImageOptions` (decodeImageFromFile passes the sidecar settings).\n */\nexport function parseImage(\n bytes: Uint8Array,\n mime: string,\n opts: ParseImageOptions = {},\n): Result<DecodedImage, ImageError> {\n if (!SUPPORTED_MIMES.includes(mime)) {\n return err(\n imageError({\n code: 'image-format-unsupported',\n actualMime: mime,\n ...(opts.path !== undefined ? { path: opts.path } : {}),\n }),\n );\n }\n\n let width = 0;\n let height = 0;\n let rgba: Uint8Array;\n\n try {\n if (mime === 'image/png') {\n const upngMod: {\n decode: (b: Uint8Array | ArrayBuffer) => UpngDecoded;\n toRGBA8: (i: UpngDecoded) => ArrayBuffer[];\n } = ((UPNG as unknown as { default?: typeof UPNG }).default ?? UPNG) as unknown as {\n decode: (b: Uint8Array | ArrayBuffer) => UpngDecoded;\n toRGBA8: (i: UpngDecoded) => ArrayBuffer[];\n };\n const decoded = upngMod.decode(bytes);\n width = decoded.width;\n height = decoded.height;\n const frames = upngMod.toRGBA8(decoded);\n const first = frames[0];\n if (first === undefined) {\n return err(\n imageError({\n code: 'image-decode-failed',\n reason: 'UPNG.toRGBA8 returned no frames',\n ...(opts.path !== undefined ? { path: opts.path } : {}),\n }),\n );\n }\n rgba = new Uint8Array(first);\n } else {\n // image/jpeg\n const jpegMod: {\n decode: (\n b: Uint8Array | ArrayBuffer,\n o?: { useTArray?: boolean; formatAsRGBA?: boolean },\n ) => JpegDecoded;\n } = ((jpeg as unknown as { default?: typeof jpeg }).default ?? jpeg) as unknown as {\n decode: (\n b: Uint8Array | ArrayBuffer,\n o?: { useTArray?: boolean; formatAsRGBA?: boolean },\n ) => JpegDecoded;\n };\n const decoded = jpegMod.decode(bytes, { useTArray: true, formatAsRGBA: true });\n width = decoded.width;\n height = decoded.height;\n rgba = decoded.data;\n }\n } catch (e) {\n return err(\n imageError({\n code: 'image-decode-failed',\n reason: e instanceof Error ? e.message : String(e),\n ...(opts.path !== undefined ? { path: opts.path } : {}),\n }),\n );\n }\n\n const downscaleLimit = opts.downscaleMaxDimension;\n if (downscaleLimit !== undefined && Number.isInteger(downscaleLimit) && downscaleLimit > 0) {\n const downscaled = downscaleRgba(rgba, width, height, downscaleLimit);\n rgba = downscaled.bytes;\n width = downscaled.width;\n height = downscaled.height;\n }\n\n const limit = opts.maxDimension ?? DEFAULT_MAX_DIMENSION;\n if (width > limit || height > limit) {\n return err(\n imageError({\n code: 'image-dimension-out-of-bounds',\n requested: { width, height },\n limit,\n }),\n );\n }\n\n return ok({\n bytes: rgba,\n width,\n height,\n mime: mime as 'image/png' | 'image/jpeg',\n colorSpace: opts.colorSpace ?? 'srgb',\n mipmap: opts.mipmap ?? true,\n });\n}\n","import { readFile, stat } from 'node:fs/promises';\nimport { dirname, extname, join } from 'node:path';\n\nimport type { DecodedImage, ImageError, ImageMeta } from '@forgeax/engine-types';\nimport { imageError } from './errors.js';\nimport { decodeHdr, type HdrDecoded } from './hdr-decoder.js';\nimport { parseImage } from './parse-image.js';\nimport type { Result } from './result.js';\nimport { err, ok } from './result.js';\n\n/**\n * Sidecar JSON shape consumed by decodeImageFromFile. The schema is the\n * external-asset-package $defs from `packages/pack/schema/meta.schema.json`\n * (research F-9). Only the fields required by the Node-side decoder are\n * enumerated here; importSettings remains free-form per plan-strategy R5.\n */\ninterface SidecarMeta {\n readonly schemaVersion: string;\n readonly kind: 'external-asset-package';\n readonly importer: 'image';\n readonly source: string;\n readonly importSettings: Partial<{\n colorSpace: ImageMeta['colorSpace'];\n mipmap: ImageMeta['mipmap'];\n addressMode: ImageMeta['addressMode'];\n filterMode: ImageMeta['filterMode'];\n }>;\n readonly subAssets: ReadonlyArray<{\n readonly guid: string;\n readonly sourceIndex: number;\n readonly kind: string;\n }>;\n}\n\n/**\n * Result envelope returned by decodeImageFromFile for PNG/JPG sources.\n * Carries both the raw DecodedImage POD (for direct uploadTexture consumption)\n * and the parsed ImageMeta POD.\n */\nexport interface DecodedImageWithMeta {\n readonly decoded: DecodedImage;\n readonly meta: ImageMeta;\n}\n\n/**\n * Result envelope returned by decodeImageFromFile for .hdr sources.\n * Carries the HDR-decoded float data ready for cubemap upload.\n */\nexport interface DecodedHdrWithMeta {\n readonly hdr: HdrDecoded;\n readonly meta: ImageMeta;\n}\n\nconst EXT_TO_MIME: Readonly<Record<string, 'image/png' | 'image/jpeg' | 'image/vnd.radiance'>> = {\n '.png': 'image/png',\n '.jpg': 'image/jpeg',\n '.jpeg': 'image/jpeg',\n '.hdr': 'image/vnd.radiance',\n};\n\nfunction deriveSidecarPath(sourcePath: string): string {\n const dir = dirname(sourcePath);\n // sidecar lives next to source as <source-with-ext>.meta.json\n // (feat-20260521 unify-sidecar-meta-dispatch-by-content; the importer field\n // in the JSON drives importer dispatch instead of filename suffix).\n const base = sourcePath.slice(dir.length + 1);\n return join(dir, `${base}.meta.json`);\n}\n\n/**\n * Async file-system entry to the image importer (plan-strategy section 3.2\n * sequence A; AC-17 path (a) sidecar three-way fallback).\n *\n * Behaviour (left-to-right, fail-fast on first surfaced ImageError):\n * 1. Sniff extension -- not in `.png / .jpg / .jpeg` -> image-format-unsupported\n * 2. Stat source -- absent -> image-decode-failed with the source path\n * 3. Stat sibling `<source>.meta.json` (importer: 'image') -- absent -> image-meta-missing\n * 4. Read source bytes + parse sidecar JSON\n * 5. Hand off to `parseImage(bytes, mime, opts)` -- surfaces image-decode-failed\n * / image-format-unsupported / image-dimension-out-of-bounds\n * 6. Compose DecodedImage POD + ImageMeta POD return envelope.\n *\n * AC-17 path (a) lock: when the sidecar is absent, the returned error\n * carries `detail.sourcePath` + `detail.expectedSidecarPath` so AI users\n * read .hint and run `forgeax-engine-remote-asset import <path>` to\n * self-recover (charter P3 explicit failure + IDE jump-to-source).\n */\nexport async function decodeImageFromFile(\n sourcePath: string,\n): Promise<Result<DecodedImageWithMeta, ImageError>> {\n const ext = extname(sourcePath).toLowerCase();\n const mime = EXT_TO_MIME[ext];\n if (mime === undefined) {\n return err(\n imageError({\n code: 'image-format-unsupported',\n actualMime: `extension '${ext || '<none>'}'`,\n path: sourcePath,\n }),\n );\n }\n\n const sidecarPath = deriveSidecarPath(sourcePath);\n\n try {\n await stat(sourcePath);\n } catch (e) {\n return err(\n imageError({\n code: 'image-decode-failed',\n reason: `failed to read source: ${e instanceof Error ? e.message : String(e)}`,\n path: sourcePath,\n }),\n );\n }\n\n try {\n await stat(sidecarPath);\n } catch {\n return err(\n imageError({\n code: 'image-meta-missing',\n sourcePath,\n expectedSidecarPath: sidecarPath,\n }),\n );\n }\n\n let sidecarText: string;\n try {\n sidecarText = (await readFile(sidecarPath)).toString('utf8');\n } catch (e) {\n return err(\n imageError({\n code: 'image-decode-failed',\n reason: `failed to read sidecar: ${e instanceof Error ? e.message : String(e)}`,\n path: sidecarPath,\n }),\n );\n }\n\n let sidecar: SidecarMeta;\n try {\n sidecar = JSON.parse(sidecarText) as SidecarMeta;\n } catch (e) {\n return err(\n imageError({\n code: 'image-decode-failed',\n reason: `sidecar JSON parse failed: ${e instanceof Error ? e.message : String(e)}`,\n path: sidecarPath,\n }),\n );\n }\n\n const settings = sidecar.importSettings ?? {};\n const colorSpace: ImageMeta['colorSpace'] = settings.colorSpace ?? 'srgb';\n const mipmap: ImageMeta['mipmap'] = settings.mipmap ?? 'auto';\n const addressMode: ImageMeta['addressMode'] = settings.addressMode ?? 'repeat';\n const filterMode: ImageMeta['filterMode'] = settings.filterMode ?? 'linear';\n const guid = sidecar.subAssets[0]?.guid ?? '';\n\n const meta: ImageMeta = {\n guid,\n colorSpace,\n mipmap,\n addressMode,\n filterMode,\n };\n\n let bytes: Uint8Array;\n try {\n const buf = await readFile(sourcePath);\n bytes = new Uint8Array(buf);\n } catch (e) {\n return err(\n imageError({\n code: 'image-decode-failed',\n reason: `failed to read source: ${e instanceof Error ? e.message : String(e)}`,\n path: sourcePath,\n }),\n );\n }\n\n // .hdr path: delegate to HDR decoder, enforce linear color space\n if (mime === 'image/vnd.radiance') {\n if (colorSpace !== 'linear') {\n return err(\n imageError({\n code: 'image-format-unsupported',\n actualMime: `colorSpace conflict: HDR requires linear, got ${colorSpace}`,\n path: sourcePath,\n }),\n );\n }\n const hdrRes = decodeHdr(bytes);\n if (!hdrRes.ok) return err(hdrRes.error);\n // DecodedImageWithMeta expects DecodedImage; for HDR we return the HDR\n // variant through a separate export path. Downstream consumers\n // distinguish by file extension.\n // For the DecodedImageWithMeta return type compatibility, we synthesise\n // a minimal DecodedImage POD carrying the byte representation.\n // This keeps the return type stable for existing PNG/JPG callers.\n // The HDR data is available through the separate decodeHdr export.\n const rgba8 = new Uint8Array(hdrRes.value.width * hdrRes.value.height * 4);\n // convert float to quantised 8-bit for the POD shape (lossy; consumers\n // should use decodeHdr directly for float precision)\n for (let i = 0; i < hdrRes.value.data.length; i++) {\n const v = hdrRes.value.data[i];\n const clamped = v !== undefined ? Math.max(0, Math.min(1, v as number)) : 0;\n rgba8[i] = Math.round(clamped * 255);\n }\n const decoded: DecodedImage = {\n bytes: rgba8,\n width: hdrRes.value.width,\n height: hdrRes.value.height,\n mime: 'image/jpeg',\n colorSpace: 'linear',\n mipmap: false,\n };\n return ok({ decoded, meta });\n }\n\n const r = parseImage(bytes, mime, {\n colorSpace,\n mipmap: mipmap === 'auto',\n path: sourcePath,\n });\n if (!r.ok) {\n return err(r.error);\n }\n\n return ok({ decoded: r.value, meta });\n}\n"]}
|
package/dist/hdr-decoder.mjs
CHANGED
|
@@ -3,7 +3,7 @@ import { err, ok, IMAGE_ERROR_HINTS } from '@forgeax/engine-types';
|
|
|
3
3
|
// src/errors.ts
|
|
4
4
|
var IMAGE_ERROR_EXPECTED = {
|
|
5
5
|
"image-decode-failed": "PNG / JPG byte stream decodes successfully",
|
|
6
|
-
"image-format-unsupported": "mime is one of ['image/png', 'image/jpeg'
|
|
6
|
+
"image-format-unsupported": "mime is one of ['image/png', 'image/jpeg']; uploadTexture format <-> colorSpace family agrees",
|
|
7
7
|
"image-dimension-out-of-bounds": "width and height fall under device caps maxTextureDimension2D (or 16384 hard cap)",
|
|
8
8
|
"image-meta-missing": "<source>.meta.json sidecar (importer: 'image') exists in the same directory",
|
|
9
9
|
"image-hdr-decode-failed": "Radiance RGBE header is valid and pixel data decodes successfully",
|
package/dist/hdr-decoder.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/errors.ts","../src/hdr-decoder.ts"],"names":[],"mappings":"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","file":"hdr-decoder.mjs","sourcesContent":["import type { ImageErrorCode, ImageErrorDetailFor, ImageErrorFor } from '@forgeax/engine-types';\nimport { IMAGE_ERROR_HINTS } from '@forgeax/engine-types';\n\n// Per-code .expected string literals SSOT.\n// Mirrors ASSET_ERROR_HINTS / IMAGE_ERROR_HINTS shape (charter P5 consistent\n// abstraction). AI users surface .expected when a structured error needs to\n// describe the precondition that was violated.\nconst IMAGE_ERROR_EXPECTED: Readonly<Record<ImageErrorCode, string>> = {\n 'image-decode-failed': 'PNG / JPG byte stream decodes successfully',\n 'image-format-unsupported':\n \"mime is one of ['image/png', 'image/jpeg', 'image/x-tga']; uploadTexture format <-> colorSpace family agrees\",\n 'image-dimension-out-of-bounds':\n 'width and height fall under device caps maxTextureDimension2D (or 16384 hard cap)',\n 'image-meta-missing':\n \"<source>.meta.json sidecar (importer: 'image') exists in the same directory\",\n 'image-hdr-decode-failed': 'Radiance RGBE header is valid and pixel data decodes successfully',\n // feat-20260521-sprite-atlas-animation M1 T-03 — vite-plugin-image atlas\n // hook .expected literals (plan-strategy section 2 D-2 + AC-10 a/b/c).\n // ImageErrorImpl construction path is unchanged: the new atlas-* errors\n // flow through `new ImageErrorImpl({ code: 'atlas-...', ...detail })` and\n // pick the .expected string up from this Record at construction time\n // (charter P3 explicit failure SSOT — AI users surface .expected next to\n // .hint after switch (err.code) without parsing the message).\n 'atlas-empty-input': 'images.length >= 1',\n 'atlas-size-exceeded':\n 'image width x height <= maxAtlasSize^2 and each image fits in the atlas footprint',\n 'atlas-region-mismatch': 'sum(regions[i].w x regions[i].h) <= atlasWidth x atlasHeight',\n};\n\n/** Runtime implementation of the correlated `ImageErrorFor<C>` envelope. */\nexport class ImageErrorImpl<C extends ImageErrorCode = ImageErrorCode>\n extends Error\n implements ImageErrorFor<C>\n{\n readonly code: C;\n readonly expected: string;\n readonly hint: string;\n readonly detail: ImageErrorDetailFor<C>;\n\n constructor(detail: ImageErrorDetailFor<C>) {\n const code = detail.code;\n const expected = IMAGE_ERROR_EXPECTED[code];\n const hint = IMAGE_ERROR_HINTS[code];\n super(`[ImageError ${code}] expected: ${expected}; hint: ${hint}`);\n this.name = 'ImageError';\n this.code = code;\n this.expected = expected;\n this.hint = hint;\n this.detail = detail;\n }\n}\n\n/** Construct a structured image error while preserving its code/detail pair. */\nexport function imageError<C extends ImageErrorCode>(\n detail: ImageErrorDetailFor<C>,\n): ImageErrorFor<C> {\n return new ImageErrorImpl<C>(detail);\n}\n\nexport { IMAGE_ERROR_EXPECTED };\n","import type { ImageError } from '@forgeax/engine-types';\nimport { imageError } from './errors.js';\nimport type { Result } from './result.js';\nimport { err, ok } from './result.js';\n\n/**\n * Decoded HDR image POD.\n *\n * `data` is an interleaved RGBA Float32Array (4 floats per pixel, row-major).\n * Color space is always linear (RGBE is radiance).\n */\nexport interface HdrDecoded {\n readonly width: number;\n readonly height: number;\n readonly data: Float32Array;\n}\n\n// ASCII code points\nconst LF = 0x0a;\n\n/**\n * Decode a Radiance RGBE (.hdr) byte stream into linear RGBA float pixels.\n *\n * Steps per research F-6:\n * 1. Parse ASCII header: magic (#?RADIANCE), FORMAT=32-bit_rle_rgbe,\n * resolution line (-Y height +X width).\n * 2. Decode per-scanline new-RLE pixel data: 4 independent channel runs.\n * 3. Convert RGBE to float: if E==0 -> 0; else f=2^(E-136), R=(Rm+0.5)*f.\n * 4. Output as interleaved RGBA Float32Array (alpha=1.0).\n *\n * Old-RLE scanlines (prefix not matching 0x02,0x02,hi,lo with correct width)\n * return image-hdr-decode-failed.\n */\nexport function decodeHdr(bytes: Uint8Array): Result<HdrDecoded, ImageError> {\n let pos = 0;\n\n // Step 1a: magic -- must start with \"#?RADIANCE\" or \"#?RGBE\"\n if (bytes.length < 11) {\n return err(hdrDecodeError('file too short for Radiance HDR header'));\n }\n const magic = String.fromCharCode(...bytes.subarray(0, 11));\n if (magic !== '#?RADIANCE\\n' && magic !== '#?RGBE\\n') {\n return err(\n hdrDecodeError('missing or invalid Radiance HDR magic; expected #?RADIANCE or #?RGBE'),\n );\n }\n pos = 11;\n\n // Step 1b: key=value lines until empty line\n let formatFound = false;\n while (pos < bytes.length) {\n const lineEnd = findByte(bytes, LF, pos);\n if (lineEnd === -1) return err(hdrDecodeError('header truncated before empty line'));\n const line = asciiSubstring(bytes, pos, lineEnd);\n pos = lineEnd + 1;\n if (line === '') break;\n if (line.startsWith('FORMAT=')) {\n const val = line.slice(7);\n if (val === '32-bit_rle_rgbe') {\n formatFound = true;\n }\n }\n }\n if (!formatFound) {\n return err(hdrDecodeError('missing or unsupported FORMAT; expected FORMAT=32-bit_rle_rgbe'));\n }\n\n // Step 1c: resolution line (-Y height +X width)\n if (pos >= bytes.length) return err(hdrDecodeError('missing resolution line'));\n const resLineEnd = findByte(bytes, LF, pos);\n if (resLineEnd === -1) return err(hdrDecodeError('resolution line truncated'));\n const resLine = asciiSubstring(bytes, pos, resLineEnd);\n pos = resLineEnd + 1;\n const resMatch = /^-Y\\s+(\\d+)\\s+\\+X\\s+(\\d+)\\s*$/.exec(resLine);\n if (resMatch === null) {\n return err(\n hdrDecodeError(`unexpected resolution line format: \"${resLine}\"; expected \"-Y H +X W\"`),\n );\n }\n const height = Number(resMatch[1]);\n const width = Number(resMatch[2]);\n if (height === undefined || width === undefined || height <= 0 || width <= 0) {\n return err(hdrDecodeError(`invalid dimensions: ${width}x${height}`));\n }\n\n // Step 2: decode per-scanline new-RLE pixel data\n const pixelCount = width * height;\n const rgbe = new Uint8Array(pixelCount * 4);\n const scanlineBytes = width * 4;\n\n for (let y = 0; y < height; y++) {\n if (pos + 4 > bytes.length) {\n return err(hdrDecodeError(`truncated at scanline ${y}: expected 4-byte prefix`));\n }\n const p0 = bytes[pos];\n const p1 = bytes[pos + 1];\n const pHi = bytes[pos + 2];\n const pLo = bytes[pos + 3];\n if (p0 === undefined || p1 === undefined || pHi === undefined || pLo === undefined) {\n return err(hdrDecodeError(`truncated at scanline ${y}`));\n }\n // biome-ignore lint/style/noNonNullAssertion: checked above\n const prefixWidth = (pHi! << 8) | pLo!;\n\n if (p0 === 0x02 && p1 === 0x02 && prefixWidth === width && width >= 8 && width <= 32767) {\n // New-RLE: 4 channels independently RLE-encoded, then interleaved\n const decoded = decodeNewRleScanline(bytes, pos + 4, width);\n if (decoded === null) {\n return err(hdrDecodeError(`RLE decode failed at scanline ${y}`));\n }\n // Copy into rgbe interleaved buffer\n const base = y * scanlineBytes;\n const channels = [decoded[0], decoded[1], decoded[2], decoded[3]];\n for (let x = 0; x < width; x++) {\n for (let ch = 0; ch < 4; ch++) {\n // biome-ignore lint/style/noNonNullAssertion: checked at allocation\n rgbe[base + x * 4 + ch] = channels[ch]![x]!;\n }\n }\n // Advance pos past the 4-byte prefix + the total RLE bytes consumed\n pos += 4 + decoded.totalBytes;\n } else {\n // Old-RLE: not supported\n return err(\n hdrDecodeError(\n 'old-RLE format is not supported; only new-RLE (32-bit_rle_rgbe) is accepted',\n ),\n );\n }\n }\n\n // Step 3: RGBE to float\n const out = new Float32Array(pixelCount * 4);\n for (let i = 0; i < pixelCount; i++) {\n const base = i * 4;\n // biome-ignore lint/style/noNonNullAssertion: checked at allocation\n const r = rgbe[base]!;\n // biome-ignore lint/style/noNonNullAssertion: checked at allocation\n const g = rgbe[base + 1]!;\n // biome-ignore lint/style/noNonNullAssertion: checked at allocation\n const b = rgbe[base + 2]!;\n // biome-ignore lint/style/noNonNullAssertion: checked at allocation\n const e = rgbe[base + 3]!;\n out[base + 3] = 1.0;\n\n if (e === 0) {\n out[base] = 0;\n out[base + 1] = 0;\n out[base + 2] = 0;\n } else {\n // f = 2^(E - 136) = ldexp(1.0, E - 128 - 8)\n // Equivalent: ldexp(1.0 / 256.0, E - 128)\n // But simpler: const f = (1.0 / 256.0) * Math.pow(2, e - 128);\n // Using ldexp: Math.pow(2, e - 136)\n const f = 2 ** (e - 136);\n out[base] = (r + 0.5) * f;\n out[base + 1] = (g + 0.5) * f;\n out[base + 2] = (b + 0.5) * f;\n }\n }\n\n return ok({ width, height, data: out });\n}\n\ninterface DecodedScanline {\n /** 4 channels, each width bytes */\n readonly 0: Uint8Array;\n readonly 1: Uint8Array;\n readonly 2: Uint8Array;\n readonly 3: Uint8Array;\n readonly totalBytes: number;\n}\n\n/**\n * Decode one scanline of new-RLE data into 4 channel arrays.\n * Returns null on failure (data underrun).\n */\nfunction decodeNewRleScanline(\n bytes: Uint8Array,\n start: number,\n width: number,\n): DecodedScanline | null {\n const channels: Uint8Array[] = [];\n let cursor = start;\n\n for (let ch = 0; ch < 4; ch++) {\n const data = new Uint8Array(width);\n let x = 0;\n while (x < width) {\n if (cursor >= bytes.length) return null;\n // biome-ignore lint/style/noNonNullAssertion: checked above\n const code = bytes[cursor]!;\n cursor++;\n if (code > 128) {\n // RLE run: next byte repeated (code - 128) times\n const count = code - 128;\n if (cursor >= bytes.length || x + count > width) return null;\n // biome-ignore lint/style/noNonNullAssertion: checked above\n const val = bytes[cursor]!;\n cursor++;\n data.fill(val, x, x + count);\n x += count;\n } else {\n // Literal run: next `code` bytes copied verbatim\n if (code === 0) continue;\n if (cursor + code > bytes.length || x + code > width) return null;\n for (let k = 0; k < code; k++) {\n // biome-ignore lint/style/noNonNullAssertion: checked above\n data[x + k] = bytes[cursor + k]!;\n }\n cursor += code;\n x += code;\n }\n }\n channels.push(data);\n }\n\n const ch0 = channels[0];\n const ch1 = channels[1];\n const ch2 = channels[2];\n const ch3 = channels[3];\n if (ch0 === undefined || ch1 === undefined || ch2 === undefined || ch3 === undefined) {\n return null; // channels should always have 4 entries\n }\n return {\n 0: ch0,\n 1: ch1,\n 2: ch2,\n 3: ch3,\n totalBytes: cursor - start,\n };\n}\n\nfunction findByte(bytes: Uint8Array, target: number, start: number): number {\n for (let i = start; i < bytes.length; i++) {\n if (bytes[i] === target) return i;\n }\n return -1;\n}\n\nfunction asciiSubstring(bytes: Uint8Array, start: number, end: number): string {\n let s = '';\n for (let i = start; i < end; i++) {\n // biome-ignore lint/style/noNonNullAssertion: in bounds\n s += String.fromCharCode(bytes[i]!);\n }\n return s;\n}\n\nfunction hdrDecodeError(reason: string): ImageError {\n return imageError({\n code: 'image-hdr-decode-failed',\n reason,\n });\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/errors.ts","../src/hdr-decoder.ts"],"names":[],"mappings":"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","file":"hdr-decoder.mjs","sourcesContent":["import type { ImageErrorCode, ImageErrorDetailFor, ImageErrorFor } from '@forgeax/engine-types';\nimport { IMAGE_ERROR_HINTS } from '@forgeax/engine-types';\n\n// Per-code .expected string literals SSOT.\n// Mirrors ASSET_ERROR_HINTS / IMAGE_ERROR_HINTS shape (charter P5 consistent\n// abstraction). AI users surface .expected when a structured error needs to\n// describe the precondition that was violated.\nconst IMAGE_ERROR_EXPECTED: Readonly<Record<ImageErrorCode, string>> = {\n 'image-decode-failed': 'PNG / JPG byte stream decodes successfully',\n 'image-format-unsupported':\n \"mime is one of ['image/png', 'image/jpeg']; uploadTexture format <-> colorSpace family agrees\",\n 'image-dimension-out-of-bounds':\n 'width and height fall under device caps maxTextureDimension2D (or 16384 hard cap)',\n 'image-meta-missing':\n \"<source>.meta.json sidecar (importer: 'image') exists in the same directory\",\n 'image-hdr-decode-failed': 'Radiance RGBE header is valid and pixel data decodes successfully',\n // feat-20260521-sprite-atlas-animation M1 T-03 — vite-plugin-image atlas\n // hook .expected literals (plan-strategy section 2 D-2 + AC-10 a/b/c).\n // ImageErrorImpl construction path is unchanged: the new atlas-* errors\n // flow through `new ImageErrorImpl({ code: 'atlas-...', ...detail })` and\n // pick the .expected string up from this Record at construction time\n // (charter P3 explicit failure SSOT — AI users surface .expected next to\n // .hint after switch (err.code) without parsing the message).\n 'atlas-empty-input': 'images.length >= 1',\n 'atlas-size-exceeded':\n 'image width x height <= maxAtlasSize^2 and each image fits in the atlas footprint',\n 'atlas-region-mismatch': 'sum(regions[i].w x regions[i].h) <= atlasWidth x atlasHeight',\n};\n\n/** Runtime implementation of the correlated `ImageErrorFor<C>` envelope. */\nexport class ImageErrorImpl<C extends ImageErrorCode = ImageErrorCode>\n extends Error\n implements ImageErrorFor<C>\n{\n readonly code: C;\n readonly expected: string;\n readonly hint: string;\n readonly detail: ImageErrorDetailFor<C>;\n\n constructor(detail: ImageErrorDetailFor<C>) {\n const code = detail.code;\n const expected = IMAGE_ERROR_EXPECTED[code];\n const hint = IMAGE_ERROR_HINTS[code];\n super(`[ImageError ${code}] expected: ${expected}; hint: ${hint}`);\n this.name = 'ImageError';\n this.code = code;\n this.expected = expected;\n this.hint = hint;\n this.detail = detail;\n }\n}\n\n/** Construct a structured image error while preserving its code/detail pair. */\nexport function imageError<C extends ImageErrorCode>(\n detail: ImageErrorDetailFor<C>,\n): ImageErrorFor<C> {\n return new ImageErrorImpl<C>(detail);\n}\n\nexport { IMAGE_ERROR_EXPECTED };\n","import type { ImageError } from '@forgeax/engine-types';\nimport { imageError } from './errors.js';\nimport type { Result } from './result.js';\nimport { err, ok } from './result.js';\n\n/**\n * Decoded HDR image POD.\n *\n * `data` is an interleaved RGBA Float32Array (4 floats per pixel, row-major).\n * Color space is always linear (RGBE is radiance).\n */\nexport interface HdrDecoded {\n readonly width: number;\n readonly height: number;\n readonly data: Float32Array;\n}\n\n// ASCII code points\nconst LF = 0x0a;\n\n/**\n * Decode a Radiance RGBE (.hdr) byte stream into linear RGBA float pixels.\n *\n * Steps per research F-6:\n * 1. Parse ASCII header: magic (#?RADIANCE), FORMAT=32-bit_rle_rgbe,\n * resolution line (-Y height +X width).\n * 2. Decode per-scanline new-RLE pixel data: 4 independent channel runs.\n * 3. Convert RGBE to float: if E==0 -> 0; else f=2^(E-136), R=(Rm+0.5)*f.\n * 4. Output as interleaved RGBA Float32Array (alpha=1.0).\n *\n * Old-RLE scanlines (prefix not matching 0x02,0x02,hi,lo with correct width)\n * return image-hdr-decode-failed.\n */\nexport function decodeHdr(bytes: Uint8Array): Result<HdrDecoded, ImageError> {\n let pos = 0;\n\n // Step 1a: magic -- must start with \"#?RADIANCE\" or \"#?RGBE\"\n if (bytes.length < 11) {\n return err(hdrDecodeError('file too short for Radiance HDR header'));\n }\n const magic = String.fromCharCode(...bytes.subarray(0, 11));\n if (magic !== '#?RADIANCE\\n' && magic !== '#?RGBE\\n') {\n return err(\n hdrDecodeError('missing or invalid Radiance HDR magic; expected #?RADIANCE or #?RGBE'),\n );\n }\n pos = 11;\n\n // Step 1b: key=value lines until empty line\n let formatFound = false;\n while (pos < bytes.length) {\n const lineEnd = findByte(bytes, LF, pos);\n if (lineEnd === -1) return err(hdrDecodeError('header truncated before empty line'));\n const line = asciiSubstring(bytes, pos, lineEnd);\n pos = lineEnd + 1;\n if (line === '') break;\n if (line.startsWith('FORMAT=')) {\n const val = line.slice(7);\n if (val === '32-bit_rle_rgbe') {\n formatFound = true;\n }\n }\n }\n if (!formatFound) {\n return err(hdrDecodeError('missing or unsupported FORMAT; expected FORMAT=32-bit_rle_rgbe'));\n }\n\n // Step 1c: resolution line (-Y height +X width)\n if (pos >= bytes.length) return err(hdrDecodeError('missing resolution line'));\n const resLineEnd = findByte(bytes, LF, pos);\n if (resLineEnd === -1) return err(hdrDecodeError('resolution line truncated'));\n const resLine = asciiSubstring(bytes, pos, resLineEnd);\n pos = resLineEnd + 1;\n const resMatch = /^-Y\\s+(\\d+)\\s+\\+X\\s+(\\d+)\\s*$/.exec(resLine);\n if (resMatch === null) {\n return err(\n hdrDecodeError(`unexpected resolution line format: \"${resLine}\"; expected \"-Y H +X W\"`),\n );\n }\n const height = Number(resMatch[1]);\n const width = Number(resMatch[2]);\n if (height === undefined || width === undefined || height <= 0 || width <= 0) {\n return err(hdrDecodeError(`invalid dimensions: ${width}x${height}`));\n }\n\n // Step 2: decode per-scanline new-RLE pixel data\n const pixelCount = width * height;\n const rgbe = new Uint8Array(pixelCount * 4);\n const scanlineBytes = width * 4;\n\n for (let y = 0; y < height; y++) {\n if (pos + 4 > bytes.length) {\n return err(hdrDecodeError(`truncated at scanline ${y}: expected 4-byte prefix`));\n }\n const p0 = bytes[pos];\n const p1 = bytes[pos + 1];\n const pHi = bytes[pos + 2];\n const pLo = bytes[pos + 3];\n if (p0 === undefined || p1 === undefined || pHi === undefined || pLo === undefined) {\n return err(hdrDecodeError(`truncated at scanline ${y}`));\n }\n // biome-ignore lint/style/noNonNullAssertion: checked above\n const prefixWidth = (pHi! << 8) | pLo!;\n\n if (p0 === 0x02 && p1 === 0x02 && prefixWidth === width && width >= 8 && width <= 32767) {\n // New-RLE: 4 channels independently RLE-encoded, then interleaved\n const decoded = decodeNewRleScanline(bytes, pos + 4, width);\n if (decoded === null) {\n return err(hdrDecodeError(`RLE decode failed at scanline ${y}`));\n }\n // Copy into rgbe interleaved buffer\n const base = y * scanlineBytes;\n const channels = [decoded[0], decoded[1], decoded[2], decoded[3]];\n for (let x = 0; x < width; x++) {\n for (let ch = 0; ch < 4; ch++) {\n // biome-ignore lint/style/noNonNullAssertion: checked at allocation\n rgbe[base + x * 4 + ch] = channels[ch]![x]!;\n }\n }\n // Advance pos past the 4-byte prefix + the total RLE bytes consumed\n pos += 4 + decoded.totalBytes;\n } else {\n // Old-RLE: not supported\n return err(\n hdrDecodeError(\n 'old-RLE format is not supported; only new-RLE (32-bit_rle_rgbe) is accepted',\n ),\n );\n }\n }\n\n // Step 3: RGBE to float\n const out = new Float32Array(pixelCount * 4);\n for (let i = 0; i < pixelCount; i++) {\n const base = i * 4;\n // biome-ignore lint/style/noNonNullAssertion: checked at allocation\n const r = rgbe[base]!;\n // biome-ignore lint/style/noNonNullAssertion: checked at allocation\n const g = rgbe[base + 1]!;\n // biome-ignore lint/style/noNonNullAssertion: checked at allocation\n const b = rgbe[base + 2]!;\n // biome-ignore lint/style/noNonNullAssertion: checked at allocation\n const e = rgbe[base + 3]!;\n out[base + 3] = 1.0;\n\n if (e === 0) {\n out[base] = 0;\n out[base + 1] = 0;\n out[base + 2] = 0;\n } else {\n // f = 2^(E - 136) = ldexp(1.0, E - 128 - 8)\n // Equivalent: ldexp(1.0 / 256.0, E - 128)\n // But simpler: const f = (1.0 / 256.0) * Math.pow(2, e - 128);\n // Using ldexp: Math.pow(2, e - 136)\n const f = 2 ** (e - 136);\n out[base] = (r + 0.5) * f;\n out[base + 1] = (g + 0.5) * f;\n out[base + 2] = (b + 0.5) * f;\n }\n }\n\n return ok({ width, height, data: out });\n}\n\ninterface DecodedScanline {\n /** 4 channels, each width bytes */\n readonly 0: Uint8Array;\n readonly 1: Uint8Array;\n readonly 2: Uint8Array;\n readonly 3: Uint8Array;\n readonly totalBytes: number;\n}\n\n/**\n * Decode one scanline of new-RLE data into 4 channel arrays.\n * Returns null on failure (data underrun).\n */\nfunction decodeNewRleScanline(\n bytes: Uint8Array,\n start: number,\n width: number,\n): DecodedScanline | null {\n const channels: Uint8Array[] = [];\n let cursor = start;\n\n for (let ch = 0; ch < 4; ch++) {\n const data = new Uint8Array(width);\n let x = 0;\n while (x < width) {\n if (cursor >= bytes.length) return null;\n // biome-ignore lint/style/noNonNullAssertion: checked above\n const code = bytes[cursor]!;\n cursor++;\n if (code > 128) {\n // RLE run: next byte repeated (code - 128) times\n const count = code - 128;\n if (cursor >= bytes.length || x + count > width) return null;\n // biome-ignore lint/style/noNonNullAssertion: checked above\n const val = bytes[cursor]!;\n cursor++;\n data.fill(val, x, x + count);\n x += count;\n } else {\n // Literal run: next `code` bytes copied verbatim\n if (code === 0) continue;\n if (cursor + code > bytes.length || x + code > width) return null;\n for (let k = 0; k < code; k++) {\n // biome-ignore lint/style/noNonNullAssertion: checked above\n data[x + k] = bytes[cursor + k]!;\n }\n cursor += code;\n x += code;\n }\n }\n channels.push(data);\n }\n\n const ch0 = channels[0];\n const ch1 = channels[1];\n const ch2 = channels[2];\n const ch3 = channels[3];\n if (ch0 === undefined || ch1 === undefined || ch2 === undefined || ch3 === undefined) {\n return null; // channels should always have 4 entries\n }\n return {\n 0: ch0,\n 1: ch1,\n 2: ch2,\n 3: ch3,\n totalBytes: cursor - start,\n };\n}\n\nfunction findByte(bytes: Uint8Array, target: number, start: number): number {\n for (let i = start; i < bytes.length; i++) {\n if (bytes[i] === target) return i;\n }\n return -1;\n}\n\nfunction asciiSubstring(bytes: Uint8Array, start: number, end: number): string {\n let s = '';\n for (let i = start; i < end; i++) {\n // biome-ignore lint/style/noNonNullAssertion: in bounds\n s += String.fromCharCode(bytes[i]!);\n }\n return s;\n}\n\nfunction hdrDecodeError(reason: string): ImageError {\n return imageError({\n code: 'image-hdr-decode-failed',\n reason,\n });\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"image-importer.d.ts","sourceRoot":"","sources":["../src/image-importer.ts"],"names":[],"mappings":"AAmCA,OAAO,KAAK,EAGV,aAAa,EAEb,QAAQ,EAGT,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"image-importer.d.ts","sourceRoot":"","sources":["../src/image-importer.ts"],"names":[],"mappings":"AAmCA,OAAO,KAAK,EAGV,aAAa,EAEb,QAAQ,EAGT,MAAM,uBAAuB,CAAC;AAgtB/B,4EAA4E;AAC5E,eAAO,MAAM,oBAAoB,EAAE,aAAa,CAAC,aAAa,CA0E7D,CAAC;AAEF;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,aAAa,EAAE,QAI3B,CAAC"}
|