@woosh/meep-engine 3.9.0 → 3.10.0

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 (696) hide show
  1. package/build/bundle-worker-image-decoder.js +1 -1
  2. package/build/bundle-worker-terrain.js +1 -1
  3. package/package.json +3 -2
  4. package/src/avif/encode_image_source.d.ts +70 -0
  5. package/src/avif/encode_image_source.d.ts.map +1 -0
  6. package/src/avif/encode_image_source.js +117 -0
  7. package/src/avif/encoder_worker.js +15 -126
  8. package/src/avif/index.d.ts +4 -2
  9. package/src/avif/index.js +21 -2
  10. package/src/avif/native/DECISIONS.md +537 -0
  11. package/src/avif/native/NOTICE.md +61 -0
  12. package/src/avif/native/api/AvifDecoder.d.ts +67 -0
  13. package/src/avif/native/api/AvifDecoder.d.ts.map +1 -0
  14. package/src/avif/native/api/AvifDecoder.js +121 -0
  15. package/src/avif/native/api/apply_transformations.d.ts +30 -0
  16. package/src/avif/native/api/apply_transformations.d.ts.map +1 -0
  17. package/src/avif/native/api/apply_transformations.js +120 -0
  18. package/src/avif/native/api/avif_to_sampler2d.d.ts +25 -0
  19. package/src/avif/native/api/avif_to_sampler2d.d.ts.map +1 -0
  20. package/src/avif/native/api/avif_to_sampler2d.js +42 -0
  21. package/src/avif/native/api/convert_to_rgba.d.ts +54 -0
  22. package/src/avif/native/api/convert_to_rgba.d.ts.map +1 -0
  23. package/src/avif/native/api/convert_to_rgba.js +204 -0
  24. package/src/avif/native/api/decode_avif.d.ts +24 -0
  25. package/src/avif/native/api/decode_avif.d.ts.map +1 -0
  26. package/src/avif/native/api/decode_avif.js +111 -0
  27. package/src/avif/native/api/decode_image_item.d.ts +18 -0
  28. package/src/avif/native/api/decode_image_item.d.ts.map +1 -0
  29. package/src/avif/native/api/decode_image_item.js +234 -0
  30. package/src/avif/native/api/encode_avif.d.ts +73 -0
  31. package/src/avif/native/api/encode_avif.d.ts.map +1 -0
  32. package/src/avif/native/api/encode_avif.js +488 -0
  33. package/src/avif/native/api/sampler2d_to_avif.d.ts +22 -0
  34. package/src/avif/native/api/sampler2d_to_avif.d.ts.map +1 -0
  35. package/src/avif/native/api/sampler2d_to_avif.js +108 -0
  36. package/src/avif/native/av1/decode/Av1FrameContext.d.ts +286 -0
  37. package/src/avif/native/av1/decode/Av1FrameContext.d.ts.map +1 -0
  38. package/src/avif/native/av1/decode/Av1FrameContext.js +622 -0
  39. package/src/avif/native/av1/decode/block_decoded.d.ts +27 -0
  40. package/src/avif/native/av1/decode/block_decoded.d.ts.map +1 -0
  41. package/src/avif/native/av1/decode/block_decoded.js +59 -0
  42. package/src/avif/native/av1/decode/decode_av1_still.d.ts +22 -0
  43. package/src/avif/native/av1/decode/decode_av1_still.d.ts.map +1 -0
  44. package/src/avif/native/av1/decode/decode_av1_still.js +309 -0
  45. package/src/avif/native/av1/decode/decode_coefficients.d.ts +20 -0
  46. package/src/avif/native/av1/decode/decode_coefficients.d.ts.map +1 -0
  47. package/src/avif/native/av1/decode/decode_coefficients.js +259 -0
  48. package/src/avif/native/av1/decode/decode_palette.d.ts +28 -0
  49. package/src/avif/native/av1/decode/decode_palette.d.ts.map +1 -0
  50. package/src/avif/native/av1/decode/decode_palette.js +442 -0
  51. package/src/avif/native/av1/decode/decode_tile.d.ts +14 -0
  52. package/src/avif/native/av1/decode/decode_tile.d.ts.map +1 -0
  53. package/src/avif/native/av1/decode/decode_tile.js +1213 -0
  54. package/src/avif/native/av1/decode/get_scan.d.ts +16 -0
  55. package/src/avif/native/av1/decode/get_scan.d.ts.map +1 -0
  56. package/src/avif/native/av1/decode/get_scan.js +138 -0
  57. package/src/avif/native/av1/decode/read_lr.d.ts +35 -0
  58. package/src/avif/native/av1/decode/read_lr.d.ts.map +1 -0
  59. package/src/avif/native/av1/decode/read_lr.js +277 -0
  60. package/src/avif/native/av1/decode/read_transform_type.d.ts +19 -0
  61. package/src/avif/native/av1/decode/read_transform_type.d.ts.map +1 -0
  62. package/src/avif/native/av1/decode/read_transform_type.js +92 -0
  63. package/src/avif/native/av1/decode/reconstruct.d.ts +36 -0
  64. package/src/avif/native/av1/decode/reconstruct.d.ts.map +1 -0
  65. package/src/avif/native/av1/decode/reconstruct.js +241 -0
  66. package/src/avif/native/av1/decode/transform_type.d.ts +49 -0
  67. package/src/avif/native/av1/decode/transform_type.d.ts.map +1 -0
  68. package/src/avif/native/av1/decode/transform_type.js +137 -0
  69. package/src/avif/native/av1/encode/Av1BlockSnapshot.d.ts +81 -0
  70. package/src/avif/native/av1/encode/Av1BlockSnapshot.d.ts.map +1 -0
  71. package/src/avif/native/av1/encode/Av1BlockSnapshot.js +299 -0
  72. package/src/avif/native/av1/encode/Av1EncodeContext.d.ts +384 -0
  73. package/src/avif/native/av1/encode/Av1EncodeContext.d.ts.map +1 -0
  74. package/src/avif/native/av1/encode/Av1EncodeContext.js +590 -0
  75. package/src/avif/native/av1/encode/build_headers.d.ts +50 -0
  76. package/src/avif/native/av1/encode/build_headers.d.ts.map +1 -0
  77. package/src/avif/native/av1/encode/build_headers.js +359 -0
  78. package/src/avif/native/av1/encode/choose_chroma_alpha.d.ts +26 -0
  79. package/src/avif/native/av1/encode/choose_chroma_alpha.d.ts.map +1 -0
  80. package/src/avif/native/av1/encode/choose_chroma_alpha.js +90 -0
  81. package/src/avif/native/av1/encode/choose_filters.d.ts +21 -0
  82. package/src/avif/native/av1/encode/choose_filters.d.ts.map +1 -0
  83. package/src/avif/native/av1/encode/choose_filters.js +318 -0
  84. package/src/avif/native/av1/encode/choose_restoration.d.ts +35 -0
  85. package/src/avif/native/av1/encode/choose_restoration.d.ts.map +1 -0
  86. package/src/avif/native/av1/encode/choose_restoration.js +616 -0
  87. package/src/avif/native/av1/encode/decode_effort.d.ts +57 -0
  88. package/src/avif/native/av1/encode/decode_effort.d.ts.map +1 -0
  89. package/src/avif/native/av1/encode/decode_effort.js +59 -0
  90. package/src/avif/native/av1/encode/encode_av1_still.d.ts +17 -0
  91. package/src/avif/native/av1/encode/encode_av1_still.d.ts.map +1 -0
  92. package/src/avif/native/av1/encode/encode_av1_still.js +194 -0
  93. package/src/avif/native/av1/encode/encode_effort.d.ts +56 -0
  94. package/src/avif/native/av1/encode/encode_effort.d.ts.map +1 -0
  95. package/src/avif/native/av1/encode/encode_effort.js +97 -0
  96. package/src/avif/native/av1/encode/encode_tile.d.ts +26 -0
  97. package/src/avif/native/av1/encode/encode_tile.d.ts.map +1 -0
  98. package/src/avif/native/av1/encode/encode_tile.js +2331 -0
  99. package/src/avif/native/av1/encode/quantise.d.ts +51 -0
  100. package/src/avif/native/av1/encode/quantise.d.ts.map +1 -0
  101. package/src/avif/native/av1/encode/quantise.js +212 -0
  102. package/src/avif/native/av1/encode/write_coefficients.d.ts +42 -0
  103. package/src/avif/native/av1/encode/write_coefficients.d.ts.map +1 -0
  104. package/src/avif/native/av1/encode/write_coefficients.js +367 -0
  105. package/src/avif/native/av1/encode/write_lr.d.ts +33 -0
  106. package/src/avif/native/av1/encode/write_lr.d.ts.map +1 -0
  107. package/src/avif/native/av1/encode/write_lr.js +174 -0
  108. package/src/avif/native/av1/encode/write_transform_type.d.ts +21 -0
  109. package/src/avif/native/av1/encode/write_transform_type.d.ts.map +1 -0
  110. package/src/avif/native/av1/encode/write_transform_type.js +70 -0
  111. package/src/avif/native/av1/entropy/CdfContext.d.ts +167 -0
  112. package/src/avif/native/av1/entropy/CdfContext.d.ts.map +1 -0
  113. package/src/avif/native/av1/entropy/CdfContext.js +433 -0
  114. package/src/avif/native/av1/entropy/SymbolReader.d.ts +103 -0
  115. package/src/avif/native/av1/entropy/SymbolReader.d.ts.map +1 -0
  116. package/src/avif/native/av1/entropy/SymbolReader.js +317 -0
  117. package/src/avif/native/av1/entropy/SymbolWriter.d.ts +107 -0
  118. package/src/avif/native/av1/entropy/SymbolWriter.d.ts.map +1 -0
  119. package/src/avif/native/av1/entropy/SymbolWriter.js +352 -0
  120. package/src/avif/native/av1/entropy/coefficient_context.d.ts +71 -0
  121. package/src/avif/native/av1/entropy/coefficient_context.d.ts.map +1 -0
  122. package/src/avif/native/av1/entropy/coefficient_context.js +325 -0
  123. package/src/avif/native/av1/entropy/partition_cdf.d.ts +54 -0
  124. package/src/avif/native/av1/entropy/partition_cdf.d.ts.map +1 -0
  125. package/src/avif/native/av1/entropy/partition_cdf.js +98 -0
  126. package/src/avif/native/av1/entropy/symbol_cost.d.ts +37 -0
  127. package/src/avif/native/av1/entropy/symbol_cost.d.ts.map +1 -0
  128. package/src/avif/native/av1/entropy/symbol_cost.js +76 -0
  129. package/src/avif/native/av1/filter/cdef.d.ts +15 -0
  130. package/src/avif/native/av1/filter/cdef.d.ts.map +1 -0
  131. package/src/avif/native/av1/filter/cdef.js +313 -0
  132. package/src/avif/native/av1/filter/loop_filter.d.ts +14 -0
  133. package/src/avif/native/av1/filter/loop_filter.d.ts.map +1 -0
  134. package/src/avif/native/av1/filter/loop_filter.js +377 -0
  135. package/src/avif/native/av1/filter/loop_restoration.d.ts +42 -0
  136. package/src/avif/native/av1/filter/loop_restoration.d.ts.map +1 -0
  137. package/src/avif/native/av1/filter/loop_restoration.js +451 -0
  138. package/src/avif/native/av1/filter/superres.d.ts +18 -0
  139. package/src/avif/native/av1/filter/superres.d.ts.map +1 -0
  140. package/src/avif/native/av1/filter/superres.js +86 -0
  141. package/src/avif/native/av1/grain/FilmGrainState.d.ts +62 -0
  142. package/src/avif/native/av1/grain/FilmGrainState.d.ts.map +1 -0
  143. package/src/avif/native/av1/grain/FilmGrainState.js +87 -0
  144. package/src/avif/native/av1/grain/film_grain.d.ts +16 -0
  145. package/src/avif/native/av1/grain/film_grain.d.ts.map +1 -0
  146. package/src/avif/native/av1/grain/film_grain.js +623 -0
  147. package/src/avif/native/av1/obu/FrameHeader.d.ts +248 -0
  148. package/src/avif/native/av1/obu/FrameHeader.d.ts.map +1 -0
  149. package/src/avif/native/av1/obu/FrameHeader.js +381 -0
  150. package/src/avif/native/av1/obu/ObuHeader.d.ts +53 -0
  151. package/src/avif/native/av1/obu/ObuHeader.d.ts.map +1 -0
  152. package/src/avif/native/av1/obu/ObuHeader.js +62 -0
  153. package/src/avif/native/av1/obu/SequenceHeader.d.ts +157 -0
  154. package/src/avif/native/av1/obu/SequenceHeader.d.ts.map +1 -0
  155. package/src/avif/native/av1/obu/SequenceHeader.js +225 -0
  156. package/src/avif/native/av1/obu/for_each_obu.d.ts +24 -0
  157. package/src/avif/native/av1/obu/for_each_obu.d.ts.map +1 -0
  158. package/src/avif/native/av1/obu/for_each_obu.js +50 -0
  159. package/src/avif/native/av1/obu/parse_frame_header.d.ts +18 -0
  160. package/src/avif/native/av1/obu/parse_frame_header.d.ts.map +1 -0
  161. package/src/avif/native/av1/obu/parse_frame_header.js +841 -0
  162. package/src/avif/native/av1/obu/parse_obu_header.d.ts +23 -0
  163. package/src/avif/native/av1/obu/parse_obu_header.d.ts.map +1 -0
  164. package/src/avif/native/av1/obu/parse_obu_header.js +69 -0
  165. package/src/avif/native/av1/obu/parse_sequence_header.d.ts +17 -0
  166. package/src/avif/native/av1/obu/parse_sequence_header.d.ts.map +1 -0
  167. package/src/avif/native/av1/obu/parse_sequence_header.js +263 -0
  168. package/src/avif/native/av1/obu/write_frame_header.d.ts +16 -0
  169. package/src/avif/native/av1/obu/write_frame_header.d.ts.map +1 -0
  170. package/src/avif/native/av1/obu/write_frame_header.js +579 -0
  171. package/src/avif/native/av1/obu/write_sequence_header.d.ts +17 -0
  172. package/src/avif/native/av1/obu/write_sequence_header.d.ts.map +1 -0
  173. package/src/avif/native/av1/obu/write_sequence_header.js +189 -0
  174. package/src/avif/native/av1/predict/IntraPredictionState.d.ts +49 -0
  175. package/src/avif/native/av1/predict/IntraPredictionState.d.ts.map +1 -0
  176. package/src/avif/native/av1/predict/IntraPredictionState.js +58 -0
  177. package/src/avif/native/av1/predict/intra_filter_type.d.ts +17 -0
  178. package/src/avif/native/av1/predict/intra_filter_type.d.ts.map +1 -0
  179. package/src/avif/native/av1/predict/intra_filter_type.js +79 -0
  180. package/src/avif/native/av1/predict/predict_chroma_from_luma.d.ts +50 -0
  181. package/src/avif/native/av1/predict/predict_chroma_from_luma.d.ts.map +1 -0
  182. package/src/avif/native/av1/predict/predict_chroma_from_luma.js +153 -0
  183. package/src/avif/native/av1/predict/predict_intra.d.ts +42 -0
  184. package/src/avif/native/av1/predict/predict_intra.d.ts.map +1 -0
  185. package/src/avif/native/av1/predict/predict_intra.js +803 -0
  186. package/src/avif/native/av1/predict/predict_palette.d.ts +23 -0
  187. package/src/avif/native/av1/predict/predict_palette.d.ts.map +1 -0
  188. package/src/avif/native/av1/predict/predict_palette.js +36 -0
  189. package/src/avif/native/av1/tables/av1_symbols.d.ts +411 -0
  190. package/src/avif/native/av1/tables/av1_symbols.d.ts.map +1 -0
  191. package/src/avif/native/av1/tables/av1_symbols.js +419 -0
  192. package/src/avif/native/av1/tables/block_tables.d.ts +181 -0
  193. package/src/avif/native/av1/tables/block_tables.d.ts.map +1 -0
  194. package/src/avif/native/av1/tables/block_tables.js +270 -0
  195. package/src/avif/native/av1/tables/cdf_tables.d.ts +961 -0
  196. package/src/avif/native/av1/tables/cdf_tables.d.ts.map +1 -0
  197. package/src/avif/native/av1/tables/cdf_tables.js +1893 -0
  198. package/src/avif/native/av1/tables/coefficient_tables.d.ts +41 -0
  199. package/src/avif/native/av1/tables/coefficient_tables.d.ts.map +1 -0
  200. package/src/avif/native/av1/tables/coefficient_tables.js +82 -0
  201. package/src/avif/native/av1/tables/derived_block_tables.d.ts +15 -0
  202. package/src/avif/native/av1/tables/derived_block_tables.d.ts.map +1 -0
  203. package/src/avif/native/av1/tables/derived_block_tables.js +23 -0
  204. package/src/avif/native/av1/tables/filter_tables.d.ts +171 -0
  205. package/src/avif/native/av1/tables/filter_tables.d.ts.map +1 -0
  206. package/src/avif/native/av1/tables/filter_tables.js +313 -0
  207. package/src/avif/native/av1/tables/grain_tables.d.ts +11 -0
  208. package/src/avif/native/av1/tables/grain_tables.d.ts.map +1 -0
  209. package/src/avif/native/av1/tables/grain_tables.js +190 -0
  210. package/src/avif/native/av1/tables/prediction_tables.d.ts +141 -0
  211. package/src/avif/native/av1/tables/prediction_tables.d.ts.map +1 -0
  212. package/src/avif/native/av1/tables/prediction_tables.js +223 -0
  213. package/src/avif/native/av1/tables/quantizer_matrix_tables.d.ts +21 -0
  214. package/src/avif/native/av1/tables/quantizer_matrix_tables.d.ts.map +1 -0
  215. package/src/avif/native/av1/tables/quantizer_matrix_tables.js +38 -0
  216. package/src/avif/native/av1/tables/quantizer_tables.d.ts +21 -0
  217. package/src/avif/native/av1/tables/quantizer_tables.d.ts.map +1 -0
  218. package/src/avif/native/av1/tables/quantizer_tables.js +160 -0
  219. package/src/avif/native/av1/tables/scan_tables.d.ts +321 -0
  220. package/src/avif/native/av1/tables/scan_tables.d.ts.map +1 -0
  221. package/src/avif/native/av1/tables/scan_tables.js +727 -0
  222. package/src/avif/native/av1/tables/segmentation_tables.d.ts +31 -0
  223. package/src/avif/native/av1/tables/segmentation_tables.d.ts.map +1 -0
  224. package/src/avif/native/av1/tables/segmentation_tables.js +48 -0
  225. package/src/avif/native/av1/tables/transform_tables.d.ts +91 -0
  226. package/src/avif/native/av1/tables/transform_tables.d.ts.map +1 -0
  227. package/src/avif/native/av1/tables/transform_tables.js +140 -0
  228. package/src/avif/native/av1/tables/unpack_table.d.ts +33 -0
  229. package/src/avif/native/av1/tables/unpack_table.d.ts.map +1 -0
  230. package/src/avif/native/av1/tables/unpack_table.js +73 -0
  231. package/src/avif/native/av1/transform/forward_transform_2d.d.ts +61 -0
  232. package/src/avif/native/av1/transform/forward_transform_2d.d.ts.map +1 -0
  233. package/src/avif/native/av1/transform/forward_transform_2d.js +574 -0
  234. package/src/avif/native/av1/transform/inverse_transform_1d.d.ts +76 -0
  235. package/src/avif/native/av1/transform/inverse_transform_1d.d.ts.map +1 -0
  236. package/src/avif/native/av1/transform/inverse_transform_1d.js +590 -0
  237. package/src/avif/native/av1/transform/inverse_transform_2d.d.ts +46 -0
  238. package/src/avif/native/av1/transform/inverse_transform_2d.d.ts.map +1 -0
  239. package/src/avif/native/av1/transform/inverse_transform_2d.js +180 -0
  240. package/src/avif/native/av1/util/ceil_log2.d.ts +13 -0
  241. package/src/avif/native/av1/util/ceil_log2.d.ts.map +1 -0
  242. package/src/avif/native/av1/util/ceil_log2.js +18 -0
  243. package/src/avif/native/av1/util/floor_log2.d.ts +20 -0
  244. package/src/avif/native/av1/util/floor_log2.d.ts.map +1 -0
  245. package/src/avif/native/av1/util/floor_log2.js +20 -0
  246. package/src/avif/native/av1/util/round2.d.ts +22 -0
  247. package/src/avif/native/av1/util/round2.d.ts.map +1 -0
  248. package/src/avif/native/av1/util/round2.js +27 -0
  249. package/src/avif/native/av1/util/round2_signed.d.ts +14 -0
  250. package/src/avif/native/av1/util/round2_signed.d.ts.map +1 -0
  251. package/src/avif/native/av1/util/round2_signed.js +17 -0
  252. package/src/avif/native/av1/util/tile_log2.d.ts +16 -0
  253. package/src/avif/native/av1/util/tile_log2.d.ts.map +1 -0
  254. package/src/avif/native/av1/util/tile_log2.js +23 -0
  255. package/src/avif/native/bitstream/BitReader.d.ts +174 -0
  256. package/src/avif/native/bitstream/BitReader.d.ts.map +1 -0
  257. package/src/avif/native/bitstream/BitReader.js +391 -0
  258. package/src/avif/native/bitstream/BitWriter.d.ts +122 -0
  259. package/src/avif/native/bitstream/BitWriter.d.ts.map +1 -0
  260. package/src/avif/native/bitstream/BitWriter.js +351 -0
  261. package/src/avif/native/color/ColourTransform.d.ts +62 -0
  262. package/src/avif/native/color/ColourTransform.d.ts.map +1 -0
  263. package/src/avif/native/color/ColourTransform.js +189 -0
  264. package/src/avif/native/color/YuvImage.d.ts +51 -0
  265. package/src/avif/native/color/YuvImage.d.ts.map +1 -0
  266. package/src/avif/native/color/YuvImage.js +66 -0
  267. package/src/avif/native/color/clamp_sample.d.ts +18 -0
  268. package/src/avif/native/color/clamp_sample.d.ts.map +1 -0
  269. package/src/avif/native/color/clamp_sample.js +25 -0
  270. package/src/avif/native/color/linear_to_transfer.d.ts +24 -0
  271. package/src/avif/native/color/linear_to_transfer.d.ts.map +1 -0
  272. package/src/avif/native/color/linear_to_transfer.js +126 -0
  273. package/src/avif/native/color/primaries.d.ts +20 -0
  274. package/src/avif/native/color/primaries.d.ts.map +1 -0
  275. package/src/avif/native/color/primaries.js +94 -0
  276. package/src/avif/native/color/rgb_row_to_ycbcr.d.ts +20 -0
  277. package/src/avif/native/color/rgb_row_to_ycbcr.d.ts.map +1 -0
  278. package/src/avif/native/color/rgb_row_to_ycbcr.js +78 -0
  279. package/src/avif/native/color/transfer_to_linear.d.ts +49 -0
  280. package/src/avif/native/color/transfer_to_linear.d.ts.map +1 -0
  281. package/src/avif/native/color/transfer_to_linear.js +171 -0
  282. package/src/avif/native/color/upsample_chroma_row.d.ts +35 -0
  283. package/src/avif/native/color/upsample_chroma_row.d.ts.map +1 -0
  284. package/src/avif/native/color/upsample_chroma_row.js +89 -0
  285. package/src/avif/native/color/ycbcr_row_to_rgb.d.ts +23 -0
  286. package/src/avif/native/color/ycbcr_row_to_rgb.d.ts.map +1 -0
  287. package/src/avif/native/color/ycbcr_row_to_rgb.js +78 -0
  288. package/src/avif/native/heif/AvifFile.d.ts +112 -0
  289. package/src/avif/native/heif/AvifFile.d.ts.map +1 -0
  290. package/src/avif/native/heif/AvifFile.js +142 -0
  291. package/src/avif/native/heif/ItemProperty.d.ts +244 -0
  292. package/src/avif/native/heif/ItemProperty.d.ts.map +1 -0
  293. package/src/avif/native/heif/ItemProperty.js +321 -0
  294. package/src/avif/native/heif/find_item_property.d.ts +17 -0
  295. package/src/avif/native/heif/find_item_property.d.ts.map +1 -0
  296. package/src/avif/native/heif/find_item_property.js +28 -0
  297. package/src/avif/native/heif/find_item_references.d.ts +16 -0
  298. package/src/avif/native/heif/find_item_references.d.ts.map +1 -0
  299. package/src/avif/native/heif/find_item_references.js +29 -0
  300. package/src/avif/native/heif/find_items_referencing.d.ts +17 -0
  301. package/src/avif/native/heif/find_items_referencing.d.ts.map +1 -0
  302. package/src/avif/native/heif/find_items_referencing.js +36 -0
  303. package/src/avif/native/heif/parse_avif_file.d.ts +15 -0
  304. package/src/avif/native/heif/parse_avif_file.d.ts.map +1 -0
  305. package/src/avif/native/heif/parse_avif_file.js +383 -0
  306. package/src/avif/native/heif/parse_image_grid.d.ts +18 -0
  307. package/src/avif/native/heif/parse_image_grid.d.ts.map +1 -0
  308. package/src/avif/native/heif/parse_image_grid.js +48 -0
  309. package/src/avif/native/heif/parse_item_property.d.ts +18 -0
  310. package/src/avif/native/heif/parse_item_property.d.ts.map +1 -0
  311. package/src/avif/native/heif/parse_item_property.js +243 -0
  312. package/src/avif/native/heif/read_item_data.d.ts +22 -0
  313. package/src/avif/native/heif/read_item_data.d.ts.map +1 -0
  314. package/src/avif/native/heif/read_item_data.js +97 -0
  315. package/src/avif/native/heif/write_avif_file.d.ts +50 -0
  316. package/src/avif/native/heif/write_avif_file.d.ts.map +1 -0
  317. package/src/avif/native/heif/write_avif_file.js +348 -0
  318. package/src/avif/native/heif/write_item_property.d.ts +16 -0
  319. package/src/avif/native/heif/write_item_property.d.ts.map +1 -0
  320. package/src/avif/native/heif/write_item_property.js +182 -0
  321. package/src/avif/native/index.d.ts +14 -0
  322. package/src/avif/native/index.d.ts.map +1 -0
  323. package/src/avif/native/index.js +56 -0
  324. package/src/avif/native/isobmff/BoxHeader.d.ts +50 -0
  325. package/src/avif/native/isobmff/BoxHeader.d.ts.map +1 -0
  326. package/src/avif/native/isobmff/BoxHeader.js +57 -0
  327. package/src/avif/native/isobmff/BoxWriter.d.ts +118 -0
  328. package/src/avif/native/isobmff/BoxWriter.d.ts.map +1 -0
  329. package/src/avif/native/isobmff/BoxWriter.js +264 -0
  330. package/src/avif/native/isobmff/ByteCursor.d.ts +125 -0
  331. package/src/avif/native/isobmff/ByteCursor.d.ts.map +1 -0
  332. package/src/avif/native/isobmff/ByteCursor.js +298 -0
  333. package/src/avif/native/isobmff/for_each_box.d.ts +23 -0
  334. package/src/avif/native/isobmff/for_each_box.d.ts.map +1 -0
  335. package/src/avif/native/isobmff/for_each_box.js +39 -0
  336. package/src/avif/native/isobmff/read_box_header.d.ts +22 -0
  337. package/src/avif/native/isobmff/read_box_header.d.ts.map +1 -0
  338. package/src/avif/native/isobmff/read_box_header.js +59 -0
  339. package/src/avif/native/isobmff/read_full_box_header.d.ts +26 -0
  340. package/src/avif/native/isobmff/read_full_box_header.d.ts.map +1 -0
  341. package/src/avif/native/isobmff/read_full_box_header.js +33 -0
  342. package/src/avif/threaded_image_encoder.d.ts +7 -2
  343. package/src/avif/threaded_image_encoder.d.ts.map +1 -1
  344. package/src/avif/threaded_image_encoder.js +107 -21
  345. package/src/core/binary/BinaryBuffer.d.ts +40 -0
  346. package/src/core/binary/BinaryBuffer.d.ts.map +1 -1
  347. package/src/core/binary/BinaryBuffer.js +100 -0
  348. package/src/core/binary/hash/crc32.d.ts +16 -0
  349. package/src/core/binary/hash/crc32.d.ts.map +1 -0
  350. package/src/core/binary/hash/crc32.js +96 -0
  351. package/src/core/binary/utf8/utf8_decode.d.ts +21 -0
  352. package/src/core/binary/utf8/utf8_decode.d.ts.map +1 -0
  353. package/src/core/binary/utf8/utf8_decode.js +43 -0
  354. package/src/core/binary/utf8/utf8_encode_into.d.ts +20 -0
  355. package/src/core/binary/utf8/utf8_encode_into.d.ts.map +1 -0
  356. package/src/core/binary/utf8/utf8_encode_into.js +48 -0
  357. package/src/core/binary/utf8/utf8_encoded_length.d.ts +14 -0
  358. package/src/core/binary/utf8/utf8_encoded_length.d.ts.map +1 -0
  359. package/src/core/binary/utf8/utf8_encoded_length.js +36 -0
  360. package/src/core/color/COLOR_FIX_PLAN_2026_08_28.md +417 -0
  361. package/src/core/color/COLOR_REVIEW_2026_08_28.md +779 -0
  362. package/src/core/color/Color.d.ts +128 -20
  363. package/src/core/color/Color.d.ts.map +1 -1
  364. package/src/core/color/Color.js +959 -866
  365. package/src/core/color/PQ/PQ_constants.d.ts +10 -0
  366. package/src/core/color/PQ/PQ_constants.d.ts.map +1 -1
  367. package/src/core/color/PQ/PQ_constants.js +10 -0
  368. package/src/core/color/PQ/linear_to_PQ.d.ts.map +1 -1
  369. package/src/core/color/PQ/linear_to_PQ.js +6 -0
  370. package/src/core/color/REC709_PRIMARIES.d.ts +50 -0
  371. package/src/core/color/REC709_PRIMARIES.d.ts.map +1 -0
  372. package/src/core/color/REC709_PRIMARIES.js +53 -0
  373. package/src/core/color/construct/color_from_hex.d.ts +21 -0
  374. package/src/core/color/construct/color_from_hex.d.ts.map +1 -0
  375. package/src/core/color/construct/color_from_hex.js +33 -0
  376. package/src/core/color/construct/color_from_temperature.d.ts +22 -0
  377. package/src/core/color/construct/color_from_temperature.d.ts.map +1 -0
  378. package/src/core/color/construct/color_from_temperature.js +33 -0
  379. package/src/core/color/construct/color_from_uint24.d.ts +19 -0
  380. package/src/core/color/construct/color_from_uint24.d.ts.map +1 -0
  381. package/src/core/color/construct/color_from_uint24.js +26 -0
  382. package/src/core/color/construct/color_from_uint32.d.ts +20 -0
  383. package/src/core/color/construct/color_from_uint32.d.ts.map +1 -0
  384. package/src/core/color/construct/color_from_uint32.js +39 -0
  385. package/src/core/color/construct/color_gray.d.ts +17 -0
  386. package/src/core/color/construct/color_gray.d.ts.map +1 -0
  387. package/src/core/color/construct/color_gray.js +22 -0
  388. package/src/core/color/hex/hex2rgb.d.ts +1 -11
  389. package/src/core/color/hex/hex2rgb.d.ts.map +1 -1
  390. package/src/core/color/hex/hex2rgb.js +7 -24
  391. package/src/core/color/hex/hex_to_rgb.d.ts +18 -0
  392. package/src/core/color/hex/hex_to_rgb.d.ts.map +1 -0
  393. package/src/core/color/hex/hex_to_rgb.js +62 -0
  394. package/src/core/color/hex/rgb2hex.d.ts +1 -8
  395. package/src/core/color/hex/rgb2hex.d.ts.map +1 -1
  396. package/src/core/color/hex/rgb2hex.js +7 -13
  397. package/src/core/color/hex/rgb_to_hex.d.ts +9 -0
  398. package/src/core/color/hex/rgb_to_hex.d.ts.map +1 -0
  399. package/src/core/color/hex/rgb_to_hex.js +13 -0
  400. package/src/core/color/hex/rgba_to_hex.d.ts +19 -0
  401. package/src/core/color/hex/rgba_to_hex.d.ts.map +1 -0
  402. package/src/core/color/hex/rgba_to_hex.js +23 -0
  403. package/src/core/color/hsv/hsv2rgb.d.ts +1 -12
  404. package/src/core/color/hsv/hsv2rgb.d.ts.map +1 -1
  405. package/src/core/color/hsv/hsv2rgb.js +8 -21
  406. package/src/core/color/hsv/hsv2rgb_float.d.ts +1 -12
  407. package/src/core/color/hsv/hsv2rgb_float.d.ts.map +1 -1
  408. package/src/core/color/hsv/hsv2rgb_float.js +8 -56
  409. package/src/core/color/hsv/hsv_to_rgb.d.ts +13 -0
  410. package/src/core/color/hsv/hsv_to_rgb.d.ts.map +1 -0
  411. package/src/core/color/hsv/hsv_to_rgb.js +39 -0
  412. package/src/core/color/hsv/hsv_to_rgb_uint8.d.ts +13 -0
  413. package/src/core/color/hsv/hsv_to_rgb_uint8.d.ts.map +1 -0
  414. package/src/core/color/hsv/hsv_to_rgb_uint8.js +21 -0
  415. package/src/core/color/hsv/hue_to_rgb_sextant.d.ts +24 -0
  416. package/src/core/color/hsv/hue_to_rgb_sextant.d.ts.map +1 -0
  417. package/src/core/color/hsv/hue_to_rgb_sextant.js +67 -0
  418. package/src/core/color/hsv/rgb2hsv.d.ts +1 -12
  419. package/src/core/color/hsv/rgb2hsv.d.ts.map +1 -1
  420. package/src/core/color/hsv/rgb2hsv.js +7 -42
  421. package/src/core/color/hsv/rgb_to_hsv.d.ts +13 -0
  422. package/src/core/color/hsv/rgb_to_hsv.d.ts.map +1 -0
  423. package/src/core/color/hsv/rgb_to_hsv.js +42 -0
  424. package/src/core/color/hunt/hpe_to_xyz.d.ts +2 -0
  425. package/src/core/color/hunt/hpe_to_xyz.d.ts.map +1 -0
  426. package/src/core/color/hunt/hpe_to_xyz.js +4 -0
  427. package/src/core/color/hunt/xyz_to_hpe.d.ts +1 -6
  428. package/src/core/color/hunt/xyz_to_hpe.d.ts.map +1 -1
  429. package/src/core/color/hunt/xyz_to_hpe.js +12 -15
  430. package/src/core/color/illuminant/D65_spd_tabulated.d.ts.map +1 -1
  431. package/src/core/color/illuminant/D65_spd_tabulated.js +107 -104
  432. package/src/core/color/int2rgb.d.ts +1 -10
  433. package/src/core/color/int2rgb.d.ts.map +1 -1
  434. package/src/core/color/int2rgb.js +7 -12
  435. package/src/core/color/int_to_rgb.d.ts +21 -0
  436. package/src/core/color/int_to_rgb.d.ts.map +1 -0
  437. package/src/core/color/int_to_rgb.js +33 -0
  438. package/src/core/color/kelvin/kelvin_to_rgb.d.ts.map +1 -1
  439. package/src/core/color/kelvin/kelvin_to_rgb.js +22 -2
  440. package/src/core/color/kelvin/rgb_to_kelvin.d.ts +22 -2
  441. package/src/core/color/kelvin/rgb_to_kelvin.d.ts.map +1 -1
  442. package/src/core/color/kelvin/rgb_to_kelvin.js +22 -2
  443. package/src/core/color/lab/lab_to_xyz.d.ts +20 -0
  444. package/src/core/color/lab/lab_to_xyz.d.ts.map +1 -0
  445. package/src/core/color/lab/lab_to_xyz.js +69 -0
  446. package/src/core/color/oklab/gamut_clip_adaptive_l0.d.ts +34 -0
  447. package/src/core/color/oklab/gamut_clip_adaptive_l0.d.ts.map +1 -0
  448. package/src/core/color/oklab/gamut_clip_adaptive_l0.js +46 -0
  449. package/src/core/color/oklab/gamut_clip_at_l0.d.ts +33 -0
  450. package/src/core/color/oklab/gamut_clip_at_l0.d.ts.map +1 -0
  451. package/src/core/color/oklab/gamut_clip_at_l0.js +84 -0
  452. package/src/core/color/oklab/gamut_clip_preserve_chroma.d.ts +24 -0
  453. package/src/core/color/oklab/gamut_clip_preserve_chroma.d.ts.map +1 -0
  454. package/src/core/color/oklab/gamut_clip_preserve_chroma.js +37 -0
  455. package/src/core/color/oklab/gamut_clip_project_to_lcusp.d.ts +23 -0
  456. package/src/core/color/oklab/gamut_clip_project_to_lcusp.d.ts.map +1 -0
  457. package/src/core/color/oklab/gamut_clip_project_to_lcusp.js +46 -0
  458. package/src/core/color/oklab/linear_srgb_to_okhsl.d.ts +19 -0
  459. package/src/core/color/oklab/linear_srgb_to_okhsl.d.ts.map +1 -0
  460. package/src/core/color/oklab/linear_srgb_to_okhsl.js +93 -0
  461. package/src/core/color/oklab/linear_srgb_to_okhsv.d.ts +12 -4
  462. package/src/core/color/oklab/linear_srgb_to_okhsv.d.ts.map +1 -1
  463. package/src/core/color/oklab/linear_srgb_to_okhsv.js +108 -84
  464. package/src/core/color/oklab/linear_srgb_to_oklab.d.ts +13 -4
  465. package/src/core/color/oklab/linear_srgb_to_oklab.d.ts.map +1 -1
  466. package/src/core/color/oklab/linear_srgb_to_oklab.js +28 -19
  467. package/src/core/color/oklab/okhsl_chroma_bounds.d.ts +51 -0
  468. package/src/core/color/oklab/okhsl_chroma_bounds.d.ts.map +1 -0
  469. package/src/core/color/oklab/okhsl_chroma_bounds.js +128 -0
  470. package/src/core/color/oklab/okhsl_to_linear_srgb.d.ts +19 -0
  471. package/src/core/color/oklab/okhsl_to_linear_srgb.d.ts.map +1 -0
  472. package/src/core/color/oklab/okhsl_to_linear_srgb.js +85 -0
  473. package/src/core/color/oklab/oklab_to_oklch.d.ts +24 -0
  474. package/src/core/color/oklab/oklab_to_oklch.d.ts.map +1 -0
  475. package/src/core/color/oklab/oklab_to_oklch.js +35 -0
  476. package/src/core/color/oklab/oklch_to_oklab.d.ts +18 -0
  477. package/src/core/color/oklab/oklch_to_oklab.d.ts.map +1 -0
  478. package/src/core/color/oklab/oklch_to_oklab.js +26 -0
  479. package/src/core/color/oklab/toe.d.ts +12 -0
  480. package/src/core/color/oklab/toe.d.ts.map +1 -1
  481. package/src/core/color/oklab/toe.js +34 -22
  482. package/src/core/color/operations/color_add.d.ts +21 -0
  483. package/src/core/color/operations/color_add.d.ts.map +1 -0
  484. package/src/core/color/operations/color_add.js +23 -0
  485. package/src/core/color/operations/color_add_scaled.d.ts +20 -0
  486. package/src/core/color/operations/color_add_scaled.d.ts.map +1 -0
  487. package/src/core/color/operations/color_add_scaled.js +30 -0
  488. package/src/core/color/operations/color_clamp.d.ts +22 -0
  489. package/src/core/color/operations/color_clamp.d.ts.map +1 -0
  490. package/src/core/color/operations/color_clamp.js +25 -0
  491. package/src/core/color/operations/color_darken.d.ts +2 -1
  492. package/src/core/color/operations/color_darken.d.ts.map +1 -1
  493. package/src/core/color/operations/color_darken.js +25 -24
  494. package/src/core/color/operations/color_desaturate.d.ts +2 -1
  495. package/src/core/color/operations/color_desaturate.d.ts.map +1 -1
  496. package/src/core/color/operations/color_desaturate.js +25 -24
  497. package/src/core/color/operations/color_get_hsl.d.ts +18 -0
  498. package/src/core/color/operations/color_get_hsl.d.ts.map +1 -0
  499. package/src/core/color/operations/color_get_hsl.js +60 -0
  500. package/src/core/color/operations/color_is_in_gamut.d.ts +20 -0
  501. package/src/core/color/operations/color_is_in_gamut.d.ts.map +1 -0
  502. package/src/core/color/operations/color_is_in_gamut.js +30 -0
  503. package/src/core/color/operations/color_lerp.d.ts +12 -3
  504. package/src/core/color/operations/color_lerp.d.ts.map +1 -1
  505. package/src/core/color/operations/color_lerp.js +55 -46
  506. package/src/core/color/operations/color_lighten.d.ts +9 -5
  507. package/src/core/color/operations/color_lighten.d.ts.map +1 -1
  508. package/src/core/color/operations/color_lighten.js +29 -24
  509. package/src/core/color/operations/color_mix_okhsv_channel.d.ts +24 -0
  510. package/src/core/color/operations/color_mix_okhsv_channel.d.ts.map +1 -0
  511. package/src/core/color/operations/color_mix_okhsv_channel.js +48 -0
  512. package/src/core/color/operations/color_multiply_rgb.d.ts +21 -0
  513. package/src/core/color/operations/color_multiply_rgb.d.ts.map +1 -0
  514. package/src/core/color/operations/color_multiply_rgb.js +26 -0
  515. package/src/core/color/operations/color_saturate.d.ts +9 -5
  516. package/src/core/color/operations/color_saturate.d.ts.map +1 -1
  517. package/src/core/color/operations/color_saturate.js +29 -24
  518. package/src/core/color/operations/color_scale_okhsv_channel.d.ts +1 -1
  519. package/src/core/color/operations/color_scale_okhsv_channel.d.ts.map +1 -1
  520. package/src/core/color/operations/color_scale_okhsv_channel.js +58 -45
  521. package/src/core/color/operations/color_srgb_apply.d.ts +28 -0
  522. package/src/core/color/operations/color_srgb_apply.d.ts.map +1 -0
  523. package/src/core/color/operations/color_srgb_apply.js +40 -0
  524. package/src/core/color/operations/color_sub.d.ts +16 -0
  525. package/src/core/color/operations/color_sub.d.ts.map +1 -0
  526. package/src/core/color/operations/color_sub.js +18 -0
  527. package/src/core/color/parse_color.d.ts +10 -1
  528. package/src/core/color/parse_color.d.ts.map +1 -1
  529. package/src/core/color/parse_color.js +100 -84
  530. package/src/core/color/parse_color_normalized.d.ts +24 -0
  531. package/src/core/color/parse_color_normalized.d.ts.map +1 -0
  532. package/src/core/color/parse_color_normalized.js +38 -0
  533. package/src/core/color/rgb2uint24.d.ts +1 -8
  534. package/src/core/color/rgb2uint24.d.ts.map +1 -1
  535. package/src/core/color/rgb2uint24.js +7 -20
  536. package/src/core/color/rgb2uint32.d.ts +1 -9
  537. package/src/core/color/rgb2uint32.d.ts.map +1 -1
  538. package/src/core/color/rgb2uint32.js +7 -18
  539. package/src/core/color/rgb_to_luminance.d.ts +14 -5
  540. package/src/core/color/rgb_to_luminance.d.ts.map +1 -1
  541. package/src/core/color/rgb_to_luminance.js +24 -13
  542. package/src/core/color/rgb_to_uint24.d.ts +9 -0
  543. package/src/core/color/rgb_to_uint24.d.ts.map +1 -0
  544. package/src/core/color/rgb_to_uint24.js +20 -0
  545. package/src/core/color/rgb_to_uint32.d.ts +10 -0
  546. package/src/core/color/rgb_to_uint32.d.ts.map +1 -0
  547. package/src/core/color/rgb_to_uint32.js +21 -0
  548. package/src/core/color/sRGB/linear_to_sRGB.d.ts +7 -0
  549. package/src/core/color/sRGB/linear_to_sRGB.d.ts.map +1 -1
  550. package/src/core/color/sRGB/linear_to_sRGB.js +39 -32
  551. package/src/core/color/xyz/hpe_to_xyz.d.ts +24 -0
  552. package/src/core/color/xyz/hpe_to_xyz.d.ts.map +1 -0
  553. package/src/core/color/xyz/hpe_to_xyz.js +39 -0
  554. package/src/core/color/xyz/rgb_to_xyz.d.ts +17 -2
  555. package/src/core/color/xyz/rgb_to_xyz.d.ts.map +1 -1
  556. package/src/core/color/xyz/rgb_to_xyz.js +44 -18
  557. package/src/core/color/xyz/xyz_cmf_tabulated.d.ts.map +1 -1
  558. package/src/core/color/xyz/xyz_cmf_tabulated.js +217 -211
  559. package/src/core/color/xyz/xyz_cmf_wyman.d.ts +4 -0
  560. package/src/core/color/xyz/xyz_cmf_wyman.d.ts.map +1 -1
  561. package/src/core/color/xyz/xyz_cmf_wyman.js +74 -65
  562. package/src/core/color/xyz/xyz_to_hpe.d.ts +18 -0
  563. package/src/core/color/xyz/xyz_to_hpe.d.ts.map +1 -0
  564. package/src/core/color/xyz/xyz_to_hpe.js +33 -0
  565. package/src/core/color/xyz/xyz_to_rgb.d.ts +17 -2
  566. package/src/core/color/xyz/xyz_to_rgb.d.ts.map +1 -1
  567. package/src/core/color/xyz/xyz_to_rgb.js +39 -16
  568. package/src/core/color/ycxcz/xyz_to_ycxcz.d.ts +7 -2
  569. package/src/core/color/ycxcz/xyz_to_ycxcz.d.ts.map +1 -1
  570. package/src/core/color/ycxcz/xyz_to_ycxcz.js +33 -28
  571. package/src/core/events/signal/Signal.js +820 -820
  572. package/src/core/math/clamp_index.d.ts +18 -0
  573. package/src/core/math/clamp_index.d.ts.map +1 -0
  574. package/src/core/math/clamp_index.js +23 -0
  575. package/src/engine/asset/loaders/image/IMAGE_CODEC_REVIEW_2026_08_28.md +803 -0
  576. package/src/engine/asset/loaders/image/jpeg/JpegFrame.d.ts +5 -3
  577. package/src/engine/asset/loaders/image/jpeg/JpegFrame.d.ts.map +1 -1
  578. package/src/engine/asset/loaders/image/jpeg/JpegFrame.js +5 -3
  579. package/src/engine/asset/loaders/image/jpeg/JpegFrameComponent.d.ts +2 -1
  580. package/src/engine/asset/loaders/image/jpeg/JpegFrameComponent.d.ts.map +1 -1
  581. package/src/engine/asset/loaders/image/jpeg/JpegFrameComponent.js +2 -1
  582. package/src/engine/asset/loaders/image/jpeg/JpegImage.d.ts +17 -0
  583. package/src/engine/asset/loaders/image/jpeg/JpegImage.d.ts.map +1 -1
  584. package/src/engine/asset/loaders/image/jpeg/JpegImage.js +197 -157
  585. package/src/engine/asset/loaders/image/jpeg/idct8x8_float.d.ts +0 -1
  586. package/src/engine/asset/loaders/image/jpeg/idct8x8_float.d.ts.map +1 -1
  587. package/src/engine/asset/loaders/image/jpeg/idct8x8_float.js +0 -1
  588. package/src/engine/asset/loaders/image/jpeg/jpeg_component_row.d.ts +28 -0
  589. package/src/engine/asset/loaders/image/jpeg/jpeg_component_row.d.ts.map +1 -0
  590. package/src/engine/asset/loaders/image/jpeg/jpeg_component_row.js +31 -0
  591. package/src/engine/asset/loaders/image/png/PNG.d.ts +3 -18
  592. package/src/engine/asset/loaders/image/png/PNG.d.ts.map +1 -1
  593. package/src/engine/asset/loaders/image/png/PNG.js +3 -139
  594. package/src/engine/asset/loaders/image/png/PNGReader.d.ts +16 -16
  595. package/src/engine/asset/loaders/image/png/PNGReader.d.ts.map +1 -1
  596. package/src/engine/asset/loaders/image/png/PNGReader.js +143 -160
  597. package/src/engine/asset/loaders/image/png/chunk/png_chunk_decode_iTXt.js +3 -3
  598. package/src/engine/asset/loaders/image/png/chunk/png_chunk_decode_zTXt.js +3 -3
  599. package/src/engine/asset/loaders/image/png/crc32.d.ts +1 -15
  600. package/src/engine/asset/loaders/image/png/crc32.d.ts.map +1 -1
  601. package/src/engine/asset/loaders/image/png/crc32.js +5 -89
  602. package/src/engine/asset/loaders/image/png/filter/png_filter_unFilterNone.d.ts +12 -10
  603. package/src/engine/asset/loaders/image/png/filter/png_filter_unFilterNone.d.ts.map +1 -1
  604. package/src/engine/asset/loaders/image/png/filter/png_filter_unFilterNone.js +20 -45
  605. package/src/engine/asset/loaders/image/png/filter/png_filter_unFilterSub.d.ts +2 -1
  606. package/src/engine/asset/loaders/image/png/filter/png_filter_unFilterSub.d.ts.map +1 -1
  607. package/src/engine/asset/loaders/image/png/filter/png_filter_unFilterSub.js +2 -0
  608. package/src/engine/asset/loaders/image/png/filter/png_filter_unFilterUp.d.ts +2 -1
  609. package/src/engine/asset/loaders/image/png/filter/png_filter_unFilterUp.d.ts.map +1 -1
  610. package/src/engine/asset/loaders/image/png/filter/png_filter_unFilterUp.js +2 -0
  611. package/src/engine/asset/loaders/image/png/filter/png_unfilter.d.ts +22 -0
  612. package/src/engine/asset/loaders/image/png/filter/png_unfilter.d.ts.map +1 -0
  613. package/src/engine/asset/loaders/image/png/filter/png_unfilter.js +68 -0
  614. package/src/engine/asset/loaders/image/png/png_inflate.d.ts +20 -0
  615. package/src/engine/asset/loaders/image/png/png_inflate.d.ts.map +1 -0
  616. package/src/engine/asset/loaders/image/png/png_inflate.js +85 -0
  617. package/src/engine/asset/loaders/image/png/png_unpack_samples.d.ts +22 -0
  618. package/src/engine/asset/loaders/image/png/png_unpack_samples.d.ts.map +1 -0
  619. package/src/engine/asset/loaders/image/png/png_unpack_samples.js +38 -0
  620. package/src/engine/graphics/ecs/sprite/SpriteSystemPE.d.ts.map +1 -1
  621. package/src/engine/graphics/ecs/sprite/SpriteSystemPE.js +135 -132
  622. package/src/engine/graphics/particles/particular/engine/emitter/ParticleEmitter.d.ts +16 -3
  623. package/src/engine/graphics/particles/particular/engine/emitter/ParticleEmitter.d.ts.map +1 -1
  624. package/src/engine/graphics/particles/particular/engine/emitter/ParticleEmitter.js +1157 -1149
  625. package/src/engine/graphics/render/frame_graph/FrameGraph.js +900 -900
  626. package/src/engine/physics/ecs/PhysicsSystem.d.ts +72 -81
  627. package/src/engine/physics/ecs/PhysicsSystem.d.ts.map +1 -1
  628. package/src/engine/physics/ecs/PhysicsSystem.js +86 -77
  629. package/src/engine/physics/fluid/ecs/FluidObstacleSystem.d.ts +4 -4
  630. package/src/shade/device/IMMEDIATE_DATA_PLAN_2026_08_28.md +451 -0
  631. package/src/shade/device/timing/GPU_PROFILER_PROPOSAL_2026_08_28.md +625 -0
  632. package/src/shade/renderer/postprocess/nss/NSS.d.ts +1 -1
  633. package/src/shade/renderer/scene/serialization/decode_image_source_hdr.d.ts +14 -3
  634. package/src/shade/renderer/scene/serialization/decode_image_source_hdr.d.ts.map +1 -1
  635. package/src/shade/renderer/scene/serialization/decode_image_source_hdr.js +50 -51
  636. package/src/shade/renderer/scene/serialization/deserialize_scene.d.ts.map +1 -1
  637. package/src/shade/renderer/scene/serialization/deserialize_scene.js +531 -521
  638. package/src/shade/renderer/scene/serialization/write_image_source.d.ts.map +1 -1
  639. package/src/shade/renderer/scene/serialization/write_image_source.js +6 -4
  640. package/src/shade/renderer/shader/chunk/color/chunk_rgb_to_luminance.d.ts.map +1 -1
  641. package/src/shade/renderer/shader/chunk/color/chunk_rgb_to_luminance.js +34 -32
  642. package/src/shade/renderer/shader/chunk/color/iCtCp/chunk_eotf_st2084.d.ts +31 -0
  643. package/src/shade/renderer/shader/chunk/color/iCtCp/chunk_eotf_st2084.d.ts.map +1 -1
  644. package/src/shade/renderer/shader/chunk/color/iCtCp/chunk_eotf_st2084.js +62 -31
  645. package/src/shade/renderer/shader/chunk/color/iCtCp/chunk_inverse_eotf_st2084.d.ts +10 -0
  646. package/src/shade/renderer/shader/chunk/color/iCtCp/chunk_inverse_eotf_st2084.d.ts.map +1 -1
  647. package/src/shade/renderer/shader/chunk/color/iCtCp/chunk_inverse_eotf_st2084.js +32 -22
  648. package/src/shade/renderer/shader/chunk/color/sRGB/chunk_sRGBTransferOETF.d.ts.map +1 -1
  649. package/src/shade/renderer/shader/chunk/color/sRGB/chunk_sRGBTransferOETF.js +22 -21
  650. package/src/shade/renderer/shader/chunk/color/tonemap/gt7/chunk_tonemap_gt7.d.ts +6 -0
  651. package/src/shade/renderer/shader/chunk/color/tonemap/gt7/chunk_tonemap_gt7.d.ts.map +1 -1
  652. package/src/shade/renderer/shader/chunk/color/tonemap/gt7/chunk_tonemap_gt7.js +110 -104
  653. package/src/shade/renderer/shader/chunk/color/xyz/chunk_rgb_to_xyz.d.ts.map +1 -1
  654. package/src/shade/renderer/shader/chunk/color/xyz/chunk_rgb_to_xyz.js +23 -21
  655. package/src/view/elements/ColorPickerView.js +296 -296
  656. package/src/avif/codec/dec/Readme.md +0 -14
  657. package/src/avif/codec/dec/avif_dec.cpp +0 -234
  658. package/src/avif/codec/dec/avif_dec.d.ts +0 -3
  659. package/src/avif/codec/dec/avif_dec.d.ts.map +0 -1
  660. package/src/avif/codec/dec/avif_dec.js +0 -16
  661. package/src/avif/codec/dec/avif_dec.wasm +0 -0
  662. package/src/avif/codec/enc/README.md +0 -37
  663. package/src/avif/codec/enc/avif_enc.cpp +0 -216
  664. package/src/avif/codec/enc/avif_enc.d.ts +0 -3
  665. package/src/avif/codec/enc/avif_enc.d.ts.map +0 -1
  666. package/src/avif/codec/enc/avif_enc.js +0 -6144
  667. package/src/avif/codec/enc/avif_enc.wasm +0 -0
  668. package/src/avif/codec/enc/avif_enc_mt.d.ts +0 -3
  669. package/src/avif/codec/enc/avif_enc_mt.d.ts.map +0 -1
  670. package/src/avif/codec/enc/avif_enc_mt.js +0 -6811
  671. package/src/avif/codec/enc/avif_enc_mt.wasm +0 -0
  672. package/src/avif/codec/enc/avif_enc_mt.worker.d.mts +0 -2
  673. package/src/avif/codec/enc/avif_enc_mt.worker.d.mts.map +0 -1
  674. package/src/avif/codec/enc/avif_enc_mt.worker.d.ts +0 -11
  675. package/src/avif/codec/enc/avif_enc_mt.worker.d.ts.map +0 -1
  676. package/src/avif/codec/enc/avif_enc_mt.worker.js +0 -171
  677. package/src/avif/codec/enc/avif_enc_mt.worker.mjs +0 -1
  678. package/src/avif/decode.d.ts +0 -3
  679. package/src/avif/decode.d.ts.map +0 -1
  680. package/src/avif/decode.js +0 -42
  681. package/src/avif/encode.d.ts +0 -3
  682. package/src/avif/encode.d.ts.map +0 -1
  683. package/src/avif/encode.js +0 -75
  684. package/src/avif/meta.d.ts +0 -24
  685. package/src/avif/meta.d.ts.map +0 -1
  686. package/src/avif/meta.js +0 -23
  687. package/src/avif/tsconfig.tsbuildinfo +0 -1
  688. package/src/avif/utils.d.ts +0 -17
  689. package/src/avif/utils.d.ts.map +0 -1
  690. package/src/avif/utils.js +0 -31
  691. package/src/engine/asset/loaders/image/jpeg/idct8x8_fixed.d.ts +0 -23
  692. package/src/engine/asset/loaders/image/jpeg/idct8x8_fixed.d.ts.map +0 -1
  693. package/src/engine/asset/loaders/image/jpeg/idct8x8_fixed.js +0 -214
  694. package/src/engine/asset/loaders/image/png/inflate.d.ts +0 -7
  695. package/src/engine/asset/loaders/image/png/inflate.d.ts.map +0 -1
  696. package/src/engine/asset/loaders/image/png/inflate.js +0 -20
@@ -0,0 +1,803 @@
1
+ # Image codecs — duplication and shared-construct review
2
+
3
+ Review, 2026-08-28. Alex Goldring / Company Named Limited.
4
+
5
+ Scope: `src/avif/native/`, `src/engine/asset/loaders/image/png/`,
6
+ `src/engine/asset/loaders/image/jpeg/`. Everything below is an itemised proposal; nothing has been
7
+ changed.
8
+
9
+ Placed here rather than under `src/avif/native/` because the majority of the *cross-tree* items land
10
+ on the loader side, and `src/avif/native/DECISIONS.md` is a decision log rather than a review.
11
+
12
+ ---
13
+
14
+ ## 0. Verdict
15
+
16
+ Three decoders, written at three different times against three different sets of conventions, doing
17
+ substantially the same job.
18
+
19
+ **The AVIF tree is the disciplined one.** It already has the abstractions the other two lack —
20
+ a bounds-checked cursor, a container walker, a parameterised colour transform, generated tables, a
21
+ decision log. Its own duplication is residue rather than structure: about **fifteen verbatim
22
+ copy-paste sites**, almost all of them the encode/decode mirror that
23
+ [D30](../../../../avif/native/DECISIONS.md) already set out to eliminate and did not quite finish.
24
+ Cheap to close, and closing it is the difference between a mirror that stays honest and one that
25
+ drifts.
26
+
27
+ **The cross-tree duplication is the expensive one, and it is not symmetrical.** PNG and JPEG each
28
+ reimplement, badly and privately, things AVIF already has generalised — channel expansion to RGBA,
29
+ YCbCr conversion, chroma upsampling, chunk walking, bounds checking. The single most valuable move
30
+ in this document is not a merge: it is **registering AVIF and JPEG behind the `Codec` interface that
31
+ already exists** ([Codec.js](codec/Codec.js)) so that all three formats reach the engine through one
32
+ door with one result shape. Today PNG goes through a worker, JPEG goes nowhere at all, and AVIF
33
+ bypasses the asset-loader system entirely via `avif_to_sampler2d`.
34
+
35
+ **One number worth stating up front:** `JpegImage.getData` hard-codes the coefficients `1.402`,
36
+ `-0.3441363`, `-0.71413636`, `1.772`. Those are *exactly* what
37
+ `colour_transform_init(t, MC_BT_601, ·, 1, 8)` computes
38
+ ([ColourTransform.js:98](../../../../avif/native/color/ColourTransform.js#L98)). The JPEG decoder is
39
+ a hand-inlined special case of a general transform that already exists two directories away.
40
+
41
+ To be clear about what that is and is not worth: JFIF is full-range BT.601, so those constants are
42
+ *correct* and JPEG has no need for the other matrices. The win is not new format coverage. It is
43
+ that the derivation `cr_to_r = 2(1 − kr)`, `cb_to_g = −2·kb(1 − kb)/kg`, … is computed once in
44
+ `ColourTransform` and hand-transcribed twice more — as BT.601 literals in `JpegImage`, as BT.709
45
+ literals in `core/color/YCbCr/` — and that adopting the row-based transform is what makes C4
46
+ possible.
47
+
48
+ **What not to do:** do not merge `ByteCursor` into `BinaryBuffer`, and do not try to fold
49
+ `encode_tile` into `decode_tile`. D10 and D30 already settled both, with reasons that still hold.
50
+ Section 6 says so explicitly so nobody re-litigates them from this document.
51
+
52
+ Priority ordering is in section 7.
53
+
54
+ ---
55
+
56
+ ## Status
57
+
58
+ Eleven items were selected and have landed. **File and line references below are as of the review
59
+ date and pre-date those changes** — the proposals for the remaining items still hold, but their
60
+ anchors have moved.
61
+
62
+ | Item | Commit | Departure from the proposal |
63
+ | --- | --- | --- |
64
+ | D1, D4 | `chore(image): the unreachable halves…` | none |
65
+ | P4 | `refactor(png): the reader keeps one cursor…` | also removed `this.bytes`, assigned and never read |
66
+ | J2, J4 | `refactor(jpeg): frame components become a Map…` | none |
67
+ | C7 | `refactor(binary): crc32 moves to core…` | none |
68
+ | C8 | `refactor(binary): one UTF-8 codec…` | three functions, not two: splitting `utf8_encoded_length` off is what lets `BoxWriter` grow once and encode straight into the buffer |
69
+ | A10 | `refactor(avif): the small numeric helpers…` | six inline `clz32` sites, not seven — see the correction in A10 |
70
+ | P2 | `refactor(png): one inflate path…` | no deprecation shim; the export changes name, arity and return type |
71
+ | P3 | `fix(png): bit depths of 1, 2 and 4 decode correctly…` | the suspected bug was real, and worse than described — see P3 |
72
+ | C4 | `refactor(jpeg): one row sampler…` | **`upsample_chroma_row` is not reusable here** — see the correction in C4 |
73
+
74
+ Not taken this round: C1, C2, C3, C5, C6, A1–A9, A11, A12, D2, D3, D5, D6, P1, P5, P6, J1, J3.
75
+ D5 and D6 came along with P4 and J2 respectively.
76
+
77
+ ---
78
+
79
+ ## 1. Cross-package items
80
+
81
+ These are the ones where a construct exists in one tree and is re-invented in another.
82
+
83
+ ### C1 — One `Codec` door for all three formats *(highest value)*
84
+
85
+ **Today.** Three formats, three unrelated entry conventions, three result shapes:
86
+
87
+ | Format | Entry point | Result shape |
88
+ | --- | --- | --- |
89
+ | PNG | worker → [ImageDecoderWorker.js:3](ImageDecoderWorker.js#L3) → `PNGReader` | `{data, width, height, itemSize, bitDepth}` |
90
+ | JPEG | [jpeg_decode.js:9](jpeg/jpeg_decode.js#L9) — **not reachable from any loader** | `{data, width, height, exifBuffer, comments?}` — no `itemSize`, no `bitDepth`, always 3-channel |
91
+ | AVIF | [avif_to_sampler2d.js:26](../../../../avif/native/api/avif_to_sampler2d.js#L26) — bypasses the loader entirely, called direct from `deserialize_scene` | `{data, width, height}` — no `itemSize`, no `bitDepth`, always RGBA |
92
+
93
+ `ImageDecoderWorker.decode` has a `switch (type)` with exactly one arm and a `default: throw new
94
+ Error('Unsupported type')`. `ThreadedImageDecoder.test` sniffs `PNG_HEADER_BYTES` and returns `false`
95
+ for everything else, so a JPEG or AVIF asset silently falls through to `NativeImageDecoder` — the
96
+ browser canvas path — which is why the native JPEG decoder has never needed to work.
97
+
98
+ **Proposal.**
99
+
100
+ 1. Define one result struct, `DecodedImage {data, width, height, itemSize, bitDepth, ...}`, next to
101
+ `Codec.js`. Fill it in from all three decoders. `exifBuffer` and `comments` become optional
102
+ fields rather than a shape difference.
103
+ 2. Add `PngCodec`, `JpegCodec`, `AvifCodec`, each a `Codec` subclass whose `test(data)` is a magic
104
+ sniff and whose `decode(data)` returns a `DecodedImage`. The sniffs already exist in three places
105
+ in three styles — `PNG_HEADER_BYTES` as an exported array, `0xFFD8` inline at
106
+ [JpegImage.js:139](jpeg/JpegImage.js#L139), and `ftyp`/`meta` presence at
107
+ [parse_avif_file.js:51](../../../../avif/native/heif/parse_avif_file.js#L51). Give each format a
108
+ `*_HEADER_BYTES` (or a `test` predicate) beside its decoder and have the codec use it.
109
+ 3. `CodecWithFallback` ([CodecWithFallback.js:11](codec/CodecWithFallback.js#L11)) already does
110
+ ordered try-with-sniff correctly. Compose `[Threaded(PNG), Jpeg, Avif, Native]` and the format
111
+ switch disappears from `ImageRGBADataLoader` and from `ImageDecoderWorker`.
112
+ 4. `avif_to_sampler2d` stays — it is the HDR-aware adapter and the exact inverse of
113
+ `sampler2d_to_avif` (D32) — but it becomes a thin wrapper over `AvifCodec` rather than a second
114
+ independent path into the decoder.
115
+
116
+ **Payoff.** JPEG becomes reachable. AVIF becomes loadable as an asset. The worker learns a second
117
+ format for free. One place decides what "a decoded image" is.
118
+
119
+ **Risk.** Low. `CodecWithFallback` and `Codec` already exist and are spec-covered; this is
120
+ population, not new machinery.
121
+
122
+ ---
123
+
124
+ ### C2 — Channel expansion to RGBA is written four times
125
+
126
+ Same job — take interleaved samples with `itemSize` channels and produce RGBA — in four places:
127
+
128
+ - [PNG.js:303](png/PNG.js#L303) `getRGBA8Array` + `getRGBA8Array_fromRGB` +
129
+ `getRGBA8Array_generic` + `getPixel` — **all four dead**, see D1 below
130
+ - [PNG.js:325](png/PNG.js#L325) `getUint8Data_case3`, palette expansion with the `tRNS` alpha rule
131
+ - [JpegImage.js:561](jpeg/JpegImage.js#L561) `copyToImageData`, one loop per component count with
132
+ `formatAsRGBA` deciding whether to emit the fourth channel
133
+ - [sampler2d_to_avif.js:68](../../../../avif/native/api/sampler2d_to_avif.js#L68) `to_rgba`, the
134
+ cleanest of the four and the only one that handles half-float input
135
+
136
+ And the engine already has a fifth: [sampler2d_to_uint8_RGBA.js](../../../graphics/texture/sampler/sampler2d_to_uint8_RGBA.js),
137
+ wrapped by `sampler2d_ensure_uint8_RGBA`.
138
+
139
+ **Proposal.** One free function in `core/` or beside `Codec.js`:
140
+
141
+ ```js
142
+ expand_to_rgba(source, item_size, count, destination, opaque_value)
143
+ ```
144
+
145
+ with the three input families (`Uint8`, `Uint16` half-float, `Float32`) selected by the caller, not
146
+ sniffed. `to_rgba`'s structure is the right one to generalise from: it is the only one that already
147
+ handles `item_size < 3` by zero-filling and `item_size < 4` by writing opaque.
148
+
149
+ Palette expansion (`getUint8Data_case3`) stays PNG-specific — a palette lookup is not a channel
150
+ expansion — but it should call the shared function for the final write rather than open-coding the
151
+ `destination_address + 0..3` stores.
152
+
153
+ **Payoff.** Removes ~120 lines of dead code plus ~150 lines of live duplication, and makes the
154
+ "what fills alpha when the source has none" rule a single decision instead of four (today: `255`,
155
+ `255`, `255`, and `1` for float — all correct, but nothing says so in one place).
156
+
157
+ ---
158
+
159
+ ### C3 — YCbCr → RGB exists three times, with three different capability levels
160
+
161
+ | Where | Matrix | Range | Depth | Parameterised? |
162
+ | --- | --- | --- | --- | --- |
163
+ | [ycbcr_row_to_rgb.js:24](../../../../avif/native/color/ycbcr_row_to_rgb.js#L24) | any `MC_*`, plus identity and YCgCo | full or limited | 8/10/12 | yes, via `ColourTransform` |
164
+ | [JpegImage.js:491](jpeg/JpegImage.js#L491) | BT.601 only, hard-coded | full only | 8 only | no |
165
+ | [YCbCr_to_rgb_uint24.js](../../../../core/color/YCbCr/YCbCr_to_rgb_uint24.js) | BT.709 only, hard-coded | full only | 8 only | no |
166
+
167
+ The JPEG constants are not "close to" BT.601 — they *are* `colour_transform_init` with
168
+ `MC_BT_601`, `color_range = 1`, `bit_depth = 8`. `cr_to_r = 2(1 − 0.299) = 1.402`,
169
+ `cb_to_b = 2(1 − 0.114) = 1.772`, `cb_to_g = −2·0.114·0.886/0.587 = −0.3441363`,
170
+ `cr_to_g = −2·0.299·0.701/0.587 = −0.71413636`. Digit for digit.
171
+
172
+ The same holds for YCgCo. `rgb_row_to_ycbcr`'s `CONVERSION_YCGCO` branch
173
+ ([rgb_row_to_ycbcr.js:40](../../../../avif/native/color/rgb_row_to_ycbcr.js#L40)) computes
174
+ `0.25r + 0.5g + 0.25b`, `−0.25r + 0.5g − 0.25b`, `0.5r − 0.5b` — which is
175
+ [rgb_to_ycocg.js](../../../../core/color/YCoCg/rgb_to_ycocg.js) verbatim, with Cg and Co swapped in
176
+ the output order. Its inverse likewise reproduces `ycocg_to_rgb`.
177
+
178
+ **Proposal.** Promote `ColourTransform` + `colour_transform_init` + the two row functions out of
179
+ `src/avif/native/color/` into `src/core/color/`. They have no AV1 dependency beyond the `MC_*` /
180
+ `CP_*` symbol constants, which should move with them (they are CICP codes from ITU-T H.273, not AV1
181
+ inventions — the AVIF tree just happens to be where they were first needed).
182
+
183
+ Then:
184
+
185
+ - `JpegImage.getData` builds a `ColourTransform` once per image from `adobe.transformCode` /
186
+ component count and calls `ycbcr_row_to_rgb` per row. The arithmetic is unchanged — JFIF is
187
+ full-range BT.601 and that is exactly what `MC_BT_601` with `color_range = 1` produces — so this
188
+ is deduplication, not a behaviour change. What it buys is the row-based shape that C4 needs, and
189
+ one fewer transcription of the coefficients.
190
+ - `YCbCr_to_rgb_uint24` / `rgb_to_YCbCr_uint24` keep their packed-uint24 signature but derive their
191
+ coefficients from a module-level `ColourTransform` initialised with `MC_BT_709`, so the constants
192
+ stop being transcribed.
193
+ - The `CONVERSION_YCGCO` branches delegate to `rgb_to_ycocg` / `ycocg_to_rgb`.
194
+
195
+ **Risk.** Medium. This moves live colour code. `color.spec.js` and the AVIF fixture round-trips cover
196
+ the AVIF side; the JPEG side is a bit-exactness question — the current code computes in `number` with
197
+ those literals, the shared path computes the same coefficients from `kr`/`kb`, and the two can differ
198
+ in the last ulp before rounding. Extend `jpeg_decode.spec.js` with a per-pixel comparison against the
199
+ current output (tolerance 0, then 1) before it lands. The JPEG path is currently unreachable (C1), so
200
+ the exposure while that is settled is nil.
201
+
202
+ ---
203
+
204
+ ### C4 — Chroma upsampling is written twice, one of them by accident
205
+
206
+ [upsample_chroma_row.js:36](../../../../avif/native/color/upsample_chroma_row.js#L36) has nearest and
207
+ bilinear modes, edge clamping, and honours `subsampling_x` / `subsampling_y` independently.
208
+
209
+ `JpegImage.getData` does the same thing implicitly, in every one of its four `switch` arms: the
210
+ expression `0 | (· * componentN.scale· · ·)` appears **27 times** in that one function (17 on the
211
+ column axis, 10 on the row axis). That is
212
+ nearest-neighbour upsampling combined with output rescaling, inlined per component per arm. It is
213
+ also why JPEG 4:2:0 chroma is visibly blockier than AVIF 4:2:0 chroma at the same subsampling.
214
+
215
+ **Proposal.** Have the JPEG component loop upsample each component to a full-width row, then run
216
+ one interleave loop instead of four near-identical ones. `getData`'s four cases collapse to: build
217
+ N full-width rows, then one component-count-dependent combine.
218
+
219
+ **Correction, from doing it.** The row sampler could *not* be `upsample_chroma_row`.
220
+ `upsample_chroma_row` expresses subsampling as a shift — `sub_x` and `sub_y` are 0 or 1 — so it can
221
+ say "half" and not "a third", while JPEG's `h` and `v` sampling factors run from one to four and
222
+ `getData` also rescales to an arbitrary requested output size. Generalising it to rational scaling
223
+ would have changed the AVIF decoder's per-row path for no AVIF benefit. The sampler lives in
224
+ `jpeg/jpeg_component_row.js` instead, with that reasoning on it, and stays nearest-neighbour: the
225
+ four arms were nearest-neighbour, and interpolating would change every decoded picture.
226
+
227
+ So the shared-with-AVIF half of this item is withdrawn. The deduplication half stands and was worth
228
+ it on its own.
229
+
230
+ **Payoff, realised.** `getData` drops from ~140 lines to ~55 across three functions, the twenty-seven
231
+ sampling expressions become one, and the colour-transform decision — spelt out twice with a
232
+ different default each time — becomes one method. Verified byte-for-byte against the previous
233
+ implementation over 300+ combinations of sampling ratio, output size, component count, Adobe marker
234
+ and option state.
235
+
236
+ ---
237
+
238
+ ### C5 — A container walker for length-prefixed records
239
+
240
+ The AVIF tree has this construct twice, correctly, in two variants:
241
+
242
+ - [for_each_box.js:24](../../../../avif/native/isobmff/for_each_box.js#L24) — reusable header
243
+ instance, visitor returns `false` to stop, cursor repositioned to the next record regardless of how
244
+ much the visitor consumed
245
+ - [for_each_obu.js:25](../../../../avif/native/av1/obu/for_each_obu.js#L25) — same contract, two
246
+ reusable readers
247
+
248
+ PNG has the same construct, open-coded and split across two methods:
249
+ [PNGReader.decodeChunk:151](png/PNGReader.js#L151) reads `length / type / payload / crc` and then
250
+ `switch`es on type in the same function, and [PNGReader.parse:654](png/PNGReader.js#L654) is the
251
+ `for(;;)` that drives it and detects `IEND`. JPEG has it as a 200-line `switch` inside
252
+ `JpegImage.parse` with `offset` mutated by nested closures.
253
+
254
+ **Proposal.** Two separable pieces, and the second matters more than the first:
255
+
256
+ 1. `for_each_png_chunk(cursor, visit)` in the PNG folder, mirroring `for_each_box`'s contract
257
+ exactly — reusable `PngChunkHeader {type, offset, payload_offset, payload_size, crc}`, visitor
258
+ returns `false` to stop. `decodeChunk` becomes a visitor with a `switch` and nothing else; the
259
+ CRC check moves into the walker where it belongs.
260
+ 2. **Separate parsing from dispatch in JPEG.** `JpegImage.parse` currently reads the segment,
261
+ interprets it, and mutates decoder state in one 250-line function with four closures
262
+ (`readUint16`, `readDataBlock`, `prepareComponents`) capturing `offset`. Split it into
263
+ `for_each_jpeg_segment(data, visit)` plus one handler per marker family. The malformed-APP1
264
+ recovery ([JpegImage.js:368](jpeg/JpegImage.js#L368)) and the eaten-`0xFF` recovery belong to the
265
+ walker, not to the `default:` arm of a dispatch switch.
266
+
267
+ I am **not** proposing one generic TLV walker across all three formats. The record headers differ
268
+ too much (ISOBMFF has three size encodings and a `uuid` case; PNG has a trailing CRC; JPEG has
269
+ stuffed bytes and standalone markers). What should be shared is the *contract* — reusable header
270
+ struct, visitor returns `false`, walker guarantees forward progress — so that the three read alike.
271
+
272
+ ---
273
+
274
+ ### C6 — Bounds checking exists on one side of the fence only
275
+
276
+ `ByteCursor.require(count, what)` ([ByteCursor.js:96](../../../../avif/native/isobmff/ByteCursor.js#L96))
277
+ validates every read and names the field in the error. `BitReader` does the same for every
278
+ descriptor. There is a `robustness.spec.js` that feeds the decoder truncated and hostile files.
279
+
280
+ PNG reads through two unchecked private helpers
281
+ ([PNGReader.js:28](png/PNGReader.js#L28), [PNGReader.js:42](png/PNGReader.js#L42)):
282
+
283
+ ```js
284
+ function readUInt32(buffer, offset) {
285
+ return (buffer[offset] << 24) | (buffer[offset+1] << 16) | (buffer[offset+2] << 8) | buffer[offset+3];
286
+ }
287
+ ```
288
+
289
+ Past the end this yields `NaN`-free garbage — `undefined << 24` is `0` — so a truncated `IHDR`
290
+ produces a plausible width and height rather than an error. JPEG reads `data[offset++]` throughout
291
+ with no checks at all.
292
+
293
+ Both of those helpers are also **already redundant with the `BinaryBuffer` the same constructor
294
+ built**: `PNGReader` creates `this.buffer` with `endianness = BigEndian`
295
+ ([PNGReader.js:83](png/PNGReader.js#L83)) and then reads chunk payloads with the unchecked free
296
+ functions anyway. `buffer.readUint32()` / `readUint8()` were there the whole time.
297
+
298
+ **Proposal.** Delete `readUInt32` and `readUInt8` from `PNGReader.js`; read chunk payloads through a
299
+ `BinaryBuffer` positioned on the chunk (or through the `for_each_png_chunk` header from C5). Add a
300
+ `robustness.spec.js` to the PNG and JPEG folders modelled on the AVIF one — truncated header,
301
+ truncated chunk, chunk length exceeding the file, garbage after `IEND`.
302
+
303
+ **Payoff.** Removes two functions and closes the class of failure where a malformed image decodes to
304
+ a plausible-looking wrong picture instead of throwing.
305
+
306
+ ---
307
+
308
+ ### C7 — `crc32` is a general utility living in the PNG folder
309
+
310
+ [crc32.js](png/crc32.js) is a complete, spec-cited, tested CRC-32/ISO-3309 implementation with an
311
+ `(buffer, offset, length)` signature. It sits under `png/` and is imported by exactly one file.
312
+
313
+ Meanwhile [unpack_usdz.js:66](../../../../shade/renderer/loader/usd/unpack_usdz.js#L66) reads ZIP
314
+ local file headers and *skips* the CRC field with a comment, because verifying it would mean writing
315
+ this function again.
316
+
317
+ **Proposal.** Move to `src/core/binary/hash/crc32.js`, beside `xxh3_64` and `xxh3_128`. Re-export
318
+ from `png/crc32.js` for one release if any external consumer matters, otherwise just move it and fix
319
+ the two imports.
320
+
321
+ Also worth noting while it moves: `PNGReader.crc_enabled` defaults to `false`
322
+ ([PNGReader.js:91](png/PNGReader.js#L91)) and, when enabled, `console.warn`s rather than throwing.
323
+ That is a defensible choice for a game engine — but it is an undocumented one, and the AVIF tree
324
+ records choices like this in `DECISIONS.md`. Give the PNG folder the same treatment.
325
+
326
+ ---
327
+
328
+ ### C8 — NUL-terminated UTF-8, encoded and decoded privately in two places
329
+
330
+ [ByteCursor.js:307](../../../../avif/native/isobmff/ByteCursor.js#L307) has a private `utf8_decode`
331
+ with the stated reason that `TextDecoder` is not universally available.
332
+ [BoxWriter.js:205](../../../../avif/native/isobmff/BoxWriter.js#L205) has the matching encoder inline
333
+ in `utf8_string`. `BinaryBuffer` has `readUTF8String` / `writeUTF8String` but they are
334
+ **length-prefixed** (meep's own serialisation format) and therefore genuinely unusable here — this
335
+ is a real gap, not a missed reuse.
336
+
337
+ **Proposal.** One pair of free functions in `core/binary/`:
338
+
339
+ ```js
340
+ utf8_decode_range(bytes, start, end) // → string
341
+ utf8_encode_into(text, buffer) // → bytes written
342
+ ```
343
+
344
+ `ByteCursor.utf8_string` and `BoxWriter.utf8_string` become three-line wrappers that add the
345
+ NUL handling. Optionally give `BinaryBuffer` `readUTF8StringNullTerminated` /
346
+ `writeUTF8StringNullTerminated` built on the same pair, which would let a future ISOBMFF-adjacent
347
+ reader use it directly.
348
+
349
+ This does **not** conflict with D10: it shares the character codec, not the cursor.
350
+
351
+ ---
352
+
353
+ ## 2. Inside `src/avif/native/` — finishing D30
354
+
355
+ D30 says derived state is shared and traversal is mirrored. That is the right rule. These are the
356
+ places where derived state is still written twice, and they are the residue D30 was aiming at.
357
+
358
+ ### A1 — Tile geometry derivation, 18 identical lines
359
+
360
+ [parse_frame_header.js:250–267](../../../../avif/native/av1/obu/parse_frame_header.js#L250) and
361
+ [write_frame_header.js:162–179](../../../../avif/native/av1/obu/write_frame_header.js#L162) open with
362
+ the same eighteen lines: `sb_shift`, `sb_size`, `sb_cols`, `sb_rows`, `max_tile_width_sb`,
363
+ `max_tile_area_sb`, `min_log2_tile_cols`, `max_log2_tile_cols`, `max_log2_tile_rows`,
364
+ `min_log2_tiles`. Character for character, `let` versus `const` on `max_tile_area_sb` aside.
365
+
366
+ **Proposal.** `derive_tile_limits(sequence, header, out)` in a shared module (`av1/obu/tile_limits.js`
367
+ or extending the existing `av1/util/`), filling a small struct or an `Int32Array`. Both call it. This
368
+ is the highest-consequence one in this section: these ten values decide how many `f(1)` bits the
369
+ uniform-spacing loop reads and writes, so a divergence here is a bitstream desync, not a wrong pixel.
370
+
371
+ ### A2 — CDF adaptation, 24 identical lines in reader and writer
372
+
373
+ [SymbolReader.js:178–202](../../../../avif/native/av1/entropy/SymbolReader.js#L178) and
374
+ [SymbolWriter.js:216–240](../../../../avif/native/av1/entropy/SymbolWriter.js#L216) are the same
375
+ block verbatim: the `rate` computation, the `tmp` sweep, the count saturation at 32.
376
+
377
+ **Proposal.** `cdf_adapt(cdf, offset, n, symbol)` beside `symbol_cost.js`. Both call it. The
378
+ adaptation rate is normative and shared by construction; there is no reason for two copies.
379
+
380
+ While there: the interval boundary
381
+ `((range_high * (f >> EC_PROB_SHIFT)) >> (7 - EC_PROB_SHIFT)) + EC_MIN_PROB * (n - symbol - 1)`
382
+ appears once in the reader and twice in the writer
383
+ ([SymbolWriter.js:200](../../../../avif/native/av1/entropy/SymbolWriter.js#L200) and
384
+ [:206](../../../../avif/native/av1/entropy/SymbolWriter.js#L206)). A `symbol_boundary(range_high,
385
+ cdf, offset, n, symbol)` helper covers all three; it is on the per-symbol path, so measure before
386
+ committing, but it is a pure function of five numbers and should inline.
387
+
388
+ ### A3 — `clear_above_context` / `clear_left_context`, byte-identical
389
+
390
+ [decode_tile.js:115](../../../../avif/native/av1/decode/decode_tile.js#L115) and
391
+ [:125](../../../../avif/native/av1/decode/decode_tile.js#L125); the same two functions verbatim at
392
+ [encode_tile.js:277](../../../../avif/native/av1/encode/encode_tile.js#L277) and
393
+ [:287](../../../../avif/native/av1/encode/encode_tile.js#L287).
394
+
395
+ **Proposal.** Move both onto `Av1FrameContext` as methods, or export them from
396
+ `av1/entropy/coefficient_context.js` — which is already the shared home for exactly this kind of
397
+ context state. Twelve lines, zero risk.
398
+
399
+ ### A4 — `dc_delta` / `ac_delta`, byte-identical
400
+
401
+ [reconstruct.js:222](../../../../avif/native/av1/decode/reconstruct.js#L222) and
402
+ [:235](../../../../avif/native/av1/decode/reconstruct.js#L235); identical copies at
403
+ [quantise.js:193](../../../../avif/native/av1/encode/quantise.js#L193) and
404
+ [:206](../../../../avif/native/av1/encode/quantise.js#L206). `reconstruct.js` already exports
405
+ `dc_quant`, `ac_quant` and `get_qindex` and `quantise.js` already imports from it — these two just
406
+ never made the trip.
407
+
408
+ **Proposal.** Export them from `reconstruct.js` alongside their siblings. Delete the copies.
409
+
410
+ ### A5 — `BINARY_PARTITION_CDF` construction
411
+
412
+ [decode_tile.js:273](../../../../avif/native/av1/decode/decode_tile.js#L273) and
413
+ [encode_tile.js:838](../../../../avif/native/av1/encode/encode_tile.js#L838) each declare a
414
+ three-element scratch and each fill it with `(1<<15) - psum, 1<<15, 0`, each save and restore
415
+ `disable_cdf_update` around the call. The decoder explains in a comment why the adaptation is
416
+ skipped (the spec rebuilds this CDF from scratch every time, so any update is discarded); the
417
+ encoder does the same thing with no explanation, which is the half of the pair that will get
418
+ "simplified" by someone who does not know.
419
+
420
+ **Proposal.** `build_binary_partition_cdf(psum, out)` in
421
+ [partition_cdf.js](../../../../avif/native/av1/entropy/partition_cdf.js), which is already the shared
422
+ module for partition CDF selection. The scratch array stays per-module (correctly — it prevents
423
+ reentrancy coupling), only the fill and the save/restore discipline move.
424
+
425
+ ### A6 — `plane_residual_size`, one expression in seven places
426
+
427
+ `SUBSAMPLED_SIZE[(size * 2 + sub_x) * 2 + sub_y]` appears at:
428
+
429
+ - [decode_tile.js:572](../../../../avif/native/av1/decode/decode_tile.js#L572) (as a named function)
430
+ - [encode_tile.js:525](../../../../avif/native/av1/encode/encode_tile.js#L525),
431
+ [:1728](../../../../avif/native/av1/encode/encode_tile.js#L1728),
432
+ [:2088](../../../../avif/native/av1/encode/encode_tile.js#L2088),
433
+ [:2179](../../../../avif/native/av1/encode/encode_tile.js#L2179) (four times, inline)
434
+ - [coefficient_context.js:50](../../../../avif/native/av1/entropy/coefficient_context.js#L50)
435
+ - [loop_filter.js:110](../../../../avif/native/av1/filter/loop_filter.js#L110)
436
+
437
+ **Proposal.** Export `plane_residual_size(mi_size, sub_x, sub_y)` from
438
+ [derived_block_tables.js](../../../../avif/native/av1/tables/derived_block_tables.js) — which already
439
+ exists for precisely this purpose ("Deriving beats transcribing: the two can never drift apart") —
440
+ and use it in all seven sites.
441
+
442
+ **And flag while you are there:** the decoder's `get_tx_size`
443
+ ([decode_tile.js:963](../../../../avif/native/av1/decode/decode_tile.js#L963)) clamps a chroma
444
+ transform of 64 down to 32/16x32/32x16; the encoder's `transform_size`
445
+ ([encode_tile.js:2167](../../../../avif/native/av1/encode/encode_tile.js#L2167)) does not, and adds a
446
+ `lossless → TX_4X4` case the decoder handles elsewhere. They may both be correct given the encoder's
447
+ narrow syntax (D30: blocks 8x8 to 32x32, so 64 is unreachable) — but that is an invariant holding by
448
+ accident across two files, which is exactly the shape of bug D30 exists to prevent. Either share the
449
+ function with the encoder's extra cases guarded, or leave a comment on both stating the invariant.
450
+
451
+ ### A7 — Losslessness derived twice, and the two do not agree
452
+
453
+ [parse_frame_header.js:527](../../../../avif/native/av1/obu/parse_frame_header.js#L527)
454
+ `compute_losslessness` walks all `MAX_SEGMENTS`, checks all five quantiser deltas, fills
455
+ `lossless_array` and `seg_qm_level`, and derives `all_lossless` from `frame_width ===
456
+ upscaled_width`.
457
+
458
+ [build_headers.js:236](../../../../avif/native/av1/encode/build_headers.js#L236) does:
459
+
460
+ ```js
461
+ const lossless = frame.base_q_idx === 0;
462
+ frame.coded_lossless = lossless ? 1 : 0;
463
+ frame.all_lossless = frame.coded_lossless;
464
+ frame.lossless_array.fill(lossless ? 1 : 0);
465
+ ```
466
+
467
+ Correct *today*, because the encoder emits no segmentation and no non-zero deltas (D30). It stops
468
+ being correct the moment either is added — and `seg_qm_level` is never populated on the encode side
469
+ at all, which matters if `using_qmatrix` is ever turned on.
470
+
471
+ **Proposal.** Export `compute_losslessness` and have `build_headers` call it after setting
472
+ `base_q_idx` and the deltas. The encoder's assumptions then become inputs to the shared derivation
473
+ rather than a parallel implementation of it.
474
+
475
+ ### A8 — Shared constants declared twice
476
+
477
+ | Constant | Sites |
478
+ | --- | --- |
479
+ | `ALPHA_AUX_TYPE` | [decode_avif.js:15](../../../../avif/native/api/decode_avif.js#L15), [encode_avif.js:41](../../../../avif/native/api/encode_avif.js#L41) |
480
+ | `QM_TOTAL_SIZE` | [av1_symbols.js:261](../../../../avif/native/av1/tables/av1_symbols.js#L261) (exported) and [reconstruct.js:33](../../../../avif/native/av1/decode/reconstruct.js#L33) (local copy of the same `3344`) |
481
+ | `HLG_A/B/C` | [transfer_to_linear.js:50](../../../../avif/native/color/transfer_to_linear.js#L50), [linear_to_transfer.js:18](../../../../avif/native/color/linear_to_transfer.js#L18) |
482
+
483
+ **Proposal.** `ALPHA_AUX_TYPE` → a shared `heif/aux_types.js` (there will be others: depth, gain
484
+ map). `QM_TOTAL_SIZE` → delete the local copy, import the exported one. `HLG_A/B/C` → a small
485
+ `color/hlg_constants.js`, or better, a `core/color/HLG/` module beside the existing `core/color/PQ/`
486
+ — the two curves are peers and only one of them has a home today.
487
+
488
+ ### A9 — `MAX_TX_DEPTH` is two different exported symbols
489
+
490
+ [av1_symbols.js:176](../../../../avif/native/av1/tables/av1_symbols.js#L176) exports
491
+ `MAX_TX_DEPTH = 2` (a scalar bound).
492
+ [block_tables.js:12](../../../../avif/native/av1/tables/block_tables.js#L12) exports
493
+ `MAX_TX_DEPTH = new Uint8Array([...])` (a 22-entry table).
494
+
495
+ `decode_tile.js` imports the table under the alias `MAX_TX_DEPTH_TABLE`; `encode_tile.js` imports it
496
+ unaliased. Nothing currently imports both into one module, so nothing is broken — but the next file
497
+ that needs both gets a shadowing bug that will look like a table lookup returning a number.
498
+
499
+ **Proposal.** Rename the generated table to `MAX_TX_DEPTH_BY_BLOCK` in the generator's naming rule
500
+ (D22 — the generator is out of tree, so this is a rename in `block_tables.js` plus a note in
501
+ `DECISIONS.md` recording the deviation from the spec's name), or rename the scalar to
502
+ `MAX_TX_DEPTH_LIMIT`. Either way, not both.
503
+
504
+ ### A10 — `clamp_unit`, `clamp_index`, and friends against `core/math`
505
+
506
+ - `clamp_unit` is duplicated at
507
+ [convert_to_rgba.js:217](../../../../avif/native/api/convert_to_rgba.js#L217) and
508
+ [ycbcr_row_to_rgb.js:83](../../../../avif/native/color/ycbcr_row_to_rgb.js#L83) — and is
509
+ byte-for-byte [core/math/clamp01.js](../../../../core/math/clamp01.js), which `loop_restoration.js`
510
+ already knows how to import from (it uses `core/math/clamp.js`).
511
+ - `clamp_index` is duplicated at
512
+ [choose_restoration.js:622](../../../../avif/native/av1/encode/choose_restoration.js#L622) and
513
+ [upsample_chroma_row.js:94](../../../../avif/native/color/upsample_chroma_row.js#L94) — same body,
514
+ different parameter names.
515
+ - [floor_log2.js:15](../../../../avif/native/av1/util/floor_log2.js#L15) is `31 - Math.clz32(x)`,
516
+ which is [core/binary/msb_32.js](../../../../core/binary/msb_32.js) exactly. And it is
517
+ *re-inlined* six more times in `SymbolReader`/`SymbolWriter` rather than called. (Six, not seven:
518
+ `SymbolWriter.#normalize`'s `32 - Math.clz32(range)` is a bit *length*, libaom's `OD_ILOG_NZ`,
519
+ not a floor log. It computes the same shift as the reader's `15 - floor_log2(x)` and is
520
+ deliberately left alone — rewriting it would break the correspondence with the source it was
521
+ transliterated from.)
522
+ - [convert_to_rgba.js:199](../../../../avif/native/api/convert_to_rgba.js#L199) `quantise` and
523
+ [encode_avif.js:415](../../../../avif/native/api/encode_avif.js#L415) `clamp_sample` are the same
524
+ "round then clamp to `0..maximum`", differing only in whether the scale-up happens inside.
525
+
526
+ **Proposal.** Import `clamp01` for `clamp_unit`. Extract one `clamp_index(index, extent)` beside
527
+ `clamp01`. Make `floor_log2` an alias — `export const floor_log2 = msb_32;` with the AV1 spec
528
+ docstring retained, since the spec-named alias is genuinely useful — and replace the six inline
529
+ `31 - Math.clz32(...)` sites with calls. Unify `quantise` / `clamp_sample` into one function.
530
+
531
+ **Note on the entropy coder.** `SymbolReader` says of itself that it is the hottest code in the
532
+ decoder, and it bypasses `BitReader` for that reason, so replacing an inline expression with a call
533
+ there needs evidence rather than taste. Three runs of the AVIF suite before and after: 13.77s,
534
+ 15.19s, 12.74s against 13.03s, 12.91s, 12.77s. No regression, so the change stands; had there been
535
+ one, the inline forms should have kept a comment naming them instead.
536
+
537
+ The landed shape put `clamp_sample(value, maximum)` — round then clamp — in `avif/native/color/`,
538
+ with `convert_to_rgba` keeping `quantise` as a one-line normalised wrapper over it, so the inner
539
+ loop reads as it did.
540
+
541
+ The `w`/`m` derivation for `ns(n)` is a fourth instance of the same theme: it appears at
542
+ [BitReader.js:216](../../../../avif/native/bitstream/BitReader.js#L216),
543
+ `BitWriter.ns`, [SymbolReader.js:286](../../../../avif/native/av1/entropy/SymbolReader.js#L286) and
544
+ [SymbolWriter.js:283](../../../../avif/native/av1/entropy/SymbolWriter.js#L283). The two symbol-coder
545
+ copies use the inline `clz32` form rather than calling `floor_log2`, which is how they drifted apart
546
+ in style in the first place.
547
+
548
+ ### A11 — Name collisions across unrelated modules
549
+
550
+ Not duplication, but the same trap as A9 and worth one pass:
551
+
552
+ - `filter_edge` — [loop_filter.js](../../../../avif/native/av1/filter/loop_filter.js) (a deblocking
553
+ edge) and [predict_intra.js](../../../../avif/native/av1/predict/predict_intra.js) (an intra edge
554
+ smoothing kernel). Unrelated jobs, identical name.
555
+ - `restore_block` — [loop_restoration.js](../../../../avif/native/av1/filter/loop_restoration.js)
556
+ (Wiener/SGR restoration) and
557
+ [Av1BlockSnapshot.js](../../../../avif/native/av1/encode/Av1BlockSnapshot.js) (undo an encoder
558
+ trial). Opposite meanings of "restore".
559
+ - `MAX_SIDE` — 64 in `Av1BlockSnapshot.js`, a different 64 in `forward_transform_2d.js`.
560
+
561
+ **Proposal.** Rename to the job: `filter_deblock_edge` / `filter_intra_edge`,
562
+ `restore_lr_block` / `snapshot_restore_block`, `SNAPSHOT_MAX_SIDE` / `TRANSFORM_MAX_SIDE`. All six are
563
+ file-local, so each rename is contained to one file.
564
+
565
+ ### A12 — Table shape exports are almost entirely unused
566
+
567
+ `block_tables.js` exports a `*_SHAPE` array next to every table (`SUBSAMPLED_SIZE_SHAPE`,
568
+ `MAX_TX_DEPTH_SHAPE`, …). Exactly one is consumed outside the tables folder:
569
+ `PARTITION_SUBSIZE_SHAPE` at
570
+ [encode_tile.js:142](../../../../avif/native/av1/encode/encode_tile.js#L142), to derive a stride.
571
+
572
+ **Proposal.** Either keep them (they are generated, they cost nothing at runtime, and they document
573
+ the flattening) — in which case say so in `DECISIONS.md` so nobody prunes them — or have the
574
+ generator emit `*_STRIDE` scalars for the multi-dimensional tables only, which is the thing callers
575
+ actually want. I lean towards keeping and documenting: D24 validates the generated tables
576
+ structurally, and the shapes are what that validation reads.
577
+
578
+ ---
579
+
580
+ ## 3. Inside the PNG loader
581
+
582
+ ### P1 — `PNGReader` is prototype-style in an ES-class codebase
583
+
584
+ [PNGReader.js:51](png/PNGReader.js#L51) is a `function` constructor with fourteen
585
+ `PNGReader.prototype.x = function` assignments. Everything around it — `PNG`, `Codec`,
586
+ `ColourTransform`, `ByteCursor`, `JpegImage` — is an ES class. The file also opens with
587
+ `"use strict"` and a `/*global Uint8Array:true ArrayBuffer:true */` comment, both vestigial.
588
+
589
+ **Proposal.** Straight mechanical conversion to `class PNGReader`. No behaviour change, no signature
590
+ change. Worth doing as its own commit before any of the other PNG items so their diffs stay readable.
591
+
592
+ ### P2 — Two inflate paths in one package
593
+
594
+ [PNGReader.decodePixels:356](png/PNGReader.js#L356) constructs a `zlib.Inflate`, pushes chunks,
595
+ and on error falls back to `inflateRaw` on the concatenated bytes with the 2-byte header stripped —
596
+ about 30 lines with a well-argued comment about real-world encoders and libpng leniency.
597
+
598
+ [inflate.js:8](png/inflate.js#L8) is a separate, simpler wrapper used only by the two text-chunk
599
+ decoders, with no leniency and a different error shape (`throw new Error(inflator.err)` — a numeric
600
+ code, not a message).
601
+
602
+ **Proposal.** One `png_inflate(chunks, {lenient})` in `inflate.js` covering both. The multi-chunk
603
+ concatenation is the general case; single-chunk is `[chunk]`. The lenient fallback stays opt-in and
604
+ keeps its comment, which is the most valuable thing in that function.
605
+
606
+ ### P3 — Unfilter functions have five different signatures
607
+
608
+ | Filter | Signature |
609
+ | --- | --- |
610
+ | `None` | `(data, addr, out, out_offset, length, bit_depth)` |
611
+ | `Sub` | `(data, addr, out, bpp, out_offset, length)` |
612
+ | `Up` | `(data, addr, out, out_offset, prev_offset, length)` |
613
+ | `Average` | `(data, addr, out, bpp, out_offset, prev_offset, length)` |
614
+ | `Paeth` | `(data, addr, out, bpp, out_offset, prev_offset, length)` |
615
+
616
+ Which forces [PNGReader.unFilter:576](png/PNGReader.js#L576) to be a 60-line `switch` that
617
+ reassembles a different argument list per case.
618
+
619
+ **Proposal.** One uniform signature — `(data, scanline_address, output, bytes_per_pixel,
620
+ output_offset, output_offset_previous, length, bit_depth)` — with each filter ignoring what it does
621
+ not need, and a frozen dispatch table `PNG_UNFILTER = [none, sub, up, average, paeth]`. `unFilter`
622
+ becomes a bounds check plus `PNG_UNFILTER[filter_type](...)`.
623
+
624
+ **The signature mess is hiding a bug — check this before refactoring.** `png_filter_unFilterNone` is
625
+ doing two jobs: unfiltering *and* unpacking sub-byte samples (bit depths 1/2/4). No other filter
626
+ unpacks. Two consequences follow from reading `interlaceNone`
627
+ ([PNGReader.js:420](png/PNGReader.js#L420)) against it:
628
+
629
+ - It passes `length = color_bytes_per_row`, a **byte** count, and `unFilterNone` treats that as a
630
+ **sample** count when `bit_depth < 8`. A 16-pixel-wide 1-bit greyscale row has
631
+ `color_bytes_per_row = 2`, so two samples get unpacked instead of sixteen — and `pixels` is
632
+ allocated at `color_bytes_per_row * height`, so there is nowhere to put the other fourteen anyway.
633
+ - Filters 1–4 write packed bytes into an output that `PNG.getUint8Data` then reads as unpacked
634
+ samples, so any sub-byte-depth image using them is wrong on a second axis.
635
+
636
+ `PNGReader.spec.js` tests bit depths 8 and 16 only — **no test anywhere uses depth 1, 2 or 4**, which
637
+ is why this has survived. Write that test first; the refactor must not preserve the behaviour.
638
+ Separating "unfilter" (byte-wise, all five filters) from "unpack samples" (a second pass, depth-aware)
639
+ fixes both axes at once and is the right shape regardless of whether you agree with the diagnosis.
640
+
641
+ **Confirmed, and there was a third axis.** The tests were written first and failed exactly as
642
+ predicted: a 16-wide 1-bit row produced two samples, and a Sub-filtered 4-bit row came back as raw
643
+ packed bytes. What the diagnosis missed is `interlaceAdam7`, which was worse — its bytes-per-pixel
644
+ was `colors * bitDepth / 8`, a *fraction* below eight bits, and every length and buffer size derived
645
+ from it. Seven new decoder tests cover it now, two of them rebuilding an 8x8 image from all seven
646
+ Adam7 passes at 1 and 4 bits.
647
+
648
+ ### P4 — `PNGReader` carries two cursors and uses one
649
+
650
+ The constructor sets `this.i = 0` ([PNGReader.js:56](png/PNGReader.js#L56)) *and* builds a
651
+ `BinaryBuffer` with its own `position`. `this.i` is written once, read once (in `decodeHeader`'s
652
+ `if (this.i !== 0) throw`) and never advanced — a dead cursor guarding a condition that can never be
653
+ false.
654
+
655
+ **Proposal.** Delete `this.i` and the guard. `this.header` (an 8-byte `Uint8Array` field) can be a
656
+ local in `decodeHeader`.
657
+
658
+ ### P5 — `crc_enabled` defaults off with a `console.warn` on failure
659
+
660
+ Covered in C7. Restating here because it is a PNG-side decision that deserves a written rationale in
661
+ the same place the other PNG decisions will live.
662
+
663
+ ### P6 — Text chunks are decoded and thrown away
664
+
665
+ `decodeChunk` calls `png_chunk_decode_zTXt(chunk)` and `png_chunk_decode_iTXt(chunk)`
666
+ ([PNGReader.js:212](png/PNGReader.js#L212)) and **discards both return values**. `png.text` is only
667
+ ever written by `decodetEXt` / `decodeiEXt`, which the dispatch switch never reaches (D2 below).
668
+
669
+ So the PNG decoder pays for inflating every compressed text chunk and keeps none of it.
670
+
671
+ **Proposal.** Either wire the results into `png.text` (three lines, and it makes the `text` field
672
+ mean something), or stop calling them from `decodeChunk` and keep the two decoders as a library the
673
+ metadata path can use when there is one. Pick one; the current state is the cost of the first with
674
+ the benefit of neither.
675
+
676
+ ---
677
+
678
+ ## 4. Inside the JPEG loader
679
+
680
+ ### J1 — `JpegImage.parse` is one function doing six jobs
681
+
682
+ 250 lines, four nested closures capturing a mutable `offset`, a `switch` with sixteen cases, two
683
+ error-recovery paths, and the component-preparation pass all in one scope. There is also a
684
+ duplicated `case 0xFFEC:` (APP12 appears twice, [JpegImage.js:161](jpeg/JpegImage.js#L161) and
685
+ [:162](jpeg/JpegImage.js#L162)) — harmless, but a symptom.
686
+
687
+ **Proposal.** Covered structurally by C5.2. Concretely: `for_each_jpeg_segment` walker, then
688
+ `parse_app_segment`, `parse_dqt`, `parse_sof`, `parse_dht`, `parse_sos`, `prepare_components` as
689
+ free functions taking explicit state. `prepareComponents` in particular is a pure function of a
690
+ `JpegFrame` and belongs in its own file next to `JpegFrame.js`.
691
+
692
+ ### J2 — `for...in` over `frame.components` with `hasOwnProperty` guards
693
+
694
+ `frame.components` is a plain object keyed by numeric component id, iterated with `for (componentId
695
+ in frame.components)` plus `hasOwnProperty` checks
696
+ ([JpegImage.js:74](jpeg/JpegImage.js#L74)), and later mutated with `delete cp[j].quantizationIdx`
697
+ ([JpegImage.js:390](jpeg/JpegImage.js#L390)). `frame.componentsOrder` already carries the ids in
698
+ order.
699
+
700
+ **Proposal.** `components` becomes a `Map<number, JpegFrameComponent>`, or the array indexed by
701
+ `componentsOrder` that the code already maintains alongside it. The `delete` goes away — a
702
+ `quantizationIdx` that has been resolved into `quantizationTable` can simply stay, or be set to
703
+ `-1`. Deleting a property to mark state is a deopt and a lie about the struct's shape, and
704
+ `JpegFrameComponent` is a declared class with declared fields
705
+ ([JpegFrameComponent.js](jpeg/JpegFrameComponent.js)) — the delete contradicts it.
706
+
707
+ ### J3 — `getData` and `copyToImageData` both switch on component count
708
+
709
+ [JpegImage.js:421](jpeg/JpegImage.js#L421) `getData` has arms for 1, 2, 3, 4 components.
710
+ [JpegImage.js:561](jpeg/JpegImage.js#L561) `copyToImageData` has arms for 1, 3, 4 — no 2, so a
711
+ two-component PDF-style JPEG parses and then throws at the last step. The CMYK arm inverts twice
712
+ (`255 - C` inside `getData`, `255 - clampTo8bit(C * (1 - K/255) + K)` in `copyToImageData`), which is
713
+ correct but only legible if you read both functions together.
714
+
715
+ **Proposal.** After C3 and C4 land, `getData` produces normalised RGB (or RGBA) rows directly and
716
+ `copyToImageData` becomes the channel-expansion call from C2. The component-count switch survives in
717
+ exactly one place: choosing the colour transform.
718
+
719
+ ### J4 — `clampTo8bit` against `core/math`
720
+
721
+ [JpegImage.js:39](jpeg/JpegImage.js#L39) is `clamp(a, 0, 255)` with a nested ternary.
722
+ `core/math/clamp.js` exists and `core/binary/float_to_uint8.js` exists. Use one.
723
+
724
+ ---
725
+
726
+ ## 5. Dead code
727
+
728
+ Removing these first makes every other item in this document a smaller diff.
729
+
730
+ | # | What | Lines | Evidence |
731
+ | --- | --- | --- | --- |
732
+ | D1 | [PNG.js:188](png/PNG.js#L188) `getPixel`, [:257](png/PNG.js#L257) `getRGBA8Array_fromRGB`, [:282](png/PNG.js#L282) `getRGBA8Array_generic`, [:303](png/PNG.js#L303) `getRGBA8Array` | ~120 | No importer, no spec. Only `getUint8Data` is called. |
733
+ | D2 | [PNGReader.js:260](png/PNGReader.js#L260) `decodetEXt`, [:273](png/PNGReader.js#L273) `decodeiEXt` | ~35 | `decodeChunk`'s switch has no `tEXt` or `iTXt`→`decodeiEXt` arm. Also **both are wrong if revived**: `decodetEXt` reads the value with `readASCIICharacters(keyword.length - 1)` (the *keyword's* length), and `decodeiEXt` calls `readUTF8String()`, which reads a 4-byte length prefix PNG does not have. |
734
+ | D3 | [prototypePNG.js](png/prototypePNG.js) | 37 | A hard-coded-path browser demo with top-level side effects, sitting in `src/`. No importer. |
735
+ | D4 | [idct8x8_fixed.js](jpeg/idct8x8_fixed.js) | 213 | No importer. `buildComponentData` uses `idct8x8_float`. If it is kept as a documented alternative, say so in a header comment and give it a spec — otherwise the MBOIT-chunk precedent (guarded + spec'd + a note saying why) is the bar. |
736
+ | D5 | [PNGReader.js:56](png/PNGReader.js#L56) `this.i` | 3 | P4. |
737
+ | D6 | [JpegImage.js:162](jpeg/JpegImage.js#L162) duplicate `case 0xFFEC` | 1 | J1. |
738
+
739
+ Total: roughly 410 lines, none of it reachable.
740
+
741
+ ---
742
+
743
+ ## 6. What *not* to unify
744
+
745
+ Recording these so the document cannot be read as arguing for them.
746
+
747
+ - **`ByteCursor` into `BinaryBuffer`.** D10 settled this: growable stream for writing, zero-copy
748
+ `DataView` cursor for reading. The reasoning still holds, and `ByteCursor.sub_cursor` has no sane
749
+ `BinaryBuffer` equivalent. C8 shares the UTF-8 codec, not the cursor. What *is* worth doing is
750
+ making the naming rhyme — `ByteCursor.u32()` versus `BoxWriter.u32()` versus
751
+ `BinaryBuffer.readUint32BE()` are three spellings of one operation, and `BoxWriter` already chose
752
+ the cursor's spelling for its own methods, so the cursor pair is self-consistent and only
753
+ `BinaryBuffer` reads differently. Leave it.
754
+ - **`encode_tile` into `decode_tile`.** D30 settled this. The traversal is mirrored deliberately;
755
+ inverting the decoder's control flow to serve both would be worse. Every item in section 2 shares
756
+ *derived state*, which is precisely what D30 asks for, and none of them touches traversal.
757
+ - **`BitReader` / `BitWriter` into one class.** They are a clean symmetric pair with no shared
758
+ implementation to extract — A10's `ns` width derivation is the only overlap and that is four
759
+ lines.
760
+ - **A single generic TLV walker for boxes, chunks and markers.** See C5. Share the contract, not the
761
+ implementation.
762
+ - **The generated tables.** D4 and D22 settled provenance; A9 and A12 are naming and documentation
763
+ items, not proposals to hand-edit generated output.
764
+
765
+ ---
766
+
767
+ ## 7. Sequencing
768
+
769
+ Ordered by value per unit of risk. Each row is independently landable.
770
+
771
+ | Order | Items | Effort | Risk | Why here |
772
+ | --- | --- | --- | --- | --- |
773
+ | 1 | **D1–D6** (dead code) | ~1h | none | Shrinks every later diff. |
774
+ | 2 | **A3, A4, A5, A8, A10** (verbatim AVIF duplicates) | ~2h | none | Pure deletion + import. Fixture round-trips cover it. |
775
+ | 3 | **A1, A2** (tile limits, CDF adapt) | ~2h | low | Highest-consequence duplicates: divergence here is a bitstream desync. Do them while the memory of section 2 is fresh. |
776
+ | 4 | **P1, P4, C6** (PNG class conversion, dead cursor, checked reads) | ~3h | low | Mechanical, and C6 needs P1's shape to be clean. |
777
+ | 5 | **C7, C8** (crc32 and UTF-8 to `core`) | ~2h | low | Unblocks USDZ CRC verification as a side effect. |
778
+ | 6 | **C1** (`Codec` registration) | ~1d | low | The one that changes what the engine can load. Everything above makes it a smaller change. |
779
+ | 7 | **A6, A7, A9** (plane_residual_size, losslessness, MAX_TX_DEPTH) | ~half day | low–med | A7 needs a decision about whether the encoder's assumptions become inputs or stay assumptions. |
780
+ | 8 | **P2, P3, J1, J2** (PNG inflate, filter table, JPEG parse split) | ~1d | med | P3 wants the sub-byte-depth test from section P3 written **first**. |
781
+ | 9 | **C3, C4, C2** (colour transform, upsampling, channel expansion to `core`) | ~2d | med | The largest win and the largest blast radius. Needs the JPEG fixtures extended first; do it last, when JPEG is reachable (C1) and therefore testable end to end. |
782
+ | 10 | **A11, A12, P5, P6** (naming, docs, text chunks) | ~half day | none | Housekeeping; can slot in anywhere. |
783
+
784
+ Items 1–5 are about a day and a half and are all deletions or moves. If only one thing from this
785
+ document gets done, do row 1 and row 3.
786
+
787
+ ---
788
+
789
+ ## 8. One thing this review did not do
790
+
791
+ I read `src/avif/native/DECISIONS.md` before writing, and several proposals here are shaped by it —
792
+ sections 2 and 6 in particular. **PNG and JPEG have no equivalent.** There is a `jpeg/NOTES.md` with
793
+ two URLs in it and nothing else.
794
+
795
+ That asymmetry is itself a finding. The AVIF tree's `crc_enabled`-style choices are all written
796
+ down; the PNG tree's are not, which is why C6 (unchecked reads), P5 (CRC off by default) and P6
797
+ (text decoded and discarded) each read as "possible oversight" rather than "decision". Some of them
798
+ almost certainly *are* decisions — a game engine that warns on a bad CRC instead of refusing the
799
+ texture is making a defensible call — but nothing in the tree says so, so the next person to touch it
800
+ has to guess.
801
+
802
+ Whatever subset of this document gets acted on, a `src/engine/asset/loaders/image/DECISIONS.md`
803
+ recording the ones that get **rejected** is worth as much as the refactors that get accepted.