@forgeax/engine-image 0.1.28 → 0.1.30

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.
@@ -16,6 +16,15 @@ var __export = (target, all) => {
16
16
  function imageError(detail) {
17
17
  return new ImageErrorImpl(detail);
18
18
  }
19
+ function cubeParserError(code, sourceKey, field, actual, line) {
20
+ const lineDetail = line === void 0 ? "" : ` at line ${line}`;
21
+ return {
22
+ code,
23
+ expected: `valid .cube source with LUT_3D_SIZE, DOMAIN_MIN/MAX, and exactly N^3 RGB rows${lineDetail}`,
24
+ hint: `repair field ${field} in sourceKey ${sourceKey} and re-run the Node image producer`,
25
+ detail: { sourceKey, field, actual, ...line === void 0 ? {} : { line } }
26
+ };
27
+ }
19
28
  var IMAGE_ERROR_EXPECTED, ImageErrorImpl;
20
29
  var init_errors = __esm({
21
30
  "src/errors.ts"() {
@@ -331,6 +340,278 @@ async function encodeTextureToKtx2(pixels, width, height, mode, source) {
331
340
  return { ok: true, value: { ktx2: result.value, mode: resolved } };
332
341
  }
333
342
 
343
+ // src/lut/cube-parser.ts
344
+ init_errors();
345
+ var ALLOWED_SIZES = /* @__PURE__ */ new Set([16, 32, 64]);
346
+ function finite(value) {
347
+ return Number.isFinite(value);
348
+ }
349
+ function parseTriple(tokens) {
350
+ if (tokens.length !== 3) return void 0;
351
+ const values = tokens.map(Number);
352
+ if (!values.every(finite)) return void 0;
353
+ const [red, green, blue] = values;
354
+ if (red === void 0 || green === void 0 || blue === void 0) return void 0;
355
+ return [red, green, blue];
356
+ }
357
+ function float32ToFloat16(value) {
358
+ const input = new Float32Array([value]);
359
+ const bits = new Uint32Array(input.buffer).at(0) ?? 0;
360
+ const sign = bits >>> 16 & 32768;
361
+ const exponent = bits >>> 23 & 255;
362
+ const fraction = bits & 8388607;
363
+ if (exponent === 255) return sign | (fraction === 0 ? 31744 : 32256);
364
+ const halfExponent = exponent - 127 + 15;
365
+ if (halfExponent >= 31) return sign | 31744;
366
+ if (halfExponent <= 0) {
367
+ if (halfExponent < -10) return sign;
368
+ const mantissa = (fraction | 8388608) >>> 1 - halfExponent;
369
+ return sign | mantissa + 4096 >>> 13;
370
+ }
371
+ return sign | halfExponent << 10 | fraction + 4096 >>> 13;
372
+ }
373
+ function parseCubeLut(source, sourceKey = "<inline .cube>") {
374
+ let size;
375
+ let domainMin;
376
+ let domainMax;
377
+ const values = [];
378
+ const lines = source.split(/\r?\n/);
379
+ for (const [index, rawLine] of lines.entries()) {
380
+ const line = rawLine.replace(/#.*/, "").trim();
381
+ const lineNumber = index + 1;
382
+ if (line.length === 0) continue;
383
+ const tokens = line.split(/\s+/);
384
+ const directive = tokens.shift();
385
+ if (directive === void 0) continue;
386
+ if (directive === "TITLE") continue;
387
+ if (directive === "LUT_3D_SIZE") {
388
+ const sizeToken = tokens[0];
389
+ if (size !== void 0 || tokens.length !== 1 || sizeToken === void 0 || !/^\d+$/.test(sizeToken)) {
390
+ return {
391
+ ok: false,
392
+ error: cubeParserError("cube-size-invalid", sourceKey, "LUT_3D_SIZE", line, lineNumber)
393
+ };
394
+ }
395
+ size = Number(sizeToken);
396
+ if (!ALLOWED_SIZES.has(size)) {
397
+ return {
398
+ ok: false,
399
+ error: cubeParserError("cube-size-invalid", sourceKey, "LUT_3D_SIZE", size, lineNumber)
400
+ };
401
+ }
402
+ continue;
403
+ }
404
+ if (directive === "DOMAIN_MIN" || directive === "DOMAIN_MAX") {
405
+ const triple = parseTriple(tokens);
406
+ if (triple === void 0) {
407
+ return {
408
+ ok: false,
409
+ error: cubeParserError("cube-domain-invalid", sourceKey, directive, line, lineNumber)
410
+ };
411
+ }
412
+ if (directive === "DOMAIN_MIN") {
413
+ if (domainMin !== void 0)
414
+ return {
415
+ ok: false,
416
+ error: cubeParserError("cube-domain-invalid", sourceKey, directive, line, lineNumber)
417
+ };
418
+ domainMin = triple;
419
+ } else {
420
+ if (domainMax !== void 0)
421
+ return {
422
+ ok: false,
423
+ error: cubeParserError("cube-domain-invalid", sourceKey, directive, line, lineNumber)
424
+ };
425
+ domainMax = triple;
426
+ }
427
+ continue;
428
+ }
429
+ if (directive === "LUT_1D_SIZE") {
430
+ return {
431
+ ok: false,
432
+ error: cubeParserError("cube-header-invalid", sourceKey, directive, line, lineNumber)
433
+ };
434
+ }
435
+ const row = parseTriple([directive, ...tokens]);
436
+ if (row === void 0 || row.some((value) => value < 0 || value > 1)) {
437
+ return {
438
+ ok: false,
439
+ error: cubeParserError("cube-row-invalid", sourceKey, "RGB row", line, lineNumber)
440
+ };
441
+ }
442
+ values.push(...row);
443
+ }
444
+ if (size === void 0)
445
+ return {
446
+ ok: false,
447
+ error: cubeParserError("cube-header-invalid", sourceKey, "LUT_3D_SIZE", "missing")
448
+ };
449
+ if (domainMin === void 0 || domainMax === void 0) {
450
+ return {
451
+ ok: false,
452
+ error: cubeParserError("cube-domain-invalid", sourceKey, "DOMAIN_MIN/MAX", "missing")
453
+ };
454
+ }
455
+ if (domainMin.some((value, index) => {
456
+ const maxValue = domainMax[index];
457
+ return maxValue !== void 0 && value >= maxValue;
458
+ })) {
459
+ return {
460
+ ok: false,
461
+ error: cubeParserError("cube-domain-invalid", sourceKey, "DOMAIN_MIN/MAX", {
462
+ domainMin,
463
+ domainMax
464
+ })
465
+ };
466
+ }
467
+ const expectedValues = size ** 3 * 3;
468
+ if (values.length !== expectedValues) {
469
+ return {
470
+ ok: false,
471
+ error: cubeParserError("cube-data-count-invalid", sourceKey, "RGB rows", {
472
+ expected: expectedValues / 3,
473
+ actual: values.length / 3
474
+ })
475
+ };
476
+ }
477
+ return {
478
+ ok: true,
479
+ value: {
480
+ size,
481
+ domainMin,
482
+ domainMax,
483
+ values: new Float32Array(values),
484
+ sourceKey
485
+ }
486
+ };
487
+ }
488
+ function cubeLutBytes(lut) {
489
+ const bytes = new Uint8Array(lut.size ** 3 * 4 * 2);
490
+ const view = new DataView(bytes.buffer);
491
+ for (let index = 0; index < lut.size ** 3; index += 1) {
492
+ const valueOffset = index * 3;
493
+ const byteOffset = index * 8;
494
+ view.setUint16(byteOffset, float32ToFloat16(lut.values[valueOffset] ?? 0), true);
495
+ view.setUint16(byteOffset + 2, float32ToFloat16(lut.values[valueOffset + 1] ?? 0), true);
496
+ view.setUint16(byteOffset + 4, float32ToFloat16(lut.values[valueOffset + 2] ?? 0), true);
497
+ view.setUint16(byteOffset + 6, 15360, true);
498
+ }
499
+ return bytes;
500
+ }
501
+
502
+ // src/lut/cube-producer.ts
503
+ function produceCubeTexture(input, source) {
504
+ const parsed = parseCubeLut(source, input.sourceKey);
505
+ if (!parsed.ok) return parsed;
506
+ const bytes = cubeLutBytes(parsed.value);
507
+ return {
508
+ ok: true,
509
+ value: {
510
+ guid: input.guid,
511
+ kind: "texture",
512
+ payload: {
513
+ kind: "texture",
514
+ shape: {
515
+ viewDimension: "3d",
516
+ extent: { width: parsed.value.size, height: parsed.value.size, depth: parsed.value.size }
517
+ },
518
+ format: "rgba16float",
519
+ data: bytes,
520
+ colorSpace: "linear",
521
+ mips: { kind: "none" }
522
+ },
523
+ refs: [],
524
+ artifacts: {
525
+ body: {
526
+ mediaType: "application/x-forgeax-rgba16float",
527
+ assetCodec: { name: "rgba16float", version: "1" },
528
+ bytes
529
+ }
530
+ }
531
+ }
532
+ };
533
+ }
534
+ async function importCubeSource(ctx) {
535
+ const subAsset = ctx.subAssets.length === 1 ? ctx.subAssets[0] : void 0;
536
+ if (subAsset === void 0 || subAsset.kind !== "texture" || subAsset.sourceIndex !== 0) {
537
+ return {
538
+ ok: false,
539
+ error: new ImportError({
540
+ code: "source-validation-failed",
541
+ expected: "one texture subAsset at sourceIndex 0",
542
+ actual: JSON.stringify(ctx.subAssets),
543
+ hint: IMPORT_ERROR_HINTS["source-validation-failed"],
544
+ detail: {
545
+ diagnostics: [
546
+ {
547
+ code: "cube-subasset-topology",
548
+ severity: "error",
549
+ sourcePath: `${ctx.source}#subAssets`,
550
+ sourceRange: { start: 0, end: 0, line: 1, column: 1 },
551
+ rule: "cube-required-single-texture",
552
+ expected: "one texture subAsset at sourceIndex 0",
553
+ actual: JSON.stringify(ctx.subAssets),
554
+ hint: "declare one ordinary texture subAsset and retry the same sourceKey"
555
+ }
556
+ ]
557
+ }
558
+ })
559
+ };
560
+ }
561
+ const source = await ctx.readSource();
562
+ if (!source.ok) {
563
+ return {
564
+ ok: false,
565
+ error: new ImportError({
566
+ code: "source-read-failed",
567
+ expected: `readable .cube source at "${ctx.source}"`,
568
+ actual: String(source.error),
569
+ hint: IMPORT_ERROR_HINTS["source-read-failed"],
570
+ detail: { source: ctx.source, reason: String(source.error) }
571
+ })
572
+ };
573
+ }
574
+ const produced = produceCubeTexture(
575
+ {
576
+ source: ctx.source,
577
+ sourceKey: subAsset.sourceKey ?? `${ctx.source}:texture`,
578
+ guid: subAsset.guid
579
+ },
580
+ new TextDecoder().decode(source.value)
581
+ );
582
+ if (!produced.ok) {
583
+ return {
584
+ ok: false,
585
+ error: new ImportError({
586
+ code: "source-validation-failed",
587
+ expected: produced.error.expected,
588
+ actual: JSON.stringify(produced.error.detail),
589
+ hint: produced.error.hint,
590
+ detail: {
591
+ diagnostics: [
592
+ {
593
+ code: produced.error.code,
594
+ severity: "error",
595
+ sourcePath: `${ctx.source}#${produced.error.detail.field}`,
596
+ sourceRange: {
597
+ start: 0,
598
+ end: 0,
599
+ line: produced.error.detail.line ?? 1,
600
+ column: 1
601
+ },
602
+ rule: "cube-source-parser",
603
+ expected: produced.error.expected,
604
+ actual: JSON.stringify(produced.error.detail.actual),
605
+ hint: produced.error.hint
606
+ }
607
+ ]
608
+ }
609
+ })
610
+ };
611
+ }
612
+ return { ok: true, value: { assets: [produced.value], sourceDependencies: [ctx.source] } };
613
+ }
614
+
334
615
  // src/parse-image.ts
335
616
  init_errors();
336
617
 
@@ -655,7 +936,7 @@ function mimeFromSource(source) {
655
936
  function requiredImageOutputKind(source) {
656
937
  const lower = source.toLowerCase();
657
938
  if (lower.endsWith(".hdr")) return "equirect";
658
- if (mimeFromSource(source) !== void 0 || lower.endsWith(".basis") || lower.endsWith(".ktx2")) {
939
+ if (mimeFromSource(source) !== void 0 || lower.endsWith(".basis") || lower.endsWith(".ktx2") || lower.endsWith(".cube")) {
659
940
  return "texture";
660
941
  }
661
942
  return void 0;
@@ -1075,6 +1356,9 @@ async function importImage(ctx) {
1075
1356
  if (ctx.source.toLowerCase().endsWith(".texture.json")) {
1076
1357
  return importTextureSource(ctx);
1077
1358
  }
1359
+ if (ctx.source.toLowerCase().endsWith(".cube")) {
1360
+ return importCubeSource(ctx);
1361
+ }
1078
1362
  const requiredKind = requiredImageOutputKind(ctx.source);
1079
1363
  if (requiredKind !== void 0) {
1080
1364
  const topologyError = validateImageOutputTopology(ctx, requiredKind);
@@ -1284,6 +1568,6 @@ var imageImporter = {
1284
1568
  capabilities: { decodeImage: decodeImageForImport }
1285
1569
  };
1286
1570
 
1287
- export { decodeImageForImport, imageImporter };
1571
+ export { cubeLutBytes, decodeImageForImport, imageImporter, importCubeSource, parseCubeLut, produceCubeTexture };
1288
1572
  //# sourceMappingURL=image-importer.mjs.map
1289
1573
  //# sourceMappingURL=image-importer.mjs.map