@forgeax/engine-image 0.1.3 → 0.1.6
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 +5 -2
- package/dist/image-importer.d.ts.map +1 -1
- package/dist/image-importer.mjs +80 -102
- 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 +83 -5
- 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
package/dist/image-importer.mjs
CHANGED
|
@@ -21,7 +21,7 @@ var init_errors = __esm({
|
|
|
21
21
|
"src/errors.ts"() {
|
|
22
22
|
IMAGE_ERROR_EXPECTED = {
|
|
23
23
|
"image-decode-failed": "PNG / JPG byte stream decodes successfully",
|
|
24
|
-
"image-format-unsupported": "mime is one of ['image/png', 'image/jpeg'
|
|
24
|
+
"image-format-unsupported": "mime is one of ['image/png', 'image/jpeg']; uploadTexture format <-> colorSpace family agrees",
|
|
25
25
|
"image-dimension-out-of-bounds": "width and height fall under device caps maxTextureDimension2D (or 16384 hard cap)",
|
|
26
26
|
"image-meta-missing": "<source>.meta.json sidecar (importer: 'image') exists in the same directory",
|
|
27
27
|
"image-hdr-decode-failed": "Radiance RGBE header is valid and pixel data decodes successfully",
|
|
@@ -238,36 +238,34 @@ var init_hdr_decoder = __esm({
|
|
|
238
238
|
LF = 10;
|
|
239
239
|
}
|
|
240
240
|
});
|
|
241
|
-
var COMPRESSION_MODES = ["auto", "etc1s", "uastc", "none"];
|
|
242
|
-
var RESOLVED_ENCODE_MODES = ["etc1s", "uastc", "uastc-hdr", "none"];
|
|
243
241
|
function resolveEncodeMode(mode, source) {
|
|
244
242
|
switch (mode) {
|
|
245
|
-
case
|
|
246
|
-
return
|
|
247
|
-
case
|
|
248
|
-
return
|
|
249
|
-
case
|
|
250
|
-
return source.isHdr ?
|
|
251
|
-
case
|
|
252
|
-
if (source.isHdr) return
|
|
253
|
-
return source.colorSpace === "srgb" ?
|
|
243
|
+
case "none":
|
|
244
|
+
return "none";
|
|
245
|
+
case "etc1s":
|
|
246
|
+
return "etc1s";
|
|
247
|
+
case "uastc":
|
|
248
|
+
return source.isHdr ? "uastc-hdr" : "uastc";
|
|
249
|
+
case "auto":
|
|
250
|
+
if (source.isHdr) return "uastc-hdr";
|
|
251
|
+
return source.colorSpace === "srgb" ? "etc1s" : "uastc";
|
|
254
252
|
}
|
|
255
253
|
}
|
|
256
254
|
function basisEncodeParamsFor(mode, source) {
|
|
257
255
|
const resolved = resolveEncodeMode(mode, source);
|
|
258
256
|
const srgbColor = source.colorSpace === "srgb";
|
|
259
257
|
switch (resolved) {
|
|
260
|
-
case
|
|
258
|
+
case "none":
|
|
261
259
|
return null;
|
|
262
|
-
case
|
|
260
|
+
case "etc1s":
|
|
263
261
|
return {
|
|
264
|
-
mode:
|
|
262
|
+
mode: "etc1s",
|
|
265
263
|
srgb: srgbColor,
|
|
266
264
|
perceptual: srgbColor,
|
|
267
265
|
uastcSupercompression: false,
|
|
268
266
|
mipGen: false
|
|
269
267
|
};
|
|
270
|
-
case
|
|
268
|
+
case "uastc":
|
|
271
269
|
return {
|
|
272
270
|
mode: "uastc-ldr",
|
|
273
271
|
srgb: srgbColor,
|
|
@@ -275,9 +273,9 @@ function basisEncodeParamsFor(mode, source) {
|
|
|
275
273
|
uastcSupercompression: true,
|
|
276
274
|
mipGen: false
|
|
277
275
|
};
|
|
278
|
-
case
|
|
276
|
+
case "uastc-hdr":
|
|
279
277
|
return {
|
|
280
|
-
mode:
|
|
278
|
+
mode: "uastc-hdr",
|
|
281
279
|
srgb: false,
|
|
282
280
|
perceptual: false,
|
|
283
281
|
uastcSupercompression: false,
|
|
@@ -369,7 +367,7 @@ function downscaleRgba(bytes, width, height, maxDimension) {
|
|
|
369
367
|
// src/parse-image.ts
|
|
370
368
|
init_result();
|
|
371
369
|
var DEFAULT_MAX_DIMENSION = 16384;
|
|
372
|
-
var SUPPORTED_MIMES = ["image/png", "image/jpeg"
|
|
370
|
+
var SUPPORTED_MIMES = ["image/png", "image/jpeg"];
|
|
373
371
|
function parseImage(bytes, mime, opts = {}) {
|
|
374
372
|
if (!SUPPORTED_MIMES.includes(mime)) {
|
|
375
373
|
return err(
|
|
@@ -401,17 +399,12 @@ function parseImage(bytes, mime, opts = {}) {
|
|
|
401
399
|
);
|
|
402
400
|
}
|
|
403
401
|
rgba = new Uint8Array(first);
|
|
404
|
-
} else
|
|
402
|
+
} else {
|
|
405
403
|
const jpegMod = jpeg.default ?? jpeg;
|
|
406
404
|
const decoded = jpegMod.decode(bytes, { useTArray: true, formatAsRGBA: true });
|
|
407
405
|
width = decoded.width;
|
|
408
406
|
height = decoded.height;
|
|
409
407
|
rgba = decoded.data;
|
|
410
|
-
} else {
|
|
411
|
-
const decoded = decodeTga(bytes);
|
|
412
|
-
width = decoded.width;
|
|
413
|
-
height = decoded.height;
|
|
414
|
-
rgba = decoded.bytes;
|
|
415
408
|
}
|
|
416
409
|
} catch (e) {
|
|
417
410
|
return err(
|
|
@@ -448,85 +441,12 @@ function parseImage(bytes, mime, opts = {}) {
|
|
|
448
441
|
mipmap: opts.mipmap ?? true
|
|
449
442
|
});
|
|
450
443
|
}
|
|
451
|
-
function decodeTga(bytes) {
|
|
452
|
-
if (bytes.length < 18) throw new Error("TGA header is truncated");
|
|
453
|
-
const colorMapType = bytes[1];
|
|
454
|
-
const imageType = bytes[2];
|
|
455
|
-
const idLength = bytes[0] ?? 0;
|
|
456
|
-
const width = (bytes[12] ?? 0) | (bytes[13] ?? 0) << 8;
|
|
457
|
-
const height = (bytes[14] ?? 0) | (bytes[15] ?? 0) << 8;
|
|
458
|
-
const pixelDepth = bytes[16] ?? 0;
|
|
459
|
-
const descriptor = bytes[17] ?? 0;
|
|
460
|
-
if (colorMapType !== 0) throw new Error("color-mapped TGA is unsupported");
|
|
461
|
-
if (width < 1 || height < 1) throw new Error("TGA dimensions must be positive");
|
|
462
|
-
const isTrueColor = imageType === 2 || imageType === 10;
|
|
463
|
-
const isGray = imageType === 3 || imageType === 11;
|
|
464
|
-
if (!isTrueColor && !isGray) throw new Error(`TGA image type ${imageType} is unsupported`);
|
|
465
|
-
if (isTrueColor && pixelDepth !== 24 && pixelDepth !== 32) {
|
|
466
|
-
throw new Error(`TGA true-color depth ${pixelDepth} is unsupported`);
|
|
467
|
-
}
|
|
468
|
-
if (isGray && pixelDepth !== 8)
|
|
469
|
-
throw new Error(`TGA grayscale depth ${pixelDepth} is unsupported`);
|
|
470
|
-
const bytesPerPixel = pixelDepth / 8;
|
|
471
|
-
let offset = 18 + idLength;
|
|
472
|
-
const pixelCount = width * height;
|
|
473
|
-
const out = new Uint8Array(pixelCount * 4);
|
|
474
|
-
const topOrigin = (descriptor & 32) !== 0;
|
|
475
|
-
const rightOrigin = (descriptor & 16) !== 0;
|
|
476
|
-
let filePixel = 0;
|
|
477
|
-
const writePixel = (pixel) => {
|
|
478
|
-
if (filePixel >= pixelCount) throw new Error("TGA pixel data has too many pixels");
|
|
479
|
-
const fileX = filePixel % width;
|
|
480
|
-
const fileY = Math.floor(filePixel / width);
|
|
481
|
-
const x = rightOrigin ? width - 1 - fileX : fileX;
|
|
482
|
-
const y = topOrigin ? fileY : height - 1 - fileY;
|
|
483
|
-
const destination = (y * width + x) * 4;
|
|
484
|
-
if (isGray) {
|
|
485
|
-
const value = pixel[0] ?? 0;
|
|
486
|
-
out[destination] = value;
|
|
487
|
-
out[destination + 1] = value;
|
|
488
|
-
out[destination + 2] = value;
|
|
489
|
-
out[destination + 3] = 255;
|
|
490
|
-
} else {
|
|
491
|
-
out[destination] = pixel[2] ?? 0;
|
|
492
|
-
out[destination + 1] = pixel[1] ?? 0;
|
|
493
|
-
out[destination + 2] = pixel[0] ?? 0;
|
|
494
|
-
out[destination + 3] = bytesPerPixel === 4 ? pixel[3] ?? 255 : 255;
|
|
495
|
-
}
|
|
496
|
-
filePixel += 1;
|
|
497
|
-
};
|
|
498
|
-
const readPixel = () => {
|
|
499
|
-
if (offset + bytesPerPixel > bytes.length) throw new Error("TGA pixel data is truncated");
|
|
500
|
-
const pixel = bytes.slice(offset, offset + bytesPerPixel);
|
|
501
|
-
offset += bytesPerPixel;
|
|
502
|
-
return pixel;
|
|
503
|
-
};
|
|
504
|
-
if (imageType === 2 || imageType === 3) {
|
|
505
|
-
while (filePixel < pixelCount) writePixel(readPixel());
|
|
506
|
-
} else {
|
|
507
|
-
while (filePixel < pixelCount) {
|
|
508
|
-
if (offset >= bytes.length) throw new Error("TGA RLE packet header is truncated");
|
|
509
|
-
const packet = bytes[offset++] ?? 0;
|
|
510
|
-
const count = (packet & 127) + 1;
|
|
511
|
-
if (filePixel + count > pixelCount) throw new Error("TGA RLE packet exceeds image bounds");
|
|
512
|
-
if ((packet & 128) !== 0) {
|
|
513
|
-
const pixel = readPixel();
|
|
514
|
-
for (let i = 0; i < count; i++) writePixel(pixel);
|
|
515
|
-
} else {
|
|
516
|
-
for (let i = 0; i < count; i++) writePixel(readPixel());
|
|
517
|
-
}
|
|
518
|
-
}
|
|
519
|
-
}
|
|
520
|
-
if (filePixel !== pixelCount) throw new Error("TGA pixel count mismatch");
|
|
521
|
-
return { width, height, bytes: out };
|
|
522
|
-
}
|
|
523
444
|
|
|
524
445
|
// src/image-importer.ts
|
|
525
446
|
function mimeFromSource(source) {
|
|
526
447
|
const lower = source.toLowerCase();
|
|
527
448
|
if (lower.endsWith(".png")) return "image/png";
|
|
528
449
|
if (lower.endsWith(".jpg") || lower.endsWith(".jpeg")) return "image/jpeg";
|
|
529
|
-
if (lower.endsWith(".tga")) return "image/x-tga";
|
|
530
450
|
return void 0;
|
|
531
451
|
}
|
|
532
452
|
function requiredImageOutputKind(source) {
|
|
@@ -1027,7 +947,7 @@ async function importImage(ctx) {
|
|
|
1027
947
|
}
|
|
1028
948
|
if (mime === void 0) {
|
|
1029
949
|
throw new Error(
|
|
1030
|
-
`imageImporter: unsupported source extension for "${ctx.source}" (expected .png / .jpg / .jpeg / .
|
|
950
|
+
`imageImporter: unsupported source extension for "${ctx.source}" (expected .png / .jpg / .jpeg / .hdr / .ktx2 / .basis)`
|
|
1031
951
|
);
|
|
1032
952
|
}
|
|
1033
953
|
const colorSpace = ctx.importSettings.colorSpace === "srgb" ? "srgb" : "linear";
|
|
@@ -1047,7 +967,7 @@ async function importImage(ctx) {
|
|
|
1047
967
|
"decode",
|
|
1048
968
|
"ldr",
|
|
1049
969
|
ctx.source,
|
|
1050
|
-
"valid PNG
|
|
970
|
+
"valid PNG or JPEG bytes to decode into RGBA pixels",
|
|
1051
971
|
`image:${decoded.error.code}`,
|
|
1052
972
|
"repair the source image bytes and retry the import"
|
|
1053
973
|
)
|
|
@@ -1097,11 +1017,69 @@ async function importImage(ctx) {
|
|
|
1097
1017
|
}
|
|
1098
1018
|
return { ok: true, value: { assets: out, sourceDependencies: [] } };
|
|
1099
1019
|
}
|
|
1020
|
+
var decodeImageForImport = async (bytes, mimeType, importSettings) => {
|
|
1021
|
+
const colorSpace = importSettings.colorSpace === "srgb" || importSettings.colorSpace === "linear" ? importSettings.colorSpace : "linear";
|
|
1022
|
+
const mipmap = importSettings.mipmap === true;
|
|
1023
|
+
const downscaleMaxDimension = typeof importSettings.downscaleMaxDimension === "number" && Number.isInteger(importSettings.downscaleMaxDimension) && importSettings.downscaleMaxDimension > 0 ? importSettings.downscaleMaxDimension : void 0;
|
|
1024
|
+
const decoded = parseImage(bytes, mimeType, {
|
|
1025
|
+
colorSpace,
|
|
1026
|
+
mipmap,
|
|
1027
|
+
...downscaleMaxDimension === void 0 ? {} : { downscaleMaxDimension }
|
|
1028
|
+
});
|
|
1029
|
+
if (!decoded.ok) return decoded;
|
|
1030
|
+
const tex = decoded.value;
|
|
1031
|
+
const requestedCompression = importSettings.compressionMode === "auto" || importSettings.compressionMode === "etc1s" || importSettings.compressionMode === "uastc" || importSettings.compressionMode === "none" ? importSettings.compressionMode : "none";
|
|
1032
|
+
const resolvedCompression = resolveEncodeMode(requestedCompression, {
|
|
1033
|
+
colorSpace,
|
|
1034
|
+
isHdr: false
|
|
1035
|
+
});
|
|
1036
|
+
let cookedBytes = tex.bytes;
|
|
1037
|
+
let mediaType = mimeType;
|
|
1038
|
+
let assetCodec = {
|
|
1039
|
+
name: "rgba8",
|
|
1040
|
+
version: "1"
|
|
1041
|
+
};
|
|
1042
|
+
if (resolvedCompression !== "none") {
|
|
1043
|
+
const encoded = await encodeTextureToKtx2(
|
|
1044
|
+
tex.bytes,
|
|
1045
|
+
tex.width,
|
|
1046
|
+
tex.height,
|
|
1047
|
+
requestedCompression,
|
|
1048
|
+
{ colorSpace, isHdr: false }
|
|
1049
|
+
);
|
|
1050
|
+
if (!encoded.ok) {
|
|
1051
|
+
throw new Error(
|
|
1052
|
+
`embedded texture compression failed (${encoded.error.code} / ${encoded.error.mode}): ${encoded.error.reason}`
|
|
1053
|
+
);
|
|
1054
|
+
}
|
|
1055
|
+
cookedBytes = encoded.value.ktx2;
|
|
1056
|
+
mediaType = "image/ktx2";
|
|
1057
|
+
assetCodec = { name: "basis", profile: encoded.value.mode };
|
|
1058
|
+
}
|
|
1059
|
+
return {
|
|
1060
|
+
ok: true,
|
|
1061
|
+
value: {
|
|
1062
|
+
texture: {
|
|
1063
|
+
kind: "texture",
|
|
1064
|
+
data: cookedBytes,
|
|
1065
|
+
width: tex.width,
|
|
1066
|
+
height: tex.height,
|
|
1067
|
+
format: colorSpaceToFormat(colorSpace),
|
|
1068
|
+
colorSpace,
|
|
1069
|
+
mipmap
|
|
1070
|
+
},
|
|
1071
|
+
bytes: cookedBytes,
|
|
1072
|
+
mediaType,
|
|
1073
|
+
assetCodec
|
|
1074
|
+
}
|
|
1075
|
+
};
|
|
1076
|
+
};
|
|
1100
1077
|
var imageImporter = {
|
|
1101
1078
|
key: "image",
|
|
1102
|
-
import: importImage
|
|
1079
|
+
import: importImage,
|
|
1080
|
+
capabilities: { decodeImage: decodeImageForImport }
|
|
1103
1081
|
};
|
|
1104
1082
|
|
|
1105
|
-
export { imageImporter };
|
|
1083
|
+
export { decodeImageForImport, imageImporter };
|
|
1106
1084
|
//# sourceMappingURL=image-importer.mjs.map
|
|
1107
1085
|
//# sourceMappingURL=image-importer.mjs.map
|