@woosh/meep-engine 3.14.0 → 3.14.2

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.
Files changed (220) hide show
  1. package/README.md +33 -64
  2. package/package.json +1 -1
  3. package/src/avif/native/DECISIONS.md +544 -536
  4. package/src/avif/native/heif/parse_avif_file.d.ts.map +1 -1
  5. package/src/avif/native/heif/parse_avif_file.js +384 -383
  6. package/src/avif/native/heif/parse_image_grid.d.ts.map +1 -1
  7. package/src/avif/native/heif/parse_image_grid.js +49 -48
  8. package/src/basis/BasisBitReader.d.ts +77 -0
  9. package/src/basis/BasisBitReader.d.ts.map +1 -0
  10. package/src/basis/BasisBitReader.js +231 -0
  11. package/src/basis/BasisHuffmanTable.d.ts +53 -0
  12. package/src/basis/BasisHuffmanTable.d.ts.map +1 -0
  13. package/src/basis/BasisHuffmanTable.js +263 -0
  14. package/src/basis/basis_read_huffman_table.d.ts +20 -0
  15. package/src/basis/basis_read_huffman_table.d.ts.map +1 -0
  16. package/src/basis/basis_read_huffman_table.js +187 -0
  17. package/src/basis/etc1s/Etc1sSelectorHistory.d.ts +50 -0
  18. package/src/basis/etc1s/Etc1sSelectorHistory.d.ts.map +1 -0
  19. package/src/basis/etc1s/Etc1sSelectorHistory.js +89 -0
  20. package/src/basis/etc1s/etc1s_block_colors.d.ts +14 -0
  21. package/src/basis/etc1s/etc1s_block_colors.d.ts.map +1 -0
  22. package/src/basis/etc1s/etc1s_block_colors.js +75 -0
  23. package/src/basis/etc1s/etc1s_decode_slice.d.ts +57 -0
  24. package/src/basis/etc1s/etc1s_decode_slice.d.ts.map +1 -0
  25. package/src/basis/etc1s/etc1s_decode_slice.js +258 -0
  26. package/src/basis/etc1s/etc1s_read_endpoint_codebook.d.ts +48 -0
  27. package/src/basis/etc1s/etc1s_read_endpoint_codebook.d.ts.map +1 -0
  28. package/src/basis/etc1s/etc1s_read_endpoint_codebook.js +111 -0
  29. package/src/basis/etc1s/etc1s_read_selector_codebook.d.ts +42 -0
  30. package/src/basis/etc1s/etc1s_read_selector_codebook.d.ts.map +1 -0
  31. package/src/basis/etc1s/etc1s_read_selector_codebook.js +127 -0
  32. package/src/basis/etc1s/etc1s_read_slice_tables.d.ts +54 -0
  33. package/src/basis/etc1s/etc1s_read_slice_tables.d.ts.map +1 -0
  34. package/src/basis/etc1s/etc1s_read_slice_tables.js +47 -0
  35. package/src/basis/transcode/build_range_plan.js +54 -0
  36. package/src/basis/transcode/etc1s_to_bc1.d.ts +24 -0
  37. package/src/basis/transcode/etc1s_to_bc1.d.ts.map +1 -0
  38. package/src/basis/transcode/etc1s_to_bc1.js +205 -0
  39. package/src/basis/transcode/etc1s_to_bc7.d.ts +30 -0
  40. package/src/basis/transcode/etc1s_to_bc7.d.ts.map +1 -0
  41. package/src/basis/transcode/etc1s_to_bc7.js +496 -0
  42. package/src/basis/transcode/fit_endpoints.d.ts +59 -0
  43. package/src/basis/transcode/fit_endpoints.d.ts.map +1 -0
  44. package/src/basis/transcode/fit_endpoints.js +226 -0
  45. package/src/{avif/native/isobmff → core/binary}/ByteCursor.d.ts +19 -6
  46. package/src/core/binary/ByteCursor.d.ts.map +1 -0
  47. package/src/{avif/native/isobmff → core/binary}/ByteCursor.js +41 -14
  48. package/src/engine/EngineHarness.js +1 -1
  49. package/src/engine/asset/codec/Codec.d.ts.map +1 -0
  50. package/src/engine/asset/codec/CodecWithFallback.d.ts.map +1 -0
  51. package/src/engine/asset/{loaders/image/codec → codec}/CodecWithFallback.js +2 -2
  52. package/src/engine/asset/loaders/image/IMAGE_CODEC_REVIEW_2026_08_28.md +4 -4
  53. package/src/engine/asset/loaders/image/ImageRGBADataLoader.d.ts +1 -1
  54. package/src/engine/asset/loaders/image/ImageRGBADataLoader.d.ts.map +1 -1
  55. package/src/engine/asset/loaders/image/ImageRGBADataLoader.js +1 -1
  56. package/src/engine/asset/loaders/image/codec/NativeImageDecoder.d.ts +1 -1
  57. package/src/engine/asset/loaders/image/codec/NativeImageDecoder.d.ts.map +1 -1
  58. package/src/engine/asset/loaders/image/codec/NativeImageDecoder.js +1 -1
  59. package/src/engine/asset/loaders/image/codec/ThreadedImageDecoder.d.ts +1 -1
  60. package/src/engine/asset/loaders/image/codec/ThreadedImageDecoder.d.ts.map +1 -1
  61. package/src/engine/asset/loaders/image/codec/ThreadedImageDecoder.js +1 -1
  62. package/src/engine/graphics/texture/atlas/TextureAtlas.d.ts.map +1 -1
  63. package/src/engine/graphics/texture/atlas/TextureAtlas.js +13 -0
  64. package/src/engine/physics/broadphase/generate_pairs.d.ts.map +1 -1
  65. package/src/engine/physics/broadphase/generate_pairs.js +7 -1
  66. package/src/engine/physics/fluid/ecs/FluidObstacleSystem.d.ts +4 -4
  67. package/src/ktx2/KTX2_IDENTIFIER.d.ts +14 -0
  68. package/src/ktx2/KTX2_IDENTIFIER.d.ts.map +1 -0
  69. package/src/ktx2/KTX2_IDENTIFIER.js +15 -0
  70. package/src/ktx2/KhrDfModel.d.ts +35 -0
  71. package/src/ktx2/KhrDfModel.d.ts.map +1 -0
  72. package/src/ktx2/KhrDfModel.js +49 -0
  73. package/src/ktx2/SupercompressionScheme.d.ts +16 -0
  74. package/src/ktx2/SupercompressionScheme.d.ts.map +1 -0
  75. package/src/ktx2/SupercompressionScheme.js +23 -0
  76. package/src/ktx2/ktx2_read.d.ts +71 -0
  77. package/src/ktx2/ktx2_read.d.ts.map +1 -0
  78. package/src/ktx2/ktx2_read.js +581 -0
  79. package/src/ktx2/ktx2_read_basis_lz_global_data.d.ts +27 -0
  80. package/src/ktx2/ktx2_read_basis_lz_global_data.d.ts.map +1 -0
  81. package/src/ktx2/ktx2_read_basis_lz_global_data.js +189 -0
  82. package/src/ktx2/vk_format_to_texture_format.d.ts +55 -0
  83. package/src/ktx2/vk_format_to_texture_format.d.ts.map +1 -0
  84. package/src/ktx2/vk_format_to_texture_format.js +290 -0
  85. package/src/shade/descriptor/GPUTextureUsage.d.ts +23 -0
  86. package/src/shade/descriptor/GPUTextureUsage.d.ts.map +1 -0
  87. package/src/shade/descriptor/GPUTextureUsage.js +24 -0
  88. package/src/shade/descriptor/WebGPUExtensionType.d.ts +3 -0
  89. package/src/shade/descriptor/WebGPUExtensionType.js +26 -0
  90. package/src/shade/descriptor/texture/TextureDescriptor.d.ts +2 -6
  91. package/src/shade/descriptor/texture/TextureDescriptor.d.ts.map +1 -1
  92. package/src/shade/descriptor/texture/TextureDescriptor.js +24 -40
  93. package/src/shade/descriptor/texture/format/TextureFormat.d.ts +125 -0
  94. package/src/shade/descriptor/texture/format/TextureFormat.d.ts.map +1 -0
  95. package/src/shade/descriptor/texture/format/TextureFormat.js +146 -0
  96. package/src/shade/descriptor/texture/format/gpu_texture_format_info.d.ts +60 -0
  97. package/src/shade/descriptor/texture/format/gpu_texture_format_info.d.ts.map +1 -1
  98. package/src/shade/descriptor/texture/format/gpu_texture_format_info.js +102 -0
  99. package/src/shade/device/graph/TextureResourceDescriptor.d.ts.map +1 -1
  100. package/src/shade/device/graph/TextureResourceDescriptor.js +160 -155
  101. package/src/shade/device/mock/SoftwareGPUQueue.d.ts +11 -4
  102. package/src/shade/device/mock/SoftwareGPUQueue.d.ts.map +1 -1
  103. package/src/shade/device/mock/SoftwareGPUQueue.js +31 -37
  104. package/src/shade/device/mock/SoftwareGPUTexture.d.ts.map +1 -1
  105. package/src/shade/device/mock/SoftwareGPUTexture.js +8 -4
  106. package/src/shade/device/timing/profile/frame_graph_extract_topology.d.ts.map +1 -1
  107. package/src/shade/device/timing/profile/frame_graph_extract_topology.js +164 -174
  108. package/src/shade/playground/ground_seam/README.md +235 -0
  109. package/src/shade/playground/ground_seam/capture_scene_color.d.ts +45 -0
  110. package/src/shade/playground/ground_seam/capture_scene_color.d.ts.map +1 -0
  111. package/src/shade/playground/ground_seam/capture_scene_color.js +242 -0
  112. package/src/shade/playground/ground_seam/depth_step_across_seam.d.ts +11 -0
  113. package/src/shade/playground/ground_seam/depth_step_across_seam.d.ts.map +1 -0
  114. package/src/shade/playground/ground_seam/depth_step_across_seam.js +185 -0
  115. package/src/shade/playground/ground_seam/index.html +195 -0
  116. package/src/shade/playground/ground_seam/main.d.ts +2 -0
  117. package/src/shade/playground/ground_seam/main.d.ts.map +1 -0
  118. package/src/shade/playground/ground_seam/main.js +879 -0
  119. package/src/shade/playground/ground_seam/make_grid_texture.d.ts +29 -0
  120. package/src/shade/playground/ground_seam/make_grid_texture.d.ts.map +1 -0
  121. package/src/shade/playground/ground_seam/make_grid_texture.js +105 -0
  122. package/src/shade/playground/ground_seam/make_ground.d.ts +70 -0
  123. package/src/shade/playground/ground_seam/make_ground.d.ts.map +1 -0
  124. package/src/shade/playground/ground_seam/make_ground.js +161 -0
  125. package/src/shade/playground/ground_seam/measure_flicker.d.ts +71 -0
  126. package/src/shade/playground/ground_seam/measure_flicker.d.ts.map +1 -0
  127. package/src/shade/playground/ground_seam/measure_flicker.js +531 -0
  128. package/src/shade/renderer/Renderer.d.ts.map +1 -1
  129. package/src/shade/renderer/Renderer.js +2519 -2511
  130. package/src/shade/renderer/hiz/HierarchicalZBuffer.d.ts.map +1 -1
  131. package/src/shade/renderer/hiz/HierarchicalZBuffer.js +275 -274
  132. package/src/shade/renderer/light/GPULightCollection.d.ts.map +1 -1
  133. package/src/shade/renderer/light/GPULightCollection.js +323 -314
  134. package/src/shade/renderer/lightmap/GPULightMap.js +336 -336
  135. package/src/shade/renderer/material/resident/GPUResidentMaterialContext.d.ts.map +1 -1
  136. package/src/shade/renderer/material/resident/GPUResidentMaterialContext.js +12 -2
  137. package/src/shade/renderer/material/standard/fragment_gbuffer.d.ts.map +1 -1
  138. package/src/shade/renderer/material/standard/fragment_gbuffer.js +3 -1
  139. package/src/shade/renderer/material/standard/fragment_gbuffer_vt.js +169 -169
  140. package/src/shade/renderer/path_tracer/shader/chunk_sample_material_data.d.ts.map +1 -1
  141. package/src/shade/renderer/path_tracer/shader/chunk_sample_material_data.js +3 -1
  142. package/src/shade/renderer/postprocess/nss/NSS.d.ts +1 -1
  143. package/src/shade/renderer/rasterize/native/oit/chunk_forward_shade_standard_fragment_brick4.d.ts.map +1 -1
  144. package/src/shade/renderer/rasterize/native/oit/chunk_forward_shade_standard_fragment_brick4.js +3 -1
  145. package/src/shade/renderer/rasterize/native/oit/chunk_forward_shade_standard_fragment_ibl.d.ts.map +1 -1
  146. package/src/shade/renderer/rasterize/native/oit/chunk_forward_shade_standard_fragment_ibl.js +3 -1
  147. package/src/shade/renderer/shader/chunk/geometry/normal/chunk_decode_tangent_normal.d.ts +28 -0
  148. package/src/shade/renderer/shader/chunk/geometry/normal/chunk_decode_tangent_normal.d.ts.map +1 -0
  149. package/src/shade/renderer/shader/chunk/geometry/normal/chunk_decode_tangent_normal.js +39 -0
  150. package/src/shade/renderer/shader/chunk/geometry/triangle/chunk_barycentric_full.d.ts +45 -0
  151. package/src/shade/renderer/shader/chunk/geometry/triangle/chunk_barycentric_full.d.ts.map +1 -1
  152. package/src/shade/renderer/shader/chunk/geometry/triangle/chunk_barycentric_full.js +94 -61
  153. package/src/shade/renderer/shader/chunk/geometry/triangle/chunk_barycentric_solve.d.ts +24 -0
  154. package/src/shade/renderer/shader/chunk/geometry/triangle/chunk_barycentric_solve.d.ts.map +1 -0
  155. package/src/shade/renderer/shader/chunk/geometry/triangle/chunk_barycentric_solve.js +43 -0
  156. package/src/shade/renderer/texture/COMPRESSED_TEXTURE_PLAN_2026_09_01.md +1462 -0
  157. package/src/shade/renderer/texture/TextureManager.d.ts.map +1 -1
  158. package/src/shade/renderer/texture/TextureManager.js +20 -8
  159. package/src/shade/renderer/texture/TextureRole.d.ts +19 -0
  160. package/src/shade/renderer/texture/TextureRole.d.ts.map +1 -0
  161. package/src/shade/renderer/texture/TextureRole.js +26 -0
  162. package/src/shade/renderer/texture/TextureVariantSet.d.ts +71 -0
  163. package/src/shade/renderer/texture/TextureVariantSet.d.ts.map +1 -0
  164. package/src/shade/renderer/texture/TextureVariantSet.js +105 -0
  165. package/src/shade/renderer/texture/download_texture_data.d.ts.map +1 -1
  166. package/src/shade/renderer/texture/download_texture_data.js +22 -0
  167. package/src/shade/renderer/texture/format/gpu_texture_compression_support.d.ts +15 -0
  168. package/src/shade/renderer/texture/format/gpu_texture_compression_support.d.ts.map +1 -0
  169. package/src/shade/renderer/texture/format/gpu_texture_compression_support.js +50 -0
  170. package/src/shade/renderer/texture/format/gpu_texture_format_can_generate_mipmaps.d.ts +27 -0
  171. package/src/shade/renderer/texture/format/gpu_texture_format_can_generate_mipmaps.d.ts.map +1 -0
  172. package/src/shade/renderer/texture/format/gpu_texture_format_can_generate_mipmaps.js +65 -0
  173. package/src/shade/renderer/texture/format/infer_format_from_parameters.d.ts +11 -0
  174. package/src/shade/renderer/texture/format/infer_format_from_parameters.d.ts.map +1 -0
  175. package/src/shade/renderer/texture/format/infer_format_from_parameters.js +88 -0
  176. package/src/shade/renderer/texture/format/texture_format_from_shade_image.d.ts +24 -0
  177. package/src/shade/renderer/texture/format/texture_format_from_shade_image.d.ts.map +1 -0
  178. package/src/shade/renderer/texture/format/texture_format_from_shade_image.js +71 -0
  179. package/src/shade/renderer/texture/format/texture_format_preference.d.ts +22 -0
  180. package/src/shade/renderer/texture/format/texture_format_preference.d.ts.map +1 -0
  181. package/src/shade/renderer/texture/format/texture_format_preference.js +89 -0
  182. package/src/shade/renderer/texture/format/texture_variant_select.d.ts +24 -0
  183. package/src/shade/renderer/texture/format/texture_variant_select.d.ts.map +1 -0
  184. package/src/shade/renderer/texture/format/texture_variant_select.js +38 -0
  185. package/src/shade/renderer/texture/shade_texture_might_have_alpha.d.ts +9 -1
  186. package/src/shade/renderer/texture/shade_texture_might_have_alpha.d.ts.map +1 -1
  187. package/src/shade/renderer/texture/shade_texture_might_have_alpha.js +73 -8
  188. package/src/shade/renderer/texture/source/AlphaMode.d.ts +27 -0
  189. package/src/shade/renderer/texture/source/AlphaMode.d.ts.map +1 -0
  190. package/src/shade/renderer/texture/source/AlphaMode.js +52 -0
  191. package/src/shade/renderer/texture/source/ShadeImage.d.ts +33 -0
  192. package/src/shade/renderer/texture/source/ShadeImage.d.ts.map +1 -1
  193. package/src/shade/renderer/texture/source/ShadeImage.js +74 -0
  194. package/src/shade/renderer/texture/source/ShadeTexelData.d.ts +78 -0
  195. package/src/shade/renderer/texture/source/ShadeTexelData.d.ts.map +1 -0
  196. package/src/shade/renderer/texture/source/ShadeTexelData.js +158 -0
  197. package/src/shade/renderer/texture/texture_descriptor_from_texture_shade.d.ts +0 -10
  198. package/src/shade/renderer/texture/texture_descriptor_from_texture_shade.d.ts.map +1 -1
  199. package/src/shade/renderer/texture/texture_descriptor_from_texture_shade.js +73 -175
  200. package/src/shade/renderer/texture/texture_write_level.d.ts +32 -0
  201. package/src/shade/renderer/texture/texture_write_level.d.ts.map +1 -0
  202. package/src/shade/renderer/texture/texture_write_level.js +77 -0
  203. package/src/shade/renderer/texture/texture_write_to_gpu.d.ts +8 -1
  204. package/src/shade/renderer/texture/texture_write_to_gpu.d.ts.map +1 -1
  205. package/src/shade/renderer/texture/texture_write_to_gpu.js +62 -37
  206. package/src/shade/renderer/texture/virtual/source/VTSourceImage.d.ts +8 -0
  207. package/src/shade/renderer/texture/virtual/source/VTSourceImage.d.ts.map +1 -1
  208. package/src/shade/renderer/texture/virtual/source/VTSourceImage.js +8 -0
  209. package/src/shade/util/texture_mip_extent.d.ts +49 -0
  210. package/src/shade/util/texture_mip_extent.d.ts.map +1 -0
  211. package/src/shade/util/texture_mip_extent.js +87 -0
  212. package/src/avif/native/isobmff/ByteCursor.d.ts.map +0 -1
  213. package/src/engine/asset/loaders/image/codec/Codec.d.ts.map +0 -1
  214. package/src/engine/asset/loaders/image/codec/CodecWithFallback.d.ts.map +0 -1
  215. package/src/shade/util/compute_max_mip_count.d.ts +0 -8
  216. package/src/shade/util/compute_max_mip_count.d.ts.map +0 -1
  217. package/src/shade/util/compute_max_mip_count.js +0 -27
  218. /package/src/engine/asset/{loaders/image/codec → codec}/Codec.d.ts +0 -0
  219. /package/src/engine/asset/{loaders/image/codec → codec}/Codec.js +0 -0
  220. /package/src/engine/asset/{loaders/image/codec → codec}/CodecWithFallback.d.ts +0 -0
@@ -1,383 +1,384 @@
1
- import { for_each_box } from "../isobmff/for_each_box.js";
2
- import { ByteCursor } from "../isobmff/ByteCursor.js";
3
- import { full_box_flags, full_box_version, read_full_box_header } from "../isobmff/read_full_box_header.js";
4
- import { AvifFile, AvifItem, ItemExtent, ItemReference } from "./AvifFile.js";
5
- import { parse_item_property } from "./parse_item_property.js";
6
-
7
- /**
8
- * Ceiling on counts read from the file before they are used to size anything. A real AVIF has a
9
- * handful of items and properties; a count in the millions is corruption, and without a ceiling it
10
- * becomes a multi-gigabyte allocation or a loop that never ends.
11
- * @type {number}
12
- */
13
- const MAX_ENTRIES = 65536;
14
-
15
- /**
16
- * Parse an AVIF file into its item model.
17
- *
18
- * Only structure is read: item payloads are located, not decoded, and their bytes are left in
19
- * place. Nothing is copied out of `buffer`.
20
- *
21
- * @param {ArrayBuffer|Uint8Array} buffer
22
- * @returns {AvifFile}
23
- *
24
- * @author Alex Goldring
25
- * @copyright Company Named Limited (c) 2026
26
- */
27
- export function parse_avif_file(buffer) {
28
- const file = new AvifFile();
29
-
30
- const cursor = new ByteCursor().init(buffer);
31
-
32
- file.bytes = cursor.bytes.subarray(0, cursor.end);
33
-
34
- let saw_ftyp = false;
35
- let saw_meta = false;
36
-
37
- for_each_box(cursor, (header, box_cursor) => {
38
- if (header.type === "ftyp") {
39
- read_ftyp(file, box_cursor, header.end);
40
-
41
- saw_ftyp = true;
42
- } else if (header.type === "meta") {
43
- read_meta(file, box_cursor, header.end);
44
-
45
- saw_meta = true;
46
- } else if (header.type === "moov") {
47
- file.has_image_sequence = true;
48
- }
49
- });
50
-
51
- if (!saw_ftyp) {
52
- throw new Error("not an ISOBMFF file: no ftyp box");
53
- }
54
-
55
- if (!saw_meta) {
56
- throw new Error("not an AVIF file: no meta box");
57
- }
58
-
59
- resolve_item_offsets(file);
60
-
61
- return file;
62
- }
63
-
64
- /**
65
- * @param {AvifFile} file
66
- * @param {ByteCursor} cursor
67
- * @param {number} end
68
- */
69
- function read_ftyp(file, cursor, end) {
70
- file.major_brand = cursor.fourcc();
71
- file.minor_version = cursor.u32();
72
-
73
- while (cursor.position + 4 <= end) {
74
- file.compatible_brands.push(cursor.fourcc());
75
- }
76
- }
77
-
78
- /**
79
- * @param {AvifFile} file
80
- * @param {ByteCursor} cursor
81
- * @param {number} end
82
- */
83
- function read_meta(file, cursor, end) {
84
- read_full_box_header(cursor);
85
-
86
- const children = cursor.sub_cursor(cursor.position, end - cursor.position);
87
-
88
- for_each_box(children, (header, box_cursor) => {
89
- switch (header.type) {
90
- case "pitm":
91
- read_pitm(file, box_cursor);
92
- break;
93
- case "iloc":
94
- read_iloc(file, box_cursor);
95
- break;
96
- case "iinf":
97
- read_iinf(file, box_cursor, header.end);
98
- break;
99
- case "iref":
100
- read_iref(file, box_cursor, header.end);
101
- break;
102
- case "iprp":
103
- read_iprp(file, box_cursor, header.end);
104
- break;
105
- case "idat":
106
- file.item_data = box_cursor.view_bytes(header.payload_size);
107
- break;
108
- default:
109
- break;
110
- }
111
- });
112
- }
113
-
114
- /**
115
- * @param {AvifFile} file
116
- * @param {ByteCursor} cursor
117
- */
118
- function read_pitm(file, cursor) {
119
- const version = full_box_version(read_full_box_header(cursor));
120
-
121
- file.primary_item_id = version === 0 ? cursor.u16() : cursor.u32();
122
- }
123
-
124
- /**
125
- * @param {AvifFile} file
126
- * @param {number} id
127
- * @returns {AvifItem}
128
- */
129
- function item_for_id(file, id) {
130
- let item = file.items.get(id);
131
-
132
- if (item === undefined) {
133
- item = new AvifItem();
134
- item.id = id;
135
-
136
- file.items.set(id, item);
137
- }
138
-
139
- return item;
140
- }
141
-
142
- /**
143
- * @param {number} count
144
- * @param {string} what
145
- */
146
- function check_count(count, what) {
147
- if (count > MAX_ENTRIES) {
148
- throw new Error(`${what} declares ${count} entries, beyond the ${MAX_ENTRIES} supported`);
149
- }
150
- }
151
-
152
- /**
153
- * @param {AvifFile} file
154
- * @param {ByteCursor} cursor
155
- * @see ISO/IEC 14496-12 section 8.11.3 "Item location box"
156
- */
157
- function read_iloc(file, cursor) {
158
- const version = full_box_version(read_full_box_header(cursor));
159
-
160
- const sizes = cursor.u8();
161
- const offset_size = (sizes >> 4) & 0xf;
162
- const length_size = sizes & 0xf;
163
-
164
- const base_and_index = cursor.u8();
165
- const base_offset_size = (base_and_index >> 4) & 0xf;
166
- const index_size = version === 1 || version === 2 ? base_and_index & 0xf : 0;
167
-
168
- const item_count = version < 2 ? cursor.u16() : cursor.u32();
169
-
170
- check_count(item_count, "iloc");
171
-
172
- for (let i = 0; i < item_count; i++) {
173
- const id = version < 2 ? cursor.u16() : cursor.u32();
174
-
175
- const item = item_for_id(file, id);
176
-
177
- if (version === 1 || version === 2) {
178
- item.construction_method = cursor.u16() & 0xf;
179
- }
180
-
181
- item.data_reference_index = cursor.u16();
182
- item.base_offset = cursor.uint_of_size(base_offset_size);
183
-
184
- const extent_count = cursor.u16();
185
-
186
- check_count(extent_count, "iloc extents");
187
-
188
- for (let j = 0; j < extent_count; j++) {
189
- if (index_size > 0) {
190
- cursor.uint_of_size(index_size);
191
- }
192
-
193
- const extent = new ItemExtent();
194
-
195
- extent.offset = cursor.uint_of_size(offset_size);
196
- extent.length = cursor.uint_of_size(length_size);
197
-
198
- item.extents.push(extent);
199
- }
200
- }
201
- }
202
-
203
- /**
204
- * @param {AvifFile} file
205
- * @param {ByteCursor} cursor
206
- * @param {number} end
207
- * @see ISO/IEC 14496-12 section 8.11.6 "Item information box"
208
- */
209
- function read_iinf(file, cursor, end) {
210
- const version = full_box_version(read_full_box_header(cursor));
211
-
212
- const entry_count = version === 0 ? cursor.u16() : cursor.u32();
213
-
214
- check_count(entry_count, "iinf");
215
-
216
- const children = cursor.sub_cursor(cursor.position, end - cursor.position);
217
-
218
- for_each_box(children, (header, box_cursor) => {
219
- if (header.type !== "infe") {
220
- return;
221
- }
222
-
223
- read_infe(file, box_cursor, header.end);
224
- });
225
- }
226
-
227
- /**
228
- * @param {AvifFile} file
229
- * @param {ByteCursor} cursor
230
- * @param {number} end
231
- */
232
- function read_infe(file, cursor, end) {
233
- const version_and_flags = read_full_box_header(cursor);
234
- const version = full_box_version(version_and_flags);
235
- const flags = full_box_flags(version_and_flags);
236
-
237
- if (version < 2) {
238
- // versions 0 and 1 have no item_type field at all, so an item's nature can only be guessed
239
- // from its name. No AVIF writer emits them; refusing is better than guessing.
240
- throw new Error(`infe version ${version} is not supported by AVIF`);
241
- }
242
-
243
- const id = version === 2 ? cursor.u16() : cursor.u32();
244
-
245
- const item = item_for_id(file, id);
246
-
247
- cursor.u16();
248
-
249
- item.item_type = cursor.fourcc();
250
- item.item_name = cursor.utf8_string();
251
- item.hidden = (flags & 1) !== 0;
252
-
253
- if (item.item_type === "mime" && cursor.position < end) {
254
- item.content_type = cursor.utf8_string();
255
- } else if (item.item_type === "uri " && cursor.position < end) {
256
- item.content_type = cursor.utf8_string();
257
- }
258
- }
259
-
260
- /**
261
- * @param {AvifFile} file
262
- * @param {ByteCursor} cursor
263
- * @param {number} end
264
- * @see ISO/IEC 14496-12 section 8.11.12 "Item reference box"
265
- */
266
- function read_iref(file, cursor, end) {
267
- const version = full_box_version(read_full_box_header(cursor));
268
-
269
- const children = cursor.sub_cursor(cursor.position, end - cursor.position);
270
-
271
- for_each_box(children, (header, box_cursor) => {
272
- const reference = new ItemReference();
273
-
274
- reference.reference_type = header.type;
275
- reference.from_item_id = version === 0 ? box_cursor.u16() : box_cursor.u32();
276
-
277
- const count = box_cursor.u16();
278
-
279
- check_count(count, "iref");
280
-
281
- for (let i = 0; i < count; i++) {
282
- reference.to_item_ids.push(version === 0 ? box_cursor.u16() : box_cursor.u32());
283
- }
284
-
285
- file.references.push(reference);
286
- });
287
- }
288
-
289
- /**
290
- * @param {AvifFile} file
291
- * @param {ByteCursor} cursor
292
- * @param {number} end
293
- * @see ISO/IEC 23008-12 section 9.3 "Item properties"
294
- */
295
- function read_iprp(file, cursor, end) {
296
- const children = cursor.sub_cursor(cursor.position, end - cursor.position);
297
-
298
- for_each_box(children, (header, box_cursor) => {
299
- if (header.type === "ipco") {
300
- read_ipco(file, box_cursor, header.end);
301
- } else if (header.type === "ipma") {
302
- read_ipma(file, box_cursor);
303
- }
304
- });
305
- }
306
-
307
- /**
308
- * @param {AvifFile} file
309
- * @param {ByteCursor} cursor
310
- * @param {number} end
311
- */
312
- function read_ipco(file, cursor, end) {
313
- const children = cursor.sub_cursor(cursor.position, end - cursor.position);
314
-
315
- for_each_box(children, (header, box_cursor) => {
316
- file.properties.push(parse_item_property(header.type, box_cursor, header.payload_size));
317
- });
318
- }
319
-
320
- /**
321
- * @param {AvifFile} file
322
- * @param {ByteCursor} cursor
323
- */
324
- function read_ipma(file, cursor) {
325
- const version_and_flags = read_full_box_header(cursor);
326
- const version = full_box_version(version_and_flags);
327
- const wide_indices = (full_box_flags(version_and_flags) & 1) !== 0;
328
-
329
- const entry_count = cursor.u32();
330
-
331
- check_count(entry_count, "ipma");
332
-
333
- for (let i = 0; i < entry_count; i++) {
334
- const id = version < 1 ? cursor.u16() : cursor.u32();
335
-
336
- const item = item_for_id(file, id);
337
-
338
- const association_count = cursor.u8();
339
-
340
- for (let j = 0; j < association_count; j++) {
341
- let essential;
342
- let index;
343
-
344
- if (wide_indices) {
345
- const packed = cursor.u16();
346
-
347
- essential = (packed & 0x8000) !== 0;
348
- index = packed & 0x7fff;
349
- } else {
350
- const packed = cursor.u8();
351
-
352
- essential = (packed & 0x80) !== 0;
353
- index = packed & 0x7f;
354
- }
355
-
356
- // index 0 means "no property"; the specification allows it and it must not be treated
357
- // as a reference to the first property
358
- if (index === 0) {
359
- continue;
360
- }
361
-
362
- item.property_indices.push(index);
363
- item.property_essential.push(essential);
364
- }
365
- }
366
- }
367
-
368
- /**
369
- * Fold `base_offset` into each extent, so consumers see absolute offsets and never have to know
370
- * which of the three construction methods produced them.
371
- *
372
- * Method 1 offsets are relative to `idat` and method 2 to another item; both are left in their own
373
- * space and resolved by {@link read_item_data}, which is the only place that can see the target.
374
- *
375
- * @param {AvifFile} file
376
- */
377
- function resolve_item_offsets(file) {
378
- for (const item of file.items.values()) {
379
- for (let i = 0; i < item.extents.length; i++) {
380
- item.extents[i].offset += item.base_offset;
381
- }
382
- }
383
- }
1
+ import { for_each_box } from "../isobmff/for_each_box.js";
2
+ import { ByteCursor } from "../../../core/binary/ByteCursor.js";
3
+ import { EndianType } from "../../../core/binary/EndianType.js";
4
+ import { full_box_flags, full_box_version, read_full_box_header } from "../isobmff/read_full_box_header.js";
5
+ import { AvifFile, AvifItem, ItemExtent, ItemReference } from "./AvifFile.js";
6
+ import { parse_item_property } from "./parse_item_property.js";
7
+
8
+ /**
9
+ * Ceiling on counts read from the file before they are used to size anything. A real AVIF has a
10
+ * handful of items and properties; a count in the millions is corruption, and without a ceiling it
11
+ * becomes a multi-gigabyte allocation or a loop that never ends.
12
+ * @type {number}
13
+ */
14
+ const MAX_ENTRIES = 65536;
15
+
16
+ /**
17
+ * Parse an AVIF file into its item model.
18
+ *
19
+ * Only structure is read: item payloads are located, not decoded, and their bytes are left in
20
+ * place. Nothing is copied out of `buffer`.
21
+ *
22
+ * @param {ArrayBuffer|Uint8Array} buffer
23
+ * @returns {AvifFile}
24
+ *
25
+ * @author Alex Goldring
26
+ * @copyright Company Named Limited (c) 2026
27
+ */
28
+ export function parse_avif_file(buffer) {
29
+ const file = new AvifFile();
30
+
31
+ const cursor = new ByteCursor().init(buffer, EndianType.BigEndian);
32
+
33
+ file.bytes = cursor.bytes.subarray(0, cursor.end);
34
+
35
+ let saw_ftyp = false;
36
+ let saw_meta = false;
37
+
38
+ for_each_box(cursor, (header, box_cursor) => {
39
+ if (header.type === "ftyp") {
40
+ read_ftyp(file, box_cursor, header.end);
41
+
42
+ saw_ftyp = true;
43
+ } else if (header.type === "meta") {
44
+ read_meta(file, box_cursor, header.end);
45
+
46
+ saw_meta = true;
47
+ } else if (header.type === "moov") {
48
+ file.has_image_sequence = true;
49
+ }
50
+ });
51
+
52
+ if (!saw_ftyp) {
53
+ throw new Error("not an ISOBMFF file: no ftyp box");
54
+ }
55
+
56
+ if (!saw_meta) {
57
+ throw new Error("not an AVIF file: no meta box");
58
+ }
59
+
60
+ resolve_item_offsets(file);
61
+
62
+ return file;
63
+ }
64
+
65
+ /**
66
+ * @param {AvifFile} file
67
+ * @param {ByteCursor} cursor
68
+ * @param {number} end
69
+ */
70
+ function read_ftyp(file, cursor, end) {
71
+ file.major_brand = cursor.fourcc();
72
+ file.minor_version = cursor.u32();
73
+
74
+ while (cursor.position + 4 <= end) {
75
+ file.compatible_brands.push(cursor.fourcc());
76
+ }
77
+ }
78
+
79
+ /**
80
+ * @param {AvifFile} file
81
+ * @param {ByteCursor} cursor
82
+ * @param {number} end
83
+ */
84
+ function read_meta(file, cursor, end) {
85
+ read_full_box_header(cursor);
86
+
87
+ const children = cursor.sub_cursor(cursor.position, end - cursor.position);
88
+
89
+ for_each_box(children, (header, box_cursor) => {
90
+ switch (header.type) {
91
+ case "pitm":
92
+ read_pitm(file, box_cursor);
93
+ break;
94
+ case "iloc":
95
+ read_iloc(file, box_cursor);
96
+ break;
97
+ case "iinf":
98
+ read_iinf(file, box_cursor, header.end);
99
+ break;
100
+ case "iref":
101
+ read_iref(file, box_cursor, header.end);
102
+ break;
103
+ case "iprp":
104
+ read_iprp(file, box_cursor, header.end);
105
+ break;
106
+ case "idat":
107
+ file.item_data = box_cursor.view_bytes(header.payload_size);
108
+ break;
109
+ default:
110
+ break;
111
+ }
112
+ });
113
+ }
114
+
115
+ /**
116
+ * @param {AvifFile} file
117
+ * @param {ByteCursor} cursor
118
+ */
119
+ function read_pitm(file, cursor) {
120
+ const version = full_box_version(read_full_box_header(cursor));
121
+
122
+ file.primary_item_id = version === 0 ? cursor.u16() : cursor.u32();
123
+ }
124
+
125
+ /**
126
+ * @param {AvifFile} file
127
+ * @param {number} id
128
+ * @returns {AvifItem}
129
+ */
130
+ function item_for_id(file, id) {
131
+ let item = file.items.get(id);
132
+
133
+ if (item === undefined) {
134
+ item = new AvifItem();
135
+ item.id = id;
136
+
137
+ file.items.set(id, item);
138
+ }
139
+
140
+ return item;
141
+ }
142
+
143
+ /**
144
+ * @param {number} count
145
+ * @param {string} what
146
+ */
147
+ function check_count(count, what) {
148
+ if (count > MAX_ENTRIES) {
149
+ throw new Error(`${what} declares ${count} entries, beyond the ${MAX_ENTRIES} supported`);
150
+ }
151
+ }
152
+
153
+ /**
154
+ * @param {AvifFile} file
155
+ * @param {ByteCursor} cursor
156
+ * @see ISO/IEC 14496-12 section 8.11.3 "Item location box"
157
+ */
158
+ function read_iloc(file, cursor) {
159
+ const version = full_box_version(read_full_box_header(cursor));
160
+
161
+ const sizes = cursor.u8();
162
+ const offset_size = (sizes >> 4) & 0xf;
163
+ const length_size = sizes & 0xf;
164
+
165
+ const base_and_index = cursor.u8();
166
+ const base_offset_size = (base_and_index >> 4) & 0xf;
167
+ const index_size = version === 1 || version === 2 ? base_and_index & 0xf : 0;
168
+
169
+ const item_count = version < 2 ? cursor.u16() : cursor.u32();
170
+
171
+ check_count(item_count, "iloc");
172
+
173
+ for (let i = 0; i < item_count; i++) {
174
+ const id = version < 2 ? cursor.u16() : cursor.u32();
175
+
176
+ const item = item_for_id(file, id);
177
+
178
+ if (version === 1 || version === 2) {
179
+ item.construction_method = cursor.u16() & 0xf;
180
+ }
181
+
182
+ item.data_reference_index = cursor.u16();
183
+ item.base_offset = cursor.uint_of_size(base_offset_size);
184
+
185
+ const extent_count = cursor.u16();
186
+
187
+ check_count(extent_count, "iloc extents");
188
+
189
+ for (let j = 0; j < extent_count; j++) {
190
+ if (index_size > 0) {
191
+ cursor.uint_of_size(index_size);
192
+ }
193
+
194
+ const extent = new ItemExtent();
195
+
196
+ extent.offset = cursor.uint_of_size(offset_size);
197
+ extent.length = cursor.uint_of_size(length_size);
198
+
199
+ item.extents.push(extent);
200
+ }
201
+ }
202
+ }
203
+
204
+ /**
205
+ * @param {AvifFile} file
206
+ * @param {ByteCursor} cursor
207
+ * @param {number} end
208
+ * @see ISO/IEC 14496-12 section 8.11.6 "Item information box"
209
+ */
210
+ function read_iinf(file, cursor, end) {
211
+ const version = full_box_version(read_full_box_header(cursor));
212
+
213
+ const entry_count = version === 0 ? cursor.u16() : cursor.u32();
214
+
215
+ check_count(entry_count, "iinf");
216
+
217
+ const children = cursor.sub_cursor(cursor.position, end - cursor.position);
218
+
219
+ for_each_box(children, (header, box_cursor) => {
220
+ if (header.type !== "infe") {
221
+ return;
222
+ }
223
+
224
+ read_infe(file, box_cursor, header.end);
225
+ });
226
+ }
227
+
228
+ /**
229
+ * @param {AvifFile} file
230
+ * @param {ByteCursor} cursor
231
+ * @param {number} end
232
+ */
233
+ function read_infe(file, cursor, end) {
234
+ const version_and_flags = read_full_box_header(cursor);
235
+ const version = full_box_version(version_and_flags);
236
+ const flags = full_box_flags(version_and_flags);
237
+
238
+ if (version < 2) {
239
+ // versions 0 and 1 have no item_type field at all, so an item's nature can only be guessed
240
+ // from its name. No AVIF writer emits them; refusing is better than guessing.
241
+ throw new Error(`infe version ${version} is not supported by AVIF`);
242
+ }
243
+
244
+ const id = version === 2 ? cursor.u16() : cursor.u32();
245
+
246
+ const item = item_for_id(file, id);
247
+
248
+ cursor.u16();
249
+
250
+ item.item_type = cursor.fourcc();
251
+ item.item_name = cursor.utf8_string();
252
+ item.hidden = (flags & 1) !== 0;
253
+
254
+ if (item.item_type === "mime" && cursor.position < end) {
255
+ item.content_type = cursor.utf8_string();
256
+ } else if (item.item_type === "uri " && cursor.position < end) {
257
+ item.content_type = cursor.utf8_string();
258
+ }
259
+ }
260
+
261
+ /**
262
+ * @param {AvifFile} file
263
+ * @param {ByteCursor} cursor
264
+ * @param {number} end
265
+ * @see ISO/IEC 14496-12 section 8.11.12 "Item reference box"
266
+ */
267
+ function read_iref(file, cursor, end) {
268
+ const version = full_box_version(read_full_box_header(cursor));
269
+
270
+ const children = cursor.sub_cursor(cursor.position, end - cursor.position);
271
+
272
+ for_each_box(children, (header, box_cursor) => {
273
+ const reference = new ItemReference();
274
+
275
+ reference.reference_type = header.type;
276
+ reference.from_item_id = version === 0 ? box_cursor.u16() : box_cursor.u32();
277
+
278
+ const count = box_cursor.u16();
279
+
280
+ check_count(count, "iref");
281
+
282
+ for (let i = 0; i < count; i++) {
283
+ reference.to_item_ids.push(version === 0 ? box_cursor.u16() : box_cursor.u32());
284
+ }
285
+
286
+ file.references.push(reference);
287
+ });
288
+ }
289
+
290
+ /**
291
+ * @param {AvifFile} file
292
+ * @param {ByteCursor} cursor
293
+ * @param {number} end
294
+ * @see ISO/IEC 23008-12 section 9.3 "Item properties"
295
+ */
296
+ function read_iprp(file, cursor, end) {
297
+ const children = cursor.sub_cursor(cursor.position, end - cursor.position);
298
+
299
+ for_each_box(children, (header, box_cursor) => {
300
+ if (header.type === "ipco") {
301
+ read_ipco(file, box_cursor, header.end);
302
+ } else if (header.type === "ipma") {
303
+ read_ipma(file, box_cursor);
304
+ }
305
+ });
306
+ }
307
+
308
+ /**
309
+ * @param {AvifFile} file
310
+ * @param {ByteCursor} cursor
311
+ * @param {number} end
312
+ */
313
+ function read_ipco(file, cursor, end) {
314
+ const children = cursor.sub_cursor(cursor.position, end - cursor.position);
315
+
316
+ for_each_box(children, (header, box_cursor) => {
317
+ file.properties.push(parse_item_property(header.type, box_cursor, header.payload_size));
318
+ });
319
+ }
320
+
321
+ /**
322
+ * @param {AvifFile} file
323
+ * @param {ByteCursor} cursor
324
+ */
325
+ function read_ipma(file, cursor) {
326
+ const version_and_flags = read_full_box_header(cursor);
327
+ const version = full_box_version(version_and_flags);
328
+ const wide_indices = (full_box_flags(version_and_flags) & 1) !== 0;
329
+
330
+ const entry_count = cursor.u32();
331
+
332
+ check_count(entry_count, "ipma");
333
+
334
+ for (let i = 0; i < entry_count; i++) {
335
+ const id = version < 1 ? cursor.u16() : cursor.u32();
336
+
337
+ const item = item_for_id(file, id);
338
+
339
+ const association_count = cursor.u8();
340
+
341
+ for (let j = 0; j < association_count; j++) {
342
+ let essential;
343
+ let index;
344
+
345
+ if (wide_indices) {
346
+ const packed = cursor.u16();
347
+
348
+ essential = (packed & 0x8000) !== 0;
349
+ index = packed & 0x7fff;
350
+ } else {
351
+ const packed = cursor.u8();
352
+
353
+ essential = (packed & 0x80) !== 0;
354
+ index = packed & 0x7f;
355
+ }
356
+
357
+ // index 0 means "no property"; the specification allows it and it must not be treated
358
+ // as a reference to the first property
359
+ if (index === 0) {
360
+ continue;
361
+ }
362
+
363
+ item.property_indices.push(index);
364
+ item.property_essential.push(essential);
365
+ }
366
+ }
367
+ }
368
+
369
+ /**
370
+ * Fold `base_offset` into each extent, so consumers see absolute offsets and never have to know
371
+ * which of the three construction methods produced them.
372
+ *
373
+ * Method 1 offsets are relative to `idat` and method 2 to another item; both are left in their own
374
+ * space and resolved by {@link read_item_data}, which is the only place that can see the target.
375
+ *
376
+ * @param {AvifFile} file
377
+ */
378
+ function resolve_item_offsets(file) {
379
+ for (const item of file.items.values()) {
380
+ for (let i = 0; i < item.extents.length; i++) {
381
+ item.extents[i].offset += item.base_offset;
382
+ }
383
+ }
384
+ }