@woosh/meep-engine 3.8.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.
- package/build/bundle-worker-image-decoder.js +1 -1
- package/build/bundle-worker-terrain.js +1 -1
- package/package.json +3 -2
- package/src/avif/encode_image_source.d.ts +70 -0
- package/src/avif/encode_image_source.d.ts.map +1 -0
- package/src/avif/encode_image_source.js +117 -0
- package/src/avif/encoder_worker.js +15 -126
- package/src/avif/index.d.ts +4 -2
- package/src/avif/index.js +21 -2
- package/src/avif/native/DECISIONS.md +537 -0
- package/src/avif/native/NOTICE.md +61 -0
- package/src/avif/native/api/AvifDecoder.d.ts +67 -0
- package/src/avif/native/api/AvifDecoder.d.ts.map +1 -0
- package/src/avif/native/api/AvifDecoder.js +121 -0
- package/src/avif/native/api/apply_transformations.d.ts +30 -0
- package/src/avif/native/api/apply_transformations.d.ts.map +1 -0
- package/src/avif/native/api/apply_transformations.js +120 -0
- package/src/avif/native/api/avif_to_sampler2d.d.ts +25 -0
- package/src/avif/native/api/avif_to_sampler2d.d.ts.map +1 -0
- package/src/avif/native/api/avif_to_sampler2d.js +42 -0
- package/src/avif/native/api/convert_to_rgba.d.ts +54 -0
- package/src/avif/native/api/convert_to_rgba.d.ts.map +1 -0
- package/src/avif/native/api/convert_to_rgba.js +204 -0
- package/src/avif/native/api/decode_avif.d.ts +24 -0
- package/src/avif/native/api/decode_avif.d.ts.map +1 -0
- package/src/avif/native/api/decode_avif.js +111 -0
- package/src/avif/native/api/decode_image_item.d.ts +18 -0
- package/src/avif/native/api/decode_image_item.d.ts.map +1 -0
- package/src/avif/native/api/decode_image_item.js +234 -0
- package/src/avif/native/api/encode_avif.d.ts +73 -0
- package/src/avif/native/api/encode_avif.d.ts.map +1 -0
- package/src/avif/native/api/encode_avif.js +488 -0
- package/src/avif/native/api/sampler2d_to_avif.d.ts +22 -0
- package/src/avif/native/api/sampler2d_to_avif.d.ts.map +1 -0
- package/src/avif/native/api/sampler2d_to_avif.js +108 -0
- package/src/avif/native/av1/decode/Av1FrameContext.d.ts +286 -0
- package/src/avif/native/av1/decode/Av1FrameContext.d.ts.map +1 -0
- package/src/avif/native/av1/decode/Av1FrameContext.js +622 -0
- package/src/avif/native/av1/decode/block_decoded.d.ts +27 -0
- package/src/avif/native/av1/decode/block_decoded.d.ts.map +1 -0
- package/src/avif/native/av1/decode/block_decoded.js +59 -0
- package/src/avif/native/av1/decode/decode_av1_still.d.ts +22 -0
- package/src/avif/native/av1/decode/decode_av1_still.d.ts.map +1 -0
- package/src/avif/native/av1/decode/decode_av1_still.js +309 -0
- package/src/avif/native/av1/decode/decode_coefficients.d.ts +20 -0
- package/src/avif/native/av1/decode/decode_coefficients.d.ts.map +1 -0
- package/src/avif/native/av1/decode/decode_coefficients.js +259 -0
- package/src/avif/native/av1/decode/decode_palette.d.ts +28 -0
- package/src/avif/native/av1/decode/decode_palette.d.ts.map +1 -0
- package/src/avif/native/av1/decode/decode_palette.js +442 -0
- package/src/avif/native/av1/decode/decode_tile.d.ts +14 -0
- package/src/avif/native/av1/decode/decode_tile.d.ts.map +1 -0
- package/src/avif/native/av1/decode/decode_tile.js +1213 -0
- package/src/avif/native/av1/decode/get_scan.d.ts +16 -0
- package/src/avif/native/av1/decode/get_scan.d.ts.map +1 -0
- package/src/avif/native/av1/decode/get_scan.js +138 -0
- package/src/avif/native/av1/decode/read_lr.d.ts +35 -0
- package/src/avif/native/av1/decode/read_lr.d.ts.map +1 -0
- package/src/avif/native/av1/decode/read_lr.js +277 -0
- package/src/avif/native/av1/decode/read_transform_type.d.ts +19 -0
- package/src/avif/native/av1/decode/read_transform_type.d.ts.map +1 -0
- package/src/avif/native/av1/decode/read_transform_type.js +92 -0
- package/src/avif/native/av1/decode/reconstruct.d.ts +36 -0
- package/src/avif/native/av1/decode/reconstruct.d.ts.map +1 -0
- package/src/avif/native/av1/decode/reconstruct.js +241 -0
- package/src/avif/native/av1/decode/transform_type.d.ts +49 -0
- package/src/avif/native/av1/decode/transform_type.d.ts.map +1 -0
- package/src/avif/native/av1/decode/transform_type.js +137 -0
- package/src/avif/native/av1/encode/Av1BlockSnapshot.d.ts +81 -0
- package/src/avif/native/av1/encode/Av1BlockSnapshot.d.ts.map +1 -0
- package/src/avif/native/av1/encode/Av1BlockSnapshot.js +299 -0
- package/src/avif/native/av1/encode/Av1EncodeContext.d.ts +384 -0
- package/src/avif/native/av1/encode/Av1EncodeContext.d.ts.map +1 -0
- package/src/avif/native/av1/encode/Av1EncodeContext.js +590 -0
- package/src/avif/native/av1/encode/build_headers.d.ts +50 -0
- package/src/avif/native/av1/encode/build_headers.d.ts.map +1 -0
- package/src/avif/native/av1/encode/build_headers.js +359 -0
- package/src/avif/native/av1/encode/choose_chroma_alpha.d.ts +26 -0
- package/src/avif/native/av1/encode/choose_chroma_alpha.d.ts.map +1 -0
- package/src/avif/native/av1/encode/choose_chroma_alpha.js +90 -0
- package/src/avif/native/av1/encode/choose_filters.d.ts +21 -0
- package/src/avif/native/av1/encode/choose_filters.d.ts.map +1 -0
- package/src/avif/native/av1/encode/choose_filters.js +318 -0
- package/src/avif/native/av1/encode/choose_restoration.d.ts +35 -0
- package/src/avif/native/av1/encode/choose_restoration.d.ts.map +1 -0
- package/src/avif/native/av1/encode/choose_restoration.js +616 -0
- package/src/avif/native/av1/encode/decode_effort.d.ts +57 -0
- package/src/avif/native/av1/encode/decode_effort.d.ts.map +1 -0
- package/src/avif/native/av1/encode/decode_effort.js +59 -0
- package/src/avif/native/av1/encode/encode_av1_still.d.ts +17 -0
- package/src/avif/native/av1/encode/encode_av1_still.d.ts.map +1 -0
- package/src/avif/native/av1/encode/encode_av1_still.js +194 -0
- package/src/avif/native/av1/encode/encode_effort.d.ts +56 -0
- package/src/avif/native/av1/encode/encode_effort.d.ts.map +1 -0
- package/src/avif/native/av1/encode/encode_effort.js +97 -0
- package/src/avif/native/av1/encode/encode_tile.d.ts +26 -0
- package/src/avif/native/av1/encode/encode_tile.d.ts.map +1 -0
- package/src/avif/native/av1/encode/encode_tile.js +2331 -0
- package/src/avif/native/av1/encode/quantise.d.ts +51 -0
- package/src/avif/native/av1/encode/quantise.d.ts.map +1 -0
- package/src/avif/native/av1/encode/quantise.js +212 -0
- package/src/avif/native/av1/encode/write_coefficients.d.ts +42 -0
- package/src/avif/native/av1/encode/write_coefficients.d.ts.map +1 -0
- package/src/avif/native/av1/encode/write_coefficients.js +367 -0
- package/src/avif/native/av1/encode/write_lr.d.ts +33 -0
- package/src/avif/native/av1/encode/write_lr.d.ts.map +1 -0
- package/src/avif/native/av1/encode/write_lr.js +174 -0
- package/src/avif/native/av1/encode/write_transform_type.d.ts +21 -0
- package/src/avif/native/av1/encode/write_transform_type.d.ts.map +1 -0
- package/src/avif/native/av1/encode/write_transform_type.js +70 -0
- package/src/avif/native/av1/entropy/CdfContext.d.ts +167 -0
- package/src/avif/native/av1/entropy/CdfContext.d.ts.map +1 -0
- package/src/avif/native/av1/entropy/CdfContext.js +433 -0
- package/src/avif/native/av1/entropy/SymbolReader.d.ts +103 -0
- package/src/avif/native/av1/entropy/SymbolReader.d.ts.map +1 -0
- package/src/avif/native/av1/entropy/SymbolReader.js +317 -0
- package/src/avif/native/av1/entropy/SymbolWriter.d.ts +107 -0
- package/src/avif/native/av1/entropy/SymbolWriter.d.ts.map +1 -0
- package/src/avif/native/av1/entropy/SymbolWriter.js +352 -0
- package/src/avif/native/av1/entropy/coefficient_context.d.ts +71 -0
- package/src/avif/native/av1/entropy/coefficient_context.d.ts.map +1 -0
- package/src/avif/native/av1/entropy/coefficient_context.js +325 -0
- package/src/avif/native/av1/entropy/partition_cdf.d.ts +54 -0
- package/src/avif/native/av1/entropy/partition_cdf.d.ts.map +1 -0
- package/src/avif/native/av1/entropy/partition_cdf.js +98 -0
- package/src/avif/native/av1/entropy/symbol_cost.d.ts +37 -0
- package/src/avif/native/av1/entropy/symbol_cost.d.ts.map +1 -0
- package/src/avif/native/av1/entropy/symbol_cost.js +76 -0
- package/src/avif/native/av1/filter/cdef.d.ts +15 -0
- package/src/avif/native/av1/filter/cdef.d.ts.map +1 -0
- package/src/avif/native/av1/filter/cdef.js +313 -0
- package/src/avif/native/av1/filter/loop_filter.d.ts +14 -0
- package/src/avif/native/av1/filter/loop_filter.d.ts.map +1 -0
- package/src/avif/native/av1/filter/loop_filter.js +377 -0
- package/src/avif/native/av1/filter/loop_restoration.d.ts +42 -0
- package/src/avif/native/av1/filter/loop_restoration.d.ts.map +1 -0
- package/src/avif/native/av1/filter/loop_restoration.js +451 -0
- package/src/avif/native/av1/filter/superres.d.ts +18 -0
- package/src/avif/native/av1/filter/superres.d.ts.map +1 -0
- package/src/avif/native/av1/filter/superres.js +86 -0
- package/src/avif/native/av1/grain/FilmGrainState.d.ts +62 -0
- package/src/avif/native/av1/grain/FilmGrainState.d.ts.map +1 -0
- package/src/avif/native/av1/grain/FilmGrainState.js +87 -0
- package/src/avif/native/av1/grain/film_grain.d.ts +16 -0
- package/src/avif/native/av1/grain/film_grain.d.ts.map +1 -0
- package/src/avif/native/av1/grain/film_grain.js +623 -0
- package/src/avif/native/av1/obu/FrameHeader.d.ts +248 -0
- package/src/avif/native/av1/obu/FrameHeader.d.ts.map +1 -0
- package/src/avif/native/av1/obu/FrameHeader.js +381 -0
- package/src/avif/native/av1/obu/ObuHeader.d.ts +53 -0
- package/src/avif/native/av1/obu/ObuHeader.d.ts.map +1 -0
- package/src/avif/native/av1/obu/ObuHeader.js +62 -0
- package/src/avif/native/av1/obu/SequenceHeader.d.ts +157 -0
- package/src/avif/native/av1/obu/SequenceHeader.d.ts.map +1 -0
- package/src/avif/native/av1/obu/SequenceHeader.js +225 -0
- package/src/avif/native/av1/obu/for_each_obu.d.ts +24 -0
- package/src/avif/native/av1/obu/for_each_obu.d.ts.map +1 -0
- package/src/avif/native/av1/obu/for_each_obu.js +50 -0
- package/src/avif/native/av1/obu/parse_frame_header.d.ts +18 -0
- package/src/avif/native/av1/obu/parse_frame_header.d.ts.map +1 -0
- package/src/avif/native/av1/obu/parse_frame_header.js +841 -0
- package/src/avif/native/av1/obu/parse_obu_header.d.ts +23 -0
- package/src/avif/native/av1/obu/parse_obu_header.d.ts.map +1 -0
- package/src/avif/native/av1/obu/parse_obu_header.js +69 -0
- package/src/avif/native/av1/obu/parse_sequence_header.d.ts +17 -0
- package/src/avif/native/av1/obu/parse_sequence_header.d.ts.map +1 -0
- package/src/avif/native/av1/obu/parse_sequence_header.js +263 -0
- package/src/avif/native/av1/obu/write_frame_header.d.ts +16 -0
- package/src/avif/native/av1/obu/write_frame_header.d.ts.map +1 -0
- package/src/avif/native/av1/obu/write_frame_header.js +579 -0
- package/src/avif/native/av1/obu/write_sequence_header.d.ts +17 -0
- package/src/avif/native/av1/obu/write_sequence_header.d.ts.map +1 -0
- package/src/avif/native/av1/obu/write_sequence_header.js +189 -0
- package/src/avif/native/av1/predict/IntraPredictionState.d.ts +49 -0
- package/src/avif/native/av1/predict/IntraPredictionState.d.ts.map +1 -0
- package/src/avif/native/av1/predict/IntraPredictionState.js +58 -0
- package/src/avif/native/av1/predict/intra_filter_type.d.ts +17 -0
- package/src/avif/native/av1/predict/intra_filter_type.d.ts.map +1 -0
- package/src/avif/native/av1/predict/intra_filter_type.js +79 -0
- package/src/avif/native/av1/predict/predict_chroma_from_luma.d.ts +50 -0
- package/src/avif/native/av1/predict/predict_chroma_from_luma.d.ts.map +1 -0
- package/src/avif/native/av1/predict/predict_chroma_from_luma.js +153 -0
- package/src/avif/native/av1/predict/predict_intra.d.ts +42 -0
- package/src/avif/native/av1/predict/predict_intra.d.ts.map +1 -0
- package/src/avif/native/av1/predict/predict_intra.js +803 -0
- package/src/avif/native/av1/predict/predict_palette.d.ts +23 -0
- package/src/avif/native/av1/predict/predict_palette.d.ts.map +1 -0
- package/src/avif/native/av1/predict/predict_palette.js +36 -0
- package/src/avif/native/av1/tables/av1_symbols.d.ts +411 -0
- package/src/avif/native/av1/tables/av1_symbols.d.ts.map +1 -0
- package/src/avif/native/av1/tables/av1_symbols.js +419 -0
- package/src/avif/native/av1/tables/block_tables.d.ts +181 -0
- package/src/avif/native/av1/tables/block_tables.d.ts.map +1 -0
- package/src/avif/native/av1/tables/block_tables.js +270 -0
- package/src/avif/native/av1/tables/cdf_tables.d.ts +961 -0
- package/src/avif/native/av1/tables/cdf_tables.d.ts.map +1 -0
- package/src/avif/native/av1/tables/cdf_tables.js +1893 -0
- package/src/avif/native/av1/tables/coefficient_tables.d.ts +41 -0
- package/src/avif/native/av1/tables/coefficient_tables.d.ts.map +1 -0
- package/src/avif/native/av1/tables/coefficient_tables.js +82 -0
- package/src/avif/native/av1/tables/derived_block_tables.d.ts +15 -0
- package/src/avif/native/av1/tables/derived_block_tables.d.ts.map +1 -0
- package/src/avif/native/av1/tables/derived_block_tables.js +23 -0
- package/src/avif/native/av1/tables/filter_tables.d.ts +171 -0
- package/src/avif/native/av1/tables/filter_tables.d.ts.map +1 -0
- package/src/avif/native/av1/tables/filter_tables.js +313 -0
- package/src/avif/native/av1/tables/grain_tables.d.ts +11 -0
- package/src/avif/native/av1/tables/grain_tables.d.ts.map +1 -0
- package/src/avif/native/av1/tables/grain_tables.js +190 -0
- package/src/avif/native/av1/tables/prediction_tables.d.ts +141 -0
- package/src/avif/native/av1/tables/prediction_tables.d.ts.map +1 -0
- package/src/avif/native/av1/tables/prediction_tables.js +223 -0
- package/src/avif/native/av1/tables/quantizer_matrix_tables.d.ts +21 -0
- package/src/avif/native/av1/tables/quantizer_matrix_tables.d.ts.map +1 -0
- package/src/avif/native/av1/tables/quantizer_matrix_tables.js +38 -0
- package/src/avif/native/av1/tables/quantizer_tables.d.ts +21 -0
- package/src/avif/native/av1/tables/quantizer_tables.d.ts.map +1 -0
- package/src/avif/native/av1/tables/quantizer_tables.js +160 -0
- package/src/avif/native/av1/tables/scan_tables.d.ts +321 -0
- package/src/avif/native/av1/tables/scan_tables.d.ts.map +1 -0
- package/src/avif/native/av1/tables/scan_tables.js +727 -0
- package/src/avif/native/av1/tables/segmentation_tables.d.ts +31 -0
- package/src/avif/native/av1/tables/segmentation_tables.d.ts.map +1 -0
- package/src/avif/native/av1/tables/segmentation_tables.js +48 -0
- package/src/avif/native/av1/tables/transform_tables.d.ts +91 -0
- package/src/avif/native/av1/tables/transform_tables.d.ts.map +1 -0
- package/src/avif/native/av1/tables/transform_tables.js +140 -0
- package/src/avif/native/av1/tables/unpack_table.d.ts +33 -0
- package/src/avif/native/av1/tables/unpack_table.d.ts.map +1 -0
- package/src/avif/native/av1/tables/unpack_table.js +73 -0
- package/src/avif/native/av1/transform/forward_transform_2d.d.ts +61 -0
- package/src/avif/native/av1/transform/forward_transform_2d.d.ts.map +1 -0
- package/src/avif/native/av1/transform/forward_transform_2d.js +574 -0
- package/src/avif/native/av1/transform/inverse_transform_1d.d.ts +76 -0
- package/src/avif/native/av1/transform/inverse_transform_1d.d.ts.map +1 -0
- package/src/avif/native/av1/transform/inverse_transform_1d.js +590 -0
- package/src/avif/native/av1/transform/inverse_transform_2d.d.ts +46 -0
- package/src/avif/native/av1/transform/inverse_transform_2d.d.ts.map +1 -0
- package/src/avif/native/av1/transform/inverse_transform_2d.js +180 -0
- package/src/avif/native/av1/util/ceil_log2.d.ts +13 -0
- package/src/avif/native/av1/util/ceil_log2.d.ts.map +1 -0
- package/src/avif/native/av1/util/ceil_log2.js +18 -0
- package/src/avif/native/av1/util/floor_log2.d.ts +20 -0
- package/src/avif/native/av1/util/floor_log2.d.ts.map +1 -0
- package/src/avif/native/av1/util/floor_log2.js +20 -0
- package/src/avif/native/av1/util/round2.d.ts +22 -0
- package/src/avif/native/av1/util/round2.d.ts.map +1 -0
- package/src/avif/native/av1/util/round2.js +27 -0
- package/src/avif/native/av1/util/round2_signed.d.ts +14 -0
- package/src/avif/native/av1/util/round2_signed.d.ts.map +1 -0
- package/src/avif/native/av1/util/round2_signed.js +17 -0
- package/src/avif/native/av1/util/tile_log2.d.ts +16 -0
- package/src/avif/native/av1/util/tile_log2.d.ts.map +1 -0
- package/src/avif/native/av1/util/tile_log2.js +23 -0
- package/src/avif/native/bitstream/BitReader.d.ts +174 -0
- package/src/avif/native/bitstream/BitReader.d.ts.map +1 -0
- package/src/avif/native/bitstream/BitReader.js +391 -0
- package/src/avif/native/bitstream/BitWriter.d.ts +122 -0
- package/src/avif/native/bitstream/BitWriter.d.ts.map +1 -0
- package/src/avif/native/bitstream/BitWriter.js +351 -0
- package/src/avif/native/color/ColourTransform.d.ts +62 -0
- package/src/avif/native/color/ColourTransform.d.ts.map +1 -0
- package/src/avif/native/color/ColourTransform.js +189 -0
- package/src/avif/native/color/YuvImage.d.ts +51 -0
- package/src/avif/native/color/YuvImage.d.ts.map +1 -0
- package/src/avif/native/color/YuvImage.js +66 -0
- package/src/avif/native/color/clamp_sample.d.ts +18 -0
- package/src/avif/native/color/clamp_sample.d.ts.map +1 -0
- package/src/avif/native/color/clamp_sample.js +25 -0
- package/src/avif/native/color/linear_to_transfer.d.ts +24 -0
- package/src/avif/native/color/linear_to_transfer.d.ts.map +1 -0
- package/src/avif/native/color/linear_to_transfer.js +126 -0
- package/src/avif/native/color/primaries.d.ts +20 -0
- package/src/avif/native/color/primaries.d.ts.map +1 -0
- package/src/avif/native/color/primaries.js +94 -0
- package/src/avif/native/color/rgb_row_to_ycbcr.d.ts +20 -0
- package/src/avif/native/color/rgb_row_to_ycbcr.d.ts.map +1 -0
- package/src/avif/native/color/rgb_row_to_ycbcr.js +78 -0
- package/src/avif/native/color/transfer_to_linear.d.ts +49 -0
- package/src/avif/native/color/transfer_to_linear.d.ts.map +1 -0
- package/src/avif/native/color/transfer_to_linear.js +171 -0
- package/src/avif/native/color/upsample_chroma_row.d.ts +35 -0
- package/src/avif/native/color/upsample_chroma_row.d.ts.map +1 -0
- package/src/avif/native/color/upsample_chroma_row.js +89 -0
- package/src/avif/native/color/ycbcr_row_to_rgb.d.ts +23 -0
- package/src/avif/native/color/ycbcr_row_to_rgb.d.ts.map +1 -0
- package/src/avif/native/color/ycbcr_row_to_rgb.js +78 -0
- package/src/avif/native/heif/AvifFile.d.ts +112 -0
- package/src/avif/native/heif/AvifFile.d.ts.map +1 -0
- package/src/avif/native/heif/AvifFile.js +142 -0
- package/src/avif/native/heif/ItemProperty.d.ts +244 -0
- package/src/avif/native/heif/ItemProperty.d.ts.map +1 -0
- package/src/avif/native/heif/ItemProperty.js +321 -0
- package/src/avif/native/heif/find_item_property.d.ts +17 -0
- package/src/avif/native/heif/find_item_property.d.ts.map +1 -0
- package/src/avif/native/heif/find_item_property.js +28 -0
- package/src/avif/native/heif/find_item_references.d.ts +16 -0
- package/src/avif/native/heif/find_item_references.d.ts.map +1 -0
- package/src/avif/native/heif/find_item_references.js +29 -0
- package/src/avif/native/heif/find_items_referencing.d.ts +17 -0
- package/src/avif/native/heif/find_items_referencing.d.ts.map +1 -0
- package/src/avif/native/heif/find_items_referencing.js +36 -0
- package/src/avif/native/heif/parse_avif_file.d.ts +15 -0
- package/src/avif/native/heif/parse_avif_file.d.ts.map +1 -0
- package/src/avif/native/heif/parse_avif_file.js +383 -0
- package/src/avif/native/heif/parse_image_grid.d.ts +18 -0
- package/src/avif/native/heif/parse_image_grid.d.ts.map +1 -0
- package/src/avif/native/heif/parse_image_grid.js +48 -0
- package/src/avif/native/heif/parse_item_property.d.ts +18 -0
- package/src/avif/native/heif/parse_item_property.d.ts.map +1 -0
- package/src/avif/native/heif/parse_item_property.js +243 -0
- package/src/avif/native/heif/read_item_data.d.ts +22 -0
- package/src/avif/native/heif/read_item_data.d.ts.map +1 -0
- package/src/avif/native/heif/read_item_data.js +97 -0
- package/src/avif/native/heif/write_avif_file.d.ts +50 -0
- package/src/avif/native/heif/write_avif_file.d.ts.map +1 -0
- package/src/avif/native/heif/write_avif_file.js +348 -0
- package/src/avif/native/heif/write_item_property.d.ts +16 -0
- package/src/avif/native/heif/write_item_property.d.ts.map +1 -0
- package/src/avif/native/heif/write_item_property.js +182 -0
- package/src/avif/native/index.d.ts +14 -0
- package/src/avif/native/index.d.ts.map +1 -0
- package/src/avif/native/index.js +56 -0
- package/src/avif/native/isobmff/BoxHeader.d.ts +50 -0
- package/src/avif/native/isobmff/BoxHeader.d.ts.map +1 -0
- package/src/avif/native/isobmff/BoxHeader.js +57 -0
- package/src/avif/native/isobmff/BoxWriter.d.ts +118 -0
- package/src/avif/native/isobmff/BoxWriter.d.ts.map +1 -0
- package/src/avif/native/isobmff/BoxWriter.js +264 -0
- package/src/avif/native/isobmff/ByteCursor.d.ts +125 -0
- package/src/avif/native/isobmff/ByteCursor.d.ts.map +1 -0
- package/src/avif/native/isobmff/ByteCursor.js +298 -0
- package/src/avif/native/isobmff/for_each_box.d.ts +23 -0
- package/src/avif/native/isobmff/for_each_box.d.ts.map +1 -0
- package/src/avif/native/isobmff/for_each_box.js +39 -0
- package/src/avif/native/isobmff/read_box_header.d.ts +22 -0
- package/src/avif/native/isobmff/read_box_header.d.ts.map +1 -0
- package/src/avif/native/isobmff/read_box_header.js +59 -0
- package/src/avif/native/isobmff/read_full_box_header.d.ts +26 -0
- package/src/avif/native/isobmff/read_full_box_header.d.ts.map +1 -0
- package/src/avif/native/isobmff/read_full_box_header.js +33 -0
- package/src/avif/threaded_image_encoder.d.ts +7 -2
- package/src/avif/threaded_image_encoder.d.ts.map +1 -1
- package/src/avif/threaded_image_encoder.js +107 -21
- package/src/core/binary/BinaryBuffer.d.ts +40 -0
- package/src/core/binary/BinaryBuffer.d.ts.map +1 -1
- package/src/core/binary/BinaryBuffer.js +100 -0
- package/src/core/binary/hash/crc32.d.ts +16 -0
- package/src/core/binary/hash/crc32.d.ts.map +1 -0
- package/src/core/binary/hash/crc32.js +96 -0
- package/src/core/binary/utf8/utf8_decode.d.ts +21 -0
- package/src/core/binary/utf8/utf8_decode.d.ts.map +1 -0
- package/src/core/binary/utf8/utf8_decode.js +43 -0
- package/src/core/binary/utf8/utf8_encode_into.d.ts +20 -0
- package/src/core/binary/utf8/utf8_encode_into.d.ts.map +1 -0
- package/src/core/binary/utf8/utf8_encode_into.js +48 -0
- package/src/core/binary/utf8/utf8_encoded_length.d.ts +14 -0
- package/src/core/binary/utf8/utf8_encoded_length.d.ts.map +1 -0
- package/src/core/binary/utf8/utf8_encoded_length.js +36 -0
- package/src/core/color/COLOR_FIX_PLAN_2026_08_28.md +417 -0
- package/src/core/color/COLOR_REVIEW_2026_08_28.md +779 -0
- package/src/core/color/Color.d.ts +128 -20
- package/src/core/color/Color.d.ts.map +1 -1
- package/src/core/color/Color.js +959 -866
- package/src/core/color/PQ/PQ_constants.d.ts +10 -0
- package/src/core/color/PQ/PQ_constants.d.ts.map +1 -1
- package/src/core/color/PQ/PQ_constants.js +10 -0
- package/src/core/color/PQ/linear_to_PQ.d.ts.map +1 -1
- package/src/core/color/PQ/linear_to_PQ.js +6 -0
- package/src/core/color/REC709_PRIMARIES.d.ts +50 -0
- package/src/core/color/REC709_PRIMARIES.d.ts.map +1 -0
- package/src/core/color/REC709_PRIMARIES.js +53 -0
- package/src/core/color/construct/color_from_hex.d.ts +21 -0
- package/src/core/color/construct/color_from_hex.d.ts.map +1 -0
- package/src/core/color/construct/color_from_hex.js +33 -0
- package/src/core/color/construct/color_from_temperature.d.ts +22 -0
- package/src/core/color/construct/color_from_temperature.d.ts.map +1 -0
- package/src/core/color/construct/color_from_temperature.js +33 -0
- package/src/core/color/construct/color_from_uint24.d.ts +19 -0
- package/src/core/color/construct/color_from_uint24.d.ts.map +1 -0
- package/src/core/color/construct/color_from_uint24.js +26 -0
- package/src/core/color/construct/color_from_uint32.d.ts +20 -0
- package/src/core/color/construct/color_from_uint32.d.ts.map +1 -0
- package/src/core/color/construct/color_from_uint32.js +39 -0
- package/src/core/color/construct/color_gray.d.ts +17 -0
- package/src/core/color/construct/color_gray.d.ts.map +1 -0
- package/src/core/color/construct/color_gray.js +22 -0
- package/src/core/color/hex/hex2rgb.d.ts +1 -11
- package/src/core/color/hex/hex2rgb.d.ts.map +1 -1
- package/src/core/color/hex/hex2rgb.js +7 -24
- package/src/core/color/hex/hex_to_rgb.d.ts +18 -0
- package/src/core/color/hex/hex_to_rgb.d.ts.map +1 -0
- package/src/core/color/hex/hex_to_rgb.js +62 -0
- package/src/core/color/hex/rgb2hex.d.ts +1 -8
- package/src/core/color/hex/rgb2hex.d.ts.map +1 -1
- package/src/core/color/hex/rgb2hex.js +7 -13
- package/src/core/color/hex/rgb_to_hex.d.ts +9 -0
- package/src/core/color/hex/rgb_to_hex.d.ts.map +1 -0
- package/src/core/color/hex/rgb_to_hex.js +13 -0
- package/src/core/color/hex/rgba_to_hex.d.ts +19 -0
- package/src/core/color/hex/rgba_to_hex.d.ts.map +1 -0
- package/src/core/color/hex/rgba_to_hex.js +23 -0
- package/src/core/color/hsv/hsv2rgb.d.ts +1 -12
- package/src/core/color/hsv/hsv2rgb.d.ts.map +1 -1
- package/src/core/color/hsv/hsv2rgb.js +8 -21
- package/src/core/color/hsv/hsv2rgb_float.d.ts +1 -12
- package/src/core/color/hsv/hsv2rgb_float.d.ts.map +1 -1
- package/src/core/color/hsv/hsv2rgb_float.js +8 -56
- package/src/core/color/hsv/hsv_to_rgb.d.ts +13 -0
- package/src/core/color/hsv/hsv_to_rgb.d.ts.map +1 -0
- package/src/core/color/hsv/hsv_to_rgb.js +39 -0
- package/src/core/color/hsv/hsv_to_rgb_uint8.d.ts +13 -0
- package/src/core/color/hsv/hsv_to_rgb_uint8.d.ts.map +1 -0
- package/src/core/color/hsv/hsv_to_rgb_uint8.js +21 -0
- package/src/core/color/hsv/hue_to_rgb_sextant.d.ts +24 -0
- package/src/core/color/hsv/hue_to_rgb_sextant.d.ts.map +1 -0
- package/src/core/color/hsv/hue_to_rgb_sextant.js +67 -0
- package/src/core/color/hsv/rgb2hsv.d.ts +1 -12
- package/src/core/color/hsv/rgb2hsv.d.ts.map +1 -1
- package/src/core/color/hsv/rgb2hsv.js +7 -42
- package/src/core/color/hsv/rgb_to_hsv.d.ts +13 -0
- package/src/core/color/hsv/rgb_to_hsv.d.ts.map +1 -0
- package/src/core/color/hsv/rgb_to_hsv.js +42 -0
- package/src/core/color/hunt/hpe_to_xyz.d.ts +2 -0
- package/src/core/color/hunt/hpe_to_xyz.d.ts.map +1 -0
- package/src/core/color/hunt/hpe_to_xyz.js +4 -0
- package/src/core/color/hunt/xyz_to_hpe.d.ts +1 -6
- package/src/core/color/hunt/xyz_to_hpe.d.ts.map +1 -1
- package/src/core/color/hunt/xyz_to_hpe.js +12 -15
- package/src/core/color/illuminant/D65_spd_tabulated.d.ts.map +1 -1
- package/src/core/color/illuminant/D65_spd_tabulated.js +107 -104
- package/src/core/color/int2rgb.d.ts +1 -10
- package/src/core/color/int2rgb.d.ts.map +1 -1
- package/src/core/color/int2rgb.js +7 -12
- package/src/core/color/int_to_rgb.d.ts +21 -0
- package/src/core/color/int_to_rgb.d.ts.map +1 -0
- package/src/core/color/int_to_rgb.js +33 -0
- package/src/core/color/kelvin/kelvin_to_rgb.d.ts.map +1 -1
- package/src/core/color/kelvin/kelvin_to_rgb.js +22 -2
- package/src/core/color/kelvin/rgb_to_kelvin.d.ts +22 -2
- package/src/core/color/kelvin/rgb_to_kelvin.d.ts.map +1 -1
- package/src/core/color/kelvin/rgb_to_kelvin.js +22 -2
- package/src/core/color/lab/lab_to_xyz.d.ts +20 -0
- package/src/core/color/lab/lab_to_xyz.d.ts.map +1 -0
- package/src/core/color/lab/lab_to_xyz.js +69 -0
- package/src/core/color/oklab/gamut_clip_adaptive_l0.d.ts +34 -0
- package/src/core/color/oklab/gamut_clip_adaptive_l0.d.ts.map +1 -0
- package/src/core/color/oklab/gamut_clip_adaptive_l0.js +46 -0
- package/src/core/color/oklab/gamut_clip_at_l0.d.ts +33 -0
- package/src/core/color/oklab/gamut_clip_at_l0.d.ts.map +1 -0
- package/src/core/color/oklab/gamut_clip_at_l0.js +84 -0
- package/src/core/color/oklab/gamut_clip_preserve_chroma.d.ts +24 -0
- package/src/core/color/oklab/gamut_clip_preserve_chroma.d.ts.map +1 -0
- package/src/core/color/oklab/gamut_clip_preserve_chroma.js +37 -0
- package/src/core/color/oklab/gamut_clip_project_to_lcusp.d.ts +23 -0
- package/src/core/color/oklab/gamut_clip_project_to_lcusp.d.ts.map +1 -0
- package/src/core/color/oklab/gamut_clip_project_to_lcusp.js +46 -0
- package/src/core/color/oklab/linear_srgb_to_okhsl.d.ts +19 -0
- package/src/core/color/oklab/linear_srgb_to_okhsl.d.ts.map +1 -0
- package/src/core/color/oklab/linear_srgb_to_okhsl.js +93 -0
- package/src/core/color/oklab/linear_srgb_to_okhsv.d.ts +12 -4
- package/src/core/color/oklab/linear_srgb_to_okhsv.d.ts.map +1 -1
- package/src/core/color/oklab/linear_srgb_to_okhsv.js +108 -84
- package/src/core/color/oklab/linear_srgb_to_oklab.d.ts +13 -4
- package/src/core/color/oklab/linear_srgb_to_oklab.d.ts.map +1 -1
- package/src/core/color/oklab/linear_srgb_to_oklab.js +28 -19
- package/src/core/color/oklab/okhsl_chroma_bounds.d.ts +51 -0
- package/src/core/color/oklab/okhsl_chroma_bounds.d.ts.map +1 -0
- package/src/core/color/oklab/okhsl_chroma_bounds.js +128 -0
- package/src/core/color/oklab/okhsl_to_linear_srgb.d.ts +19 -0
- package/src/core/color/oklab/okhsl_to_linear_srgb.d.ts.map +1 -0
- package/src/core/color/oklab/okhsl_to_linear_srgb.js +85 -0
- package/src/core/color/oklab/oklab_to_oklch.d.ts +24 -0
- package/src/core/color/oklab/oklab_to_oklch.d.ts.map +1 -0
- package/src/core/color/oklab/oklab_to_oklch.js +35 -0
- package/src/core/color/oklab/oklch_to_oklab.d.ts +18 -0
- package/src/core/color/oklab/oklch_to_oklab.d.ts.map +1 -0
- package/src/core/color/oklab/oklch_to_oklab.js +26 -0
- package/src/core/color/oklab/toe.d.ts +12 -0
- package/src/core/color/oklab/toe.d.ts.map +1 -1
- package/src/core/color/oklab/toe.js +34 -22
- package/src/core/color/operations/color_add.d.ts +21 -0
- package/src/core/color/operations/color_add.d.ts.map +1 -0
- package/src/core/color/operations/color_add.js +23 -0
- package/src/core/color/operations/color_add_scaled.d.ts +20 -0
- package/src/core/color/operations/color_add_scaled.d.ts.map +1 -0
- package/src/core/color/operations/color_add_scaled.js +30 -0
- package/src/core/color/operations/color_clamp.d.ts +22 -0
- package/src/core/color/operations/color_clamp.d.ts.map +1 -0
- package/src/core/color/operations/color_clamp.js +25 -0
- package/src/core/color/operations/color_darken.d.ts +2 -1
- package/src/core/color/operations/color_darken.d.ts.map +1 -1
- package/src/core/color/operations/color_darken.js +25 -24
- package/src/core/color/operations/color_desaturate.d.ts +2 -1
- package/src/core/color/operations/color_desaturate.d.ts.map +1 -1
- package/src/core/color/operations/color_desaturate.js +25 -24
- package/src/core/color/operations/color_get_hsl.d.ts +18 -0
- package/src/core/color/operations/color_get_hsl.d.ts.map +1 -0
- package/src/core/color/operations/color_get_hsl.js +60 -0
- package/src/core/color/operations/color_is_in_gamut.d.ts +20 -0
- package/src/core/color/operations/color_is_in_gamut.d.ts.map +1 -0
- package/src/core/color/operations/color_is_in_gamut.js +30 -0
- package/src/core/color/operations/color_lerp.d.ts +12 -3
- package/src/core/color/operations/color_lerp.d.ts.map +1 -1
- package/src/core/color/operations/color_lerp.js +55 -46
- package/src/core/color/operations/color_lighten.d.ts +9 -5
- package/src/core/color/operations/color_lighten.d.ts.map +1 -1
- package/src/core/color/operations/color_lighten.js +29 -24
- package/src/core/color/operations/color_mix_okhsv_channel.d.ts +24 -0
- package/src/core/color/operations/color_mix_okhsv_channel.d.ts.map +1 -0
- package/src/core/color/operations/color_mix_okhsv_channel.js +48 -0
- package/src/core/color/operations/color_multiply_rgb.d.ts +21 -0
- package/src/core/color/operations/color_multiply_rgb.d.ts.map +1 -0
- package/src/core/color/operations/color_multiply_rgb.js +26 -0
- package/src/core/color/operations/color_saturate.d.ts +9 -5
- package/src/core/color/operations/color_saturate.d.ts.map +1 -1
- package/src/core/color/operations/color_saturate.js +29 -24
- package/src/core/color/operations/color_scale_okhsv_channel.d.ts +1 -1
- package/src/core/color/operations/color_scale_okhsv_channel.d.ts.map +1 -1
- package/src/core/color/operations/color_scale_okhsv_channel.js +58 -45
- package/src/core/color/operations/color_srgb_apply.d.ts +28 -0
- package/src/core/color/operations/color_srgb_apply.d.ts.map +1 -0
- package/src/core/color/operations/color_srgb_apply.js +40 -0
- package/src/core/color/operations/color_sub.d.ts +16 -0
- package/src/core/color/operations/color_sub.d.ts.map +1 -0
- package/src/core/color/operations/color_sub.js +18 -0
- package/src/core/color/parse_color.d.ts +10 -1
- package/src/core/color/parse_color.d.ts.map +1 -1
- package/src/core/color/parse_color.js +100 -84
- package/src/core/color/parse_color_normalized.d.ts +24 -0
- package/src/core/color/parse_color_normalized.d.ts.map +1 -0
- package/src/core/color/parse_color_normalized.js +38 -0
- package/src/core/color/rgb2uint24.d.ts +1 -8
- package/src/core/color/rgb2uint24.d.ts.map +1 -1
- package/src/core/color/rgb2uint24.js +7 -20
- package/src/core/color/rgb2uint32.d.ts +1 -9
- package/src/core/color/rgb2uint32.d.ts.map +1 -1
- package/src/core/color/rgb2uint32.js +7 -18
- package/src/core/color/rgb_to_luminance.d.ts +14 -5
- package/src/core/color/rgb_to_luminance.d.ts.map +1 -1
- package/src/core/color/rgb_to_luminance.js +24 -13
- package/src/core/color/rgb_to_uint24.d.ts +9 -0
- package/src/core/color/rgb_to_uint24.d.ts.map +1 -0
- package/src/core/color/rgb_to_uint24.js +20 -0
- package/src/core/color/rgb_to_uint32.d.ts +10 -0
- package/src/core/color/rgb_to_uint32.d.ts.map +1 -0
- package/src/core/color/rgb_to_uint32.js +21 -0
- package/src/core/color/sRGB/linear_to_sRGB.d.ts +7 -0
- package/src/core/color/sRGB/linear_to_sRGB.d.ts.map +1 -1
- package/src/core/color/sRGB/linear_to_sRGB.js +39 -32
- package/src/core/color/xyz/hpe_to_xyz.d.ts +24 -0
- package/src/core/color/xyz/hpe_to_xyz.d.ts.map +1 -0
- package/src/core/color/xyz/hpe_to_xyz.js +39 -0
- package/src/core/color/xyz/rgb_to_xyz.d.ts +17 -2
- package/src/core/color/xyz/rgb_to_xyz.d.ts.map +1 -1
- package/src/core/color/xyz/rgb_to_xyz.js +44 -18
- package/src/core/color/xyz/xyz_cmf_tabulated.d.ts.map +1 -1
- package/src/core/color/xyz/xyz_cmf_tabulated.js +217 -211
- package/src/core/color/xyz/xyz_cmf_wyman.d.ts +4 -0
- package/src/core/color/xyz/xyz_cmf_wyman.d.ts.map +1 -1
- package/src/core/color/xyz/xyz_cmf_wyman.js +74 -65
- package/src/core/color/xyz/xyz_to_hpe.d.ts +18 -0
- package/src/core/color/xyz/xyz_to_hpe.d.ts.map +1 -0
- package/src/core/color/xyz/xyz_to_hpe.js +33 -0
- package/src/core/color/xyz/xyz_to_rgb.d.ts +17 -2
- package/src/core/color/xyz/xyz_to_rgb.d.ts.map +1 -1
- package/src/core/color/xyz/xyz_to_rgb.js +39 -16
- package/src/core/color/ycxcz/xyz_to_ycxcz.d.ts +7 -2
- package/src/core/color/ycxcz/xyz_to_ycxcz.d.ts.map +1 -1
- package/src/core/color/ycxcz/xyz_to_ycxcz.js +33 -28
- package/src/core/events/signal/Signal.js +820 -820
- package/src/core/math/clamp_index.d.ts +18 -0
- package/src/core/math/clamp_index.d.ts.map +1 -0
- package/src/core/math/clamp_index.js +23 -0
- package/src/engine/asset/loaders/image/IMAGE_CODEC_REVIEW_2026_08_28.md +803 -0
- package/src/engine/asset/loaders/image/jpeg/JpegFrame.d.ts +5 -3
- package/src/engine/asset/loaders/image/jpeg/JpegFrame.d.ts.map +1 -1
- package/src/engine/asset/loaders/image/jpeg/JpegFrame.js +5 -3
- package/src/engine/asset/loaders/image/jpeg/JpegFrameComponent.d.ts +2 -1
- package/src/engine/asset/loaders/image/jpeg/JpegFrameComponent.d.ts.map +1 -1
- package/src/engine/asset/loaders/image/jpeg/JpegFrameComponent.js +2 -1
- package/src/engine/asset/loaders/image/jpeg/JpegImage.d.ts +17 -0
- package/src/engine/asset/loaders/image/jpeg/JpegImage.d.ts.map +1 -1
- package/src/engine/asset/loaders/image/jpeg/JpegImage.js +197 -157
- package/src/engine/asset/loaders/image/jpeg/idct8x8_float.d.ts +0 -1
- package/src/engine/asset/loaders/image/jpeg/idct8x8_float.d.ts.map +1 -1
- package/src/engine/asset/loaders/image/jpeg/idct8x8_float.js +0 -1
- package/src/engine/asset/loaders/image/jpeg/jpeg_component_row.d.ts +28 -0
- package/src/engine/asset/loaders/image/jpeg/jpeg_component_row.d.ts.map +1 -0
- package/src/engine/asset/loaders/image/jpeg/jpeg_component_row.js +31 -0
- package/src/engine/asset/loaders/image/png/PNG.d.ts +3 -18
- package/src/engine/asset/loaders/image/png/PNG.d.ts.map +1 -1
- package/src/engine/asset/loaders/image/png/PNG.js +3 -139
- package/src/engine/asset/loaders/image/png/PNGReader.d.ts +16 -16
- package/src/engine/asset/loaders/image/png/PNGReader.d.ts.map +1 -1
- package/src/engine/asset/loaders/image/png/PNGReader.js +143 -160
- package/src/engine/asset/loaders/image/png/chunk/png_chunk_decode_iTXt.js +3 -3
- package/src/engine/asset/loaders/image/png/chunk/png_chunk_decode_zTXt.js +3 -3
- package/src/engine/asset/loaders/image/png/crc32.d.ts +1 -15
- package/src/engine/asset/loaders/image/png/crc32.d.ts.map +1 -1
- package/src/engine/asset/loaders/image/png/crc32.js +5 -89
- package/src/engine/asset/loaders/image/png/filter/png_filter_unFilterNone.d.ts +12 -10
- package/src/engine/asset/loaders/image/png/filter/png_filter_unFilterNone.d.ts.map +1 -1
- package/src/engine/asset/loaders/image/png/filter/png_filter_unFilterNone.js +20 -45
- package/src/engine/asset/loaders/image/png/filter/png_filter_unFilterSub.d.ts +2 -1
- package/src/engine/asset/loaders/image/png/filter/png_filter_unFilterSub.d.ts.map +1 -1
- package/src/engine/asset/loaders/image/png/filter/png_filter_unFilterSub.js +2 -0
- package/src/engine/asset/loaders/image/png/filter/png_filter_unFilterUp.d.ts +2 -1
- package/src/engine/asset/loaders/image/png/filter/png_filter_unFilterUp.d.ts.map +1 -1
- package/src/engine/asset/loaders/image/png/filter/png_filter_unFilterUp.js +2 -0
- package/src/engine/asset/loaders/image/png/filter/png_unfilter.d.ts +22 -0
- package/src/engine/asset/loaders/image/png/filter/png_unfilter.d.ts.map +1 -0
- package/src/engine/asset/loaders/image/png/filter/png_unfilter.js +68 -0
- package/src/engine/asset/loaders/image/png/png_inflate.d.ts +20 -0
- package/src/engine/asset/loaders/image/png/png_inflate.d.ts.map +1 -0
- package/src/engine/asset/loaders/image/png/png_inflate.js +85 -0
- package/src/engine/asset/loaders/image/png/png_unpack_samples.d.ts +22 -0
- package/src/engine/asset/loaders/image/png/png_unpack_samples.d.ts.map +1 -0
- package/src/engine/asset/loaders/image/png/png_unpack_samples.js +38 -0
- package/src/engine/graphics/ecs/sprite/SpriteSystemPE.d.ts.map +1 -1
- package/src/engine/graphics/ecs/sprite/SpriteSystemPE.js +135 -132
- package/src/engine/graphics/particles/particular/engine/emitter/ParticleEmitter.d.ts +16 -3
- package/src/engine/graphics/particles/particular/engine/emitter/ParticleEmitter.d.ts.map +1 -1
- package/src/engine/graphics/particles/particular/engine/emitter/ParticleEmitter.js +1157 -1149
- package/src/engine/graphics/render/frame_graph/FrameGraph.js +900 -900
- package/src/engine/physics/ecs/PhysicsSystem.d.ts +72 -81
- package/src/engine/physics/ecs/PhysicsSystem.d.ts.map +1 -1
- package/src/engine/physics/ecs/PhysicsSystem.js +86 -77
- package/src/engine/physics/fluid/ecs/FluidObstacleSystem.d.ts +4 -4
- package/src/shade/device/IMMEDIATE_DATA_PLAN_2026_08_28.md +451 -0
- package/src/shade/device/timing/GPU_PROFILER_PROPOSAL_2026_08_28.md +625 -0
- package/src/shade/renderer/postprocess/nss/NSS.d.ts +1 -1
- package/src/shade/renderer/rasterize/native/oit/shader_oit_blend.d.ts.map +1 -1
- package/src/shade/renderer/rasterize/native/oit/shader_oit_blend.js +5 -4
- package/src/shade/renderer/scene/serialization/decode_image_source_hdr.d.ts +14 -3
- package/src/shade/renderer/scene/serialization/decode_image_source_hdr.d.ts.map +1 -1
- package/src/shade/renderer/scene/serialization/decode_image_source_hdr.js +50 -51
- package/src/shade/renderer/scene/serialization/deserialize_scene.d.ts.map +1 -1
- package/src/shade/renderer/scene/serialization/deserialize_scene.js +531 -521
- package/src/shade/renderer/scene/serialization/write_image_source.d.ts.map +1 -1
- package/src/shade/renderer/scene/serialization/write_image_source.js +6 -4
- package/src/shade/renderer/shader/chunk/color/chunk_rgb_to_luminance.d.ts.map +1 -1
- package/src/shade/renderer/shader/chunk/color/chunk_rgb_to_luminance.js +34 -32
- package/src/shade/renderer/shader/chunk/color/iCtCp/chunk_eotf_st2084.d.ts +31 -0
- package/src/shade/renderer/shader/chunk/color/iCtCp/chunk_eotf_st2084.d.ts.map +1 -1
- package/src/shade/renderer/shader/chunk/color/iCtCp/chunk_eotf_st2084.js +62 -31
- package/src/shade/renderer/shader/chunk/color/iCtCp/chunk_inverse_eotf_st2084.d.ts +10 -0
- package/src/shade/renderer/shader/chunk/color/iCtCp/chunk_inverse_eotf_st2084.d.ts.map +1 -1
- package/src/shade/renderer/shader/chunk/color/iCtCp/chunk_inverse_eotf_st2084.js +32 -22
- package/src/shade/renderer/shader/chunk/color/sRGB/chunk_sRGBTransferOETF.d.ts.map +1 -1
- package/src/shade/renderer/shader/chunk/color/sRGB/chunk_sRGBTransferOETF.js +22 -21
- package/src/shade/renderer/shader/chunk/color/tonemap/gt7/chunk_tonemap_gt7.d.ts +6 -0
- package/src/shade/renderer/shader/chunk/color/tonemap/gt7/chunk_tonemap_gt7.d.ts.map +1 -1
- package/src/shade/renderer/shader/chunk/color/tonemap/gt7/chunk_tonemap_gt7.js +110 -104
- package/src/shade/renderer/shader/chunk/color/xyz/chunk_rgb_to_xyz.d.ts.map +1 -1
- package/src/shade/renderer/shader/chunk/color/xyz/chunk_rgb_to_xyz.js +23 -21
- package/src/shade/renderer/shader/chunk/math/chunk_magnitude_floor.d.ts +9 -0
- package/src/shade/renderer/shader/chunk/math/chunk_magnitude_floor.d.ts.map +1 -0
- package/src/shade/renderer/shader/chunk/math/chunk_magnitude_floor.js +16 -0
- package/src/shade/renderer/shader/chunk/math/complex/chunk_complex_magnitude_floor.d.ts +10 -0
- package/src/shade/renderer/shader/chunk/math/complex/chunk_complex_magnitude_floor.d.ts.map +1 -0
- package/src/shade/renderer/shader/chunk/math/complex/chunk_complex_magnitude_floor.js +17 -0
- package/src/shade/renderer/shader/chunk/math/complex/chunk_complex_solve_cubic_blinn.d.ts.map +1 -1
- package/src/shade/renderer/shader/chunk/math/complex/chunk_complex_solve_cubic_blinn.js +7 -1
- package/src/shade/renderer/shader/chunk/math/complex/chunk_complex_square_root.d.ts.map +1 -1
- package/src/shade/renderer/shader/chunk/math/complex/chunk_complex_square_root.js +9 -3
- package/src/shade/renderer/shader/chunk/math/polynomial/chunk_solve_cubic.d.ts.map +1 -1
- package/src/shade/renderer/shader/chunk/math/polynomial/chunk_solve_cubic.js +13 -7
- package/src/shade/renderer/shader/chunk/math/polynomial/chunk_solve_cubic_blinn_smallest.d.ts.map +1 -1
- package/src/shade/renderer/shader/chunk/math/polynomial/chunk_solve_cubic_blinn_smallest.js +13 -4
- package/src/shade/renderer/shader/chunk/math/polynomial/chunk_solve_quadratic.d.ts.map +1 -1
- package/src/shade/renderer/shader/chunk/math/polynomial/chunk_solve_quadratic.js +20 -13
- package/src/shade/renderer/shader/chunk/math/polynomial/chunk_solve_quartic_neumark.d.ts.map +1 -1
- package/src/shade/renderer/shader/chunk/math/polynomial/chunk_solve_quartic_neumark.js +8 -4
- package/src/shade/renderer/shader/chunk/mboit/NOTES.md +35 -1
- package/src/shade/renderer/shader/chunk/mboit/moments/geometric/chunk_compute_transmittance_from_2_trigonometric_moments.d.ts.map +1 -1
- package/src/shade/renderer/shader/chunk/mboit/moments/geometric/chunk_compute_transmittance_from_2_trigonometric_moments.js +18 -5
- package/src/shade/renderer/shader/chunk/mboit/moments/geometric/chunk_compute_transmittance_from_3_trigonometric_moments.d.ts.map +1 -1
- package/src/shade/renderer/shader/chunk/mboit/moments/geometric/chunk_compute_transmittance_from_3_trigonometric_moments.js +21 -8
- package/src/shade/renderer/shader/chunk/mboit/moments/geometric/chunk_compute_transmittance_from_4_trigonometric_moments.d.ts.map +1 -1
- package/src/shade/renderer/shader/chunk/mboit/moments/geometric/chunk_compute_transmittance_from_4_trigonometric_moments.js +25 -12
- package/src/shade/renderer/shader/chunk/mboit/moments/geometric/chunk_generate_trigonometric_moments_4.d.ts +6 -2
- package/src/shade/renderer/shader/chunk/mboit/moments/geometric/chunk_generate_trigonometric_moments_4.d.ts.map +1 -1
- package/src/shade/renderer/shader/chunk/mboit/moments/geometric/chunk_generate_trigonometric_moments_4.js +42 -35
- package/src/shade/renderer/shader/chunk/mboit/moments/power/chunk_compute_transmittance_from_4_power_moments.d.ts.map +1 -1
- package/src/shade/renderer/shader/chunk/mboit/moments/power/chunk_compute_transmittance_from_4_power_moments.js +18 -5
- package/src/shade/renderer/shader/chunk/mboit/moments/power/chunk_compute_transmittance_from_6_power_moments.d.ts.map +1 -1
- package/src/shade/renderer/shader/chunk/mboit/moments/power/chunk_compute_transmittance_from_6_power_moments.js +24 -9
- package/src/shade/renderer/shader/chunk/mboit/moments/power/chunk_compute_transmittance_from_8_power_moments.d.ts.map +1 -1
- package/src/shade/renderer/shader/chunk/mboit/moments/power/chunk_compute_transmittance_from_8_power_moments.js +29 -13
- package/src/shade/renderer/shader/chunk/mboit/moments/power/chunk_generate_power_moments_4.d.ts +12 -0
- package/src/shade/renderer/shader/chunk/mboit/moments/power/chunk_generate_power_moments_4.d.ts.map +1 -1
- package/src/shade/renderer/shader/chunk/mboit/moments/power/chunk_generate_power_moments_4.js +38 -48
- package/src/view/elements/ColorPickerView.js +296 -296
- package/src/avif/codec/dec/Readme.md +0 -14
- package/src/avif/codec/dec/avif_dec.cpp +0 -234
- package/src/avif/codec/dec/avif_dec.d.ts +0 -3
- package/src/avif/codec/dec/avif_dec.d.ts.map +0 -1
- package/src/avif/codec/dec/avif_dec.js +0 -16
- package/src/avif/codec/dec/avif_dec.wasm +0 -0
- package/src/avif/codec/enc/README.md +0 -37
- package/src/avif/codec/enc/avif_enc.cpp +0 -216
- package/src/avif/codec/enc/avif_enc.d.ts +0 -3
- package/src/avif/codec/enc/avif_enc.d.ts.map +0 -1
- package/src/avif/codec/enc/avif_enc.js +0 -6144
- package/src/avif/codec/enc/avif_enc.wasm +0 -0
- package/src/avif/codec/enc/avif_enc_mt.d.ts +0 -3
- package/src/avif/codec/enc/avif_enc_mt.d.ts.map +0 -1
- package/src/avif/codec/enc/avif_enc_mt.js +0 -6811
- package/src/avif/codec/enc/avif_enc_mt.wasm +0 -0
- package/src/avif/codec/enc/avif_enc_mt.worker.d.mts +0 -2
- package/src/avif/codec/enc/avif_enc_mt.worker.d.mts.map +0 -1
- package/src/avif/codec/enc/avif_enc_mt.worker.d.ts +0 -11
- package/src/avif/codec/enc/avif_enc_mt.worker.d.ts.map +0 -1
- package/src/avif/codec/enc/avif_enc_mt.worker.js +0 -171
- package/src/avif/codec/enc/avif_enc_mt.worker.mjs +0 -1
- package/src/avif/decode.d.ts +0 -3
- package/src/avif/decode.d.ts.map +0 -1
- package/src/avif/decode.js +0 -42
- package/src/avif/encode.d.ts +0 -3
- package/src/avif/encode.d.ts.map +0 -1
- package/src/avif/encode.js +0 -75
- package/src/avif/meta.d.ts +0 -24
- package/src/avif/meta.d.ts.map +0 -1
- package/src/avif/meta.js +0 -23
- package/src/avif/tsconfig.tsbuildinfo +0 -1
- package/src/avif/utils.d.ts +0 -17
- package/src/avif/utils.d.ts.map +0 -1
- package/src/avif/utils.js +0 -31
- package/src/engine/asset/loaders/image/jpeg/idct8x8_fixed.d.ts +0 -23
- package/src/engine/asset/loaders/image/jpeg/idct8x8_fixed.d.ts.map +0 -1
- package/src/engine/asset/loaders/image/jpeg/idct8x8_fixed.js +0 -214
- package/src/engine/asset/loaders/image/png/inflate.d.ts +0 -7
- package/src/engine/asset/loaders/image/png/inflate.d.ts.map +0 -1
- package/src/engine/asset/loaders/image/png/inflate.js +0 -20
|
@@ -0,0 +1,779 @@
|
|
|
1
|
+
# COLOR_REVIEW — `src/core/color`
|
|
2
|
+
|
|
3
|
+
Date: 2026-08-28 · Reviewer: Claude (Opus 5) · Scope: every one of the 58 source files and 33 spec
|
|
4
|
+
files under `src/core/color`, plus the CPU/GPU boundary with `src/shade/renderer/shader/chunk/color`.
|
|
5
|
+
|
|
6
|
+
**Method.** Every source file was read in full. Claims that could be executed were executed: the
|
|
7
|
+
package's own suite (`npx vitest run src/core/color` — **33 files, 158 tests, all passing**) plus six
|
|
8
|
+
throwaway probe scripts run against the real modules (Appendix A). Every defect below marked
|
|
9
|
+
*(reproduced)* has a printed failing value next to it; nothing in §2 is inferred from reading alone.
|
|
10
|
+
Reachability was established by grepping actual `import ... from ".../<file>.js"` statements across
|
|
11
|
+
`src/`, not by symbol name.
|
|
12
|
+
|
|
13
|
+
Severity, following the `core/geom` reviews: **major** = wrong results, silent data loss, or a
|
|
14
|
+
cross-cutting trap the caller cannot predict; **minor** = consistency/ergonomics gap with no
|
|
15
|
+
correctness impact.
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## 0. TL;DR
|
|
20
|
+
|
|
21
|
+
The package is a **well-built colour-science library with a badly maintained perimeter**. The
|
|
22
|
+
numerically hard parts — Lab, OkLab/OkHSV, PQ, ycxcz, the D65/CMF tables, ACES — are correct,
|
|
23
|
+
carefully documented, and in several places better than the textbook (the D65 white point is derived
|
|
24
|
+
from the actual matrix rows so linear white lands on `L* = 100, a* = 0, b* = 0` *exactly*, which I
|
|
25
|
+
confirmed). The problems are all at the edges: the ingest path (`parse_color`/`hex2rgb`), the
|
|
26
|
+
`Color` façade, the alpha channel, and the seams between approximation regimes.
|
|
27
|
+
|
|
28
|
+
Five things worth acting on first:
|
|
29
|
+
|
|
30
|
+
1. **`kelvin_to_rgb` has a 0.078 hard step in blue at exactly 2000 K** (§2.1 C1, reproduced). The
|
|
31
|
+
author already blended the 6600 K seam; the 2000 K seam was missed. Any temperature ramp bands
|
|
32
|
+
there.
|
|
33
|
+
2. **`rgb_to_kelvin` is non-injective below 2000 K** — 800 K, 1500 K and 1999 K all return 1000 K
|
|
34
|
+
(§2.1 C2, reproduced). It is a consequence of C1 and cannot be fixed independently.
|
|
35
|
+
3. **Alpha is silently dropped in five public entry points** — `Color.from_linear_to_sRGB`,
|
|
36
|
+
`from_sRGB_to_linear`, and all four `color_lighten/darken/saturate/desaturate` (§2.1 C5, C6,
|
|
37
|
+
reproduced). `color_lerp` handles alpha correctly, which makes the inconsistency worse, not better.
|
|
38
|
+
4. **`Color.parse` accepts malformed input and produces garbage instead of throwing** — `#f00`
|
|
39
|
+
yields `b: NaN`, `0xFF8000FF` yields `r: -0.502` (§2.1 C3, C4, reproduced). This is the one
|
|
40
|
+
defect class that reaches user-authored data.
|
|
41
|
+
5. **A third of the package is unreachable** (§1.4): 8 files no module imports at all, another 12
|
|
42
|
+
reachable only from their own tests. Including `prototype_kelvin_to_rgb.js`, a dev scratch script
|
|
43
|
+
that calls `document.body.append` at import time from inside a core library folder.
|
|
44
|
+
|
|
45
|
+
The single largest *structural* issue is not a bug: **nothing in the package records whether a
|
|
46
|
+
`Color` holds linear or encoded values** (§3.4). `color_lighten` calls `linear_srgb_to_okhsv` on
|
|
47
|
+
whatever `Color` you hand it, and `Color.parse("#808080")` produces encoded values. Both are
|
|
48
|
+
reasonable; together they are wrong, silently.
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## 1. Overall shape of the package
|
|
53
|
+
|
|
54
|
+
### 1.1 Inventory
|
|
55
|
+
|
|
56
|
+
| | |
|
|
57
|
+
|---|---|
|
|
58
|
+
| Source files / LOC | 58 / 3,263 (544 of which are tabulated data) |
|
|
59
|
+
| Spec files / LOC / tests | 33 / 2,090 / 158 — all passing |
|
|
60
|
+
| Directories | 16 — `PQ` `YCbCr` `YCoCg` `colormap` `hex` `hsv` `hunt` `illuminant` `kelvin` `lab` `oklab` `operations` `sRGB` `tonemap` `xyz` `ycxcz` |
|
|
61
|
+
| Entry point | none — no barrel, consistent with the rest of `core` (no `index.js` exists anywhere under `src/core`). Not a gap. |
|
|
62
|
+
|
|
63
|
+
### 1.2 The three layers that are actually there
|
|
64
|
+
|
|
65
|
+
The directory listing suggests one flat namespace; the code is really three layers with different
|
|
66
|
+
authors, ages, and quality levels.
|
|
67
|
+
|
|
68
|
+
**Layer A — the modern colour-science core.** `lab/`, `ycxcz/`, `oklab/`, `PQ/`, `illuminant/`,
|
|
69
|
+
`xyz/`, `tonemap/`, `colormap/`. Snake-case, out-param-first, offset-aware, heavily documented with
|
|
70
|
+
the *reason* for each choice, not just the formula. [illuminant/D65_TRISTIMULUS_XYZ.js](illuminant/D65_TRISTIMULUS_XYZ.js)
|
|
71
|
+
and [lab/xyz_to_lab.js](lab/xyz_to_lab.js) are the best files in the package: they explain why the
|
|
72
|
+
white point is derived rather than quoted, and what breaks if you mix sources. This layer exists
|
|
73
|
+
because of two consumers — the ꟻLIP image-difference metric (`engine/graphics/texture/sampler/compare/flip/`)
|
|
74
|
+
and the AVIF encoder — and it shows: it is exactly as complete as those two need and no more.
|
|
75
|
+
|
|
76
|
+
**Layer B — the legacy CSS/UI layer.** [Color.js](Color.js), [parse_color.js](parse_color.js),
|
|
77
|
+
`hex/`, `hsv/`, [int2rgb.js](int2rgb.js), [rgb2uint24.js](rgb2uint24.js), [rgb2uint32.js](rgb2uint32.js).
|
|
78
|
+
`name2name` naming, object-returning, allocating, 0–255 units, no offsets. This is the layer with
|
|
79
|
+
essentially all of §2's defects. It has 29 non-spec importers (via `Color.js`) and is the most-used
|
|
80
|
+
and least-maintained part of the package.
|
|
81
|
+
|
|
82
|
+
**Layer C — orphan experiments.** `hunt/`, `kelvin/`, `YCbCr/`, `YCoCg/`,
|
|
83
|
+
[sRGB/sRGB_cmf.js](sRGB/sRGB_cmf.js), [oklab/find_gamut_intersection.js](oklab/find_gamut_intersection.js),
|
|
84
|
+
[illuminant/D65_spd_analytical.js](illuminant/D65_spd_analytical.js). Each is a competent
|
|
85
|
+
implementation of something nobody calls (§1.4).
|
|
86
|
+
|
|
87
|
+
### 1.3 Where the boundary is drawn wrong
|
|
88
|
+
|
|
89
|
+
Three placement problems, in descending order of how much they cost:
|
|
90
|
+
|
|
91
|
+
- **`operations/` sits above `Color`, but `Color` sits above nothing.** `Color.js` imports
|
|
92
|
+
`hsv2rgb_float`, `rgb2hsv`, `rgb2uint24/32`, `rgb_to_luminance`, `parse_color`, `rgb2hex`,
|
|
93
|
+
`linear_to_sRGB`, `sRGB_to_linear` — eight modules. Meanwhile `operations/` imports `Color`. So
|
|
94
|
+
the dependency graph is `operations → Color → {8 leaf modules}`, and the OkHSV operations live
|
|
95
|
+
*outside* the class while HSV/HSL/HCL/HSI setters live *inside* it. There is no principle
|
|
96
|
+
separating the two sets; `setHSV` could as easily have been `color_set_hsv`.
|
|
97
|
+
- **`hunt/` contains the wrong Hunt.** [hunt/xyz_to_hpe.js](hunt/xyz_to_hpe.js) is the
|
|
98
|
+
Hunt–Pointer–Estévez *cone response matrix*; the actual Hunt-*effect* function is
|
|
99
|
+
[lab/lab_apply_hunt_adjustment.js](lab/lab_apply_hunt_adjustment.js). The latter carries an
|
|
100
|
+
explicit disclaimer — *"Unrelated to `xyz_to_hpe`"* — which is the tell that the directory name is
|
|
101
|
+
doing damage. `xyz_to_hpe` has zero importers; it belongs in `xyz/` or nowhere.
|
|
102
|
+
- **`colormap/` holds one unused LUT.** Magma, base64-encoded, well documented, zero importers,
|
|
103
|
+
while the GPU side ships four different LUTs (inferno/plasma/turbo/viridis) that the CPU cannot
|
|
104
|
+
reach. The directory is right; the contents are the wrong four-fifths.
|
|
105
|
+
|
|
106
|
+
### 1.4 Reachability
|
|
107
|
+
|
|
108
|
+
Established by grepping real import statements. This is the most actionable single table in the
|
|
109
|
+
review.
|
|
110
|
+
|
|
111
|
+
**Unreferenced by anything, including their own tests (8 files):**
|
|
112
|
+
|
|
113
|
+
| File | Note |
|
|
114
|
+
|---|---|
|
|
115
|
+
| [kelvin/prototype_kelvin_to_rgb.js](kelvin/prototype_kelvin_to_rgb.js) | **Side-effecting dev script.** Appends a canvas to `document.body` and `console.log`s at *import* time. Reaches into `engine/graphics` and `view/elements` from `core/`, inverting the layering. Delete or move to a playground. |
|
|
116
|
+
| [oklab/find_gamut_intersection.js](oklab/find_gamut_intersection.js) | 100 lines of correct Halley-iteration gamut math. Its whole purpose is gamut clipping, and no clipper exists (§4.3). |
|
|
117
|
+
| [int2rgb.js](int2rgb.js) | Third copy of uint24 unpacking (§5.1). |
|
|
118
|
+
| [hunt/xyz_to_hpe.js](hunt/xyz_to_hpe.js) | Matrix verified correct against the standard HPE matrix. |
|
|
119
|
+
| [illuminant/D65_spd_analytical.js](illuminant/D65_spd_analytical.js) | Sole importer of `planckian_radiance` → a dead 2-file subtree. |
|
|
120
|
+
| [operations/color_lighten.js](operations/color_lighten.js) · [color_saturate.js](operations/color_saturate.js) · [color_desaturate.js](operations/color_desaturate.js) | Untested *and* unused; `color_darken` is the only one of the four with a spec. |
|
|
121
|
+
|
|
122
|
+
**Reachable only from their own spec (12 files):** [PQ/PQ_to_linear.js](PQ/PQ_to_linear.js) (the
|
|
123
|
+
encoder only encodes), `YCbCr/*` (2), `YCoCg/*` (2), [colormap/MAGMA_LUT.js](colormap/MAGMA_LUT.js),
|
|
124
|
+
[kelvin/rgb_to_kelvin.js](kelvin/rgb_to_kelvin.js), [oklab/oklab_to_xyz.js](oklab/oklab_to_xyz.js),
|
|
125
|
+
[oklab/xyz_to_oklab.js](oklab/xyz_to_oklab.js), [operations/color_darken.js](operations/color_darken.js),
|
|
126
|
+
[operations/color_lerp.js](operations/color_lerp.js), [rgb_to_rgbe9995.js](rgb_to_rgbe9995.js),
|
|
127
|
+
[rgbe9995_to_rgb.js](rgbe9995_to_rgb.js), [sRGB/sRGB_cmf.js](sRGB/sRGB_cmf.js).
|
|
128
|
+
|
|
129
|
+
**Load-bearing (≥3 non-spec importers):** `Color.js` (29), `sRGB/sRGB_to_linear.js` (5),
|
|
130
|
+
`xyz/rgb_to_xyz.js` (4), `xyz/xyz_to_rgb.js` (4), `sRGB/linear_to_sRGB.js` (4),
|
|
131
|
+
`operations/color_scale_okhsv_channel.js` (4), `oklab/okhsv_to_linear_srgb.js` (4),
|
|
132
|
+
`illuminant/D65_TRISTIMULUS_XYZ.js` (3), `kelvin/kelvin_to_rgb.js` (3), `oklab/find_cusp.js` (3),
|
|
133
|
+
`oklab/oklab_to_linear_srgb.js` (3), `rgb_to_luminance.js` (3).
|
|
134
|
+
|
|
135
|
+
---
|
|
136
|
+
|
|
137
|
+
## 2. Correctness
|
|
138
|
+
|
|
139
|
+
### 2.1 Confirmed defects
|
|
140
|
+
|
|
141
|
+
| # | Symbol | File | Defect | Sev |
|
|
142
|
+
|---|---|---|---|---|
|
|
143
|
+
| C1 | `kelvin_to_rgb` | [kelvin/kelvin_to_rgb.js:23](kelvin/kelvin_to_rgb.js) | Blue steps 0 → 0.0779 across 2000 K | major |
|
|
144
|
+
| C2 | `rgb_to_kelvin` | [kelvin/rgb_to_kelvin.js](kelvin/rgb_to_kelvin.js) | All T < 2000 K collapse to 1000 K | major |
|
|
145
|
+
| C3 | `hex2rgb` | [hex/hex2rgb.js:12](hex/hex2rgb.js) | 3-/4-digit hex silently yields `NaN` | major |
|
|
146
|
+
| C4 | `parse_color` | [parse_color.js:68](parse_color.js) | Numeric branch unmasked; negative channels | major |
|
|
147
|
+
| C5 | `Color.from_*` | [Color.js:788](Color.js) | Drop alpha; never fire `onChanged` | major |
|
|
148
|
+
| C6 | `color_lighten` &c. | [operations/color_scale_okhsv_channel.js](operations/color_scale_okhsv_channel.js) | Drop alpha (but `color_lerp` keeps it) | major |
|
|
149
|
+
| C7 | `rgb2uint32` | [rgb2uint32.js:17](rgb2uint32.js) | Returns signed int32, not uint32 | minor |
|
|
150
|
+
| C8 | `Color.setHCL` / `setHSI` | [Color.js:332](Color.js) | Emit out-of-gamut, unclamped, undocumented | minor |
|
|
151
|
+
| C9 | `Color.multiplyScalar` | [Color.js:702](Color.js) | Scales alpha along with RGB | minor |
|
|
152
|
+
| C10 | `Color.fromUint` | [Color.js:497](Color.js) | `>> 16` with no `& 0xFF` | minor |
|
|
153
|
+
| C11 | `parse_color` | [parse_color.js:6](parse_color.js) | Regexes unanchored | minor |
|
|
154
|
+
| C12 | `linear_srgb_to_okhsv` | [oklab/linear_srgb_to_okhsv.js:43](oklab/linear_srgb_to_okhsv.js) | Unguarded divide by zero chroma | minor |
|
|
155
|
+
| C13 | `color_lighten` | [operations/color_lighten.js](operations/color_lighten.js) | Multiplicative — cannot lighten black | minor |
|
|
156
|
+
| C14 | `rgb_to_luminance` | [rgb_to_luminance.js](rgb_to_luminance.js) | Doc conflates luminance with luma | minor |
|
|
157
|
+
| C15 | `Color.hash` | [Color.js:563](Color.js) | Ignores alpha | minor |
|
|
158
|
+
| C16 | `D65_spd_tabulated` | [illuminant/D65_spd_tabulated.js](illuminant/D65_spd_tabulated.js) | Boundary logic asymmetric with its CMF twin | minor |
|
|
159
|
+
|
|
160
|
+
---
|
|
161
|
+
|
|
162
|
+
#### C1 · `kelvin_to_rgb` — 0.078 discontinuity in blue at exactly 2000 K · **major** · *(reproduced)*
|
|
163
|
+
|
|
164
|
+
`approximate_regime_red` gates the blue channel with `t <= 20 ? 0 : clamp01(...)`
|
|
165
|
+
([kelvin/kelvin_to_rgb.js:23](kelvin/kelvin_to_rgb.js)). The logarithmic fit it guards does not
|
|
166
|
+
approach 0 at `t = 20`; it approaches 0.0779. So blue jumps by a step ~20× the 8-bit quantum:
|
|
167
|
+
|
|
168
|
+
```
|
|
169
|
+
1999.99 K → 1.000000, 0.544072, 0.000000
|
|
170
|
+
2000.00 K → 1.000000, 0.544074, 0.000000
|
|
171
|
+
2000.01 K → 1.000000, 0.544076, 0.077917 ← +0.0779 in one 0.01 K step
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
The 1000 K seam is clean (the blackbody-fade hue is locked to the value the red regime produces at
|
|
175
|
+
`t = 10`, and it matches to 6 decimals — verified), and the 6600 K seam is explicitly blended over
|
|
176
|
+
`t ∈ [65, 67]`. So the author already understood this class of problem and solved it twice; this
|
|
177
|
+
third seam was simply not noticed.
|
|
178
|
+
|
|
179
|
+
**Fix:** either blend across `t ∈ [20, 20.x]` the way the 6600 K seam is blended, or — better —
|
|
180
|
+
shift the fit so the blue term is continuous at its own gate. The seam is where the physical
|
|
181
|
+
Draper-adjacent region hands over to the fit, so a short blend window matches the existing house
|
|
182
|
+
style. Add a spec that walks temperature in fine steps and asserts a bounded first difference on
|
|
183
|
+
each channel; the existing [kelvin/kelvin_to_rgb.spec.js](kelvin/kelvin_to_rgb.spec.js) has 9 tests
|
|
184
|
+
and none of them samples across a seam.
|
|
185
|
+
|
|
186
|
+
#### C2 · `rgb_to_kelvin` collapses everything below 2000 K to 1000 K · **major** · *(reproduced)*
|
|
187
|
+
|
|
188
|
+
The solver bisects on the ratio `b / r`. Because C1 pins blue to exactly 0 for all `t <= 20`, that
|
|
189
|
+
ratio is 0 across the entire 0–2000 K span, so the bisection has no gradient to follow and always
|
|
190
|
+
walks to the lower bound:
|
|
191
|
+
|
|
192
|
+
```
|
|
193
|
+
kelvin_to_rgb(T) → rgb_to_kelvin:
|
|
194
|
+
800 K → 1000 1500 K → 1000 1999 K → 1000
|
|
195
|
+
1000 K → 1000 1800 K → 1000 2000 K → 1000
|
|
196
|
+
2100 K → 2100 2500 K → 2500 (correct from here up)
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
Candle-light (~1900 K) and tungsten (~2400 K) straddle the break, so this is not an academic range.
|
|
200
|
+
Two secondary issues in the same file: the green channel is never read, so any colour with a matching
|
|
201
|
+
`b/r` maps to the same temperature regardless of how far it is from the Planckian locus; and the
|
|
202
|
+
JSDoc promises "approx 1000 to 40000" while the function returns `0` for black.
|
|
203
|
+
|
|
204
|
+
**Fix:** C1 first — the collapse is downstream of it. Then either document the ±0.4 K `epsilon` and
|
|
205
|
+
the 2000 K floor honestly, or switch to a proper CCT estimator (McCamy's approximation over CIE
|
|
206
|
+
1960 `uv`, or Robertson) which needs no solver, uses all three channels, and has a well-defined
|
|
207
|
+
Duv-based validity domain.
|
|
208
|
+
|
|
209
|
+
#### C3 · `hex2rgb` silently produces `NaN` for short-form hex · **major** · *(reproduced)*
|
|
210
|
+
|
|
211
|
+
[hex/hex2rgb.js](hex/hex2rgb.js) slices fixed byte offsets and never validates length, so the two
|
|
212
|
+
most common CSS hex forms decode to nonsense:
|
|
213
|
+
|
|
214
|
+
```
|
|
215
|
+
hex2rgb('#f00') → { r: 240, g: 0, b: NaN, a: 255 }
|
|
216
|
+
parse_color('#f00') → [ 240, 0, NaN, 1 ]
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
`Color.parse('#f00')` then reaches `set()`, whose `assert.notNaN` throws — but with a message about
|
|
220
|
+
a NaN channel, pointing at `Color.js`, five frames away from the actual cause. And that only holds
|
|
221
|
+
while asserts are live: the guard is `assert`, not a validating parser, so any build that strips
|
|
222
|
+
asserts propagates `NaN` downstream.
|
|
223
|
+
|
|
224
|
+
**Fix:** handle `#RGB` and `#RGBA` by expansion (`f00` → `ff0000`), and throw a message naming the
|
|
225
|
+
offending string for anything that is not 3/4/6/8 hex digits. `rgb2hex` should grow the matching
|
|
226
|
+
alpha form at the same time (§4.1).
|
|
227
|
+
|
|
228
|
+
#### C4 · `parse_color` numeric branch is unmasked and unvalidated · **major** · *(reproduced)*
|
|
229
|
+
|
|
230
|
+
[parse_color.js:68](parse_color.js) does `r = c >> 16` with no `& 0xFF` and no range check.
|
|
231
|
+
For any value with bit 23 set, the shift is signed:
|
|
232
|
+
|
|
233
|
+
```
|
|
234
|
+
parse_color(0xFF8000) → [ 255, 128, 0, 1 ] ✓
|
|
235
|
+
parse_color(0xFF8000FF) → [ -128, 0, 255, 1 ] ✗ (a plausible RGBA literal)
|
|
236
|
+
parse_color(-1) → [ -1, 255, 255, 1 ] ✗
|
|
237
|
+
Color.parse(0xFF8000FF) → { r: -0.502, g: 0, b: 1, a: 1 }
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
No assert fires, because `-0.502` is a finite number — `Color` explicitly supports out-of-`[0,1]`
|
|
241
|
+
values for HDR, so the very feature that makes the class useful is what lets this through silently.
|
|
242
|
+
`Color.parse`'s own JSDoc advertises `Color.parse(0xFF00FF)`, so numeric input is a documented path.
|
|
243
|
+
|
|
244
|
+
**Fix:** mask all three channels, and reject values outside `[0, 0xFFFFFF]` with a thrown error
|
|
245
|
+
rather than wrapping. Decide explicitly whether `0xAARRGGBB`-style 32-bit input is supported; today
|
|
246
|
+
it is neither supported nor rejected.
|
|
247
|
+
|
|
248
|
+
#### C5 · `Color.from_linear_to_sRGB` / `from_sRGB_to_linear` drop alpha and skip `onChanged` · **major** · *(reproduced)*
|
|
249
|
+
|
|
250
|
+
```js
|
|
251
|
+
Color.from_linear_to_sRGB(new Color(0.5, 0.5, 0.5, 0.25))
|
|
252
|
+
// → { r: 0.735, g: 0.735, b: 0.735, a: 1 } alpha 0.25 lost
|
|
253
|
+
|
|
254
|
+
const out = new Color(); let fired = 0; out.onChanged.add(() => fired++);
|
|
255
|
+
Color.from_sRGB_to_linear(new Color(0.5, 0.5, 0.5), out);
|
|
256
|
+
// → out mutated to 0.214…, fired === 0 listeners never notified
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
Two distinct causes, one call site. The underlying `linear_to_sRGB(output, 0, input, 0)` writes
|
|
260
|
+
indices 0–2 only, so with the default `output = new Color()` alpha stays at the constructor's `1`
|
|
261
|
+
and the caller's alpha is discarded. And because the write goes through `Color`'s numeric index
|
|
262
|
+
setters (`set 0(v) { this.r = v }`), it bypasses `set()` entirely — no equality check, no
|
|
263
|
+
`onChanged.send8`. Any observer bound to that `Color` (the editor's `ColorEditor`, a material
|
|
264
|
+
uniform, a signal-driven binding) sees stale state while the object has already changed. This is the
|
|
265
|
+
most dangerous defect in the review because it is *silent on both sides*.
|
|
266
|
+
|
|
267
|
+
**Fix:** in both statics, copy `input.a` to `output` and route the write through `output.set(...)`
|
|
268
|
+
rather than raw index assignment. The same pattern is used by `okhsv_to_linear_srgb(result, …)` in
|
|
269
|
+
`color_scale_okhsv_channel` — safe there only because the target is freshly allocated, which is a
|
|
270
|
+
precondition nothing states. Worth a comment on the index setters warning that writing a `Color`
|
|
271
|
+
positionally is an unobserved mutation.
|
|
272
|
+
|
|
273
|
+
#### C6 · The four `operations/` scale functions drop alpha · **major** · *(reproduced)*
|
|
274
|
+
|
|
275
|
+
```js
|
|
276
|
+
color_lighten(new Color(0.2, 0.4, 0.6, 0.3), 0.5)
|
|
277
|
+
// → { r: 0.333, g: 0.667, b: 1.0, a: 1 } alpha 0.3 → 1
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
[operations/color_scale_okhsv_channel.js](operations/color_scale_okhsv_channel.js) allocates
|
|
281
|
+
`new Color()` and never touches `.a`. Its sibling [color_lerp](operations/color_lerp.js) *does*
|
|
282
|
+
interpolate alpha (`r.a = lerp(color_0.a, color_1.a, _t)`), so within a five-file directory two
|
|
283
|
+
different alpha policies coexist with nothing marking the difference. Fix: `result.a = input.a` in
|
|
284
|
+
`color_scale_okhsv_channel`; it fixes all four callers at once.
|
|
285
|
+
|
|
286
|
+
#### C7 · `rgb2uint32` returns a signed int32 · **minor**
|
|
287
|
+
|
|
288
|
+
`(partial << 8) | (_a & 0xFF)` ([rgb2uint32.js:17](rgb2uint32.js)) overflows into the sign bit
|
|
289
|
+
for any red ≥ 0x80: `Color.white.toUint32() === -1`. This is **already known** —
|
|
290
|
+
[Color.spec.js:51](Color.spec.js) documents it and normalises with `>>> 0` in every assertion,
|
|
291
|
+
and `deserialize_scene.js` reads the value back through `>>>`. So it is not a live bug; it is a
|
|
292
|
+
method whose name promises a `uint32` and whose contract is "an int32 you must convert". Appending
|
|
293
|
+
`>>> 0` to the return is behaviour-preserving for every existing consumer (both already normalise)
|
|
294
|
+
and removes the wart. Low risk, worth doing.
|
|
295
|
+
|
|
296
|
+
#### C8 · `setHCL` and `setHSI` emit unclamped out-of-gamut values · **minor** · *(reproduced)*
|
|
297
|
+
|
|
298
|
+
```
|
|
299
|
+
setHCL(0, 1, 0.5) → { r: 1.2, g: 0.2, b: 0.2 }
|
|
300
|
+
setHCL(1/3, 1, 0.5) → { r: -0.09, g: 0.91, b: -0.09 }
|
|
301
|
+
setHSI(0, 1, 0.5) → { r: 1.5, g: 0, b: 0 }
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
For a class documented as "0 to 1 for SDR" this is surprising, and `setHSL`/`setHSV` — verified
|
|
305
|
+
correct against CSS at all six primaries — do not behave this way. `setHSI` additionally carries the
|
|
306
|
+
author's own `NOTE: I'm not sure if the implementation is correct`. Neither has a spec.
|
|
307
|
+
Both switch statements also leave `r`/`g`/`b` as bare `let` with no `default` case; the sextant range
|
|
308
|
+
makes that unreachable today, but it is one refactor away from `undefined` propagation.
|
|
309
|
+
|
|
310
|
+
**Fix:** decide per method whether the contract is "clamped to gamut" or "unbounded, caller clamps",
|
|
311
|
+
document it, and add a spec. If `setHSI` cannot be validated against a reference, deleting it is a
|
|
312
|
+
better outcome than shipping a method whose docstring says it might be wrong.
|
|
313
|
+
|
|
314
|
+
#### C9 · `multiplyScalar` scales alpha · **minor** · *(reproduced)*
|
|
315
|
+
|
|
316
|
+
`new Color(0.5,0.5,0.5,1).multiplyScalar(0.5)` → `{ …, a: 0.5 }`. Defensible as "scale the 4-vector",
|
|
317
|
+
but the overwhelming use of a scalar multiply on a colour is exposure/tint adjustment, where fading
|
|
318
|
+
transparency as a side effect is a bug in the caller that looks like a bug in the engine. `multiply`
|
|
319
|
+
(component-wise) has the same behaviour. At minimum document it; better, add `multiplyRGB`.
|
|
320
|
+
|
|
321
|
+
#### C10 · `Color.fromUint` does not mask the red byte · **minor** · *(reproduced)*
|
|
322
|
+
|
|
323
|
+
`value >> 16` with no `& 0xFF` ([Color.js:501](Color.js)). Round-trips with `toUint()` correctly
|
|
324
|
+
(uint24 never sets bit 31), but `fromUint(0xFF8000FF)` throws `Expected -128 >= 0` from inside
|
|
325
|
+
`uint8_to_float` — an error message that names neither the method nor the input. `int2rgb` masks all
|
|
326
|
+
three channels; `parse_color` masks two of three; `fromUint` masks two of three. Three copies, three
|
|
327
|
+
behaviours (§5.1).
|
|
328
|
+
|
|
329
|
+
#### C11 · `parse_color` regexes are unanchored · **minor** · *(reproduced)*
|
|
330
|
+
|
|
331
|
+
`parse_color('background: rgb(1,2,3) !important')` → `[1, 2, 3, 1]`. Lenient parsing of a
|
|
332
|
+
colour literal is a reasonable choice, but it is not the documented one ("Parses most CSS-format
|
|
333
|
+
colors"), and it means a malformed string that happens to contain a colour never surfaces as an
|
|
334
|
+
error. Anchor with `^…$` after trimming, or state the substring-scan behaviour in the docstring.
|
|
335
|
+
|
|
336
|
+
#### C12 · `linear_srgb_to_okhsv` divides by an unguarded chroma · **minor**
|
|
337
|
+
|
|
338
|
+
[oklab/linear_srgb_to_okhsv.js:43](oklab/linear_srgb_to_okhsv.js) computes `a_ = Lab[1] / C`
|
|
339
|
+
with an early return only for `L === 0`, not `C === 0`. In practice exact-zero chroma appears
|
|
340
|
+
unreachable from the sRGB path — the three OkLab row sums differ in their last bits, so neutrals
|
|
341
|
+
land at `C ≈ 1e-7` rather than 0 — but the consequence of that near-miss is itself worth knowing:
|
|
342
|
+
|
|
343
|
+
```
|
|
344
|
+
linear rgb (1,1,1) → okhsv (0.24965…, 1.0348e-7, 1.0000000270)
|
|
345
|
+
linear rgb (0.5,0.5,0.5) → okhsv (0.24965…, 1.0348e-7, 0.7600066)
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
Every neutral gets the same **arbitrary hue 0.2496** and a saturation of `1e-7` instead of `0`. That
|
|
349
|
+
is what makes `color_lerp(white, red)` behave (it interpolates from a stable, if meaningless, hue) —
|
|
350
|
+
but it is load-bearing accident, not design. Note also `v = 1.0000000270 > 1`, and the OkHSV
|
|
351
|
+
round-trip of white returns `(1.00000007, 0.99999998, 0.99999974)`: the `operations/` functions do
|
|
352
|
+
not clamp their output, so they can hand back marginally out-of-`[0,1]` colours.
|
|
353
|
+
|
|
354
|
+
**Fix:** early-return `s = 0, h = 0` when `C` is below a small epsilon (not just when it is exactly
|
|
355
|
+
0), and clamp the final RGB in `color_scale_okhsv_channel`.
|
|
356
|
+
|
|
357
|
+
#### C13 · `color_lighten` cannot lighten black · **minor** · *(reproduced)*
|
|
358
|
+
|
|
359
|
+
`color_lighten(new Color(0,0,0), 1)` → `{0,0,0}`. The operation is `v * (1 + amount)`, and `0`
|
|
360
|
+
is a fixed point. Symmetrically `color_darken` cannot darken further once `v` hits 0, which is
|
|
361
|
+
fine, but the lighten direction failing at the most common input (black) is a design gap.
|
|
362
|
+
`v + amount * (1 - v)` is the usual formulation and has no fixed point at either end. Whatever is
|
|
363
|
+
chosen, this needs a spec — three of the four operations have none at all.
|
|
364
|
+
|
|
365
|
+
#### C14 · `rgb_to_luminance` documents itself as space-agnostic · **minor**
|
|
366
|
+
|
|
367
|
+
The docstring says *"Red component (linear or gamma-corrected)"* and the function is named
|
|
368
|
+
`…_luminance`. Applied to linear values these coefficients give **luminance**; applied to encoded
|
|
369
|
+
values they give **luma** (`Y′`). Both are legitimate quantities and both use these numbers by
|
|
370
|
+
definition, but they are not interchangeable, and the doc invites callers to treat them as such. The
|
|
371
|
+
GPU twin gets this right — `chunk_rgb_to_luminance` says *"Input color must be in linear space to get
|
|
372
|
+
correct results"*. Align the JS doc with it. (See also §3.5 on the coefficient precision split.)
|
|
373
|
+
|
|
374
|
+
#### C15 · `Color.hash()` ignores alpha · **minor** · *(reproduced)*
|
|
375
|
+
|
|
376
|
+
`hash()` delegates to `toUint()` (24-bit, RGB only) while `equals()` compares all four channels, so
|
|
377
|
+
colours differing only in alpha share a hash. The `equals ⇒ same hash` contract still holds, so this
|
|
378
|
+
is correct-but-lossy rather than broken; using `toUint32()` would make it exact at no cost.
|
|
379
|
+
|
|
380
|
+
#### C16 · `D65_spd_tabulated` and `xyz_cmf_tabulated` guard their edges differently · **minor**
|
|
381
|
+
|
|
382
|
+
[D65_spd_tabulated](illuminant/D65_spd_tabulated.js) uses `<=` / `>=` and then re-tests for equality
|
|
383
|
+
inside the rejection branch; [xyz_cmf_tabulated](xyz/xyz_cmf_tabulated.js) uses `<` / `>` and clamps
|
|
384
|
+
`i1`. Both produce correct values; they are two solutions to the same problem sitting in sibling
|
|
385
|
+
files, and one asserts its input while the other does not. Pick one shape.
|
|
386
|
+
|
|
387
|
+
### 2.2 Verified correct
|
|
388
|
+
|
|
389
|
+
Recorded so the next reviewer does not re-derive it. Each was checked by execution, not by reading.
|
|
390
|
+
|
|
391
|
+
- **sRGB OETF/EOTF** round-trip to a maximum error of `2.2e-11` over 100,001 samples of `[0,1]`;
|
|
392
|
+
breakpoints `0.0031308` / `0.04045` are mutually consistent; negative and >1 inputs both behave.
|
|
393
|
+
- **`rgb_to_xyz` → `xyz_to_lab`** maps linear white to `(100, 0, 0)` **exactly**, and black to
|
|
394
|
+
`(0,0,0)`. This is the payoff of deriving `D65_TRISTIMULUS_XYZ` from the matrix rows and it works.
|
|
395
|
+
`xyz_to_ycxcz` lands within `5.6e-14` of the same, as expected.
|
|
396
|
+
- **YCbCr** Rec.709 coefficients re-derived from `Kr = 0.2126 / Kb = 0.0722`: forward and inverse
|
|
397
|
+
both match to 4 dp. **YCoCg** forward/inverse are exact algebraic inverses (verified symbolically).
|
|
398
|
+
**ycxcz ↔ xyz** is an exact inverse.
|
|
399
|
+
- **PQ** OETF/EOTF round-trip to ~1e-15 relative across `[0, 1]`; the 203-nit normalisation is
|
|
400
|
+
correctly applied by the one real consumer (`avif/encoder_worker.js`). The `PQ_constants.js`
|
|
401
|
+
docstring's warning about "the classic way this round-trip ends up off by a factor of ~49" is
|
|
402
|
+
earned — see §3.5 for where the engine does still disagree with itself.
|
|
403
|
+
- **rgbe9995** round-trips `[1,1,1]`, `[0.5,0.25,0.125]`, `[10,5,0]` exactly and `[0.001,0,0]` to
|
|
404
|
+
0.05%; the shared-exponent crush at `[100000,1,1]` is inherent to the format. Both files are
|
|
405
|
+
marked `TODO unverified` — that TODO can be retired.
|
|
406
|
+
- **`xyz_to_hpe`** matches the standard Hunt–Pointer–Estévez matrix; in-place safe.
|
|
407
|
+
- **`setHSL`** matches CSS `hsl()` at all six primaries and at neutral.
|
|
408
|
+
- **In-place safety** claimed by `tonemap_aces`, `lab_apply_hunt_adjustment`, `rgb_to_xyz`,
|
|
409
|
+
`xyz_to_rgb`, `xyz_to_hpe` holds — all read their inputs before the first write.
|
|
410
|
+
- **Frozen constants** (`Color.red` &c.) do throw on mutation under module strict mode. Note the
|
|
411
|
+
failure is state-dependent: `Color.red.set(1,0,0,1)` returns silently via `set()`'s no-change
|
|
412
|
+
early return, while `Color.red.setA(0.5)` throws `TypeError`. Worth a one-line comment.
|
|
413
|
+
|
|
414
|
+
---
|
|
415
|
+
|
|
416
|
+
## 3. Consistency
|
|
417
|
+
|
|
418
|
+
### 3.1 Seven calling conventions for one package
|
|
419
|
+
|
|
420
|
+
This is the package's largest consistency cost, and unlike naming it is not fixable by rename alone.
|
|
421
|
+
|
|
422
|
+
| Convention | Members |
|
|
423
|
+
|---|---|
|
|
424
|
+
| `(out, out_off, in, in_off)` | `linear_to_sRGB`, `sRGB_to_linear`, `xyz_to_lab`, `xyz_to_ycxcz`, `ycxcz_to_xyz`, `lab_apply_hunt_adjustment`, `tonemap_aces`, `lab_distance_hyab` (as `(a, a_off, b, b_off)`) |
|
|
425
|
+
| `(out, in)` — no offsets | `rgb_to_xyz`, `xyz_to_rgb`, `xyz_to_hpe` |
|
|
426
|
+
| `(out, out_off, …scalars)` | `rgb_to_ycocg`, `ycocg_to_rgb`, `kelvin_to_rgb`, `rgbe9995_to_rgb` |
|
|
427
|
+
| `(out, …scalars)` — no offset | `xyz_to_oklab`, `oklab_to_xyz`, `linear_srgb_to_oklab`, `oklab_to_linear_srgb`, `linear_srgb_to_okhsv`, `okhsv_to_linear_srgb`, `find_cusp`, `xyz_cmf_wyman`, `xyz_cmf_tabulated`, `sRGB_cmf` |
|
|
428
|
+
| `(…scalars) → new object` | `hsv2rgb`, `hsv2rgb_float`, `rgb2hsv`, `hex2rgb`, `int2rgb` |
|
|
429
|
+
| `(…scalars) → scalar` | `linear_to_PQ`, `PQ_to_linear`, `convert_channel_*`, `rgb_to_luminance`, `toe`, `toe_inv`, `compute_max_saturation`, `find_gamut_intersection`, `D65_spd_*`, `planckian_radiance`, `rgb_to_YCbCr_uint24`, `YCbCr_to_rgb_uint24`, `rgb_to_rgbe9995`, `rgb2uint24`, `rgb2uint32`, `rgb2hex` |
|
|
430
|
+
| `(Color, …) → new Color` | `color_lerp`, `color_lighten`, `color_darken`, `color_saturate`, `color_desaturate`, `color_scale_okhsv_channel` |
|
|
431
|
+
|
|
432
|
+
The split is not even directory-clean. `xyz/` holds both `rgb_to_xyz(out, in)` and
|
|
433
|
+
`xyz_cmf_wyman(out, nm)`. `oklab/` holds `(out, scalars)` conversions next to
|
|
434
|
+
`find_gamut_intersection(a,b,L1,C1,L0) → number`. Chaining across two conventions requires a
|
|
435
|
+
temporary and an offset the caller has to remember is *not* there:
|
|
436
|
+
|
|
437
|
+
```js
|
|
438
|
+
rgb_to_xyz(xyz, rgb); // no offsets
|
|
439
|
+
xyz_to_lab(lab, 0, xyz, 0); // offsets
|
|
440
|
+
lab_apply_hunt_adjustment(lab, 0, lab, 0);
|
|
441
|
+
```
|
|
442
|
+
|
|
443
|
+
— which is real code from `flip_compute_color_difference.js`.
|
|
444
|
+
|
|
445
|
+
**Recommendation:** the `(out, out_off, in, in_off)` form is the most capable and is what the newest
|
|
446
|
+
and most-used code uses; treat it as the target. The `(out, …scalars)` form is legitimate where the
|
|
447
|
+
inputs genuinely are not a contiguous triple (`kelvin_to_rgb`, the CMFs). What has no defence is
|
|
448
|
+
`(out, in)` without offsets on `rgb_to_xyz`/`xyz_to_rgb` — the two most-imported conversions in the
|
|
449
|
+
package (4 non-spec importers each) — and the allocating object-returning Layer-B set. Note this is
|
|
450
|
+
the same finding the `core/geom` review raised as *"API shape: parameter order, output-argument
|
|
451
|
+
placement, calling conventions"*; the same disease, one directory over.
|
|
452
|
+
|
|
453
|
+
### 3.2 Units are not encoded in names or checked at boundaries
|
|
454
|
+
|
|
455
|
+
| Unit | Members |
|
|
456
|
+
|---|---|
|
|
457
|
+
| `[0,1]` float | `Color`, `hsv2rgb_float`, `sRGB_*`, all of `oklab/`, `rgb2uint24/32` inputs |
|
|
458
|
+
| `[0,255]` | `hsv2rgb`, `rgb2hex`, `hex2rgb`, `int2rgb`, `rgb_to_YCbCr_uint24`, `YCbCr_to_rgb_uint24` |
|
|
459
|
+
| **mixed within one value** | `parse_color` returns `[0-255, 0-255, 0-255, 0-1]` — RGB in bytes, alpha normalised |
|
|
460
|
+
| `L* ∈ [0,100]` | `xyz_to_lab`, `xyz_to_ycxcz` |
|
|
461
|
+
| `1.0 = 10000 nits` | `PQ/*` (documented loudly, and correctly) |
|
|
462
|
+
| Kelvin / Kelvin÷100 | `kelvin_to_rgb` public vs its three private regime helpers |
|
|
463
|
+
|
|
464
|
+
`hsv2rgb` and `hsv2rgb_float` differ *only* in output scale, and the byte version is the one with
|
|
465
|
+
the unsuffixed name — so the default-looking call is the non-default unit. `parse_color`'s mixed
|
|
466
|
+
return is the most surprising: `Color.parse` compensates by dividing three of four elements by 255,
|
|
467
|
+
which is exactly the kind of knowledge that should not live in the caller.
|
|
468
|
+
|
|
469
|
+
### 3.3 Naming
|
|
470
|
+
|
|
471
|
+
- **`2` vs `to`.** `hsv2rgb`, `rgb2hsv`, `rgb2hex`, `hex2rgb`, `int2rgb`, `rgb2uint24`, `rgb2uint32`
|
|
472
|
+
(Layer B) against `rgb_to_xyz`, `xyz_to_lab`, `linear_to_PQ`, `rgb_to_ycocg`, … (Layers A/C).
|
|
473
|
+
Splits cleanly by age, which makes it a mechanical rename.
|
|
474
|
+
- **sRGB casing.** `sRGB_to_linear` / `linear_to_sRGB` / `sRGB_cmf` against `linear_srgb_to_oklab` /
|
|
475
|
+
`oklab_to_linear_srgb` / `linear_srgb_to_okhsv`. Same token, two casings, adjacent directories.
|
|
476
|
+
- **Directory casing.** `PQ`, `YCbCr`, `YCoCg`, `sRGB` uppercase; `colormap`, `hex`, `hsv`, `hunt`,
|
|
477
|
+
`illuminant`, `kelvin`, `lab`, `oklab`, `operations`, `tonemap`, `xyz`, `ycxcz` lowercase. Under
|
|
478
|
+
`YCoCg/` the functions are `rgb_to_ycocg` (lower) while under `YCbCr/` they are
|
|
479
|
+
`rgb_to_YCbCr_uint24` (mixed).
|
|
480
|
+
- **Two files named `XYZ.spec.js`** — [xyz/XYZ.spec.js](xyz/XYZ.spec.js) and
|
|
481
|
+
[oklab/XYZ.spec.js](oklab/XYZ.spec.js) — and *three* tests all called `"to/from consistency"`
|
|
482
|
+
(those two plus [sRGB/sRGB.spec.js](sRGB/sRGB.spec.js)). A failure report naming
|
|
483
|
+
`XYZ.spec.js > to/from consistency` is genuinely ambiguous.
|
|
484
|
+
- **`hunt/`** — see §1.3.
|
|
485
|
+
|
|
486
|
+
### 3.4 The unstated linear-vs-encoded contract — the most consequential inconsistency
|
|
487
|
+
|
|
488
|
+
Nothing in the package records which transfer function a value carries. Consider what a caller
|
|
489
|
+
actually gets:
|
|
490
|
+
|
|
491
|
+
- `Color.parse("#808080")` divides bytes by 255 → the `Color` holds **encoded** sRGB.
|
|
492
|
+
- `color_lighten(that)` calls `linear_srgb_to_okhsv(hsv, input.r, input.g, input.b)` → treats it as
|
|
493
|
+
**linear**.
|
|
494
|
+
- `Color.prototype.computeLuminance()` applies Rec.709 weights → correct only for **linear**.
|
|
495
|
+
- `Color.prototype.lerpColors` interpolates raw channels → correct only for **linear**.
|
|
496
|
+
- `kelvin_to_rgb`'s docstring explicitly says its output is **encoded** and warns you to convert
|
|
497
|
+
before mixing — the one place the distinction is stated.
|
|
498
|
+
|
|
499
|
+
So the most common construction path yields encoded values and three of the class's own methods
|
|
500
|
+
assume linear. Each individual choice is defensible; the combination is a silent, systematic error
|
|
501
|
+
in the perceptual operations. It is not visible in tests because every `operations/` spec builds its
|
|
502
|
+
inputs from raw floats rather than from `Color.parse`.
|
|
503
|
+
|
|
504
|
+
**Recommendation:** this is the one issue worth a design decision rather than a patch. Either (a)
|
|
505
|
+
declare `Color` to be linear, make `parse`/`toHex`/`toCssRGBAString` apply the transfer function, and
|
|
506
|
+
fix the resulting fallout; or (b) keep `Color` encoded and have `operations/` decode on entry and
|
|
507
|
+
re-encode on exit. (a) is the right answer for an HDR renderer and matches what `Color`'s own
|
|
508
|
+
`@param {number} [r=0] Red from 0 to 1 for SDR` and the HDR-tolerant `set()` already imply; (b) is
|
|
509
|
+
the smaller change. Either way, say so in `Color`'s class docstring, which currently says nothing.
|
|
510
|
+
|
|
511
|
+
### 3.5 CPU and GPU disagree about the same constants
|
|
512
|
+
|
|
513
|
+
`src/shade/renderer/shader/chunk/color/` reimplements much of this package in WGSL. Independent
|
|
514
|
+
implementations are fine; independently drifting constants are not.
|
|
515
|
+
|
|
516
|
+
| Quantity | CPU | GPU | Note |
|
|
517
|
+
|---|---|---|---|
|
|
518
|
+
| Rec.709 luma weights | `0.2126, 0.7152, 0.0722` ([rgb_to_luminance.js](rgb_to_luminance.js)) | `0.212639005871510, …` (`chunk_rgb_to_luminance`) | and `rgb_to_xyz` row 1 carries **20** digits of the same number. Three precisions, one quantity. |
|
|
519
|
+
| **PQ / ST 2084 reference white** | **203 nits** (BT.2408, [PQ/PQ_constants.js](PQ/PQ_constants.js)) | **100 nits** (`chunk_eotf_st2084`) | **Real divergence.** Same engine, two SDR-white conventions; content encoded on one path and decoded on the other is off by 2.03×. |
|
|
520
|
+
| sRGB OETF exponent | `0.4166666666666667` | `0.41666666666` | Harmless; still two spellings of `1/2.4`. |
|
|
521
|
+
| `rgb_to_xyz` matrix | 20 significant digits | 16 | Same matrix, two roundings. |
|
|
522
|
+
|
|
523
|
+
The PQ row is the one to act on. `PQ_constants.js` goes out of its way to document why folding a
|
|
524
|
+
white point into the transfer function is dangerous — and the GPU chunk folds a *different* white
|
|
525
|
+
point into it. Whichever is right, they must agree, and the constant should exist once.
|
|
526
|
+
|
|
527
|
+
### 3.6 Documentation, asserts and tests are applied unevenly
|
|
528
|
+
|
|
529
|
+
- **Docs.** Layer A files carry multi-paragraph rationale. Layer B files carry a bare `@param`
|
|
530
|
+
list, and `int2rgb.js` has no prose at all. `Color.js` — the most-imported file in the package —
|
|
531
|
+
documents index accessors that need no explanation and says nothing about colour space, HDR range,
|
|
532
|
+
the `onChanged` contract, or why the numeric index setters bypass `set()`. Its class JSDoc also
|
|
533
|
+
claims `@extends {Array.<number>}`, which is false (`Array.isArray(color) === false`).
|
|
534
|
+
- **Asserts.** `planckian_radiance` and `D65_spd_tabulated` validate their scalar inputs;
|
|
535
|
+
`xyz_cmf_tabulated`, `xyz_cmf_wyman`, `linear_to_PQ`, `D65_spd_analytical` do not. `kelvin_to_rgb`
|
|
536
|
+
asserts, `rgb_to_ycocg` does not. No visible rule.
|
|
537
|
+
- **`@author` / `@copyright`.** Present on `compute_max_saturation`, `find_cusp`,
|
|
538
|
+
`find_gamut_intersection`, `okhsv_to_linear_srgb`, `oklab_to_linear_srgb`, `oklab_to_xyz`,
|
|
539
|
+
`xyz_to_oklab`, `color_scale_okhsv_channel`, `PQ/*`; absent on `linear_srgb_to_okhsv`,
|
|
540
|
+
`linear_srgb_to_oklab`, `toe` — sitting in the same directory, from the same source.
|
|
541
|
+
- **Tests.** 158 tests over 33 files, but the distribution is 21 for `Color` and 1 each for
|
|
542
|
+
`Okhsv.spec.js`, `YCbCr.spec.js`, `oklab/XYZ.spec.js`, `xyz/XYZ.spec.js`, `sRGB.spec.js`,
|
|
543
|
+
`compute_max_saturation.spec.js`. Six of the package's hardest files share six assertions between
|
|
544
|
+
them. §4.5 lists what is untested.
|
|
545
|
+
|
|
546
|
+
---
|
|
547
|
+
|
|
548
|
+
## 4. Prominent missing features and members
|
|
549
|
+
|
|
550
|
+
### 4.1 Missing inverses and partners
|
|
551
|
+
|
|
552
|
+
Every one of these has its opposite direction already present.
|
|
553
|
+
|
|
554
|
+
| Present | Missing | Consequence |
|
|
555
|
+
|---|---|---|
|
|
556
|
+
| `xyz_to_lab` | **`lab_to_xyz`** | Lab is write-only. Nothing can move a colour *in* Lab and come back — the standard reason to go there. |
|
|
557
|
+
| `xyz_to_hpe` | `hpe_to_xyz` | Cone-space adaptation is one-way. |
|
|
558
|
+
| `rgb2hex` | alpha-aware `rgba2hex` | `hex2rgb` already **reads** `#RRGGBBAA`; the writer cannot produce it, and `Color.toHex()` silently drops alpha. |
|
|
559
|
+
| `hsv2rgb` (0-255) | `rgb2hsv_uint8` | Asymmetric pair — one direction has both unit variants, the other has one. |
|
|
560
|
+
| `Color.setHSL` | `Color.getHSL`, `Color.fromHSL` | `getHSV`/`fromHSV` exist; HSL has neither. |
|
|
561
|
+
| `Color.setHCL` / `setHSI` | `getHCL` / `getHSI` | Write-only. |
|
|
562
|
+
| `linear_srgb_to_okhsv` | `srgb_to_okhsv` (encoded input) | Every UI caller has encoded values (§3.4). |
|
|
563
|
+
| `oklab` L/a/b | **OkLCh** (`oklab_to_oklch` / back) | OkLCh is the space CSS Color 4 exposes and the natural one for hue rotation; the package has OkHSV but not OkLCh. |
|
|
564
|
+
| `toe` / `toe_inv` | **OkHSL** pair | Ottosson defines OkHSL alongside OkHSV; only OkHSV was ported. OkHSL is the better choice for lighten/darken (§4.3). |
|
|
565
|
+
| `linear_to_PQ` | HLG (`linear_to_HLG` / back) | The other half of the HDR transfer-function pair. |
|
|
566
|
+
|
|
567
|
+
### 4.2 Missing on `Color`
|
|
568
|
+
|
|
569
|
+
`Color` is the package's public face (29 non-spec importers) and its surface has gaps a caller hits
|
|
570
|
+
in the first hour:
|
|
571
|
+
|
|
572
|
+
- **No `static fromUint` / `fromHex` / `setHex`** — `fromUint` and `parse` exist as instance
|
|
573
|
+
methods, but only `fromRGB`, `fromHSV`, `fromArray` and `parse` are static. Constructing from a
|
|
574
|
+
packed integer takes two lines.
|
|
575
|
+
- **No arithmetic** beyond `multiply` / `multiplyScalar`: no `add`, `sub`, `addScaled`, `divide`.
|
|
576
|
+
Blending and accumulation — the two things an engine does most with colour — are absent.
|
|
577
|
+
- **No instance `lerp(other, f)`**, only `lerpColors(a, b, f)` which writes into `this` from two
|
|
578
|
+
*other* colours. `Color.lerp(a,b,f) → Color` as a static is also missing.
|
|
579
|
+
- **No `Color.gray(v)`**, no `Color.fromTemperature(k)` (despite `kelvin_to_rgb` existing), no
|
|
580
|
+
`Color.orange`/`grey` — the constant set stops at the six primaries plus black/white/transparent.
|
|
581
|
+
- **No `clamp()` / `isInGamut()`**, despite three methods that can produce out-of-gamut values (C8)
|
|
582
|
+
and OkHSV round-trips that overshoot by ~1e-7 (C12).
|
|
583
|
+
- **No space tag** (§3.4) — the single most valuable missing member.
|
|
584
|
+
- **`onChanged` is allocated eagerly for every instance.** Measured: 500k `Color` allocations take
|
|
585
|
+
**71.7 ms** versus **17.9 ms** for an otherwise identical class without the `Signal` — a **4×**
|
|
586
|
+
construction cost paid by every caller, including the many that never subscribe. A lazily created
|
|
587
|
+
signal (or a `Color`/`ObservableColor` split) is the standard fix, and matters because `Color` is
|
|
588
|
+
used in per-particle and per-vertex paths.
|
|
589
|
+
|
|
590
|
+
### 4.3 Missing spaces and operations
|
|
591
|
+
|
|
592
|
+
- **No gamut clipping.** `find_gamut_intersection` — the exact primitive Ottosson provides *for*
|
|
593
|
+
gamut clipping — is present, correct, and imported by nothing. There is no `gamut_clip_preserve_chroma`
|
|
594
|
+
/ `_project_to_lcusp` / `_adaptive_L0` entry point. This is the largest single missing feature: the
|
|
595
|
+
hard part is done and the 10-line wrapper that makes it usable is absent.
|
|
596
|
+
- **No colour-difference metrics beyond HyAB.** No ΔE76, ΔE94, ΔE2000, or ΔE-OK. `lab_distance_hyab`
|
|
597
|
+
exists solely for ꟻLIP; anything wanting a perceptual "are these two colours the same" must
|
|
598
|
+
reimplement.
|
|
599
|
+
- **No chromatic adaptation.** No Bradford / CAT02 / von Kries, and no white point other than D65.
|
|
600
|
+
`xyz_to_lab` hardcodes `D65_TRISTIMULUS_XYZ`, so D50 workflows (ICC, print, most JPEG/TIFF
|
|
601
|
+
metadata) are unreachable.
|
|
602
|
+
- **No primaries other than sRGB/Rec.709.** No Display-P3, Rec.2020, ACEScg, or Adobe RGB matrices —
|
|
603
|
+
while the GPU side ships all four `CRM_FROM_*` conversion matrices. An HDR renderer that can encode
|
|
604
|
+
PQ (a Rec.2020-centric format) has no CPU-side way to get *into* Rec.2020.
|
|
605
|
+
- **No BT.601 YCbCr.** The `YCbCr/` directory is Rec.709 only, so `engine/asset/loaders/image/jpeg/JpegImage.js`
|
|
606
|
+
carries its own inline BT.601 matrix (`1.402`, `-0.3441363`, `-0.71413636`, `1.772`) — the correct
|
|
607
|
+
coefficients for JPEG, which the package cannot supply.
|
|
608
|
+
- **No `Color`-level gradient/palette type**, despite `color_lerp` and `MAGMA_LUT` both existing.
|
|
609
|
+
|
|
610
|
+
### 4.4 CPU/GPU parity gaps
|
|
611
|
+
|
|
612
|
+
Every one of these exists on the GPU and not on the CPU, which matters for CPU-side baking,
|
|
613
|
+
thumbnailing, and test oracles:
|
|
614
|
+
|
|
615
|
+
| GPU has | CPU has |
|
|
616
|
+
|---|---|
|
|
617
|
+
| ACES, AgX, BT.2390, Khronos PBR, PBR-neutral, Reinhard-luma, GT7 | **ACES only** |
|
|
618
|
+
| inferno, plasma, turbo, viridis LUTs | **magma only** (and unused) |
|
|
619
|
+
| iCtCp (+ ST 2084 pair) | PQ scalars only, no iCtCp |
|
|
620
|
+
| LogLuv encode/decode | — |
|
|
621
|
+
| P3↔Rec2020↔Rec709 matrices | — |
|
|
622
|
+
| EV100 / exposure helpers | — |
|
|
623
|
+
| Purkinje shift | — |
|
|
624
|
+
|
|
625
|
+
### 4.5 Test gaps
|
|
626
|
+
|
|
627
|
+
**33 of 58 source files have no spec.** The ones that matter:
|
|
628
|
+
|
|
629
|
+
- **All of `oklab/`'s primitives** — `linear_srgb_to_oklab`, `oklab_to_linear_srgb`, `find_cusp`,
|
|
630
|
+
`toe`, `linear_srgb_to_okhsv`, `okhsv_to_linear_srgb` are covered only by two whole-pipeline
|
|
631
|
+
round-trip tests with 5 sample points between them. `find_gamut_intersection` has none at all.
|
|
632
|
+
- **`xyz/rgb_to_xyz` and `xyz_to_rgb`** (4 non-spec importers each) are covered only by a single
|
|
633
|
+
mutual round-trip, which cannot catch a matrix that is wrong in both directions.
|
|
634
|
+
- **`rgb_to_luminance`** — 3 non-spec importers, no spec.
|
|
635
|
+
- **`rgb2uint24` / `rgb2uint32`** — tested only indirectly through `Color`.
|
|
636
|
+
- **`ycxcz`, `YCoCg`, `PQ`** are covered only by combined round-trip specs; a systematically wrong
|
|
637
|
+
forward matrix with a matching inverse passes every one of them. Absolute reference values are
|
|
638
|
+
needed, not just inverses.
|
|
639
|
+
- **No seam/monotonicity tests** anywhere — which is why C1 survived.
|
|
640
|
+
- **No alpha tests** on any `operations/` function — which is why C6 survived.
|
|
641
|
+
- **No allocation or performance guard** on `Color`.
|
|
642
|
+
|
|
643
|
+
---
|
|
644
|
+
|
|
645
|
+
## 5. Redundancies
|
|
646
|
+
|
|
647
|
+
### 5.1 Three implementations of uint24 → RGB, with three different behaviours
|
|
648
|
+
|
|
649
|
+
| Site | Red | Green | Blue |
|
|
650
|
+
|---|---|---|---|
|
|
651
|
+
| [int2rgb.js](int2rgb.js) | `(v >> 16) & 255` | masked | masked |
|
|
652
|
+
| [Color.js:501](Color.js) `fromUint` | `v >> 16` **unmasked** | masked | masked |
|
|
653
|
+
| [parse_color.js:68](parse_color.js) | `c >> 16` **unmasked** | masked | masked |
|
|
654
|
+
|
|
655
|
+
`int2rgb` has no importers; the other two both carry C4/C10. Keep one (masked, validating), delete
|
|
656
|
+
`int2rgb.js`, and route the other two through it.
|
|
657
|
+
|
|
658
|
+
### 5.2 Hue wrapping copy-pasted four times, in two orders
|
|
659
|
+
|
|
660
|
+
`Color.setHSL`, `setHCL` and `setHSI` all do `h % 1` then a negative fixup; `hsv2rgb_float` does the
|
|
661
|
+
negative fixup *then* `% 1`. Both orders happen to be correct, which is why the divergence has
|
|
662
|
+
survived. One `wrap01(h)` helper in `core/math` replaces all four.
|
|
663
|
+
|
|
664
|
+
### 5.3 The HCL/HSI sextant switch is duplicated verbatim
|
|
665
|
+
|
|
666
|
+
`setHCL` and `setHSI` contain **byte-identical** 30-line `switch (Math.floor(h_face))` blocks; only
|
|
667
|
+
the `c`/`x` derivation above and the `m` offset below differ. `hsv2rgb_float` contains a third
|
|
668
|
+
variant of the same sextant dispatch. One `hue_to_rgb_sextant(out, h_face, c, x)` collapses all
|
|
669
|
+
three — and would have made C8's missing clamp a one-place fix.
|
|
670
|
+
|
|
671
|
+
### 5.4 Two lerps with different semantics and no signpost
|
|
672
|
+
|
|
673
|
+
`Color.prototype.lerpColors` interpolates raw channels linearly;
|
|
674
|
+
[operations/color_lerp.js](operations/color_lerp.js) converts to OkHSV and interpolates hue along the
|
|
675
|
+
shortest arc. Neither docstring mentions the other. A caller reaching for "blend two colours" will
|
|
676
|
+
find whichever they grep first, and the results differ substantially at mid-`t`.
|
|
677
|
+
|
|
678
|
+
### 5.5 Two D65 SPDs and two CMF sets, one of each dead
|
|
679
|
+
|
|
680
|
+
- `D65_spd_tabulated` (used by the Mie scattering code) vs `D65_spd_analytical` (**0 importers**),
|
|
681
|
+
which is the sole importer of `planckian_radiance` → a dead two-file subtree.
|
|
682
|
+
- `xyz_cmf_tabulated` (used) vs `xyz_cmf_wyman` (reachable only via `sRGB_cmf`, itself unused) →
|
|
683
|
+
a second dead two-file subtree.
|
|
684
|
+
|
|
685
|
+
Keeping an analytic alternative alongside a tabulated one is defensible — the MBOIT chunk library
|
|
686
|
+
sets that precedent. If that is the intent here, say so in the file header the way the MBOIT chunks
|
|
687
|
+
do; today nothing marks them as deliberately-retained.
|
|
688
|
+
|
|
689
|
+
### 5.6 `sRGB_cmf` documents itself as the wrong choice
|
|
690
|
+
|
|
691
|
+
Its own docstring reads *"Prefer to integrate in XYZ space and then perform the conversion to sRGB"* —
|
|
692
|
+
i.e. do not call this. It has no importers. It is a two-line composition of `xyz_cmf_wyman` and
|
|
693
|
+
`xyz_to_rgb` that a caller can write inline. Deleting it also frees `xyz_cmf_wyman` for deletion or
|
|
694
|
+
promotion.
|
|
695
|
+
|
|
696
|
+
### 5.7 CPU/GPU duplication with drifting constants
|
|
697
|
+
|
|
698
|
+
`rgb_to_xyz`, `xyz_to_rgb`, the OkLab pair, the sRGB OETF, ACES, rgbe9995, YCoCg and ST 2084 all
|
|
699
|
+
exist twice (§3.5). The duplication is unavoidable — WGSL cannot import JS — but the *constants*
|
|
700
|
+
need not be: a single generated source of truth for matrices and transfer-function coefficients
|
|
701
|
+
would remove the 203-vs-100-nit divergence structurally rather than by vigilance. The AVIF work
|
|
702
|
+
already generates normative constant tables into source (`feat(avif): generate the normative AV1
|
|
703
|
+
constant tables`), so the mechanism exists in-repo.
|
|
704
|
+
|
|
705
|
+
### 5.8 `prototype_kelvin_to_rgb.js`
|
|
706
|
+
|
|
707
|
+
A dev visualisation script inside a core library directory. It executes at import time
|
|
708
|
+
(`document.body.append`), imports from `engine/` and `view/` — inverting the `core/` layering — and
|
|
709
|
+
would crash any non-DOM consumer that touched it. Delete it, or move it to a playground directory
|
|
710
|
+
where side-effecting entry points are expected.
|
|
711
|
+
|
|
712
|
+
### 5.9 Test-name collisions
|
|
713
|
+
|
|
714
|
+
`xyz/XYZ.spec.js` and `oklab/XYZ.spec.js` — same filename, same suite name, plus `sRGB/sRGB.spec.js`
|
|
715
|
+
sharing the test name `"to/from consistency"`. Rename to `rgb_xyz_roundtrip.spec.js`,
|
|
716
|
+
`oklab_xyz_roundtrip.spec.js`, `srgb_transfer_roundtrip.spec.js`.
|
|
717
|
+
|
|
718
|
+
---
|
|
719
|
+
|
|
720
|
+
## 6. Suggested order of work
|
|
721
|
+
|
|
722
|
+
Sequenced by risk-reduction per unit of effort; each step is independently shippable.
|
|
723
|
+
|
|
724
|
+
1. **Alpha** — C5, C6. Two small edits (`result.a = input.a`, route through `set()`), removes a
|
|
725
|
+
whole class of silent data loss, and fixes the `onChanged` blind spot at the same time.
|
|
726
|
+
2. **Ingest hardening** — C3, C4, C11. Make `parse_color`/`hex2rgb` reject what they cannot parse
|
|
727
|
+
instead of emitting `NaN` and negative channels. Add short-hex support while in there.
|
|
728
|
+
3. **Kelvin seam** — C1, then C2 falls out. Add a monotonicity/continuity spec that sweeps
|
|
729
|
+
temperature; it is the test shape that would have caught this and is reusable for the 1000 K and
|
|
730
|
+
6600 K seams.
|
|
731
|
+
4. **Decide the colour-space contract** (§3.4) and write it into `Color`'s class docstring. This is
|
|
732
|
+
a decision, not a patch, and everything in `operations/` depends on it.
|
|
733
|
+
5. **Delete the dead perimeter** — §1.4's 8 unreferenced files (`prototype_kelvin_to_rgb.js` first),
|
|
734
|
+
§5.5's two dead subtrees, §5.6, §5.1's `int2rgb.js`. Roughly 400 lines, zero behavioural risk.
|
|
735
|
+
For anything deliberately retained, add the "kept on purpose, here's why" header.
|
|
736
|
+
6. **Reconcile the PQ white point** with the GPU (§3.5) — a correctness issue that only shows up
|
|
737
|
+
across the CPU/GPU boundary and will be expensive to diagnose later.
|
|
738
|
+
7. **Gamut clipping** (§4.3) — the highest-value missing feature, and most of it is already written.
|
|
739
|
+
8. **Convention convergence** (§3.1, §3.2, §3.3) — mechanical, large, best done last and in one
|
|
740
|
+
sweep so the churn lands once. `rgb_to_xyz`/`xyz_to_rgb` gaining offsets is the highest-value
|
|
741
|
+
single change.
|
|
742
|
+
|
|
743
|
+
---
|
|
744
|
+
|
|
745
|
+
## Appendix A — reproduction
|
|
746
|
+
|
|
747
|
+
Every *(reproduced)* claim above came from running the modules directly. To re-check, save as
|
|
748
|
+
`probe.mjs` in the repo root and run `node probe.mjs`:
|
|
749
|
+
|
|
750
|
+
```js
|
|
751
|
+
import { Color } from './src/core/color/Color.js';
|
|
752
|
+
import { kelvin_to_rgb } from './src/core/color/kelvin/kelvin_to_rgb.js';
|
|
753
|
+
import { rgb_to_kelvin } from './src/core/color/kelvin/rgb_to_kelvin.js';
|
|
754
|
+
import { parse_color } from './src/core/color/parse_color.js';
|
|
755
|
+
import { hex2rgb } from './src/core/color/hex/hex2rgb.js';
|
|
756
|
+
import { color_lighten } from './src/core/color/operations/color_lighten.js';
|
|
757
|
+
|
|
758
|
+
const o = [0, 0, 0];
|
|
759
|
+
for (const T of [1999.99, 2000, 2000.01]) { // C1
|
|
760
|
+
kelvin_to_rgb(o, 0, T); console.log(T, o.map(x => x.toFixed(6)).join(', '));
|
|
761
|
+
}
|
|
762
|
+
for (const T of [800, 1500, 1999, 2100]) { // C2
|
|
763
|
+
kelvin_to_rgb(o, 0, T); console.log(T, '->', rgb_to_kelvin(o, 0));
|
|
764
|
+
}
|
|
765
|
+
console.log(hex2rgb('#f00')); // C3 b: NaN
|
|
766
|
+
console.log(parse_color(0xFF8000FF)); // C4 r: -128
|
|
767
|
+
console.log(Color.from_linear_to_sRGB(new Color(0.5, 0.5, 0.5, 0.25)).toJSON()); // C5 a: 1
|
|
768
|
+
const out = new Color(); let fired = 0; out.onChanged.add(() => fired++);
|
|
769
|
+
Color.from_sRGB_to_linear(new Color(0.5, 0.5, 0.5), out);
|
|
770
|
+
console.log('onChanged fired:', fired); // C5 0
|
|
771
|
+
console.log(color_lighten(new Color(0.2, 0.4, 0.6, 0.3), 0.5).toJSON()); // C6 a: 1
|
|
772
|
+
console.log(new Color(1, 1, 1, 1).toUint32()); // C7 -1
|
|
773
|
+
const c = new Color(); c.setHCL(0, 1, 0.5); console.log(c.toJSON()); // C8 r: 1.2
|
|
774
|
+
console.log(parse_color('background: rgb(1,2,3) !important')); // C11
|
|
775
|
+
console.log(color_lighten(new Color(0, 0, 0), 1).toJSON()); // C13 unchanged
|
|
776
|
+
```
|
|
777
|
+
|
|
778
|
+
Baseline for comparison: `npx vitest run src/core/color` → **33 files, 158 tests, all passing** as of
|
|
779
|
+
this review. None of the defects above is caught by the existing suite.
|