summaryrefslogtreecommitdiff
path: root/media
diff options
context:
space:
mode:
authorJob Bautista <jobbautista9@protonmail.com>2022-12-22 12:53:52 +0800
committerJob Bautista <jobbautista9@protonmail.com>2022-12-22 12:53:52 +0800
commit3d070292327405acdc89279cee1a54a233d05cc2 (patch)
treead5c5df11e0c429b00216940dc1a7d6a6778a09a /media
parentfc62ce007670111f3b7f76b27d22810a530d8973 (diff)
downloaduxp-3d070292327405acdc89279cee1a54a233d05cc2.tar.gz
Issue #2061 - Part 1: Update libjxl source to 0.7.0.
Diffstat (limited to 'media')
-rw-r--r--media/libjxl/src/.gitmodules5
-rw-r--r--media/libjxl/src/AUTHORS9
-rw-r--r--media/libjxl/src/CHANGELOG.md77
-rw-r--r--media/libjxl/src/CMakeLists.txt77
-rw-r--r--media/libjxl/src/README.md12
-rwxr-xr-xmedia/libjxl/src/bash_test.sh8
-rwxr-xr-xmedia/libjxl/src/ci.sh97
-rw-r--r--media/libjxl/src/cmake/FindBrotli.cmake2
-rw-r--r--media/libjxl/src/cmake/FindHWY.cmake2
-rw-r--r--media/libjxl/src/cmake/FindLCMS2.cmake2
-rw-r--r--media/libjxl/src/debian/changelog2
-rw-r--r--media/libjxl/src/debian/control1
-rw-r--r--media/libjxl/src/debian/copyright28
-rwxr-xr-xmedia/libjxl/src/deps.sh4
-rw-r--r--media/libjxl/src/doc/developing_in_debian.md2
-rw-r--r--media/libjxl/src/doc/release.md25
-rw-r--r--media/libjxl/src/doc/software_support.md9
-rwxr-xr-xmedia/libjxl/src/js-wasm-wrapper.sh19
-rw-r--r--media/libjxl/src/lib/CMakeLists.txt10
-rw-r--r--media/libjxl/src/lib/extras/codec.cc77
-rw-r--r--media/libjxl/src/lib/extras/codec.h2
-rw-r--r--media/libjxl/src/lib/extras/codec_test.cc441
-rw-r--r--media/libjxl/src/lib/extras/dec/decode.cc19
-rw-r--r--media/libjxl/src/lib/extras/dec/decode.h22
-rw-r--r--media/libjxl/src/lib/extras/dec/jxl.cc480
-rw-r--r--media/libjxl/src/lib/extras/dec/jxl.h66
-rw-r--r--media/libjxl/src/lib/extras/dec/pnm.cc11
-rw-r--r--media/libjxl/src/lib/extras/enc/apng.cc235
-rw-r--r--media/libjxl/src/lib/extras/enc/apng.h13
-rw-r--r--media/libjxl/src/lib/extras/enc/encode.cc136
-rw-r--r--media/libjxl/src/lib/extras/enc/encode.h77
-rw-r--r--media/libjxl/src/lib/extras/enc/exr.cc211
-rw-r--r--media/libjxl/src/lib/extras/enc/exr.h15
-rw-r--r--media/libjxl/src/lib/extras/enc/jpg.cc225
-rw-r--r--media/libjxl/src/lib/extras/enc/jpg.h19
-rw-r--r--media/libjxl/src/lib/extras/enc/npy.cc322
-rw-r--r--media/libjxl/src/lib/extras/enc/npy.h23
-rw-r--r--media/libjxl/src/lib/extras/enc/pgx.cc129
-rw-r--r--media/libjxl/src/lib/extras/enc/pgx.h13
-rw-r--r--media/libjxl/src/lib/extras/enc/pnm.cc198
-rw-r--r--media/libjxl/src/lib/extras/enc/pnm.h16
-rw-r--r--media/libjxl/src/lib/extras/exif.cc55
-rw-r--r--media/libjxl/src/lib/extras/exif.h20
-rw-r--r--media/libjxl/src/lib/extras/packed_image.h56
-rw-r--r--media/libjxl/src/lib/extras/packed_image_convert.cc132
-rw-r--r--media/libjxl/src/lib/extras/render_hdr.cc60
-rw-r--r--media/libjxl/src/lib/extras/render_hdr.h27
-rw-r--r--media/libjxl/src/lib/extras/tone_mapping.cc118
-rw-r--r--media/libjxl/src/lib/extras/tone_mapping_gbench.cc3
-rw-r--r--media/libjxl/src/lib/include/jxl/cms_interface.h2
-rw-r--r--media/libjxl/src/lib/include/jxl/codestream_header.h13
-rw-r--r--media/libjxl/src/lib/include/jxl/color_encoding.h4
-rw-r--r--media/libjxl/src/lib/include/jxl/decode.h1273
-rw-r--r--media/libjxl/src/lib/include/jxl/encode.h122
-rw-r--r--media/libjxl/src/lib/include/jxl/types.h27
-rw-r--r--media/libjxl/src/lib/jxl.cmake71
-rw-r--r--media/libjxl/src/lib/jxl/alpha.cc9
-rw-r--r--media/libjxl/src/lib/jxl/alpha.h1
-rw-r--r--media/libjxl/src/lib/jxl/alpha_test.cc3
-rw-r--r--media/libjxl/src/lib/jxl/ans_common.cc6
-rw-r--r--media/libjxl/src/lib/jxl/ans_common.h4
-rw-r--r--media/libjxl/src/lib/jxl/ans_test.cc2
-rw-r--r--media/libjxl/src/lib/jxl/aux_out.cc6
-rw-r--r--media/libjxl/src/lib/jxl/aux_out.h81
-rw-r--r--media/libjxl/src/lib/jxl/base/cache_aligned.cc5
-rw-r--r--media/libjxl/src/lib/jxl/base/compiler_specific.h12
-rw-r--r--media/libjxl/src/lib/jxl/base/data_parallel.h3
-rw-r--r--media/libjxl/src/lib/jxl/base/file_io.h3
-rw-r--r--media/libjxl/src/lib/jxl/base/random.h3
-rw-r--r--media/libjxl/src/lib/jxl/blending_test.cc7
-rw-r--r--media/libjxl/src/lib/jxl/butteraugli/butteraugli.cc335
-rw-r--r--media/libjxl/src/lib/jxl/codec_in_out.h53
-rw-r--r--media/libjxl/src/lib/jxl/color_encoding_internal.h2
-rw-r--r--media/libjxl/src/lib/jxl/color_management.cc3
-rw-r--r--media/libjxl/src/lib/jxl/color_management_test.cc2
-rw-r--r--media/libjxl/src/lib/jxl/compressed_dc.cc30
-rw-r--r--media/libjxl/src/lib/jxl/convolve-inl.h178
-rw-r--r--media/libjxl/src/lib/jxl/convolve.cc1332
-rw-r--r--media/libjxl/src/lib/jxl/convolve.h26
-rw-r--r--media/libjxl/src/lib/jxl/convolve_separable5.cc261
-rw-r--r--media/libjxl/src/lib/jxl/convolve_separable7.cc285
-rw-r--r--media/libjxl/src/lib/jxl/convolve_slow.cc212
-rw-r--r--media/libjxl/src/lib/jxl/convolve_symmetric3.cc194
-rw-r--r--media/libjxl/src/lib/jxl/convolve_symmetric5.cc185
-rw-r--r--media/libjxl/src/lib/jxl/convolve_test.cc6
-rw-r--r--media/libjxl/src/lib/jxl/data_parallel_test.cc2
-rw-r--r--media/libjxl/src/lib/jxl/dct-inl.h29
-rw-r--r--media/libjxl/src/lib/jxl/dec_ans.cc4
-rw-r--r--media/libjxl/src/lib/jxl/dec_cache.cc45
-rw-r--r--media/libjxl/src/lib/jxl/dec_cache.h6
-rw-r--r--media/libjxl/src/lib/jxl/dec_context_map.cc8
-rw-r--r--media/libjxl/src/lib/jxl/dec_context_map.h4
-rw-r--r--media/libjxl/src/lib/jxl/dec_external_image.cc11
-rw-r--r--media/libjxl/src/lib/jxl/dec_external_image.h3
-rw-r--r--media/libjxl/src/lib/jxl/dec_file.cc177
-rw-r--r--media/libjxl/src/lib/jxl/dec_file.h48
-rw-r--r--media/libjxl/src/lib/jxl/dec_frame.cc364
-rw-r--r--media/libjxl/src/lib/jxl/dec_frame.h140
-rw-r--r--media/libjxl/src/lib/jxl/dec_group.cc27
-rw-r--r--media/libjxl/src/lib/jxl/dec_group.h1
-rw-r--r--media/libjxl/src/lib/jxl/dec_modular.cc102
-rw-r--r--media/libjxl/src/lib/jxl/dec_modular.h9
-rw-r--r--media/libjxl/src/lib/jxl/dec_noise.cc5
-rw-r--r--media/libjxl/src/lib/jxl/dec_params.h52
-rw-r--r--media/libjxl/src/lib/jxl/dec_patch_dictionary.cc206
-rw-r--r--media/libjxl/src/lib/jxl/dec_patch_dictionary.h53
-rw-r--r--media/libjxl/src/lib/jxl/dec_tone_mapping-inl.h232
-rw-r--r--media/libjxl/src/lib/jxl/dec_transforms-inl.h3
-rw-r--r--media/libjxl/src/lib/jxl/dec_xyb-inl.h27
-rw-r--r--media/libjxl/src/lib/jxl/dec_xyb.cc196
-rw-r--r--media/libjxl/src/lib/jxl/dec_xyb.h40
-rw-r--r--media/libjxl/src/lib/jxl/decode.cc1127
-rw-r--r--media/libjxl/src/lib/jxl/decode_test.cc1831
-rw-r--r--media/libjxl/src/lib/jxl/enc_ac_strategy.cc26
-rw-r--r--media/libjxl/src/lib/jxl/enc_ac_strategy.h4
-rw-r--r--media/libjxl/src/lib/jxl/enc_adaptive_quantization.cc118
-rw-r--r--media/libjxl/src/lib/jxl/enc_ans.cc34
-rw-r--r--media/libjxl/src/lib/jxl/enc_ar_control_field.cc27
-rw-r--r--media/libjxl/src/lib/jxl/enc_bit_writer.h7
-rw-r--r--media/libjxl/src/lib/jxl/enc_butteraugli_pnorm.cc21
-rw-r--r--media/libjxl/src/lib/jxl/enc_cache.cc62
-rw-r--r--media/libjxl/src/lib/jxl/enc_chroma_from_luma.cc59
-rw-r--r--media/libjxl/src/lib/jxl/enc_cluster.cc103
-rw-r--r--media/libjxl/src/lib/jxl/enc_cluster.h3
-rw-r--r--media/libjxl/src/lib/jxl/enc_coeff_order.cc3
-rw-r--r--media/libjxl/src/lib/jxl/enc_color_management.cc49
-rw-r--r--media/libjxl/src/lib/jxl/enc_context_map.cc7
-rw-r--r--media/libjxl/src/lib/jxl/enc_context_map.h3
-rw-r--r--media/libjxl/src/lib/jxl/enc_detect_dots.cc35
-rw-r--r--media/libjxl/src/lib/jxl/enc_entropy_coder.cc14
-rw-r--r--media/libjxl/src/lib/jxl/enc_external_image.cc20
-rw-r--r--media/libjxl/src/lib/jxl/enc_external_image.h4
-rw-r--r--media/libjxl/src/lib/jxl/enc_external_image_gbench.cc1
-rw-r--r--media/libjxl/src/lib/jxl/enc_external_image_test.cc28
-rw-r--r--media/libjxl/src/lib/jxl/enc_fast_heuristics.cc361
-rw-r--r--media/libjxl/src/lib/jxl/enc_file.cc7
-rw-r--r--media/libjxl/src/lib/jxl/enc_frame.cc84
-rw-r--r--media/libjxl/src/lib/jxl/enc_group.cc20
-rw-r--r--media/libjxl/src/lib/jxl/enc_heuristics.cc21
-rw-r--r--media/libjxl/src/lib/jxl/enc_heuristics.h9
-rw-r--r--media/libjxl/src/lib/jxl/enc_image_bundle.cc1
-rw-r--r--media/libjxl/src/lib/jxl/enc_modular.cc698
-rw-r--r--media/libjxl/src/lib/jxl/enc_modular.h33
-rw-r--r--media/libjxl/src/lib/jxl/enc_params.h8
-rw-r--r--media/libjxl/src/lib/jxl/enc_patch_dictionary.cc124
-rw-r--r--media/libjxl/src/lib/jxl/enc_patch_dictionary.h17
-rw-r--r--media/libjxl/src/lib/jxl/enc_photon_noise_test.cc2
-rw-r--r--media/libjxl/src/lib/jxl/enc_quant_weights.cc13
-rw-r--r--media/libjxl/src/lib/jxl/enc_quant_weights.h2
-rw-r--r--media/libjxl/src/lib/jxl/enc_xyb.cc103
-rw-r--r--media/libjxl/src/lib/jxl/enc_xyb.h3
-rw-r--r--media/libjxl/src/lib/jxl/encode.cc661
-rw-r--r--media/libjxl/src/lib/jxl/encode_internal.h84
-rw-r--r--media/libjxl/src/lib/jxl/encode_test.cc187
-rw-r--r--media/libjxl/src/lib/jxl/epf.cc2
-rw-r--r--media/libjxl/src/lib/jxl/exif.cc89
-rw-r--r--media/libjxl/src/lib/jxl/exif.h66
-rw-r--r--media/libjxl/src/lib/jxl/fast_math-inl.h105
-rw-r--r--media/libjxl/src/lib/jxl/fast_math_test.cc20
-rw-r--r--media/libjxl/src/lib/jxl/fields_test.cc2
-rw-r--r--media/libjxl/src/lib/jxl/frame_header.cc99
-rw-r--r--media/libjxl/src/lib/jxl/frame_header.h24
-rw-r--r--media/libjxl/src/lib/jxl/gauss_blur.cc41
-rw-r--r--media/libjxl/src/lib/jxl/gauss_blur_gbench.cc28
-rw-r--r--media/libjxl/src/lib/jxl/gradient_test.cc7
-rw-r--r--media/libjxl/src/lib/jxl/headers.cc14
-rw-r--r--media/libjxl/src/lib/jxl/headers.h4
-rw-r--r--media/libjxl/src/lib/jxl/image.h43
-rw-r--r--media/libjxl/src/lib/jxl/image_bundle.cc27
-rw-r--r--media/libjxl/src/lib/jxl/image_bundle.h4
-rw-r--r--media/libjxl/src/lib/jxl/image_metadata.cc56
-rw-r--r--media/libjxl/src/lib/jxl/image_metadata.h9
-rw-r--r--media/libjxl/src/lib/jxl/jpeg/jpeg_data.cc5
-rw-r--r--media/libjxl/src/lib/jxl/jpeg/jpeg_data.h1
-rw-r--r--media/libjxl/src/lib/jxl/jxl_test.cc657
-rw-r--r--media/libjxl/src/lib/jxl/libjxl.pc.in5
-rw-r--r--media/libjxl/src/lib/jxl/modular/encoding/context_predict.h2
-rw-r--r--media/libjxl/src/lib/jxl/modular/encoding/enc_encoding.cc3
-rw-r--r--media/libjxl/src/lib/jxl/modular/encoding/enc_ma.cc55
-rw-r--r--media/libjxl/src/lib/jxl/modular/encoding/enc_ma.h4
-rw-r--r--media/libjxl/src/lib/jxl/modular/encoding/encoding.cc3
-rw-r--r--media/libjxl/src/lib/jxl/modular/modular_image.cc16
-rw-r--r--media/libjxl/src/lib/jxl/modular/modular_image.h10
-rw-r--r--media/libjxl/src/lib/jxl/modular/transform/enc_palette.cc40
-rw-r--r--media/libjxl/src/lib/jxl/modular/transform/palette.h6
-rw-r--r--media/libjxl/src/lib/jxl/modular/transform/rct.cc17
-rw-r--r--media/libjxl/src/lib/jxl/modular/transform/squeeze.cc85
-rw-r--r--media/libjxl/src/lib/jxl/modular/transform/transform.cc6
-rw-r--r--media/libjxl/src/lib/jxl/modular_test.cc186
-rw-r--r--media/libjxl/src/lib/jxl/opsin_image_test.cc5
-rw-r--r--media/libjxl/src/lib/jxl/passes_test.cc106
-rw-r--r--media/libjxl/src/lib/jxl/patch_dictionary_test.cc7
-rw-r--r--media/libjxl/src/lib/jxl/preview_test.cc7
-rw-r--r--media/libjxl/src/lib/jxl/progressive_split.h5
-rw-r--r--media/libjxl/src/lib/jxl/quant_weights.cc22
-rw-r--r--media/libjxl/src/lib/jxl/quantizer-inl.h12
-rw-r--r--media/libjxl/src/lib/jxl/quantizer.cc20
-rw-r--r--media/libjxl/src/lib/jxl/quantizer.h17
-rw-r--r--media/libjxl/src/lib/jxl/quantizer_test.cc4
-rw-r--r--media/libjxl/src/lib/jxl/rational_polynomial-inl.h10
-rw-r--r--media/libjxl/src/lib/jxl/rational_polynomial_test.cc13
-rw-r--r--media/libjxl/src/lib/jxl/render_pipeline/low_memory_render_pipeline.cc357
-rw-r--r--media/libjxl/src/lib/jxl/render_pipeline/render_pipeline.cc6
-rw-r--r--media/libjxl/src/lib/jxl/render_pipeline/render_pipeline_stage.h15
-rw-r--r--media/libjxl/src/lib/jxl/render_pipeline/render_pipeline_test.cc119
-rw-r--r--media/libjxl/src/lib/jxl/render_pipeline/simple_render_pipeline.cc7
-rw-r--r--media/libjxl/src/lib/jxl/render_pipeline/stage_chroma_upsampling.cc8
-rw-r--r--media/libjxl/src/lib/jxl/render_pipeline/stage_epf.cc94
-rw-r--r--media/libjxl/src/lib/jxl/render_pipeline/stage_from_linear.cc189
-rw-r--r--media/libjxl/src/lib/jxl/render_pipeline/stage_from_linear.h20
-rw-r--r--media/libjxl/src/lib/jxl/render_pipeline/stage_gaborish.cc11
-rw-r--r--media/libjxl/src/lib/jxl/render_pipeline/stage_noise.cc108
-rw-r--r--media/libjxl/src/lib/jxl/render_pipeline/stage_patches.cc24
-rw-r--r--media/libjxl/src/lib/jxl/render_pipeline/stage_patches.h2
-rw-r--r--media/libjxl/src/lib/jxl/render_pipeline/stage_to_linear.cc200
-rw-r--r--media/libjxl/src/lib/jxl/render_pipeline/stage_to_linear.h21
-rw-r--r--media/libjxl/src/lib/jxl/render_pipeline/stage_tone_mapping.cc151
-rw-r--r--media/libjxl/src/lib/jxl/render_pipeline/stage_tone_mapping.h37
-rw-r--r--media/libjxl/src/lib/jxl/render_pipeline/stage_upsampling.cc35
-rw-r--r--media/libjxl/src/lib/jxl/render_pipeline/stage_write.cc36
-rw-r--r--media/libjxl/src/lib/jxl/render_pipeline/stage_write.h2
-rw-r--r--media/libjxl/src/lib/jxl/render_pipeline/stage_xyb.cc154
-rw-r--r--media/libjxl/src/lib/jxl/render_pipeline/stage_xyb.h10
-rw-r--r--media/libjxl/src/lib/jxl/render_pipeline/stage_ycbcr.cc24
-rw-r--r--media/libjxl/src/lib/jxl/roundtrip_test.cc7
-rw-r--r--media/libjxl/src/lib/jxl/sanitizers.h17
-rw-r--r--media/libjxl/src/lib/jxl/speed_tier_test.cc7
-rw-r--r--media/libjxl/src/lib/jxl/splines.cc24
-rw-r--r--media/libjxl/src/lib/jxl/splines_test.cc8
-rw-r--r--media/libjxl/src/lib/jxl/test_image.h103
-rw-r--r--media/libjxl/src/lib/jxl/test_utils.h153
-rw-r--r--media/libjxl/src/lib/jxl/testdata.h34
-rw-r--r--media/libjxl/src/lib/jxl/toc.cc34
-rw-r--r--media/libjxl/src/lib/jxl/toc.h5
-rw-r--r--media/libjxl/src/lib/jxl/toc_test.cc2
-rw-r--r--media/libjxl/src/lib/jxl/transfer_functions-inl.h57
-rw-r--r--media/libjxl/src/lib/jxl/version.h.in39
-rw-r--r--media/libjxl/src/lib/jxl/xorshift128plus-inl.h8
-rw-r--r--media/libjxl/src/lib/jxl/xorshift128plus_test.cc6
-rw-r--r--media/libjxl/src/lib/jxl_extras.cmake110
-rw-r--r--media/libjxl/src/lib/jxl_tests.cmake18
-rw-r--r--media/libjxl/src/lib/jxl_threads.cmake22
-rw-r--r--media/libjxl/src/lib/lib.gni29
-rw-r--r--media/libjxl/src/lib/profiler/tsc_timer.h3
-rw-r--r--media/libjxl/src/lib/threads/libjxl_threads.pc.in5
-rw-r--r--media/libjxl/src/lib/threads/thread_parallel_runner_internal.cc6
-rw-r--r--media/libjxl/src/lib/threads/thread_parallel_runner_test.cc2
-rw-r--r--media/libjxl/src/plugins/gdk-pixbuf/CMakeLists.txt4
-rw-r--r--media/libjxl/src/third_party/CMakeLists.txt110
-rw-r--r--media/libjxl/src/third_party/lcms2.cmake2
-rw-r--r--media/libjxl/src/third_party/testing.cmake83
-rw-r--r--media/libjxl/src/tools/CMakeLists.txt182
-rw-r--r--media/libjxl/src/tools/args.h62
-rw-r--r--media/libjxl/src/tools/benchmark/benchmark_codec.cc3
-rw-r--r--media/libjxl/src/tools/benchmark/benchmark_codec.h5
-rw-r--r--media/libjxl/src/tools/benchmark/benchmark_codec_avif.cc4
-rw-r--r--media/libjxl/src/tools/benchmark/benchmark_codec_custom.cc6
-rw-r--r--media/libjxl/src/tools/benchmark/benchmark_codec_jpeg.cc55
-rw-r--r--media/libjxl/src/tools/benchmark/benchmark_codec_jxl.cc185
-rw-r--r--media/libjxl/src/tools/benchmark/benchmark_codec_png.cc9
-rw-r--r--media/libjxl/src/tools/benchmark/benchmark_codec_webp.cc24
-rw-r--r--media/libjxl/src/tools/benchmark/benchmark_stats.cc7
-rw-r--r--media/libjxl/src/tools/benchmark/benchmark_xl.cc22
-rw-r--r--media/libjxl/src/tools/box/CMakeLists.txt2
-rw-r--r--media/libjxl/src/tools/box/box.cc55
-rw-r--r--media/libjxl/src/tools/box/box.h7
-rwxr-xr-xmedia/libjxl/src/tools/build_cleaner.py6
-rw-r--r--media/libjxl/src/tools/butteraugli_main.cc10
-rw-r--r--media/libjxl/src/tools/cjxl.cc769
-rw-r--r--media/libjxl/src/tools/cjxl.h109
-rw-r--r--media/libjxl/src/tools/cjxl_fuzzer.cc231
-rw-r--r--media/libjxl/src/tools/cjxl_main.cc1246
-rw-r--r--media/libjxl/src/tools/cjxl_ng_main.cc922
-rw-r--r--media/libjxl/src/tools/cmdline.h80
-rw-r--r--media/libjxl/src/tools/codec_config.cc6
-rw-r--r--media/libjxl/src/tools/codec_config.h1
-rw-r--r--media/libjxl/src/tools/conformance/CMakeLists.txt3
-rwxr-xr-xmedia/libjxl/src/tools/conformance/conformance.py116
-rw-r--r--media/libjxl/src/tools/conformance/djxl_conformance.cc669
-rwxr-xr-xmedia/libjxl/src/tools/conformance/generator.py9
-rwxr-xr-xmedia/libjxl/src/tools/conformance/tooling_test.sh4
-rw-r--r--media/libjxl/src/tools/djxl.cc329
-rw-r--r--media/libjxl/src/tools/djxl.h91
-rw-r--r--media/libjxl/src/tools/djxl_fuzzer_test.cc44
-rw-r--r--media/libjxl/src/tools/djxl_main.cc526
-rw-r--r--media/libjxl/src/tools/djxl_ng_main.cc476
-rw-r--r--media/libjxl/src/tools/file_io.cc75
-rw-r--r--media/libjxl/src/tools/file_io.h23
-rw-r--r--media/libjxl/src/tools/fuzzer_corpus.cc16
-rw-r--r--media/libjxl/src/tools/hdr/generate_lut_template.cc1
-rw-r--r--media/libjxl/src/tools/jxl_emcc.cc269
-rw-r--r--media/libjxl/src/tools/jxlinfo.c26
-rwxr-xr-xmedia/libjxl/src/tools/reference_zip.sh4
-rw-r--r--media/libjxl/src/tools/roundtrip_test.sh91
-rw-r--r--media/libjxl/src/tools/speed_stats.cc21
-rw-r--r--media/libjxl/src/tools/speed_stats.h6
296 files changed, 15491 insertions, 12004 deletions
diff --git a/media/libjxl/src/.gitmodules b/media/libjxl/src/.gitmodules
index b211bed3be..bd008a6129 100644
--- a/media/libjxl/src/.gitmodules
+++ b/media/libjxl/src/.gitmodules
@@ -22,9 +22,6 @@
[submodule "third_party/zlib"]
path = third_party/zlib
url = https://github.com/madler/zlib.git
-[submodule "third_party/gflags"]
- path = third_party/gflags
- url = https://github.com/gflags/gflags
[submodule "third_party/testdata"]
- path = third_party/testdata
+ path = testdata
url = https://github.com/libjxl/testdata
diff --git a/media/libjxl/src/AUTHORS b/media/libjxl/src/AUTHORS
index 401350f86a..c8522b8fc3 100644
--- a/media/libjxl/src/AUTHORS
+++ b/media/libjxl/src/AUTHORS
@@ -19,26 +19,35 @@ Google LLC <*@google.com>
Alex Xu (Hello71) <alex_y_xu@yahoo.ca>
Alexander Sago <cagelight@gmail.com>
Andrius Lukas Narbutas <andrius4669@gmail.com>
+Aous Naman <aous@unsw.edu.au>
Artem Selishchev
+Biswapriyo Nath <nathbappai@gmail.com>
CanadianBaconBoi <beamconnor@gmail.com>
Daniel Novomeský <dnovomesky@gmail.com>
David Burnett <vargolsoft@gmail.com>
Dirk Lemstra <dirk@lemstra.org>
Don Olmstead <don.j.olmstead@gmail.com>
+Even Rouault <even.rouault@spatialys.com>
Heiko Becker <heirecka@exherbo.org>
Jon Sneyers <jon@cloudinary.com>
+Kai Hollberg <Schweinepriester@users.noreply.github.com>
Kleis Auke Wolthuizen <github@kleisauke.nl>
L. E. Segovia
Leo Izen <leo.izen@gmail.com>
Lovell Fuller
+Maarten DB <anonymous.maarten@gmail.com>
Marcin Konicki <ahwayakchih@gmail.com>
Martin Strunz
Mathieu Malaterre <mathieu.malaterre@gmail.com>
+Mikk Leini <mikk.leini@krakul.eu>
Misaki Kasumi <misakikasumi@outlook.com>
Petr Diblík
Pieter Wuille
+roland-rollo
Samuel Leong <wvvwvvvvwvvw@gmail.com>
+Sandro <sandro.jaeckel@gmail.com>
Stephan T. Lavavej <stl@nuwen.net>
+Thomas Bonfort <thomas.bonfort@airbus.com>
Vincent Torri <vincent.torri@gmail.com>
xiota
Yonatan Nebenzhal <yonatan.nebenzhl@gmail.com>
diff --git a/media/libjxl/src/CHANGELOG.md b/media/libjxl/src/CHANGELOG.md
index 9cfc3da86d..cf68400807 100644
--- a/media/libjxl/src/CHANGELOG.md
+++ b/media/libjxl/src/CHANGELOG.md
@@ -6,18 +6,27 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## Unreleased
+
+## [0.7] - 2022-07-21
+
### Added
+ - Export version information in headers.
- decoder API: Ability to decode the content of metadata boxes:
- `JXL_DEC_BOX`, `JXL_DEC_BOX_NEED_MORE_OUTPUT`, `JxlDecoderSetBoxBuffer`,
+ `JXL_DEC_BOX`, `JXL_DEC_BOX_NEED_MORE_OUTPUT`, `JxlDecoderSetBoxBuffer`,
`JxlDecoderGetBoxType`, `JxlDecoderGetBoxSizeRaw` and
- `JxlDecoderSetDecompressBoxes`
- - decoder API: ability to mark the input is finished: `JxlDecoderCloseInput`
- - encoder API: ability to add metadata boxes, added new functions
- `JxlEncoderAddBox`, `JxlEncoderUseBoxes`, `JxlEncoderCloseBoxes` and
- `JxlEncoderCloseFrames`.
+ `JxlDecoderSetDecompressBoxes`.
+ - decoder API: ability to mark the input is finished: `JxlDecoderCloseInput`.
+ - decoder API: ability to request updates on different progressive events using
+ `JxlDecoderSetProgressiveDetail`; currently supported events are
+ `kDC`, `kLastPasses` and `kPasses`.
+ - decoder API: ability to specify desired intensity target using
+ `JxlDecoderSetDesiredIntensityTarget`
- decoder API: new function `JxlDecoderSetCoalesced` to allow decoding
non-coalesced (unblended) frames, e.g. layers of a composite still image
or the cropped frames of a recompressed GIF/APNG.
+ - decoder API: new function `JxlDecoderSetUnpremultiplyAlpha` to set
+ preference for getting an associated alpha channel with premultiplied or
+ unpremultiplied colors.
- decoder API: field added to `JxlFrameHeader`: a `JxlLayerInfo` struct
that contains crop dimensions and offsets and blending information for
the non-coalesced case.
@@ -26,37 +35,67 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- decoder API: new function `JxlDecoderSetMultithreadedImageOutCallback`,
allowing output callbacks to receive more information about the number of
threads on which they are running.
- - encoder API: added ability to set several encoder options to frames using
- `JxlEncoderFrameSettingsSetOption`
+ - decoder API: new function `JxlDecoderSkipCurrentFrame` to skip processing
+ the current frame after a progressive detail is reached.
+ - decoder API: new function `JxlDecoderGetIntendedDownsamplingRatio` to get
+ the intended downsampling ratio of progressive steps, based on the
+ information in the frame header.
+ - decoder API: new function `JxlDecoderSetRenderSpotcolors` to allow disabling
+ rendering of spot colors.
+ - decoder/encoder API: add two fields to `JXLBasicInfo`: `intrinsic_xsize`
+ and `intrinsic_ysize` to signal the intrinsic size.
+ - encoder API: ability to add metadata boxes, added new functions
+ `JxlEncoderAddBox`, `JxlEncoderUseBoxes`, `JxlEncoderCloseBoxes` and
+ `JxlEncoderCloseFrames`.
+ - encoder API: added ability to set several encoder options / extra fields to
+ frames using `JxlEncoderSetFrameName`, `JxlEncoderFrameSettingsSetOption`,
+ `JxlEncoderFrameSettingsSetFloatOption`.
+ - encoder API: added ability to check required codestream compatibility level
+ and force specified using `JxlEncoderGetRequiredCodestreamLevel` and
+ `JxlEncoderSetCodestreamLevel`.
+ - encoder API: added ability to force emitting box-based container format
+ using `JxlEncoderUseContainer`.
+ - encoder API: added ability to store JPEG metadata for lossless reconstruction
+ using `JxlEncoderStoreJPEGMetadata`
- encoder API: new functions `JxlEncoderSetFrameHeader` and
`JxlEncoderSetExtraChannelBlendInfo` to set animation
and blending parameters of the frame, and `JxlEncoderInitFrameHeader` and
`JxlEncoderInitBlendInfo` to initialize the structs to set.
- - decoder/encoder API: add two fields to `JXLBasicInfo`: `intrinsic_xsize`
- and `intrinsic_ysize` to signal the intrinsic size.
- encoder API: ability to encode arbitrary extra channels:
`JxlEncoderInitExtraChannelInfo`, `JxlEncoderSetExtraChannelInfo`,
`JxlEncoderSetExtraChannelName` and `JxlEncoderSetExtraChannelBuffer`.
+ - encoder API: ability to plug custom CMS implementation using
+ `JxlEncoderSetCms(JxlEncoder* enc, JxlCmsInterface cms)`
+ - encoder API: added `JxlEncoderGetError` to retrieve last encoder error.
### Changed
- decoder API: using `JxlDecoderCloseInput` at the end of all input is required
when using JXL_DEC_BOX, and is now also encouraged in other cases, but not
- required in those other cases for backwards compatiblity.
+ required in those other cases for backwards compatibility.
- encoder API: `JxlEncoderCloseInput` now closes both frames and boxes input.
+- CLI: `cjxl` and `djxl` have been reimplemented on the base of public decoder
+ and encoder API; dropped dependency on `gflags` for argument parsing.
### Deprecated
-- encoder API: `JxlEncoderOptions`: use `JxlEncoderFrameSettings` instead
+- decoder API: `JXL_DEC_EXTENSIONS` event: use `JXL_DEC_BASIC_INFO`
+- decoder / encoder API: pixel types `JXL_TYPE_BOOLEAN` and `JXL_TYPE_UINT32`:
+ consider using `JXL_TYPE_UINT8` and `JXL_TYPE_FLOAT` correspondingly.
+- decoder API: pixel format parameter for `JxlDecoderGetColorAsEncodedProfile`
+ and `JxlDecoderGetICCProfileSize`: pass `NULL`.
+- decoder API: `JxlDecoderDefaultPixelFormat`
+- encoder API: `JxlEncoderOptions`: use `JxlEncoderFrameSettings` instead.
- encoder API: `JxlEncoderOptionsCreate`: use `JxlEncoderFrameSettingsCreate`
- instead
+ instead.
- encoder API: `JxlEncoderOptionsSetDistance`: use `JxlEncoderSetFrameDistance`
- instead
+ instead.
- encoder API: `JxlEncoderOptionsSetLossless`: use `JxlEncoderSetFrameLossless`
- instead
-- encoder API: `JxlEncoderOptionsSetEffort`: use `JxlEncoderFrameSettingsSetOption(
- frame_settings, JXL_ENC_FRAME_SETTING_EFFORT, effort)` instead.
+ instead.
+- encoder API: `JxlEncoderOptionsSetEffort`: use
+ `JxlEncoderFrameSettingsSetOption(frame_settings, JXL_ENC_FRAME_SETTING_EFFORT, effort)`
+ instead.
- encoder API: `JxlEncoderOptionsSetDecodingSpeed`: use
- `JxlEncoderFrameSettingsSetOption(frame_settings,
- JXL_ENC_FRAME_SETTING_DECODING_SPEED, tier)` instead.
+ `JxlEncoderFrameSettingsSetOption(frame_settings, JXL_ENC_FRAME_SETTING_DECODING_SPEED, tier)`
+ instead.
- encoder API: deprecated `JXL_ENC_NOT_SUPPORTED`, the encoder returns
`JXL_ENC_ERROR` instead and there is no need to handle
`JXL_ENC_NOT_SUPPORTED`.
diff --git a/media/libjxl/src/CMakeLists.txt b/media/libjxl/src/CMakeLists.txt
index 50251566fe..533815d232 100644
--- a/media/libjxl/src/CMakeLists.txt
+++ b/media/libjxl/src/CMakeLists.txt
@@ -51,12 +51,12 @@ if(CHECK_PIE_SUPPORTED)
endif()
### Project build options:
-if(${CXX_FUZZERS_SUPPORTED})
+if(CXX_FUZZERS_SUPPORTED)
# Enabled by default except on arm64, Windows and Apple builds.
set(ENABLE_FUZZERS_DEFAULT true)
endif()
find_package(PkgConfig)
-if(NOT APPLE AND NOT WIN32 AND NOT HAIKU AND ${CMAKE_SYSTEM_PROCESSOR} MATCHES "x86_64")
+if(NOT APPLE AND NOT WIN32 AND NOT HAIKU AND CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64")
pkg_check_modules(TCMallocMinimalVersionCheck QUIET IMPORTED_TARGET
libtcmalloc_minimal)
if(TCMallocMinimalVersionCheck_FOUND AND
@@ -72,18 +72,27 @@ if(NOT APPLE AND NOT WIN32 AND NOT HAIKU AND ${CMAKE_SYSTEM_PROCESSOR} MATCHES "
endif()
endif()
+check_cxx_source_compiles(
+ "int main() {
+ #if !defined(HWY_DISABLED_TARGETS)
+ static_assert(false, \"HWY_DISABLED_TARGETS is not defined\");
+ #endif
+ return 0;
+ }"
+ JXL_HWY_DISABLED_TARGETS_FORCED
+)
+
set(WARNINGS_AS_ERRORS_DEFAULT false)
if((SANITIZER STREQUAL "msan") OR JPEGXL_EMSCRIPTEN)
set(BUNDLE_LIBPNG_DEFAULT YES)
- set(BUNDLE_GFLAGS_DEFAULT YES)
else()
set(BUNDLE_LIBPNG_DEFAULT NO)
- set(BUNDLE_GFLAGS_DEFAULT NO)
endif()
# Standard cmake naming for building shared libraries.
-option(BUILD_SHARED_LIBS "Build shared libraries instead of static ones" ON)
+get_property(SHARED_LIBS_SUPPORTED GLOBAL PROPERTY TARGET_SUPPORTS_SHARED_LIBS)
+option(BUILD_SHARED_LIBS "Build shared libraries instead of static ones" ${SHARED_LIBS_SUPPORTED})
set(JPEGXL_ENABLE_FUZZERS ${ENABLE_FUZZERS_DEFAULT} CACHE BOOL
"Build JPEGXL fuzzer targets.")
@@ -99,8 +108,6 @@ set(JPEGXL_ENABLE_BENCHMARK true CACHE BOOL
"Build JPEGXL benchmark tools.")
set(JPEGXL_ENABLE_EXAMPLES true CACHE BOOL
"Build JPEGXL library usage examples.")
-set(JPEGXL_BUNDLE_GFLAGS ${BUNDLE_GFLAGS_DEFAULT} CACHE BOOL
- "Build gflags from source and link it statically.")
set(JPEGXL_BUNDLE_LIBPNG ${BUNDLE_LIBPNG_DEFAULT} CACHE BOOL
"Build libpng from source and link it statically.")
set(JPEGXL_ENABLE_JNI true CACHE BOOL
@@ -122,7 +129,9 @@ set(JPEGXL_ENABLE_PLUGINS false CACHE BOOL
set(JPEGXL_ENABLE_COVERAGE false CACHE BOOL
"Enable code coverage tracking for libjxl. This also enables debug and disables optimizations.")
set(JPEGXL_ENABLE_PROFILER false CACHE BOOL
- "Builds in support for profiling (printed by tools if extra flags given")
+ "Builds in support for profiling (printed by tools if extra flags given)")
+set(JPEGXL_ENABLE_SIZELESS_VECTORS false CACHE BOOL
+ "Builds in support for SVE/RVV vectorization")
set(JPEGXL_ENABLE_TRANSCODE_JPEG true CACHE BOOL
"Builds in support for decoding transcoded JXL files back to JPEG,\
disabling it makes the decoder reject JXL_DEC_JPEG_RECONSTRUCTION events,\
@@ -149,20 +158,20 @@ set(JPEGXL_FORCE_SYSTEM_HWY false CACHE BOOL
# Check minimum compiler versions. Older compilers are not supported and fail
# with hard to understand errors.
-if (NOT ${CMAKE_C_COMPILER_ID} STREQUAL ${CMAKE_CXX_COMPILER_ID})
+if (NOT CMAKE_C_COMPILER_ID STREQUAL CMAKE_CXX_COMPILER_ID)
message(FATAL_ERROR "Different C/C++ compilers set: "
"${CMAKE_C_COMPILER_ID} vs ${CMAKE_CXX_COMPILER_ID}")
endif()
-if (${CMAKE_CXX_COMPILER_ID} MATCHES "Clang")
+if (CMAKE_CXX_COMPILER_ID MATCHES "Clang")
# Android NDK's toolchain.cmake fakes the clang version in
# CMAKE_CXX_COMPILER_VERSION with an incorrect number, so ignore this.
- if (NOT ${CMAKE_ANDROID_NDK_TOOLCHAIN_VERSION} MATCHES "clang"
- AND ${CMAKE_CXX_COMPILER_VERSION} VERSION_LESS 6)
+ if (NOT CMAKE_ANDROID_NDK_TOOLCHAIN_VERSION MATCHES "clang"
+ AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS 5)
message(FATAL_ERROR
- "Minimum Clang version required is Clang 6, please update.")
+ "Minimum Clang version required is Clang 5, please update.")
endif()
-elseif (${CMAKE_CXX_COMPILER_ID} MATCHES "GNU")
- if (${CMAKE_CXX_COMPILER_VERSION} VERSION_LESS 7)
+elseif (CMAKE_CXX_COMPILER_ID MATCHES "GNU")
+ if (CMAKE_CXX_COMPILER_VERSION VERSION_LESS 7)
message(FATAL_ERROR
"Minimum GCC version required is 7, please update.")
endif()
@@ -234,7 +243,13 @@ if(JPEGXL_STATIC)
endif()
endif() # JPEGXL_STATIC
-if ("${CXX_MACRO_PREFIX_MAP}")
+if (JPEGXL_EMSCRIPTEN)
+ set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -pthread")
+ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pthread")
+ set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -pthread")
+endif()
+
+if (CXX_MACRO_PREFIX_MAP)
add_compile_options(-fmacro-prefix-map=${CMAKE_CURRENT_SOURCE_DIR}=.)
endif()
@@ -259,16 +274,22 @@ if(MSVC)
add_definitions(-D_CRT_SECURE_NO_WARNINGS)
endif()
+# TODO(eustas): JXL currently compiles, but does not pass tests...
+if (NOT JXL_HWY_DISABLED_TARGETS_FORCED AND NOT JPEGXL_ENABLE_SIZELESS_VECTORS)
+ add_definitions(-DHWY_DISABLED_TARGETS=\(HWY_SVE|HWY_SVE2|HWY_SVE_256|HWY_SVE2_128|HWY_RVV\))
+ message("Warning: HWY_SVE, HWY_SVE2, HWY_SVE_256, HWY_SVE2_128 and HWY_RVV CPU targets are disabled")
+endif()
+
# In CMake before 3.12 it is problematic to pass repeated flags like -Xclang.
# For this reason we place them in CMAKE_CXX_FLAGS instead.
# See https://gitlab.kitware.com/cmake/cmake/issues/15826
# Machine flags.
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -funwind-tables")
-if (${CMAKE_CXX_COMPILER_ID} MATCHES "Clang")
+if (CMAKE_CXX_COMPILER_ID MATCHES "Clang")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Xclang -mrelax-all")
endif()
-if ("${CXX_CONSTRUCTOR_ALIASES_SUPPORTED}")
+if (CXX_CONSTRUCTOR_ALIASES_SUPPORTED)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Xclang -mconstructor-aliases")
endif()
@@ -281,7 +302,7 @@ endif()
# CPU flags - remove once we have NEON dynamic dispatch
# TODO(janwas): this also matches M1, but only ARMv7 is intended/needed.
-if(${CMAKE_SYSTEM_PROCESSOR} MATCHES "arm")
+if(CMAKE_SYSTEM_PROCESSOR MATCHES "arm")
if(JPEGXL_FORCE_NEON)
# GCC requires these flags, otherwise __ARM_NEON is undefined.
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} \
@@ -307,8 +328,10 @@ endif () # !MSVC
include(GNUInstallDirs)
+# Separately build/configure testing frameworks and other third_party libraries
+# to allow disabling tests in those libraries.
+include(third_party/testing.cmake)
add_subdirectory(third_party)
-
# Copy the JXL license file to the output build directory.
configure_file("${CMAKE_CURRENT_SOURCE_DIR}/LICENSE"
${PROJECT_BINARY_DIR}/LICENSE.jpeg-xl COPYONLY)
@@ -318,7 +341,7 @@ enable_testing()
include(CTest)
# Specify default location of `testdata`:
if(NOT DEFINED JPEGXL_TEST_DATA_PATH)
- set(JPEGXL_TEST_DATA_PATH "${PROJECT_SOURCE_DIR}/third_party/testdata")
+ set(JPEGXL_TEST_DATA_PATH "${PROJECT_SOURCE_DIR}/testdata")
endif()
# Libraries.
@@ -382,9 +405,9 @@ endif() # JPEGXL_ENABLE_DOXYGEN
if(JPEGXL_ENABLE_MANPAGES)
find_program(ASCIIDOC a2x)
-if(NOT "${ASCIIDOC}" STREQUAL "ASCIIDOC-NOTFOUND")
+if(ASCIIDOC)
file(STRINGS "${ASCIIDOC}" ASCIIDOC_SHEBANG LIMIT_COUNT 1)
-if(ASCIIDOC_SHEBANG MATCHES "/sh")
+if(ASCIIDOC_SHEBANG MATCHES "/sh|/bash")
set(ASCIIDOC_PY_FOUND ON)
# Run the program directly and set ASCIIDOC as empty.
set(ASCIIDOC_PY "${ASCIIDOC}")
@@ -401,7 +424,7 @@ else()
find_package(Python COMPONENTS Interpreter QUIET)
if(NOT Python_Interpreter_FOUND)
find_program(ASCIIDOC_PY python)
- if(NOT ASCIIDOC_PY STREQUAL "ASCIIDOC_PY-NOTFOUND")
+ if(ASCIIDOC_PY)
set(ASCIIDOC_PY_FOUND ON)
endif()
else()
@@ -432,16 +455,16 @@ if (ASCIIDOC_PY_FOUND)
endif() # ASCIIDOC_PY_FOUND
else()
message(WARNING "asciidoc was not found, the man pages will not be installed.")
-endif() # ASCIIDOC != "ASCIIDOC-NOTFOUND"
+endif() # ASCIIDOC
endif() # JPEGXL_ENABLE_MANPAGES
# Example usage code.
-if (${JPEGXL_ENABLE_EXAMPLES})
+if (JPEGXL_ENABLE_EXAMPLES)
include(examples/examples.cmake)
endif ()
# Plugins for third-party software
-if (${JPEGXL_ENABLE_PLUGINS})
+if (JPEGXL_ENABLE_PLUGINS)
add_subdirectory(plugins)
endif ()
diff --git a/media/libjxl/src/README.md b/media/libjxl/src/README.md
index d0aa4b7dd2..b0f2e3b50e 100644
--- a/media/libjxl/src/README.md
+++ b/media/libjxl/src/README.md
@@ -1,7 +1,13 @@
# JPEG XL reference implementation
-[![Build&Test](https://github.com/libjxl/libjxl/actions/workflows/build_test.yml/badge.svg)](
+[![Build/Test](https://github.com/libjxl/libjxl/actions/workflows/build_test.yml/badge.svg)](
https://github.com/libjxl/libjxl/actions/workflows/build_test.yml)
+[![Build/Test Cross](https://github.com/libjxl/libjxl/actions/workflows/build_test_cross.yml/badge.svg)](
+https://github.com/libjxl/libjxl/actions/workflows/build_test_cross.yml)
+[![Conformance](https://github.com/libjxl/libjxl/actions/workflows/conformance.yml/badge.svg)](
+https://github.com/libjxl/libjxl/actions/workflows/conformance.yml)
+[![CIFuzz](https://github.com/libjxl/libjxl/actions/workflows/fuzz.yml/badge.svg)](
+https://github.com/libjxl/libjxl/actions/workflows/fuzz.yml)
[![Releases](https://github.com/libjxl/libjxl/actions/workflows/release.yaml/badge.svg)](
https://github.com/libjxl/libjxl/actions/workflows/release.yaml)
[![Doc](https://readthedocs.org/projects/libjxl/badge/?version=latest)](
@@ -63,7 +69,7 @@ Required dependencies for compiling the code, in a Debian/Ubuntu based
distribution run:
```bash
-sudo apt install cmake pkg-config libbrotli-dev libgflags-dev
+sudo apt install cmake pkg-config libbrotli-dev
```
Optional dependencies for supporting other formats in the `cjxl`/`djxl` tools,
@@ -168,7 +174,7 @@ format: Cloudinary and Google.
* [JPEG XL Format Overview](doc/format_overview.md)
* [Introductory paper](https://www.spiedigitallibrary.org/proceedings/Download?fullDOI=10.1117%2F12.2529237) (open-access)
* [XL Overview](doc/xl_overview.md) - a brief introduction to the source code modules
-* [JPEG XL white paper](http://ds.jpeg.org/whitepapers/jpeg-xl-whitepaper.pdf)
+* [JPEG XL white paper](https://ds.jpeg.org/whitepapers/jpeg-xl-whitepaper.pdf)
* [JPEG XL official website](https://jpeg.org/jpegxl)
* [JPEG XL community website](https://jpegxl.info)
diff --git a/media/libjxl/src/bash_test.sh b/media/libjxl/src/bash_test.sh
index 6a628c9349..675026ab2e 100755
--- a/media/libjxl/src/bash_test.sh
+++ b/media/libjxl/src/bash_test.sh
@@ -103,6 +103,12 @@ test_printf_size_t() {
ret=1
fi
+ if grep -n -E 'gmock\.h' \
+ $(git ls-files | grep -E '(\.c|\.cc|\.cpp|\.h)$' | grep -v -F /test_utils.h); then
+ echo "Don't include gmock directly, instead include 'test_utils.h'. " >&2
+ ret=1
+ fi
+
local f
for f in $(git ls-files | grep -E "\.cc$" | xargs grep 'PRI[udx]S' |
cut -f 1 -d : | uniq); do
@@ -116,7 +122,7 @@ test_printf_size_t() {
fi
done
- for f in $(git ls-files | grep -E "\.h$" | grep -v -F printf_macros.h |
+ for f in $(git ls-files | grep -E "\.h$" | grep -v -E '(printf_macros\.h|test_utils\.h)' |
xargs grep -n 'PRI[udx]S'); do
# Having PRIuS / PRIdS in a header file means that printf_macros.h may
# be included before a system header, in particular before gtest headers.
diff --git a/media/libjxl/src/ci.sh b/media/libjxl/src/ci.sh
index 49ac042596..45d5218d23 100755
--- a/media/libjxl/src/ci.sh
+++ b/media/libjxl/src/ci.sh
@@ -15,13 +15,14 @@ OS=`uname -s`
MYDIR=$(dirname $(realpath "$0"))
### Environment parameters:
-TEST_STACK_LIMIT="${TEST_STACK_LIMIT:-128}"
+TEST_STACK_LIMIT="${TEST_STACK_LIMIT:-256}"
CMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE:-RelWithDebInfo}
CMAKE_PREFIX_PATH=${CMAKE_PREFIX_PATH:-}
CMAKE_C_COMPILER_LAUNCHER=${CMAKE_C_COMPILER_LAUNCHER:-}
CMAKE_CXX_COMPILER_LAUNCHER=${CMAKE_CXX_COMPILER_LAUNCHER:-}
CMAKE_MAKE_PROGRAM=${CMAKE_MAKE_PROGRAM:-}
SKIP_TEST="${SKIP_TEST:-0}"
+TEST_SELECTOR="${TEST_SELECTOR:-}"
BUILD_TARGET="${BUILD_TARGET:-}"
ENABLE_WASM_SIMD="${ENABLE_WASM_SIMD:-0}"
if [[ -n "${BUILD_TARGET}" ]]; then
@@ -41,27 +42,11 @@ FUZZER_MAX_TIME="${FUZZER_MAX_TIME:-0}"
SANITIZER="none"
-if [[ "${BUILD_TARGET}" == wasm* ]]; then
- # Check that environment is setup for the WASM build target.
- if [[ -z "${EMSCRIPTEN}" ]]; then
- echo "'EMSCRIPTEN' is not defined. Use 'emconfigure' wrapper to setup WASM build environment" >&2
- return 1
- fi
- # Remove the side-effect of "emconfigure" wrapper - it considers NodeJS environment.
- unset EMMAKEN_JUST_CONFIGURE
- EMS_TOOLCHAIN_FILE="${EMSCRIPTEN}/cmake/Modules/Platform/Emscripten.cmake"
- if [[ -f "${EMS_TOOLCHAIN_FILE}" ]]; then
- CMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE:-${EMS_TOOLCHAIN_FILE}}
- else
- echo "Warning: EMSCRIPTEN CMake module not found" >&2
- fi
- CMAKE_CROSSCOMPILING_EMULATOR="${MYDIR}/js-wasm-wrapper.sh"
-fi
if [[ "${BUILD_TARGET%%-*}" == "x86_64" ||
"${BUILD_TARGET%%-*}" == "i686" ]]; then
# Default to building all targets, even if compiler baseline is SSE4
- HWY_BASELINE_TARGETS=${HWY_BASELINE_TARGETS:-HWY_SCALAR}
+ HWY_BASELINE_TARGETS=${HWY_BASELINE_TARGETS:-HWY_EMU128}
else
HWY_BASELINE_TARGETS=${HWY_BASELINE_TARGETS:-}
fi
@@ -350,7 +335,6 @@ cmake_configure() {
-G Ninja
-DCMAKE_CXX_FLAGS="${CMAKE_CXX_FLAGS}"
-DCMAKE_C_FLAGS="${CMAKE_C_FLAGS}"
- -DCMAKE_TOOLCHAIN_FILE="${CMAKE_TOOLCHAIN_FILE}"
-DCMAKE_EXE_LINKER_FLAGS="${CMAKE_EXE_LINKER_FLAGS}"
-DCMAKE_MODULE_LINKER_FLAGS="${CMAKE_MODULE_LINKER_FLAGS}"
-DCMAKE_SHARED_LINKER_FLAGS="${CMAKE_SHARED_LINKER_FLAGS}"
@@ -392,11 +376,14 @@ cmake_configure() {
# Only the first element of the target triplet.
-DCMAKE_SYSTEM_PROCESSOR="${BUILD_TARGET%%-*}"
-DCMAKE_SYSTEM_NAME="${system_name}"
+ -DCMAKE_TOOLCHAIN_FILE="${CMAKE_TOOLCHAIN_FILE}"
)
else
- # sjpeg confuses WASM SIMD with SSE.
args+=(
+ # sjpeg confuses WASM SIMD with SSE.
-DSJPEG_ENABLE_SIMD=OFF
+ # Building shared libs is not very useful for WASM.
+ -DBUILD_SHARED_LIBS=OFF
)
fi
args+=(
@@ -457,7 +444,11 @@ cmake_configure() {
-DCMAKE_MAKE_PROGRAM="${CMAKE_MAKE_PROGRAM}"
)
fi
- cmake "${args[@]}" "$@"
+ if [[ "${BUILD_TARGET}" == wasm* ]]; then
+ emcmake cmake "${args[@]}" "$@"
+ else
+ cmake "${args[@]}" "$@"
+ fi
}
cmake_build_and_test() {
@@ -485,7 +476,7 @@ cmake_build_and_test() {
(cd "${BUILD_DIR}"
export UBSAN_OPTIONS=print_stacktrace=1
[[ "${TEST_STACK_LIMIT}" == "none" ]] || ulimit -s "${TEST_STACK_LIMIT}"
- ctest -j $(nproc --all || echo 1) --output-on-failure)
+ ctest -j $(nproc --all || echo 1) ${TEST_SELECTOR} --output-on-failure)
fi
}
@@ -494,7 +485,7 @@ cmake_build_and_test() {
# library.
strip_dead_code() {
# Emscripten does tree shaking without any extra flags.
- if [[ "${CMAKE_TOOLCHAIN_FILE##*/}" == "Emscripten.cmake" ]]; then
+ if [[ "${BUILD_TARGET}" == wasm* ]]; then
return 0
fi
# -ffunction-sections, -fdata-sections and -Wl,--gc-sections effectively
@@ -711,20 +702,24 @@ cmd_msan_install() {
export CC="${CC:-clang}"
export CXX="${CXX:-clang++}"
detect_clang_version
- local llvm_tag="llvmorg-${CLANG_VERSION}.0.0"
- case "${CLANG_VERSION}" in
- "6.0")
- llvm_tag="llvmorg-6.0.1"
- ;;
- "7")
- llvm_tag="llvmorg-7.0.1"
- ;;
- esac
- local llvm_targz="${tmpdir}/${llvm_tag}.tar.gz"
- curl -L --show-error -o "${llvm_targz}" \
- "https://github.com/llvm/llvm-project/archive/${llvm_tag}.tar.gz"
- tar -C "${tmpdir}" -zxf "${llvm_targz}"
- local llvm_root="${tmpdir}/llvm-project-${llvm_tag}"
+ # Allow overriding the LLVM checkout.
+ local llvm_root="${LLVM_ROOT:-}"
+ if [ -z "${llvm_root}" ]; then
+ local llvm_tag="llvmorg-${CLANG_VERSION}.0.0"
+ case "${CLANG_VERSION}" in
+ "6.0")
+ llvm_tag="llvmorg-6.0.1"
+ ;;
+ "7")
+ llvm_tag="llvmorg-7.0.1"
+ ;;
+ esac
+ local llvm_targz="${tmpdir}/${llvm_tag}.tar.gz"
+ curl -L --show-error -o "${llvm_targz}" \
+ "https://github.com/llvm/llvm-project/archive/${llvm_tag}.tar.gz"
+ tar -C "${tmpdir}" -zxf "${llvm_targz}"
+ llvm_root="${tmpdir}/llvm-project-${llvm_tag}"
+ fi
local msan_prefix="${HOME}/.msan/${CLANG_VERSION}"
rm -rf "${msan_prefix}"
@@ -1020,11 +1015,11 @@ cmd_arm_benchmark() {
)
local images=(
- "third_party/testdata/third_party/imagecompression.info/flower_foveon.png"
+ "testdata/jxl/flower/flower.png"
)
local jpg_images=(
- "third_party/testdata/third_party/imagecompression.info/flower_foveon.png.im_q85_420.jpg"
+ "testdata/jxl/flower/flower.png.im_q85_420.jpg"
)
if [[ "${SKIP_CPUSET:-}" == "1" ]]; then
@@ -1219,10 +1214,10 @@ cmd_lint() {
# We include in this linter all the changes including the uncommitted changes
# to avoid printing changes already applied.
set -x
+ # Ignoring the error that git-clang-format outputs.
git -C "${MYDIR}" "${clang_format}" --binary "${clang_format}" \
- --style=file --diff "${MR_ANCESTOR_SHA}" -- >"${tmppatch}"
+ --style=file --diff "${MR_ANCESTOR_SHA}" -- >"${tmppatch}" || true
{ set +x; } 2>/dev/null
-
if grep -E '^--- ' "${tmppatch}">/dev/null; then
if [[ -n "${LINT_OUTPUT:-}" ]]; then
cp "${tmppatch}" "${LINT_OUTPUT}"
@@ -1399,10 +1394,8 @@ cmd_bump_version() {
fi
fi
- newver="${major}.${minor}"
- if [[ "${patch}" != "0" ]]; then
- newver="${newver}.${patch}"
- fi
+ newver="${major}.${minor}.${patch}"
+
echo "Bumping version to ${newver} (${major}.${minor}.${patch})"
sed -E \
-e "s/(set\\(JPEGXL_MAJOR_VERSION) [0-9]+\\)/\\1 ${major})/" \
@@ -1423,11 +1416,14 @@ cmd_bump_version() {
# Check that the AUTHORS file contains the email of the committer.
cmd_authors() {
merge_request_commits
- # TODO(deymo): Handle multiple commits and check that they are all the same
- # author.
- local email=$(git log --format='%ae' "${MR_HEAD_SHA}^!")
- local name=$(git log --format='%an' "${MR_HEAD_SHA}^!")
- "${MYDIR}"/tools/check_author.py "${email}" "${name}"
+ local emails
+ local names
+ readarray -t emails < <(git log --format='%ae' "${MR_HEAD_SHA}...${MR_ANCESTOR_SHA}")
+ readarray -t names < <(git log --format='%an' "${MR_HEAD_SHA}...${MR_ANCESTOR_SHA}")
+ for i in "${!names[@]}"; do
+ echo "Checking name '${names[$i]}' with email '${emails[$i]}' ..."
+ "${MYDIR}"/tools/check_author.py "${emails[$i]}" "${names[$i]}"
+ done
}
main() {
@@ -1490,6 +1486,7 @@ You can pass some optional environment variables as well:
- SKIP_TEST=1: Skip the test stage.
- STORE_IMAGES=0: Makes the benchmark discard the computed images.
- TEST_STACK_LIMIT: Stack size limit (ulimit -s) during tests, in KiB.
+ - TEST_SELECTOR: pass additional arguments to ctest, e.g. "-R .Resample.".
- STACK_SIZE=1: Generate binaries with the .stack_sizes sections.
These optional environment variables are forwarded to the cmake call as
diff --git a/media/libjxl/src/cmake/FindBrotli.cmake b/media/libjxl/src/cmake/FindBrotli.cmake
index 568356fe4c..5c6cb09870 100644
--- a/media/libjxl/src/cmake/FindBrotli.cmake
+++ b/media/libjxl/src/cmake/FindBrotli.cmake
@@ -26,7 +26,7 @@ foreach(brlib IN ITEMS ${brlibs})
)
if (${BRPREFIX}_LIBRARY AND NOT TARGET ${brlib})
- if(${CMAKE_VERSION} VERSION_LESS "3.13.5")
+ if(CMAKE_VERSION VERSION_LESS "3.13.5")
add_library(${brlib} INTERFACE IMPORTED GLOBAL)
set_property(TARGET ${brlib} PROPERTY INTERFACE_INCLUDE_DIRECTORIES ${BROTLI_INCLUDE_DIR})
target_link_libraries(${brlib} INTERFACE ${${BRPREFIX}_LIBRARY})
diff --git a/media/libjxl/src/cmake/FindHWY.cmake b/media/libjxl/src/cmake/FindHWY.cmake
index 267b4224ef..c1deb9b851 100644
--- a/media/libjxl/src/cmake/FindHWY.cmake
+++ b/media/libjxl/src/cmake/FindHWY.cmake
@@ -46,7 +46,7 @@ find_package_handle_standard_args(HWY
if (HWY_LIBRARY AND NOT TARGET hwy)
add_library(hwy INTERFACE IMPORTED GLOBAL)
- if(${CMAKE_VERSION} VERSION_LESS "3.13.5")
+ if(CMAKE_VERSION VERSION_LESS "3.13.5")
set_property(TARGET hwy PROPERTY INTERFACE_INCLUDE_DIRECTORIES ${HWY_INCLUDE_DIR})
target_link_libraries(hwy INTERFACE ${HWY_LIBRARY})
set_property(TARGET hwy PROPERTY INTERFACE_COMPILE_OPTIONS ${PC_HWY_CFLAGS_OTHER})
diff --git a/media/libjxl/src/cmake/FindLCMS2.cmake b/media/libjxl/src/cmake/FindLCMS2.cmake
index c36f7f9edd..0a7b54eb96 100644
--- a/media/libjxl/src/cmake/FindLCMS2.cmake
+++ b/media/libjxl/src/cmake/FindLCMS2.cmake
@@ -39,7 +39,7 @@ find_package_handle_standard_args(LCMS2
if (LCMS2_LIBRARY AND NOT TARGET lcms2)
add_library(lcms2 INTERFACE IMPORTED GLOBAL)
- if(${CMAKE_VERSION} VERSION_LESS "3.13.5")
+ if(CMAKE_VERSION VERSION_LESS "3.13.5")
set_property(TARGET lcms2 PROPERTY INTERFACE_INCLUDE_DIRECTORIES ${LCMS2_INCLUDE_DIR})
target_link_libraries(lcms2 INTERFACE ${LCMS2_LIBRARY})
set_property(TARGET lcms2 PROPERTY INTERFACE_COMPILE_OPTIONS ${PC_LCMS2_CFLAGS_OTHER})
diff --git a/media/libjxl/src/debian/changelog b/media/libjxl/src/debian/changelog
index 1041baabf0..a63607e5e5 100644
--- a/media/libjxl/src/debian/changelog
+++ b/media/libjxl/src/debian/changelog
@@ -2,7 +2,7 @@ jpeg-xl (0.7) UNRELEASED; urgency=medium
* Bump JPEG XL version to 0.7.
- -- JPEG XL Maintainers <jpegxl@google.com> Fri, 10 Sep 2021 16:08:17 +0200
+ -- JPEG XL Maintainers <jpegxl@google.com> Mon, 08 Aug 2022 14:43:58 +0000
jpeg-xl (0.6) unstable; urgency=medium
diff --git a/media/libjxl/src/debian/control b/media/libjxl/src/debian/control
index cab8e0e7f3..7a3c502e0e 100644
--- a/media/libjxl/src/debian/control
+++ b/media/libjxl/src/debian/control
@@ -9,7 +9,6 @@ Build-Depends:
debhelper (>= 9),
libbrotli-dev,
libgdk-pixbuf-2.0-dev | libgdk-pixbuf2.0-dev,
- libgflags-dev | libgflag-dev,
libgif-dev,
libgimp2.0-dev,
libgmock-dev,
diff --git a/media/libjxl/src/debian/copyright b/media/libjxl/src/debian/copyright
index ca0c7922fc..20225a9209 100644
--- a/media/libjxl/src/debian/copyright
+++ b/media/libjxl/src/debian/copyright
@@ -38,27 +38,7 @@ License: BSD-3-clause
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-Files: third_party/testdata/third_party/imagecompression.info/*
-Copyright: their respective owners.
-License: License without any prohibitive copyright restrictions.
- See https://imagecompression.info/test_images/ for details.
- .
- These Images are available without any prohibitive copyright restrictions.
- .
- These images are (c) there respective owners. You are granted full
- redistribution and publication rights on these images provided:
- .
- 1. The origin of the pictures must not be misrepresented; you must not claim
- that you took the original pictures. If you use, publish or redistribute them,
- an acknowledgment would be appreciated but is not required.
- 2. Altered versions must be plainly marked as such, and must not be
- misinterpreted as being the originals.
- 3. No payment is required for distribution of this material, it must be
- available freely under the conditions stated here. That is, it is prohibited to
- sell the material.
- 4. This notice may not be removed or altered from any distribution.
-
-Files: third_party/testdata/third_party/pngsuite/*
+Files: testdata/external/pngsuite/*
Copyright: Willem van Schaik, 1996, 2011
License: PngSuite License
See http://www.schaik.com/pngsuite/ for details.
@@ -66,15 +46,15 @@ License: PngSuite License
Permission to use, copy, modify and distribute these images for any
purpose and without fee is hereby granted.
-Files: third_party/testdata/raw.pixls/*
+Files: testdata/external/raw.pixls/*
Copyright: their respective owners listed in https://raw.pixls.us/
License: CC0-1.0
-Files: third_party/testdata/raw.pixls/*
+Files: testdata/external/wesaturate/*
Copyright: their respective owners listed in https://www.wesaturate.com/
License: CC0-1.0
-Files: third_party/testdata/third_party/wide-gamut-tests/
+Files: testdata/external/wide-gamut-tests/
Copyright: github.com/codelogic/wide-gamut-tests authors.
License: Apache-2.0
diff --git a/media/libjxl/src/deps.sh b/media/libjxl/src/deps.sh
index 907589b267..9aaabba2e0 100755
--- a/media/libjxl/src/deps.sh
+++ b/media/libjxl/src/deps.sh
@@ -14,8 +14,7 @@ MYDIR=$(dirname $(realpath "$0"))
# Git revisions we use for the given submodules. Update these whenever you
# update a git submodule.
THIRD_PARTY_BROTLI="35ef5c554d888bef217d449346067de05e269b30"
-THIRD_PARTY_GFLAGS="827c769e5fc98e0f2a34c47cef953cc6328abced"
-THIRD_PARTY_HIGHWAY="f13e3b956eb226561ac79427893ec0afd66f91a8"
+THIRD_PARTY_HIGHWAY="22e3d7276f4157d4a47586ba9fd91dd6303f441a"
THIRD_PARTY_SKCMS="64374756e03700d649f897dbd98c95e78c30c7da"
THIRD_PARTY_SJPEG="868ab558fad70fcbe8863ba4e85179eeb81cc840"
THIRD_PARTY_ZLIB="cacf7f1d4e3d44d871b605da3b647f07d718623f"
@@ -73,7 +72,6 @@ EOF
# Sources downloaded from a tarball.
download_github third_party/brotli google/brotli
- download_github third_party/gflags gflags/gflags
download_github third_party/highway google/highway
download_github third_party/sjpeg webmproject/sjpeg
download_github third_party/skcms \
diff --git a/media/libjxl/src/doc/developing_in_debian.md b/media/libjxl/src/doc/developing_in_debian.md
index fd349fb675..a88b682ff3 100644
--- a/media/libjxl/src/doc/developing_in_debian.md
+++ b/media/libjxl/src/doc/developing_in_debian.md
@@ -12,7 +12,7 @@ Apart from the dependencies in `third_party`, some of the tools use external
dependencies that need to be installed on your system first:
```bash
-sudo apt install cmake clang doxygen g++ extra-cmake-modules libgflags-dev \
+sudo apt install cmake clang doxygen g++ extra-cmake-modules \
libgif-dev libjpeg-dev ninja-build libgoogle-perftools-dev
```
diff --git a/media/libjxl/src/doc/release.md b/media/libjxl/src/doc/release.md
index b1b903bb27..70f1278a42 100644
--- a/media/libjxl/src/doc/release.md
+++ b/media/libjxl/src/doc/release.md
@@ -21,12 +21,15 @@ been merged to `main`, resulting in some errors being detected hours after the
code is merged or even days after in the case of fuzzer-detected bugs.
Release tags are cut from *release branches*. Each MAJOR.MINOR version has its
-own release branch, for example releases `0.5`, `0.5.1`, `0.5.2`, ... would have
-tags `v0.5`, `v0.5.1`, `v0.5.2`, ... on commits from the `v0.5.x` branch.
-`v0.5.x` is a branch name, not a tag name, and doesn't represent a released
+own release branch, for example releases `0.7.0`, `0.7.1`, `0.7.2`, ... would
+have tags `v0.7.0`, `v0.7.1`, `v0.7.2`, ... on commits from the `v0.7.x` branch.
+`v0.7.x` is a branch name, not a tag name, and doesn't represent a released
version since semantic versioning requires that the PATCH is a non-negative
number. Released tags don't each one have their own release branch, all releases
-from the same MAJOR.MINOR version will share the same branch.
+from the same MAJOR.MINOR version will share the same branch. The first commit
+after the branch-off points between the main branch and the release branch
+should be tagged with the suffix `-snapshot` and the name of the next
+MAJOR.MINOR version, in order to get meaningful ouput for `git --describe`.
The main purpose of the release branch is to stabilize the code before a
release. This involves including fixes to existing bugs but **not** including
@@ -36,7 +39,7 @@ branch into the release branch without including the new *features* from `main`.
For this reason it is important to make small commits in `main` and separate bug
fixes from new features.
-After the initial minor release (`M.N`, for example `0.5.0` or just `0.5`) the
+After the initial minor release (`MAJOR.MINOR.PATCH`, for example `0.5.0`) the
release branch is used to continue to cherry-pick fixes to be included in a
patch release, for example a version `0.5.1` release. Patch fixes are only meant
to fix security bugs or other critical bugs that can't wait until the next major
@@ -114,12 +117,12 @@ branch is created the code in `main` will only be included in the next major
or minor release. Right after a release branch update the version targeting the
next release. Artifacts from `main` should include the new (unreleased) version,
so it is important to update it. For example, after the `v0.5.x` branch is
-created from main, you should update the version on `main` to `0.6`.
+created from main, you should update the version on `main` to `0.6.0`.
To help update it, run this helper command (in a Debian-based system):
```bash
-./ci.sh bump_version 0.6
+./ci.sh bump_version 0.6.0
```
This will update the version in the following files:
@@ -248,12 +251,10 @@ To publish a release open the [New Release
page](https://github.com/libjxl/libjxl/releases/new) and follow these
instructions:
- * Set the "Tag version" as "v" plus the semantic version number. Omit the ".0"
- when the PATCH version is 0, for example use "v0.5" or "v0.5.1" but not
- "v0.5.0".
+ * Set the "Tag version" as "v" plus the semantic version number.
- * Select the "Target" as your release branch. For a "v0.5" release tag you
- would use the "v0.5.x" branch.
+ * Select the "Target" as your release branch. For example for a "v0.7.1"
+ release tag you should use the "v0.7.x" branch.
* Use the version number as the release title.
diff --git a/media/libjxl/src/doc/software_support.md b/media/libjxl/src/doc/software_support.md
index b9be07ada3..9dddad7de7 100644
--- a/media/libjxl/src/doc/software_support.md
+++ b/media/libjxl/src/doc/software_support.md
@@ -24,6 +24,8 @@ Please add missing software to this list.
- [ImageMagick](https://imagemagick.org/): supported since 7.0.10-54
- [libvips](https://libvips.github.io/libvips/): supported since 8.11
- [Imlib2](https://github.com/alistair7/imlib2-jxl)
+- [FFmpeg](https://github.com/FFmpeg/FFmpeg/search?q=jpeg-xl&type=commits)
+- [GDAL](https://gdal.org/drivers/raster/jpegxl.html): supported since 3.4.0 as a TIFF codec, and 3.6.0 as standalone format
## OS-level support / UI frameworks / file browser plugins
@@ -35,10 +37,12 @@ Please add missing software to this list.
- [Windows thumbnail handler](https://github.com/saschanaz/jxl-winthumb)
- [OpenMandriva Lx (since 4.3 RC)](https://www.openmandriva.org/en/news/article/openmandriva-lx-4-3-rc-available-for-testing)
- [KaOS (since 2021.06)](https://news.itsfoss.com/kaos-2021-06-release/)
+- [EFL (since 1.27, no external plugin needed)](https://www.enlightenment.org)
## Image editors
- [GIMP (since 2.99.8)](https://www.gimp.org/news/2021/10/20/gimp-2-99-8-released/); plugin for older versions available in libjxl repo
+- [Krita](https://invent.kde.org/graphics/krita/-/commit/13e5d2e5b9f0eac5c8064b7767f0b62264a0797b)
- Photoshop: no plugin available yet, no official support yet
## Image viewers
@@ -46,9 +50,12 @@ Please add missing software to this list.
- [XnView](https://www.xnview.com/en/)
- [ImageGlass](https://imageglass.org/)
- [IrfanView](https://www.irfanview.com/); supported since 4.59 - requires a [plugin](https://www.irfanview.com/plugins.htm) to be downloaded and enabled.
-- Any viewer based on Qt, KDE, GDK-pixbuf, ImageMagick, libvips or imlib2 (see above)
+- [Tachiyomi](https://github.com/tachiyomiorg/tachiyomi/releases/tag/v0.12.1)
+- Any viewer based on Qt, KDE, GDK-pixbuf, EFL, ImageMagick, libvips or imlib2 (see above)
- Qt viewers: gwenview, digiKam, KolourPaint, KPhotoAlbum, LXImage-Qt, qimgv, qView, nomacs, VookiImageViewer, PhotoQt
- GTK viewers: Eye of Gnome (eog), gThumb, Geeqie
+ - EFL viewers: entice, ephoto
+- [Swayimg](https://github.com/artemsen/swayimg)
## Online tools
diff --git a/media/libjxl/src/js-wasm-wrapper.sh b/media/libjxl/src/js-wasm-wrapper.sh
deleted file mode 100755
index fb91c5578c..0000000000
--- a/media/libjxl/src/js-wasm-wrapper.sh
+++ /dev/null
@@ -1,19 +0,0 @@
-#!/usr/bin/env bash
-# Copyright (c) the JPEG XL Project Authors. All rights reserved.
-#
-# Use of this source code is governed by a BSD-style
-# license that can be found in the LICENSE file.
-
-# Continuous integration helper module. This module is meant to be called from
-# the .gitlab-ci.yml file during the continuous integration build, as well as
-# from the command line for developers.
-
-# This wrapper is used to enable WASM SIMD when running tests.
-# Unfortunately, it is impossible to pass the option directly via the
-# CMAKE_CROSSCOMPILING_EMULATOR variable.
-
-# Fallback to default v8 binary, if override is not set.
-V8="${V8:-$(which v8)}"
-SCRIPT="$1"
-shift
-"${V8}" --experimental-wasm-simd "${SCRIPT}" -- "$@"
diff --git a/media/libjxl/src/lib/CMakeLists.txt b/media/libjxl/src/lib/CMakeLists.txt
index 4bb4ebd35e..5c8e0bada5 100644
--- a/media/libjxl/src/lib/CMakeLists.txt
+++ b/media/libjxl/src/lib/CMakeLists.txt
@@ -52,7 +52,7 @@ set(JPEGXL_INTERNAL_FLAGS
)
# Warning flags supported by clang.
-if (${CMAKE_CXX_COMPILER_ID} MATCHES "Clang")
+if (CMAKE_CXX_COMPILER_ID MATCHES "Clang")
list(APPEND JPEGXL_INTERNAL_FLAGS
-Wdeprecated-increment-bool
# TODO(deymo): Add -Wextra-semi once we update third_party/highway.
@@ -90,7 +90,7 @@ if (WIN32)
-Wno-zero-as-null-pointer-constant
)
- if (${CMAKE_CXX_COMPILER_ID} MATCHES "Clang")
+ if (CMAKE_CXX_COMPILER_ID MATCHES "Clang")
list(APPEND JPEGXL_INTERNAL_FLAGS
-Wno-used-but-marked-unused
-Wno-unused-template
@@ -110,7 +110,7 @@ else() # WIN32
-fmath-errno
)
- if (${CMAKE_CXX_COMPILER_ID} MATCHES "Clang")
+ if (CMAKE_CXX_COMPILER_ID MATCHES "Clang")
list(APPEND JPEGXL_INTERNAL_FLAGS
-fnew-alignment=8
-fno-cxx-exceptions
@@ -136,7 +136,9 @@ endif() #!MSVC
include(jxl.cmake)
# Other libraries outside the core jxl library.
-include(jxl_extras.cmake)
+if(JPEGXL_ENABLE_TOOLS)
+ include(jxl_extras.cmake)
+endif()
include(jxl_threads.cmake)
# Install all the library headers from the source and the generated ones. There
diff --git a/media/libjxl/src/lib/extras/codec.cc b/media/libjxl/src/lib/extras/codec.cc
index 80ed7c867c..774b4ccb6e 100644
--- a/media/libjxl/src/lib/extras/codec.cc
+++ b/media/libjxl/src/lib/extras/codec.cc
@@ -52,7 +52,7 @@ Status SetFromBytes(const Span<const uint8_t> bytes,
Status SetFromFile(const std::string& pathname,
const extras::ColorHints& color_hints, CodecInOut* io,
ThreadPool* pool, extras::Codec* orig_codec) {
- PaddedBytes encoded;
+ std::vector<uint8_t> encoded;
JXL_RETURN_IF_ERROR(ReadFile(pathname, &encoded));
JXL_RETURN_IF_ERROR(SetFromBytes(Span<const uint8_t>(encoded), color_hints,
io, pool, orig_codec));
@@ -61,67 +61,66 @@ Status SetFromFile(const std::string& pathname,
Status Encode(const CodecInOut& io, const extras::Codec codec,
const ColorEncoding& c_desired, size_t bits_per_sample,
- PaddedBytes* bytes, ThreadPool* pool) {
+ std::vector<uint8_t>* bytes, ThreadPool* pool) {
JXL_CHECK(!io.Main().c_current().ICC().empty());
JXL_CHECK(!c_desired.ICC().empty());
io.CheckMetadata();
if (io.Main().IsJPEG()) {
JXL_WARNING("Writing JPEG data as pixels");
}
-
- extras::PackedPixelFile ppf;
- size_t num_channels = io.metadata.m.color_encoding.Channels();
JxlPixelFormat format = {
- static_cast<uint32_t>(num_channels),
- bits_per_sample <= 8 ? JXL_TYPE_UINT8 : JXL_TYPE_UINT16,
- JXL_NATIVE_ENDIAN, 0};
- std::vector<uint8_t> bytes_vector;
+ 0, // num_channels is ignored by the converter
+ bits_per_sample <= 8 ? JXL_TYPE_UINT8 : JXL_TYPE_UINT16, JXL_BIG_ENDIAN,
+ 0};
const bool floating_point = bits_per_sample > 16;
+ std::unique_ptr<extras::Encoder> encoder;
+ std::ostringstream os;
switch (codec) {
case extras::Codec::kPNG:
#if JPEGXL_ENABLE_APNG
- return extras::EncodeImageAPNG(&io, c_desired, bits_per_sample, pool,
- bytes);
+ encoder = extras::GetAPNGEncoder();
+ break;
#else
return JXL_FAILURE("JPEG XL was built without (A)PNG support");
#endif
case extras::Codec::kJPG:
#if JPEGXL_ENABLE_JPEG
- return EncodeImageJPG(&io,
- io.use_sjpeg ? extras::JpegEncoder::kSJpeg
- : extras::JpegEncoder::kLibJpeg,
- io.jpeg_quality, YCbCrChromaSubsampling(), pool,
- bytes);
+ format.data_type = JXL_TYPE_UINT8;
+ encoder = extras::GetJPEGEncoder();
+ os << io.jpeg_quality;
+ encoder->SetOption("q", os.str());
+ break;
#else
return JXL_FAILURE("JPEG XL was built without JPEG support");
#endif
case extras::Codec::kPNM:
-
- // Choose native for PFM; PGM/PPM require big-endian (N/A for PBM)
- format.endianness = floating_point ? JXL_NATIVE_ENDIAN : JXL_BIG_ENDIAN;
- if (floating_point) {
+ if (io.Main().HasAlpha()) {
+ encoder = extras::GetPAMEncoder();
+ } else if (io.Main().IsGray()) {
+ encoder = extras::GetPGMEncoder();
+ } else if (!floating_point) {
+ encoder = extras::GetPPMEncoder();
+ } else {
format.data_type = JXL_TYPE_FLOAT;
+ format.endianness = JXL_NATIVE_ENDIAN;
+ encoder = extras::GetPFMEncoder();
}
if (!c_desired.IsSRGB()) {
JXL_WARNING(
- "PNM encoder cannot store custom ICC profile; decoder\n"
+ "PNM encoder cannot store custom ICC profile; decoder "
"will need hint key=color_space to get the same values");
}
- JXL_RETURN_IF_ERROR(extras::ConvertCodecInOutToPackedPixelFile(
- io, format, c_desired, pool, &ppf));
- JXL_RETURN_IF_ERROR(extras::EncodeImagePNM(
- ppf, bits_per_sample, pool, /*frame_index=*/0, &bytes_vector));
- bytes->assign(bytes_vector.data(),
- bytes_vector.data() + bytes_vector.size());
- return true;
+ break;
case extras::Codec::kPGX:
- return extras::EncodeImagePGX(&io, c_desired, bits_per_sample, pool,
- bytes);
+ encoder = extras::GetPGXEncoder();
+ break;
case extras::Codec::kGIF:
return JXL_FAILURE("Encoding to GIF is not implemented");
case extras::Codec::kEXR:
#if JPEGXL_ENABLE_EXR
- return extras::EncodeImageEXR(&io, c_desired, pool, bytes);
+ format.data_type = JXL_TYPE_FLOAT;
+ encoder = extras::GetEXREncoder();
+ break;
#else
return JXL_FAILURE("JPEG XL was built without OpenEXR support");
#endif
@@ -129,7 +128,19 @@ Status Encode(const CodecInOut& io, const extras::Codec codec,
return JXL_FAILURE("Cannot encode using Codec::kUnknown");
}
- return JXL_FAILURE("Invalid codec");
+ if (!encoder) {
+ return JXL_FAILURE("Invalid codec.");
+ }
+
+ extras::PackedPixelFile ppf;
+ JXL_RETURN_IF_ERROR(
+ ConvertCodecInOutToPackedPixelFile(io, format, c_desired, pool, &ppf));
+ extras::EncodedImage encoded_image;
+ JXL_RETURN_IF_ERROR(encoder->Encode(ppf, &encoded_image, pool));
+ JXL_ASSERT(encoded_image.bitstreams.size() == 1);
+ *bytes = encoded_image.bitstreams[0];
+
+ return true;
}
Status EncodeToFile(const CodecInOut& io, const ColorEncoding& c_desired,
@@ -163,7 +174,7 @@ Status EncodeToFile(const CodecInOut& io, const ColorEncoding& c_desired,
bits_per_sample = 16;
}
- PaddedBytes encoded;
+ std::vector<uint8_t> encoded;
return Encode(io, codec, c_desired, bits_per_sample, &encoded, pool) &&
WriteFile(encoded, pathname);
}
diff --git a/media/libjxl/src/lib/extras/codec.h b/media/libjxl/src/lib/extras/codec.h
index 88e96d308e..73fdc80be9 100644
--- a/media/libjxl/src/lib/extras/codec.h
+++ b/media/libjxl/src/lib/extras/codec.h
@@ -49,7 +49,7 @@ Status SetFromFile(const std::string& pathname,
// color space to c_desired.
Status Encode(const CodecInOut& io, extras::Codec codec,
const ColorEncoding& c_desired, size_t bits_per_sample,
- PaddedBytes* bytes, ThreadPool* pool = nullptr);
+ std::vector<uint8_t>* bytes, ThreadPool* pool = nullptr);
// Deduces codec, calls Encode and writes to file.
Status EncodeToFile(const CodecInOut& io, const ColorEncoding& c_desired,
diff --git a/media/libjxl/src/lib/extras/codec_test.cc b/media/libjxl/src/lib/extras/codec_test.cc
index d7d9b86954..19cac39979 100644
--- a/media/libjxl/src/lib/extras/codec_test.cc
+++ b/media/libjxl/src/lib/extras/codec_test.cc
@@ -9,12 +9,15 @@
#include <stdio.h>
#include <algorithm>
+#include <sstream>
+#include <string>
#include <utility>
#include <vector>
-#include "gtest/gtest.h"
#include "lib/extras/dec/pgx.h"
#include "lib/extras/dec/pnm.h"
+#include "lib/extras/enc/encode.h"
+#include "lib/extras/packed_image_convert.h"
#include "lib/jxl/base/printf_macros.h"
#include "lib/jxl/base/random.h"
#include "lib/jxl/base/thread_pool_internal.h"
@@ -23,12 +26,20 @@
#include "lib/jxl/image.h"
#include "lib/jxl/image_bundle.h"
#include "lib/jxl/image_test_utils.h"
+#include "lib/jxl/test_utils.h"
#include "lib/jxl/testdata.h"
namespace jxl {
namespace extras {
namespace {
+using ::testing::AllOf;
+using ::testing::Contains;
+using ::testing::Field;
+using ::testing::IsEmpty;
+using ::testing::NotNull;
+using ::testing::SizeIs;
+
std::string ExtensionFromCodec(Codec codec, const bool is_gray,
const bool has_alpha,
const size_t bits_per_sample) {
@@ -54,159 +65,233 @@ std::string ExtensionFromCodec(Codec codec, const bool is_gray,
return std::string();
}
-CodecInOut CreateTestImage(const size_t xsize, const size_t ysize,
- const bool is_gray, const bool add_alpha,
- const size_t bits_per_sample,
- const ColorEncoding& c_native) {
- Image3F image(xsize, ysize);
- Rng rng(129);
- if (is_gray) {
- for (size_t y = 0; y < ysize; ++y) {
- float* JXL_RESTRICT row0 = image.PlaneRow(0, y);
- float* JXL_RESTRICT row1 = image.PlaneRow(1, y);
- float* JXL_RESTRICT row2 = image.PlaneRow(2, y);
- for (size_t x = 0; x < xsize; ++x) {
- row0[x] = row1[x] = row2[x] = rng.UniformF(0.0f, 1.0f);
+void VerifySameImage(const PackedImage& im0, size_t bits_per_sample0,
+ const PackedImage& im1, size_t bits_per_sample1,
+ bool lossless = true) {
+ ASSERT_EQ(im0.xsize, im1.xsize);
+ ASSERT_EQ(im0.ysize, im1.ysize);
+ ASSERT_EQ(im0.format.num_channels, im1.format.num_channels);
+ auto get_factor = [](JxlPixelFormat f, size_t bits) -> double {
+ return 1.0 / ((1u << std::min(test::GetPrecision(f.data_type), bits)) - 1);
+ };
+ double factor0 = get_factor(im0.format, bits_per_sample0);
+ double factor1 = get_factor(im1.format, bits_per_sample1);
+ auto pixels0 = static_cast<const uint8_t*>(im0.pixels());
+ auto pixels1 = static_cast<const uint8_t*>(im1.pixels());
+ auto rgba0 =
+ test::ConvertToRGBA32(pixels0, im0.xsize, im0.ysize, im0.format, factor0);
+ auto rgba1 =
+ test::ConvertToRGBA32(pixels1, im1.xsize, im1.ysize, im1.format, factor1);
+ double tolerance =
+ lossless ? 0.5 * std::min(factor0, factor1) : 3.0f / 255.0f;
+ if (bits_per_sample0 == 32 || bits_per_sample1 == 32) {
+ tolerance = 0.5 * std::max(factor0, factor1);
+ }
+ for (size_t y = 0; y < im0.ysize; ++y) {
+ for (size_t x = 0; x < im0.xsize; ++x) {
+ for (size_t c = 0; c < im0.format.num_channels; ++c) {
+ size_t ix = (y * im0.xsize + x) * 4 + c;
+ double val0 = rgba0[ix];
+ double val1 = rgba1[ix];
+ ASSERT_NEAR(val1, val0, tolerance)
+ << "y = " << y << " x = " << x << " c = " << c;
}
}
- } else {
- RandomFillImage(&image, 0.0f, 1.0f);
}
- CodecInOut io;
+}
+
+JxlColorEncoding CreateTestColorEncoding(bool is_gray) {
+ JxlColorEncoding c;
+ c.color_space = is_gray ? JXL_COLOR_SPACE_GRAY : JXL_COLOR_SPACE_RGB;
+ c.white_point = JXL_WHITE_POINT_D65;
+ c.primaries = JXL_PRIMARIES_P3;
+ c.rendering_intent = JXL_RENDERING_INTENT_RELATIVE;
+ c.transfer_function = JXL_TRANSFER_FUNCTION_LINEAR;
+ // Roundtrip through internal color encoding to fill in primaries and white
+ // point CIE xy coordinates.
+ ColorEncoding c_internal;
+ JXL_CHECK(ConvertExternalToInternalColorEncoding(c, &c_internal));
+ ConvertInternalToExternalColorEncoding(c_internal, &c);
+ return c;
+}
- if (bits_per_sample == 32) {
- io.metadata.m.SetFloat32Samples();
+std::vector<uint8_t> GenerateICC(JxlColorEncoding color_encoding) {
+ ColorEncoding c;
+ JXL_CHECK(ConvertExternalToInternalColorEncoding(color_encoding, &c));
+ JXL_CHECK(c.CreateICC());
+ PaddedBytes icc = c.ICC();
+ return std::vector<uint8_t>(icc.begin(), icc.end());
+}
+
+void StoreRandomValue(uint8_t* out, Rng* rng, JxlPixelFormat format,
+ size_t bits_per_sample) {
+ uint64_t max_val = (1ull << bits_per_sample) - 1;
+ if (format.data_type == JXL_TYPE_UINT8) {
+ *out = rng->UniformU(0, max_val);
+ } else if (format.data_type == JXL_TYPE_UINT16) {
+ uint32_t val = rng->UniformU(0, max_val);
+ if (format.endianness == JXL_BIG_ENDIAN) {
+ StoreBE16(val, out);
+ } else {
+ StoreLE16(val, out);
+ }
} else {
- io.metadata.m.SetUintSamples(bits_per_sample);
- }
- io.metadata.m.color_encoding = c_native;
- io.SetFromImage(std::move(image), c_native);
- if (add_alpha) {
- ImageF alpha(xsize, ysize);
- RandomFillImage(&alpha, 0.0f, 1.f);
- io.metadata.m.SetAlphaBits(bits_per_sample <= 8 ? 8 : 16);
- io.Main().SetAlpha(std::move(alpha), /*alpha_is_premultiplied=*/false);
+ ASSERT_EQ(format.data_type, JXL_TYPE_FLOAT);
+ float val = rng->UniformF(0.0, 1.0);
+ uint32_t uval;
+ memcpy(&uval, &val, 4);
+ if (format.endianness == JXL_BIG_ENDIAN) {
+ StoreBE32(uval, out);
+ } else {
+ StoreLE32(uval, out);
+ }
}
- return io;
}
-// Ensures reading a newly written file leads to the same image pixels.
-void TestRoundTrip(Codec codec, const size_t xsize, const size_t ysize,
- const bool is_gray, const bool add_alpha,
- const size_t bits_per_sample, ThreadPool* pool) {
- // JPEG encoding is not lossless.
- if (codec == Codec::kJPG) return;
- if (codec == Codec::kPNM && add_alpha) return;
- // Our EXR codec always uses 16-bit premultiplied alpha, does not support
- // grayscale, and somehow does not have sufficient precision for this test.
- if (codec == Codec::kEXR) return;
- printf("Codec %s bps:%" PRIuS " gr:%d al:%d\n",
- ExtensionFromCodec(codec, is_gray, add_alpha, bits_per_sample).c_str(),
- bits_per_sample, is_gray, add_alpha);
-
- ColorEncoding c_native;
- c_native.SetColorSpace(is_gray ? ColorSpace::kGray : ColorSpace::kRGB);
- // Note: this must not be wider than c_external, otherwise gamut clipping
- // will cause large round-trip errors.
- c_native.primaries = Primaries::kP3;
- c_native.tf.SetTransferFunction(TransferFunction::kLinear);
- JXL_CHECK(c_native.CreateICC());
-
- // Generally store same color space to reduce round trip errors..
- ColorEncoding c_external = c_native;
- // .. unless we have enough precision for some transforms.
- if (bits_per_sample >= 16) {
- c_external.white_point = WhitePoint::kE;
- c_external.primaries = Primaries::k2100;
- c_external.tf.SetTransferFunction(TransferFunction::kSRGB);
+void FillPackedImage(size_t bits_per_sample, PackedImage* image) {
+ JxlPixelFormat format = image->format;
+ size_t bytes_per_channel = PackedImage::BitsPerChannel(format.data_type) / 8;
+ uint8_t* out = static_cast<uint8_t*>(image->pixels());
+ size_t stride = image->xsize * format.num_channels * bytes_per_channel;
+ ASSERT_EQ(image->pixels_size, image->ysize * stride);
+ Rng rng(129);
+ for (size_t y = 0; y < image->ysize; ++y) {
+ for (size_t x = 0; x < image->xsize; ++x) {
+ for (size_t c = 0; c < format.num_channels; ++c) {
+ StoreRandomValue(out, &rng, format, bits_per_sample);
+ out += bytes_per_channel;
+ }
+ }
}
- JXL_CHECK(c_external.CreateICC());
+}
- const CodecInOut io = CreateTestImage(xsize, ysize, is_gray, add_alpha,
- bits_per_sample, c_native);
- const ImageBundle& ib1 = io.Main();
+struct TestImageParams {
+ Codec codec;
+ size_t xsize;
+ size_t ysize;
+ size_t bits_per_sample;
+ bool is_gray;
+ bool add_alpha;
+ bool big_endian;
+
+ bool ShouldTestRoundtrip() const {
+ if (codec == Codec::kPNG) {
+ return true;
+ } else if (codec == Codec::kPNM) {
+ // TODO(szabadka) Make PNM encoder endianness-aware.
+ return ((bits_per_sample <= 16 && big_endian) ||
+ (bits_per_sample == 32 && !add_alpha && !big_endian));
+ } else if (codec == Codec::kPGX) {
+ return ((bits_per_sample == 8 || bits_per_sample == 16) && is_gray &&
+ !add_alpha);
+ } else if (codec == Codec::kEXR) {
+#if defined(ADDRESS_SANITIZER) || defined(MEMORY_SANITIZER) || \
+ defined(THREAD_SANITIZER)
+ // OpenEXR 2.3 has a memory leak in IlmThread_2_3::ThreadPool
+ return false;
+#else
+ return bits_per_sample == 32 && !is_gray;
+#endif
+ } else if (codec == Codec::kJPG) {
+ return bits_per_sample == 8 && !add_alpha;
+ } else {
+ return false;
+ }
+ }
- PaddedBytes encoded;
- JXL_CHECK(Encode(io, codec, c_external, bits_per_sample, &encoded, pool));
+ JxlPixelFormat PixelFormat() const {
+ JxlPixelFormat format;
+ format.num_channels = (is_gray ? 1 : 3) + (add_alpha ? 1 : 0);
+ format.data_type = (bits_per_sample == 32 ? JXL_TYPE_FLOAT
+ : bits_per_sample > 8 ? JXL_TYPE_UINT16
+ : JXL_TYPE_UINT8);
+ format.endianness = big_endian ? JXL_BIG_ENDIAN : JXL_LITTLE_ENDIAN;
+ format.align = 0;
+ return format;
+ }
- CodecInOut io2;
- ColorHints color_hints;
- // Only for PNM because PNG will warn about ignoring them.
- if (codec == Codec::kPNM) {
- color_hints.Add("color_space", Description(c_external));
+ std::string DebugString() const {
+ std::ostringstream os;
+ os << "bps:" << bits_per_sample << " gr:" << is_gray << " al:" << add_alpha
+ << " be: " << big_endian;
+ return os.str();
}
- JXL_CHECK(SetFromBytes(Span<const uint8_t>(encoded), color_hints, &io2, pool,
- nullptr));
- ImageBundle& ib2 = io2.Main();
+};
+
+void CreateTestImage(const TestImageParams& params, PackedPixelFile* ppf) {
+ ppf->info.xsize = params.xsize;
+ ppf->info.ysize = params.ysize;
+ ppf->info.bits_per_sample = params.bits_per_sample;
+ ppf->info.exponent_bits_per_sample = params.bits_per_sample == 32 ? 8 : 0;
+ ppf->info.num_color_channels = params.is_gray ? 1 : 3;
+ ppf->info.alpha_bits = params.add_alpha ? params.bits_per_sample : 0;
+ ppf->info.alpha_premultiplied = (params.codec == Codec::kEXR);
+
+ JxlColorEncoding color_encoding = CreateTestColorEncoding(params.is_gray);
+ ppf->icc = GenerateICC(color_encoding);
+ ppf->color_encoding = color_encoding;
+
+ PackedFrame frame(params.xsize, params.ysize, params.PixelFormat());
+ FillPackedImage(params.bits_per_sample, &frame.color);
+ ppf->frames.emplace_back(std::move(frame));
+}
- EXPECT_EQ(Description(c_external),
- Description(io2.metadata.m.color_encoding));
+// Ensures reading a newly written file leads to the same image pixels.
+void TestRoundTrip(const TestImageParams& params, ThreadPool* pool) {
+ if (!params.ShouldTestRoundtrip()) return;
- // See c_external above - for low bits_per_sample the encoded space is
- // already the same.
- if (bits_per_sample < 16) {
- EXPECT_EQ(Description(ib1.c_current()), Description(ib2.c_current()));
- }
+ std::string extension = ExtensionFromCodec(
+ params.codec, params.is_gray, params.add_alpha, params.bits_per_sample);
+ printf("Codec %s %s\n", extension.c_str(), params.DebugString().c_str());
- if (add_alpha) {
- EXPECT_TRUE(SamePixels(ib1.alpha(), *ib2.alpha()));
- }
+ PackedPixelFile ppf_in;
+ CreateTestImage(params, &ppf_in);
- JXL_CHECK(ib2.TransformTo(ib1.c_current(), GetJxlCms(), pool));
+ EncodedImage encoded;
+ auto encoder = Encoder::FromExtension(extension);
+ ASSERT_TRUE(encoder.get());
+ ASSERT_TRUE(encoder->Encode(ppf_in, &encoded, pool));
+ ASSERT_EQ(encoded.bitstreams.size(), 1);
- double max_l1, max_rel;
- // Round-trip tolerances must be higher than in external_image_test because
- // codecs do not support unbounded ranges.
-#if JPEGXL_ENABLE_SKCMS
- if (bits_per_sample <= 12) {
- max_l1 = 0.5;
- max_rel = 6E-3;
- } else {
- max_l1 = 1E-3;
- max_rel = 5E-4;
- }
-#else // JPEGXL_ENABLE_SKCMS
- if (bits_per_sample <= 12) {
- max_l1 = 0.5;
- max_rel = 6E-3;
- } else if (bits_per_sample == 16) {
- max_l1 = 3E-3;
- max_rel = 1E-4;
- } else {
-#ifdef __ARM_ARCH
- // pow() implementation in arm is a bit less precise than in x86 and
- // therefore we need a bigger error margin in this case.
- max_l1 = 1E-7;
- max_rel = 1E-4;
-#else
- max_l1 = 1E-7;
- max_rel = 1E-5;
-#endif
+ PackedPixelFile ppf_out;
+ ASSERT_TRUE(DecodeBytes(Span<const uint8_t>(encoded.bitstreams[0]),
+ ColorHints(), SizeConstraints(), &ppf_out));
+
+ if (params.codec != Codec::kPNM && params.codec != Codec::kPGX &&
+ params.codec != Codec::kEXR) {
+ EXPECT_EQ(ppf_in.icc, ppf_out.icc);
}
-#endif // JPEGXL_ENABLE_SKCMS
- VerifyRelativeError(ib1.color(), *ib2.color(), max_l1, max_rel);
+ ASSERT_EQ(ppf_out.frames.size(), 1);
+ VerifySameImage(ppf_in.frames[0].color, ppf_in.info.bits_per_sample,
+ ppf_out.frames[0].color, ppf_out.info.bits_per_sample,
+ /*lossless=*/params.codec != Codec::kJPG);
}
-#if 0
TEST(CodecTest, TestRoundTrip) {
ThreadPoolInternal pool(12);
- const size_t xsize = 7;
- const size_t ysize = 4;
+ TestImageParams params;
+ params.xsize = 7;
+ params.ysize = 4;
- for (Codec codec : Values<Codec>()) {
- for (int bits_per_sample : {8, 10, 12, 16, 32}) {
+ for (Codec codec : AvailableCodecs()) {
+ for (int bits_per_sample : {4, 8, 10, 12, 16, 32}) {
for (bool is_gray : {false, true}) {
for (bool add_alpha : {false, true}) {
- TestRoundTrip(codec, xsize, ysize, is_gray, add_alpha,
- static_cast<size_t>(bits_per_sample), &pool);
+ for (bool big_endian : {false, true}) {
+ params.codec = codec;
+ params.bits_per_sample = static_cast<size_t>(bits_per_sample);
+ params.is_gray = is_gray;
+ params.add_alpha = add_alpha;
+ params.big_endian = big_endian;
+ TestRoundTrip(params, &pool);
+ }
}
}
}
}
}
-#endif
CodecInOut DecodeRoundtrip(const std::string& pathname, ThreadPool* pool,
const ColorHints& color_hints = ColorHints()) {
@@ -217,7 +302,7 @@ CodecInOut DecodeRoundtrip(const std::string& pathname, ThreadPool* pool,
const ImageBundle& ib1 = io.Main();
// Encode/Decode again to make sure Encode carries through all metadata.
- PaddedBytes encoded;
+ std::vector<uint8_t> encoded;
JXL_CHECK(Encode(io, Codec::kPNG, io.metadata.m.color_encoding,
io.metadata.m.bit_depth.bits_per_sample, &encoded, pool));
@@ -256,11 +341,11 @@ CodecInOut DecodeRoundtrip(const std::string& pathname, ThreadPool* pool,
TEST(CodecTest, TestMetadataSRGB) {
ThreadPoolInternal pool(12);
- const char* paths[] = {"third_party/raw.pixls/DJI-FC6310-16bit_srgb8_v4_krita.png",
- "third_party/raw.pixls/Google-Pixel2XL-16bit_srgb8_v4_krita.png",
- "third_party/raw.pixls/HUAWEI-EVA-L09-16bit_srgb8_dt.png",
- "third_party/raw.pixls/Nikon-D300-12bit_srgb8_dt.png",
- "third_party/raw.pixls/Sony-DSC-RX1RM2-14bit_srgb8_v4_krita.png"};
+ const char* paths[] = {"external/raw.pixls/DJI-FC6310-16bit_srgb8_v4_krita.png",
+ "external/raw.pixls/Google-Pixel2XL-16bit_srgb8_v4_krita.png",
+ "external/raw.pixls/HUAWEI-EVA-L09-16bit_srgb8_dt.png",
+ "external/raw.pixls/Nikon-D300-12bit_srgb8_dt.png",
+ "external/raw.pixls/Sony-DSC-RX1RM2-14bit_srgb8_v4_krita.png"};
for (const char* relative_pathname : paths) {
const CodecInOut io =
DecodeRoundtrip(relative_pathname, Codec::kPNG, &pool);
@@ -285,9 +370,9 @@ TEST(CodecTest, TestMetadataLinear) {
ThreadPoolInternal pool(12);
const char* paths[3] = {
- "third_party/raw.pixls/Google-Pixel2XL-16bit_acescg_g1_v4_krita.png",
- "third_party/raw.pixls/HUAWEI-EVA-L09-16bit_709_g1_dt.png",
- "third_party/raw.pixls/Nikon-D300-12bit_2020_g1_dt.png",
+ "external/raw.pixls/Google-Pixel2XL-16bit_acescg_g1_v4_krita.png",
+ "external/raw.pixls/HUAWEI-EVA-L09-16bit_709_g1_dt.png",
+ "external/raw.pixls/Nikon-D300-12bit_2020_g1_dt.png",
};
const WhitePoint white_points[3] = {WhitePoint::kCustom, WhitePoint::kD65,
WhitePoint::kD65};
@@ -317,8 +402,8 @@ TEST(CodecTest, TestMetadataICC) {
ThreadPoolInternal pool(12);
const char* paths[] = {
- "third_party/raw.pixls/DJI-FC6310-16bit_709_v4_krita.png",
- "third_party/raw.pixls/Sony-DSC-RX1RM2-14bit_709_v4_krita.png",
+ "external/raw.pixls/DJI-FC6310-16bit_709_v4_krita.png",
+ "external/raw.pixls/Sony-DSC-RX1RM2-14bit_709_v4_krita.png",
};
for (const char* relative_pathname : paths) {
const CodecInOut io =
@@ -340,28 +425,28 @@ TEST(CodecTest, TestMetadataICC) {
}
}
-TEST(CodecTest, Testthird_party/pngsuite) {
+TEST(CodecTest, Testexternal/pngsuite) {
ThreadPoolInternal pool(12);
// Ensure we can load PNG with text, japanese UTF-8, compressed text.
- (void)DecodeRoundtrip("third_party/pngsuite/ct1n0g04.png", Codec::kPNG, &pool);
- (void)DecodeRoundtrip("third_party/pngsuite/ctjn0g04.png", Codec::kPNG, &pool);
- (void)DecodeRoundtrip("third_party/pngsuite/ctzn0g04.png", Codec::kPNG, &pool);
+ (void)DecodeRoundtrip("external/pngsuite/ct1n0g04.png", Codec::kPNG, &pool);
+ (void)DecodeRoundtrip("external/pngsuite/ctjn0g04.png", Codec::kPNG, &pool);
+ (void)DecodeRoundtrip("external/pngsuite/ctzn0g04.png", Codec::kPNG, &pool);
// Extract gAMA
const CodecInOut b1 =
- DecodeRoundtrip("third_party/pngsuite/g10n3p04.png", Codec::kPNG, &pool);
+ DecodeRoundtrip("external/pngsuite/g10n3p04.png", Codec::kPNG, &pool);
EXPECT_TRUE(b1.metadata.color_encoding.tf.IsLinear());
// Extract cHRM
const CodecInOut b_p =
- DecodeRoundtrip("third_party/pngsuite/ccwn2c08.png", Codec::kPNG, &pool);
+ DecodeRoundtrip("external/pngsuite/ccwn2c08.png", Codec::kPNG, &pool);
EXPECT_EQ(Primaries::kSRGB, b_p.metadata.color_encoding.primaries);
EXPECT_EQ(WhitePoint::kD65, b_p.metadata.color_encoding.white_point);
// Extract EXIF from (new-style) dedicated chunk
const CodecInOut b_exif =
- DecodeRoundtrip("third_party/pngsuite/exif2c08.png", Codec::kPNG, &pool);
+ DecodeRoundtrip("external/pngsuite/exif2c08.png", Codec::kPNG, &pool);
EXPECT_EQ(978, b_exif.blobs.exif.size());
}
#endif
@@ -384,18 +469,88 @@ void VerifyWideGamutMetadata(const std::string& relative_pathname,
TEST(CodecTest, TestWideGamut) {
ThreadPoolInternal pool(12);
- // VerifyWideGamutMetadata("third_party/wide-gamut-tests/P3-sRGB-color-bars.png",
+ // VerifyWideGamutMetadata("external/wide-gamut-tests/P3-sRGB-color-bars.png",
// Primaries::kP3, &pool);
- VerifyWideGamutMetadata("third_party/wide-gamut-tests/P3-sRGB-color-ring.png",
+ VerifyWideGamutMetadata("external/wide-gamut-tests/P3-sRGB-color-ring.png",
Primaries::kP3, &pool);
- // VerifyWideGamutMetadata("third_party/wide-gamut-tests/R2020-sRGB-color-bars.png",
+ // VerifyWideGamutMetadata("external/wide-gamut-tests/R2020-sRGB-color-bars.png",
// Primaries::k2100, &pool);
- // VerifyWideGamutMetadata("third_party/wide-gamut-tests/R2020-sRGB-color-ring.png",
+ // VerifyWideGamutMetadata("external/wide-gamut-tests/R2020-sRGB-color-ring.png",
// Primaries::k2100, &pool);
}
TEST(CodecTest, TestPNM) { TestCodecPNM(); }
+TEST(CodecTest, FormatNegotiation) {
+ const std::vector<JxlPixelFormat> accepted_formats = {
+ {/*num_channels=*/4,
+ /*data_type=*/JXL_TYPE_UINT16,
+ /*endianness=*/JXL_NATIVE_ENDIAN,
+ /*align=*/0},
+ {/*num_channels=*/3,
+ /*data_type=*/JXL_TYPE_UINT8,
+ /*endianness=*/JXL_NATIVE_ENDIAN,
+ /*align=*/0},
+ {/*num_channels=*/3,
+ /*data_type=*/JXL_TYPE_UINT16,
+ /*endianness=*/JXL_NATIVE_ENDIAN,
+ /*align=*/0},
+ {/*num_channels=*/1,
+ /*data_type=*/JXL_TYPE_UINT8,
+ /*endianness=*/JXL_NATIVE_ENDIAN,
+ /*align=*/0},
+ };
+
+ JxlBasicInfo info;
+ JxlEncoderInitBasicInfo(&info);
+ info.bits_per_sample = 12;
+ info.num_color_channels = 2;
+
+ JxlPixelFormat format;
+ EXPECT_FALSE(SelectFormat(accepted_formats, info, &format));
+
+ info.num_color_channels = 3;
+ ASSERT_TRUE(SelectFormat(accepted_formats, info, &format));
+ EXPECT_EQ(format.num_channels, info.num_color_channels);
+ // 16 is the smallest accepted format that can accommodate the 12-bit data.
+ EXPECT_EQ(format.data_type, JXL_TYPE_UINT16);
+}
+
+TEST(CodecTest, EncodeToPNG) {
+ ThreadPool* const pool = nullptr;
+
+ std::unique_ptr<Encoder> png_encoder = Encoder::FromExtension(".png");
+ ASSERT_THAT(png_encoder, NotNull());
+
+ const PaddedBytes original_png =
+ ReadTestData("external/wesaturate/500px/tmshre_riaphotographs_srgb8.png");
+ PackedPixelFile ppf;
+ ASSERT_TRUE(extras::DecodeBytes(Span<const uint8_t>(original_png),
+ ColorHints(), SizeConstraints(), &ppf));
+
+ const JxlPixelFormat& format = ppf.frames.front().color.format;
+ ASSERT_THAT(
+ png_encoder->AcceptedFormats(),
+ Contains(AllOf(Field(&JxlPixelFormat::num_channels, format.num_channels),
+ Field(&JxlPixelFormat::data_type, format.data_type),
+ Field(&JxlPixelFormat::endianness, format.endianness))));
+ EncodedImage encoded_png;
+ ASSERT_TRUE(png_encoder->Encode(ppf, &encoded_png, pool));
+ EXPECT_THAT(encoded_png.icc, IsEmpty());
+ ASSERT_THAT(encoded_png.bitstreams, SizeIs(1));
+
+ PackedPixelFile decoded_ppf;
+ ASSERT_TRUE(
+ extras::DecodeBytes(Span<const uint8_t>(encoded_png.bitstreams.front()),
+ ColorHints(), SizeConstraints(), &decoded_ppf));
+
+ ASSERT_EQ(decoded_ppf.info.bits_per_sample, ppf.info.bits_per_sample);
+ ASSERT_EQ(decoded_ppf.frames.size(), 1);
+ VerifySameImage(ppf.frames[0].color, ppf.info.bits_per_sample,
+ decoded_ppf.frames[0].color,
+ decoded_ppf.info.bits_per_sample);
+}
+
} // namespace
} // namespace extras
} // namespace jxl
diff --git a/media/libjxl/src/lib/extras/dec/decode.cc b/media/libjxl/src/lib/extras/dec/decode.cc
index 55ce0ba3f2..8712e03aac 100644
--- a/media/libjxl/src/lib/extras/dec/decode.cc
+++ b/media/libjxl/src/lib/extras/dec/decode.cc
@@ -31,6 +31,25 @@ constexpr size_t kMinBytes = 9;
} // namespace
+std::vector<Codec> AvailableCodecs() {
+ std::vector<Codec> out;
+#if JPEGXL_ENABLE_APNG
+ out.push_back(Codec::kPNG);
+#endif
+#if JPEGXL_ENABLE_EXR
+ out.push_back(Codec::kEXR);
+#endif
+#if JPEGXL_ENABLE_GIF
+ out.push_back(Codec::kGIF);
+#endif
+#if JPEGXL_ENABLE_JPEG
+ out.push_back(Codec::kJPG);
+#endif
+ out.push_back(Codec::kPGX);
+ out.push_back(Codec::kPNM);
+ return out;
+}
+
Codec CodecFromExtension(std::string extension,
size_t* JXL_RESTRICT bits_per_sample) {
std::transform(
diff --git a/media/libjxl/src/lib/extras/dec/decode.h b/media/libjxl/src/lib/extras/dec/decode.h
index 32ec4c6368..7f0ff70aa8 100644
--- a/media/libjxl/src/lib/extras/dec/decode.h
+++ b/media/libjxl/src/lib/extras/dec/decode.h
@@ -12,15 +12,12 @@
#include <stdint.h>
#include <string>
+#include <vector>
#include "lib/extras/dec/color_hints.h"
-#include "lib/jxl/base/compiler_specific.h"
-#include "lib/jxl/base/data_parallel.h"
-#include "lib/jxl/base/padded_bytes.h"
#include "lib/jxl/base/span.h"
#include "lib/jxl/base/status.h"
#include "lib/jxl/codec_in_out.h"
-#include "lib/jxl/field_encodings.h" // MakeBit
namespace jxl {
namespace extras {
@@ -36,27 +33,14 @@ enum class Codec : uint32_t {
kEXR
};
-static inline constexpr uint64_t EnumBits(Codec /*unused*/) {
- // Return only fully-supported codecs (kGIF is decode-only).
- return MakeBit(Codec::kPNM)
-#if JPEGXL_ENABLE_APNG
- | MakeBit(Codec::kPNG)
-#endif
-#if JPEGXL_ENABLE_JPEG
- | MakeBit(Codec::kJPG)
-#endif
-#if JPEGXL_ENABLE_EXR
- | MakeBit(Codec::kEXR)
-#endif
- ;
-}
+std::vector<Codec> AvailableCodecs();
// If and only if extension is ".pfm", *bits_per_sample is updated to 32 so
// that Encode() would encode to PFM instead of PPM.
Codec CodecFromExtension(std::string extension,
size_t* JXL_RESTRICT bits_per_sample = nullptr);
-// Decodes "bytes" and sets io->metadata.m.
+// Decodes "bytes" info *ppf.
// color_space_hint may specify the color space, otherwise, defaults to sRGB.
Status DecodeBytes(Span<const uint8_t> bytes, const ColorHints& color_hints,
const SizeConstraints& constraints,
diff --git a/media/libjxl/src/lib/extras/dec/jxl.cc b/media/libjxl/src/lib/extras/dec/jxl.cc
new file mode 100644
index 0000000000..0e1035646d
--- /dev/null
+++ b/media/libjxl/src/lib/extras/dec/jxl.cc
@@ -0,0 +1,480 @@
+// Copyright (c) the JPEG XL Project Authors. All rights reserved.
+//
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+#include "lib/extras/dec/jxl.h"
+
+#include "jxl/decode.h"
+#include "jxl/decode_cxx.h"
+#include "jxl/types.h"
+#include "lib/extras/dec/color_description.h"
+#include "lib/extras/enc/encode.h"
+#include "lib/jxl/base/printf_macros.h"
+
+namespace jxl {
+namespace extras {
+namespace {
+
+struct BoxProcessor {
+ BoxProcessor(JxlDecoder* dec) : dec_(dec) { Reset(); }
+
+ void InitializeOutput(std::vector<uint8_t>* out) {
+ box_data_ = out;
+ AddMoreOutput();
+ }
+
+ bool AddMoreOutput() {
+ Flush();
+ static const size_t kBoxOutputChunkSize = 1 << 16;
+ box_data_->resize(box_data_->size() + kBoxOutputChunkSize);
+ next_out_ = box_data_->data() + total_size_;
+ avail_out_ = box_data_->size() - total_size_;
+ if (JXL_DEC_SUCCESS !=
+ JxlDecoderSetBoxBuffer(dec_, next_out_, avail_out_)) {
+ fprintf(stderr, "JxlDecoderSetBoxBuffer failed\n");
+ return false;
+ }
+ return true;
+ }
+
+ void FinalizeOutput() {
+ if (box_data_ == nullptr) return;
+ Flush();
+ box_data_->resize(total_size_);
+ Reset();
+ }
+
+ private:
+ JxlDecoder* dec_;
+ std::vector<uint8_t>* box_data_;
+ uint8_t* next_out_;
+ size_t avail_out_;
+ size_t total_size_;
+
+ void Reset() {
+ box_data_ = nullptr;
+ next_out_ = nullptr;
+ avail_out_ = 0;
+ total_size_ = 0;
+ }
+ void Flush() {
+ if (box_data_ == nullptr) return;
+ size_t remaining = JxlDecoderReleaseBoxBuffer(dec_);
+ size_t bytes_written = avail_out_ - remaining;
+ next_out_ += bytes_written;
+ avail_out_ -= bytes_written;
+ total_size_ += bytes_written;
+ }
+};
+
+} // namespace
+
+bool DecodeImageJXL(const uint8_t* bytes, size_t bytes_size,
+ const JXLDecompressParams& dparams, size_t* decoded_bytes,
+ PackedPixelFile* ppf, std::vector<uint8_t>* jpeg_bytes) {
+ auto decoder = JxlDecoderMake(/*memory_manager=*/nullptr);
+ JxlDecoder* dec = decoder.get();
+ ppf->frames.clear();
+
+ if (dparams.runner_opaque != nullptr &&
+ JXL_DEC_SUCCESS != JxlDecoderSetParallelRunner(dec, dparams.runner,
+ dparams.runner_opaque)) {
+ fprintf(stderr, "JxlEncoderSetParallelRunner failed\n");
+ return false;
+ }
+
+ JxlPixelFormat format;
+ std::vector<JxlPixelFormat> accepted_formats = dparams.accepted_formats;
+ if (accepted_formats.empty()) {
+ for (const uint32_t num_channels : {1, 2, 3, 4}) {
+ accepted_formats.push_back(
+ {num_channels, JXL_TYPE_FLOAT, JXL_LITTLE_ENDIAN, /*align=*/0});
+ }
+ }
+ JxlColorEncoding color_encoding;
+ size_t num_color_channels = 0;
+ if (!dparams.color_space.empty()) {
+ if (!jxl::ParseDescription(dparams.color_space, &color_encoding)) {
+ fprintf(stderr, "Failed to parse color space %s.\n",
+ dparams.color_space.c_str());
+ return false;
+ }
+ num_color_channels =
+ color_encoding.color_space == JXL_COLOR_SPACE_GRAY ? 1 : 3;
+ }
+
+ bool can_reconstruct_jpeg = false;
+ std::vector<uint8_t> jpeg_data_chunk;
+ if (jpeg_bytes != nullptr) {
+ jpeg_data_chunk.resize(16384);
+ jpeg_bytes->resize(0);
+ }
+
+ int events = (JXL_DEC_BASIC_INFO | JXL_DEC_FULL_IMAGE);
+
+ bool max_passes_defined =
+ (dparams.max_passes < std::numeric_limits<uint32_t>::max());
+ if (max_passes_defined || dparams.max_downsampling > 1) {
+ events |= JXL_DEC_FRAME_PROGRESSION;
+ if (max_passes_defined) {
+ JxlDecoderSetProgressiveDetail(dec, JxlProgressiveDetail::kPasses);
+ } else {
+ JxlDecoderSetProgressiveDetail(dec, JxlProgressiveDetail::kLastPasses);
+ }
+ }
+ if (jpeg_bytes != nullptr) {
+ events |= JXL_DEC_JPEG_RECONSTRUCTION;
+ } else {
+ events |= (JXL_DEC_COLOR_ENCODING | JXL_DEC_FRAME | JXL_DEC_PREVIEW_IMAGE |
+ JXL_DEC_BOX);
+ }
+ if (JXL_DEC_SUCCESS != JxlDecoderSubscribeEvents(dec, events)) {
+ fprintf(stderr, "JxlDecoderSubscribeEvents failed\n");
+ return false;
+ }
+ if (jpeg_bytes == nullptr) {
+ if (JXL_DEC_SUCCESS !=
+ JxlDecoderSetRenderSpotcolors(dec, dparams.render_spotcolors)) {
+ fprintf(stderr, "JxlDecoderSetRenderSpotColors failed\n");
+ return false;
+ }
+ if (JXL_DEC_SUCCESS !=
+ JxlDecoderSetKeepOrientation(dec, dparams.keep_orientation)) {
+ fprintf(stderr, "JxlDecoderSetKeepOrientation failed\n");
+ return false;
+ }
+ if (JXL_DEC_SUCCESS !=
+ JxlDecoderSetUnpremultiplyAlpha(dec, dparams.unpremultiply_alpha)) {
+ fprintf(stderr, "JxlDecoderSetUnpremultiplyAlpha failed\n");
+ return false;
+ }
+ if (dparams.display_nits > 0 &&
+ JXL_DEC_SUCCESS !=
+ JxlDecoderSetDesiredIntensityTarget(dec, dparams.display_nits)) {
+ fprintf(stderr, "Decoder failed to set desired intensity target\n");
+ return false;
+ }
+ if (JXL_DEC_SUCCESS != JxlDecoderSetDecompressBoxes(dec, JXL_TRUE)) {
+ fprintf(stderr, "JxlDecoderSetDecompressBoxes failed\n");
+ return false;
+ }
+ }
+ if (JXL_DEC_SUCCESS != JxlDecoderSetInput(dec, bytes, bytes_size)) {
+ fprintf(stderr, "Decoder failed to set input\n");
+ return false;
+ }
+ uint32_t progression_index = 0;
+ bool codestream_done = false;
+ BoxProcessor boxes(dec);
+ for (;;) {
+ JxlDecoderStatus status = JxlDecoderProcessInput(dec);
+ if (status == JXL_DEC_ERROR) {
+ fprintf(stderr, "Failed to decode image\n");
+ return false;
+ } else if (status == JXL_DEC_NEED_MORE_INPUT) {
+ if (codestream_done) {
+ break;
+ }
+ if (dparams.allow_partial_input) {
+ if (JXL_DEC_SUCCESS != JxlDecoderFlushImage(dec)) {
+ fprintf(stderr,
+ "Input file is truncated and there is no preview "
+ "available yet.\n");
+ return false;
+ }
+ break;
+ }
+ fprintf(stderr,
+ "Input file is truncated and allow_partial_input was disabled.");
+ return false;
+ } else if (status == JXL_DEC_BOX) {
+ boxes.FinalizeOutput();
+ JxlBoxType box_type;
+ if (JXL_DEC_SUCCESS != JxlDecoderGetBoxType(dec, box_type, JXL_TRUE)) {
+ fprintf(stderr, "JxlDecoderGetBoxType failed\n");
+ return false;
+ }
+ std::vector<uint8_t>* box_data = nullptr;
+ if (memcmp(box_type, "Exif", 4) == 0) {
+ box_data = &ppf->metadata.exif;
+ } else if (memcmp(box_type, "iptc", 4) == 0) {
+ box_data = &ppf->metadata.iptc;
+ } else if (memcmp(box_type, "jumb", 4) == 0) {
+ box_data = &ppf->metadata.jumbf;
+ } else if (memcmp(box_type, "xml ", 4) == 0) {
+ box_data = &ppf->metadata.xmp;
+ }
+ if (box_data) {
+ boxes.InitializeOutput(box_data);
+ }
+ } else if (status == JXL_DEC_BOX_NEED_MORE_OUTPUT) {
+ boxes.AddMoreOutput();
+ } else if (status == JXL_DEC_JPEG_RECONSTRUCTION) {
+ can_reconstruct_jpeg = true;
+ // Decoding to JPEG.
+ if (JXL_DEC_SUCCESS != JxlDecoderSetJPEGBuffer(dec,
+ jpeg_data_chunk.data(),
+ jpeg_data_chunk.size())) {
+ fprintf(stderr, "Decoder failed to set JPEG Buffer\n");
+ return false;
+ }
+ } else if (status == JXL_DEC_JPEG_NEED_MORE_OUTPUT) {
+ // Decoded a chunk to JPEG.
+ size_t used_jpeg_output =
+ jpeg_data_chunk.size() - JxlDecoderReleaseJPEGBuffer(dec);
+ jpeg_bytes->insert(jpeg_bytes->end(), jpeg_data_chunk.data(),
+ jpeg_data_chunk.data() + used_jpeg_output);
+ if (used_jpeg_output == 0) {
+ // Chunk is too small.
+ jpeg_data_chunk.resize(jpeg_data_chunk.size() * 2);
+ }
+ if (JXL_DEC_SUCCESS != JxlDecoderSetJPEGBuffer(dec,
+ jpeg_data_chunk.data(),
+ jpeg_data_chunk.size())) {
+ fprintf(stderr, "Decoder failed to set JPEG Buffer\n");
+ return false;
+ }
+ } else if (status == JXL_DEC_BASIC_INFO) {
+ if (JXL_DEC_SUCCESS != JxlDecoderGetBasicInfo(dec, &ppf->info)) {
+ fprintf(stderr, "JxlDecoderGetBasicInfo failed\n");
+ return false;
+ }
+ if (num_color_channels != 0) {
+ // Mark the change in number of color channels due to the requested
+ // color space.
+ ppf->info.num_color_channels = num_color_channels;
+ }
+ // Select format according to accepted formats.
+ if (!jxl::extras::SelectFormat(accepted_formats, ppf->info, &format)) {
+ fprintf(stderr, "SelectFormat failed\n");
+ return false;
+ }
+ bool have_alpha = (format.num_channels == 2 || format.num_channels == 4);
+ if (!have_alpha) {
+ // Mark in the basic info that alpha channel was dropped.
+ ppf->info.alpha_bits = 0;
+ } else if (dparams.unpremultiply_alpha) {
+ // Mark in the basic info that alpha was unpremultiplied.
+ ppf->info.alpha_premultiplied = false;
+ }
+ bool alpha_found = false;
+ for (uint32_t i = 0; i < ppf->info.num_extra_channels; ++i) {
+ JxlExtraChannelInfo eci;
+ if (JXL_DEC_SUCCESS != JxlDecoderGetExtraChannelInfo(dec, i, &eci)) {
+ fprintf(stderr, "JxlDecoderGetExtraChannelInfo failed\n");
+ return false;
+ }
+ if (eci.type == JXL_CHANNEL_ALPHA && have_alpha && !alpha_found) {
+ // Skip the first alpha channels because it is already present in the
+ // interleaved image.
+ alpha_found = true;
+ continue;
+ }
+ std::string name(eci.name_length + 1, 0);
+ if (JXL_DEC_SUCCESS !=
+ JxlDecoderGetExtraChannelName(dec, i, &name[0], name.size())) {
+ fprintf(stderr, "JxlDecoderGetExtraChannelName failed\n");
+ return false;
+ }
+ name.resize(eci.name_length);
+ ppf->extra_channels_info.push_back({eci, i, name});
+ }
+ } else if (status == JXL_DEC_COLOR_ENCODING) {
+ if (!dparams.color_space.empty()) {
+ if (ppf->info.uses_original_profile) {
+ fprintf(stderr,
+ "Warning: --color_space ignored because the image is "
+ "not XYB encoded.\n");
+ } else {
+ if (JXL_DEC_SUCCESS !=
+ JxlDecoderSetPreferredColorProfile(dec, &color_encoding)) {
+ fprintf(stderr, "Failed to set color space.\n");
+ return false;
+ }
+ }
+ }
+ size_t icc_size = 0;
+ JxlColorProfileTarget target = JXL_COLOR_PROFILE_TARGET_DATA;
+ if (JXL_DEC_SUCCESS !=
+ JxlDecoderGetICCProfileSize(dec, nullptr, target, &icc_size)) {
+ fprintf(stderr, "JxlDecoderGetICCProfileSize failed\n");
+ }
+ if (icc_size != 0) {
+ ppf->icc.resize(icc_size);
+ if (JXL_DEC_SUCCESS !=
+ JxlDecoderGetColorAsICCProfile(dec, nullptr, target,
+ ppf->icc.data(), icc_size)) {
+ fprintf(stderr, "JxlDecoderGetColorAsICCProfile failed\n");
+ return false;
+ }
+ }
+ if (JXL_DEC_SUCCESS != JxlDecoderGetColorAsEncodedProfile(
+ dec, nullptr, target, &ppf->color_encoding)) {
+ ppf->color_encoding.color_space = JXL_COLOR_SPACE_UNKNOWN;
+ }
+ icc_size = 0;
+ target = JXL_COLOR_PROFILE_TARGET_ORIGINAL;
+ if (JXL_DEC_SUCCESS !=
+ JxlDecoderGetICCProfileSize(dec, nullptr, target, &icc_size)) {
+ fprintf(stderr, "JxlDecoderGetICCProfileSize failed\n");
+ }
+ if (icc_size != 0) {
+ ppf->orig_icc.resize(icc_size);
+ if (JXL_DEC_SUCCESS !=
+ JxlDecoderGetColorAsICCProfile(dec, nullptr, target,
+ ppf->orig_icc.data(), icc_size)) {
+ fprintf(stderr, "JxlDecoderGetColorAsICCProfile failed\n");
+ return false;
+ }
+ }
+ } else if (status == JXL_DEC_FRAME) {
+ jxl::extras::PackedFrame frame(ppf->info.xsize, ppf->info.ysize, format);
+ if (JXL_DEC_SUCCESS != JxlDecoderGetFrameHeader(dec, &frame.frame_info)) {
+ fprintf(stderr, "JxlDecoderGetFrameHeader failed\n");
+ return false;
+ }
+ frame.name.resize(frame.frame_info.name_length + 1, 0);
+ if (JXL_DEC_SUCCESS !=
+ JxlDecoderGetFrameName(dec, &frame.name[0], frame.name.size())) {
+ fprintf(stderr, "JxlDecoderGetFrameName failed\n");
+ return false;
+ }
+ frame.name.resize(frame.frame_info.name_length);
+ ppf->frames.emplace_back(std::move(frame));
+ progression_index = 0;
+ } else if (status == JXL_DEC_FRAME_PROGRESSION) {
+ size_t downsampling = JxlDecoderGetIntendedDownsamplingRatio(dec);
+ if ((max_passes_defined && progression_index >= dparams.max_passes) ||
+ (!max_passes_defined && downsampling <= dparams.max_downsampling)) {
+ if (JXL_DEC_SUCCESS != JxlDecoderFlushImage(dec)) {
+ fprintf(stderr, "JxlDecoderFlushImage failed\n");
+ return false;
+ }
+ if (ppf->frames.back().frame_info.is_last) {
+ break;
+ }
+ if (JXL_DEC_SUCCESS != JxlDecoderSkipCurrentFrame(dec)) {
+ fprintf(stderr, "JxlDecoderSkipCurrentFrame failed\n");
+ return false;
+ }
+ }
+ ++progression_index;
+ } else if (status == JXL_DEC_NEED_PREVIEW_OUT_BUFFER) {
+ size_t buffer_size;
+ if (JXL_DEC_SUCCESS !=
+ JxlDecoderPreviewOutBufferSize(dec, &format, &buffer_size)) {
+ fprintf(stderr, "JxlDecoderPreviewOutBufferSize failed\n");
+ return false;
+ }
+ ppf->preview_frame = std::unique_ptr<jxl::extras::PackedFrame>(
+ new jxl::extras::PackedFrame(ppf->info.preview.xsize,
+ ppf->info.preview.ysize, format));
+ if (buffer_size != ppf->preview_frame->color.pixels_size) {
+ fprintf(stderr, "Invalid out buffer size %" PRIuS " %" PRIuS "\n",
+ buffer_size, ppf->preview_frame->color.pixels_size);
+ return false;
+ }
+ if (JXL_DEC_SUCCESS !=
+ JxlDecoderSetPreviewOutBuffer(
+ dec, &format, ppf->preview_frame->color.pixels(), buffer_size)) {
+ fprintf(stderr, "JxlDecoderSetPreviewOutBuffer failed\n");
+ return false;
+ }
+ } else if (status == JXL_DEC_NEED_IMAGE_OUT_BUFFER) {
+ if (jpeg_bytes != nullptr) {
+ break;
+ }
+ size_t buffer_size;
+ if (JXL_DEC_SUCCESS !=
+ JxlDecoderImageOutBufferSize(dec, &format, &buffer_size)) {
+ fprintf(stderr, "JxlDecoderImageOutBufferSize failed\n");
+ return false;
+ }
+ jxl::extras::PackedFrame& frame = ppf->frames.back();
+ if (buffer_size != frame.color.pixels_size) {
+ fprintf(stderr, "Invalid out buffer size %" PRIuS " %" PRIuS "\n",
+ buffer_size, frame.color.pixels_size);
+ return false;
+ }
+
+ if (dparams.use_image_callback) {
+ auto callback = [](void* opaque, size_t x, size_t y, size_t num_pixels,
+ const void* pixels) {
+ auto* ppf = reinterpret_cast<jxl::extras::PackedPixelFile*>(opaque);
+ jxl::extras::PackedImage& color = ppf->frames.back().color;
+ uint8_t* pixels_buffer = reinterpret_cast<uint8_t*>(color.pixels());
+ size_t sample_size = color.pixel_stride();
+ memcpy(pixels_buffer + (color.stride * y + sample_size * x), pixels,
+ num_pixels * sample_size);
+ };
+ if (JXL_DEC_SUCCESS !=
+ JxlDecoderSetImageOutCallback(dec, &format, callback, ppf)) {
+ fprintf(stderr, "JxlDecoderSetImageOutCallback failed\n");
+ return false;
+ }
+ } else {
+ if (JXL_DEC_SUCCESS != JxlDecoderSetImageOutBuffer(dec, &format,
+ frame.color.pixels(),
+ buffer_size)) {
+ fprintf(stderr, "JxlDecoderSetImageOutBuffer failed\n");
+ return false;
+ }
+ }
+ JxlPixelFormat ec_format = format;
+ ec_format.num_channels = 1;
+ for (const auto& eci : ppf->extra_channels_info) {
+ frame.extra_channels.emplace_back(jxl::extras::PackedImage(
+ ppf->info.xsize, ppf->info.ysize, ec_format));
+ auto& ec = frame.extra_channels.back();
+ size_t buffer_size;
+ if (JXL_DEC_SUCCESS != JxlDecoderExtraChannelBufferSize(
+ dec, &ec_format, &buffer_size, eci.index)) {
+ fprintf(stderr, "JxlDecoderExtraChannelBufferSize failed\n");
+ return false;
+ }
+ if (buffer_size != ec.pixels_size) {
+ fprintf(stderr,
+ "Invalid extra channel buffer size"
+ " %" PRIuS " %" PRIuS "\n",
+ buffer_size, ec.pixels_size);
+ return false;
+ }
+ if (JXL_DEC_SUCCESS !=
+ JxlDecoderSetExtraChannelBuffer(dec, &ec_format, ec.pixels(),
+ buffer_size, eci.index)) {
+ fprintf(stderr, "JxlDecoderSetExtraChannelBuffer failed\n");
+ return false;
+ }
+ }
+ } else if (status == JXL_DEC_SUCCESS) {
+ // Decoding finished successfully.
+ break;
+ } else if (status == JXL_DEC_PREVIEW_IMAGE) {
+ // Nothing to do.
+ } else if (status == JXL_DEC_FULL_IMAGE) {
+ if (jpeg_bytes != nullptr || ppf->frames.back().frame_info.is_last) {
+ codestream_done = true;
+ }
+ } else {
+ fprintf(stderr, "Error: unexpected status: %d\n",
+ static_cast<int>(status));
+ return false;
+ }
+ }
+ boxes.FinalizeOutput();
+ if (jpeg_bytes != nullptr) {
+ if (!can_reconstruct_jpeg) return false;
+ size_t used_jpeg_output =
+ jpeg_data_chunk.size() - JxlDecoderReleaseJPEGBuffer(dec);
+ jpeg_bytes->insert(jpeg_bytes->end(), jpeg_data_chunk.data(),
+ jpeg_data_chunk.data() + used_jpeg_output);
+ }
+ if (decoded_bytes) {
+ *decoded_bytes = bytes_size - JxlDecoderReleaseInput(dec);
+ }
+ return true;
+}
+
+} // namespace extras
+} // namespace jxl
diff --git a/media/libjxl/src/lib/extras/dec/jxl.h b/media/libjxl/src/lib/extras/dec/jxl.h
new file mode 100644
index 0000000000..c462fa4b74
--- /dev/null
+++ b/media/libjxl/src/lib/extras/dec/jxl.h
@@ -0,0 +1,66 @@
+// Copyright (c) the JPEG XL Project Authors. All rights reserved.
+//
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+#ifndef LIB_EXTRAS_DEC_JXL_H_
+#define LIB_EXTRAS_DEC_JXL_H_
+
+// Decodes JPEG XL images in memory.
+
+#include <stdint.h>
+
+#include <limits>
+#include <string>
+#include <vector>
+
+#include "jxl/parallel_runner.h"
+#include "jxl/types.h"
+#include "lib/extras/packed_image.h"
+
+namespace jxl {
+namespace extras {
+
+struct JXLDecompressParams {
+ // If empty, little endian float formats will be accepted.
+ std::vector<JxlPixelFormat> accepted_formats;
+
+ // Requested output color space description.
+ std::string color_space;
+ // If set, performs tone mapping to this intensity target luminance.
+ float display_nits = 0.0;
+ // Whether spot colors are rendered on the image.
+ bool render_spotcolors = true;
+ // Whether to keep or undo the orientation given in the header.
+ bool keep_orientation = false;
+
+ // If runner_opaque is set, the decoder uses this parallel runner.
+ JxlParallelRunner runner;
+ void* runner_opaque = nullptr;
+
+ // Whether truncated input should be treated as an error.
+ bool allow_partial_input = false;
+
+ // How many passes to decode at most. By default, decode everything.
+ uint32_t max_passes = std::numeric_limits<uint32_t>::max();
+
+ // Alternatively, one can specify the maximum tolerable downscaling factor
+ // with respect to the full size of the image. By default, nothing less than
+ // the full size is requested.
+ size_t max_downsampling = 1;
+
+ // Whether to use the image callback or the image buffer to get the output.
+ bool use_image_callback = true;
+ // Whether to unpremultiply colors for associated alpha channels.
+ bool unpremultiply_alpha = false;
+};
+
+bool DecodeImageJXL(const uint8_t* bytes, size_t bytes_size,
+ const JXLDecompressParams& dparams, size_t* decoded_bytes,
+ PackedPixelFile* ppf,
+ std::vector<uint8_t>* jpeg_bytes = nullptr);
+
+} // namespace extras
+} // namespace jxl
+
+#endif // LIB_EXTRAS_DEC_JXL_H_
diff --git a/media/libjxl/src/lib/extras/dec/pnm.cc b/media/libjxl/src/lib/extras/dec/pnm.cc
index 93a42be321..03aecef29c 100644
--- a/media/libjxl/src/lib/extras/dec/pnm.cc
+++ b/media/libjxl/src/lib/extras/dec/pnm.cc
@@ -366,13 +366,18 @@ Status DecodeImagePNM(const Span<const uint8_t> bytes,
ppf->frames.emplace_back(header.xsize, header.ysize, format);
auto* frame = &ppf->frames.back();
- frame->color.bitdepth_from_format = false;
- frame->color.flipped_y = header.bits_per_sample == 32; // PFMs are flipped
size_t pnm_remaining_size = bytes.data() + bytes.size() - pos;
if (pnm_remaining_size < frame->color.pixels_size) {
return JXL_FAILURE("PNM file too small");
}
- memcpy(frame->color.pixels(), pos, frame->color.pixels_size);
+ const bool flipped_y = header.bits_per_sample == 32; // PFMs are flipped
+ uint8_t* out = reinterpret_cast<uint8_t*>(frame->color.pixels());
+ for (size_t y = 0; y < header.ysize; ++y) {
+ size_t y_in = flipped_y ? header.ysize - 1 - y : y;
+ const uint8_t* row_in = &pos[y_in * frame->color.stride];
+ uint8_t* row_out = &out[y * frame->color.stride];
+ memcpy(row_out, row_in, frame->color.stride);
+ }
return true;
}
diff --git a/media/libjxl/src/lib/extras/enc/apng.cc b/media/libjxl/src/lib/extras/enc/apng.cc
index 372b3b32ea..db6cf9ef4a 100644
--- a/media/libjxl/src/lib/extras/enc/apng.cc
+++ b/media/libjxl/src/lib/extras/enc/apng.cc
@@ -42,18 +42,9 @@
#include <string>
#include <vector>
-#include "jxl/encode.h"
-#include "lib/jxl/base/compiler_specific.h"
+#include "lib/extras/exif.h"
+#include "lib/jxl/base/byte_order.h"
#include "lib/jxl/base/printf_macros.h"
-#include "lib/jxl/color_encoding_internal.h"
-#include "lib/jxl/dec_external_image.h"
-#include "lib/jxl/enc_color_management.h"
-#include "lib/jxl/enc_image_bundle.h"
-#include "lib/jxl/exif.h"
-#include "lib/jxl/frame_header.h"
-#include "lib/jxl/headers.h"
-#include "lib/jxl/image.h"
-#include "lib/jxl/image_bundle.h"
#include "png.h" /* original (unpatched) libpng is ok */
namespace jxl {
@@ -61,15 +52,44 @@ namespace extras {
namespace {
+class APNGEncoder : public Encoder {
+ public:
+ std::vector<JxlPixelFormat> AcceptedFormats() const override {
+ std::vector<JxlPixelFormat> formats;
+ for (const uint32_t num_channels : {1, 2, 3, 4}) {
+ for (const JxlDataType data_type : {JXL_TYPE_UINT8, JXL_TYPE_UINT16}) {
+ formats.push_back(JxlPixelFormat{num_channels, data_type,
+ JXL_BIG_ENDIAN, /*align=*/0});
+ }
+ }
+ return formats;
+ }
+ Status Encode(const PackedPixelFile& ppf, EncodedImage* encoded_image,
+ ThreadPool* pool) const override {
+ JXL_RETURN_IF_ERROR(VerifyBasicInfo(ppf.info));
+ encoded_image->icc.clear();
+ encoded_image->bitstreams.resize(1);
+ return EncodePackedPixelFileToAPNG(ppf, pool,
+ &encoded_image->bitstreams.front());
+ }
+
+ private:
+ Status EncodePackedPixelFileToAPNG(const PackedPixelFile& ppf,
+ ThreadPool* pool,
+ std::vector<uint8_t>* bytes) const;
+};
+
static void PngWrite(png_structp png_ptr, png_bytep data, png_size_t length) {
- PaddedBytes* bytes = static_cast<PaddedBytes*>(png_get_io_ptr(png_ptr));
- bytes->append(data, data + length);
+ std::vector<uint8_t>* bytes =
+ static_cast<std::vector<uint8_t>*>(png_get_io_ptr(png_ptr));
+ bytes->insert(bytes->end(), data, data + length);
}
// Stores XMP and EXIF/IPTC into key/value strings for PNG
class BlobsWriterPNG {
public:
- static Status Encode(const Blobs& blobs, std::vector<std::string>* strings) {
+ static Status Encode(const PackedMetadata& blobs,
+ std::vector<std::string>* strings) {
if (!blobs.exif.empty()) {
// PNG viewers typically ignore Exif orientation but not all of them do
// (and e.g. cjxl doesn't), so we overwrite the Exif orientation to the
@@ -122,26 +142,112 @@ class BlobsWriterPNG {
}
};
-} // namespace
+void MaybeAddCICP(JxlColorEncoding c_enc, png_structp png_ptr,
+ png_infop info_ptr) {
+ png_byte cicp_data[4] = {};
+ png_unknown_chunk cicp_chunk;
+ if (c_enc.color_space != JXL_COLOR_SPACE_RGB) {
+ return;
+ }
+ if (c_enc.primaries == JXL_PRIMARIES_P3) {
+ if (c_enc.white_point == JXL_WHITE_POINT_D65) {
+ cicp_data[0] = 12;
+ } else if (c_enc.white_point == JXL_WHITE_POINT_DCI) {
+ cicp_data[0] = 11;
+ } else {
+ return;
+ }
+ } else if (c_enc.primaries != JXL_PRIMARIES_CUSTOM &&
+ c_enc.white_point == JXL_WHITE_POINT_D65) {
+ cicp_data[0] = static_cast<png_byte>(c_enc.primaries);
+ } else {
+ return;
+ }
+ if (c_enc.transfer_function == JXL_TRANSFER_FUNCTION_UNKNOWN ||
+ c_enc.transfer_function == JXL_TRANSFER_FUNCTION_GAMMA) {
+ return;
+ }
+ cicp_data[1] = static_cast<png_byte>(c_enc.transfer_function);
+ cicp_data[2] = 0;
+ cicp_data[3] = 1;
+ cicp_chunk.data = cicp_data;
+ cicp_chunk.size = sizeof(cicp_data);
+ cicp_chunk.location = PNG_HAVE_PLTE;
+ memcpy(cicp_chunk.name, "cICP", 5);
+ png_set_keep_unknown_chunks(png_ptr, 3,
+ reinterpret_cast<const png_byte*>("cICP"), 1);
+ png_set_unknown_chunks(png_ptr, info_ptr, &cicp_chunk, 1);
+}
-Status EncodeImageAPNG(const CodecInOut* io, const ColorEncoding& c_desired,
- size_t bits_per_sample, ThreadPool* pool,
- PaddedBytes* bytes) {
- if (bits_per_sample > 8) {
- bits_per_sample = 16;
- } else if (bits_per_sample < 8) {
- // PNG can also do 4, 2, and 1 bits per sample, but it isn't implemented
- bits_per_sample = 8;
+Status APNGEncoder::EncodePackedPixelFileToAPNG(
+ const PackedPixelFile& ppf, ThreadPool* pool,
+ std::vector<uint8_t>* bytes) const {
+ size_t xsize = ppf.info.xsize;
+ size_t ysize = ppf.info.ysize;
+ bool has_alpha = ppf.info.alpha_bits != 0;
+ bool is_gray = ppf.info.num_color_channels == 1;
+ size_t color_channels = ppf.info.num_color_channels;
+ size_t num_channels = color_channels + (has_alpha ? 1 : 0);
+ size_t num_samples = num_channels * xsize * ysize;
+
+ if (!ppf.info.have_animation && ppf.frames.size() != 1) {
+ return JXL_FAILURE("Invalid number of frames");
}
size_t count = 0;
- bool have_anim = io->metadata.m.have_animation;
size_t anim_chunks = 0;
- int W = 0, H = 0;
- for (size_t i = 0; i < io->frames.size(); i++) {
- auto& frame = io->frames[i];
- if (!have_anim && i + 1 < io->frames.size()) continue;
+ for (const auto& frame : ppf.frames) {
+ JXL_RETURN_IF_ERROR(VerifyPackedImage(frame.color, ppf.info));
+
+ const PackedImage& color = frame.color;
+ const JxlPixelFormat format = color.format;
+ const uint8_t* in = reinterpret_cast<const uint8_t*>(color.pixels());
+ size_t data_bits_per_sample = PackedImage::BitsPerChannel(format.data_type);
+ size_t bytes_per_sample = data_bits_per_sample / 8;
+ size_t out_bytes_per_sample = bytes_per_sample > 1 ? 2 : 1;
+ size_t out_stride = xsize * num_channels * out_bytes_per_sample;
+ size_t out_size = ysize * out_stride;
+ std::vector<uint8_t> out(out_size);
+
+ if (format.data_type == JXL_TYPE_UINT8) {
+ if (ppf.info.bits_per_sample < 8) {
+ float mul = 255.0 / ((1u << ppf.info.bits_per_sample) - 1);
+ for (size_t i = 0; i < num_samples; ++i) {
+ out[i] = static_cast<uint8_t>(in[i] * mul + 0.5);
+ }
+ } else {
+ memcpy(&out[0], in, out_size);
+ }
+ } else if (format.data_type == JXL_TYPE_UINT16) {
+ if (ppf.info.bits_per_sample < 16 ||
+ format.endianness != JXL_BIG_ENDIAN) {
+ float mul = 65535.0 / ((1u << ppf.info.bits_per_sample) - 1);
+ const uint8_t* p_in = in;
+ uint8_t* p_out = out.data();
+ for (size_t i = 0; i < num_samples; ++i, p_in += 2, p_out += 2) {
+ uint32_t val = (format.endianness == JXL_BIG_ENDIAN ? LoadBE16(p_in)
+ : LoadLE16(p_in));
+ StoreBE16(static_cast<uint32_t>(val * mul + 0.5), p_out);
+ }
+ } else {
+ memcpy(&out[0], in, out_size);
+ }
+ } else if (format.data_type == JXL_TYPE_FLOAT) {
+ float mul = 65535.0;
+ const uint8_t* p_in = in;
+ uint8_t* p_out = out.data();
+ for (size_t i = 0; i < num_samples; ++i, p_in += 4, p_out += 2) {
+ uint32_t val = (format.endianness == JXL_BIG_ENDIAN ? LoadBE32(p_in)
+ : LoadLE32(p_in));
+ float fval;
+ memcpy(&fval, &val, 4);
+ StoreBE16(static_cast<uint32_t>(fval * mul + 0.5), p_out);
+ }
+ } else {
+ return JXL_FAILURE("Unsupported pixel data type");
+ }
+
png_structp png_ptr;
png_infop info_ptr;
@@ -155,46 +261,24 @@ Status EncodeImageAPNG(const CodecInOut* io, const ColorEncoding& c_desired,
png_set_write_fn(png_ptr, bytes, PngWrite, NULL);
png_set_flush(png_ptr, 0);
- ImageBundle ib = frame.Copy();
- const size_t alpha_bits = ib.HasAlpha() ? bits_per_sample : 0;
- ImageMetadata metadata = io->metadata.m;
- ImageBundle store(&metadata);
- const ImageBundle* transformed;
- JXL_RETURN_IF_ERROR(TransformIfNeeded(ib, c_desired, GetJxlCms(), pool,
- &store, &transformed));
- size_t stride = ib.oriented_xsize() *
- DivCeil(c_desired.Channels() * bits_per_sample + alpha_bits,
- kBitsPerByte);
- std::vector<uint8_t> raw_bytes(stride * ib.oriented_ysize());
- JXL_RETURN_IF_ERROR(ConvertToExternal(
- *transformed, bits_per_sample, /*float_out=*/false,
- c_desired.Channels() + (ib.HasAlpha() ? 1 : 0), JXL_BIG_ENDIAN, stride,
- pool, raw_bytes.data(), raw_bytes.size(),
- /*out_callback=*/{}, metadata.GetOrientation()));
-
- int width = ib.oriented_xsize();
- int height = ib.oriented_ysize();
-
- png_byte color_type =
- (c_desired.Channels() == 3 ? PNG_COLOR_TYPE_RGB : PNG_COLOR_TYPE_GRAY);
- if (ib.HasAlpha()) color_type |= PNG_COLOR_MASK_ALPHA;
- png_byte bit_depth = bits_per_sample;
+ int width = xsize;
+ int height = ysize;
+
+ png_byte color_type = (is_gray ? PNG_COLOR_TYPE_GRAY : PNG_COLOR_TYPE_RGB);
+ if (has_alpha) color_type |= PNG_COLOR_MASK_ALPHA;
+ png_byte bit_depth = out_bytes_per_sample * 8;
png_set_IHDR(png_ptr, info_ptr, width, height, bit_depth, color_type,
PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE,
PNG_FILTER_TYPE_BASE);
if (count == 0) {
- W = width;
- H = height;
-
- // TODO(jon): instead of always setting an iCCP, could try to avoid that
- // have to avoid warnings on the ICC profile becoming fatal
- png_set_benign_errors(png_ptr, 1);
- png_set_iCCP(png_ptr, info_ptr, "1", 0, c_desired.ICC().data(),
- c_desired.ICC().size());
-
+ MaybeAddCICP(ppf.color_encoding, png_ptr, info_ptr);
+ if (!ppf.icc.empty()) {
+ png_set_benign_errors(png_ptr, 1);
+ png_set_iCCP(png_ptr, info_ptr, "1", 0, ppf.icc.data(), ppf.icc.size());
+ }
std::vector<std::string> textstrings;
- JXL_RETURN_IF_ERROR(BlobsWriterPNG::Encode(io->blobs, &textstrings));
+ JXL_RETURN_IF_ERROR(BlobsWriterPNG::Encode(ppf.metadata, &textstrings));
for (size_t kk = 0; kk + 1 < textstrings.size(); kk += 2) {
png_text text;
text.key = const_cast<png_charp>(textstrings[kk].c_str());
@@ -211,27 +295,24 @@ Status EncodeImageAPNG(const CodecInOut* io, const ColorEncoding& c_desired,
bytes->resize(pos);
}
- if (have_anim) {
+ if (ppf.info.have_animation) {
if (count == 0) {
png_byte adata[8];
- png_save_uint_32(adata, io->frames.size());
- png_save_uint_32(adata + 4, io->metadata.m.animation.num_loops);
+ png_save_uint_32(adata, ppf.frames.size());
+ png_save_uint_32(adata + 4, ppf.info.animation.num_loops);
png_byte actl[5] = "acTL";
png_write_chunk(png_ptr, actl, adata, 8);
}
png_byte fdata[26];
- JXL_ASSERT(W == width);
- JXL_ASSERT(H == height);
// TODO(jon): also make this work for the non-coalesced case
png_save_uint_32(fdata, anim_chunks++);
png_save_uint_32(fdata + 4, width);
png_save_uint_32(fdata + 8, height);
png_save_uint_32(fdata + 12, 0);
png_save_uint_32(fdata + 16, 0);
- png_save_uint_16(
- fdata + 20,
- frame.duration * io->metadata.m.animation.tps_denominator);
- png_save_uint_16(fdata + 22, io->metadata.m.animation.tps_numerator);
+ png_save_uint_16(fdata + 20, frame.frame_info.duration *
+ ppf.info.animation.tps_denominator);
+ png_save_uint_16(fdata + 22, ppf.info.animation.tps_numerator);
fdata[24] = 1;
fdata[25] = 0;
png_byte fctl[5] = "fcTL";
@@ -240,7 +321,7 @@ Status EncodeImageAPNG(const CodecInOut* io, const ColorEncoding& c_desired,
std::vector<uint8_t*> rows(height);
for (int y = 0; y < height; ++y) {
- rows[y] = raw_bytes.data() + y * stride;
+ rows[y] = out.data() + y * out_stride;
}
png_write_flush(png_ptr);
@@ -268,7 +349,9 @@ Status EncodeImageAPNG(const CodecInOut* io, const ColorEncoding& c_desired,
}
count++;
- if (count == io->frames.size() || !have_anim) png_write_end(png_ptr, NULL);
+ if (count == ppf.frames.size() || !ppf.info.have_animation) {
+ png_write_end(png_ptr, NULL);
+ }
png_destroy_write_struct(&png_ptr, &info_ptr);
}
@@ -276,5 +359,11 @@ Status EncodeImageAPNG(const CodecInOut* io, const ColorEncoding& c_desired,
return true;
}
+} // namespace
+
+std::unique_ptr<Encoder> GetAPNGEncoder() {
+ return jxl::make_unique<APNGEncoder>();
+}
+
} // namespace extras
} // namespace jxl
diff --git a/media/libjxl/src/lib/extras/enc/apng.h b/media/libjxl/src/lib/extras/enc/apng.h
index 7f0fb94d9b..2a2139c8fa 100644
--- a/media/libjxl/src/lib/extras/enc/apng.h
+++ b/media/libjxl/src/lib/extras/enc/apng.h
@@ -8,19 +8,14 @@
// Encodes APNG images in memory.
-#include "lib/jxl/base/data_parallel.h"
-#include "lib/jxl/base/padded_bytes.h"
-#include "lib/jxl/base/span.h"
-#include "lib/jxl/base/status.h"
-#include "lib/jxl/codec_in_out.h"
+#include <memory>
+
+#include "lib/extras/enc/encode.h"
namespace jxl {
namespace extras {
-// Encodes `io` into `bytes`.
-Status EncodeImageAPNG(const CodecInOut* io, const ColorEncoding& c_desired,
- size_t bits_per_sample, ThreadPool* pool,
- PaddedBytes* bytes);
+std::unique_ptr<Encoder> GetAPNGEncoder();
} // namespace extras
} // namespace jxl
diff --git a/media/libjxl/src/lib/extras/enc/encode.cc b/media/libjxl/src/lib/extras/enc/encode.cc
new file mode 100644
index 0000000000..dc593d2900
--- /dev/null
+++ b/media/libjxl/src/lib/extras/enc/encode.cc
@@ -0,0 +1,136 @@
+// Copyright (c) the JPEG XL Project Authors. All rights reserved.
+//
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+#include "lib/extras/enc/encode.h"
+
+#include <locale>
+
+#if JPEGXL_ENABLE_APNG
+#include "lib/extras/enc/apng.h"
+#endif
+#if JPEGXL_ENABLE_EXR
+#include "lib/extras/enc/exr.h"
+#endif
+#if JPEGXL_ENABLE_JPEG
+#include "lib/extras/enc/jpg.h"
+#endif
+#include "lib/extras/enc/npy.h"
+#include "lib/extras/enc/pgx.h"
+#include "lib/extras/enc/pnm.h"
+#include "lib/jxl/base/printf_macros.h"
+
+namespace jxl {
+namespace extras {
+
+Status Encoder::VerifyBasicInfo(const JxlBasicInfo& info) const {
+ if (info.xsize == 0 || info.ysize == 0) {
+ return JXL_FAILURE("Empty image");
+ }
+ if (info.num_color_channels != 1 && info.num_color_channels != 3) {
+ return JXL_FAILURE("Invalid number of color channels");
+ }
+ if (info.alpha_bits > 0 && info.alpha_bits != info.bits_per_sample) {
+ return JXL_FAILURE("Alpha bit depth does not match image bit depth");
+ }
+ if (info.orientation != JXL_ORIENT_IDENTITY) {
+ return JXL_FAILURE("Orientation must be identity");
+ }
+ return true;
+}
+
+Status Encoder::VerifyPackedImage(const PackedImage& image,
+ const JxlBasicInfo& info) const {
+ if (image.pixels() == nullptr) {
+ return JXL_FAILURE("Invalid image.");
+ }
+ if (image.stride != image.xsize * image.pixel_stride()) {
+ return JXL_FAILURE("Invalid image stride.");
+ }
+ if (image.pixels_size != image.ysize * image.stride) {
+ return JXL_FAILURE("Invalid image size.");
+ }
+ size_t info_num_channels =
+ (info.num_color_channels + (info.alpha_bits > 0 ? 1 : 0));
+ if (image.xsize != info.xsize || image.ysize != info.ysize ||
+ image.format.num_channels != info_num_channels) {
+ return JXL_FAILURE("Frame size does not match image size");
+ }
+ if (info.bits_per_sample >
+ PackedImage::BitsPerChannel(image.format.data_type)) {
+ return JXL_FAILURE("Bit depth does not fit pixel data type");
+ }
+ return true;
+}
+
+Status SelectFormat(const std::vector<JxlPixelFormat>& accepted_formats,
+ const JxlBasicInfo& basic_info, JxlPixelFormat* format) {
+ const size_t original_bit_depth = basic_info.bits_per_sample;
+ size_t current_bit_depth = 0;
+ size_t num_alpha_channels = (basic_info.alpha_bits != 0 ? 1 : 0);
+ size_t num_channels = basic_info.num_color_channels + num_alpha_channels;
+ for (;;) {
+ for (const JxlPixelFormat& candidate : accepted_formats) {
+ if (candidate.num_channels != num_channels) continue;
+ const size_t candidate_bit_depth =
+ PackedImage::BitsPerChannel(candidate.data_type);
+ if (
+ // Candidate bit depth is less than what we have and still enough
+ (original_bit_depth <= candidate_bit_depth &&
+ candidate_bit_depth < current_bit_depth) ||
+ // Or larger than the too-small bit depth we currently have
+ (current_bit_depth < candidate_bit_depth &&
+ current_bit_depth < original_bit_depth)) {
+ *format = candidate;
+ current_bit_depth = candidate_bit_depth;
+ }
+ }
+ if (current_bit_depth == 0) {
+ if (num_channels > basic_info.num_color_channels) {
+ // Try dropping the alpha channel.
+ --num_channels;
+ continue;
+ }
+ return JXL_FAILURE("no appropriate format found");
+ }
+ break;
+ }
+ if (current_bit_depth < original_bit_depth) {
+ JXL_WARNING("encoding %" PRIuS "-bit original to %" PRIuS " bits",
+ original_bit_depth, current_bit_depth);
+ }
+ return true;
+}
+
+std::unique_ptr<Encoder> Encoder::FromExtension(std::string extension) {
+ std::transform(
+ extension.begin(), extension.end(), extension.begin(),
+ [](char c) { return std::tolower(c, std::locale::classic()); });
+#if JPEGXL_ENABLE_APNG
+ if (extension == ".png" || extension == ".apng") return GetAPNGEncoder();
+#endif
+
+#if JPEGXL_ENABLE_JPEG
+ if (extension == ".jpg") return GetJPEGEncoder();
+ if (extension == ".jpeg") return GetJPEGEncoder();
+#endif
+
+ if (extension == ".npy") return GetNumPyEncoder();
+
+ if (extension == ".pgx") return GetPGXEncoder();
+
+ if (extension == ".pam") return GetPAMEncoder();
+ if (extension == ".pgm") return GetPGMEncoder();
+ if (extension == ".ppm") return GetPPMEncoder();
+ if (extension == ".pfm") return GetPFMEncoder();
+
+#if JPEGXL_ENABLE_EXR
+ if (extension == ".exr") return GetEXREncoder();
+#endif
+
+ return nullptr;
+}
+
+} // namespace extras
+} // namespace jxl
diff --git a/media/libjxl/src/lib/extras/enc/encode.h b/media/libjxl/src/lib/extras/enc/encode.h
new file mode 100644
index 0000000000..92eec50b6f
--- /dev/null
+++ b/media/libjxl/src/lib/extras/enc/encode.h
@@ -0,0 +1,77 @@
+// Copyright (c) the JPEG XL Project Authors. All rights reserved.
+//
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+#ifndef LIB_EXTRAS_ENC_ENCODE_H_
+#define LIB_EXTRAS_ENC_ENCODE_H_
+
+// Facade for image encoders.
+
+#include <string>
+#include <unordered_map>
+
+#include "lib/extras/dec/decode.h"
+#include "lib/jxl/base/data_parallel.h"
+#include "lib/jxl/base/status.h"
+
+namespace jxl {
+namespace extras {
+
+struct EncodedImage {
+ // One (if the format supports animations or the image has only one frame) or
+ // more sequential bitstreams.
+ std::vector<std::vector<uint8_t>> bitstreams;
+
+ // For each extra channel one or more sequential bitstreams.
+ std::vector<std::vector<std::vector<uint8_t>>> extra_channel_bitstreams;
+
+ std::vector<uint8_t> preview_bitstream;
+
+ // If the format does not support embedding color profiles into the bitstreams
+ // above, it will be present here, to be written as a separate file. If it
+ // does support them, this field will be empty.
+ std::vector<uint8_t> icc;
+
+ // Additional output for conformance testing, only filled in by NumPyEncoder.
+ std::vector<uint8_t> metadata;
+};
+
+class Encoder {
+ public:
+ static std::unique_ptr<Encoder> FromExtension(std::string extension);
+
+ virtual ~Encoder() = default;
+
+ virtual std::vector<JxlPixelFormat> AcceptedFormats() const = 0;
+
+ // Any existing data in encoded_image is discarded.
+ virtual Status Encode(const PackedPixelFile& ppf, EncodedImage* encoded_image,
+ ThreadPool* pool = nullptr) const = 0;
+
+ void SetOption(std::string name, std::string value) {
+ options_[std::move(name)] = std::move(value);
+ }
+
+ protected:
+ const std::unordered_map<std::string, std::string>& options() const {
+ return options_;
+ }
+
+ Status VerifyBasicInfo(const JxlBasicInfo& info) const;
+
+ Status VerifyPackedImage(const PackedImage& image,
+ const JxlBasicInfo& info) const;
+
+ private:
+ std::unordered_map<std::string, std::string> options_;
+};
+
+// TODO(sboukortt): consider exposing this as part of the C API.
+Status SelectFormat(const std::vector<JxlPixelFormat>& accepted_formats,
+ const JxlBasicInfo& basic_info, JxlPixelFormat* format);
+
+} // namespace extras
+} // namespace jxl
+
+#endif // LIB_EXTRAS_ENC_ENCODE_H_
diff --git a/media/libjxl/src/lib/extras/enc/exr.cc b/media/libjxl/src/lib/extras/enc/exr.cc
index 3d88404888..05e05f96ce 100644
--- a/media/libjxl/src/lib/extras/enc/exr.cc
+++ b/media/libjxl/src/lib/extras/enc/exr.cc
@@ -12,11 +12,9 @@
#include <vector>
-#include "lib/jxl/alpha.h"
-#include "lib/jxl/color_encoding_internal.h"
-#include "lib/jxl/color_management.h"
-#include "lib/jxl/enc_color_management.h"
-#include "lib/jxl/enc_image_bundle.h"
+#include "jxl/codestream_header.h"
+#include "lib/extras/packed_image.h"
+#include "lib/jxl/base/byte_order.h"
namespace jxl {
namespace extras {
@@ -32,22 +30,10 @@ namespace Imath = IMATH_NAMESPACE;
// to uint64_t. This alternative should work in all cases.
using ExrInt64 = decltype(std::declval<OpenEXR::IStream>().tellg());
-size_t GetNumThreads(ThreadPool* pool) {
- size_t exr_num_threads = 1;
- JXL_CHECK(RunOnPool(
- pool, 0, 1,
- [&](size_t num_threads) {
- exr_num_threads = num_threads;
- return true;
- },
- [&](uint32_t /* task */, size_t /*thread*/) {}, "DecodeImageEXRThreads"));
- return exr_num_threads;
-}
-
class InMemoryOStream : public OpenEXR::OStream {
public:
// `bytes` must outlive the InMemoryOStream.
- explicit InMemoryOStream(PaddedBytes* const bytes)
+ explicit InMemoryOStream(std::vector<uint8_t>* const bytes)
: OStream(/*fileName=*/""), bytes_(*bytes) {}
void write(const char c[], const int n) override {
@@ -67,42 +53,69 @@ class InMemoryOStream : public OpenEXR::OStream {
}
private:
- PaddedBytes& bytes_;
+ std::vector<uint8_t>& bytes_;
size_t pos_ = 0;
};
-} // namespace
+// Loads a Big-Endian float
+float LoadBEFloat(const uint8_t* p) {
+ uint32_t u = LoadBE32(p);
+ float result;
+ memcpy(&result, &u, 4);
+ return result;
+}
+
+// Loads a Little-Endian float
+float LoadLEFloat(const uint8_t* p) {
+ uint32_t u = LoadLE32(p);
+ float result;
+ memcpy(&result, &u, 4);
+ return result;
+}
+
+Status EncodeImageEXR(const PackedImage& image, const JxlBasicInfo& info,
+ const JxlColorEncoding& c_enc, ThreadPool* pool,
+ std::vector<uint8_t>* bytes) {
+ OpenEXR::setGlobalThreadCount(0);
+
+ const size_t xsize = info.xsize;
+ const size_t ysize = info.ysize;
+ const bool has_alpha = info.alpha_bits > 0;
+ const bool alpha_is_premultiplied = info.alpha_premultiplied;
+
+ if (info.num_color_channels != 3 ||
+ c_enc.color_space != JXL_COLOR_SPACE_RGB ||
+ c_enc.transfer_function != JXL_TRANSFER_FUNCTION_LINEAR) {
+ return JXL_FAILURE("Unsupported color encoding for OpenEXR output.");
+ }
+
+ const size_t num_channels = 3 + (has_alpha ? 1 : 0);
+ const JxlPixelFormat format = image.format;
+
+ if (format.data_type != JXL_TYPE_FLOAT) {
+ return JXL_FAILURE("Unsupported pixel format for OpenEXR output");
+ }
-Status EncodeImageEXR(const CodecInOut* io, const ColorEncoding& c_desired,
- ThreadPool* pool, PaddedBytes* bytes) {
- // As in `DecodeImageEXR`, `pool` is only used for pixel conversion, not for
- // actual OpenEXR I/O.
- OpenEXR::setGlobalThreadCount(GetNumThreads(pool));
-
- ColorEncoding c_linear = c_desired;
- c_linear.tf.SetTransferFunction(TransferFunction::kLinear);
- JXL_RETURN_IF_ERROR(c_linear.CreateICC());
- ImageMetadata metadata = io->metadata.m;
- ImageBundle store(&metadata);
- const ImageBundle* linear;
- JXL_RETURN_IF_ERROR(TransformIfNeeded(io->Main(), c_linear, GetJxlCms(), pool,
- &store, &linear));
-
- const bool has_alpha = io->Main().HasAlpha();
- const bool alpha_is_premultiplied = io->Main().AlphaIsPremultiplied();
-
- OpenEXR::Header header(io->xsize(), io->ysize());
- const PrimariesCIExy& primaries = c_linear.HasPrimaries()
- ? c_linear.GetPrimaries()
- : ColorEncoding::SRGB().GetPrimaries();
+ const uint8_t* in = reinterpret_cast<const uint8_t*>(image.pixels());
+ size_t in_stride = num_channels * 4 * xsize;
+
+ OpenEXR::Header header(xsize, ysize);
OpenEXR::Chromaticities chromaticities;
- chromaticities.red = Imath::V2f(primaries.r.x, primaries.r.y);
- chromaticities.green = Imath::V2f(primaries.g.x, primaries.g.y);
- chromaticities.blue = Imath::V2f(primaries.b.x, primaries.b.y);
+ chromaticities.red =
+ Imath::V2f(c_enc.primaries_red_xy[0], c_enc.primaries_red_xy[1]);
+ chromaticities.green =
+ Imath::V2f(c_enc.primaries_green_xy[0], c_enc.primaries_green_xy[1]);
+ chromaticities.blue =
+ Imath::V2f(c_enc.primaries_blue_xy[0], c_enc.primaries_blue_xy[1]);
chromaticities.white =
- Imath::V2f(c_linear.GetWhitePoint().x, c_linear.GetWhitePoint().y);
+ Imath::V2f(c_enc.white_point_xy[0], c_enc.white_point_xy[1]);
OpenEXR::addChromaticities(header, chromaticities);
- OpenEXR::addWhiteLuminance(header, io->metadata.m.IntensityTarget());
+ OpenEXR::addWhiteLuminance(header, 255.0f);
+
+ auto loadFloat =
+ format.endianness == JXL_BIG_ENDIAN ? LoadBEFloat : LoadLEFloat;
+ auto loadAlpha =
+ has_alpha ? loadFloat : [](const uint8_t* p) -> float { return 1.0f; };
// Ensure that the destructor of RgbaOutputFile has run before we look at the
// size of `bytes`.
@@ -112,50 +125,32 @@ Status EncodeImageEXR(const CodecInOut* io, const ColorEncoding& c_desired,
os, header, has_alpha ? OpenEXR::WRITE_RGBA : OpenEXR::WRITE_RGB);
// How many rows to write at once. Again, the OpenEXR documentation
// recommends writing the whole image in one call.
- const int y_chunk_size = io->ysize();
- std::vector<OpenEXR::Rgba> output_rows(io->xsize() * y_chunk_size);
+ const int y_chunk_size = ysize;
+ std::vector<OpenEXR::Rgba> output_rows(xsize * y_chunk_size);
- for (size_t start_y = 0; start_y < io->ysize(); start_y += y_chunk_size) {
+ for (size_t start_y = 0; start_y < ysize; start_y += y_chunk_size) {
// Inclusive.
- const size_t end_y =
- std::min(start_y + y_chunk_size - 1, io->ysize() - 1);
- output.setFrameBuffer(output_rows.data() - start_y * io->xsize(),
- /*xStride=*/1, /*yStride=*/io->xsize());
- JXL_RETURN_IF_ERROR(RunOnPool(
- pool, start_y, end_y + 1, ThreadPool::NoInit,
- [&](const uint32_t y, size_t /* thread */) {
- const float* const JXL_RESTRICT input_rows[] = {
- linear->color().ConstPlaneRow(0, y),
- linear->color().ConstPlaneRow(1, y),
- linear->color().ConstPlaneRow(2, y),
- };
- OpenEXR::Rgba* const JXL_RESTRICT row_data =
- &output_rows[(y - start_y) * io->xsize()];
- if (has_alpha) {
- const float* const JXL_RESTRICT alpha_row =
- io->Main().alpha().ConstRow(y);
- if (alpha_is_premultiplied) {
- for (size_t x = 0; x < io->xsize(); ++x) {
- row_data[x] =
- OpenEXR::Rgba(input_rows[0][x], input_rows[1][x],
- input_rows[2][x], alpha_row[x]);
- }
- } else {
- for (size_t x = 0; x < io->xsize(); ++x) {
- row_data[x] = OpenEXR::Rgba(alpha_row[x] * input_rows[0][x],
- alpha_row[x] * input_rows[1][x],
- alpha_row[x] * input_rows[2][x],
- alpha_row[x]);
- }
- }
- } else {
- for (size_t x = 0; x < io->xsize(); ++x) {
- row_data[x] = OpenEXR::Rgba(input_rows[0][x], input_rows[1][x],
- input_rows[2][x], 1.f);
- }
- }
- },
- "EncodeImageEXR"));
+ const size_t end_y = std::min(start_y + y_chunk_size - 1, ysize - 1);
+ output.setFrameBuffer(output_rows.data() - start_y * xsize,
+ /*xStride=*/1, /*yStride=*/xsize);
+ for (size_t y = start_y; y <= end_y; ++y) {
+ const uint8_t* in_row = &in[(y - start_y) * in_stride];
+ OpenEXR::Rgba* const JXL_RESTRICT row_data =
+ &output_rows[(y - start_y) * xsize];
+ for (size_t x = 0; x < xsize; ++x) {
+ const uint8_t* in_pixel = &in_row[4 * num_channels * x];
+ float r = loadFloat(&in_pixel[0]);
+ float g = loadFloat(&in_pixel[4]);
+ float b = loadFloat(&in_pixel[8]);
+ const float alpha = loadAlpha(&in_pixel[12]);
+ if (!alpha_is_premultiplied) {
+ r *= alpha;
+ g *= alpha;
+ b *= alpha;
+ }
+ row_data[x] = OpenEXR::Rgba(r, g, b, alpha);
+ }
+ }
output.writePixels(/*numScanLines=*/end_y - start_y + 1);
}
}
@@ -163,5 +158,43 @@ Status EncodeImageEXR(const CodecInOut* io, const ColorEncoding& c_desired,
return true;
}
+class EXREncoder : public Encoder {
+ std::vector<JxlPixelFormat> AcceptedFormats() const override {
+ std::vector<JxlPixelFormat> formats;
+ for (const uint32_t num_channels : {1, 2, 3, 4}) {
+ for (const JxlDataType data_type : {JXL_TYPE_FLOAT, JXL_TYPE_FLOAT16}) {
+ for (JxlEndianness endianness : {JXL_BIG_ENDIAN, JXL_LITTLE_ENDIAN}) {
+ formats.push_back(JxlPixelFormat{/*num_channels=*/num_channels,
+ /*data_type=*/data_type,
+ /*endianness=*/endianness,
+ /*align=*/0});
+ }
+ }
+ }
+ return formats;
+ }
+ Status Encode(const PackedPixelFile& ppf, EncodedImage* encoded_image,
+ ThreadPool* pool = nullptr) const override {
+ JXL_RETURN_IF_ERROR(VerifyBasicInfo(ppf.info));
+ encoded_image->icc.clear();
+ encoded_image->bitstreams.clear();
+ encoded_image->bitstreams.reserve(ppf.frames.size());
+ for (const auto& frame : ppf.frames) {
+ JXL_RETURN_IF_ERROR(VerifyPackedImage(frame.color, ppf.info));
+ encoded_image->bitstreams.emplace_back();
+ JXL_RETURN_IF_ERROR(EncodeImageEXR(frame.color, ppf.info,
+ ppf.color_encoding, pool,
+ &encoded_image->bitstreams.back()));
+ }
+ return true;
+ }
+};
+
+} // namespace
+
+std::unique_ptr<Encoder> GetEXREncoder() {
+ return jxl::make_unique<EXREncoder>();
+}
+
} // namespace extras
} // namespace jxl
diff --git a/media/libjxl/src/lib/extras/enc/exr.h b/media/libjxl/src/lib/extras/enc/exr.h
index 0423bcbadb..1baaa0272f 100644
--- a/media/libjxl/src/lib/extras/enc/exr.h
+++ b/media/libjxl/src/lib/extras/enc/exr.h
@@ -8,21 +8,14 @@
// Encodes OpenEXR images in memory.
-#include "lib/extras/packed_image.h"
-#include "lib/jxl/base/data_parallel.h"
-#include "lib/jxl/base/padded_bytes.h"
-#include "lib/jxl/base/span.h"
-#include "lib/jxl/base/status.h"
-#include "lib/jxl/codec_in_out.h"
-#include "lib/jxl/color_encoding_internal.h"
+#include <memory>
+
+#include "lib/extras/enc/encode.h"
namespace jxl {
namespace extras {
-// Transforms from io->c_current to `c_desired` (with the transfer function set
-// to linear as that is the OpenEXR convention) and encodes into `bytes`.
-Status EncodeImageEXR(const CodecInOut* io, const ColorEncoding& c_desired,
- ThreadPool* pool, PaddedBytes* bytes);
+std::unique_ptr<Encoder> GetEXREncoder();
} // namespace extras
} // namespace jxl
diff --git a/media/libjxl/src/lib/extras/enc/jpg.cc b/media/libjxl/src/lib/extras/enc/jpg.cc
index 83b2895756..93a39dd2e4 100644
--- a/media/libjxl/src/lib/extras/enc/jpg.cc
+++ b/media/libjxl/src/lib/extras/enc/jpg.cc
@@ -12,19 +12,12 @@
#include <algorithm>
#include <iterator>
#include <numeric>
+#include <sstream>
#include <utility>
#include <vector>
-#include "lib/jxl/base/compiler_specific.h"
+#include "lib/extras/exif.h"
#include "lib/jxl/base/status.h"
-#include "lib/jxl/color_encoding_internal.h"
-#include "lib/jxl/common.h"
-#include "lib/jxl/dec_external_image.h"
-#include "lib/jxl/enc_color_management.h"
-#include "lib/jxl/enc_image_bundle.h"
-#include "lib/jxl/exif.h"
-#include "lib/jxl/image.h"
-#include "lib/jxl/image_bundle.h"
#include "lib/jxl/sanitizers.h"
#if JPEGXL_ENABLE_SJPEG
#include "sjpeg.h"
@@ -44,8 +37,21 @@ constexpr unsigned char kExifSignature[6] = {0x45, 0x78, 0x69,
0x66, 0x00, 0x00};
constexpr int kExifMarker = JPEG_APP0 + 1;
+enum class JpegEncoder {
+ kLibJpeg,
+ kSJpeg,
+};
+
+bool IsSRGBEncoding(const JxlColorEncoding& c) {
+ return ((c.color_space == JXL_COLOR_SPACE_RGB ||
+ c.color_space == JXL_COLOR_SPACE_GRAY) &&
+ c.primaries == JXL_PRIMARIES_SRGB &&
+ c.white_point == JXL_WHITE_POINT_D65 &&
+ c.transfer_function == JXL_TRANSFER_FUNCTION_SRGB);
+}
+
void WriteICCProfile(jpeg_compress_struct* const cinfo,
- const PaddedBytes& icc) {
+ const std::vector<uint8_t>& icc) {
constexpr size_t kMaxIccBytesInMarker =
kMaxBytesInMarker - sizeof kICCSignature - 2;
const int num_markers =
@@ -80,25 +86,34 @@ void WriteExif(jpeg_compress_struct* const cinfo,
}
}
-Status SetChromaSubsampling(const YCbCrChromaSubsampling& chroma_subsampling,
+Status SetChromaSubsampling(const std::string& subsampling,
jpeg_compress_struct* const cinfo) {
- for (size_t i = 0; i < 3; i++) {
- cinfo->comp_info[i].h_samp_factor =
- 1 << (chroma_subsampling.MaxHShift() -
- chroma_subsampling.HShift(i < 2 ? i ^ 1 : i));
- cinfo->comp_info[i].v_samp_factor =
- 1 << (chroma_subsampling.MaxVShift() -
- chroma_subsampling.VShift(i < 2 ? i ^ 1 : i));
+ const std::pair<const char*,
+ std::pair<std::array<uint8_t, 3>, std::array<uint8_t, 3>>>
+ options[] = {{"444", {{{1, 1, 1}}, {{1, 1, 1}}}},
+ {"420", {{{2, 1, 1}}, {{2, 1, 1}}}},
+ {"422", {{{2, 1, 1}}, {{1, 1, 1}}}},
+ {"440", {{{1, 1, 1}}, {{2, 1, 1}}}}};
+ for (const auto& option : options) {
+ if (subsampling == option.first) {
+ for (size_t i = 0; i < 3; i++) {
+ cinfo->comp_info[i].h_samp_factor = option.second.first[i];
+ cinfo->comp_info[i].v_samp_factor = option.second.second[i];
+ }
+ return true;
+ }
}
- return true;
+ return false;
}
-} // namespace
-
-Status EncodeWithLibJpeg(const ImageBundle* ib, const CodecInOut* io,
- size_t quality,
- const YCbCrChromaSubsampling& chroma_subsampling,
- PaddedBytes* bytes) {
+Status EncodeWithLibJpeg(const PackedImage& image, const JxlBasicInfo& info,
+ const std::vector<uint8_t>& icc,
+ std::vector<uint8_t> exif, size_t quality,
+ const std::string& chroma_subsampling,
+ std::vector<uint8_t>* bytes) {
+ if (BITS_IN_JSAMPLE != 8 || sizeof(JSAMPLE) != 1) {
+ return JXL_FAILURE("Only 8 bit JSAMPLE is supported.");
+ }
jpeg_compress_struct cinfo;
// cinfo is initialized by libjpeg, which we are not instrumenting with
// msan.
@@ -109,15 +124,10 @@ Status EncodeWithLibJpeg(const ImageBundle* ib, const CodecInOut* io,
unsigned char* buffer = nullptr;
unsigned long size = 0;
jpeg_mem_dest(&cinfo, &buffer, &size);
- cinfo.image_width = ib->oriented_xsize();
- cinfo.image_height = ib->oriented_ysize();
- if (ib->IsGray()) {
- cinfo.input_components = 1;
- cinfo.in_color_space = JCS_GRAYSCALE;
- } else {
- cinfo.input_components = 3;
- cinfo.in_color_space = JCS_RGB;
- }
+ cinfo.image_width = image.xsize;
+ cinfo.image_height = image.ysize;
+ cinfo.input_components = info.num_color_channels;
+ cinfo.in_color_space = info.num_color_channels == 1 ? JCS_GRAYSCALE : JCS_RGB;
jpeg_set_defaults(&cinfo);
cinfo.optimize_coding = TRUE;
if (cinfo.input_components == 3) {
@@ -125,27 +135,21 @@ Status EncodeWithLibJpeg(const ImageBundle* ib, const CodecInOut* io,
}
jpeg_set_quality(&cinfo, quality, TRUE);
jpeg_start_compress(&cinfo, TRUE);
- if (!ib->IsSRGB()) {
- WriteICCProfile(&cinfo, ib->c_current().ICC());
+ if (!icc.empty()) {
+ WriteICCProfile(&cinfo, icc);
}
- if (!io->blobs.exif.empty()) {
- std::vector<uint8_t> exif = io->blobs.exif;
+ if (!exif.empty()) {
ResetExifOrientation(exif);
WriteExif(&cinfo, exif);
}
if (cinfo.input_components > 3 || cinfo.input_components < 0)
return JXL_FAILURE("invalid numbers of components");
- size_t stride =
- ib->oriented_xsize() * cinfo.input_components * sizeof(JSAMPLE);
- PaddedBytes raw_bytes(stride * ib->oriented_ysize());
- JXL_RETURN_IF_ERROR(ConvertToExternal(
- *ib, BITS_IN_JSAMPLE, /*float_out=*/false, cinfo.input_components,
- JXL_BIG_ENDIAN, stride, nullptr, raw_bytes.data(), raw_bytes.size(),
- /*out_callback=*/{}, ib->metadata()->GetOrientation()));
-
- for (size_t y = 0; y < ib->oriented_ysize(); ++y) {
- JSAMPROW row[] = {raw_bytes.data() + y * stride};
+ std::vector<uint8_t> raw_bytes(image.pixels_size);
+ memcpy(&raw_bytes[0], reinterpret_cast<const uint8_t*>(image.pixels()),
+ image.pixels_size);
+ for (size_t y = 0; y < info.ysize; ++y) {
+ JSAMPROW row[] = {raw_bytes.data() + y * image.stride};
jpeg_write_scanlines(&cinfo, row, 1);
}
@@ -160,41 +164,34 @@ Status EncodeWithLibJpeg(const ImageBundle* ib, const CodecInOut* io,
return true;
}
-Status EncodeWithSJpeg(const ImageBundle* ib, const CodecInOut* io,
- size_t quality,
- const YCbCrChromaSubsampling& chroma_subsampling,
- PaddedBytes* bytes) {
+Status EncodeWithSJpeg(const PackedImage& image, const JxlBasicInfo& info,
+ const std::vector<uint8_t>& icc,
+ std::vector<uint8_t> exif, size_t quality,
+ const std::string& chroma_subsampling,
+ std::vector<uint8_t>* bytes) {
#if !JPEGXL_ENABLE_SJPEG
return JXL_FAILURE("JPEG XL was built without sjpeg support");
#else
sjpeg::EncoderParam param(quality);
- if (!ib->IsSRGB()) {
- param.iccp.assign(ib->metadata()->color_encoding.ICC().begin(),
- ib->metadata()->color_encoding.ICC().end());
+ if (!icc.empty()) {
+ param.iccp.assign(icc.begin(), icc.end());
}
- std::vector<uint8_t> exif = io->blobs.exif;
if (!exif.empty()) {
ResetExifOrientation(exif);
param.exif.assign(exif.begin(), exif.end());
}
- if (chroma_subsampling.Is444()) {
+ if (chroma_subsampling == "444") {
param.yuv_mode = SJPEG_YUV_444;
- } else if (chroma_subsampling.Is420()) {
+ } else if (chroma_subsampling == "420") {
param.yuv_mode = SJPEG_YUV_SHARP;
} else {
return JXL_FAILURE("sjpeg does not support this chroma subsampling mode");
}
- size_t stride = ib->oriented_xsize() * 3;
- PaddedBytes rgb(ib->xsize() * ib->ysize() * 3);
- JXL_RETURN_IF_ERROR(
- ConvertToExternal(*ib, 8, /*float_out=*/false, 3, JXL_BIG_ENDIAN, stride,
- nullptr, rgb.data(), rgb.size(),
- /*out_callback=*/{}, ib->metadata()->GetOrientation()));
-
+ size_t stride = info.xsize * 3;
+ const uint8_t* pixels = reinterpret_cast<const uint8_t*>(image.pixels());
std::string output;
- JXL_RETURN_IF_ERROR(sjpeg::Encode(rgb.data(), ib->oriented_xsize(),
- ib->oriented_ysize(), stride, param,
- &output));
+ JXL_RETURN_IF_ERROR(
+ sjpeg::Encode(pixels, image.xsize, image.ysize, stride, param, &output));
bytes->assign(
reinterpret_cast<const uint8_t*>(output.data()),
reinterpret_cast<const uint8_t*>(output.data() + output.size()));
@@ -202,31 +199,30 @@ Status EncodeWithSJpeg(const ImageBundle* ib, const CodecInOut* io,
#endif
}
-Status EncodeImageJPG(const CodecInOut* io, JpegEncoder encoder, size_t quality,
- YCbCrChromaSubsampling chroma_subsampling,
- ThreadPool* pool, PaddedBytes* bytes) {
- if (io->Main().HasAlpha()) {
+Status EncodeImageJPG(const PackedImage& image, const JxlBasicInfo& info,
+ const std::vector<uint8_t>& icc,
+ std::vector<uint8_t> exif, JpegEncoder encoder,
+ size_t quality, const std::string& chroma_subsampling,
+ ThreadPool* pool, std::vector<uint8_t>* bytes) {
+ if (image.format.data_type != JXL_TYPE_UINT8) {
+ return JXL_FAILURE("Unsupported pixel data type");
+ }
+ if (info.alpha_bits > 0) {
return JXL_FAILURE("alpha is not supported");
}
if (quality > 100) {
return JXL_FAILURE("please specify a 0-100 JPEG quality");
}
- const ImageBundle* ib;
- ImageMetadata metadata = io->metadata.m;
- ImageBundle ib_store(&metadata);
- JXL_RETURN_IF_ERROR(TransformIfNeeded(io->Main(),
- io->metadata.m.color_encoding,
- GetJxlCms(), pool, &ib_store, &ib));
-
switch (encoder) {
case JpegEncoder::kLibJpeg:
- JXL_RETURN_IF_ERROR(
- EncodeWithLibJpeg(ib, io, quality, chroma_subsampling, bytes));
+ JXL_RETURN_IF_ERROR(EncodeWithLibJpeg(image, info, icc, std::move(exif),
+ quality, chroma_subsampling,
+ bytes));
break;
case JpegEncoder::kSJpeg:
- JXL_RETURN_IF_ERROR(
- EncodeWithSJpeg(ib, io, quality, chroma_subsampling, bytes));
+ JXL_RETURN_IF_ERROR(EncodeWithSJpeg(image, info, icc, std::move(exif),
+ quality, chroma_subsampling, bytes));
break;
default:
return JXL_FAILURE("tried to use an unknown JPEG encoder");
@@ -235,5 +231,68 @@ Status EncodeImageJPG(const CodecInOut* io, JpegEncoder encoder, size_t quality,
return true;
}
+class JPEGEncoder : public Encoder {
+ std::vector<JxlPixelFormat> AcceptedFormats() const override {
+ std::vector<JxlPixelFormat> formats;
+ for (const uint32_t num_channels : {1, 3}) {
+ for (JxlEndianness endianness : {JXL_BIG_ENDIAN, JXL_LITTLE_ENDIAN}) {
+ formats.push_back(JxlPixelFormat{/*num_channels=*/num_channels,
+ /*data_type=*/JXL_TYPE_UINT8,
+ /*endianness=*/endianness,
+ /*align=*/0});
+ }
+ }
+ return formats;
+ }
+ Status Encode(const PackedPixelFile& ppf, EncodedImage* encoded_image,
+ ThreadPool* pool = nullptr) const override {
+ JXL_RETURN_IF_ERROR(VerifyBasicInfo(ppf.info));
+ const auto& options = this->options();
+ int quality = 100;
+ auto it_quality = options.find("q");
+ if (it_quality != options.end()) {
+ std::istringstream is(it_quality->second);
+ JXL_RETURN_IF_ERROR(static_cast<bool>(is >> quality));
+ }
+ std::string chroma_subsampling = "444";
+ auto it_chroma_subsampling = options.find("chroma_subsampling");
+ if (it_chroma_subsampling != options.end()) {
+ chroma_subsampling = it_chroma_subsampling->second;
+ }
+ JpegEncoder jpeg_encoder = JpegEncoder::kLibJpeg;
+ auto it_encoder = options.find("jpeg_encoder");
+ if (it_encoder != options.end()) {
+ if (it_encoder->second == "libjpeg") {
+ jpeg_encoder = JpegEncoder::kLibJpeg;
+ } else if (it_encoder->second == "sjpeg") {
+ jpeg_encoder = JpegEncoder::kSJpeg;
+ } else {
+ return JXL_FAILURE("unknown jpeg encoder \"%s\"",
+ it_encoder->second.c_str());
+ }
+ }
+ std::vector<uint8_t> icc;
+ if (!IsSRGBEncoding(ppf.color_encoding)) {
+ icc = ppf.icc;
+ }
+ encoded_image->bitstreams.clear();
+ encoded_image->bitstreams.reserve(ppf.frames.size());
+ for (const auto& frame : ppf.frames) {
+ JXL_RETURN_IF_ERROR(VerifyPackedImage(frame.color, ppf.info));
+ encoded_image->bitstreams.emplace_back();
+ JXL_RETURN_IF_ERROR(EncodeImageJPG(
+ frame.color, ppf.info, icc, ppf.metadata.exif, jpeg_encoder, quality,
+ chroma_subsampling, pool, &encoded_image->bitstreams.back()));
+ }
+ return true;
+ }
+};
+
+} // namespace
+
+std::unique_ptr<Encoder> GetJPEGEncoder() {
+ return jxl::make_unique<JPEGEncoder>();
+}
+
} // namespace extras
} // namespace jxl
diff --git a/media/libjxl/src/lib/extras/enc/jpg.h b/media/libjxl/src/lib/extras/enc/jpg.h
index ccea1415a8..20b37cd168 100644
--- a/media/libjxl/src/lib/extras/enc/jpg.h
+++ b/media/libjxl/src/lib/extras/enc/jpg.h
@@ -8,27 +8,14 @@
// Encodes JPG pixels and metadata in memory.
-#include <stdint.h>
+#include <memory>
-#include "lib/extras/codec.h"
-#include "lib/jxl/base/data_parallel.h"
-#include "lib/jxl/base/padded_bytes.h"
-#include "lib/jxl/base/span.h"
-#include "lib/jxl/base/status.h"
-#include "lib/jxl/codec_in_out.h"
+#include "lib/extras/enc/encode.h"
namespace jxl {
namespace extras {
-enum class JpegEncoder {
- kLibJpeg,
- kSJpeg,
-};
-
-// Encodes into `bytes`.
-Status EncodeImageJPG(const CodecInOut* io, JpegEncoder encoder, size_t quality,
- YCbCrChromaSubsampling chroma_subsampling,
- ThreadPool* pool, PaddedBytes* bytes);
+std::unique_ptr<Encoder> GetJPEGEncoder();
} // namespace extras
} // namespace jxl
diff --git a/media/libjxl/src/lib/extras/enc/npy.cc b/media/libjxl/src/lib/extras/enc/npy.cc
new file mode 100644
index 0000000000..1428e6427d
--- /dev/null
+++ b/media/libjxl/src/lib/extras/enc/npy.cc
@@ -0,0 +1,322 @@
+// Copyright (c) the JPEG XL Project Authors. All rights reserved.
+//
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+#include "lib/extras/enc/npy.h"
+
+#include <stdio.h>
+
+#include <sstream>
+#include <string>
+#include <vector>
+
+#include "jxl/types.h"
+#include "lib/extras/packed_image.h"
+
+namespace jxl {
+namespace extras {
+namespace {
+
+// JSON value writing
+
+class JSONField {
+ public:
+ virtual ~JSONField() = default;
+ virtual void Write(std::ostream& o, uint32_t indent) const = 0;
+
+ protected:
+ JSONField() = default;
+};
+
+class JSONValue : public JSONField {
+ public:
+ template <typename T>
+ explicit JSONValue(const T& value) : value_(std::to_string(value)) {}
+
+ explicit JSONValue(const std::string& value) : value_("\"" + value + "\"") {}
+
+ explicit JSONValue(bool value) : value_(value ? "true" : "false") {}
+
+ void Write(std::ostream& o, uint32_t indent) const override { o << value_; }
+
+ private:
+ std::string value_;
+};
+
+class JSONDict : public JSONField {
+ public:
+ JSONDict() = default;
+
+ template <typename T>
+ T* AddEmpty(const std::string& key) {
+ static_assert(std::is_convertible<T*, JSONField*>::value,
+ "T must be a JSONField");
+ T* ret = new T();
+ values_.emplace_back(
+ key, std::unique_ptr<JSONField>(static_cast<JSONField*>(ret)));
+ return ret;
+ }
+
+ template <typename T>
+ void Add(const std::string& key, const T& value) {
+ values_.emplace_back(key, std::unique_ptr<JSONField>(new JSONValue(value)));
+ }
+
+ void Write(std::ostream& o, uint32_t indent) const override {
+ std::string indent_str(indent, ' ');
+ o << "{";
+ bool is_first = true;
+ for (const auto& key_value : values_) {
+ if (!is_first) {
+ o << ",";
+ }
+ is_first = false;
+ o << std::endl << indent_str << " \"" << key_value.first << "\": ";
+ key_value.second->Write(o, indent + 2);
+ }
+ if (!values_.empty()) {
+ o << std::endl << indent_str;
+ }
+ o << "}";
+ }
+
+ private:
+ // Dictionary with order.
+ std::vector<std::pair<std::string, std::unique_ptr<JSONField>>> values_;
+};
+
+class JSONArray : public JSONField {
+ public:
+ JSONArray() = default;
+
+ template <typename T>
+ T* AddEmpty() {
+ static_assert(std::is_convertible<T*, JSONField*>::value,
+ "T must be a JSONField");
+ T* ret = new T();
+ values_.emplace_back(ret);
+ return ret;
+ }
+
+ template <typename T>
+ void Add(const T& value) {
+ values_.emplace_back(new JSONValue(value));
+ }
+
+ void Write(std::ostream& o, uint32_t indent) const override {
+ std::string indent_str(indent, ' ');
+ o << "[";
+ bool is_first = true;
+ for (const auto& value : values_) {
+ if (!is_first) {
+ o << ",";
+ }
+ is_first = false;
+ o << std::endl << indent_str << " ";
+ value->Write(o, indent + 2);
+ }
+ if (!values_.empty()) {
+ o << std::endl << indent_str;
+ }
+ o << "]";
+ }
+
+ private:
+ std::vector<std::unique_ptr<JSONField>> values_;
+};
+
+void GenerateMetadata(const PackedPixelFile& ppf, std::vector<uint8_t>* out) {
+ JSONDict meta;
+ // Same order as in 18181-3 CD.
+
+ // Frames.
+ auto* meta_frames = meta.AddEmpty<JSONArray>("frames");
+ for (size_t i = 0; i < ppf.frames.size(); i++) {
+ auto* frame_i = meta_frames->AddEmpty<JSONDict>();
+ if (ppf.info.have_animation) {
+ frame_i->Add("duration",
+ JSONValue(ppf.frames[i].frame_info.duration * 1.0f *
+ ppf.info.animation.tps_denominator /
+ ppf.info.animation.tps_numerator));
+ }
+
+ frame_i->Add("name", JSONValue(ppf.frames[i].name));
+
+ if (ppf.info.animation.have_timecodes) {
+ frame_i->Add("timecode", JSONValue(ppf.frames[i].frame_info.timecode));
+ }
+ }
+
+#define METADATA(FIELD) meta.Add(#FIELD, ppf.info.FIELD)
+
+ METADATA(intensity_target);
+ METADATA(min_nits);
+ METADATA(relative_to_max_display);
+ METADATA(linear_below);
+
+ if (ppf.info.have_preview) {
+ meta.AddEmpty<JSONDict>("preview");
+ // TODO(veluca): can we have duration/name/timecode here?
+ }
+
+ {
+ auto ectype = meta.AddEmpty<JSONArray>("extra_channel_type");
+ auto bps = meta.AddEmpty<JSONArray>("bits_per_sample");
+ auto ebps = meta.AddEmpty<JSONArray>("exp_bits_per_sample");
+ bps->Add(ppf.info.bits_per_sample);
+ ebps->Add(ppf.info.exponent_bits_per_sample);
+ for (size_t i = 0; i < ppf.extra_channels_info.size(); i++) {
+ switch (ppf.extra_channels_info[i].ec_info.type) {
+ case JXL_CHANNEL_ALPHA: {
+ ectype->Add(std::string("Alpha"));
+ break;
+ }
+ case JXL_CHANNEL_DEPTH: {
+ ectype->Add(std::string("Depth"));
+ break;
+ }
+ case JXL_CHANNEL_SPOT_COLOR: {
+ ectype->Add(std::string("SpotColor"));
+ break;
+ }
+ case JXL_CHANNEL_SELECTION_MASK: {
+ ectype->Add(std::string("SelectionMask"));
+ break;
+ }
+ case JXL_CHANNEL_BLACK: {
+ ectype->Add(std::string("Black"));
+ break;
+ }
+ case JXL_CHANNEL_CFA: {
+ ectype->Add(std::string("CFA"));
+ break;
+ }
+ case JXL_CHANNEL_THERMAL: {
+ ectype->Add(std::string("Thermal"));
+ break;
+ }
+ default: {
+ ectype->Add(std::string("UNKNOWN"));
+ break;
+ }
+ }
+ bps->Add(ppf.extra_channels_info[i].ec_info.bits_per_sample);
+ ebps->Add(ppf.extra_channels_info[i].ec_info.exponent_bits_per_sample);
+ }
+ }
+
+ std::ostringstream os;
+ meta.Write(os, 0);
+ out->resize(os.str().size());
+ memcpy(out->data(), os.str().data(), os.str().size());
+}
+
+void Append(std::vector<uint8_t>* out, const void* data, size_t size) {
+ size_t pos = out->size();
+ out->resize(pos + size);
+ memcpy(out->data() + pos, data, size);
+}
+
+void WriteNPYHeader(size_t xsize, size_t ysize, uint32_t num_channels,
+ size_t num_frames, std::vector<uint8_t>* out) {
+ const uint8_t header[] = "\x93NUMPY\x01\x00";
+ Append(out, header, 8);
+ std::stringstream ss;
+ ss << "{'descr': '<f4', 'fortran_order': False, 'shape': (" << num_frames
+ << ", " << ysize << ", " << xsize << ", " << num_channels << "), }\n";
+ // 16-bit little endian header length.
+ uint8_t header_len[2] = {static_cast<uint8_t>(ss.str().size() % 256),
+ static_cast<uint8_t>(ss.str().size() / 256)};
+ Append(out, header_len, 2);
+ Append(out, ss.str().data(), ss.str().size());
+}
+
+bool WriteFrameToNPYArray(size_t xsize, size_t ysize, const PackedFrame& frame,
+ std::vector<uint8_t>* out) {
+ const auto& color = frame.color;
+ if (color.xsize != xsize || color.ysize != ysize) {
+ return false;
+ }
+ for (const auto& ec : frame.extra_channels) {
+ if (ec.xsize != xsize || ec.ysize != ysize) {
+ return false;
+ }
+ }
+ // interleave the samples from color and extra channels
+ for (size_t y = 0; y < ysize; ++y) {
+ for (size_t x = 0; x < xsize; ++x) {
+ {
+ size_t sample_size = color.pixel_stride();
+ size_t offset = y * color.stride + x * sample_size;
+ uint8_t* pixels = reinterpret_cast<uint8_t*>(color.pixels());
+ JXL_ASSERT(offset + sample_size <= color.pixels_size);
+ Append(out, pixels + offset, sample_size);
+ }
+ for (const auto& ec : frame.extra_channels) {
+ size_t sample_size = ec.pixel_stride();
+ size_t offset = y * ec.stride + x * sample_size;
+ uint8_t* pixels = reinterpret_cast<uint8_t*>(ec.pixels());
+ JXL_ASSERT(offset + sample_size <= ec.pixels_size);
+ Append(out, pixels + offset, sample_size);
+ }
+ }
+ }
+ return true;
+}
+
+// Writes a PackedPixelFile as a numpy 4D ndarray in binary format.
+bool WriteNPYArray(const PackedPixelFile& ppf, std::vector<uint8_t>* out) {
+ size_t xsize = ppf.info.xsize;
+ size_t ysize = ppf.info.ysize;
+ WriteNPYHeader(xsize, ysize,
+ ppf.info.num_color_channels + ppf.extra_channels_info.size(),
+ ppf.frames.size(), out);
+ for (const auto& frame : ppf.frames) {
+ if (!WriteFrameToNPYArray(xsize, ysize, frame, out)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+class NumPyEncoder : public Encoder {
+ public:
+ Status Encode(const PackedPixelFile& ppf, EncodedImage* encoded_image,
+ ThreadPool* pool = nullptr) const override {
+ JXL_RETURN_IF_ERROR(VerifyBasicInfo(ppf.info));
+ GenerateMetadata(ppf, &encoded_image->metadata);
+ encoded_image->bitstreams.emplace_back();
+ if (!WriteNPYArray(ppf, &encoded_image->bitstreams.back())) {
+ return false;
+ }
+ if (ppf.preview_frame) {
+ size_t xsize = ppf.info.preview.xsize;
+ size_t ysize = ppf.info.preview.ysize;
+ WriteNPYHeader(xsize, ysize, ppf.info.num_color_channels, 1,
+ &encoded_image->preview_bitstream);
+ if (!WriteFrameToNPYArray(xsize, ysize, *ppf.preview_frame,
+ &encoded_image->preview_bitstream)) {
+ return false;
+ }
+ }
+ return true;
+ }
+ std::vector<JxlPixelFormat> AcceptedFormats() const override {
+ std::vector<JxlPixelFormat> formats;
+ for (const uint32_t num_channels : {1, 3}) {
+ formats.push_back(JxlPixelFormat{num_channels, JXL_TYPE_FLOAT,
+ JXL_LITTLE_ENDIAN, /*align=*/0});
+ }
+ return formats;
+ }
+};
+
+} // namespace
+
+std::unique_ptr<Encoder> GetNumPyEncoder() {
+ return jxl::make_unique<NumPyEncoder>();
+}
+
+} // namespace extras
+} // namespace jxl
diff --git a/media/libjxl/src/lib/extras/enc/npy.h b/media/libjxl/src/lib/extras/enc/npy.h
new file mode 100644
index 0000000000..3ee6208ec2
--- /dev/null
+++ b/media/libjxl/src/lib/extras/enc/npy.h
@@ -0,0 +1,23 @@
+// Copyright (c) the JPEG XL Project Authors. All rights reserved.
+//
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+#ifndef LIB_EXTRAS_ENC_NPY_H_
+#define LIB_EXTRAS_ENC_NPY_H_
+
+// Encodes pixels to numpy array, used for conformance testing.
+
+#include <memory>
+
+#include "lib/extras/enc/encode.h"
+
+namespace jxl {
+namespace extras {
+
+std::unique_ptr<Encoder> GetNumPyEncoder();
+
+} // namespace extras
+} // namespace jxl
+
+#endif // LIB_EXTRAS_ENC_NPY_H_
diff --git a/media/libjxl/src/lib/extras/enc/pgx.cc b/media/libjxl/src/lib/extras/enc/pgx.cc
index cc333a62ac..ef204ad1c6 100644
--- a/media/libjxl/src/lib/extras/enc/pgx.cc
+++ b/media/libjxl/src/lib/extras/enc/pgx.cc
@@ -8,18 +8,10 @@
#include <stdio.h>
#include <string.h>
-#include "lib/jxl/base/bits.h"
-#include "lib/jxl/base/compiler_specific.h"
-#include "lib/jxl/base/file_io.h"
+#include "jxl/codestream_header.h"
+#include "lib/extras/packed_image.h"
+#include "lib/jxl/base/byte_order.h"
#include "lib/jxl/base/printf_macros.h"
-#include "lib/jxl/color_management.h"
-#include "lib/jxl/dec_external_image.h"
-#include "lib/jxl/enc_color_management.h"
-#include "lib/jxl/enc_external_image.h"
-#include "lib/jxl/enc_image_bundle.h"
-#include "lib/jxl/fields.h" // AllDefault
-#include "lib/jxl/image.h"
-#include "lib/jxl/image_bundle.h"
namespace jxl {
namespace extras {
@@ -27,60 +19,63 @@ namespace {
constexpr size_t kMaxHeaderSize = 200;
-Status EncodeHeader(const ImageBundle& ib, const size_t bits_per_sample,
- char* header, int* JXL_RESTRICT chars_written) {
- if (ib.HasAlpha()) return JXL_FAILURE("PGX: can't store alpha");
- if (!ib.IsGray()) return JXL_FAILURE("PGX: must be grayscale");
+Status EncodeHeader(const JxlBasicInfo& info, char* header,
+ int* chars_written) {
+ if (info.alpha_bits > 0) {
+ return JXL_FAILURE("PGX: can't store alpha");
+ }
+ if (info.num_color_channels != 1) {
+ return JXL_FAILURE("PGX: must be grayscale");
+ }
// TODO(lode): verify other bit depths: for other bit depths such as 1 or 4
// bits, have a test case to verify it works correctly. For bits > 16, we may
// need to change the way external_image works.
- if (bits_per_sample != 8 && bits_per_sample != 16) {
+ if (info.bits_per_sample != 8 && info.bits_per_sample != 16) {
return JXL_FAILURE("PGX: bits other than 8 or 16 not yet supported");
}
// Use ML (Big Endian), LM may not be well supported by all decoders.
- *chars_written = snprintf(header, kMaxHeaderSize,
- "PG ML + %" PRIuS " %" PRIuS " %" PRIuS "\n",
- bits_per_sample, ib.xsize(), ib.ysize());
+ *chars_written = snprintf(header, kMaxHeaderSize, "PG ML + %u %u %u\n",
+ info.bits_per_sample, info.xsize, info.ysize);
JXL_RETURN_IF_ERROR(static_cast<unsigned int>(*chars_written) <
kMaxHeaderSize);
return true;
}
-} // namespace
+Status EncodeImagePGX(const PackedFrame& frame, const JxlBasicInfo& info,
+ std::vector<uint8_t>* bytes) {
+ char header[kMaxHeaderSize];
+ int header_size = 0;
+ JXL_RETURN_IF_ERROR(EncodeHeader(info, header, &header_size));
-Status EncodeImagePGX(const CodecInOut* io, const ColorEncoding& c_desired,
- size_t bits_per_sample, ThreadPool* pool,
- PaddedBytes* bytes) {
- if (!Bundle::AllDefault(io->metadata.m)) {
- JXL_WARNING("PGX encoder ignoring metadata - use a different codec");
- }
- if (!c_desired.IsSRGB()) {
- JXL_WARNING(
- "PGX encoder cannot store custom ICC profile; decoder\n"
- "will need hint key=color_space to get the same values");
+ const PackedImage& color = frame.color;
+ const JxlPixelFormat format = color.format;
+ const uint8_t* in = reinterpret_cast<const uint8_t*>(color.pixels());
+ size_t data_bits_per_sample = PackedImage::BitsPerChannel(format.data_type);
+ size_t bytes_per_sample = data_bits_per_sample / kBitsPerByte;
+ size_t num_samples = info.xsize * info.ysize;
+
+ if (info.bits_per_sample != data_bits_per_sample) {
+ return JXL_FAILURE("Bit depth does not match pixel data type");
}
- ImageBundle ib = io->Main().Copy();
-
- ImageMetadata metadata = io->metadata.m;
- ImageBundle store(&metadata);
- const ImageBundle* transformed;
- JXL_RETURN_IF_ERROR(TransformIfNeeded(ib, c_desired, GetJxlCms(), pool,
- &store, &transformed));
- PaddedBytes pixels(ib.xsize() * ib.ysize() *
- (bits_per_sample / kBitsPerByte));
- size_t stride = ib.xsize() * (bits_per_sample / kBitsPerByte);
- JXL_RETURN_IF_ERROR(
- ConvertToExternal(*transformed, bits_per_sample,
- /*float_out=*/false,
- /*num_channels=*/1, JXL_BIG_ENDIAN, stride, pool,
- pixels.data(), pixels.size(),
- /*out_callback=*/{}, metadata.GetOrientation()));
+ std::vector<uint8_t> pixels(num_samples * bytes_per_sample);
- char header[kMaxHeaderSize];
- int header_size = 0;
- JXL_RETURN_IF_ERROR(EncodeHeader(ib, bits_per_sample, header, &header_size));
+ if (format.data_type == JXL_TYPE_UINT8) {
+ memcpy(&pixels[0], in, num_samples * bytes_per_sample);
+ } else if (format.data_type == JXL_TYPE_UINT16) {
+ if (format.endianness != JXL_BIG_ENDIAN) {
+ const uint8_t* p_in = in;
+ uint8_t* p_out = pixels.data();
+ for (size_t i = 0; i < num_samples; ++i, p_in += 2, p_out += 2) {
+ StoreBE16(LoadLE16(p_in), p_out);
+ }
+ } else {
+ memcpy(&pixels[0], in, num_samples * bytes_per_sample);
+ }
+ } else {
+ return JXL_FAILURE("Unsupported pixel data type");
+ }
bytes->resize(static_cast<size_t>(header_size) + pixels.size());
memcpy(bytes->data(), header, static_cast<size_t>(header_size));
@@ -89,5 +84,41 @@ Status EncodeImagePGX(const CodecInOut* io, const ColorEncoding& c_desired,
return true;
}
+class PGXEncoder : public Encoder {
+ public:
+ std::vector<JxlPixelFormat> AcceptedFormats() const override {
+ std::vector<JxlPixelFormat> formats;
+ for (const JxlDataType data_type : {JXL_TYPE_UINT8, JXL_TYPE_UINT16}) {
+ for (JxlEndianness endianness : {JXL_BIG_ENDIAN, JXL_LITTLE_ENDIAN}) {
+ formats.push_back(JxlPixelFormat{/*num_channels=*/1,
+ /*data_type=*/data_type,
+ /*endianness=*/endianness,
+ /*align=*/0});
+ }
+ }
+ return formats;
+ }
+ Status Encode(const PackedPixelFile& ppf, EncodedImage* encoded_image,
+ ThreadPool* pool) const override {
+ JXL_RETURN_IF_ERROR(VerifyBasicInfo(ppf.info));
+ encoded_image->icc.assign(ppf.icc.begin(), ppf.icc.end());
+ encoded_image->bitstreams.clear();
+ encoded_image->bitstreams.reserve(ppf.frames.size());
+ for (const auto& frame : ppf.frames) {
+ JXL_RETURN_IF_ERROR(VerifyPackedImage(frame.color, ppf.info));
+ encoded_image->bitstreams.emplace_back();
+ JXL_RETURN_IF_ERROR(
+ EncodeImagePGX(frame, ppf.info, &encoded_image->bitstreams.back()));
+ }
+ return true;
+ }
+};
+
+} // namespace
+
+std::unique_ptr<Encoder> GetPGXEncoder() {
+ return jxl::make_unique<PGXEncoder>();
+}
+
} // namespace extras
} // namespace jxl
diff --git a/media/libjxl/src/lib/extras/enc/pgx.h b/media/libjxl/src/lib/extras/enc/pgx.h
index 6f14e20668..f24e391b09 100644
--- a/media/libjxl/src/lib/extras/enc/pgx.h
+++ b/media/libjxl/src/lib/extras/enc/pgx.h
@@ -11,21 +11,12 @@
#include <stddef.h>
#include <stdint.h>
-#include "lib/extras/packed_image.h"
-#include "lib/jxl/base/data_parallel.h"
-#include "lib/jxl/base/padded_bytes.h"
-#include "lib/jxl/base/span.h"
-#include "lib/jxl/base/status.h"
-#include "lib/jxl/codec_in_out.h"
-#include "lib/jxl/color_encoding_internal.h"
+#include "lib/extras/enc/encode.h"
namespace jxl {
namespace extras {
-// Transforms from io->c_current to `c_desired` and encodes into `bytes`.
-Status EncodeImagePGX(const CodecInOut* io, const ColorEncoding& c_desired,
- size_t bits_per_sample, ThreadPool* pool,
- PaddedBytes* bytes);
+std::unique_ptr<Encoder> GetPGXEncoder();
} // namespace extras
} // namespace jxl
diff --git a/media/libjxl/src/lib/extras/enc/pnm.cc b/media/libjxl/src/lib/extras/enc/pnm.cc
index 9cedda09d5..9b5f6cbc95 100644
--- a/media/libjxl/src/lib/extras/enc/pnm.cc
+++ b/media/libjxl/src/lib/extras/enc/pnm.cc
@@ -32,22 +32,19 @@ namespace {
constexpr size_t kMaxHeaderSize = 200;
-Status EncodeHeader(const PackedPixelFile& ppf, const size_t bits_per_sample,
- const bool little_endian, char* header,
- int* JXL_RESTRICT chars_written) {
- bool is_gray = ppf.info.num_color_channels <= 2;
- size_t oriented_xsize =
- ppf.info.orientation <= 4 ? ppf.info.xsize : ppf.info.ysize;
- size_t oriented_ysize =
- ppf.info.orientation <= 4 ? ppf.info.ysize : ppf.info.xsize;
- if (ppf.info.alpha_bits > 0) { // PAM
+Status EncodeHeader(const PackedImage& image, size_t bits_per_sample,
+ bool little_endian, char* header, int* chars_written) {
+ size_t num_channels = image.format.num_channels;
+ bool is_gray = num_channels <= 2;
+ bool has_alpha = num_channels == 2 || num_channels == 4;
+ if (has_alpha) { // PAM
if (bits_per_sample > 16) return JXL_FAILURE("PNM cannot have > 16 bits");
const uint32_t max_val = (1U << bits_per_sample) - 1;
*chars_written =
snprintf(header, kMaxHeaderSize,
"P7\nWIDTH %" PRIuS "\nHEIGHT %" PRIuS
"\nDEPTH %u\nMAXVAL %u\nTUPLTYPE %s\nENDHDR\n",
- oriented_xsize, oriented_ysize, is_gray ? 2 : 4, max_val,
+ image.xsize, image.ysize, is_gray ? 2 : 4, max_val,
is_gray ? "GRAYSCALE_ALPHA" : "RGB_ALPHA");
JXL_RETURN_IF_ERROR(static_cast<unsigned int>(*chars_written) <
kMaxHeaderSize);
@@ -56,77 +53,164 @@ Status EncodeHeader(const PackedPixelFile& ppf, const size_t bits_per_sample,
const double scale = little_endian ? -1.0 : 1.0;
*chars_written =
snprintf(header, kMaxHeaderSize, "P%c\n%" PRIuS " %" PRIuS "\n%.1f\n",
- type, oriented_xsize, oriented_ysize, scale);
+ type, image.xsize, image.ysize, scale);
JXL_RETURN_IF_ERROR(static_cast<unsigned int>(*chars_written) <
kMaxHeaderSize);
} else { // PGM/PPM
+ if (bits_per_sample > 16) return JXL_FAILURE("PNM cannot have > 16 bits");
const uint32_t max_val = (1U << bits_per_sample) - 1;
- if (max_val >= 65536) return JXL_FAILURE("PNM cannot have > 16 bits");
const char type = is_gray ? '5' : '6';
*chars_written =
snprintf(header, kMaxHeaderSize, "P%c\n%" PRIuS " %" PRIuS "\n%u\n",
- type, oriented_xsize, oriented_ysize, max_val);
+ type, image.xsize, image.ysize, max_val);
JXL_RETURN_IF_ERROR(static_cast<unsigned int>(*chars_written) <
kMaxHeaderSize);
}
return true;
}
-Span<const uint8_t> MakeSpan(const char* str) {
- return Span<const uint8_t>(reinterpret_cast<const uint8_t*>(str),
- strlen(str));
+Status EncodeImagePNM(const PackedImage& image, size_t bits_per_sample,
+ std::vector<uint8_t>* bytes) {
+ // Choose native for PFM; PGM/PPM require big-endian
+ bool is_little_endian = bits_per_sample > 16 && IsLittleEndian();
+ char header[kMaxHeaderSize];
+ int header_size = 0;
+ JXL_RETURN_IF_ERROR(EncodeHeader(image, bits_per_sample, is_little_endian,
+ header, &header_size));
+ bytes->resize(static_cast<size_t>(header_size) + image.pixels_size);
+ memcpy(bytes->data(), header, static_cast<size_t>(header_size));
+ const bool flipped_y = bits_per_sample == 32; // PFMs are flipped
+ const uint8_t* in = reinterpret_cast<const uint8_t*>(image.pixels());
+ uint8_t* out = bytes->data() + header_size;
+ for (size_t y = 0; y < image.ysize; ++y) {
+ size_t y_out = flipped_y ? image.ysize - 1 - y : y;
+ const uint8_t* row_in = &in[y * image.stride];
+ uint8_t* row_out = &out[y_out * image.stride];
+ memcpy(row_out, row_in, image.stride);
+ }
+ return true;
}
-// Flip the image vertically for loading/saving PFM files which have the
-// scanlines inverted.
-void VerticallyFlipImage(float* const float_image, const size_t xsize,
- const size_t ysize, const size_t num_channels) {
- for (size_t y = 0; y < ysize / 2; y++) {
- float* first_row = &float_image[y * num_channels * xsize];
- float* other_row = &float_image[(ysize - y - 1) * num_channels * xsize];
- for (size_t c = 0; c < num_channels; c++) {
- for (size_t x = 0; x < xsize; ++x) {
- float tmp = first_row[x * num_channels + c];
- first_row[x * num_channels + c] = other_row[x * num_channels + c];
- other_row[x * num_channels + c] = tmp;
+class PNMEncoder : public Encoder {
+ public:
+ Status Encode(const PackedPixelFile& ppf, EncodedImage* encoded_image,
+ ThreadPool* pool = nullptr) const override {
+ JXL_RETURN_IF_ERROR(VerifyBasicInfo(ppf.info));
+ if (!ppf.metadata.exif.empty() || !ppf.metadata.iptc.empty() ||
+ !ppf.metadata.jumbf.empty() || !ppf.metadata.xmp.empty()) {
+ JXL_WARNING("PNM encoder ignoring metadata - use a different codec");
+ }
+ encoded_image->icc = ppf.icc;
+ encoded_image->bitstreams.clear();
+ encoded_image->bitstreams.reserve(ppf.frames.size());
+ for (const auto& frame : ppf.frames) {
+ JXL_RETURN_IF_ERROR(VerifyPackedImage(frame.color, ppf.info));
+ encoded_image->bitstreams.emplace_back();
+ JXL_RETURN_IF_ERROR(EncodeImagePNM(frame.color, ppf.info.bits_per_sample,
+ &encoded_image->bitstreams.back()));
+ }
+ for (size_t i = 0; i < ppf.extra_channels_info.size(); ++i) {
+ const auto& ec_info = ppf.extra_channels_info[i].ec_info;
+ encoded_image->extra_channel_bitstreams.emplace_back();
+ auto& ec_bitstreams = encoded_image->extra_channel_bitstreams.back();
+ for (const auto& frame : ppf.frames) {
+ ec_bitstreams.emplace_back();
+ JXL_RETURN_IF_ERROR(EncodeImagePNM(frame.extra_channels[i],
+ ec_info.bits_per_sample,
+ &ec_bitstreams.back()));
}
}
+ return true;
}
-}
+};
-} // namespace
+class PPMEncoder : public PNMEncoder {
+ public:
+ std::vector<JxlPixelFormat> AcceptedFormats() const override {
+ std::vector<JxlPixelFormat> formats;
+ for (const uint32_t num_channels : {1, 2, 3, 4}) {
+ for (const JxlDataType data_type : {JXL_TYPE_UINT8, JXL_TYPE_UINT16}) {
+ for (JxlEndianness endianness : {JXL_BIG_ENDIAN, JXL_LITTLE_ENDIAN}) {
+ formats.push_back(JxlPixelFormat{/*num_channels=*/num_channels,
+ /*data_type=*/data_type,
+ /*endianness=*/endianness,
+ /*align=*/0});
+ }
+ }
+ }
+ return formats;
+ }
+};
-Status EncodeImagePNM(const PackedPixelFile& ppf, size_t bits_per_sample,
- ThreadPool* pool, size_t frame_index,
- std::vector<uint8_t>* bytes) {
- const bool floating_point = bits_per_sample > 16;
- // Choose native for PFM; PGM/PPM require big-endian
- const JxlEndianness endianness =
- floating_point ? JXL_NATIVE_ENDIAN : JXL_BIG_ENDIAN;
- if (!ppf.metadata.exif.empty() || !ppf.metadata.iptc.empty() ||
- !ppf.metadata.jumbf.empty() || !ppf.metadata.xmp.empty()) {
- JXL_WARNING("PNM encoder ignoring metadata - use a different codec");
+class PFMEncoder : public PNMEncoder {
+ public:
+ std::vector<JxlPixelFormat> AcceptedFormats() const override {
+ std::vector<JxlPixelFormat> formats;
+ for (const uint32_t num_channels : {1, 3}) {
+ for (const JxlDataType data_type : {JXL_TYPE_FLOAT16, JXL_TYPE_FLOAT}) {
+ for (JxlEndianness endianness : {JXL_BIG_ENDIAN, JXL_LITTLE_ENDIAN}) {
+ formats.push_back(JxlPixelFormat{/*num_channels=*/num_channels,
+ /*data_type=*/data_type,
+ /*endianness=*/endianness,
+ /*align=*/0});
+ }
+ }
+ }
+ return formats;
}
+};
- char header[kMaxHeaderSize];
- int header_size = 0;
- bool is_little_endian = endianness == JXL_LITTLE_ENDIAN ||
- (endianness == JXL_NATIVE_ENDIAN && IsLittleEndian());
- JXL_RETURN_IF_ERROR(EncodeHeader(ppf, bits_per_sample, is_little_endian,
- header, &header_size));
- bytes->resize(static_cast<size_t>(header_size) +
- ppf.frames[frame_index].color.pixels_size);
- memcpy(bytes->data(), header, static_cast<size_t>(header_size));
- memcpy(bytes->data() + header_size, ppf.frames[frame_index].color.pixels(),
- ppf.frames[frame_index].color.pixels_size);
- if (floating_point) {
- VerticallyFlipImage(reinterpret_cast<float*>(bytes->data() + header_size),
- ppf.frames[frame_index].color.xsize,
- ppf.frames[frame_index].color.ysize,
- ppf.info.num_color_channels);
+class PGMEncoder : public PPMEncoder {
+ public:
+ std::vector<JxlPixelFormat> AcceptedFormats() const override {
+ std::vector<JxlPixelFormat> formats = PPMEncoder::AcceptedFormats();
+ for (auto it = formats.begin(); it != formats.end();) {
+ if (it->num_channels > 2) {
+ it = formats.erase(it);
+ } else {
+ ++it;
+ }
+ }
+ return formats;
}
+};
- return true;
+class PAMEncoder : public PPMEncoder {
+ public:
+ std::vector<JxlPixelFormat> AcceptedFormats() const override {
+ std::vector<JxlPixelFormat> formats = PPMEncoder::AcceptedFormats();
+ for (auto it = formats.begin(); it != formats.end();) {
+ if (it->num_channels != 2 && it->num_channels != 4) {
+ it = formats.erase(it);
+ } else {
+ ++it;
+ }
+ }
+ return formats;
+ }
+};
+
+Span<const uint8_t> MakeSpan(const char* str) {
+ return Span<const uint8_t>(reinterpret_cast<const uint8_t*>(str),
+ strlen(str));
+}
+
+} // namespace
+
+std::unique_ptr<Encoder> GetPPMEncoder() {
+ return jxl::make_unique<PPMEncoder>();
+}
+
+std::unique_ptr<Encoder> GetPFMEncoder() {
+ return jxl::make_unique<PFMEncoder>();
+}
+
+std::unique_ptr<Encoder> GetPGMEncoder() {
+ return jxl::make_unique<PGMEncoder>();
+}
+
+std::unique_ptr<Encoder> GetPAMEncoder() {
+ return jxl::make_unique<PAMEncoder>();
}
} // namespace extras
diff --git a/media/libjxl/src/lib/extras/enc/pnm.h b/media/libjxl/src/lib/extras/enc/pnm.h
index ecf0526a1a..403208cecd 100644
--- a/media/libjxl/src/lib/extras/enc/pnm.h
+++ b/media/libjxl/src/lib/extras/enc/pnm.h
@@ -10,21 +10,17 @@
// TODO(janwas): workaround for incorrect Win64 codegen (cause unknown)
#include <hwy/highway.h>
+#include <memory>
-#include "lib/extras/packed_image.h"
-#include "lib/jxl/base/data_parallel.h"
-#include "lib/jxl/base/padded_bytes.h"
-#include "lib/jxl/base/status.h"
-#include "lib/jxl/codec_in_out.h"
-#include "lib/jxl/color_encoding_internal.h"
+#include "lib/extras/enc/encode.h"
namespace jxl {
namespace extras {
-// Transforms from io->c_current to `c_desired` and encodes into `bytes`.
-Status EncodeImagePNM(const PackedPixelFile& ppf, size_t bits_per_sample,
- ThreadPool* pool, size_t frame_index,
- std::vector<uint8_t>* bytes);
+std::unique_ptr<Encoder> GetPAMEncoder();
+std::unique_ptr<Encoder> GetPGMEncoder();
+std::unique_ptr<Encoder> GetPPMEncoder();
+std::unique_ptr<Encoder> GetPFMEncoder();
} // namespace extras
} // namespace jxl
diff --git a/media/libjxl/src/lib/extras/exif.cc b/media/libjxl/src/lib/extras/exif.cc
new file mode 100644
index 0000000000..7d926558c3
--- /dev/null
+++ b/media/libjxl/src/lib/extras/exif.cc
@@ -0,0 +1,55 @@
+// Copyright (c) the JPEG XL Project Authors. All rights reserved.
+//
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+#include "lib/extras/exif.h"
+
+#include "lib/jxl/base/byte_order.h"
+
+namespace jxl {
+
+constexpr uint16_t kExifOrientationTag = 274;
+
+void ResetExifOrientation(std::vector<uint8_t>& exif) {
+ if (exif.size() < 12) return; // not enough bytes for a valid exif blob
+ bool bigendian;
+ uint8_t* t = exif.data();
+ if (LoadLE32(t) == 0x2A004D4D) {
+ bigendian = true;
+ } else if (LoadLE32(t) == 0x002A4949) {
+ bigendian = false;
+ } else {
+ return; // not a valid tiff header
+ }
+ t += 4;
+ uint32_t offset = (bigendian ? LoadBE32(t) : LoadLE32(t));
+ if (exif.size() < 12 + offset + 2 || offset < 8) return;
+ t += offset - 4;
+ uint16_t nb_tags = (bigendian ? LoadBE16(t) : LoadLE16(t));
+ t += 2;
+ while (nb_tags > 0) {
+ if (t + 12 >= exif.data() + exif.size()) return;
+ uint16_t tag = (bigendian ? LoadBE16(t) : LoadLE16(t));
+ t += 2;
+ if (tag == kExifOrientationTag) {
+ uint16_t type = (bigendian ? LoadBE16(t) : LoadLE16(t));
+ t += 2;
+ uint32_t count = (bigendian ? LoadBE32(t) : LoadLE32(t));
+ t += 4;
+ if (type == 3 && count == 1) {
+ if (bigendian) {
+ StoreBE16(1, t);
+ } else {
+ StoreLE16(1, t);
+ }
+ }
+ return;
+ } else {
+ t += 10;
+ nb_tags--;
+ }
+ }
+}
+
+} // namespace jxl
diff --git a/media/libjxl/src/lib/extras/exif.h b/media/libjxl/src/lib/extras/exif.h
new file mode 100644
index 0000000000..f22b2ccef5
--- /dev/null
+++ b/media/libjxl/src/lib/extras/exif.h
@@ -0,0 +1,20 @@
+// Copyright (c) the JPEG XL Project Authors. All rights reserved.
+//
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+#ifndef LIB_EXTRAS_EXIF_H_
+#define LIB_EXTRAS_EXIF_H_
+
+#include <stdint.h>
+
+#include <vector>
+
+namespace jxl {
+
+// Sets the Exif orientation to the identity, to avoid repeated orientation
+void ResetExifOrientation(std::vector<uint8_t>& exif);
+
+} // namespace jxl
+
+#endif // LIB_EXTRAS_EXIF_H_
diff --git a/media/libjxl/src/lib/extras/packed_image.h b/media/libjxl/src/lib/extras/packed_image.h
index 59dbfe57c4..1296472101 100644
--- a/media/libjxl/src/lib/extras/packed_image.h
+++ b/media/libjxl/src/lib/extras/packed_image.h
@@ -22,7 +22,6 @@
#include "jxl/codestream_header.h"
#include "jxl/encode.h"
#include "jxl/types.h"
-#include "lib/jxl/base/status.h"
#include "lib/jxl/common.h"
namespace jxl {
@@ -33,26 +32,6 @@ class PackedImage {
public:
PackedImage(size_t xsize, size_t ysize, const JxlPixelFormat& format)
: PackedImage(xsize, ysize, format, CalcStride(format, xsize)) {}
- PackedImage(size_t xsize, size_t ysize, const JxlPixelFormat& format,
- size_t stride)
- : xsize(xsize),
- ysize(ysize),
- stride(stride),
- format(format),
- pixels_size(ysize * stride),
- pixels_(malloc(std::max<size_t>(1, pixels_size)), free) {}
- // Construct the image using the passed pixel buffer. The buffer is owned by
- // this object and released with free().
- PackedImage(size_t xsize, size_t ysize, const JxlPixelFormat& format,
- void* pixels, size_t pixels_size)
- : xsize(xsize),
- ysize(ysize),
- stride(CalcStride(format, xsize)),
- format(format),
- pixels_size(pixels_size),
- pixels_(pixels, free) {
- JXL_ASSERT(pixels_size >= stride * ysize);
- }
// The interleaved pixels as defined in the storage format.
void* pixels() const { return pixels_.get(); }
@@ -61,15 +40,6 @@ class PackedImage {
size_t xsize;
size_t ysize;
- // Whether the y coordinate is flipped (y=0 is the last row).
- bool flipped_y = false;
-
- // Whether the range is determined by format or by JxlBasicInfo
- // e.g. if format is UINT16 and JxlBasicInfo bits_per_sample is 10,
- // then if bitdepth_from_format == true, the range is 0..65535
- // while if bitdepth_from_format == false, the range is 0..1023.
- bool bitdepth_from_format = true;
-
// The number of bytes per row.
size_t stride;
@@ -77,6 +47,11 @@ class PackedImage {
JxlPixelFormat format;
size_t pixels_size;
+ size_t pixel_stride() const {
+ return (BitsPerChannel(format.data_type) * format.num_channels /
+ jxl::kBitsPerByte);
+ }
+
static size_t BitsPerChannel(JxlDataType data_type) {
switch (data_type) {
case JXL_TYPE_UINT8:
@@ -93,6 +68,15 @@ class PackedImage {
}
private:
+ PackedImage(size_t xsize, size_t ysize, const JxlPixelFormat& format,
+ size_t stride)
+ : xsize(xsize),
+ ysize(ysize),
+ stride(stride),
+ format(format),
+ pixels_size(ysize * stride),
+ pixels_(malloc(std::max<size_t>(1, pixels_size)), free) {}
+
static size_t CalcStride(const JxlPixelFormat& format, size_t xsize) {
size_t stride = xsize * (BitsPerChannel(format.data_type) *
format.num_channels / jxl::kBitsPerByte);
@@ -140,20 +124,20 @@ class PackedPixelFile {
// The extra channel metadata information.
struct PackedExtraChannel {
- PackedExtraChannel(const JxlExtraChannelInfo& ec_info,
- const std::string& name)
- : ec_info(ec_info), name(name) {}
-
JxlExtraChannelInfo ec_info;
+ size_t index;
std::string name;
};
std::vector<PackedExtraChannel> extra_channels_info;
- // Color information. If the icc is empty, the JxlColorEncoding should be used
- // instead.
+ // Color information of the decoded pixels.
+ // If the icc is empty, the JxlColorEncoding should be used instead.
std::vector<uint8_t> icc;
JxlColorEncoding color_encoding = {};
+ // The icc profile of the original image.
+ std::vector<uint8_t> orig_icc;
+ std::unique_ptr<PackedFrame> preview_frame;
std::vector<PackedFrame> frames;
PackedMetadata metadata;
diff --git a/media/libjxl/src/lib/extras/packed_image_convert.cc b/media/libjxl/src/lib/extras/packed_image_convert.cc
index 65336a70a1..dcdd12a673 100644
--- a/media/libjxl/src/lib/extras/packed_image_convert.cc
+++ b/media/libjxl/src/lib/extras/packed_image_convert.cc
@@ -20,6 +20,60 @@
namespace jxl {
namespace extras {
+Status ConvertPackedFrameToImageBundle(const JxlBasicInfo& info,
+ const PackedFrame& frame,
+ const CodecInOut& io, ThreadPool* pool,
+ ImageBundle* bundle) {
+ JXL_ASSERT(frame.color.pixels() != nullptr);
+ const bool float_in = frame.color.format.data_type == JXL_TYPE_FLOAT16 ||
+ frame.color.format.data_type == JXL_TYPE_FLOAT;
+ size_t frame_bits_per_sample =
+ float_in ? PackedImage::BitsPerChannel(frame.color.format.data_type)
+ : info.bits_per_sample;
+ JXL_ASSERT(frame_bits_per_sample != 0);
+ // It is ok for the frame.color.format.num_channels to not match the
+ // number of channels on the image.
+ JXL_ASSERT(1 <= frame.color.format.num_channels &&
+ frame.color.format.num_channels <= 4);
+
+ const Span<const uint8_t> span(
+ static_cast<const uint8_t*>(frame.color.pixels()),
+ frame.color.pixels_size);
+ JXL_ASSERT(Rect(frame.frame_info.layer_info.crop_x0,
+ frame.frame_info.layer_info.crop_y0,
+ frame.frame_info.layer_info.xsize,
+ frame.frame_info.layer_info.ysize)
+ .IsInside(Rect(0, 0, info.xsize, info.ysize)));
+ if (info.have_animation) {
+ bundle->duration = frame.frame_info.duration;
+ bundle->blend = frame.frame_info.layer_info.blend_info.blendmode > 0;
+ bundle->use_for_next_frame =
+ frame.frame_info.layer_info.save_as_reference > 0;
+ bundle->origin.x0 = frame.frame_info.layer_info.crop_x0;
+ bundle->origin.y0 = frame.frame_info.layer_info.crop_y0;
+ }
+ bundle->name = frame.name; // frame.frame_info.name_length is ignored here.
+ JXL_ASSERT(io.metadata.m.color_encoding.IsGray() ==
+ (frame.color.format.num_channels <= 2));
+
+ JXL_RETURN_IF_ERROR(ConvertFromExternal(
+ span, frame.color.xsize, frame.color.ysize, io.metadata.m.color_encoding,
+ frame.color.format.num_channels,
+ /*alpha_is_premultiplied=*/info.alpha_premultiplied,
+ frame_bits_per_sample, frame.color.format.endianness, pool, bundle,
+ /*float_in=*/float_in, /*align=*/0));
+
+ bundle->extra_channels().resize(io.metadata.m.extra_channel_info.size());
+ for (size_t i = 0; i < frame.extra_channels.size(); i++) {
+ const auto& ppf_ec = frame.extra_channels[i];
+ bundle->extra_channels()[i] = ImageF(ppf_ec.xsize, ppf_ec.ysize);
+ JXL_CHECK(BufferToImageF(ppf_ec.format, ppf_ec.xsize, ppf_ec.ysize,
+ ppf_ec.pixels(), ppf_ec.pixels_size, pool,
+ &bundle->extra_channels()[i]));
+ }
+ return true;
+}
+
Status ConvertPackedPixelFileToCodecInOut(const PackedPixelFile& ppf,
ThreadPool* pool, CodecInOut* io) {
const bool has_alpha = ppf.info.alpha_bits != 0;
@@ -63,7 +117,7 @@ Status ConvertPackedPixelFileToCodecInOut(const PackedPixelFile& ppf,
PaddedBytes icc;
icc.append(ppf.icc);
if (!io->metadata.m.color_encoding.SetICC(std::move(icc))) {
- fprintf(stderr, "Warning: error setting ICC profile, assuming SRGB");
+ fprintf(stderr, "Warning: error setting ICC profile, assuming SRGB\n");
io->metadata.m.color_encoding = ColorEncoding::SRGB(is_gray);
} else {
if (io->metadata.m.color_encoding.IsGray() != is_gray) {
@@ -105,61 +159,24 @@ Status ConvertPackedPixelFileToCodecInOut(const PackedPixelFile& ppf,
io->metadata.m.extra_channel_info.push_back(std::move(out));
}
+ // Convert the preview
+ if (ppf.preview_frame) {
+ size_t preview_xsize = ppf.preview_frame->color.xsize;
+ size_t preview_ysize = ppf.preview_frame->color.ysize;
+ io->metadata.m.have_preview = true;
+ JXL_RETURN_IF_ERROR(
+ io->metadata.m.preview_size.Set(preview_xsize, preview_ysize));
+ JXL_RETURN_IF_ERROR(ConvertPackedFrameToImageBundle(
+ ppf.info, *ppf.preview_frame, *io, pool, &io->preview_frame));
+ }
+
// Convert the pixels
io->dec_pixels = 0;
io->frames.clear();
for (const auto& frame : ppf.frames) {
- JXL_ASSERT(frame.color.pixels() != nullptr);
- size_t frame_bits_per_sample =
- (frame.color.bitdepth_from_format
- ? frame.color.BitsPerChannel(frame.color.format.data_type)
- : ppf.info.bits_per_sample);
- JXL_ASSERT(frame_bits_per_sample != 0);
- // It is ok for the frame.color.format.num_channels to not match the
- // number of channels on the image.
- JXL_ASSERT(1 <= frame.color.format.num_channels &&
- frame.color.format.num_channels <= 4);
-
- const Span<const uint8_t> span(
- static_cast<const uint8_t*>(frame.color.pixels()),
- frame.color.pixels_size);
- Rect frame_rect = Rect(frame.frame_info.layer_info.crop_x0,
- frame.frame_info.layer_info.crop_y0,
- frame.frame_info.layer_info.xsize,
- frame.frame_info.layer_info.ysize);
- JXL_ASSERT(frame_rect.IsInside(Rect(0, 0, ppf.info.xsize, ppf.info.ysize)));
ImageBundle bundle(&io->metadata.m);
- if (ppf.info.have_animation) {
- bundle.duration = frame.frame_info.duration;
- bundle.blend = frame.frame_info.layer_info.blend_info.blendmode > 0;
- bundle.use_for_next_frame =
- frame.frame_info.layer_info.save_as_reference > 0;
- bundle.origin.x0 = frame.frame_info.layer_info.crop_x0;
- bundle.origin.y0 = frame.frame_info.layer_info.crop_y0;
- }
- bundle.name = frame.name; // frame.frame_info.name_length is ignored here.
- JXL_ASSERT(io->metadata.m.color_encoding.IsGray() ==
- (frame.color.format.num_channels <= 2));
-
- const bool float_in = frame.color.format.data_type == JXL_TYPE_FLOAT16 ||
- frame.color.format.data_type == JXL_TYPE_FLOAT;
- JXL_RETURN_IF_ERROR(ConvertFromExternal(
- span, frame.color.xsize, frame.color.ysize,
- io->metadata.m.color_encoding, frame.color.format.num_channels,
- /*alpha_is_premultiplied=*/ppf.info.alpha_premultiplied,
- frame_bits_per_sample, frame.color.format.endianness,
- /*flipped_y=*/frame.color.flipped_y, pool, &bundle,
- /*float_in=*/float_in, /*align=*/0));
-
- bundle.extra_channels().resize(io->metadata.m.extra_channel_info.size());
- for (size_t i = 0; i < frame.extra_channels.size(); i++) {
- const auto& ppf_ec = frame.extra_channels[i];
- bundle.extra_channels()[i] = ImageF(ppf_ec.xsize, ppf_ec.ysize);
- JXL_CHECK(BufferToImageF(ppf_ec.format, ppf_ec.xsize, ppf_ec.ysize,
- ppf_ec.pixels(), ppf_ec.pixels_size, pool,
- &bundle.extra_channels()[i]));
- }
-
+ JXL_RETURN_IF_ERROR(
+ ConvertPackedFrameToImageBundle(ppf.info, frame, *io, pool, &bundle));
io->frames.push_back(std::move(bundle));
io->dec_pixels += frame.color.xsize * frame.color.ysize;
}
@@ -222,9 +239,7 @@ Status ConvertCodecInOutToPackedPixelFile(const CodecInOut& io,
// Convert the color encoding
ppf->icc.assign(c_desired.ICC().begin(), c_desired.ICC().end());
- if (ppf->icc.empty()) {
- ConvertInternalToExternalColorEncoding(c_desired, &ppf->color_encoding);
- }
+ ConvertInternalToExternalColorEncoding(c_desired, &ppf->color_encoding);
// Convert the extra blobs
ppf->metadata.exif = io.blobs.exif;
@@ -236,8 +251,7 @@ Status ConvertCodecInOutToPackedPixelFile(const CodecInOut& io,
// Convert the pixels
ppf->frames.clear();
for (const auto& frame : io.frames) {
- size_t frame_bits_per_sample = frame.metadata()->bit_depth.bits_per_sample;
- JXL_ASSERT(frame_bits_per_sample != 0);
+ JXL_ASSERT(frame.metadata()->bit_depth.bits_per_sample != 0);
// It is ok for the frame.color().kNumPlanes to not match the
// number of channels on the image.
const uint32_t num_channels =
@@ -249,11 +263,9 @@ Status ConvertCodecInOutToPackedPixelFile(const CodecInOut& io,
PackedFrame packed_frame(frame.oriented_xsize(), frame.oriented_ysize(),
format);
- packed_frame.color.bitdepth_from_format = float_out;
const size_t bits_per_sample =
- packed_frame.color.bitdepth_from_format
- ? packed_frame.color.BitsPerChannel(pixel_format.data_type)
- : ppf->info.bits_per_sample;
+ float_out ? packed_frame.color.BitsPerChannel(pixel_format.data_type)
+ : ppf->info.bits_per_sample;
packed_frame.name = frame.name;
packed_frame.frame_info.name_length = frame.name.size();
// Color transform
diff --git a/media/libjxl/src/lib/extras/render_hdr.cc b/media/libjxl/src/lib/extras/render_hdr.cc
new file mode 100644
index 0000000000..b247699cd3
--- /dev/null
+++ b/media/libjxl/src/lib/extras/render_hdr.cc
@@ -0,0 +1,60 @@
+// Copyright (c) the JPEG XL Project Authors. All rights reserved.
+//
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+#include "lib/extras/render_hdr.h"
+
+#include "lib/extras/hlg.h"
+#include "lib/extras/tone_mapping.h"
+#include "lib/jxl/enc_color_management.h"
+
+namespace jxl {
+
+Status RenderHDR(CodecInOut* io, float display_nits, ThreadPool* pool) {
+ const ColorEncoding& original_color_encoding = io->metadata.m.color_encoding;
+ if (!(original_color_encoding.tf.IsPQ() ||
+ original_color_encoding.tf.IsHLG())) {
+ // Nothing to do.
+ return true;
+ }
+
+ if (original_color_encoding.tf.IsPQ()) {
+ JXL_RETURN_IF_ERROR(ToneMapTo({0, display_nits}, io, pool));
+ JXL_RETURN_IF_ERROR(GamutMap(io, /*preserve_saturation=*/0.1, pool));
+ } else {
+ const float intensity_target = io->metadata.m.IntensityTarget();
+ const float gamma_hlg_to_display = GetHlgGamma(display_nits);
+ // If the image is already in display space, we need to account for the
+ // already-applied OOTF.
+ const float gamma_display_to_display =
+ gamma_hlg_to_display / GetHlgGamma(intensity_target);
+ // Ensures that conversions to linear in HlgOOTF below will not themselves
+ // include the OOTF.
+ io->metadata.m.SetIntensityTarget(300);
+
+ bool need_gamut_mapping = false;
+ for (ImageBundle& ib : io->frames) {
+ const float gamma = ib.c_current().tf.IsHLG() ? gamma_hlg_to_display
+ : gamma_display_to_display;
+ if (gamma < 1) need_gamut_mapping = true;
+ JXL_RETURN_IF_ERROR(HlgOOTF(&ib, gamma, pool));
+ }
+ io->metadata.m.SetIntensityTarget(display_nits);
+
+ if (need_gamut_mapping) {
+ JXL_RETURN_IF_ERROR(GamutMap(io, /*preserve_saturation=*/0.1, pool));
+ }
+ }
+
+ ColorEncoding rec2020_pq;
+ rec2020_pq.SetColorSpace(ColorSpace::kRGB);
+ rec2020_pq.white_point = WhitePoint::kD65;
+ rec2020_pq.primaries = Primaries::k2100;
+ rec2020_pq.tf.SetTransferFunction(TransferFunction::kPQ);
+ JXL_RETURN_IF_ERROR(rec2020_pq.CreateICC());
+ io->metadata.m.color_encoding = rec2020_pq;
+ return io->TransformTo(rec2020_pq, GetJxlCms(), pool);
+}
+
+} // namespace jxl
diff --git a/media/libjxl/src/lib/extras/render_hdr.h b/media/libjxl/src/lib/extras/render_hdr.h
new file mode 100644
index 0000000000..95127e074b
--- /dev/null
+++ b/media/libjxl/src/lib/extras/render_hdr.h
@@ -0,0 +1,27 @@
+// Copyright (c) the JPEG XL Project Authors. All rights reserved.
+//
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+#ifndef LIB_EXTRAS_RENDER_HDR_H_
+#define LIB_EXTRAS_RENDER_HDR_H_
+
+#include "lib/jxl/codec_in_out.h"
+
+namespace jxl {
+
+// If `io` has an original color space using PQ or HLG, this renders it
+// appropriately for a display with a peak luminance of `display_nits` and
+// converts the result to a Rec. 2020 / PQ image. Otherwise, leaves the image as
+// is.
+// PQ images are tone-mapped using the method described in Rep. ITU-R BT.2408-5
+// annex 5, while HLG images are rendered using the HLG OOTF with a gamma
+// appropriate for the given target luminance.
+// With a sufficiently bright SDR display, converting the output of this
+// function to an SDR colorspace may look decent.
+Status RenderHDR(CodecInOut* io, float display_nits,
+ ThreadPool* pool = nullptr);
+
+} // namespace jxl
+
+#endif // LIB_EXTRAS_RENDER_HDR_H_
diff --git a/media/libjxl/src/lib/extras/tone_mapping.cc b/media/libjxl/src/lib/extras/tone_mapping.cc
index ac4306f8ed..1ed1b29119 100644
--- a/media/libjxl/src/lib/extras/tone_mapping.cc
+++ b/media/libjxl/src/lib/extras/tone_mapping.cc
@@ -10,13 +10,15 @@
#include <hwy/foreach_target.h>
#include <hwy/highway.h>
+#include "lib/jxl/dec_tone_mapping-inl.h"
#include "lib/jxl/enc_color_management.h"
-#include "lib/jxl/transfer_functions-inl.h"
HWY_BEFORE_NAMESPACE();
namespace jxl {
namespace HWY_NAMESPACE {
+static constexpr float rec2020_luminances[3] = {0.2627f, 0.6780f, 0.0593f};
+
Status ToneMapFrame(const std::pair<float, float> display_nits,
ImageBundle* const ib, ThreadPool* const pool) {
// Perform tone mapping as described in Report ITU-R BT.2390-8, section 5.4
@@ -34,42 +36,12 @@ Status ToneMapFrame(const std::pair<float, float> display_nits,
JXL_RETURN_IF_ERROR(linear_rec2020.CreateICC());
JXL_RETURN_IF_ERROR(ib->TransformTo(linear_rec2020, GetJxlCms(), pool));
- const auto eotf_inv = [&df](const V luminance) -> V {
- return TF_PQ().EncodedFromDisplay(df, luminance * Set(df, 1. / 10000));
- };
-
- const V pq_mastering_min =
- eotf_inv(Set(df, ib->metadata()->tone_mapping.min_nits));
- const V pq_mastering_max =
- eotf_inv(Set(df, ib->metadata()->tone_mapping.intensity_target));
- const V pq_mastering_range = pq_mastering_max - pq_mastering_min;
- const V inv_pq_mastering_range =
- Set(df, 1) / (pq_mastering_max - pq_mastering_min);
- const V min_lum = (eotf_inv(Set(df, display_nits.first)) - pq_mastering_min) *
- inv_pq_mastering_range;
- const V max_lum =
- (eotf_inv(Set(df, display_nits.second)) - pq_mastering_min) *
- inv_pq_mastering_range;
- const V ks = MulAdd(Set(df, 1.5f), max_lum, Set(df, -0.5f));
- const V b = min_lum;
-
- const V inv_one_minus_ks = Set(df, 1) / Max(Set(df, 1e-6f), Set(df, 1) - ks);
- const auto T = [ks, inv_one_minus_ks](const V a) {
- return (a - ks) * inv_one_minus_ks;
- };
- const auto P = [&T, &df, ks, max_lum](const V b) {
- const V t_b = T(b);
- const V t_b_2 = t_b * t_b;
- const V t_b_3 = t_b_2 * t_b;
- return MulAdd(
- MulAdd(Set(df, 2), t_b_3, MulAdd(Set(df, -3), t_b_2, Set(df, 1))), ks,
- MulAdd(t_b_3 + MulAdd(Set(df, -2), t_b_2, t_b), Set(df, 1) - ks,
- MulAdd(Set(df, -2), t_b_3, Set(df, 3) * t_b_2) * max_lum));
- };
-
- const V inv_max_display_nits = Set(df, 1 / display_nits.second);
+ Rec2408ToneMapper<decltype(df)> tone_mapper(
+ {ib->metadata()->tone_mapping.min_nits,
+ ib->metadata()->IntensityTarget()},
+ display_nits, rec2020_luminances);
- JXL_RETURN_IF_ERROR(RunOnPool(
+ return RunOnPool(
pool, 0, ib->ysize(), ThreadPool::NoInit,
[&](const uint32_t y, size_t /* thread */) {
float* const JXL_RESTRICT row_r = ib->color()->PlaneRow(0, y);
@@ -79,43 +51,13 @@ Status ToneMapFrame(const std::pair<float, float> display_nits,
V red = Load(df, row_r + x);
V green = Load(df, row_g + x);
V blue = Load(df, row_b + x);
- const V luminance = Set(df, ib->metadata()->IntensityTarget()) *
- (MulAdd(Set(df, 0.2627f), red,
- MulAdd(Set(df, 0.6780f), green,
- Set(df, 0.0593f) * blue)));
- const V normalized_pq =
- Min(Set(df, 1.f), (eotf_inv(luminance) - pq_mastering_min) *
- inv_pq_mastering_range);
- const V e2 =
- IfThenElse(normalized_pq < ks, normalized_pq, P(normalized_pq));
- const V one_minus_e2 = Set(df, 1) - e2;
- const V one_minus_e2_2 = one_minus_e2 * one_minus_e2;
- const V one_minus_e2_4 = one_minus_e2_2 * one_minus_e2_2;
- const V e3 = MulAdd(b, one_minus_e2_4, e2);
- const V e4 = MulAdd(e3, pq_mastering_range, pq_mastering_min);
- const V new_luminance =
- Min(Set(df, display_nits.second),
- ZeroIfNegative(Set(df, 10000) *
- TF_PQ().DisplayFromEncoded(df, e4)));
-
- const V ratio = new_luminance / luminance;
- const V normalizer =
- Set(df, ib->metadata()->IntensityTarget()) * inv_max_display_nits;
-
- for (V* const val : {&red, &green, &blue}) {
- *val = IfThenElse(luminance <= Set(df, 1e-6f), new_luminance,
- *val * ratio) *
- normalizer;
- }
-
+ tone_mapper.ToneMap(&red, &green, &blue);
Store(red, df, row_r + x);
Store(green, df, row_g + x);
Store(blue, df, row_b + x);
}
},
- "ToneMap"));
-
- return true;
+ "ToneMap");
}
Status GamutMapFrame(ImageBundle* const ib, float preserve_saturation,
@@ -141,44 +83,8 @@ Status GamutMapFrame(ImageBundle* const ib, float preserve_saturation,
V red = Load(df, row_r + x);
V green = Load(df, row_g + x);
V blue = Load(df, row_b + x);
- const V luminance =
- MulAdd(Set(df, 0.2627f), red,
- MulAdd(Set(df, 0.6780f), green, Set(df, 0.0593f) * blue));
-
- // Desaturate out-of-gamut pixels. This is done by mixing each pixel
- // with just enough gray of the target luminance to make all
- // components non-negative.
- // - For saturation preservation, if a component is still larger than
- // 1 then the pixel is normalized to have a maximum component of 1.
- // That will reduce its luminance.
- // - For luminance preservation, getting all components below 1 is
- // done by mixing in yet more gray. That will desaturate it further.
- V gray_mix_saturation = Zero(df);
- V gray_mix_luminance = Zero(df);
- for (const V val : {red, green, blue}) {
- const V inv_val_minus_gray = Set(df, 1) / (val - luminance);
- gray_mix_saturation =
- IfThenElse(val >= luminance, gray_mix_saturation,
- Max(gray_mix_saturation, val * inv_val_minus_gray));
- gray_mix_luminance =
- Max(gray_mix_luminance,
- IfThenElse(val <= luminance, gray_mix_saturation,
- (val - Set(df, 1)) * inv_val_minus_gray));
- }
- const V gray_mix =
- Clamp(Set(df, preserve_saturation) *
- (gray_mix_saturation - gray_mix_luminance) +
- gray_mix_luminance,
- Zero(df), Set(df, 1));
- for (V* const val : {&red, &green, &blue}) {
- *val = MulAdd(gray_mix, luminance - *val, *val);
- }
- const V normalizer =
- Set(df, 1) / Max(Set(df, 1), Max(red, Max(green, blue)));
- for (V* const val : {&red, &green, &blue}) {
- *val = *val * normalizer;
- }
-
+ GamutMap(&red, &green, &blue, rec2020_luminances,
+ preserve_saturation);
Store(red, df, row_r + x);
Store(green, df, row_g + x);
Store(blue, df, row_b + x);
diff --git a/media/libjxl/src/lib/extras/tone_mapping_gbench.cc b/media/libjxl/src/lib/extras/tone_mapping_gbench.cc
index b156992813..2f97b88667 100644
--- a/media/libjxl/src/lib/extras/tone_mapping_gbench.cc
+++ b/media/libjxl/src/lib/extras/tone_mapping_gbench.cc
@@ -13,8 +13,7 @@ namespace jxl {
static void BM_ToneMapping(benchmark::State& state) {
CodecInOut image;
- const PaddedBytes image_bytes =
- ReadTestData("third_party/imagecompression.info/flower_foveon.png");
+ const PaddedBytes image_bytes = ReadTestData("jxl/flower/flower.png");
JXL_CHECK(SetFromBytes(Span<const uint8_t>(image_bytes), &image));
// Convert to linear Rec. 2020 so that `ToneMapTo` doesn't have to and we
diff --git a/media/libjxl/src/lib/include/jxl/cms_interface.h b/media/libjxl/src/lib/include/jxl/cms_interface.h
index 50ee406865..fb852eeb1f 100644
--- a/media/libjxl/src/lib/include/jxl/cms_interface.h
+++ b/media/libjxl/src/lib/include/jxl/cms_interface.h
@@ -19,7 +19,7 @@
#define JXL_CMS_INTERFACE_H_
#include "jxl/color_encoding.h"
-#include "jxl/memory_manager.h"
+#include "jxl/types.h"
#if defined(__cplusplus) || defined(c_plusplus)
extern "C" {
diff --git a/media/libjxl/src/lib/include/jxl/codestream_header.h b/media/libjxl/src/lib/include/jxl/codestream_header.h
index 536b5a3aa6..d126577870 100644
--- a/media/libjxl/src/lib/include/jxl/codestream_header.h
+++ b/media/libjxl/src/lib/include/jxl/codestream_header.h
@@ -18,7 +18,6 @@
#include <stddef.h>
#include <stdint.h>
-#include "jxl/color_encoding.h"
#include "jxl/types.h"
#if defined(__cplusplus) || defined(c_plusplus)
@@ -175,10 +174,14 @@ typedef struct {
* (linear if outputting to floating point, nonlinear with standard sRGB
* transfer function if outputting to unsigned integers) but will not convert
* it to to the original color profile. The decoder also does not convert to
- * the target display color profile, but instead will always indicate which
- * color profile the returned pixel data is encoded in when using @see
- * JXL_COLOR_PROFILE_TARGET_DATA so that a CMS can be used to convert the
- * data.
+ * the target display color profile. To convert the pixel data produced by
+ * the decoder to the original color profile, one of the JxlDecoderGetColor*
+ * functions needs to be called with @ref JXL_COLOR_PROFILE_TARGET_DATA to get
+ * the color profile of the decoder output, and then an external CMS can be
+ * used for conversion.
+ * Note that for lossy compression, this should be set to false for most use
+ * cases, and if needed, the image should be converted to the original color
+ * profile after decoding, as described above.
*/
JXL_BOOL uses_original_profile;
diff --git a/media/libjxl/src/lib/include/jxl/color_encoding.h b/media/libjxl/src/lib/include/jxl/color_encoding.h
index 2815c53bb5..b16f6a01ee 100644
--- a/media/libjxl/src/lib/include/jxl/color_encoding.h
+++ b/media/libjxl/src/lib/include/jxl/color_encoding.h
@@ -16,8 +16,6 @@
#include <stdint.h>
-#include "jxl/types.h"
-
#if defined(__cplusplus) || defined(c_plusplus)
extern "C" {
#endif
@@ -90,7 +88,7 @@ typedef enum {
JXL_TRANSFER_FUNCTION_LINEAR = 8,
/** As specified in IEC 61966-2-1 sRGB */
JXL_TRANSFER_FUNCTION_SRGB = 13,
- /** As specified in SMPTE ST 428-1 */
+ /** As specified in SMPTE ST 2084 */
JXL_TRANSFER_FUNCTION_PQ = 16,
/** As specified in SMPTE ST 428-1 */
JXL_TRANSFER_FUNCTION_DCI = 17,
diff --git a/media/libjxl/src/lib/include/jxl/decode.h b/media/libjxl/src/lib/include/jxl/decode.h
index 9b9d2f0e30..66820bfcd9 100644
--- a/media/libjxl/src/lib/include/jxl/decode.h
+++ b/media/libjxl/src/lib/include/jxl/decode.h
@@ -16,13 +16,13 @@
#include <stddef.h>
#include <stdint.h>
-#include "jxl/cms_interface.h"
#include "jxl/codestream_header.h"
#include "jxl/color_encoding.h"
#include "jxl/jxl_export.h"
#include "jxl/memory_manager.h"
#include "jxl/parallel_runner.h"
#include "jxl/types.h"
+#include "jxl/version.h"
#if defined(__cplusplus) || defined(c_plusplus)
extern "C" {
@@ -37,14 +37,14 @@ extern "C" {
*/
JXL_EXPORT uint32_t JxlDecoderVersion(void);
-/** The result of JxlSignatureCheck.
+/** The result of @ref JxlSignatureCheck.
*/
typedef enum {
/** Not enough bytes were passed to determine if a valid signature was found.
*/
JXL_SIG_NOT_ENOUGH_BYTES = 0,
- /** No valid JPEGXL header was found. */
+ /** No valid JPEG XL header was found. */
JXL_SIG_INVALID = 1,
/** A valid JPEG XL codestream signature was found, that is a JPEG XL image
@@ -66,61 +66,72 @@ typedef enum {
* @p size doesn't need to be a full image, only the beginning of the file.
*
* @return a flag indicating if a JPEG XL signature was found and what type.
- * - JXL_SIG_NOT_ENOUGH_BYTES not enough bytes were passed to determine
- * if a valid signature is there.
- * - JXL_SIG_INVALID: no valid signature found for JPEG XL decoding.
- * - JXL_SIG_CODESTREAM a valid JPEG XL codestream signature was found.
- * - JXL_SIG_CONTAINER a valid JPEG XL container signature was found.
+ * - @ref JXL_SIG_NOT_ENOUGH_BYTES if not enough bytes were passed to
+ * determine if a valid signature is there.
+ * - @ref JXL_SIG_INVALID if no valid signature found for JPEG XL decoding.
+ * - @ref JXL_SIG_CODESTREAM if a valid JPEG XL codestream signature was
+ * found.
+ * - @ref JXL_SIG_CONTAINER if a valid JPEG XL container signature was found.
*/
JXL_EXPORT JxlSignature JxlSignatureCheck(const uint8_t* buf, size_t len);
/**
- * Opaque structure that holds the JPEGXL decoder.
+ * Opaque structure that holds the JPEG XL decoder.
*
- * Allocated and initialized with JxlDecoderCreate().
- * Cleaned up and deallocated with JxlDecoderDestroy().
+ * Allocated and initialized with @ref JxlDecoderCreate().
+ * Cleaned up and deallocated with @ref JxlDecoderDestroy().
*/
typedef struct JxlDecoderStruct JxlDecoder;
/**
- * Creates an instance of JxlDecoder and initializes it.
+ * Creates an instance of @ref JxlDecoder and initializes it.
*
* @p memory_manager will be used for all the library dynamic allocations made
* from this instance. The parameter may be NULL, in which case the default
- * allocator will be used. See jpegxl/memory_manager.h for details.
+ * allocator will be used. See jxl/memory_manager.h for details.
*
* @param memory_manager custom allocator function. It may be NULL. The memory
* manager will be copied internally.
* @return @c NULL if the instance can not be allocated or initialized
- * @return pointer to initialized JxlDecoder otherwise
+ * @return pointer to initialized @ref JxlDecoder otherwise
*/
JXL_EXPORT JxlDecoder* JxlDecoderCreate(const JxlMemoryManager* memory_manager);
/**
- * Re-initializes a JxlDecoder instance, so it can be re-used for decoding
+ * Re-initializes a @ref JxlDecoder instance, so it can be re-used for decoding
* another image. All state and settings are reset as if the object was
- * newly created with JxlDecoderCreate, but the memory manager is kept.
+ * newly created with @ref JxlDecoderCreate, but the memory manager is kept.
*
* @param dec instance to be re-initialized.
*/
JXL_EXPORT void JxlDecoderReset(JxlDecoder* dec);
/**
- * Deinitializes and frees JxlDecoder instance.
+ * Deinitializes and frees @ref JxlDecoder instance.
*
* @param dec instance to be cleaned up and deallocated.
*/
JXL_EXPORT void JxlDecoderDestroy(JxlDecoder* dec);
/**
- * Return value for JxlDecoderProcessInput.
- * The values from JXL_DEC_BASIC_INFO onwards are optional informative
+ * Return value for @ref JxlDecoderProcessInput.
+ * The values from @ref JXL_DEC_BASIC_INFO onwards are optional informative
* events that can be subscribed to, they are never returned if they
- * have not been registered with JxlDecoderSubscribeEvents.
+ * have not been registered with @ref JxlDecoderSubscribeEvents.
*/
typedef enum {
/** Function call finished successfully, or decoding is finished and there is
* nothing more to be done.
+ *
+ * Note that @ref JxlDecoderProcessInput will return JXL_DEC_SUCCESS if all
+ * events that were registered with @ref JxlDecoderSubscribeEvents were
+ * processed, even before the end of the JPEG XL codestream.
+ *
+ * In this case, the return value @ref JxlDecoderReleaseInput will be the same
+ * as it was at the last signaled event. E.g. if JXL_DEC_FULL_IMAGE was
+ * subscribed to, then all bytes from the end of the JPEG XL codestream
+ * (including possible boxes needed for jpeg reconstruction) will be returned
+ * as unprocessed.
*/
JXL_DEC_SUCCESS = 0,
@@ -129,219 +140,296 @@ typedef enum {
*/
JXL_DEC_ERROR = 1,
- /** The decoder needs more input bytes to continue. Before the next
- * JxlDecoderProcessInput call, more input data must be set, by calling
- * JxlDecoderReleaseInput (if input was set previously) and then calling
- * JxlDecoderSetInput. JxlDecoderReleaseInput returns how many bytes are
- * not yet processed, before a next call to JxlDecoderProcessInput all
- * unprocessed bytes must be provided again (the address need not match, but
- * the contents must), and more bytes must be concatenated after the
+ /** The decoder needs more input bytes to continue. Before the next @ref
+ * JxlDecoderProcessInput call, more input data must be set, by calling @ref
+ * JxlDecoderReleaseInput (if input was set previously) and then calling @ref
+ * JxlDecoderSetInput. @ref JxlDecoderReleaseInput returns how many bytes
+ * are not yet processed, before a next call to @ref JxlDecoderProcessInput
+ * all unprocessed bytes must be provided again (the address need not match,
+ * but the contents must), and more bytes must be concatenated after the
* unprocessed bytes.
+ * In most cases, @ref JxlDecoderReleaseInput will return no unprocessed bytes
+ * at this event, the only exceptions are if the previously set input ended
+ * within (a) the raw codestream signature, (b) the signature box, (c) a box
+ * header, or (d) the first 4 bytes of a brob, ftyp, or jxlp box. In any of
+ * these cases the number of unprocessed bytes is less than 20.
*/
JXL_DEC_NEED_MORE_INPUT = 2,
/** The decoder is able to decode a preview image and requests setting a
- * preview output buffer using JxlDecoderSetPreviewOutBuffer. This occurs if
- * JXL_DEC_PREVIEW_IMAGE is requested and it is possible to decode a preview
- * image from the codestream and the preview out buffer was not yet set. There
- * is maximum one preview image in a codestream.
+ * preview output buffer using @ref JxlDecoderSetPreviewOutBuffer. This occurs
+ * if @ref JXL_DEC_PREVIEW_IMAGE is requested and it is possible to decode a
+ * preview image from the codestream and the preview out buffer was not yet
+ * set. There is maximum one preview image in a codestream.
+ * In this case, @ref JxlDecoderReleaseInput will return all bytes from the
+ * end of the frame header (including ToC) of the preview frame as
+ * unprocessed.
*/
JXL_DEC_NEED_PREVIEW_OUT_BUFFER = 3,
/** The decoder is able to decode a DC image and requests setting a DC output
- * buffer using JxlDecoderSetDCOutBuffer. This occurs if JXL_DEC_DC_IMAGE is
- * requested and it is possible to decode a DC image from the codestream and
- * the DC out buffer was not yet set. This event re-occurs for new frames
- * if there are multiple animation frames.
- * DEPRECATED: the DC feature in this form will be removed. For progressive
- * rendering, JxlDecoderFlushImage should be used.
+ * buffer using @ref JxlDecoderSetDCOutBuffer. This occurs if @ref
+ * JXL_DEC_DC_IMAGE is requested and it is possible to decode a DC image from
+ * the codestream and the DC out buffer was not yet set. This event re-occurs
+ * for new frames if there are multiple animation frames.
+ * @deprecated The DC feature in this form will be removed. For progressive
+ * rendering, @ref JxlDecoderFlushImage should be used.
*/
JXL_DEC_NEED_DC_OUT_BUFFER = 4,
/** The decoder requests an output buffer to store the full resolution image,
- * which can be set with JxlDecoderSetImageOutBuffer or with
- * JxlDecoderSetImageOutCallback. This event re-occurs for new frames if there
- * are multiple animation frames and requires setting an output again.
+ * which can be set with @ref JxlDecoderSetImageOutBuffer or with @ref
+ * JxlDecoderSetImageOutCallback. This event re-occurs for new frames if
+ * there are multiple animation frames and requires setting an output again.
+ * In this case, @ref JxlDecoderReleaseInput will return all bytes from the
+ * end of the frame header (including ToC) as unprocessed.
*/
JXL_DEC_NEED_IMAGE_OUT_BUFFER = 5,
/** The JPEG reconstruction buffer is too small for reconstructed JPEG
- * codestream to fit. JxlDecoderSetJPEGBuffer must be called again to make
- * room for remaining bytes. This event may occur multiple times after
- * JXL_DEC_JPEG_RECONSTRUCTION.
+ * codestream to fit. @ref JxlDecoderSetJPEGBuffer must be called again to
+ * make room for remaining bytes. This event may occur multiple times
+ * after @ref JXL_DEC_JPEG_RECONSTRUCTION.
*/
JXL_DEC_JPEG_NEED_MORE_OUTPUT = 6,
- /** The box contents output buffer is too small. JxlDecoderSetBoxBuffer must
- * be called again to make room for remaining bytes. This event may occur
- * multiple times after JXL_DEC_BOX.
+ /** The box contents output buffer is too small. @ref JxlDecoderSetBoxBuffer
+ * must be called again to make room for remaining bytes. This event may occur
+ * multiple times after @ref JXL_DEC_BOX.
*/
JXL_DEC_BOX_NEED_MORE_OUTPUT = 7,
- /** Informative event by JxlDecoderProcessInput: basic information such as
- * image dimensions and extra channels. This event occurs max once per image.
+ /** Informative event by @ref JxlDecoderProcessInput
+ * "JxlDecoderProcessInput": Basic information such as image dimensions and
+ * extra channels. This event occurs max once per image.
+ * In this case, @ref JxlDecoderReleaseInput will return all bytes from the
+ * end of the basic info as unprocessed (including the last byte of basic info
+ * if it did not end on a byte boundary).
*/
JXL_DEC_BASIC_INFO = 0x40,
- /** Informative event by JxlDecoderProcessInput: user extensions of the
- * codestream header. This event occurs max once per image and always later
- * than JXL_DEC_BASIC_INFO and earlier than any pixel data.
+ /** Informative event by @ref JxlDecoderProcessInput
+ * "JxlDecoderProcessInput": User extensions of the codestream header. This
+ * event occurs max once per image and always later than @ref
+ * JXL_DEC_BASIC_INFO and earlier than any pixel data.
+ *
+ * @deprecated The decoder no longer returns this, the header extensions,
+ * if any, are available at the JXL_DEC_BASIC_INFO event.
*/
JXL_DEC_EXTENSIONS = 0x80,
- /** Informative event by JxlDecoderProcessInput: color encoding or ICC
- * profile from the codestream header. This event occurs max once per image
- * and always later than JXL_DEC_BASIC_INFO and earlier than any pixel
- * data.
+ /** Informative event by @ref JxlDecoderProcessInput
+ * "JxlDecoderProcessInput": Color encoding or ICC profile from the
+ * codestream header. This event occurs max once per image and always later
+ * than @ref JXL_DEC_BASIC_INFO and earlier than any pixel data.
+ * In this case, @ref JxlDecoderReleaseInput will return all bytes from the
+ * end of the image header (which is the start of the first frame) as
+ * unprocessed.
*/
JXL_DEC_COLOR_ENCODING = 0x100,
- /** Informative event by JxlDecoderProcessInput: Preview image, a small
- * frame, decoded. This event can only happen if the image has a preview
- * frame encoded. This event occurs max once for the codestream and always
- * later than JXL_DEC_COLOR_ENCODING and before JXL_DEC_FRAME.
+ /** Informative event by @ref JxlDecoderProcessInput
+ * "JxlDecoderProcessInput": Preview image, a small frame, decoded. This
+ * event can only happen if the image has a preview frame encoded. This event
+ * occurs max once for the codestream and always later than @ref
+ * JXL_DEC_COLOR_ENCODING and before @ref JXL_DEC_FRAME.
+ * In this case, @ref JxlDecoderReleaseInput will return all bytes from the
+ * end of the preview frame as unprocessed.
*/
JXL_DEC_PREVIEW_IMAGE = 0x200,
- /** Informative event by JxlDecoderProcessInput: Beginning of a frame.
+ /** Informative event by @ref JxlDecoderProcessInput
+ * "JxlDecoderProcessInput": Beginning of a frame. @ref
* JxlDecoderGetFrameHeader can be used at this point. A note on frames:
* a JPEG XL image can have internal frames that are not intended to be
* displayed (e.g. used for compositing a final frame), but this only returns
- * displayed frames, unless JxlDecoderSetCoalescing was set to JXL_FALSE: in
- * that case, the individual layers are returned, without blending. Note that
- * even when coalescing is disabled, only frames of type kRegularFrame are
- * returned; frames of type kReferenceOnly and kLfFrame are always for
+ * displayed frames, unless @ref JxlDecoderSetCoalescing was set to JXL_FALSE:
+ * in that case, the individual layers are returned, without blending. Note
+ * that even when coalescing is disabled, only frames of type kRegularFrame
+ * are returned; frames of type kReferenceOnly and kLfFrame are always for
* internal purposes only and cannot be accessed. A displayed frame either has
* an animation duration or is the only or last frame in the image. This event
- * occurs max once per displayed frame, always later than
- * JXL_DEC_COLOR_ENCODING, and always earlier than any pixel data. While JPEG
- * XL supports encoding a single frame as the composition of multiple internal
- * sub-frames also called frames, this event is not indicated for the internal
- * frames.
+ * occurs max once per displayed frame, always later than @ref
+ * JXL_DEC_COLOR_ENCODING, and always earlier than any pixel data. While
+ * JPEG XL supports encoding a single frame as the composition of multiple
+ * internal sub-frames also called frames, this event is not indicated for the
+ * internal frames.
+ * In this case, @ref JxlDecoderReleaseInput will return all bytes from the
+ * end of the frame header (including ToC) as unprocessed.
*/
JXL_DEC_FRAME = 0x400,
- /** Informative event by JxlDecoderProcessInput: DC image, 8x8 sub-sampled
- * frame, decoded. It is not guaranteed that the decoder will always return DC
- * separately, but when it does it will do so before outputting the full
- * frame. JxlDecoderSetDCOutBuffer must be used after getting the basic
- * image information to be able to get the DC pixels, if not this return
- * status only indicates we're past this point in the codestream. This event
- * occurs max once per frame and always later than JXL_DEC_FRAME_HEADER
- * and other header events and earlier than full resolution pixel data.
- * DEPRECATED: the DC feature in this form will be removed. For progressive
- * rendering, JxlDecoderFlushImage should be used.
+ /** Informative event by @ref JxlDecoderProcessInput
+ * "JxlDecoderProcessInput": DC image, 8x8 sub-sampled frame, decoded. It is
+ * not guaranteed that the decoder will always return DC separately, but when
+ * it does it will do so before outputting the full frame. @ref
+ * JxlDecoderSetDCOutBuffer must be used after getting the basic image
+ * information to be able to get the DC pixels, if not this return status only
+ * indicates we're past this point in the codestream. This event occurs max
+ * once per frame and always later than @ref JXL_DEC_FRAME and other header
+ * events and earlier than full resolution pixel data.
+ *
+ * @deprecated The DC feature in this form will be removed. For progressive
+ * rendering, @ref JxlDecoderFlushImage should be used.
*/
JXL_DEC_DC_IMAGE = 0x800,
- /** Informative event by JxlDecoderProcessInput: full frame (or layer, in case
- * coalescing is disabled) is decoded. JxlDecoderSetImageOutBuffer must be
- * used after getting the basic image information to be able to get the image
- * pixels, if not this return status only indicates we're past this point in
- * the codestream. This event occurs max once per frame and always later than
+ /** Informative event by @ref JxlDecoderProcessInput
+ * "JxlDecoderProcessInput": full frame (or layer, in case coalescing is
+ * disabled) is decoded. @ref JxlDecoderSetImageOutBuffer must be used after
+ * getting the basic image information to be able to get the image pixels, if
+ * not this return status only indicates we're past this point in the
+ * codestream. This event occurs max once per frame and always later than @ref
* JXL_DEC_DC_IMAGE.
+ * In this case, @ref JxlDecoderReleaseInput will return all bytes from the
+ * end of the frame (or if @ref JXL_DEC_JPEG_RECONSTRUCTION is subscribed to,
+ * from the end of the last box that is needed for jpeg reconstruction) as
+ * unprocessed.
*/
JXL_DEC_FULL_IMAGE = 0x1000,
- /** Informative event by JxlDecoderProcessInput: JPEG reconstruction data
- * decoded. JxlDecoderSetJPEGBuffer may be used to set a JPEG
- * reconstruction buffer after getting the JPEG reconstruction data. If a JPEG
- * reconstruction buffer is set a byte stream identical to the JPEG codestream
- * used to encode the image will be written to the JPEG reconstruction buffer
- * instead of pixels to the image out buffer. This event occurs max once per
- * image and always before JXL_DEC_FULL_IMAGE.
+ /** Informative event by @ref JxlDecoderProcessInput
+ * "JxlDecoderProcessInput": JPEG reconstruction data decoded. @ref
+ * JxlDecoderSetJPEGBuffer may be used to set a JPEG reconstruction buffer
+ * after getting the JPEG reconstruction data. If a JPEG reconstruction buffer
+ * is set a byte stream identical to the JPEG codestream used to encode the
+ * image will be written to the JPEG reconstruction buffer instead of pixels
+ * to the image out buffer. This event occurs max once per image and always
+ * before @ref JXL_DEC_FULL_IMAGE.
+ * In this case, @ref JxlDecoderReleaseInput will return all bytes from the
+ * end of the 'jbrd' box as unprocessed.
*/
JXL_DEC_JPEG_RECONSTRUCTION = 0x2000,
- /** Informative event by JxlDecoderProcessInput: The header of a box of the
- * container format (BMFF) is decoded. The following API functions related to
- * boxes can be used after this event:
- * @see JxlDecoderSetBoxBuffer and JxlDecoderReleaseBoxBuffer: set and release
- * a buffer to get the box data.
- * @see JxlDecoderGetBoxType get the 4-character box typename.
- * @see JxlDecoderGetBoxSizeRaw get the size of the box as it appears in the
- * container file, not decompressed.
- * @see JxlDecoderSetDecompressBoxes to configure whether to get the box
- * data decompressed, or possibly compressed.
+ /** Informative event by @ref JxlDecoderProcessInput
+ * "JxlDecoderProcessInput": The header of a box of the container format
+ * (BMFF) is decoded. The following API functions related to boxes can be used
+ * after this event:
+ * - @ref JxlDecoderSetBoxBuffer and @ref JxlDecoderReleaseBoxBuffer
+ * "JxlDecoderReleaseBoxBuffer": set and release a buffer to get the box
+ * data.
+ * - @ref JxlDecoderGetBoxType get the 4-character box typename.
+ * - @ref JxlDecoderGetBoxSizeRaw get the size of the box as it appears in
+ * the container file, not decompressed.
+ * - @ref JxlDecoderSetDecompressBoxes to configure whether to get the box
+ * data decompressed, or possibly compressed.
*
* Boxes can be compressed. This is so when their box type is
* "brob". In that case, they have an underlying decompressed box
- * type and decompressed data. JxlDecoderSetDecompressBoxes allows
+ * type and decompressed data. @ref JxlDecoderSetDecompressBoxes allows
* configuring which data to get. Decompressing requires
- * Brotli. JxlDecoderGetBoxType has a flag to get the compressed box
+ * Brotli. @ref JxlDecoderGetBoxType has a flag to get the compressed box
* type, which can be "brob", or the decompressed box type. If a box
* is not compressed (its compressed type is not "brob"), then
* the output decompressed box type and data is independent of what
* setting is configured.
*
- * The buffer set with JxlDecoderSetBoxBuffer must be set again for each next
- * box to be obtained, or can be left unset to skip outputting this box.
- * The output buffer contains the full box data when the next JXL_DEC_BOX
- * event or JXL_DEC_SUCCESS occurs. JXL_DEC_BOX occurs for all boxes,
- * including non-metadata boxes such as the signature box or codestream boxes.
- * To check whether the box is a metadata type for respectively EXIF, XMP or
- * JUMBF, use JxlDecoderGetBoxType and check for types "Exif", "xml " and
- * "jumb" respectively.
+ * The buffer set with @ref JxlDecoderSetBoxBuffer must be set again for each
+ * next box to be obtained, or can be left unset to skip outputting this box.
+ * The output buffer contains the full box data when the next @ref JXL_DEC_BOX
+ * event or @ref JXL_DEC_SUCCESS occurs. @ref JXL_DEC_BOX occurs for all
+ * boxes, including non-metadata boxes such as the signature box or codestream
+ * boxes. To check whether the box is a metadata type for respectively EXIF,
+ * XMP or JUMBF, use @ref JxlDecoderGetBoxType and check for types "Exif",
+ * "xml " and "jumb" respectively.
+ *
+ * In this case, @ref JxlDecoderReleaseInput will return all bytes from the
+ * start of the box header as unprocessed.
*/
JXL_DEC_BOX = 0x4000,
- /** Informative event by JxlDecoderProcessInput: a progressive step in
- * decoding the frame is reached. When calling @ref JxlDecoderFlushImage at
- * this point, the flushed image will correspond exactly to this point in
- * decoding, and not yet contain partial results (such as partially more fine
- * detail) of a next step. By default, this event will trigger maximum once
- * per frame, when a 8x8th resolution (DC) image is ready (the image data is
- * still returned at full resolution, giving upscaled DC). Use @ref
+ /** Informative event by @ref JxlDecoderProcessInput
+ * "JxlDecoderProcessInput": a progressive step in decoding the frame is
+ * reached. When calling @ref JxlDecoderFlushImage at this point, the flushed
+ * image will correspond exactly to this point in decoding, and not yet
+ * contain partial results (such as partially more fine detail) of a next
+ * step. By default, this event will trigger maximum once per frame, when a
+ * 8x8th resolution (DC) image is ready (the image data is still returned at
+ * full resolution, giving upscaled DC). Use @ref
* JxlDecoderSetProgressiveDetail to configure more fine-grainedness. The
* event is not guaranteed to trigger, not all images have progressive steps
* or DC encoded.
+ * In this case, @ref JxlDecoderReleaseInput will return all bytes from the
+ * end of the section that was needed to produce this progressive event as
+ * unprocessed.
*/
JXL_DEC_FRAME_PROGRESSION = 0x8000,
} JxlDecoderStatus;
/** Rewinds decoder to the beginning. The same input must be given again from
* the beginning of the file and the decoder will emit events from the beginning
- * again. When rewinding (as opposed to JxlDecoderReset), the decoder can keep
- * state about the image, which it can use to skip to a requested frame more
- * efficiently with JxlDecoderSkipFrames. Settings such as parallel runner or
- * subscribed events are kept. After rewind, JxlDecoderSubscribeEvents can be
- * used again, and it is feasible to leave out events that were already handled
- * before, such as JXL_DEC_BASIC_INFO and JXL_DEC_COLOR_ENCODING, since they
- * will provide the same information as before.
+ * again. When rewinding (as opposed to @ref JxlDecoderReset), the decoder can
+ * keep state about the image, which it can use to skip to a requested frame
+ * more efficiently with @ref JxlDecoderSkipFrames. Settings such as parallel
+ * runner or subscribed events are kept. After rewind, @ref
+ * JxlDecoderSubscribeEvents can be used again, and it is feasible to leave out
+ * events that were already handled before, such as @ref JXL_DEC_BASIC_INFO
+ * and @ref JXL_DEC_COLOR_ENCODING, since they will provide the same information
+ * as before.
+ * The difference to @ref JxlDecoderReset is that some state is kept, namely
+ * settings set by a call to
+ * - @ref JxlDecoderSetCoalescing,
+ * - @ref JxlDecoderSetDesiredIntensityTarget,
+ * - @ref JxlDecoderSetDecompressBoxes,
+ * - @ref JxlDecoderSetKeepOrientation,
+ * - @ref JxlDecoderSetUnpremultiplyAlpha,
+ * - @ref JxlDecoderSetParallelRunner,
+ * - @ref JxlDecoderSetRenderSpotcolors, and
+ * - @ref JxlDecoderSubscribeEvents.
+ *
* @param dec decoder object
*/
JXL_EXPORT void JxlDecoderRewind(JxlDecoder* dec);
/** Makes the decoder skip the next `amount` frames. It still needs to process
* the input, but will not output the frame events. It can be more efficient
- * when skipping frames, and even more so when using this after
+ * when skipping frames, and even more so when using this after @ref
* JxlDecoderRewind. If the decoder is already processing a frame (could
- * have emitted JXL_DEC_FRAME but not yet JXL_DEC_FULL_IMAGE), it starts
- * skipping from the next frame. If the amount is larger than the amount of
- * frames remaining in the image, all remaining frames are skipped. Calling this
- * function multiple times adds the amount to skip to the already existing
+ * have emitted @ref JXL_DEC_FRAME but not yet @ref JXL_DEC_FULL_IMAGE), it
+ * starts skipping from the next frame. If the amount is larger than the amount
+ * of frames remaining in the image, all remaining frames are skipped. Calling
+ * this function multiple times adds the amount to skip to the already existing
* amount.
- * A frame here is defined as a frame that without skipping emits events such as
- * JXL_DEC_FRAME and JXL_FULL_IMAGE, frames that are internal to the file format
- * but are not rendered as part of an animation, or are not the final still
- * frame of a still image, are not counted.
+ *
+ * A frame here is defined as a frame that without skipping emits events such
+ * as @ref JXL_DEC_FRAME and @ref JXL_DEC_FULL_IMAGE, frames that are internal
+ * to the file format but are not rendered as part of an animation, or are not
+ * the final still frame of a still image, are not counted.
+ *
* @param dec decoder object
* @param amount the amount of frames to skip
*/
JXL_EXPORT void JxlDecoderSkipFrames(JxlDecoder* dec, size_t amount);
/**
+ * Skips processing the current frame. Can be called after frame processing
+ * already started, signaled by a @ref JXL_DEC_NEED_IMAGE_OUT_BUFFER event,
+ * but before the corrsponding @ref JXL_DEC_FULL_IMAGE event. The next signaled
+ * event will be another @ref JXL_DEC_FRAME, or @ref JXL_DEC_SUCCESS if there
+ * are no more frames. If pixel data is required from the already processed part
+ * of the frame, @ref JxlDecoderFlushImage must be called before this.
+ *
+ * @param dec decoder object
+ * @return @ref JXL_DEC_SUCCESS if there is a frame to skip, and @ref
+ * JXL_DEC_ERROR if the function was not called during frame processing.
+ */
+JXL_EXPORT JxlDecoderStatus JxlDecoderSkipCurrentFrame(JxlDecoder* dec);
+
+/**
* Get the default pixel format for this decoder.
*
* Requires that the decoder can produce JxlBasicInfo.
*
- * @param dec JxlDecoder to query when creating the recommended pixel format.
+ * @param dec @ref JxlDecoder to query when creating the recommended pixel
+ * format.
* @param format JxlPixelFormat to populate with the recommended settings for
- * the data loaded into this decoder.
- * @return JXL_DEC_SUCCESS if no error, JXL_DEC_NEED_MORE_INPUT if the
- * basic info isn't yet available, and JXL_DEC_ERROR otherwise.
+ * the data loaded into this decoder.
+ * @return @ref JXL_DEC_SUCCESS if no error, @ref JXL_DEC_NEED_MORE_INPUT if the
+ * basic info isn't yet available, and @ref JXL_DEC_ERROR otherwise.
+ *
+ * DEPRECATED: this function will be removed in the future.
*/
-JXL_EXPORT JxlDecoderStatus
+JXL_EXPORT JXL_DEPRECATED JxlDecoderStatus
JxlDecoderDefaultPixelFormat(const JxlDecoder* dec, JxlPixelFormat* format);
/**
@@ -350,11 +438,11 @@ JxlDecoderDefaultPixelFormat(const JxlDecoder* dec, JxlPixelFormat* format);
*
* @param dec decoder object
* @param parallel_runner function pointer to runner for multithreading. It may
- * be NULL to use the default, single-threaded, runner. A multithreaded
- * runner should be set to reach fast performance.
+ * be NULL to use the default, single-threaded, runner. A multithreaded
+ * runner should be set to reach fast performance.
* @param parallel_runner_opaque opaque pointer for parallel_runner.
- * @return JXL_DEC_SUCCESS if the runner was set, JXL_DEC_ERROR
- * otherwise (the previous runner remains set).
+ * @return @ref JXL_DEC_SUCCESS if the runner was set, @ref JXL_DEC_ERROR
+ * otherwise (the previous runner remains set).
*/
JXL_EXPORT JxlDecoderStatus
JxlDecoderSetParallelRunner(JxlDecoder* dec, JxlParallelRunner parallel_runner,
@@ -362,14 +450,14 @@ JxlDecoderSetParallelRunner(JxlDecoder* dec, JxlParallelRunner parallel_runner,
/**
* Returns a hint indicating how many more bytes the decoder is expected to
- * need to make JxlDecoderGetBasicInfo available after the next
+ * need to make @ref JxlDecoderGetBasicInfo available after the next @ref
* JxlDecoderProcessInput call. This is a suggested large enough value for
- * the amount of bytes to provide in the next JxlDecoderSetInput call, but it is
- * not guaranteed to be an upper bound nor a lower bound. This number does not
- * include bytes that have already been released from the input.
- * Can be used before the first JxlDecoderProcessInput call, and is correct
- * the first time in most cases. If not, JxlDecoderSizeHintBasicInfo can be
- * called again to get an updated hint.
+ * the amount of bytes to provide in the next @ref JxlDecoderSetInput call, but
+ * it is not guaranteed to be an upper bound nor a lower bound. This number does
+ * not include bytes that have already been released from the input. Can be used
+ * before the first @ref JxlDecoderProcessInput call, and is correct the first
+ * time in most cases. If not, @ref JxlDecoderSizeHintBasicInfo can be called
+ * again to get an updated hint.
*
* @param dec decoder object
* @return the size hint in bytes if the basic info is not yet fully decoded.
@@ -377,17 +465,17 @@ JxlDecoderSetParallelRunner(JxlDecoder* dec, JxlParallelRunner parallel_runner,
*/
JXL_EXPORT size_t JxlDecoderSizeHintBasicInfo(const JxlDecoder* dec);
-/** Select for which informative events (JXL_DEC_BASIC_INFO, etc...) the
+/** Select for which informative events, i.e. @ref JXL_DEC_BASIC_INFO, etc., the
* decoder should return with a status. It is not required to subscribe to any
* events, data can still be requested from the decoder as soon as it available.
* By default, the decoder is subscribed to no events (events_wanted == 0), and
* the decoder will then only return when it cannot continue because it needs
* more input data or more output buffer. This function may only be be called
- * before using JxlDecoderProcessInput
+ * before using @ref JxlDecoderProcessInput.
*
* @param dec decoder object
* @param events_wanted bitfield of desired events.
- * @return JXL_DEC_SUCCESS if no error, JXL_DEC_ERROR otherwise.
+ * @return @ref JXL_DEC_SUCCESS if no error, @ref JXL_DEC_ERROR otherwise.
*/
JXL_EXPORT JxlDecoderStatus JxlDecoderSubscribeEvents(JxlDecoder* dec,
int events_wanted);
@@ -397,45 +485,62 @@ JXL_EXPORT JxlDecoderStatus JxlDecoderSubscribeEvents(JxlDecoder* dec,
* indicating that the decoder must perform a rotation and/or
* mirroring to the encoded image data.
*
- * *) If skip_reorientation is JXL_FALSE (the default): the decoder
- * will apply the transformation from the orientation setting, hence
- * rendering the image according to its specified intent. When
- * producing a JxlBasicInfo, the decoder will always set the
- * orientation field to JXL_ORIENT_IDENTITY (matching the returned
- * pixel data) and also align xsize and ysize so that they correspond
- * to the width and the height of the returned pixel data.
- *
- * *) If skip_reorientation is JXL_TRUE: the decoder will skip
- * applying the transformation from the orientation setting, returning
- * the image in the as-in-bitstream pixeldata orientation.
- * This may be faster to decode since the decoder doesn't have to apply the
- * transformation, but can cause wrong display of the image if the orientation
- * tag is not correctly taken into account by the user.
+ * - If skip_reorientation is JXL_FALSE (the default): the decoder
+ * will apply the transformation from the orientation setting, hence
+ * rendering the image according to its specified intent. When
+ * producing a JxlBasicInfo, the decoder will always set the
+ * orientation field to JXL_ORIENT_IDENTITY (matching the returned
+ * pixel data) and also align xsize and ysize so that they correspond
+ * to the width and the height of the returned pixel data.
+ * - If skip_reorientation is JXL_TRUE: the decoder will skip
+ * applying the transformation from the orientation setting, returning
+ * the image in the as-in-bitstream pixeldata orientation.
+ * This may be faster to decode since the decoder doesn't have to apply the
+ * transformation, but can cause wrong display of the image if the
+ * orientation tag is not correctly taken into account by the user.
*
* By default, this option is disabled, and the returned pixel data is
* re-oriented according to the image's Orientation setting.
*
* This function must be called at the beginning, before decoding is performed.
*
- * @see JxlBasicInfo for the orientation field, and @see JxlOrientation for the
+ * @see JxlBasicInfo for the orientation field, and @ref JxlOrientation for the
* possible values.
*
* @param dec decoder object
* @param skip_reorientation JXL_TRUE to enable, JXL_FALSE to disable.
- * @return JXL_DEC_SUCCESS if no error, JXL_DEC_ERROR otherwise.
+ * @return @ref JXL_DEC_SUCCESS if no error, @ref JXL_DEC_ERROR otherwise.
*/
JXL_EXPORT JxlDecoderStatus
JxlDecoderSetKeepOrientation(JxlDecoder* dec, JXL_BOOL skip_reorientation);
+/**
+ * Enables or disables preserving of associated alpha channels. If
+ * unpremul_alpha is set to JXL_FALSE then for associated alpha channel, the
+ * pixel data is returned with premultiplied colors. If it is set to JXL_TRUE,
+ * The colors will be unpremultiplied based on the alpha channel. This function
+ * has no effect if the image does not have an associated alpha channel.
+ *
+ * By default, this option is disabled, and the returned pixel data "as is".
+ *
+ * This function must be called at the beginning, before decoding is performed.
+ *
+ * @param dec decoder object
+ * @param unpremul_alpha JXL_TRUE to enable, JXL_FALSE to disable.
+ * @return @ref JXL_DEC_SUCCESS if no error, @ref JXL_DEC_ERROR otherwise.
+ */
+JXL_EXPORT JxlDecoderStatus
+JxlDecoderSetUnpremultiplyAlpha(JxlDecoder* dec, JXL_BOOL unpremul_alpha);
+
/** Enables or disables rendering spot colors. By default, spot colors
* are rendered, which is OK for viewing the decoded image. If render_spotcolors
* is JXL_FALSE, then spot colors are not rendered, and have to be retrieved
- * separately using JxlDecoderSetExtraChannelBuffer. This is useful for e.g.
- * printing applications.
+ * separately using @ref JxlDecoderSetExtraChannelBuffer. This is useful for
+ * e.g. printing applications.
*
* @param dec decoder object
* @param render_spotcolors JXL_TRUE to enable (default), JXL_FALSE to disable.
- * @return JXL_DEC_SUCCESS if no error, JXL_DEC_ERROR otherwise.
+ * @return @ref JXL_DEC_SUCCESS if no error, @ref JXL_DEC_ERROR otherwise.
*/
JXL_EXPORT JxlDecoderStatus
JxlDecoderSetRenderSpotcolors(JxlDecoder* dec, JXL_BOOL render_spotcolors);
@@ -450,106 +555,111 @@ JxlDecoderSetRenderSpotcolors(JxlDecoder* dec, JXL_BOOL render_spotcolors);
*
* @param dec decoder object
* @param coalescing JXL_TRUE to enable coalescing (default), JXL_FALSE to
- * disable it.
- * @return JXL_DEC_SUCCESS if no error, JXL_DEC_ERROR otherwise.
+ * disable it.
+ * @return @ref JXL_DEC_SUCCESS if no error, @ref JXL_DEC_ERROR otherwise.
*/
JXL_EXPORT JxlDecoderStatus JxlDecoderSetCoalescing(JxlDecoder* dec,
JXL_BOOL coalescing);
/**
* Decodes JPEG XL file using the available bytes. Requires input has been
- * set with JxlDecoderSetInput. After JxlDecoderProcessInput, input can
- * optionally be released with JxlDecoderReleaseInput and then set again to
- * next bytes in the stream. JxlDecoderReleaseInput returns how many bytes are
- * not yet processed, before a next call to JxlDecoderProcessInput all
- * unprocessed bytes must be provided again (the address need not match, but the
- * contents must), and more bytes may be concatenated after the unprocessed
- * bytes.
+ * set with @ref JxlDecoderSetInput. After @ref JxlDecoderProcessInput, input
+ * can optionally be released with @ref JxlDecoderReleaseInput and then set
+ * again to next bytes in the stream. @ref JxlDecoderReleaseInput returns how
+ * many bytes are not yet processed, before a next call to @ref
+ * JxlDecoderProcessInput all unprocessed bytes must be provided again (the
+ * address need not match, but the contents must), and more bytes may be
+ * concatenated after the unprocessed bytes.
*
* The returned status indicates whether the decoder needs more input bytes, or
* more output buffer for a certain type of output data. No matter what the
- * returned status is (other than JXL_DEC_ERROR), new information, such as
- * JxlDecoderGetBasicInfo, may have become available after this call. When
- * the return value is not JXL_DEC_ERROR or JXL_DEC_SUCCESS, the decoding
- * requires more JxlDecoderProcessInput calls to continue.
+ * returned status is (other than @ref JXL_DEC_ERROR), new information, such
+ * as @ref JxlDecoderGetBasicInfo, may have become available after this call.
+ * When the return value is not @ref JXL_DEC_ERROR or @ref JXL_DEC_SUCCESS, the
+ * decoding requires more @ref JxlDecoderProcessInput calls to continue.
*
* @param dec decoder object
- * @return JXL_DEC_SUCCESS when decoding finished and all events handled. If you
- * still have more unprocessed input data anyway, then you can still continue
- * by using JxlDecoderSetInput and calling JxlDecoderProcessInput again, similar
- * to handling JXL_DEC_NEED_MORE_INPUT. JXL_DEC_SUCCESS can occur instead of
- * JXL_DEC_NEED_MORE_INPUT when, for example, the input data ended right at
- * the boundary of a box of the container format, all essential codestream boxes
- * were already decoded, but extra metadata boxes are still present in the next
- * data. JxlDecoderProcessInput cannot return success if all codestream boxes
- * have not been seen yet.
- * @return JXL_DEC_ERROR when decoding failed, e.g. invalid codestream.
- * TODO(lode) document the input data mechanism
- * @return JXL_DEC_NEED_MORE_INPUT when more input data is necessary.
- * @return JXL_DEC_BASIC_INFO when basic info such as image dimensions is
- * available and this informative event is subscribed to.
- * @return JXL_DEC_EXTENSIONS when JPEG XL codestream user extensions are
- * available and this informative event is subscribed to.
- * @return JXL_DEC_COLOR_ENCODING when color profile information is
- * available and this informative event is subscribed to.
- * @return JXL_DEC_PREVIEW_IMAGE when preview pixel information is available and
- * output in the preview buffer.
- * @return JXL_DEC_DC_IMAGE when DC pixel information (8x8 downscaled version
- * of the image) is available and output is in the DC buffer.
- * @return JXL_DEC_FULL_IMAGE when all pixel information at highest detail is
- * available and has been output in the pixel buffer.
+ * @return @ref JXL_DEC_SUCCESS when decoding finished and all events handled.
+ * If you still have more unprocessed input data anyway, then you can still
+ * continue by using @ref JxlDecoderSetInput and calling @ref
+ * JxlDecoderProcessInput again, similar to handling @ref
+ * JXL_DEC_NEED_MORE_INPUT. @ref JXL_DEC_SUCCESS can occur instead of @ref
+ * JXL_DEC_NEED_MORE_INPUT when, for example, the input data ended right at
+ * the boundary of a box of the container format, all essential codestream
+ * boxes were already decoded, but extra metadata boxes are still present in
+ * the next data. @ref JxlDecoderProcessInput cannot return success if all
+ * codestream boxes have not been seen yet.
+ * @return @ref JXL_DEC_ERROR when decoding failed, e.g. invalid codestream.
+ * TODO(lode): document the input data mechanism
+ * @return @ref JXL_DEC_NEED_MORE_INPUT when more input data is necessary.
+ * @return @ref JXL_DEC_BASIC_INFO when basic info such as image dimensions is
+ * available and this informative event is subscribed to.
+ * @return @ref JXL_DEC_COLOR_ENCODING when color profile information is
+ * available and this informative event is subscribed to.
+ * @return @ref JXL_DEC_PREVIEW_IMAGE when preview pixel information is
+ * available and output in the preview buffer.
+ * @return @ref JXL_DEC_DC_IMAGE when DC pixel information (8x8 downscaled
+ * version of the image) is available and output is in the DC buffer.
+ * @return @ref JXL_DEC_FULL_IMAGE when all pixel information at highest detail
+ * is available and has been output in the pixel buffer.
*/
JXL_EXPORT JxlDecoderStatus JxlDecoderProcessInput(JxlDecoder* dec);
/**
- * Sets input data for JxlDecoderProcessInput. The data is owned by the caller
- * and may be used by the decoder until JxlDecoderReleaseInput is called or
- * the decoder is destroyed or reset so must be kept alive until then.
- * Cannot be called if JxlDecoderSetInput was already called and
- * JxlDecoderReleaseInput was not yet called, and cannot be called after
+ * Sets input data for @ref JxlDecoderProcessInput. The data is owned by the
+ * caller and may be used by the decoder until @ref JxlDecoderReleaseInput is
+ * called or the decoder is destroyed or reset so must be kept alive until then.
+ * Cannot be called if @ref JxlDecoderSetInput was already called and @ref
+ * JxlDecoderReleaseInput was not yet called, and cannot be called after @ref
* JxlDecoderCloseInput indicating the end of input was called.
+ *
* @param dec decoder object
* @param data pointer to next bytes to read from
* @param size amount of bytes available starting from data
- * @return JXL_DEC_ERROR if input was already set without releasing or
- * JxlDecoderCloseInput was already called, JXL_DEC_SUCCESS otherwise.
+ * @return @ref JXL_DEC_ERROR if input was already set without releasing or @ref
+ * JxlDecoderCloseInput was already called, @ref JXL_DEC_SUCCESS otherwise.
*/
JXL_EXPORT JxlDecoderStatus JxlDecoderSetInput(JxlDecoder* dec,
const uint8_t* data,
size_t size);
/**
- * Releases input which was provided with JxlDecoderSetInput. Between
- * JxlDecoderProcessInput and JxlDecoderReleaseInput, the user may not alter
- * the data in the buffer. Calling JxlDecoderReleaseInput is required whenever
- * any input is already set and new input needs to be added with
- * JxlDecoderSetInput, but is not required before JxlDecoderDestroy or
- * JxlDecoderReset. Calling JxlDecoderReleaseInput when no input is set is
+ * Releases input which was provided with @ref JxlDecoderSetInput. Between @ref
+ * JxlDecoderProcessInput and @ref JxlDecoderReleaseInput, the user may not
+ * alter the data in the buffer. Calling @ref JxlDecoderReleaseInput is required
+ * whenever any input is already set and new input needs to be added with @ref
+ * JxlDecoderSetInput, but is not required before @ref JxlDecoderDestroy or @ref
+ * JxlDecoderReset. Calling @ref JxlDecoderReleaseInput when no input is set is
* not an error and returns 0.
+ *
* @param dec decoder object
- * @return the amount of bytes the decoder has not yet processed that are
- * still remaining in the data set by JxlDecoderSetInput, or 0 if no input is
- * set or JxlDecoderReleaseInput was already called. For a next call to
- * JxlDecoderProcessInput, the buffer must start with these unprocessed bytes.
- * This value doesn't provide information about how many bytes the decoder
- * truly processed internally or how large the original JPEG XL codestream or
- * file are.
+ * @return The amount of bytes the decoder has not yet processed that are still
+ * remaining in the data set by @ref JxlDecoderSetInput, or 0 if no input is
+ * set or @ref JxlDecoderReleaseInput was already called. For a next call
+ * to @ref JxlDecoderProcessInput, the buffer must start with these
+ * unprocessed bytes. From this value it is possible to infer the position
+ * of certain JPEG XL codestream elements (e.g. end of headers, frame
+ * start/end). See the documentation of individual values of @ref
+ * JxlDecoderStatus for more information.
*/
JXL_EXPORT size_t JxlDecoderReleaseInput(JxlDecoder* dec);
/**
- * Marks the input as finished, indicates that no more JxlDecoderSetInput will
- * be called. This function allows the decoder to determine correctly if it
+ * Marks the input as finished, indicates that no more @ref JxlDecoderSetInput
+ * will be called. This function allows the decoder to determine correctly if it
* should return success, need more input or error in certain cases. For
* backwards compatibility with a previous version of the API, using this
- * function is optional when not using the JXL_DEC_BOX event (the decoder is
- * able to determine the end of the image frames without marking the end), but
- * using this function is required when using JXL_DEC_BOX for getting metadata
- * box contents. This function does not replace JxlDecoderReleaseInput, that
- * function should still be called if its return value is needed.
- * JxlDecoderCloseInput should be called as soon as all known input bytes are
- * set (e.g. at the beginning when not streaming but setting all input at once),
- * before the final JxlDecoderProcessInput calls.
+ * function is optional when not using the @ref JXL_DEC_BOX event (the decoder
+ * is able to determine the end of the image frames without marking the end),
+ * but using this function is required when using @ref JXL_DEC_BOX for getting
+ * metadata box contents. This function does not replace @ref
+ * JxlDecoderReleaseInput, that function should still be called if its return
+ * value is needed.
+ *
+ * @ref JxlDecoderCloseInput should be called as soon as all known input bytes
+ * are set (e.g. at the beginning when not streaming but setting all input
+ * at once), before the final @ref JxlDecoderProcessInput calls.
+ *
* @param dec decoder object
*/
JXL_EXPORT void JxlDecoderCloseInput(JxlDecoder* dec);
@@ -560,10 +670,10 @@ JXL_EXPORT void JxlDecoderCloseInput(JxlDecoder* dec);
*
* @param dec decoder object
* @param info struct to copy the information into, or NULL to only check
- * whether the information is available through the return value.
- * @return JXL_DEC_SUCCESS if the value is available,
- * JXL_DEC_NEED_MORE_INPUT if not yet available, JXL_DEC_ERROR in case
- * of other error conditions.
+ * whether the information is available through the return value.
+ * @return @ref JXL_DEC_SUCCESS if the value is available, @ref
+ * JXL_DEC_NEED_MORE_INPUT if not yet available, @ref JXL_DEC_ERROR
+ * in case of other error conditions.
*/
JXL_EXPORT JxlDecoderStatus JxlDecoderGetBasicInfo(const JxlDecoder* dec,
JxlBasicInfo* info);
@@ -575,10 +685,10 @@ JXL_EXPORT JxlDecoderStatus JxlDecoderGetBasicInfo(const JxlDecoder* dec,
* @param dec decoder object
* @param index index of the extra channel to query.
* @param info struct to copy the information into, or NULL to only check
- * whether the information is available through the return value.
- * @return JXL_DEC_SUCCESS if the value is available,
- * JXL_DEC_NEED_MORE_INPUT if not yet available, JXL_DEC_ERROR in case
- * of other error conditions.
+ * whether the information is available through the return value.
+ * @return @ref JXL_DEC_SUCCESS if the value is available, @ref
+ * JXL_DEC_NEED_MORE_INPUT if not yet available, @ref JXL_DEC_ERROR
+ * in case of other error conditions.
*/
JXL_EXPORT JxlDecoderStatus JxlDecoderGetExtraChannelInfo(
const JxlDecoder* dec, size_t index, JxlExtraChannelInfo* info);
@@ -593,9 +703,9 @@ JXL_EXPORT JxlDecoderStatus JxlDecoderGetExtraChannelInfo(
* @param index index of the extra channel to query.
* @param name buffer to copy the name into
* @param size size of the name buffer in bytes
- * @return JXL_DEC_SUCCESS if the value is available,
- * JXL_DEC_NEED_MORE_INPUT if not yet available, JXL_DEC_ERROR in case
- * of other error conditions.
+ * @return @ref JXL_DEC_SUCCESS if the value is available, @ref
+ * JXL_DEC_NEED_MORE_INPUT if not yet available, @ref JXL_DEC_ERROR
+ * in case of other error conditions.
*/
JXL_EXPORT JxlDecoderStatus JxlDecoderGetExtraChannelName(const JxlDecoder* dec,
size_t index,
@@ -604,11 +714,11 @@ JXL_EXPORT JxlDecoderStatus JxlDecoderGetExtraChannelName(const JxlDecoder* dec,
/** Defines which color profile to get: the profile from the codestream
* metadata header, which represents the color profile of the original image,
- * or the color profile from the pixel data received by the decoder. Both are
- * the same if the basic has uses_original_profile set.
+ * or the color profile from the pixel data produced by the decoder. Both are
+ * the same if the JxlBasicInfo has uses_original_profile set.
*/
typedef enum {
- /** Get the color profile of the original image from the metadata..
+ /** Get the color profile of the original image from the metadata.
*/
JXL_COLOR_PROFILE_TARGET_ORIGINAL = 0,
@@ -621,174 +731,182 @@ typedef enum {
* This is an alternative to an ICC Profile, which can represent a more limited
* amount of color spaces, but represents them exactly through enum values.
*
- * It is often possible to use JxlDecoderGetColorAsICCProfile as an
+ * It is often possible to use @ref JxlDecoderGetColorAsICCProfile as an
* alternative anyway. The following scenarios are possible:
- * - The JPEG XL image has an attached ICC Profile, in that case, the encoded
- * structured data is not available, this function will return an error
- * status. JxlDecoderGetColorAsICCProfile should be called instead.
- * - The JPEG XL image has an encoded structured color profile, and it
- * represents an RGB or grayscale color space. This function will return it.
- * You can still use JxlDecoderGetColorAsICCProfile as well as an
- * alternative if desired, though depending on which RGB color space is
- * represented, the ICC profile may be a close approximation. It is also not
- * always feasible to deduce from an ICC profile which named color space it
- * exactly represents, if any, as it can represent any arbitrary space.
- * - The JPEG XL image has an encoded structured color profile, and it indicates
- * an unknown or xyb color space. In that case,
- * JxlDecoderGetColorAsICCProfile is not available.
- *
- * When rendering an image on a system that supports ICC profiles,
+ * - The JPEG XL image has an attached ICC Profile, in that case, the encoded
+ * structured data is not available, this function will return an error
+ * status. @ref JxlDecoderGetColorAsICCProfile should be called instead.
+ * - The JPEG XL image has an encoded structured color profile, and it
+ * represents an RGB or grayscale color space. This function will return it.
+ * You can still use @ref JxlDecoderGetColorAsICCProfile as well as an
+ * alternative if desired, though depending on which RGB color space is
+ * represented, the ICC profile may be a close approximation. It is also not
+ * always feasible to deduce from an ICC profile which named color space it
+ * exactly represents, if any, as it can represent any arbitrary space.
+ * - The JPEG XL image has an encoded structured color profile, and it
+ * indicates an unknown or xyb color space. In that case, @ref
+ * JxlDecoderGetColorAsICCProfile is not available.
+ *
+ * When rendering an image on a system that supports ICC profiles, @ref
* JxlDecoderGetColorAsICCProfile should be used first. When rendering
* for a specific color space, possibly indicated in the JPEG XL
- * image, JxlDecoderGetColorAsEncodedProfile should be used first.
+ * image, @ref JxlDecoderGetColorAsEncodedProfile should be used first.
*
* @param dec decoder object
- * @param format pixel format to output the data to. Only used for
- * JXL_COLOR_PROFILE_TARGET_DATA, may be nullptr otherwise.
+ * @param unused_format deprecated, can be NULL
* @param target whether to get the original color profile from the metadata
* or the color profile of the decoded pixels.
* @param color_encoding struct to copy the information into, or NULL to only
- * check whether the information is available through the return value.
- * @return JXL_DEC_SUCCESS if the data is available and returned,
- * JXL_DEC_NEED_MORE_INPUT if not yet available, JXL_DEC_ERROR in case
- * the encoded structured color profile does not exist in the codestream.
+ * check whether the information is available through the return value.
+ * @return @ref JXL_DEC_SUCCESS if the data is available and returned, @ref
+ * JXL_DEC_NEED_MORE_INPUT if not yet available, @ref JXL_DEC_ERROR in
+ * case the encoded structured color profile does not exist in the
+ * codestream.
*/
JXL_EXPORT JxlDecoderStatus JxlDecoderGetColorAsEncodedProfile(
- const JxlDecoder* dec, const JxlPixelFormat* format,
+ const JxlDecoder* dec, const JxlPixelFormat* unused_format,
JxlColorProfileTarget target, JxlColorEncoding* color_encoding);
/**
- * Outputs the size in bytes of the ICC profile returned by
+ * Outputs the size in bytes of the ICC profile returned by @ref
* JxlDecoderGetColorAsICCProfile, if available, or indicates there is none
* available. In most cases, the image will have an ICC profile available, but
- * if it does not, JxlDecoderGetColorAsEncodedProfile must be used instead.
+ * if it does not, @ref JxlDecoderGetColorAsEncodedProfile must be used instead.
+ *
* @see JxlDecoderGetColorAsEncodedProfile for more information. The ICC
* profile is either the exact ICC profile attached to the codestream metadata,
* or a close approximation generated from JPEG XL encoded structured data,
* depending of what is encoded in the codestream.
*
* @param dec decoder object
- * @param format pixel format to output the data to. Only used for
- * JXL_COLOR_PROFILE_TARGET_DATA, may be nullptr otherwise.
+ * @param unused_format deprecated, can be NULL
* @param target whether to get the original color profile from the metadata
* or the color profile of the decoded pixels.
* @param size variable to output the size into, or NULL to only check the
- * return status.
- * @return JXL_DEC_SUCCESS if the ICC profile is available,
- * JXL_DEC_NEED_MORE_INPUT if the decoder has not yet received enough
- * input data to determine whether an ICC profile is available or what its
- * size is, JXL_DEC_ERROR in case the ICC profile is not available and
- * cannot be generated.
+ * return status.
+ * @return @ref JXL_DEC_SUCCESS if the ICC profile is available, @ref
+ * JXL_DEC_NEED_MORE_INPUT if the decoder has not yet received enough
+ * input data to determine whether an ICC profile is available or what its
+ * size is, @ref JXL_DEC_ERROR in case the ICC profile is not available and
+ * cannot be generated.
*/
-JXL_EXPORT JxlDecoderStatus
-JxlDecoderGetICCProfileSize(const JxlDecoder* dec, const JxlPixelFormat* format,
- JxlColorProfileTarget target, size_t* size);
+JXL_EXPORT JxlDecoderStatus JxlDecoderGetICCProfileSize(
+ const JxlDecoder* dec, const JxlPixelFormat* unused_format,
+ JxlColorProfileTarget target, size_t* size);
/**
- * Outputs ICC profile if available. The profile is only available if
+ * Outputs ICC profile if available. The profile is only available if @ref
* JxlDecoderGetICCProfileSize returns success. The output buffer must have
- * at least as many bytes as given by JxlDecoderGetICCProfileSize.
+ * at least as many bytes as given by @ref JxlDecoderGetICCProfileSize.
*
* @param dec decoder object
- * @param format pixel format to output the data to. Only used for
- * JXL_COLOR_PROFILE_TARGET_DATA, may be nullptr otherwise.
+ * @param unused_format deprecated, can be NULL
* @param target whether to get the original color profile from the metadata
* or the color profile of the decoded pixels.
* @param icc_profile buffer to copy the ICC profile into
* @param size size of the icc_profile buffer in bytes
- * @return JXL_DEC_SUCCESS if the profile was successfully returned is
- * available, JXL_DEC_NEED_MORE_INPUT if not yet available,
- * JXL_DEC_ERROR if the profile doesn't exist or the output size is not
- * large enough.
+ * @return @ref JXL_DEC_SUCCESS if the profile was successfully returned is
+ * available, @ref JXL_DEC_NEED_MORE_INPUT if not yet available, @ref
+ * JXL_DEC_ERROR if the profile doesn't exist or the output size is not
+ * large enough.
*/
JXL_EXPORT JxlDecoderStatus JxlDecoderGetColorAsICCProfile(
- const JxlDecoder* dec, const JxlPixelFormat* format,
+ const JxlDecoder* dec, const JxlPixelFormat* unused_format,
JxlColorProfileTarget target, uint8_t* icc_profile, size_t size);
-/** Sets the color profile to use for JXL_COLOR_PROFILE_TARGET_DATA for the
+/** Sets the color profile to use for @ref JXL_COLOR_PROFILE_TARGET_DATA for the
* special case when the decoder has a choice. This only has effect for a JXL
- * image where uses_original_profile is false, and the original color profile is
- * encoded as an ICC color profile rather than a JxlColorEncoding with known
- * enum values. In most other cases (uses uses_original_profile is true, or the
- * color profile is already given as a JxlColorEncoding), this setting is
- * ignored and the decoder uses a profile related to the image.
- * No matter what, the JXL_COLOR_PROFILE_TARGET_DATA must still be queried to
- * know the actual data format of the decoded pixels after decoding.
- *
- * The intended use case of this function is for cases where you are using
- * a color management system to parse the original ICC color profile
- * (JXL_COLOR_PROFILE_TARGET_ORIGINAL), from this you know that the ICC
- * profile represents one of the color profiles supported by JxlColorEncoding
- * (such as sRGB, PQ or HLG): in that case it is beneficial (but not necessary)
- * to use JxlDecoderSetPreferredColorProfile to match the parsed profile. The
- * JXL decoder has no color management system built in, but can convert XYB
- * color to any of the ones supported by JxlColorEncoding.
- *
- * Can only be set after the JXL_DEC_COLOR_ENCODING event occurred and before
- * any other event occurred, and can affect the result of
- * JXL_COLOR_PROFILE_TARGET_DATA (but not of JXL_COLOR_PROFILE_TARGET_ORIGINAL),
- * so should be used after getting JXL_COLOR_PROFILE_TARGET_ORIGINAL but before
- * getting JXL_COLOR_PROFILE_TARGET_DATA. The color_encoding must be grayscale
- * if num_color_channels from the basic info is 1, RGB if num_color_channels
- * from the basic info is 3.
- *
- * If JxlDecoderSetPreferredColorProfile is not used, then for images for which
- * uses_original_profile is false and with ICC color profile, the decoder will
- * choose linear sRGB for color images, linear grayscale for grayscale images.
- * This function only sets a preference, since for other images the decoder has
- * no choice what color profile to use, it is determined by the image.
+ * image where uses_original_profile is false. If uses_original_profile is true,
+ * this setting is ignored and the decoder uses a profile related to the image.
+ * No matter what, the @ref JXL_COLOR_PROFILE_TARGET_DATA must still be queried
+ * to know the actual data format of the decoded pixels after decoding.
+ *
+ * The JXL decoder has no color management system built in, but can convert XYB
+ * color to any of the ones supported by JxlColorEncoding. Note that if the
+ * requested color encoding has a narrower gamut, or the white points differ,
+ * then the resulting image can have significant color distortion.
+ *
+ * Can only be set after the @ref JXL_DEC_COLOR_ENCODING event occurred and
+ * before any other event occurred, and can affect the result of @ref
+ * JXL_COLOR_PROFILE_TARGET_DATA (but not of @ref
+ * JXL_COLOR_PROFILE_TARGET_ORIGINAL), so should be used after getting @ref
+ * JXL_COLOR_PROFILE_TARGET_ORIGINAL but before getting @ref
+ * JXL_COLOR_PROFILE_TARGET_DATA. The color_encoding must be grayscale if
+ * num_color_channels from the basic info is 1, RGB if num_color_channels from
+ * the basic info is 3.
+ *
+ * If @ref JxlDecoderSetPreferredColorProfile is not used, then for images for
+ * which uses_original_profile is false and with ICC color profile, the decoder
+ * will choose linear sRGB for color images, linear grayscale for grayscale
+ * images. This function only sets a preference, since for other images the
+ * decoder has no choice what color profile to use, it is determined by the
+ * image.
*
* @param dec decoder object
* @param color_encoding the default color encoding to set
- * @return JXL_DEC_SUCCESS if the preference was set successfully, JXL_DEC_ERROR
- * otherwise.
+ * @return @ref JXL_DEC_SUCCESS if the preference was set successfully, @ref
+ * JXL_DEC_ERROR otherwise.
*/
JXL_EXPORT JxlDecoderStatus JxlDecoderSetPreferredColorProfile(
JxlDecoder* dec, const JxlColorEncoding* color_encoding);
+/** Requests that the decoder perform tone mapping to the peak display luminance
+ * passed as @c desired_intensity_target, if appropriate.
+ * @note This is provided for convenience and the exact tone mapping that is
+ * performed is not meant to be considered authoritative in any way. It may
+ * change from version to version.
+ * @param dec decoder object
+ * @param desired_intensity_target the intended target peak luminance
+ * @return @ref JXL_DEC_SUCCESS if the preference was set successfully, @ref
+ * JXL_DEC_ERROR otherwise.
+ */
+JXL_EXPORT JxlDecoderStatus JxlDecoderSetDesiredIntensityTarget(
+ JxlDecoder* dec, float desired_intensity_target);
+
/**
* Returns the minimum size in bytes of the preview image output pixel buffer
- * for the given format. This is the buffer for JxlDecoderSetPreviewOutBuffer.
- * Requires the preview header information is available in the decoder.
+ * for the given format. This is the buffer for @ref
+ * JxlDecoderSetPreviewOutBuffer. Requires the preview header information is
+ * available in the decoder.
*
* @param dec decoder object
* @param format format of pixels
* @param size output value, buffer size in bytes
- * @return JXL_DEC_SUCCESS on success, JXL_DEC_ERROR on error, such as
- * information not available yet.
+ * @return @ref JXL_DEC_SUCCESS on success, @ref JXL_DEC_ERROR on error, such as
+ * information not available yet.
*/
JXL_EXPORT JxlDecoderStatus JxlDecoderPreviewOutBufferSize(
const JxlDecoder* dec, const JxlPixelFormat* format, size_t* size);
/**
* Sets the buffer to write the small resolution preview image
- * to. The size of the buffer must be at least as large as given by
- * JxlDecoderPreviewOutBufferSize. The buffer follows the format described by
- * JxlPixelFormat. The preview image dimensions are given by the
+ * to. The size of the buffer must be at least as large as given by @ref
+ * JxlDecoderPreviewOutBufferSize. The buffer follows the format described
+ * by JxlPixelFormat. The preview image dimensions are given by the
* JxlPreviewHeader. The buffer is owned by the caller.
*
* @param dec decoder object
* @param format format of pixels. Object owned by user and its contents are
- * copied internally.
+ * copied internally.
* @param buffer buffer type to output the pixel data to
* @param size size of buffer in bytes
- * @return JXL_DEC_SUCCESS on success, JXL_DEC_ERROR on error, such as
- * size too small.
+ * @return @ref JXL_DEC_SUCCESS on success, @ref JXL_DEC_ERROR on error, such as
+ * size too small.
*/
JXL_EXPORT JxlDecoderStatus JxlDecoderSetPreviewOutBuffer(
JxlDecoder* dec, const JxlPixelFormat* format, void* buffer, size_t size);
/**
* Outputs the information from the frame, such as duration when have_animation.
- * This function can be called when JXL_DEC_FRAME occurred for the current
+ * This function can be called when @ref JXL_DEC_FRAME occurred for the current
* frame, even when have_animation in the JxlBasicInfo is JXL_FALSE.
*
* @param dec decoder object
* @param header struct to copy the information into, or NULL to only check
- * whether the information is available through the return value.
- * @return JXL_DEC_SUCCESS if the value is available,
- * JXL_DEC_NEED_MORE_INPUT if not yet available, JXL_DEC_ERROR in case
- * of other error conditions.
+ * whether the information is available through the return value.
+ * @return @ref JXL_DEC_SUCCESS if the value is available, @ref
+ * JXL_DEC_NEED_MORE_INPUT if not yet available, @ref JXL_DEC_ERROR in
+ * case of other error conditions.
*/
JXL_EXPORT JxlDecoderStatus JxlDecoderGetFrameHeader(const JxlDecoder* dec,
JxlFrameHeader* header);
@@ -801,16 +919,16 @@ JXL_EXPORT JxlDecoderStatus JxlDecoderGetFrameHeader(const JxlDecoder* dec,
* @param name buffer to copy the name into
* @param size size of the name buffer in bytes, including zero termination
* character, so this must be at least JxlFrameHeader.name_length + 1.
- * @return JXL_DEC_SUCCESS if the value is available,
- * JXL_DEC_NEED_MORE_INPUT if not yet available, JXL_DEC_ERROR in case
- * of other error conditions.
+ * @return @ref JXL_DEC_SUCCESS if the value is available, @ref
+ * JXL_DEC_NEED_MORE_INPUT if not yet available, @ref JXL_DEC_ERROR in
+ * case of other error conditions.
*/
JXL_EXPORT JxlDecoderStatus JxlDecoderGetFrameName(const JxlDecoder* dec,
char* name, size_t size);
/**
* Outputs the blend information for the current frame for a specific extra
- * channel. This function can be called when JXL_DEC_FRAME occurred for the
+ * channel. This function can be called when @ref JXL_DEC_FRAME occurred for the
* current frame, even when have_animation in the JxlBasicInfo is JXL_FALSE.
* This information is only useful if coalescing is disabled; otherwise the
* decoder will have performed blending already.
@@ -818,162 +936,156 @@ JXL_EXPORT JxlDecoderStatus JxlDecoderGetFrameName(const JxlDecoder* dec,
* @param dec decoder object
* @param index the index of the extra channel
* @param blend_info struct to copy the information into
- * @return JXL_DEC_SUCCESS on success, JXL_DEC_ERROR on error
+ * @return @ref JXL_DEC_SUCCESS on success, @ref JXL_DEC_ERROR on error
*/
JXL_EXPORT JxlDecoderStatus JxlDecoderGetExtraChannelBlendInfo(
const JxlDecoder* dec, size_t index, JxlBlendInfo* blend_info);
/**
* Returns the minimum size in bytes of the DC image output buffer
- * for the given format. This is the buffer for JxlDecoderSetDCOutBuffer.
+ * for the given format. This is the buffer for @ref JxlDecoderSetDCOutBuffer.
* Requires the basic image information is available in the decoder.
*
* @param dec decoder object
* @param format format of pixels
* @param size output value, buffer size in bytes
- * @return JXL_DEC_SUCCESS on success, JXL_DEC_ERROR on error, such as
- * information not available yet.
+ * @return @ref JXL_DEC_SUCCESS on success, @ref JXL_DEC_ERROR on error, such as
+ * information not available yet.
*
- * DEPRECATED: the DC feature in this form will be removed. Use
- * JxlDecoderFlushImage for progressive rendering.
+ * @deprecated The DC feature in this form will be removed. Use @ref
+ * JxlDecoderFlushImage for progressive rendering.
*/
JXL_EXPORT JXL_DEPRECATED JxlDecoderStatus JxlDecoderDCOutBufferSize(
const JxlDecoder* dec, const JxlPixelFormat* format, size_t* size);
/**
* Sets the buffer to write the lower resolution (8x8 sub-sampled) DC image
- * to. The size of the buffer must be at least as large as given by
+ * to. The size of the buffer must be at least as large as given by @ref
* JxlDecoderDCOutBufferSize. The buffer follows the format described by
* JxlPixelFormat. The DC image has dimensions ceil(xsize / 8) * ceil(ysize /
* 8). The buffer is owned by the caller.
*
* @param dec decoder object
* @param format format of pixels. Object owned by user and its contents are
- * copied internally.
+ * copied internally.
* @param buffer buffer type to output the pixel data to
* @param size size of buffer in bytes
- * @return JXL_DEC_SUCCESS on success, JXL_DEC_ERROR on error, such as
- * size too small.
+ * @return @ref JXL_DEC_SUCCESS on success, @ref JXL_DEC_ERROR on error, such as
+ * size too small.
*
- * DEPRECATED: the DC feature in this form will be removed. Use
- * JxlDecoderFlushImage for progressive rendering.
+ * @deprecated The DC feature in this form will be removed. Use @ref
+ * JxlDecoderFlushImage for progressive rendering.
*/
JXL_EXPORT JXL_DEPRECATED JxlDecoderStatus JxlDecoderSetDCOutBuffer(
JxlDecoder* dec, const JxlPixelFormat* format, void* buffer, size_t size);
/**
* Returns the minimum size in bytes of the image output pixel buffer for the
- * given format. This is the buffer for JxlDecoderSetImageOutBuffer. Requires
- * that the basic image information is available in the decoder in the case of
- * coalescing enabled (default). In case coalescing is disabled, this can only
- * be called after the JXL_DEC_FRAME event occurs. In that case, it will return
- * the size required to store the possibly cropped frame (which can be larger or
- * smaller than the image dimensions).
+ * given format. This is the buffer for @ref JxlDecoderSetImageOutBuffer.
+ * Requires that the basic image information is available in the decoder in the
+ * case of coalescing enabled (default). In case coalescing is disabled, this
+ * can only be called after the @ref JXL_DEC_FRAME event occurs. In that case,
+ * it will return the size required to store the possibly cropped frame (which
+ * can be larger or smaller than the image dimensions).
*
* @param dec decoder object
* @param format format of the pixels.
* @param size output value, buffer size in bytes
- * @return JXL_DEC_SUCCESS on success, JXL_DEC_ERROR on error, such as
- * information not available yet.
+ * @return @ref JXL_DEC_SUCCESS on success, @ref JXL_DEC_ERROR on error, such as
+ * information not available yet.
*/
JXL_EXPORT JxlDecoderStatus JxlDecoderImageOutBufferSize(
const JxlDecoder* dec, const JxlPixelFormat* format, size_t* size);
/**
* Sets the buffer to write the full resolution image to. This can be set when
- * the JXL_DEC_FRAME event occurs, must be set when the
- * JXL_DEC_NEED_IMAGE_OUT_BUFFER event occurs, and applies only for the current
- * frame. The size of the buffer must be at least as large as given by
- * JxlDecoderImageOutBufferSize. The buffer follows the format described by
- * JxlPixelFormat. The buffer is owned by the caller.
+ * the @ref JXL_DEC_FRAME event occurs, must be set when the @ref
+ * JXL_DEC_NEED_IMAGE_OUT_BUFFER event occurs, and applies only for the
+ * current frame. The size of the buffer must be at least as large as given
+ * by @ref JxlDecoderImageOutBufferSize. The buffer follows the format described
+ * by JxlPixelFormat. The buffer is owned by the caller.
*
* @param dec decoder object
* @param format format of the pixels. Object owned by user and its contents
- * are copied internally.
+ * are copied internally.
* @param buffer buffer type to output the pixel data to
* @param size size of buffer in bytes
- * @return JXL_DEC_SUCCESS on success, JXL_DEC_ERROR on error, such as
- * size too small.
+ * @return @ref JXL_DEC_SUCCESS on success, @ref JXL_DEC_ERROR on error, such as
+ * size too small.
*/
JXL_EXPORT JxlDecoderStatus JxlDecoderSetImageOutBuffer(
JxlDecoder* dec, const JxlPixelFormat* format, void* buffer, size_t size);
/**
- * Function type for JxlDecoderSetImageOutCallback.
- * @see JxlDecoderSetImageOutCallback for usage.
+ * Function type for @ref JxlDecoderSetImageOutCallback.
*
* The callback may be called simultaneously by different threads when using a
* threaded parallel runner, on different pixels.
*
- * @param opaque optional user data, as given to JxlDecoderSetImageOutCallback.
+ * @param opaque optional user data, as given to @ref
+ * JxlDecoderSetImageOutCallback.
* @param x horizontal position of leftmost pixel of the pixel data.
* @param y vertical position of the pixel data.
* @param num_pixels amount of pixels included in the pixel data, horizontally.
- * This is not the same as xsize of the full image, it may be smaller.
- * @param pixels pixel data as a horizontal stripe, in the format passed to
- * JxlDecoderSetImageOutCallback. The memory is not owned by the user, and is
- * only valid during the time the callback is running.
+ * This is not the same as xsize of the full image, it may be smaller.
+ * @param pixels pixel data as a horizontal stripe, in the format passed to @ref
+ * JxlDecoderSetImageOutCallback. The memory is not owned by the user, and
+ * is only valid during the time the callback is running.
*/
typedef void (*JxlImageOutCallback)(void* opaque, size_t x, size_t y,
size_t num_pixels, const void* pixels);
/**
- * Initialization callback for @c JxlDecoderSetMultithreadedImageOutCallback.
- *
- * @see JxlDecoderSetMultithreadedImageOutCallback
+ * Initialization callback for @ref JxlDecoderSetMultithreadedImageOutCallback.
*
- * @param init_opaque optional user data, as given to
- * @c JxlDecoderSetMultithreadedImageOutCallback.
+ * @param init_opaque optional user data, as given to @ref
+ * JxlDecoderSetMultithreadedImageOutCallback.
* @param num_threads maximum number of threads that will call the @c run
- * callback concurrently.
+ * callback concurrently.
* @param num_pixels_per_thread maximum number of pixels that will be passed in
- * one call to @c run.
+ * one call to @c run.
* @return a pointer to data that will be passed to the @c run callback, or
- * @c NULL if initialization failed.
+ * @c NULL if initialization failed.
*/
typedef void* (*JxlImageOutInitCallback)(void* init_opaque, size_t num_threads,
size_t num_pixels_per_thread);
/**
- * Worker callback for @c JxlDecoderSetMultithreadedImageOutCallback.
- *
- * @see JxlDecoderSetMultithreadedImageOutCallback
+ * Worker callback for @ref JxlDecoderSetMultithreadedImageOutCallback.
*
* @param run_opaque user data returned by the @c init callback.
* @param thread_id number in `[0, num_threads)` identifying the thread of the
- * current invocation of the callback.
+ * current invocation of the callback.
* @param x horizontal position of the first (leftmost) pixel of the pixel data.
* @param y vertical position of the pixel data.
* @param num_pixels number of pixels in the pixel data. May be less than the
- * full @c xsize of the image, and will be at most equal to the @c
- * num_pixels_per_thread that was passed to @c init.
- * @param pixels pixel data as a horizontal stripe, in the format passed to @c
- * JxlDecoderSetMultithreadedImageOutCallback. The data pointed to remains owned
- * by the caller and is only guaranteed to outlive the current callback
- * invocation.
+ * full @c xsize of the image, and will be at most equal to the @c
+ * num_pixels_per_thread that was passed to @c init.
+ * @param pixels pixel data as a horizontal stripe, in the format passed to @ref
+ * JxlDecoderSetMultithreadedImageOutCallback. The data pointed to
+ * remains owned by the caller and is only guaranteed to outlive the current
+ * callback invocation.
*/
typedef void (*JxlImageOutRunCallback)(void* run_opaque, size_t thread_id,
size_t x, size_t y, size_t num_pixels,
const void* pixels);
/**
- * Destruction callback for @c JxlDecoderSetMultithreadedImageOutCallback,
+ * Destruction callback for @ref JxlDecoderSetMultithreadedImageOutCallback,
* called after all invocations of the @c run callback to perform any
* appropriate clean-up of the @c run_opaque data returned by @c init.
*
- * @see JxlDecoderSetMultithreadedImageOutCallback
- *
* @param run_opaque user data returned by the @c init callback.
*/
typedef void (*JxlImageOutDestroyCallback)(void* run_opaque);
/**
- * Sets pixel output callback. This is an alternative to
- * JxlDecoderSetImageOutBuffer. This can be set when the JXL_DEC_FRAME event
- * occurs, must be set when the JXL_DEC_NEED_IMAGE_OUT_BUFFER event occurs, and
- * applies only for the current frame. Only one of JxlDecoderSetImageOutBuffer
- * or JxlDecoderSetImageOutCallback may be used for the same frame, not both at
- * the same time.
+ * Sets pixel output callback. This is an alternative to @ref
+ * JxlDecoderSetImageOutBuffer. This can be set when the @ref JXL_DEC_FRAME
+ * event occurs, must be set when the @ref JXL_DEC_NEED_IMAGE_OUT_BUFFER event
+ * occurs, and applies only for the current frame. Only one of @ref
+ * JxlDecoderSetImageOutBuffer or @ref JxlDecoderSetImageOutCallback may be used
+ * for the same frame, not both at the same time.
*
* The callback will be called multiple times, to receive the image
* data in small chunks. The callback receives a horizontal stripe of pixel
@@ -984,53 +1096,53 @@ typedef void (*JxlImageOutDestroyCallback)(void* run_opaque);
* simultaneously by different threads when using a threaded parallel runner, on
* different pixels.
*
- * If JxlDecoderFlushImage is not used, then each pixel will be visited exactly
- * once by the different callback calls, during processing with one or more
- * JxlDecoderProcessInput calls. These pixels are decoded to full detail, they
- * are not part of a lower resolution or lower quality progressive pass, but the
- * final pass.
- *
- * If JxlDecoderFlushImage is used, then in addition each pixel will be visited
- * zero or one times during the blocking JxlDecoderFlushImage call. Pixels
- * visited as a result of JxlDecoderFlushImage may represent a lower resolution
- * or lower quality intermediate progressive pass of the image. Any visited
- * pixel will be of a quality at least as good or better than previous visits of
- * this pixel. A pixel may be visited zero times if it cannot be decoded yet
- * or if it was already decoded to full precision (this behavior is not
- * guaranteed).
+ * If @ref JxlDecoderFlushImage is not used, then each pixel will be visited
+ * exactly once by the different callback calls, during processing with one or
+ * more @ref JxlDecoderProcessInput calls. These pixels are decoded to full
+ * detail, they are not part of a lower resolution or lower quality progressive
+ * pass, but the final pass.
+ *
+ * If @ref JxlDecoderFlushImage is used, then in addition each pixel will be
+ * visited zero or one times during the blocking @ref JxlDecoderFlushImage call.
+ * Pixels visited as a result of @ref JxlDecoderFlushImage may represent a lower
+ * resolution or lower quality intermediate progressive pass of the image. Any
+ * visited pixel will be of a quality at least as good or better than previous
+ * visits of this pixel. A pixel may be visited zero times if it cannot be
+ * decoded yet or if it was already decoded to full precision (this behavior is
+ * not guaranteed).
*
* @param dec decoder object
* @param format format of the pixels. Object owned by user; its contents are
- * copied internally.
+ * copied internally.
* @param callback the callback function receiving partial scanlines of pixel
- * data.
+ * data.
* @param opaque optional user data, which will be passed on to the callback,
- * may be NULL.
- * @return JXL_DEC_SUCCESS on success, JXL_DEC_ERROR on error, such as
- * JxlDecoderSetImageOutBuffer already set.
+ * may be NULL.
+ * @return @ref JXL_DEC_SUCCESS on success, @ref JXL_DEC_ERROR on error, such
+ * as @ref JxlDecoderSetImageOutBuffer already set.
*/
JXL_EXPORT JxlDecoderStatus
JxlDecoderSetImageOutCallback(JxlDecoder* dec, const JxlPixelFormat* format,
JxlImageOutCallback callback, void* opaque);
-/** Similar to @c JxlDecoderSetImageOutCallback except that the callback is
+/** Similar to @ref JxlDecoderSetImageOutCallback except that the callback is
* allowed an initialization phase during which it is informed of how many
* threads will call it concurrently, and those calls are further informed of
* which thread they are occurring in.
*
* @param dec decoder object
* @param format format of the pixels. Object owned by user; its contents are
- * copied internally.
+ * copied internally.
* @param init_callback initialization callback.
* @param run_callback the callback function receiving partial scanlines of
- * pixel data.
+ * pixel data.
* @param destroy_callback clean-up callback invoked after all calls to @c
- * run_callback. May be NULL if no clean-up is necessary.
+ * run_callback. May be NULL if no clean-up is necessary.
* @param init_opaque optional user data passed to @c init_callback, may be NULL
- * (unlike the return value from @c init_callback which may only be NULL if
- * initialization failed).
- * @return JXL_DEC_SUCCESS on success, JXL_DEC_ERROR on error, such as @c
- * JxlDecoderSetImageOutBuffer having already been called.
+ * (unlike the return value from @c init_callback which may only be NULL if
+ * initialization failed).
+ * @return @ref JXL_DEC_SUCCESS on success, @ref JXL_DEC_ERROR on error, such
+ * as @ref JxlDecoderSetImageOutBuffer having already been called.
*/
JXL_EXPORT JxlDecoderStatus JxlDecoderSetMultithreadedImageOutCallback(
JxlDecoder* dec, const JxlPixelFormat* format,
@@ -1039,18 +1151,18 @@ JXL_EXPORT JxlDecoderStatus JxlDecoderSetMultithreadedImageOutCallback(
/**
* Returns the minimum size in bytes of an extra channel pixel buffer for the
- * given format. This is the buffer for JxlDecoderSetExtraChannelBuffer.
+ * given format. This is the buffer for @ref JxlDecoderSetExtraChannelBuffer.
* Requires the basic image information is available in the decoder.
*
* @param dec decoder object
* @param format format of the pixels. The num_channels value is ignored and is
- * always treated to be 1.
+ * always treated to be 1.
* @param size output value, buffer size in bytes
- * @param index which extra channel to get, matching the index used in @see
- * JxlDecoderGetExtraChannelInfo. Must be smaller than num_extra_channels in the
- * associated JxlBasicInfo.
- * @return JXL_DEC_SUCCESS on success, JXL_DEC_ERROR on error, such as
- * information not available yet or invalid index.
+ * @param index which extra channel to get, matching the index used in @ref
+ * JxlDecoderGetExtraChannelInfo. Must be smaller than num_extra_channels in
+ * the associated JxlBasicInfo.
+ * @return @ref JXL_DEC_SUCCESS on success, @ref JXL_DEC_ERROR on error, such as
+ * information not available yet or invalid index.
*/
JXL_EXPORT JxlDecoderStatus JxlDecoderExtraChannelBufferSize(
const JxlDecoder* dec, const JxlPixelFormat* format, size_t* size,
@@ -1058,32 +1170,33 @@ JXL_EXPORT JxlDecoderStatus JxlDecoderExtraChannelBufferSize(
/**
* Sets the buffer to write an extra channel to. This can be set when
- * the JXL_DEC_FRAME or JXL_DEC_NEED_IMAGE_OUT_BUFFER event occurs, and applies
- * only for the current frame. The size of the buffer must be at least as large
- * as given by JxlDecoderExtraChannelBufferSize. The buffer follows the format
- * described by JxlPixelFormat, but where num_channels is 1. The buffer is owned
- * by the caller. The amount of extra channels is given by the
- * num_extra_channels field in the associated JxlBasicInfo, and the information
- * of individual extra channels can be queried with @see
+ * the @ref JXL_DEC_FRAME or @ref JXL_DEC_NEED_IMAGE_OUT_BUFFER event occurs,
+ * and applies only for the current frame. The size of the buffer must be at
+ * least as large as given by @ref JxlDecoderExtraChannelBufferSize. The buffer
+ * follows the format described by JxlPixelFormat, but where num_channels is 1.
+ * The buffer is owned by the caller. The amount of extra channels is given by
+ * the num_extra_channels field in the associated JxlBasicInfo, and the
+ * information of individual extra channels can be queried with @ref
* JxlDecoderGetExtraChannelInfo. To get multiple extra channels, this function
* must be called multiple times, once for each wanted index. Not all images
* have extra channels. The alpha channel is an extra channel and can be gotten
- * as part of the color channels when using an RGBA pixel buffer with
- * JxlDecoderSetImageOutBuffer, but additionally also can be gotten separately
- * as extra channel. The color channels themselves cannot be gotten this way.
+ * as part of the color channels when using an RGBA pixel buffer with @ref
+ * JxlDecoderSetImageOutBuffer, but additionally also can be gotten
+ * separately as extra channel. The color channels themselves cannot be gotten
+ * this way.
*
*
* @param dec decoder object
* @param format format of the pixels. Object owned by user and its contents
- * are copied internally. The num_channels value is ignored and is always
- * treated to be 1.
+ * are copied internally. The num_channels value is ignored and is always
+ * treated to be 1.
* @param buffer buffer type to output the pixel data to
* @param size size of buffer in bytes
- * @param index which extra channel to get, matching the index used in @see
- * JxlDecoderGetExtraChannelInfo. Must be smaller than num_extra_channels in the
- * associated JxlBasicInfo.
- * @return JXL_DEC_SUCCESS on success, JXL_DEC_ERROR on error, such as
- * size too small or invalid index.
+ * @param index which extra channel to get, matching the index used in @ref
+ * JxlDecoderGetExtraChannelInfo. Must be smaller than num_extra_channels in
+ * the associated JxlBasicInfo.
+ * @return @ref JXL_DEC_SUCCESS on success, @ref JXL_DEC_ERROR on error, such as
+ * size too small or invalid index.
*/
JXL_EXPORT JxlDecoderStatus
JxlDecoderSetExtraChannelBuffer(JxlDecoder* dec, const JxlPixelFormat* format,
@@ -1092,79 +1205,82 @@ JxlDecoderSetExtraChannelBuffer(JxlDecoder* dec, const JxlPixelFormat* format,
/**
* Sets output buffer for reconstructed JPEG codestream.
*
- * The data is owned by the caller and may be used by the decoder until
- * JxlDecoderReleaseJPEGBuffer is called or the decoder is destroyed or reset so
- * must be kept alive until then.
+ * The data is owned by the caller and may be used by the decoder until @ref
+ * JxlDecoderReleaseJPEGBuffer is called or the decoder is destroyed or
+ * reset so must be kept alive until then.
*
- * If a JPEG buffer was set before and released with
- * JxlDecoderReleaseJPEGBuffer, bytes that the decoder has already output should
- * not be included, only the remaining bytes output must be set.
+ * If a JPEG buffer was set before and released with @ref
+ * JxlDecoderReleaseJPEGBuffer, bytes that the decoder has already output
+ * should not be included, only the remaining bytes output must be set.
*
* @param dec decoder object
* @param data pointer to next bytes to write to
* @param size amount of bytes available starting from data
- * @return JXL_DEC_ERROR if output buffer was already set and
- * JxlDecoderReleaseJPEGBuffer was not called on it, JXL_DEC_SUCCESS otherwise
+ * @return @ref JXL_DEC_ERROR if output buffer was already set and @ref
+ * JxlDecoderReleaseJPEGBuffer was not called on it, @ref JXL_DEC_SUCCESS
+ * otherwise
*/
JXL_EXPORT JxlDecoderStatus JxlDecoderSetJPEGBuffer(JxlDecoder* dec,
uint8_t* data, size_t size);
/**
- * Releases buffer which was provided with JxlDecoderSetJPEGBuffer.
+ * Releases buffer which was provided with @ref JxlDecoderSetJPEGBuffer.
*
- * Calling JxlDecoderReleaseJPEGBuffer is required whenever
- * a buffer is already set and a new buffer needs to be added with
- * JxlDecoderSetJPEGBuffer, but is not required before JxlDecoderDestroy or
- * JxlDecoderReset.
+ * Calling @ref JxlDecoderReleaseJPEGBuffer is required whenever
+ * a buffer is already set and a new buffer needs to be added with @ref
+ * JxlDecoderSetJPEGBuffer, but is not required before @ref
+ * JxlDecoderDestroy or @ref JxlDecoderReset.
*
- * Calling JxlDecoderReleaseJPEGBuffer when no buffer is set is
+ * Calling @ref JxlDecoderReleaseJPEGBuffer when no buffer is set is
* not an error and returns 0.
*
* @param dec decoder object
* @return the amount of bytes the decoder has not yet written to of the data
- * set by JxlDecoderSetJPEGBuffer, or 0 if no buffer is set or
- * JxlDecoderReleaseJPEGBuffer was already called.
+ * set by @ref JxlDecoderSetJPEGBuffer, or 0 if no buffer is set or @ref
+ * JxlDecoderReleaseJPEGBuffer was already called.
*/
JXL_EXPORT size_t JxlDecoderReleaseJPEGBuffer(JxlDecoder* dec);
/**
* Sets output buffer for box output codestream.
*
- * The data is owned by the caller and may be used by the decoder until
- * JxlDecoderReleaseBoxBuffer is called or the decoder is destroyed or reset so
- * must be kept alive until then.
+ * The data is owned by the caller and may be used by the decoder until @ref
+ * JxlDecoderReleaseBoxBuffer is called or the decoder is destroyed or
+ * reset so must be kept alive until then.
*
- * If for the current box a box buffer was set before and released with
- * JxlDecoderReleaseBoxBuffer, bytes that the decoder has already output should
- * not be included, only the remaining bytes output must be set.
+ * If for the current box a box buffer was set before and released with @ref
+ * JxlDecoderReleaseBoxBuffer, bytes that the decoder has already output
+ * should not be included, only the remaining bytes output must be set.
*
- * The JxlDecoderReleaseBoxBuffer must be used at the next JXL_DEC_BOX event
- * or final JXL_DEC_SUCCESS event to compute the size of the output box bytes.
+ * The @ref JxlDecoderReleaseBoxBuffer must be used at the next @ref JXL_DEC_BOX
+ * event or final @ref JXL_DEC_SUCCESS event to compute the size of the output
+ * box bytes.
*
* @param dec decoder object
* @param data pointer to next bytes to write to
* @param size amount of bytes available starting from data
- * @return JXL_DEC_ERROR if output buffer was already set and
- * JxlDecoderReleaseBoxBuffer was not called on it, JXL_DEC_SUCCESS otherwise
+ * @return @ref JXL_DEC_ERROR if output buffer was already set and @ref
+ * JxlDecoderReleaseBoxBuffer was not called on it, @ref JXL_DEC_SUCCESS
+ * otherwise
*/
JXL_EXPORT JxlDecoderStatus JxlDecoderSetBoxBuffer(JxlDecoder* dec,
uint8_t* data, size_t size);
/**
- * Releases buffer which was provided with JxlDecoderSetBoxBuffer.
+ * Releases buffer which was provided with @ref JxlDecoderSetBoxBuffer.
*
- * Calling JxlDecoderReleaseBoxBuffer is required whenever
- * a buffer is already set and a new buffer needs to be added with
- * JxlDecoderSetBoxBuffer, but is not required before JxlDecoderDestroy or
- * JxlDecoderReset.
+ * Calling @ref JxlDecoderReleaseBoxBuffer is required whenever
+ * a buffer is already set and a new buffer needs to be added with @ref
+ * JxlDecoderSetBoxBuffer, but is not required before @ref
+ * JxlDecoderDestroy or @ref JxlDecoderReset.
*
- * Calling JxlDecoderReleaseBoxBuffer when no buffer is set is
+ * Calling @ref JxlDecoderReleaseBoxBuffer when no buffer is set is
* not an error and returns 0.
*
* @param dec decoder object
* @return the amount of bytes the decoder has not yet written to of the data
- * set by JxlDecoderSetBoxBuffer, or 0 if no buffer is set or
- * JxlDecoderReleaseBoxBuffer was already called.
+ * set by @ref JxlDecoderSetBoxBuffer, or 0 if no buffer is set or @ref
+ * JxlDecoderReleaseBoxBuffer was already called.
*/
JXL_EXPORT size_t JxlDecoderReleaseBoxBuffer(JxlDecoder* dec);
@@ -1177,66 +1293,68 @@ JXL_EXPORT size_t JxlDecoderReleaseBoxBuffer(JxlDecoder* dec);
* finished.
*
* The default mode is raw. This setting can only be changed before decoding, or
- * directly after a JXL_DEC_BOX event, and is remembered until the decoder is
- * reset or destroyed.
+ * directly after a @ref JXL_DEC_BOX event, and is remembered until the decoder
+ * is reset or destroyed.
*
* Enabling decompressed mode requires Brotli support from the library.
*
* @param dec decoder object
* @param decompress JXL_TRUE to transparently decompress, JXL_FALSE to get
- * boxes in raw mode.
- * @return JXL_DEC_ERROR if decompressed mode is set and Brotli is not
- * available, JXL_DEC_SUCCESS otherwise.
+ * boxes in raw mode.
+ * @return @ref JXL_DEC_ERROR if decompressed mode is set and Brotli is not
+ * available, @ref JXL_DEC_SUCCESS otherwise.
*/
JXL_EXPORT JxlDecoderStatus JxlDecoderSetDecompressBoxes(JxlDecoder* dec,
JXL_BOOL decompress);
/**
- * Outputs the type of the current box, after a JXL_DEC_BOX event occured, as 4
- * characters without null termination character. In case of a compressed "brob"
- * box, this will return "brob" if the decompressed argument is JXL_FALSE, or
- * the underlying box type if the decompressed argument is JXL_TRUE.
+ * Outputs the type of the current box, after a @ref JXL_DEC_BOX event occured,
+ * as 4 characters without null termination character. In case of a compressed
+ * "brob" box, this will return "brob" if the decompressed argument is
+ * JXL_FALSE, or the underlying box type if the decompressed argument is
+ * JXL_TRUE.
*
* The following box types are currently described in ISO/IEC 18181-2:
- * - "Exif": a box with EXIF metadata. Starts with a 4-byte tiff header
- * offset (big-endian uint32) that indicates the start of the actual EXIF data
- * (which starts with a tiff header). Usually the offset will be zero and the
- * EXIF data starts immediately after the offset field. The Exif orientation
- * should be ignored by applications; the JPEG XL codestream orientation takes
- * precedence and libjxl will by default apply the correct orientation
- * automatically (see JxlDecoderSetKeepOrientation).
- * - "xml ": a box with XML data, in particular XMP metadata.
- * - "jumb": a JUMBF superbox (JPEG Universal Metadata Box Format, ISO/IEC
- * 19566-5).
- * - "JXL ": mandatory signature box, must come first, 12 bytes long including
- * the box header
- * - "ftyp": a second mandatory signature box, must come second, 20 bytes long
- * including the box header
- * - "jxll": a JXL level box. This indicates if the codestream is level 5 or
- * level 10 compatible. If not present, it is level 5. Level 10 allows more
- * features such as very high image resolution and bit-depths above 16 bits
- * per channel. Added automatically by the encoder when
- * JxlEncoderSetCodestreamLevel is used
- * - "jxlc": a box with the image codestream, in case the codestream is not
- * split across multiple boxes. The codestream contains the JPEG XL image
- * itself, including the basic info such as image dimensions, ICC color
- * profile, and all the pixel data of all the image frames.
- * - "jxlp": a codestream box in case it is split across multiple boxes.
- * The contents are the same as in case of a jxlc box, when concatenated.
- * - "brob": a Brotli-compressed box, which otherwise represents an existing
- * type of box such as Exif or "xml ". When JxlDecoderSetDecompressBoxes is
- * set to JXL_TRUE, these boxes will be transparently decompressed by the
- * decoder.
- * - "jxli": frame index box, can list the keyframes in case of a JXL animation,
- * allowing the decoder to jump to individual frames more efficiently.
- * - "jbrd": JPEG reconstruction box, contains the information required to
- * byte-for-byte losslessly recontruct a JPEG-1 image. The JPEG DCT
- * coefficients (pixel content) themselves as well as the ICC profile are
- * encoded in the JXL codestream (jxlc or jxlp) itself. EXIF, XMP and JUMBF
- * metadata is encoded in the corresponding boxes. The jbrd box itself
- * contains information such as the remaining app markers of the JPEG-1 file
- * and everything else required to fit the information together into the
- * exact original JPEG file.
+ * - "Exif": a box with EXIF metadata. Starts with a 4-byte tiff header offset
+ * (big-endian uint32) that indicates the start of the actual EXIF data
+ * (which starts with a tiff header). Usually the offset will be zero and the
+ * EXIF data starts immediately after the offset field. The Exif orientation
+ * should be ignored by applications; the JPEG XL codestream orientation
+ * takes precedence and libjxl will by default apply the correct orientation
+ * automatically (see @ref JxlDecoderSetKeepOrientation).
+ * - "xml ": a box with XML data, in particular XMP metadata.
+ * - "jumb": a JUMBF superbox (JPEG Universal Metadata Box Format, ISO/IEC
+ * 19566-5).
+ * - "JXL ": mandatory signature box, must come first, 12 bytes long including
+ * the box header
+ * - "ftyp": a second mandatory signature box, must come second, 20 bytes long
+ * including the box header
+ * - "jxll": a JXL level box. This indicates if the codestream is level 5 or
+ * level 10 compatible. If not present, it is level 5. Level 10 allows more
+ * features such as very high image resolution and bit-depths above 16 bits
+ * per channel. Added automatically by the encoder when
+ * JxlEncoderSetCodestreamLevel is used
+ * - "jxlc": a box with the image codestream, in case the codestream is not
+ * split across multiple boxes. The codestream contains the JPEG XL image
+ * itself, including the basic info such as image dimensions, ICC color
+ * profile, and all the pixel data of all the image frames.
+ * - "jxlp": a codestream box in case it is split across multiple boxes.
+ * The contents are the same as in case of a jxlc box, when concatenated.
+ * - "brob": a Brotli-compressed box, which otherwise represents an existing
+ * type of box such as Exif or "xml ". When @ref JxlDecoderSetDecompressBoxes
+ * is set to JXL_TRUE, these boxes will be transparently decompressed by the
+ * decoder.
+ * - "jxli": frame index box, can list the keyframes in case of a JPEG XL
+ * animation allowing the decoder to jump to individual frames more
+ * efficiently.
+ * - "jbrd": JPEG reconstruction box, contains the information required to
+ * byte-for-byte losslessly recontruct a JPEG-1 image. The JPEG DCT
+ * coefficients (pixel content) themselves as well as the ICC profile are
+ * encoded in the JXL codestream (jxlc or jxlp) itself. EXIF, XMP and JUMBF
+ * metadata is encoded in the corresponding boxes. The jbrd box itself
+ * contains information such as the remaining app markers of the JPEG-1 file
+ * and everything else required to fit the information together into the
+ * exact original JPEG file.
*
* Other application-specific boxes can exist. Their typename should not begin
* with "jxl" or "JXL" or conflict with other existing typenames.
@@ -1250,62 +1368,73 @@ JXL_EXPORT JxlDecoderStatus JxlDecoderSetDecompressBoxes(JxlDecoder* dec,
* @param dec decoder object
* @param type buffer to copy the type into
* @param decompressed which box type to get: JXL_FALSE to get the raw box type,
- * which can be "brob", JXL_TRUE, get the underlying box type.
- * @return JXL_DEC_SUCCESS if the value is available, JXL_DEC_ERROR if not, for
- * example the JXL file does not use the container format.
+ * which can be "brob", JXL_TRUE, get the underlying box type.
+ * @return @ref JXL_DEC_SUCCESS if the value is available, @ref JXL_DEC_ERROR if
+ * not, for example the JXL file does not use the container format.
*/
JXL_EXPORT JxlDecoderStatus JxlDecoderGetBoxType(JxlDecoder* dec,
JxlBoxType type,
JXL_BOOL decompressed);
/**
- * Returns the size of a box as it appears in the container file, after the
+ * Returns the size of a box as it appears in the container file, after the @ref
* JXL_DEC_BOX event. For a non-compressed box, this is the size of the
* contents, excluding the 4 bytes indicating the box type. For a compressed
* "brob" box, this is the size of the compressed box contents plus the
* additional 4 byte indicating the underlying box type, but excluding the 4
* bytes indicating "brob". This function gives the size of the data that will
* be written in the output buffer when getting boxes in the default raw
- * compressed mode. When JxlDecoderSetDecompressBoxes is enabled, the return
- * value of function does not change, and the decompressed size is not known
- * before it has already been decompressed and output.
+ * compressed mode. When @ref JxlDecoderSetDecompressBoxes is enabled, the
+ * return value of function does not change, and the decompressed size is not
+ * known before it has already been decompressed and output.
*
* @param dec decoder object
* @param size raw size of the box in bytes
- * @return JXL_DEC_ERROR if no box size is available, JXL_DEC_SUCCESS otherwise.
+ * @return @ref JXL_DEC_ERROR if no box size is available, @ref JXL_DEC_SUCCESS
+ * otherwise.
*/
JXL_EXPORT JxlDecoderStatus JxlDecoderGetBoxSizeRaw(const JxlDecoder* dec,
uint64_t* size);
/**
- * Configures at which progressive steps in frame decoding the @ref
- * JXL_DEC_FRAME_PROGRESSION event occurs. By default, this is 0. The detail
- * values mean: 0 = only trigger for the DC image, the 8x8th lower resolution
- * image. 1 = also trigger when a full pass of groups is ready. Higher values
- * indicate more steps but are not yet implemented. Higher values always include
- * the events of lower values as well.
+ * Configures at which progressive steps in frame decoding these @ref
+ * JXL_DEC_FRAME_PROGRESSION event occurs. The default value for the level
+ * of detail if this function is never called is `kDC`.
+ *
+ * @param dec decoder object
+ * @param detail at which level of detail to trigger @ref
+ * JXL_DEC_FRAME_PROGRESSION
+ * @return @ref JXL_DEC_SUCCESS on success, @ref JXL_DEC_ERROR on error, such as
+ * an invalid value for the progressive detail.
+ */
+JXL_EXPORT JxlDecoderStatus
+JxlDecoderSetProgressiveDetail(JxlDecoder* dec, JxlProgressiveDetail detail);
+
+/**
+ * Returns the intended downsampling ratio for the progressive frame produced
+ * by @ref JxlDecoderFlushImage after the latest @ref JXL_DEC_FRAME_PROGRESSION
+ * event.
*
* @param dec decoder object
- * @param detail at which level of detail to trigger JXL_DEC_FRAME_PROGRESSION
+ * @return The intended downsampling ratio, can be 1, 2, 4 or 8.
*/
-JXL_EXPORT void JxlDecoderSetProgressiveDetail(JxlDecoder* dec,
- uint32_t detail);
+JXL_EXPORT size_t JxlDecoderGetIntendedDownsamplingRatio(JxlDecoder* dec);
/**
* Outputs progressive step towards the decoded image so far when only partial
- * input was received. If the flush was successful, the buffer set with
+ * input was received. If the flush was successful, the buffer set with @ref
* JxlDecoderSetImageOutBuffer will contain partial image data.
*
- * Can be called when JxlDecoderProcessInput returns JXL_DEC_NEED_MORE_INPUT,
- * after the JXL_DEC_FRAME event already occurred and before the
- * JXL_DEC_FULL_IMAGE event occurred for a frame.
+ * Can be called when @ref JxlDecoderProcessInput returns @ref
+ * JXL_DEC_NEED_MORE_INPUT, after the @ref JXL_DEC_FRAME event already occurred
+ * and before the @ref JXL_DEC_FULL_IMAGE event occurred for a frame.
*
* @param dec decoder object
- * @return JXL_DEC_SUCCESS if image data was flushed to the output buffer, or
- * JXL_DEC_ERROR when no flush was done, e.g. if not enough image data was
- * available yet even for flush, or no output buffer was set yet. An error is
- * not fatal, it only indicates no flushed image is available now, regular,
- * decoding can still be performed.
+ * @return @ref JXL_DEC_SUCCESS if image data was flushed to the output buffer,
+ * or @ref JXL_DEC_ERROR when no flush was done, e.g. if not enough image
+ * data was available yet even for flush, or no output buffer was set yet.
+ * This error is not fatal, it only indicates no flushed image is available
+ * right now. Regular decoding can still be performed.
*/
JXL_EXPORT JxlDecoderStatus JxlDecoderFlushImage(JxlDecoder* dec);
diff --git a/media/libjxl/src/lib/include/jxl/encode.h b/media/libjxl/src/lib/include/jxl/encode.h
index eb0d6c8370..4813e3b7cf 100644
--- a/media/libjxl/src/lib/include/jxl/encode.h
+++ b/media/libjxl/src/lib/include/jxl/encode.h
@@ -13,8 +13,8 @@
#ifndef JXL_ENCODE_H_
#define JXL_ENCODE_H_
+#include "jxl/cms_interface.h"
#include "jxl/codestream_header.h"
-#include "jxl/decode.h"
#include "jxl/jxl_export.h"
#include "jxl/memory_manager.h"
#include "jxl/parallel_runner.h"
@@ -73,15 +73,60 @@ typedef enum {
/** DEPRECATED: the encoder does not return this status and there is no need
* to handle or expect it.
+ * Instead, JXL_ENC_ERROR is returned with error condition
+ * JXL_ENC_ERR_NOT_SUPPORTED.
*/
JXL_ENC_NOT_SUPPORTED = 3,
} JxlEncoderStatus;
/**
- * Id of encoder options for a frame. This includes options such as the
- * image quality and compression speed for this frame. This does not include
- * non-frame related encoder options such as for boxes.
+ * Error conditions:
+ * API usage errors have the 0x80 bit set to 1
+ * Other errors have the 0x80 bit set to 0
+ */
+typedef enum {
+ /** No error
+ */
+ JXL_ENC_ERR_OK = 0,
+
+ /** Generic encoder error due to unspecified cause
+ */
+ JXL_ENC_ERR_GENERIC = 1,
+
+ /** Out of memory
+ * TODO(jon): actually catch this and return this error
+ */
+ JXL_ENC_ERR_OOM = 2,
+
+ /** JPEG bitstream reconstruction data could not be
+ * represented (e.g. too much tail data)
+ */
+ JXL_ENC_ERR_JBRD = 3,
+
+ /** Input is invalid (e.g. corrupt JPEG file or ICC profile)
+ */
+ JXL_ENC_ERR_BAD_INPUT = 4,
+
+ /** The encoder doesn't (yet) support this. Either no version of libjxl
+ * supports this, and the API is used incorrectly, or the libjxl version
+ * should have been checked before trying to do this.
+ */
+ JXL_ENC_ERR_NOT_SUPPORTED = 0x80,
+
+ /** The encoder API is used in an incorrect way.
+ * In this case, a debug build of libjxl should output a specific error
+ * message. (if not, please open an issue about it)
+ */
+ JXL_ENC_ERR_API_USAGE = 0x81,
+
+} JxlEncoderError;
+
+/**
+ * Id of encoder options for a frame. This includes options such as setting
+ * encoding effort/speed or overriding the use of certain coding tools, for this
+ * frame. This does not include non-frame related encoder options such as for
+ * boxes.
*/
typedef enum {
/** Sets encoder effort/speed level without affecting decoding speed. Valid
@@ -236,9 +281,12 @@ typedef enum {
*/
JXL_ENC_FRAME_SETTING_COLOR_TRANSFORM = 24,
- /** Color space for modular encoding: -1=default, 0-35=reverse color transform
+ /** Reversible color transform for modular encoding: -1=default, 0-41=RCT
* index, e.g. index 0 = none, index 6 = YCoCg.
- * The default behavior is to try several, depending on the speed setting.
+ * If this option is set to a non-default value, the RCT will be globally
+ * applied to the whole frame.
+ * The default behavior is to try several RCTs locally per modular group,
+ * depending on the speed and distance setting.
*/
JXL_ENC_FRAME_SETTING_MODULAR_COLOR_SPACE = 25,
@@ -355,6 +403,15 @@ JxlEncoderSetParallelRunner(JxlEncoder* enc, JxlParallelRunner parallel_runner,
void* parallel_runner_opaque);
/**
+ * Get the (last) error code in case JXL_ENC_ERROR was returned.
+ *
+ * @param enc encoder object.
+ * @return the JxlEncoderError that caused the (last) JXL_ENC_ERROR to be
+ * returned.
+ */
+JXL_EXPORT JxlEncoderError JxlEncoderGetError(JxlEncoder* enc);
+
+/**
* Encodes JPEG XL file using the available bytes. @p *avail_out indicates how
* many output bytes are available, and @p *next_out points to the input bytes.
* *avail_out will be decremented by the amount of bytes that have been
@@ -519,12 +576,12 @@ JxlEncoderAddJPEGFrame(const JxlEncoderFrameSettings* frame_settings,
* If the image has alpha, and alpha is not passed here, it will implicitly be
* set to all-opaque (an alpha value of 1.0 everywhere).
*
- * The color profile of the pixels depends on the value of uses_original_profile
- * in the JxlBasicInfo. If true, the pixels are assumed to be encoded in the
- * original profile that is set with JxlEncoderSetColorEncoding or
- * JxlEncoderSetICCProfile. If false, the pixels are assumed to be nonlinear
- * sRGB for integer data types (JXL_TYPE_UINT8, JXL_TYPE_UINT16), and linear
- * sRGB for floating point data types (JXL_TYPE_FLOAT16, JXL_TYPE_FLOAT).
+ * The pixels are assumed to be encoded in the original profile that is set with
+ * JxlEncoderSetColorEncoding or JxlEncoderSetICCProfile. If none of these
+ * functions were used, the pixels are assumed to be nonlinear sRGB for integer
+ * data types (JXL_TYPE_UINT8, JXL_TYPE_UINT16), and linear sRGB for floating
+ * point data types (JXL_TYPE_FLOAT16, JXL_TYPE_FLOAT).
+ *
* Sample values in floating-point pixel formats are allowed to be outside the
* nominal range, e.g. to represent out-of-sRGB-gamut colors in the
* uses_original_profile=false case. They are however not allowed to be NaN or
@@ -715,6 +772,7 @@ JXL_EXPORT void JxlEncoderCloseInput(JxlEncoder* enc);
* is an alternative to JxlEncoderSetICCProfile and only one of these two must
* be used. This one sets the color encoding as a @ref JxlColorEncoding, while
* the other sets it as ICC binary data.
+ * Must be called after JxlEncoderSetBasicInfo.
*
* @param enc encoder object.
* @param color color encoding. Object owned by the caller and its contents are
@@ -730,6 +788,7 @@ JxlEncoderSetColorEncoding(JxlEncoder* enc, const JxlColorEncoding* color);
* ICC color profile. This is an alternative to JxlEncoderSetColorEncoding and
* only one of these two must be used. This one sets the color encoding as ICC
* binary data, while the other defines it as a @ref JxlColorEncoding.
+ * Must be called after JxlEncoderSetBasicInfo.
*
* @param enc encoder object.
* @param icc_profile bytes of the original ICC profile
@@ -852,7 +911,25 @@ JXL_EXPORT JxlEncoderStatus JxlEncoderSetExtraChannelName(JxlEncoder* enc,
*/
JXL_EXPORT JxlEncoderStatus JxlEncoderFrameSettingsSetOption(
JxlEncoderFrameSettings* frame_settings, JxlEncoderFrameSettingId option,
- int32_t value);
+ int64_t value);
+
+/**
+ * Sets a frame-specific option of float type to the encoder options.
+ * The JxlEncoderFrameSettingId argument determines which option is set.
+ *
+ * @param frame_settings set of options and metadata for this frame. Also
+ * includes reference to the encoder object.
+ * @param option ID of the option to set.
+ * @param value Float value to set for this option.
+ * @return JXL_ENC_SUCCESS if the operation was successful, JXL_ENC_ERROR in
+ * case of an error, such as invalid or unknown option id, or invalid integer
+ * value for the given option. If an error is returned, the state of the
+ * JxlEncoderFrameSettings object is still valid and is the same as before this
+ * function was called.
+ */
+JXL_EXPORT JxlEncoderStatus JxlEncoderFrameSettingsSetFloatOption(
+ JxlEncoderFrameSettings* frame_settings, JxlEncoderFrameSettingId option,
+ float value);
/** Forces the encoder to use the box-based container format (BMFF) even
* when not necessary.
@@ -893,8 +970,8 @@ JXL_EXPORT JxlEncoderStatus
JxlEncoderStoreJPEGMetadata(JxlEncoder* enc, JXL_BOOL store_jpeg_metadata);
/** Sets the feature level of the JPEG XL codestream. Valid values are 5 and
- * 10. Keeping the default value of 5 is recommended for compatibility with all
- * decoders.
+ * 10, or -1 (to choose automatically). Using the minimum required level, or
+ * level 5 in most cases, is recommended for compatibility with all decoders.
*
* Level 5: for end-user image delivery, this level is the most widely
* supported level by image decoders and the recommended level to use unless a
@@ -910,16 +987,19 @@ JxlEncoderStoreJPEGMetadata(JxlEncoder* enc, JXL_BOOL store_jpeg_metadata);
* 5 limitations, allows CMYK color and up to 32 bits per color channel, but
* may be less widely supported.
*
- * The default value is 5. To use level 10 features, the setting must be
- * explicitly set to 10, the encoder will not automatically enable it. If
- * incompatible parameters such as too high image resolution for the current
- * level are set, the encoder will return an error. For internal coding tools,
- * the encoder will only use those compatible with the level setting.
+ * The default value is -1. This means the encoder will automatically choose
+ * between level 5 and level 10 based on what information is inside the @ref
+ * JxlBasicInfo structure. Do note that some level 10 features, particularly
+ * those used by animated JPEG XL codestreams, might require level 10, even
+ * though the @ref JxlBasicInfo only suggests level 5. In this case, the level
+ * must be explicitly set to 10, otherwise the encoder will return an error.
+ * The encoder will restrict internal encoding choices to those compatible with
+ * the level setting.
*
* This setting can only be set at the beginning, before encoding starts.
*
* @param enc encoder object.
- * @param level the level value to set, must be 5 or 10.
+ * @param level the level value to set, must be -1, 5, or 10.
* @return JXL_ENC_SUCCESS if the operation was successful, JXL_ENC_ERROR
* otherwise.
*/
diff --git a/media/libjxl/src/lib/include/jxl/types.h b/media/libjxl/src/lib/include/jxl/types.h
index ef804edcf1..722780fe8d 100644
--- a/media/libjxl/src/lib/include/jxl/types.h
+++ b/media/libjxl/src/lib/include/jxl/types.h
@@ -115,6 +115,33 @@ typedef struct {
*/
typedef char JxlBoxType[4];
+/** Types of progressive detail.
+ * Setting a progressive detail with value N implies all progressive details
+ * with smaller or equal value. Currently only the following level of
+ * progressive detail is implemented:
+ * - kDC (which implies kFrames)
+ * - kLastPasses (which implies kDC and kFrames)
+ * - kPasses (which implies kLastPasses, kDC and kFrames)
+ */
+typedef enum {
+ // after completed kRegularFrames
+ kFrames = 0,
+ // after completed DC (1:8)
+ kDC = 1,
+ // after completed AC passes that are the last pass for their resolution
+ // target.
+ kLastPasses = 2,
+ // after completed AC passes that are not the last pass for their resolution
+ // target.
+ kPasses = 3,
+ // during DC frame when lower resolution are completed (1:32, 1:16)
+ kDCProgressive = 4,
+ // after completed groups
+ kDCGroups = 5,
+ // after completed groups
+ kGroups = 6,
+} JxlProgressiveDetail;
+
#if defined(__cplusplus) || defined(c_plusplus)
}
#endif
diff --git a/media/libjxl/src/lib/jxl.cmake b/media/libjxl/src/lib/jxl.cmake
index 81fa04f98c..72c07f48e5 100644
--- a/media/libjxl/src/lib/jxl.cmake
+++ b/media/libjxl/src/lib/jxl.cmake
@@ -62,8 +62,12 @@ set(JPEGXL_INTERNAL_SOURCES_DEC
jxl/compressed_dc.cc
jxl/compressed_dc.h
jxl/convolve-inl.h
- jxl/convolve.cc
jxl/convolve.h
+ jxl/convolve_separable5.cc
+ jxl/convolve_separable7.cc
+ jxl/convolve_slow.cc
+ jxl/convolve_symmetric3.cc
+ jxl/convolve_symmetric5.cc
jxl/dct-inl.h
jxl/dct_block-inl.h
jxl/dct_scales.cc
@@ -90,9 +94,9 @@ set(JPEGXL_INTERNAL_SOURCES_DEC
jxl/dec_modular.h
jxl/dec_noise.cc
jxl/dec_noise.h
- jxl/dec_params.h
jxl/dec_patch_dictionary.cc
jxl/dec_patch_dictionary.h
+ jxl/dec_tone_mapping-inl.h
jxl/dec_transforms-inl.h
jxl/dec_xyb-inl.h
jxl/dec_xyb.cc
@@ -106,7 +110,6 @@ set(JPEGXL_INTERNAL_SOURCES_DEC
jxl/entropy_coder.h
jxl/epf.cc
jxl/epf.h
- jxl/exif.cc
jxl/exif.h
jxl/fast_dct-inl.h
jxl/fast_dct.cc
@@ -198,6 +201,8 @@ set(JPEGXL_INTERNAL_SOURCES_DEC
jxl/render_pipeline/stage_chroma_upsampling.h
jxl/render_pipeline/stage_epf.cc
jxl/render_pipeline/stage_epf.h
+ jxl/render_pipeline/stage_from_linear.cc
+ jxl/render_pipeline/stage_from_linear.h
jxl/render_pipeline/stage_gaborish.cc
jxl/render_pipeline/stage_gaborish.h
jxl/render_pipeline/stage_noise.cc
@@ -208,6 +213,10 @@ set(JPEGXL_INTERNAL_SOURCES_DEC
jxl/render_pipeline/stage_splines.h
jxl/render_pipeline/stage_spot.cc
jxl/render_pipeline/stage_spot.h
+ jxl/render_pipeline/stage_to_linear.cc
+ jxl/render_pipeline/stage_to_linear.h
+ jxl/render_pipeline/stage_tone_mapping.cc
+ jxl/render_pipeline/stage_tone_mapping.h
jxl/render_pipeline/stage_upsampling.cc
jxl/render_pipeline/stage_upsampling.h
jxl/render_pipeline/stage_write.cc
@@ -235,8 +244,6 @@ set(JPEGXL_INTERNAL_SOURCES_ENC
jxl/butteraugli/butteraugli.cc
jxl/butteraugli/butteraugli.h
jxl/butteraugli_wrapper.cc
- jxl/dec_file.cc
- jxl/dec_file.h
jxl/enc_ac_strategy.cc
jxl/enc_ac_strategy.h
jxl/enc_adaptive_quantization.cc
@@ -272,7 +279,6 @@ set(JPEGXL_INTERNAL_SOURCES_ENC
jxl/enc_entropy_coder.h
jxl/enc_external_image.cc
jxl/enc_external_image.h
- jxl/enc_fast_heuristics.cc
jxl/enc_file.cc
jxl/enc_file.h
jxl/enc_frame.cc
@@ -400,10 +406,10 @@ target_compile_options(jxl_dec-obj PRIVATE ${JPEGXL_INTERNAL_FLAGS})
target_compile_options(jxl_dec-obj PUBLIC ${JPEGXL_COVERAGE_FLAGS})
set_property(TARGET jxl_dec-obj PROPERTY POSITION_INDEPENDENT_CODE ON)
target_include_directories(jxl_dec-obj PUBLIC
- ${PROJECT_SOURCE_DIR}
- ${CMAKE_CURRENT_SOURCE_DIR}/include
- $<TARGET_PROPERTY:hwy,INTERFACE_INCLUDE_DIRECTORIES>
- $<TARGET_PROPERTY:brotlicommon-static,INTERFACE_INCLUDE_DIRECTORIES>
+ "$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}>"
+ "$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>"
+ "$<BUILD_INTERFACE:$<TARGET_PROPERTY:hwy,INTERFACE_INCLUDE_DIRECTORIES>>"
+ "$<BUILD_INTERFACE:$<TARGET_PROPERTY:brotlicommon-static,INTERFACE_INCLUDE_DIRECTORIES>>"
)
target_compile_definitions(jxl_dec-obj PUBLIC
${OBJ_COMPILE_DEFINITIONS}
@@ -441,6 +447,9 @@ else ()
)
endif ()
+# Generate version.h
+configure_file("jxl/version.h.in" "include/jxl/version.h")
+
# Headers for exporting/importing public headers
include(GenerateExportHeader)
set_target_properties(jxl_dec-obj PROPERTIES
@@ -470,9 +479,9 @@ add_library(jxl_dec-static STATIC
target_link_libraries(jxl_dec-static
PUBLIC ${JPEGXL_COVERAGE_FLAGS} ${JPEGXL_DEC_INTERNAL_LIBS})
target_include_directories(jxl_dec-static PUBLIC
- "${PROJECT_SOURCE_DIR}"
- "${CMAKE_CURRENT_SOURCE_DIR}/include"
- "${CMAKE_CURRENT_BINARY_DIR}/include")
+ "$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}>"
+ "$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>"
+ "$<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}/include>")
# The list of objects in the static and shared libraries.
set(JPEGXL_INTERNAL_OBJECTS
@@ -491,9 +500,9 @@ add_library(jxl-static STATIC ${JPEGXL_INTERNAL_OBJECTS})
target_link_libraries(jxl-static
PUBLIC ${JPEGXL_COVERAGE_FLAGS} ${JPEGXL_INTERNAL_LIBS})
target_include_directories(jxl-static PUBLIC
- "${PROJECT_SOURCE_DIR}"
- "${CMAKE_CURRENT_SOURCE_DIR}/include"
- "${CMAKE_CURRENT_BINARY_DIR}/include")
+ "$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}>"
+ "$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>"
+ "$<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}/include>")
# JXL_EXPORT is defined to "__declspec(dllimport)" automatically by CMake
# in Windows builds when including headers from the C API and compiling from
@@ -506,7 +515,7 @@ target_compile_definitions(jxl_dec-static INTERFACE -DJXL_EXPORT=)
# TODO(deymo): Move TCMalloc linkage to the tools/ directory since the library
# shouldn't do any allocs anyway.
-if(${JPEGXL_ENABLE_TCMALLOC})
+if(JPEGXL_ENABLE_TCMALLOC)
pkg_check_modules(TCMallocMinimal REQUIRED IMPORTED_TARGET
libtcmalloc_minimal)
# tcmalloc 2.8 has concurrency issues that makes it sometimes return nullptr
@@ -526,15 +535,14 @@ endif() # JPEGXL_ENABLE_TCMALLOC
# Install the static library too, but as jxl.a file without the -static except
# in Windows.
-if (NOT WIN32)
+if (NOT WIN32 OR MINGW)
set_target_properties(jxl-static PROPERTIES OUTPUT_NAME "jxl")
set_target_properties(jxl_dec-static PROPERTIES OUTPUT_NAME "jxl_dec")
endif()
install(TARGETS jxl-static DESTINATION ${CMAKE_INSTALL_LIBDIR})
install(TARGETS jxl_dec-static DESTINATION ${CMAKE_INSTALL_LIBDIR})
-if (((NOT DEFINED "${TARGET_SUPPORTS_SHARED_LIBS}") OR
- TARGET_SUPPORTS_SHARED_LIBS) AND NOT JPEGXL_STATIC AND BUILD_SHARED_LIBS)
+if (BUILD_SHARED_LIBS)
# Public shared library.
add_library(jxl SHARED ${JPEGXL_INTERNAL_OBJECTS})
@@ -543,8 +551,8 @@ target_link_libraries(jxl PUBLIC ${JPEGXL_COVERAGE_FLAGS})
target_link_libraries(jxl PRIVATE ${JPEGXL_INTERNAL_SHARED_LIBS})
# Shared library include path contains only the "include/" paths.
target_include_directories(jxl PUBLIC
- "${CMAKE_CURRENT_SOURCE_DIR}/include"
- "${CMAKE_CURRENT_BINARY_DIR}/include")
+ "$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>"
+ "$<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}/include>")
set_target_properties(jxl PROPERTIES
VERSION ${JPEGXL_LIBRARY_VERSION}
SOVERSION ${JPEGXL_LIBRARY_SOVERSION}
@@ -591,7 +599,7 @@ foreach(target IN ITEMS jxl jxl_dec)
# This hides the default visibility symbols from static libraries bundled into
# the shared library. In particular this prevents exposing symbols from hwy
# and skcms in the shared library.
- if(${LINKER_SUPPORT_EXCLUDE_LIBS})
+ if(LINKER_SUPPORT_EXCLUDE_LIBS)
set_property(TARGET ${target} APPEND_STRING PROPERTY
LINK_FLAGS " ${LINKER_EXCLUDE_LIBS_FLAG}")
endif()
@@ -607,8 +615,7 @@ install(TARGETS jxl
else()
add_library(jxl ALIAS jxl-static)
add_library(jxl_dec ALIAS jxl_dec-static)
-endif() # TARGET_SUPPORTS_SHARED_LIBS AND NOT JPEGXL_STATIC AND
- # BUILD_SHARED_LIBS
+endif() # BUILD_SHARED_LIBS
# Add a pkg-config file for libjxl.
set(JPEGXL_LIBRARY_REQUIRES
@@ -616,6 +623,20 @@ set(JPEGXL_LIBRARY_REQUIRES
if(NOT JPEGXL_ENABLE_SKCMS)
set(JPEGXL_LIBRARY_REQUIRES "${JPEGXL_LIBRARY_REQUIRES} lcms2")
endif()
+
+# Allow adding prefix if CMAKE_INSTALL_INCLUDEDIR not absolute.
+if(IS_ABSOLUTE "${CMAKE_INSTALL_INCLUDEDIR}")
+ set(PKGCONFIG_TARGET_INCLUDES "${CMAKE_INSTALL_INCLUDEDIR}")
+else()
+ set(PKGCONFIG_TARGET_INCLUDES "\${prefix}/${CMAKE_INSTALL_INCLUDEDIR}")
+endif()
+# Allow adding prefix if CMAKE_INSTALL_LIBDIR not absolute.
+if(IS_ABSOLUTE "${CMAKE_INSTALL_LIBDIR}")
+ set(PKGCONFIG_TARGET_LIBS "${CMAKE_INSTALL_LIBDIR}")
+else()
+ set(PKGCONFIG_TARGET_LIBS "\${exec_prefix}/${CMAKE_INSTALL_LIBDIR}")
+endif()
+
configure_file("${CMAKE_CURRENT_SOURCE_DIR}/jxl/libjxl.pc.in"
"libjxl.pc" @ONLY)
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/libjxl.pc"
diff --git a/media/libjxl/src/lib/jxl/alpha.cc b/media/libjxl/src/lib/jxl/alpha.cc
index 77ac9021d7..f0ab39ac08 100644
--- a/media/libjxl/src/lib/jxl/alpha.cc
+++ b/media/libjxl/src/lib/jxl/alpha.cc
@@ -108,4 +108,13 @@ void UnpremultiplyAlpha(float* JXL_RESTRICT r, float* JXL_RESTRICT g,
}
}
+void UnpremultiplyAlpha(float* JXL_RESTRICT rgba, size_t num_pixels) {
+ for (size_t x = 0, ix = 0; x < num_pixels; ++x, ix += 4) {
+ const float multiplier = 1.f / std::max(kSmallAlpha, rgba[ix + 3]);
+ rgba[ix] *= multiplier;
+ rgba[ix + 1] *= multiplier;
+ rgba[ix + 2] *= multiplier;
+ }
+}
+
} // namespace jxl
diff --git a/media/libjxl/src/lib/jxl/alpha.h b/media/libjxl/src/lib/jxl/alpha.h
index efb76c800f..f49790b582 100644
--- a/media/libjxl/src/lib/jxl/alpha.h
+++ b/media/libjxl/src/lib/jxl/alpha.h
@@ -60,6 +60,7 @@ void PremultiplyAlpha(float* JXL_RESTRICT r, float* JXL_RESTRICT g,
void UnpremultiplyAlpha(float* JXL_RESTRICT r, float* JXL_RESTRICT g,
float* JXL_RESTRICT b, const float* JXL_RESTRICT a,
size_t num_pixels);
+void UnpremultiplyAlpha(float* JXL_RESTRICT rgba, size_t num_pixels);
} // namespace jxl
diff --git a/media/libjxl/src/lib/jxl/alpha_test.cc b/media/libjxl/src/lib/jxl/alpha_test.cc
index d90bbd37d9..c643fbdc54 100644
--- a/media/libjxl/src/lib/jxl/alpha_test.cc
+++ b/media/libjxl/src/lib/jxl/alpha_test.cc
@@ -5,8 +5,7 @@
#include "lib/jxl/alpha.h"
-#include "gmock/gmock.h"
-#include "gtest/gtest.h"
+#include "lib/jxl/test_utils.h"
namespace jxl {
namespace {
diff --git a/media/libjxl/src/lib/jxl/ans_common.cc b/media/libjxl/src/lib/jxl/ans_common.cc
index cc0d58b446..32a658fb43 100644
--- a/media/libjxl/src/lib/jxl/ans_common.cc
+++ b/media/libjxl/src/lib/jxl/ans_common.cc
@@ -12,11 +12,11 @@
namespace jxl {
-std::vector<int> CreateFlatHistogram(int length, int total_count) {
+std::vector<int32_t> CreateFlatHistogram(int length, int total_count) {
JXL_ASSERT(length > 0);
JXL_ASSERT(length <= total_count);
const int count = total_count / length;
- std::vector<int> result(length, count);
+ std::vector<int32_t> result(length, count);
const int rem_counts = total_count % length;
for (int i = 0; i < rem_counts; ++i) {
++result[i];
@@ -48,7 +48,7 @@ std::vector<int> CreateFlatHistogram(int length, int total_count) {
// underfull nor overfull, and represents exactly two symbols. The overfull
// entry might be either overfull or underfull, and is pushed into the
// corresponding stack.
-void InitAliasTable(std::vector<int> distribution, uint32_t range,
+void InitAliasTable(std::vector<int32_t> distribution, uint32_t range,
size_t log_alpha_size, AliasTable::Entry* JXL_RESTRICT a) {
while (!distribution.empty() && distribution.back() == 0) {
distribution.pop_back();
diff --git a/media/libjxl/src/lib/jxl/ans_common.h b/media/libjxl/src/lib/jxl/ans_common.h
index 12ce1eff36..fb5058e310 100644
--- a/media/libjxl/src/lib/jxl/ans_common.h
+++ b/media/libjxl/src/lib/jxl/ans_common.h
@@ -32,7 +32,7 @@ static JXL_INLINE uint32_t GetPopulationCountPrecision(uint32_t logcount,
// Returns a histogram where the counts are positive, differ by at most 1,
// and add up to total_count. The bigger counts (if any) are at the beginning
// of the histogram.
-std::vector<int> CreateFlatHistogram(int length, int total_count);
+std::vector<int32_t> CreateFlatHistogram(int length, int total_count);
// An alias table implements a mapping from the [0, ANS_TAB_SIZE) range into
// the [0, ANS_MAX_ALPHABET_SIZE) range, satisfying the following conditions:
@@ -135,7 +135,7 @@ struct AliasTable {
};
// Computes an alias table for a given distribution.
-void InitAliasTable(std::vector<int> distribution, uint32_t range,
+void InitAliasTable(std::vector<int32_t> distribution, uint32_t range,
size_t log_alpha_size, AliasTable::Entry* JXL_RESTRICT a);
} // namespace jxl
diff --git a/media/libjxl/src/lib/jxl/ans_test.cc b/media/libjxl/src/lib/jxl/ans_test.cc
index 3d7eaf59cc..ca9883d372 100644
--- a/media/libjxl/src/lib/jxl/ans_test.cc
+++ b/media/libjxl/src/lib/jxl/ans_test.cc
@@ -112,7 +112,7 @@ void RoundtripRandomUnbalancedStream(int alphabet_size) {
constexpr int kPrecision = 1 << 10;
Rng rng(0);
for (size_t i = 0; i < kReps; i++) {
- std::vector<int> distributions[kNumHistograms];
+ std::vector<int> distributions[kNumHistograms] = {};
for (int j = 0; j < kNumHistograms; j++) {
distributions[j].resize(kPrecision);
int symbol = 0;
diff --git a/media/libjxl/src/lib/jxl/aux_out.cc b/media/libjxl/src/lib/jxl/aux_out.cc
index 5624bf492c..d8ee9460f6 100644
--- a/media/libjxl/src/lib/jxl/aux_out.cc
+++ b/media/libjxl/src/lib/jxl/aux_out.cc
@@ -25,6 +25,12 @@ void AuxOut::Print(size_t num_inputs) const {
printf("Average butteraugli iters: %10.2f\n",
num_butteraugli_iters * 1.0 / num_inputs);
+ if (min_quant_rescale != 1.0 || max_quant_rescale != 1.0) {
+ printf("quant rescale range: %f .. %f\n", min_quant_rescale,
+ max_quant_rescale);
+ printf("bitrate error range: %.3f%% .. %.3f%%\n",
+ 100.0f * min_bitrate_error, 100.0f * max_bitrate_error);
+ }
for (size_t i = 0; i < layers.size(); ++i) {
if (layers[i].total_bits != 0) {
diff --git a/media/libjxl/src/lib/jxl/aux_out.h b/media/libjxl/src/lib/jxl/aux_out.h
index 23d498652f..7076603125 100644
--- a/media/libjxl/src/lib/jxl/aux_out.h
+++ b/media/libjxl/src/lib/jxl/aux_out.h
@@ -37,72 +37,54 @@ namespace jxl {
enum {
kLayerHeader = 0,
kLayerTOC,
+ kLayerDictionary,
+ kLayerSplines,
kLayerNoise,
kLayerQuant,
- kLayerDequantTables,
- kLayerOrder,
+ kLayerModularTree,
+ kLayerModularGlobal,
kLayerDC,
+ kLayerModularDcGroup,
kLayerControlFields,
+ kLayerOrder,
kLayerAC,
kLayerACTokens,
- kLayerDictionary,
- kLayerDots,
- kLayerSplines,
- kLayerLossless,
- kLayerModularGlobal,
- kLayerModularDcGroup,
kLayerModularAcGroup,
- kLayerModularTree,
- kLayerAlpha,
- kLayerDepth,
- kLayerExtraChannels,
kNumImageLayers
};
static inline const char* LayerName(size_t layer) {
switch (layer) {
case kLayerHeader:
- return "headers";
+ return "Headers";
case kLayerTOC:
return "TOC";
+ case kLayerDictionary:
+ return "Patches";
+ case kLayerSplines:
+ return "Splines";
case kLayerNoise:
- return "noise";
+ return "Noise";
case kLayerQuant:
- return "quantizer";
- case kLayerDequantTables:
- return "quant tables";
- case kLayerOrder:
- return "order";
+ return "Quantizer";
+ case kLayerModularTree:
+ return "ModularTree";
+ case kLayerModularGlobal:
+ return "ModularGlobal";
case kLayerDC:
return "DC";
+ case kLayerModularDcGroup:
+ return "ModularDcGroup";
case kLayerControlFields:
return "ControlFields";
+ case kLayerOrder:
+ return "CoeffOrder";
case kLayerAC:
- return "AC";
+ return "ACHistograms";
case kLayerACTokens:
return "ACTokens";
- case kLayerDictionary:
- return "dictionary";
- case kLayerDots:
- return "dots";
- case kLayerSplines:
- return "splines";
- case kLayerLossless:
- return "lossless";
- case kLayerModularGlobal:
- return "modularGlobal";
- case kLayerModularDcGroup:
- return "modularDcGroup";
case kLayerModularAcGroup:
- return "modularAcGroup";
- case kLayerModularTree:
- return "modularTree";
- case kLayerAlpha:
- return "alpha";
- case kLayerDepth:
- return "depth";
- case kLayerExtraChannels:
- return "extra channels";
+ return "ModularAcGroup";
default:
JXL_ABORT("Invalid layer %d\n", static_cast<int>(layer));
}
@@ -167,10 +149,22 @@ struct AuxOut {
dc_pred_usage[i] += victim.dc_pred_usage[i];
dc_pred_usage_xb[i] += victim.dc_pred_usage_xb[i];
}
+ max_quant_rescale = std::max(max_quant_rescale, victim.max_quant_rescale);
+ min_quant_rescale = std::min(min_quant_rescale, victim.min_quant_rescale);
+ max_bitrate_error = std::max(max_bitrate_error, victim.max_bitrate_error);
+ min_bitrate_error = std::min(min_bitrate_error, victim.min_bitrate_error);
}
void Print(size_t num_inputs) const;
+ size_t TotalBits() const {
+ size_t total = 0;
+ for (const auto& layer : layers) {
+ total += layer.total_bits;
+ }
+ return total;
+ }
+
template <typename T>
void DumpImage(const char* label, const Image3<T>& image) const {
if (!dump_image) return;
@@ -285,6 +279,11 @@ struct AuxOut {
int num_butteraugli_iters = 0;
+ float max_quant_rescale = 1.0f;
+ float min_quant_rescale = 1.0f;
+ float min_bitrate_error = 0.0f;
+ float max_bitrate_error = 0.0f;
+
// If not empty, additional debugging information (e.g. debug images) is
// saved in files with this prefix.
std::string debug_prefix;
diff --git a/media/libjxl/src/lib/jxl/base/cache_aligned.cc b/media/libjxl/src/lib/jxl/base/cache_aligned.cc
index 411d414521..9a9cc585a1 100644
--- a/media/libjxl/src/lib/jxl/base/cache_aligned.cc
+++ b/media/libjxl/src/lib/jxl/base/cache_aligned.cc
@@ -71,8 +71,9 @@ void* CacheAligned::Allocate(const size_t payload_size, size_t offset) {
// To avoid wasting space, the header resides at the end of `unused`,
// which therefore cannot be empty (offset == 0).
if (offset == 0) {
- offset = kAlignment; // = round_up(sizeof(AllocationHeader), kAlignment)
- static_assert(sizeof(AllocationHeader) <= kAlignment, "Else: round up");
+ // SVE/RVV vectors can be large, so we cannot rely on them (including the
+ // padding at the end of AllocationHeader) to fit in kAlignment.
+ offset = hwy::RoundUpTo(sizeof(AllocationHeader), kAlignment);
}
#if JXL_USE_MMAP
diff --git a/media/libjxl/src/lib/jxl/base/compiler_specific.h b/media/libjxl/src/lib/jxl/base/compiler_specific.h
index 24bbf74754..7aa8b99151 100644
--- a/media/libjxl/src/lib/jxl/base/compiler_specific.h
+++ b/media/libjxl/src/lib/jxl/base/compiler_specific.h
@@ -10,6 +10,8 @@
#include <stdint.h>
+#include "lib/jxl/base/sanitizer_definitions.h"
+
// #if is shorter and safer than #ifdef. *_VERSION are zero if not detected,
// otherwise 100 * major + minor version. Note that other packages check for
// #ifdef COMPILER_MSVC, so we cannot use that same name.
@@ -75,6 +77,16 @@
#define JXL_MAYBE_UNUSED __attribute__((unused))
#endif
+// MSAN execution won't hurt if some code it not inlined, but this can greatly
+// improve compilation time. Unfortunately this macro can not be used just
+// everywhere - inside header files it leads to "multiple definition" error;
+// though it would be better not to have JXL_INLINE in header overall.
+#if JXL_MEMORY_SANITIZER || JXL_ADDRESS_SANITIZER || JXL_THREAD_SANITIZER
+#define JXL_MAYBE_INLINE JXL_MAYBE_UNUSED
+#else
+#define JXL_MAYBE_INLINE JXL_INLINE
+#endif
+
#if JXL_COMPILER_MSVC
// Unsupported, __assume is not the same.
#define JXL_LIKELY(expr) expr
diff --git a/media/libjxl/src/lib/jxl/base/data_parallel.h b/media/libjxl/src/lib/jxl/base/data_parallel.h
index 2fc03de6e2..666925aea4 100644
--- a/media/libjxl/src/lib/jxl/base/data_parallel.h
+++ b/media/libjxl/src/lib/jxl/base/data_parallel.h
@@ -31,6 +31,9 @@ class ThreadPool {
ThreadPool(const ThreadPool&) = delete;
ThreadPool& operator&(const ThreadPool&) = delete;
+ JxlParallelRunner runner() const { return runner_; }
+ void* runner_opaque() const { return runner_opaque_; }
+
// Runs init_func(num_threads) followed by data_func(task, thread) on worker
// thread(s) for every task in [begin, end). init_func() must return a Status
// indicating whether the initialization succeeded.
diff --git a/media/libjxl/src/lib/jxl/base/file_io.h b/media/libjxl/src/lib/jxl/base/file_io.h
index cb42eae834..8c7777c945 100644
--- a/media/libjxl/src/lib/jxl/base/file_io.h
+++ b/media/libjxl/src/lib/jxl/base/file_io.h
@@ -77,7 +77,8 @@ template <typename ContainerType>
static inline Status ReadFile(const std::string& pathname,
ContainerType* JXL_RESTRICT bytes) {
FileWrapper f(pathname, "rb");
- if (f == nullptr) return JXL_FAILURE("Failed to open file for reading");
+ if (f == nullptr)
+ return JXL_FAILURE("Failed to open file for reading: %s", pathname.c_str());
// Get size of file in bytes
const int64_t size = f.size();
diff --git a/media/libjxl/src/lib/jxl/base/random.h b/media/libjxl/src/lib/jxl/base/random.h
index c8b426b1cb..663b88c95d 100644
--- a/media/libjxl/src/lib/jxl/base/random.h
+++ b/media/libjxl/src/lib/jxl/base/random.h
@@ -20,7 +20,8 @@
namespace jxl {
struct Rng {
explicit Rng(size_t seed)
- : s{0x94D049BB133111EBull, 0xBF58476D1CE4E5B9ull + seed} {}
+ : s{static_cast<uint64_t>(0x94D049BB133111EBull),
+ static_cast<uint64_t>(0xBF58476D1CE4E5B9ull) + seed} {}
// Xorshift128+ adapted from xorshift128+-inl.h
uint64_t operator()() {
diff --git a/media/libjxl/src/lib/jxl/blending_test.cc b/media/libjxl/src/lib/jxl/blending_test.cc
index cede9ce664..e032b99a4e 100644
--- a/media/libjxl/src/lib/jxl/blending_test.cc
+++ b/media/libjxl/src/lib/jxl/blending_test.cc
@@ -3,11 +3,9 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
-#include "gmock/gmock.h"
-#include "gtest/gtest.h"
#include "lib/extras/codec.h"
-#include "lib/jxl/dec_file.h"
#include "lib/jxl/image_test_utils.h"
+#include "lib/jxl/test_utils.h"
#include "lib/jxl/testdata.h"
namespace jxl {
@@ -20,9 +18,8 @@ TEST(BlendingTest, Crops) {
const PaddedBytes compressed =
ReadTestData("jxl/blending/cropped_traffic_light.jxl");
- DecompressParams dparams;
CodecInOut decoded;
- ASSERT_TRUE(DecodeFile(dparams, compressed, &decoded, pool));
+ ASSERT_TRUE(test::DecodeFile({}, compressed, &decoded, pool));
ASSERT_THAT(decoded.frames, SizeIs(4));
int i = 0;
diff --git a/media/libjxl/src/lib/jxl/butteraugli/butteraugli.cc b/media/libjxl/src/lib/jxl/butteraugli/butteraugli.cc
index c08dd0536c..ee1a53013f 100644
--- a/media/libjxl/src/lib/jxl/butteraugli/butteraugli.cc
+++ b/media/libjxl/src/lib/jxl/butteraugli/butteraugli.cc
@@ -272,7 +272,20 @@ namespace jxl {
namespace HWY_NAMESPACE {
// These templates are not found via ADL.
+using hwy::HWY_NAMESPACE::Abs;
+using hwy::HWY_NAMESPACE::Div;
+using hwy::HWY_NAMESPACE::Gt;
+using hwy::HWY_NAMESPACE::IfThenElse;
+using hwy::HWY_NAMESPACE::IfThenElseZero;
+using hwy::HWY_NAMESPACE::Lt;
+using hwy::HWY_NAMESPACE::Max;
+using hwy::HWY_NAMESPACE::Mul;
+using hwy::HWY_NAMESPACE::MulAdd;
+using hwy::HWY_NAMESPACE::MulSub;
+using hwy::HWY_NAMESPACE::Neg;
+using hwy::HWY_NAMESPACE::Sub;
using hwy::HWY_NAMESPACE::Vec;
+using hwy::HWY_NAMESPACE::ZeroIfNegative;
template <class D, class V>
HWY_INLINE V MaximumClamp(D d, V v, double kMaxVal) {
@@ -280,24 +293,26 @@ HWY_INLINE V MaximumClamp(D d, V v, double kMaxVal) {
const V mul = Set(d, kMul);
const V maxval = Set(d, kMaxVal);
// If greater than maxval or less than -maxval, replace with if_*.
- const V if_pos = MulAdd(v - maxval, mul, maxval);
- const V if_neg = MulSub(v + maxval, mul, maxval);
- const V pos_or_v = IfThenElse(v >= maxval, if_pos, v);
- return IfThenElse(v < Neg(maxval), if_neg, pos_or_v);
+ const V if_pos = MulAdd(Sub(v, maxval), mul, maxval);
+ const V if_neg = MulSub(Add(v, maxval), mul, maxval);
+ const V pos_or_v = IfThenElse(Ge(v, maxval), if_pos, v);
+ return IfThenElse(Lt(v, Neg(maxval)), if_neg, pos_or_v);
}
// Make area around zero less important (remove it).
template <class D, class V>
HWY_INLINE V RemoveRangeAroundZero(const D d, const double kw, const V x) {
const auto w = Set(d, kw);
- return IfThenElse(x > w, x - w, IfThenElseZero(x < Neg(w), x + w));
+ return IfThenElse(Gt(x, w), Sub(x, w),
+ IfThenElseZero(Lt(x, Neg(w)), Add(x, w)));
}
// Make area around zero more important (2x it until the limit).
template <class D, class V>
HWY_INLINE V AmplifyRangeAroundZero(const D d, const double kw, const V x) {
const auto w = Set(d, kw);
- return IfThenElse(x > w, x + w, IfThenElse(x < Neg(w), x - w, x + x));
+ return IfThenElse(Gt(x, w), Add(x, w),
+ IfThenElse(Lt(x, Neg(w)), Sub(x, w), Add(x, x)));
}
// XybLowFreqToVals converts from low-frequency XYB space to the 'vals' space.
@@ -316,9 +331,9 @@ HWY_INLINE void XybLowFreqToVals(const D d, const V& x, const V& y,
const V bmul = Set(d, bmuli);
const V y_to_b_mul = Set(d, y_to_b_muli);
const V b = MulAdd(y_to_b_mul, y, b_arg);
- *valb = b * bmul;
- *valx = x * xmul;
- *valy = y * ymul;
+ *valb = Mul(b, bmul);
+ *valx = Mul(x, xmul);
+ *valy = Mul(y, ymul);
}
void SuppressXByY(const ImageF& in_x, const ImageF& in_y, const double yw,
@@ -341,8 +356,9 @@ void SuppressXByY(const ImageF& in_x, const ImageF& in_y, const double yw,
for (size_t x = 0; x < xsize; x += Lanes(d)) {
const auto vx = Load(d, row_x + x);
const auto vy = Load(d, row_y + x);
- const auto scaler = MulAdd(ywv / MulAdd(vy, vy, ywv), one_minus_s, sv);
- Store(scaler * vx, d, row_out + x);
+ const auto scaler =
+ MulAdd(Div(ywv, MulAdd(vy, vy, ywv)), one_minus_s, sv);
+ Store(Mul(scaler, vx), d, row_out + x);
}
}
}
@@ -372,7 +388,7 @@ static void SeparateFrequencies(size_t xsize, size_t ysize,
const float* BUTTERAUGLI_RESTRICT row_lf = ps.lf.ConstPlaneRow(i, y);
float* BUTTERAUGLI_RESTRICT row_mf = ps.mf.PlaneRow(i, y);
for (size_t x = 0; x < xsize; x += Lanes(d)) {
- const auto mf = Load(d, row_xyb + x) - Load(d, row_lf + x);
+ const auto mf = Sub(Load(d, row_xyb + x), Load(d, row_lf + x));
Store(mf, d, row_mf + x);
}
}
@@ -397,7 +413,7 @@ static void SeparateFrequencies(size_t xsize, size_t ysize,
float* BUTTERAUGLI_RESTRICT row_hf = ps.hf[0].Row(y);
for (size_t x = 0; x < xsize; x += Lanes(d)) {
auto mf = Load(d, row_mf + x);
- auto hf = Load(d, row_hf + x) - mf;
+ auto hf = Sub(Load(d, row_hf + x), mf);
mf = RemoveRangeAroundZero(d, kRemoveMfRange, mf);
Store(mf, d, row_mf + x);
Store(hf, d, row_hf + x);
@@ -409,7 +425,7 @@ static void SeparateFrequencies(size_t xsize, size_t ysize,
float* BUTTERAUGLI_RESTRICT row_hf = ps.hf[1].Row(y);
for (size_t x = 0; x < xsize; x += Lanes(d)) {
auto mf = Load(d, row_mf + x);
- auto hf = Load(d, row_hf + x) - mf;
+ auto hf = Sub(Load(d, row_hf + x), mf);
mf = AmplifyRangeAroundZero(d, kAddMfRange, mf);
Store(mf, d, row_mf + x);
@@ -452,7 +468,7 @@ static void SeparateFrequencies(size_t xsize, size_t ysize,
float* BUTTERAUGLI_RESTRICT row_hf = ps.hf[0].Row(y);
for (size_t x = 0; x < xsize; x += Lanes(d)) {
auto hf = Load(d, row_hf + x);
- auto uhf = Load(d, row_uhf + x) - hf;
+ auto uhf = Sub(Load(d, row_uhf + x), hf);
hf = RemoveRangeAroundZero(d, kRemoveHfRange, hf);
uhf = RemoveRangeAroundZero(d, kRemoveUhfRange, uhf);
Store(hf, d, row_hf + x);
@@ -467,12 +483,12 @@ static void SeparateFrequencies(size_t xsize, size_t ysize,
auto hf = Load(d, row_hf + x);
hf = MaximumClamp(d, hf, kMaxclampHf);
- auto uhf = Load(d, row_uhf + x) - hf;
+ auto uhf = Sub(Load(d, row_uhf + x), hf);
uhf = MaximumClamp(d, uhf, kMaxclampUhf);
- uhf *= Set(d, kMulYUhf);
+ uhf = Mul(uhf, Set(d, kMulYUhf));
Store(uhf, d, row_uhf + x);
- hf *= Set(d, kMulYHf);
+ hf = Mul(hf, Set(d, kMulYHf));
hf = AmplifyRangeAroundZero(d, kAddHfRange, hf);
Store(hf, d, row_hf + x);
}
@@ -500,6 +516,25 @@ static void SeparateFrequencies(size_t xsize, size_t ysize,
}
}
+namespace {
+template <typename V>
+BUTTERAUGLI_INLINE V Sum(V a, V b, V c, V d) {
+ return Add(Add(a, b), Add(c, d));
+}
+template <typename V>
+BUTTERAUGLI_INLINE V Sum(V a, V b, V c, V d, V e) {
+ return Sum(a, b, c, Add(d, e));
+}
+template <typename V>
+BUTTERAUGLI_INLINE V Sum(V a, V b, V c, V d, V e, V f, V g) {
+ return Sum(a, b, c, Sum(d, e, f, g));
+}
+template <typename V>
+BUTTERAUGLI_INLINE V Sum(V a, V b, V c, V d, V e, V f, V g, V h, V i) {
+ return Add(Add(Sum(a, b, c, d), Sum(e, f, g, h)), i);
+}
+} // namespace
+
template <class D>
Vec<D> MaltaUnit(MaltaTagLF /*tag*/, const D df,
const float* BUTTERAUGLI_RESTRICT d, const intptr_t xs) {
@@ -508,52 +543,52 @@ Vec<D> MaltaUnit(MaltaTagLF /*tag*/, const D df,
const auto center = LoadU(df, d);
// x grows, y constant
- const auto sum_yconst = LoadU(df, d - 4) + LoadU(df, d - 2) + center +
- LoadU(df, d + 2) + LoadU(df, d + 4);
+ const auto sum_yconst = Sum(LoadU(df, d - 4), LoadU(df, d - 2), center,
+ LoadU(df, d + 2), LoadU(df, d + 4));
// Will return this, sum of all line kernels
- auto retval = sum_yconst * sum_yconst;
+ auto retval = Mul(sum_yconst, sum_yconst);
{
// y grows, x constant
- auto sum = LoadU(df, d - xs3 - xs) + LoadU(df, d - xs - xs) + center +
- LoadU(df, d + xs + xs) + LoadU(df, d + xs3 + xs);
+ auto sum = Sum(LoadU(df, d - xs3 - xs), LoadU(df, d - xs - xs), center,
+ LoadU(df, d + xs + xs), LoadU(df, d + xs3 + xs));
retval = MulAdd(sum, sum, retval);
}
{
// both grow
- auto sum = LoadU(df, d - xs3 - 3) + LoadU(df, d - xs - xs - 2) + center +
- LoadU(df, d + xs + xs + 2) + LoadU(df, d + xs3 + 3);
+ auto sum = Sum(LoadU(df, d - xs3 - 3), LoadU(df, d - xs - xs - 2), center,
+ LoadU(df, d + xs + xs + 2), LoadU(df, d + xs3 + 3));
retval = MulAdd(sum, sum, retval);
}
{
// y grows, x shrinks
- auto sum = LoadU(df, d - xs3 + 3) + LoadU(df, d - xs - xs + 2) + center +
- LoadU(df, d + xs + xs - 2) + LoadU(df, d + xs3 - 3);
+ auto sum = Sum(LoadU(df, d - xs3 + 3), LoadU(df, d - xs - xs + 2), center,
+ LoadU(df, d + xs + xs - 2), LoadU(df, d + xs3 - 3));
retval = MulAdd(sum, sum, retval);
}
{
// y grows -4 to 4, x shrinks 1 -> -1
- auto sum = LoadU(df, d - xs3 - xs + 1) + LoadU(df, d - xs - xs + 1) +
- center + LoadU(df, d + xs + xs - 1) +
- LoadU(df, d + xs3 + xs - 1);
+ auto sum =
+ Sum(LoadU(df, d - xs3 - xs + 1), LoadU(df, d - xs - xs + 1), center,
+ LoadU(df, d + xs + xs - 1), LoadU(df, d + xs3 + xs - 1));
retval = MulAdd(sum, sum, retval);
}
{
// y grows -4 to 4, x grows -1 -> 1
- auto sum = LoadU(df, d - xs3 - xs - 1) + LoadU(df, d - xs - xs - 1) +
- center + LoadU(df, d + xs + xs + 1) +
- LoadU(df, d + xs3 + xs + 1);
+ auto sum =
+ Sum(LoadU(df, d - xs3 - xs - 1), LoadU(df, d - xs - xs - 1), center,
+ LoadU(df, d + xs + xs + 1), LoadU(df, d + xs3 + xs + 1));
retval = MulAdd(sum, sum, retval);
}
{
// x grows -4 to 4, y grows -1 to 1
- auto sum = LoadU(df, d - 4 - xs) + LoadU(df, d - 2 - xs) + center +
- LoadU(df, d + 2 + xs) + LoadU(df, d + 4 + xs);
+ auto sum = Sum(LoadU(df, d - 4 - xs), LoadU(df, d - 2 - xs), center,
+ LoadU(df, d + 2 + xs), LoadU(df, d + 4 + xs));
retval = MulAdd(sum, sum, retval);
}
{
// x grows -4 to 4, y shrinks 1 to -1
- auto sum = LoadU(df, d - 4 + xs) + LoadU(df, d - 2 + xs) + center +
- LoadU(df, d + 2 - xs) + LoadU(df, d + 4 - xs);
+ auto sum = Sum(LoadU(df, d - 4 + xs), LoadU(df, d - 2 + xs), center,
+ LoadU(df, d + 2 - xs), LoadU(df, d + 4 - xs));
retval = MulAdd(sum, sum, retval);
}
{
@@ -566,8 +601,8 @@ Vec<D> MaltaUnit(MaltaTagLF /*tag*/, const D df,
6_____*___
7______*__
8_________ */
- auto sum = LoadU(df, d - xs3 - 2) + LoadU(df, d - xs - xs - 1) + center +
- LoadU(df, d + xs + xs + 1) + LoadU(df, d + xs3 + 2);
+ auto sum = Sum(LoadU(df, d - xs3 - 2), LoadU(df, d - xs - xs - 1), center,
+ LoadU(df, d + xs + xs + 1), LoadU(df, d + xs3 + 2));
retval = MulAdd(sum, sum, retval);
}
{
@@ -580,8 +615,8 @@ Vec<D> MaltaUnit(MaltaTagLF /*tag*/, const D df,
6___*_____
7__*______
8_________ */
- auto sum = LoadU(df, d - xs3 + 2) + LoadU(df, d - xs - xs + 1) + center +
- LoadU(df, d + xs + xs - 1) + LoadU(df, d + xs3 - 2);
+ auto sum = Sum(LoadU(df, d - xs3 + 2), LoadU(df, d - xs - xs + 1), center,
+ LoadU(df, d + xs + xs - 1), LoadU(df, d + xs3 - 2));
retval = MulAdd(sum, sum, retval);
}
{
@@ -594,8 +629,8 @@ Vec<D> MaltaUnit(MaltaTagLF /*tag*/, const D df,
6_______*_
7_________
8_________ */
- auto sum = LoadU(df, d - xs - xs - 3) + LoadU(df, d - xs - 2) + center +
- LoadU(df, d + xs + 2) + LoadU(df, d + xs + xs + 3);
+ auto sum = Sum(LoadU(df, d - xs - xs - 3), LoadU(df, d - xs - 2), center,
+ LoadU(df, d + xs + 2), LoadU(df, d + xs + xs + 3));
retval = MulAdd(sum, sum, retval);
}
{
@@ -608,8 +643,8 @@ Vec<D> MaltaUnit(MaltaTagLF /*tag*/, const D df,
6_*_______
7_________
8_________ */
- auto sum = LoadU(df, d - xs - xs + 3) + LoadU(df, d - xs + 2) + center +
- LoadU(df, d + xs - 2) + LoadU(df, d + xs + xs - 3);
+ auto sum = Sum(LoadU(df, d - xs - xs + 3), LoadU(df, d - xs + 2), center,
+ LoadU(df, d + xs - 2), LoadU(df, d + xs + xs - 3));
retval = MulAdd(sum, sum, retval);
}
{
@@ -623,8 +658,8 @@ Vec<D> MaltaUnit(MaltaTagLF /*tag*/, const D df,
7_________
8_________ */
- auto sum = LoadU(df, d + xs + xs - 4) + LoadU(df, d + xs - 2) + center +
- LoadU(df, d - xs + 2) + LoadU(df, d - xs - xs + 4);
+ auto sum = Sum(LoadU(df, d + xs + xs - 4), LoadU(df, d + xs - 2), center,
+ LoadU(df, d - xs + 2), LoadU(df, d - xs - xs + 4));
retval = MulAdd(sum, sum, retval);
}
{
@@ -637,8 +672,8 @@ Vec<D> MaltaUnit(MaltaTagLF /*tag*/, const D df,
6________*
7_________
8_________ */
- auto sum = LoadU(df, d - xs - xs - 4) + LoadU(df, d - xs - 2) + center +
- LoadU(df, d + xs + 2) + LoadU(df, d + xs + xs + 4);
+ auto sum = Sum(LoadU(df, d - xs - xs - 4), LoadU(df, d - xs - 2), center,
+ LoadU(df, d + xs + 2), LoadU(df, d + xs + xs + 4));
retval = MulAdd(sum, sum, retval);
}
{
@@ -651,9 +686,9 @@ Vec<D> MaltaUnit(MaltaTagLF /*tag*/, const D df,
6_____*___
7_________
8______*__ */
- auto sum = LoadU(df, d - xs3 - xs - 2) + LoadU(df, d - xs - xs - 1) +
- center + LoadU(df, d + xs + xs + 1) +
- LoadU(df, d + xs3 + xs + 2);
+ auto sum =
+ Sum(LoadU(df, d - xs3 - xs - 2), LoadU(df, d - xs - xs - 1), center,
+ LoadU(df, d + xs + xs + 1), LoadU(df, d + xs3 + xs + 2));
retval = MulAdd(sum, sum, retval);
}
{
@@ -666,9 +701,9 @@ Vec<D> MaltaUnit(MaltaTagLF /*tag*/, const D df,
6___*_____
7_________
8__*______ */
- auto sum = LoadU(df, d - xs3 - xs + 2) + LoadU(df, d - xs - xs + 1) +
- center + LoadU(df, d + xs + xs - 1) +
- LoadU(df, d + xs3 + xs - 2);
+ auto sum =
+ Sum(LoadU(df, d - xs3 - xs + 2), LoadU(df, d - xs - xs + 1), center,
+ LoadU(df, d + xs + xs - 1), LoadU(df, d + xs3 + xs - 2));
retval = MulAdd(sum, sum, retval);
}
return retval;
@@ -682,65 +717,65 @@ Vec<D> MaltaUnit(MaltaTag /*tag*/, const D df,
const auto center = LoadU(df, d);
// x grows, y constant
- const auto sum_yconst = LoadU(df, d - 4) + LoadU(df, d - 3) +
- LoadU(df, d - 2) + LoadU(df, d - 1) + center +
- LoadU(df, d + 1) + LoadU(df, d + 2) +
- LoadU(df, d + 3) + LoadU(df, d + 4);
+ const auto sum_yconst =
+ Sum(LoadU(df, d - 4), LoadU(df, d - 3), LoadU(df, d - 2),
+ LoadU(df, d - 1), center, LoadU(df, d + 1), LoadU(df, d + 2),
+ LoadU(df, d + 3), LoadU(df, d + 4));
// Will return this, sum of all line kernels
- auto retval = sum_yconst * sum_yconst;
+ auto retval = Mul(sum_yconst, sum_yconst);
{
// y grows, x constant
- auto sum = LoadU(df, d - xs3 - xs) + LoadU(df, d - xs3) +
- LoadU(df, d - xs - xs) + LoadU(df, d - xs) + center +
- LoadU(df, d + xs) + LoadU(df, d + xs + xs) + LoadU(df, d + xs3) +
- LoadU(df, d + xs3 + xs);
+ auto sum = Sum(LoadU(df, d - xs3 - xs), LoadU(df, d - xs3),
+ LoadU(df, d - xs - xs), LoadU(df, d - xs), center,
+ LoadU(df, d + xs), LoadU(df, d + xs + xs),
+ LoadU(df, d + xs3), LoadU(df, d + xs3 + xs));
retval = MulAdd(sum, sum, retval);
}
{
// both grow
- auto sum = LoadU(df, d - xs3 - 3) + LoadU(df, d - xs - xs - 2) +
- LoadU(df, d - xs - 1) + center + LoadU(df, d + xs + 1) +
- LoadU(df, d + xs + xs + 2) + LoadU(df, d + xs3 + 3);
+ auto sum = Sum(LoadU(df, d - xs3 - 3), LoadU(df, d - xs - xs - 2),
+ LoadU(df, d - xs - 1), center, LoadU(df, d + xs + 1),
+ LoadU(df, d + xs + xs + 2), LoadU(df, d + xs3 + 3));
retval = MulAdd(sum, sum, retval);
}
{
// y grows, x shrinks
- auto sum = LoadU(df, d - xs3 + 3) + LoadU(df, d - xs - xs + 2) +
- LoadU(df, d - xs + 1) + center + LoadU(df, d + xs - 1) +
- LoadU(df, d + xs + xs - 2) + LoadU(df, d + xs3 - 3);
+ auto sum = Sum(LoadU(df, d - xs3 + 3), LoadU(df, d - xs - xs + 2),
+ LoadU(df, d - xs + 1), center, LoadU(df, d + xs - 1),
+ LoadU(df, d + xs + xs - 2), LoadU(df, d + xs3 - 3));
retval = MulAdd(sum, sum, retval);
}
{
// y grows -4 to 4, x shrinks 1 -> -1
- auto sum = LoadU(df, d - xs3 - xs + 1) + LoadU(df, d - xs3 + 1) +
- LoadU(df, d - xs - xs + 1) + LoadU(df, d - xs) + center +
- LoadU(df, d + xs) + LoadU(df, d + xs + xs - 1) +
- LoadU(df, d + xs3 - 1) + LoadU(df, d + xs3 + xs - 1);
+ auto sum = Sum(LoadU(df, d - xs3 - xs + 1), LoadU(df, d - xs3 + 1),
+ LoadU(df, d - xs - xs + 1), LoadU(df, d - xs), center,
+ LoadU(df, d + xs), LoadU(df, d + xs + xs - 1),
+ LoadU(df, d + xs3 - 1), LoadU(df, d + xs3 + xs - 1));
retval = MulAdd(sum, sum, retval);
}
{
// y grows -4 to 4, x grows -1 -> 1
- auto sum = LoadU(df, d - xs3 - xs - 1) + LoadU(df, d - xs3 - 1) +
- LoadU(df, d - xs - xs - 1) + LoadU(df, d - xs) + center +
- LoadU(df, d + xs) + LoadU(df, d + xs + xs + 1) +
- LoadU(df, d + xs3 + 1) + LoadU(df, d + xs3 + xs + 1);
+ auto sum = Sum(LoadU(df, d - xs3 - xs - 1), LoadU(df, d - xs3 - 1),
+ LoadU(df, d - xs - xs - 1), LoadU(df, d - xs), center,
+ LoadU(df, d + xs), LoadU(df, d + xs + xs + 1),
+ LoadU(df, d + xs3 + 1), LoadU(df, d + xs3 + xs + 1));
retval = MulAdd(sum, sum, retval);
}
{
// x grows -4 to 4, y grows -1 to 1
- auto sum = LoadU(df, d - 4 - xs) + LoadU(df, d - 3 - xs) +
- LoadU(df, d - 2 - xs) + LoadU(df, d - 1) + center +
- LoadU(df, d + 1) + LoadU(df, d + 2 + xs) +
- LoadU(df, d + 3 + xs) + LoadU(df, d + 4 + xs);
+ auto sum =
+ Sum(LoadU(df, d - 4 - xs), LoadU(df, d - 3 - xs), LoadU(df, d - 2 - xs),
+ LoadU(df, d - 1), center, LoadU(df, d + 1), LoadU(df, d + 2 + xs),
+ LoadU(df, d + 3 + xs), LoadU(df, d + 4 + xs));
retval = MulAdd(sum, sum, retval);
}
{
// x grows -4 to 4, y shrinks 1 to -1
- auto sum = LoadU(df, d - 4 + xs) + LoadU(df, d - 3 + xs) +
- LoadU(df, d - 2 + xs) + LoadU(df, d - 1) + center +
- LoadU(df, d + 1) + LoadU(df, d + 2 - xs) +
- LoadU(df, d + 3 - xs) + LoadU(df, d + 4 - xs);
+ auto sum =
+ Sum(LoadU(df, d - 4 + xs), LoadU(df, d - 3 + xs), LoadU(df, d - 2 + xs),
+ LoadU(df, d - 1), center, LoadU(df, d + 1), LoadU(df, d + 2 - xs),
+ LoadU(df, d + 3 - xs), LoadU(df, d + 4 - xs));
retval = MulAdd(sum, sum, retval);
}
{
@@ -753,9 +788,9 @@ Vec<D> MaltaUnit(MaltaTag /*tag*/, const D df,
6_____*___
7______*__
8_________ */
- auto sum = LoadU(df, d - xs3 - 2) + LoadU(df, d - xs - xs - 1) +
- LoadU(df, d - xs - 1) + center + LoadU(df, d + xs + 1) +
- LoadU(df, d + xs + xs + 1) + LoadU(df, d + xs3 + 2);
+ auto sum = Sum(LoadU(df, d - xs3 - 2), LoadU(df, d - xs - xs - 1),
+ LoadU(df, d - xs - 1), center, LoadU(df, d + xs + 1),
+ LoadU(df, d + xs + xs + 1), LoadU(df, d + xs3 + 2));
retval = MulAdd(sum, sum, retval);
}
{
@@ -768,9 +803,9 @@ Vec<D> MaltaUnit(MaltaTag /*tag*/, const D df,
6___*_____
7__*______
8_________ */
- auto sum = LoadU(df, d - xs3 + 2) + LoadU(df, d - xs - xs + 1) +
- LoadU(df, d - xs + 1) + center + LoadU(df, d + xs - 1) +
- LoadU(df, d + xs + xs - 1) + LoadU(df, d + xs3 - 2);
+ auto sum = Sum(LoadU(df, d - xs3 + 2), LoadU(df, d - xs - xs + 1),
+ LoadU(df, d - xs + 1), center, LoadU(df, d + xs - 1),
+ LoadU(df, d + xs + xs - 1), LoadU(df, d + xs3 - 2));
retval = MulAdd(sum, sum, retval);
}
{
@@ -783,9 +818,9 @@ Vec<D> MaltaUnit(MaltaTag /*tag*/, const D df,
6_______*_
7_________
8_________ */
- auto sum = LoadU(df, d - xs - xs - 3) + LoadU(df, d - xs - 2) +
- LoadU(df, d - xs - 1) + center + LoadU(df, d + xs + 1) +
- LoadU(df, d + xs + 2) + LoadU(df, d + xs + xs + 3);
+ auto sum = Sum(LoadU(df, d - xs - xs - 3), LoadU(df, d - xs - 2),
+ LoadU(df, d - xs - 1), center, LoadU(df, d + xs + 1),
+ LoadU(df, d + xs + 2), LoadU(df, d + xs + xs + 3));
retval = MulAdd(sum, sum, retval);
}
{
@@ -798,9 +833,9 @@ Vec<D> MaltaUnit(MaltaTag /*tag*/, const D df,
6_*_______
7_________
8_________ */
- auto sum = LoadU(df, d - xs - xs + 3) + LoadU(df, d - xs + 2) +
- LoadU(df, d - xs + 1) + center + LoadU(df, d + xs - 1) +
- LoadU(df, d + xs - 2) + LoadU(df, d + xs + xs - 3);
+ auto sum = Sum(LoadU(df, d - xs - xs + 3), LoadU(df, d - xs + 2),
+ LoadU(df, d - xs + 1), center, LoadU(df, d + xs - 1),
+ LoadU(df, d + xs - 2), LoadU(df, d + xs + xs - 3));
retval = MulAdd(sum, sum, retval);
}
{
@@ -814,10 +849,10 @@ Vec<D> MaltaUnit(MaltaTag /*tag*/, const D df,
7_________
8_________ */
- auto sum = LoadU(df, d + xs - 4) + LoadU(df, d + xs - 3) +
- LoadU(df, d + xs - 2) + LoadU(df, d - 1) + center +
- LoadU(df, d + 1) + LoadU(df, d - xs + 2) +
- LoadU(df, d - xs + 3) + LoadU(df, d - xs + 4);
+ auto sum =
+ Sum(LoadU(df, d + xs - 4), LoadU(df, d + xs - 3), LoadU(df, d + xs - 2),
+ LoadU(df, d - 1), center, LoadU(df, d + 1), LoadU(df, d - xs + 2),
+ LoadU(df, d - xs + 3), LoadU(df, d - xs + 4));
retval = MulAdd(sum, sum, retval);
}
{
@@ -830,10 +865,10 @@ Vec<D> MaltaUnit(MaltaTag /*tag*/, const D df,
6_________
7_________
8_________ */
- auto sum = LoadU(df, d - xs - 4) + LoadU(df, d - xs - 3) +
- LoadU(df, d - xs - 2) + LoadU(df, d - 1) + center +
- LoadU(df, d + 1) + LoadU(df, d + xs + 2) +
- LoadU(df, d + xs + 3) + LoadU(df, d + xs + 4);
+ auto sum =
+ Sum(LoadU(df, d - xs - 4), LoadU(df, d - xs - 3), LoadU(df, d - xs - 2),
+ LoadU(df, d - 1), center, LoadU(df, d + 1), LoadU(df, d + xs + 2),
+ LoadU(df, d + xs + 3), LoadU(df, d + xs + 4));
retval = MulAdd(sum, sum, retval);
}
{
@@ -846,10 +881,10 @@ Vec<D> MaltaUnit(MaltaTag /*tag*/, const D df,
6_____*___
7_____*___
8_____*___ */
- auto sum = LoadU(df, d - xs3 - xs - 1) + LoadU(df, d - xs3 - 1) +
- LoadU(df, d - xs - xs - 1) + LoadU(df, d - xs) + center +
- LoadU(df, d + xs) + LoadU(df, d + xs + xs + 1) +
- LoadU(df, d + xs3 + 1) + LoadU(df, d + xs3 + xs + 1);
+ auto sum = Sum(LoadU(df, d - xs3 - xs - 1), LoadU(df, d - xs3 - 1),
+ LoadU(df, d - xs - xs - 1), LoadU(df, d - xs), center,
+ LoadU(df, d + xs), LoadU(df, d + xs + xs + 1),
+ LoadU(df, d + xs3 + 1), LoadU(df, d + xs3 + xs + 1));
retval = MulAdd(sum, sum, retval);
}
{
@@ -862,10 +897,10 @@ Vec<D> MaltaUnit(MaltaTag /*tag*/, const D df,
6___*_____
7___*_____
8___*_____ */
- auto sum = LoadU(df, d - xs3 - xs + 1) + LoadU(df, d - xs3 + 1) +
- LoadU(df, d - xs - xs + 1) + LoadU(df, d - xs) + center +
- LoadU(df, d + xs) + LoadU(df, d + xs + xs - 1) +
- LoadU(df, d + xs3 - 1) + LoadU(df, d + xs3 + xs - 1);
+ auto sum = Sum(LoadU(df, d - xs3 - xs + 1), LoadU(df, d - xs3 + 1),
+ LoadU(df, d - xs - xs + 1), LoadU(df, d - xs), center,
+ LoadU(df, d + xs), LoadU(df, d + xs + xs - 1),
+ LoadU(df, d + xs3 - 1), LoadU(df, d + xs3 + xs - 1));
retval = MulAdd(sum, sum, retval);
}
return retval;
@@ -989,7 +1024,7 @@ static void MaltaDiffMapT(const Tag tag, const ImageF& lum0, const ImageF& lum1,
}
for (; x0 + Lanes(df) + 4 <= xsize_; x0 += Lanes(df)) {
auto diff = Load(df, row_diff + x0);
- diff += MaltaUnit(Tag(), df, row_in + x0, stride);
+ diff = Add(diff, MaltaUnit(Tag(), df, row_in + x0, stride));
Store(diff, df, row_diff + x0);
}
@@ -1250,8 +1285,8 @@ static void L2Diff(const ImageF& i0, const ImageF& i1, const float w,
float* BUTTERAUGLI_RESTRICT row_diff = diffmap->PlaneRow(c, y);
for (size_t x = 0; x < i0.xsize(); x += Lanes(d)) {
- const auto diff = Load(d, row0 + x) - Load(d, row1 + x);
- const auto diff2 = diff * diff;
+ const auto diff = Sub(Load(d, row0 + x), Load(d, row1 + x));
+ const auto diff2 = Mul(diff, diff);
const auto prev = Load(d, row_diff + x);
Store(MulAdd(diff2, weight, prev), d, row_diff + x);
}
@@ -1272,9 +1307,9 @@ static void SetL2Diff(const ImageF& i0, const ImageF& i1, const float w,
float* BUTTERAUGLI_RESTRICT row_diff = diffmap->PlaneRow(c, y);
for (size_t x = 0; x < i0.xsize(); x += Lanes(d)) {
- const auto diff = Load(d, row0 + x) - Load(d, row1 + x);
- const auto diff2 = diff * diff;
- Store(diff2 * weight, d, row_diff + x);
+ const auto diff = Sub(Load(d, row0 + x), Load(d, row1 + x));
+ const auto diff2 = Mul(diff, diff);
+ Store(Mul(diff2, weight), d, row_diff + x);
}
}
}
@@ -1302,22 +1337,22 @@ static void L2DiffAsymmetric(const ImageF& i0, const ImageF& i1, float w_0gt1,
const auto val1 = Load(d, row1 + x);
// Primary symmetric quadratic objective.
- const auto diff = val0 - val1;
- auto total = MulAdd(diff * diff, vw_0gt1, Load(d, row_diff + x));
+ const auto diff = Sub(val0, val1);
+ auto total = MulAdd(Mul(diff, diff), vw_0gt1, Load(d, row_diff + x));
// Secondary half-open quadratic objectives.
const auto fabs0 = Abs(val0);
- const auto too_small = Set(d, 0.4) * fabs0;
+ const auto too_small = Mul(Set(d, 0.4), fabs0);
const auto too_big = fabs0;
- const auto if_neg =
- IfThenElse(val1 > Neg(too_small), val1 + too_small,
- IfThenElseZero(val1 < Neg(too_big), Neg(val1) - too_big));
+ const auto if_neg = IfThenElse(
+ Gt(val1, Neg(too_small)), Add(val1, too_small),
+ IfThenElseZero(Lt(val1, Neg(too_big)), Sub(Neg(val1), too_big)));
const auto if_pos =
- IfThenElse(val1 < too_small, too_small - val1,
- IfThenElseZero(val1 > too_big, val1 - too_big));
- const auto v = IfThenElse(val0 < Zero(d), if_neg, if_pos);
- total += vw_0lt1 * v * v;
+ IfThenElse(Lt(val1, too_small), Sub(too_small, val1),
+ IfThenElseZero(Gt(val1, too_big), Sub(val1, too_big)));
+ const auto v = IfThenElse(Lt(val0, Zero(d)), if_neg, if_pos);
+ total = MulAdd(vw_0lt1, Mul(v, v), total);
Store(total, d, row_diff + x);
}
}
@@ -1334,7 +1369,7 @@ V Gamma(const DF df, V v) {
// clamping here.
v = ZeroIfNegative(v);
- const auto biased = v + Set(df, 9.9710635769299145);
+ const auto biased = Add(v, Set(df, 9.9710635769299145));
const auto log = FastLog2f(df, biased);
// We could fold this into a custom Log2 polynomial, but there would be
// relatively little gain.
@@ -1373,9 +1408,9 @@ BUTTERAUGLI_INLINE void OpsinAbsorbance(const DF df, const V& in0, const V& in1,
const V mix10 = Set(df, mixi10);
const V mix11 = Set(df, mixi11);
- *out0 = mix0 * in0 + mix1 * in1 + mix2 * in2 + mix3;
- *out1 = mix4 * in0 + mix5 * in1 + mix6 * in2 + mix7;
- *out2 = mix8 * in0 + mix9 * in1 + mix10 * in2 + mix11;
+ *out0 = MulAdd(mix0, in0, MulAdd(mix1, in1, MulAdd(mix2, in2, mix3)));
+ *out1 = MulAdd(mix4, in0, MulAdd(mix5, in1, MulAdd(mix6, in2, mix7)));
+ *out2 = MulAdd(mix8, in0, MulAdd(mix9, in1, MulAdd(mix10, in2, mix11)));
if (Clamp) {
*out0 = Max(*out0, mix3);
@@ -1419,16 +1454,16 @@ Image3F OpsinDynamicsImage(const Image3F& rgb, const ButteraugliParams& params,
auto pre_mixed1 = Undefined(df);
auto pre_mixed2 = Undefined(df);
OpsinAbsorbance<true>(
- df, Load(df, row_blurred_r + x) * intensity_target_multiplier,
- Load(df, row_blurred_g + x) * intensity_target_multiplier,
- Load(df, row_blurred_b + x) * intensity_target_multiplier,
+ df, Mul(Load(df, row_blurred_r + x), intensity_target_multiplier),
+ Mul(Load(df, row_blurred_g + x), intensity_target_multiplier),
+ Mul(Load(df, row_blurred_b + x), intensity_target_multiplier),
&pre_mixed0, &pre_mixed1, &pre_mixed2);
pre_mixed0 = Max(pre_mixed0, min);
pre_mixed1 = Max(pre_mixed1, min);
pre_mixed2 = Max(pre_mixed2, min);
- sensitivity0 = Gamma(df, pre_mixed0) / pre_mixed0;
- sensitivity1 = Gamma(df, pre_mixed1) / pre_mixed1;
- sensitivity2 = Gamma(df, pre_mixed2) / pre_mixed2;
+ sensitivity0 = Div(Gamma(df, pre_mixed0), pre_mixed0);
+ sensitivity1 = Div(Gamma(df, pre_mixed1), pre_mixed1);
+ sensitivity2 = Div(Gamma(df, pre_mixed2), pre_mixed2);
sensitivity0 = Max(sensitivity0, min);
sensitivity1 = Max(sensitivity1, min);
sensitivity2 = Max(sensitivity2, min);
@@ -1436,14 +1471,14 @@ Image3F OpsinDynamicsImage(const Image3F& rgb, const ButteraugliParams& params,
auto cur_mixed0 = Undefined(df);
auto cur_mixed1 = Undefined(df);
auto cur_mixed2 = Undefined(df);
- OpsinAbsorbance<false>(df,
- Load(df, row_r + x) * intensity_target_multiplier,
- Load(df, row_g + x) * intensity_target_multiplier,
- Load(df, row_b + x) * intensity_target_multiplier,
- &cur_mixed0, &cur_mixed1, &cur_mixed2);
- cur_mixed0 *= sensitivity0;
- cur_mixed1 *= sensitivity1;
- cur_mixed2 *= sensitivity2;
+ OpsinAbsorbance<false>(
+ df, Mul(Load(df, row_r + x), intensity_target_multiplier),
+ Mul(Load(df, row_g + x), intensity_target_multiplier),
+ Mul(Load(df, row_b + x), intensity_target_multiplier), &cur_mixed0,
+ &cur_mixed1, &cur_mixed2);
+ cur_mixed0 = Mul(cur_mixed0, sensitivity0);
+ cur_mixed1 = Mul(cur_mixed1, sensitivity1);
+ cur_mixed2 = Mul(cur_mixed2, sensitivity2);
// This is a kludge. The negative values should be zeroed away before
// blurring. Ideally there would be no negative values in the first place.
const auto min01 = Set(df, 1.7557483643287353f);
@@ -1452,8 +1487,8 @@ Image3F OpsinDynamicsImage(const Image3F& rgb, const ButteraugliParams& params,
cur_mixed1 = Max(cur_mixed1, min01);
cur_mixed2 = Max(cur_mixed2, min2);
- Store(cur_mixed0 - cur_mixed1, df, row_out_x + x);
- Store(cur_mixed0 + cur_mixed1, df, row_out_y + x);
+ Store(Sub(cur_mixed0, cur_mixed1), df, row_out_x + x);
+ Store(Add(cur_mixed0, cur_mixed1), df, row_out_y + x);
Store(cur_mixed2, df, row_out_b + x);
}
}
diff --git a/media/libjxl/src/lib/jxl/codec_in_out.h b/media/libjxl/src/lib/jxl/codec_in_out.h
index c1bff66366..23f0a4afeb 100644
--- a/media/libjxl/src/lib/jxl/codec_in_out.h
+++ b/media/libjxl/src/lib/jxl/codec_in_out.h
@@ -13,6 +13,7 @@
#include <utility>
#include <vector>
+#include "lib/jxl/alpha.h"
#include "lib/jxl/base/data_parallel.h"
#include "lib/jxl/common.h"
#include "lib/jxl/frame_header.h"
@@ -133,18 +134,57 @@ class CodecInOut {
}
return true;
}
- // Calls PremultiplyAlpha for each ImageBundle (preview/frames).
- void PremultiplyAlpha() {
+ // Performs "PremultiplyAlpha" for each ImageBundle (preview/frames).
+ bool PremultiplyAlpha() {
+ const auto doPremultiplyAlpha = [](ImageBundle& bundle) {
+ if (!bundle.HasAlpha()) return;
+ if (!bundle.HasColor()) return;
+ auto* color = bundle.color();
+ const auto* alpha = bundle.alpha();
+ JXL_CHECK(color->ysize() == alpha->ysize());
+ JXL_CHECK(color->xsize() == alpha->xsize());
+ for (size_t y = 0; y < color->ysize(); y++) {
+ ::jxl::PremultiplyAlpha(color->PlaneRow(0, y), color->PlaneRow(1, y),
+ color->PlaneRow(2, y), alpha->Row(y),
+ color->xsize());
+ }
+ };
ExtraChannelInfo* eci = metadata.m.Find(ExtraChannel::kAlpha);
- if (eci == nullptr || eci->alpha_associated) return; // nothing to do
+ if (eci == nullptr || eci->alpha_associated) return false;
if (metadata.m.have_preview) {
- preview_frame.PremultiplyAlpha();
+ doPremultiplyAlpha(preview_frame);
}
for (ImageBundle& ib : frames) {
- ib.PremultiplyAlpha();
+ doPremultiplyAlpha(ib);
}
eci->alpha_associated = true;
- return;
+ return true;
+ }
+
+ bool UnpremultiplyAlpha() {
+ const auto doUnpremultiplyAlpha = [](ImageBundle& bundle) {
+ if (!bundle.HasAlpha()) return;
+ if (!bundle.HasColor()) return;
+ auto* color = bundle.color();
+ const auto* alpha = bundle.alpha();
+ JXL_CHECK(color->ysize() == alpha->ysize());
+ JXL_CHECK(color->xsize() == alpha->xsize());
+ for (size_t y = 0; y < color->ysize(); y++) {
+ ::jxl::UnpremultiplyAlpha(color->PlaneRow(0, y), color->PlaneRow(1, y),
+ color->PlaneRow(2, y), alpha->Row(y),
+ color->xsize());
+ }
+ };
+ ExtraChannelInfo* eci = metadata.m.Find(ExtraChannel::kAlpha);
+ if (eci == nullptr || !eci->alpha_associated) return false;
+ if (metadata.m.have_preview) {
+ doUnpremultiplyAlpha(preview_frame);
+ }
+ for (ImageBundle& ib : frames) {
+ doUnpremultiplyAlpha(ib);
+ }
+ eci->alpha_associated = false;
+ return true;
}
// -- DECODER INPUT:
@@ -170,7 +210,6 @@ class CodecInOut {
std::vector<ImageBundle> frames; // size=1 if !metadata.have_animation
- bool use_sjpeg = false;
// If the image should be written to a JPEG, use this quality for encoding.
size_t jpeg_quality;
};
diff --git a/media/libjxl/src/lib/jxl/color_encoding_internal.h b/media/libjxl/src/lib/jxl/color_encoding_internal.h
index 2a8ea07456..d9e0448feb 100644
--- a/media/libjxl/src/lib/jxl/color_encoding_internal.h
+++ b/media/libjxl/src/lib/jxl/color_encoding_internal.h
@@ -88,7 +88,7 @@ static inline constexpr uint64_t EnumBits(Primaries /*unused*/) {
}
// Values from CICP TransferCharacteristics
-enum TransferFunction : uint32_t {
+enum class TransferFunction : uint32_t {
k709 = 1,
kUnknown = 2,
kLinear = 8,
diff --git a/media/libjxl/src/lib/jxl/color_management.cc b/media/libjxl/src/lib/jxl/color_management.cc
index 5d83db2db1..521a75adf6 100644
--- a/media/libjxl/src/lib/jxl/color_management.cc
+++ b/media/libjxl/src/lib/jxl/color_management.cc
@@ -468,7 +468,8 @@ Status MaybeCreateProfile(const ColorEncoding& c,
CreateICCCurvParaTag({2.6, 1.0, 0.0, 1.0, 0.0}, 3, &tags));
break;
default:
- JXL_ABORT("Unknown TF %d", c.tf.GetTransferFunction());
+ JXL_ABORT("Unknown TF %u",
+ static_cast<unsigned int>(c.tf.GetTransferFunction()));
}
}
FinalizeICCTag(&tags, &tag_offset, &tag_size);
diff --git a/media/libjxl/src/lib/jxl/color_management_test.cc b/media/libjxl/src/lib/jxl/color_management_test.cc
index b38fac4650..99382ca64b 100644
--- a/media/libjxl/src/lib/jxl/color_management_test.cc
+++ b/media/libjxl/src/lib/jxl/color_management_test.cc
@@ -13,8 +13,6 @@
#include <string>
#include <utility>
-#include "gmock/gmock.h"
-#include "gtest/gtest.h"
#include "lib/jxl/base/compiler_specific.h"
#include "lib/jxl/base/data_parallel.h"
#include "lib/jxl/base/file_io.h"
diff --git a/media/libjxl/src/lib/jxl/compressed_dc.cc b/media/libjxl/src/lib/jxl/compressed_dc.cc
index 7fae7bb446..3b2c323991 100644
--- a/media/libjxl/src/lib/jxl/compressed_dc.cc
+++ b/media/libjxl/src/lib/jxl/compressed_dc.cc
@@ -46,8 +46,16 @@ using D = HWY_FULL(float);
using DScalar = HWY_CAPPED(float, 1);
// These templates are not found via ADL.
+using hwy::HWY_NAMESPACE::Abs;
+using hwy::HWY_NAMESPACE::Add;
+using hwy::HWY_NAMESPACE::Div;
+using hwy::HWY_NAMESPACE::Max;
+using hwy::HWY_NAMESPACE::Mul;
+using hwy::HWY_NAMESPACE::MulAdd;
using hwy::HWY_NAMESPACE::Rebind;
+using hwy::HWY_NAMESPACE::Sub;
using hwy::HWY_NAMESPACE::Vec;
+using hwy::HWY_NAMESPACE::ZeroIfNegative;
// TODO(veluca): optimize constants.
const float w1 = 0.20345139757231578f;
@@ -88,12 +96,12 @@ JXL_INLINE void ComputePixelChannel(const D d, const float dc_factor,
const auto w_side = Set(d, w1);
const auto w_corner = Set(d, w2);
- const auto corner = tl + tr + bl + br;
- const auto side = ml + mr + tc + bc;
- *sm = corner * w_corner + side * w_side + *mc * w_center;
+ const auto corner = Add(Add(tl, tr), Add(bl, br));
+ const auto side = Add(Add(ml, mr), Add(tc, bc));
+ *sm = MulAdd(corner, w_corner, MulAdd(side, w_side, Mul(*mc, w_center)));
const auto dc_quant = Set(d, dc_factor);
- *gap = MaxWorkaround(*gap, Abs((*mc - *sm) / dc_quant));
+ *gap = MaxWorkaround(*gap, Abs(Div(Sub(*mc, *sm), dc_quant)));
}
template <typename D>
@@ -120,11 +128,11 @@ JXL_INLINE void ComputePixel(
auto factor = MulAdd(Set(d, -4.0f), gap, Set(d, 3.0f));
factor = ZeroIfNegative(factor);
- auto out = MulAdd(sm_x - mc_x, factor, mc_x);
+ auto out = MulAdd(Sub(sm_x, mc_x), factor, mc_x);
Store(out, d, out_rows[0] + x);
- out = MulAdd(sm_y - mc_y, factor, mc_y);
+ out = MulAdd(Sub(sm_y, mc_y), factor, mc_y);
Store(out, d, out_rows[1] + x);
- out = MulAdd(sm_b - mc_b, factor, mc_b);
+ out = MulAdd(Sub(sm_b, mc_b), factor, mc_b);
Store(out, d, out_rows[2] + x);
}
@@ -222,9 +230,9 @@ void DequantDC(const Rect& r, Image3F* dc, ImageB* quant_dc, const Image& in,
const auto in_q_x = Load(di, quant_row_x + x);
const auto in_q_y = Load(di, quant_row_y + x);
const auto in_q_b = Load(di, quant_row_b + x);
- const auto in_x = ConvertTo(df, in_q_x) * fac_x;
- const auto in_y = ConvertTo(df, in_q_y) * fac_y;
- const auto in_b = ConvertTo(df, in_q_b) * fac_b;
+ const auto in_x = Mul(ConvertTo(df, in_q_x), fac_x);
+ const auto in_y = Mul(ConvertTo(df, in_q_y), fac_y);
+ const auto in_b = Mul(ConvertTo(df, in_q_b), fac_b);
Store(in_y, df, dec_row_y + x);
Store(MulAdd(in_y, cfl_fac_x, in_x), df, dec_row_x + x);
Store(MulAdd(in_y, cfl_fac_b, in_b), df, dec_row_b + x);
@@ -243,7 +251,7 @@ void DequantDC(const Rect& r, Image3F* dc, ImageB* quant_dc, const Image& in,
float* row = rect.PlaneRow(dc, c, y);
for (size_t x = 0; x < rect.xsize(); x += Lanes(di)) {
const auto in_q = Load(di, quant_row + x);
- const auto in = ConvertTo(df, in_q) * fac;
+ const auto in = Mul(ConvertTo(df, in_q), fac);
Store(in, df, row + x);
}
}
diff --git a/media/libjxl/src/lib/jxl/convolve-inl.h b/media/libjxl/src/lib/jxl/convolve-inl.h
index 724163d4c7..054c9c6f0d 100644
--- a/media/libjxl/src/lib/jxl/convolve-inl.h
+++ b/media/libjxl/src/lib/jxl/convolve-inl.h
@@ -12,7 +12,10 @@
#include <hwy/highway.h>
+#include "lib/jxl/base/profiler.h"
#include "lib/jxl/base/status.h"
+#include "lib/jxl/image_ops.h"
+
HWY_BEFORE_NAMESPACE();
namespace jxl {
namespace HWY_NAMESPACE {
@@ -23,6 +26,7 @@ using hwy::HWY_NAMESPACE::Broadcast;
#if HWY_TARGET != HWY_SCALAR
using hwy::HWY_NAMESPACE::CombineShiftRightBytes;
#endif
+using hwy::HWY_NAMESPACE::TableLookupLanes;
using hwy::HWY_NAMESPACE::Vec;
// Synthesizes left/right neighbors from a vector of center pixels.
@@ -110,6 +114,180 @@ class Neighbors {
}
};
+#if HWY_TARGET != HWY_SCALAR
+
+// Returns indices for SetTableIndices such that TableLookupLanes on the
+// rightmost unaligned vector (rightmost sample in its most-significant lane)
+// returns the mirrored values, with the mirror outside the last valid sample.
+static inline const int32_t* MirrorLanes(const size_t mod) {
+ const HWY_CAPPED(float, 16) d;
+ constexpr size_t kN = MaxLanes(d);
+
+ // For mod = `image width mod 16` 0..15:
+ // last full vec mirrored (mem order) loadedVec mirrorVec idxVec
+ // 0123456789abcdef| fedcba9876543210 fed..210 012..def 012..def
+ // 0123456789abcdef|0 0fedcba98765432 0fe..321 234..f00 123..eff
+ // 0123456789abcdef|01 10fedcba987654 10f..432 456..110 234..ffe
+ // 0123456789abcdef|012 210fedcba9876 210..543 67..2210 34..ffed
+ // 0123456789abcdef|0123 3210fedcba98 321..654 8..33210 4..ffedc
+ // 0123456789abcdef|01234 43210fedcba
+ // 0123456789abcdef|012345 543210fedc
+ // 0123456789abcdef|0123456 6543210fe
+ // 0123456789abcdef|01234567 76543210
+ // 0123456789abcdef|012345678 8765432
+ // 0123456789abcdef|0123456789 987654
+ // 0123456789abcdef|0123456789A A9876
+ // 0123456789abcdef|0123456789AB BA98
+ // 0123456789abcdef|0123456789ABC CBA
+ // 0123456789abcdef|0123456789ABCD DC
+ // 0123456789abcdef|0123456789ABCDE E EDC..10f EED..210 ffe..321
+#if HWY_CAP_GE512
+ HWY_ALIGN static constexpr int32_t idx_lanes[2 * kN - 1] = {
+ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 15, //
+ 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0};
+#elif HWY_CAP_GE256
+ HWY_ALIGN static constexpr int32_t idx_lanes[2 * kN - 1] = {
+ 1, 2, 3, 4, 5, 6, 7, 7, //
+ 6, 5, 4, 3, 2, 1, 0};
+#else // 128-bit
+ HWY_ALIGN static constexpr int32_t idx_lanes[2 * kN - 1] = {1, 2, 3, 3, //
+ 2, 1, 0};
+#endif
+ return idx_lanes + kN - 1 - mod;
+}
+
+#endif // HWY_TARGET != HWY_SCALAR
+
+// Single entry point for convolution.
+// "Strategy" (Direct*/Separable*) decides kernel size and how to evaluate it.
+template <class Strategy>
+class ConvolveT {
+ static constexpr int64_t kRadius = Strategy::kRadius;
+ using Simd = HWY_CAPPED(float, 16);
+
+ public:
+ static size_t MinWidth() {
+#if HWY_TARGET == HWY_SCALAR
+ // First/Last use mirrored loads of up to +/- kRadius.
+ return 2 * kRadius;
+#else
+ return Lanes(Simd()) + kRadius;
+#endif
+ }
+
+ // "Image" is ImageF or Image3F.
+ template <class Image, class Weights>
+ static void Run(const Image& in, const Rect& rect, const Weights& weights,
+ ThreadPool* pool, Image* out) {
+ PROFILER_ZONE("ConvolveT::Run");
+ JXL_CHECK(SameSize(rect, *out));
+ JXL_CHECK(rect.xsize() >= MinWidth());
+
+ static_assert(int64_t(kRadius) <= 3,
+ "Must handle [0, kRadius) and >= kRadius");
+ switch (rect.xsize() % Lanes(Simd())) {
+ case 0:
+ return RunRows<0>(in, rect, weights, pool, out);
+ case 1:
+ return RunRows<1>(in, rect, weights, pool, out);
+ case 2:
+ return RunRows<2>(in, rect, weights, pool, out);
+ default:
+ return RunRows<3>(in, rect, weights, pool, out);
+ }
+ }
+
+ private:
+ template <size_t kSizeModN, class WrapRow, class Weights>
+ static JXL_INLINE void RunRow(const float* JXL_RESTRICT in,
+ const size_t xsize, const int64_t stride,
+ const WrapRow& wrap_row, const Weights& weights,
+ float* JXL_RESTRICT out) {
+ Strategy::template ConvolveRow<kSizeModN>(in, xsize, stride, wrap_row,
+ weights, out);
+ }
+
+ template <size_t kSizeModN, class Weights>
+ static JXL_INLINE void RunBorderRows(const ImageF& in, const Rect& rect,
+ const int64_t ybegin, const int64_t yend,
+ const Weights& weights, ImageF* out) {
+ const int64_t stride = in.PixelsPerRow();
+ const WrapRowMirror wrap_row(in, rect.ysize());
+ for (int64_t y = ybegin; y < yend; ++y) {
+ RunRow<kSizeModN>(rect.ConstRow(in, y), rect.xsize(), stride, wrap_row,
+ weights, out->Row(y));
+ }
+ }
+
+ // Image3F.
+ template <size_t kSizeModN, class Weights>
+ static JXL_INLINE void RunBorderRows(const Image3F& in, const Rect& rect,
+ const int64_t ybegin, const int64_t yend,
+ const Weights& weights, Image3F* out) {
+ const int64_t stride = in.PixelsPerRow();
+ for (int64_t y = ybegin; y < yend; ++y) {
+ for (size_t c = 0; c < 3; ++c) {
+ const WrapRowMirror wrap_row(in.Plane(c), rect.ysize());
+ RunRow<kSizeModN>(rect.ConstPlaneRow(in, c, y), rect.xsize(), stride,
+ wrap_row, weights, out->PlaneRow(c, y));
+ }
+ }
+ }
+
+ template <size_t kSizeModN, class Weights>
+ static JXL_INLINE void RunInteriorRows(const ImageF& in, const Rect& rect,
+ const int64_t ybegin,
+ const int64_t yend,
+ const Weights& weights,
+ ThreadPool* pool, ImageF* out) {
+ const int64_t stride = in.PixelsPerRow();
+ JXL_CHECK(RunOnPool(
+ pool, ybegin, yend, ThreadPool::NoInit,
+ [&](const uint32_t y, size_t /*thread*/) HWY_ATTR {
+ RunRow<kSizeModN>(rect.ConstRow(in, y), rect.xsize(), stride,
+ WrapRowUnchanged(), weights, out->Row(y));
+ },
+ "Convolve"));
+ }
+
+ // Image3F.
+ template <size_t kSizeModN, class Weights>
+ static JXL_INLINE void RunInteriorRows(const Image3F& in, const Rect& rect,
+ const int64_t ybegin,
+ const int64_t yend,
+ const Weights& weights,
+ ThreadPool* pool, Image3F* out) {
+ const int64_t stride = in.PixelsPerRow();
+ JXL_CHECK(RunOnPool(
+ pool, ybegin, yend, ThreadPool::NoInit,
+ [&](const uint32_t y, size_t /*thread*/) HWY_ATTR {
+ for (size_t c = 0; c < 3; ++c) {
+ RunRow<kSizeModN>(rect.ConstPlaneRow(in, c, y), rect.xsize(),
+ stride, WrapRowUnchanged(), weights,
+ out->PlaneRow(c, y));
+ }
+ },
+ "Convolve3"));
+ }
+
+ template <size_t kSizeModN, class Image, class Weights>
+ static JXL_INLINE void RunRows(const Image& in, const Rect& rect,
+ const Weights& weights, ThreadPool* pool,
+ Image* out) {
+ const int64_t ysize = rect.ysize();
+ RunBorderRows<kSizeModN>(in, rect, 0, std::min(int64_t(kRadius), ysize),
+ weights, out);
+ if (ysize > 2 * int64_t(kRadius)) {
+ RunInteriorRows<kSizeModN>(in, rect, int64_t(kRadius),
+ ysize - int64_t(kRadius), weights, pool, out);
+ }
+ if (ysize > int64_t(kRadius)) {
+ RunBorderRows<kSizeModN>(in, rect, ysize - int64_t(kRadius), ysize,
+ weights, out);
+ }
+ }
+};
+
} // namespace
// NOLINTNEXTLINE(google-readability-namespace-comments)
} // namespace HWY_NAMESPACE
diff --git a/media/libjxl/src/lib/jxl/convolve.cc b/media/libjxl/src/lib/jxl/convolve.cc
deleted file mode 100644
index b3e81f0ee7..0000000000
--- a/media/libjxl/src/lib/jxl/convolve.cc
+++ /dev/null
@@ -1,1332 +0,0 @@
-// Copyright (c) the JPEG XL Project Authors. All rights reserved.
-//
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-#include "lib/jxl/convolve.h"
-
-#undef HWY_TARGET_INCLUDE
-#define HWY_TARGET_INCLUDE "lib/jxl/convolve.cc"
-#include <hwy/foreach_target.h>
-#include <hwy/highway.h>
-
-#include "lib/jxl/common.h" // RoundUpTo
-#include "lib/jxl/convolve-inl.h"
-#include "lib/jxl/image_ops.h"
-HWY_BEFORE_NAMESPACE();
-namespace jxl {
-namespace HWY_NAMESPACE {
-
-// These templates are not found via ADL.
-using hwy::HWY_NAMESPACE::Vec;
-
-// Weighted sum of 1x5 pixels around ix, iy with [wx2 wx1 wx0 wx1 wx2].
-template <class WrapY>
-static float WeightedSumBorder(const ImageF& in, const WrapY wrap_y,
- const int64_t ix, const int64_t iy,
- const size_t xsize, const size_t ysize,
- const float wx0, const float wx1,
- const float wx2) {
- const WrapMirror wrap_x;
- const float* JXL_RESTRICT row = in.ConstRow(wrap_y(iy, ysize));
- const float in_m2 = row[wrap_x(ix - 2, xsize)];
- const float in_p2 = row[wrap_x(ix + 2, xsize)];
- const float in_m1 = row[wrap_x(ix - 1, xsize)];
- const float in_p1 = row[wrap_x(ix + 1, xsize)];
- const float in_00 = row[ix];
- const float sum_2 = wx2 * (in_m2 + in_p2);
- const float sum_1 = wx1 * (in_m1 + in_p1);
- const float sum_0 = wx0 * in_00;
- return sum_2 + sum_1 + sum_0;
-}
-
-template <class WrapY, class V>
-static V WeightedSum(const ImageF& in, const WrapY wrap_y, const size_t ix,
- const int64_t iy, const size_t ysize, const V wx0,
- const V wx1, const V wx2) {
- const HWY_FULL(float) d;
- const float* JXL_RESTRICT center = in.ConstRow(wrap_y(iy, ysize)) + ix;
- const auto in_m2 = LoadU(d, center - 2);
- const auto in_p2 = LoadU(d, center + 2);
- const auto in_m1 = LoadU(d, center - 1);
- const auto in_p1 = LoadU(d, center + 1);
- const auto in_00 = Load(d, center);
- const auto sum_2 = wx2 * (in_m2 + in_p2);
- const auto sum_1 = wx1 * (in_m1 + in_p1);
- const auto sum_0 = wx0 * in_00;
- return sum_2 + sum_1 + sum_0;
-}
-
-// Produces result for one pixel
-template <class WrapY>
-float Symmetric5Border(const ImageF& in, const Rect& rect, const int64_t ix,
- const int64_t iy, const WeightsSymmetric5& weights) {
- const float w0 = weights.c[0];
- const float w1 = weights.r[0];
- const float w2 = weights.R[0];
- const float w4 = weights.d[0];
- const float w5 = weights.L[0];
- const float w8 = weights.D[0];
-
- const size_t xsize = rect.xsize();
- const size_t ysize = rect.ysize();
- const WrapY wrap_y;
- // Unrolled loop over all 5 rows of the kernel.
- float sum0 = WeightedSumBorder(in, wrap_y, ix, iy, xsize, ysize, w0, w1, w2);
-
- sum0 += WeightedSumBorder(in, wrap_y, ix, iy - 2, xsize, ysize, w2, w5, w8);
- float sum1 =
- WeightedSumBorder(in, wrap_y, ix, iy + 2, xsize, ysize, w2, w5, w8);
-
- sum0 += WeightedSumBorder(in, wrap_y, ix, iy - 1, xsize, ysize, w1, w4, w5);
- sum1 += WeightedSumBorder(in, wrap_y, ix, iy + 1, xsize, ysize, w1, w4, w5);
-
- return sum0 + sum1;
-}
-
-// Produces result for one vector's worth of pixels
-template <class WrapY>
-static void Symmetric5Interior(const ImageF& in, const Rect& rect,
- const int64_t ix, const int64_t iy,
- const WeightsSymmetric5& weights,
- float* JXL_RESTRICT row_out) {
- const HWY_FULL(float) d;
-
- const auto w0 = LoadDup128(d, weights.c);
- const auto w1 = LoadDup128(d, weights.r);
- const auto w2 = LoadDup128(d, weights.R);
- const auto w4 = LoadDup128(d, weights.d);
- const auto w5 = LoadDup128(d, weights.L);
- const auto w8 = LoadDup128(d, weights.D);
-
- const size_t ysize = rect.ysize();
- const WrapY wrap_y;
- // Unrolled loop over all 5 rows of the kernel.
- auto sum0 = WeightedSum(in, wrap_y, ix, iy, ysize, w0, w1, w2);
-
- sum0 += WeightedSum(in, wrap_y, ix, iy - 2, ysize, w2, w5, w8);
- auto sum1 = WeightedSum(in, wrap_y, ix, iy + 2, ysize, w2, w5, w8);
-
- sum0 += WeightedSum(in, wrap_y, ix, iy - 1, ysize, w1, w4, w5);
- sum1 += WeightedSum(in, wrap_y, ix, iy + 1, ysize, w1, w4, w5);
-
- Store(sum0 + sum1, d, row_out + ix);
-}
-
-template <class WrapY>
-static void Symmetric5Row(const ImageF& in, const Rect& rect, const int64_t iy,
- const WeightsSymmetric5& weights,
- float* JXL_RESTRICT row_out) {
- const int64_t kRadius = 2;
- const size_t xsize = rect.xsize();
-
- size_t ix = 0;
- const HWY_FULL(float) d;
- const size_t N = Lanes(d);
- const size_t aligned_x = RoundUpTo(kRadius, N);
- for (; ix < std::min(aligned_x, xsize); ++ix) {
- row_out[ix] = Symmetric5Border<WrapY>(in, rect, ix, iy, weights);
- }
- for (; ix + N + kRadius <= xsize; ix += N) {
- Symmetric5Interior<WrapY>(in, rect, ix, iy, weights, row_out);
- }
- for (; ix < xsize; ++ix) {
- row_out[ix] = Symmetric5Border<WrapY>(in, rect, ix, iy, weights);
- }
-}
-
-static JXL_NOINLINE void Symmetric5BorderRow(const ImageF& in, const Rect& rect,
- const int64_t iy,
- const WeightsSymmetric5& weights,
- float* JXL_RESTRICT row_out) {
- return Symmetric5Row<WrapMirror>(in, rect, iy, weights, row_out);
-}
-
-#if HWY_TARGET != HWY_SCALAR
-
-// Returns indices for SetTableIndices such that TableLookupLanes on the
-// rightmost unaligned vector (rightmost sample in its most-significant lane)
-// returns the mirrored values, with the mirror outside the last valid sample.
-static inline const int32_t* MirrorLanes(const size_t mod) {
- const HWY_CAPPED(float, 16) d;
- constexpr size_t kN = MaxLanes(d);
-
- // For mod = `image width mod 16` 0..15:
- // last full vec mirrored (mem order) loadedVec mirrorVec idxVec
- // 0123456789abcdef| fedcba9876543210 fed..210 012..def 012..def
- // 0123456789abcdef|0 0fedcba98765432 0fe..321 234..f00 123..eff
- // 0123456789abcdef|01 10fedcba987654 10f..432 456..110 234..ffe
- // 0123456789abcdef|012 210fedcba9876 210..543 67..2210 34..ffed
- // 0123456789abcdef|0123 3210fedcba98 321..654 8..33210 4..ffedc
- // 0123456789abcdef|01234 43210fedcba
- // 0123456789abcdef|012345 543210fedc
- // 0123456789abcdef|0123456 6543210fe
- // 0123456789abcdef|01234567 76543210
- // 0123456789abcdef|012345678 8765432
- // 0123456789abcdef|0123456789 987654
- // 0123456789abcdef|0123456789A A9876
- // 0123456789abcdef|0123456789AB BA98
- // 0123456789abcdef|0123456789ABC CBA
- // 0123456789abcdef|0123456789ABCD DC
- // 0123456789abcdef|0123456789ABCDE E EDC..10f EED..210 ffe..321
-#if HWY_CAP_GE512
- HWY_ALIGN static constexpr int32_t idx_lanes[2 * kN - 1] = {
- 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 15, //
- 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0};
-#elif HWY_CAP_GE256
- HWY_ALIGN static constexpr int32_t idx_lanes[2 * kN - 1] = {
- 1, 2, 3, 4, 5, 6, 7, 7, //
- 6, 5, 4, 3, 2, 1, 0};
-#else // 128-bit
- HWY_ALIGN static constexpr int32_t idx_lanes[2 * kN - 1] = {1, 2, 3, 3, //
- 2, 1, 0};
-#endif
- return idx_lanes + kN - 1 - mod;
-}
-
-#endif // HWY_TARGET != HWY_SCALAR
-
-namespace strategy {
-
-struct StrategyBase {
- using D = HWY_CAPPED(float, 16);
- using V = Vec<D>;
-};
-
-// 3x3 convolution by symmetric kernel with a single scan through the input.
-class Symmetric3 : public StrategyBase {
- public:
- static constexpr int64_t kRadius = 1;
-
- // Only accesses pixels in [0, xsize).
- template <size_t kSizeModN, class WrapRow>
- static JXL_INLINE void ConvolveRow(const float* const JXL_RESTRICT row_m,
- const size_t xsize, const int64_t stride,
- const WrapRow& wrap_row,
- const WeightsSymmetric3& weights,
- float* const JXL_RESTRICT row_out) {
- const D d;
- // t, m, b = top, middle, bottom row;
- const float* const JXL_RESTRICT row_t = wrap_row(row_m - stride, stride);
- const float* const JXL_RESTRICT row_b = wrap_row(row_m + stride, stride);
-
- // Must load in advance - compiler doesn't understand LoadDup128 and
- // schedules them too late.
- const V w0 = LoadDup128(d, weights.c);
- const V w1 = LoadDup128(d, weights.r);
- const V w2 = LoadDup128(d, weights.d);
-
- // l, c, r = left, center, right. Leftmost vector: need FirstL1.
- {
- const V tc = LoadU(d, row_t + 0);
- const V mc = LoadU(d, row_m + 0);
- const V bc = LoadU(d, row_b + 0);
- const V tl = Neighbors::FirstL1(tc);
- const V tr = LoadU(d, row_t + 0 + 1);
- const V ml = Neighbors::FirstL1(mc);
- const V mr = LoadU(d, row_m + 0 + 1);
- const V bl = Neighbors::FirstL1(bc);
- const V br = LoadU(d, row_b + 0 + 1);
- const V conv =
- WeightedSum(tl, tc, tr, ml, mc, mr, bl, bc, br, w0, w1, w2);
- Store(conv, d, row_out + 0);
- }
-
- // Loop as long as we can load enough new values:
- const size_t N = Lanes(d);
- size_t x = N;
- for (; x + N + kRadius <= xsize; x += N) {
- const auto conv = ConvolveValid(row_t, row_m, row_b, x, w0, w1, w2);
- Store(conv, d, row_out + x);
- }
-
- // For final (partial) vector:
- const V tc = LoadU(d, row_t + x);
- const V mc = LoadU(d, row_m + x);
- const V bc = LoadU(d, row_b + x);
-
- V tr, mr, br;
-#if HWY_TARGET == HWY_SCALAR
- tr = tc; // Single-lane => mirrored right neighbor = center value.
- mr = mc;
- br = bc;
-#else
- if (kSizeModN == 0) {
- // The above loop didn't handle the last vector because it needs an
- // additional right neighbor (generated via mirroring).
- auto mirror = SetTableIndices(d, MirrorLanes(N - 1));
- tr = TableLookupLanes(tc, mirror);
- mr = TableLookupLanes(mc, mirror);
- br = TableLookupLanes(bc, mirror);
- } else {
- auto mirror = SetTableIndices(d, MirrorLanes((xsize % N) - 1));
- // Loads last valid value into uppermost lane and mirrors.
- tr = TableLookupLanes(LoadU(d, row_t + xsize - N), mirror);
- mr = TableLookupLanes(LoadU(d, row_m + xsize - N), mirror);
- br = TableLookupLanes(LoadU(d, row_b + xsize - N), mirror);
- }
-#endif
-
- const V tl = LoadU(d, row_t + x - 1);
- const V ml = LoadU(d, row_m + x - 1);
- const V bl = LoadU(d, row_b + x - 1);
- const V conv = WeightedSum(tl, tc, tr, ml, mc, mr, bl, bc, br, w0, w1, w2);
- Store(conv, d, row_out + x);
- }
-
- private:
- // Returns sum{x_i * w_i}.
- template <class V>
- static JXL_INLINE V WeightedSum(const V tl, const V tc, const V tr,
- const V ml, const V mc, const V mr,
- const V bl, const V bc, const V br,
- const V w0, const V w1, const V w2) {
- const V sum_tb = tc + bc;
-
- // Faster than 5 mul + 4 FMA.
- const V mul0 = mc * w0;
- const V sum_lr = ml + mr;
-
- const V x1 = sum_tb + sum_lr;
- const V mul1 = MulAdd(x1, w1, mul0);
-
- const V sum_t2 = tl + tr;
- const V sum_b2 = bl + br;
- const V x2 = sum_t2 + sum_b2;
- const V mul2 = MulAdd(x2, w2, mul1);
- return mul2;
- }
-
- static JXL_INLINE V ConvolveValid(const float* JXL_RESTRICT row_t,
- const float* JXL_RESTRICT row_m,
- const float* JXL_RESTRICT row_b,
- const int64_t x, const V w0, const V w1,
- const V w2) {
- const D d;
- const V tc = LoadU(d, row_t + x);
- const V mc = LoadU(d, row_m + x);
- const V bc = LoadU(d, row_b + x);
- const V tl = LoadU(d, row_t + x - 1);
- const V tr = LoadU(d, row_t + x + 1);
- const V ml = LoadU(d, row_m + x - 1);
- const V mr = LoadU(d, row_m + x + 1);
- const V bl = LoadU(d, row_b + x - 1);
- const V br = LoadU(d, row_b + x + 1);
- return WeightedSum(tl, tc, tr, ml, mc, mr, bl, bc, br, w0, w1, w2);
- }
-};
-
-// 5x5 convolution by separable kernel with a single scan through the input.
-// This is more cache-efficient than separate horizontal/vertical passes, and
-// possibly faster (given enough registers) than tiling and/or transposing.
-//
-// Overview: imagine a 5x5 window around a central pixel. First convolve the
-// rows by multiplying the pixels with the corresponding weights from
-// WeightsSeparable5.horz[abs(x_offset) * 4]. Then multiply each of these
-// intermediate results by the corresponding vertical weight, i.e.
-// vert[abs(y_offset) * 4]. Finally, store the sum of these values as the
-// convolution result at the position of the central pixel in the output.
-//
-// Each of these operations uses SIMD vectors. The central pixel and most
-// importantly the output are aligned, so neighnoring pixels (e.g. x_offset=1)
-// require unaligned loads. Because weights are supplied in identical groups of
-// 4, we can use LoadDup128 to load them (slightly faster).
-//
-// Uses mirrored boundary handling. Until x >= kRadius, the horizontal
-// convolution uses Neighbors class to shuffle vectors as if each of its lanes
-// had been loaded from the mirrored offset. Similarly, the last full vector to
-// write uses mirroring. In the case of scalar vectors, Neighbors is not usable
-// and the value is loaded directly. Otherwise, the number of valid pixels
-// modulo the vector size enables a small optimization: for smaller offsets,
-// a non-mirrored load is sufficient.
-class Separable5 : public StrategyBase {
- public:
- static constexpr int64_t kRadius = 2;
-
- template <size_t kSizeModN, class WrapRow>
- static JXL_INLINE void ConvolveRow(const float* const JXL_RESTRICT row_m,
- const size_t xsize, const int64_t stride,
- const WrapRow& wrap_row,
- const WeightsSeparable5& weights,
- float* const JXL_RESTRICT row_out) {
- const D d;
- const int64_t neg_stride = -stride; // allows LEA addressing.
- const float* const JXL_RESTRICT row_t2 =
- wrap_row(row_m + 2 * neg_stride, stride);
- const float* const JXL_RESTRICT row_t1 =
- wrap_row(row_m + 1 * neg_stride, stride);
- const float* const JXL_RESTRICT row_b1 =
- wrap_row(row_m + 1 * stride, stride);
- const float* const JXL_RESTRICT row_b2 =
- wrap_row(row_m + 2 * stride, stride);
-
- const V wh0 = LoadDup128(d, weights.horz + 0 * 4);
- const V wh1 = LoadDup128(d, weights.horz + 1 * 4);
- const V wh2 = LoadDup128(d, weights.horz + 2 * 4);
- const V wv0 = LoadDup128(d, weights.vert + 0 * 4);
- const V wv1 = LoadDup128(d, weights.vert + 1 * 4);
- const V wv2 = LoadDup128(d, weights.vert + 2 * 4);
-
- size_t x = 0;
-
- // More than one iteration for scalars.
- for (; x < kRadius; x += Lanes(d)) {
- const V conv0 = HorzConvolveFirst(row_m, x, xsize, wh0, wh1, wh2) * wv0;
-
- const V conv1t = HorzConvolveFirst(row_t1, x, xsize, wh0, wh1, wh2);
- const V conv1b = HorzConvolveFirst(row_b1, x, xsize, wh0, wh1, wh2);
- const V conv1 = MulAdd(conv1t + conv1b, wv1, conv0);
-
- const V conv2t = HorzConvolveFirst(row_t2, x, xsize, wh0, wh1, wh2);
- const V conv2b = HorzConvolveFirst(row_b2, x, xsize, wh0, wh1, wh2);
- const V conv2 = MulAdd(conv2t + conv2b, wv2, conv1);
- Store(conv2, d, row_out + x);
- }
-
- // Main loop: load inputs without padding
- for (; x + Lanes(d) + kRadius <= xsize; x += Lanes(d)) {
- const V conv0 = HorzConvolve(row_m + x, wh0, wh1, wh2) * wv0;
-
- const V conv1t = HorzConvolve(row_t1 + x, wh0, wh1, wh2);
- const V conv1b = HorzConvolve(row_b1 + x, wh0, wh1, wh2);
- const V conv1 = MulAdd(conv1t + conv1b, wv1, conv0);
-
- const V conv2t = HorzConvolve(row_t2 + x, wh0, wh1, wh2);
- const V conv2b = HorzConvolve(row_b2 + x, wh0, wh1, wh2);
- const V conv2 = MulAdd(conv2t + conv2b, wv2, conv1);
- Store(conv2, d, row_out + x);
- }
-
- // Last full vector to write (the above loop handled mod >= kRadius)
-#if HWY_TARGET == HWY_SCALAR
- while (x < xsize) {
-#else
- if (kSizeModN < kRadius) {
-#endif
- const V conv0 =
- HorzConvolveLast<kSizeModN>(row_m, x, xsize, wh0, wh1, wh2) * wv0;
-
- const V conv1t =
- HorzConvolveLast<kSizeModN>(row_t1, x, xsize, wh0, wh1, wh2);
- const V conv1b =
- HorzConvolveLast<kSizeModN>(row_b1, x, xsize, wh0, wh1, wh2);
- const V conv1 = MulAdd(conv1t + conv1b, wv1, conv0);
-
- const V conv2t =
- HorzConvolveLast<kSizeModN>(row_t2, x, xsize, wh0, wh1, wh2);
- const V conv2b =
- HorzConvolveLast<kSizeModN>(row_b2, x, xsize, wh0, wh1, wh2);
- const V conv2 = MulAdd(conv2t + conv2b, wv2, conv1);
- Store(conv2, d, row_out + x);
- x += Lanes(d);
- }
-
- // If mod = 0, the above vector was the last.
- if (kSizeModN != 0) {
- for (; x < xsize; ++x) {
- float mul = 0.0f;
- for (int64_t dy = -kRadius; dy <= kRadius; ++dy) {
- const float wy = weights.vert[std::abs(dy) * 4];
- const float* clamped_row = wrap_row(row_m + dy * stride, stride);
- for (int64_t dx = -kRadius; dx <= kRadius; ++dx) {
- const float wx = weights.horz[std::abs(dx) * 4];
- const int64_t clamped_x = Mirror(x + dx, xsize);
- mul += clamped_row[clamped_x] * wx * wy;
- }
- }
- row_out[x] = mul;
- }
- }
- }
-
- private:
- // Same as HorzConvolve for the first/last vector in a row.
- static JXL_INLINE V HorzConvolveFirst(const float* const JXL_RESTRICT row,
- const int64_t x, const int64_t xsize,
- const V wh0, const V wh1, const V wh2) {
- const D d;
- const V c = LoadU(d, row + x);
- const V mul0 = c * wh0;
-
-#if HWY_TARGET == HWY_SCALAR
- const V l1 = LoadU(d, row + Mirror(x - 1, xsize));
- const V l2 = LoadU(d, row + Mirror(x - 2, xsize));
-#else
- (void)xsize;
- const V l1 = Neighbors::FirstL1(c);
- const V l2 = Neighbors::FirstL2(c);
-#endif
-
- const V r1 = LoadU(d, row + x + 1);
- const V r2 = LoadU(d, row + x + 2);
-
- const V mul1 = MulAdd(l1 + r1, wh1, mul0);
- const V mul2 = MulAdd(l2 + r2, wh2, mul1);
- return mul2;
- }
-
- template <size_t kSizeModN>
- static JXL_INLINE V HorzConvolveLast(const float* const JXL_RESTRICT row,
- const int64_t x, const int64_t xsize,
- const V wh0, const V wh1, const V wh2) {
- const D d;
- const V c = LoadU(d, row + x);
- const V mul0 = c * wh0;
-
- const V l1 = LoadU(d, row + x - 1);
- const V l2 = LoadU(d, row + x - 2);
-
- V r1, r2;
-#if HWY_TARGET == HWY_SCALAR
- r1 = LoadU(d, row + Mirror(x + 1, xsize));
- r2 = LoadU(d, row + Mirror(x + 2, xsize));
-#else
- const size_t N = Lanes(d);
- if (kSizeModN == 0) {
- r2 = TableLookupLanes(c, SetTableIndices(d, MirrorLanes(N - 2)));
- r1 = TableLookupLanes(c, SetTableIndices(d, MirrorLanes(N - 1)));
- } else { // == 1
- const auto last = LoadU(d, row + xsize - N);
- r2 = TableLookupLanes(last, SetTableIndices(d, MirrorLanes(N - 1)));
- r1 = last;
- }
-#endif
-
- // Sum of pixels with Manhattan distance i, multiplied by weights[i].
- const V sum1 = l1 + r1;
- const V mul1 = MulAdd(sum1, wh1, mul0);
- const V sum2 = l2 + r2;
- const V mul2 = MulAdd(sum2, wh2, mul1);
- return mul2;
- }
-
- // Requires kRadius valid pixels before/after pos.
- static JXL_INLINE V HorzConvolve(const float* const JXL_RESTRICT pos,
- const V wh0, const V wh1, const V wh2) {
- const D d;
- const V c = LoadU(d, pos);
- const V mul0 = c * wh0;
-
- // Loading anew is faster than combining vectors.
- const V l1 = LoadU(d, pos - 1);
- const V r1 = LoadU(d, pos + 1);
- const V l2 = LoadU(d, pos - 2);
- const V r2 = LoadU(d, pos + 2);
- // Sum of pixels with Manhattan distance i, multiplied by weights[i].
- const V sum1 = l1 + r1;
- const V mul1 = MulAdd(sum1, wh1, mul0);
- const V sum2 = l2 + r2;
- const V mul2 = MulAdd(sum2, wh2, mul1);
- return mul2;
- }
-}; // namespace strategy
-
-// 7x7 convolution by separable kernel with a single scan through the input.
-// Extended version of Separable5, see documentation there.
-class Separable7 : public StrategyBase {
- public:
- static constexpr int64_t kRadius = 3;
-
- template <size_t kSizeModN, class WrapRow>
- static JXL_INLINE void ConvolveRow(const float* const JXL_RESTRICT row_m,
- const size_t xsize, const int64_t stride,
- const WrapRow& wrap_row,
- const WeightsSeparable7& weights,
- float* const JXL_RESTRICT row_out) {
- const D d;
- const int64_t neg_stride = -stride; // allows LEA addressing.
- const float* const JXL_RESTRICT row_t3 =
- wrap_row(row_m + 3 * neg_stride, stride);
- const float* const JXL_RESTRICT row_t2 =
- wrap_row(row_m + 2 * neg_stride, stride);
- const float* const JXL_RESTRICT row_t1 =
- wrap_row(row_m + 1 * neg_stride, stride);
- const float* const JXL_RESTRICT row_b1 =
- wrap_row(row_m + 1 * stride, stride);
- const float* const JXL_RESTRICT row_b2 =
- wrap_row(row_m + 2 * stride, stride);
- const float* const JXL_RESTRICT row_b3 =
- wrap_row(row_m + 3 * stride, stride);
-
- const V wh0 = LoadDup128(d, weights.horz + 0 * 4);
- const V wh1 = LoadDup128(d, weights.horz + 1 * 4);
- const V wh2 = LoadDup128(d, weights.horz + 2 * 4);
- const V wh3 = LoadDup128(d, weights.horz + 3 * 4);
- const V wv0 = LoadDup128(d, weights.vert + 0 * 4);
- const V wv1 = LoadDup128(d, weights.vert + 1 * 4);
- const V wv2 = LoadDup128(d, weights.vert + 2 * 4);
- const V wv3 = LoadDup128(d, weights.vert + 3 * 4);
-
- size_t x = 0;
-
- // More than one iteration for scalars.
- for (; x < kRadius; x += Lanes(d)) {
- const V conv0 =
- HorzConvolveFirst(row_m, x, xsize, wh0, wh1, wh2, wh3) * wv0;
-
- const V conv1t = HorzConvolveFirst(row_t1, x, xsize, wh0, wh1, wh2, wh3);
- const V conv1b = HorzConvolveFirst(row_b1, x, xsize, wh0, wh1, wh2, wh3);
- const V conv1 = MulAdd(conv1t + conv1b, wv1, conv0);
-
- const V conv2t = HorzConvolveFirst(row_t2, x, xsize, wh0, wh1, wh2, wh3);
- const V conv2b = HorzConvolveFirst(row_b2, x, xsize, wh0, wh1, wh2, wh3);
- const V conv2 = MulAdd(conv2t + conv2b, wv2, conv1);
-
- const V conv3t = HorzConvolveFirst(row_t3, x, xsize, wh0, wh1, wh2, wh3);
- const V conv3b = HorzConvolveFirst(row_b3, x, xsize, wh0, wh1, wh2, wh3);
- const V conv3 = MulAdd(conv3t + conv3b, wv3, conv2);
-
- Store(conv3, d, row_out + x);
- }
-
- // Main loop: load inputs without padding
- for (; x + Lanes(d) + kRadius <= xsize; x += Lanes(d)) {
- const V conv0 = HorzConvolve(row_m + x, wh0, wh1, wh2, wh3) * wv0;
-
- const V conv1t = HorzConvolve(row_t1 + x, wh0, wh1, wh2, wh3);
- const V conv1b = HorzConvolve(row_b1 + x, wh0, wh1, wh2, wh3);
- const V conv1 = MulAdd(conv1t + conv1b, wv1, conv0);
-
- const V conv2t = HorzConvolve(row_t2 + x, wh0, wh1, wh2, wh3);
- const V conv2b = HorzConvolve(row_b2 + x, wh0, wh1, wh2, wh3);
- const V conv2 = MulAdd(conv2t + conv2b, wv2, conv1);
-
- const V conv3t = HorzConvolve(row_t3 + x, wh0, wh1, wh2, wh3);
- const V conv3b = HorzConvolve(row_b3 + x, wh0, wh1, wh2, wh3);
- const V conv3 = MulAdd(conv3t + conv3b, wv3, conv2);
-
- Store(conv3, d, row_out + x);
- }
-
- // Last full vector to write (the above loop handled mod >= kRadius)
-#if HWY_TARGET == HWY_SCALAR
- while (x < xsize) {
-#else
- if (kSizeModN < kRadius) {
-#endif
- const V conv0 =
- HorzConvolveLast<kSizeModN>(row_m, x, xsize, wh0, wh1, wh2, wh3) *
- wv0;
-
- const V conv1t =
- HorzConvolveLast<kSizeModN>(row_t1, x, xsize, wh0, wh1, wh2, wh3);
- const V conv1b =
- HorzConvolveLast<kSizeModN>(row_b1, x, xsize, wh0, wh1, wh2, wh3);
- const V conv1 = MulAdd(conv1t + conv1b, wv1, conv0);
-
- const V conv2t =
- HorzConvolveLast<kSizeModN>(row_t2, x, xsize, wh0, wh1, wh2, wh3);
- const V conv2b =
- HorzConvolveLast<kSizeModN>(row_b2, x, xsize, wh0, wh1, wh2, wh3);
- const V conv2 = MulAdd(conv2t + conv2b, wv2, conv1);
-
- const V conv3t =
- HorzConvolveLast<kSizeModN>(row_t3, x, xsize, wh0, wh1, wh2, wh3);
- const V conv3b =
- HorzConvolveLast<kSizeModN>(row_b3, x, xsize, wh0, wh1, wh2, wh3);
- const V conv3 = MulAdd(conv3t + conv3b, wv3, conv2);
-
- Store(conv3, d, row_out + x);
- x += Lanes(d);
- }
-
- // If mod = 0, the above vector was the last.
- if (kSizeModN != 0) {
- for (; x < xsize; ++x) {
- float mul = 0.0f;
- for (int64_t dy = -kRadius; dy <= kRadius; ++dy) {
- const float wy = weights.vert[std::abs(dy) * 4];
- const float* clamped_row = wrap_row(row_m + dy * stride, stride);
- for (int64_t dx = -kRadius; dx <= kRadius; ++dx) {
- const float wx = weights.horz[std::abs(dx) * 4];
- const int64_t clamped_x = Mirror(x + dx, xsize);
- mul += clamped_row[clamped_x] * wx * wy;
- }
- }
- row_out[x] = mul;
- }
- }
- }
-
- private:
- // Same as HorzConvolve for the first/last vector in a row.
- static JXL_INLINE V HorzConvolveFirst(const float* const JXL_RESTRICT row,
- const int64_t x, const int64_t xsize,
- const V wh0, const V wh1, const V wh2,
- const V wh3) {
- const D d;
- const V c = LoadU(d, row + x);
- const V mul0 = c * wh0;
-
-#if HWY_TARGET == HWY_SCALAR
- const V l1 = LoadU(d, row + Mirror(x - 1, xsize));
- const V l2 = LoadU(d, row + Mirror(x - 2, xsize));
- const V l3 = LoadU(d, row + Mirror(x - 3, xsize));
-#else
- (void)xsize;
- const V l1 = Neighbors::FirstL1(c);
- const V l2 = Neighbors::FirstL2(c);
- const V l3 = Neighbors::FirstL3(c);
-#endif
-
- const V r1 = LoadU(d, row + x + 1);
- const V r2 = LoadU(d, row + x + 2);
- const V r3 = LoadU(d, row + x + 3);
-
- const V mul1 = MulAdd(l1 + r1, wh1, mul0);
- const V mul2 = MulAdd(l2 + r2, wh2, mul1);
- const V mul3 = MulAdd(l3 + r3, wh3, mul2);
- return mul3;
- }
-
- template <size_t kSizeModN>
- static JXL_INLINE V HorzConvolveLast(const float* const JXL_RESTRICT row,
- const int64_t x, const int64_t xsize,
- const V wh0, const V wh1, const V wh2,
- const V wh3) {
- const D d;
- const V c = LoadU(d, row + x);
- const V mul0 = c * wh0;
-
- const V l1 = LoadU(d, row + x - 1);
- const V l2 = LoadU(d, row + x - 2);
- const V l3 = LoadU(d, row + x - 3);
-
- V r1, r2, r3;
-#if HWY_TARGET == HWY_SCALAR
- r1 = LoadU(d, row + Mirror(x + 1, xsize));
- r2 = LoadU(d, row + Mirror(x + 2, xsize));
- r3 = LoadU(d, row + Mirror(x + 3, xsize));
-#else
- const size_t N = Lanes(d);
- if (kSizeModN == 0) {
- r3 = TableLookupLanes(c, SetTableIndices(d, MirrorLanes(N - 3)));
- r2 = TableLookupLanes(c, SetTableIndices(d, MirrorLanes(N - 2)));
- r1 = TableLookupLanes(c, SetTableIndices(d, MirrorLanes(N - 1)));
- } else if (kSizeModN == 1) {
- const auto last = LoadU(d, row + xsize - N);
- r3 = TableLookupLanes(last, SetTableIndices(d, MirrorLanes(N - 2)));
- r2 = TableLookupLanes(last, SetTableIndices(d, MirrorLanes(N - 1)));
- r1 = last;
- } else /* kSizeModN >= 2 */ {
- const auto last = LoadU(d, row + xsize - N);
- r3 = TableLookupLanes(last, SetTableIndices(d, MirrorLanes(N - 1)));
- r2 = last;
- r1 = LoadU(d, row + x + 1);
- }
-#endif
-
- // Sum of pixels with Manhattan distance i, multiplied by weights[i].
- const V sum1 = l1 + r1;
- const V mul1 = MulAdd(sum1, wh1, mul0);
- const V sum2 = l2 + r2;
- const V mul2 = MulAdd(sum2, wh2, mul1);
- const V sum3 = l3 + r3;
- const V mul3 = MulAdd(sum3, wh3, mul2);
- return mul3;
- }
-
- // Returns one vector of horizontal convolution results; lane i is the result
- // for pixel pos + i. This is the fast path for interior pixels, i.e. kRadius
- // valid pixels before/after pos.
- static JXL_INLINE V HorzConvolve(const float* const JXL_RESTRICT pos,
- const V wh0, const V wh1, const V wh2,
- const V wh3) {
- const D d;
- const V c = LoadU(d, pos);
- const V mul0 = c * wh0;
-
- // TODO(janwas): better to Combine
- const V l1 = LoadU(d, pos - 1);
- const V r1 = LoadU(d, pos + 1);
- const V l2 = LoadU(d, pos - 2);
- const V r2 = LoadU(d, pos + 2);
- const V l3 = LoadU(d, pos - 3);
- const V r3 = LoadU(d, pos + 3);
- // Sum of pixels with Manhattan distance i, multiplied by weights[i].
- const V sum1 = l1 + r1;
- const V mul1 = MulAdd(sum1, wh1, mul0);
- const V sum2 = l2 + r2;
- const V mul2 = MulAdd(sum2, wh2, mul1);
- const V sum3 = l3 + r3;
- const V mul3 = MulAdd(sum3, wh3, mul2);
- return mul3;
- }
-}; // namespace HWY_NAMESPACE
-
-} // namespace strategy
-
-// Single entry point for convolution.
-// "Strategy" (Direct*/Separable*) decides kernel size and how to evaluate it.
-template <class Strategy>
-class ConvolveT {
- static constexpr int64_t kRadius = Strategy::kRadius;
- using Simd = HWY_CAPPED(float, 16);
-
- public:
- static size_t MinWidth() {
-#if HWY_TARGET == HWY_SCALAR
- // First/Last use mirrored loads of up to +/- kRadius.
- return 2 * kRadius;
-#else
- return Lanes(Simd()) + kRadius;
-#endif
- }
-
- // "Image" is ImageF or Image3F.
- template <class Image, class Weights>
- static void Run(const Image& in, const Rect& rect, const Weights& weights,
- ThreadPool* pool, Image* out) {
- PROFILER_ZONE("ConvolveT::Run");
- JXL_CHECK(SameSize(rect, *out));
- JXL_CHECK(rect.xsize() >= MinWidth());
-
- static_assert(int64_t(kRadius) <= 3,
- "Must handle [0, kRadius) and >= kRadius");
- switch (rect.xsize() % Lanes(Simd())) {
- case 0:
- return RunRows<0>(in, rect, weights, pool, out);
- case 1:
- return RunRows<1>(in, rect, weights, pool, out);
- case 2:
- return RunRows<2>(in, rect, weights, pool, out);
- default:
- return RunRows<3>(in, rect, weights, pool, out);
- }
- }
-
- private:
- template <size_t kSizeModN, class WrapRow, class Weights>
- static JXL_INLINE void RunRow(const float* JXL_RESTRICT in,
- const size_t xsize, const int64_t stride,
- const WrapRow& wrap_row, const Weights& weights,
- float* JXL_RESTRICT out) {
- Strategy::template ConvolveRow<kSizeModN>(in, xsize, stride, wrap_row,
- weights, out);
- }
-
- template <size_t kSizeModN, class Weights>
- static JXL_INLINE void RunBorderRows(const ImageF& in, const Rect& rect,
- const int64_t ybegin, const int64_t yend,
- const Weights& weights, ImageF* out) {
- const int64_t stride = in.PixelsPerRow();
- const WrapRowMirror wrap_row(in, rect.ysize());
- for (int64_t y = ybegin; y < yend; ++y) {
- RunRow<kSizeModN>(rect.ConstRow(in, y), rect.xsize(), stride, wrap_row,
- weights, out->Row(y));
- }
- }
-
- // Image3F.
- template <size_t kSizeModN, class Weights>
- static JXL_INLINE void RunBorderRows(const Image3F& in, const Rect& rect,
- const int64_t ybegin, const int64_t yend,
- const Weights& weights, Image3F* out) {
- const int64_t stride = in.PixelsPerRow();
- for (int64_t y = ybegin; y < yend; ++y) {
- for (size_t c = 0; c < 3; ++c) {
- const WrapRowMirror wrap_row(in.Plane(c), rect.ysize());
- RunRow<kSizeModN>(rect.ConstPlaneRow(in, c, y), rect.xsize(), stride,
- wrap_row, weights, out->PlaneRow(c, y));
- }
- }
- }
-
- template <size_t kSizeModN, class Weights>
- static JXL_INLINE void RunInteriorRows(const ImageF& in, const Rect& rect,
- const int64_t ybegin,
- const int64_t yend,
- const Weights& weights,
- ThreadPool* pool, ImageF* out) {
- const int64_t stride = in.PixelsPerRow();
- JXL_CHECK(RunOnPool(
- pool, ybegin, yend, ThreadPool::NoInit,
- [&](const uint32_t y, size_t /*thread*/) HWY_ATTR {
- RunRow<kSizeModN>(rect.ConstRow(in, y), rect.xsize(), stride,
- WrapRowUnchanged(), weights, out->Row(y));
- },
- "Convolve"));
- }
-
- // Image3F.
- template <size_t kSizeModN, class Weights>
- static JXL_INLINE void RunInteriorRows(const Image3F& in, const Rect& rect,
- const int64_t ybegin,
- const int64_t yend,
- const Weights& weights,
- ThreadPool* pool, Image3F* out) {
- const int64_t stride = in.PixelsPerRow();
- JXL_CHECK(RunOnPool(
- pool, ybegin, yend, ThreadPool::NoInit,
- [&](const uint32_t y, size_t /*thread*/) HWY_ATTR {
- for (size_t c = 0; c < 3; ++c) {
- RunRow<kSizeModN>(rect.ConstPlaneRow(in, c, y), rect.xsize(),
- stride, WrapRowUnchanged(), weights,
- out->PlaneRow(c, y));
- }
- },
- "Convolve3"));
- }
-
- template <size_t kSizeModN, class Image, class Weights>
- static JXL_INLINE void RunRows(const Image& in, const Rect& rect,
- const Weights& weights, ThreadPool* pool,
- Image* out) {
- const int64_t ysize = rect.ysize();
- RunBorderRows<kSizeModN>(in, rect, 0, std::min(int64_t(kRadius), ysize),
- weights, out);
- if (ysize > 2 * int64_t(kRadius)) {
- RunInteriorRows<kSizeModN>(in, rect, int64_t(kRadius),
- ysize - int64_t(kRadius), weights, pool, out);
- }
- if (ysize > int64_t(kRadius)) {
- RunBorderRows<kSizeModN>(in, rect, ysize - int64_t(kRadius), ysize,
- weights, out);
- }
- }
-};
-
-void Symmetric3(const ImageF& in, const Rect& rect,
- const WeightsSymmetric3& weights, ThreadPool* pool,
- ImageF* out) {
- using Conv = ConvolveT<strategy::Symmetric3>;
- if (rect.xsize() >= Conv::MinWidth()) {
- return Conv::Run(in, rect, weights, pool, out);
- }
-
- return SlowSymmetric3(in, rect, weights, pool, out);
-}
-
-// Symmetric5 is implemented above without ConvolveT.
-
-void Separable5(const ImageF& in, const Rect& rect,
- const WeightsSeparable5& weights, ThreadPool* pool,
- ImageF* out) {
- using Conv = ConvolveT<strategy::Separable5>;
- if (rect.xsize() >= Conv::MinWidth()) {
- return Conv::Run(in, rect, weights, pool, out);
- }
-
- return SlowSeparable5(in, rect, weights, pool, out);
-}
-void Separable5_3(const Image3F& in, const Rect& rect,
- const WeightsSeparable5& weights, ThreadPool* pool,
- Image3F* out) {
- using Conv = ConvolveT<strategy::Separable5>;
- if (rect.xsize() >= Conv::MinWidth()) {
- return Conv::Run(in, rect, weights, pool, out);
- }
-
- return SlowSeparable5(in, rect, weights, pool, out);
-}
-
-void Separable7(const ImageF& in, const Rect& rect,
- const WeightsSeparable7& weights, ThreadPool* pool,
- ImageF* out) {
- using Conv = ConvolveT<strategy::Separable7>;
- if (rect.xsize() >= Conv::MinWidth()) {
- return Conv::Run(in, rect, weights, pool, out);
- }
-
- return SlowSeparable7(in, rect, weights, pool, out);
-}
-void Separable7_3(const Image3F& in, const Rect& rect,
- const WeightsSeparable7& weights, ThreadPool* pool,
- Image3F* out) {
- using Conv = ConvolveT<strategy::Separable7>;
- if (rect.xsize() >= Conv::MinWidth()) {
- return Conv::Run(in, rect, weights, pool, out);
- }
-
- return SlowSeparable7(in, rect, weights, pool, out);
-}
-
-// Semi-vectorized (interior pixels Fonly); called directly like slow::, unlike
-// the fully vectorized strategies below.
-void Symmetric5(const ImageF& in, const Rect& rect,
- const WeightsSymmetric5& weights, ThreadPool* pool,
- ImageF* JXL_RESTRICT out) {
- PROFILER_FUNC;
-
- const size_t ysize = rect.ysize();
- JXL_CHECK(RunOnPool(
- pool, 0, static_cast<uint32_t>(ysize), ThreadPool::NoInit,
- [&](const uint32_t task, size_t /*thread*/) {
- const int64_t iy = task;
-
- if (iy < 2 || iy >= static_cast<ssize_t>(ysize) - 2) {
- Symmetric5BorderRow(in, rect, iy, weights, out->Row(iy));
- } else {
- Symmetric5Row<WrapUnchanged>(in, rect, iy, weights, out->Row(iy));
- }
- },
- "Symmetric5x5Convolution"));
-}
-
-void Symmetric5_3(const Image3F& in, const Rect& rect,
- const WeightsSymmetric5& weights, ThreadPool* pool,
- Image3F* JXL_RESTRICT out) {
- PROFILER_FUNC;
-
- const size_t ysize = rect.ysize();
- JXL_CHECK(RunOnPool(
- pool, 0, static_cast<uint32_t>(ysize), ThreadPool::NoInit,
- [&](const uint32_t task, size_t /*thread*/) {
- const size_t iy = task;
-
- if (iy < 2 || iy >= ysize - 2) {
- for (size_t c = 0; c < 3; ++c) {
- Symmetric5BorderRow(in.Plane(c), rect, iy, weights,
- out->PlaneRow(c, iy));
- }
- } else {
- for (size_t c = 0; c < 3; ++c) {
- Symmetric5Row<WrapUnchanged>(in.Plane(c), rect, iy, weights,
- out->PlaneRow(c, iy));
- }
- }
- },
- "Symmetric5x5Convolution3"));
-}
-
-// NOLINTNEXTLINE(google-readability-namespace-comments)
-} // namespace HWY_NAMESPACE
-} // namespace jxl
-HWY_AFTER_NAMESPACE();
-
-#if HWY_ONCE
-namespace jxl {
-
-HWY_EXPORT(Symmetric3);
-void Symmetric3(const ImageF& in, const Rect& rect,
- const WeightsSymmetric3& weights, ThreadPool* pool,
- ImageF* out) {
- return HWY_DYNAMIC_DISPATCH(Symmetric3)(in, rect, weights, pool, out);
-}
-
-HWY_EXPORT(Symmetric5);
-void Symmetric5(const ImageF& in, const Rect& rect,
- const WeightsSymmetric5& weights, ThreadPool* pool,
- ImageF* JXL_RESTRICT out) {
- return HWY_DYNAMIC_DISPATCH(Symmetric5)(in, rect, weights, pool, out);
-}
-
-HWY_EXPORT(Symmetric5_3);
-void Symmetric5_3(const Image3F& in, const Rect& rect,
- const WeightsSymmetric5& weights, ThreadPool* pool,
- Image3F* JXL_RESTRICT out) {
- return HWY_DYNAMIC_DISPATCH(Symmetric5_3)(in, rect, weights, pool, out);
-}
-
-HWY_EXPORT(Separable5);
-void Separable5(const ImageF& in, const Rect& rect,
- const WeightsSeparable5& weights, ThreadPool* pool,
- ImageF* out) {
- return HWY_DYNAMIC_DISPATCH(Separable5)(in, rect, weights, pool, out);
-}
-
-HWY_EXPORT(Separable5_3);
-void Separable5_3(const Image3F& in, const Rect& rect,
- const WeightsSeparable5& weights, ThreadPool* pool,
- Image3F* out) {
- return HWY_DYNAMIC_DISPATCH(Separable5_3)(in, rect, weights, pool, out);
-}
-
-HWY_EXPORT(Separable7);
-void Separable7(const ImageF& in, const Rect& rect,
- const WeightsSeparable7& weights, ThreadPool* pool,
- ImageF* out) {
- return HWY_DYNAMIC_DISPATCH(Separable7)(in, rect, weights, pool, out);
-}
-
-HWY_EXPORT(Separable7_3);
-void Separable7_3(const Image3F& in, const Rect& rect,
- const WeightsSeparable7& weights, ThreadPool* pool,
- Image3F* out) {
- return HWY_DYNAMIC_DISPATCH(Separable7_3)(in, rect, weights, pool, out);
-}
-
-//------------------------------------------------------------------------------
-// Kernels
-
-// Concentrates energy in low-frequency components (e.g. for antialiasing).
-const WeightsSymmetric3& WeightsSymmetric3Lowpass() {
- // Computed by research/convolve_weights.py's cubic spline approximations of
- // prolate spheroidal wave functions.
- constexpr float w0 = 0.36208932f;
- constexpr float w1 = 0.12820096f;
- constexpr float w2 = 0.03127668f;
- static constexpr WeightsSymmetric3 weights = {
- {HWY_REP4(w0)}, {HWY_REP4(w1)}, {HWY_REP4(w2)}};
- return weights;
-}
-
-const WeightsSeparable5& WeightsSeparable5Lowpass() {
- constexpr float w0 = 0.41714928f;
- constexpr float w1 = 0.25539268f;
- constexpr float w2 = 0.03603267f;
- static constexpr WeightsSeparable5 weights = {
- {HWY_REP4(w0), HWY_REP4(w1), HWY_REP4(w2)},
- {HWY_REP4(w0), HWY_REP4(w1), HWY_REP4(w2)}};
- return weights;
-}
-
-const WeightsSymmetric5& WeightsSymmetric5Lowpass() {
- static constexpr WeightsSymmetric5 weights = {
- {HWY_REP4(0.1740135f)}, {HWY_REP4(0.1065369f)}, {HWY_REP4(0.0150310f)},
- {HWY_REP4(0.0652254f)}, {HWY_REP4(0.0012984f)}, {HWY_REP4(0.0092025f)}};
- return weights;
-}
-
-const WeightsSeparable5& WeightsSeparable5Gaussian1() {
- constexpr float w0 = 0.38774f;
- constexpr float w1 = 0.24477f;
- constexpr float w2 = 0.06136f;
- static constexpr WeightsSeparable5 weights = {
- {HWY_REP4(w0), HWY_REP4(w1), HWY_REP4(w2)},
- {HWY_REP4(w0), HWY_REP4(w1), HWY_REP4(w2)}};
- return weights;
-}
-
-const WeightsSeparable5& WeightsSeparable5Gaussian2() {
- constexpr float w0 = 0.250301f;
- constexpr float w1 = 0.221461f;
- constexpr float w2 = 0.153388f;
- static constexpr WeightsSeparable5 weights = {
- {HWY_REP4(w0), HWY_REP4(w1), HWY_REP4(w2)},
- {HWY_REP4(w0), HWY_REP4(w1), HWY_REP4(w2)}};
- return weights;
-}
-
-//------------------------------------------------------------------------------
-// Slow
-
-namespace {
-
-template <class WrapX, class WrapY>
-float SlowSymmetric3Pixel(const ImageF& in, const int64_t ix, const int64_t iy,
- const int64_t xsize, const int64_t ysize,
- const WeightsSymmetric3& weights) {
- float sum = 0.0f;
-
- // ix: image; kx: kernel
- for (int64_t ky = -1; ky <= 1; ky++) {
- const int64_t y = WrapY()(iy + ky, ysize);
- const float* JXL_RESTRICT row_in = in.ConstRow(static_cast<size_t>(y));
-
- const float wc = ky == 0 ? weights.c[0] : weights.r[0];
- const float wlr = ky == 0 ? weights.r[0] : weights.d[0];
-
- const int64_t xm1 = WrapX()(ix - 1, xsize);
- const int64_t xp1 = WrapX()(ix + 1, xsize);
- sum += row_in[ix] * wc + (row_in[xm1] + row_in[xp1]) * wlr;
- }
- return sum;
-}
-
-template <class WrapY>
-void SlowSymmetric3Row(const ImageF& in, const int64_t iy, const int64_t xsize,
- const int64_t ysize, const WeightsSymmetric3& weights,
- float* JXL_RESTRICT row_out) {
- row_out[0] =
- SlowSymmetric3Pixel<WrapMirror, WrapY>(in, 0, iy, xsize, ysize, weights);
- for (int64_t ix = 1; ix < xsize - 1; ix++) {
- row_out[ix] = SlowSymmetric3Pixel<WrapUnchanged, WrapY>(in, ix, iy, xsize,
- ysize, weights);
- }
- {
- const int64_t ix = xsize - 1;
- row_out[ix] = SlowSymmetric3Pixel<WrapMirror, WrapY>(in, ix, iy, xsize,
- ysize, weights);
- }
-}
-
-} // namespace
-
-void SlowSymmetric3(const ImageF& in, const Rect& rect,
- const WeightsSymmetric3& weights, ThreadPool* pool,
- ImageF* JXL_RESTRICT out) {
- PROFILER_FUNC;
-
- const int64_t xsize = static_cast<int64_t>(rect.xsize());
- const int64_t ysize = static_cast<int64_t>(rect.ysize());
- const int64_t kRadius = 1;
-
- JXL_CHECK(RunOnPool(
- pool, 0, static_cast<uint32_t>(ysize), ThreadPool::NoInit,
- [&](const uint32_t task, size_t /*thread*/) {
- const int64_t iy = task;
- float* JXL_RESTRICT out_row = out->Row(static_cast<size_t>(iy));
-
- if (iy < kRadius || iy >= ysize - kRadius) {
- SlowSymmetric3Row<WrapMirror>(in, iy, xsize, ysize, weights, out_row);
- } else {
- SlowSymmetric3Row<WrapUnchanged>(in, iy, xsize, ysize, weights,
- out_row);
- }
- },
- "SlowSymmetric3"));
-}
-
-void SlowSymmetric3(const Image3F& in, const Rect& rect,
- const WeightsSymmetric3& weights, ThreadPool* pool,
- Image3F* JXL_RESTRICT out) {
- PROFILER_FUNC;
-
- const int64_t xsize = static_cast<int64_t>(rect.xsize());
- const int64_t ysize = static_cast<int64_t>(rect.ysize());
- const int64_t kRadius = 1;
-
- JXL_CHECK(RunOnPool(
- pool, 0, static_cast<uint32_t>(ysize), ThreadPool::NoInit,
- [&](const uint32_t task, size_t /*thread*/) {
- const int64_t iy = task;
- const size_t oy = static_cast<size_t>(iy);
-
- if (iy < kRadius || iy >= ysize - kRadius) {
- for (size_t c = 0; c < 3; ++c) {
- SlowSymmetric3Row<WrapMirror>(in.Plane(c), iy, xsize, ysize,
- weights, out->PlaneRow(c, oy));
- }
- } else {
- for (size_t c = 0; c < 3; ++c) {
- SlowSymmetric3Row<WrapUnchanged>(in.Plane(c), iy, xsize, ysize,
- weights, out->PlaneRow(c, oy));
- }
- }
- },
- "SlowSymmetric3"));
-}
-
-namespace {
-
-// Separable kernels, any radius.
-float SlowSeparablePixel(const ImageF& in, const Rect& rect, const int64_t x,
- const int64_t y, const int64_t radius,
- const float* JXL_RESTRICT horz_weights,
- const float* JXL_RESTRICT vert_weights) {
- const size_t xsize = rect.xsize();
- const size_t ysize = rect.ysize();
- const WrapMirror wrap;
-
- float mul = 0.0f;
- for (int dy = -radius; dy <= radius; ++dy) {
- const float wy = vert_weights[std::abs(dy) * 4];
- const size_t sy = wrap(y + dy, ysize);
- JXL_CHECK(sy < ysize);
- const float* const JXL_RESTRICT row = rect.ConstRow(in, sy);
- for (int dx = -radius; dx <= radius; ++dx) {
- const float wx = horz_weights[std::abs(dx) * 4];
- const size_t sx = wrap(x + dx, xsize);
- JXL_CHECK(sx < xsize);
- mul += row[sx] * wx * wy;
- }
- }
- return mul;
-}
-
-} // namespace
-
-void SlowSeparable5(const ImageF& in, const Rect& rect,
- const WeightsSeparable5& weights, ThreadPool* pool,
- ImageF* out) {
- PROFILER_FUNC;
- const float* horz_weights = &weights.horz[0];
- const float* vert_weights = &weights.vert[0];
-
- const size_t ysize = rect.ysize();
- JXL_CHECK(RunOnPool(
- pool, 0, static_cast<uint32_t>(ysize), ThreadPool::NoInit,
- [&](const uint32_t task, size_t /*thread*/) {
- const int64_t y = task;
-
- float* const JXL_RESTRICT row_out = out->Row(y);
- for (size_t x = 0; x < rect.xsize(); ++x) {
- row_out[x] = SlowSeparablePixel(in, rect, x, y, /*radius=*/2,
- horz_weights, vert_weights);
- }
- },
- "SlowSeparable5"));
-}
-
-void SlowSeparable5(const Image3F& in, const Rect& rect,
- const WeightsSeparable5& weights, ThreadPool* pool,
- Image3F* out) {
- for (size_t c = 0; c < 3; ++c) {
- SlowSeparable5(in.Plane(c), rect, weights, pool, &out->Plane(c));
- }
-}
-
-void SlowSeparable7(const ImageF& in, const Rect& rect,
- const WeightsSeparable7& weights, ThreadPool* pool,
- ImageF* out) {
- PROFILER_FUNC;
- const float* horz_weights = &weights.horz[0];
- const float* vert_weights = &weights.vert[0];
-
- const size_t ysize = rect.ysize();
- JXL_CHECK(RunOnPool(
- pool, 0, static_cast<uint32_t>(ysize), ThreadPool::NoInit,
- [&](const uint32_t task, size_t /*thread*/) {
- const int64_t y = task;
-
- float* const JXL_RESTRICT row_out = out->Row(y);
- for (size_t x = 0; x < rect.xsize(); ++x) {
- row_out[x] = SlowSeparablePixel(in, rect, x, y, /*radius=*/3,
- horz_weights, vert_weights);
- }
- },
- "SlowSeparable7"));
-}
-
-void SlowSeparable7(const Image3F& in, const Rect& rect,
- const WeightsSeparable7& weights, ThreadPool* pool,
- Image3F* out) {
- for (size_t c = 0; c < 3; ++c) {
- SlowSeparable7(in.Plane(c), rect, weights, pool, &out->Plane(c));
- }
-}
-
-void SlowLaplacian5(const ImageF& in, const Rect& rect, ThreadPool* pool,
- ImageF* out) {
- PROFILER_FUNC;
- JXL_CHECK(SameSize(rect, *out));
-
- const size_t xsize = rect.xsize();
- const size_t ysize = rect.ysize();
- const WrapMirror wrap;
-
- JXL_CHECK(RunOnPool(
- pool, 0, static_cast<uint32_t>(ysize), ThreadPool::NoInit,
- [&](const uint32_t task, size_t /*thread*/) {
- const int64_t y = task;
-
- const float* const JXL_RESTRICT row_t =
- rect.ConstRow(in, wrap(y - 2, ysize));
- const float* const JXL_RESTRICT row_m = rect.ConstRow(in, y);
- const float* const JXL_RESTRICT row_b =
- rect.ConstRow(in, wrap(y + 2, ysize));
- float* const JXL_RESTRICT row_out = out->Row(y);
-
- for (int64_t x = 0; static_cast<size_t>(x) < xsize; ++x) {
- const int64_t xm2 = wrap(x - 2, xsize);
- const int64_t xp2 = wrap(x + 2, xsize);
- float r = 0.0f;
- r += /* */ 1.0f * row_t[x];
- r += 1.0f * row_m[xm2] - 4.0f * row_m[x] + 1.0f * row_m[xp2];
- r += /* */ 1.0f * row_b[x];
- row_out[x] = r;
- }
- },
- "SlowLaplacian5"));
-}
-
-void SlowLaplacian5(const Image3F& in, const Rect& rect, ThreadPool* pool,
- Image3F* out) {
- for (size_t c = 0; c < 3; ++c) {
- SlowLaplacian5(in.Plane(c), rect, pool, &out->Plane(c));
- }
-}
-
-} // namespace jxl
-#endif // HWY_ONCE
diff --git a/media/libjxl/src/lib/jxl/convolve.h b/media/libjxl/src/lib/jxl/convolve.h
index c2e2ae42fb..2fcd2d0980 100644
--- a/media/libjxl/src/lib/jxl/convolve.h
+++ b/media/libjxl/src/lib/jxl/convolve.h
@@ -75,28 +75,14 @@ const WeightsSymmetric5& WeightsSymmetric5Lowpass();
void SlowSymmetric3(const ImageF& in, const Rect& rect,
const WeightsSymmetric3& weights, ThreadPool* pool,
ImageF* JXL_RESTRICT out);
-void SlowSymmetric3(const Image3F& in, const Rect& rect,
- const WeightsSymmetric3& weights, ThreadPool* pool,
- Image3F* JXL_RESTRICT out);
void SlowSeparable5(const ImageF& in, const Rect& rect,
const WeightsSeparable5& weights, ThreadPool* pool,
ImageF* out);
-void SlowSeparable5(const Image3F& in, const Rect& rect,
- const WeightsSeparable5& weights, ThreadPool* pool,
- Image3F* out);
void SlowSeparable7(const ImageF& in, const Rect& rect,
const WeightsSeparable7& weights, ThreadPool* pool,
ImageF* out);
-void SlowSeparable7(const Image3F& in, const Rect& rect,
- const WeightsSeparable7& weights, ThreadPool* pool,
- Image3F* out);
-
-void SlowLaplacian5(const ImageF& in, const Rect& rect, ThreadPool* pool,
- ImageF* out);
-void SlowLaplacian5(const Image3F& in, const Rect& rect, ThreadPool* pool,
- Image3F* out);
void Symmetric3(const ImageF& in, const Rect& rect,
const WeightsSymmetric3& weights, ThreadPool* pool,
@@ -106,26 +92,14 @@ void Symmetric5(const ImageF& in, const Rect& rect,
const WeightsSymmetric5& weights, ThreadPool* pool,
ImageF* JXL_RESTRICT out);
-void Symmetric5_3(const Image3F& in, const Rect& rect,
- const WeightsSymmetric5& weights, ThreadPool* pool,
- Image3F* JXL_RESTRICT out);
-
void Separable5(const ImageF& in, const Rect& rect,
const WeightsSeparable5& weights, ThreadPool* pool,
ImageF* out);
-void Separable5_3(const Image3F& in, const Rect& rect,
- const WeightsSeparable5& weights, ThreadPool* pool,
- Image3F* out);
-
void Separable7(const ImageF& in, const Rect& rect,
const WeightsSeparable7& weights, ThreadPool* pool,
ImageF* out);
-void Separable7_3(const Image3F& in, const Rect& rect,
- const WeightsSeparable7& weights, ThreadPool* pool,
- Image3F* out);
-
} // namespace jxl
#endif // LIB_JXL_CONVOLVE_H_
diff --git a/media/libjxl/src/lib/jxl/convolve_separable5.cc b/media/libjxl/src/lib/jxl/convolve_separable5.cc
new file mode 100644
index 0000000000..b26ff54bbc
--- /dev/null
+++ b/media/libjxl/src/lib/jxl/convolve_separable5.cc
@@ -0,0 +1,261 @@
+// Copyright (c) the JPEG XL Project Authors. All rights reserved.
+//
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+#include "lib/jxl/convolve.h"
+
+#undef HWY_TARGET_INCLUDE
+#define HWY_TARGET_INCLUDE "lib/jxl/convolve_separable5.cc"
+#include <hwy/foreach_target.h>
+#include <hwy/highway.h>
+
+#include "lib/jxl/convolve-inl.h"
+
+HWY_BEFORE_NAMESPACE();
+namespace jxl {
+namespace HWY_NAMESPACE {
+
+// These templates are not found via ADL.
+using hwy::HWY_NAMESPACE::Add;
+using hwy::HWY_NAMESPACE::Mul;
+using hwy::HWY_NAMESPACE::MulAdd;
+using hwy::HWY_NAMESPACE::Vec;
+
+// 5x5 convolution by separable kernel with a single scan through the input.
+// This is more cache-efficient than separate horizontal/vertical passes, and
+// possibly faster (given enough registers) than tiling and/or transposing.
+//
+// Overview: imagine a 5x5 window around a central pixel. First convolve the
+// rows by multiplying the pixels with the corresponding weights from
+// WeightsSeparable5.horz[abs(x_offset) * 4]. Then multiply each of these
+// intermediate results by the corresponding vertical weight, i.e.
+// vert[abs(y_offset) * 4]. Finally, store the sum of these values as the
+// convolution result at the position of the central pixel in the output.
+//
+// Each of these operations uses SIMD vectors. The central pixel and most
+// importantly the output are aligned, so neighnoring pixels (e.g. x_offset=1)
+// require unaligned loads. Because weights are supplied in identical groups of
+// 4, we can use LoadDup128 to load them (slightly faster).
+//
+// Uses mirrored boundary handling. Until x >= kRadius, the horizontal
+// convolution uses Neighbors class to shuffle vectors as if each of its lanes
+// had been loaded from the mirrored offset. Similarly, the last full vector to
+// write uses mirroring. In the case of scalar vectors, Neighbors is not usable
+// and the value is loaded directly. Otherwise, the number of valid pixels
+// modulo the vector size enables a small optimization: for smaller offsets,
+// a non-mirrored load is sufficient.
+class Separable5Strategy {
+ using D = HWY_CAPPED(float, 16);
+ using V = Vec<D>;
+
+ public:
+ static constexpr int64_t kRadius = 2;
+
+ template <size_t kSizeModN, class WrapRow>
+ static JXL_MAYBE_INLINE void ConvolveRow(
+ const float* const JXL_RESTRICT row_m, const size_t xsize,
+ const int64_t stride, const WrapRow& wrap_row,
+ const WeightsSeparable5& weights, float* const JXL_RESTRICT row_out) {
+ const D d;
+ const int64_t neg_stride = -stride; // allows LEA addressing.
+ const float* const JXL_RESTRICT row_t2 =
+ wrap_row(row_m + 2 * neg_stride, stride);
+ const float* const JXL_RESTRICT row_t1 =
+ wrap_row(row_m + 1 * neg_stride, stride);
+ const float* const JXL_RESTRICT row_b1 =
+ wrap_row(row_m + 1 * stride, stride);
+ const float* const JXL_RESTRICT row_b2 =
+ wrap_row(row_m + 2 * stride, stride);
+
+ const V wh0 = LoadDup128(d, weights.horz + 0 * 4);
+ const V wh1 = LoadDup128(d, weights.horz + 1 * 4);
+ const V wh2 = LoadDup128(d, weights.horz + 2 * 4);
+ const V wv0 = LoadDup128(d, weights.vert + 0 * 4);
+ const V wv1 = LoadDup128(d, weights.vert + 1 * 4);
+ const V wv2 = LoadDup128(d, weights.vert + 2 * 4);
+
+ size_t x = 0;
+
+ // More than one iteration for scalars.
+ for (; x < kRadius; x += Lanes(d)) {
+ const V conv0 =
+ Mul(HorzConvolveFirst(row_m, x, xsize, wh0, wh1, wh2), wv0);
+
+ const V conv1t = HorzConvolveFirst(row_t1, x, xsize, wh0, wh1, wh2);
+ const V conv1b = HorzConvolveFirst(row_b1, x, xsize, wh0, wh1, wh2);
+ const V conv1 = MulAdd(Add(conv1t, conv1b), wv1, conv0);
+
+ const V conv2t = HorzConvolveFirst(row_t2, x, xsize, wh0, wh1, wh2);
+ const V conv2b = HorzConvolveFirst(row_b2, x, xsize, wh0, wh1, wh2);
+ const V conv2 = MulAdd(Add(conv2t, conv2b), wv2, conv1);
+ Store(conv2, d, row_out + x);
+ }
+
+ // Main loop: load inputs without padding
+ for (; x + Lanes(d) + kRadius <= xsize; x += Lanes(d)) {
+ const V conv0 = Mul(HorzConvolve(row_m + x, wh0, wh1, wh2), wv0);
+
+ const V conv1t = HorzConvolve(row_t1 + x, wh0, wh1, wh2);
+ const V conv1b = HorzConvolve(row_b1 + x, wh0, wh1, wh2);
+ const V conv1 = MulAdd(Add(conv1t, conv1b), wv1, conv0);
+
+ const V conv2t = HorzConvolve(row_t2 + x, wh0, wh1, wh2);
+ const V conv2b = HorzConvolve(row_b2 + x, wh0, wh1, wh2);
+ const V conv2 = MulAdd(Add(conv2t, conv2b), wv2, conv1);
+ Store(conv2, d, row_out + x);
+ }
+
+ // Last full vector to write (the above loop handled mod >= kRadius)
+#if HWY_TARGET == HWY_SCALAR
+ while (x < xsize) {
+#else
+ if (kSizeModN < kRadius) {
+#endif
+ const V conv0 =
+ Mul(HorzConvolveLast<kSizeModN>(row_m, x, xsize, wh0, wh1, wh2), wv0);
+
+ const V conv1t =
+ HorzConvolveLast<kSizeModN>(row_t1, x, xsize, wh0, wh1, wh2);
+ const V conv1b =
+ HorzConvolveLast<kSizeModN>(row_b1, x, xsize, wh0, wh1, wh2);
+ const V conv1 = MulAdd(Add(conv1t, conv1b), wv1, conv0);
+
+ const V conv2t =
+ HorzConvolveLast<kSizeModN>(row_t2, x, xsize, wh0, wh1, wh2);
+ const V conv2b =
+ HorzConvolveLast<kSizeModN>(row_b2, x, xsize, wh0, wh1, wh2);
+ const V conv2 = MulAdd(Add(conv2t, conv2b), wv2, conv1);
+ Store(conv2, d, row_out + x);
+ x += Lanes(d);
+ }
+
+ // If mod = 0, the above vector was the last.
+ if (kSizeModN != 0) {
+ for (; x < xsize; ++x) {
+ float mul = 0.0f;
+ for (int64_t dy = -kRadius; dy <= kRadius; ++dy) {
+ const float wy = weights.vert[std::abs(dy) * 4];
+ const float* clamped_row = wrap_row(row_m + dy * stride, stride);
+ for (int64_t dx = -kRadius; dx <= kRadius; ++dx) {
+ const float wx = weights.horz[std::abs(dx) * 4];
+ const int64_t clamped_x = Mirror(x + dx, xsize);
+ mul += clamped_row[clamped_x] * wx * wy;
+ }
+ }
+ row_out[x] = mul;
+ }
+ }
+ }
+
+ private:
+ // Same as HorzConvolve for the first/last vector in a row.
+ static JXL_MAYBE_INLINE V HorzConvolveFirst(
+ const float* const JXL_RESTRICT row, const int64_t x, const int64_t xsize,
+ const V wh0, const V wh1, const V wh2) {
+ const D d;
+ const V c = LoadU(d, row + x);
+ const V mul0 = Mul(c, wh0);
+
+#if HWY_TARGET == HWY_SCALAR
+ const V l1 = LoadU(d, row + Mirror(x - 1, xsize));
+ const V l2 = LoadU(d, row + Mirror(x - 2, xsize));
+#else
+ (void)xsize;
+ const V l1 = Neighbors::FirstL1(c);
+ const V l2 = Neighbors::FirstL2(c);
+#endif
+
+ const V r1 = LoadU(d, row + x + 1);
+ const V r2 = LoadU(d, row + x + 2);
+
+ const V mul1 = MulAdd(Add(l1, r1), wh1, mul0);
+ const V mul2 = MulAdd(Add(l2, r2), wh2, mul1);
+ return mul2;
+ }
+
+ template <size_t kSizeModN>
+ static JXL_MAYBE_INLINE V
+ HorzConvolveLast(const float* const JXL_RESTRICT row, const int64_t x,
+ const int64_t xsize, const V wh0, const V wh1, const V wh2) {
+ const D d;
+ const V c = LoadU(d, row + x);
+ const V mul0 = Mul(c, wh0);
+
+ const V l1 = LoadU(d, row + x - 1);
+ const V l2 = LoadU(d, row + x - 2);
+
+ V r1, r2;
+#if HWY_TARGET == HWY_SCALAR
+ r1 = LoadU(d, row + Mirror(x + 1, xsize));
+ r2 = LoadU(d, row + Mirror(x + 2, xsize));
+#else
+ const size_t N = Lanes(d);
+ if (kSizeModN == 0) {
+ r2 = TableLookupLanes(c, SetTableIndices(d, MirrorLanes(N - 2)));
+ r1 = TableLookupLanes(c, SetTableIndices(d, MirrorLanes(N - 1)));
+ } else { // == 1
+ const auto last = LoadU(d, row + xsize - N);
+ r2 = TableLookupLanes(last, SetTableIndices(d, MirrorLanes(N - 1)));
+ r1 = last;
+ }
+#endif
+
+ // Sum of pixels with Manhattan distance i, multiplied by weights[i].
+ const V sum1 = Add(l1, r1);
+ const V mul1 = MulAdd(sum1, wh1, mul0);
+ const V sum2 = Add(l2, r2);
+ const V mul2 = MulAdd(sum2, wh2, mul1);
+ return mul2;
+ }
+
+ // Requires kRadius valid pixels before/after pos.
+ static JXL_MAYBE_INLINE V HorzConvolve(const float* const JXL_RESTRICT pos,
+ const V wh0, const V wh1,
+ const V wh2) {
+ const D d;
+ const V c = LoadU(d, pos);
+ const V mul0 = Mul(c, wh0);
+
+ // Loading anew is faster than combining vectors.
+ const V l1 = LoadU(d, pos - 1);
+ const V r1 = LoadU(d, pos + 1);
+ const V l2 = LoadU(d, pos - 2);
+ const V r2 = LoadU(d, pos + 2);
+ // Sum of pixels with Manhattan distance i, multiplied by weights[i].
+ const V sum1 = Add(l1, r1);
+ const V mul1 = MulAdd(sum1, wh1, mul0);
+ const V sum2 = Add(l2, r2);
+ const V mul2 = MulAdd(sum2, wh2, mul1);
+ return mul2;
+ }
+};
+
+void Separable5(const ImageF& in, const Rect& rect,
+ const WeightsSeparable5& weights, ThreadPool* pool,
+ ImageF* out) {
+ using Conv = ConvolveT<Separable5Strategy>;
+ if (rect.xsize() >= Conv::MinWidth()) {
+ return Conv::Run(in, rect, weights, pool, out);
+ }
+
+ return SlowSeparable5(in, rect, weights, pool, out);
+}
+
+// NOLINTNEXTLINE(google-readability-namespace-comments)
+} // namespace HWY_NAMESPACE
+} // namespace jxl
+HWY_AFTER_NAMESPACE();
+
+#if HWY_ONCE
+namespace jxl {
+
+HWY_EXPORT(Separable5);
+void Separable5(const ImageF& in, const Rect& rect,
+ const WeightsSeparable5& weights, ThreadPool* pool,
+ ImageF* out) {
+ return HWY_DYNAMIC_DISPATCH(Separable5)(in, rect, weights, pool, out);
+}
+
+} // namespace jxl
+#endif // HWY_ONCE
diff --git a/media/libjxl/src/lib/jxl/convolve_separable7.cc b/media/libjxl/src/lib/jxl/convolve_separable7.cc
new file mode 100644
index 0000000000..086dfd22b5
--- /dev/null
+++ b/media/libjxl/src/lib/jxl/convolve_separable7.cc
@@ -0,0 +1,285 @@
+// Copyright (c) the JPEG XL Project Authors. All rights reserved.
+//
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+#include "lib/jxl/convolve.h"
+
+#undef HWY_TARGET_INCLUDE
+#define HWY_TARGET_INCLUDE "lib/jxl/convolve_separable7.cc"
+#include <hwy/foreach_target.h>
+#include <hwy/highway.h>
+
+#include "lib/jxl/convolve-inl.h"
+
+HWY_BEFORE_NAMESPACE();
+namespace jxl {
+namespace HWY_NAMESPACE {
+
+// These templates are not found via ADL.
+using hwy::HWY_NAMESPACE::Add;
+using hwy::HWY_NAMESPACE::Mul;
+using hwy::HWY_NAMESPACE::MulAdd;
+using hwy::HWY_NAMESPACE::Vec;
+
+// 7x7 convolution by separable kernel with a single scan through the input.
+// Extended version of Separable5, see documentation there.
+class Separable7Strategy {
+ using D = HWY_CAPPED(float, 16);
+ using V = Vec<D>;
+
+ public:
+ static constexpr int64_t kRadius = 3;
+
+ template <size_t kSizeModN, class WrapRow>
+ static JXL_MAYBE_INLINE void ConvolveRow(
+ const float* const JXL_RESTRICT row_m, const size_t xsize,
+ const int64_t stride, const WrapRow& wrap_row,
+ const WeightsSeparable7& weights, float* const JXL_RESTRICT row_out) {
+ const D d;
+ const int64_t neg_stride = -stride; // allows LEA addressing.
+ const float* const JXL_RESTRICT row_t3 =
+ wrap_row(row_m + 3 * neg_stride, stride);
+ const float* const JXL_RESTRICT row_t2 =
+ wrap_row(row_m + 2 * neg_stride, stride);
+ const float* const JXL_RESTRICT row_t1 =
+ wrap_row(row_m + 1 * neg_stride, stride);
+ const float* const JXL_RESTRICT row_b1 =
+ wrap_row(row_m + 1 * stride, stride);
+ const float* const JXL_RESTRICT row_b2 =
+ wrap_row(row_m + 2 * stride, stride);
+ const float* const JXL_RESTRICT row_b3 =
+ wrap_row(row_m + 3 * stride, stride);
+
+ const V wh0 = LoadDup128(d, weights.horz + 0 * 4);
+ const V wh1 = LoadDup128(d, weights.horz + 1 * 4);
+ const V wh2 = LoadDup128(d, weights.horz + 2 * 4);
+ const V wh3 = LoadDup128(d, weights.horz + 3 * 4);
+ const V wv0 = LoadDup128(d, weights.vert + 0 * 4);
+ const V wv1 = LoadDup128(d, weights.vert + 1 * 4);
+ const V wv2 = LoadDup128(d, weights.vert + 2 * 4);
+ const V wv3 = LoadDup128(d, weights.vert + 3 * 4);
+
+ size_t x = 0;
+
+ // More than one iteration for scalars.
+ for (; x < kRadius; x += Lanes(d)) {
+ const V conv0 =
+ Mul(HorzConvolveFirst(row_m, x, xsize, wh0, wh1, wh2, wh3), wv0);
+
+ const V conv1t = HorzConvolveFirst(row_t1, x, xsize, wh0, wh1, wh2, wh3);
+ const V conv1b = HorzConvolveFirst(row_b1, x, xsize, wh0, wh1, wh2, wh3);
+ const V conv1 = MulAdd(Add(conv1t, conv1b), wv1, conv0);
+
+ const V conv2t = HorzConvolveFirst(row_t2, x, xsize, wh0, wh1, wh2, wh3);
+ const V conv2b = HorzConvolveFirst(row_b2, x, xsize, wh0, wh1, wh2, wh3);
+ const V conv2 = MulAdd(Add(conv2t, conv2b), wv2, conv1);
+
+ const V conv3t = HorzConvolveFirst(row_t3, x, xsize, wh0, wh1, wh2, wh3);
+ const V conv3b = HorzConvolveFirst(row_b3, x, xsize, wh0, wh1, wh2, wh3);
+ const V conv3 = MulAdd(Add(conv3t, conv3b), wv3, conv2);
+
+ Store(conv3, d, row_out + x);
+ }
+
+ // Main loop: load inputs without padding
+ for (; x + Lanes(d) + kRadius <= xsize; x += Lanes(d)) {
+ const V conv0 = Mul(HorzConvolve(row_m + x, wh0, wh1, wh2, wh3), wv0);
+
+ const V conv1t = HorzConvolve(row_t1 + x, wh0, wh1, wh2, wh3);
+ const V conv1b = HorzConvolve(row_b1 + x, wh0, wh1, wh2, wh3);
+ const V conv1 = MulAdd(Add(conv1t, conv1b), wv1, conv0);
+
+ const V conv2t = HorzConvolve(row_t2 + x, wh0, wh1, wh2, wh3);
+ const V conv2b = HorzConvolve(row_b2 + x, wh0, wh1, wh2, wh3);
+ const V conv2 = MulAdd(Add(conv2t, conv2b), wv2, conv1);
+
+ const V conv3t = HorzConvolve(row_t3 + x, wh0, wh1, wh2, wh3);
+ const V conv3b = HorzConvolve(row_b3 + x, wh0, wh1, wh2, wh3);
+ const V conv3 = MulAdd(Add(conv3t, conv3b), wv3, conv2);
+
+ Store(conv3, d, row_out + x);
+ }
+
+ // Last full vector to write (the above loop handled mod >= kRadius)
+#if HWY_TARGET == HWY_SCALAR
+ while (x < xsize) {
+#else
+ if (kSizeModN < kRadius) {
+#endif
+ const V conv0 =
+ Mul(HorzConvolveLast<kSizeModN>(row_m, x, xsize, wh0, wh1, wh2, wh3),
+ wv0);
+
+ const V conv1t =
+ HorzConvolveLast<kSizeModN>(row_t1, x, xsize, wh0, wh1, wh2, wh3);
+ const V conv1b =
+ HorzConvolveLast<kSizeModN>(row_b1, x, xsize, wh0, wh1, wh2, wh3);
+ const V conv1 = MulAdd(Add(conv1t, conv1b), wv1, conv0);
+
+ const V conv2t =
+ HorzConvolveLast<kSizeModN>(row_t2, x, xsize, wh0, wh1, wh2, wh3);
+ const V conv2b =
+ HorzConvolveLast<kSizeModN>(row_b2, x, xsize, wh0, wh1, wh2, wh3);
+ const V conv2 = MulAdd(Add(conv2t, conv2b), wv2, conv1);
+
+ const V conv3t =
+ HorzConvolveLast<kSizeModN>(row_t3, x, xsize, wh0, wh1, wh2, wh3);
+ const V conv3b =
+ HorzConvolveLast<kSizeModN>(row_b3, x, xsize, wh0, wh1, wh2, wh3);
+ const V conv3 = MulAdd(Add(conv3t, conv3b), wv3, conv2);
+
+ Store(conv3, d, row_out + x);
+ x += Lanes(d);
+ }
+
+ // If mod = 0, the above vector was the last.
+ if (kSizeModN != 0) {
+ for (; x < xsize; ++x) {
+ float mul = 0.0f;
+ for (int64_t dy = -kRadius; dy <= kRadius; ++dy) {
+ const float wy = weights.vert[std::abs(dy) * 4];
+ const float* clamped_row = wrap_row(row_m + dy * stride, stride);
+ for (int64_t dx = -kRadius; dx <= kRadius; ++dx) {
+ const float wx = weights.horz[std::abs(dx) * 4];
+ const int64_t clamped_x = Mirror(x + dx, xsize);
+ mul += clamped_row[clamped_x] * wx * wy;
+ }
+ }
+ row_out[x] = mul;
+ }
+ }
+ }
+
+ private:
+ // Same as HorzConvolve for the first/last vector in a row.
+ static JXL_MAYBE_INLINE V HorzConvolveFirst(
+ const float* const JXL_RESTRICT row, const int64_t x, const int64_t xsize,
+ const V wh0, const V wh1, const V wh2, const V wh3) {
+ const D d;
+ const V c = LoadU(d, row + x);
+ const V mul0 = Mul(c, wh0);
+
+#if HWY_TARGET == HWY_SCALAR
+ const V l1 = LoadU(d, row + Mirror(x - 1, xsize));
+ const V l2 = LoadU(d, row + Mirror(x - 2, xsize));
+ const V l3 = LoadU(d, row + Mirror(x - 3, xsize));
+#else
+ (void)xsize;
+ const V l1 = Neighbors::FirstL1(c);
+ const V l2 = Neighbors::FirstL2(c);
+ const V l3 = Neighbors::FirstL3(c);
+#endif
+
+ const V r1 = LoadU(d, row + x + 1);
+ const V r2 = LoadU(d, row + x + 2);
+ const V r3 = LoadU(d, row + x + 3);
+
+ const V mul1 = MulAdd(Add(l1, r1), wh1, mul0);
+ const V mul2 = MulAdd(Add(l2, r2), wh2, mul1);
+ const V mul3 = MulAdd(Add(l3, r3), wh3, mul2);
+ return mul3;
+ }
+
+ template <size_t kSizeModN>
+ static JXL_MAYBE_INLINE V HorzConvolveLast(
+ const float* const JXL_RESTRICT row, const int64_t x, const int64_t xsize,
+ const V wh0, const V wh1, const V wh2, const V wh3) {
+ const D d;
+ const V c = LoadU(d, row + x);
+ const V mul0 = Mul(c, wh0);
+
+ const V l1 = LoadU(d, row + x - 1);
+ const V l2 = LoadU(d, row + x - 2);
+ const V l3 = LoadU(d, row + x - 3);
+
+ V r1, r2, r3;
+#if HWY_TARGET == HWY_SCALAR
+ r1 = LoadU(d, row + Mirror(x + 1, xsize));
+ r2 = LoadU(d, row + Mirror(x + 2, xsize));
+ r3 = LoadU(d, row + Mirror(x + 3, xsize));
+#else
+ const size_t N = Lanes(d);
+ if (kSizeModN == 0) {
+ r3 = TableLookupLanes(c, SetTableIndices(d, MirrorLanes(N - 3)));
+ r2 = TableLookupLanes(c, SetTableIndices(d, MirrorLanes(N - 2)));
+ r1 = TableLookupLanes(c, SetTableIndices(d, MirrorLanes(N - 1)));
+ } else if (kSizeModN == 1) {
+ const auto last = LoadU(d, row + xsize - N);
+ r3 = TableLookupLanes(last, SetTableIndices(d, MirrorLanes(N - 2)));
+ r2 = TableLookupLanes(last, SetTableIndices(d, MirrorLanes(N - 1)));
+ r1 = last;
+ } else /* kSizeModN >= 2 */ {
+ const auto last = LoadU(d, row + xsize - N);
+ r3 = TableLookupLanes(last, SetTableIndices(d, MirrorLanes(N - 1)));
+ r2 = last;
+ r1 = LoadU(d, row + x + 1);
+ }
+#endif
+
+ // Sum of pixels with Manhattan distance i, multiplied by weights[i].
+ const V sum1 = Add(l1, r1);
+ const V mul1 = MulAdd(sum1, wh1, mul0);
+ const V sum2 = Add(l2, r2);
+ const V mul2 = MulAdd(sum2, wh2, mul1);
+ const V sum3 = Add(l3, r3);
+ const V mul3 = MulAdd(sum3, wh3, mul2);
+ return mul3;
+ }
+
+ // Returns one vector of horizontal convolution results; lane i is the result
+ // for pixel pos + i. This is the fast path for interior pixels, i.e. kRadius
+ // valid pixels before/after pos.
+ static JXL_MAYBE_INLINE V HorzConvolve(const float* const JXL_RESTRICT pos,
+ const V wh0, const V wh1, const V wh2,
+ const V wh3) {
+ const D d;
+ const V c = LoadU(d, pos);
+ const V mul0 = Mul(c, wh0);
+
+ // TODO(janwas): better to Combine
+ const V l1 = LoadU(d, pos - 1);
+ const V r1 = LoadU(d, pos + 1);
+ const V l2 = LoadU(d, pos - 2);
+ const V r2 = LoadU(d, pos + 2);
+ const V l3 = LoadU(d, pos - 3);
+ const V r3 = LoadU(d, pos + 3);
+ // Sum of pixels with Manhattan distance i, multiplied by weights[i].
+ const V sum1 = Add(l1, r1);
+ const V mul1 = MulAdd(sum1, wh1, mul0);
+ const V sum2 = Add(l2, r2);
+ const V mul2 = MulAdd(sum2, wh2, mul1);
+ const V sum3 = Add(l3, r3);
+ const V mul3 = MulAdd(sum3, wh3, mul2);
+ return mul3;
+ }
+};
+
+void Separable7(const ImageF& in, const Rect& rect,
+ const WeightsSeparable7& weights, ThreadPool* pool,
+ ImageF* out) {
+ using Conv = ConvolveT<Separable7Strategy>;
+ if (rect.xsize() >= Conv::MinWidth()) {
+ return Conv::Run(in, rect, weights, pool, out);
+ }
+
+ return SlowSeparable7(in, rect, weights, pool, out);
+}
+
+// NOLINTNEXTLINE(google-readability-namespace-comments)
+} // namespace HWY_NAMESPACE
+} // namespace jxl
+HWY_AFTER_NAMESPACE();
+
+#if HWY_ONCE
+namespace jxl {
+
+HWY_EXPORT(Separable7);
+void Separable7(const ImageF& in, const Rect& rect,
+ const WeightsSeparable7& weights, ThreadPool* pool,
+ ImageF* out) {
+ return HWY_DYNAMIC_DISPATCH(Separable7)(in, rect, weights, pool, out);
+}
+
+} // namespace jxl
+#endif // HWY_ONCE
diff --git a/media/libjxl/src/lib/jxl/convolve_slow.cc b/media/libjxl/src/lib/jxl/convolve_slow.cc
new file mode 100644
index 0000000000..fffe5f74c8
--- /dev/null
+++ b/media/libjxl/src/lib/jxl/convolve_slow.cc
@@ -0,0 +1,212 @@
+// Copyright (c) the JPEG XL Project Authors. All rights reserved.
+//
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+#include "lib/jxl/convolve.h"
+
+#include "lib/jxl/convolve-inl.h"
+
+namespace jxl {
+
+//------------------------------------------------------------------------------
+// Kernels
+
+// 4 instances of a given literal value, useful as input to LoadDup128.
+#define JXL_REP4(literal) literal, literal, literal, literal
+
+// Concentrates energy in low-frequency components (e.g. for antialiasing).
+const WeightsSymmetric3& WeightsSymmetric3Lowpass() {
+ // Computed by research/convolve_weights.py's cubic spline approximations of
+ // prolate spheroidal wave functions.
+ constexpr float w0 = 0.36208932f;
+ constexpr float w1 = 0.12820096f;
+ constexpr float w2 = 0.03127668f;
+ static constexpr WeightsSymmetric3 weights = {
+ {JXL_REP4(w0)}, {JXL_REP4(w1)}, {JXL_REP4(w2)}};
+ return weights;
+}
+
+const WeightsSeparable5& WeightsSeparable5Lowpass() {
+ constexpr float w0 = 0.41714928f;
+ constexpr float w1 = 0.25539268f;
+ constexpr float w2 = 0.03603267f;
+ static constexpr WeightsSeparable5 weights = {
+ {JXL_REP4(w0), JXL_REP4(w1), JXL_REP4(w2)},
+ {JXL_REP4(w0), JXL_REP4(w1), JXL_REP4(w2)}};
+ return weights;
+}
+
+const WeightsSymmetric5& WeightsSymmetric5Lowpass() {
+ static constexpr WeightsSymmetric5 weights = {
+ {JXL_REP4(0.1740135f)}, {JXL_REP4(0.1065369f)}, {JXL_REP4(0.0150310f)},
+ {JXL_REP4(0.0652254f)}, {JXL_REP4(0.0012984f)}, {JXL_REP4(0.0092025f)}};
+ return weights;
+}
+
+const WeightsSeparable5& WeightsSeparable5Gaussian1() {
+ constexpr float w0 = 0.38774f;
+ constexpr float w1 = 0.24477f;
+ constexpr float w2 = 0.06136f;
+ static constexpr WeightsSeparable5 weights = {
+ {JXL_REP4(w0), JXL_REP4(w1), JXL_REP4(w2)},
+ {JXL_REP4(w0), JXL_REP4(w1), JXL_REP4(w2)}};
+ return weights;
+}
+
+const WeightsSeparable5& WeightsSeparable5Gaussian2() {
+ constexpr float w0 = 0.250301f;
+ constexpr float w1 = 0.221461f;
+ constexpr float w2 = 0.153388f;
+ static constexpr WeightsSeparable5 weights = {
+ {JXL_REP4(w0), JXL_REP4(w1), JXL_REP4(w2)},
+ {JXL_REP4(w0), JXL_REP4(w1), JXL_REP4(w2)}};
+ return weights;
+}
+
+#undef JXL_REP4
+
+//------------------------------------------------------------------------------
+// Slow
+
+namespace {
+
+template <class WrapX, class WrapY>
+float SlowSymmetric3Pixel(const ImageF& in, const int64_t ix, const int64_t iy,
+ const int64_t xsize, const int64_t ysize,
+ const WeightsSymmetric3& weights) {
+ float sum = 0.0f;
+
+ // ix: image; kx: kernel
+ for (int64_t ky = -1; ky <= 1; ky++) {
+ const int64_t y = WrapY()(iy + ky, ysize);
+ const float* JXL_RESTRICT row_in = in.ConstRow(static_cast<size_t>(y));
+
+ const float wc = ky == 0 ? weights.c[0] : weights.r[0];
+ const float wlr = ky == 0 ? weights.r[0] : weights.d[0];
+
+ const int64_t xm1 = WrapX()(ix - 1, xsize);
+ const int64_t xp1 = WrapX()(ix + 1, xsize);
+ sum += row_in[ix] * wc + (row_in[xm1] + row_in[xp1]) * wlr;
+ }
+ return sum;
+}
+
+template <class WrapY>
+void SlowSymmetric3Row(const ImageF& in, const int64_t iy, const int64_t xsize,
+ const int64_t ysize, const WeightsSymmetric3& weights,
+ float* JXL_RESTRICT row_out) {
+ row_out[0] =
+ SlowSymmetric3Pixel<WrapMirror, WrapY>(in, 0, iy, xsize, ysize, weights);
+ for (int64_t ix = 1; ix < xsize - 1; ix++) {
+ row_out[ix] = SlowSymmetric3Pixel<WrapUnchanged, WrapY>(in, ix, iy, xsize,
+ ysize, weights);
+ }
+ {
+ const int64_t ix = xsize - 1;
+ row_out[ix] = SlowSymmetric3Pixel<WrapMirror, WrapY>(in, ix, iy, xsize,
+ ysize, weights);
+ }
+}
+
+} // namespace
+
+void SlowSymmetric3(const ImageF& in, const Rect& rect,
+ const WeightsSymmetric3& weights, ThreadPool* pool,
+ ImageF* JXL_RESTRICT out) {
+ PROFILER_FUNC;
+
+ const int64_t xsize = static_cast<int64_t>(rect.xsize());
+ const int64_t ysize = static_cast<int64_t>(rect.ysize());
+ const int64_t kRadius = 1;
+
+ JXL_CHECK(RunOnPool(
+ pool, 0, static_cast<uint32_t>(ysize), ThreadPool::NoInit,
+ [&](const uint32_t task, size_t /*thread*/) {
+ const int64_t iy = task;
+ float* JXL_RESTRICT out_row = out->Row(static_cast<size_t>(iy));
+
+ if (iy < kRadius || iy >= ysize - kRadius) {
+ SlowSymmetric3Row<WrapMirror>(in, iy, xsize, ysize, weights, out_row);
+ } else {
+ SlowSymmetric3Row<WrapUnchanged>(in, iy, xsize, ysize, weights,
+ out_row);
+ }
+ },
+ "SlowSymmetric3"));
+}
+
+namespace {
+
+// Separable kernels, any radius.
+float SlowSeparablePixel(const ImageF& in, const Rect& rect, const int64_t x,
+ const int64_t y, const int64_t radius,
+ const float* JXL_RESTRICT horz_weights,
+ const float* JXL_RESTRICT vert_weights) {
+ const size_t xsize = rect.xsize();
+ const size_t ysize = rect.ysize();
+ const WrapMirror wrap;
+
+ float mul = 0.0f;
+ for (int dy = -radius; dy <= radius; ++dy) {
+ const float wy = vert_weights[std::abs(dy) * 4];
+ const size_t sy = wrap(y + dy, ysize);
+ JXL_CHECK(sy < ysize);
+ const float* const JXL_RESTRICT row = rect.ConstRow(in, sy);
+ for (int dx = -radius; dx <= radius; ++dx) {
+ const float wx = horz_weights[std::abs(dx) * 4];
+ const size_t sx = wrap(x + dx, xsize);
+ JXL_CHECK(sx < xsize);
+ mul += row[sx] * wx * wy;
+ }
+ }
+ return mul;
+}
+
+} // namespace
+
+void SlowSeparable5(const ImageF& in, const Rect& rect,
+ const WeightsSeparable5& weights, ThreadPool* pool,
+ ImageF* out) {
+ PROFILER_FUNC;
+ const float* horz_weights = &weights.horz[0];
+ const float* vert_weights = &weights.vert[0];
+
+ const size_t ysize = rect.ysize();
+ JXL_CHECK(RunOnPool(
+ pool, 0, static_cast<uint32_t>(ysize), ThreadPool::NoInit,
+ [&](const uint32_t task, size_t /*thread*/) {
+ const int64_t y = task;
+
+ float* const JXL_RESTRICT row_out = out->Row(y);
+ for (size_t x = 0; x < rect.xsize(); ++x) {
+ row_out[x] = SlowSeparablePixel(in, rect, x, y, /*radius=*/2,
+ horz_weights, vert_weights);
+ }
+ },
+ "SlowSeparable5"));
+}
+
+void SlowSeparable7(const ImageF& in, const Rect& rect,
+ const WeightsSeparable7& weights, ThreadPool* pool,
+ ImageF* out) {
+ PROFILER_FUNC;
+ const float* horz_weights = &weights.horz[0];
+ const float* vert_weights = &weights.vert[0];
+
+ const size_t ysize = rect.ysize();
+ JXL_CHECK(RunOnPool(
+ pool, 0, static_cast<uint32_t>(ysize), ThreadPool::NoInit,
+ [&](const uint32_t task, size_t /*thread*/) {
+ const int64_t y = task;
+
+ float* const JXL_RESTRICT row_out = out->Row(y);
+ for (size_t x = 0; x < rect.xsize(); ++x) {
+ row_out[x] = SlowSeparablePixel(in, rect, x, y, /*radius=*/3,
+ horz_weights, vert_weights);
+ }
+ },
+ "SlowSeparable7"));
+}
+
+} // namespace jxl
diff --git a/media/libjxl/src/lib/jxl/convolve_symmetric3.cc b/media/libjxl/src/lib/jxl/convolve_symmetric3.cc
new file mode 100644
index 0000000000..06b59dfb60
--- /dev/null
+++ b/media/libjxl/src/lib/jxl/convolve_symmetric3.cc
@@ -0,0 +1,194 @@
+// Copyright (c) the JPEG XL Project Authors. All rights reserved.
+//
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+#include "lib/jxl/convolve.h"
+
+#undef HWY_TARGET_INCLUDE
+#define HWY_TARGET_INCLUDE "lib/jxl/convolve_symmetric3.cc"
+#include <hwy/foreach_target.h>
+#include <hwy/highway.h>
+
+#include "lib/jxl/convolve-inl.h"
+
+HWY_BEFORE_NAMESPACE();
+namespace jxl {
+namespace HWY_NAMESPACE {
+
+// These templates are not found via ADL.
+using hwy::HWY_NAMESPACE::Add;
+using hwy::HWY_NAMESPACE::Mul;
+using hwy::HWY_NAMESPACE::MulAdd;
+using hwy::HWY_NAMESPACE::Vec;
+
+template <class WrapY, class V>
+static V WeightedSum(const ImageF& in, const WrapY wrap_y, const size_t ix,
+ const int64_t iy, const size_t ysize, const V wx0,
+ const V wx1, const V wx2) {
+ const HWY_FULL(float) d;
+ const float* JXL_RESTRICT center = in.ConstRow(wrap_y(iy, ysize)) + ix;
+ const auto in_m2 = LoadU(d, center - 2);
+ const auto in_p2 = LoadU(d, center + 2);
+ const auto in_m1 = LoadU(d, center - 1);
+ const auto in_p1 = LoadU(d, center + 1);
+ const auto in_00 = Load(d, center);
+ const auto sum_2 = Mul(wx2, Add(in_m2, in_p2));
+ const auto sum_1 = Mul(wx1, Add(in_m1, in_p1));
+ const auto sum_0 = Mul(wx0, in_00);
+ return Add(sum_2, Add(sum_1, sum_0));
+}
+
+// 3x3 convolution by symmetric kernel with a single scan through the input.
+class Symmetric3Strategy {
+ using D = HWY_CAPPED(float, 16);
+ using V = Vec<D>;
+
+ public:
+ static constexpr int64_t kRadius = 1;
+
+ // Only accesses pixels in [0, xsize).
+ template <size_t kSizeModN, class WrapRow>
+ static JXL_MAYBE_INLINE void ConvolveRow(
+ const float* const JXL_RESTRICT row_m, const size_t xsize,
+ const int64_t stride, const WrapRow& wrap_row,
+ const WeightsSymmetric3& weights, float* const JXL_RESTRICT row_out) {
+ const D d;
+ // t, m, b = top, middle, bottom row;
+ const float* const JXL_RESTRICT row_t = wrap_row(row_m - stride, stride);
+ const float* const JXL_RESTRICT row_b = wrap_row(row_m + stride, stride);
+
+ // Must load in advance - compiler doesn't understand LoadDup128 and
+ // schedules them too late.
+ const V w0 = LoadDup128(d, weights.c);
+ const V w1 = LoadDup128(d, weights.r);
+ const V w2 = LoadDup128(d, weights.d);
+
+ // l, c, r = left, center, right. Leftmost vector: need FirstL1.
+ {
+ const V tc = LoadU(d, row_t + 0);
+ const V mc = LoadU(d, row_m + 0);
+ const V bc = LoadU(d, row_b + 0);
+ const V tl = Neighbors::FirstL1(tc);
+ const V tr = LoadU(d, row_t + 0 + 1);
+ const V ml = Neighbors::FirstL1(mc);
+ const V mr = LoadU(d, row_m + 0 + 1);
+ const V bl = Neighbors::FirstL1(bc);
+ const V br = LoadU(d, row_b + 0 + 1);
+ const V conv =
+ WeightedSum(tl, tc, tr, ml, mc, mr, bl, bc, br, w0, w1, w2);
+ Store(conv, d, row_out + 0);
+ }
+
+ // Loop as long as we can load enough new values:
+ const size_t N = Lanes(d);
+ size_t x = N;
+ for (; x + N + kRadius <= xsize; x += N) {
+ const auto conv = ConvolveValid(row_t, row_m, row_b, x, w0, w1, w2);
+ Store(conv, d, row_out + x);
+ }
+
+ // For final (partial) vector:
+ const V tc = LoadU(d, row_t + x);
+ const V mc = LoadU(d, row_m + x);
+ const V bc = LoadU(d, row_b + x);
+
+ V tr, mr, br;
+#if HWY_TARGET == HWY_SCALAR
+ tr = tc; // Single-lane => mirrored right neighbor = center value.
+ mr = mc;
+ br = bc;
+#else
+ if (kSizeModN == 0) {
+ // The above loop didn't handle the last vector because it needs an
+ // additional right neighbor (generated via mirroring).
+ auto mirror = SetTableIndices(d, MirrorLanes(N - 1));
+ tr = TableLookupLanes(tc, mirror);
+ mr = TableLookupLanes(mc, mirror);
+ br = TableLookupLanes(bc, mirror);
+ } else {
+ auto mirror = SetTableIndices(d, MirrorLanes((xsize % N) - 1));
+ // Loads last valid value into uppermost lane and mirrors.
+ tr = TableLookupLanes(LoadU(d, row_t + xsize - N), mirror);
+ mr = TableLookupLanes(LoadU(d, row_m + xsize - N), mirror);
+ br = TableLookupLanes(LoadU(d, row_b + xsize - N), mirror);
+ }
+#endif
+
+ const V tl = LoadU(d, row_t + x - 1);
+ const V ml = LoadU(d, row_m + x - 1);
+ const V bl = LoadU(d, row_b + x - 1);
+ const V conv = WeightedSum(tl, tc, tr, ml, mc, mr, bl, bc, br, w0, w1, w2);
+ Store(conv, d, row_out + x);
+ }
+
+ private:
+ // Returns sum{x_i * w_i}.
+ template <class V>
+ static JXL_MAYBE_INLINE V WeightedSum(const V tl, const V tc, const V tr,
+ const V ml, const V mc, const V mr,
+ const V bl, const V bc, const V br,
+ const V w0, const V w1, const V w2) {
+ const V sum_tb = Add(tc, bc);
+
+ // Faster than 5 mul + 4 FMA.
+ const V mul0 = Mul(mc, w0);
+ const V sum_lr = Add(ml, mr);
+
+ const V x1 = Add(sum_tb, sum_lr);
+ const V mul1 = MulAdd(x1, w1, mul0);
+
+ const V sum_t2 = Add(tl, tr);
+ const V sum_b2 = Add(bl, br);
+ const V x2 = Add(sum_t2, sum_b2);
+ const V mul2 = MulAdd(x2, w2, mul1);
+ return mul2;
+ }
+
+ static JXL_MAYBE_INLINE V ConvolveValid(const float* JXL_RESTRICT row_t,
+ const float* JXL_RESTRICT row_m,
+ const float* JXL_RESTRICT row_b,
+ const int64_t x, const V w0,
+ const V w1, const V w2) {
+ const D d;
+ const V tc = LoadU(d, row_t + x);
+ const V mc = LoadU(d, row_m + x);
+ const V bc = LoadU(d, row_b + x);
+ const V tl = LoadU(d, row_t + x - 1);
+ const V tr = LoadU(d, row_t + x + 1);
+ const V ml = LoadU(d, row_m + x - 1);
+ const V mr = LoadU(d, row_m + x + 1);
+ const V bl = LoadU(d, row_b + x - 1);
+ const V br = LoadU(d, row_b + x + 1);
+ return WeightedSum(tl, tc, tr, ml, mc, mr, bl, bc, br, w0, w1, w2);
+ }
+};
+
+void Symmetric3(const ImageF& in, const Rect& rect,
+ const WeightsSymmetric3& weights, ThreadPool* pool,
+ ImageF* out) {
+ using Conv = ConvolveT<Symmetric3Strategy>;
+ if (rect.xsize() >= Conv::MinWidth()) {
+ return Conv::Run(in, rect, weights, pool, out);
+ }
+
+ return SlowSymmetric3(in, rect, weights, pool, out);
+}
+
+// NOLINTNEXTLINE(google-readability-namespace-comments)
+} // namespace HWY_NAMESPACE
+} // namespace jxl
+HWY_AFTER_NAMESPACE();
+
+#if HWY_ONCE
+namespace jxl {
+
+HWY_EXPORT(Symmetric3);
+void Symmetric3(const ImageF& in, const Rect& rect,
+ const WeightsSymmetric3& weights, ThreadPool* pool,
+ ImageF* out) {
+ return HWY_DYNAMIC_DISPATCH(Symmetric3)(in, rect, weights, pool, out);
+}
+
+} // namespace jxl
+#endif // HWY_ONCE
diff --git a/media/libjxl/src/lib/jxl/convolve_symmetric5.cc b/media/libjxl/src/lib/jxl/convolve_symmetric5.cc
new file mode 100644
index 0000000000..55a16899c3
--- /dev/null
+++ b/media/libjxl/src/lib/jxl/convolve_symmetric5.cc
@@ -0,0 +1,185 @@
+// Copyright (c) the JPEG XL Project Authors. All rights reserved.
+//
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+#include "lib/jxl/convolve.h"
+
+#undef HWY_TARGET_INCLUDE
+#define HWY_TARGET_INCLUDE "lib/jxl/convolve_symmetric5.cc"
+#include <hwy/foreach_target.h>
+#include <hwy/highway.h>
+
+#include "lib/jxl/common.h" // RoundUpTo
+#include "lib/jxl/convolve-inl.h"
+
+HWY_BEFORE_NAMESPACE();
+namespace jxl {
+namespace HWY_NAMESPACE {
+
+// These templates are not found via ADL.
+using hwy::HWY_NAMESPACE::Add;
+using hwy::HWY_NAMESPACE::Mul;
+using hwy::HWY_NAMESPACE::Vec;
+
+// Weighted sum of 1x5 pixels around ix, iy with [wx2 wx1 wx0 wx1 wx2].
+template <class WrapY>
+static float WeightedSumBorder(const ImageF& in, const WrapY wrap_y,
+ const int64_t ix, const int64_t iy,
+ const size_t xsize, const size_t ysize,
+ const float wx0, const float wx1,
+ const float wx2) {
+ const WrapMirror wrap_x;
+ const float* JXL_RESTRICT row = in.ConstRow(wrap_y(iy, ysize));
+ const float in_m2 = row[wrap_x(ix - 2, xsize)];
+ const float in_p2 = row[wrap_x(ix + 2, xsize)];
+ const float in_m1 = row[wrap_x(ix - 1, xsize)];
+ const float in_p1 = row[wrap_x(ix + 1, xsize)];
+ const float in_00 = row[ix];
+ const float sum_2 = wx2 * (in_m2 + in_p2);
+ const float sum_1 = wx1 * (in_m1 + in_p1);
+ const float sum_0 = wx0 * in_00;
+ return sum_2 + sum_1 + sum_0;
+}
+
+template <class WrapY, class V>
+static V WeightedSum(const ImageF& in, const WrapY wrap_y, const size_t ix,
+ const int64_t iy, const size_t ysize, const V wx0,
+ const V wx1, const V wx2) {
+ const HWY_FULL(float) d;
+ const float* JXL_RESTRICT center = in.ConstRow(wrap_y(iy, ysize)) + ix;
+ const auto in_m2 = LoadU(d, center - 2);
+ const auto in_p2 = LoadU(d, center + 2);
+ const auto in_m1 = LoadU(d, center - 1);
+ const auto in_p1 = LoadU(d, center + 1);
+ const auto in_00 = Load(d, center);
+ const auto sum_2 = Mul(wx2, Add(in_m2, in_p2));
+ const auto sum_1 = Mul(wx1, Add(in_m1, in_p1));
+ const auto sum_0 = Mul(wx0, in_00);
+ return Add(sum_2, Add(sum_1, sum_0));
+}
+
+// Produces result for one pixel
+template <class WrapY>
+float Symmetric5Border(const ImageF& in, const Rect& rect, const int64_t ix,
+ const int64_t iy, const WeightsSymmetric5& weights) {
+ const float w0 = weights.c[0];
+ const float w1 = weights.r[0];
+ const float w2 = weights.R[0];
+ const float w4 = weights.d[0];
+ const float w5 = weights.L[0];
+ const float w8 = weights.D[0];
+
+ const size_t xsize = rect.xsize();
+ const size_t ysize = rect.ysize();
+ const WrapY wrap_y;
+ // Unrolled loop over all 5 rows of the kernel.
+ float sum0 = WeightedSumBorder(in, wrap_y, ix, iy, xsize, ysize, w0, w1, w2);
+
+ sum0 += WeightedSumBorder(in, wrap_y, ix, iy - 2, xsize, ysize, w2, w5, w8);
+ float sum1 =
+ WeightedSumBorder(in, wrap_y, ix, iy + 2, xsize, ysize, w2, w5, w8);
+
+ sum0 += WeightedSumBorder(in, wrap_y, ix, iy - 1, xsize, ysize, w1, w4, w5);
+ sum1 += WeightedSumBorder(in, wrap_y, ix, iy + 1, xsize, ysize, w1, w4, w5);
+
+ return sum0 + sum1;
+}
+
+// Produces result for one vector's worth of pixels
+template <class WrapY>
+static void Symmetric5Interior(const ImageF& in, const Rect& rect,
+ const int64_t ix, const int64_t iy,
+ const WeightsSymmetric5& weights,
+ float* JXL_RESTRICT row_out) {
+ const HWY_FULL(float) d;
+
+ const auto w0 = LoadDup128(d, weights.c);
+ const auto w1 = LoadDup128(d, weights.r);
+ const auto w2 = LoadDup128(d, weights.R);
+ const auto w4 = LoadDup128(d, weights.d);
+ const auto w5 = LoadDup128(d, weights.L);
+ const auto w8 = LoadDup128(d, weights.D);
+
+ const size_t ysize = rect.ysize();
+ const WrapY wrap_y;
+ // Unrolled loop over all 5 rows of the kernel.
+ auto sum0 = WeightedSum(in, wrap_y, ix, iy, ysize, w0, w1, w2);
+
+ sum0 = Add(sum0, WeightedSum(in, wrap_y, ix, iy - 2, ysize, w2, w5, w8));
+ auto sum1 = WeightedSum(in, wrap_y, ix, iy + 2, ysize, w2, w5, w8);
+
+ sum0 = Add(sum0, WeightedSum(in, wrap_y, ix, iy - 1, ysize, w1, w4, w5));
+ sum1 = Add(sum1, WeightedSum(in, wrap_y, ix, iy + 1, ysize, w1, w4, w5));
+
+ Store(Add(sum0, sum1), d, row_out + ix);
+}
+
+template <class WrapY>
+static void Symmetric5Row(const ImageF& in, const Rect& rect, const int64_t iy,
+ const WeightsSymmetric5& weights,
+ float* JXL_RESTRICT row_out) {
+ const int64_t kRadius = 2;
+ const size_t xsize = rect.xsize();
+
+ size_t ix = 0;
+ const HWY_FULL(float) d;
+ const size_t N = Lanes(d);
+ const size_t aligned_x = RoundUpTo(kRadius, N);
+ for (; ix < std::min(aligned_x, xsize); ++ix) {
+ row_out[ix] = Symmetric5Border<WrapY>(in, rect, ix, iy, weights);
+ }
+ for (; ix + N + kRadius <= xsize; ix += N) {
+ Symmetric5Interior<WrapY>(in, rect, ix, iy, weights, row_out);
+ }
+ for (; ix < xsize; ++ix) {
+ row_out[ix] = Symmetric5Border<WrapY>(in, rect, ix, iy, weights);
+ }
+}
+
+static JXL_NOINLINE void Symmetric5BorderRow(const ImageF& in, const Rect& rect,
+ const int64_t iy,
+ const WeightsSymmetric5& weights,
+ float* JXL_RESTRICT row_out) {
+ return Symmetric5Row<WrapMirror>(in, rect, iy, weights, row_out);
+}
+
+// Semi-vectorized (interior pixels Fonly); called directly like slow::, unlike
+// the fully vectorized strategies below.
+void Symmetric5(const ImageF& in, const Rect& rect,
+ const WeightsSymmetric5& weights, ThreadPool* pool,
+ ImageF* JXL_RESTRICT out) {
+ PROFILER_FUNC;
+
+ const size_t ysize = rect.ysize();
+ JXL_CHECK(RunOnPool(
+ pool, 0, static_cast<uint32_t>(ysize), ThreadPool::NoInit,
+ [&](const uint32_t task, size_t /*thread*/) {
+ const int64_t iy = task;
+
+ if (iy < 2 || iy >= static_cast<ssize_t>(ysize) - 2) {
+ Symmetric5BorderRow(in, rect, iy, weights, out->Row(iy));
+ } else {
+ Symmetric5Row<WrapUnchanged>(in, rect, iy, weights, out->Row(iy));
+ }
+ },
+ "Symmetric5x5Convolution"));
+}
+
+// NOLINTNEXTLINE(google-readability-namespace-comments)
+} // namespace HWY_NAMESPACE
+} // namespace jxl
+HWY_AFTER_NAMESPACE();
+
+#if HWY_ONCE
+namespace jxl {
+
+HWY_EXPORT(Symmetric5);
+void Symmetric5(const ImageF& in, const Rect& rect,
+ const WeightsSymmetric5& weights, ThreadPool* pool,
+ ImageF* JXL_RESTRICT out) {
+ return HWY_DYNAMIC_DISPATCH(Symmetric5)(in, rect, weights, pool, out);
+}
+
+} // namespace jxl
+#endif // HWY_ONCE
diff --git a/media/libjxl/src/lib/jxl/convolve_test.cc b/media/libjxl/src/lib/jxl/convolve_test.cc
index b31eb2f973..2d75c31ea8 100644
--- a/media/libjxl/src/lib/jxl/convolve_test.cc
+++ b/media/libjxl/src/lib/jxl/convolve_test.cc
@@ -148,9 +148,9 @@ void TestConvolve() {
ThreadPoolInternal pool3(3);
for (size_t ysize = kConvolveMaxRadius; ysize < 16; ++ysize) {
JXL_DEBUG(JXL_DEBUG_CONVOLVE,
- "%" PRIuS " x %" PRIuS
- " (target %d)===============================",
- xsize, ysize, HWY_TARGET);
+ "%" PRIuS " x %" PRIuS " (target %" PRIx64
+ ")===============================",
+ xsize, ysize, static_cast<int64_t>(HWY_TARGET));
JXL_DEBUG(JXL_DEBUG_CONVOLVE, "Sym3------------------");
VerifySymmetric3(xsize, ysize, null_pool, &rng);
diff --git a/media/libjxl/src/lib/jxl/data_parallel_test.cc b/media/libjxl/src/lib/jxl/data_parallel_test.cc
index 79aee0f3f7..dd6ea625fb 100644
--- a/media/libjxl/src/lib/jxl/data_parallel_test.cc
+++ b/media/libjxl/src/lib/jxl/data_parallel_test.cc
@@ -51,7 +51,7 @@ int TestInit(void* jpegxl_opaque, size_t num_threads) { return 0; }
} // namespace
-TEST_F(DataParallelTest, RunnerCalledParamenters) {
+TEST_F(DataParallelTest, RunnerCalledParameters) {
EXPECT_TRUE(pool_.Run(
1234, 5678, [](size_t /* num_threads */) { return true; },
[](uint32_t /* task */, size_t /* thread */) { return; }));
diff --git a/media/libjxl/src/lib/jxl/dct-inl.h b/media/libjxl/src/lib/jxl/dct-inl.h
index 5ccb9341df..532606075e 100644
--- a/media/libjxl/src/lib/jxl/dct-inl.h
+++ b/media/libjxl/src/lib/jxl/dct-inl.h
@@ -24,6 +24,13 @@ namespace jxl {
namespace HWY_NAMESPACE {
namespace {
+// These templates are not found via ADL.
+using hwy::HWY_NAMESPACE::Add;
+using hwy::HWY_NAMESPACE::Mul;
+using hwy::HWY_NAMESPACE::MulAdd;
+using hwy::HWY_NAMESPACE::NegMulAdd;
+using hwy::HWY_NAMESPACE::Sub;
+
template <size_t SZ>
struct FVImpl {
using type = HWY_CAPPED(float, SZ);
@@ -48,7 +55,7 @@ struct CoeffBundle {
for (size_t i = 0; i < N; i++) {
auto in1 = Load(FV<SZ>(), ain1 + i * SZ);
auto in2 = Load(FV<SZ>(), ain2 + (N - i - 1) * SZ);
- Store(in1 + in2, FV<SZ>(), aout + i * SZ);
+ Store(Add(in1, in2), FV<SZ>(), aout + i * SZ);
}
}
static void SubReverse(const float* JXL_RESTRICT ain1,
@@ -57,7 +64,7 @@ struct CoeffBundle {
for (size_t i = 0; i < N; i++) {
auto in1 = Load(FV<SZ>(), ain1 + i * SZ);
auto in2 = Load(FV<SZ>(), ain2 + (N - i - 1) * SZ);
- Store(in1 - in2, FV<SZ>(), aout + i * SZ);
+ Store(Sub(in1, in2), FV<SZ>(), aout + i * SZ);
}
}
static void B(float* JXL_RESTRICT coeff) {
@@ -68,18 +75,18 @@ struct CoeffBundle {
for (size_t i = 1; i + 1 < N; i++) {
auto in1 = Load(FV<SZ>(), coeff + i * SZ);
auto in2 = Load(FV<SZ>(), coeff + (i + 1) * SZ);
- Store(in1 + in2, FV<SZ>(), coeff + i * SZ);
+ Store(Add(in1, in2), FV<SZ>(), coeff + i * SZ);
}
}
static void BTranspose(float* JXL_RESTRICT coeff) {
for (size_t i = N - 1; i > 0; i--) {
auto in1 = Load(FV<SZ>(), coeff + i * SZ);
auto in2 = Load(FV<SZ>(), coeff + (i - 1) * SZ);
- Store(in1 + in2, FV<SZ>(), coeff + i * SZ);
+ Store(Add(in1, in2), FV<SZ>(), coeff + i * SZ);
}
auto sqrt2 = Set(FV<SZ>(), kSqrt2);
auto in1 = Load(FV<SZ>(), coeff);
- Store(in1 * sqrt2, FV<SZ>(), coeff);
+ Store(Mul(in1, sqrt2), FV<SZ>(), coeff);
}
// Ideally optimized away by compiler (except the multiply).
static void InverseEvenOdd(const float* JXL_RESTRICT ain,
@@ -110,7 +117,7 @@ struct CoeffBundle {
for (size_t i = 0; i < N / 2; i++) {
auto in1 = Load(FV<SZ>(), coeff + (N / 2 + i) * SZ);
auto mul = Set(FV<SZ>(), WcMultipliers<N>::kMultipliers[i]);
- Store(in1 * mul, FV<SZ>(), coeff + (N / 2 + i) * SZ);
+ Store(Mul(in1, mul), FV<SZ>(), coeff + (N / 2 + i) * SZ);
}
}
static void MultiplyAndAdd(const float* JXL_RESTRICT coeff,
@@ -137,7 +144,7 @@ struct CoeffBundle {
const Block& out, size_t off) {
auto mul = Set(FV<SZ>(), 1.0f / N);
for (size_t i = 0; i < N; i++) {
- out.StorePart(FV<SZ>(), mul * Load(FV<SZ>(), coeff + i * SZ), i, off);
+ out.StorePart(FV<SZ>(), Mul(mul, Load(FV<SZ>(), coeff + i * SZ)), i, off);
}
}
};
@@ -155,8 +162,8 @@ struct DCT1DImpl<2, SZ> {
JXL_INLINE void operator()(float* JXL_RESTRICT mem) {
auto in1 = Load(FV<SZ>(), mem);
auto in2 = Load(FV<SZ>(), mem + SZ);
- Store(in1 + in2, FV<SZ>(), mem);
- Store(in1 - in2, FV<SZ>(), mem + SZ);
+ Store(Add(in1, in2), FV<SZ>(), mem);
+ Store(Sub(in1, in2), FV<SZ>(), mem + SZ);
}
};
@@ -194,8 +201,8 @@ struct IDCT1DImpl<2, SZ> {
JXL_DASSERT(to_stride >= SZ);
auto in1 = LoadU(FV<SZ>(), from);
auto in2 = LoadU(FV<SZ>(), from + from_stride);
- StoreU(in1 + in2, FV<SZ>(), to);
- StoreU(in1 - in2, FV<SZ>(), to + to_stride);
+ StoreU(Add(in1, in2), FV<SZ>(), to);
+ StoreU(Sub(in1, in2), FV<SZ>(), to + to_stride);
}
};
diff --git a/media/libjxl/src/lib/jxl/dec_ans.cc b/media/libjxl/src/lib/jxl/dec_ans.cc
index a64493237e..c9145472e0 100644
--- a/media/libjxl/src/lib/jxl/dec_ans.cc
+++ b/media/libjxl/src/lib/jxl/dec_ans.cc
@@ -48,7 +48,7 @@ inline int DecodeVarLenUint16(BitReader* input) {
return 0;
}
-Status ReadHistogram(int precision_bits, std::vector<int>* counts,
+Status ReadHistogram(int precision_bits, std::vector<int32_t>* counts,
BitReader* input) {
int simple_code = input->ReadBits(1);
if (simple_code == 1) {
@@ -228,7 +228,7 @@ Status DecodeANSCodes(const size_t num_histograms,
AliasTable::Entry* alias_tables =
reinterpret_cast<AliasTable::Entry*>(result->alias_tables.get());
for (size_t c = 0; c < num_histograms; ++c) {
- std::vector<int> counts;
+ std::vector<int32_t> counts;
if (!ReadHistogram(ANS_LOG_TAB_SIZE, &counts, in)) {
return JXL_FAILURE("Invalid histogram bitstream.");
}
diff --git a/media/libjxl/src/lib/jxl/dec_cache.cc b/media/libjxl/src/lib/jxl/dec_cache.cc
index eb11297014..b819b5104a 100644
--- a/media/libjxl/src/lib/jxl/dec_cache.cc
+++ b/media/libjxl/src/lib/jxl/dec_cache.cc
@@ -9,11 +9,14 @@
#include "lib/jxl/render_pipeline/stage_blending.h"
#include "lib/jxl/render_pipeline/stage_chroma_upsampling.h"
#include "lib/jxl/render_pipeline/stage_epf.h"
+#include "lib/jxl/render_pipeline/stage_from_linear.h"
#include "lib/jxl/render_pipeline/stage_gaborish.h"
#include "lib/jxl/render_pipeline/stage_noise.h"
#include "lib/jxl/render_pipeline/stage_patches.h"
#include "lib/jxl/render_pipeline/stage_splines.h"
#include "lib/jxl/render_pipeline/stage_spot.h"
+#include "lib/jxl/render_pipeline/stage_to_linear.h"
+#include "lib/jxl/render_pipeline/stage_tone_mapping.h"
#include "lib/jxl/render_pipeline/stage_upsampling.h"
#include "lib/jxl/render_pipeline/stage_write.h"
#include "lib/jxl/render_pipeline/stage_xyb.h"
@@ -89,7 +92,9 @@ Status PassesDecoderState::PreparePipeline(ImageBundle* decoded,
}
if ((frame_header.flags & FrameHeader::kPatches) != 0) {
- builder.AddStage(GetPatchesStage(&shared->image_features.patches));
+ builder.AddStage(
+ GetPatchesStage(&shared->image_features.patches,
+ 3 + shared->metadata->m.num_extra_channels));
}
if ((frame_header.flags & FrameHeader::kSplines) != 0) {
builder.AddStage(GetSplineStage(&shared->image_features.splines));
@@ -150,19 +155,29 @@ Status PassesDecoderState::PreparePipeline(ImageBundle* decoded,
height, rgb_output_is_rgba,
has_alpha, alpha_c));
} else {
+ bool linear = false;
if (frame_header.color_transform == ColorTransform::kYCbCr) {
builder.AddStage(GetYCbCrStage());
} else if (frame_header.color_transform == ColorTransform::kXYB) {
- builder.AddStage(GetXYBStage(output_encoding_info));
+ builder.AddStage(GetXYBStage(output_encoding_info.opsin_params));
+ linear = true;
} // Nothing to do for kNone.
if (options.coalescing && NeedsBlending(this)) {
+ if (linear) {
+ builder.AddStage(GetFromLinearStage(output_encoding_info));
+ linear = false;
+ }
builder.AddStage(
GetBlendingStage(this, output_encoding_info.color_encoding));
}
if (options.coalescing && frame_header.CanBeReferenced() &&
!frame_header.save_before_color_transform) {
+ if (linear) {
+ builder.AddStage(GetFromLinearStage(output_encoding_info));
+ linear = false;
+ }
builder.AddStage(GetWriteToImageBundleStage(
&frame_storage_for_referencing, output_encoding_info.color_encoding));
}
@@ -180,10 +195,30 @@ Status PassesDecoderState::PreparePipeline(ImageBundle* decoded,
}
}
+ auto tone_mapping_stage = GetToneMappingStage(output_encoding_info);
+ if (tone_mapping_stage) {
+ if (!linear) {
+ auto to_linear_stage = GetToLinearStage(output_encoding_info);
+ if (!to_linear_stage) {
+ return JXL_FAILURE(
+ "attempting to perform tone mapping on colorspace not "
+ "convertible to linear");
+ }
+ builder.AddStage(std::move(to_linear_stage));
+ linear = true;
+ }
+ builder.AddStage(std::move(tone_mapping_stage));
+ }
+
+ if (linear) {
+ builder.AddStage(GetFromLinearStage(output_encoding_info));
+ linear = false;
+ }
+
if (pixel_callback.IsPresent()) {
- builder.AddStage(GetWriteToPixelCallbackStage(pixel_callback, width,
- height, rgb_output_is_rgba,
- has_alpha, alpha_c));
+ builder.AddStage(GetWriteToPixelCallbackStage(
+ pixel_callback, width, height, rgb_output_is_rgba, has_alpha,
+ unpremul_alpha, alpha_c));
} else if (rgb_output) {
builder.AddStage(GetWriteToU8Stage(rgb_output, rgb_stride, height,
rgb_output_is_rgba, has_alpha,
diff --git a/media/libjxl/src/lib/jxl/dec_cache.h b/media/libjxl/src/lib/jxl/dec_cache.h
index 7f1b15315b..7105ba8ba8 100644
--- a/media/libjxl/src/lib/jxl/dec_cache.h
+++ b/media/libjxl/src/lib/jxl/dec_cache.h
@@ -8,6 +8,7 @@
#include <stdint.h>
+#include <atomic>
#include <hwy/base.h> // HWY_ALIGN_MAX
#include "jxl/decode.h"
@@ -89,6 +90,9 @@ struct PassesDecoderState {
// If true, rgb_output or callback output is RGBA using 4 instead of 3 bytes
// per pixel.
bool rgb_output_is_rgba;
+ // If true, the RGBA output will be unpremultiplied before writing to the
+ // output callback (the output buffer case is handled in ConvertToExternal).
+ bool unpremul_alpha;
// Callback for line-by-line output.
PixelCallback pixel_callback;
@@ -134,7 +138,9 @@ struct PassesDecoderState {
rgb_output = nullptr;
rgb_output_is_rgba = false;
+ unpremul_alpha = false;
fast_xyb_srgb8_conversion = false;
+ pixel_callback = PixelCallback();
used_acs = 0;
upsampler8x = GetUpsamplingStage(shared->metadata->transform_data, 0, 3);
diff --git a/media/libjxl/src/lib/jxl/dec_context_map.cc b/media/libjxl/src/lib/jxl/dec_context_map.cc
index f7fc3d27a4..93c59f773b 100644
--- a/media/libjxl/src/lib/jxl/dec_context_map.cc
+++ b/media/libjxl/src/lib/jxl/dec_context_map.cc
@@ -37,8 +37,8 @@ void InverseMoveToFrontTransform(uint8_t* v, int v_len) {
}
}
-bool VerifyContextMap(const std::vector<uint8_t>& context_map,
- const size_t num_htrees) {
+Status VerifyContextMap(const std::vector<uint8_t>& context_map,
+ const size_t num_htrees) {
std::vector<bool> have_htree(num_htrees);
size_t num_found = 0;
for (const uint8_t htree : context_map) {
@@ -58,8 +58,8 @@ bool VerifyContextMap(const std::vector<uint8_t>& context_map,
} // namespace
-bool DecodeContextMap(std::vector<uint8_t>* context_map, size_t* num_htrees,
- BitReader* input) {
+Status DecodeContextMap(std::vector<uint8_t>* context_map, size_t* num_htrees,
+ BitReader* input) {
bool is_simple = input->ReadFixedBits<1>();
if (is_simple) {
int bits_per_entry = input->ReadFixedBits<2>();
diff --git a/media/libjxl/src/lib/jxl/dec_context_map.h b/media/libjxl/src/lib/jxl/dec_context_map.h
index 1db2317827..95b8a0ca92 100644
--- a/media/libjxl/src/lib/jxl/dec_context_map.h
+++ b/media/libjxl/src/lib/jxl/dec_context_map.h
@@ -22,8 +22,8 @@ constexpr size_t kMaxClusters = 256;
// context_map->size() must be the number of possible context ids.
// Sets *num_htrees to the number of different histogram ids in
// *context_map.
-bool DecodeContextMap(std::vector<uint8_t>* context_map, size_t* num_htrees,
- BitReader* input);
+Status DecodeContextMap(std::vector<uint8_t>* context_map, size_t* num_htrees,
+ BitReader* input);
} // namespace jxl
diff --git a/media/libjxl/src/lib/jxl/dec_external_image.cc b/media/libjxl/src/lib/jxl/dec_external_image.cc
index daead15768..abf3ed4337 100644
--- a/media/libjxl/src/lib/jxl/dec_external_image.cc
+++ b/media/libjxl/src/lib/jxl/dec_external_image.cc
@@ -32,6 +32,10 @@ HWY_BEFORE_NAMESPACE();
namespace jxl {
namespace HWY_NAMESPACE {
+// These templates are not found via ADL.
+using hwy::HWY_NAMESPACE::Clamp;
+using hwy::HWY_NAMESPACE::NearestInt;
+
// TODO(jon): check if this can be replaced by a FloatToU16 function
void FloatToU32(const float* in, uint32_t* out, size_t num, float mul,
size_t bits_per_sample) {
@@ -50,7 +54,7 @@ void FloatToU32(const float* in, uint32_t* out, size_t num, float mul,
auto v = Load(d, in + x);
// Clamp turns NaN to 'min'.
v = Clamp(v, Zero(d), one);
- auto i = NearestInt(v * scale);
+ auto i = NearestInt(Mul(v, scale));
Store(BitCast(du, i), du, out + x);
}
@@ -451,14 +455,15 @@ Status ConvertToExternal(const jxl::ImageBundle& ib, size_t bits_per_sample,
JxlEndianness endianness, size_t stride,
jxl::ThreadPool* pool, void* out_image,
size_t out_size, const PixelCallback& out_callback,
- jxl::Orientation undo_orientation) {
+ jxl::Orientation undo_orientation,
+ bool unpremul_alpha) {
bool want_alpha = num_channels == 2 || num_channels == 4;
size_t color_channels = num_channels <= 2 ? 1 : 3;
const Image3F* color = &ib.color();
// Undo premultiplied alpha.
Image3F unpremul;
- if (ib.AlphaIsPremultiplied() && ib.HasAlpha()) {
+ if (ib.AlphaIsPremultiplied() && ib.HasAlpha() && unpremul_alpha) {
unpremul = Image3F(color->xsize(), color->ysize());
CopyImageTo(*color, &unpremul);
for (size_t y = 0; y < unpremul.ysize(); y++) {
diff --git a/media/libjxl/src/lib/jxl/dec_external_image.h b/media/libjxl/src/lib/jxl/dec_external_image.h
index 134bfa6b65..9b3b8bf66a 100644
--- a/media/libjxl/src/lib/jxl/dec_external_image.h
+++ b/media/libjxl/src/lib/jxl/dec_external_image.h
@@ -38,7 +38,8 @@ Status ConvertToExternal(const jxl::ImageBundle& ib, size_t bits_per_sample,
JxlEndianness endianness, size_t stride_out,
jxl::ThreadPool* thread_pool, void* out_image,
size_t out_size, const PixelCallback& out_callback,
- jxl::Orientation undo_orientation);
+ jxl::Orientation undo_orientation,
+ bool unpremul_alpha = false);
// Converts single-channel image to interleaved void* pixel buffer with the
// given format, with a single channel.
diff --git a/media/libjxl/src/lib/jxl/dec_file.cc b/media/libjxl/src/lib/jxl/dec_file.cc
deleted file mode 100644
index 4ff1a7d797..0000000000
--- a/media/libjxl/src/lib/jxl/dec_file.cc
+++ /dev/null
@@ -1,177 +0,0 @@
-// Copyright (c) the JPEG XL Project Authors. All rights reserved.
-//
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-#include "lib/jxl/dec_file.h"
-
-#include <stddef.h>
-
-#include <utility>
-#include <vector>
-
-#include "jxl/decode.h"
-#include "lib/jxl/base/compiler_specific.h"
-#include "lib/jxl/base/override.h"
-#include "lib/jxl/base/profiler.h"
-#include "lib/jxl/color_management.h"
-#include "lib/jxl/common.h"
-#include "lib/jxl/dec_bit_reader.h"
-#include "lib/jxl/dec_frame.h"
-#include "lib/jxl/frame_header.h"
-#include "lib/jxl/headers.h"
-#include "lib/jxl/icc_codec.h"
-#include "lib/jxl/image_bundle.h"
-#include "lib/jxl/jpeg/dec_jpeg_data_writer.h"
-
-namespace jxl {
-namespace {
-
-Status DecodeHeaders(BitReader* reader, CodecInOut* io) {
- JXL_RETURN_IF_ERROR(ReadSizeHeader(reader, &io->metadata.size));
-
- JXL_RETURN_IF_ERROR(ReadImageMetadata(reader, &io->metadata.m));
-
- io->metadata.transform_data.nonserialized_xyb_encoded =
- io->metadata.m.xyb_encoded;
- JXL_RETURN_IF_ERROR(Bundle::Read(reader, &io->metadata.transform_data));
-
- return true;
-}
-
-} // namespace
-
-// To avoid the complexity of file I/O and buffering, we assume the bitstream
-// is loaded (or for large images/sequences: mapped into) memory.
-Status DecodeFile(const DecompressParams& dparams,
- const Span<const uint8_t> file, CodecInOut* JXL_RESTRICT io,
- ThreadPool* pool) {
- PROFILER_ZONE("DecodeFile uninstrumented");
-
- // Marker
- JxlSignature signature = JxlSignatureCheck(file.data(), file.size());
- if (signature == JXL_SIG_NOT_ENOUGH_BYTES || signature == JXL_SIG_INVALID) {
- return JXL_FAILURE("File does not start with known JPEG XL signature");
- }
-
- std::unique_ptr<jpeg::JPEGData> jpeg_data = nullptr;
- if (dparams.keep_dct) {
- if (io->Main().jpeg_data == nullptr) {
- return JXL_FAILURE("Caller must set jpeg_data");
- }
- jpeg_data = std::move(io->Main().jpeg_data);
- }
-
- Status ret = true;
- {
- BitReader reader(file);
- BitReaderScopedCloser reader_closer(&reader, &ret);
- if (reader.ReadFixedBits<16>() != 0x0AFF) {
- // We don't have a naked codestream. Make a quick & dirty attempt to find
- // the codestream.
- // TODO(jon): get rid of this whole function
- const unsigned char* begin = file.data();
- const unsigned char* end = file.data() + file.size() - 4;
- while (begin < end) {
- if (!memcmp(begin, "jxlc", 4)) break;
- begin++;
- }
- if (begin >= end) return JXL_FAILURE("Couldn't find jxl codestream");
- reader.SkipBits(8 * (begin - file.data() + 2));
- unsigned int firstbytes = reader.ReadFixedBits<16>();
- if (firstbytes != 0x0AFF)
- return JXL_FAILURE("Codestream didn't start with FF0A but with %X",
- firstbytes);
- }
-
- {
- JXL_RETURN_IF_ERROR(DecodeHeaders(&reader, io));
- size_t xsize = io->metadata.xsize();
- size_t ysize = io->metadata.ysize();
- JXL_RETURN_IF_ERROR(VerifyDimensions(&io->constraints, xsize, ysize));
- }
-
- if (io->metadata.m.color_encoding.WantICC()) {
- PaddedBytes icc;
- JXL_RETURN_IF_ERROR(ReadICC(&reader, &icc));
- JXL_RETURN_IF_ERROR(io->metadata.m.color_encoding.SetICC(std::move(icc)));
- }
- // Set ICC profile in jpeg_data.
- if (jpeg_data) {
- Status res = jpeg::SetJPEGDataFromICC(io->metadata.m.color_encoding.ICC(),
- jpeg_data.get());
- if (!res) {
- return res;
- }
- }
-
- PassesDecoderState dec_state;
- JXL_RETURN_IF_ERROR(dec_state.output_encoding_info.Set(
- io->metadata,
- ColorEncoding::LinearSRGB(io->metadata.m.color_encoding.IsGray())));
-
- if (io->metadata.m.have_preview) {
- JXL_RETURN_IF_ERROR(reader.JumpToByteBoundary());
- JXL_RETURN_IF_ERROR(DecodeFrame(dparams, &dec_state, pool, &reader,
- &io->preview_frame, io->metadata,
- &io->constraints, /*is_preview=*/true));
- }
-
- // Only necessary if no ICC and no preview.
- JXL_RETURN_IF_ERROR(reader.JumpToByteBoundary());
- if (io->metadata.m.have_animation && dparams.keep_dct) {
- return JXL_FAILURE("Cannot decode to JPEG an animation");
- }
-
- io->frames.clear();
- Status dec_ok(false);
- do {
- io->frames.emplace_back(&io->metadata.m);
- if (jpeg_data) {
- io->frames.back().jpeg_data = std::move(jpeg_data);
- }
- // Skip frames that are not displayed.
- bool found_displayed_frame = true;
- do {
- dec_ok =
- DecodeFrame(dparams, &dec_state, pool, &reader, &io->frames.back(),
- io->metadata, &io->constraints);
- if (!dparams.allow_partial_files) {
- JXL_RETURN_IF_ERROR(dec_ok);
- } else if (!dec_ok) {
- io->frames.pop_back();
- found_displayed_frame = false;
- break;
- }
- } while (dec_state.shared->frame_header.frame_type !=
- FrameType::kRegularFrame &&
- dec_state.shared->frame_header.frame_type !=
- FrameType::kSkipProgressive);
- if (found_displayed_frame) {
- // if found_displayed_frame is true io->frames shouldn't be empty
- // because we added a frame before the loop.
- JXL_ASSERT(!io->frames.empty());
- io->dec_pixels += io->frames.back().xsize() * io->frames.back().ysize();
- }
- } while (!dec_state.shared->frame_header.is_last && dec_ok);
-
- if (io->frames.empty()) return JXL_FAILURE("Not enough data.");
-
- if (dparams.check_decompressed_size && !dparams.allow_partial_files &&
- dparams.max_downsampling == 1) {
- if (reader.TotalBitsConsumed() != file.size() * kBitsPerByte) {
- return JXL_FAILURE("DecodeFile reader position not at EOF.");
- }
- }
- // Suppress errors when decoding partial files with DC frames.
- if (!reader.AllReadsWithinBounds() && dparams.allow_partial_files) {
- reader_closer.CloseAndSuppressError();
- }
-
- io->CheckMetadata();
- // reader is closed here.
- }
- return ret;
-}
-
-} // namespace jxl
diff --git a/media/libjxl/src/lib/jxl/dec_file.h b/media/libjxl/src/lib/jxl/dec_file.h
deleted file mode 100644
index cd04d5d4c7..0000000000
--- a/media/libjxl/src/lib/jxl/dec_file.h
+++ /dev/null
@@ -1,48 +0,0 @@
-// Copyright (c) the JPEG XL Project Authors. All rights reserved.
-//
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-#ifndef LIB_JXL_DEC_FILE_H_
-#define LIB_JXL_DEC_FILE_H_
-
-// Top-level interface for JXL decoding.
-
-#include <stdint.h>
-
-#include "lib/jxl/base/data_parallel.h"
-#include "lib/jxl/base/padded_bytes.h"
-#include "lib/jxl/base/span.h"
-#include "lib/jxl/base/status.h"
-#include "lib/jxl/codec_in_out.h"
-#include "lib/jxl/dec_params.h"
-
-namespace jxl {
-
-// Decodes the preview image, if present, and stores it in `preview`.
-// Must be the first frame in the file. Does nothing if there is no preview
-// frame present according to the metadata.
-Status DecodePreview(const DecompressParams& dparams,
- const CodecMetadata& metadata,
- BitReader* JXL_RESTRICT reader, ThreadPool* pool,
- ImageBundle* JXL_RESTRICT preview, uint64_t* dec_pixels,
- const SizeConstraints* constraints);
-
-// Implementation detail: currently decodes to linear sRGB. The contract is:
-// `io` appears 'identical' (modulo compression artifacts) to the encoder input
-// in a color-aware viewer. Note that `io->metadata.m.color_encoding`
-// identifies the color space that was passed to the encoder; clients that want
-// that same encoding must call `io->TransformTo` afterwards.
-Status DecodeFile(const DecompressParams& params,
- const Span<const uint8_t> file, CodecInOut* io,
- ThreadPool* pool = nullptr);
-
-static inline Status DecodeFile(const DecompressParams& params,
- const PaddedBytes& file, CodecInOut* io,
- ThreadPool* pool = nullptr) {
- return DecodeFile(params, Span<const uint8_t>(file), io, pool);
-}
-
-} // namespace jxl
-
-#endif // LIB_JXL_DEC_FILE_H_
diff --git a/media/libjxl/src/lib/jxl/dec_frame.cc b/media/libjxl/src/lib/jxl/dec_frame.cc
index d5509aa79c..c90bb4720a 100644
--- a/media/libjxl/src/lib/jxl/dec_frame.cc
+++ b/media/libjxl/src/lib/jxl/dec_frame.cc
@@ -15,6 +15,7 @@
#include <utility>
#include <vector>
+#include "jxl/types.h"
#include "lib/jxl/ac_context.h"
#include "lib/jxl/ac_strategy.h"
#include "lib/jxl/ans_params.h"
@@ -35,7 +36,6 @@
#include "lib/jxl/dec_cache.h"
#include "lib/jxl/dec_group.h"
#include "lib/jxl/dec_modular.h"
-#include "lib/jxl/dec_params.h"
#include "lib/jxl/dec_patch_dictionary.h"
#include "lib/jxl/dec_xyb.h"
#include "lib/jxl/epf.h"
@@ -77,186 +77,72 @@ Status DecodeGlobalDCInfo(BitReader* reader, bool is_jpeg,
}
} // namespace
-Status DecodeFrameHeader(BitReader* JXL_RESTRICT reader,
- FrameHeader* JXL_RESTRICT frame_header) {
- JXL_ASSERT(frame_header->nonserialized_metadata != nullptr);
- JXL_RETURN_IF_ERROR(ReadFrameHeader(reader, frame_header));
- return true;
-}
-
-static BitReader* GetReaderForSection(
- size_t num_groups, size_t num_passes, size_t group_codes_begin,
- const std::vector<uint64_t>& group_offsets,
- const std::vector<uint32_t>& group_sizes, BitReader* JXL_RESTRICT reader,
- BitReader* JXL_RESTRICT store, size_t index) {
- if (num_groups == 1 && num_passes == 1) return reader;
- const size_t group_offset = group_codes_begin + group_offsets[index];
- const size_t next_group_offset =
- group_codes_begin + group_offsets[index] + group_sizes[index];
- // The order of these variables must be:
- // group_codes_begin <= group_offset <= next_group_offset <= file.size()
- JXL_DASSERT(group_codes_begin <= group_offset);
- JXL_DASSERT(group_offset <= next_group_offset);
- JXL_DASSERT(next_group_offset <= reader->TotalBytes());
- const size_t group_size = next_group_offset - group_offset;
- const size_t remaining_size = reader->TotalBytes() - group_offset;
- const size_t size = std::min(group_size + 8, remaining_size);
- *store =
- BitReader(Span<const uint8_t>(reader->FirstByte() + group_offset, size));
- return store;
-}
-
-Status DecodeFrame(const DecompressParams& dparams,
- PassesDecoderState* dec_state, ThreadPool* JXL_RESTRICT pool,
- BitReader* JXL_RESTRICT reader, ImageBundle* decoded,
- const CodecMetadata& metadata,
- const SizeConstraints* constraints, bool is_preview) {
- PROFILER_ZONE("DecodeFrame uninstrumented");
-
+Status DecodeFrame(PassesDecoderState* dec_state, ThreadPool* JXL_RESTRICT pool,
+ const uint8_t* next_in, size_t avail_in,
+ ImageBundle* decoded, const CodecMetadata& metadata,
+ bool use_slow_rendering_pipeline) {
FrameDecoder frame_decoder(dec_state, metadata, pool,
- dparams.use_slow_render_pipeline);
-
- frame_decoder.SetFrameSizeLimits(constraints);
-
- JXL_RETURN_IF_ERROR(frame_decoder.InitFrame(
- reader, decoded, is_preview, dparams.allow_partial_files,
- dparams.allow_partial_files && dparams.allow_more_progressive_steps,
- true));
-
- // Handling of progressive decoding.
- const FrameHeader& frame_header = frame_decoder.GetFrameHeader();
- {
- size_t max_passes = dparams.max_passes;
- size_t max_downsampling = std::max(
- dparams.max_downsampling >> (frame_header.dc_level * 3), size_t(1));
- // TODO(veluca): deal with downsamplings >= 8.
- if (max_downsampling >= 8) {
- max_passes = 0;
- } else {
- for (uint32_t i = 0; i < frame_header.passes.num_downsample; ++i) {
- if (max_downsampling >= frame_header.passes.downsample[i] &&
- max_passes > frame_header.passes.last_pass[i]) {
- max_passes = frame_header.passes.last_pass[i] + 1;
- }
- }
- }
- // Do not use downsampling for kReferenceOnly frames.
- if (frame_header.frame_type == FrameType::kReferenceOnly) {
- max_passes = frame_header.passes.num_passes;
- }
- max_passes = std::min<size_t>(max_passes, frame_header.passes.num_passes);
- frame_decoder.SetMaxPasses(max_passes);
- }
- frame_decoder.SetRenderSpotcolors(dparams.render_spotcolors);
- frame_decoder.SetCoalescing(dparams.coalescing);
+ use_slow_rendering_pipeline);
- size_t processed_bytes = reader->TotalBitsConsumed() / kBitsPerByte;
+ BitReader reader(Span<const uint8_t>(next_in, avail_in));
+ JXL_RETURN_IF_ERROR(frame_decoder.InitFrame(&reader, decoded,
+ /*is_preview=*/false,
+ /*output_needed=*/true));
+ JXL_RETURN_IF_ERROR(reader.AllReadsWithinBounds());
+ size_t header_bytes = reader.TotalBitsConsumed() / kBitsPerByte;
+ JXL_RETURN_IF_ERROR(reader.Close());
+ size_t processed_bytes = header_bytes;
Status close_ok = true;
std::vector<std::unique_ptr<BitReader>> section_readers;
{
std::vector<std::unique_ptr<BitReaderScopedCloser>> section_closers;
std::vector<FrameDecoder::SectionInfo> section_info;
std::vector<FrameDecoder::SectionStatus> section_status;
- size_t bytes_to_skip = 0;
- for (size_t i = 0; i < frame_decoder.NumSections(); i++) {
- size_t b = frame_decoder.SectionOffsets()[i];
- size_t e = b + frame_decoder.SectionSizes()[i];
- bytes_to_skip += e - b;
- size_t pos = reader->TotalBitsConsumed() / kBitsPerByte;
- if (pos + (dparams.allow_more_progressive_steps &&
- (i == 0 ||
- frame_header.encoding == FrameEncoding::kModular)
- ? b
- : e) <=
- reader->TotalBytes() ||
- (i == 0 && dparams.allow_more_progressive_steps)) {
- auto br = make_unique<BitReader>(Span<const uint8_t>(
- reader->FirstByte() + b + pos,
- (pos + b > reader->TotalBytes()
- ? 0
- : std::min(reader->TotalBytes() - pos - b, e - b))));
- section_info.emplace_back(FrameDecoder::SectionInfo{br.get(), i});
- section_closers.emplace_back(
- make_unique<BitReaderScopedCloser>(br.get(), &close_ok));
- section_readers.emplace_back(std::move(br));
- } else if (!dparams.allow_partial_files) {
- return JXL_FAILURE("Premature end of stream.");
- }
+ size_t pos = header_bytes;
+ for (auto toc_entry : frame_decoder.Toc()) {
+ JXL_RETURN_IF_ERROR(pos + toc_entry.size <= avail_in);
+ auto br = make_unique<BitReader>(
+ Span<const uint8_t>(next_in + pos, toc_entry.size));
+ section_info.emplace_back(
+ FrameDecoder::SectionInfo{br.get(), toc_entry.id});
+ section_closers.emplace_back(
+ make_unique<BitReaderScopedCloser>(br.get(), &close_ok));
+ section_readers.emplace_back(std::move(br));
+ pos += toc_entry.size;
}
- // Skip over the to-be-decoded sections.
- reader->SkipBits(kBitsPerByte * bytes_to_skip);
section_status.resize(section_info.size());
-
JXL_RETURN_IF_ERROR(frame_decoder.ProcessSections(
section_info.data(), section_info.size(), section_status.data()));
-
for (size_t i = 0; i < section_status.size(); i++) {
- auto s = section_status[i];
- if (s == FrameDecoder::kDone) {
- processed_bytes += frame_decoder.SectionSizes()[i];
- continue;
- }
- if (dparams.allow_more_progressive_steps && s == FrameDecoder::kPartial) {
- continue;
- }
- if (dparams.max_downsampling > 1 && s == FrameDecoder::kSkipped) {
- continue;
- }
- return JXL_FAILURE("Invalid section %" PRIuS " status: %d",
- section_info[i].id, s);
+ JXL_RETURN_IF_ERROR(section_status[i] == FrameDecoder::kDone);
+ processed_bytes += frame_decoder.Toc()[i].size;
}
}
-
JXL_RETURN_IF_ERROR(close_ok);
-
JXL_RETURN_IF_ERROR(frame_decoder.FinalizeFrame());
decoded->SetDecodedBytes(processed_bytes);
return true;
}
Status FrameDecoder::InitFrame(BitReader* JXL_RESTRICT br, ImageBundle* decoded,
- bool is_preview, bool allow_partial_frames,
- bool allow_partial_dc_global,
- bool output_needed) {
+ bool is_preview, bool output_needed) {
PROFILER_FUNC;
decoded_ = decoded;
JXL_ASSERT(is_finalized_);
- allow_partial_frames_ = allow_partial_frames;
- allow_partial_dc_global_ = allow_partial_dc_global;
-
// Reset the dequantization matrices to their default values.
dec_state_->shared_storage.matrices = DequantMatrices();
frame_header_.nonserialized_is_preview = is_preview;
- size_t pos = br->TotalBitsConsumed() / kBitsPerByte;
- Status have_frameheader =
- br->TotalBytes() > pos && DecodeFrameHeader(br, &frame_header_);
- JXL_RETURN_IF_ERROR(have_frameheader || allow_partial_frames);
- if (!have_frameheader) {
- if (dec_state_->shared_storage.dc_frames[0].xsize() > 0) {
- // If we have a (partial) DC frame available, but we don't have the next
- // frame header (so allow_partial_frames is true), then we'll assume the
- // next frame uses that DC frame (which may not be true, e.g. there might
- // first be a ReferenceOnly patch frame, but it's reasonable to assume
- // that the DC frame is a good progressive preview)
- frame_header_.flags |= FrameHeader::kUseDcFrame;
- frame_header_.encoding = FrameEncoding::kVarDCT;
- frame_header_.dc_level = 0;
- } else
- return JXL_FAILURE("Couldn't read frame header");
- }
+ JXL_ASSERT(frame_header_.nonserialized_metadata != nullptr);
+ JXL_RETURN_IF_ERROR(ReadFrameHeader(br, &frame_header_));
frame_dim_ = frame_header_.ToFrameDimensions();
+ JXL_DEBUG_V(2, "FrameHeader: %s", frame_header_.DebugString().c_str());
const size_t num_passes = frame_header_.passes.num_passes;
- const size_t xsize = frame_dim_.xsize;
- const size_t ysize = frame_dim_.ysize;
const size_t num_groups = frame_dim_.num_groups;
- // Check validity of frame dimensions.
- JXL_RETURN_IF_ERROR(VerifyDimensions(constraints_, xsize, ysize));
-
// If the previous frame was not a kRegularFrame, `decoded` may have different
// dimensions; must reset to avoid errors.
decoded->RemoveColor();
@@ -275,20 +161,31 @@ Status FrameDecoder::InitFrame(BitReader* JXL_RESTRICT br, ImageBundle* decoded,
}
// Read TOC.
- uint64_t groups_total_size;
const bool has_ac_global = true;
const size_t toc_entries = NumTocEntries(num_groups, frame_dim_.num_dc_groups,
num_passes, has_ac_global);
- JXL_RETURN_IF_ERROR(ReadGroupOffsets(toc_entries, br, &section_offsets_,
- &section_sizes_, &groups_total_size) ||
- allow_partial_frames);
+ std::vector<uint32_t> sizes;
+ std::vector<coeff_order_t> permutation;
+ JXL_RETURN_IF_ERROR(ReadToc(toc_entries, br, &sizes, &permutation));
+ bool have_permutation = !permutation.empty();
+ toc_.resize(toc_entries);
+ section_sizes_sum_ = 0;
+ for (size_t i = 0; i < toc_entries; ++i) {
+ toc_[i].size = sizes[i];
+ size_t index = have_permutation ? permutation[i] : i;
+ toc_[index].id = i;
+ if (section_sizes_sum_ + toc_[i].size < section_sizes_sum_) {
+ return JXL_FAILURE("group offset overflow");
+ }
+ section_sizes_sum_ += toc_[i].size;
+ }
JXL_DASSERT((br->TotalBitsConsumed() % kBitsPerByte) == 0);
const size_t group_codes_begin = br->TotalBitsConsumed() / kBitsPerByte;
- JXL_DASSERT(!section_offsets_.empty());
+ JXL_DASSERT(!toc_.empty());
// Overflow check.
- if (group_codes_begin + groups_total_size < group_codes_begin) {
+ if (group_codes_begin + section_sizes_sum_ < group_codes_begin) {
return JXL_FAILURE("Invalid group codes");
}
@@ -347,9 +244,7 @@ Status FrameDecoder::InitFrame(BitReader* JXL_RESTRICT br, ImageBundle* decoded,
decoded_passes_per_ac_group_.clear();
decoded_passes_per_ac_group_.resize(frame_dim_.num_groups, 0);
processed_section_.clear();
- processed_section_.resize(section_offsets_.size());
- max_passes_ = frame_header_.passes.num_passes;
- num_renders_ = 0;
+ processed_section_.resize(toc_.size());
allocated_ = false;
return true;
}
@@ -382,14 +277,11 @@ Status FrameDecoder::ProcessDCGlobal(BitReader* br) {
if (shared.frame_header.flags & FrameHeader::kNoise) {
JXL_RETURN_IF_ERROR(DecodeNoise(br, &shared.image_features.noise_params));
}
- if (!allow_partial_dc_global_ ||
- br->TotalBitsConsumed() < br->TotalBytes() * kBitsPerByte) {
- JXL_RETURN_IF_ERROR(dec_state_->shared_storage.matrices.DecodeDC(br));
+ JXL_RETURN_IF_ERROR(dec_state_->shared_storage.matrices.DecodeDC(br));
- if (frame_header_.encoding == FrameEncoding::kVarDCT) {
- JXL_RETURN_IF_ERROR(
- jxl::DecodeGlobalDCInfo(br, decoded_->IsJPEG(), dec_state_, pool_));
- }
+ if (frame_header_.encoding == FrameEncoding::kVarDCT) {
+ JXL_RETURN_IF_ERROR(
+ jxl::DecodeGlobalDCInfo(br, decoded_->IsJPEG(), dec_state_, pool_));
}
// Splines' draw cache uses the color correlation map.
if (shared.frame_header.flags & FrameHeader::kSplines) {
@@ -398,7 +290,7 @@ Status FrameDecoder::ProcessDCGlobal(BitReader* br) {
dec_state_->shared->cmap));
}
Status dec_status = modular_frame_decoder_.DecodeGlobalInfo(
- br, frame_header_, allow_partial_dc_global_);
+ br, frame_header_, /*allow_truncated_group=*/false);
if (dec_status.IsFatalError()) return dec_status;
if (dec_status) {
decoded_dc_global_ = true;
@@ -420,7 +312,8 @@ Status FrameDecoder::ProcessDCGroup(size_t dc_group_id, BitReader* br) {
frame_dim_.dc_group_dim, frame_dim_.dc_group_dim);
JXL_RETURN_IF_ERROR(modular_frame_decoder_.DecodeGroup(
mrect, br, 3, 1000, ModularStreamId::ModularDC(dc_group_id),
- /*zerofill=*/false, nullptr, nullptr, nullptr, allow_partial_frames_));
+ /*zerofill=*/false, nullptr, nullptr,
+ /*allow_truncated=*/false));
if (frame_header_.encoding == FrameEncoding::kVarDCT) {
JXL_RETURN_IF_ERROR(
modular_frame_decoder_.DecodeAcMetadata(dc_group_id, br, dec_state_));
@@ -562,6 +455,11 @@ Status FrameDecoder::ProcessACGroup(size_t ac_group_id,
const size_t gy = ac_group_id / frame_dim_.xsize_groups;
const size_t x = gx * group_dim;
const size_t y = gy * group_dim;
+ JXL_DEBUG_V(3,
+ "Processing AC group %" PRIuS "(%" PRIuS ",%" PRIuS
+ ") group_dim: %" PRIuS " decoded passes: %u new passes: %" PRIuS,
+ ac_group_id, gx, gy, group_dim,
+ decoded_passes_per_ac_group_[ac_group_id], num_passes);
RenderPipelineInput render_pipeline_input =
dec_state_->render_pipeline->GetInputBuffers(ac_group_id, thread);
@@ -580,23 +478,28 @@ Status FrameDecoder::ProcessACGroup(size_t ac_group_id,
// don't limit to image dimensions here (is done in DecodeGroup)
const Rect mrect(x, y, group_dim, group_dim);
- for (size_t i = 0; i < frame_header_.passes.num_passes; i++) {
+ bool modular_ready = false;
+ size_t pass0 = decoded_passes_per_ac_group_[ac_group_id];
+ size_t pass1 =
+ force_draw ? frame_header_.passes.num_passes : pass0 + num_passes;
+ for (size_t i = pass0; i < pass1; ++i) {
int minShift, maxShift;
frame_header_.passes.GetDownsamplingBracket(i, minShift, maxShift);
- if (i >= decoded_passes_per_ac_group_[ac_group_id] &&
- i < decoded_passes_per_ac_group_[ac_group_id] + num_passes) {
+ bool modular_pass_ready = true;
+ if (i < pass0 + num_passes) {
JXL_RETURN_IF_ERROR(modular_frame_decoder_.DecodeGroup(
- mrect, br[i - decoded_passes_per_ac_group_[ac_group_id]], minShift,
- maxShift, ModularStreamId::ModularAC(ac_group_id, i),
- /*zerofill=*/false, dec_state_, &render_pipeline_input, decoded_,
- allow_partial_frames_));
- } else if (i >= decoded_passes_per_ac_group_[ac_group_id] + num_passes &&
- force_draw) {
+ mrect, br[i - pass0], minShift, maxShift,
+ ModularStreamId::ModularAC(ac_group_id, i),
+ /*zerofill=*/false, dec_state_, &render_pipeline_input,
+ /*allow_truncated=*/false, &modular_pass_ready));
+ } else {
JXL_RETURN_IF_ERROR(modular_frame_decoder_.DecodeGroup(
mrect, nullptr, minShift, maxShift,
ModularStreamId::ModularAC(ac_group_id, i), /*zerofill=*/true,
- dec_state_, &render_pipeline_input, decoded_, allow_partial_frames_));
+ dec_state_, &render_pipeline_input,
+ /*allow_truncated=*/false, &modular_pass_ready));
}
+ if (modular_pass_ready) modular_ready = true;
}
decoded_passes_per_ac_group_[ac_group_id] += num_passes;
@@ -627,19 +530,21 @@ Status FrameDecoder::ProcessACGroup(size_t ac_group_id,
}
}
- if (!modular_frame_decoder_.UsesFullImage() && !decoded_->IsJPEG() &&
- should_run_pipeline) {
- render_pipeline_input.Done();
+ if (!modular_frame_decoder_.UsesFullImage() && !decoded_->IsJPEG()) {
+ if (should_run_pipeline && modular_ready) {
+ render_pipeline_input.Done();
+ } else if (force_draw) {
+ return JXL_FAILURE("Modular group decoding failed.");
+ }
}
return true;
}
void FrameDecoder::MarkSections(const SectionInfo* sections, size_t num,
SectionStatus* section_status) {
- num_sections_done_ = num;
+ num_sections_done_ += num;
for (size_t i = 0; i < num; i++) {
- if (section_status[i] == SectionStatus::kSkipped ||
- section_status[i] == SectionStatus::kPartial) {
+ if (section_status[i] != SectionStatus::kDone) {
processed_section_[sections[i].id] = false;
num_sections_done_--;
}
@@ -656,7 +561,9 @@ Status FrameDecoder::ProcessSections(const SectionInfo* sections, size_t num,
std::vector<std::vector<size_t>> ac_group_sec(
frame_dim_.num_groups,
std::vector<size_t>(frame_header_.passes.num_passes, num));
- std::vector<size_t> num_ac_passes(frame_dim_.num_groups);
+ // This keeps track of the number of ac passes we want to process during this
+ // call of ProcessSections.
+ std::vector<size_t> desired_num_ac_passes(frame_dim_.num_groups);
bool single_section =
frame_dim_.num_groups == 1 && frame_header_.passes.num_passes == 1;
if (single_section) {
@@ -666,7 +573,7 @@ Status FrameDecoder::ProcessSections(const SectionInfo* sections, size_t num,
processed_section_[0] = true;
ac_group_sec[0].resize(1);
dc_global_sec = ac_global_sec = dc_group_sec[0] = ac_group_sec[0][0] = 0;
- num_ac_passes[0] = 1;
+ desired_num_ac_passes[0] = 1;
} else {
section_status[0] = SectionStatus::kDuplicate;
}
@@ -691,9 +598,6 @@ Status FrameDecoder::ProcessSections(const SectionInfo* sections, size_t num,
if (acp >= frame_header_.passes.num_passes) {
return JXL_FAILURE("Invalid section ID");
}
- if (acp >= max_passes_) {
- continue;
- }
ac_group_sec[acg][acp] = i;
}
processed_section_[sections[i].id] = true;
@@ -701,12 +605,14 @@ Status FrameDecoder::ProcessSections(const SectionInfo* sections, size_t num,
// Count number of new passes per group.
for (size_t g = 0; g < ac_group_sec.size(); g++) {
size_t j = 0;
- for (; j + decoded_passes_per_ac_group_[g] < max_passes_; j++) {
+ for (; j + decoded_passes_per_ac_group_[g] <
+ frame_header_.passes.num_passes;
+ j++) {
if (ac_group_sec[g][j + decoded_passes_per_ac_group_[g]] == num) {
break;
}
}
- num_ac_passes[g] = j;
+ desired_num_ac_passes[g] = j;
}
}
if (dc_global_sec != num) {
@@ -747,33 +653,9 @@ Status FrameDecoder::ProcessSections(const SectionInfo* sections, size_t num,
dec_state_->PreparePipeline(decoded_, pipeline_options));
FinalizeDC();
JXL_RETURN_IF_ERROR(AllocateOutput());
- if (pause_at_progressive_ && !single_section) {
- bool can_return_dc = true;
- if (single_section) {
- // If there's only one group and one pass, there is no separate section
- // for DC and the entire full resolution image is available at once.
- can_return_dc = false;
- }
- if (!decoded_->metadata()->extra_channel_info.empty()) {
- // If extra channels are encoded with modular without squeeze, they
- // don't support DC. If the are encoded with squeeze, DC works in theory
- // but the implementation may not yet correctly support this for Flush.
- // Therefore, can't correctly pause for a progressive step if there is
- // an extra channel (including alpha channel)
- can_return_dc = false;
- }
- if (frame_header_.encoding != FrameEncoding::kVarDCT) {
- // DC is not guaranteed to be available in modular mode and may be a
- // black image. If squeeze is used, it may be available depending on the
- // current implementation.
- // TODO(lode): do return DC if it's known that flushing at this point
- // will produce a valid 1/8th downscaled image with modular encoding.
- can_return_dc = false;
- }
- if (can_return_dc) {
- MarkSections(sections, num, section_status);
- return true;
- }
+ if (progressive_detail_ >= JxlProgressiveDetail::kDC) {
+ MarkSections(sections, num, section_status);
+ return true;
}
}
@@ -782,13 +664,22 @@ Status FrameDecoder::ProcessSections(const SectionInfo* sections, size_t num,
section_status[ac_global_sec] = SectionStatus::kDone;
}
+ if (progressive_detail_ >= JxlProgressiveDetail::kLastPasses) {
+ // Mark that we only want the next progression pass.
+ size_t target_complete_passes = NextNumPassesToPause();
+ for (size_t i = 0; i < ac_group_sec.size(); i++) {
+ desired_num_ac_passes[i] =
+ std::min(desired_num_ac_passes[i],
+ target_complete_passes - decoded_passes_per_ac_group_[i]);
+ }
+ }
+
if (decoded_ac_global_) {
// Mark all the AC groups that we received as not complete yet.
for (size_t i = 0; i < ac_group_sec.size(); i++) {
- if (num_ac_passes[i] == 0 && !modular_frame_decoder_.UsesFullImage()) {
- continue;
+ if (desired_num_ac_passes[i] != 0) {
+ dec_state_->render_pipeline->ClearDone(i);
}
- dec_state_->render_pipeline->ClearDone(i);
}
JXL_RETURN_IF_ERROR(RunOnPool(
@@ -797,24 +688,25 @@ Status FrameDecoder::ProcessSections(const SectionInfo* sections, size_t num,
return PrepareStorage(num_threads,
decoded_passes_per_ac_group_.size());
},
- [this, &ac_group_sec, &num_ac_passes, &num, &sections, &section_status,
- &has_error](size_t g, size_t thread) {
- if (num_ac_passes[g] == 0) { // no new AC pass, nothing to do.
+ [this, &ac_group_sec, &desired_num_ac_passes, &num, &sections,
+ &section_status, &has_error](size_t g, size_t thread) {
+ if (desired_num_ac_passes[g] == 0) {
+ // no new AC pass, nothing to do
return;
}
(void)num;
size_t first_pass = decoded_passes_per_ac_group_[g];
BitReader* JXL_RESTRICT readers[kMaxNumPasses];
- for (size_t i = 0; i < num_ac_passes[g]; i++) {
+ for (size_t i = 0; i < desired_num_ac_passes[g]; i++) {
JXL_ASSERT(ac_group_sec[g][first_pass + i] != num);
readers[i] = sections[ac_group_sec[g][first_pass + i]].br;
}
- if (!ProcessACGroup(g, readers, num_ac_passes[g],
+ if (!ProcessACGroup(g, readers, desired_num_ac_passes[g],
GetStorageLocation(thread, g),
/*force_draw=*/false, /*dc_only=*/false)) {
has_error = true;
} else {
- for (size_t i = 0; i < num_ac_passes[g]; i++) {
+ for (size_t i = 0; i < desired_num_ac_passes[g]; i++) {
section_status[ac_group_sec[g][first_pass + i]] =
SectionStatus::kDone;
}
@@ -857,9 +749,9 @@ Status FrameDecoder::Flush() {
// We don't have all AC yet: force a draw of all the missing areas.
// Mark all sections as not complete.
for (size_t i = 0; i < decoded_passes_per_ac_group_.size(); i++) {
- if (decoded_passes_per_ac_group_[i] == frame_header_.passes.num_passes)
- continue;
- dec_state_->render_pipeline->ClearDone(i);
+ if (decoded_passes_per_ac_group_[i] < frame_header_.passes.num_passes) {
+ dec_state_->render_pipeline->ClearDone(i);
+ }
}
std::atomic<bool> has_error{false};
JXL_RETURN_IF_ERROR(RunOnPool(
@@ -887,10 +779,9 @@ Status FrameDecoder::Flush() {
}
// undo global modular transforms and copy int pixel buffers to float ones
- JXL_RETURN_IF_ERROR(modular_frame_decoder_.FinalizeDecoding(
- dec_state_, pool_, decoded_, is_finalized_));
+ JXL_RETURN_IF_ERROR(modular_frame_decoder_.FinalizeDecoding(dec_state_, pool_,
+ is_finalized_));
- num_renders_++;
return true;
}
@@ -913,7 +804,7 @@ bool FrameDecoder::HasEverything() const {
if (!have_dc_group) return false;
}
for (auto& nb_passes : decoded_passes_per_ac_group_) {
- if (nb_passes < max_passes_) return false;
+ if (nb_passes < frame_header_.passes.num_passes) return false;
}
return true;
}
@@ -967,18 +858,9 @@ Status FrameDecoder::FinalizeFrame() {
return true;
}
if (!finalized_dc_) {
- // We don't have all of DC: EPF might not behave correctly (and is not
- // particularly useful anyway on upsampling results), so we disable it.
- dec_state_->shared_storage.frame_header.loop_filter.epf_iters = 0;
- }
- if (!HasEverything() && !allow_partial_frames_) {
- return JXL_FAILURE(
- "FinalizeFrame called before the frame was fully decoded");
- }
-
- if (!finalized_dc_) {
- JXL_ASSERT(allow_partial_frames_);
- JXL_RETURN_IF_ERROR(AllocateOutput());
+ // We don't have all of DC, and render pipeline is not created yet, so we
+ // can not call Flush() yet.
+ return JXL_FAILURE("FinalizeFrame called before DC was fully decoded");
}
JXL_RETURN_IF_ERROR(Flush());
diff --git a/media/libjxl/src/lib/jxl/dec_frame.h b/media/libjxl/src/lib/jxl/dec_frame.h
index ac52405336..62c61c0fa9 100644
--- a/media/libjxl/src/lib/jxl/dec_frame.h
+++ b/media/libjxl/src/lib/jxl/dec_frame.h
@@ -9,6 +9,7 @@
#include <stdint.h>
#include "jxl/decode.h"
+#include "jxl/types.h"
#include "lib/jxl/base/compiler_specific.h"
#include "lib/jxl/base/data_parallel.h"
#include "lib/jxl/base/span.h"
@@ -19,31 +20,20 @@
#include "lib/jxl/dec_bit_reader.h"
#include "lib/jxl/dec_cache.h"
#include "lib/jxl/dec_modular.h"
-#include "lib/jxl/dec_params.h"
#include "lib/jxl/frame_header.h"
#include "lib/jxl/headers.h"
#include "lib/jxl/image_bundle.h"
namespace jxl {
-// TODO(veluca): remove DecodeFrameHeader once the API migrates to FrameDecoder.
-
-// `frame_header` must have nonserialized_metadata and
-// nonserialized_is_preview set.
-Status DecodeFrameHeader(BitReader* JXL_RESTRICT reader,
- FrameHeader* JXL_RESTRICT frame_header);
-
// Decodes a frame. Groups may be processed in parallel by `pool`.
-// See DecodeFile for explanation of c_decoded.
-// `io` is only used for reading maximum image size. Also updates
-// `dec_state` with the new frame header.
// `metadata` is the metadata that applies to all frames of the codestream
// `decoded->metadata` must already be set and must match metadata.m.
-Status DecodeFrame(const DecompressParams& dparams,
- PassesDecoderState* dec_state, ThreadPool* JXL_RESTRICT pool,
- BitReader* JXL_RESTRICT reader, ImageBundle* decoded,
- const CodecMetadata& metadata,
- const SizeConstraints* constraints, bool is_preview = false);
+// Used in the encoder to model decoder behaviour, and in tests.
+Status DecodeFrame(PassesDecoderState* dec_state, ThreadPool* JXL_RESTRICT pool,
+ const uint8_t* next_in, size_t avail_in,
+ ImageBundle* decoded, const CodecMetadata& metadata,
+ bool use_slow_rendering_pipeline = false);
// TODO(veluca): implement "forced drawing".
class FrameDecoder {
@@ -56,28 +46,25 @@ class FrameDecoder {
frame_header_(&metadata),
use_slow_rendering_pipeline_(use_slow_rendering_pipeline) {}
- // `constraints` must outlive the FrameDecoder if not null, or stay alive
- // until the next call to SetFrameSizeLimits.
- void SetFrameSizeLimits(const SizeConstraints* constraints) {
- constraints_ = constraints;
- }
void SetRenderSpotcolors(bool rsc) { render_spotcolors_ = rsc; }
void SetCoalescing(bool c) { coalescing_ = c; }
// Read FrameHeader and table of contents from the given BitReader.
// Also checks frame dimensions for their limits, and sets the output
// image buffer.
- // TODO(veluca): remove the `allow_partial_frames` flag - this should be moved
- // on callers.
Status InitFrame(BitReader* JXL_RESTRICT br, ImageBundle* decoded,
- bool is_preview, bool allow_partial_frames,
- bool allow_partial_dc_global, bool output_needed);
+ bool is_preview, bool output_needed);
struct SectionInfo {
BitReader* JXL_RESTRICT br;
size_t id;
};
+ struct TocEntry {
+ size_t size;
+ size_t id;
+ };
+
enum SectionStatus {
// Processed correctly.
kDone = 0,
@@ -118,22 +105,20 @@ class FrameDecoder {
// soon as the frame header is known.
static int SavedAs(const FrameHeader& header);
- // Returns offset of this section after the end of the TOC. The end of the TOC
- // is the byte position of the bit reader after InitFrame was called.
- const std::vector<uint64_t>& SectionOffsets() const {
- return section_offsets_;
- }
- const std::vector<uint32_t>& SectionSizes() const { return section_sizes_; }
- size_t NumSections() const { return section_sizes_.size(); }
+ uint64_t SumSectionSizes() const { return section_sizes_sum_; }
+ const std::vector<TocEntry>& Toc() const { return toc_; }
- // TODO(veluca): remove once we remove --downsampling flag.
- void SetMaxPasses(size_t max_passes) { max_passes_ = max_passes; }
const FrameHeader& GetFrameHeader() const { return frame_header_; }
// Returns whether a DC image has been decoded, accessible at low resolution
// at passes.shared_storage.dc_storage
bool HasDecodedDC() const { return finalized_dc_; }
- bool HasDecodedAll() const { return NumSections() == num_sections_done_; }
+ bool HasDecodedAll() const { return toc_.size() == num_sections_done_; }
+
+ size_t NumCompletePasses() const {
+ return *std::min_element(decoded_passes_per_ac_group_.begin(),
+ decoded_passes_per_ac_group_.end());
+ };
// If enabled, ProcessSections will stop and return true when the DC
// sections have been processed, instead of starting the AC sections. This
@@ -141,7 +126,59 @@ class FrameDecoder {
// 1/8th*1/8th resolution image). The return value of true then does not mean
// all sections have been processed, use HasDecodedDC and HasDecodedAll
// to check the true finished state.
- void SetPauseAtProgressive() { pause_at_progressive_ = true; }
+ // Returns the progressive detail that will be effective for the frame.
+ JxlProgressiveDetail SetPauseAtProgressive(JxlProgressiveDetail prog_detail) {
+ bool single_section =
+ frame_dim_.num_groups == 1 && frame_header_.passes.num_passes == 1;
+ if (frame_header_.frame_type != kSkipProgressive &&
+ // If there's only one group and one pass, there is no separate section
+ // for DC and the entire full resolution image is available at once.
+ !single_section &&
+ // If extra channels are encoded with modular without squeeze, they
+ // don't support DC. If the are encoded with squeeze, DC works in theory
+ // but the implementation may not yet correctly support this for Flush.
+ // Therefore, can't correctly pause for a progressive step if there is
+ // an extra channel (including alpha channel)
+ // TOOD(firsching): Check if this is still the case.
+ decoded_->metadata()->extra_channel_info.empty() &&
+ // DC is not guaranteed to be available in modular mode and may be a
+ // black image. If squeeze is used, it may be available depending on the
+ // current implementation.
+ // TODO(lode): do return DC if it's known that flushing at this point
+ // will produce a valid 1/8th downscaled image with modular encoding.
+ frame_header_.encoding == FrameEncoding::kVarDCT) {
+ progressive_detail_ = prog_detail;
+ } else {
+ progressive_detail_ = JxlProgressiveDetail::kFrames;
+ }
+ if (progressive_detail_ >= JxlProgressiveDetail::kPasses) {
+ for (size_t i = 1; i < frame_header_.passes.num_passes; ++i) {
+ passes_to_pause_.push_back(i);
+ }
+ } else if (progressive_detail_ >= JxlProgressiveDetail::kLastPasses) {
+ for (size_t i = 0; i < frame_header_.passes.num_downsample; ++i) {
+ passes_to_pause_.push_back(frame_header_.passes.last_pass[i] + 1);
+ }
+ // The format does not guarantee that these values are sorted.
+ std::sort(passes_to_pause_.begin(), passes_to_pause_.end());
+ }
+ return progressive_detail_;
+ }
+
+ size_t NextNumPassesToPause() const {
+ auto it = std::upper_bound(passes_to_pause_.begin(), passes_to_pause_.end(),
+ NumCompletePasses());
+ return (it != passes_to_pause_.end() ? *it
+ : std::numeric_limits<size_t>::max());
+ }
+
+ void MaybeSetUnpremultiplyAlpha(bool unpremul_alpha) {
+ const jxl::ExtraChannelInfo* alpha =
+ decoded_->metadata()->Find(jxl::ExtraChannel::kAlpha);
+ if (alpha && alpha->alpha_associated && unpremul_alpha) {
+ dec_state_->unpremul_alpha = true;
+ }
+ }
// Sets the buffer to which uint8 sRGB pixels will be decoded. This is not
// supported for all images. If it succeeds, HasRGBBuffer() will return true.
@@ -156,7 +193,9 @@ class FrameDecoder {
// orientation. When outputting to the ImageBundle, no orientation is undone.
void MaybeSetRGB8OutputBuffer(uint8_t* rgb_output, size_t stride,
bool is_rgba, bool undo_orientation) const {
- if (!CanDoLowMemoryPath(undo_orientation)) return;
+ if (!CanDoLowMemoryPath(undo_orientation) || dec_state_->unpremul_alpha) {
+ return;
+ }
dec_state_->rgb_output = rgb_output;
dec_state_->rgb_output_is_rgba = is_rgba;
dec_state_->rgb_stride = stride;
@@ -165,6 +204,8 @@ class FrameDecoder {
if (decoded_->metadata()->xyb_encoded &&
dec_state_->output_encoding_info.color_encoding.IsSRGB() &&
dec_state_->output_encoding_info.all_default_opsin &&
+ dec_state_->output_encoding_info.desired_intensity_target ==
+ dec_state_->output_encoding_info.orig_intensity_target &&
HasFastXYBTosRGB8() && frame_header_.needs_color_transform()) {
dec_state_->fast_xyb_srgb8_conversion = true;
}
@@ -183,7 +224,7 @@ class FrameDecoder {
// results in not setting the buffer if the image has a non-identity EXIF
// orientation. When outputting to the ImageBundle, no orientation is undone.
void MaybeSetFloatCallback(const PixelCallback& pixel_callback, bool is_rgba,
- bool undo_orientation) const {
+ bool unpremul_alpha, bool undo_orientation) const {
if (!CanDoLowMemoryPath(undo_orientation)) return;
dec_state_->pixel_callback = pixel_callback;
dec_state_->rgb_output_is_rgba = is_rgba;
@@ -221,11 +262,12 @@ class FrameDecoder {
group_dec_caches_.resize(storage_size);
}
use_task_id_ = num_threads > num_tasks;
+ bool use_group_ids = (modular_frame_decoder_.UsesFullImage() &&
+ (frame_header_.encoding == FrameEncoding::kVarDCT ||
+ (frame_header_.flags & FrameHeader::kNoise)));
if (dec_state_->render_pipeline) {
JXL_RETURN_IF_ERROR(dec_state_->render_pipeline->PrepareForThreads(
- storage_size,
- /*use_group_ids=*/modular_frame_decoder_.UsesFullImage() &&
- frame_header_.encoding == FrameEncoding::kVarDCT));
+ storage_size, use_group_ids));
}
return true;
}
@@ -248,16 +290,13 @@ class FrameDecoder {
PassesDecoderState* dec_state_;
ThreadPool* pool_;
- std::vector<uint64_t> section_offsets_;
- std::vector<uint32_t> section_sizes_;
- size_t max_passes_;
+ std::vector<TocEntry> toc_;
+ uint64_t section_sizes_sum_;
// TODO(veluca): figure out the duplication between these and dec_state_.
FrameHeader frame_header_;
FrameDimensions frame_dim_;
ImageBundle* decoded_;
ModularFrameDecoder modular_frame_decoder_;
- bool allow_partial_frames_;
- bool allow_partial_dc_global_;
bool render_spotcolors_ = true;
bool coalescing_ = true;
@@ -270,14 +309,10 @@ class FrameDecoder {
bool finalized_dc_ = true;
size_t num_sections_done_ = 0;
bool is_finalized_ = true;
- size_t num_renders_ = 0;
bool allocated_ = false;
std::vector<GroupDecCache> group_dec_caches_;
- // Frame size limits.
- const SizeConstraints* constraints_ = nullptr;
-
// Whether or not the task id should be used for storage indexing, instead of
// the thread id.
bool use_task_id_ = false;
@@ -285,7 +320,10 @@ class FrameDecoder {
// Testing setting: whether or not to use the slow rendering pipeline.
bool use_slow_rendering_pipeline_;
- bool pause_at_progressive_ = false;
+ JxlProgressiveDetail progressive_detail_ = kFrames;
+ // Number of completed passes where section decoding should pause.
+ // Used for progressive details at least kLastPasses.
+ std::vector<int> passes_to_pause_;
};
} // namespace jxl
diff --git a/media/libjxl/src/lib/jxl/dec_group.cc b/media/libjxl/src/lib/jxl/dec_group.cc
index 628c932e7c..689bc81779 100644
--- a/media/libjxl/src/lib/jxl/dec_group.cc
+++ b/media/libjxl/src/lib/jxl/dec_group.cc
@@ -97,10 +97,11 @@ void DequantLane(Vec<D> scaled_dequant_x, Vec<D> scaled_dequant_y,
size_t k, Vec<D> x_cc_mul, Vec<D> b_cc_mul,
const float* JXL_RESTRICT biases, ACPtr qblock[3],
float* JXL_RESTRICT block) {
- const auto x_mul = Load(d, dequant_matrices + k) * scaled_dequant_x;
- const auto y_mul = Load(d, dequant_matrices + size + k) * scaled_dequant_y;
+ const auto x_mul = Mul(Load(d, dequant_matrices + k), scaled_dequant_x);
+ const auto y_mul =
+ Mul(Load(d, dequant_matrices + size + k), scaled_dequant_y);
const auto b_mul =
- Load(d, dequant_matrices + 2 * size + k) * scaled_dequant_b;
+ Mul(Load(d, dequant_matrices + 2 * size + k), scaled_dequant_b);
Vec<DI> quantized_x_int;
Vec<DI> quantized_y_int;
@@ -117,11 +118,11 @@ void DequantLane(Vec<D> scaled_dequant_x, Vec<D> scaled_dequant_y,
}
const auto dequant_x_cc =
- AdjustQuantBias(di, 0, quantized_x_int, biases) * x_mul;
+ Mul(AdjustQuantBias(di, 0, quantized_x_int, biases), x_mul);
const auto dequant_y =
- AdjustQuantBias(di, 1, quantized_y_int, biases) * y_mul;
+ Mul(AdjustQuantBias(di, 1, quantized_y_int, biases), y_mul);
const auto dequant_b_cc =
- AdjustQuantBias(di, 2, quantized_b_int, biases) * b_mul;
+ Mul(AdjustQuantBias(di, 2, quantized_b_int, biases), b_mul);
const auto dequant_x = MulAdd(x_cc_mul, dequant_y, dequant_x_cc);
const auto dequant_b = MulAdd(b_cc_mul, dequant_y, dequant_b_cc);
@@ -241,8 +242,10 @@ Status DecodeGroupImpl(GetBlock* JXL_RESTRICT get_block,
r[i] =
Rect(block_rect.x0() >> hshift[i], block_rect.y0() >> vshift[i],
block_rect.xsize() >> hshift[i], block_rect.ysize() >> vshift[i]);
- JXL_ASSERT(r[i].IsInside({0, 0, dec_state->shared->dc->Plane(i).xsize(),
- dec_state->shared->dc->Plane(i).ysize()}));
+ if (!r[i].IsInside({0, 0, dec_state->shared->dc->Plane(i).xsize(),
+ dec_state->shared->dc->Plane(i).ysize()})) {
+ return JXL_FAILURE("Frame dimensions are too big for the image.");
+ }
}
for (size_t by = 0; by < ysize_blocks; ++by) {
@@ -383,11 +386,11 @@ Status DecodeGroupImpl(GetBlock* JXL_RESTRICT get_block,
auto in = Load(di, transposed_dct + i);
auto in_y = Load(di, transposed_dct_y + i);
auto qt = Load(di, scaled_qtable + c * size + i);
- auto coeff_scale =
- ShiftRight<kCFLFixedPointPrecision>(qt * scale + round);
+ auto coeff_scale = ShiftRight<kCFLFixedPointPrecision>(
+ Add(Mul(qt, scale), round));
auto cfl_factor = ShiftRight<kCFLFixedPointPrecision>(
- in_y * coeff_scale + round);
- StoreU(DemoteTo(di16, in + cfl_factor), di16, jpeg_pos + i);
+ Add(Mul(in_y, coeff_scale), round));
+ StoreU(DemoteTo(di16, Add(in, cfl_factor)), di16, jpeg_pos + i);
}
}
jpeg_pos[0] =
diff --git a/media/libjxl/src/lib/jxl/dec_group.h b/media/libjxl/src/lib/jxl/dec_group.h
index 56542e9519..e50d22d2f8 100644
--- a/media/libjxl/src/lib/jxl/dec_group.h
+++ b/media/libjxl/src/lib/jxl/dec_group.h
@@ -21,7 +21,6 @@
#include "lib/jxl/dec_ans.h"
#include "lib/jxl/dec_bit_reader.h"
#include "lib/jxl/dec_cache.h"
-#include "lib/jxl/dec_params.h"
#include "lib/jxl/frame_header.h"
#include "lib/jxl/image.h"
#include "lib/jxl/quantizer.h"
diff --git a/media/libjxl/src/lib/jxl/dec_modular.cc b/media/libjxl/src/lib/jxl/dec_modular.cc
index b6601c12d5..bf85eaa05c 100644
--- a/media/libjxl/src/lib/jxl/dec_modular.cc
+++ b/media/libjxl/src/lib/jxl/dec_modular.cc
@@ -7,6 +7,8 @@
#include <stdint.h>
+#include <atomic>
+#include <sstream>
#include <vector>
#include "lib/jxl/frame_header.h"
@@ -32,6 +34,8 @@ namespace jxl {
namespace HWY_NAMESPACE {
// These templates are not found via ADL.
+using hwy::HWY_NAMESPACE::Add;
+using hwy::HWY_NAMESPACE::Mul;
using hwy::HWY_NAMESPACE::Rebind;
void MultiplySum(const size_t xsize,
@@ -42,8 +46,8 @@ void MultiplySum(const size_t xsize,
const Rebind<pixel_type, HWY_FULL(float)> di; // assumes pixel_type <= float
const auto factor_v = Set(df, factor);
for (size_t x = 0; x < xsize; x += Lanes(di)) {
- const auto in = Load(di, row_in + x) + Load(di, row_in_Y + x);
- const auto out = ConvertTo(df, in) * factor_v;
+ const auto in = Add(Load(di, row_in + x), Load(di, row_in_Y + x));
+ const auto out = Mul(ConvertTo(df, in), factor_v);
Store(out, df, row_out + x);
}
}
@@ -58,7 +62,7 @@ void RgbFromSingle(const size_t xsize,
const auto factor_v = Set(df, factor);
for (size_t x = 0; x < xsize; x += Lanes(di)) {
const auto in = Load(di, row_in + x);
- const auto out = ConvertTo(df, in) * factor_v;
+ const auto out = Mul(ConvertTo(df, in), factor_v);
Store(out, df, out_r + x);
Store(out, df, out_g + x);
Store(out, df, out_b + x);
@@ -74,7 +78,7 @@ void SingleFromSingle(const size_t xsize,
const auto factor_v = Set(df, factor);
for (size_t x = 0; x < xsize; x += Lanes(di)) {
const auto in = Load(di, row_in + x);
- const auto out = ConvertTo(df, in) * factor_v;
+ const auto out = Mul(ConvertTo(df, in), factor_v);
Store(out, df, row_out + x);
}
}
@@ -149,6 +153,28 @@ void int_to_float(const pixel_type* const JXL_RESTRICT row_in,
}
}
+std::string ModularStreamId::DebugString() const {
+ std::ostringstream os;
+ os << (kind == kGlobalData ? "ModularGlobal"
+ : kind == kVarDCTDC ? "VarDCTDC"
+ : kind == kModularDC ? "ModularDC"
+ : kind == kACMetadata ? "ACMeta"
+ : kind == kQuantTable ? "QuantTable"
+ : kind == kModularAC ? "ModularAC"
+ : "");
+ if (kind == kVarDCTDC || kind == kModularDC || kind == kACMetadata ||
+ kind == kModularAC) {
+ os << " group " << group_id;
+ }
+ if (kind == kModularAC) {
+ os << " pass " << pass_id;
+ }
+ if (kind == kQuantTable) {
+ os << " " << quant_table_id;
+ }
+ return os.str();
+}
+
Status ModularFrameDecoder::DecodeGlobalInfo(BitReader* reader,
const FrameHeader& frame_header,
bool allow_truncated_group) {
@@ -218,6 +244,8 @@ Status ModularFrameDecoder::DecodeGlobalInfo(BitReader* reader,
all_same_shift = false;
}
+ JXL_DEBUG_V(6, "DecodeGlobalInfo: full_image (w/o transforms) %s",
+ gi.DebugString().c_str());
ModularOptions options;
options.max_chan_size = frame_dim.group_dim;
options.group_dim = frame_dim.group_dim;
@@ -248,12 +276,15 @@ Status ModularFrameDecoder::DecodeGlobalInfo(BitReader* reader,
}
}
full_image = std::move(gi);
+ JXL_DEBUG_V(6, "DecodeGlobalInfo: full_image (with transforms) %s",
+ full_image.DebugString().c_str());
return dec_status;
}
void ModularFrameDecoder::MaybeDropFullImage() {
if (full_image.transform.empty() && !have_something && all_same_shift) {
use_full_image = false;
+ JXL_DEBUG_V(6, "Dropping full image");
for (auto& ch : full_image.channel) {
// keep metadata on channels around, but dealloc their planes
ch.plane = Plane<pixel_type>();
@@ -264,8 +295,11 @@ void ModularFrameDecoder::MaybeDropFullImage() {
Status ModularFrameDecoder::DecodeGroup(
const Rect& rect, BitReader* reader, int minShift, int maxShift,
const ModularStreamId& stream, bool zerofill, PassesDecoderState* dec_state,
- RenderPipelineInput* render_pipeline_input, ImageBundle* output,
- bool allow_truncated) {
+ RenderPipelineInput* render_pipeline_input, bool allow_truncated,
+ bool* should_run_pipeline) {
+ JXL_DEBUG_V(6, "Decoding %s with rect %s and shift bracket %d..%d %s",
+ stream.DebugString().c_str(), Description(rect).c_str(), minShift,
+ maxShift, zerofill ? "using zerofill" : "");
JXL_DASSERT(stream.kind == ModularStreamId::kModularDC ||
stream.kind == ModularStreamId::kModularAC);
const size_t xsize = rect.xsize();
@@ -302,7 +336,19 @@ Status ModularFrameDecoder::DecodeGroup(
if (zerofill && use_full_image) return true;
// Return early if there's nothing to decode. Otherwise there might be
// problems later (in ModularImageToDecodedRect).
- if (gi.channel.empty()) return true;
+ if (gi.channel.empty()) {
+ if (dec_state && should_run_pipeline) {
+ const auto& frame_header = dec_state->shared->frame_header;
+ const auto* metadata = frame_header.nonserialized_metadata;
+ if (do_color || metadata->m.num_extra_channels > 0) {
+ // Signal to FrameDecoder that we do not have some of the required input
+ // for the render pipeline.
+ *should_run_pipeline = false;
+ }
+ }
+ JXL_DEBUG_V(6, "Nothing to decode, returning early.");
+ return true;
+ }
ModularOptions options;
if (!zerofill) {
auto status = ModularGenericDecompress(
@@ -407,11 +453,11 @@ Status ModularFrameDecoder::DecodeAcMetadata(size_t group_id, BitReader* reader,
uint32_t local_used_acs = 0;
for (size_t iy = 0; iy < r.ysize(); iy++) {
size_t y = r.y0() + iy;
- int* row_qf = r.Row(&dec_state->shared_storage.raw_quant_field, iy);
+ int32_t* row_qf = r.Row(&dec_state->shared_storage.raw_quant_field, iy);
uint8_t* row_epf = r.Row(&dec_state->shared_storage.epf_sharpness, iy);
- int* row_in_1 = image.channel[2].plane.Row(0);
- int* row_in_2 = image.channel[2].plane.Row(1);
- int* row_in_3 = image.channel[3].plane.Row(iy);
+ int32_t* row_in_1 = image.channel[2].plane.Row(0);
+ int32_t* row_in_2 = image.channel[2].plane.Row(1);
+ int32_t* row_in_3 = image.channel[3].plane.Row(iy);
for (size_t ix = 0; ix < r.xsize(); ix++) {
size_t x = r.x0() + ix;
int sharpness = row_in_3[ix];
@@ -448,8 +494,8 @@ Status ModularFrameDecoder::DecodeAcMetadata(size_t group_id, BitReader* reader,
}
JXL_RETURN_IF_ERROR(
ac_strategy.SetNoBoundsCheck(x, y, AcStrategy::Type(row_in_1[num])));
- row_qf[ix] =
- 1 + std::max(0, std::min(Quantizer::kQuantMax - 1, row_in_2[num]));
+ row_qf[ix] = 1 + std::max<int32_t>(0, std::min(Quantizer::kQuantMax - 1,
+ row_in_2[num]));
num++;
}
}
@@ -605,7 +651,13 @@ Status ModularFrameDecoder::ModularImageToDecodedRect(
DivCeil(modular_rect.xsize(), 1 << ch_in.hshift),
DivCeil(modular_rect.ysize(), 1 << ch_in.vshift));
mr = mr.Crop(ch_in.plane);
-
+ if (r.ysize() != mr.ysize() || r.xsize() != mr.xsize()) {
+ return JXL_FAILURE("Dimension mismatch: trying to fit a %" PRIuS
+ "x%" PRIuS
+ " modular channel into "
+ "a %" PRIuS "x%" PRIuS " rect",
+ mr.xsize(), mr.ysize(), r.xsize(), r.ysize());
+ }
for (size_t y = 0; y < r.ysize(); ++y) {
float* const JXL_RESTRICT row_out =
r.Row(render_pipeline_input.GetBuffer(3 + ec).first, y);
@@ -627,31 +679,35 @@ Status ModularFrameDecoder::ModularImageToDecodedRect(
Status ModularFrameDecoder::FinalizeDecoding(PassesDecoderState* dec_state,
jxl::ThreadPool* pool,
- ImageBundle* output,
bool inplace) {
if (!use_full_image) return true;
Image gi = (inplace ? std::move(full_image) : full_image.clone());
size_t xsize = gi.w;
size_t ysize = gi.h;
+ JXL_DEBUG_V(3, "Finalizing decoding for modular image: %s",
+ gi.DebugString().c_str());
+
// Don't use threads if total image size is smaller than a group
if (xsize * ysize < frame_dim.group_dim * frame_dim.group_dim) pool = nullptr;
// Undo the global transforms
gi.undo_transforms(global_header.wp_header, pool);
- for (auto t : global_transform) {
- JXL_RETURN_IF_ERROR(t.Inverse(gi, global_header.wp_header));
- }
+ JXL_DASSERT(global_transform.empty());
if (gi.error) return JXL_FAILURE("Undoing transforms failed");
+ for (size_t i = 0; i < dec_state->shared->frame_dim.num_groups; i++) {
+ dec_state->render_pipeline->ClearDone(i);
+ }
std::atomic<bool> has_error{false};
JXL_RETURN_IF_ERROR(RunOnPool(
pool, 0, dec_state->shared->frame_dim.num_groups,
[&](size_t num_threads) {
- return dec_state->render_pipeline->PrepareForThreads(
- num_threads,
- /*use_group_ids=*/dec_state->shared->frame_header.encoding ==
- FrameEncoding::kVarDCT);
+ const auto& frame_header = dec_state->shared->frame_header;
+ bool use_group_ids = (frame_header.encoding == FrameEncoding::kVarDCT ||
+ (frame_header.flags & FrameHeader::kNoise));
+ return dec_state->render_pipeline->PrepareForThreads(num_threads,
+ use_group_ids);
},
[&](const uint32_t group, size_t thread_id) {
RenderPipelineInput input =
@@ -701,7 +757,7 @@ Status ModularFrameDecoder::DecodeQuantTable(
encoding->qraw.qtable->resize(required_size_x * required_size_y * 3);
for (size_t c = 0; c < 3; c++) {
for (size_t y = 0; y < required_size_y; y++) {
- int* JXL_RESTRICT row = image.channel[c].Row(y);
+ int32_t* JXL_RESTRICT row = image.channel[c].Row(y);
for (size_t x = 0; x < required_size_x; x++) {
(*encoding->qraw.qtable)[c * required_size_x * required_size_y +
y * required_size_x + x] = row[x];
diff --git a/media/libjxl/src/lib/jxl/dec_modular.h b/media/libjxl/src/lib/jxl/dec_modular.h
index 026a2c9e2a..ec94b46482 100644
--- a/media/libjxl/src/lib/jxl/dec_modular.h
+++ b/media/libjxl/src/lib/jxl/dec_modular.h
@@ -8,15 +8,15 @@
#include <stddef.h>
+#include <string>
+
#include "lib/jxl/aux_out_fwd.h"
#include "lib/jxl/base/data_parallel.h"
#include "lib/jxl/base/status.h"
#include "lib/jxl/dec_bit_reader.h"
#include "lib/jxl/dec_cache.h"
-#include "lib/jxl/dec_params.h"
#include "lib/jxl/frame_header.h"
#include "lib/jxl/image.h"
-#include "lib/jxl/image_bundle.h"
#include "lib/jxl/modular/encoding/encoding.h"
#include "lib/jxl/modular/modular_image.h"
@@ -82,6 +82,7 @@ struct ModularStreamId {
static size_t Num(const FrameDimensions& frame_dim, size_t passes) {
return ModularAC(0, passes).ID(frame_dim);
}
+ std::string DebugString() const;
};
class ModularFrameDecoder {
@@ -93,7 +94,7 @@ class ModularFrameDecoder {
int maxShift, const ModularStreamId& stream, bool zerofill,
PassesDecoderState* dec_state,
RenderPipelineInput* render_pipeline_input,
- ImageBundle* output, bool allow_truncated);
+ bool allow_truncated, bool* should_run_pipeline = nullptr);
// Decodes a VarDCT DC group (`group_id`) from the given `reader`.
Status DecodeVarDCTDC(size_t group_id, BitReader* reader,
PassesDecoderState* dec_state);
@@ -111,7 +112,7 @@ class ModularFrameDecoder {
// if it is false, it can be called multiple times (e.g. for progressive
// steps)
Status FinalizeDecoding(PassesDecoderState* dec_state, jxl::ThreadPool* pool,
- ImageBundle* output, bool inplace);
+ bool inplace);
bool have_dc() const { return have_something; }
void MaybeDropFullImage();
bool UsesFullImage() const { return use_full_image; }
diff --git a/media/libjxl/src/lib/jxl/dec_noise.cc b/media/libjxl/src/lib/jxl/dec_noise.cc
index d232e48c03..f48398b5cf 100644
--- a/media/libjxl/src/lib/jxl/dec_noise.cc
+++ b/media/libjxl/src/lib/jxl/dec_noise.cc
@@ -29,6 +29,7 @@ namespace jxl {
namespace HWY_NAMESPACE {
// These templates are not found via ADL.
+using hwy::HWY_NAMESPACE::Or;
using hwy::HWY_NAMESPACE::ShiftRight;
using hwy::HWY_NAMESPACE::Vec;
@@ -46,7 +47,7 @@ void BitsToFloat(const uint32_t* JXL_RESTRICT random_bits,
const auto bits = Load(du, random_bits);
// 1.0 + 23 random mantissa bits = [1, 2)
- const auto rand12 = BitCast(df, ShiftRight<9>(bits) | Set(du, 0x3F800000));
+ const auto rand12 = BitCast(df, Or(ShiftRight<9>(bits), Set(du, 0x3F800000)));
Store(rand12, df, floats);
}
@@ -58,7 +59,7 @@ void RandomImage(Xorshift128Plus* rng, const Rect& rect,
// May exceed the vector size, hence we have two loops over x below.
constexpr size_t kFloatsPerBatch =
Xorshift128Plus::N * sizeof(uint64_t) / sizeof(float);
- HWY_ALIGN uint64_t batch[Xorshift128Plus::N];
+ HWY_ALIGN uint64_t batch[Xorshift128Plus::N] = {};
const HWY_FULL(float) df;
const size_t N = Lanes(df);
diff --git a/media/libjxl/src/lib/jxl/dec_params.h b/media/libjxl/src/lib/jxl/dec_params.h
deleted file mode 100644
index a9a4b105ce..0000000000
--- a/media/libjxl/src/lib/jxl/dec_params.h
+++ /dev/null
@@ -1,52 +0,0 @@
-// Copyright (c) the JPEG XL Project Authors. All rights reserved.
-//
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-#ifndef LIB_JXL_DEC_PARAMS_H_
-#define LIB_JXL_DEC_PARAMS_H_
-
-// Parameters and flags that govern JXL decompression.
-
-#include <stddef.h>
-#include <stdint.h>
-
-#include <limits>
-
-#include "lib/jxl/base/override.h"
-
-namespace jxl {
-
-struct DecompressParams {
- // If true, checks at the end of decoding that all of the compressed data
- // was consumed by the decoder.
- bool check_decompressed_size = true;
-
- // If true, skip dequant and iDCT and decode to JPEG (only if possible)
- bool keep_dct = false;
- // If true, render spot colors (otherwise only returned as extra channels)
- bool render_spotcolors = true;
- // If true, coalesce frames (otherwise return unblended frames)
- bool coalescing = true;
-
- // How many passes to decode at most. By default, decode everything.
- uint32_t max_passes = std::numeric_limits<uint32_t>::max();
- // Alternatively, one can specify the maximum tolerable downscaling factor
- // with respect to the full size of the image. By default, nothing less than
- // the full size is requested.
- size_t max_downsampling = 1;
-
- // Try to decode as much as possible of a truncated codestream, but only whole
- // sections at a time.
- bool allow_partial_files = false;
- // Allow even more progression.
- bool allow_more_progressive_steps = false;
-
- // Internal test-only setting: whether or not to use the slow rendering
- // pipeline.
- bool use_slow_render_pipeline = false;
-};
-
-} // namespace jxl
-
-#endif // LIB_JXL_DEC_PARAMS_H_
diff --git a/media/libjxl/src/lib/jxl/dec_patch_dictionary.cc b/media/libjxl/src/lib/jxl/dec_patch_dictionary.cc
index 194a932f36..4f8720922f 100644
--- a/media/libjxl/src/lib/jxl/dec_patch_dictionary.cc
+++ b/media/libjxl/src/lib/jxl/dec_patch_dictionary.cc
@@ -35,8 +35,6 @@
namespace jxl {
-constexpr size_t kMaxPatches = 1 << 24;
-
Status PatchDictionary::Decode(BitReader* br, size_t xsize, size_t ysize,
bool* uses_extra_channels) {
positions_.clear();
@@ -52,10 +50,16 @@ Status PatchDictionary::Decode(BitReader* br, size_t xsize, size_t ysize,
};
size_t num_ref_patch = read_num(kNumRefPatchContext);
- // TODO(veluca): does this make sense?
- if (num_ref_patch > kMaxPatches) {
+ // Limit max memory usage of patches to about 66 bytes per pixel (assuming 8
+ // bytes per size_t)
+ const size_t num_pixels = xsize * ysize;
+ const size_t max_ref_patches = 1024 + num_pixels / 4;
+ const size_t max_patches = max_ref_patches * 4;
+ const size_t max_blending_infos = max_patches * 4;
+ if (num_ref_patch > max_ref_patches) {
return JXL_FAILURE("Too many patches in dictionary");
}
+ size_t num_ec = shared_->metadata->m.num_extra_channels;
size_t total_patches = 0;
size_t next_size = 1;
@@ -84,17 +88,21 @@ Status PatchDictionary::Decode(BitReader* br, size_t xsize, size_t ysize,
}
size_t id_count = read_num(kPatchCountContext) + 1;
total_patches += id_count;
- if (total_patches > kMaxPatches) {
+ if (total_patches > max_patches) {
return JXL_FAILURE("Too many patches in dictionary");
}
if (next_size < total_patches) {
next_size *= 2;
- next_size = std::min<size_t>(next_size, kMaxPatches);
+ next_size = std::min<size_t>(next_size, max_patches);
+ }
+ if (next_size * (num_ec + 1) > max_blending_infos) {
+ return JXL_FAILURE("Too many patches in dictionary");
}
positions_.reserve(next_size);
+ blendings_.reserve(next_size * (num_ec + 1));
for (size_t i = 0; i < id_count; i++) {
PatchPosition pos;
- pos.ref_pos = ref_pos;
+ pos.ref_pos_idx = ref_positions_.size();
if (i == 0) {
pos.x = read_num(kPatchPositionContext);
pos.y = read_num(kPatchPositionContext);
@@ -114,8 +122,7 @@ Status PatchDictionary::Decode(BitReader* br, size_t xsize, size_t ysize,
" > %" PRIuS,
pos.y, ref_pos.ysize, ysize);
}
- for (size_t j = 0; j < shared_->metadata->m.extra_channel_info.size() + 1;
- j++) {
+ for (size_t j = 0; j < num_ec + 1; j++) {
uint32_t blend_mode = read_num(kPatchBlendModeContext);
if (blend_mode >= uint32_t(PatchBlendMode::kNumBlendModes)) {
return JXL_FAILURE("Invalid patch blend mode: %u", blend_mode);
@@ -146,10 +153,11 @@ Status PatchDictionary::Decode(BitReader* br, size_t xsize, size_t ysize,
} else {
info.clamp = false;
}
- pos.blending.push_back(info);
+ blendings_.push_back(info);
}
positions_.push_back(std::move(pos));
}
+ ref_positions_.emplace_back(std::move(ref_pos));
}
positions_.shrink_to_fit();
@@ -157,80 +165,182 @@ Status PatchDictionary::Decode(BitReader* br, size_t xsize, size_t ysize,
return JXL_FAILURE("ANS checksum failure.");
}
- ComputePatchCache();
+ ComputePatchTree();
return true;
}
int PatchDictionary::GetReferences() const {
int result = 0;
- for (size_t i = 0; i < positions_.size(); ++i) {
- result |= (1 << static_cast<int>(positions_[i].ref_pos.ref));
+ for (size_t i = 0; i < ref_positions_.size(); ++i) {
+ result |= (1 << static_cast<int>(ref_positions_[i].ref));
}
return result;
}
-void PatchDictionary::ComputePatchCache() {
- patch_starts_.clear();
- sorted_patches_.clear();
- if (positions_.empty()) return;
- std::vector<std::pair<size_t, size_t>> sorted_patches_y;
- for (size_t i = 0; i < positions_.size(); i++) {
- const PatchPosition& pos = positions_[i];
- for (size_t y = pos.y; y < pos.y + pos.ref_pos.ysize; y++) {
- sorted_patches_y.emplace_back(y, i);
- }
+namespace {
+struct PatchInterval {
+ size_t idx;
+ size_t y0, y1;
+};
+} // namespace
+
+void PatchDictionary::ComputePatchTree() {
+ patch_tree_.clear();
+ num_patches_.clear();
+ sorted_patches_y0_.clear();
+ sorted_patches_y1_.clear();
+ if (positions_.empty()) {
+ return;
}
- // The relative order of patches that affect the same pixels is preserved.
- // This is important for patches that have a blend mode different from kAdd.
- std::sort(sorted_patches_y.begin(), sorted_patches_y.end());
- patch_starts_.resize(sorted_patches_y.back().first + 2,
- sorted_patches_y.size());
- sorted_patches_.resize(sorted_patches_y.size());
- for (size_t i = 0; i < sorted_patches_y.size(); i++) {
- sorted_patches_[i] = sorted_patches_y[i].second;
- patch_starts_[sorted_patches_y[i].first] =
- std::min(patch_starts_[sorted_patches_y[i].first], i);
+ // Create a y-interval for each patch.
+ std::vector<PatchInterval> intervals(positions_.size());
+ for (size_t i = 0; i < positions_.size(); ++i) {
+ const auto& pos = positions_[i];
+ intervals[i].idx = i;
+ intervals[i].y0 = pos.y;
+ intervals[i].y1 = pos.y + ref_positions_[pos.ref_pos_idx].ysize;
+ }
+ auto sort_by_y0 = [&intervals](size_t start, size_t end) {
+ std::sort(intervals.data() + start, intervals.data() + end,
+ [](const PatchInterval& i0, const PatchInterval& i1) {
+ return i0.y0 < i1.y0;
+ });
+ };
+ auto sort_by_y1 = [&intervals](size_t start, size_t end) {
+ std::sort(intervals.data() + start, intervals.data() + end,
+ [](const PatchInterval& i0, const PatchInterval& i1) {
+ return i0.y1 < i1.y1;
+ });
+ };
+ // Count the number of patches for each row.
+ sort_by_y1(0, intervals.size());
+ num_patches_.resize(intervals.back().y1);
+ for (auto iv : intervals) {
+ for (size_t y = iv.y0; y < iv.y1; ++y) num_patches_[y]++;
+ }
+ PatchTreeNode root;
+ root.start = 0;
+ root.num = intervals.size();
+ patch_tree_.push_back(root);
+ size_t next = 0;
+ while (next < patch_tree_.size()) {
+ auto& node = patch_tree_[next];
+ size_t start = node.start;
+ size_t end = node.start + node.num;
+ // Choose the y_center for this node to be the median of interval starts.
+ sort_by_y0(start, end);
+ size_t middle_idx = start + node.num / 2;
+ node.y_center = intervals[middle_idx].y0;
+ // Divide the intervals in [start, end) into three groups:
+ // * those completely to the right of y_center: [right_start, end)
+ // * those overlapping y_center: [left_end, right_start)
+ // * those completely to the left of y_center: [start, left_end)
+ size_t right_start = middle_idx;
+ while (right_start < end && intervals[right_start].y0 == node.y_center) {
+ ++right_start;
+ }
+ sort_by_y1(start, right_start);
+ size_t left_end = right_start;
+ while (left_end > start && intervals[left_end - 1].y1 > node.y_center) {
+ --left_end;
+ }
+ // Fill in sorted_patches_y0_ and sorted_patches_y1_ for the current node.
+ node.num = right_start - left_end;
+ node.start = sorted_patches_y0_.size();
+ for (ssize_t i = static_cast<ssize_t>(right_start) - 1;
+ i >= static_cast<ssize_t>(left_end); --i) {
+ sorted_patches_y1_.push_back({intervals[i].y1, intervals[i].idx});
+ }
+ sort_by_y0(left_end, right_start);
+ for (size_t i = left_end; i < right_start; ++i) {
+ sorted_patches_y0_.push_back({intervals[i].y0, intervals[i].idx});
+ }
+ // Create the left and right nodes (if not empty).
+ node.left_child = node.right_child = -1;
+ if (left_end > start) {
+ PatchTreeNode left;
+ left.start = start;
+ left.num = left_end - left.start;
+ patch_tree_[next].left_child = patch_tree_.size();
+ patch_tree_.push_back(left);
+ }
+ if (right_start < end) {
+ PatchTreeNode right;
+ right.start = right_start;
+ right.num = end - right.start;
+ patch_tree_[next].right_child = patch_tree_.size();
+ patch_tree_.push_back(right);
+ }
+ ++next;
}
- for (size_t i = patch_starts_.size() - 1; i > 0; i--) {
- patch_starts_[i - 1] = std::min(patch_starts_[i], patch_starts_[i - 1]);
+}
+
+std::vector<size_t> PatchDictionary::GetPatchesForRow(size_t y) const {
+ std::vector<size_t> result;
+ if (y < num_patches_.size() && num_patches_[y] > 0) {
+ result.reserve(num_patches_[y]);
+ for (ssize_t tree_idx = 0; tree_idx != -1;) {
+ JXL_DASSERT(tree_idx < (ssize_t)patch_tree_.size());
+ const auto& node = patch_tree_[tree_idx];
+ if (y <= node.y_center) {
+ for (size_t i = 0; i < node.num; ++i) {
+ const auto& p = sorted_patches_y0_[node.start + i];
+ if (y < p.first) break;
+ result.push_back(p.second);
+ }
+ tree_idx = y < node.y_center ? node.left_child : -1;
+ } else {
+ for (size_t i = 0; i < node.num; ++i) {
+ const auto& p = sorted_patches_y1_[node.start + i];
+ if (y >= p.first) break;
+ result.push_back(p.second);
+ }
+ tree_idx = node.right_child;
+ }
+ }
+ // Ensure that he relative order of patches that affect the same pixels is
+ // preserved. This is important for patches that have a blend mode
+ // different from kAdd.
+ std::sort(result.begin(), result.end());
}
+ return result;
}
// Adds patches to a segment of `xsize` pixels, starting at `inout`, assumed
// to be located at position (x0, y) in the frame.
void PatchDictionary::AddOneRow(float* const* inout, size_t y, size_t x0,
size_t xsize) const {
- if (patch_starts_.empty()) return;
size_t num_ec = shared_->metadata->m.num_extra_channels;
std::vector<const float*> fg_ptrs(3 + num_ec);
- if (y + 1 >= patch_starts_.size()) return;
- for (size_t id = patch_starts_[y]; id < patch_starts_[y + 1]; id++) {
- const PatchPosition& pos = positions_[sorted_patches_[id]];
+ for (size_t pos_idx : GetPatchesForRow(y)) {
+ const size_t blending_idx = pos_idx * (num_ec + 1);
+ const PatchPosition& pos = positions_[pos_idx];
+ const PatchReferencePosition& ref_pos = ref_positions_[pos.ref_pos_idx];
size_t by = pos.y;
size_t bx = pos.x;
- size_t patch_xsize = pos.ref_pos.xsize;
+ size_t patch_xsize = ref_pos.xsize;
JXL_DASSERT(y >= by);
- JXL_DASSERT(y < by + pos.ref_pos.ysize);
+ JXL_DASSERT(y < by + ref_pos.ysize);
size_t iy = y - by;
- size_t ref = pos.ref_pos.ref;
+ size_t ref = ref_pos.ref;
if (bx >= x0 + xsize) continue;
if (bx + patch_xsize < x0) continue;
size_t patch_x0 = std::max(bx, x0);
size_t patch_x1 = std::min(bx + patch_xsize, x0 + xsize);
for (size_t c = 0; c < 3; c++) {
fg_ptrs[c] = shared_->reference_frames[ref].frame->color()->ConstPlaneRow(
- c, pos.ref_pos.y0 + iy) +
- pos.ref_pos.x0 + x0 - bx;
+ c, ref_pos.y0 + iy) +
+ ref_pos.x0 + x0 - bx;
}
for (size_t i = 0; i < num_ec; i++) {
fg_ptrs[3 + i] =
shared_->reference_frames[ref].frame->extra_channels()[i].ConstRow(
- pos.ref_pos.y0 + iy) +
- pos.ref_pos.x0 + x0 - bx;
+ ref_pos.y0 + iy) +
+ ref_pos.x0 + x0 - bx;
}
PerformBlending(inout, fg_ptrs.data(), inout, patch_x0 - x0,
- patch_x1 - patch_x0, pos.blending[0],
- pos.blending.data() + 1,
+ patch_x1 - patch_x0, blendings_[blending_idx],
+ blendings_.data() + blending_idx + 1,
shared_->metadata->m.extra_channel_info);
}
}
diff --git a/media/libjxl/src/lib/jxl/dec_patch_dictionary.h b/media/libjxl/src/lib/jxl/dec_patch_dictionary.h
index 5ffdb8b6db..a950e83e85 100644
--- a/media/libjxl/src/lib/jxl/dec_patch_dictionary.h
+++ b/media/libjxl/src/lib/jxl/dec_patch_dictionary.h
@@ -78,25 +78,12 @@ struct PatchBlending {
// Position and size of the patch in the reference frame.
struct PatchReferencePosition {
size_t ref, x0, y0, xsize, ysize;
- bool operator<(const PatchReferencePosition& oth) const {
- return std::make_tuple(ref, x0, y0, xsize, ysize) <
- std::make_tuple(oth.ref, oth.x0, oth.y0, oth.xsize, oth.ysize);
- }
- bool operator==(const PatchReferencePosition& oth) const {
- return !(*this < oth) && !(oth < *this);
- }
};
struct PatchPosition {
// Position of top-left corner of the patch in the image.
size_t x, y;
- // Different blend mode for color and extra channels.
- std::vector<PatchBlending> blending;
- PatchReferencePosition ref_pos;
- bool operator<(const PatchPosition& oth) const {
- return std::make_tuple(ref_pos, x, y) <
- std::make_tuple(oth.ref_pos, oth.x, oth.y);
- }
+ size_t ref_pos_idx;
};
struct PassesSharedState;
@@ -119,7 +106,7 @@ class PatchDictionary {
void Clear() {
positions_.clear();
- ComputePatchCache();
+ ComputePatchTree();
}
// Adds patches to a segment of `xsize` pixels, starting at `inout`, assumed
@@ -130,25 +117,33 @@ class PatchDictionary {
// bit mask: bits 0-3 indicate reference frame 0-3.
int GetReferences() const;
+ std::vector<size_t> GetPatchesForRow(size_t y) const;
+
private:
friend class PatchDictionaryEncoder;
const PassesSharedState* shared_;
std::vector<PatchPosition> positions_;
-
- // Patch occurrences sorted by y.
- std::vector<size_t> sorted_patches_;
- // Index of the first patch for each y value.
- std::vector<size_t> patch_starts_;
-
- // Patch IDs in position [patch_starts_[y], patch_start_[y+1]) of
- // sorted_patches_ are all the patches that intersect the horizontal line at
- // y.
- // The relative order of patches that affect the same pixels is the same -
- // important when applying patches is noncommutative.
-
- // Compute patches_by_y_ after updating positions_.
- void ComputePatchCache();
+ std::vector<PatchReferencePosition> ref_positions_;
+ std::vector<PatchBlending> blendings_;
+
+ // Interval tree on the y coordinates of the patches.
+ struct PatchTreeNode {
+ ssize_t left_child;
+ ssize_t right_child;
+ size_t y_center;
+ // Range of patches in sorted_patches_y0_ and sorted_patches_y1_ that
+ // contain the row y_center.
+ size_t start;
+ size_t num;
+ };
+ std::vector<PatchTreeNode> patch_tree_;
+ // Number of patches for each row.
+ std::vector<size_t> num_patches_;
+ std::vector<std::pair<size_t, size_t>> sorted_patches_y0_;
+ std::vector<std::pair<size_t, size_t>> sorted_patches_y1_;
+
+ void ComputePatchTree();
};
} // namespace jxl
diff --git a/media/libjxl/src/lib/jxl/dec_tone_mapping-inl.h b/media/libjxl/src/lib/jxl/dec_tone_mapping-inl.h
new file mode 100644
index 0000000000..a3260372cf
--- /dev/null
+++ b/media/libjxl/src/lib/jxl/dec_tone_mapping-inl.h
@@ -0,0 +1,232 @@
+// Copyright (c) the JPEG XL Project Authors. All rights reserved.
+//
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+#if defined(LIB_JXL_DEC_TONE_MAPPING_INL_H_) == defined(HWY_TARGET_TOGGLE)
+#ifdef LIB_JXL_DEC_TONE_MAPPING_INL_H_
+#undef LIB_JXL_DEC_TONE_MAPPING_INL_H_
+#else
+#define LIB_JXL_DEC_TONE_MAPPING_INL_H_
+#endif
+
+#include <hwy/highway.h>
+
+#include "lib/jxl/transfer_functions-inl.h"
+
+HWY_BEFORE_NAMESPACE();
+namespace jxl {
+namespace HWY_NAMESPACE {
+namespace {
+
+// These templates are not found via ADL.
+using hwy::HWY_NAMESPACE::Clamp;
+using hwy::HWY_NAMESPACE::Max;
+using hwy::HWY_NAMESPACE::ZeroIfNegative;
+
+template <typename D>
+class Rec2408ToneMapper {
+ private:
+ using V = hwy::HWY_NAMESPACE::Vec<D>;
+
+ public:
+ explicit Rec2408ToneMapper(std::pair<float, float> source_range,
+ std::pair<float, float> target_range,
+ const float primaries_luminances[3])
+ : source_range_(source_range),
+ target_range_(target_range),
+ red_Y_(primaries_luminances[0]),
+ green_Y_(primaries_luminances[1]),
+ blue_Y_(primaries_luminances[2]) {}
+
+ void ToneMap(V* red, V* green, V* blue) const {
+ const V luminance = Mul(Set(df_, source_range_.second),
+ (MulAdd(Set(df_, red_Y_), *red,
+ MulAdd(Set(df_, green_Y_), *green,
+ Mul(Set(df_, blue_Y_), *blue)))));
+ const V pq_mastering_min = Set(df_, pq_mastering_min_);
+ const V inv_pq_mastering_range = Set(df_, inv_pq_mastering_range_);
+ const V normalized_pq = Min(
+ Set(df_, 1.f),
+ Mul(Sub(InvEOTF(luminance), pq_mastering_min), inv_pq_mastering_range));
+ const V ks = Set(df_, ks_);
+ const V e2 =
+ IfThenElse(Lt(normalized_pq, ks), normalized_pq, P(normalized_pq));
+ const V one_minus_e2 = Sub(Set(df_, 1), e2);
+ const V one_minus_e2_2 = Mul(one_minus_e2, one_minus_e2);
+ const V one_minus_e2_4 = Mul(one_minus_e2_2, one_minus_e2_2);
+ const V b = Set(df_, min_lum_);
+ const V e3 = MulAdd(b, one_minus_e2_4, e2);
+ const V pq_mastering_range = Set(df_, pq_mastering_range_);
+ const V e4 = MulAdd(e3, pq_mastering_range, pq_mastering_min);
+ const V new_luminance =
+ Min(Set(df_, target_range_.second),
+ ZeroIfNegative(
+ Mul(Set(df_, 10000), TF_PQ().DisplayFromEncoded(df_, e4))));
+
+ const V ratio = Div(new_luminance, luminance);
+
+ const V normalizer = Set(df_, normalizer_);
+ for (V* const val : {red, green, blue}) {
+ *val = Mul(IfThenElse(Le(luminance, Set(df_, 1e-6f)), new_luminance,
+ Mul(*val, ratio)),
+ normalizer);
+ }
+ }
+
+ private:
+ V InvEOTF(const V luminance) const {
+ return TF_PQ().EncodedFromDisplay(df_,
+ Mul(luminance, Set(df_, 1. / 10000)));
+ }
+ float InvEOTF(const float luminance) const {
+ return TF_PQ().EncodedFromDisplay(luminance / 10000.0f);
+ }
+ V T(const V a) const {
+ const V ks = Set(df_, ks_);
+ const V inv_one_minus_ks = Set(df_, inv_one_minus_ks_);
+ return Mul(Sub(a, ks), inv_one_minus_ks);
+ }
+ V P(const V b) const {
+ const V t_b = T(b);
+ const V t_b_2 = Mul(t_b, t_b);
+ const V t_b_3 = Mul(t_b_2, t_b);
+ const V ks = Set(df_, ks_);
+ const V max_lum = Set(df_, max_lum_);
+ return MulAdd(
+ MulAdd(Set(df_, 2), t_b_3, MulAdd(Set(df_, -3), t_b_2, Set(df_, 1))),
+ ks,
+ MulAdd(Add(t_b_3, MulAdd(Set(df_, -2), t_b_2, t_b)),
+ Sub(Set(df_, 1), ks),
+ MulAdd(Set(df_, -2), t_b_3,
+ Mul(Mul(Set(df_, 3), t_b_2), max_lum))));
+ }
+
+ D df_;
+ const std::pair<float, float> source_range_;
+ const std::pair<float, float> target_range_;
+ const float red_Y_;
+ const float green_Y_;
+ const float blue_Y_;
+
+ const float pq_mastering_min_ = InvEOTF(source_range_.first);
+ const float pq_mastering_max_ = InvEOTF(source_range_.second);
+ const float pq_mastering_range_ = pq_mastering_max_ - pq_mastering_min_;
+ const float inv_pq_mastering_range_ = 1.0f / pq_mastering_range_;
+ // TODO(eustas): divide instead of inverse-multiply?
+ const float min_lum_ = (InvEOTF(target_range_.first) - pq_mastering_min_) *
+ inv_pq_mastering_range_;
+ // TODO(eustas): divide instead of inverse-multiply?
+ const float max_lum_ = (InvEOTF(target_range_.second) - pq_mastering_min_) *
+ inv_pq_mastering_range_;
+ const float ks_ = 1.5f * max_lum_ - 0.5f;
+ const float b_ = min_lum_;
+
+ const float inv_one_minus_ks_ = 1.0f / std::max(1e-6f, 1.0f - ks_);
+
+ const float normalizer_ = source_range_.second / target_range_.second;
+};
+
+class HlgOOTF {
+ public:
+ explicit HlgOOTF(float source_luminance, float target_luminance,
+ const float primaries_luminances[3])
+ : HlgOOTF(/*gamma=*/std::pow(
+ 1.111f, std::log2(target_luminance / source_luminance)),
+ primaries_luminances) {}
+
+ static HlgOOTF FromSceneLight(float display_luminance,
+ const float primaries_luminances[3]) {
+ return HlgOOTF(/*gamma=*/1.2f *
+ std::pow(1.111f, std::log2(display_luminance / 1000.f)),
+ primaries_luminances);
+ }
+
+ static HlgOOTF ToSceneLight(float display_luminance,
+ const float primaries_luminances[3]) {
+ return HlgOOTF(
+ /*gamma=*/(1 / 1.2f) *
+ std::pow(1.111f, -std::log2(display_luminance / 1000.f)),
+ primaries_luminances);
+ }
+
+ template <typename V>
+ void Apply(V* red, V* green, V* blue) const {
+ hwy::HWY_NAMESPACE::DFromV<V> df;
+ if (!apply_ootf_) return;
+ const V luminance =
+ MulAdd(Set(df, red_Y_), *red,
+ MulAdd(Set(df, green_Y_), *green, Mul(Set(df, blue_Y_), *blue)));
+ const V ratio =
+ Min(FastPowf(df, luminance, Set(df, exponent_)), Set(df, 1e9));
+ *red = Mul(*red, ratio);
+ *green = Mul(*green, ratio);
+ *blue = Mul(*blue, ratio);
+ }
+
+ bool WarrantsGamutMapping() const { return apply_ootf_ && exponent_ < 0; }
+
+ private:
+ explicit HlgOOTF(float gamma, const float luminances[3])
+ : exponent_(gamma - 1),
+ red_Y_(luminances[0]),
+ green_Y_(luminances[1]),
+ blue_Y_(luminances[2]) {}
+ const float exponent_;
+ const bool apply_ootf_ = exponent_ < -0.01f || 0.01f < exponent_;
+ const float red_Y_;
+ const float green_Y_;
+ const float blue_Y_;
+};
+
+template <typename V>
+void GamutMap(V* red, V* green, V* blue, const float primaries_luminances[3],
+ float preserve_saturation = 0.1f) {
+ hwy::HWY_NAMESPACE::DFromV<V> df;
+ const V luminance =
+ MulAdd(Set(df, primaries_luminances[0]), *red,
+ MulAdd(Set(df, primaries_luminances[1]), *green,
+ Mul(Set(df, primaries_luminances[2]), *blue)));
+
+ // Desaturate out-of-gamut pixels. This is done by mixing each pixel
+ // with just enough gray of the target luminance to make all
+ // components non-negative.
+ // - For saturation preservation, if a component is still larger than
+ // 1 then the pixel is normalized to have a maximum component of 1.
+ // That will reduce its luminance.
+ // - For luminance preservation, getting all components below 1 is
+ // done by mixing in yet more gray. That will desaturate it further.
+ V gray_mix_saturation = Zero(df);
+ V gray_mix_luminance = Zero(df);
+ for (const V* ch : {red, green, blue}) {
+ const V& val = *ch;
+ const V inv_val_minus_gray = Div(Set(df, 1), (Sub(val, luminance)));
+ gray_mix_saturation =
+ IfThenElse(Ge(val, luminance), gray_mix_saturation,
+ Max(gray_mix_saturation, Mul(val, inv_val_minus_gray)));
+ gray_mix_luminance =
+ Max(gray_mix_luminance,
+ IfThenElse(Le(val, luminance), gray_mix_saturation,
+ Mul(Sub(val, Set(df, 1)), inv_val_minus_gray)));
+ }
+ const V gray_mix = Clamp(
+ MulAdd(Set(df, preserve_saturation),
+ Sub(gray_mix_saturation, gray_mix_luminance), gray_mix_luminance),
+ Zero(df), Set(df, 1));
+ for (V* const val : {red, green, blue}) {
+ *val = MulAdd(gray_mix, Sub(luminance, *val), *val);
+ }
+ const V normalizer =
+ Div(Set(df, 1), Max(Set(df, 1), Max(*red, Max(*green, *blue))));
+ for (V* const val : {red, green, blue}) {
+ *val = Mul(*val, normalizer);
+ }
+}
+
+} // namespace
+// NOLINTNEXTLINE(google-readability-namespace-comments)
+} // namespace HWY_NAMESPACE
+} // namespace jxl
+HWY_AFTER_NAMESPACE();
+
+#endif // LIB_JXL_DEC_TONE_MAPPING_INL_H_
diff --git a/media/libjxl/src/lib/jxl/dec_transforms-inl.h b/media/libjxl/src/lib/jxl/dec_transforms-inl.h
index 37f3cb7a4d..075619b3b9 100644
--- a/media/libjxl/src/lib/jxl/dec_transforms-inl.h
+++ b/media/libjxl/src/lib/jxl/dec_transforms-inl.h
@@ -23,6 +23,9 @@ namespace jxl {
namespace HWY_NAMESPACE {
namespace {
+// These templates are not found via ADL.
+using hwy::HWY_NAMESPACE::MulAdd;
+
// Computes the lowest-frequency LF_ROWSxLF_COLS-sized square in output, which
// is a DCT_ROWS*DCT_COLS-sized DCT block, by doing a ROWS*COLS DCT on the
// input block.
diff --git a/media/libjxl/src/lib/jxl/dec_xyb-inl.h b/media/libjxl/src/lib/jxl/dec_xyb-inl.h
index 344b4cfe6c..a4f24cd123 100644
--- a/media/libjxl/src/lib/jxl/dec_xyb-inl.h
+++ b/media/libjxl/src/lib/jxl/dec_xyb-inl.h
@@ -21,7 +21,11 @@ namespace HWY_NAMESPACE {
namespace {
// These templates are not found via ADL.
+using hwy::HWY_NAMESPACE::Add;
using hwy::HWY_NAMESPACE::Broadcast;
+using hwy::HWY_NAMESPACE::Mul;
+using hwy::HWY_NAMESPACE::MulAdd;
+using hwy::HWY_NAMESPACE::Sub;
// Inverts the pixel-wise RGB->XYB conversion in OpsinDynamicsImage() (including
// the gamma mixing and simple gamma). Avoids clamping to [0, 1] - out of (sRGB)
@@ -48,18 +52,18 @@ HWY_INLINE HWY_MAYBE_UNUSED void XybToRgb(D d, const V opsin_x, const V opsin_y,
#endif
// Color space: XYB -> RGB
- auto gamma_r = opsin_y + opsin_x;
- auto gamma_g = opsin_y - opsin_x;
+ auto gamma_r = Add(opsin_y, opsin_x);
+ auto gamma_g = Sub(opsin_y, opsin_x);
auto gamma_b = opsin_b;
- gamma_r -= Set(d, opsin_params.opsin_biases_cbrt[0]);
- gamma_g -= Set(d, opsin_params.opsin_biases_cbrt[1]);
- gamma_b -= Set(d, opsin_params.opsin_biases_cbrt[2]);
+ gamma_r = Sub(gamma_r, Set(d, opsin_params.opsin_biases_cbrt[0]));
+ gamma_g = Sub(gamma_g, Set(d, opsin_params.opsin_biases_cbrt[1]));
+ gamma_b = Sub(gamma_b, Set(d, opsin_params.opsin_biases_cbrt[2]));
// Undo gamma compression: linear = gamma^3 for efficiency.
- const auto gamma_r2 = gamma_r * gamma_r;
- const auto gamma_g2 = gamma_g * gamma_g;
- const auto gamma_b2 = gamma_b * gamma_b;
+ const auto gamma_r2 = Mul(gamma_r, gamma_r);
+ const auto gamma_g2 = Mul(gamma_g, gamma_g);
+ const auto gamma_b2 = Mul(gamma_b, gamma_b);
const auto mixed_r = MulAdd(gamma_r2, gamma_r, neg_bias_r);
const auto mixed_g = MulAdd(gamma_g2, gamma_g, neg_bias_g);
const auto mixed_b = MulAdd(gamma_b2, gamma_b, neg_bias_b);
@@ -67,9 +71,10 @@ HWY_INLINE HWY_MAYBE_UNUSED void XybToRgb(D d, const V opsin_x, const V opsin_y,
const float* HWY_RESTRICT inverse_matrix = opsin_params.inverse_opsin_matrix;
// Unmix (multiply by 3x3 inverse_matrix)
- *linear_r = LoadDup128(d, &inverse_matrix[0 * 4]) * mixed_r;
- *linear_g = LoadDup128(d, &inverse_matrix[3 * 4]) * mixed_r;
- *linear_b = LoadDup128(d, &inverse_matrix[6 * 4]) * mixed_r;
+ // TODO(eustas): ref would be more readable than pointer
+ *linear_r = Mul(LoadDup128(d, &inverse_matrix[0 * 4]), mixed_r);
+ *linear_g = Mul(LoadDup128(d, &inverse_matrix[3 * 4]), mixed_r);
+ *linear_b = Mul(LoadDup128(d, &inverse_matrix[6 * 4]), mixed_r);
*linear_r = MulAdd(LoadDup128(d, &inverse_matrix[1 * 4]), mixed_g, *linear_r);
*linear_g = MulAdd(LoadDup128(d, &inverse_matrix[4 * 4]), mixed_g, *linear_g);
*linear_b = MulAdd(LoadDup128(d, &inverse_matrix[7 * 4]), mixed_g, *linear_b);
diff --git a/media/libjxl/src/lib/jxl/dec_xyb.cc b/media/libjxl/src/lib/jxl/dec_xyb.cc
index 313abd6a81..ef4088f101 100644
--- a/media/libjxl/src/lib/jxl/dec_xyb.cc
+++ b/media/libjxl/src/lib/jxl/dec_xyb.cc
@@ -28,6 +28,7 @@ namespace HWY_NAMESPACE {
// These templates are not found via ADL.
using hwy::HWY_NAMESPACE::Broadcast;
+using hwy::HWY_NAMESPACE::MulAdd;
void OpsinToLinearInplace(Image3F* JXL_RESTRICT inout, ThreadPool* pool,
const OpsinParams& opsin_params) {
@@ -135,12 +136,12 @@ void YcbcrToRgb(const Image3F& ycbcr, Image3F* rgb, const Rect& rect) {
float* g_row = rect.PlaneRow(rgb, 1, y);
float* b_row = rect.PlaneRow(rgb, 2, y);
for (size_t x = 0; x < xsize; x += S) {
- const auto y_vec = Load(df, y_row + x) + c128;
+ const auto y_vec = Add(Load(df, y_row + x), c128);
const auto cb_vec = Load(df, cb_row + x);
const auto cr_vec = Load(df, cr_row + x);
- const auto r_vec = crcr * cr_vec + y_vec;
- const auto g_vec = cgcr * cr_vec + cgcb * cb_vec + y_vec;
- const auto b_vec = cbcb * cb_vec + y_vec;
+ const auto r_vec = MulAdd(crcr, cr_vec, y_vec);
+ const auto g_vec = MulAdd(cgcr, cr_vec, MulAdd(cgcb, cb_vec, y_vec));
+ const auto b_vec = MulAdd(cbcb, cb_vec, y_vec);
Store(r_vec, df, r_row + x);
Store(g_vec, df, g_row + x);
Store(b_vec, df, b_row + x);
@@ -196,86 +197,31 @@ void OpsinParams::Init(float intensity_target) {
}
}
-Status OutputEncodingInfo::Set(const CodecMetadata& metadata,
- const ColorEncoding& default_enc) {
- const auto& im = metadata.transform_data.opsin_inverse_matrix;
- float inverse_matrix[9];
- memcpy(inverse_matrix, im.inverse_matrix, sizeof(inverse_matrix));
- intensity_target = metadata.m.IntensityTarget();
- if (metadata.m.xyb_encoded) {
- const auto& orig_color_encoding = metadata.m.color_encoding;
- color_encoding = default_enc;
- // Figure out if we can output to this color encoding.
- do {
- if (!orig_color_encoding.HaveFields()) break;
- // TODO(veluca): keep in sync with dec_reconstruct.cc
- if (!orig_color_encoding.tf.IsPQ() && !orig_color_encoding.tf.IsSRGB() &&
- !orig_color_encoding.tf.IsGamma() &&
- !orig_color_encoding.tf.IsLinear() &&
- !orig_color_encoding.tf.IsHLG() && !orig_color_encoding.tf.IsDCI() &&
- !orig_color_encoding.tf.Is709()) {
- break;
- }
- if (orig_color_encoding.tf.IsGamma()) {
- inverse_gamma = orig_color_encoding.tf.GetGamma();
- }
- if (orig_color_encoding.tf.IsDCI()) {
- inverse_gamma = 1.0f / 2.6f;
- }
- if (orig_color_encoding.IsGray() &&
- orig_color_encoding.white_point != WhitePoint::kD65) {
- // TODO(veluca): figure out what should happen here.
- break;
- }
-
- if ((orig_color_encoding.primaries != Primaries::kSRGB ||
- orig_color_encoding.white_point != WhitePoint::kD65) &&
- !orig_color_encoding.IsGray()) {
- all_default_opsin = false;
- float srgb_to_xyzd50[9];
- const auto& srgb = ColorEncoding::SRGB(/*is_gray=*/false);
- JXL_CHECK(PrimariesToXYZD50(
- srgb.GetPrimaries().r.x, srgb.GetPrimaries().r.y,
- srgb.GetPrimaries().g.x, srgb.GetPrimaries().g.y,
- srgb.GetPrimaries().b.x, srgb.GetPrimaries().b.y,
- srgb.GetWhitePoint().x, srgb.GetWhitePoint().y, srgb_to_xyzd50));
- float original_to_xyz[3][3];
- JXL_RETURN_IF_ERROR(PrimariesToXYZ(
- orig_color_encoding.GetPrimaries().r.x,
- orig_color_encoding.GetPrimaries().r.y,
- orig_color_encoding.GetPrimaries().g.x,
- orig_color_encoding.GetPrimaries().g.y,
- orig_color_encoding.GetPrimaries().b.x,
- orig_color_encoding.GetPrimaries().b.y,
- orig_color_encoding.GetWhitePoint().x,
- orig_color_encoding.GetWhitePoint().y, &original_to_xyz[0][0]));
- memcpy(luminances, original_to_xyz[1], sizeof luminances);
- float adapt_to_d50[9];
- JXL_RETURN_IF_ERROR(AdaptToXYZD50(orig_color_encoding.GetWhitePoint().x,
- orig_color_encoding.GetWhitePoint().y,
- adapt_to_d50));
- float xyzd50_to_original[9];
- MatMul(adapt_to_d50, &original_to_xyz[0][0], 3, 3, 3,
- xyzd50_to_original);
- JXL_RETURN_IF_ERROR(Inv3x3Matrix(xyzd50_to_original));
- float srgb_to_original[9];
- MatMul(xyzd50_to_original, srgb_to_xyzd50, 3, 3, 3, srgb_to_original);
- MatMul(srgb_to_original, im.inverse_matrix, 3, 3, 3, inverse_matrix);
- }
- color_encoding = orig_color_encoding;
- color_encoding_is_original = true;
- if (color_encoding.tf.IsPQ()) {
- intensity_target = 10000;
- }
- } while (false);
- } else {
- color_encoding = metadata.m.color_encoding;
+bool CanOutputToColorEncoding(const ColorEncoding& c_desired) {
+ if (!c_desired.HaveFields()) {
+ return false;
}
- if (std::abs(intensity_target - 255.0) > 0.1f || !im.all_default) {
- all_default_opsin = false;
+ // TODO(veluca): keep in sync with dec_reconstruct.cc
+ if (!c_desired.tf.IsPQ() && !c_desired.tf.IsSRGB() &&
+ !c_desired.tf.IsGamma() && !c_desired.tf.IsLinear() &&
+ !c_desired.tf.IsHLG() && !c_desired.tf.IsDCI() && !c_desired.tf.Is709()) {
+ return false;
}
- InitSIMDInverseMatrix(inverse_matrix, opsin_params.inverse_opsin_matrix,
- intensity_target);
+ if (c_desired.IsGray() && c_desired.white_point != WhitePoint::kD65) {
+ // TODO(veluca): figure out what should happen here.
+ return false;
+ }
+ return true;
+}
+
+Status OutputEncodingInfo::SetFromMetadata(const CodecMetadata& metadata) {
+ orig_color_encoding = metadata.m.color_encoding;
+ orig_intensity_target = metadata.m.IntensityTarget();
+ desired_intensity_target = orig_intensity_target;
+ const auto& im = metadata.transform_data.opsin_inverse_matrix;
+ memcpy(orig_inverse_matrix, im.inverse_matrix, sizeof(orig_inverse_matrix));
+ default_transform = im.all_default;
+ xyb_encoded = metadata.m.xyb_encoded;
std::copy(std::begin(im.opsin_biases), std::end(im.opsin_biases),
opsin_params.opsin_biases);
for (int i = 0; i < 3; ++i) {
@@ -284,6 +230,92 @@ Status OutputEncodingInfo::Set(const CodecMetadata& metadata,
opsin_params.opsin_biases_cbrt[3] = opsin_params.opsin_biases[3] = 1;
std::copy(std::begin(im.quant_biases), std::end(im.quant_biases),
opsin_params.quant_biases);
+ bool orig_ok = CanOutputToColorEncoding(orig_color_encoding);
+ bool orig_grey = orig_color_encoding.IsGray();
+ return SetColorEncoding(!xyb_encoded || orig_ok
+ ? orig_color_encoding
+ : ColorEncoding::LinearSRGB(orig_grey));
+}
+
+Status OutputEncodingInfo::MaybeSetColorEncoding(
+ const ColorEncoding& c_desired) {
+ if (!xyb_encoded || !CanOutputToColorEncoding(c_desired)) {
+ return false;
+ }
+ return SetColorEncoding(c_desired);
+}
+
+Status OutputEncodingInfo::SetColorEncoding(const ColorEncoding& c_desired) {
+ color_encoding = c_desired;
+ color_encoding_is_original = orig_color_encoding.SameColorEncoding(c_desired);
+
+ // Compute the opsin inverse matrix and luminances based on primaries and
+ // white point.
+ float inverse_matrix[9];
+ bool inverse_matrix_is_default = default_transform;
+ memcpy(inverse_matrix, orig_inverse_matrix, sizeof(inverse_matrix));
+ constexpr float kSRGBLuminances[3] = {0.2126, 0.7152, 0.0722};
+ memcpy(luminances, kSRGBLuminances, sizeof(luminances));
+ if ((c_desired.primaries != Primaries::kSRGB ||
+ c_desired.white_point != WhitePoint::kD65) &&
+ !c_desired.IsGray()) {
+ float srgb_to_xyzd50[9];
+ const auto& srgb = ColorEncoding::SRGB(/*is_gray=*/false);
+ JXL_CHECK(PrimariesToXYZD50(
+ srgb.GetPrimaries().r.x, srgb.GetPrimaries().r.y,
+ srgb.GetPrimaries().g.x, srgb.GetPrimaries().g.y,
+ srgb.GetPrimaries().b.x, srgb.GetPrimaries().b.y,
+ srgb.GetWhitePoint().x, srgb.GetWhitePoint().y, srgb_to_xyzd50));
+ float original_to_xyz[3][3];
+ JXL_RETURN_IF_ERROR(PrimariesToXYZ(
+ c_desired.GetPrimaries().r.x, c_desired.GetPrimaries().r.y,
+ c_desired.GetPrimaries().g.x, c_desired.GetPrimaries().g.y,
+ c_desired.GetPrimaries().b.x, c_desired.GetPrimaries().b.y,
+ c_desired.GetWhitePoint().x, c_desired.GetWhitePoint().y,
+ &original_to_xyz[0][0]));
+ memcpy(luminances, original_to_xyz[1], sizeof luminances);
+ if (xyb_encoded) {
+ float adapt_to_d50[9];
+ JXL_RETURN_IF_ERROR(AdaptToXYZD50(c_desired.GetWhitePoint().x,
+ c_desired.GetWhitePoint().y,
+ adapt_to_d50));
+ float xyzd50_to_original[9];
+ MatMul(adapt_to_d50, &original_to_xyz[0][0], 3, 3, 3, xyzd50_to_original);
+ JXL_RETURN_IF_ERROR(Inv3x3Matrix(xyzd50_to_original));
+ float srgb_to_original[9];
+ MatMul(xyzd50_to_original, srgb_to_xyzd50, 3, 3, 3, srgb_to_original);
+ MatMul(srgb_to_original, orig_inverse_matrix, 3, 3, 3, inverse_matrix);
+ inverse_matrix_is_default = false;
+ }
+ }
+
+ if (c_desired.IsGray()) {
+ float tmp_inv_matrix[9];
+ memcpy(tmp_inv_matrix, inverse_matrix, sizeof(inverse_matrix));
+ float srgb_to_luma[9];
+ memcpy(&srgb_to_luma[0], luminances, sizeof(luminances));
+ memcpy(&srgb_to_luma[3], luminances, sizeof(luminances));
+ memcpy(&srgb_to_luma[6], luminances, sizeof(luminances));
+ MatMul(srgb_to_luma, tmp_inv_matrix, 3, 3, 3, inverse_matrix);
+ }
+
+ // The internal XYB color space uses absolute luminance, so we scale back the
+ // opsin inverse matrix to relative luminance where 1.0 corresponds to the
+ // original intensity target, or to absolute luminance for PQ, where 1.0
+ // corresponds to 10000 nits.
+ if (xyb_encoded) {
+ float intensity_target =
+ (c_desired.tf.IsPQ() ? 10000 : orig_intensity_target);
+ InitSIMDInverseMatrix(inverse_matrix, opsin_params.inverse_opsin_matrix,
+ intensity_target);
+ all_default_opsin = (std::abs(intensity_target - 255.0) <= 0.1f &&
+ inverse_matrix_is_default);
+ }
+
+ // Set the inverse gamma based on color space transfer function.
+ inverse_gamma = (c_desired.tf.IsGamma() ? c_desired.tf.GetGamma()
+ : c_desired.tf.IsDCI() ? 1.0f / 2.6f
+ : 1.0);
return true;
}
diff --git a/media/libjxl/src/lib/jxl/dec_xyb.h b/media/libjxl/src/lib/jxl/dec_xyb.h
index 21a46fcac9..ebaae9a176 100644
--- a/media/libjxl/src/lib/jxl/dec_xyb.h
+++ b/media/libjxl/src/lib/jxl/dec_xyb.h
@@ -29,23 +29,39 @@ struct OpsinParams {
};
struct OutputEncodingInfo {
+ //
+ // Fields depending only on image metadata
+ //
+ ColorEncoding orig_color_encoding;
+ // Used for the HLG OOTF and PQ tone mapping.
+ float orig_intensity_target;
+ // Opsin inverse matrix taken from the metadata.
+ float orig_inverse_matrix[9];
+ bool default_transform;
+ bool xyb_encoded;
+ //
+ // Fields depending on output color encoding
+ //
ColorEncoding color_encoding;
- // Used for Gamma and DCI transfer functions.
- float inverse_gamma;
+ bool color_encoding_is_original;
// Contains an opsin matrix that converts to the primaries of the output
// encoding.
OpsinParams opsin_params;
- // default_enc is used for xyb encoded image with ICC profile, in other
- // cases it has no effect. Use linear sRGB or grayscale if ICC profile is
- // not matched (not parsed or no matching ColorEncoding exists)
- Status Set(const CodecMetadata& metadata, const ColorEncoding& default_enc);
- bool all_default_opsin = true;
- bool color_encoding_is_original = false;
- // Luminances of color_encoding's primaries, used for the HLG inverse OOTF.
+ bool all_default_opsin;
+ // Used for Gamma and DCI transfer functions.
+ float inverse_gamma;
+ // Luminances of color_encoding's primaries, used for the HLG inverse OOTF and
+ // for PQ tone mapping.
// Default to sRGB's.
- float luminances[3] = {0.2126, 0.7152, 0.0722};
- // Also used for the HLG inverse OOTF.
- float intensity_target;
+ float luminances[3];
+ // Used for the HLG inverse OOTF and PQ tone mapping.
+ float desired_intensity_target;
+
+ Status SetFromMetadata(const CodecMetadata& metadata);
+ Status MaybeSetColorEncoding(const ColorEncoding& c_desired);
+
+ private:
+ Status SetColorEncoding(const ColorEncoding& c_desired);
};
// Converts `inout` (not padded) from opsin to linear sRGB in-place. Called from
diff --git a/media/libjxl/src/lib/jxl/decode.cc b/media/libjxl/src/lib/jxl/decode.cc
index e934f7ea0e..1a0facce12 100644
--- a/media/libjxl/src/lib/jxl/decode.cc
+++ b/media/libjxl/src/lib/jxl/decode.cc
@@ -5,6 +5,7 @@
#include "jxl/decode.h"
+#include "jxl/types.h"
#include "lib/jxl/base/byte_order.h"
#include "lib/jxl/base/span.h"
#include "lib/jxl/base/status.h"
@@ -14,6 +15,7 @@
#include "lib/jxl/dec_modular.h"
#include "lib/jxl/decode_to_jpeg.h"
#include "lib/jxl/fields.h"
+#include "lib/jxl/frame_header.h"
#include "lib/jxl/headers.h"
#include "lib/jxl/icc_codec.h"
#include "lib/jxl/image_bundle.h"
@@ -32,16 +34,6 @@ bool OutOfBounds(size_t a, size_t b, size_t size) {
return false;
}
-// Checks if a + b + c > size, taking possible integer overflow into account.
-bool OutOfBounds(size_t a, size_t b, size_t c, size_t size) {
- size_t pos = a + b;
- if (pos < b) return true; // overflow happened
- pos += c;
- if (pos < c) return true; // overflow happened
- if (pos > size) return true;
- return false;
-}
-
bool SumOverflows(size_t a, size_t b, size_t c) {
size_t sum = a + b;
if (sum < b) return true;
@@ -165,8 +157,7 @@ enum class DecoderStage : uint32_t {
};
enum class FrameStage : uint32_t {
- kHeader, // Must parse frame header. dec->frame_start must be set up
- // correctly already.
+ kHeader, // Must parse frame header.
kTOC, // Must parse TOC
kFull, // Must parse full pixels
kFullOutput, // Must output full pixels
@@ -189,105 +180,6 @@ enum class JpegReconStage : uint32_t {
kFinished, // JPEG reconstruction fully handled
};
-// Manages the sections for the FrameDecoder based on input bytes received.
-struct Sections {
- // sections_begin = position in the frame where the sections begin, after
- // the frame header and TOC, so sections_begin = sum of frame header size and
- // TOC size.
- Sections(jxl::FrameDecoder* frame_dec, size_t frame_size,
- size_t sections_begin)
- : frame_dec_(frame_dec),
- frame_size_(frame_size),
- sections_begin_(sections_begin) {}
-
- Sections(const Sections&) = delete;
- Sections& operator=(const Sections&) = delete;
- Sections(Sections&&) = delete;
- Sections& operator=(Sections&&) = delete;
-
- ~Sections() {
- // Avoid memory leaks if the JXL decoder quits early and doesn't end up
- // calling CloseInput().
- CloseInput();
- }
-
- // frame_dec_ must have been Inited already, but not yet done ProcessSections.
- JxlDecoderStatus Init() {
- section_received.resize(frame_dec_->NumSections(), 0);
-
- const auto& offsets = frame_dec_->SectionOffsets();
- const auto& sizes = frame_dec_->SectionSizes();
-
- // Ensure none of the sums of section offset and size overflow.
- for (size_t i = 0; i < frame_dec_->NumSections(); i++) {
- if (OutOfBounds(sections_begin_, offsets[i], sizes[i], frame_size_)) {
- return JXL_API_ERROR("section out of bounds");
- }
- }
-
- return JXL_DEC_SUCCESS;
- }
-
- // Sets the input data for the frame. The frame pointer must point to the
- // beginning of the frame, size is the amount of bytes gotten so far and
- // should increase with next calls until the full frame is loaded.
- // TODO(lode): allow caller to provide only later chunks of memory when
- // earlier sections are fully processed already.
- void SetInput(const uint8_t* frame, size_t size) {
- const auto& offsets = frame_dec_->SectionOffsets();
- const auto& sizes = frame_dec_->SectionSizes();
-
- for (size_t i = 0; i < frame_dec_->NumSections(); i++) {
- if (section_received[i]) continue;
- if (!OutOfBounds(sections_begin_, offsets[i], sizes[i], size)) {
- section_received[i] = 1;
- section_info.emplace_back(jxl::FrameDecoder::SectionInfo{nullptr, i});
- section_status.emplace_back();
- }
- }
- // Reset all the bitreaders, because the address of the frame pointer may
- // change, even if it always represents the same frame start.
- for (size_t i = 0; i < section_info.size(); i++) {
- size_t id = section_info[i].id;
- JXL_ASSERT(section_info[i].br == nullptr);
- section_info[i].br = new jxl::BitReader(jxl::Span<const uint8_t>(
- frame + sections_begin_ + offsets[id], sizes[id]));
- }
- }
-
- JxlDecoderStatus CloseInput() {
- bool out_of_bounds = false;
- for (size_t i = 0; i < section_info.size(); i++) {
- if (!section_info[i].br) continue;
- if (!section_info[i].br->AllReadsWithinBounds()) {
- // Mark out of bounds section, but keep closing and deleting the next
- // ones as well.
- out_of_bounds = true;
- }
- JXL_ASSERT(section_info[i].br->Close());
- delete section_info[i].br;
- section_info[i].br = nullptr;
- }
- if (out_of_bounds) {
- // If any bit reader indicates out of bounds, it's an error, not just
- // needing more input, since we ensure only bit readers containing
- // a complete section are provided to the FrameDecoder.
- return JXL_API_ERROR("frame out of bounds");
- }
- return JXL_DEC_SUCCESS;
- }
-
- // Not managed by us.
- jxl::FrameDecoder* frame_dec_;
-
- size_t frame_size_;
- size_t sections_begin_;
-
- std::vector<jxl::FrameDecoder::SectionInfo> section_info;
- std::vector<jxl::FrameDecoder::SectionStatus> section_status;
- std::vector<char> section_received;
-};
-
/*
Given list of frame references to storage slots, and storage slots in which this
frame is saved, computes which frames are required to decode the frame at the
@@ -374,6 +266,50 @@ struct ExtraChannelOutput {
} // namespace
+namespace jxl {
+
+typedef struct JxlDecoderFrameIndexBoxEntryStruct {
+ // OFFi: offset of start byte of this frame compared to start
+ // byte of previous frame from this index in the JPEG XL codestream. For the
+ // first frame, this is the offset from the first byte of the JPEG XL
+ // codestream.
+ uint64_t OFFi;
+ // Ti: duration in ticks between the start of this frame and
+ // the start of the next frame in the index. If this is the last frame in the
+ // index, this is the duration in ticks between the start of this frame and
+ // the end of the stream. A tick lasts TNUM / TDEN seconds.
+ uint32_t Ti;
+ // Fi: amount of frames the next frame in the index occurs
+ // after this frame. If this is the last frame in the index, this is the
+ // amount of frames after this frame in the remainder of the stream. Only
+ // frames that are presented by the decoder are counted for this purpose, this
+ // excludes frames that are not intended for display but for compositing with
+ // other frames, such as frames that aren't the last frame with a duration of
+ // 0 ticks.
+ uint32_t Fi;
+} JxlDecoderFrameIndexBoxEntry;
+
+typedef struct JxlDecoderFrameIndexBoxStruct {
+ int64_t NF() const { return entries.size(); }
+ int32_t TNUM = 1;
+ int32_t TDEN = 1000;
+
+ std::vector<JxlDecoderFrameIndexBoxEntry> entries;
+
+ // That way we can ensure that every index box will have the first frame.
+ // If the API user decides to mark it as an indexed frame, we call
+ // the AddFrame again, this time with requested.
+ void AddFrame(uint64_t OFFi, uint32_t Ti, uint32_t Fi) {
+ JxlDecoderFrameIndexBoxEntry e;
+ e.OFFi = OFFi;
+ e.Ti = Ti;
+ e.Fi = Fi;
+ entries.push_back(e);
+ }
+} JxlDecoderFrameIndexBox;
+
+} // namespace jxl
+
// NOLINTNEXTLINE(clang-analyzer-optin.performance.Padding)
struct JxlDecoderStruct {
JxlDecoderStruct() = default;
@@ -385,22 +321,23 @@ struct JxlDecoderStruct {
// Status of progression, internal.
bool got_signature;
- bool first_codestream_seen;
// Indicates we know that we've seen the last codestream box: either this
// was a jxlc box, or a jxlp box that has its index indicated as last by
// having its most significant bit set, or no boxes are used at all. This
// does not indicate the full codestream has already been seen, only the
// last box of it has been initiated.
bool last_codestream_seen;
+ bool got_codestream_signature;
bool got_basic_info;
- size_t header_except_icc_bits = 0; // To skip everything before ICC.
+ bool got_transform_data; // To skip everything before ICC.
bool got_all_headers; // Codestream metadata headers.
bool post_headers; // Already decoding pixels.
jxl::ICCReader icc_reader;
-
+ jxl::JxlDecoderFrameIndexBox frame_index_box;
// This means either we actually got the preview image, or determined we
// cannot get it or there is none.
bool got_preview_image;
+ bool preview_frame;
// Position of next_in in the original file including box format if present
// (as opposed to position in the codestream)
@@ -410,6 +347,7 @@ struct JxlDecoderStruct {
size_t box_contents_end;
size_t box_contents_size;
size_t box_size;
+ size_t header_size;
// Either a final box that runs until EOF, or the case of no container format
// at all.
bool box_contents_unbounded;
@@ -436,8 +374,10 @@ struct JxlDecoderStruct {
// Settings
bool keep_orientation;
+ bool unpremul_alpha;
bool render_spotcolors;
bool coalescing;
+ float desired_intensity_target;
// Bitfield, for which informative events (JXL_DEC_BASIC_INFO, etc...) the
// decoder returns a status. By default, do not return for any of the events,
@@ -451,11 +391,19 @@ struct JxlDecoderStruct {
bool have_container;
size_t box_count;
+ // The level of progressive detail in frame decoding.
+ JxlProgressiveDetail prog_detail = kDC;
+ // The progressive detail of the current frame.
+ JxlProgressiveDetail frame_prog_detail;
+ // The intended downsampling ratio for the current progression step.
+ size_t downsampling_target;
+
// Whether the preview out buffer was set. It is possible for the buffer to
// be nullptr and buffer_set to be true, indicating it was deliberately
// set to nullptr.
bool preview_out_buffer_set;
// Idem for the image buffer.
+ // Set to true if either an image out buffer or an image out callback was set.
bool image_out_buffer_set;
// Owned by the caller, buffers for DC image and full resolution images
@@ -482,13 +430,15 @@ struct JxlDecoderStruct {
std::vector<ExtraChannelOutput> extra_channel_output;
jxl::CodecMetadata metadata;
+ // Same as metadata.m, except for the color_encoding, which is set to the
+ // output encoding.
+ jxl::ImageMetadata image_metadata;
std::unique_ptr<jxl::ImageBundle> ib;
- // ColorEncoding to use for xyb encoded image with ICC profile.
- jxl::ColorEncoding default_enc;
std::unique_ptr<jxl::PassesDecoderState> passes_state;
std::unique_ptr<jxl::FrameDecoder> frame_dec;
- std::unique_ptr<Sections> sections;
+ size_t next_section;
+ std::vector<char> section_processed;
// The FrameDecoder is initialized, and not yet finalized
bool frame_dec_in_progress;
@@ -497,11 +447,9 @@ struct JxlDecoderStruct {
// that is, the displayed frame.
std::unique_ptr<jxl::FrameHeader> frame_header;
- // Start of the current frame being processed, as offset from the beginning of
- // the codestream.
- size_t frame_start;
- size_t frame_size;
+ size_t remaining_frame_size;
FrameStage frame_stage;
+ bool dc_frame_progression_done;
// The currently processed frame is the last of the current composite still,
// and so must be returned as pixels
bool is_last_of_still;
@@ -537,17 +485,21 @@ struct JxlDecoderStruct {
// vector, it must be treated as a required frame.
std::vector<char> frame_required;
- // Codestream input data is stored here, when the decoder takes in and stores
- // the user input bytes. If the decoder does not do that (e.g. in one-shot
- // case), this field is unused.
- // TODO(lode): avoid needing this field once the C++ decoder doesn't need
- // all bytes at once, to save memory. Find alternative to std::vector doubling
- // strategy to prevent some memory usage.
+ // Codestream input data is copied here temporarily when the decoder needs
+ // more input bytes to process the next part of the stream. We copy the input
+ // data in order to be able to release it all through the API it when
+ // returning JXL_DEC_NEED_MORE_INPUT.
std::vector<uint8_t> codestream_copy;
- // Position in the actual codestream, which codestream_copy.begin() points to.
- // Non-zero once earlier parts of the codestream vector have been erased.
- // TODO(lode): use this variable to allow pruning codestream_copy
+ // Number of bytes at the end of codestream_copy that were not yet consumed
+ // by calling AdvanceInput().
+ size_t codestream_unconsumed;
+ // Position in the codestream_copy vector that the decoder already finished
+ // processing. It can be greater than the current size of codestream_copy in
+ // case where the decoder skips some parts of the frame that were not yet
+ // provided.
size_t codestream_pos;
+ // Number of bits after codestream_pos that were already processed.
+ size_t codestream_bits_ahead;
BoxStage box_stage;
@@ -582,11 +534,87 @@ struct JxlDecoderStruct {
bool input_closed;
void AdvanceInput(size_t size) {
+ JXL_DASSERT(avail_in >= size);
next_in += size;
avail_in -= size;
file_pos += size;
}
+ size_t AvailableCodestream() const {
+ size_t avail_codestream = avail_in;
+ if (!box_contents_unbounded) {
+ avail_codestream =
+ std::min<size_t>(avail_codestream, box_contents_end - file_pos);
+ }
+ return avail_codestream;
+ }
+
+ void AdvanceCodestream(size_t size) {
+ size_t avail_codestream = AvailableCodestream();
+ if (codestream_copy.empty()) {
+ if (size <= avail_codestream) {
+ AdvanceInput(size);
+ } else {
+ codestream_pos = size - avail_codestream;
+ AdvanceInput(avail_codestream);
+ }
+ } else {
+ codestream_pos += size;
+ if (codestream_pos + codestream_unconsumed >= codestream_copy.size()) {
+ size_t advance = std::min(
+ codestream_unconsumed,
+ codestream_unconsumed + codestream_pos - codestream_copy.size());
+ AdvanceInput(advance);
+ codestream_pos -= std::min(codestream_pos, codestream_copy.size());
+ codestream_unconsumed = 0;
+ codestream_copy.clear();
+ }
+ }
+ }
+
+ JxlDecoderStatus RequestMoreInput() {
+ if (codestream_copy.empty()) {
+ size_t avail_codestream = AvailableCodestream();
+ codestream_copy.insert(codestream_copy.end(), next_in,
+ next_in + avail_codestream);
+ AdvanceInput(avail_codestream);
+ } else {
+ AdvanceInput(codestream_unconsumed);
+ codestream_unconsumed = 0;
+ }
+ return JXL_DEC_NEED_MORE_INPUT;
+ }
+
+ JxlDecoderStatus GetCodestreamInput(jxl::Span<const uint8_t>* span) {
+ if (codestream_copy.empty() && codestream_pos > 0) {
+ size_t avail_codestream = AvailableCodestream();
+ size_t skip = std::min<size_t>(codestream_pos, avail_codestream);
+ AdvanceInput(skip);
+ codestream_pos -= skip;
+ if (codestream_pos > 0) {
+ return RequestMoreInput();
+ }
+ }
+ JXL_ASSERT(codestream_pos <= codestream_copy.size());
+ JXL_ASSERT(codestream_unconsumed <= codestream_copy.size());
+ size_t avail_codestream = AvailableCodestream();
+ if (codestream_copy.empty()) {
+ if (avail_codestream == 0) {
+ return RequestMoreInput();
+ }
+ *span = jxl::Span<const uint8_t>(next_in, avail_codestream);
+ return JXL_DEC_SUCCESS;
+ } else {
+ codestream_copy.insert(codestream_copy.end(),
+ next_in + codestream_unconsumed,
+ next_in + avail_codestream);
+ codestream_unconsumed = avail_codestream;
+ *span = jxl::Span<const uint8_t>(codestream_copy.data() + codestream_pos,
+ codestream_copy.size() - codestream_pos);
+ return JXL_DEC_SUCCESS;
+ }
+ }
+
// Whether the decoder can use more codestream input for a purpose it needs.
// This returns false if the user didn't subscribe to any events that
// require the codestream (e.g. only subscribed to metadata boxes), or all
@@ -637,19 +665,21 @@ JxlDecoderStatus JxlDecoderDefaultPixelFormat(const JxlDecoder* dec,
void JxlDecoderRewindDecodingState(JxlDecoder* dec) {
dec->stage = DecoderStage::kInited;
dec->got_signature = false;
- dec->first_codestream_seen = false;
dec->last_codestream_seen = false;
+ dec->got_codestream_signature = false;
dec->got_basic_info = false;
- dec->header_except_icc_bits = 0;
+ dec->got_transform_data = false;
dec->got_all_headers = false;
dec->post_headers = false;
dec->icc_reader.Reset();
dec->got_preview_image = false;
+ dec->preview_frame = false;
dec->file_pos = 0;
dec->box_contents_begin = 0;
dec->box_contents_end = 0;
dec->box_contents_size = 0;
dec->box_size = 0;
+ dec->header_size = 0;
dec->box_contents_unbounded = false;
memset(dec->box_type, 0, sizeof(dec->box_type));
memset(dec->box_decoded_type, 0, sizeof(dec->box_decoded_type));
@@ -674,6 +704,7 @@ void JxlDecoderRewindDecodingState(JxlDecoder* dec) {
dec->basic_info_size_hint = InitialBasicInfoSizeHint();
dec->have_container = 0;
dec->box_count = 0;
+ dec->downsampling_target = 8;
dec->preview_out_buffer_set = false;
dec->image_out_buffer_set = false;
dec->preview_out_buffer = nullptr;
@@ -692,18 +723,22 @@ void JxlDecoderRewindDecodingState(JxlDecoder* dec) {
dec->passes_state.reset(nullptr);
dec->frame_dec.reset(nullptr);
- dec->sections.reset(nullptr);
+ dec->next_section = 0;
+ dec->section_processed.clear();
dec->frame_dec_in_progress = false;
dec->ib.reset();
dec->metadata = jxl::CodecMetadata();
+ dec->image_metadata = dec->metadata.m;
dec->frame_header.reset(new jxl::FrameHeader(&dec->metadata));
dec->codestream_copy.clear();
+ dec->codestream_unconsumed = 0;
+ dec->codestream_pos = 0;
+ dec->codestream_bits_ahead = 0;
dec->frame_stage = FrameStage::kHeader;
- dec->frame_start = 0;
- dec->frame_size = 0;
+ dec->remaining_frame_size = 0;
dec->is_last_of_still = false;
dec->is_last_total = false;
dec->skip_frames = 0;
@@ -717,8 +752,10 @@ void JxlDecoderReset(JxlDecoder* dec) {
dec->thread_pool.reset();
dec->keep_orientation = false;
+ dec->unpremul_alpha = false;
dec->render_spotcolors = true;
dec->coalescing = true;
+ dec->desired_intensity_target = 0;
dec->orig_events_wanted = 0;
dec->frame_references.clear();
dec->frame_saved_as.clear();
@@ -794,6 +831,19 @@ void JxlDecoderSkipFrames(JxlDecoder* dec, size_t amount) {
}
}
+JxlDecoderStatus JxlDecoderSkipCurrentFrame(JxlDecoder* dec) {
+ if (!dec->frame_dec || !dec->frame_dec_in_progress) {
+ return JXL_DEC_ERROR;
+ }
+ dec->frame_stage = FrameStage::kHeader;
+ dec->AdvanceCodestream(dec->remaining_frame_size);
+ dec->frame_dec_in_progress = false;
+ if (dec->is_last_of_still) {
+ dec->image_out_buffer_set = false;
+ }
+ return JXL_DEC_SUCCESS;
+}
+
JXL_EXPORT JxlDecoderStatus
JxlDecoderSetParallelRunner(JxlDecoder* dec, JxlParallelRunner parallel_runner,
void* parallel_runner_opaque) {
@@ -831,6 +881,15 @@ JxlDecoderStatus JxlDecoderSetKeepOrientation(JxlDecoder* dec,
return JXL_DEC_SUCCESS;
}
+JxlDecoderStatus JxlDecoderSetUnpremultiplyAlpha(JxlDecoder* dec,
+ JXL_BOOL unpremul_alpha) {
+ if (dec->stage != DecoderStage::kInited) {
+ return JXL_API_ERROR("Must set unpremul_alpha option before starting");
+ }
+ dec->unpremul_alpha = !!unpremul_alpha;
+ return JXL_DEC_SUCCESS;
+}
+
JxlDecoderStatus JxlDecoderSetRenderSpotcolors(JxlDecoder* dec,
JXL_BOOL render_spotcolors) {
if (dec->stage != DecoderStage::kInited) {
@@ -887,10 +946,10 @@ bool CanRead(Span<const uint8_t> data, BitReader* reader, T* JXL_RESTRICT t) {
// Returns JXL_DEC_SUCCESS if the full bundle was successfully read, status
// indicating either error or need more input otherwise.
template <class T>
-JxlDecoderStatus ReadBundle(Span<const uint8_t> data, BitReader* reader,
- T* JXL_RESTRICT t) {
+JxlDecoderStatus ReadBundle(JxlDecoder* dec, Span<const uint8_t> data,
+ BitReader* reader, T* JXL_RESTRICT t) {
if (!CanRead(data, reader, t)) {
- return JXL_DEC_NEED_MORE_INPUT;
+ return dec->RequestMoreInput();
}
if (!Bundle::Read(reader, t)) {
return JXL_DEC_ERROR;
@@ -919,33 +978,35 @@ std::unique_ptr<BitReader, std::function<void(BitReader*)>> GetBitReader(
});
}
-JxlDecoderStatus JxlDecoderReadBasicInfo(JxlDecoder* dec, const uint8_t* in,
- size_t size) {
- size_t pos = 0;
-
- // Check and skip the codestream signature
- JxlSignature signature = ReadSignature(in, size, &pos);
- if (signature == JXL_SIG_NOT_ENOUGH_BYTES) {
- return JXL_DEC_NEED_MORE_INPUT;
- }
- if (signature == JXL_SIG_CONTAINER) {
- // There is a container signature where we expect a codestream, container
- // is handled at a higher level already.
- return JXL_API_ERROR("invalid: nested container");
- }
- if (signature != JXL_SIG_CODESTREAM) {
- return JXL_API_ERROR("invalid signature");
+JxlDecoderStatus JxlDecoderReadBasicInfo(JxlDecoder* dec) {
+ if (!dec->got_codestream_signature) {
+ // Check and skip the codestream signature
+ Span<const uint8_t> span;
+ JXL_API_RETURN_IF_ERROR(dec->GetCodestreamInput(&span));
+ if (span.size() < 2) {
+ return dec->RequestMoreInput();
+ }
+ if (span.data()[0] != 0xff || span.data()[1] != jxl::kCodestreamMarker) {
+ return JXL_API_ERROR("invalid signature");
+ }
+ dec->got_codestream_signature = true;
+ dec->AdvanceCodestream(2);
}
- Span<const uint8_t> span(in + pos, size - pos);
+ Span<const uint8_t> span;
+ JXL_API_RETURN_IF_ERROR(dec->GetCodestreamInput(&span));
auto reader = GetBitReader(span);
- JXL_API_RETURN_IF_ERROR(ReadBundle(span, reader.get(), &dec->metadata.size));
-
- dec->metadata.m.nonserialized_only_parse_basic_info = true;
- JXL_API_RETURN_IF_ERROR(ReadBundle(span, reader.get(), &dec->metadata.m));
- dec->metadata.m.nonserialized_only_parse_basic_info = false;
+ JXL_API_RETURN_IF_ERROR(
+ ReadBundle(dec, span, reader.get(), &dec->metadata.size));
+ JXL_API_RETURN_IF_ERROR(
+ ReadBundle(dec, span, reader.get(), &dec->metadata.m));
+ size_t total_bits = reader->TotalBitsConsumed();
+ dec->AdvanceCodestream(total_bits / jxl::kBitsPerByte);
+ dec->codestream_bits_ahead = total_bits % jxl::kBitsPerByte;
dec->got_basic_info = true;
dec->basic_info_size_hint = 0;
+ dec->image_metadata = dec->metadata.m;
+ JXL_DEBUG_V(2, "Decoded BasicInfo: %s", dec->metadata.DebugString().c_str());
if (!CheckSizeLimit(dec, dec->metadata.size.xsize(),
dec->metadata.size.ysize())) {
@@ -956,41 +1017,26 @@ JxlDecoderStatus JxlDecoderReadBasicInfo(JxlDecoder* dec, const uint8_t* in,
}
// Reads all codestream headers (but not frame headers)
-JxlDecoderStatus JxlDecoderReadAllHeaders(JxlDecoder* dec, const uint8_t* in,
- size_t size) {
- size_t pos = 0;
-
- // Check and skip the codestream signature
- JxlSignature signature = ReadSignature(in, size, &pos);
- if (signature == JXL_SIG_CONTAINER) {
- return JXL_API_ERROR("invalid: nested container");
- }
- if (signature != JXL_SIG_CODESTREAM) {
- return JXL_API_ERROR("invalid signature");
- }
-
- Span<const uint8_t> span(in + pos, size - pos);
- auto reader = GetBitReader(span);
-
- if (dec->header_except_icc_bits != 0) {
- // Headers were decoded already.
- reader->SkipBits(dec->header_except_icc_bits);
- } else {
- SizeHeader dummy_size_header;
- JXL_API_RETURN_IF_ERROR(ReadBundle(span, reader.get(), &dummy_size_header));
-
- // We already decoded the metadata to dec->metadata.m, no reason to
- // overwrite it, use a dummy metadata instead.
- ImageMetadata dummy_metadata;
- JXL_API_RETURN_IF_ERROR(ReadBundle(span, reader.get(), &dummy_metadata));
-
+JxlDecoderStatus JxlDecoderReadAllHeaders(JxlDecoder* dec) {
+ if (!dec->got_transform_data) {
+ Span<const uint8_t> span;
+ JXL_API_RETURN_IF_ERROR(dec->GetCodestreamInput(&span));
+ auto reader = GetBitReader(span);
+ reader->SkipBits(dec->codestream_bits_ahead);
dec->metadata.transform_data.nonserialized_xyb_encoded =
dec->metadata.m.xyb_encoded;
JXL_API_RETURN_IF_ERROR(
- ReadBundle(span, reader.get(), &dec->metadata.transform_data));
+ ReadBundle(dec, span, reader.get(), &dec->metadata.transform_data));
+ size_t total_bits = reader->TotalBitsConsumed();
+ dec->AdvanceCodestream(total_bits / jxl::kBitsPerByte);
+ dec->codestream_bits_ahead = total_bits % jxl::kBitsPerByte;
+ dec->got_transform_data = true;
}
- dec->header_except_icc_bits = reader->TotalBitsConsumed();
+ Span<const uint8_t> span;
+ JXL_API_RETURN_IF_ERROR(dec->GetCodestreamInput(&span));
+ auto reader = GetBitReader(span);
+ reader->SkipBits(dec->codestream_bits_ahead);
if (dec->metadata.m.color_encoding.WantICC()) {
jxl::Status status =
@@ -999,22 +1045,20 @@ JxlDecoderStatus JxlDecoderReadAllHeaders(JxlDecoder* dec, const uint8_t* in,
// handles reader out of bounds correctly yet (e.g. context map). Not
// checking AllReadsWithinBounds can cause reader->Close() to trigger an
// assert, but we don't want library to quit program for invalid codestream.
- if (!reader->AllReadsWithinBounds()) {
- return JXL_DEC_NEED_MORE_INPUT;
+ if (!reader->AllReadsWithinBounds() ||
+ status.code() == StatusCode::kNotEnoughBytes) {
+ return dec->RequestMoreInput();
}
if (!status) {
- if (status.code() == StatusCode::kNotEnoughBytes) {
- return JXL_DEC_NEED_MORE_INPUT;
- }
// Other non-successful status is an error
return JXL_DEC_ERROR;
}
PaddedBytes icc;
status = dec->icc_reader.Process(reader.get(), &icc);
+ if (status.code() == StatusCode::kNotEnoughBytes) {
+ return dec->RequestMoreInput();
+ }
if (!status) {
- if (status.code() == StatusCode::kNotEnoughBytes) {
- return JXL_DEC_NEED_MORE_INPUT;
- }
// Other non-successful status is an error
return JXL_DEC_ERROR;
}
@@ -1026,17 +1070,20 @@ JxlDecoderStatus JxlDecoderReadAllHeaders(JxlDecoder* dec, const uint8_t* in,
dec->got_all_headers = true;
JXL_API_RETURN_IF_ERROR(reader->JumpToByteBoundary());
- dec->frame_start = pos + reader->TotalBitsConsumed() / jxl::kBitsPerByte;
+ dec->AdvanceCodestream(reader->TotalBitsConsumed() / jxl::kBitsPerByte);
+ dec->codestream_bits_ahead = 0;
if (!dec->passes_state) {
dec->passes_state.reset(new jxl::PassesDecoderState());
}
- dec->default_enc =
- ColorEncoding::LinearSRGB(dec->metadata.m.color_encoding.IsGray());
-
- JXL_API_RETURN_IF_ERROR(dec->passes_state->output_encoding_info.Set(
- dec->metadata, dec->default_enc));
+ JXL_API_RETURN_IF_ERROR(
+ dec->passes_state->output_encoding_info.SetFromMetadata(dec->metadata));
+ if (dec->desired_intensity_target > 0) {
+ dec->passes_state->output_encoding_info.desired_intensity_target =
+ dec->desired_intensity_target;
+ }
+ dec->image_metadata = dec->metadata.m;
return JXL_DEC_SUCCESS;
}
@@ -1086,85 +1133,79 @@ static JxlDecoderStatus ConvertImageInternal(
status = jxl::ConvertToExternal(
frame, BitsPerChannel(format.data_type), float_format,
format.num_channels, format.endianness, stride, dec->thread_pool.get(),
- out_image, out_size, out_callback, undo_orientation);
+ out_image, out_size, out_callback, undo_orientation,
+ dec->unpremul_alpha);
}
return status ? JXL_DEC_SUCCESS : JXL_DEC_ERROR;
}
-// Parses the FrameHeader and the total frame_size, given the initial bytes
-// of the frame up to and including the TOC.
-// TODO(lode): merge this with FrameDecoder
-JxlDecoderStatus ParseFrameHeader(JxlDecoder* dec,
- jxl::FrameHeader* frame_header,
- const uint8_t* in, size_t size, size_t pos,
- bool is_preview, size_t* frame_size,
- int* saved_as) {
- if (pos >= size) {
- return JXL_DEC_NEED_MORE_INPUT;
- }
- Span<const uint8_t> span(in + pos, size - pos);
- auto reader = GetBitReader(span);
-
- frame_header->nonserialized_is_preview = is_preview;
- jxl::Status status = DecodeFrameHeader(reader.get(), frame_header);
- jxl::FrameDimensions frame_dim = frame_header->ToFrameDimensions();
- if (!CheckSizeLimit(dec, frame_dim.xsize_upsampled_padded,
- frame_dim.ysize_upsampled_padded)) {
- return JXL_API_ERROR("frame is too large");
- }
-
- if (status.code() == StatusCode::kNotEnoughBytes) {
- // TODO(lode): prevent asking for way too much input bytes in case of
- // invalid header that the decoder thinks is a very long user extension
- // instead. Example: fields can currently print something like this:
- // "../lib/jxl/fields.cc:416: Skipping 71467322-bit extension(s)"
- // Maybe fields.cc should return error in the above case rather than
- // print a message.
- return JXL_DEC_NEED_MORE_INPUT;
- } else if (!status) {
- return JXL_API_ERROR("invalid frame header");
- }
-
- // Read TOC.
- uint64_t groups_total_size;
- const bool has_ac_global = true;
- const size_t toc_entries =
- NumTocEntries(frame_dim.num_groups, frame_dim.num_dc_groups,
- frame_header->passes.num_passes, has_ac_global);
-
- std::vector<uint64_t> group_offsets;
- std::vector<uint32_t> group_sizes;
- status = ReadGroupOffsets(toc_entries, reader.get(), &group_offsets,
- &group_sizes, &groups_total_size);
-
- // TODO(lode): we're actually relying on AllReadsWithinBounds() here
- // instead of on status.code(), change the internal TOC C++ code to
- // correctly set the status.code() instead so we can rely on that one.
- if (!reader->AllReadsWithinBounds() ||
- status.code() == StatusCode::kNotEnoughBytes) {
- return JXL_DEC_NEED_MORE_INPUT;
- } else if (!status) {
- return JXL_API_ERROR("invalid toc entries");
- }
-
- JXL_DASSERT((reader->TotalBitsConsumed() % kBitsPerByte) == 0);
- JXL_API_RETURN_IF_ERROR(reader->JumpToByteBoundary());
- size_t header_size = (reader->TotalBitsConsumed() >> 3);
- *frame_size = header_size + groups_total_size;
-
- if (saved_as != nullptr) {
- *saved_as = FrameDecoder::SavedAs(*frame_header);
+JxlDecoderStatus JxlDecoderProcessSections(JxlDecoder* dec) {
+ Span<const uint8_t> span;
+ JXL_API_RETURN_IF_ERROR(dec->GetCodestreamInput(&span));
+ const auto& toc = dec->frame_dec->Toc();
+ size_t pos = 0;
+ std::vector<jxl::FrameDecoder::SectionInfo> section_info;
+ std::vector<jxl::FrameDecoder::SectionStatus> section_status;
+ for (size_t i = dec->next_section; i < toc.size(); ++i) {
+ if (dec->section_processed[i]) continue;
+ size_t id = toc[i].id;
+ size_t size = toc[i].size;
+ if (OutOfBounds(pos, size, span.size())) {
+ break;
+ }
+ auto br =
+ new jxl::BitReader(jxl::Span<const uint8_t>(span.data() + pos, size));
+ section_info.emplace_back(jxl::FrameDecoder::SectionInfo{br, id});
+ section_status.emplace_back();
+ pos += size;
+ }
+ jxl::Status status = dec->frame_dec->ProcessSections(
+ section_info.data(), section_info.size(), section_status.data());
+ bool out_of_bounds = false;
+ for (const auto& info : section_info) {
+ if (!info.br->AllReadsWithinBounds()) {
+ // Mark out of bounds section, but keep closing and deleting the next
+ // ones as well.
+ out_of_bounds = true;
+ }
+ JXL_ASSERT(info.br->Close());
+ delete info.br;
+ }
+ if (out_of_bounds) {
+ // If any bit reader indicates out of bounds, it's an error, not just
+ // needing more input, since we ensure only bit readers containing
+ // a complete section are provided to the FrameDecoder.
+ return JXL_API_ERROR("frame out of bounds");
+ }
+ if (!status) {
+ return JXL_API_ERROR("frame processing failed");
+ }
+ bool found_skipped_section = false;
+ size_t num_done = 0;
+ size_t processed_bytes = 0;
+ for (size_t i = 0; i < section_status.size(); ++i) {
+ auto status = section_status[i];
+ if (status == jxl::FrameDecoder::kDone) {
+ if (!found_skipped_section) {
+ processed_bytes += toc[dec->next_section + i].size;
+ ++num_done;
+ }
+ dec->section_processed[dec->next_section + i] = 1;
+ } else if (status == jxl::FrameDecoder::kSkipped) {
+ found_skipped_section = true;
+ } else {
+ return JXL_API_ERROR("unexpected section status");
+ }
}
-
+ dec->next_section += num_done;
+ dec->remaining_frame_size -= processed_bytes;
+ dec->AdvanceCodestream(processed_bytes);
return JXL_DEC_SUCCESS;
}
// TODO(eustas): no CodecInOut -> no image size reinforcement -> possible OOM.
-// TODO(lode): allow this function to indicate bytes of in that have already
-// been processed and no longer need to be stored in codestream_copy.
-JxlDecoderStatus JxlDecoderProcessCodestream(JxlDecoder* dec, const uint8_t* in,
- size_t size) {
+JxlDecoderStatus JxlDecoderProcessCodestream(JxlDecoder* dec) {
// If no parallel runner is set, use the default
// TODO(lode): move this initialization to an appropriate location once the
// runner is used to decode pixels.
@@ -1174,7 +1215,7 @@ JxlDecoderStatus JxlDecoderProcessCodestream(JxlDecoder* dec, const uint8_t* in,
// No matter what events are wanted, the basic info is always required.
if (!dec->got_basic_info) {
- JxlDecoderStatus status = JxlDecoderReadBasicInfo(dec, in, size);
+ JxlDecoderStatus status = JxlDecoderReadBasicInfo(dec);
if (status != JXL_DEC_SUCCESS) return status;
}
@@ -1183,16 +1224,14 @@ JxlDecoderStatus JxlDecoderProcessCodestream(JxlDecoder* dec, const uint8_t* in,
return JXL_DEC_BASIC_INFO;
}
- if (!dec->got_all_headers) {
- JxlDecoderStatus status = JxlDecoderReadAllHeaders(dec, in, size);
- if (status != JXL_DEC_SUCCESS) return status;
+ if (!dec->events_wanted) {
+ dec->stage = DecoderStage::kCodestreamFinished;
+ return JXL_DEC_SUCCESS;
}
- if (dec->events_wanted & JXL_DEC_EXTENSIONS) {
- dec->events_wanted &= ~JXL_DEC_EXTENSIONS;
- if (dec->metadata.m.extensions != 0) {
- return JXL_DEC_EXTENSIONS;
- }
+ if (!dec->got_all_headers) {
+ JxlDecoderStatus status = JxlDecoderReadAllHeaders(dec);
+ if (status != JXL_DEC_SUCCESS) return status;
}
if (dec->events_wanted & JXL_DEC_COLOR_ENCODING) {
@@ -1200,85 +1239,28 @@ JxlDecoderStatus JxlDecoderProcessCodestream(JxlDecoder* dec, const uint8_t* in,
return JXL_DEC_COLOR_ENCODING;
}
- dec->post_headers = true;
-
- // Decode to pixels, only if required for the events the user wants.
- if (!dec->got_preview_image) {
- // Parse the preview, or at least its TOC to be able to skip the frame, if
- // any frame or image decoding is desired.
- bool parse_preview =
- (dec->events_wanted &
- (JXL_DEC_PREVIEW_IMAGE | JXL_DEC_FRAME | JXL_DEC_FULL_IMAGE));
-
- if (!dec->metadata.m.have_preview) {
- // There is no preview, mark this as done and go to next step
- dec->got_preview_image = true;
- } else if (!parse_preview) {
- // No preview parsing needed, mark this step as done
- dec->got_preview_image = true;
- } else {
- // Want to decode the preview, not just skip the frame
- bool want_preview = (dec->events_wanted & JXL_DEC_PREVIEW_IMAGE);
- size_t frame_size;
- size_t pos = dec->frame_start;
- dec->frame_header.reset(new FrameHeader(&dec->metadata));
- JxlDecoderStatus status = ParseFrameHeader(
- dec, dec->frame_header.get(), in, size, pos, true, &frame_size,
- /*saved_as=*/nullptr);
- if (status != JXL_DEC_SUCCESS) return status;
- if (OutOfBounds(pos, frame_size, size)) {
- return JXL_DEC_NEED_MORE_INPUT;
- }
-
- if (want_preview && !dec->preview_out_buffer_set) {
- return JXL_DEC_NEED_PREVIEW_OUT_BUFFER;
- }
+ if (!dec->events_wanted) {
+ dec->stage = DecoderStage::kCodestreamFinished;
+ return JXL_DEC_SUCCESS;
+ }
- jxl::Span<const uint8_t> compressed(in + dec->frame_start,
- size - dec->frame_start);
- auto reader = GetBitReader(compressed);
- jxl::DecompressParams dparams;
- dparams.render_spotcolors = dec->render_spotcolors;
- dparams.coalescing = true;
- jxl::ImageBundle ib(&dec->metadata.m);
- PassesDecoderState preview_dec_state;
- JXL_API_RETURN_IF_ERROR(preview_dec_state.output_encoding_info.Set(
- dec->metadata,
- ColorEncoding::LinearSRGB(dec->metadata.m.color_encoding.IsGray())));
- if (!DecodeFrame(dparams, &preview_dec_state, dec->thread_pool.get(),
- reader.get(), &ib, dec->metadata,
- /*constraints=*/nullptr,
- /*is_preview=*/true)) {
- return JXL_API_ERROR("decoding preview failed");
- }
+ dec->post_headers = true;
- // Set frame_start to the first non-preview frame.
- dec->frame_start += DivCeil(reader->TotalBitsConsumed(), kBitsPerByte);
- dec->got_preview_image = true;
-
- if (want_preview) {
- if (dec->preview_out_buffer) {
- JxlDecoderStatus status = ConvertImageInternal(
- dec, ib, dec->preview_out_format, /*want_extra_channel=*/false,
- /*extra_channel_index=*/0, dec->preview_out_buffer,
- dec->preview_out_size,
- /*out_callback=*/{});
- if (status != JXL_DEC_SUCCESS) return status;
- }
- return JXL_DEC_PREVIEW_IMAGE;
- }
- }
+ if (!dec->got_preview_image && dec->metadata.m.have_preview) {
+ dec->preview_frame = true;
}
// Handle frames
for (;;) {
- if (!(dec->events_wanted & (JXL_DEC_FULL_IMAGE | JXL_DEC_FRAME))) {
+ bool parse_frames =
+ (dec->events_wanted &
+ (JXL_DEC_PREVIEW_IMAGE | JXL_DEC_FRAME | JXL_DEC_FULL_IMAGE));
+ if (!parse_frames) {
break;
}
if (dec->frame_stage == FrameStage::kHeader && dec->is_last_total) {
break;
}
-
if (dec->frame_stage == FrameStage::kHeader) {
if (dec->recon_output_jpeg == JpegReconStage::kSettingMetadata ||
dec->recon_output_jpeg == JpegReconStage::kOutputting) {
@@ -1289,17 +1271,64 @@ JxlDecoderStatus JxlDecoderProcessCodestream(JxlDecoder* dec, const uint8_t* in,
return JXL_API_ERROR(
"cannot decode a next frame after JPEG reconstruction frame");
}
- size_t pos = dec->frame_start - dec->codestream_pos;
- if (pos >= size) {
- return JXL_DEC_NEED_MORE_INPUT;
+ if (!dec->ib) {
+ dec->ib.reset(new jxl::ImageBundle(&dec->image_metadata));
}
+ // If JPEG reconstruction is wanted and possible, set the jpeg_data of
+ // the ImageBundle.
+ if (!dec->jpeg_decoder.SetImageBundleJpegData(dec->ib.get()))
+ return JXL_DEC_ERROR;
+
+ dec->frame_dec.reset(new FrameDecoder(
+ dec->passes_state.get(), dec->metadata, dec->thread_pool.get(),
+ /*use_slow_rendering_pipeline=*/false));
dec->frame_header.reset(new FrameHeader(&dec->metadata));
- int saved_as = 0;
- JxlDecoderStatus status =
- ParseFrameHeader(dec, dec->frame_header.get(), in, size, pos,
- /*is_preview=*/false, &dec->frame_size, &saved_as);
- if (status != JXL_DEC_SUCCESS) return status;
+ Span<const uint8_t> span;
+ JXL_API_RETURN_IF_ERROR(dec->GetCodestreamInput(&span));
+ auto reader = GetBitReader(span);
+ bool output_needed =
+ (dec->preview_frame ? (dec->events_wanted & JXL_DEC_PREVIEW_IMAGE)
+ : (dec->events_wanted & JXL_DEC_FULL_IMAGE));
+ jxl::Status status = dec->frame_dec->InitFrame(
+ reader.get(), dec->ib.get(), dec->preview_frame, output_needed);
+ if (!reader->AllReadsWithinBounds() ||
+ status.code() == StatusCode::kNotEnoughBytes) {
+ return dec->RequestMoreInput();
+ } else if (!status) {
+ return JXL_API_ERROR("invalid frame header");
+ }
+ dec->AdvanceCodestream(reader->TotalBitsConsumed() / kBitsPerByte);
+ *dec->frame_header = dec->frame_dec->GetFrameHeader();
+ jxl::FrameDimensions frame_dim = dec->frame_header->ToFrameDimensions();
+ if (!CheckSizeLimit(dec, frame_dim.xsize_upsampled_padded,
+ frame_dim.ysize_upsampled_padded)) {
+ return JXL_API_ERROR("frame is too large");
+ }
+ if (dec->cpu_limit_base != 0) {
+ // No overflow, checked in CheckSizeLimit.
+ size_t num_pixels = frame_dim.xsize * frame_dim.ysize;
+ if (dec->used_cpu_base + num_pixels < dec->used_cpu_base) {
+ return JXL_API_ERROR("used too much CPU");
+ }
+ dec->used_cpu_base += num_pixels;
+ if (dec->used_cpu_base > dec->cpu_limit_base) {
+ return JXL_API_ERROR("used too much CPU");
+ }
+ }
+ dec->remaining_frame_size = dec->frame_dec->SumSectionSizes();
+ dec->frame_stage = FrameStage::kTOC;
+ if (dec->preview_frame) {
+ if (!(dec->events_wanted & JXL_DEC_PREVIEW_IMAGE)) {
+ dec->frame_stage = FrameStage::kHeader;
+ dec->AdvanceCodestream(dec->remaining_frame_size);
+ dec->got_preview_image = true;
+ dec->preview_frame = false;
+ }
+ continue;
+ }
+
+ int saved_as = FrameDecoder::SavedAs(*dec->frame_header);
// is last in entire codestream
dec->is_last_total = dec->frame_header->is_last;
// is last of current still
@@ -1309,14 +1338,11 @@ JxlDecoderStatus JxlDecoderProcessCodestream(JxlDecoder* dec, const uint8_t* in,
dec->is_last_of_still |=
(!dec->coalescing &&
dec->frame_header->frame_type == FrameType::kRegularFrame);
-
const size_t internal_frame_index = dec->internal_frames;
const size_t external_frame_index = dec->external_frames;
if (dec->is_last_of_still) dec->external_frames++;
dec->internal_frames++;
- dec->frame_stage = FrameStage::kTOC;
-
if (dec->skip_frames > 0) {
dec->skipping_frame = true;
if (dec->is_last_of_still) {
@@ -1359,7 +1385,7 @@ JxlDecoderStatus JxlDecoderProcessCodestream(JxlDecoder* dec, const uint8_t* in,
// Skip all decoding for this frame, since the user is skipping this
// frame and no future frames can reference it.
dec->frame_stage = FrameStage::kHeader;
- dec->frame_start += dec->frame_size;
+ dec->AdvanceCodestream(dec->remaining_frame_size);
continue;
}
}
@@ -1375,61 +1401,44 @@ JxlDecoderStatus JxlDecoderProcessCodestream(JxlDecoder* dec, const uint8_t* in,
}
if (dec->frame_stage == FrameStage::kTOC) {
- size_t pos = dec->frame_start - dec->codestream_pos;
- if (pos >= size) {
- return JXL_DEC_NEED_MORE_INPUT;
- }
- Span<const uint8_t> span(in + pos, size - pos);
- auto reader = GetBitReader(span);
-
- if (!dec->passes_state) {
- dec->passes_state.reset(new jxl::PassesDecoderState());
- }
- if (!dec->ib) {
- dec->ib.reset(new jxl::ImageBundle(&dec->metadata.m));
- }
-
- dec->frame_dec.reset(new FrameDecoder(
- dec->passes_state.get(), dec->metadata, dec->thread_pool.get(),
- /*use_slow_rendering_pipeline=*/false));
dec->frame_dec->SetRenderSpotcolors(dec->render_spotcolors);
dec->frame_dec->SetCoalescing(dec->coalescing);
- if (dec->events_wanted & JXL_DEC_FRAME_PROGRESSION) {
- dec->frame_dec->SetPauseAtProgressive();
- }
- // If JPEG reconstruction is wanted and possible, set the jpeg_data of
- // the ImageBundle.
- if (!dec->jpeg_decoder.SetImageBundleJpegData(dec->ib.get()))
- return JXL_DEC_ERROR;
-
- jxl::Status status = dec->frame_dec->InitFrame(
- reader.get(), dec->ib.get(), /*is_preview=*/false,
- /*allow_partial_frames=*/true, /*allow_partial_dc_global=*/false,
- /*output_needed=*/dec->events_wanted & JXL_DEC_FULL_IMAGE);
- if (!status) JXL_API_RETURN_IF_ERROR(status);
-
- size_t sections_begin =
- DivCeil(reader->TotalBitsConsumed(), kBitsPerByte);
+ if (!dec->preview_frame &&
+ (dec->events_wanted & JXL_DEC_FRAME_PROGRESSION)) {
+ dec->frame_prog_detail =
+ dec->frame_dec->SetPauseAtProgressive(dec->prog_detail);
+ } else {
+ dec->frame_prog_detail = JxlProgressiveDetail::kFrames;
+ }
+ dec->dc_frame_progression_done = 0;
- dec->sections.reset(
- new Sections(dec->frame_dec.get(), dec->frame_size, sections_begin));
- JXL_API_RETURN_IF_ERROR(dec->sections->Init());
+ dec->next_section = 0;
+ dec->section_processed.clear();
+ dec->section_processed.resize(dec->frame_dec->Toc().size(), 0);
// If we don't need pixels, we can skip actually decoding the frames
- // (kFull / kFullOut). By not updating frame_stage, none of
- // these stages will execute, and the loop will continue from the next
- // frame.
- if (dec->events_wanted & JXL_DEC_FULL_IMAGE) {
+ // (kFull / kFullOut).
+ if (dec->preview_frame || (dec->events_wanted & JXL_DEC_FULL_IMAGE)) {
dec->frame_dec_in_progress = true;
dec->frame_stage = FrameStage::kFull;
+ } else if (!dec->is_last_total) {
+ dec->frame_stage = FrameStage::kHeader;
+ dec->AdvanceCodestream(dec->remaining_frame_size);
+ continue;
+ } else {
+ break;
}
}
bool return_full_image = false;
if (dec->frame_stage == FrameStage::kFull) {
- if (dec->events_wanted & JXL_DEC_FULL_IMAGE) {
+ if (dec->preview_frame) {
+ if (!dec->preview_out_buffer_set) {
+ return JXL_DEC_NEED_PREVIEW_OUT_BUFFER;
+ }
+ } else if (dec->events_wanted & JXL_DEC_FULL_IMAGE) {
if (!dec->image_out_buffer_set &&
(!dec->jpeg_decoder.IsOutputSet() ||
dec->ib->jpeg_data == nullptr) &&
@@ -1443,7 +1452,10 @@ JxlDecoderStatus JxlDecoderProcessCodestream(JxlDecoder* dec, const uint8_t* in,
}
}
- if (dec->image_out_buffer_set && !!dec->image_out_buffer &&
+ dec->frame_dec->MaybeSetUnpremultiplyAlpha(dec->unpremul_alpha);
+
+ if (!dec->preview_frame && dec->image_out_buffer_set &&
+ !!dec->image_out_buffer &&
dec->image_out_format.data_type == JXL_TYPE_UINT8 &&
dec->image_out_format.num_channels >= 3 &&
dec->extra_channel_output.empty()) {
@@ -1462,78 +1474,67 @@ JxlDecoderStatus JxlDecoderProcessCodestream(JxlDecoder* dec, const uint8_t* in,
// TODO(lode): Support more formats than just native endian float32 for
// the low-memory callback path
- if (dec->image_out_buffer_set && !!dec->image_out_init_callback &&
- !!dec->image_out_run_callback &&
+ if (!dec->preview_frame && dec->image_out_buffer_set &&
+ !!dec->image_out_init_callback && !!dec->image_out_run_callback &&
dec->image_out_format.data_type == JXL_TYPE_FLOAT &&
- dec->image_out_format.num_channels >= 3 && !swap_endianness &&
+ dec->image_out_format.num_channels >= 3 &&
+ dec->extra_channel_output.empty() && !swap_endianness &&
dec->frame_dec_in_progress) {
bool is_rgba = dec->image_out_format.num_channels == 4;
dec->frame_dec->MaybeSetFloatCallback(
PixelCallback{
dec->image_out_init_callback, dec->image_out_run_callback,
dec->image_out_destroy_callback, dec->image_out_init_opaque},
- is_rgba, !dec->keep_orientation);
+ is_rgba, dec->unpremul_alpha, !dec->keep_orientation);
}
- size_t pos = dec->frame_start - dec->codestream_pos;
- if (pos >= size) {
- return JXL_DEC_NEED_MORE_INPUT;
- }
- dec->sections->SetInput(in + pos, size - pos);
-
- if (dec->cpu_limit_base != 0) {
- FrameDimensions frame_dim = dec->frame_header->ToFrameDimensions();
- // No overflow, checked in ParseHeader.
- size_t num_pixels = frame_dim.xsize * frame_dim.ysize;
- if (dec->used_cpu_base + num_pixels < dec->used_cpu_base) {
- return JXL_API_ERROR("used too much CPU");
- }
- dec->used_cpu_base += num_pixels;
- if (dec->used_cpu_base > dec->cpu_limit_base) {
- return JXL_API_ERROR("used too much CPU");
- }
- }
+ size_t next_num_passes_to_pause = dec->frame_dec->NextNumPassesToPause();
- jxl::Status status =
- dec->frame_dec->ProcessSections(dec->sections->section_info.data(),
- dec->sections->section_info.size(),
- dec->sections->section_status.data());
- JXL_API_RETURN_IF_ERROR(dec->sections->CloseInput());
- if (status.IsFatalError()) {
- return JXL_API_ERROR("decoding frame failed");
- }
+ JXL_API_RETURN_IF_ERROR(JxlDecoderProcessSections(dec));
- // TODO(lode): allow next_in to move forward if sections from the
- // beginning of the stream have been processed
+ bool all_sections_done = dec->frame_dec->HasDecodedAll();
+ bool got_dc_only = !all_sections_done && dec->frame_dec->HasDecodedDC();
- bool all_sections_done = !!status && dec->frame_dec->HasDecodedAll();
+ if (dec->frame_prog_detail >= JxlProgressiveDetail::kDC &&
+ !dec->dc_frame_progression_done && got_dc_only) {
+ dec->dc_frame_progression_done = true;
+ dec->downsampling_target = 8;
+ return JXL_DEC_FRAME_PROGRESSION;
+ }
- bool got_dc_only =
- !!status && !all_sections_done && dec->frame_dec->HasDecodedDC();
+ bool new_progression_step_done =
+ dec->frame_dec->NumCompletePasses() >= next_num_passes_to_pause;
- if ((dec->events_wanted & JXL_DEC_FRAME_PROGRESSION) && got_dc_only) {
- dec->events_wanted &= ~JXL_DEC_FRAME_PROGRESSION;
+ if (!all_sections_done &&
+ dec->frame_prog_detail >= JxlProgressiveDetail::kLastPasses &&
+ new_progression_step_done) {
+ dec->downsampling_target =
+ dec->frame_header->passes.GetDownsamplingTargetForCompletedPasses(
+ dec->frame_dec->NumCompletePasses());
return JXL_DEC_FRAME_PROGRESSION;
}
if (!all_sections_done) {
// Not all sections have been processed yet
- return JXL_DEC_NEED_MORE_INPUT;
+ return dec->RequestMoreInput();
+ }
+
+ if (!dec->preview_frame) {
+ size_t internal_index = dec->internal_frames - 1;
+ JXL_ASSERT(dec->frame_references.size() > internal_index);
+ // Always fill this in, even if it was already written, it could be that
+ // this frame was skipped before and set to 255, while only now we know
+ // the true value.
+ dec->frame_references[internal_index] = dec->frame_dec->References();
+ // Copy exif/xmp metadata from their boxes into the jpeg_data, if
+ // JPEG reconstruction is requested.
+ if (dec->jpeg_decoder.IsOutputSet() && dec->ib->jpeg_data != nullptr) {
+ }
}
- size_t internal_index = dec->internal_frames - 1;
- JXL_ASSERT(dec->frame_references.size() > internal_index);
- // Always fill this in, even if it was already written, it could be that
- // this frame was skipped before and set to 255, while only now we know
- // the true value.
- dec->frame_references[internal_index] = dec->frame_dec->References();
if (!dec->frame_dec->FinalizeFrame()) {
return JXL_API_ERROR("decoding frame failed");
}
- // Copy exif/xmp metadata from their boxes into the jpeg_data, if
- // JPEG reconstruction is requested.
- if (dec->jpeg_decoder.IsOutputSet() && dec->ib->jpeg_data != nullptr) {
- }
dec->frame_dec_in_progress = false;
dec->frame_stage = FrameStage::kFullOutput;
@@ -1542,7 +1543,15 @@ JxlDecoderStatus JxlDecoderProcessCodestream(JxlDecoder* dec, const uint8_t* in,
bool output_jpeg_reconstruction = false;
if (dec->frame_stage == FrameStage::kFullOutput) {
- if (dec->is_last_of_still) {
+ if (dec->preview_frame) {
+ JxlDecoderStatus status =
+ ConvertImageInternal(dec, *dec->ib, dec->preview_out_format,
+ /*want_extra_channel=*/false,
+ /*extra_channel_index=*/0,
+ dec->preview_out_buffer, dec->preview_out_size,
+ /*out_callback=*/{});
+ if (status != JXL_DEC_SUCCESS) return status;
+ } else if (dec->is_last_of_still) {
if (dec->events_wanted & JXL_DEC_FULL_IMAGE) {
dec->events_wanted &= ~JXL_DEC_FULL_IMAGE;
return_full_image = true;
@@ -1598,7 +1607,6 @@ JxlDecoderStatus JxlDecoderProcessCodestream(JxlDecoder* dec, const uint8_t* in,
}
dec->frame_stage = FrameStage::kHeader;
- dec->frame_start += dec->frame_size;
if (output_jpeg_reconstruction) {
dec->recon_output_jpeg = JpegReconStage::kSettingMetadata;
@@ -1607,7 +1615,12 @@ JxlDecoderStatus JxlDecoderProcessCodestream(JxlDecoder* dec, const uint8_t* in,
// The pixels have been output or are not needed, do not keep them in
// memory here.
dec->ib.reset();
- if (return_full_image && !dec->skipping_frame) {
+ if (dec->preview_frame) {
+ dec->got_preview_image = true;
+ dec->preview_frame = false;
+ dec->events_wanted &= ~JXL_DEC_PREVIEW_IMAGE;
+ return JXL_DEC_PREVIEW_IMAGE;
+ } else if (return_full_image && !dec->skipping_frame) {
return JXL_DEC_FULL_IMAGE;
}
}
@@ -1689,14 +1702,15 @@ static JxlDecoderStatus ParseBoxHeader(const uint8_t* in, size_t size,
size_t box_start = pos;
// Box size, including this header itself.
*box_size = LoadBE32(in + pos);
- memcpy(type, in + pos + 4, 4);
- pos += 8;
+ pos += 4;
if (*box_size == 1) {
*header_size = 16;
- if (OutOfBounds(pos, 8, size)) return JXL_DEC_NEED_MORE_INPUT;
+ if (OutOfBounds(pos, 12, size)) return JXL_DEC_NEED_MORE_INPUT;
*box_size = LoadBE64(in + pos);
pos += 8;
}
+ memcpy(type, in + pos, 4);
+ pos += 4;
*header_size = pos - box_start;
if (*box_size > 0 && *box_size < *header_size) {
return JXL_API_ERROR("invalid box size");
@@ -1712,6 +1726,8 @@ static JxlDecoderStatus HandleBoxes(JxlDecoder* dec) {
// Box handling loop
for (;;) {
if (dec->box_stage != BoxStage::kHeader) {
+ dec->AdvanceInput(dec->header_size);
+ dec->header_size = 0;
if ((dec->events_wanted & JXL_DEC_BOX) &&
dec->box_out_buffer_set_current_box) {
uint8_t* next_out = dec->box_out_buffer + dec->box_out_buffer_pos;
@@ -1836,6 +1852,18 @@ static JxlDecoderStatus HandleBoxes(JxlDecoder* dec) {
return JXL_DEC_NEED_MORE_INPUT;
}
+ bool boxed_codestream_done =
+ ((dec->events_wanted & JXL_DEC_BOX) &&
+ dec->stage == DecoderStage::kCodestreamFinished &&
+ dec->last_codestream_seen && !dec->JbrdNeedMoreBoxes());
+ if (boxed_codestream_done && dec->avail_in >= 2 &&
+ dec->next_in[0] == 0xff &&
+ dec->next_in[1] == jxl::kCodestreamMarker) {
+ // We detected the start of the next naked codestream, so we can return
+ // success here.
+ return JXL_DEC_SUCCESS;
+ }
+
uint64_t box_size, header_size;
JxlDecoderStatus status =
ParseBoxHeader(dec->next_in, dec->avail_in, 0, dec->file_pos,
@@ -1862,6 +1890,11 @@ static JxlDecoderStatus HandleBoxes(JxlDecoder* dec) {
// The signature box at box_count == 1 is not checked here since that's
// already done at the beginning.
dec->box_count++;
+ if (boxed_codestream_done && memcmp(dec->box_type, "JXL ", 4) == 0) {
+ // We detected the start of the next boxed stream, so we can return
+ // success here.
+ return JXL_DEC_SUCCESS;
+ }
if (dec->box_count == 2 && memcmp(dec->box_type, "ftyp", 4) != 0) {
return JXL_API_ERROR("the second box must be the ftyp box");
}
@@ -1869,16 +1902,14 @@ static JxlDecoderStatus HandleBoxes(JxlDecoder* dec) {
return JXL_API_ERROR("the ftyp box must come second");
}
- dec->AdvanceInput(header_size);
-
dec->box_contents_unbounded = (box_size == 0);
- dec->box_contents_begin = dec->file_pos;
- dec->box_contents_end = dec->box_contents_unbounded
- ? 0
- : (dec->file_pos + box_size - header_size);
+ dec->box_contents_begin = dec->file_pos + header_size;
+ dec->box_contents_end =
+ dec->box_contents_unbounded ? 0 : (dec->file_pos + box_size);
dec->box_contents_size =
dec->box_contents_unbounded ? 0 : (box_size - header_size);
dec->box_size = box_size;
+ dec->header_size = header_size;
if (dec->orig_events_wanted & JXL_DEC_JPEG_RECONSTRUCTION) {
// Initiate storing of Exif or XMP data for JPEG reconstruction
@@ -1960,40 +1991,15 @@ static JxlDecoderStatus HandleBoxes(JxlDecoder* dec) {
dec->AdvanceInput(4);
dec->box_stage = BoxStage::kCodestream;
} else if (dec->box_stage == BoxStage::kCodestream) {
- size_t avail_codestream = dec->avail_in;
- if (!dec->box_contents_unbounded) {
- avail_codestream = std::min<size_t>(
- avail_codestream, dec->box_contents_end - dec->file_pos);
- }
-
- bool have_copy = !dec->codestream_copy.empty();
- if (have_copy) {
- // TODO(lode): prune the codestream_copy vector if the codestream
- // decoder no longer needs data from previous frames.
- dec->codestream_copy.insert(dec->codestream_copy.end(), dec->next_in,
- dec->next_in + avail_codestream);
- dec->AdvanceInput(avail_codestream);
- avail_codestream = dec->codestream_copy.size();
- }
-
- const uint8_t* codestream =
- have_copy ? dec->codestream_copy.data() : dec->next_in;
-
- JxlDecoderStatus status =
- jxl::JxlDecoderProcessCodestream(dec, codestream, avail_codestream);
+ JxlDecoderStatus status = jxl::JxlDecoderProcessCodestream(dec);
if (status == JXL_DEC_FULL_IMAGE) {
if (dec->recon_output_jpeg != JpegReconStage::kNone) {
continue;
}
}
if (status == JXL_DEC_NEED_MORE_INPUT) {
- if (!have_copy) {
- dec->codestream_copy.insert(dec->codestream_copy.end(), dec->next_in,
- dec->next_in + avail_codestream);
- dec->AdvanceInput(avail_codestream);
- }
-
- if (dec->file_pos == dec->box_contents_end) {
+ if (dec->file_pos == dec->box_contents_end &&
+ !dec->box_contents_unbounded) {
dec->box_stage = BoxStage::kHeader;
continue;
}
@@ -2006,27 +2012,12 @@ static JxlDecoderStatus HandleBoxes(JxlDecoder* dec) {
}
if (dec->box_contents_unbounded) {
// Last box reached and codestream done, nothing more to do.
- dec->AdvanceInput(dec->avail_in);
break;
}
if (dec->events_wanted & JXL_DEC_BOX) {
// Codestream done, but there may be more other boxes.
dec->box_stage = BoxStage::kSkip;
continue;
- } else if (!dec->last_codestream_seen &&
- dec->CanUseMoreCodestreamInput()) {
- // Even though the codestream was successfully decoded, the last seen
- // jxlp box was not marked as last, so more jxlp boxes are expected.
- // Since the codestream already successfully finished, the only valid
- // case where this could happen is if there are empty jxlp boxes after
- // this.
- dec->box_stage = BoxStage::kSkip;
- continue;
- } else {
- // Codestream decoded, and no box output requested, skip all further
- // input and return success.
- dec->AdvanceInput(dec->avail_in);
- break;
}
}
return status;
@@ -2092,16 +2083,17 @@ static JxlDecoderStatus HandleBoxes(JxlDecoder* dec) {
}
// Arbitrarily more bytes may follow, only JxlDecoderCloseInput can
// mark the end.
+ dec->AdvanceInput(dec->avail_in);
return JXL_DEC_NEED_MORE_INPUT;
}
// Amount of remaining bytes in the box that is being skipped.
size_t remaining = dec->box_contents_end - dec->file_pos;
if (dec->avail_in < remaining) {
- // Don't have the full box yet, skip all we have so far
- dec->AdvanceInput(dec->avail_in);
// Indicate how many more bytes needed starting from next_in.
dec->basic_info_size_hint =
InitialBasicInfoSizeHint() + dec->box_contents_end - dec->file_pos;
+ // Don't have the full box yet, skip all we have so far
+ dec->AdvanceInput(dec->avail_in);
return JXL_DEC_NEED_MORE_INPUT;
} else {
// Full box available, skip all its remaining bytes
@@ -2155,15 +2147,8 @@ JxlDecoderStatus JxlDecoderProcessInput(JxlDecoder* dec) {
// data may be missing.
if (status == JXL_DEC_SUCCESS) {
if (dec->CanUseMoreCodestreamInput()) {
- if (!dec->last_codestream_seen) {
- // In case of jxlp boxes, this means no jxlp box marked as final was
- // seen yet. Perhaps there is an empty jxlp box after this, this is not
- // an error but will require more input.
- return JXL_DEC_NEED_MORE_INPUT;
- }
return JXL_API_ERROR("codestream never finished");
}
-
if (dec->JbrdNeedMoreBoxes()) {
return JXL_API_ERROR("missing metadata boxes for jpeg reconstruction");
}
@@ -2181,6 +2166,8 @@ JxlDecoderStatus JxlDecoderGetBasicInfo(const JxlDecoder* dec,
if (!dec->got_basic_info) return JXL_DEC_NEED_MORE_INPUT;
if (info) {
+ memset(info, 0, sizeof(*info));
+
const jxl::ImageMetadata& meta = dec->metadata.m;
info->have_container = dec->have_container;
@@ -2203,6 +2190,9 @@ JxlDecoderStatus JxlDecoderGetBasicInfo(const JxlDecoder* dec,
}
info->intensity_target = meta.IntensityTarget();
+ if (dec->desired_intensity_target > 0) {
+ info->intensity_target = dec->desired_intensity_target;
+ }
info->min_nits = meta.tone_mapping.min_nits;
info->relative_to_max_display = meta.tone_mapping.relative_to_max_display;
info->linear_below = meta.tone_mapping.linear_below;
@@ -2303,8 +2293,8 @@ namespace {
// but ensures that if the color encoding is not the encoding from the
// codestream header metadata, it cannot require ICC profile.
JxlDecoderStatus GetColorEncodingForTarget(
- const JxlDecoder* dec, const JxlPixelFormat* format,
- JxlColorProfileTarget target, const jxl::ColorEncoding** encoding) {
+ const JxlDecoder* dec, JxlColorProfileTarget target,
+ const jxl::ColorEncoding** encoding) {
if (!dec->got_all_headers) return JXL_DEC_NEED_MORE_INPUT;
*encoding = nullptr;
if (target == JXL_COLOR_PROFILE_TARGET_DATA && dec->metadata.m.xyb_encoded) {
@@ -2317,11 +2307,11 @@ JxlDecoderStatus GetColorEncodingForTarget(
} // namespace
JxlDecoderStatus JxlDecoderGetColorAsEncodedProfile(
- const JxlDecoder* dec, const JxlPixelFormat* format,
+ const JxlDecoder* dec, const JxlPixelFormat* unused_format,
JxlColorProfileTarget target, JxlColorEncoding* color_encoding) {
const jxl::ColorEncoding* jxl_color_encoding = nullptr;
JxlDecoderStatus status =
- GetColorEncodingForTarget(dec, format, target, &jxl_color_encoding);
+ GetColorEncodingForTarget(dec, target, &jxl_color_encoding);
if (status) return status;
if (jxl_color_encoding->WantICC())
@@ -2334,13 +2324,12 @@ JxlDecoderStatus JxlDecoderGetColorAsEncodedProfile(
return JXL_DEC_SUCCESS;
}
-JxlDecoderStatus JxlDecoderGetICCProfileSize(const JxlDecoder* dec,
- const JxlPixelFormat* format,
- JxlColorProfileTarget target,
- size_t* size) {
+JxlDecoderStatus JxlDecoderGetICCProfileSize(
+ const JxlDecoder* dec, const JxlPixelFormat* unused_format,
+ JxlColorProfileTarget target, size_t* size) {
const jxl::ColorEncoding* jxl_color_encoding = nullptr;
JxlDecoderStatus status =
- GetColorEncodingForTarget(dec, format, target, &jxl_color_encoding);
+ GetColorEncodingForTarget(dec, target, &jxl_color_encoding);
if (status != JXL_DEC_SUCCESS) return status;
if (jxl_color_encoding->WantICC()) {
@@ -2363,20 +2352,18 @@ JxlDecoderStatus JxlDecoderGetICCProfileSize(const JxlDecoder* dec,
return JXL_DEC_SUCCESS;
}
-JxlDecoderStatus JxlDecoderGetColorAsICCProfile(const JxlDecoder* dec,
- const JxlPixelFormat* format,
- JxlColorProfileTarget target,
- uint8_t* icc_profile,
- size_t size) {
+JxlDecoderStatus JxlDecoderGetColorAsICCProfile(
+ const JxlDecoder* dec, const JxlPixelFormat* unused_format,
+ JxlColorProfileTarget target, uint8_t* icc_profile, size_t size) {
size_t wanted_size;
// This also checks the NEED_MORE_INPUT and the unknown/xyb cases
JxlDecoderStatus status =
- JxlDecoderGetICCProfileSize(dec, format, target, &wanted_size);
+ JxlDecoderGetICCProfileSize(dec, nullptr, target, &wanted_size);
if (status != JXL_DEC_SUCCESS) return status;
if (size < wanted_size) return JXL_API_ERROR("ICC profile output too small");
const jxl::ColorEncoding* jxl_color_encoding = nullptr;
- status = GetColorEncodingForTarget(dec, format, target, &jxl_color_encoding);
+ status = GetColorEncodingForTarget(dec, target, &jxl_color_encoding);
if (status != JXL_DEC_SUCCESS) return status;
memcpy(icc_profile, jxl_color_encoding->ICC().data(),
@@ -2414,11 +2401,12 @@ JxlDecoderStatus PrepareSizeCheck(const JxlDecoder* dec,
} // namespace
+size_t JxlDecoderGetIntendedDownsamplingRatio(JxlDecoder* dec) {
+ return dec->downsampling_target;
+}
+
JxlDecoderStatus JxlDecoderFlushImage(JxlDecoder* dec) {
- if (!dec->image_out_buffer) return JXL_DEC_ERROR;
- if (!dec->sections || dec->sections->section_info.empty()) {
- return JXL_DEC_ERROR;
- }
+ if (!dec->image_out_buffer_set) return JXL_DEC_ERROR;
if (!dec->frame_dec || !dec->frame_dec_in_progress) {
return JXL_DEC_ERROR;
}
@@ -2451,7 +2439,9 @@ JxlDecoderStatus JxlDecoderFlushImage(JxlDecoder* dec) {
dec, *dec->ib, dec->image_out_format,
/*want_extra_channel=*/false,
/*extra_channel_index=*/0, dec->image_out_buffer, dec->image_out_size,
- /*out_callback=*/{});
+ jxl::PixelCallback{
+ dec->image_out_init_callback, dec->image_out_run_callback,
+ dec->image_out_destroy_callback, dec->image_out_init_opaque});
dec->ib->ShrinkTo(xsize, ysize);
if (status != JXL_DEC_SUCCESS) return status;
return JXL_DEC_SUCCESS;
@@ -2462,8 +2452,9 @@ JXL_EXPORT JxlDecoderStatus JxlDecoderPreviewOutBufferSize(
size_t bits;
JxlDecoderStatus status = PrepareSizeCheck(dec, format, &bits);
if (status != JXL_DEC_SUCCESS) return status;
- if (format->num_channels < 3 && !dec->metadata.m.color_encoding.IsGray()) {
- return JXL_API_ERROR("Grayscale output not possible for color image");
+ if (format->num_channels < 3 &&
+ !dec->image_metadata.color_encoding.IsGray()) {
+ return JXL_API_ERROR("Number of channels is too low for color output");
}
size_t xsize = dec->metadata.oriented_preview_xsize(dec->keep_orientation);
@@ -2485,8 +2476,9 @@ JXL_EXPORT JxlDecoderStatus JxlDecoderSetPreviewOutBuffer(
!(dec->orig_events_wanted & JXL_DEC_PREVIEW_IMAGE)) {
return JXL_API_ERROR("No preview out buffer needed at this time");
}
- if (format->num_channels < 3 && !dec->metadata.m.color_encoding.IsGray()) {
- return JXL_API_ERROR("Grayscale output not possible for color image");
+ if (format->num_channels < 3 &&
+ !dec->image_metadata.color_encoding.IsGray()) {
+ return JXL_API_ERROR("Number of channels is too low for color output");
}
size_t min_size;
@@ -2538,8 +2530,9 @@ JXL_EXPORT JxlDecoderStatus JxlDecoderImageOutBufferSize(
size_t bits;
JxlDecoderStatus status = PrepareSizeCheck(dec, format, &bits);
if (status != JXL_DEC_SUCCESS) return status;
- if (format->num_channels < 3 && !dec->metadata.m.color_encoding.IsGray()) {
- return JXL_API_ERROR("Grayscale output not possible for color image");
+ if (format->num_channels < 3 &&
+ !dec->image_metadata.color_encoding.IsGray()) {
+ return JXL_API_ERROR("Number of channels is too low for color output");
}
size_t xsize, ysize;
GetCurrentDimensions(dec, xsize, ysize, true);
@@ -2563,8 +2556,9 @@ JxlDecoderStatus JxlDecoderSetImageOutBuffer(JxlDecoder* dec,
return JXL_API_ERROR(
"Cannot change from image out callback to image out buffer");
}
- if (format->num_channels < 3 && !dec->metadata.m.color_encoding.IsGray()) {
- return JXL_API_ERROR("Grayscale output not possible for color image");
+ if (format->num_channels < 3 &&
+ !dec->image_metadata.color_encoding.IsGray()) {
+ return JXL_API_ERROR("Number of channels is too low for color output");
}
size_t min_size;
// This also checks whether the format is valid and supported and basic info
@@ -2701,6 +2695,7 @@ JxlDecoderStatus JxlDecoderGetFrameHeader(const JxlDecoder* dec,
return JXL_API_ERROR("no frame header available");
}
const auto& metadata = dec->metadata.m;
+ memset(header, 0, sizeof(*header));
if (metadata.have_animation) {
header->duration = dec->frame_header->animation_frame.duration;
if (metadata.animation.have_timecodes) {
@@ -2800,19 +2795,35 @@ JxlDecoderStatus JxlDecoderSetPreferredColorProfile(
if (dec->post_headers) {
return JXL_API_ERROR("too late to set the color encoding");
}
- if (dec->metadata.m.color_encoding.IsGray() !=
- (color_encoding->color_space == JXL_COLOR_SPACE_GRAY)) {
- return JXL_API_ERROR("grayscale mismatch");
+ if (dec->image_metadata.color_encoding.IsGray() &&
+ color_encoding->color_space != JXL_COLOR_SPACE_GRAY &&
+ ((dec->preview_out_buffer_set &&
+ dec->preview_out_format.num_channels < 3) ||
+ (dec->image_out_buffer_set && dec->image_out_format.num_channels < 3))) {
+ return JXL_API_ERROR("Number of channels is too low for color output");
}
if (color_encoding->color_space == JXL_COLOR_SPACE_UNKNOWN ||
color_encoding->color_space == JXL_COLOR_SPACE_XYB) {
return JXL_API_ERROR("only RGB or grayscale output supported");
}
- JXL_API_RETURN_IF_ERROR(ConvertExternalToInternalColorEncoding(
- *color_encoding, &dec->default_enc));
- JXL_API_RETURN_IF_ERROR(dec->passes_state->output_encoding_info.Set(
- dec->metadata, dec->default_enc));
+ jxl::ColorEncoding c_out;
+ JXL_API_RETURN_IF_ERROR(
+ ConvertExternalToInternalColorEncoding(*color_encoding, &c_out));
+ auto& output_encoding = dec->passes_state->output_encoding_info;
+ if (!c_out.SameColorEncoding(output_encoding.color_encoding)) {
+ JXL_API_RETURN_IF_ERROR(output_encoding.MaybeSetColorEncoding(c_out));
+ dec->image_metadata.color_encoding = output_encoding.color_encoding;
+ }
+ return JXL_DEC_SUCCESS;
+}
+
+JxlDecoderStatus JxlDecoderSetDesiredIntensityTarget(
+ JxlDecoder* dec, float desired_intensity_target) {
+ if (desired_intensity_target < 0) {
+ return JXL_API_ERROR("negative intensity target requested");
+ }
+ dec->desired_intensity_target = desired_intensity_target;
return JXL_DEC_SUCCESS;
}
@@ -2882,3 +2893,15 @@ JxlDecoderStatus JxlDecoderGetBoxSizeRaw(const JxlDecoder* dec,
}
return JXL_DEC_SUCCESS;
}
+
+JxlDecoderStatus JxlDecoderSetProgressiveDetail(JxlDecoder* dec,
+ JxlProgressiveDetail detail) {
+ if (detail != kDC && detail != kLastPasses && detail != kPasses) {
+ return JXL_API_ERROR(
+ "Values other than kDC (%d), kLastPasses (%d) and kPasses (%d), "
+ "like %d are not implemented.",
+ kDC, kLastPasses, kPasses, detail);
+ }
+ dec->prog_detail = detail;
+ return JXL_DEC_SUCCESS;
+}
diff --git a/media/libjxl/src/lib/jxl/decode_test.cc b/media/libjxl/src/lib/jxl/decode_test.cc
index 4f98ffaec3..5b9b735e18 100644
--- a/media/libjxl/src/lib/jxl/decode_test.cc
+++ b/media/libjxl/src/lib/jxl/decode_test.cc
@@ -17,9 +17,12 @@
#include "jxl/decode_cxx.h"
#include "jxl/resizable_parallel_runner_cxx.h"
#include "jxl/thread_parallel_runner_cxx.h"
-#include "lib/extras/enc/jpg.h"
+#include "jxl/types.h"
+#include "lib/extras/codec.h"
+#include "lib/extras/dec/color_description.h"
#include "lib/jxl/base/byte_order.h"
#include "lib/jxl/base/file_io.h"
+#include "lib/jxl/base/padded_bytes.h"
#include "lib/jxl/base/span.h"
#include "lib/jxl/base/status.h"
#include "lib/jxl/common.h"
@@ -31,11 +34,15 @@
#include "lib/jxl/enc_icc_codec.h"
#include "lib/jxl/encode_internal.h"
#include "lib/jxl/fields.h"
+#include "lib/jxl/frame_header.h"
#include "lib/jxl/headers.h"
#include "lib/jxl/icc_codec.h"
+#include "lib/jxl/image_metadata.h"
#include "lib/jxl/jpeg/enc_jpeg_data.h"
+#include "lib/jxl/progressive_split.h"
#include "lib/jxl/test_utils.h"
#include "lib/jxl/testdata.h"
+#include "lib/jxl/toc.h"
////////////////////////////////////////////////////////////////////////////////
@@ -70,6 +77,9 @@ enum CodeStreamBoxFormat {
// Have multiple partial codestream boxes, and the first one has a content
// of zero length
kCSBF_Multi_First_Empty,
+ // Have multiple partial codestream boxes, and the last one has a content
+ // of zero length and there is an unknown empty box at the end
+ kCSBF_Multi_Last_Empty_Other,
// Have a compressed exif box before a regular codestream box
kCSBF_Brob_Exif,
// Not a value but used for counting amount of enum entries
@@ -160,6 +170,19 @@ void AppendTestBox(const char* type, const char* contents, size_t contents_size,
bytes->append(contents_u, contents_u + contents_size);
}
+struct TestCodestreamParams {
+ CompressParams cparams;
+ CodeStreamBoxFormat box_format = kCSBF_None;
+ JxlOrientation orientation = JXL_ORIENT_IDENTITY;
+ bool add_preview = false;
+ bool add_intrinsic_size = false;
+ bool add_icc_profile = false;
+ float intensity_target = 0.0;
+ std::string color_space;
+ PaddedBytes* jpeg_codestream = nullptr;
+ const ProgressiveMode* progressive_mode = nullptr;
+};
+
// Input pixels always given as 16-bit RGBA, 8 bytes per pixel.
// include_alpha determines if the encoded image should contain the alpha
// channel.
@@ -170,70 +193,83 @@ void AppendTestBox(const char* type, const char* contents, size_t contents_size,
// Providing jpeg_codestream will populate the jpeg_codestream with compressed
// JPEG bytes, and make it possible to reconstruct those exact JPEG bytes using
// the return value _if_ add_container indicates a box format.
-PaddedBytes CreateTestJXLCodestream(
- Span<const uint8_t> pixels, size_t xsize, size_t ysize, size_t num_channels,
- const CompressParams& cparams, CodeStreamBoxFormat add_container,
- JxlOrientation orientation, bool add_preview, bool add_intrinsic_size,
- bool add_icc_profile = false, PaddedBytes* jpeg_codestream = nullptr) {
+PaddedBytes CreateTestJXLCodestream(Span<const uint8_t> pixels, size_t xsize,
+ size_t ysize, size_t num_channels,
+ const TestCodestreamParams& params) {
// Compress the pixels with JPEG XL.
bool grayscale = (num_channels <= 2);
- bool include_alpha = !(num_channels & 1) && jpeg_codestream == nullptr;
- size_t bitdepth = jpeg_codestream == nullptr ? 16 : 8;
+ bool include_alpha = !(num_channels & 1) && params.jpeg_codestream == nullptr;
+ size_t bitdepth = params.jpeg_codestream == nullptr ? 16 : 8;
CodecInOut io;
io.SetSize(xsize, ysize);
- ColorEncoding color_encoding =
- jxl::ColorEncoding::SRGB(/*is_gray=*/grayscale);
- if (add_icc_profile) {
+ ColorEncoding color_encoding;
+ if (params.add_icc_profile) {
// the hardcoded ICC profile we attach requires RGB.
EXPECT_EQ(false, grayscale);
+ EXPECT_TRUE(params.color_space.empty());
EXPECT_TRUE(color_encoding.SetICC(GetIccTestProfile()));
+ } else if (!params.color_space.empty()) {
+ JxlColorEncoding c;
+ EXPECT_TRUE(jxl::ParseDescription(params.color_space, &c));
+ EXPECT_TRUE(ConvertExternalToInternalColorEncoding(c, &color_encoding));
+ EXPECT_EQ(color_encoding.IsGray(), grayscale);
+ } else {
+ color_encoding = jxl::ColorEncoding::SRGB(/*is_gray=*/grayscale);
}
ThreadPool pool(nullptr, nullptr);
io.metadata.m.SetUintSamples(bitdepth);
if (include_alpha) {
io.metadata.m.SetAlphaBits(bitdepth);
}
+ if (params.intensity_target != 0) {
+ io.metadata.m.SetIntensityTarget(params.intensity_target);
+ }
// Make the grayscale-ness of the io metadata color_encoding and the packed
// image match.
io.metadata.m.color_encoding = color_encoding;
EXPECT_TRUE(ConvertFromExternal(
pixels, xsize, ysize, color_encoding, num_channels,
/*alpha_is_premultiplied=*/false, /*bits_per_sample=*/16, JXL_BIG_ENDIAN,
- /*flipped_y=*/false, &pool, &io.Main(), /*float_in=*/false, /*align=*/0));
+ &pool, &io.Main(), /*float_in=*/false, /*align=*/0));
jxl::PaddedBytes jpeg_data;
- if (jpeg_codestream != nullptr) {
+ if (params.jpeg_codestream != nullptr) {
#if JPEGXL_ENABLE_JPEG
- jxl::PaddedBytes jpeg_bytes;
- EXPECT_TRUE(EncodeImageJPG(&io, jxl::extras::JpegEncoder::kLibJpeg,
- /*quality=*/70, jxl::YCbCrChromaSubsampling(),
- &pool, &jpeg_bytes));
- jpeg_codestream->append(jpeg_bytes.data(),
- jpeg_bytes.data() + jpeg_bytes.size());
+ std::vector<uint8_t> jpeg_bytes;
+ io.jpeg_quality = 70;
+ EXPECT_TRUE(Encode(io, extras::Codec::kJPG, io.metadata.m.color_encoding,
+ /*bits_per_sample=*/8, &jpeg_bytes, &pool));
+ params.jpeg_codestream->append(jpeg_bytes.data(),
+ jpeg_bytes.data() + jpeg_bytes.size());
EXPECT_TRUE(jxl::jpeg::DecodeImageJPG(
jxl::Span<const uint8_t>(jpeg_bytes.data(), jpeg_bytes.size()), &io));
- EXPECT_TRUE(EncodeJPEGData(*io.Main().jpeg_data, &jpeg_data, cparams));
+ EXPECT_TRUE(
+ EncodeJPEGData(*io.Main().jpeg_data, &jpeg_data, params.cparams));
io.metadata.m.xyb_encoded = false;
#else // JPEGXL_ENABLE_JPEG
JXL_ABORT(
"unable to create reconstructible JPEG without JPEG support enabled");
#endif // JPEGXL_ENABLE_JPEG
}
- if (add_preview) {
+ if (params.add_preview) {
io.preview_frame = io.Main().Copy();
io.preview_frame.ShrinkTo(xsize / 7, ysize / 7);
io.metadata.m.have_preview = true;
EXPECT_TRUE(io.metadata.m.preview_size.Set(io.preview_frame.xsize(),
io.preview_frame.ysize()));
}
- if (add_intrinsic_size) {
+ if (params.add_intrinsic_size) {
EXPECT_TRUE(io.metadata.m.intrinsic_size.Set(xsize / 3, ysize / 3));
}
- io.metadata.m.orientation = orientation;
+ io.metadata.m.orientation = params.orientation;
AuxOut aux_out;
PaddedBytes compressed;
PassesEncoderState enc_state;
- EXPECT_TRUE(EncodeFile(cparams, &io, &enc_state, &compressed, GetJxlCms(),
- &aux_out, &pool));
+ if (params.progressive_mode) {
+ enc_state.progressive_splitter.SetProgressiveMode(*params.progressive_mode);
+ }
+ EXPECT_TRUE(EncodeFile(params.cparams, &io, &enc_state, &compressed,
+ GetJxlCms(), &aux_out, &pool));
+ CodeStreamBoxFormat add_container = params.box_format;
if (add_container != kCSBF_None) {
// Header with signature box and ftyp box.
const uint8_t header[] = {0, 0, 0, 0xc, 0x4a, 0x58, 0x4c, 0x20,
@@ -245,7 +281,8 @@ PaddedBytes CreateTestJXLCodestream(
add_container == kCSBF_Multi_Zero_Terminated ||
add_container == kCSBF_Multi_Other_Terminated ||
add_container == kCSBF_Multi_Other_Zero_Terminated ||
- add_container == kCSBF_Multi_First_Empty;
+ add_container == kCSBF_Multi_First_Empty ||
+ add_container == kCSBF_Multi_Last_Empty_Other;
if (is_multi) {
size_t third = compressed.size() / 3;
@@ -258,7 +295,7 @@ PaddedBytes CreateTestJXLCodestream(
PaddedBytes c;
c.append(header, header + sizeof(header));
- if (jpeg_codestream != nullptr) {
+ if (params.jpeg_codestream != nullptr) {
jxl::AppendBoxHeader(jxl::MakeBoxType("jbrd"), jpeg_data.size(), false,
&c);
c.append(jpeg_data.data(), jpeg_data.data() + jpeg_data.size());
@@ -308,8 +345,23 @@ PaddedBytes CreateTestJXLCodestream(
c.push_back('x');
c.push_back('l');
c.push_back('p');
- AppendU32BE(jxlp_index++ | 0x80000000, &c);
+ if (add_container != kCSBF_Multi_Last_Empty_Other) {
+ AppendU32BE(jxlp_index++ | 0x80000000, &c);
+ } else {
+ AppendU32BE(jxlp_index++, &c);
+ }
c.append(compressed2.data(), compressed2.data() + compressed2.size());
+ if (add_container == kCSBF_Multi_Last_Empty_Other) {
+ // Dummy (empty) codestream part
+ AppendU32BE(12, &c);
+ c.push_back('j');
+ c.push_back('x');
+ c.push_back('l');
+ c.push_back('p');
+ AppendU32BE(jxlp_index++ | 0x80000000, &c);
+ AppendTestBox(unk3_box_type, unk3_box_contents, unk3_box_size, false,
+ &c);
+ }
if (add_container == kCSBF_Multi_Other_Terminated) {
AppendTestBox(unk3_box_type, unk3_box_contents, unk3_box_size, false,
&c);
@@ -322,7 +374,7 @@ PaddedBytes CreateTestJXLCodestream(
} else {
PaddedBytes c;
c.append(header, header + sizeof(header));
- if (jpeg_codestream != nullptr) {
+ if (params.jpeg_codestream != nullptr) {
jxl::AppendBoxHeader(jxl::MakeBoxType("jbrd"), jpeg_data.size(), false,
&c);
c.append(jpeg_data.data(), jpeg_data.data() + jpeg_data.size());
@@ -350,12 +402,21 @@ PaddedBytes CreateTestJXLCodestream(
return compressed;
}
+JxlDecoderStatus ProcessInputIgnoreBoxes(JxlDecoder* dec) {
+ JxlDecoderStatus status;
+ while ((status = JxlDecoderProcessInput(dec)) == JXL_DEC_BOX) {
+ continue;
+ }
+ return status;
+}
+
// Decodes one-shot with the API for non-streaming decoding tests.
std::vector<uint8_t> DecodeWithAPI(JxlDecoder* dec,
Span<const uint8_t> compressed,
const JxlPixelFormat& format,
bool use_callback, bool set_buffer_early,
bool use_resizable_runner,
+ bool require_boxes, bool expect_success,
PaddedBytes* icc = nullptr) {
JxlThreadParallelRunnerPtr runner_fixed;
JxlResizableParallelRunnerPtr runner_resizable;
@@ -376,16 +437,20 @@ std::vector<uint8_t> DecodeWithAPI(JxlDecoder* dec,
EXPECT_EQ(JXL_DEC_SUCCESS,
JxlDecoderSetParallelRunner(dec, runner_fn, runner));
+ auto process_input =
+ require_boxes ? ProcessInputIgnoreBoxes : JxlDecoderProcessInput;
+
EXPECT_EQ(
JXL_DEC_SUCCESS,
JxlDecoderSubscribeEvents(
dec, JXL_DEC_BASIC_INFO | (set_buffer_early ? JXL_DEC_FRAME : 0) |
JXL_DEC_PREVIEW_IMAGE | JXL_DEC_FULL_IMAGE |
+ (require_boxes ? JXL_DEC_BOX : 0) |
(icc != nullptr ? JXL_DEC_COLOR_ENCODING : 0)));
EXPECT_EQ(JXL_DEC_SUCCESS,
JxlDecoderSetInput(dec, compressed.data(), compressed.size()));
- EXPECT_EQ(JXL_DEC_BASIC_INFO, JxlDecoderProcessInput(dec));
+ EXPECT_EQ(JXL_DEC_BASIC_INFO, process_input(dec));
size_t buffer_size;
EXPECT_EQ(JXL_DEC_SUCCESS,
JxlDecoderImageOutBufferSize(dec, &format, &buffer_size));
@@ -411,7 +476,7 @@ std::vector<uint8_t> DecodeWithAPI(JxlDecoder* dec,
num_pixels * bytes_per_pixel);
};
- JxlDecoderStatus status = JxlDecoderProcessInput(dec);
+ JxlDecoderStatus status = process_input(dec);
if (status == JXL_DEC_COLOR_ENCODING) {
size_t icc_size = 0;
@@ -423,7 +488,7 @@ std::vector<uint8_t> DecodeWithAPI(JxlDecoder* dec,
dec, &format, JXL_COLOR_PROFILE_TARGET_DATA,
icc->data(), icc_size));
- status = JxlDecoderProcessInput(dec);
+ status = process_input(dec);
}
std::vector<uint8_t> preview;
@@ -435,9 +500,9 @@ std::vector<uint8_t> DecodeWithAPI(JxlDecoder* dec,
EXPECT_EQ(JXL_DEC_SUCCESS,
JxlDecoderSetPreviewOutBuffer(dec, &format, preview.data(),
preview.size()));
- EXPECT_EQ(JXL_DEC_PREVIEW_IMAGE, JxlDecoderProcessInput(dec));
+ EXPECT_EQ(JXL_DEC_PREVIEW_IMAGE, process_input(dec));
- status = JxlDecoderProcessInput(dec);
+ status = process_input(dec);
}
if (set_buffer_early) {
@@ -461,11 +526,15 @@ std::vector<uint8_t> DecodeWithAPI(JxlDecoder* dec,
dec, &format, pixels.data(), pixels.size()));
}
- EXPECT_EQ(JXL_DEC_FULL_IMAGE, JxlDecoderProcessInput(dec));
+ EXPECT_EQ(JXL_DEC_FULL_IMAGE, process_input(dec));
// After the full image was output, JxlDecoderProcessInput should return
- // success to indicate all is done.
- EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderProcessInput(dec));
+ // success to indicate all is done, unless we requested boxes and the last
+ // box was not a terminal unbounded box, in which case it should ask for
+ // more input.
+ JxlDecoderStatus expected_status =
+ expect_success ? JXL_DEC_SUCCESS : JXL_DEC_NEED_MORE_INPUT;
+ EXPECT_EQ(expected_status, process_input(dec));
return pixels;
}
@@ -474,11 +543,12 @@ std::vector<uint8_t> DecodeWithAPI(JxlDecoder* dec,
std::vector<uint8_t> DecodeWithAPI(Span<const uint8_t> compressed,
const JxlPixelFormat& format,
bool use_callback, bool set_buffer_early,
- bool use_resizable_runner) {
+ bool use_resizable_runner,
+ bool require_boxes, bool expect_success) {
JxlDecoder* dec = JxlDecoderCreate(NULL);
std::vector<uint8_t> pixels =
DecodeWithAPI(dec, compressed, format, use_callback, set_buffer_early,
- use_resizable_runner);
+ use_resizable_runner, require_boxes, expect_success);
JxlDecoderDestroy(dec);
return pixels;
}
@@ -1184,15 +1254,19 @@ TEST_P(DecodeTestParam, PixelTest) {
jxl::test::GetSomeTestImage(config.xsize, config.ysize, orig_channels, 0);
JxlPixelFormat format_orig = {orig_channels, JXL_TYPE_UINT16, JXL_BIG_ENDIAN,
0};
- jxl::CompressParams cparams;
- cparams.SetLossless(); // Lossless to verify pixels exactly after roundtrip.
- cparams.speed_tier = jxl::SpeedTier::kThunder;
- cparams.resampling = config.upsampling;
- cparams.ec_resampling = config.upsampling;
+ jxl::TestCodestreamParams params;
+ // Lossless to verify pixels exactly after roundtrip.
+ params.cparams.SetLossless();
+ params.cparams.speed_tier = jxl::SpeedTier::kThunder;
+ params.cparams.resampling = config.upsampling;
+ params.cparams.ec_resampling = config.upsampling;
+ params.box_format = config.add_container;
+ params.orientation = config.orientation;
+ params.add_preview = config.add_preview;
+ params.add_intrinsic_size = config.add_intrinsic_size;
jxl::PaddedBytes compressed = jxl::CreateTestJXLCodestream(
jxl::Span<const uint8_t>(pixels.data(), pixels.size()), config.xsize,
- config.ysize, orig_channels, cparams, config.add_container,
- config.orientation, config.add_preview, config.add_intrinsic_size);
+ config.ysize, orig_channels, params);
JxlPixelFormat format = {config.output_channels, config.data_type,
config.endianness, 0};
@@ -1204,7 +1278,8 @@ TEST_P(DecodeTestParam, PixelTest) {
std::vector<uint8_t> pixels2 = jxl::DecodeWithAPI(
dec, jxl::Span<const uint8_t>(compressed.data(), compressed.size()),
format, config.use_callback, config.set_buffer_early,
- config.use_resizable_runner);
+ config.use_resizable_runner, /*require_boxes=*/false,
+ /*expect_success=*/true);
JxlDecoderReset(dec);
EXPECT_EQ(num_pixels * config.output_channels *
jxl::test::GetDataBits(config.data_type) / jxl::kBitsPerByte,
@@ -1222,12 +1297,12 @@ TEST_P(DecodeTestParam, PixelTest) {
if (config.include_alpha) io.metadata.m.SetAlphaBits(16);
io.SetSize(config.xsize, config.ysize);
- EXPECT_TRUE(ConvertFromExternal(
- bytes, config.xsize, config.ysize, color_encoding,
- config.output_channels,
- /*alpha_is_premultiplied=*/false, 16, JXL_BIG_ENDIAN,
- /*flipped_y=*/false, nullptr, &io.Main(), /*float_in=*/false,
- /*align=*/0));
+ EXPECT_TRUE(ConvertFromExternal(bytes, config.xsize, config.ysize,
+ color_encoding, config.output_channels,
+ /*alpha_is_premultiplied=*/false, 16,
+ JXL_BIG_ENDIAN, nullptr, &io.Main(),
+ /*float_in=*/false,
+ /*align=*/0));
for (size_t i = 0; i < pixels.size(); i++) pixels[i] = 0;
EXPECT_TRUE(ConvertToExternal(
@@ -1452,14 +1527,16 @@ TEST(DecodeTest, PixelTestWithICCProfileLossless) {
size_t num_pixels = xsize * ysize;
std::vector<uint8_t> pixels = jxl::test::GetSomeTestImage(xsize, ysize, 4, 0);
JxlPixelFormat format_orig = {4, JXL_TYPE_UINT16, JXL_BIG_ENDIAN, 0};
- jxl::CompressParams cparams;
- cparams.SetLossless(); // Lossless to verify pixels exactly after roundtrip.
- cparams.speed_tier = jxl::SpeedTier::kThunder;
+ jxl::TestCodestreamParams params;
+ // Lossless to verify pixels exactly after roundtrip.
+ params.cparams.SetLossless();
+ params.cparams.speed_tier = jxl::SpeedTier::kThunder;
+ params.add_icc_profile = true;
// For variation: some have container and no preview, others have preview
// and no container.
jxl::PaddedBytes compressed = jxl::CreateTestJXLCodestream(
jxl::Span<const uint8_t>(pixels.data(), pixels.size()), xsize, ysize, 4,
- cparams, kCSBF_None, JXL_ORIENT_IDENTITY, false, false, true);
+ params);
for (uint32_t channels = 3; channels <= 4; ++channels) {
{
@@ -1468,7 +1545,8 @@ TEST(DecodeTest, PixelTestWithICCProfileLossless) {
std::vector<uint8_t> pixels2 = jxl::DecodeWithAPI(
dec, jxl::Span<const uint8_t>(compressed.data(), compressed.size()),
format, /*use_callback=*/false, /*set_buffer_early=*/false,
- /*use_resizable_runner=*/false);
+ /*use_resizable_runner=*/false, /*require_boxes=*/false,
+ /*expect_success=*/true);
JxlDecoderReset(dec);
EXPECT_EQ(num_pixels * channels, pixels2.size());
EXPECT_EQ(0u,
@@ -1482,7 +1560,8 @@ TEST(DecodeTest, PixelTestWithICCProfileLossless) {
std::vector<uint8_t> pixels2 = jxl::DecodeWithAPI(
dec, jxl::Span<const uint8_t>(compressed.data(), compressed.size()),
format, /*use_callback=*/true, /*set_buffer_early=*/true,
- /*use_resizable_runner=*/false);
+ /*use_resizable_runner=*/false, /*require_boxes=*/false,
+ /*expect_success=*/true);
JxlDecoderReset(dec);
EXPECT_EQ(num_pixels * channels * 2, pixels2.size());
EXPECT_EQ(0u,
@@ -1496,7 +1575,8 @@ TEST(DecodeTest, PixelTestWithICCProfileLossless) {
std::vector<uint8_t> pixels2 = jxl::DecodeWithAPI(
dec, jxl::Span<const uint8_t>(compressed.data(), compressed.size()),
format, /*use_callback=*/false, /*set_buffer_early=*/false,
- /*use_resizable_runner=*/false);
+ /*use_resizable_runner=*/false, /*reuqire_boxes=*/false,
+ /*expect_success=*/true);
JxlDecoderReset(dec);
EXPECT_EQ(num_pixels * channels * 4, pixels2.size());
EXPECT_EQ(0u,
@@ -1515,12 +1595,11 @@ TEST(DecodeTest, PixelTestWithICCProfileLossy) {
size_t num_pixels = xsize * ysize;
std::vector<uint8_t> pixels = jxl::test::GetSomeTestImage(xsize, ysize, 3, 0);
JxlPixelFormat format_orig = {3, JXL_TYPE_UINT16, JXL_BIG_ENDIAN, 0};
- jxl::CompressParams cparams;
+ jxl::TestCodestreamParams params;
+ params.add_icc_profile = true;
jxl::PaddedBytes compressed = jxl::CreateTestJXLCodestream(
jxl::Span<const uint8_t>(pixels.data(), pixels.size()), xsize, ysize, 3,
- cparams, kCSBF_None, JXL_ORIENT_IDENTITY, /*add_preview=*/false,
- /*add_intrinsic_size=*/false,
- /*add_icc_profile=*/true);
+ params);
uint32_t channels = 3;
JxlPixelFormat format = {channels, JXL_TYPE_FLOAT, JXL_LITTLE_ENDIAN, 0};
@@ -1529,7 +1608,8 @@ TEST(DecodeTest, PixelTestWithICCProfileLossy) {
std::vector<uint8_t> pixels2 = jxl::DecodeWithAPI(
dec, jxl::Span<const uint8_t>(compressed.data(), compressed.size()),
format, /*use_callback=*/false, /*set_buffer_early=*/true,
- /*use_resizable_runner=*/false, /*icc=*/&icc);
+ /*use_resizable_runner=*/false, /*require_boxes=*/false,
+ /*expect_success=*/true, /*icc=*/&icc);
JxlDecoderReset(dec);
EXPECT_EQ(num_pixels * channels * 4, pixels2.size());
@@ -1540,12 +1620,12 @@ TEST(DecodeTest, PixelTestWithICCProfileLossy) {
jxl::Span<const uint8_t> span0(pixels.data(), pixels.size());
jxl::CodecInOut io0;
io0.SetSize(xsize, ysize);
- EXPECT_TRUE(ConvertFromExternal(
- span0, xsize, ysize, color_encoding0, /*channels=*/3,
- /*alpha_is_premultiplied=*/false, /*bits_per_sample=*/16,
- format_orig.endianness,
- /*flipped_y=*/false, /*pool=*/nullptr, &io0.Main(), /*float_in=*/false,
- /*align=*/0));
+ EXPECT_TRUE(
+ ConvertFromExternal(span0, xsize, ysize, color_encoding0, /*channels=*/3,
+ /*alpha_is_premultiplied=*/false,
+ /*bits_per_sample=*/16, format_orig.endianness,
+ /*pool=*/nullptr, &io0.Main(), /*float_in=*/false,
+ /*align=*/0));
jxl::ColorEncoding color_encoding1;
EXPECT_TRUE(color_encoding1.SetICC(std::move(icc)));
@@ -1555,17 +1635,209 @@ TEST(DecodeTest, PixelTestWithICCProfileLossy) {
EXPECT_TRUE(ConvertFromExternal(span1, xsize, ysize, color_encoding1,
channels, /*alpha_is_premultiplied=*/false,
/*bits_per_sample=*/32, format.endianness,
- /*flipped_y=*/false, /*pool=*/nullptr,
- &io1.Main(), /*float_in=*/true, /*align=*/0));
+ /*pool=*/nullptr, &io1.Main(),
+ /*float_in=*/true, /*align=*/0));
jxl::ButteraugliParams ba;
EXPECT_THAT(ButteraugliDistance(io0, io1, ba, jxl::GetJxlCms(),
/*distmap=*/nullptr, nullptr),
- IsSlightlyBelow(0.77f));
+ IsSlightlyBelow(0.785f));
+
+ JxlDecoderDestroy(dec);
+}
+
+std::string ColorDescription(JxlColorEncoding c) {
+ jxl::ColorEncoding color_encoding;
+ EXPECT_TRUE(ConvertExternalToInternalColorEncoding(c, &color_encoding));
+ return Description(color_encoding);
+}
+
+std::string GetOrigProfile(JxlDecoder* dec) {
+ JxlColorEncoding c;
+ JxlColorProfileTarget target = JXL_COLOR_PROFILE_TARGET_ORIGINAL;
+ EXPECT_EQ(JXL_DEC_SUCCESS,
+ JxlDecoderGetColorAsEncodedProfile(dec, nullptr, target, &c));
+ return ColorDescription(c);
+}
+std::string GetDataProfile(JxlDecoder* dec) {
+ JxlColorEncoding c;
+ JxlColorProfileTarget target = JXL_COLOR_PROFILE_TARGET_DATA;
+ EXPECT_EQ(JXL_DEC_SUCCESS,
+ JxlDecoderGetColorAsEncodedProfile(dec, nullptr, target, &c));
+ return ColorDescription(c);
+}
+
+double ButteraugliDistance(size_t xsize, size_t ysize,
+ const std::vector<uint8_t>& pixels_in,
+ const jxl::ColorEncoding& color_in,
+ float intensity_in,
+ const std::vector<uint8_t>& pixels_out,
+ const jxl::ColorEncoding& color_out,
+ float intensity_out) {
+ jxl::CodecInOut in;
+ in.metadata.m.color_encoding = color_in;
+ in.metadata.m.SetIntensityTarget(intensity_in);
+ EXPECT_TRUE(jxl::ConvertFromExternal(
+ jxl::Span<const uint8_t>(pixels_in.data(), pixels_in.size()), xsize,
+ ysize, color_in, color_in.Channels(),
+ /*alpha_is_premultiplied=*/false,
+ /*bits_per_sample=*/16, JXL_BIG_ENDIAN,
+ /*pool=*/nullptr, &in.Main(), /*float_in=*/false, /*align=*/0));
+ jxl::CodecInOut out;
+ out.metadata.m.color_encoding = color_out;
+ out.metadata.m.SetIntensityTarget(intensity_out);
+ EXPECT_TRUE(jxl::ConvertFromExternal(
+ jxl::Span<const uint8_t>(pixels_out.data(), pixels_out.size()), xsize,
+ ysize, color_out, color_out.Channels(),
+ /*alpha_is_premultiplied=*/false,
+ /*bits_per_sample=*/16, JXL_BIG_ENDIAN,
+ /*pool=*/nullptr, &out.Main(), /*float_in=*/false, /*align=*/0));
+ return ButteraugliDistance(in, out, jxl::ButteraugliParams(),
+ jxl::GetJxlCms(), nullptr, nullptr);
+}
+
+class DecodeAllEncodingsTest
+ : public ::testing::TestWithParam<jxl::test::ColorEncodingDescriptor> {};
+JXL_GTEST_INSTANTIATE_TEST_SUITE_P(
+ DecodeAllEncodingsTestInstantiation, DecodeAllEncodingsTest,
+ ::testing::ValuesIn(jxl::test::AllEncodings()));
+TEST_P(DecodeAllEncodingsTest, PreserveOriginalProfileTest) {
+ size_t xsize = 123, ysize = 77;
+ std::vector<uint8_t> pixels = jxl::test::GetSomeTestImage(xsize, ysize, 3, 0);
+ JxlPixelFormat format = {3, JXL_TYPE_UINT16, JXL_BIG_ENDIAN, 0};
+ int events = JXL_DEC_BASIC_INFO | JXL_DEC_COLOR_ENCODING | JXL_DEC_FULL_IMAGE;
+ const auto& cdesc = GetParam();
+ jxl::ColorEncoding c_in = jxl::test::ColorEncodingFromDescriptor(cdesc);
+ if (c_in.rendering_intent != jxl::RenderingIntent::kRelative) return;
+ std::string color_space_in = Description(c_in);
+ float intensity_in = c_in.tf.IsPQ() ? 10000 : 255;
+ printf("Testing input color space %s\n", color_space_in.c_str());
+ jxl::TestCodestreamParams params;
+ params.color_space = color_space_in;
+ params.intensity_target = intensity_in;
+ jxl::PaddedBytes data = jxl::CreateTestJXLCodestream(
+ jxl::Span<const uint8_t>(pixels.data(), pixels.size()), xsize, ysize, 3,
+ params);
+ JxlDecoder* dec = JxlDecoderCreate(nullptr);
+ EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderSubscribeEvents(dec, events));
+ EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderSetInput(dec, data.data(), data.size()));
+ EXPECT_EQ(JXL_DEC_BASIC_INFO, JxlDecoderProcessInput(dec));
+ JxlBasicInfo info;
+ EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderGetBasicInfo(dec, &info));
+ EXPECT_EQ(xsize, info.xsize);
+ EXPECT_EQ(ysize, info.ysize);
+ EXPECT_FALSE(info.uses_original_profile);
+ EXPECT_EQ(JXL_DEC_COLOR_ENCODING, JxlDecoderProcessInput(dec));
+ EXPECT_EQ(GetOrigProfile(dec), color_space_in);
+ EXPECT_EQ(GetDataProfile(dec), color_space_in);
+ EXPECT_EQ(JXL_DEC_NEED_IMAGE_OUT_BUFFER, JxlDecoderProcessInput(dec));
+ std::vector<uint8_t> out(pixels.size());
+ EXPECT_EQ(JXL_DEC_SUCCESS,
+ JxlDecoderSetImageOutBuffer(dec, &format, out.data(), out.size()));
+ EXPECT_EQ(JXL_DEC_FULL_IMAGE, JxlDecoderProcessInput(dec));
+ double dist = ButteraugliDistance(xsize, ysize, pixels, c_in, intensity_in,
+ out, c_in, intensity_in);
+ EXPECT_LT(dist, 1.2);
+ EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderProcessInput(dec));
JxlDecoderDestroy(dec);
}
+namespace {
+void SetPreferredColorProfileTest(
+ const jxl::test::ColorEncodingDescriptor& from) {
+ size_t xsize = 123, ysize = 77;
+ int events = JXL_DEC_BASIC_INFO | JXL_DEC_COLOR_ENCODING | JXL_DEC_FULL_IMAGE;
+ jxl::ColorEncoding c_in = jxl::test::ColorEncodingFromDescriptor(from);
+ if (c_in.rendering_intent != jxl::RenderingIntent::kRelative) return;
+ if (c_in.white_point != jxl::WhitePoint::kD65) return;
+ uint32_t num_channels = c_in.Channels();
+ std::vector<uint8_t> pixels =
+ jxl::test::GetSomeTestImage(xsize, ysize, num_channels, 0);
+ JxlPixelFormat format = {num_channels, JXL_TYPE_UINT16, JXL_BIG_ENDIAN, 0};
+ std::string color_space_in = Description(c_in);
+ float intensity_in = c_in.tf.IsPQ() ? 10000 : 255;
+ jxl::TestCodestreamParams params;
+ params.color_space = color_space_in;
+ params.intensity_target = intensity_in;
+ jxl::PaddedBytes data = jxl::CreateTestJXLCodestream(
+ jxl::Span<const uint8_t>(pixels.data(), pixels.size()), xsize, ysize,
+ num_channels, params);
+ for (const auto& c1 : jxl::test::AllEncodings()) {
+ jxl::ColorEncoding c_out = jxl::test::ColorEncodingFromDescriptor(c1);
+ float intensity_out = intensity_in;
+ if (c_out.rendering_intent != jxl::RenderingIntent::kRelative) continue;
+ if ((c_in.primaries == jxl::Primaries::k2100 &&
+ c_out.primaries != jxl::Primaries::k2100) ||
+ (c_in.primaries == jxl::Primaries::kP3 &&
+ c_out.primaries == jxl::Primaries::kSRGB)) {
+ // Converting to a narrower gamut does not work without gammut mapping.
+ continue;
+ }
+ if (c_out.tf.IsHLG() && intensity_out > 300) {
+ // The Linear->HLG OOTF function at this intensity level can push
+ // saturated colors out of gamut, so we would need gamut mapping in
+ // this case too.
+ continue;
+ }
+ std::string color_space_out = Description(c_out);
+ if (color_space_in == color_space_out) continue;
+ printf("Testing input color space %s with output color space %s\n",
+ color_space_in.c_str(), color_space_out.c_str());
+ JxlDecoder* dec = JxlDecoderCreate(nullptr);
+ EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderSubscribeEvents(dec, events));
+ EXPECT_EQ(JXL_DEC_SUCCESS,
+ JxlDecoderSetInput(dec, data.data(), data.size()));
+ EXPECT_EQ(JXL_DEC_BASIC_INFO, JxlDecoderProcessInput(dec));
+ JxlBasicInfo info;
+ EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderGetBasicInfo(dec, &info));
+ EXPECT_EQ(xsize, info.xsize);
+ EXPECT_EQ(ysize, info.ysize);
+ EXPECT_FALSE(info.uses_original_profile);
+ EXPECT_EQ(JXL_DEC_COLOR_ENCODING, JxlDecoderProcessInput(dec));
+ EXPECT_EQ(GetOrigProfile(dec), color_space_in);
+ EXPECT_EQ(GetDataProfile(dec), color_space_in);
+ JxlColorEncoding encoding_out;
+ EXPECT_TRUE(jxl::ParseDescription(color_space_out, &encoding_out));
+ EXPECT_EQ(JXL_DEC_SUCCESS,
+ JxlDecoderSetPreferredColorProfile(dec, &encoding_out));
+ EXPECT_EQ(GetOrigProfile(dec), color_space_in);
+ EXPECT_EQ(GetDataProfile(dec), color_space_out);
+ EXPECT_EQ(JXL_DEC_NEED_IMAGE_OUT_BUFFER, JxlDecoderProcessInput(dec));
+ size_t buffer_size;
+ JxlPixelFormat out_format = format;
+ out_format.num_channels = c_out.Channels();
+ EXPECT_EQ(JXL_DEC_SUCCESS,
+ JxlDecoderImageOutBufferSize(dec, &out_format, &buffer_size));
+ std::vector<uint8_t> out(buffer_size);
+ EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderSetImageOutBuffer(
+ dec, &out_format, out.data(), out.size()));
+ EXPECT_EQ(JXL_DEC_FULL_IMAGE, JxlDecoderProcessInput(dec));
+ double dist = ButteraugliDistance(xsize, ysize, pixels, c_in, intensity_in,
+ out, c_out, intensity_out);
+ if (c_in.white_point == c_out.white_point) {
+ EXPECT_LT(dist, 1.2);
+ } else {
+ EXPECT_LT(dist, 4.0);
+ }
+ EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderProcessInput(dec));
+ JxlDecoderDestroy(dec);
+ }
+}
+} // namespace
+
+TEST(DecodeTest, SetPreferredColorProfileTestFromGray) {
+ jxl::test::ColorEncodingDescriptor gray = {
+ jxl::ColorSpace::kGray, jxl::WhitePoint::kD65, jxl::Primaries::kSRGB,
+ jxl::TransferFunction::kSRGB, jxl::RenderingIntent::kRelative};
+ SetPreferredColorProfileTest(gray);
+}
+
+TEST_P(DecodeAllEncodingsTest, SetPreferredColorProfileTest) {
+ const auto& from = GetParam();
+ SetPreferredColorProfileTest(from);
+}
+
// Tests the case of lossy sRGB image without alpha channel, decoded to RGB8
// and to RGBA8
TEST(DecodeTest, PixelTestOpaqueSrgbLossy) {
@@ -1577,24 +1849,20 @@ TEST(DecodeTest, PixelTestOpaqueSrgbLossy) {
std::vector<uint8_t> pixels =
jxl::test::GetSomeTestImage(xsize, ysize, 3, 0);
JxlPixelFormat format_orig = {3, JXL_TYPE_UINT16, JXL_BIG_ENDIAN, 0};
- jxl::CompressParams cparams;
jxl::PaddedBytes compressed = jxl::CreateTestJXLCodestream(
jxl::Span<const uint8_t>(pixels.data(), pixels.size()), xsize, ysize, 3,
- cparams, kCSBF_None, JXL_ORIENT_IDENTITY, /*add_preview=*/false,
- /*add_intrinsic_size=*/false,
- /*add_icc_profile=*/false);
+ jxl::TestCodestreamParams());
JxlPixelFormat format = {channels, JXL_TYPE_UINT8, JXL_LITTLE_ENDIAN, 0};
std::vector<uint8_t> pixels2 = jxl::DecodeWithAPI(
dec, jxl::Span<const uint8_t>(compressed.data(), compressed.size()),
format, /*use_callback=*/true, /*set_buffer_early=*/false,
- /*use_resizable_runner=*/false);
+ /*use_resizable_runner=*/false, /*require_boxes=*/false,
+ /*expect_success*/ true);
JxlDecoderReset(dec);
EXPECT_EQ(num_pixels * channels, pixels2.size());
- // The input pixels use the profile matching GetIccTestProfile, since we set
- // add_icc_profile for CreateTestJXLCodestream to true.
jxl::ColorEncoding color_encoding0 = jxl::ColorEncoding::SRGB(false);
jxl::Span<const uint8_t> span0(pixels.data(), pixels.size());
jxl::CodecInOut io0;
@@ -1603,7 +1871,7 @@ TEST(DecodeTest, PixelTestOpaqueSrgbLossy) {
span0, xsize, ysize, color_encoding0, /*channels=*/3,
/*alpha_is_premultiplied=*/false, /*bits_per_sample=*/16,
format_orig.endianness,
- /*flipped_y=*/false, /*pool=*/nullptr, &io0.Main(), /*float_in=*/false,
+ /*pool=*/nullptr, &io0.Main(), /*float_in=*/false,
/*align=*/0));
jxl::ColorEncoding color_encoding1 = jxl::ColorEncoding::SRGB(false);
@@ -1612,8 +1880,8 @@ TEST(DecodeTest, PixelTestOpaqueSrgbLossy) {
EXPECT_TRUE(ConvertFromExternal(span1, xsize, ysize, color_encoding1,
channels, /*alpha_is_premultiplied=*/false,
/*bits_per_sample=*/8, format.endianness,
- /*flipped_y=*/false, /*pool=*/nullptr,
- &io1.Main(), /*float_in=*/false,
+ /*pool=*/nullptr, &io1.Main(),
+ /*float_in=*/false,
/*align=*/0));
jxl::ButteraugliParams ba;
@@ -1635,25 +1903,22 @@ TEST(DecodeTest, PixelTestOpaqueSrgbLossyNoise) {
std::vector<uint8_t> pixels =
jxl::test::GetSomeTestImage(xsize, ysize, 3, 0);
JxlPixelFormat format_orig = {3, JXL_TYPE_UINT16, JXL_BIG_ENDIAN, 0};
- jxl::CompressParams cparams;
- cparams.noise = jxl::Override::kOn;
+ jxl::TestCodestreamParams params;
+ params.cparams.noise = jxl::Override::kOn;
jxl::PaddedBytes compressed = jxl::CreateTestJXLCodestream(
jxl::Span<const uint8_t>(pixels.data(), pixels.size()), xsize, ysize, 3,
- cparams, kCSBF_None, JXL_ORIENT_IDENTITY, /*add_preview=*/false,
- /*add_intrinsic_size=*/false,
- /*add_icc_profile=*/false);
+ params);
JxlPixelFormat format = {channels, JXL_TYPE_UINT8, JXL_LITTLE_ENDIAN, 0};
std::vector<uint8_t> pixels2 = jxl::DecodeWithAPI(
dec, jxl::Span<const uint8_t>(compressed.data(), compressed.size()),
format, /*use_callback=*/false, /*set_buffer_early=*/true,
- /*use_resizable_runner=*/false);
+ /*use_resizable_runner=*/false, /*require_boxes=*/false,
+ /*expect_success=*/true);
JxlDecoderReset(dec);
EXPECT_EQ(num_pixels * channels, pixels2.size());
- // The input pixels use the profile matching GetIccTestProfile, since we set
- // add_icc_profile for CreateTestJXLCodestream to true.
jxl::ColorEncoding color_encoding0 = jxl::ColorEncoding::SRGB(false);
jxl::Span<const uint8_t> span0(pixels.data(), pixels.size());
jxl::CodecInOut io0;
@@ -1662,7 +1927,7 @@ TEST(DecodeTest, PixelTestOpaqueSrgbLossyNoise) {
span0, xsize, ysize, color_encoding0, /*channels=*/3,
/*alpha_is_premultiplied=*/false, /*bits_per_sample=*/16,
format_orig.endianness,
- /*flipped_y=*/false, /*pool=*/nullptr, &io0.Main(), /*float_in=*/false,
+ /*pool=*/nullptr, &io0.Main(), /*float_in=*/false,
/*align=*/0));
jxl::ColorEncoding color_encoding1 = jxl::ColorEncoding::SRGB(false);
@@ -1671,8 +1936,8 @@ TEST(DecodeTest, PixelTestOpaqueSrgbLossyNoise) {
EXPECT_TRUE(ConvertFromExternal(span1, xsize, ysize, color_encoding1,
channels, /*alpha_is_premultiplied=*/false,
/*bits_per_sample=*/8, format.endianness,
- /*flipped_y=*/false, /*pool=*/nullptr,
- &io1.Main(), /*float_in=*/false,
+ /*pool=*/nullptr, &io1.Main(),
+ /*float_in=*/false,
/*align=*/0));
jxl::ButteraugliParams ba;
@@ -1684,6 +1949,169 @@ TEST(DecodeTest, PixelTestOpaqueSrgbLossyNoise) {
}
}
+TEST(DecodeTest, ProcessEmptyInputWithBoxes) {
+ size_t xsize = 123, ysize = 77;
+ std::vector<uint8_t> pixels = jxl::test::GetSomeTestImage(xsize, ysize, 3, 0);
+ jxl::CompressParams cparams;
+ uint32_t channels = 3;
+ JxlPixelFormat format = {channels, JXL_TYPE_FLOAT, JXL_LITTLE_ENDIAN, 0};
+ for (int i = 0; i < kCSBF_NUM_ENTRIES; ++i) {
+ JxlDecoder* dec = JxlDecoderCreate(NULL);
+ jxl::TestCodestreamParams params;
+ params.box_format = (CodeStreamBoxFormat)i;
+ printf("Testing empty input with box format %d\n", (int)params.box_format);
+ jxl::PaddedBytes compressed = jxl::CreateTestJXLCodestream(
+ jxl::Span<const uint8_t>(pixels.data(), pixels.size()), xsize, ysize, 3,
+ params);
+ const int events =
+ JXL_DEC_BASIC_INFO | JXL_DEC_FULL_IMAGE | JXL_DEC_COLOR_ENCODING;
+ EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderSubscribeEvents(dec, events));
+ EXPECT_EQ(JXL_DEC_NEED_MORE_INPUT, JxlDecoderProcessInput(dec));
+ EXPECT_EQ(JXL_DEC_SUCCESS,
+ JxlDecoderSetInput(dec, compressed.data(), compressed.size()));
+ EXPECT_EQ(JXL_DEC_BASIC_INFO, JxlDecoderProcessInput(dec));
+ EXPECT_EQ(JXL_DEC_COLOR_ENCODING, JxlDecoderProcessInput(dec));
+ size_t buffer_size;
+ EXPECT_EQ(JXL_DEC_SUCCESS,
+ JxlDecoderImageOutBufferSize(dec, &format, &buffer_size));
+ JxlBasicInfo info;
+ EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderGetBasicInfo(dec, &info));
+ const size_t remaining = JxlDecoderReleaseInput(dec);
+ EXPECT_LE(remaining, compressed.size());
+ EXPECT_EQ(JXL_DEC_NEED_MORE_INPUT, JxlDecoderProcessInput(dec));
+ JxlDecoderDestroy(dec);
+ }
+}
+
+TEST(DecodeTest, ExtraBytesAfterCompressedStream) {
+ size_t xsize = 123, ysize = 77;
+ size_t num_pixels = xsize * ysize;
+ std::vector<uint8_t> pixels = jxl::test::GetSomeTestImage(xsize, ysize, 3, 0);
+ jxl::CompressParams cparams;
+ for (int i = 0; i < kCSBF_NUM_ENTRIES; ++i) {
+ CodeStreamBoxFormat box_format = (CodeStreamBoxFormat)i;
+ if (box_format == kCSBF_Multi_Other_Zero_Terminated) continue;
+ printf("Testing with box format %d\n", (int)box_format);
+ size_t last_unknown_box_size = 0;
+ if (box_format == kCSBF_Single_Other) {
+ last_unknown_box_size = unk1_box_size + 8;
+ } else if (box_format == kCSBF_Multi_Other_Terminated) {
+ last_unknown_box_size = unk3_box_size + 8;
+ } else if (box_format == kCSBF_Multi_Last_Empty_Other) {
+ // If boxes are not required, the decoder wont consume the last empty
+ // jxlp box.
+ last_unknown_box_size = 12 + unk3_box_size + 8;
+ }
+ jxl::TestCodestreamParams params;
+ params.box_format = box_format;
+ jxl::PaddedBytes compressed = jxl::CreateTestJXLCodestream(
+ jxl::Span<const uint8_t>(pixels.data(), pixels.size()), xsize, ysize, 3,
+ params);
+ // Add some more bytes after compressed data.
+ compressed.push_back(0);
+ compressed.push_back(1);
+ compressed.push_back(2);
+ JxlDecoder* dec = JxlDecoderCreate(NULL);
+ uint32_t channels = 3;
+ JxlPixelFormat format = {channels, JXL_TYPE_FLOAT, JXL_LITTLE_ENDIAN, 0};
+ std::vector<uint8_t> pixels2 = jxl::DecodeWithAPI(
+ dec, jxl::Span<const uint8_t>(compressed.data(), compressed.size()),
+ format, /*use_callback=*/false, /*set_buffer_early=*/true,
+ /*use_resizable_runner=*/false, /*require_boxes=*/false,
+ /*expect_success=*/true);
+ size_t unconsumed_bytes = JxlDecoderReleaseInput(dec);
+ EXPECT_EQ(last_unknown_box_size + 3, unconsumed_bytes);
+ EXPECT_EQ(num_pixels * channels * 4, pixels2.size());
+ JxlDecoderDestroy(dec);
+ }
+}
+
+TEST(DecodeTest, ExtraBytesAfterCompressedStreamRequireBoxes) {
+ size_t xsize = 123, ysize = 77;
+ size_t num_pixels = xsize * ysize;
+ std::vector<uint8_t> pixels = jxl::test::GetSomeTestImage(xsize, ysize, 3, 0);
+ jxl::CompressParams cparams;
+ for (int i = 0; i < kCSBF_NUM_ENTRIES; ++i) {
+ CodeStreamBoxFormat box_format = (CodeStreamBoxFormat)i;
+ if (box_format == kCSBF_Multi_Other_Zero_Terminated) continue;
+ printf("Testing with box format %d\n", (int)box_format);
+ bool expect_success = (box_format == kCSBF_None ||
+ box_format == kCSBF_Single_Zero_Terminated ||
+ box_format == kCSBF_Multi_Zero_Terminated);
+ jxl::TestCodestreamParams params;
+ params.box_format = box_format;
+ jxl::PaddedBytes compressed = jxl::CreateTestJXLCodestream(
+ jxl::Span<const uint8_t>(pixels.data(), pixels.size()), xsize, ysize, 3,
+ params);
+ // Add some more bytes after compressed data.
+ compressed.push_back(0);
+ compressed.push_back(1);
+ compressed.push_back(2);
+ JxlDecoder* dec = JxlDecoderCreate(NULL);
+ uint32_t channels = 3;
+ JxlPixelFormat format = {channels, JXL_TYPE_FLOAT, JXL_LITTLE_ENDIAN, 0};
+ std::vector<uint8_t> pixels2 = jxl::DecodeWithAPI(
+ dec, jxl::Span<const uint8_t>(compressed.data(), compressed.size()),
+ format, /*use_callback=*/false, /*set_buffer_early=*/true,
+ /*use_resizable_runner=*/false, /*require_boxes=*/true, expect_success);
+ size_t unconsumed_bytes = JxlDecoderReleaseInput(dec);
+ EXPECT_EQ(3, unconsumed_bytes);
+ EXPECT_EQ(num_pixels * channels * 4, pixels2.size());
+ JxlDecoderDestroy(dec);
+ }
+}
+
+TEST(DecodeTest, ConcatenatedCompressedStreams) {
+ size_t xsize = 123, ysize = 77;
+ size_t num_pixels = xsize * ysize;
+ std::vector<uint8_t> pixels = jxl::test::GetSomeTestImage(xsize, ysize, 3, 0);
+ jxl::CompressParams cparams;
+ for (int i = 0; i < kCSBF_NUM_ENTRIES; ++i) {
+ CodeStreamBoxFormat first_box_format = (CodeStreamBoxFormat)i;
+ if (first_box_format == kCSBF_Multi_Other_Zero_Terminated) continue;
+ jxl::TestCodestreamParams params1;
+ params1.box_format = first_box_format;
+ jxl::PaddedBytes compressed1 = jxl::CreateTestJXLCodestream(
+ jxl::Span<const uint8_t>(pixels.data(), pixels.size()), xsize, ysize, 3,
+ params1);
+ for (int j = 0; j < kCSBF_NUM_ENTRIES; ++j) {
+ CodeStreamBoxFormat second_box_format = (CodeStreamBoxFormat)j;
+ if (second_box_format == kCSBF_Multi_Other_Zero_Terminated) continue;
+ printf("Testing with box format pair %d, %d\n", (int)first_box_format,
+ (int)second_box_format);
+ jxl::TestCodestreamParams params2;
+ params2.box_format = second_box_format;
+ jxl::PaddedBytes compressed2 = jxl::CreateTestJXLCodestream(
+ jxl::Span<const uint8_t>(pixels.data(), pixels.size()), xsize, ysize,
+ 3, params2);
+ jxl::PaddedBytes concat;
+ concat.append(compressed1);
+ concat.append(compressed2);
+ uint32_t channels = 3;
+ JxlPixelFormat format = {channels, JXL_TYPE_FLOAT, JXL_LITTLE_ENDIAN, 0};
+ size_t remaining = concat.size();
+ for (int part = 0; part < 2; ++part) {
+ printf(" Decoding part %d\n", part + 1);
+ JxlDecoder* dec = JxlDecoderCreate(NULL);
+ size_t pos = concat.size() - remaining;
+ bool expect_success =
+ (part == 0 || second_box_format == kCSBF_None ||
+ second_box_format == kCSBF_Single_Zero_Terminated ||
+ second_box_format == kCSBF_Multi_Zero_Terminated);
+ std::vector<uint8_t> pixels2 = jxl::DecodeWithAPI(
+ dec, jxl::Span<const uint8_t>(concat.data() + pos, remaining),
+ format, /*use_callback=*/false, /*set_buffer_early=*/true,
+ /*use_resizable_runner=*/false, /*require_boxes=*/true,
+ expect_success);
+ EXPECT_EQ(num_pixels * channels * 4, pixels2.size());
+ remaining = JxlDecoderReleaseInput(dec);
+ JxlDecoderDestroy(dec);
+ }
+ EXPECT_EQ(0, remaining);
+ }
+ }
+}
+
void TestPartialStream(bool reconstructible_jpeg) {
size_t xsize = 123, ysize = 77;
uint32_t channels = 4;
@@ -1693,12 +2121,12 @@ void TestPartialStream(bool reconstructible_jpeg) {
std::vector<uint8_t> pixels =
jxl::test::GetSomeTestImage(xsize, ysize, channels, 0);
JxlPixelFormat format_orig = {channels, JXL_TYPE_UINT16, JXL_BIG_ENDIAN, 0};
- jxl::CompressParams cparams;
+ jxl::TestCodestreamParams params;
if (reconstructible_jpeg) {
- cparams.color_transform = jxl::ColorTransform::kNone;
+ params.cparams.color_transform = jxl::ColorTransform::kNone;
} else {
// Lossless to verify pixels exactly after roundtrip.
- cparams.SetLossless();
+ params.cparams.SetLossless();
}
std::vector<uint8_t> pixels2;
@@ -1710,14 +2138,13 @@ void TestPartialStream(bool reconstructible_jpeg) {
std::vector<jxl::PaddedBytes> codestreams(kCSBF_NUM_ENTRIES);
std::vector<jxl::PaddedBytes> jpeg_codestreams(kCSBF_NUM_ENTRIES);
for (size_t i = 0; i < kCSBF_NUM_ENTRIES; ++i) {
- CodeStreamBoxFormat add_container = (CodeStreamBoxFormat)i;
+ params.box_format = (CodeStreamBoxFormat)i;
+ if (reconstructible_jpeg) {
+ params.jpeg_codestream = &jpeg_codestreams[i];
+ }
codestreams[i] = jxl::CreateTestJXLCodestream(
jxl::Span<const uint8_t>(pixels.data(), pixels.size()), xsize, ysize,
- channels, cparams, add_container, JXL_ORIENT_IDENTITY,
- /*add_preview=*/true,
- /*add_intrinsic_size=*/false,
- /*add_icc_profile=*/false,
- reconstructible_jpeg ? &jpeg_codestreams[i] : nullptr);
+ channels, params);
}
// Test multiple step sizes, to test different combinations of the streaming
@@ -1877,11 +2304,11 @@ TEST(DecodeTest, PreviewTest) {
size_t xsize = 77, ysize = 120;
std::vector<uint8_t> pixels = jxl::test::GetSomeTestImage(xsize, ysize, 3, 0);
- jxl::CompressParams cparams;
+ jxl::TestCodestreamParams params;
+ params.add_preview = true;
jxl::PaddedBytes compressed = jxl::CreateTestJXLCodestream(
jxl::Span<const uint8_t>(pixels.data(), pixels.size()), xsize, ysize, 3,
- cparams, kCSBF_Multi, JXL_ORIENT_IDENTITY, /*add_preview=*/true,
- /*add_intrinsic_size=*/false);
+ params);
JxlPixelFormat format = {3, JXL_TYPE_UINT8, JXL_LITTLE_ENDIAN, 0};
@@ -1963,12 +2390,13 @@ TEST(DecodeTest, AlignTest) {
std::vector<uint8_t> pixels = jxl::test::GetSomeTestImage(xsize, ysize, 4, 0);
JxlPixelFormat format_orig = {4, JXL_TYPE_UINT16, JXL_BIG_ENDIAN, 0};
- jxl::CompressParams cparams;
- cparams.SetLossless(); // Lossless to verify pixels exactly after roundtrip.
- cparams.speed_tier = jxl::SpeedTier::kThunder;
+ jxl::TestCodestreamParams params;
+ // Lossless to verify pixels exactly after roundtrip.
+ params.cparams.SetLossless();
+ params.cparams.speed_tier = jxl::SpeedTier::kThunder;
jxl::PaddedBytes compressed = jxl::CreateTestJXLCodestream(
jxl::Span<const uint8_t>(pixels.data(), pixels.size()), xsize, ysize, 4,
- cparams, kCSBF_None, JXL_ORIENT_IDENTITY, false, false);
+ params);
size_t align = 17;
JxlPixelFormat format = {3, JXL_TYPE_UINT8, JXL_LITTLE_ENDIAN, align};
@@ -1979,7 +2407,8 @@ TEST(DecodeTest, AlignTest) {
std::vector<uint8_t> pixels2 = jxl::DecodeWithAPI(
jxl::Span<const uint8_t>(compressed.data(), compressed.size()), format,
use_callback, /*set_buffer_early=*/false,
- /*use_resizable_runner=*/false);
+ /*use_resizable_runner=*/false, /*require_boxes=*/false,
+ /*expect_succes=*/true);
EXPECT_EQ(expected_line_bytes * ysize, pixels2.size());
EXPECT_EQ(0u, jxl::test::ComparePixels(pixels.data(), pixels2.data(), xsize,
ysize, format_orig, format));
@@ -2015,7 +2444,7 @@ TEST(DecodeTest, AnimationTest) {
jxl::Span<const uint8_t>(frames[i].data(), frames[i].size()), xsize,
ysize, jxl::ColorEncoding::SRGB(/*is_gray=*/false), /*channels=*/3,
/*alpha_is_premultiplied=*/false, /*bits_per_sample=*/16,
- JXL_BIG_ENDIAN, /*flipped_y=*/false, /*pool=*/nullptr, &bundle,
+ JXL_BIG_ENDIAN, /*pool=*/nullptr, &bundle,
/*float_in=*/false, /*align=*/0));
bundle.duration = frame_durations[i];
io.frames.push_back(std::move(bundle));
@@ -2119,7 +2548,7 @@ TEST(DecodeTest, AnimationTestStreaming) {
jxl::Span<const uint8_t>(frames[i].data(), frames[i].size()), xsize,
ysize, jxl::ColorEncoding::SRGB(/*is_gray=*/false), /*channels=*/3,
/*alpha_is_premultiplied=*/false, /*bits_per_sample=*/16,
- JXL_BIG_ENDIAN, /*flipped_y=*/false, /*pool=*/nullptr, &bundle,
+ JXL_BIG_ENDIAN, /*pool=*/nullptr, &bundle,
/*float_in=*/false, /*align=*/0));
bundle.duration = frame_durations[i];
io.frames.push_back(std::move(bundle));
@@ -2231,12 +2660,13 @@ TEST(DecodeTest, ExtraChannelTest) {
std::vector<uint8_t> pixels = jxl::test::GetSomeTestImage(xsize, ysize, 4, 0);
JxlPixelFormat format_orig = {4, JXL_TYPE_UINT16, JXL_BIG_ENDIAN, 0};
- jxl::CompressParams cparams;
- cparams.SetLossless(); // Lossless to verify pixels exactly after roundtrip.
- cparams.speed_tier = jxl::SpeedTier::kThunder;
+ jxl::TestCodestreamParams params;
+ // Lossless to verify pixels exactly after roundtrip.
+ params.cparams.SetLossless();
+ params.cparams.speed_tier = jxl::SpeedTier::kThunder;
jxl::PaddedBytes compressed = jxl::CreateTestJXLCodestream(
jxl::Span<const uint8_t>(pixels.data(), pixels.size()), xsize, ysize, 4,
- cparams, kCSBF_None, JXL_ORIENT_IDENTITY, false, false);
+ params);
size_t align = 17;
JxlPixelFormat format = {3, JXL_TYPE_UINT8, JXL_LITTLE_ENDIAN, align};
@@ -2303,6 +2733,122 @@ TEST(DecodeTest, ExtraChannelTest) {
format_orig_alpha, format_alpha));
}
+TEST(DecodeTest, SkipCurrentFrameTest) {
+ size_t xsize = 90, ysize = 120;
+ constexpr size_t num_frames = 7;
+ std::vector<uint8_t> frames[num_frames];
+ for (size_t i = 0; i < num_frames; i++) {
+ frames[i] = jxl::test::GetSomeTestImage(xsize, ysize, 3, i);
+ }
+ JxlPixelFormat format = {3, JXL_TYPE_UINT16, JXL_BIG_ENDIAN, 0};
+
+ jxl::CodecInOut io;
+ io.SetSize(xsize, ysize);
+ io.metadata.m.SetUintSamples(16);
+ io.metadata.m.color_encoding = jxl::ColorEncoding::SRGB(false);
+ io.metadata.m.have_animation = true;
+ io.frames.clear();
+ io.frames.reserve(num_frames);
+ io.SetSize(xsize, ysize);
+
+ std::vector<uint32_t> frame_durations(num_frames);
+ for (size_t i = 0; i < num_frames; ++i) {
+ frame_durations[i] = 5 + i;
+ }
+
+ for (size_t i = 0; i < num_frames; ++i) {
+ jxl::ImageBundle bundle(&io.metadata.m);
+ if (i & 1) {
+ // Mark some frames as referenceable, others not.
+ bundle.use_for_next_frame = true;
+ }
+
+ EXPECT_TRUE(ConvertFromExternal(
+ jxl::Span<const uint8_t>(frames[i].data(), frames[i].size()), xsize,
+ ysize, jxl::ColorEncoding::SRGB(/*is_gray=*/false), /*channels=*/3,
+ /*alpha_is_premultiplied=*/false, /*bits_per_sample=*/16,
+ JXL_BIG_ENDIAN, /*pool=*/nullptr, &bundle,
+ /*float_in=*/false, /*align=*/0));
+ bundle.duration = frame_durations[i];
+ io.frames.push_back(std::move(bundle));
+ }
+
+ jxl::CompressParams cparams;
+ cparams.speed_tier = jxl::SpeedTier::kThunder;
+ jxl::AuxOut aux_out;
+ jxl::PaddedBytes compressed;
+ jxl::PassesEncoderState enc_state;
+ jxl::PassDefinition passes[] = {
+ {2, 0, false, 4}, {4, 0, false, 4}, {8, 2, false, 2}, {8, 0, false, 1}};
+ jxl::ProgressiveMode progressive_mode{passes};
+ enc_state.progressive_splitter.SetProgressiveMode(progressive_mode);
+ EXPECT_TRUE(jxl::EncodeFile(cparams, &io, &enc_state, &compressed,
+ jxl::GetJxlCms(), &aux_out, nullptr));
+
+ JxlDecoder* dec = JxlDecoderCreate(NULL);
+ const uint8_t* next_in = compressed.data();
+ size_t avail_in = compressed.size();
+
+ EXPECT_EQ(JXL_DEC_SUCCESS,
+ JxlDecoderSubscribeEvents(dec, JXL_DEC_BASIC_INFO | JXL_DEC_FRAME |
+ JXL_DEC_FRAME_PROGRESSION |
+ JXL_DEC_FULL_IMAGE));
+ EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderSetProgressiveDetail(dec, kLastPasses));
+ EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderSetInput(dec, next_in, avail_in));
+
+ EXPECT_EQ(JXL_DEC_BASIC_INFO, JxlDecoderProcessInput(dec));
+ size_t buffer_size;
+ EXPECT_EQ(JXL_DEC_SUCCESS,
+ JxlDecoderImageOutBufferSize(dec, &format, &buffer_size));
+ JxlBasicInfo info;
+ EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderGetBasicInfo(dec, &info));
+
+ for (size_t i = 0; i < num_frames; ++i) {
+ printf("Decoding frame %d\n", (int)i);
+ EXPECT_EQ(JXL_DEC_ERROR, JxlDecoderSkipCurrentFrame(dec));
+ std::vector<uint8_t> pixels(buffer_size);
+ EXPECT_EQ(JXL_DEC_FRAME, JxlDecoderProcessInput(dec));
+ EXPECT_EQ(JXL_DEC_ERROR, JxlDecoderSkipCurrentFrame(dec));
+ JxlFrameHeader frame_header;
+ EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderGetFrameHeader(dec, &frame_header));
+ EXPECT_EQ(frame_durations[i], frame_header.duration);
+ EXPECT_EQ(i + 1 == num_frames, frame_header.is_last);
+ EXPECT_EQ(JXL_DEC_NEED_IMAGE_OUT_BUFFER, JxlDecoderProcessInput(dec));
+ EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderSetImageOutBuffer(
+ dec, &format, pixels.data(), pixels.size()));
+ if (i == 2) {
+ EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderSkipCurrentFrame(dec));
+ continue;
+ }
+ EXPECT_EQ(JXL_DEC_FRAME_PROGRESSION, JxlDecoderProcessInput(dec));
+ EXPECT_EQ(8, JxlDecoderGetIntendedDownsamplingRatio(dec));
+ if (i == 3) {
+ EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderSkipCurrentFrame(dec));
+ continue;
+ }
+ EXPECT_EQ(JXL_DEC_FRAME_PROGRESSION, JxlDecoderProcessInput(dec));
+ EXPECT_EQ(4, JxlDecoderGetIntendedDownsamplingRatio(dec));
+ if (i == 4) {
+ EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderSkipCurrentFrame(dec));
+ continue;
+ }
+ EXPECT_EQ(JXL_DEC_FRAME_PROGRESSION, JxlDecoderProcessInput(dec));
+ EXPECT_EQ(2, JxlDecoderGetIntendedDownsamplingRatio(dec));
+ if (i == 5) {
+ EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderSkipCurrentFrame(dec));
+ continue;
+ }
+ EXPECT_EQ(JXL_DEC_FULL_IMAGE, JxlDecoderProcessInput(dec));
+ EXPECT_EQ(JXL_DEC_ERROR, JxlDecoderSkipCurrentFrame(dec));
+ }
+
+ // After all frames were decoded, JxlDecoderProcessInput should return
+ // success to indicate all is done.
+ EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderProcessInput(dec));
+
+ JxlDecoderDestroy(dec);
+}
+
TEST(DecodeTest, SkipFrameTest) {
size_t xsize = 90, ysize = 120;
constexpr size_t num_frames = 16;
@@ -2337,7 +2883,7 @@ TEST(DecodeTest, SkipFrameTest) {
jxl::Span<const uint8_t>(frames[i].data(), frames[i].size()), xsize,
ysize, jxl::ColorEncoding::SRGB(/*is_gray=*/false), /*channels=*/3,
/*alpha_is_premultiplied=*/false, /*bits_per_sample=*/16,
- JXL_BIG_ENDIAN, /*flipped_y=*/false, /*pool=*/nullptr, &bundle,
+ JXL_BIG_ENDIAN, /*pool=*/nullptr, &bundle,
/*float_in=*/false, /*align=*/0));
bundle.duration = frame_durations[i];
io.frames.push_back(std::move(bundle));
@@ -2474,8 +3020,8 @@ TEST(DecodeTest, SkipFrameWithBlendingTest) {
xsize, ysize, jxl::ColorEncoding::SRGB(/*is_gray=*/false),
/*channels=*/3,
/*alpha_is_premultiplied=*/false, /*bits_per_sample=*/16,
- JXL_BIG_ENDIAN, /*flipped_y=*/false, /*pool=*/nullptr,
- &bundle_internal, /*float_in=*/false, /*align=*/0));
+ JXL_BIG_ENDIAN, /*pool=*/nullptr, &bundle_internal,
+ /*float_in=*/false, /*align=*/0));
bundle_internal.duration = 0;
bundle_internal.use_for_next_frame = true;
io.frames.push_back(std::move(bundle_internal));
@@ -2490,7 +3036,7 @@ TEST(DecodeTest, SkipFrameWithBlendingTest) {
jxl::Span<const uint8_t>(frame.data(), frame.size()), xsize, ysize,
jxl::ColorEncoding::SRGB(/*is_gray=*/false), /*channels=*/3,
/*alpha_is_premultiplied=*/false, /*bits_per_sample=*/16,
- JXL_BIG_ENDIAN, /*flipped_y=*/false, /*pool=*/nullptr, &bundle,
+ JXL_BIG_ENDIAN, /*pool=*/nullptr, &bundle,
/*float_in=*/false, /*align=*/0));
bundle.duration = frame_durations[i];
// Create some variation in which frames depend on which.
@@ -2701,8 +3247,8 @@ TEST(DecodeTest, SkipFrameWithAlphaBlendingTest) {
xsize / 2, ysize / 2, jxl::ColorEncoding::SRGB(/*is_gray=*/false),
/*channels=*/4,
/*alpha_is_premultiplied=*/false, /*bits_per_sample=*/16,
- JXL_BIG_ENDIAN, /*flipped_y=*/false, /*pool=*/nullptr,
- &bundle_internal, /*float_in=*/false, /*align=*/0));
+ JXL_BIG_ENDIAN, /*pool=*/nullptr, &bundle_internal,
+ /*float_in=*/false, /*align=*/0));
bundle_internal.duration = 0;
bundle_internal.use_for_next_frame = true;
bundle_internal.origin = {13, 17};
@@ -2722,7 +3268,7 @@ TEST(DecodeTest, SkipFrameWithAlphaBlendingTest) {
jxl::Span<const uint8_t>(frame.data(), frame.size()), cropxsize,
cropysize, jxl::ColorEncoding::SRGB(/*is_gray=*/false), /*channels=*/4,
/*alpha_is_premultiplied=*/false, /*bits_per_sample=*/16,
- JXL_BIG_ENDIAN, /*flipped_y=*/false, /*pool=*/nullptr, &bundle,
+ JXL_BIG_ENDIAN, /*pool=*/nullptr, &bundle,
/*float_in=*/false, /*align=*/0));
bundle.duration = 5 + i;
frame_durations_nc.push_back(5 + i);
@@ -2986,7 +3532,7 @@ TEST(DecodeTest, OrientedCroppedFrameTest) {
cropysize, jxl::ColorEncoding::SRGB(/*is_gray=*/false),
/*channels=*/4,
/*alpha_is_premultiplied=*/false, /*bits_per_sample=*/16,
- JXL_BIG_ENDIAN, /*flipped_y=*/false, /*pool=*/nullptr, &bundle,
+ JXL_BIG_ENDIAN, /*pool=*/nullptr, &bundle,
/*float_in=*/false, /*align=*/0));
bundle.origin = {cropx0, cropy0};
bundle.use_for_next_frame = true;
@@ -3101,6 +3647,561 @@ TEST(DecodeTest, OrientedCroppedFrameTest) {
}
}
+struct FramePositions {
+ size_t frame_start;
+ size_t header_end;
+ size_t toc_end;
+ std::vector<size_t> section_end;
+};
+
+struct StreamPositions {
+ size_t codestream_start;
+ size_t codestream_end;
+ size_t basic_info;
+ size_t jbrd_end = 0;
+ std::vector<size_t> box_start;
+ std::vector<FramePositions> frames;
+};
+
+void AnalyzeCodestream(const jxl::PaddedBytes& data,
+ StreamPositions* streampos) {
+ // Unbox data to codestream and mark where it is broken up by boxes.
+ std::vector<uint8_t> codestream;
+ std::vector<std::pair<size_t, size_t>> breakpoints;
+ bool codestream_end = false;
+ ASSERT_LE(2, data.size());
+ if (data[0] == 0xff && data[1] == 0x0a) {
+ codestream = std::vector<uint8_t>(data.begin(), data.end());
+ streampos->codestream_start = 0;
+ } else {
+ const uint8_t* in = data.data();
+ size_t pos = 0;
+ while (pos < data.size()) {
+ ASSERT_LE(pos + 8, data.size());
+ streampos->box_start.push_back(pos);
+ size_t box_size = LoadBE32(in + pos);
+ if (box_size == 0) box_size = data.size() - pos;
+ ASSERT_LE(pos + box_size, data.size());
+ if (memcmp(in + pos + 4, "jxlc", 4) == 0) {
+ EXPECT_TRUE(codestream.empty());
+ streampos->codestream_start = pos + 8;
+ codestream.insert(codestream.end(), in + pos + 8, in + pos + box_size);
+ codestream_end = true;
+ } else if (memcmp(in + pos + 4, "jxlp", 4) == 0) {
+ codestream_end = (LoadBE32(in + pos + 8) & 0x80000000);
+ if (codestream.empty()) {
+ streampos->codestream_start = pos + 12;
+ } else if (box_size > 12 || !codestream_end) {
+ breakpoints.push_back({codestream.size(), 12});
+ }
+ codestream.insert(codestream.end(), in + pos + 12, in + pos + box_size);
+ } else if (memcmp(in + pos + 4, "jbrd", 4) == 0) {
+ EXPECT_TRUE(codestream.empty());
+ streampos->jbrd_end = pos + box_size;
+ } else if (!codestream.empty() && !codestream_end) {
+ breakpoints.push_back({codestream.size(), box_size});
+ }
+ pos += box_size;
+ }
+ ASSERT_EQ(pos, data.size());
+ }
+ // Translate codestream positions to boxed stream positions.
+ size_t offset = streampos->codestream_start;
+ size_t bp = 0;
+ auto add_offset = [&](size_t pos) {
+ while (bp < breakpoints.size() && pos >= breakpoints[bp].first) {
+ offset += breakpoints[bp++].second;
+ }
+ return pos + offset;
+ };
+ // Analyze the unboxed codestream.
+ jxl::BitReader br(
+ jxl::Span<const uint8_t>(codestream.data(), codestream.size()));
+ ASSERT_EQ(br.ReadFixedBits<16>(), 0x0AFF);
+ jxl::CodecMetadata metadata;
+ EXPECT_TRUE(ReadSizeHeader(&br, &metadata.size));
+ EXPECT_TRUE(ReadImageMetadata(&br, &metadata.m));
+ streampos->basic_info =
+ add_offset(br.TotalBitsConsumed() / jxl::kBitsPerByte);
+ metadata.transform_data.nonserialized_xyb_encoded = metadata.m.xyb_encoded;
+ EXPECT_TRUE(jxl::Bundle::Read(&br, &metadata.transform_data));
+ EXPECT_TRUE(br.JumpToByteBoundary());
+ bool has_preview = metadata.m.have_preview;
+ while (br.TotalBitsConsumed() < br.TotalBytes() * jxl::kBitsPerByte) {
+ FramePositions p;
+ p.frame_start = add_offset(br.TotalBitsConsumed() / jxl::kBitsPerByte);
+ jxl::FrameHeader frame_header(&metadata);
+ if (has_preview) {
+ frame_header.nonserialized_is_preview = true;
+ has_preview = false;
+ }
+ EXPECT_TRUE(ReadFrameHeader(&br, &frame_header));
+ p.header_end =
+ add_offset(jxl::DivCeil(br.TotalBitsConsumed(), jxl::kBitsPerByte));
+ jxl::FrameDimensions frame_dim = frame_header.ToFrameDimensions();
+ uint64_t groups_total_size;
+ const size_t toc_entries = jxl::NumTocEntries(
+ frame_dim.num_groups, frame_dim.num_dc_groups,
+ frame_header.passes.num_passes, /*has_ac_global=*/true);
+ std::vector<uint64_t> section_offsets;
+ std::vector<uint32_t> section_sizes;
+ EXPECT_TRUE(ReadGroupOffsets(toc_entries, &br, &section_offsets,
+ &section_sizes, &groups_total_size));
+ EXPECT_EQ(br.TotalBitsConsumed() % jxl::kBitsPerByte, 0);
+ size_t sections_start = br.TotalBitsConsumed() / jxl::kBitsPerByte;
+ p.toc_end = add_offset(sections_start);
+ for (size_t i = 0; i < toc_entries; ++i) {
+ size_t end = sections_start + section_offsets[i] + section_sizes[i];
+ p.section_end.push_back(add_offset(end));
+ }
+ br.SkipBits(groups_total_size * jxl::kBitsPerByte);
+ streampos->frames.push_back(p);
+ }
+ streampos->codestream_end = add_offset(codestream.size());
+ EXPECT_EQ(br.TotalBitsConsumed(), br.TotalBytes() * jxl::kBitsPerByte);
+ EXPECT_TRUE(br.Close());
+}
+
+enum ExpectedFlushState { NO_FLUSH, SAME_FLUSH, NEW_FLUSH };
+struct Breakpoint {
+ size_t file_pos;
+ ExpectedFlushState expect_flush;
+};
+
+void VerifyProgression(size_t xsize, size_t ysize, uint32_t num_channels,
+ const std::vector<uint8_t>& pixels,
+ const jxl::PaddedBytes& data,
+ std::vector<Breakpoint> breakpoints) {
+ // Size large enough for multiple groups, required to have progressive stages.
+ ASSERT_LT(256, xsize);
+ ASSERT_LT(256, ysize);
+ std::vector<uint8_t> pixels2;
+ pixels2.resize(pixels.size());
+ JxlPixelFormat format = {num_channels, JXL_TYPE_UINT16, JXL_BIG_ENDIAN, 0};
+ JxlDecoder* dec = JxlDecoderCreate(nullptr);
+ EXPECT_EQ(JXL_DEC_SUCCESS,
+ JxlDecoderSubscribeEvents(
+ dec, JXL_DEC_BASIC_INFO | JXL_DEC_FRAME | JXL_DEC_FULL_IMAGE));
+ int bp = 0;
+ const uint8_t* next_in = data.data();
+ size_t avail_in = breakpoints[bp].file_pos;
+ EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderSetInput(dec, next_in, avail_in));
+ double prev_dist = 1.0;
+ for (;;) {
+ JxlDecoderStatus status = JxlDecoderProcessInput(dec);
+ printf("bp: %d status: 0x%x\n", bp, (int)status);
+ if (status == JXL_DEC_BASIC_INFO) {
+ JxlBasicInfo info;
+ EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderGetBasicInfo(dec, &info));
+ EXPECT_EQ(info.xsize, xsize);
+ EXPECT_EQ(info.ysize, ysize);
+ // Output buffer/callback not yet set
+ EXPECT_EQ(JXL_DEC_ERROR, JxlDecoderFlushImage(dec));
+ size_t buffer_size;
+ EXPECT_EQ(JXL_DEC_SUCCESS,
+ JxlDecoderImageOutBufferSize(dec, &format, &buffer_size));
+ EXPECT_EQ(pixels2.size(), buffer_size);
+ EXPECT_EQ(JXL_DEC_SUCCESS,
+ JxlDecoderSetImageOutBuffer(dec, &format, pixels2.data(),
+ pixels2.size()));
+ } else if (status == JXL_DEC_FRAME) {
+ // Nothing to do.
+ } else if (status == JXL_DEC_SUCCESS) {
+ EXPECT_EQ(bp + 1, breakpoints.size());
+ break;
+ } else if (status == JXL_DEC_NEED_MORE_INPUT ||
+ status == JXL_DEC_FULL_IMAGE) {
+ if (breakpoints[bp].expect_flush == NO_FLUSH) {
+ EXPECT_EQ(JXL_DEC_ERROR, JxlDecoderFlushImage(dec));
+ } else {
+ if (status != JXL_DEC_FULL_IMAGE) {
+ EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderFlushImage(dec));
+ }
+ double dist = jxl::test::DistanceRMS(pixels2.data(), pixels.data(),
+ xsize, ysize, format);
+ if (breakpoints[bp].expect_flush == NEW_FLUSH) {
+ EXPECT_LT(dist, prev_dist);
+ prev_dist = dist;
+ } else {
+ EXPECT_EQ(dist, prev_dist);
+ }
+ }
+ if (status == JXL_DEC_FULL_IMAGE) {
+ EXPECT_EQ(bp + 1, breakpoints.size());
+ continue;
+ }
+ ASSERT_LT(++bp, breakpoints.size());
+ next_in += avail_in - JxlDecoderReleaseInput(dec);
+ avail_in = breakpoints[bp].file_pos - (next_in - data.data());
+ EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderSetInput(dec, next_in, avail_in));
+ } else {
+ printf("Unexpected status: 0x%x\n", (int)status);
+ FAIL(); // unexpected returned status
+ }
+ }
+ JxlDecoderDestroy(dec);
+}
+
+TEST(DecodeTest, ProgressionTest) {
+ size_t xsize = 508, ysize = 470;
+ uint32_t num_channels = 3;
+ std::vector<uint8_t> pixels =
+ jxl::test::GetSomeTestImage(xsize, ysize, num_channels, 0);
+ jxl::TestCodestreamParams params;
+ params.cparams.progressive_dc = 1;
+ params.add_preview = true;
+ jxl::PaddedBytes data = jxl::CreateTestJXLCodestream(
+ jxl::Span<const uint8_t>(pixels.data(), pixels.size()), xsize, ysize,
+ num_channels, params);
+ StreamPositions streampos;
+ AnalyzeCodestream(data, &streampos);
+ const std::vector<FramePositions>& fp = streampos.frames;
+ // We have preview, dc frame and regular frame.
+ EXPECT_EQ(3, fp.size());
+ EXPECT_EQ(7, fp[2].section_end.size());
+ EXPECT_EQ(data.size(), fp[2].section_end[6]);
+ std::vector<Breakpoint> breakpoints{
+ {fp[0].frame_start, NO_FLUSH}, // headers
+ {fp[1].frame_start, NO_FLUSH}, // preview
+ {fp[2].frame_start, NO_FLUSH}, // dc frame
+ {fp[2].section_end[0], NO_FLUSH}, // DC global
+ {fp[2].section_end[1] - 1, NO_FLUSH}, // partial DC group
+ {fp[2].section_end[1], NEW_FLUSH}, // DC group
+ {fp[2].section_end[2], SAME_FLUSH}, // AC global
+ {fp[2].section_end[3], NEW_FLUSH}, // AC group 0
+ {fp[2].section_end[4] - 1, SAME_FLUSH}, // partial AC group 1
+ {fp[2].section_end[4], NEW_FLUSH}, // AC group 1
+ {fp[2].section_end[5], NEW_FLUSH}, // AC group 2
+ {data.size() - 1, SAME_FLUSH}, // partial AC group 3
+ {data.size(), NEW_FLUSH}}; // full image
+ VerifyProgression(xsize, ysize, num_channels, pixels, data, breakpoints);
+}
+
+TEST(DecodeTest, ProgressionTestLosslessAlpha) {
+ size_t xsize = 508, ysize = 470;
+ uint32_t num_channels = 4;
+ std::vector<uint8_t> pixels =
+ jxl::test::GetSomeTestImage(xsize, ysize, num_channels, 0);
+ jxl::TestCodestreamParams params;
+ params.cparams.SetLossless();
+ params.cparams.speed_tier = jxl::SpeedTier::kThunder;
+ params.cparams.responsive = 1;
+ jxl::PaddedBytes data = jxl::CreateTestJXLCodestream(
+ jxl::Span<const uint8_t>(pixels.data(), pixels.size()), xsize, ysize,
+ num_channels, params);
+ StreamPositions streampos;
+ AnalyzeCodestream(data, &streampos);
+ const std::vector<FramePositions>& fp = streampos.frames;
+ // We have preview, dc frame and regular frame.
+ EXPECT_EQ(1, fp.size());
+ EXPECT_EQ(7, fp[0].section_end.size());
+ EXPECT_EQ(data.size(), fp[0].section_end[6]);
+ std::vector<Breakpoint> breakpoints{
+ {fp[0].frame_start, NO_FLUSH}, // headers
+ {fp[0].section_end[0] - 1, NO_FLUSH}, // partial DC global
+ {fp[0].section_end[0], NEW_FLUSH}, // DC global
+ {fp[0].section_end[1], SAME_FLUSH}, // DC group
+ {fp[0].section_end[2], SAME_FLUSH}, // AC global
+ {fp[0].section_end[3], NEW_FLUSH}, // AC group 0
+ {fp[0].section_end[4] - 1, SAME_FLUSH}, // partial AC group 1
+ {fp[0].section_end[4], NEW_FLUSH}, // AC group 1
+ {fp[0].section_end[5], NEW_FLUSH}, // AC group 2
+ {data.size() - 1, SAME_FLUSH}, // partial AC group 3
+ {data.size(), NEW_FLUSH}}; // full image
+ VerifyProgression(xsize, ysize, num_channels, pixels, data, breakpoints);
+}
+
+void VerifyFilePosition(size_t expected_pos, const jxl::PaddedBytes& data,
+ JxlDecoder* dec) {
+ size_t remaining = JxlDecoderReleaseInput(dec);
+ size_t pos = data.size() - remaining;
+ EXPECT_EQ(expected_pos, pos);
+ EXPECT_EQ(JXL_DEC_SUCCESS,
+ JxlDecoderSetInput(dec, data.data() + pos, remaining));
+}
+
+TEST(DecodeTest, InputHandlingTestOneShot) {
+ size_t xsize = 508, ysize = 470;
+ uint32_t num_channels = 3;
+ std::vector<uint8_t> pixels =
+ jxl::test::GetSomeTestImage(xsize, ysize, num_channels, 0);
+ for (int i = 0; i < kCSBF_NUM_ENTRIES; ++i) {
+ printf("Testing with box format %d\n", i);
+ jxl::TestCodestreamParams params;
+ params.cparams.progressive_dc = 1;
+ params.add_preview = true;
+ params.box_format = (CodeStreamBoxFormat)i;
+ jxl::PaddedBytes data = jxl::CreateTestJXLCodestream(
+ jxl::Span<const uint8_t>(pixels.data(), pixels.size()), xsize, ysize,
+ num_channels, params);
+ JxlPixelFormat format = {num_channels, JXL_TYPE_UINT16, JXL_BIG_ENDIAN, 0};
+ StreamPositions streampos;
+ AnalyzeCodestream(data, &streampos);
+ const std::vector<FramePositions>& fp = streampos.frames;
+ // We have preview, dc frame and regular frame.
+ EXPECT_EQ(3, fp.size());
+
+ std::vector<uint8_t> pixels2;
+ pixels2.resize(pixels.size());
+
+ int kNumEvents = 6;
+ int events[] = {
+ JXL_DEC_BASIC_INFO, JXL_DEC_COLOR_ENCODING, JXL_DEC_PREVIEW_IMAGE,
+ JXL_DEC_FRAME, JXL_DEC_FULL_IMAGE, JXL_DEC_FRAME_PROGRESSION,
+ };
+ size_t end_positions[] = {
+ streampos.basic_info, fp[0].frame_start,
+ fp[1].frame_start, fp[2].toc_end,
+ streampos.codestream_end, streampos.codestream_end};
+ int events_wanted = 0;
+ for (int j = 0; j < kNumEvents; ++j) {
+ events_wanted |= events[j];
+ size_t end_pos = end_positions[j];
+ JxlDecoder* dec = JxlDecoderCreate(nullptr);
+ EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderSubscribeEvents(dec, events_wanted));
+ EXPECT_EQ(JXL_DEC_SUCCESS,
+ JxlDecoderSetInput(dec, data.data(), data.size()));
+ EXPECT_EQ(JXL_DEC_BASIC_INFO, JxlDecoderProcessInput(dec));
+ VerifyFilePosition(streampos.basic_info, data, dec);
+ if (j >= 1) {
+ EXPECT_EQ(JXL_DEC_COLOR_ENCODING, JxlDecoderProcessInput(dec));
+ VerifyFilePosition(fp[0].frame_start, data, dec);
+ }
+ if (j >= 2) {
+ EXPECT_EQ(JXL_DEC_NEED_PREVIEW_OUT_BUFFER, JxlDecoderProcessInput(dec));
+ VerifyFilePosition(fp[0].toc_end, data, dec);
+ size_t buffer_size;
+ EXPECT_EQ(JXL_DEC_SUCCESS,
+ JxlDecoderPreviewOutBufferSize(dec, &format, &buffer_size));
+ EXPECT_GE(pixels2.size(), buffer_size);
+ EXPECT_EQ(JXL_DEC_SUCCESS,
+ JxlDecoderSetPreviewOutBuffer(dec, &format, pixels2.data(),
+ buffer_size));
+ EXPECT_EQ(JXL_DEC_PREVIEW_IMAGE, JxlDecoderProcessInput(dec));
+ VerifyFilePosition(fp[1].frame_start, data, dec);
+ }
+ if (j >= 3) {
+ EXPECT_EQ(JXL_DEC_FRAME, JxlDecoderProcessInput(dec));
+ VerifyFilePosition(fp[2].toc_end, data, dec);
+ if (j >= 5) {
+ EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderSetProgressiveDetail(dec, kDC));
+ }
+ }
+ if (j >= 4) {
+ EXPECT_EQ(JXL_DEC_NEED_IMAGE_OUT_BUFFER, JxlDecoderProcessInput(dec));
+ VerifyFilePosition(fp[2].toc_end, data, dec);
+ size_t buffer_size;
+ EXPECT_EQ(JXL_DEC_SUCCESS,
+ JxlDecoderImageOutBufferSize(dec, &format, &buffer_size));
+ EXPECT_EQ(pixels2.size(), buffer_size);
+ EXPECT_EQ(JXL_DEC_SUCCESS,
+ JxlDecoderSetImageOutBuffer(dec, &format, pixels2.data(),
+ pixels2.size()));
+ if (j >= 5) {
+ EXPECT_EQ(JXL_DEC_FRAME_PROGRESSION, JxlDecoderProcessInput(dec));
+ VerifyFilePosition(fp[2].section_end[1], data, dec);
+ }
+ EXPECT_EQ(JXL_DEC_FULL_IMAGE, JxlDecoderProcessInput(dec));
+ VerifyFilePosition(streampos.codestream_end, data, dec);
+ }
+ EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderProcessInput(dec));
+ VerifyFilePosition(end_pos, data, dec);
+ JxlDecoderDestroy(dec);
+ }
+ }
+}
+
+#if JPEGXL_ENABLE_JPEG
+TEST(DecodeTest, JXL_TRANSCODE_JPEG_TEST(InputHandlingTestJPEGOneshot)) {
+ size_t xsize = 123;
+ size_t ysize = 77;
+ size_t channels = 3;
+ std::vector<uint8_t> pixels =
+ jxl::test::GetSomeTestImage(xsize, ysize, channels, /*seed=*/0);
+ for (int i = 1; i < kCSBF_NUM_ENTRIES; ++i) {
+ printf("Testing with box format %d\n", i);
+ jxl::PaddedBytes jpeg_codestream;
+ jxl::TestCodestreamParams params;
+ params.cparams.color_transform = jxl::ColorTransform::kNone;
+ params.jpeg_codestream = &jpeg_codestream;
+ params.add_preview = true;
+ params.box_format = (CodeStreamBoxFormat)i;
+ jxl::PaddedBytes data = jxl::CreateTestJXLCodestream(
+ jxl::Span<const uint8_t>(pixels.data(), pixels.size()), xsize, ysize,
+ channels, params);
+ JxlPixelFormat format = {3, JXL_TYPE_UINT16, JXL_BIG_ENDIAN, 0};
+ StreamPositions streampos;
+ AnalyzeCodestream(data, &streampos);
+ const std::vector<FramePositions>& fp = streampos.frames;
+ // We have preview and regular frame.
+ EXPECT_EQ(2, fp.size());
+ EXPECT_LT(0, streampos.jbrd_end);
+
+ std::vector<uint8_t> pixels2;
+ pixels2.resize(pixels.size());
+
+ int kNumEvents = 6;
+ int events[] = {JXL_DEC_BASIC_INFO, JXL_DEC_JPEG_RECONSTRUCTION,
+ JXL_DEC_COLOR_ENCODING, JXL_DEC_PREVIEW_IMAGE,
+ JXL_DEC_FRAME, JXL_DEC_FULL_IMAGE};
+ size_t end_positions[] = {streampos.basic_info, streampos.basic_info,
+ fp[0].frame_start, fp[1].frame_start,
+ fp[1].toc_end, streampos.codestream_end};
+ int events_wanted = 0;
+ for (int j = 0; j < kNumEvents; ++j) {
+ printf("j = %d\n", j);
+ events_wanted |= events[j];
+ size_t end_pos = end_positions[j];
+ JxlDecoder* dec = JxlDecoderCreate(nullptr);
+ EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderSubscribeEvents(dec, events_wanted));
+ EXPECT_EQ(JXL_DEC_SUCCESS,
+ JxlDecoderSetInput(dec, data.data(), data.size()));
+ if (j >= 1) {
+ EXPECT_EQ(JXL_DEC_JPEG_RECONSTRUCTION, JxlDecoderProcessInput(dec));
+ VerifyFilePosition(streampos.jbrd_end, data, dec);
+ }
+ EXPECT_EQ(JXL_DEC_BASIC_INFO, JxlDecoderProcessInput(dec));
+ VerifyFilePosition(streampos.basic_info, data, dec);
+ if (j >= 2) {
+ EXPECT_EQ(JXL_DEC_COLOR_ENCODING, JxlDecoderProcessInput(dec));
+ VerifyFilePosition(fp[0].frame_start, data, dec);
+ }
+ if (j >= 3) {
+ EXPECT_EQ(JXL_DEC_NEED_PREVIEW_OUT_BUFFER, JxlDecoderProcessInput(dec));
+ VerifyFilePosition(fp[0].toc_end, data, dec);
+ size_t buffer_size;
+ EXPECT_EQ(JXL_DEC_SUCCESS,
+ JxlDecoderPreviewOutBufferSize(dec, &format, &buffer_size));
+ EXPECT_GE(pixels2.size(), buffer_size);
+ EXPECT_EQ(JXL_DEC_SUCCESS,
+ JxlDecoderSetPreviewOutBuffer(dec, &format, pixels2.data(),
+ buffer_size));
+ EXPECT_EQ(JXL_DEC_PREVIEW_IMAGE, JxlDecoderProcessInput(dec));
+ VerifyFilePosition(fp[1].frame_start, data, dec);
+ }
+ if (j >= 4) {
+ EXPECT_EQ(JXL_DEC_FRAME, JxlDecoderProcessInput(dec));
+ VerifyFilePosition(fp[1].toc_end, data, dec);
+ }
+ if (j >= 5) {
+ EXPECT_EQ(JXL_DEC_NEED_IMAGE_OUT_BUFFER, JxlDecoderProcessInput(dec));
+ VerifyFilePosition(fp[1].toc_end, data, dec);
+ size_t buffer_size;
+ EXPECT_EQ(JXL_DEC_SUCCESS,
+ JxlDecoderImageOutBufferSize(dec, &format, &buffer_size));
+ EXPECT_EQ(pixels2.size(), buffer_size);
+ EXPECT_EQ(JXL_DEC_SUCCESS,
+ JxlDecoderSetImageOutBuffer(dec, &format, pixels2.data(),
+ pixels2.size()));
+ EXPECT_EQ(JXL_DEC_FULL_IMAGE, JxlDecoderProcessInput(dec));
+ VerifyFilePosition(streampos.codestream_end, data, dec);
+ }
+ EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderProcessInput(dec));
+ VerifyFilePosition(end_pos, data, dec);
+ JxlDecoderDestroy(dec);
+ }
+ }
+}
+#endif // JPEGXL_ENABLE_JPEG
+
+TEST(DecodeTest, InputHandlingTestStreaming) {
+ size_t xsize = 508, ysize = 470;
+ uint32_t num_channels = 3;
+ std::vector<uint8_t> pixels =
+ jxl::test::GetSomeTestImage(xsize, ysize, num_channels, 0);
+ for (int i = 0; i < kCSBF_NUM_ENTRIES; ++i) {
+ printf("Testing with box format %d\n", i);
+ fflush(stdout);
+ jxl::TestCodestreamParams params;
+ params.cparams.progressive_dc = 1;
+ params.box_format = (CodeStreamBoxFormat)i;
+ params.add_preview = true;
+ jxl::PaddedBytes data = jxl::CreateTestJXLCodestream(
+ jxl::Span<const uint8_t>(pixels.data(), pixels.size()), xsize, ysize,
+ num_channels, params);
+ JxlPixelFormat format = {num_channels, JXL_TYPE_UINT16, JXL_BIG_ENDIAN, 0};
+ StreamPositions streampos;
+ AnalyzeCodestream(data, &streampos);
+ const std::vector<FramePositions>& fp = streampos.frames;
+ // We have preview, dc frame and regular frame.
+ EXPECT_EQ(3, fp.size());
+ std::vector<uint8_t> pixels2;
+ pixels2.resize(pixels.size());
+ int events_wanted =
+ (JXL_DEC_BASIC_INFO | JXL_DEC_COLOR_ENCODING | JXL_DEC_PREVIEW_IMAGE |
+ JXL_DEC_FRAME | JXL_DEC_FULL_IMAGE | JXL_DEC_FRAME_PROGRESSION |
+ JXL_DEC_BOX);
+ for (size_t increment : {1, 7, 27, 1024}) {
+ JxlDecoder* dec = JxlDecoderCreate(nullptr);
+ EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderSubscribeEvents(dec, events_wanted));
+ size_t file_pos = 0;
+ size_t box_index = 0;
+ size_t avail_in = 0;
+ for (;;) {
+ const uint8_t* next_in = data.data() + file_pos;
+ EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderSetInput(dec, next_in, avail_in));
+ JxlDecoderStatus status = JxlDecoderProcessInput(dec);
+ size_t remaining = JxlDecoderReleaseInput(dec);
+ size_t consumed = avail_in - remaining;
+ file_pos += consumed;
+ avail_in += increment;
+ avail_in = std::min<size_t>(avail_in, data.size() - file_pos);
+ if (status == JXL_DEC_BASIC_INFO) {
+ EXPECT_EQ(file_pos, streampos.basic_info);
+ } else if (status == JXL_DEC_COLOR_ENCODING) {
+ EXPECT_EQ(file_pos, streampos.frames[0].frame_start);
+ } else if (status == JXL_DEC_NEED_PREVIEW_OUT_BUFFER) {
+ EXPECT_EQ(file_pos, streampos.frames[0].toc_end);
+ size_t buffer_size;
+ EXPECT_EQ(JXL_DEC_SUCCESS,
+ JxlDecoderPreviewOutBufferSize(dec, &format, &buffer_size));
+ EXPECT_GE(pixels2.size(), buffer_size);
+ EXPECT_EQ(JXL_DEC_SUCCESS,
+ JxlDecoderSetPreviewOutBuffer(dec, &format, pixels2.data(),
+ buffer_size));
+ } else if (status == JXL_DEC_PREVIEW_IMAGE) {
+ EXPECT_EQ(file_pos, streampos.frames[1].frame_start);
+ } else if (status == JXL_DEC_FRAME) {
+ EXPECT_EQ(file_pos, streampos.frames[2].toc_end);
+ EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderSetProgressiveDetail(dec, kDC));
+ } else if (status == JXL_DEC_NEED_IMAGE_OUT_BUFFER) {
+ EXPECT_EQ(file_pos, streampos.frames[2].toc_end);
+ size_t buffer_size;
+ EXPECT_EQ(JXL_DEC_SUCCESS,
+ JxlDecoderImageOutBufferSize(dec, &format, &buffer_size));
+ EXPECT_EQ(pixels2.size(), buffer_size);
+ EXPECT_EQ(JXL_DEC_SUCCESS,
+ JxlDecoderSetImageOutBuffer(dec, &format, pixels2.data(),
+ pixels2.size()));
+ } else if (status == JXL_DEC_FRAME_PROGRESSION) {
+ EXPECT_EQ(file_pos, streampos.frames[2].section_end[1]);
+ } else if (status == JXL_DEC_FULL_IMAGE) {
+ EXPECT_EQ(file_pos, streampos.codestream_end);
+ } else if (status == JXL_DEC_SUCCESS) {
+ EXPECT_EQ(file_pos, streampos.codestream_end);
+ break;
+ } else if (status == JXL_DEC_NEED_MORE_INPUT) {
+ EXPECT_LT(remaining, 12);
+ if ((i == kCSBF_None && file_pos >= 2) ||
+ (box_index > 0 && box_index < streampos.box_start.size() &&
+ file_pos >= streampos.box_start[box_index - 1] + 12 &&
+ file_pos < streampos.box_start[box_index])) {
+ EXPECT_EQ(remaining, 0);
+ }
+ if (file_pos == data.size()) break;
+ } else if (status == JXL_DEC_BOX) {
+ ASSERT_LT(box_index, streampos.box_start.size());
+ EXPECT_EQ(file_pos, streampos.box_start[box_index++]);
+ } else {
+ printf("Unexpected status: 0x%x\n", (int)status);
+ FAIL();
+ }
+ }
+ JxlDecoderDestroy(dec);
+ }
+ }
+}
+
TEST(DecodeTest, FlushTest) {
// Size large enough for multiple groups, required to have progressive
// stages
@@ -3108,10 +4209,11 @@ TEST(DecodeTest, FlushTest) {
uint32_t num_channels = 3;
std::vector<uint8_t> pixels =
jxl::test::GetSomeTestImage(xsize, ysize, num_channels, 0);
- jxl::CompressParams cparams;
+ jxl::TestCodestreamParams params;
+ params.add_preview = true;
jxl::PaddedBytes data = jxl::CreateTestJXLCodestream(
jxl::Span<const uint8_t>(pixels.data(), pixels.size()), xsize, ysize,
- num_channels, cparams, kCSBF_None, JXL_ORIENT_IDENTITY, true, false);
+ num_channels, params);
JxlPixelFormat format = {num_channels, JXL_TYPE_UINT16, JXL_BIG_ENDIAN, 0};
std::vector<uint8_t> pixels2;
@@ -3175,6 +4277,92 @@ TEST(DecodeTest, FlushTest) {
JxlDecoderDestroy(dec);
}
+TEST(DecodeTest, FlushTestImageOutCallback) {
+ // Size large enough for multiple groups, required to have progressive
+ // stages
+ size_t xsize = 333, ysize = 300;
+ uint32_t num_channels = 3;
+ std::vector<uint8_t> pixels =
+ jxl::test::GetSomeTestImage(xsize, ysize, num_channels, 0);
+ jxl::TestCodestreamParams params;
+ params.add_preview = true;
+ jxl::PaddedBytes data = jxl::CreateTestJXLCodestream(
+ jxl::Span<const uint8_t>(pixels.data(), pixels.size()), xsize, ysize,
+ num_channels, params);
+ JxlPixelFormat format = {num_channels, JXL_TYPE_UINT16, JXL_BIG_ENDIAN, 0};
+
+ std::vector<uint8_t> pixels2;
+ pixels2.resize(pixels.size());
+
+ size_t bytes_per_pixel = format.num_channels * 2;
+ size_t stride = bytes_per_pixel * xsize;
+ auto callback = [&](size_t x, size_t y, size_t num_pixels,
+ const void* pixels_row) {
+ memcpy(pixels2.data() + stride * y + bytes_per_pixel * x, pixels_row,
+ num_pixels * bytes_per_pixel);
+ };
+
+ JxlDecoder* dec = JxlDecoderCreate(nullptr);
+
+ EXPECT_EQ(JXL_DEC_SUCCESS,
+ JxlDecoderSubscribeEvents(
+ dec, JXL_DEC_BASIC_INFO | JXL_DEC_FRAME | JXL_DEC_FULL_IMAGE));
+
+ // Ensure that the first part contains at least the full DC of the image,
+ // otherwise flush does not work.
+ size_t first_part = data.size() - 1;
+
+ EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderSetInput(dec, data.data(), first_part));
+
+ EXPECT_EQ(JXL_DEC_BASIC_INFO, JxlDecoderProcessInput(dec));
+ JxlBasicInfo info;
+ EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderGetBasicInfo(dec, &info));
+ EXPECT_EQ(info.xsize, xsize);
+ EXPECT_EQ(info.ysize, ysize);
+
+ EXPECT_EQ(JXL_DEC_FRAME, JxlDecoderProcessInput(dec));
+
+ // Output callback not yet set
+ EXPECT_EQ(JXL_DEC_ERROR, JxlDecoderFlushImage(dec));
+
+ EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderSetImageOutCallback(
+ dec, &format,
+ [](void* opaque, size_t x, size_t y,
+ size_t xsize, const void* pixels_row) {
+ auto cb =
+ static_cast<decltype(&callback)>(opaque);
+ (*cb)(x, y, xsize, pixels_row);
+ },
+ /*opaque=*/&callback));
+
+ // Must process input further until we get JXL_DEC_NEED_MORE_INPUT, even if
+ // data was already input before, since the processing of the frame only
+ // happens at the JxlDecoderProcessInput call after JXL_DEC_FRAME.
+ EXPECT_EQ(JXL_DEC_NEED_MORE_INPUT, JxlDecoderProcessInput(dec));
+
+ EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderFlushImage(dec));
+
+ // Crude test of actual pixel data: pixel threshold of about 4% (2560/65535).
+ // 29000 pixels can be above the threshold
+ EXPECT_LE(jxl::test::ComparePixels(pixels2.data(), pixels.data(), xsize,
+ ysize, format, format, 2560.0),
+ 29000u);
+
+ EXPECT_EQ(JXL_DEC_NEED_MORE_INPUT, JxlDecoderProcessInput(dec));
+
+ size_t consumed = first_part - JxlDecoderReleaseInput(dec);
+
+ EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderSetInput(dec, data.data() + consumed,
+ data.size() - consumed));
+ EXPECT_EQ(JXL_DEC_FULL_IMAGE, JxlDecoderProcessInput(dec));
+ // Lower threshold for the final (still lossy) image
+ EXPECT_LE(jxl::test::ComparePixels(pixels2.data(), pixels.data(), xsize,
+ ysize, format, format, 2560.0),
+ 11000u);
+
+ JxlDecoderDestroy(dec);
+}
+
TEST(DecodeTest, FlushTestLossyProgressiveAlpha) {
// Size large enough for multiple groups, required to have progressive
// stages
@@ -3182,10 +4370,11 @@ TEST(DecodeTest, FlushTestLossyProgressiveAlpha) {
uint32_t num_channels = 4;
std::vector<uint8_t> pixels =
jxl::test::GetSomeTestImage(xsize, ysize, num_channels, 0);
- jxl::CompressParams cparams;
+ jxl::TestCodestreamParams params;
+ params.add_preview = true;
jxl::PaddedBytes data = jxl::CreateTestJXLCodestream(
jxl::Span<const uint8_t>(pixels.data(), pixels.size()), xsize, ysize,
- num_channels, cparams, kCSBF_None, JXL_ORIENT_IDENTITY, true, false);
+ num_channels, params);
JxlPixelFormat format = {num_channels, JXL_TYPE_UINT16, JXL_BIG_ENDIAN, 0};
std::vector<uint8_t> pixels2;
@@ -3251,12 +4440,13 @@ TEST(DecodeTest, FlushTestLossyProgressiveAlphaUpsampling) {
uint32_t num_channels = 4;
std::vector<uint8_t> pixels =
jxl::test::GetSomeTestImage(xsize, ysize, num_channels, 0);
- jxl::CompressParams cparams;
- cparams.resampling = 2;
- cparams.ec_resampling = 4;
+ jxl::TestCodestreamParams params;
+ params.cparams.resampling = 2;
+ params.cparams.ec_resampling = 4;
+ params.add_preview = true;
jxl::PaddedBytes data = jxl::CreateTestJXLCodestream(
jxl::Span<const uint8_t>(pixels.data(), pixels.size()), xsize, ysize,
- num_channels, cparams, kCSBF_None, JXL_ORIENT_IDENTITY, true, false);
+ num_channels, params);
JxlPixelFormat format = {num_channels, JXL_TYPE_UINT16, JXL_BIG_ENDIAN, 0};
std::vector<uint8_t> pixels2;
@@ -3324,13 +4514,14 @@ TEST(DecodeTest, FlushTestLosslessProgressiveAlpha) {
uint32_t num_channels = 4;
std::vector<uint8_t> pixels =
jxl::test::GetSomeTestImage(xsize, ysize, num_channels, 0);
- jxl::CompressParams cparams;
- cparams.SetLossless();
- cparams.speed_tier = jxl::SpeedTier::kThunder;
- cparams.responsive = 1;
+ jxl::TestCodestreamParams params;
+ params.cparams.SetLossless();
+ params.cparams.speed_tier = jxl::SpeedTier::kThunder;
+ params.cparams.responsive = 1;
+ params.add_preview = true;
jxl::PaddedBytes data = jxl::CreateTestJXLCodestream(
jxl::Span<const uint8_t>(pixels.data(), pixels.size()), xsize, ysize,
- num_channels, cparams, kCSBF_None, JXL_ORIENT_IDENTITY, true, false);
+ num_channels, params);
JxlPixelFormat format = {num_channels, JXL_TYPE_UINT16, JXL_BIG_ENDIAN, 0};
std::vector<uint8_t> pixels2;
@@ -3392,123 +4583,179 @@ TEST(DecodeTest, FlushTestLosslessProgressiveAlpha) {
JxlDecoderDestroy(dec);
}
-TEST(DecodeTest, ProgressiveEventTest) {
- for (int single_group = 0; single_group <= 1; ++single_group) {
- for (int lossless = 0; lossless <= 1; ++lossless) {
- for (uint32_t num_channels = 3; num_channels < 4; ++num_channels) {
- // Only few combinations are expected to support outputting the DC. The
- // test can be updated if more cases are expected to support it.
- bool expect_dc = !single_group && (num_channels & 1) && !lossless;
- size_t xsize, ysize;
- if (single_group) {
- // An image smaller than 256x256 ensures it contains only 1 group.
- xsize = 99;
- ysize = 100;
- } else {
- xsize = 257;
- ysize = 280;
- }
- std::vector<uint8_t> pixels =
- jxl::test::GetSomeTestImage(xsize, ysize, num_channels, 0);
- jxl::CompressParams cparams;
- if (lossless) {
- cparams.SetLossless();
- } else {
- cparams.butteraugli_distance = 0.5f;
- }
-
- jxl::PaddedBytes data = jxl::CreateTestJXLCodestream(
- jxl::Span<const uint8_t>(pixels.data(), pixels.size()), xsize,
- ysize, num_channels, cparams, kCSBF_None, JXL_ORIENT_IDENTITY,
- false, false);
- JxlPixelFormat format = {num_channels, JXL_TYPE_UINT16, JXL_BIG_ENDIAN,
- 0};
+class DecodeProgressiveTest : public ::testing::TestWithParam<int> {};
+JXL_GTEST_INSTANTIATE_TEST_SUITE_P(DecodeProgressiveTestInstantiation,
+ DecodeProgressiveTest,
+ ::testing::Range(0, 8));
+TEST_P(DecodeProgressiveTest, ProgressiveEventTest) {
+ const int params = GetParam();
+ int single_group = params & 1;
+ int lossless = (params >> 1) & 1;
+ uint32_t num_channels = 3 + ((params >> 2) & 1);
+ std::set<JxlProgressiveDetail> progressive_details = {kDC, kLastPasses,
+ kPasses};
+ for (auto prog_detail : progressive_details) {
+ // Only few combinations are expected to support outputting
+ // intermediate flushes for complete DC and complete passes.
+ // The test can be updated if more cases are expected to support it.
+ bool expect_flush = (num_channels & 1) && !lossless;
+ size_t xsize, ysize;
+ if (single_group) {
+ // An image smaller than 256x256 ensures it contains only 1 group.
+ xsize = 99;
+ ysize = 100;
+ } else {
+ xsize = 277;
+ ysize = 280;
+ }
+ std::vector<uint8_t> pixels =
+ jxl::test::GetSomeTestImage(xsize, ysize, num_channels, 0);
+ jxl::ColorEncoding color_encoding = jxl::ColorEncoding::SRGB(false);
+ jxl::CodecInOut io;
+ EXPECT_TRUE(jxl::ConvertFromExternal(
+ jxl::Span<const uint8_t>(pixels.data(), pixels.size()), xsize, ysize,
+ color_encoding, num_channels,
+ /*alpha_is_premultiplied=*/false,
+ /*bits_per_sample=*/16, JXL_BIG_ENDIAN,
+ /*pool=*/nullptr, &io.Main(), /*float_in=*/false, /*align=*/0));
+ jxl::TestCodestreamParams params;
+ if (lossless) {
+ params.cparams.SetLossless();
+ } else {
+ params.cparams.butteraugli_distance = 0.5f;
+ }
+ jxl::PassDefinition passes[] = {{2, 0, false, 4},
+ {4, 0, false, 4},
+ {8, 2, false, 2},
+ {8, 1, false, 2},
+ {8, 0, false, 1}};
+ const int kNumPasses = 5;
+ jxl::ProgressiveMode progressive_mode{passes};
+ params.progressive_mode = &progressive_mode;
+ jxl::PaddedBytes data = jxl::CreateTestJXLCodestream(
+ jxl::Span<const uint8_t>(pixels.data(), pixels.size()), xsize, ysize,
+ num_channels, params);
+ JxlPixelFormat format = {num_channels, JXL_TYPE_UINT16, JXL_BIG_ENDIAN, 0};
+
+ for (size_t increment : {(size_t)1, data.size()}) {
+ printf(
+ "Testing with single_group=%d, lossless=%d, "
+ "num_channels=%d, prog_detail=%d, increment=%d\n",
+ single_group, lossless, (int)num_channels, (int)prog_detail,
+ (int)increment);
+ std::vector<std::vector<uint8_t>> passes(kNumPasses + 1);
+ for (int i = 0; i <= kNumPasses; ++i) {
+ passes[i].resize(pixels.size());
+ }
- std::vector<uint8_t> pixels2;
- pixels2.resize(pixels.size());
- std::vector<uint8_t> dc;
- dc.resize(pixels.size());
+ JxlDecoder* dec = JxlDecoderCreate(nullptr);
- JxlDecoder* dec = JxlDecoderCreate(nullptr);
+ EXPECT_EQ(JXL_DEC_SUCCESS,
+ JxlDecoderSubscribeEvents(
+ dec, JXL_DEC_BASIC_INFO | JXL_DEC_FRAME |
+ JXL_DEC_FULL_IMAGE | JXL_DEC_FRAME_PROGRESSION));
+ EXPECT_EQ(JXL_DEC_ERROR, JxlDecoderSetProgressiveDetail(dec, kFrames));
+ EXPECT_EQ(JXL_DEC_ERROR,
+ JxlDecoderSetProgressiveDetail(dec, kDCProgressive));
+ EXPECT_EQ(JXL_DEC_ERROR, JxlDecoderSetProgressiveDetail(dec, kDCGroups));
+ EXPECT_EQ(JXL_DEC_ERROR, JxlDecoderSetProgressiveDetail(dec, kGroups));
+ EXPECT_EQ(JXL_DEC_SUCCESS,
+ JxlDecoderSetProgressiveDetail(dec, prog_detail));
- EXPECT_EQ(JXL_DEC_SUCCESS,
- JxlDecoderSubscribeEvents(
- dec, JXL_DEC_BASIC_INFO | JXL_DEC_FRAME |
- JXL_DEC_FULL_IMAGE | JXL_DEC_FRAME_PROGRESSION));
+ uint8_t* next_in = data.data();
+ size_t avail_in = 0;
+ size_t pos = 0;
- EXPECT_EQ(JXL_DEC_SUCCESS,
- JxlDecoderSetInput(dec, data.data(), data.size()));
+ auto process_input = [&]() {
+ for (;;) {
+ EXPECT_EQ(JXL_DEC_SUCCESS,
+ JxlDecoderSetInput(dec, next_in, avail_in));
+ JxlDecoderStatus status = JxlDecoderProcessInput(dec);
+ size_t remaining = JxlDecoderReleaseInput(dec);
+ EXPECT_LE(remaining, avail_in);
+ next_in += avail_in - remaining;
+ avail_in = remaining;
+ if (status == JXL_DEC_NEED_MORE_INPUT && pos < data.size()) {
+ size_t chunk = std::min<size_t>(increment, data.size() - pos);
+ pos += chunk;
+ avail_in += chunk;
+ continue;
+ }
+ return status;
+ }
+ };
- EXPECT_EQ(JXL_DEC_BASIC_INFO, JxlDecoderProcessInput(dec));
- JxlBasicInfo info;
- EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderGetBasicInfo(dec, &info));
- EXPECT_EQ(info.xsize, xsize);
- EXPECT_EQ(info.ysize, ysize);
+ EXPECT_EQ(JXL_DEC_BASIC_INFO, process_input());
+ JxlBasicInfo info;
+ EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderGetBasicInfo(dec, &info));
+ EXPECT_EQ(info.xsize, xsize);
+ EXPECT_EQ(info.ysize, ysize);
- EXPECT_EQ(JXL_DEC_FRAME, JxlDecoderProcessInput(dec));
+ EXPECT_EQ(JXL_DEC_FRAME, process_input());
- size_t buffer_size;
- EXPECT_EQ(JXL_DEC_SUCCESS,
- JxlDecoderImageOutBufferSize(dec, &format, &buffer_size));
- EXPECT_EQ(pixels2.size(), buffer_size);
- EXPECT_EQ(JXL_DEC_SUCCESS,
- JxlDecoderSetImageOutBuffer(dec, &format, pixels2.data(),
- pixels2.size()));
+ size_t buffer_size;
+ EXPECT_EQ(JXL_DEC_SUCCESS,
+ JxlDecoderImageOutBufferSize(dec, &format, &buffer_size));
+ EXPECT_EQ(pixels.size(), buffer_size);
+ EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderSetImageOutBuffer(
+ dec, &format, passes[kNumPasses].data(),
+ passes[kNumPasses].size()));
- if (expect_dc) {
- EXPECT_EQ(JXL_DEC_FRAME_PROGRESSION, JxlDecoderProcessInput(dec));
+ auto next_pass = [&](int pass) {
+ if (prog_detail <= kDC) return kNumPasses;
+ if (prog_detail <= kLastPasses) {
+ return std::min(pass + 2, kNumPasses);
+ }
+ return pass + 1;
+ };
+
+ if (expect_flush) {
+ // Return a particular downsampling ratio only after the last
+ // pass for that downsampling was processed.
+ int expected_downsampling_ratios[] = {8, 8, 4, 4, 2};
+ for (int p = 0; p < kNumPasses; p = next_pass(p)) {
+ EXPECT_EQ(JXL_DEC_FRAME_PROGRESSION, process_input());
+ EXPECT_EQ(expected_downsampling_ratios[p],
+ JxlDecoderGetIntendedDownsamplingRatio(dec));
EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderFlushImage(dec));
- dc = pixels2;
+ passes[p] = passes[kNumPasses];
}
+ }
- EXPECT_EQ(JXL_DEC_FULL_IMAGE, JxlDecoderProcessInput(dec));
- EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderProcessInput(dec));
+ EXPECT_EQ(JXL_DEC_FULL_IMAGE, process_input());
+ EXPECT_EQ(JXL_DEC_SUCCESS, process_input());
- jxl::Image3F imagef_pixels(xsize, ysize);
- jxl::Image3F imagef_pixels2(xsize, ysize);
- jxl::Image3F imagef_dc(xsize, ysize);
- for (size_t c = 0; c < 3; c++) {
- for (size_t y = 0; y < ysize; y++) {
- float* row_pixels = imagef_pixels.PlaneRow(c, y);
- float* row_pixels2 = imagef_pixels2.PlaneRow(c, y);
- float* row_dc = imagef_dc.PlaneRow(c, y);
- for (size_t x = 0; x < xsize; x++) {
- size_t pixel_index = (y * xsize + x) * num_channels * 2 + c * 2;
- row_pixels[x] = pixels[pixel_index] / 255.0f;
- row_pixels2[x] = pixels2[pixel_index] / 255.0f;
- row_dc[x] = dc[pixel_index] / 255.0f;
- }
- }
- }
+ JxlDecoderDestroy(dec);
- jxl::CodecInOut io_pixels;
- io_pixels.SetFromImage(std::move(imagef_pixels),
- jxl::ColorEncoding::SRGB(false));
- jxl::CodecInOut io_pixels2;
- io_pixels2.SetFromImage(std::move(imagef_pixels2),
- jxl::ColorEncoding::SRGB(false));
- jxl::CodecInOut io_dc;
- io_dc.SetFromImage(std::move(imagef_dc),
- jxl::ColorEncoding::SRGB(false));
-
- jxl::ButteraugliParams ba;
- float distance_full =
- ButteraugliDistance(io_pixels, io_pixels2, ba, jxl::GetJxlCms(),
- /*distmap=*/nullptr, nullptr);
-
- if (expect_dc) {
- float distance_dc =
- ButteraugliDistance(io_pixels, io_dc, ba, jxl::GetJxlCms(),
- /*distmap=*/nullptr, nullptr);
- EXPECT_LT(distance_full, 2.0f);
- EXPECT_LT(distance_dc, 30.0f);
- // Verify that the returned DC image is actually DC, by checking that
- // it has worse butteraugli score than the full image
- EXPECT_LT(distance_full + 1.0f, distance_dc);
- } else {
- EXPECT_LT(distance_full, 2.0f);
- }
- JxlDecoderDestroy(dec);
+ if (!expect_flush) {
+ continue;
+ }
+ jxl::ButteraugliParams ba;
+ std::vector<float> distances(kNumPasses + 1);
+ for (int p = 0;; p = next_pass(p)) {
+ jxl::CodecInOut io1;
+ EXPECT_TRUE(jxl::ConvertFromExternal(
+ jxl::Span<const uint8_t>(passes[p].data(), passes[p].size()), xsize,
+ ysize, color_encoding, num_channels,
+ /*alpha_is_premultiplied=*/false, /*bits_per_sample=*/16,
+ JXL_BIG_ENDIAN,
+ /*pool=*/nullptr, &io1.Main(), /*float_in=*/false,
+ /*align=*/0));
+ distances[p] = ButteraugliDistance(io, io1, ba, jxl::GetJxlCms(),
+ nullptr, nullptr);
+ if (p == kNumPasses) break;
+ }
+ const float kMaxDistance[kNumPasses + 1] = {30.0f, 20.0f, 10.0f,
+ 5.0f, 3.0f, 2.0f};
+ EXPECT_LT(distances[kNumPasses], kMaxDistance[kNumPasses]);
+ for (int p = 0; p < kNumPasses;) {
+ int next_p = next_pass(p);
+ EXPECT_LT(distances[p], kMaxDistance[p]);
+ // Verify that the returned pass image is actually not the
+ // same as the next pass image, by checking that it has a bit
+ // worse butteraugli score.
+ EXPECT_LT(distances[next_p] * 1.2f, distances[p]);
+ p = next_p;
}
}
}
@@ -3550,22 +4797,21 @@ TEST(DecodeTest, JXL_TRANSCODE_JPEG_TEST(JPEGReconstructTestCodestream)) {
size_t channels = 3;
std::vector<uint8_t> pixels =
jxl::test::GetSomeTestImage(xsize, ysize, channels, /*seed=*/0);
- jxl::CompressParams cparams;
- cparams.color_transform = jxl::ColorTransform::kNone;
jxl::PaddedBytes jpeg_codestream;
+ jxl::TestCodestreamParams params;
+ params.cparams.color_transform = jxl::ColorTransform::kNone;
+ params.box_format = kCSBF_Single;
+ params.jpeg_codestream = &jpeg_codestream;
+ params.add_preview = true;
jxl::PaddedBytes compressed = jxl::CreateTestJXLCodestream(
jxl::Span<const uint8_t>(pixels.data(), pixels.size()), xsize, ysize,
- channels, cparams, kCSBF_Single, JXL_ORIENT_IDENTITY,
- /*add_preview=*/true,
- /*add_intrinsic_size=*/false,
- /*add_icc_profile=*/false, &jpeg_codestream);
+ channels, params);
VerifyJPEGReconstruction(compressed, jpeg_codestream);
}
#endif // JPEGXL_ENABLE_JPEG
TEST(DecodeTest, JXL_TRANSCODE_JPEG_TEST(JPEGReconstructionTest)) {
- const std::string jpeg_path =
- "third_party/imagecompression.info/flower_foveon.png.im_q85_420.jpg";
+ const std::string jpeg_path = "jxl/flower/flower.png.im_q85_420.jpg";
const jxl::PaddedBytes orig = jxl::ReadTestData(jpeg_path);
jxl::CodecInOut orig_io;
ASSERT_TRUE(
@@ -3608,12 +4854,14 @@ TEST(DecodeTest, JXL_TRANSCODE_JPEG_TEST(JPEGReconstructionMetadataTest)) {
TEST(DecodeTest, ContinueFinalNonEssentialBoxTest) {
size_t xsize = 80, ysize = 90;
std::vector<uint8_t> pixels = jxl::test::GetSomeTestImage(xsize, ysize, 4, 0);
- jxl::CompressParams cparams;
- // Lossless to verify pixels exactly after roundtrip.
+ jxl::TestCodestreamParams params;
+ params.box_format = kCSBF_Multi_Other_Terminated;
+ params.add_icc_profile = true;
jxl::PaddedBytes compressed = jxl::CreateTestJXLCodestream(
jxl::Span<const uint8_t>(pixels.data(), pixels.size()), xsize, ysize, 4,
- cparams, kCSBF_Multi_Other_Terminated, JXL_ORIENT_IDENTITY, false, false,
- true);
+ params);
+ StreamPositions streampos;
+ AnalyzeCodestream(compressed, &streampos);
// The non-essential final box size including 8-byte header
size_t final_box_size = unk3_box_size + 8;
@@ -3645,8 +4893,8 @@ TEST(DecodeTest, ContinueFinalNonEssentialBoxTest) {
size_t remaining = JxlDecoderReleaseInput(dec);
// Since the test was set up to end exactly at the boundary of the final
// codestream box, and the decoder returned success, all bytes are expected to
- // be consumed.
- EXPECT_EQ(0, remaining);
+ // be consumed until the end of the frame header.
+ EXPECT_EQ(remaining, last_box_begin - streampos.frames[0].toc_end);
// Now set the remaining non-codestream box as input.
EXPECT_EQ(JXL_DEC_SUCCESS,
@@ -3668,20 +4916,48 @@ bool BoxTypeEquals(const std::string& type_string, JxlBoxType type) {
}
} // namespace
+TEST(DecodeTest, ExtentedBoxSizeTest) {
+ const std::string jxl_path = "jxl/boxes/square-extended-size-container.jxl";
+ const jxl::PaddedBytes orig = jxl::ReadTestData(jxl_path);
+ JxlDecoder* dec = JxlDecoderCreate(nullptr);
+
+ EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderSubscribeEvents(dec, JXL_DEC_BOX));
+
+ JxlBoxType type;
+ uint64_t box_size;
+ EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderSetInput(dec, orig.data(), orig.size()));
+ EXPECT_EQ(JXL_DEC_BOX, JxlDecoderProcessInput(dec));
+ EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderGetBoxType(dec, type, JXL_FALSE));
+ EXPECT_TRUE(BoxTypeEquals("JXL ", type));
+ EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderGetBoxSizeRaw(dec, &box_size));
+ EXPECT_EQ(12, box_size);
+ EXPECT_EQ(JXL_DEC_BOX, JxlDecoderProcessInput(dec));
+ EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderGetBoxType(dec, type, JXL_FALSE));
+ EXPECT_TRUE(BoxTypeEquals("ftyp", type));
+ EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderGetBoxSizeRaw(dec, &box_size));
+ EXPECT_EQ(20, box_size);
+ EXPECT_EQ(JXL_DEC_BOX, JxlDecoderProcessInput(dec));
+ EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderGetBoxType(dec, type, JXL_FALSE));
+ EXPECT_TRUE(BoxTypeEquals("jxlc", type));
+ EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderGetBoxSizeRaw(dec, &box_size));
+ EXPECT_EQ(72, box_size);
+
+ JxlDecoderDestroy(dec);
+}
+
TEST(DecodeTest, BoxTest) {
size_t xsize = 1, ysize = 1;
std::vector<uint8_t> pixels = jxl::test::GetSomeTestImage(xsize, ysize, 4, 0);
- jxl::CompressParams cparams;
+ jxl::TestCodestreamParams params;
+ params.box_format = kCSBF_Multi_Other_Terminated;
+ params.add_icc_profile = true;
jxl::PaddedBytes compressed = jxl::CreateTestJXLCodestream(
jxl::Span<const uint8_t>(pixels.data(), pixels.size()), xsize, ysize, 4,
- cparams, kCSBF_Multi_Other_Terminated, JXL_ORIENT_IDENTITY, false, false,
- true);
+ params);
JxlDecoder* dec = JxlDecoderCreate(nullptr);
EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderSubscribeEvents(dec, JXL_DEC_BOX));
- EXPECT_EQ(JXL_DEC_SUCCESS,
- JxlDecoderSetInput(dec, compressed.data(), compressed.size()));
std::vector<std::string> expected_box_types = {
"JXL ", "ftyp", "jxlp", "unk1", "unk2", "jxlp", "jxlp", "jxlp", "unk3"};
@@ -3699,7 +4975,10 @@ TEST(DecodeTest, BoxTest) {
EXPECT_EQ(JXL_DEC_ERROR, JxlDecoderGetBoxType(dec, type, JXL_FALSE));
EXPECT_EQ(JXL_DEC_ERROR, JxlDecoderGetBoxSizeRaw(dec, &box_size));
+ uint8_t* next_in = compressed.data();
+ size_t avail_in = compressed.size();
for (size_t i = 0; i < expected_box_types.size(); i++) {
+ EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderSetInput(dec, next_in, avail_in));
EXPECT_EQ(JXL_DEC_BOX, JxlDecoderProcessInput(dec));
EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderGetBoxType(dec, type, JXL_FALSE));
EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderGetBoxSizeRaw(dec, &box_size));
@@ -3720,10 +4999,18 @@ TEST(DecodeTest, BoxTest) {
: (type[3] == '2' ? unk2_box_size : unk3_box_size);
expected_release_size = contents.size() - expected_box_contents_size;
}
+ size_t consumed = avail_in - JxlDecoderReleaseInput(dec);
+ next_in += consumed;
+ avail_in -= consumed;
}
+ // After the last DEC_BOX event, check that the input position is exactly at
+ // the stat of the box header.
+ EXPECT_EQ(avail_in, expected_box_sizes.back());
+
// Even though all input is given, the decoder cannot assume there aren't
// more boxes if the input was not closed.
+ EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderSetInput(dec, next_in, avail_in));
EXPECT_EQ(JXL_DEC_NEED_MORE_INPUT, JxlDecoderProcessInput(dec));
JxlDecoderCloseInput(dec);
EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderProcessInput(dec));
@@ -3734,11 +5021,14 @@ TEST(DecodeTest, BoxTest) {
TEST(DecodeTest, ExifBrobBoxTest) {
size_t xsize = 1, ysize = 1;
std::vector<uint8_t> pixels = jxl::test::GetSomeTestImage(xsize, ysize, 4, 0);
- jxl::CompressParams cparams;
- cparams.SetLossless(); // Lossless to verify pixels exactly after roundtrip.
+ jxl::TestCodestreamParams params;
+ // Lossless to verify pixels exactly after roundtrip.
+ params.cparams.SetLossless();
+ params.box_format = kCSBF_Brob_Exif;
+ params.add_icc_profile = true;
jxl::PaddedBytes compressed = jxl::CreateTestJXLCodestream(
jxl::Span<const uint8_t>(pixels.data(), pixels.size()), xsize, ysize, 4,
- cparams, kCSBF_Brob_Exif, JXL_ORIENT_IDENTITY, false, false, true);
+ params);
// Test raw brob box, not brotli-decompressing
for (int streaming = 0; streaming < 2; ++streaming) {
@@ -3914,12 +5204,15 @@ TEST(DecodeTest, PartialCodestreamBoxTest) {
size_t xsize = 23, ysize = 81;
std::vector<uint8_t> pixels = jxl::test::GetSomeTestImage(xsize, ysize, 4, 0);
JxlPixelFormat format_orig = {4, JXL_TYPE_UINT16, JXL_BIG_ENDIAN, 0};
- jxl::CompressParams cparams;
- cparams.SetLossless(); // Lossless to verify pixels exactly after roundtrip.
- cparams.speed_tier = jxl::SpeedTier::kThunder;
+ // Lossless to verify pixels exactly after roundtrip.
+ jxl::TestCodestreamParams params;
+ params.cparams.SetLossless();
+ params.cparams.speed_tier = jxl::SpeedTier::kThunder;
+ params.box_format = kCSBF_Multi;
+ params.add_icc_profile = true;
jxl::PaddedBytes compressed = jxl::CreateTestJXLCodestream(
jxl::Span<const uint8_t>(pixels.data(), pixels.size()), xsize, ysize, 4,
- cparams, kCSBF_Multi, JXL_ORIENT_IDENTITY, false, false, true);
+ params);
std::vector<uint8_t> extracted_codestream;
diff --git a/media/libjxl/src/lib/jxl/enc_ac_strategy.cc b/media/libjxl/src/lib/jxl/enc_ac_strategy.cc
index 3ef9792428..a6de18fbd7 100644
--- a/media/libjxl/src/lib/jxl/enc_ac_strategy.cc
+++ b/media/libjxl/src/lib/jxl/enc_ac_strategy.cc
@@ -269,6 +269,14 @@ HWY_BEFORE_NAMESPACE();
namespace jxl {
namespace HWY_NAMESPACE {
+// These templates are not found via ADL.
+using hwy::HWY_NAMESPACE::AbsDiff;
+using hwy::HWY_NAMESPACE::Eq;
+using hwy::HWY_NAMESPACE::IfThenElseZero;
+using hwy::HWY_NAMESPACE::IfThenZeroElse;
+using hwy::HWY_NAMESPACE::Round;
+using hwy::HWY_NAMESPACE::Sqrt;
+
bool MultiBlockTransformCrossesHorizontalBoundary(
const AcStrategyImage& ac_strategy, size_t start_x, size_t y,
size_t end_x) {
@@ -409,25 +417,25 @@ float EstimateEntropy(const AcStrategy& acs, size_t x, size_t y,
auto cost_delta = Set(df, config.cost_delta);
for (size_t i = 0; i < num_blocks * kDCTBlockSize; i += Lanes(df)) {
const auto in = Load(df, block + c * size + i);
- const auto in_y = Load(df, block + size + i) * cmap_factor;
+ const auto in_y = Mul(Load(df, block + size + i), cmap_factor);
const auto im = Load(df, inv_matrix + i);
- const auto val = (in - in_y) * im * q;
+ const auto val = Mul(Sub(in, in_y), Mul(im, q));
const auto rval = Round(val);
const auto diff = AbsDiff(val, rval);
- info_loss += diff;
- info_loss2 += diff * diff;
+ info_loss = Add(info_loss, diff);
+ info_loss2 = MulAdd(diff, diff, info_loss2);
const auto q = Abs(rval);
- const auto q_is_zero = q == Zero(df);
- entropy_v += IfThenElseZero(q >= Set(df, 1.5f), cost2);
+ const auto q_is_zero = Eq(q, Zero(df));
+ entropy_v = Add(entropy_v, IfThenElseZero(Ge(q, Set(df, 1.5f)), cost2));
// We used to have q * C here, but that cost model seems to
// be punishing large values more than necessary. Sqrt tries
// to avoid large values less aggressively. Having high accuracy
// around zero is most important at low qualities, and there
// we have directly specified costs for 0, 1, and 2.
- entropy_v += Sqrt(q) * cost_delta;
- nzeros_v += IfThenZeroElse(q_is_zero, Set(df, 1.0f));
+ entropy_v = MulAdd(Sqrt(q), cost_delta, entropy_v);
+ nzeros_v = Add(nzeros_v, IfThenZeroElse(q_is_zero, Set(df, 1.0f)));
}
- entropy_v += nzeros_v * cost1;
+ entropy_v = MulAdd(nzeros_v, cost1, entropy_v);
entropy += GetLane(SumOfLanes(df, entropy_v));
size_t num_nzeros = GetLane(SumOfLanes(df, nzeros_v));
diff --git a/media/libjxl/src/lib/jxl/enc_ac_strategy.h b/media/libjxl/src/lib/jxl/enc_ac_strategy.h
index 6cf82d524c..409f18b891 100644
--- a/media/libjxl/src/lib/jxl/enc_ac_strategy.h
+++ b/media/libjxl/src/lib/jxl/enc_ac_strategy.h
@@ -56,10 +56,6 @@ struct ACSConfig {
JXL_DASSERT(quant_field_row[by * quant_field_stride + bx] > 0);
return quant_field_row[by * quant_field_stride + bx];
}
- void SetQuant(size_t bx, size_t by, float value) const {
- JXL_DASSERT(value > 0);
- quant_field_row[by * quant_field_stride + bx] = value;
- }
};
struct AcStrategyHeuristics {
diff --git a/media/libjxl/src/lib/jxl/enc_adaptive_quantization.cc b/media/libjxl/src/lib/jxl/enc_adaptive_quantization.cc
index 2d0e31b35f..4d245b41d3 100644
--- a/media/libjxl/src/lib/jxl/enc_adaptive_quantization.cc
+++ b/media/libjxl/src/lib/jxl/enc_adaptive_quantization.cc
@@ -53,7 +53,13 @@ namespace HWY_NAMESPACE {
namespace {
// These templates are not found via ADL.
+using hwy::HWY_NAMESPACE::AbsDiff;
+using hwy::HWY_NAMESPACE::Add;
+using hwy::HWY_NAMESPACE::And;
+using hwy::HWY_NAMESPACE::Max;
using hwy::HWY_NAMESPACE::Rebind;
+using hwy::HWY_NAMESPACE::Sqrt;
+using hwy::HWY_NAMESPACE::ZeroIfNegative;
// The following functions modulate an exponent (out_val) and return the updated
// value. Their descriptor is limited to 8 lanes for 8x8 blocks.
@@ -74,21 +80,21 @@ V ComputeMask(const D d, const V out_val) {
const auto kOffset2 = Set(d, 305.04035728311436f);
const auto kMul3 = Set(d, 5.0220313103171232f);
const auto kOffset3 = Set(d, 2.1925739705298404f);
- const auto kOffset4 = Set(d, 0.25f) * kOffset3;
+ const auto kOffset4 = Mul(Set(d, 0.25f), kOffset3);
const auto kMul0 = Set(d, 0.74760422233706747f);
const auto k1 = Set(d, 1.0f);
// Avoid division by zero.
- const auto v1 = Max(out_val * kMul0, Set(d, 1e-3f));
- const auto v2 = k1 / (v1 + kOffset2);
- const auto v3 = k1 / MulAdd(v1, v1, kOffset3);
- const auto v4 = k1 / MulAdd(v1, v1, kOffset4);
+ const auto v1 = Max(Mul(out_val, kMul0), Set(d, 1e-3f));
+ const auto v2 = Div(k1, Add(v1, kOffset2));
+ const auto v3 = Div(k1, MulAdd(v1, v1, kOffset3));
+ const auto v4 = Div(k1, MulAdd(v1, v1, kOffset4));
// TODO(jyrki):
// A log or two here could make sense. In butteraugli we have effectively
// log(log(x + C)) for this kind of use, as a single log is used in
// saturating visual masking and here the modulation values are exponential,
// another log would counter that.
- return kBase + MulAdd(kMul4, v4, MulAdd(kMul2, v2, kMul3 * v3));
+ return Add(kBase, MulAdd(kMul4, v4, MulAdd(kMul2, v2, Mul(kMul3, v3))));
}
// For converting full vectors to a subset. Assumes `vfull` lanes are identical.
@@ -123,11 +129,11 @@ V RatioOfDerivativesOfCubicRootToSimpleGamma(const D d, V v) {
const auto kVOffset = Set(d, kSGVOffset * kLog2 + kEpsilon);
const auto kDenMul = Set(d, kLog2 * kSGmul);
- const auto v2 = v * v;
+ const auto v2 = Mul(v, v);
const auto num = MulAdd(kNumMul, v2, Set(d, kEpsilon));
- const auto den = MulAdd(kDenMul * v, v2, kVOffset);
- return invert ? num / den : den / num;
+ const auto den = MulAdd(Mul(kDenMul, v), v2, kVOffset);
+ return invert ? Div(num, den) : Div(den, num);
}
template <bool invert = false>
@@ -175,21 +181,20 @@ V GammaModulation(const D d, const size_t x, const size_t y,
const float* const JXL_RESTRICT row_in_x = xyb_x.Row(y + dy);
const float* const JXL_RESTRICT row_in_y = xyb_y.Row(y + dy);
for (size_t dx = 0; dx < 8; dx += Lanes(d)) {
- const auto iny = Load(d, row_in_y + x + dx) + bias;
+ const auto iny = Add(Load(d, row_in_y + x + dx), bias);
const auto inx = Load(d, row_in_x + x + dx);
- const auto r = iny - inx;
- const auto g = iny + inx;
+ const auto r = Sub(iny, inx);
+ const auto g = Add(iny, inx);
const auto ratio_r =
RatioOfDerivativesOfCubicRootToSimpleGamma</*invert=*/true>(d, r);
const auto ratio_g =
RatioOfDerivativesOfCubicRootToSimpleGamma</*invert=*/true>(d, g);
- const auto avg_ratio = half * (ratio_r + ratio_g);
+ const auto avg_ratio = Mul(half, Add(ratio_r, ratio_g));
- overall_ratio += avg_ratio;
+ overall_ratio = Add(overall_ratio, avg_ratio);
}
}
- overall_ratio = SumOfLanes(d, overall_ratio);
- overall_ratio *= Set(d, 1.0f / 64);
+ overall_ratio = Mul(SumOfLanes(d, overall_ratio), Set(d, 1.0f / 64));
// ideally -1.0, but likely optimal correction adds some entropy, so slightly
// less than that.
// ln(2) constant folded in because we want std::log but have FastLog2f.
@@ -217,7 +222,7 @@ V ColorModulation(const D d, const size_t x, const size_t y,
{
// Reduce some bits from areas not blue or red.
const float offset = strength * -0.009174542291185913f;
- out_val += Set(d, offset);
+ out_val = Add(out_val, Set(d, offset));
}
// Calculate how much of the 8x8 block is covered with blue or red.
auto blue_coverage = Zero(d);
@@ -227,16 +232,16 @@ V ColorModulation(const D d, const size_t x, const size_t y,
const float* const JXL_RESTRICT row_in_y = xyb_y.Row(y + dy);
const float* const JXL_RESTRICT row_in_b = xyb_b.Row(y + dy);
for (size_t dx = 0; dx < 8; dx += Lanes(d)) {
- const auto pixel_x =
- Max(Set(d, 0.0f), Load(d, row_in_x + x + dx) - Set(d, kRedRampStart));
+ const auto pixel_x = Max(
+ Set(d, 0.0f), Sub(Load(d, row_in_x + x + dx), Set(d, kRedRampStart)));
const auto pixel_y = Load(d, row_in_y + x + dx);
const auto pixel_b =
- Max(Set(d, 0.0f),
- Load(d, row_in_b + x + dx) - pixel_y - Set(d, kBlueRampStart));
+ Max(Set(d, 0.0f), Sub(Load(d, row_in_b + x + dx),
+ Add(pixel_y, Set(d, kBlueRampStart))));
const auto blue_slope = Min(pixel_b, Set(d, kBlueRampLength));
const auto red_slope = Min(pixel_x, Set(d, kRedRampLength));
- red_coverage += red_slope;
- blue_coverage += blue_slope;
+ red_coverage = Add(red_coverage, red_slope);
+ blue_coverage = Add(blue_coverage, blue_slope);
}
}
@@ -248,14 +253,16 @@ V ColorModulation(const D d, const size_t x, const size_t y,
auto overall_red_coverage = SumOfLanes(d, red_coverage);
overall_red_coverage =
Min(overall_red_coverage, Set(d, ratio * kRedRampLength));
- overall_red_coverage *= Set(d, red_strength / ratio);
+ overall_red_coverage =
+ Mul(overall_red_coverage, Set(d, red_strength / ratio));
auto overall_blue_coverage = SumOfLanes(d, blue_coverage);
overall_blue_coverage =
Min(overall_blue_coverage, Set(d, ratio * kBlueRampLength));
- overall_blue_coverage *= Set(d, blue_strength / ratio);
+ overall_blue_coverage =
+ Mul(overall_blue_coverage, Set(d, blue_strength / ratio));
- return overall_red_coverage + overall_blue_coverage + out_val;
+ return Add(overall_red_coverage, Add(overall_blue_coverage, out_val));
}
// Change precision in 8x8 blocks that have high frequency content.
@@ -287,10 +294,10 @@ V HfModulation(const D d, const size_t x, const size_t y, const ImageF& xyb,
const auto p = Load(d, row_in + dx);
const auto pr = LoadU(d, row_in + dx + 1);
const auto mask = BitCast(d, Load(du, kMaskRight + dx));
- sum += And(mask, AbsDiff(p, pr));
+ sum = Add(sum, And(mask, AbsDiff(p, pr)));
const auto pd = Load(d, row_in_next + dx);
- sum += AbsDiff(p, pd);
+ sum = Add(sum, AbsDiff(p, pd));
}
}
@@ -343,7 +350,7 @@ V MaskingSqrt(const D d, V v) {
static const float kMul = 211.50759899638012f;
const auto mul_v = Set(d, kMul * 1e8);
const auto offset_v = Set(d, kLogOffset);
- return Set(d, 0.25f) * Sqrt(MulAdd(v, Sqrt(mul_v), offset_v));
+ return Mul(Set(d, 0.25f), Sqrt(MulAdd(v, Sqrt(mul_v), offset_v)));
}
float MaskingSqrt(const float v) {
@@ -524,25 +531,26 @@ struct AdaptiveQuantizationImpl {
const auto in_l = LoadU(df, row_in + x - 1);
const auto in_t = LoadU(df, row_in2 + x);
const auto in_b = LoadU(df, row_in1 + x);
- auto base = quarter * (in_r + in_l + in_t + in_b);
+ auto base = Mul(quarter, Add(Add(in_r, in_l), Add(in_t, in_b)));
auto gammacv =
RatioOfDerivativesOfCubicRootToSimpleGamma</*invert=*/false>(
- df, in + match_gamma_offset_v);
- auto diff = gammacv * (in - base);
- diff *= diff;
+ df, Add(in, match_gamma_offset_v));
+ auto diff = Mul(gammacv, Sub(in, base));
+ diff = Mul(diff, diff);
const auto in_x = LoadU(df, row_x_in + x);
const auto in_x_r = LoadU(df, row_x_in + x + 1);
const auto in_x_l = LoadU(df, row_x_in + x - 1);
const auto in_x_t = LoadU(df, row_x_in2 + x);
const auto in_x_b = LoadU(df, row_x_in1 + x);
- auto base_x = quarter * (in_x_r + in_x_l + in_x_t + in_x_b);
- auto diff_x = gammacv * (in_x - base_x);
- diff_x *= diff_x;
- diff += kXMulv * diff_x;
+ auto base_x =
+ Mul(quarter, Add(Add(in_x_r, in_x_l), Add(in_x_t, in_x_b)));
+ auto diff_x = Mul(gammacv, Sub(in_x, base_x));
+ diff_x = Mul(diff_x, diff_x);
+ diff = MulAdd(kXMulv, diff_x, diff);
diff = MaskingSqrt(df, diff);
if ((y & 3) != 0) {
- diff += LoadU(df, row_out + x - x0);
+ diff = Add(diff, LoadU(df, row_out + x - x0));
}
StoreU(diff, df, row_out + x - x0);
}
@@ -625,7 +633,6 @@ namespace jxl {
HWY_EXPORT(AdaptiveQuantizationMap);
namespace {
-bool FLAGS_log_search_state = false;
// If true, prints the quantization maps at each iteration.
bool FLAGS_dump_quant_state = false;
@@ -735,6 +742,13 @@ void FindBestQuantization(const ImageBundle& linear, const Image3F& opsin,
const JxlCmsInterface& cms, ThreadPool* pool,
AuxOut* aux_out) {
const CompressParams& cparams = enc_state->cparams;
+ if (cparams.resampling > 1 &&
+ cparams.original_butteraugli_distance <= 4.0 * cparams.resampling) {
+ // For downsampled opsin image, the butteraugli based adaptive quantization
+ // loop would only make the size bigger without improving the distance much,
+ // so in this case we enable it only for very high butteraugli targets.
+ return;
+ }
Quantizer& quantizer = enc_state->shared.quantizer;
ImageI& raw_quant_field = enc_state->shared.raw_quant_field;
ImageF& quant_field = enc_state->initial_quant_field;
@@ -761,6 +775,7 @@ void FindBestQuantization(const ImageBundle& linear, const Image3F& opsin,
enc_state->shared.frame_header.nonserialized_metadata->ysize());
const float butteraugli_target = cparams.butteraugli_distance;
+ const float original_butteraugli = cparams.original_butteraugli_distance;
ButteraugliParams params = cparams.ba_params;
params.intensity_target = linear.metadata()->IntensityTarget();
// Hack the default intensity target value to be 80.0, the intensity
@@ -818,18 +833,20 @@ void FindBestQuantization(const ImageBundle& linear, const Image3F& opsin,
score = -score;
diffmap = ScaleImage(-1.0f, diffmap);
}
- tile_distmap = TileDistMap(diffmap, 8, 0, enc_state->shared.ac_strategy);
+ tile_distmap = TileDistMap(diffmap, 8 * cparams.resampling, 0,
+ enc_state->shared.ac_strategy);
if (WantDebugOutput(aux_out)) {
aux_out->DumpImage(("dec" + ToString(i)).c_str(), *dec_linear.color());
DumpHeatmaps(aux_out, butteraugli_target, quant_field, tile_distmap,
diffmap);
}
if (aux_out != nullptr) ++aux_out->num_butteraugli_iters;
- if (FLAGS_log_search_state) {
+ if (cparams.log_search_state) {
float minval, maxval;
ImageMinMax(quant_field, &minval, &maxval);
printf("\nButteraugli iter: %d/%d\n", i, cparams.max_butteraugli_iters);
- printf("Butteraugli distance: %f\n", score);
+ printf("Butteraugli distance: %f (target = %f)\n", score,
+ original_butteraugli);
printf("quant range: %f ... %f DC quant: %f\n", minval, maxval,
initial_quant_dc);
if (FLAGS_dump_quant_state) {
@@ -867,7 +884,7 @@ void FindBestQuantization(const ImageBundle& linear, const Image3F& opsin,
double cur_pow = 0.0;
if (i < 7) {
- cur_pow = kPow[i] + (butteraugli_target - 1.0) * kPowMod[i];
+ cur_pow = kPow[i] + (original_butteraugli - 1.0) * kPowMod[i];
if (cur_pow < 0) {
cur_pow = 0;
}
@@ -877,7 +894,7 @@ void FindBestQuantization(const ImageBundle& linear, const Image3F& opsin,
const float* const JXL_RESTRICT row_dist = tile_distmap.Row(y);
float* const JXL_RESTRICT row_q = quant_field.Row(y);
for (size_t x = 0; x < quant_field.xsize(); ++x) {
- const float diff = row_dist[x] / butteraugli_target;
+ const float diff = row_dist[x] / original_butteraugli;
if (diff > 1.0f) {
float old = row_q[x];
row_q[x] *= diff;
@@ -896,7 +913,7 @@ void FindBestQuantization(const ImageBundle& linear, const Image3F& opsin,
const float* const JXL_RESTRICT row_dist = tile_distmap.Row(y);
float* const JXL_RESTRICT row_q = quant_field.Row(y);
for (size_t x = 0; x < quant_field.xsize(); ++x) {
- const float diff = row_dist[x] / butteraugli_target;
+ const float diff = row_dist[x] / original_butteraugli;
if (diff <= 1.0f) {
row_q[x] *= std::pow(diff, cur_pow);
} else {
@@ -921,10 +938,7 @@ void FindBestQuantizationMaxError(const Image3F& opsin,
PassesEncoderState* enc_state,
const JxlCmsInterface& cms, ThreadPool* pool,
AuxOut* aux_out) {
- // TODO(veluca): this only works if opsin is in XYB. The current encoder does
- // not have code paths that produce non-XYB opsin here.
- JXL_CHECK(enc_state->shared.frame_header.color_transform ==
- ColorTransform::kXYB);
+ // TODO(szabadka): Make this work for non-opsin color spaces.
const CompressParams& cparams = enc_state->cparams;
Quantizer& quantizer = enc_state->shared.quantizer;
ImageI& raw_quant_field = enc_state->shared.raw_quant_field;
@@ -1065,10 +1079,8 @@ ImageBundle RoundtripImage(const Image3F& opsin, PassesEncoderState* enc_state,
PROFILER_ZONE("enc roundtrip");
std::unique_ptr<PassesDecoderState> dec_state =
jxl::make_unique<PassesDecoderState>();
- JXL_CHECK(dec_state->output_encoding_info.Set(
- *enc_state->shared.metadata,
- ColorEncoding::LinearSRGB(
- enc_state->shared.metadata->m.color_encoding.IsGray())));
+ JXL_CHECK(dec_state->output_encoding_info.SetFromMetadata(
+ *enc_state->shared.metadata));
dec_state->shared = &enc_state->shared;
JXL_ASSERT(opsin.ysize() % kBlockDim == 0);
diff --git a/media/libjxl/src/lib/jxl/enc_ans.cc b/media/libjxl/src/lib/jxl/enc_ans.cc
index 31bd99bee8..81ff836752 100644
--- a/media/libjxl/src/lib/jxl/enc_ans.cc
+++ b/media/libjxl/src/lib/jxl/enc_ans.cc
@@ -85,7 +85,7 @@ float EstimateDataBits(const ANSHistBin* histogram, const ANSHistBin* counts,
float EstimateDataBitsFlat(const ANSHistBin* histogram, size_t len) {
const float flat_bits = std::max(FastLog2f(len), 0.0f);
- int total_histogram = 0;
+ float total_histogram = 0;
for (size_t i = 0; i < len; ++i) {
total_histogram += histogram[i];
}
@@ -442,20 +442,24 @@ size_t BuildAndStoreANSEncodingData(
{
std::vector<uint8_t> depths(alphabet_size);
std::vector<uint16_t> bits(alphabet_size);
- BitWriter tmp_writer;
- BitWriter* w = writer ? writer : &tmp_writer;
- size_t start = w->BitsWritten();
- BitWriter::Allotment allotment(
- w, 8 * alphabet_size + 8); // safe upper bound
- BuildAndStoreHuffmanTree(histo.data(), alphabet_size, depths.data(),
- bits.data(), w);
- ReclaimAndCharge(w, &allotment, 0, /*aux_out=*/nullptr);
-
+ if (writer == nullptr) {
+ BitWriter tmp_writer;
+ BitWriter::Allotment allotment(
+ &tmp_writer, 8 * alphabet_size + 8); // safe upper bound
+ BuildAndStoreHuffmanTree(histo.data(), alphabet_size, depths.data(),
+ bits.data(), &tmp_writer);
+ ReclaimAndCharge(&tmp_writer, &allotment, 0, /*aux_out=*/nullptr);
+ cost = tmp_writer.BitsWritten();
+ } else {
+ size_t start = writer->BitsWritten();
+ BuildAndStoreHuffmanTree(histo.data(), alphabet_size, depths.data(),
+ bits.data(), writer);
+ cost = writer->BitsWritten() - start;
+ }
for (size_t i = 0; i < alphabet_size; i++) {
info[i].bits = depths[i] == 0 ? 0 : bits[i];
info[i].depth = depths[i];
}
- cost = w->BitsWritten() - start;
}
// Estimate data cost.
for (size_t i = 0; i < alphabet_size; i++) {
@@ -699,9 +703,8 @@ class HistogramBuilder {
if (histograms_.size() > 1) {
if (!ans_fuzzer_friendly_) {
std::vector<uint32_t> histogram_symbols;
- ClusterHistograms(params, histograms_, histograms_.size(),
- kClustersLimit, &clustered_histograms,
- &histogram_symbols);
+ ClusterHistograms(params, histograms_, kClustersLimit,
+ &clustered_histograms, &histogram_symbols);
for (size_t c = 0; c < histograms_.size(); ++c) {
(*context_map)[c] = static_cast<uint8_t>(histogram_symbols[c]);
}
@@ -719,7 +722,8 @@ class HistogramBuilder {
}
}
if (writer != nullptr) {
- EncodeContextMap(*context_map, clustered_histograms.size(), writer);
+ EncodeContextMap(*context_map, clustered_histograms.size(), writer,
+ layer, aux_out);
}
}
if (aux_out != nullptr) {
diff --git a/media/libjxl/src/lib/jxl/enc_ar_control_field.cc b/media/libjxl/src/lib/jxl/enc_ar_control_field.cc
index f8025ac694..9030430e2b 100644
--- a/media/libjxl/src/lib/jxl/enc_ar_control_field.cc
+++ b/media/libjxl/src/lib/jxl/enc_ar_control_field.cc
@@ -34,6 +34,13 @@ namespace jxl {
namespace HWY_NAMESPACE {
namespace {
+// These templates are not found via ADL.
+using hwy::HWY_NAMESPACE::Add;
+using hwy::HWY_NAMESPACE::GetLane;
+using hwy::HWY_NAMESPACE::Mul;
+using hwy::HWY_NAMESPACE::MulAdd;
+using hwy::HWY_NAMESPACE::Sqrt;
+
void ProcessTile(const Image3F& opsin, PassesEncoderState* enc_state,
const Rect& rect,
ArControlFieldHeuristics::TempImages* temp_image) {
@@ -114,18 +121,18 @@ void ProcessTile(const Image3F& opsin, PassesEncoderState* enc_state,
auto sumsqr = Zero(df);
for (size_t c = 0; c < 3; c++) {
auto laplacian =
- LoadU(df, in_row[c] + cx) * Set(df, kChannelWeights[c]);
+ Mul(LoadU(df, in_row[c] + cx), Set(df, kChannelWeights[c]));
auto sum_oth0 = LoadU(df, in_row[c] + cx - 1);
auto sum_oth1 = LoadU(df, in_row[c] + cx + 1);
auto sum_oth2 = LoadU(df, in_row_t[c] + cx - 1);
auto sum_oth3 = LoadU(df, in_row_t[c] + cx);
- sum_oth0 += LoadU(df, in_row_t[c] + cx + 1);
- sum_oth1 += LoadU(df, in_row_b[c] + cx - 1);
- sum_oth2 += LoadU(df, in_row_b[c] + cx);
- sum_oth3 += LoadU(df, in_row_b[c] + cx + 1);
- sum_oth0 += sum_oth1;
- sum_oth2 += sum_oth3;
- sum_oth0 += sum_oth2;
+ sum_oth0 = Add(sum_oth0, LoadU(df, in_row_t[c] + cx + 1));
+ sum_oth1 = Add(sum_oth1, LoadU(df, in_row_b[c] + cx - 1));
+ sum_oth2 = Add(sum_oth2, LoadU(df, in_row_b[c] + cx));
+ sum_oth3 = Add(sum_oth3, LoadU(df, in_row_b[c] + cx + 1));
+ sum_oth0 = Add(sum_oth0, sum_oth1);
+ sum_oth2 = Add(sum_oth2, sum_oth3);
+ sum_oth0 = Add(sum_oth0, sum_oth2);
laplacian =
MulAdd(Set(df, kChannelWeightsLapNeg[c]), sum_oth0, laplacian);
sumsqr = MulAdd(laplacian, laplacian, sumsqr);
@@ -154,7 +161,7 @@ void ProcessTile(const Image3F& opsin, PassesEncoderState* enc_state,
auto sum = Zero(df4);
for (size_t iy = 0; iy < 4; iy++) {
for (size_t ix = 0; ix < 4; ix += Lanes(df4)) {
- sum += LoadU(df4, rows_in[iy] + x * 4 + ix + 2);
+ sum = Add(sum, LoadU(df4, rows_in[iy] + x * 4 + ix + 2));
}
}
row_out[x] = GetLane(Sqrt(SumOfLanes(df4, sum))) * (1.0f / 4.0f);
@@ -190,7 +197,7 @@ void ProcessTile(const Image3F& opsin, PassesEncoderState* enc_state,
auto sum = Zero(df4);
for (size_t iy = 0; iy < 4; iy++) {
for (size_t ix = 0; ix < 4; ix += Lanes(df4)) {
- sum += Load(df4, rows_in[iy] + sx + ix);
+ sum = Add(sum, Load(df4, rows_in[iy] + sx + ix));
}
}
row_out[x] = GetLane(Sqrt(SumOfLanes(df4, sum))) * (1.0f / 4.0f);
diff --git a/media/libjxl/src/lib/jxl/enc_bit_writer.h b/media/libjxl/src/lib/jxl/enc_bit_writer.h
index 5aac355c53..4cac8dfbe5 100644
--- a/media/libjxl/src/lib/jxl/enc_bit_writer.h
+++ b/media/libjxl/src/lib/jxl/enc_bit_writer.h
@@ -38,10 +38,6 @@ struct BitWriter {
BitWriter(BitWriter&&) = default;
BitWriter& operator=(BitWriter&&) = default;
- explicit BitWriter(PaddedBytes&& donor)
- : bits_written_(donor.size() * kBitsPerByte),
- storage_(std::move(donor)) {}
-
size_t BitsWritten() const { return bits_written_; }
Span<const uint8_t> GetSpan() const {
@@ -60,8 +56,11 @@ struct BitWriter {
return std::move(storage_);
}
+ private:
// Must be byte-aligned before calling.
void AppendByteAligned(const Span<const uint8_t>& span);
+
+ public:
// NOTE: no allotment needed, the other BitWriters have already been charged.
void AppendByteAligned(const BitWriter& other);
void AppendByteAligned(const std::vector<std::unique_ptr<BitWriter>>& others);
diff --git a/media/libjxl/src/lib/jxl/enc_butteraugli_pnorm.cc b/media/libjxl/src/lib/jxl/enc_butteraugli_pnorm.cc
index ecad5135ed..fe5629dcda 100644
--- a/media/libjxl/src/lib/jxl/enc_butteraugli_pnorm.cc
+++ b/media/libjxl/src/lib/jxl/enc_butteraugli_pnorm.cc
@@ -24,6 +24,9 @@ namespace jxl {
namespace HWY_NAMESPACE {
// These templates are not found via ADL.
+using hwy::HWY_NAMESPACE::Add;
+using hwy::HWY_NAMESPACE::GetLane;
+using hwy::HWY_NAMESPACE::Mul;
using hwy::HWY_NAMESPACE::Rebind;
double ComputeDistanceP(const ImageF& distmap, const ButteraugliParams& params,
@@ -63,17 +66,17 @@ double ComputeDistanceP(const ImageF& distmap, const ButteraugliParams& params,
#else
const auto d1 = Load(d, row + x);
#endif
- const auto d2 = d1 * d1 * d1;
- sums0 += d2;
- const auto d3 = d2 * d2;
- sums1 += d3;
- const auto d4 = d3 * d3;
- sums2 += d4;
+ const auto d2 = Mul(d1, Mul(d1, d1));
+ sums0 = Add(sums0, d2);
+ const auto d3 = Mul(d2, d2);
+ sums1 = Add(sums1, d3);
+ const auto d4 = Mul(d3, d3);
+ sums2 = Add(sums2, d4);
}
- Store(sums0 + Load(d, sum_totals0), d, sum_totals0);
- Store(sums1 + Load(d, sum_totals1), d, sum_totals1);
- Store(sums2 + Load(d, sum_totals2), d, sum_totals2);
+ Store(Add(sums0, Load(d, sum_totals0)), d, sum_totals0);
+ Store(Add(sums1, Load(d, sum_totals1)), d, sum_totals1);
+ Store(Add(sums2, Load(d, sum_totals2)), d, sum_totals2);
for (; x < distmap.xsize(); ++x) {
const double d1 = row[x];
diff --git a/media/libjxl/src/lib/jxl/enc_cache.cc b/media/libjxl/src/lib/jxl/enc_cache.cc
index 493d1ba0a6..a1f2a0887a 100644
--- a/media/libjxl/src/lib/jxl/enc_cache.cc
+++ b/media/libjxl/src/lib/jxl/enc_cache.cc
@@ -25,6 +25,7 @@
#include "lib/jxl/enc_frame.h"
#include "lib/jxl/enc_group.h"
#include "lib/jxl/enc_modular.h"
+#include "lib/jxl/enc_quant_weights.h"
#include "lib/jxl/frame_header.h"
#include "lib/jxl/image.h"
#include "lib/jxl/image_bundle.h"
@@ -62,6 +63,11 @@ Status InitializePassesEncoder(const Image3F& opsin, const JxlCmsInterface& cms,
enc_state->coeffs.pop_back();
}
+ float scale =
+ shared.quantizer.ScaleGlobalScale(enc_state->cparams.quant_ac_rescale);
+ DequantMatricesScaleDC(&shared.matrices, scale);
+ shared.quantizer.RecomputeFromGlobalScale();
+
Image3F dc(shared.frame_dim.xsize_blocks, shared.frame_dim.ysize_blocks);
JXL_RETURN_IF_ERROR(RunOnPool(
pool, 0, shared.frame_dim.num_groups, ThreadPool::NoInit,
@@ -72,28 +78,17 @@ Status InitializePassesEncoder(const Image3F& opsin, const JxlCmsInterface& cms,
if (shared.frame_header.flags & FrameHeader::kUseDcFrame) {
CompressParams cparams = enc_state->cparams;
- // Guess a distance that produces good initial results.
- cparams.butteraugli_distance =
- std::max(kMinButteraugliDistance,
- enc_state->cparams.butteraugli_distance * 0.1f);
cparams.dots = Override::kOff;
cparams.noise = Override::kOff;
cparams.patches = Override::kOff;
cparams.gaborish = Override::kOff;
cparams.epf = 0;
- cparams.max_error_mode = true;
cparams.resampling = 1;
cparams.ec_resampling = 1;
- for (size_t c = 0; c < 3; c++) {
- cparams.max_error[c] = shared.quantizer.MulDC()[c];
- }
- JXL_ASSERT(cparams.progressive_dc > 0);
- cparams.progressive_dc--;
// The DC frame will have alpha=0. Don't erase its contents.
cparams.keep_invisible = Override::kOn;
- // No EPF or Gaborish in DC frames.
- cparams.epf = 0;
- cparams.gaborish = Override::kOff;
+ JXL_ASSERT(cparams.progressive_dc > 0);
+ cparams.progressive_dc--;
// Use kVarDCT in max_error_mode for intermediate progressive DC,
// and kModular for the smallest DC (first in the bitstream)
if (cparams.progressive_dc == 0) {
@@ -102,6 +97,15 @@ Status InitializePassesEncoder(const Image3F& opsin, const JxlCmsInterface& cms,
cparams.butteraugli_distance =
std::max(kMinButteraugliDistance,
enc_state->cparams.butteraugli_distance * 0.03f);
+ } else {
+ cparams.max_error_mode = true;
+ for (size_t c = 0; c < 3; c++) {
+ cparams.max_error[c] = shared.quantizer.MulDC()[c];
+ }
+ // Guess a distance that produces good initial results.
+ cparams.butteraugli_distance =
+ std::max(kMinButteraugliDistance,
+ enc_state->cparams.butteraugli_distance * 0.1f);
}
ImageBundle ib(&shared.metadata->m);
// This is a lie - dc is in XYB
@@ -134,24 +138,34 @@ Status InitializePassesEncoder(const Image3F& opsin, const JxlCmsInterface& cms,
dc_frame_info.dc_level = shared.frame_header.dc_level + 1;
dc_frame_info.ib_needs_color_transform = false;
dc_frame_info.save_before_color_transform = true; // Implicitly true
- // TODO(lode): the EncodeFrame / DecodeFrame pair here is likely broken in
- // case of dc_level >= 3, since EncodeFrame may output multiple frames
- // to the bitwriter, while DecodeFrame reads only one.
+ AuxOut dc_aux_out;
+ if (aux_out) {
+ dc_aux_out.debug_prefix = aux_out->debug_prefix;
+ }
JXL_CHECK(EncodeFrame(cparams, dc_frame_info, shared.metadata, ib,
state.get(), cms, pool, special_frame.get(),
- nullptr));
+ aux_out ? &dc_aux_out : nullptr));
+ if (aux_out) {
+ for (const auto& l : dc_aux_out.layers) {
+ aux_out->layers[kLayerDC].Assimilate(l);
+ }
+ }
const Span<const uint8_t> encoded = special_frame->GetSpan();
enc_state->special_frames.emplace_back(std::move(special_frame));
- BitReader br(encoded);
ImageBundle decoded(&shared.metadata->m);
std::unique_ptr<PassesDecoderState> dec_state =
jxl::make_unique<PassesDecoderState>();
- JXL_CHECK(dec_state->output_encoding_info.Set(
- *shared.metadata,
- ColorEncoding::LinearSRGB(shared.metadata->m.color_encoding.IsGray())));
- JXL_CHECK(DecodeFrame({}, dec_state.get(), pool, &br, &decoded,
- *shared.metadata, /*constraints=*/nullptr));
+ JXL_CHECK(
+ dec_state->output_encoding_info.SetFromMetadata(*shared.metadata));
+ const uint8_t* frame_start = encoded.data();
+ size_t encoded_size = encoded.size();
+ for (int i = 0; i <= cparams.progressive_dc; ++i) {
+ JXL_CHECK(DecodeFrame(dec_state.get(), pool, frame_start, encoded_size,
+ &decoded, *shared.metadata));
+ frame_start += decoded.decoded_bytes();
+ encoded_size -= decoded.decoded_bytes();
+ }
// TODO(lode): shared.frame_header.dc_level should be equal to
// dec_state.shared->frame_header.dc_level - 1 here, since above we set
// dc_frame_info.dc_level = shared.frame_header.dc_level + 1, and
@@ -161,7 +175,7 @@ Status InitializePassesEncoder(const Image3F& opsin, const JxlCmsInterface& cms,
CopyImage(dec_state->shared->dc_frames[shared.frame_header.dc_level]);
ZeroFillImage(&shared.quant_dc);
shared.dc = &shared.dc_storage;
- JXL_CHECK(br.Close());
+ JXL_CHECK(encoded_size == 0);
} else {
auto compute_dc_coeffs = [&](int group_index, int /* thread */) {
modular_frame_encoder->AddVarDCTDC(
diff --git a/media/libjxl/src/lib/jxl/enc_chroma_from_luma.cc b/media/libjxl/src/lib/jxl/enc_chroma_from_luma.cc
index 370595c5e5..4f0798e994 100644
--- a/media/libjxl/src/lib/jxl/enc_chroma_from_luma.cc
+++ b/media/libjxl/src/lib/jxl/enc_chroma_from_luma.cc
@@ -35,6 +35,13 @@ HWY_BEFORE_NAMESPACE();
namespace jxl {
namespace HWY_NAMESPACE {
+// These templates are not found via ADL.
+using hwy::HWY_NAMESPACE::Abs;
+using hwy::HWY_NAMESPACE::Ge;
+using hwy::HWY_NAMESPACE::GetLane;
+using hwy::HWY_NAMESPACE::IfThenElse;
+using hwy::HWY_NAMESPACE::Lt;
+
static HWY_FULL(float) df;
struct CFLFunction {
@@ -72,23 +79,27 @@ struct CFLFunction {
for (size_t i = 0; i < num; i += Lanes(df)) {
// color residual = ax + b
- const auto a = inv_color_factor * Load(df, values_m + i);
- const auto b = base_v * Load(df, values_m + i) - Load(df, values_s + i);
- const auto v = a * x_v + b;
- const auto vpe = a * xpe_v + b;
- const auto vme = a * xme_v + b;
+ const auto a = Mul(inv_color_factor, Load(df, values_m + i));
+ const auto b =
+ Sub(Mul(base_v, Load(df, values_m + i)), Load(df, values_s + i));
+ const auto v = MulAdd(a, x_v, b);
+ const auto vpe = MulAdd(a, xpe_v, b);
+ const auto vme = MulAdd(a, xme_v, b);
const auto av = Abs(v);
const auto avpe = Abs(vpe);
const auto avme = Abs(vme);
- auto d = coeffx2 * (av + one) * a;
- auto dpe = coeffx2 * (avpe + one) * a;
- auto dme = coeffx2 * (avme + one) * a;
- d = IfThenElse(v < zero, zero - d, d);
- dpe = IfThenElse(vpe < zero, zero - dpe, dpe);
- dme = IfThenElse(vme < zero, zero - dme, dme);
- fd_v += IfThenElse(av >= thres, zero, d);
- fdpe_v += IfThenElse(av >= thres, zero, dpe);
- fdme_v += IfThenElse(av >= thres, zero, dme);
+ const auto acoeffx2 = Mul(coeffx2, a);
+ auto d = Mul(acoeffx2, Add(av, one));
+ auto dpe = Mul(acoeffx2, Add(avpe, one));
+ auto dme = Mul(acoeffx2, Add(avme, one));
+ d = IfThenElse(Lt(v, zero), Sub(zero, d), d);
+ dpe = IfThenElse(Lt(vpe, zero), Sub(zero, dpe), dpe);
+ dme = IfThenElse(Lt(vme, zero), Sub(zero, dme), dme);
+ const auto above = Ge(av, thres);
+ // TODO(eustas): use IfThenElseZero
+ fd_v = Add(fd_v, IfThenElse(above, zero, d));
+ fdpe_v = Add(fdpe_v, IfThenElse(above, zero, dpe));
+ fdme_v = Add(fdme_v, IfThenElse(above, zero, dme));
}
*fpeps = first_derivative_peps + GetLane(SumOfLanes(df, fdpe_v));
@@ -118,8 +129,9 @@ int32_t FindBestMultiplier(const float* values_m, const float* values_s,
const auto base_v = Set(df, base);
for (size_t i = 0; i < num; i += Lanes(df)) {
// color residual = ax + b
- const auto a = inv_color_factor * Load(df, values_m + i);
- const auto b = base_v * Load(df, values_m + i) - Load(df, values_s + i);
+ const auto a = Mul(inv_color_factor, Load(df, values_m + i));
+ const auto b =
+ Sub(Mul(base_v, Load(df, values_m + i)), Load(df, values_s + i));
ca = MulAdd(a, a, ca);
cb = MulAdd(a, b, cb);
}
@@ -163,7 +175,8 @@ void InitDCStorage(size_t num_blocks, ImageF* dc_values) {
}
}
-void ComputeDC(const ImageF& dc_values, bool fast, int* dc_x, int* dc_b) {
+void ComputeDC(const ImageF& dc_values, bool fast, int32_t* dc_x,
+ int32_t* dc_b) {
constexpr float kDistanceMultiplierDC = 1e-5f;
const float* JXL_RESTRICT dc_values_yx = dc_values.Row(0);
const float* JXL_RESTRICT dc_values_x = dc_values.Row(1);
@@ -292,12 +305,12 @@ void ComputeTile(const Image3F& opsin, const DequantMatrices& dequant,
const auto b_y = Load(df, block_y + i);
const auto b_x = Load(df, block_x + i);
const auto b_b = Load(df, block_b + i);
- const auto qqm_x = qv * Load(df, qm_x + i);
- const auto qqm_b = qv * Load(df, qm_b + i);
- Store(b_y * qqm_x, df, coeffs_yx + num_ac);
- Store(b_x * qqm_x, df, coeffs_x + num_ac);
- Store(b_y * qqm_b, df, coeffs_yb + num_ac);
- Store(b_b * qqm_b, df, coeffs_b + num_ac);
+ const auto qqm_x = Mul(qv, Load(df, qm_x + i));
+ const auto qqm_b = Mul(qv, Load(df, qm_b + i));
+ Store(Mul(b_y, qqm_x), df, coeffs_yx + num_ac);
+ Store(Mul(b_x, qqm_x), df, coeffs_x + num_ac);
+ Store(Mul(b_y, qqm_b), df, coeffs_yb + num_ac);
+ Store(Mul(b_b, qqm_b), df, coeffs_b + num_ac);
num_ac += Lanes(df);
}
}
diff --git a/media/libjxl/src/lib/jxl/enc_cluster.cc b/media/libjxl/src/lib/jxl/enc_cluster.cc
index 8ae863c47d..c79b3ac834 100644
--- a/media/libjxl/src/lib/jxl/enc_cluster.cc
+++ b/media/libjxl/src/lib/jxl/enc_cluster.cc
@@ -26,12 +26,18 @@ HWY_BEFORE_NAMESPACE();
namespace jxl {
namespace HWY_NAMESPACE {
+// These templates are not found via ADL.
+using hwy::HWY_NAMESPACE::Eq;
+using hwy::HWY_NAMESPACE::IfThenZeroElse;
+
template <class V>
V Entropy(V count, V inv_total, V total) {
const HWY_CAPPED(float, Histogram::kRounding) d;
const auto zero = Set(d, 0.0f);
- return IfThenZeroElse(count == total,
- zero - count * FastLog2f(d, inv_total * count));
+ // TODO(eustas): why (0 - x) instead of Neg(x)?
+ return IfThenZeroElse(
+ Eq(count, total),
+ Sub(zero, Mul(count, FastLog2f(d, Mul(inv_total, count)))));
}
void HistogramEntropy(const Histogram& a) {
@@ -47,7 +53,8 @@ void HistogramEntropy(const Histogram& a) {
for (size_t i = 0; i < a.data_.size(); i += Lanes(di)) {
const auto counts = LoadU(di, &a.data_[i]);
- entropy_lanes += Entropy(ConvertTo(df, counts), inv_tot, total);
+ entropy_lanes =
+ Add(entropy_lanes, Entropy(ConvertTo(df, counts), inv_tot, total));
}
a.entropy_ += GetLane(SumOfLanes(df, entropy_lanes));
}
@@ -68,8 +75,8 @@ float HistogramDistance(const Histogram& a, const Histogram& b) {
a.data_.size() > i ? LoadU(di, &a.data_[i]) : Zero(di);
const auto b_counts =
b.data_.size() > i ? LoadU(di, &b.data_[i]) : Zero(di);
- const auto counts = ConvertTo(df, a_counts + b_counts);
- distance_lanes += Entropy(counts, inv_tot, total);
+ const auto counts = ConvertTo(df, Add(a_counts, b_counts));
+ distance_lanes = Add(distance_lanes, Entropy(counts, inv_tot, total));
}
const float total_distance = GetLane(SumOfLanes(df, distance_lanes));
return total_distance - a.entropy_ - b.entropy_;
@@ -77,60 +84,44 @@ float HistogramDistance(const Histogram& a, const Histogram& b) {
// First step of a k-means clustering with a fancy distance metric.
void FastClusterHistograms(const std::vector<Histogram>& in,
- const size_t num_contexts_in, size_t max_histograms,
- float min_distance, std::vector<Histogram>* out,
+ size_t max_histograms, std::vector<Histogram>* out,
std::vector<uint32_t>* histogram_symbols) {
PROFILER_FUNC;
+ out->clear();
+ out->reserve(max_histograms);
+ histogram_symbols->clear();
+ histogram_symbols->resize(in.size(), max_histograms);
+
+ std::vector<float> dists(in.size(), std::numeric_limits<float>::max());
size_t largest_idx = 0;
- std::vector<uint32_t> nonempty_histograms;
- nonempty_histograms.reserve(in.size());
- for (size_t i = 0; i < num_contexts_in; i++) {
- if (in[i].total_count_ == 0) continue;
+ for (size_t i = 0; i < in.size(); i++) {
+ if (in[i].total_count_ == 0) {
+ (*histogram_symbols)[i] = 0;
+ dists[i] = 0.0f;
+ continue;
+ }
HistogramEntropy(in[i]);
if (in[i].total_count_ > in[largest_idx].total_count_) {
largest_idx = i;
}
- nonempty_histograms.push_back(i);
- }
- // No symbols.
- if (nonempty_histograms.empty()) {
- out->resize(1);
- histogram_symbols->clear();
- histogram_symbols->resize(in.size(), 0);
- return;
}
- largest_idx = std::find(nonempty_histograms.begin(),
- nonempty_histograms.end(), largest_idx) -
- nonempty_histograms.begin();
- size_t num_contexts = nonempty_histograms.size();
- out->clear();
- out->reserve(max_histograms);
- std::vector<float> dists(num_contexts, std::numeric_limits<float>::max());
- histogram_symbols->clear();
- histogram_symbols->resize(in.size(), max_histograms);
- while (out->size() < max_histograms && out->size() < num_contexts) {
- (*histogram_symbols)[nonempty_histograms[largest_idx]] = out->size();
- out->push_back(in[nonempty_histograms[largest_idx]]);
+ constexpr float kMinDistanceForDistinct = 48.0f;
+ while (out->size() < max_histograms) {
+ (*histogram_symbols)[largest_idx] = out->size();
+ out->push_back(in[largest_idx]);
+ dists[largest_idx] = 0.0f;
largest_idx = 0;
- for (size_t i = 0; i < num_contexts; i++) {
- dists[i] = std::min(
- HistogramDistance(in[nonempty_histograms[i]], out->back()), dists[i]);
- // Avoid repeating histograms
- if ((*histogram_symbols)[nonempty_histograms[i]] != max_histograms) {
- continue;
- }
+ for (size_t i = 0; i < in.size(); i++) {
+ if (dists[i] == 0.0f) continue;
+ dists[i] = std::min(HistogramDistance(in[i], out->back()), dists[i]);
if (dists[i] > dists[largest_idx]) largest_idx = i;
}
- if (dists[largest_idx] < min_distance) break;
+ if (dists[largest_idx] < kMinDistanceForDistinct) break;
}
- for (size_t i = 0; i < num_contexts_in; i++) {
+ for (size_t i = 0; i < in.size(); i++) {
if ((*histogram_symbols)[i] != max_histograms) continue;
- if (in[i].total_count_ == 0) {
- (*histogram_symbols)[i] = 0;
- continue;
- }
size_t best = 0;
float best_dist = HistogramDistance(in[i], (*out)[best]);
for (size_t j = 1; j < out->size(); j++) {
@@ -191,25 +182,19 @@ void HistogramReindex(std::vector<Histogram>* out,
// placed in 'out', and for each index in 'in', *histogram_symbols will
// indicate which of the 'out' histograms is the best approximation.
void ClusterHistograms(const HistogramParams params,
- const std::vector<Histogram>& in,
- const size_t num_contexts, size_t max_histograms,
+ const std::vector<Histogram>& in, size_t max_histograms,
std::vector<Histogram>* out,
std::vector<uint32_t>* histogram_symbols) {
- constexpr float kMinDistanceForDistinctFast = 64.0f;
- constexpr float kMinDistanceForDistinctBest = 16.0f;
max_histograms = std::min(max_histograms, params.max_histograms);
+ max_histograms = std::min(max_histograms, in.size());
if (params.clustering == HistogramParams::ClusteringType::kFastest) {
- HWY_DYNAMIC_DISPATCH(FastClusterHistograms)
- (in, num_contexts, 4, kMinDistanceForDistinctFast, out, histogram_symbols);
- } else if (params.clustering == HistogramParams::ClusteringType::kFast) {
- HWY_DYNAMIC_DISPATCH(FastClusterHistograms)
- (in, num_contexts, max_histograms, kMinDistanceForDistinctFast, out,
- histogram_symbols);
- } else {
- PROFILER_FUNC;
- HWY_DYNAMIC_DISPATCH(FastClusterHistograms)
- (in, num_contexts, max_histograms, kMinDistanceForDistinctBest, out,
- histogram_symbols);
+ max_histograms = std::min(max_histograms, static_cast<size_t>(4));
+ }
+
+ HWY_DYNAMIC_DISPATCH(FastClusterHistograms)
+ (in, max_histograms, out, histogram_symbols);
+
+ if (params.clustering == HistogramParams::ClusteringType::kBest) {
for (size_t i = 0; i < out->size(); i++) {
(*out)[i].entropy_ =
ANSPopulationCost((*out)[i].data_.data(), (*out)[i].data_.size());
diff --git a/media/libjxl/src/lib/jxl/enc_cluster.h b/media/libjxl/src/lib/jxl/enc_cluster.h
index 622a567950..a06783fcca 100644
--- a/media/libjxl/src/lib/jxl/enc_cluster.h
+++ b/media/libjxl/src/lib/jxl/enc_cluster.h
@@ -53,8 +53,7 @@ struct Histogram {
};
void ClusterHistograms(HistogramParams params, const std::vector<Histogram>& in,
- size_t num_contexts, size_t max_histograms,
- std::vector<Histogram>* out,
+ size_t max_histograms, std::vector<Histogram>* out,
std::vector<uint32_t>* histogram_symbols);
} // namespace jxl
diff --git a/media/libjxl/src/lib/jxl/enc_coeff_order.cc b/media/libjxl/src/lib/jxl/enc_coeff_order.cc
index c1ec320d98..8d75cc0383 100644
--- a/media/libjxl/src/lib/jxl/enc_coeff_order.cc
+++ b/media/libjxl/src/lib/jxl/enc_coeff_order.cc
@@ -73,7 +73,8 @@ void ComputeCoeffOrder(SpeedTier speed, const ACImage& acs,
if (used_orders != 0) {
uint64_t threshold =
(std::numeric_limits<uint64_t>::max() >> 32) * block_fraction;
- uint64_t s[2] = {0x94D049BB133111EBull, 0xBF58476D1CE4E5B9ull};
+ uint64_t s[2] = {static_cast<uint64_t>(0x94D049BB133111EBull),
+ static_cast<uint64_t>(0xBF58476D1CE4E5B9ull)};
// Xorshift128+ adapted from xorshift128+-inl.h
auto use_sample = [&]() {
auto s1 = s[0];
diff --git a/media/libjxl/src/lib/jxl/enc_color_management.cc b/media/libjxl/src/lib/jxl/enc_color_management.cc
index 0a7e21f3ba..0b031d2dcd 100644
--- a/media/libjxl/src/lib/jxl/enc_color_management.cc
+++ b/media/libjxl/src/lib/jxl/enc_color_management.cc
@@ -105,7 +105,8 @@ Status BeforeTransform(JxlCms* t, const float* buf_src, float* xform_src,
: 10000.f / t->intensity_target);
for (size_t i = 0; i < buf_size; i += Lanes(df)) {
const auto val = Load(df, buf_src + i);
- const auto result = multiplier * TF_PQ().DisplayFromEncoded(df, val);
+ const auto result =
+ Mul(multiplier, TF_PQ().DisplayFromEncoded(df, val));
Store(result, df, xform_src + i);
}
#if JXL_CMS_VERBOSE >= 2
@@ -162,7 +163,8 @@ Status AfterTransform(JxlCms* t, float* JXL_RESTRICT buf_dst, size_t buf_size) {
: t->intensity_target * 1e-4f);
for (size_t i = 0; i < buf_size; i += Lanes(df)) {
const auto val = Load(df, buf_dst + i);
- const auto result = TF_PQ().EncodedFromDisplay(df, multiplier * val);
+ const auto result =
+ TF_PQ().EncodedFromDisplay(df, Mul(multiplier, val));
Store(result, df, buf_dst + i);
}
#if JXL_CMS_VERBOSE >= 2
@@ -613,8 +615,15 @@ Status ProfileEquivalentToICC(const cmsContext context, const Profile& profile1,
JXL_MUST_USE_RESULT cmsCIEXYZ UnadaptedWhitePoint(const cmsContext context,
const Profile& profile,
const ColorEncoding& c) {
- cmsCIEXYZ XYZ = {1.0, 1.0, 1.0};
+ const cmsCIEXYZ* white_point = static_cast<const cmsCIEXYZ*>(
+ cmsReadTag(profile.get(), cmsSigMediaWhitePointTag));
+ if (white_point != nullptr &&
+ cmsReadTag(profile.get(), cmsSigChromaticAdaptationTag) == nullptr) {
+ // No chromatic adaptation matrix: the white point is already unadapted.
+ return *white_point;
+ }
+ cmsCIEXYZ XYZ = {1.0, 1.0, 1.0};
Profile profile_xyz;
if (!CreateProfileXYZ(context, &profile_xyz)) return XYZ;
// Array arguments are one per profile.
@@ -637,8 +646,8 @@ JXL_MUST_USE_RESULT cmsCIEXYZ UnadaptedWhitePoint(const cmsContext context,
return XYZ;
}
-Status IdentifyPrimaries(const Profile& profile, const cmsCIEXYZ& wp_unadapted,
- ColorEncoding* c) {
+Status IdentifyPrimaries(const cmsContext context, const Profile& profile,
+ const cmsCIEXYZ& wp_unadapted, ColorEncoding* c) {
if (!c->HasPrimaries()) return true;
if (ColorSpaceFromProfile(profile) == ColorSpace::kUnknown) return true;
@@ -649,8 +658,34 @@ Status IdentifyPrimaries(const Profile& profile, const cmsCIEXYZ& wp_unadapted,
cmsReadTag(profile.get(), cmsSigGreenColorantTag));
const cmsCIEXYZ* adapted_b = static_cast<const cmsCIEXYZ*>(
cmsReadTag(profile.get(), cmsSigBlueColorantTag));
+
+ cmsCIEXYZ converted_rgb[3];
if (adapted_r == nullptr || adapted_g == nullptr || adapted_b == nullptr) {
- return JXL_FAILURE("Failed to retrieve colorants");
+ // No colorant tag, determine the XYZ coordinates of the primaries by
+ // converting from the colorspace.
+ Profile profile_xyz;
+ if (!CreateProfileXYZ(context, &profile_xyz)) {
+ return JXL_FAILURE("Failed to retrieve colorants");
+ }
+ // Array arguments are one per profile.
+ cmsHPROFILE profiles[2] = {profile.get(), profile_xyz.get()};
+ cmsUInt32Number intents[2] = {INTENT_RELATIVE_COLORIMETRIC,
+ INTENT_RELATIVE_COLORIMETRIC};
+ cmsBool black_compensation[2] = {0, 0};
+ cmsFloat64Number adaption[2] = {0.0, 0.0};
+ // Only transforming three pixels, so skip expensive optimizations.
+ cmsUInt32Number flags = cmsFLAGS_NOOPTIMIZE | cmsFLAGS_HIGHRESPRECALC;
+ Transform xform(cmsCreateExtendedTransform(
+ context, 2, profiles, black_compensation, intents, adaption, nullptr, 0,
+ Type64(*c), TYPE_XYZ_DBL, flags));
+ if (!xform) return JXL_FAILURE("Failed to retrieve colorants");
+
+ const cmsFloat64Number in[9] = {1.0, 0.0, 0.0, 0.0, 1.0,
+ 0.0, 0.0, 0.0, 1.0};
+ cmsDoTransform(xform.get(), in, &converted_rgb->X, 3);
+ adapted_r = &converted_rgb[0];
+ adapted_g = &converted_rgb[1];
+ adapted_b = &converted_rgb[2];
}
// TODO(janwas): no longer assume Bradford and D50.
@@ -869,7 +904,7 @@ Status ColorEncoding::SetFieldsFromICC() {
JXL_RETURN_IF_ERROR(SetWhitePoint(CIExyFromXYZ(wp_unadapted)));
// Relies on color_space.
- JXL_RETURN_IF_ERROR(IdentifyPrimaries(profile, wp_unadapted, this));
+ JXL_RETURN_IF_ERROR(IdentifyPrimaries(context, profile, wp_unadapted, this));
// Relies on color_space/white point/primaries being set already.
DetectTransferFunction(context, profile, this);
diff --git a/media/libjxl/src/lib/jxl/enc_context_map.cc b/media/libjxl/src/lib/jxl/enc_context_map.cc
index 4719a254a9..82e5e61813 100644
--- a/media/libjxl/src/lib/jxl/enc_context_map.cc
+++ b/media/libjxl/src/lib/jxl/enc_context_map.cc
@@ -56,7 +56,8 @@ std::vector<uint8_t> MoveToFrontTransform(const std::vector<uint8_t>& v) {
} // namespace
void EncodeContextMap(const std::vector<uint8_t>& context_map,
- size_t num_histograms, BitWriter* writer) {
+ size_t num_histograms, BitWriter* writer, size_t layer,
+ AuxOut* aux_out) {
if (num_histograms == 1) {
// Simple code
writer->Write(1, 1);
@@ -100,7 +101,7 @@ void EncodeContextMap(const std::vector<uint8_t>& context_map,
writer->Write(1, 0);
writer->Write(1, use_mtf); // Use/don't use MTF.
BuildAndEncodeHistograms(params, 1, tokens, &codes, &dummy_context_map,
- writer, 0, nullptr);
+ writer, layer, aux_out);
WriteTokens(tokens[0], codes, dummy_context_map, writer);
}
}
@@ -132,7 +133,7 @@ void EncodeBlockCtxMap(const BlockCtxMap& block_ctx_map, BitWriter* writer,
for (uint32_t i : qft) {
JXL_CHECK(U32Coder::Write(kQFThresholdDist, i - 1, writer));
}
- EncodeContextMap(ctx_map, block_ctx_map.num_ctxs, writer);
+ EncodeContextMap(ctx_map, block_ctx_map.num_ctxs, writer, kLayerAC, aux_out);
ReclaimAndCharge(writer, &allotment, kLayerAC, aux_out);
}
diff --git a/media/libjxl/src/lib/jxl/enc_context_map.h b/media/libjxl/src/lib/jxl/enc_context_map.h
index 7f6c624380..57e79a173e 100644
--- a/media/libjxl/src/lib/jxl/enc_context_map.h
+++ b/media/libjxl/src/lib/jxl/enc_context_map.h
@@ -24,7 +24,8 @@ static const size_t kClustersLimit = 128;
// Encodes the given context map to the bit stream. The number of different
// histogram ids is given by num_histograms.
void EncodeContextMap(const std::vector<uint8_t>& context_map,
- size_t num_histograms, BitWriter* writer);
+ size_t num_histograms, BitWriter* writer, size_t layer,
+ AuxOut* aux_out);
void EncodeBlockCtxMap(const BlockCtxMap& block_ctx_map, BitWriter* writer,
AuxOut* aux_out);
diff --git a/media/libjxl/src/lib/jxl/enc_detect_dots.cc b/media/libjxl/src/lib/jxl/enc_detect_dots.cc
index 33b278e400..f7021d6cce 100644
--- a/media/libjxl/src/lib/jxl/enc_detect_dots.cc
+++ b/media/libjxl/src/lib/jxl/enc_detect_dots.cc
@@ -45,6 +45,11 @@ HWY_BEFORE_NAMESPACE();
namespace jxl {
namespace HWY_NAMESPACE {
+// These templates are not found via ADL.
+using hwy::HWY_NAMESPACE::Add;
+using hwy::HWY_NAMESPACE::Mul;
+using hwy::HWY_NAMESPACE::Sub;
+
ImageF SumOfSquareDifferences(const Image3F& forig, const Image3F& smooth,
ThreadPool* pool) {
const HWY_FULL(float) d;
@@ -66,16 +71,14 @@ ImageF SumOfSquareDifferences(const Image3F& forig, const Image3F& smooth,
float* JXL_RESTRICT sos_row = sum_of_squares.Row(y);
for (size_t x = 0; x < forig.xsize(); x += Lanes(d)) {
- auto v0 = Load(d, orig_row0 + x) - Load(d, smooth_row0 + x);
- auto v1 = Load(d, orig_row1 + x) - Load(d, smooth_row1 + x);
- auto v2 = Load(d, orig_row2 + x) - Load(d, smooth_row2 + x);
- v0 *= v0;
- v1 *= v1;
- v2 *= v2;
- v0 *= color_coef0; // FMA doesn't help here.
- v1 *= color_coef1;
- v2 *= color_coef2;
- const auto sos = v0 + v1 + v2; // weighted sum of square diffs
+ auto v0 = Sub(Load(d, orig_row0 + x), Load(d, smooth_row0 + x));
+ auto v1 = Sub(Load(d, orig_row1 + x), Load(d, smooth_row1 + x));
+ auto v2 = Sub(Load(d, orig_row2 + x), Load(d, smooth_row2 + x));
+ v0 = Mul(Mul(v0, v0), color_coef0);
+ v1 = Mul(Mul(v1, v1), color_coef1);
+ v2 = Mul(Mul(v2, v2), color_coef2);
+ const auto sos =
+ Add(v0, Add(v1, v2)); // weighted sum of square diffs
Store(sos, d, sos_row + x);
}
},
@@ -151,16 +154,18 @@ ImageF ComputeEnergyImage(const Image3F& orig, Image3F* smooth,
// Prepare guidance images for dot selection.
Image3F forig(orig.xsize(), orig.ysize());
- Image3F tmp(orig.xsize(), orig.ysize());
*smooth = Image3F(orig.xsize(), orig.ysize());
+ Rect rect(orig);
const auto& weights1 = WeightsSeparable5Gaussian0_65();
const auto& weights3 = WeightsSeparable5Gaussian3();
- Separable5_3(orig, Rect(orig), weights1, pool, &forig);
-
- Separable5_3(orig, Rect(orig), weights3, pool, &tmp);
- Separable5_3(tmp, Rect(tmp), weights3, pool, smooth);
+ for (size_t c = 0; c < 3; ++c) {
+ // Use forig as temporary storage to reduce memory and keep it warmer.
+ Separable5(orig.Plane(c), rect, weights3, pool, &forig.Plane(c));
+ Separable5(forig.Plane(c), rect, weights3, pool, &smooth->Plane(c));
+ Separable5(orig.Plane(c), rect, weights1, pool, &forig.Plane(c));
+ }
#if JXL_DEBUG_DOT_DETECT
AuxOut aux;
diff --git a/media/libjxl/src/lib/jxl/enc_entropy_coder.cc b/media/libjxl/src/lib/jxl/enc_entropy_coder.cc
index 07fe5a05b2..c634445e83 100644
--- a/media/libjxl/src/lib/jxl/enc_entropy_coder.cc
+++ b/media/libjxl/src/lib/jxl/enc_entropy_coder.cc
@@ -38,6 +38,12 @@ HWY_BEFORE_NAMESPACE();
namespace jxl {
namespace HWY_NAMESPACE {
+// These templates are not found via ADL.
+using hwy::HWY_NAMESPACE::Add;
+using hwy::HWY_NAMESPACE::AndNot;
+using hwy::HWY_NAMESPACE::Eq;
+using hwy::HWY_NAMESPACE::GetLane;
+
// Returns number of non-zero coefficients (but skip LLF).
// We cannot rely on block[] being all-zero bits, so first truncate to integer.
// Also writes the per-8x8 block nzeros starting at nzeros_pos.
@@ -71,7 +77,7 @@ int32_t NumNonZeroExceptLLF(const size_t cx, const size_t cy,
const auto coef =
AndNot(llf_mask, Load(di, &block[y * cx * kBlockDim + x]));
- neg_sum_zero += VecFromMask(di, coef == zero);
+ neg_sum_zero = Add(neg_sum_zero, VecFromMask(di, Eq(coef, zero)));
}
}
}
@@ -80,7 +86,7 @@ int32_t NumNonZeroExceptLLF(const size_t cx, const size_t cy,
for (size_t y = cy; y < cy * kBlockDim; y++) {
for (size_t x = 0; x < cx * kBlockDim; x += Lanes(di)) {
const auto coef = Load(di, &block[y * cx * kBlockDim + x]);
- neg_sum_zero += VecFromMask(di, coef == zero);
+ neg_sum_zero = Add(neg_sum_zero, VecFromMask(di, Eq(coef, zero)));
}
}
@@ -121,7 +127,7 @@ int32_t NumNonZero8x8ExceptDC(const int32_t* JXL_RESTRICT block,
// DC counts as zero so we don't include it in nzeros.
const auto coef = AndNot(dc_mask, Load(di, &block[y * kBlockDim + x]));
- neg_sum_zero += VecFromMask(di, coef == zero);
+ neg_sum_zero = Add(neg_sum_zero, VecFromMask(di, Eq(coef, zero)));
}
}
@@ -129,7 +135,7 @@ int32_t NumNonZero8x8ExceptDC(const int32_t* JXL_RESTRICT block,
for (size_t y = 1; y < kBlockDim; y++) {
for (size_t x = 0; x < kBlockDim; x += Lanes(di)) {
const auto coef = Load(di, &block[y * kBlockDim + x]);
- neg_sum_zero += VecFromMask(di, coef == zero);
+ neg_sum_zero = Add(neg_sum_zero, VecFromMask(di, Eq(coef, zero)));
}
}
diff --git a/media/libjxl/src/lib/jxl/enc_external_image.cc b/media/libjxl/src/lib/jxl/enc_external_image.cc
index f283733481..346182b3bf 100644
--- a/media/libjxl/src/lib/jxl/enc_external_image.cc
+++ b/media/libjxl/src/lib/jxl/enc_external_image.cc
@@ -197,8 +197,8 @@ Status ConvertFromExternal(Span<const uint8_t> bytes, size_t xsize,
size_t ysize, const ColorEncoding& c_current,
size_t channels, bool alpha_is_premultiplied,
size_t bits_per_sample, JxlEndianness endianness,
- bool flipped_y, ThreadPool* pool, ImageBundle* ib,
- bool float_in, size_t align) {
+ ThreadPool* pool, ImageBundle* ib, bool float_in,
+ size_t align) {
JXL_CHECK(float_in ? bits_per_sample == 16 || bits_per_sample == 32
: bits_per_sample > 0 && bits_per_sample <= 16);
@@ -243,16 +243,12 @@ Status ConvertFromExternal(Span<const uint8_t> bytes, size_t xsize,
Image3F color(xsize, ysize);
- const auto get_y = [flipped_y, ysize](const size_t y) {
- return flipped_y ? ysize - 1 - y : y;
- };
-
if (float_in) {
for (size_t c = 0; c < color_channels; ++c) {
JXL_RETURN_IF_ERROR(RunOnPool(
pool, 0, static_cast<uint32_t>(ysize), ThreadPool::NoInit,
[&](const uint32_t task, size_t /*thread*/) {
- const size_t y = get_y(task);
+ const size_t y = task;
size_t i =
row_size * task + (c * bits_per_sample / jxl::kBitsPerByte);
float* JXL_RESTRICT row_out = color.PlaneRow(c, y);
@@ -291,7 +287,7 @@ Status ConvertFromExternal(Span<const uint8_t> bytes, size_t xsize,
JXL_RETURN_IF_ERROR(RunOnPool(
pool, 0, static_cast<uint32_t>(ysize), ThreadPool::NoInit,
[&](const uint32_t task, size_t /*thread*/) {
- const size_t y = get_y(task);
+ const size_t y = task;
size_t i = row_size * task + c * bytes_per_channel;
float* JXL_RESTRICT row_out = color.PlaneRow(c, y);
if (bits_per_sample <= 8) {
@@ -326,7 +322,7 @@ Status ConvertFromExternal(Span<const uint8_t> bytes, size_t xsize,
JXL_RETURN_IF_ERROR(RunOnPool(
pool, 0, static_cast<uint32_t>(ysize), ThreadPool::NoInit,
[&](const uint32_t task, size_t /*thread*/) {
- const size_t y = get_y(task);
+ const size_t y = task;
size_t i = row_size * task +
((channels - 1) * bits_per_sample / jxl::kBitsPerByte);
float* JXL_RESTRICT row_out = alpha.Row(y);
@@ -362,7 +358,7 @@ Status ConvertFromExternal(Span<const uint8_t> bytes, size_t xsize,
JXL_RETURN_IF_ERROR(RunOnPool(
pool, 0, static_cast<uint32_t>(ysize), ThreadPool::NoInit,
[&](const uint32_t task, size_t /*thread*/) {
- const size_t y = get_y(task);
+ const size_t y = task;
size_t i = row_size * task + (channels - 1) * bytes_per_channel;
float* JXL_RESTRICT row_out = alpha.Row(y);
if (bits_per_sample <= 8) {
@@ -422,8 +418,8 @@ Status BufferToImageBundle(const JxlPixelFormat& pixel_format, uint32_t xsize,
JXL_RETURN_IF_ERROR(ConvertFromExternal(
jxl::Span<const uint8_t>(static_cast<const uint8_t*>(buffer), size),
xsize, ysize, c_current, pixel_format.num_channels,
- /*alpha_is_premultiplied=*/false, bitdepth, pixel_format.endianness,
- /*flipped_y=*/false, pool, ib, float_in, pixel_format.align));
+ /*alpha_is_premultiplied=*/false, bitdepth, pixel_format.endianness, pool,
+ ib, float_in, pixel_format.align));
ib->VerifyMetadata();
return true;
diff --git a/media/libjxl/src/lib/jxl/enc_external_image.h b/media/libjxl/src/lib/jxl/enc_external_image.h
index 904babeb54..73b7175a94 100644
--- a/media/libjxl/src/lib/jxl/enc_external_image.h
+++ b/media/libjxl/src/lib/jxl/enc_external_image.h
@@ -32,8 +32,8 @@ Status ConvertFromExternal(Span<const uint8_t> bytes, size_t xsize,
size_t ysize, const ColorEncoding& c_current,
size_t channels, bool alpha_is_premultiplied,
size_t bits_per_sample, JxlEndianness endianness,
- bool flipped_y, ThreadPool* pool, ImageBundle* ib,
- bool float_in, size_t align);
+ ThreadPool* pool, ImageBundle* ib, bool float_in,
+ size_t align);
Status BufferToImageF(const JxlPixelFormat& pixel_format, size_t xsize,
size_t ysize, const void* buffer, size_t size,
ThreadPool* pool, ImageF* channel);
diff --git a/media/libjxl/src/lib/jxl/enc_external_image_gbench.cc b/media/libjxl/src/lib/jxl/enc_external_image_gbench.cc
index 8381984742..a123d4b356 100644
--- a/media/libjxl/src/lib/jxl/enc_external_image_gbench.cc
+++ b/media/libjxl/src/lib/jxl/enc_external_image_gbench.cc
@@ -31,7 +31,6 @@ void BM_EncExternalImage_ConvertImageRGBA(benchmark::State& state) {
/*channels=*/4,
/*alpha_is_premultiplied=*/false,
/*bits_per_sample=*/8, JXL_NATIVE_ENDIAN,
- /*flipped_y=*/false,
/*pool=*/nullptr, &ib, /*float_in=*/false, /*align=*/0));
}
}
diff --git a/media/libjxl/src/lib/jxl/enc_external_image_test.cc b/media/libjxl/src/lib/jxl/enc_external_image_test.cc
index baa9267712..2c5fa5acac 100644
--- a/media/libjxl/src/lib/jxl/enc_external_image_test.cc
+++ b/media/libjxl/src/lib/jxl/enc_external_image_test.cc
@@ -30,18 +30,18 @@ TEST(ExternalImageTest, InvalidSize) {
Span<const uint8_t>(buf, 10), /*xsize=*/10, /*ysize=*/100,
/*c_current=*/ColorEncoding::SRGB(), /*channels=*/4,
/*alpha_is_premultiplied=*/false, /*bits_per_sample=*/16, JXL_BIG_ENDIAN,
- /*flipped_y=*/false, nullptr, &ib, /*float_in=*/false, /*align=*/0));
+ nullptr, &ib, /*float_in=*/false, /*align=*/0));
EXPECT_FALSE(ConvertFromExternal(
Span<const uint8_t>(buf, sizeof(buf) - 1), /*xsize=*/10, /*ysize=*/100,
/*c_current=*/ColorEncoding::SRGB(), /*channels=*/4,
/*alpha_is_premultiplied=*/false, /*bits_per_sample=*/16, JXL_BIG_ENDIAN,
- /*flipped_y=*/false, nullptr, &ib, /*float_in=*/false, /*align=*/0));
- EXPECT_TRUE(ConvertFromExternal(
- Span<const uint8_t>(buf, sizeof(buf)), /*xsize=*/10,
- /*ysize=*/100, /*c_current=*/ColorEncoding::SRGB(),
- /*channels=*/4, /*alpha_is_premultiplied=*/false,
- /*bits_per_sample=*/16, JXL_BIG_ENDIAN,
- /*flipped_y=*/false, nullptr, &ib, /*float_in=*/false, /*align=*/0));
+ nullptr, &ib, /*float_in=*/false, /*align=*/0));
+ EXPECT_TRUE(
+ ConvertFromExternal(Span<const uint8_t>(buf, sizeof(buf)), /*xsize=*/10,
+ /*ysize=*/100, /*c_current=*/ColorEncoding::SRGB(),
+ /*channels=*/4, /*alpha_is_premultiplied=*/false,
+ /*bits_per_sample=*/16, JXL_BIG_ENDIAN, nullptr, &ib,
+ /*float_in=*/false, /*align=*/0));
}
#endif
@@ -56,12 +56,12 @@ TEST(ExternalImageTest, AlphaMissing) {
// has_alpha is true but the ImageBundle has no alpha. Alpha channel should
// be ignored.
- EXPECT_TRUE(ConvertFromExternal(
- Span<const uint8_t>(buf, sizeof(buf)), xsize, ysize,
- /*c_current=*/ColorEncoding::SRGB(),
- /*channels=*/4, /*alpha_is_premultiplied=*/false,
- /*bits_per_sample=*/8, JXL_BIG_ENDIAN,
- /*flipped_y=*/false, nullptr, &ib, /*float_in=*/false, /*align=*/0));
+ EXPECT_TRUE(
+ ConvertFromExternal(Span<const uint8_t>(buf, sizeof(buf)), xsize, ysize,
+ /*c_current=*/ColorEncoding::SRGB(),
+ /*channels=*/4, /*alpha_is_premultiplied=*/false,
+ /*bits_per_sample=*/8, JXL_BIG_ENDIAN, nullptr, &ib,
+ /*float_in=*/false, /*align=*/0));
EXPECT_FALSE(ib.HasAlpha());
}
diff --git a/media/libjxl/src/lib/jxl/enc_fast_heuristics.cc b/media/libjxl/src/lib/jxl/enc_fast_heuristics.cc
deleted file mode 100644
index b9da35967e..0000000000
--- a/media/libjxl/src/lib/jxl/enc_fast_heuristics.cc
+++ /dev/null
@@ -1,361 +0,0 @@
-// Copyright (c) the JPEG XL Project Authors. All rights reserved.
-//
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-#include <stddef.h>
-#include <stdint.h>
-
-#include <algorithm>
-#include <numeric>
-#include <string>
-
-#include "lib/jxl/convolve.h"
-#include "lib/jxl/enc_ac_strategy.h"
-#include "lib/jxl/enc_adaptive_quantization.h"
-#include "lib/jxl/enc_ar_control_field.h"
-#include "lib/jxl/enc_cache.h"
-#include "lib/jxl/enc_heuristics.h"
-#include "lib/jxl/enc_noise.h"
-#include "lib/jxl/gaborish.h"
-#include "lib/jxl/gauss_blur.h"
-
-#undef HWY_TARGET_INCLUDE
-#define HWY_TARGET_INCLUDE "lib/jxl/enc_fast_heuristics.cc"
-#include <hwy/foreach_target.h>
-#include <hwy/highway.h>
-
-HWY_BEFORE_NAMESPACE();
-namespace jxl {
-namespace HWY_NAMESPACE {
-namespace {
-using DF4 = HWY_CAPPED(float, 4);
-DF4 df4;
-HWY_FULL(float) df;
-
-Status Heuristics(PassesEncoderState* enc_state,
- ModularFrameEncoder* modular_frame_encoder,
- const ImageBundle* linear, Image3F* opsin, ThreadPool* pool,
- AuxOut* aux_out) {
- PROFILER_ZONE("JxlLossyFrameHeuristics uninstrumented");
- CompressParams& cparams = enc_state->cparams;
- PassesSharedState& shared = enc_state->shared;
- const FrameDimensions& frame_dim = enc_state->shared.frame_dim;
- JXL_CHECK(cparams.butteraugli_distance > 0);
-
- // TODO(veluca): make this tiled.
- if (shared.frame_header.loop_filter.gab) {
- GaborishInverse(opsin, 0.9908511000000001f, pool);
- }
- // Compute image of high frequencies by removing a blurred version.
- // TODO(veluca): certainly can be made faster, and use less memory...
- constexpr size_t pad = 16;
- Image3F padded = PadImageMirror(*opsin, pad, pad);
- // Make the image (X, Y, B-Y)
- // TODO(veluca): SubtractFrom is not parallel *and* not SIMD-fied.
- SubtractFrom(padded.Plane(1), &padded.Plane(2));
- // Ensure that OOB access for CfL does nothing. Not necessary if doing things
- // properly...
- Image3F hf(padded.xsize() + 64, padded.ysize());
- ZeroFillImage(&hf);
- hf.ShrinkTo(padded.xsize(), padded.ysize());
- ImageF temp(padded.xsize(), padded.ysize());
- // TODO(veluca): consider some faster blurring method.
- auto g = CreateRecursiveGaussian(11.415258091746161);
- for (size_t c = 0; c < 3; c++) {
- FastGaussian(g, padded.Plane(c), pool, &temp, &hf.Plane(c));
- SubtractFrom(padded.Plane(c), &hf.Plane(c));
- }
- // TODO(veluca): DC CfL?
- size_t xcolortiles = DivCeil(frame_dim.xsize_blocks, kColorTileDimInBlocks);
- size_t ycolortiles = DivCeil(frame_dim.ysize_blocks, kColorTileDimInBlocks);
- JXL_RETURN_IF_ERROR(RunOnPool(
- pool, 0, xcolortiles * ycolortiles, ThreadPool::NoInit,
- [&](size_t tile_id, size_t _) {
- size_t tx = tile_id % xcolortiles;
- size_t ty = tile_id / xcolortiles;
- size_t x0 = tx * kColorTileDim;
- size_t x1 = std::min(x0 + kColorTileDim, hf.xsize());
- size_t y0 = ty * kColorTileDim;
- size_t y1 = std::min(y0 + kColorTileDim, hf.ysize());
- for (size_t c : {0, 2}) {
- static constexpr float kInvColorFactor = 1.0f / kDefaultColorFactor;
- auto ca = Zero(df);
- auto cb = Zero(df);
- const auto inv_color_factor = Set(df, kInvColorFactor);
- for (size_t y = y0; y < y1; y++) {
- const float* row_m = hf.PlaneRow(1, y);
- const float* row_s = hf.PlaneRow(c, y);
- for (size_t x = x0; x < x1; x += Lanes(df)) {
- // color residual = ax + b
- const auto a = inv_color_factor * Load(df, row_m + x);
- const auto b = Zero(df) - Load(df, row_s + x);
- ca = MulAdd(a, a, ca);
- cb = MulAdd(a, b, cb);
- }
- }
- float best = -GetLane(SumOfLanes(df, cb)) /
- (GetLane(SumOfLanes(df, ca)) + 1e-9f);
- int8_t& res = (c == 0 ? shared.cmap.ytox_map : shared.cmap.ytob_map)
- .Row(ty)[tx];
- res = std::max(-128.0f, std::min(127.0f, roundf(best)));
- }
- },
- "CfL"));
- Image3F pooled(frame_dim.xsize_padded / 4, frame_dim.ysize_padded / 4);
- Image3F summed(frame_dim.xsize_padded / 4, frame_dim.ysize_padded / 4);
- JXL_RETURN_IF_ERROR(RunOnPool(
- pool, 0, frame_dim.ysize_padded / 4, ThreadPool::NoInit,
- [&](size_t y, size_t _) {
- for (size_t c = 0; c < 3; c++) {
- float* JXL_RESTRICT row_out = pooled.PlaneRow(c, y);
- float* JXL_RESTRICT row_out_avg = summed.PlaneRow(c, y);
- const float* JXL_RESTRICT row_in[4];
- for (size_t iy = 0; iy < 4; iy++) {
- row_in[iy] = hf.PlaneRow(c, 4 * y + pad + iy);
- }
- for (size_t x = 0; x < frame_dim.xsize_padded / 4; x++) {
- auto max = Zero(df4);
- auto sum = Zero(df4);
- for (size_t iy = 0; iy < 4; iy++) {
- for (size_t ix = 0; ix < 4; ix += Lanes(df4)) {
- const auto nn = Abs(Load(df4, row_in[iy] + x * 4 + ix + pad));
- sum += nn;
- max = IfThenElse(max > nn, max, nn);
- }
- }
- row_out_avg[x] = GetLane(SumOfLanes(df4, sum));
- row_out[x] = GetLane(MaxOfLanes(df4, max));
- }
- }
- },
- "MaxPool"));
- // TODO(veluca): better handling of the border
- // TODO(veluca): consider some faster blurring method.
- // TODO(veluca): parallelize.
- // Remove noise from the resulting image.
- auto g2 = CreateRecursiveGaussian(2.0849544429861884);
- constexpr size_t pad2 = 16;
- Image3F summed_pad = PadImageMirror(summed, pad2, pad2);
- ImageF tmp_out(summed_pad.xsize(), summed_pad.ysize());
- ImageF tmp2(summed_pad.xsize(), summed_pad.ysize());
- Image3F pooled_pad = PadImageMirror(pooled, pad2, pad2);
- for (size_t c = 0; c < 3; c++) {
- FastGaussian(g2, summed_pad.Plane(c), pool, &tmp2, &tmp_out);
- const auto unblurred_multiplier = Set(df, 0.5f);
- for (size_t y = 0; y < summed.ysize(); y++) {
- float* row = summed.PlaneRow(c, y);
- const float* row_blur = tmp_out.Row(y + pad2);
- for (size_t x = 0; x < summed.xsize(); x += Lanes(df)) {
- const auto b = Load(df, row_blur + x + pad2);
- const auto o = Load(df, row + x) * unblurred_multiplier;
- const auto m = IfThenElse(b > o, b, o);
- Store(m, df, row + x);
- }
- }
- }
- for (size_t c = 0; c < 3; c++) {
- FastGaussian(g2, pooled_pad.Plane(c), pool, &tmp2, &tmp_out);
- const auto unblurred_multiplier = Set(df, 0.5f);
- for (size_t y = 0; y < pooled.ysize(); y++) {
- float* row = pooled.PlaneRow(c, y);
- const float* row_blur = tmp_out.Row(y + pad2);
- for (size_t x = 0; x < pooled.xsize(); x += Lanes(df)) {
- const auto b = Load(df, row_blur + x + pad2);
- const auto o = Load(df, row + x) * unblurred_multiplier;
- const auto m = IfThenElse(b > o, b, o);
- Store(m, df, row + x);
- }
- }
- }
- const static float kChannelMul[3] = {
- 7.9644294909680253f,
- 0.5700000183257159f,
- 0.20267448837597055f,
- };
- ImageF pooledhf44(pooled.xsize(), pooled.ysize());
- for (size_t y = 0; y < pooled.ysize(); y++) {
- const float* row_in_x = pooled.ConstPlaneRow(0, y);
- const float* row_in_y = pooled.ConstPlaneRow(1, y);
- const float* row_in_b = pooled.ConstPlaneRow(2, y);
- float* row_out = pooledhf44.Row(y);
- for (size_t x = 0; x < pooled.xsize(); x += Lanes(df)) {
- auto v = Set(df, kChannelMul[0]) * Load(df, row_in_x + x);
- v = MulAdd(Set(df, kChannelMul[1]), Load(df, row_in_y + x), v);
- v = MulAdd(Set(df, kChannelMul[2]), Load(df, row_in_b + x), v);
- Store(v, df, row_out + x);
- }
- }
- ImageF summedhf44(summed.xsize(), summed.ysize());
- for (size_t y = 0; y < summed.ysize(); y++) {
- const float* row_in_x = summed.ConstPlaneRow(0, y);
- const float* row_in_y = summed.ConstPlaneRow(1, y);
- const float* row_in_b = summed.ConstPlaneRow(2, y);
- float* row_out = summedhf44.Row(y);
- for (size_t x = 0; x < summed.xsize(); x += Lanes(df)) {
- auto v = Set(df, kChannelMul[0]) * Load(df, row_in_x + x);
- v = MulAdd(Set(df, kChannelMul[1]), Load(df, row_in_y + x), v);
- v = MulAdd(Set(df, kChannelMul[2]), Load(df, row_in_b + x), v);
- Store(v, df, row_out + x);
- }
- }
- aux_out->DumpPlaneNormalized("pooledhf44", pooledhf44);
- aux_out->DumpPlaneNormalized("summedhf44", summedhf44);
-
- static const float kDcQuantMul = 0.88170190420916206;
- static const float kAcQuantMul = 2.5165738934721524;
-
- float dc_quant = kDcQuantMul * InitialQuantDC(cparams.butteraugli_distance);
- float ac_quant_base = kAcQuantMul / cparams.butteraugli_distance;
- ImageF quant_field(frame_dim.xsize_blocks, frame_dim.ysize_blocks);
-
- static_assert(kColorTileDim == 64, "Fix the code below");
- auto mmacs = [&](size_t bx, size_t by, AcStrategy acs, float& min,
- float& max) {
- min = 1e10;
- max = 0;
- for (size_t y = 2 * by; y < 2 * (by + acs.covered_blocks_y()); y++) {
- const float* row = summedhf44.Row(y);
- for (size_t x = 2 * bx; x < 2 * (bx + acs.covered_blocks_x()); x++) {
- min = std::min(min, row[x]);
- max = std::max(max, row[x]);
- }
- }
- };
- // Multipliers for allowed range of summedhf44.
- std::pair<AcStrategy::Type, float> candidates[] = {
- // The order is such that, in case of ties, 8x8 is favoured over 4x4 which
- // is favoured over 2x2. Similarly, we prefer square transforms over
- // same-area rectangular ones.
- {AcStrategy::Type::DCT2X2, 1.5f},
- {AcStrategy::Type::DCT4X4, 1.4f},
- {AcStrategy::Type::DCT4X8, 1.2f},
- {AcStrategy::Type::DCT8X4, 1.2f},
- {AcStrategy::Type::AFV0,
- 1.15f}, // doesn't really work with these heuristics
- {AcStrategy::Type::AFV1, 1.15f},
- {AcStrategy::Type::AFV2, 1.15f},
- {AcStrategy::Type::AFV3, 1.15f},
- {AcStrategy::Type::DCT, 1.0f},
- {AcStrategy::Type::DCT16X8, 0.8f},
- {AcStrategy::Type::DCT8X16, 0.8f},
- {AcStrategy::Type::DCT16X16, 0.2f},
- {AcStrategy::Type::DCT16X32, 0.2f},
- {AcStrategy::Type::DCT32X16, 0.2f},
- {AcStrategy::Type::DCT32X32, 0.2f},
- {AcStrategy::Type::DCT32X64, 0.1f},
- {AcStrategy::Type::DCT64X32, 0.1f},
- {AcStrategy::Type::DCT64X64, 0.04f},
-
-#if 0
- {AcStrategy::Type::DCT2X2, 1e+10}, {AcStrategy::Type::DCT4X4, 2.0f},
- {AcStrategy::Type::DCT, 1.0f}, {AcStrategy::Type::DCT16X8, 1.0f},
- {AcStrategy::Type::DCT8X16, 1.0f}, {AcStrategy::Type::DCT32X8, 1.0f},
- {AcStrategy::Type::DCT8X32, 1.0f}, {AcStrategy::Type::DCT32X16, 1.0f},
- {AcStrategy::Type::DCT16X32, 1.0f}, {AcStrategy::Type::DCT64X32, 1.0f},
- {AcStrategy::Type::DCT32X64, 1.0f}, {AcStrategy::Type::DCT16X16, 1.0f},
- {AcStrategy::Type::DCT32X32, 1.0f}, {AcStrategy::Type::DCT64X64, 1.0f},
-#endif
- // TODO(veluca): figure out if we want 4x8 and/or AVF.
- };
- float max_range = 1e-8f + 0.5f * std::pow(cparams.butteraugli_distance, 0.5f);
- // Change quant field and sharpness amounts based on (pooled|summed)hf44, and
- // compute block sizes.
- // TODO(veluca): maybe this could be done per group: it would allow choosing
- // floating blocks better.
- JXL_RETURN_IF_ERROR(RunOnPool(
- pool, 0, xcolortiles * ycolortiles, ThreadPool::NoInit,
- [&](size_t tile_id, size_t _) {
- size_t tx = tile_id % xcolortiles;
- size_t ty = tile_id / xcolortiles;
- size_t x0 = tx * kColorTileDim / kBlockDim;
- size_t x1 = std::min(x0 + kColorTileDimInBlocks, quant_field.xsize());
- size_t y0 = ty * kColorTileDim / kBlockDim;
- size_t y1 = std::min(y0 + kColorTileDimInBlocks, quant_field.ysize());
- size_t qf_stride = quant_field.PixelsPerRow();
- size_t epf_stride = shared.epf_sharpness.PixelsPerRow();
- bool chosen_mask[64] = {};
- for (size_t y = y0; y < y1; y++) {
- uint8_t* epf_row = shared.epf_sharpness.Row(y);
- float* qf_row = quant_field.Row(y);
- for (size_t x = x0; x < x1; x++) {
- if (chosen_mask[(y - y0) * 8 + (x - x0)]) continue;
- // Default to DCT8 just in case something funny happens in the loop
- // below.
- AcStrategy::Type best = AcStrategy::DCT;
- size_t best_covered = 1;
- float qf = ac_quant_base;
- for (size_t i = 0; i < sizeof(candidates) / sizeof(*candidates);
- i++) {
- AcStrategy acs = AcStrategy::FromRawStrategy(candidates[i].first);
- if (y + acs.covered_blocks_y() > y1) continue;
- if (x + acs.covered_blocks_x() > x1) continue;
- bool fits = true;
- for (size_t iy = y; iy < y + acs.covered_blocks_y(); iy++) {
- for (size_t ix = x; ix < x + acs.covered_blocks_x(); ix++) {
- if (chosen_mask[(iy - y0) * 8 + (ix - x0)]) {
- fits = false;
- break;
- }
- }
- }
- if (!fits) continue;
- float min, max;
- mmacs(x, y, acs, min, max);
- if (max - min > max_range * candidates[i].second) continue;
- size_t cb = acs.covered_blocks_x() * acs.covered_blocks_y();
- if (cb >= best_covered) {
- best_covered = cb;
- best = candidates[i].first;
- // TODO(veluca): make this better.
- qf = ac_quant_base /
- (3.9312946339134007f + 2.6011435675118082f * min);
- }
- }
- shared.ac_strategy.Set(x, y, best);
- AcStrategy acs = AcStrategy::FromRawStrategy(best);
- for (size_t iy = y; iy < y + acs.covered_blocks_y(); iy++) {
- for (size_t ix = x; ix < x + acs.covered_blocks_x(); ix++) {
- chosen_mask[(iy - y0) * 8 + (ix - x0)] = 1;
- qf_row[ix + (iy - y) * qf_stride] = qf;
- }
- }
- // TODO
- for (size_t iy = y; iy < y + acs.covered_blocks_y(); iy++) {
- for (size_t ix = x; ix < x + acs.covered_blocks_x(); ix++) {
- epf_row[ix + (iy - y) * epf_stride] = 4;
- }
- }
- }
- }
- },
- "QF+ACS+EPF"));
- aux_out->DumpPlaneNormalized("qf", quant_field);
- aux_out->DumpPlaneNormalized("epf", shared.epf_sharpness);
- DumpAcStrategy(shared.ac_strategy, frame_dim.xsize_padded,
- frame_dim.ysize_padded, "acs", aux_out);
-
- shared.quantizer.SetQuantField(dc_quant, quant_field,
- &shared.raw_quant_field);
-
- return true;
-}
-} // namespace
-// NOLINTNEXTLINE(google-readability-namespace-comments)
-} // namespace HWY_NAMESPACE
-} // namespace jxl
-HWY_AFTER_NAMESPACE();
-
-#if HWY_ONCE
-namespace jxl {
-HWY_EXPORT(Heuristics);
-Status FastEncoderHeuristics::LossyFrameHeuristics(
- PassesEncoderState* enc_state, ModularFrameEncoder* modular_frame_encoder,
- const ImageBundle* linear, Image3F* opsin, const JxlCmsInterface& cms,
- ThreadPool* pool, AuxOut* aux_out) {
- return HWY_DYNAMIC_DISPATCH(Heuristics)(enc_state, modular_frame_encoder,
- linear, opsin, pool, aux_out);
-}
-
-} // namespace jxl
-#endif
diff --git a/media/libjxl/src/lib/jxl/enc_file.cc b/media/libjxl/src/lib/jxl/enc_file.cc
index 45ae82fdf3..0f29bd92df 100644
--- a/media/libjxl/src/lib/jxl/enc_file.cc
+++ b/media/libjxl/src/lib/jxl/enc_file.cc
@@ -20,7 +20,6 @@
#include "lib/jxl/enc_cache.h"
#include "lib/jxl/enc_frame.h"
#include "lib/jxl/enc_icc_codec.h"
-#include "lib/jxl/exif.h"
#include "lib/jxl/frame_header.h"
#include "lib/jxl/headers.h"
#include "lib/jxl/image_bundle.h"
@@ -84,8 +83,8 @@ Status PrepareCodecMetadataFromIO(const CompressParams& cparams,
metadata->m.xyb_encoded =
cparams.color_transform == ColorTransform::kXYB ? true : false;
- InterpretExif(io->blobs.exif, metadata);
-
+ // TODO(firsching): move this EncodeFile to test_utils / re-implement this
+ // using API functions
return true;
}
@@ -170,7 +169,9 @@ Status EncodeFile(const CompressParams& params, const CodecInOut* io,
}
// Each frame should start on byte boundaries.
+ BitWriter::Allotment allotment(&writer, 8);
writer.ZeroPadToByte();
+ ReclaimAndCharge(&writer, &allotment, kLayerHeader, aux_out);
if (cparams.progressive_mode || cparams.qprogressive_mode) {
if (cparams.saliency_map != nullptr) {
diff --git a/media/libjxl/src/lib/jxl/enc_frame.cc b/media/libjxl/src/lib/jxl/enc_frame.cc
index cad6c4699b..f57175ba0a 100644
--- a/media/libjxl/src/lib/jxl/enc_frame.cc
+++ b/media/libjxl/src/lib/jxl/enc_frame.cc
@@ -497,7 +497,6 @@ class LossyFrameEncoder {
Image3F* JXL_RESTRICT opsin,
const JxlCmsInterface& cms, ThreadPool* pool,
ModularFrameEncoder* modular_frame_encoder,
- BitWriter* JXL_RESTRICT writer,
FrameHeader* frame_header) {
PROFILER_ZONE("ComputeEncodingData uninstrumented");
JXL_ASSERT((opsin->xsize() % kBlockDim) == 0 &&
@@ -605,7 +604,7 @@ class LossyFrameEncoder {
std::vector<int> qt(192);
for (size_t c = 0; c < 3; c++) {
size_t jpeg_c = jpeg_c_map[c];
- const int* quant =
+ const int32_t* quant =
jpeg_data.quant[jpeg_data.components[jpeg_c].quant_idx].values.data();
dcquantization[c] = 255 * 8.0f / quant[0];
@@ -630,7 +629,7 @@ class LossyFrameEncoder {
shared.quantizer.RecomputeFromGlobalScale();
// Per-block dequant scaling should be 1.
- FillImage(static_cast<int>(shared.quantizer.InvGlobalScale()),
+ FillImage(static_cast<int32_t>(shared.quantizer.InvGlobalScale()),
&shared.raw_quant_field);
std::vector<int32_t> scaled_qtable(192);
@@ -936,8 +935,8 @@ class LossyFrameEncoder {
Status EncodeGlobalACInfo(BitWriter* writer,
ModularFrameEncoder* modular_frame_encoder) {
JXL_RETURN_IF_ERROR(DequantMatricesEncode(&enc_state_->shared.matrices,
- writer, kLayerDequantTables,
- aux_out_, modular_frame_encoder));
+ writer, kLayerQuant, aux_out_,
+ modular_frame_encoder));
if (enc_state_->cparams.speed_tier <= SpeedTier::kTortoise) {
if (!doing_jpeg_recompression) ClusterGroups(enc_state_);
}
@@ -1050,6 +1049,12 @@ Status ParamsPostInit(CompressParams* p) {
if (!p->manual_xyb_factors.empty() && p->manual_xyb_factors.size() != 3) {
return JXL_FAILURE("Invalid number of XYB quantization factors");
}
+ if (!p->modular_mode && p->butteraugli_distance == 0.0) {
+ p->butteraugli_distance = kMinButteraugliDistance;
+ }
+ if (p->original_butteraugli_distance == -1.0) {
+ p->original_butteraugli_distance = p->butteraugli_distance;
+ }
if (p->resampling <= 0) {
p->resampling = 1;
// For very low bit rates, using 2x2 resampling gives better results on
@@ -1071,11 +1076,57 @@ Status EncodeFrame(const CompressParams& cparams_orig,
const ImageBundle& ib, PassesEncoderState* passes_enc_state,
const JxlCmsInterface& cms, ThreadPool* pool,
BitWriter* writer, AuxOut* aux_out) {
+ CompressParams cparams = cparams_orig;
+ if (cparams_orig.target_bitrate > 0.0f &&
+ frame_info.frame_type == FrameType::kRegularFrame) {
+ cparams.target_bitrate = 0.0f;
+ const float target_bitrate = cparams_orig.target_bitrate;
+ float bitrate = 0.0f;
+ float prev_bitrate = 0.0f;
+ float rescale = 1.0f;
+ size_t prev_bits = 0;
+ float error = 0.0f;
+ float best_error = 100.0f;
+ float best_rescale = 1.0f;
+ for (size_t i = 0; i < 10; ++i) {
+ std::unique_ptr<PassesEncoderState> state =
+ jxl::make_unique<PassesEncoderState>();
+ BitWriter bw;
+ JXL_CHECK(EncodeFrame(cparams, frame_info, metadata, ib, state.get(), cms,
+ pool, &bw, nullptr));
+ bitrate = bw.BitsWritten() * 1.0 / (ib.xsize() * ib.ysize());
+ error = target_bitrate / bitrate - 1.0f;
+ if (std::abs(error) < std::abs(best_error)) {
+ best_error = error;
+ best_rescale = cparams.quant_ac_rescale;
+ }
+ if (bw.BitsWritten() == prev_bits || std::abs(error) < 0.0005f) {
+ break;
+ }
+ float lambda = 1.0f;
+ if (i > 0) {
+ lambda = (((bitrate / prev_bitrate) - 1.0f) / (rescale - 1.0f));
+ }
+ rescale = (1.0f + ((target_bitrate / bitrate) - 1.0f) / lambda);
+ if (rescale < 0.0f) {
+ break;
+ }
+ cparams.quant_ac_rescale *= rescale;
+ prev_bitrate = bitrate;
+ prev_bits = bw.BitsWritten();
+ }
+ if (aux_out) {
+ aux_out->max_quant_rescale = best_rescale;
+ aux_out->min_quant_rescale = best_rescale;
+ aux_out->min_bitrate_error = best_error;
+ aux_out->max_bitrate_error = best_error;
+ }
+ cparams.quant_ac_rescale = best_rescale;
+ }
ib.VerifyMetadata();
passes_enc_state->special_frames.clear();
- CompressParams cparams = cparams_orig;
JXL_RETURN_IF_ERROR(ParamsPostInit(&cparams));
if (cparams.progressive_dc < 0) {
@@ -1084,16 +1135,20 @@ Status EncodeFrame(const CompressParams& cparams_orig,
cparams.progressive_dc);
}
cparams.progressive_dc = 0;
- // Enable progressive_dc for lower qualities.
- if (cparams.butteraugli_distance >=
- kMinButteraugliDistanceForProgressiveDc) {
+ // Enable progressive_dc for lower qualities, except for fast speeds where
+ // the modular encoder uses fixed tree.
+ if (cparams.speed_tier <= SpeedTier::kCheetah &&
+ cparams.butteraugli_distance >=
+ kMinButteraugliDistanceForProgressiveDc) {
cparams.progressive_dc = 1;
}
}
if (cparams.ec_resampling < cparams.resampling) {
cparams.ec_resampling = cparams.resampling;
}
- if (cparams.resampling > 1) cparams.progressive_dc = 0;
+ if (cparams.resampling > 1 || frame_info.is_preview) {
+ cparams.progressive_dc = 0;
+ }
if (frame_info.dc_level + cparams.progressive_dc > 4) {
return JXL_FAILURE("Too many levels of progressive DC");
@@ -1240,7 +1295,7 @@ Status EncodeFrame(const CompressParams& cparams_orig,
if (frame_header->encoding == FrameEncoding::kVarDCT) {
PadImageToBlockMultipleInPlace(&opsin);
JXL_RETURN_IF_ERROR(lossy_frame_encoder.ComputeEncodingData(
- ib_or_linear, &opsin, cms, pool, modular_frame_encoder.get(), writer,
+ ib_or_linear, &opsin, cms, pool, modular_frame_encoder.get(),
frame_header.get()));
} else if (frame_header->upsampling != 1 && !cparams.already_downsampled) {
// In VarDCT mode, LossyFrameHeuristics takes care of running downsampling
@@ -1249,7 +1304,7 @@ Status EncodeFrame(const CompressParams& cparams_orig,
}
} else {
JXL_RETURN_IF_ERROR(lossy_frame_encoder.ComputeEncodingData(
- &ib, &opsin, cms, pool, modular_frame_encoder.get(), writer,
+ &ib, &opsin, cms, pool, modular_frame_encoder.get(),
frame_header.get()));
}
if (cparams.ec_resampling != 1 && !cparams.already_downsampled) {
@@ -1321,7 +1376,7 @@ Status EncodeFrame(const CompressParams& cparams_orig,
JXL_RETURN_IF_ERROR(
DequantMatricesEncodeDC(&lossy_frame_encoder.State()->shared.matrices,
- get_output(0), kLayerDequantTables, aux_out));
+ get_output(0), kLayerQuant, aux_out));
if (frame_header->encoding == FrameEncoding::kVarDCT) {
JXL_RETURN_IF_ERROR(
lossy_frame_encoder.EncodeGlobalDCInfo(*frame_header, get_output(0)));
@@ -1401,7 +1456,9 @@ Status EncodeFrame(const CompressParams& cparams_orig,
JXL_RETURN_IF_ERROR(num_errors.load(std::memory_order_relaxed) == 0);
for (BitWriter& bw : group_codes) {
+ BitWriter::Allotment allotment(&bw, 8);
bw.ZeroPadToByte(); // end of group.
+ ReclaimAndCharge(&bw, &allotment, kLayerAC, aux_out);
}
std::vector<coeff_order_t>* permutation_ptr = nullptr;
@@ -1483,7 +1540,6 @@ Status EncodeFrame(const CompressParams& cparams_orig,
JXL_RETURN_IF_ERROR(
WriteGroupOffsets(group_codes, permutation_ptr, writer, aux_out));
writer->AppendByteAligned(group_codes);
- writer->ZeroPadToByte(); // end of frame.
return true;
}
diff --git a/media/libjxl/src/lib/jxl/enc_group.cc b/media/libjxl/src/lib/jxl/enc_group.cc
index 5ec53d6618..bf853064ee 100644
--- a/media/libjxl/src/lib/jxl/enc_group.cc
+++ b/media/libjxl/src/lib/jxl/enc_group.cc
@@ -32,6 +32,14 @@ HWY_BEFORE_NAMESPACE();
namespace jxl {
namespace HWY_NAMESPACE {
+// These templates are not found via ADL.
+using hwy::HWY_NAMESPACE::Abs;
+using hwy::HWY_NAMESPACE::Ge;
+using hwy::HWY_NAMESPACE::IfThenElse;
+using hwy::HWY_NAMESPACE::IfThenElseZero;
+using hwy::HWY_NAMESPACE::MaskFromVec;
+using hwy::HWY_NAMESPACE::Round;
+
// NOTE: caller takes care of extracting quant from rect of RawQuantField.
void QuantizeBlockAC(const Quantizer& quantizer, const bool error_diffusion,
size_t c, int32_t quant, float qm_multiplier,
@@ -83,10 +91,10 @@ void QuantizeBlockAC(const Quantizer& quantizer, const bool error_diffusion,
thres[yfix + static_cast<size_t>(x >= xsize * kBlockDim / 2)]);
}
- const auto q = Load(df, qm + off + x) * quant;
+ const auto q = Mul(Load(df, qm + off + x), quant);
const auto in = Load(df, block_in + off + x);
- const auto val = q * in;
- const auto nzero_mask = Abs(val) >= thr;
+ const auto val = Mul(q, in);
+ const auto nzero_mask = Ge(Abs(val), thr);
const auto v = ConvertTo(di, IfThenElseZero(nzero_mask, Round(val)));
Store(v, di, block_out + off + x);
}
@@ -174,7 +182,7 @@ void QuantizeRoundtripYBlockAC(const Quantizer& quantizer,
const auto quant = Load(di, quantized + k);
const auto adj_quant = AdjustQuantBias(di, 1, quant, biases);
const auto dequantm = Load(df, dequant_matrix + k);
- Store(adj_quant * dequantm * inv_qac, df, inout + k);
+ Store(Mul(Mul(adj_quant, dequantm), inv_qac), df, inout + k);
}
}
@@ -286,8 +294,8 @@ void ComputeCoefficients(size_t group_idx, PassesEncoderState* enc_state,
const auto in_x = Load(d, coeffs_in + k);
const auto in_y = Load(d, coeffs_in + size + k);
const auto in_b = Load(d, coeffs_in + 2 * size + k);
- const auto out_x = in_x - x_factor * in_y;
- const auto out_b = in_b - b_factor * in_y;
+ const auto out_x = NegMulAdd(x_factor, in_y, in_x);
+ const auto out_b = NegMulAdd(b_factor, in_y, in_b);
Store(out_x, d, coeffs_in + k);
Store(out_b, d, coeffs_in + 2 * size + k);
}
diff --git a/media/libjxl/src/lib/jxl/enc_heuristics.cc b/media/libjxl/src/lib/jxl/enc_heuristics.cc
index 090852b765..1ab4ea56c5 100644
--- a/media/libjxl/src/lib/jxl/enc_heuristics.cc
+++ b/media/libjxl/src/lib/jxl/enc_heuristics.cc
@@ -164,19 +164,6 @@ void FindBestBlockEntropyModel(PassesEncoderState& enc_state) {
*std::max_element(ctx_map.begin(), ctx_map.end()) + 1;
}
-// Returns the target size based on whether bitrate or direct targetsize is
-// given.
-size_t TargetSize(const CompressParams& cparams,
- const FrameDimensions& frame_dim) {
- if (cparams.target_size > 0) {
- return cparams.target_size;
- }
- if (cparams.target_bitrate > 0.0) {
- return 0.5 + cparams.target_bitrate * frame_dim.xsize * frame_dim.ysize /
- kBitsPerByte;
- }
- return 0;
-}
} // namespace
void FindBestDequantMatrices(const CompressParams& cparams,
@@ -772,12 +759,7 @@ Status DefaultEncoderHeuristics::LossyFrameHeuristics(
PadImageToBlockMultipleInPlace(opsin);
}
- const FrameDimensions& frame_dim = enc_state->shared.frame_dim;
- size_t target_size = TargetSize(cparams, frame_dim);
- size_t opsin_target_size = target_size;
- if (cparams.target_size > 0 || cparams.target_bitrate > 0.0) {
- cparams.target_size = opsin_target_size;
- } else if (cparams.butteraugli_distance < 0) {
+ if (cparams.butteraugli_distance < 0) {
return JXL_FAILURE("Expected non-negative distance");
}
@@ -859,6 +841,7 @@ Status DefaultEncoderHeuristics::LossyFrameHeuristics(
enc_state->initial_quant_field = InitialQuantField(
butteraugli_distance_for_iqf, *opsin, shared.frame_dim, pool, 1.0f,
&enc_state->initial_quant_masking);
+ quantizer.SetQuantField(quant_dc, enc_state->initial_quant_field, nullptr);
}
// TODO(veluca): do something about animations.
diff --git a/media/libjxl/src/lib/jxl/enc_heuristics.h b/media/libjxl/src/lib/jxl/enc_heuristics.h
index 4e910efe94..16509f00df 100644
--- a/media/libjxl/src/lib/jxl/enc_heuristics.h
+++ b/media/libjxl/src/lib/jxl/enc_heuristics.h
@@ -67,15 +67,6 @@ class DefaultEncoderHeuristics : public EncoderHeuristics {
const ImageBundle& ib) override;
};
-class FastEncoderHeuristics : public EncoderHeuristics {
- public:
- Status LossyFrameHeuristics(PassesEncoderState* enc_state,
- ModularFrameEncoder* modular_frame_encoder,
- const ImageBundle* linear, Image3F* opsin,
- const JxlCmsInterface& cms, ThreadPool* pool,
- AuxOut* aux_out) override;
-};
-
// Exposed here since it may be used by other EncoderHeuristics implementations
// outside this project.
void FindBestDequantMatrices(const CompressParams& cparams,
diff --git a/media/libjxl/src/lib/jxl/enc_image_bundle.cc b/media/libjxl/src/lib/jxl/enc_image_bundle.cc
index fe062475e6..fe6d282a8e 100644
--- a/media/libjxl/src/lib/jxl/enc_image_bundle.cc
+++ b/media/libjxl/src/lib/jxl/enc_image_bundle.cc
@@ -5,6 +5,7 @@
#include "lib/jxl/enc_image_bundle.h"
+#include <atomic>
#include <limits>
#include <utility>
diff --git a/media/libjxl/src/lib/jxl/enc_modular.cc b/media/libjxl/src/lib/jxl/enc_modular.cc
index 0ca27d9061..9e34fe8753 100644
--- a/media/libjxl/src/lib/jxl/enc_modular.cc
+++ b/media/libjxl/src/lib/jxl/enc_modular.cc
@@ -9,6 +9,7 @@
#include <stdint.h>
#include <array>
+#include <atomic>
#include <limits>
#include <queue>
#include <utility>
@@ -164,8 +165,7 @@ Tree PredefinedTree(ModularOptions::TreeKind tree_kind, size_t total_pixels) {
-500, -392, -255, -191, -127, -95, -63, -47, -31, -23, -15,
-11, -7, -4, -3, -1, 0, 1, 3, 5, 7, 11,
15, 23, 31, 47, 63, 95, 127, 191, 255, 392, 500};
- return MakeFixedTree(kNumNonrefProperties - weighted::kNumProperties,
- cutoffs, Predictor::Weighted, total_pixels);
+ return MakeFixedTree(kWPProp, cutoffs, Predictor::Weighted, total_pixels);
}
if (tree_kind == ModularOptions::TreeKind::kGradientFixedDC) {
std::vector<int32_t> cutoffs = {
@@ -300,60 +300,60 @@ Status float_to_int(const float* const row_in, pixel_type* const row_out,
ModularFrameEncoder::ModularFrameEncoder(const FrameHeader& frame_header,
const CompressParams& cparams_orig)
- : frame_dim(frame_header.ToFrameDimensions()), cparams(cparams_orig) {
+ : frame_dim_(frame_header.ToFrameDimensions()), cparams_(cparams_orig) {
size_t num_streams =
- ModularStreamId::Num(frame_dim, frame_header.passes.num_passes);
- if (cparams.IsLossless()) {
- switch (cparams.decoding_speed_tier) {
+ ModularStreamId::Num(frame_dim_, frame_header.passes.num_passes);
+ if (cparams_.IsLossless()) {
+ switch (cparams_.decoding_speed_tier) {
case 0:
break;
case 1:
- cparams.options.wp_tree_mode = ModularOptions::TreeMode::kWPOnly;
+ cparams_.options.wp_tree_mode = ModularOptions::TreeMode::kWPOnly;
break;
case 2: {
- cparams.options.wp_tree_mode = ModularOptions::TreeMode::kGradientOnly;
- cparams.options.predictor = Predictor::Gradient;
+ cparams_.options.wp_tree_mode = ModularOptions::TreeMode::kGradientOnly;
+ cparams_.options.predictor = Predictor::Gradient;
break;
}
case 3: { // LZ77, no Gradient.
- cparams.options.nb_repeats = 0;
- cparams.options.predictor = Predictor::Gradient;
+ cparams_.options.nb_repeats = 0;
+ cparams_.options.predictor = Predictor::Gradient;
break;
}
default: { // LZ77, no predictor.
- cparams.options.nb_repeats = 0;
- cparams.options.predictor = Predictor::Zero;
+ cparams_.options.nb_repeats = 0;
+ cparams_.options.predictor = Predictor::Zero;
break;
}
}
}
- if (cparams.decoding_speed_tier >= 1 && cparams.responsive &&
- cparams.IsLossless()) {
- cparams.options.tree_kind =
+ if (cparams_.decoding_speed_tier >= 1 && cparams_.responsive &&
+ cparams_.IsLossless()) {
+ cparams_.options.tree_kind =
ModularOptions::TreeKind::kTrivialTreeNoPredictor;
- cparams.options.nb_repeats = 0;
+ cparams_.options.nb_repeats = 0;
}
- stream_images.resize(num_streams);
+ stream_images_.resize(num_streams);
// use a sensible default if nothing explicit is specified:
// Squeeze for lossy, no squeeze for lossless
- if (cparams.responsive < 0) {
- if (cparams.IsLossless()) {
- cparams.responsive = 0;
+ if (cparams_.responsive < 0) {
+ if (cparams_.IsLossless()) {
+ cparams_.responsive = 0;
} else {
- cparams.responsive = 1;
+ cparams_.responsive = 1;
}
}
- if (cparams.speed_tier > SpeedTier::kWombat) {
- cparams.options.splitting_heuristics_node_threshold = 192;
+ if (cparams_.speed_tier > SpeedTier::kWombat) {
+ cparams_.options.splitting_heuristics_node_threshold = 192;
} else {
- cparams.options.splitting_heuristics_node_threshold = 96;
+ cparams_.options.splitting_heuristics_node_threshold = 96;
}
{
// Set properties.
std::vector<uint32_t> prop_order;
- if (cparams.responsive) {
+ if (cparams_.responsive) {
// Properties in order of their likelihood of being useful for Squeeze
// residuals.
prop_order = {0, 1, 4, 5, 6, 7, 8, 15, 9, 10, 11, 12, 13, 14, 2, 3};
@@ -361,93 +361,93 @@ ModularFrameEncoder::ModularFrameEncoder(const FrameHeader& frame_header,
// Same, but for the non-Squeeze case.
prop_order = {0, 1, 15, 9, 10, 11, 12, 13, 14, 2, 3, 4, 5, 6, 7, 8};
}
- switch (cparams.speed_tier) {
+ switch (cparams_.speed_tier) {
case SpeedTier::kSquirrel:
- cparams.options.splitting_heuristics_properties.assign(
+ cparams_.options.splitting_heuristics_properties.assign(
prop_order.begin(), prop_order.begin() + 8);
- cparams.options.max_property_values = 32;
+ cparams_.options.max_property_values = 32;
break;
case SpeedTier::kKitten:
- cparams.options.splitting_heuristics_properties.assign(
+ cparams_.options.splitting_heuristics_properties.assign(
prop_order.begin(), prop_order.begin() + 10);
- cparams.options.max_property_values = 64;
+ cparams_.options.max_property_values = 64;
break;
case SpeedTier::kTortoise:
- cparams.options.splitting_heuristics_properties = prop_order;
- cparams.options.max_property_values = 256;
+ cparams_.options.splitting_heuristics_properties = prop_order;
+ cparams_.options.max_property_values = 256;
break;
default:
- cparams.options.splitting_heuristics_properties.assign(
+ cparams_.options.splitting_heuristics_properties.assign(
prop_order.begin(), prop_order.begin() + 6);
- cparams.options.max_property_values = 16;
+ cparams_.options.max_property_values = 16;
break;
}
- if (cparams.speed_tier > SpeedTier::kTortoise) {
+ if (cparams_.speed_tier > SpeedTier::kTortoise) {
// Gradient in previous channels.
- for (int i = 0; i < cparams.options.max_properties; i++) {
- cparams.options.splitting_heuristics_properties.push_back(
+ for (int i = 0; i < cparams_.options.max_properties; i++) {
+ cparams_.options.splitting_heuristics_properties.push_back(
kNumNonrefProperties + i * 4 + 3);
}
} else {
// All the extra properties in Tortoise mode.
- for (int i = 0; i < cparams.options.max_properties * 4; i++) {
- cparams.options.splitting_heuristics_properties.push_back(
+ for (int i = 0; i < cparams_.options.max_properties * 4; i++) {
+ cparams_.options.splitting_heuristics_properties.push_back(
kNumNonrefProperties + i);
}
}
}
- if (cparams.options.predictor == static_cast<Predictor>(-1)) {
+ if (cparams_.options.predictor == static_cast<Predictor>(-1)) {
// no explicit predictor(s) given, set a good default
- if ((cparams.speed_tier <= SpeedTier::kTortoise ||
- cparams.modular_mode == false) &&
- cparams.IsLossless() && cparams.responsive == false) {
+ if ((cparams_.speed_tier <= SpeedTier::kTortoise ||
+ cparams_.modular_mode == false) &&
+ cparams_.IsLossless() && cparams_.responsive == false) {
// TODO(veluca): allow all predictors that don't break residual
// multipliers in lossy mode.
- cparams.options.predictor = Predictor::Variable;
- } else if (cparams.responsive || cparams.lossy_palette) {
+ cparams_.options.predictor = Predictor::Variable;
+ } else if (cparams_.responsive || cparams_.lossy_palette) {
// zero predictor for Squeeze residues and lossy palette
- cparams.options.predictor = Predictor::Zero;
- } else if (!cparams.IsLossless()) {
+ cparams_.options.predictor = Predictor::Zero;
+ } else if (!cparams_.IsLossless()) {
// If not responsive and lossy. TODO(veluca): use near_lossless instead?
- cparams.options.predictor = Predictor::Gradient;
- } else if (cparams.speed_tier < SpeedTier::kFalcon) {
+ cparams_.options.predictor = Predictor::Gradient;
+ } else if (cparams_.speed_tier < SpeedTier::kFalcon) {
// try median and weighted predictor for anything else
- cparams.options.predictor = Predictor::Best;
- } else if (cparams.speed_tier == SpeedTier::kFalcon) {
+ cparams_.options.predictor = Predictor::Best;
+ } else if (cparams_.speed_tier == SpeedTier::kFalcon) {
// just weighted predictor in falcon mode
- cparams.options.predictor = Predictor::Weighted;
- } else if (cparams.speed_tier > SpeedTier::kFalcon) {
+ cparams_.options.predictor = Predictor::Weighted;
+ } else if (cparams_.speed_tier > SpeedTier::kFalcon) {
// just gradient predictor in thunder mode
- cparams.options.predictor = Predictor::Gradient;
+ cparams_.options.predictor = Predictor::Gradient;
}
} else {
- delta_pred = cparams.options.predictor;
- if (cparams.lossy_palette) cparams.options.predictor = Predictor::Zero;
- }
- if (!cparams.IsLossless()) {
- if (cparams.options.predictor == Predictor::Weighted ||
- cparams.options.predictor == Predictor::Variable ||
- cparams.options.predictor == Predictor::Best)
- cparams.options.predictor = Predictor::Zero;
- }
- tree_splits.push_back(0);
- if (cparams.modular_mode == false) {
- cparams.options.fast_decode_multiplier = 1.0f;
- tree_splits.push_back(ModularStreamId::VarDCTDC(0).ID(frame_dim));
- tree_splits.push_back(ModularStreamId::ModularDC(0).ID(frame_dim));
- tree_splits.push_back(ModularStreamId::ACMetadata(0).ID(frame_dim));
- tree_splits.push_back(ModularStreamId::QuantTable(0).ID(frame_dim));
- tree_splits.push_back(ModularStreamId::ModularAC(0, 0).ID(frame_dim));
- ac_metadata_size.resize(frame_dim.num_dc_groups);
- extra_dc_precision.resize(frame_dim.num_dc_groups);
- }
- tree_splits.push_back(num_streams);
- cparams.options.max_chan_size = frame_dim.group_dim;
- cparams.options.group_dim = frame_dim.group_dim;
+ delta_pred_ = cparams_.options.predictor;
+ if (cparams_.lossy_palette) cparams_.options.predictor = Predictor::Zero;
+ }
+ if (!cparams_.IsLossless()) {
+ if (cparams_.options.predictor == Predictor::Weighted ||
+ cparams_.options.predictor == Predictor::Variable ||
+ cparams_.options.predictor == Predictor::Best)
+ cparams_.options.predictor = Predictor::Zero;
+ }
+ tree_splits_.push_back(0);
+ if (cparams_.modular_mode == false) {
+ cparams_.options.fast_decode_multiplier = 1.0f;
+ tree_splits_.push_back(ModularStreamId::VarDCTDC(0).ID(frame_dim_));
+ tree_splits_.push_back(ModularStreamId::ModularDC(0).ID(frame_dim_));
+ tree_splits_.push_back(ModularStreamId::ACMetadata(0).ID(frame_dim_));
+ tree_splits_.push_back(ModularStreamId::QuantTable(0).ID(frame_dim_));
+ tree_splits_.push_back(ModularStreamId::ModularAC(0, 0).ID(frame_dim_));
+ ac_metadata_size.resize(frame_dim_.num_dc_groups);
+ extra_dc_precision.resize(frame_dim_.num_dc_groups);
+ }
+ tree_splits_.push_back(num_streams);
+ cparams_.options.max_chan_size = frame_dim_.group_dim;
+ cparams_.options.group_dim = frame_dim_.group_dim;
// TODO(veluca): figure out how to use different predictor sets per channel.
- stream_options.resize(num_streams, cparams.options);
+ stream_options_.resize(num_streams, cparams_.options);
}
bool do_transform(Image& image, const Transform& tr,
@@ -469,28 +469,29 @@ Status ModularFrameEncoder::ComputeEncodingData(
Image3F* JXL_RESTRICT color, const std::vector<ImageF>& extra_channels,
PassesEncoderState* JXL_RESTRICT enc_state, const JxlCmsInterface& cms,
ThreadPool* pool, AuxOut* aux_out, bool do_color) {
- const FrameDimensions& frame_dim = enc_state->shared.frame_dim;
+ JXL_DEBUG_V(6, "Computing modular encoding data for frame %s",
+ frame_header.DebugString().c_str());
if (do_color && frame_header.loop_filter.gab) {
GaborishInverse(color, 0.9908511000000001f, pool);
}
if (do_color && metadata.bit_depth.bits_per_sample <= 16 &&
- cparams.speed_tier < SpeedTier::kCheetah &&
- cparams.decoding_speed_tier < 2) {
+ cparams_.speed_tier < SpeedTier::kCheetah &&
+ cparams_.decoding_speed_tier < 2) {
FindBestPatchDictionary(*color, enc_state, cms, nullptr, aux_out,
- cparams.color_transform == ColorTransform::kXYB);
+ cparams_.color_transform == ColorTransform::kXYB);
PatchDictionaryEncoder::SubtractFrom(
enc_state->shared.image_features.patches, color);
}
// Convert ImageBundle to modular Image object
- const size_t xsize = frame_dim.xsize;
- const size_t ysize = frame_dim.ysize;
+ const size_t xsize = frame_dim_.xsize;
+ const size_t ysize = frame_dim_.ysize;
int nb_chans = 3;
if (metadata.color_encoding.IsGray() &&
- cparams.color_transform == ColorTransform::kNone) {
+ cparams_.color_transform == ColorTransform::kNone) {
nb_chans = 1;
}
if (!do_color) nb_chans = 0;
@@ -498,11 +499,11 @@ Status ModularFrameEncoder::ComputeEncodingData(
nb_chans += extra_channels.size();
bool fp = metadata.bit_depth.floating_point_sample &&
- cparams.color_transform != ColorTransform::kXYB;
+ cparams_.color_transform != ColorTransform::kXYB;
// bits_per_sample is just metadata for XYB images.
if (metadata.bit_depth.bits_per_sample >= 32 && do_color &&
- cparams.color_transform != ColorTransform::kXYB) {
+ cparams_.color_transform != ColorTransform::kXYB) {
if (metadata.bit_depth.bits_per_sample == 32 && fp == false) {
return JXL_FAILURE("uint32_t not supported in enc_modular");
} else if (metadata.bit_depth.bits_per_sample > 32) {
@@ -513,22 +514,22 @@ Status ModularFrameEncoder::ComputeEncodingData(
// in the non-float case, there is an implicit 0 sign bit
int max_bitdepth =
do_color ? metadata.bit_depth.bits_per_sample + (fp ? 0 : 1) : 0;
- Image& gi = stream_images[0];
+ Image& gi = stream_images_[0];
gi = Image(xsize, ysize, metadata.bit_depth.bits_per_sample, nb_chans);
int c = 0;
- if (cparams.color_transform == ColorTransform::kXYB &&
- cparams.modular_mode == true) {
+ if (cparams_.color_transform == ColorTransform::kXYB &&
+ cparams_.modular_mode == true) {
float enc_factors[3] = {32768.0f, 2048.0f, 2048.0f};
- if (cparams.butteraugli_distance > 0 && !cparams.responsive) {
+ if (cparams_.butteraugli_distance > 0 && !cparams_.responsive) {
// quantize XYB here and then treat it as a lossless image
- enc_factors[0] *= 1.f / (1.f + 23.f * cparams.butteraugli_distance);
- enc_factors[1] *= 1.f / (1.f + 14.f * cparams.butteraugli_distance);
- enc_factors[2] *= 1.f / (1.f + 14.f * cparams.butteraugli_distance);
- cparams.butteraugli_distance = 0;
+ enc_factors[0] *= 1.f / (1.f + 23.f * cparams_.butteraugli_distance);
+ enc_factors[1] *= 1.f / (1.f + 14.f * cparams_.butteraugli_distance);
+ enc_factors[2] *= 1.f / (1.f + 14.f * cparams_.butteraugli_distance);
+ cparams_.butteraugli_distance = 0;
}
- if (cparams.manual_xyb_factors.size() == 3) {
+ if (cparams_.manual_xyb_factors.size() == 3) {
DequantMatricesSetCustomDC(&enc_state->shared.matrices,
- cparams.manual_xyb_factors.data());
+ cparams_.manual_xyb_factors.data());
// TODO(jon): update max_bitdepth in this case
} else {
DequantMatricesSetCustomDC(&enc_state->shared.matrices, enc_factors);
@@ -539,17 +540,17 @@ Status ModularFrameEncoder::ComputeEncodingData(
if (do_color) {
for (; c < 3; c++) {
if (metadata.color_encoding.IsGray() &&
- cparams.color_transform == ColorTransform::kNone &&
- c != (cparams.color_transform == ColorTransform::kXYB ? 1 : 0))
+ cparams_.color_transform == ColorTransform::kNone &&
+ c != (cparams_.color_transform == ColorTransform::kXYB ? 1 : 0))
continue;
int c_out = c;
// XYB is encoded as YX(B-Y)
- if (cparams.color_transform == ColorTransform::kXYB && c < 2)
+ if (cparams_.color_transform == ColorTransform::kXYB && c < 2)
c_out = 1 - c_out;
double factor = maxval;
- if (cparams.color_transform == ColorTransform::kXYB)
+ if (cparams_.color_transform == ColorTransform::kXYB)
factor = enc_state->shared.matrices.InvDCQuant(c);
- if (c == 2 && cparams.color_transform == ColorTransform::kXYB) {
+ if (c == 2 && cparams_.color_transform == ColorTransform::kXYB) {
JXL_ASSERT(!fp);
for (size_t y = 0; y < ysize; ++y) {
const float* const JXL_RESTRICT row_in = color->PlaneRow(c, y);
@@ -591,15 +592,15 @@ Status ModularFrameEncoder::ComputeEncodingData(
}
}
if (metadata.color_encoding.IsGray() &&
- cparams.color_transform == ColorTransform::kNone)
+ cparams_.color_transform == ColorTransform::kNone)
c = 1;
}
for (size_t ec = 0; ec < extra_channels.size(); ec++, c++) {
const ExtraChannelInfo& eci = metadata.extra_channel_info[ec];
size_t ecups = frame_header.extra_channel_upsampling[ec];
- gi.channel[c].shrink(DivCeil(frame_dim.xsize_upsampled, ecups),
- DivCeil(frame_dim.ysize_upsampled, ecups));
+ gi.channel[c].shrink(DivCeil(frame_dim_.xsize_upsampled, ecups),
+ DivCeil(frame_dim_.ysize_upsampled, ecups));
gi.channel[c].hshift = gi.channel[c].vshift =
CeilLog2Nonzero(ecups) - CeilLog2Nonzero(frame_header.upsampling);
@@ -625,25 +626,25 @@ Status ModularFrameEncoder::ComputeEncodingData(
}
JXL_ASSERT(c == nb_chans);
- int level_max_bitdepth = (cparams.level == 5 ? 16 : 32);
+ int level_max_bitdepth = (cparams_.level == 5 ? 16 : 32);
if (max_bitdepth > level_max_bitdepth)
return JXL_FAILURE(
"Bitdepth too high for level %i (need %i bits, have only %i in this "
"level)",
- cparams.level, max_bitdepth, level_max_bitdepth);
+ cparams_.level, max_bitdepth, level_max_bitdepth);
// Set options and apply transformations
- if (cparams.butteraugli_distance > 0) {
- if (cparams.palette_colors != 0) {
+ if (cparams_.butteraugli_distance > 0) {
+ if (cparams_.palette_colors != 0) {
JXL_DEBUG_V(3, "Lossy encode, not doing palette transforms");
}
- if (cparams.color_transform == ColorTransform::kXYB) {
- cparams.channel_colors_pre_transform_percent = 0;
+ if (cparams_.color_transform == ColorTransform::kXYB) {
+ cparams_.channel_colors_pre_transform_percent = 0;
}
- cparams.channel_colors_percent = 0;
- cparams.palette_colors = 0;
- cparams.lossy_palette = false;
+ cparams_.channel_colors_percent = 0;
+ cparams_.palette_colors = 0;
+ cparams_.lossy_palette = false;
}
// if few colors, do all-channel palette before trying channel palette
@@ -653,7 +654,8 @@ Status ModularFrameEncoder::ComputeEncodingData(
// signaling cost for almost no benefit
// - if the palette needs more colors, then channel palette might help to
// reduce palette signaling cost
- if (cparams.palette_colors != 0 && cparams.speed_tier < SpeedTier::kFalcon) {
+ if (cparams_.palette_colors != 0 &&
+ cparams_.speed_tier < SpeedTier::kFalcon) {
// all-channel palette (e.g. RGBA)
if (gi.channel.size() > 1) {
Transform maybe_palette(TransformId::kPalette);
@@ -661,22 +663,24 @@ Status ModularFrameEncoder::ComputeEncodingData(
maybe_palette.num_c = gi.channel.size() - gi.nb_meta_channels;
maybe_palette.nb_colors =
std::min(std::min(200, (int)(xsize * ysize / 8)),
- std::abs(cparams.palette_colors) / 16);
- maybe_palette.ordered_palette = cparams.palette_colors >= 0;
+ std::abs(cparams_.palette_colors) / 16);
+ maybe_palette.ordered_palette = cparams_.palette_colors >= 0;
maybe_palette.lossy_palette = false;
do_transform(gi, maybe_palette, weighted::Header(), pool);
}
}
// Global channel palette
- if (cparams.channel_colors_pre_transform_percent > 0 &&
- !cparams.lossy_palette &&
- (cparams.speed_tier <= SpeedTier::kThunder ||
+ if (cparams_.channel_colors_pre_transform_percent > 0 &&
+ !cparams_.lossy_palette &&
+ (cparams_.speed_tier <= SpeedTier::kThunder ||
(do_color && metadata.bit_depth.bits_per_sample > 8))) {
// single channel palette (like FLIF's ChannelCompact)
size_t nb_channels = gi.channel.size() - gi.nb_meta_channels;
+ int orig_bitdepth = max_bitdepth;
+ max_bitdepth = 0;
for (size_t i = 0; i < nb_channels; i++) {
- int min, max;
+ int32_t min, max;
compute_minmax(gi.channel[gi.nb_meta_channels + i], &min, &max);
int64_t colors = max - min + 1;
JXL_DEBUG_V(10, "Channel %" PRIuS ": range=%i..%i", i, min, max);
@@ -689,35 +693,39 @@ Status ModularFrameEncoder::ComputeEncodingData(
// image itself)
maybe_palette_1.nb_colors = std::min(
(int)(xsize * ysize / 16),
- (int)(cparams.channel_colors_pre_transform_percent / 100. * colors));
+ (int)(cparams_.channel_colors_pre_transform_percent / 100. * colors));
if (do_transform(gi, maybe_palette_1, weighted::Header(), pool)) {
// effective bit depth is lower, adjust quantization accordingly
compute_minmax(gi.channel[gi.nb_meta_channels + i], &min, &max);
if (max < maxval) maxval = max;
- }
+ int ch_bitdepth =
+ (max > 0 ? CeilLog2Nonzero(static_cast<uint32_t>(max)) : 0);
+ if (ch_bitdepth > max_bitdepth) max_bitdepth = ch_bitdepth;
+ } else
+ max_bitdepth = orig_bitdepth;
}
}
// Global palette
- if ((cparams.palette_colors != 0 || cparams.lossy_palette) &&
- cparams.speed_tier < SpeedTier::kFalcon) {
+ if ((cparams_.palette_colors != 0 || cparams_.lossy_palette) &&
+ cparams_.speed_tier < SpeedTier::kFalcon) {
// all-channel palette (e.g. RGBA)
if (gi.channel.size() - gi.nb_meta_channels > 1) {
Transform maybe_palette(TransformId::kPalette);
maybe_palette.begin_c = gi.nb_meta_channels;
maybe_palette.num_c = gi.channel.size() - gi.nb_meta_channels;
maybe_palette.nb_colors =
- std::min((int)(xsize * ysize / 8), std::abs(cparams.palette_colors));
- maybe_palette.ordered_palette = cparams.palette_colors >= 0;
+ std::min((int)(xsize * ysize / 8), std::abs(cparams_.palette_colors));
+ maybe_palette.ordered_palette = cparams_.palette_colors >= 0;
maybe_palette.lossy_palette =
- (cparams.lossy_palette && maybe_palette.num_c == 3);
+ (cparams_.lossy_palette && maybe_palette.num_c == 3);
if (maybe_palette.lossy_palette) {
- maybe_palette.predictor = delta_pred;
+ maybe_palette.predictor = delta_pred_;
}
// TODO(veluca): use a custom weighted header if using the weighted
// predictor.
do_transform(gi, maybe_palette, weighted::Header(), pool,
- cparams.options.zero_tokens);
+ cparams_.options.zero_tokens);
}
// all-minus-one-channel palette (RGB with separate alpha, or CMY with
// separate K)
@@ -726,71 +734,70 @@ Status ModularFrameEncoder::ComputeEncodingData(
maybe_palette_3.begin_c = gi.nb_meta_channels;
maybe_palette_3.num_c = gi.channel.size() - gi.nb_meta_channels - 1;
maybe_palette_3.nb_colors =
- std::min((int)(xsize * ysize / 8), std::abs(cparams.palette_colors));
- maybe_palette_3.ordered_palette = cparams.palette_colors >= 0;
- maybe_palette_3.lossy_palette = cparams.lossy_palette;
+ std::min((int)(xsize * ysize / 8), std::abs(cparams_.palette_colors));
+ maybe_palette_3.ordered_palette = cparams_.palette_colors >= 0;
+ maybe_palette_3.lossy_palette = cparams_.lossy_palette;
if (maybe_palette_3.lossy_palette) {
- maybe_palette_3.predictor = delta_pred;
+ maybe_palette_3.predictor = delta_pred_;
}
do_transform(gi, maybe_palette_3, weighted::Header(), pool,
- cparams.options.zero_tokens);
+ cparams_.options.zero_tokens);
}
}
// don't do an RCT if we're short on bits
- if (cparams.color_transform == ColorTransform::kNone && do_color && !fp &&
+ if (cparams_.color_transform == ColorTransform::kNone && do_color &&
gi.channel.size() - gi.nb_meta_channels >= 3 &&
max_bitdepth + 1 < level_max_bitdepth) {
- if (cparams.colorspace == 1 ||
- (cparams.colorspace < 0 &&
- (!cparams.IsLossless() || cparams.speed_tier > SpeedTier::kHare))) {
+ if (cparams_.colorspace < 0 &&
+ (!cparams_.IsLossless() || cparams_.speed_tier > SpeedTier::kHare)) {
Transform ycocg{TransformId::kRCT};
ycocg.rct_type = 6;
ycocg.begin_c = gi.nb_meta_channels;
do_transform(gi, ycocg, weighted::Header(), pool);
max_bitdepth++;
- } else if (cparams.colorspace >= 2) {
+ } else if (cparams_.colorspace > 0) {
Transform sg(TransformId::kRCT);
sg.begin_c = gi.nb_meta_channels;
- sg.rct_type = cparams.colorspace - 2;
+ sg.rct_type = cparams_.colorspace;
do_transform(gi, sg, weighted::Header(), pool);
max_bitdepth++;
}
}
// don't do squeeze if we don't have some spare bits
- if (cparams.responsive && !gi.channel.empty() &&
+ if (cparams_.responsive && !gi.channel.empty() &&
max_bitdepth + 2 < level_max_bitdepth) {
Transform t(TransformId::kSqueeze);
- t.squeezes = cparams.squeezes;
+ t.squeezes = cparams_.squeezes;
do_transform(gi, t, weighted::Header(), pool);
max_bitdepth += 2;
}
if (max_bitdepth + 1 > level_max_bitdepth) {
// force no group RCTs if we don't have a spare bit
- cparams.colorspace = 0;
+ cparams_.colorspace = 0;
}
JXL_ASSERT(max_bitdepth <= level_max_bitdepth);
std::vector<uint32_t> quants;
- if (cparams.butteraugli_distance > 0) {
+ if (cparams_.butteraugli_distance > 0) {
quants.resize(gi.channel.size(), 1);
- float quality = 0.25f * cparams.butteraugli_distance;
+ float quality = 0.25f * cparams_.butteraugli_distance;
JXL_DEBUG_V(2,
"Adding quantization constants corresponding to distance %.3f ",
quality);
- if (!cparams.responsive) {
+ if (!cparams_.responsive) {
JXL_DEBUG_V(1,
"Warning: lossy compression without Squeeze "
"transform is just color quantization.");
quality *= 0.1f;
}
- if (cparams.color_transform != ColorTransform::kXYB) {
+ if (cparams_.color_transform != ColorTransform::kXYB) {
quality *= maxval / 255.f;
}
- if (cparams.options.nb_repeats == 0) {
+ if (cparams_.options.nb_repeats == 0) {
return JXL_FAILURE("nb_repeats = 0 not supported with modular lossy!");
}
for (uint32_t i = gi.nb_meta_channels; i < gi.channel.size(); i++) {
@@ -803,14 +810,14 @@ Status ModularFrameEncoder::ComputeEncodingData(
int component =
(do_color ? 0 : 3) + ((i - gi.nb_meta_channels) % nb_chans);
// last 4 channels are final chroma residuals
- if (nb_chans > 2 && i >= gi.channel.size() - 4 && cparams.responsive) {
+ if (nb_chans > 2 && i >= gi.channel.size() - 4 && cparams_.responsive) {
component = 1;
}
- if (cparams.color_transform == ColorTransform::kXYB && component < 3) {
+ if (cparams_.color_transform == ColorTransform::kXYB && component < 3) {
q = quality * squeeze_quality_factor_xyb *
squeeze_xyb_qtable[component][shift];
} else {
- if (cparams.colorspace != 0 && component > 0 && component < 3) {
+ if (cparams_.colorspace != 0 && component > 0 && component < 3) {
q = quality * squeeze_quality_factor * squeeze_chroma_qtable[shift];
} else {
q = quality * squeeze_quality_factor * squeeze_luma_factor *
@@ -832,14 +839,14 @@ Status ModularFrameEncoder::ComputeEncodingData(
};
std::vector<GroupParams> stream_params;
- stream_options[0] = cparams.options;
+ stream_options_[0] = cparams_.options;
// DC
- for (size_t group_id = 0; group_id < frame_dim.num_dc_groups; group_id++) {
- const size_t gx = group_id % frame_dim.xsize_dc_groups;
- const size_t gy = group_id / frame_dim.xsize_dc_groups;
- const Rect rect(gx * frame_dim.dc_group_dim, gy * frame_dim.dc_group_dim,
- frame_dim.dc_group_dim, frame_dim.dc_group_dim);
+ for (size_t group_id = 0; group_id < frame_dim_.num_dc_groups; group_id++) {
+ const size_t gx = group_id % frame_dim_.xsize_dc_groups;
+ const size_t gy = group_id / frame_dim_.xsize_dc_groups;
+ const Rect rect(gx * frame_dim_.dc_group_dim, gy * frame_dim_.dc_group_dim,
+ frame_dim_.dc_group_dim, frame_dim_.dc_group_dim);
// minShift==3 because (frame_dim.dc_group_dim >> 3) == frame_dim.group_dim
// maxShift==1000 is infinity
stream_params.push_back(
@@ -847,11 +854,11 @@ Status ModularFrameEncoder::ComputeEncodingData(
}
// AC global -> nothing.
// AC
- for (size_t group_id = 0; group_id < frame_dim.num_groups; group_id++) {
- const size_t gx = group_id % frame_dim.xsize_groups;
- const size_t gy = group_id / frame_dim.xsize_groups;
- const Rect mrect(gx * frame_dim.group_dim, gy * frame_dim.group_dim,
- frame_dim.group_dim, frame_dim.group_dim);
+ for (size_t group_id = 0; group_id < frame_dim_.num_groups; group_id++) {
+ const size_t gx = group_id % frame_dim_.xsize_groups;
+ const size_t gy = group_id / frame_dim_.xsize_groups;
+ const Rect mrect(gx * frame_dim_.group_dim, gy * frame_dim_.group_dim,
+ frame_dim_.group_dim, frame_dim_.group_dim);
for (size_t i = 0; i < enc_state->progressive_splitter.GetNumPasses();
i++) {
int maxShift, minShift;
@@ -866,24 +873,24 @@ Status ModularFrameEncoder::ComputeEncodingData(
stream_params.push_back(GroupParams{Rect(0, 0, xsize, ysize), 0, 1000,
ModularStreamId::Global()});
}
- gi_channel.resize(stream_images.size());
+ gi_channel_.resize(stream_images_.size());
JXL_RETURN_IF_ERROR(RunOnPool(
pool, 0, stream_params.size(), ThreadPool::NoInit,
[&](const uint32_t i, size_t /* thread */) {
- stream_options[stream_params[i].id.ID(frame_dim)] = cparams.options;
+ stream_options_[stream_params[i].id.ID(frame_dim_)] = cparams_.options;
JXL_CHECK(PrepareStreamParams(
- stream_params[i].rect, cparams, stream_params[i].minShift,
+ stream_params[i].rect, cparams_, stream_params[i].minShift,
stream_params[i].maxShift, stream_params[i].id, do_color));
},
"ChooseParams"));
{
// Clear out channels that have been copied to groups.
- Image& full_image = stream_images[0];
+ Image& full_image = stream_images_[0];
size_t c = full_image.nb_meta_channels;
for (; c < full_image.channel.size(); c++) {
Channel& fc = full_image.channel[c];
- if (fc.w > frame_dim.group_dim || fc.h > frame_dim.group_dim) break;
+ if (fc.w > frame_dim_.group_dim || fc.h > frame_dim_.group_dim) break;
}
for (; c < full_image.channel.size(); c++) {
full_image.channel[c].plane = ImageI();
@@ -891,102 +898,100 @@ Status ModularFrameEncoder::ComputeEncodingData(
}
if (!quants.empty()) {
- for (uint32_t stream_id = 0; stream_id < stream_images.size();
+ for (uint32_t stream_id = 0; stream_id < stream_images_.size();
stream_id++) {
// skip non-modular stream_ids
- if (stream_id > 0 && gi_channel[stream_id].empty()) continue;
- Image& image = stream_images[stream_id];
- const ModularOptions& options = stream_options[stream_id];
+ if (stream_id > 0 && gi_channel_[stream_id].empty()) continue;
+ const Image& image = stream_images_[stream_id];
+ const ModularOptions& options = stream_options_[stream_id];
for (uint32_t i = image.nb_meta_channels; i < image.channel.size(); i++) {
if (i >= image.nb_meta_channels &&
(image.channel[i].w > options.max_chan_size ||
image.channel[i].h > options.max_chan_size)) {
continue;
}
- if (stream_id > 0 && gi_channel[stream_id].empty()) continue;
+ if (stream_id > 0 && gi_channel_[stream_id].empty()) continue;
size_t ch_id = stream_id == 0
? i
- : gi_channel[stream_id][i - image.nb_meta_channels];
+ : gi_channel_[stream_id][i - image.nb_meta_channels];
uint32_t q = quants[ch_id];
// Inform the tree splitting heuristics that each channel in each group
// used this quantization factor. This will produce a tree with the
// given multipliers.
- if (multiplier_info.empty() ||
- multiplier_info.back().range[1][0] != stream_id ||
- multiplier_info.back().multiplier != q) {
+ if (multiplier_info_.empty() ||
+ multiplier_info_.back().range[1][0] != stream_id ||
+ multiplier_info_.back().multiplier != q) {
StaticPropRange range;
range[0] = {{i, i + 1}};
range[1] = {{stream_id, stream_id + 1}};
- multiplier_info.push_back({range, (uint32_t)q});
+ multiplier_info_.push_back({range, (uint32_t)q});
} else {
// Previous channel in the same group had the same quantization
// factor. Don't provide two different ranges, as that creates
// unnecessary nodes.
- multiplier_info.back().range[0][1] = i + 1;
+ multiplier_info_.back().range[0][1] = i + 1;
}
}
}
// Merge group+channel settings that have the same channels and quantization
// factors, to avoid unnecessary nodes.
- std::sort(multiplier_info.begin(), multiplier_info.end(),
+ std::sort(multiplier_info_.begin(), multiplier_info_.end(),
[](ModularMultiplierInfo a, ModularMultiplierInfo b) {
return std::make_tuple(a.range, a.multiplier) <
std::make_tuple(b.range, b.multiplier);
});
size_t new_num = 1;
- for (size_t i = 1; i < multiplier_info.size(); i++) {
- ModularMultiplierInfo& prev = multiplier_info[new_num - 1];
- ModularMultiplierInfo& cur = multiplier_info[i];
+ for (size_t i = 1; i < multiplier_info_.size(); i++) {
+ ModularMultiplierInfo& prev = multiplier_info_[new_num - 1];
+ ModularMultiplierInfo& cur = multiplier_info_[i];
if (prev.range[0] == cur.range[0] && prev.multiplier == cur.multiplier &&
prev.range[1][1] == cur.range[1][0]) {
prev.range[1][1] = cur.range[1][1];
} else {
- multiplier_info[new_num++] = multiplier_info[i];
+ multiplier_info_[new_num++] = multiplier_info_[i];
}
}
- multiplier_info.resize(new_num);
+ multiplier_info_.resize(new_num);
}
- JXL_RETURN_IF_ERROR(ValidateChannelDimensions(gi, stream_options[0]));
+ JXL_RETURN_IF_ERROR(ValidateChannelDimensions(gi, stream_options_[0]));
- return PrepareEncoding(pool, enc_state->shared.frame_dim,
- enc_state->heuristics.get(), aux_out);
+ return PrepareEncoding(frame_header, pool, enc_state->heuristics.get(),
+ aux_out);
}
-Status ModularFrameEncoder::PrepareEncoding(ThreadPool* pool,
- const FrameDimensions& frame_dim,
+Status ModularFrameEncoder::PrepareEncoding(const FrameHeader& frame_header,
+ ThreadPool* pool,
EncoderHeuristics* heuristics,
AuxOut* aux_out) {
- if (!tree.empty()) return true;
+ if (!tree_.empty()) return true;
// Compute tree.
- size_t num_streams = stream_images.size();
- stream_headers.resize(num_streams);
- tokens.resize(num_streams);
+ size_t num_streams = stream_images_.size();
+ stream_headers_.resize(num_streams);
+ tokens_.resize(num_streams);
- if (heuristics->CustomFixedTreeLossless(frame_dim, &tree)) {
+ if (heuristics->CustomFixedTreeLossless(frame_dim_, &tree_)) {
// Using a fixed tree.
- } else if (cparams.speed_tier < SpeedTier::kFalcon || quality != 100 ||
- !cparams.modular_mode) {
+ } else if (cparams_.speed_tier < SpeedTier::kFalcon ||
+ !cparams_.modular_mode) {
// Avoid creating a tree with leaves that don't correspond to any pixels.
std::vector<size_t> useful_splits;
- useful_splits.reserve(tree_splits.size());
- for (size_t chunk = 0; chunk < tree_splits.size() - 1; chunk++) {
+ useful_splits.reserve(tree_splits_.size());
+ for (size_t chunk = 0; chunk < tree_splits_.size() - 1; chunk++) {
bool has_pixels = false;
- size_t start = tree_splits[chunk];
- size_t stop = tree_splits[chunk + 1];
+ size_t start = tree_splits_[chunk];
+ size_t stop = tree_splits_[chunk + 1];
for (size_t i = start; i < stop; i++) {
- for (const Channel& c : stream_images[i].channel) {
- if (c.w && c.h) has_pixels = true;
- }
+ if (!stream_images_[i].empty()) has_pixels = true;
}
if (has_pixels) {
- useful_splits.push_back(tree_splits[chunk]);
+ useful_splits.push_back(tree_splits_[chunk]);
}
}
// Don't do anything if modular mode does not have any pixels in this image
if (useful_splits.empty()) return true;
- useful_splits.push_back(tree_splits.back());
+ useful_splits.push_back(tree_splits_.back());
std::atomic_flag invalid_force_wp = ATOMIC_FLAG_INIT;
@@ -998,27 +1003,29 @@ Status ModularFrameEncoder::PrepareEncoding(ThreadPool* pool,
size_t total_pixels = 0;
uint32_t start = useful_splits[chunk];
uint32_t stop = useful_splits[chunk + 1];
+ while (start < stop && stream_images_[start].empty()) ++start;
+ while (start < stop && stream_images_[stop - 1].empty()) --stop;
uint32_t max_c = 0;
- if (stream_options[start].tree_kind !=
+ if (stream_options_[start].tree_kind !=
ModularOptions::TreeKind::kLearn) {
for (size_t i = start; i < stop; i++) {
- for (const Channel& ch : stream_images[i].channel) {
+ for (const Channel& ch : stream_images_[i].channel) {
total_pixels += ch.w * ch.h;
}
}
trees[chunk] =
- PredefinedTree(stream_options[start].tree_kind, total_pixels);
+ PredefinedTree(stream_options_[start].tree_kind, total_pixels);
return;
}
TreeSamples tree_samples;
- if (!tree_samples.SetPredictor(stream_options[start].predictor,
- stream_options[start].wp_tree_mode)) {
+ if (!tree_samples.SetPredictor(stream_options_[start].predictor,
+ stream_options_[start].wp_tree_mode)) {
invalid_force_wp.test_and_set(std::memory_order_acq_rel);
return;
}
if (!tree_samples.SetProperties(
- stream_options[start].splitting_heuristics_properties,
- stream_options[start].wp_tree_mode)) {
+ stream_options_[start].splitting_heuristics_properties,
+ stream_options_[start].wp_tree_mode)) {
invalid_force_wp.test_and_set(std::memory_order_acq_rel);
return;
}
@@ -1027,66 +1034,72 @@ Status ModularFrameEncoder::PrepareEncoding(ThreadPool* pool,
std::vector<uint32_t> group_pixel_count;
std::vector<uint32_t> channel_pixel_count;
for (size_t i = start; i < stop; i++) {
- max_c = std::max<uint32_t>(stream_images[i].channel.size(), max_c);
- CollectPixelSamples(stream_images[i], stream_options[i], i,
+ max_c = std::max<uint32_t>(stream_images_[i].channel.size(), max_c);
+ CollectPixelSamples(stream_images_[i], stream_options_[i], i,
group_pixel_count, channel_pixel_count,
pixel_samples, diff_samples);
}
StaticPropRange range;
range[0] = {{0, max_c}};
range[1] = {{start, stop}};
- auto local_multiplier_info = multiplier_info;
+ auto local_multiplier_info = multiplier_info_;
tree_samples.PreQuantizeProperties(
range, local_multiplier_info, group_pixel_count,
channel_pixel_count, pixel_samples, diff_samples,
- stream_options[start].max_property_values);
+ stream_options_[start].max_property_values);
for (size_t i = start; i < stop; i++) {
JXL_CHECK(ModularGenericCompress(
- stream_images[i], stream_options[i], /*writer=*/nullptr,
+ stream_images_[i], stream_options_[i], /*writer=*/nullptr,
/*aux_out=*/nullptr, 0, i, &tree_samples, &total_pixels));
}
// TODO(veluca): parallelize more.
trees[chunk] =
LearnTree(std::move(tree_samples), total_pixels,
- stream_options[start], local_multiplier_info, range);
+ stream_options_[start], local_multiplier_info, range);
},
"LearnTrees"));
if (invalid_force_wp.test_and_set(std::memory_order_acq_rel)) {
return JXL_FAILURE("PrepareEncoding: force_no_wp with {Weighted}");
}
- tree.clear();
- MergeTrees(trees, useful_splits, 0, useful_splits.size() - 1, &tree);
+ tree_.clear();
+ MergeTrees(trees, useful_splits, 0, useful_splits.size() - 1, &tree_);
} else {
// Fixed tree.
size_t total_pixels = 0;
- for (const Image& img : stream_images) {
+ for (const Image& img : stream_images_) {
for (const Channel& ch : img.channel) {
total_pixels += ch.w * ch.h;
}
}
- if (cparams.speed_tier <= SpeedTier::kFalcon) {
- tree = PredefinedTree(ModularOptions::TreeKind::kWPFixedDC, total_pixels);
- } else if (cparams.speed_tier <= SpeedTier::kThunder) {
- tree = PredefinedTree(ModularOptions::TreeKind::kGradientFixedDC,
- total_pixels);
+ if (cparams_.speed_tier <= SpeedTier::kFalcon) {
+ tree_ =
+ PredefinedTree(ModularOptions::TreeKind::kWPFixedDC, total_pixels);
+ } else if (cparams_.speed_tier <= SpeedTier::kThunder) {
+ tree_ = PredefinedTree(ModularOptions::TreeKind::kGradientFixedDC,
+ total_pixels);
} else {
- tree = {PropertyDecisionNode::Leaf(Predictor::Gradient)};
+ tree_ = {PropertyDecisionNode::Leaf(Predictor::Gradient)};
}
}
- tree_tokens.resize(1);
- tree_tokens[0].clear();
+ tree_tokens_.resize(1);
+ tree_tokens_[0].clear();
Tree decoded_tree;
- TokenizeTree(tree, &tree_tokens[0], &decoded_tree);
- JXL_ASSERT(tree.size() == decoded_tree.size());
- tree = std::move(decoded_tree);
+ TokenizeTree(tree_, &tree_tokens_[0], &decoded_tree);
+ JXL_ASSERT(tree_.size() == decoded_tree.size());
+ tree_ = std::move(decoded_tree);
if (WantDebugOutput(aux_out)) {
- PrintTree(tree, aux_out->debug_prefix + "/global_tree");
+ if (frame_header.dc_level > 0) {
+ PrintTree(tree_, aux_out->debug_prefix + "/dc_frame_level" +
+ std::to_string(frame_header.dc_level) + "_tree");
+ } else {
+ PrintTree(tree_, aux_out->debug_prefix + "/global_tree");
+ }
}
- image_widths.resize(num_streams);
+ image_widths_.resize(num_streams);
JXL_RETURN_IF_ERROR(RunOnPool(
pool, 0, num_streams, ThreadPool::NoInit,
[&](const uint32_t stream_id, size_t /* thread */) {
@@ -1095,15 +1108,15 @@ Status ModularFrameEncoder::PrepareEncoding(ThreadPool* pool,
my_aux_out.dump_image = aux_out->dump_image;
my_aux_out.debug_prefix = aux_out->debug_prefix;
}
- tokens[stream_id].clear();
+ tokens_[stream_id].clear();
JXL_CHECK(ModularGenericCompress(
- stream_images[stream_id], stream_options[stream_id],
+ stream_images_[stream_id], stream_options_[stream_id],
/*writer=*/nullptr, &my_aux_out, 0, stream_id,
/*tree_samples=*/nullptr,
/*total_pixels=*/nullptr,
- /*tree=*/&tree, /*header=*/&stream_headers[stream_id],
- /*tokens=*/&tokens[stream_id],
- /*widths=*/&image_widths[stream_id]));
+ /*tree=*/&tree_, /*header=*/&stream_headers_[stream_id],
+ /*tokens=*/&tokens_[stream_id],
+ /*widths=*/&image_widths_[stream_id]));
},
"ComputeTokens"));
return true;
@@ -1113,7 +1126,7 @@ Status ModularFrameEncoder::EncodeGlobalInfo(BitWriter* writer,
AuxOut* aux_out) {
BitWriter::Allotment allotment(writer, 1);
// If we are using brotli, or not using modular mode.
- if (tree_tokens.empty() || tree_tokens[0].empty()) {
+ if (tree_tokens_.empty() || tree_tokens_[0].empty()) {
writer->Write(1, 0);
ReclaimAndCharge(writer, &allotment, kLayerModularTree, aux_out);
return true;
@@ -1123,66 +1136,66 @@ Status ModularFrameEncoder::EncodeGlobalInfo(BitWriter* writer,
// Write tree
HistogramParams params;
- if (cparams.speed_tier > SpeedTier::kKitten) {
+ if (cparams_.speed_tier > SpeedTier::kKitten) {
params.clustering = HistogramParams::ClusteringType::kFast;
params.ans_histogram_strategy =
- cparams.speed_tier > SpeedTier::kThunder
+ cparams_.speed_tier > SpeedTier::kThunder
? HistogramParams::ANSHistogramStrategy::kFast
: HistogramParams::ANSHistogramStrategy::kApproximate;
params.lz77_method =
- cparams.decoding_speed_tier >= 3 && cparams.modular_mode
- ? (cparams.speed_tier >= SpeedTier::kFalcon
+ cparams_.decoding_speed_tier >= 3 && cparams_.modular_mode
+ ? (cparams_.speed_tier >= SpeedTier::kFalcon
? HistogramParams::LZ77Method::kRLE
: HistogramParams::LZ77Method::kLZ77)
: HistogramParams::LZ77Method::kNone;
// Near-lossless DC, as well as modular mode, require choosing hybrid uint
// more carefully.
if ((!extra_dc_precision.empty() && extra_dc_precision[0] != 0) ||
- (cparams.modular_mode && cparams.speed_tier < SpeedTier::kCheetah)) {
+ (cparams_.modular_mode && cparams_.speed_tier < SpeedTier::kCheetah)) {
params.uint_method = HistogramParams::HybridUintMethod::kFast;
} else {
params.uint_method = HistogramParams::HybridUintMethod::kNone;
}
- } else if (cparams.speed_tier <= SpeedTier::kTortoise) {
+ } else if (cparams_.speed_tier <= SpeedTier::kTortoise) {
params.lz77_method = HistogramParams::LZ77Method::kOptimal;
} else {
params.lz77_method = HistogramParams::LZ77Method::kLZ77;
}
- if (cparams.decoding_speed_tier >= 1) {
+ if (cparams_.decoding_speed_tier >= 1) {
params.max_histograms = 12;
}
- if (cparams.decoding_speed_tier >= 1 && cparams.responsive) {
- params.lz77_method = cparams.speed_tier >= SpeedTier::kCheetah
+ if (cparams_.decoding_speed_tier >= 1 && cparams_.responsive) {
+ params.lz77_method = cparams_.speed_tier >= SpeedTier::kCheetah
? HistogramParams::LZ77Method::kRLE
- : cparams.speed_tier >= SpeedTier::kKitten
+ : cparams_.speed_tier >= SpeedTier::kKitten
? HistogramParams::LZ77Method::kLZ77
: HistogramParams::LZ77Method::kOptimal;
}
- if (cparams.decoding_speed_tier >= 2 && cparams.responsive) {
+ if (cparams_.decoding_speed_tier >= 2 && cparams_.responsive) {
params.uint_method = HistogramParams::HybridUintMethod::k000;
params.force_huffman = true;
}
- BuildAndEncodeHistograms(params, kNumTreeContexts, tree_tokens, &code,
- &context_map, writer, kLayerModularTree, aux_out);
- WriteTokens(tree_tokens[0], code, context_map, writer, kLayerModularTree,
+ BuildAndEncodeHistograms(params, kNumTreeContexts, tree_tokens_, &code_,
+ &context_map_, writer, kLayerModularTree, aux_out);
+ WriteTokens(tree_tokens_[0], code_, context_map_, writer, kLayerModularTree,
aux_out);
- params.image_widths = image_widths;
+ params.image_widths = image_widths_;
// Write histograms.
- BuildAndEncodeHistograms(params, (tree.size() + 1) / 2, tokens, &code,
- &context_map, writer, kLayerModularGlobal, aux_out);
+ BuildAndEncodeHistograms(params, (tree_.size() + 1) / 2, tokens_, &code_,
+ &context_map_, writer, kLayerModularGlobal, aux_out);
return true;
}
Status ModularFrameEncoder::EncodeStream(BitWriter* writer, AuxOut* aux_out,
size_t layer,
const ModularStreamId& stream) {
- size_t stream_id = stream.ID(frame_dim);
- if (stream_images[stream_id].channel.empty()) {
+ size_t stream_id = stream.ID(frame_dim_);
+ if (stream_images_[stream_id].channel.empty()) {
return true; // Image with no channels, header never gets decoded.
}
JXL_RETURN_IF_ERROR(
- Bundle::Write(stream_headers[stream_id], writer, layer, aux_out));
- WriteTokens(tokens[stream_id], code, context_map, writer, layer, aux_out);
+ Bundle::Write(stream_headers_[stream_id], writer, layer, aux_out));
+ WriteTokens(tokens_[stream_id], code_, context_map_, writer, layer, aux_out);
return true;
}
@@ -1277,22 +1290,22 @@ float EstimateCost(const Image& img) {
} // namespace
Status ModularFrameEncoder::PrepareStreamParams(const Rect& rect,
- const CompressParams& cparams,
+ const CompressParams& cparams_,
int minShift, int maxShift,
const ModularStreamId& stream,
bool do_color) {
- size_t stream_id = stream.ID(frame_dim);
- Image& full_image = stream_images[0];
+ size_t stream_id = stream.ID(frame_dim_);
+ Image& full_image = stream_images_[0];
const size_t xsize = rect.xsize();
const size_t ysize = rect.ysize();
- Image& gi = stream_images[stream_id];
+ Image& gi = stream_images_[stream_id];
if (stream_id > 0) {
gi = Image(xsize, ysize, full_image.bitdepth, 0);
// start at the first bigger-than-frame_dim.group_dim non-metachannel
size_t c = full_image.nb_meta_channels;
for (; c < full_image.channel.size(); c++) {
Channel& fc = full_image.channel[c];
- if (fc.w > frame_dim.group_dim || fc.h > frame_dim.group_dim) break;
+ if (fc.w > frame_dim_.group_dim || fc.h > frame_dim_.group_dim) break;
}
for (; c < full_image.channel.size(); c++) {
Channel& fc = full_image.channel[c];
@@ -1302,7 +1315,7 @@ Status ModularFrameEncoder::PrepareStreamParams(const Rect& rect,
Rect r(rect.x0() >> fc.hshift, rect.y0() >> fc.vshift,
rect.xsize() >> fc.hshift, rect.ysize() >> fc.vshift, fc.w, fc.h);
if (r.xsize() == 0 || r.ysize() == 0) continue;
- gi_channel[stream_id].push_back(c);
+ gi_channel_[stream_id].push_back(c);
Channel gc(r.xsize(), r.ysize());
gc.hshift = fc.hshift;
gc.vshift = fc.vshift;
@@ -1319,15 +1332,15 @@ Status ModularFrameEncoder::PrepareStreamParams(const Rect& rect,
// Local palette
// TODO(veluca): make this work with quantize-after-prediction in lossy
// mode.
- if (cparams.butteraugli_distance == 0.f && cparams.palette_colors != 0 &&
- cparams.speed_tier < SpeedTier::kCheetah) {
+ if (cparams_.butteraugli_distance == 0.f && cparams_.palette_colors != 0 &&
+ cparams_.speed_tier < SpeedTier::kCheetah) {
// all-channel palette (e.g. RGBA)
if (gi.channel.size() - gi.nb_meta_channels > 1) {
Transform maybe_palette(TransformId::kPalette);
maybe_palette.begin_c = gi.nb_meta_channels;
maybe_palette.num_c = gi.channel.size() - gi.nb_meta_channels;
- maybe_palette.nb_colors = std::abs(cparams.palette_colors);
- maybe_palette.ordered_palette = cparams.palette_colors >= 0;
+ maybe_palette.nb_colors = std::abs(cparams_.palette_colors);
+ maybe_palette.ordered_palette = cparams_.palette_colors >= 0;
do_transform(gi, maybe_palette, weighted::Header());
}
// all-minus-one-channel palette (RGB with separate alpha, or CMY with
@@ -1336,9 +1349,9 @@ Status ModularFrameEncoder::PrepareStreamParams(const Rect& rect,
Transform maybe_palette_3(TransformId::kPalette);
maybe_palette_3.begin_c = gi.nb_meta_channels;
maybe_palette_3.num_c = gi.channel.size() - gi.nb_meta_channels - 1;
- maybe_palette_3.nb_colors = std::abs(cparams.palette_colors);
- maybe_palette_3.ordered_palette = cparams.palette_colors >= 0;
- maybe_palette_3.lossy_palette = cparams.lossy_palette;
+ maybe_palette_3.nb_colors = std::abs(cparams_.palette_colors);
+ maybe_palette_3.ordered_palette = cparams_.palette_colors >= 0;
+ maybe_palette_3.lossy_palette = cparams_.lossy_palette;
if (maybe_palette_3.lossy_palette) {
maybe_palette_3.predictor = Predictor::Weighted;
}
@@ -1347,14 +1360,14 @@ Status ModularFrameEncoder::PrepareStreamParams(const Rect& rect,
}
// Local channel palette
- if (cparams.channel_colors_percent > 0 &&
- cparams.butteraugli_distance == 0.f && !cparams.lossy_palette &&
- cparams.speed_tier < SpeedTier::kCheetah &&
- !(cparams.responsive && cparams.decoding_speed_tier >= 1)) {
+ if (cparams_.channel_colors_percent > 0 &&
+ cparams_.butteraugli_distance == 0.f && !cparams_.lossy_palette &&
+ cparams_.speed_tier < SpeedTier::kCheetah &&
+ !(cparams_.responsive && cparams_.decoding_speed_tier >= 1)) {
// single channel palette (like FLIF's ChannelCompact)
size_t nb_channels = gi.channel.size() - gi.nb_meta_channels;
for (size_t i = 0; i < nb_channels; i++) {
- int min, max;
+ int32_t min, max;
compute_minmax(gi.channel[gi.nb_meta_channels + i], &min, &max);
int colors = max - min + 1;
JXL_DEBUG_V(10, "Channel %" PRIuS ": range=%i..%i", i, min, max);
@@ -1367,7 +1380,7 @@ Status ModularFrameEncoder::PrepareStreamParams(const Rect& rect,
// image itself)
maybe_palette_1.nb_colors =
std::min((int)(xsize * ysize * 0.8),
- (int)(cparams.channel_colors_percent / 100. * colors));
+ (int)(cparams_.channel_colors_percent / 100. * colors));
do_transform(gi, maybe_palette_1, weighted::Header());
}
}
@@ -1375,15 +1388,15 @@ Status ModularFrameEncoder::PrepareStreamParams(const Rect& rect,
// lossless and no specific color transform specified: try Nothing, YCoCg,
// and 17 RCTs
- if (cparams.color_transform == ColorTransform::kNone &&
- cparams.IsLossless() && cparams.colorspace < 0 &&
+ if (cparams_.color_transform == ColorTransform::kNone &&
+ cparams_.IsLossless() && cparams_.colorspace < 0 &&
gi.channel.size() - gi.nb_meta_channels >= 3 &&
- cparams.responsive == false && do_color &&
- cparams.speed_tier <= SpeedTier::kHare) {
+ cparams_.responsive == false && do_color &&
+ cparams_.speed_tier <= SpeedTier::kHare) {
Transform sg(TransformId::kRCT);
sg.begin_c = gi.nb_meta_channels;
size_t nb_rcts_to_try = 0;
- switch (cparams.speed_tier) {
+ switch (cparams_.speed_tier) {
case SpeedTier::kLightning:
case SpeedTier::kThunder:
case SpeedTier::kFalcon:
@@ -1437,22 +1450,22 @@ Status ModularFrameEncoder::PrepareStreamParams(const Rect& rect,
// No need to try anything, just use the default options.
}
size_t nb_wp_modes = 1;
- if (cparams.speed_tier <= SpeedTier::kTortoise) {
+ if (cparams_.speed_tier <= SpeedTier::kTortoise) {
nb_wp_modes = 5;
- } else if (cparams.speed_tier <= SpeedTier::kKitten) {
+ } else if (cparams_.speed_tier <= SpeedTier::kKitten) {
nb_wp_modes = 2;
}
if (nb_wp_modes > 1 &&
- (stream_options[stream_id].predictor == Predictor::Weighted ||
- stream_options[stream_id].predictor == Predictor::Best ||
- stream_options[stream_id].predictor == Predictor::Variable)) {
+ (stream_options_[stream_id].predictor == Predictor::Weighted ||
+ stream_options_[stream_id].predictor == Predictor::Best ||
+ stream_options_[stream_id].predictor == Predictor::Variable)) {
float best_cost = std::numeric_limits<float>::max();
- stream_options[stream_id].wp_mode = 0;
+ stream_options_[stream_id].wp_mode = 0;
for (size_t i = 0; i < nb_wp_modes; i++) {
float cost = EstimateWPCost(gi, i);
if (cost < best_cost) {
best_cost = cost;
- stream_options[stream_id].wp_mode = i;
+ stream_options_[stream_id].wp_mode = i;
}
}
}
@@ -1492,27 +1505,28 @@ void ModularFrameEncoder::AddVarDCTDC(const Image3F& dc, size_t group_index,
extra_dc_precision[group_index] = nl_dc ? 1 : 0;
float mul = 1 << extra_dc_precision[group_index];
- size_t stream_id = ModularStreamId::VarDCTDC(group_index).ID(frame_dim);
- stream_options[stream_id].max_chan_size = 0xFFFFFF;
- stream_options[stream_id].predictor = Predictor::Weighted;
- stream_options[stream_id].wp_tree_mode = ModularOptions::TreeMode::kWPOnly;
- if (cparams.speed_tier >= SpeedTier::kSquirrel) {
- stream_options[stream_id].tree_kind = ModularOptions::TreeKind::kWPFixedDC;
- }
- if (cparams.speed_tier < SpeedTier::kSquirrel && !nl_dc) {
- stream_options[stream_id].predictor =
- (cparams.speed_tier < SpeedTier::kKitten ? Predictor::Variable
- : Predictor::Best);
- stream_options[stream_id].wp_tree_mode = ModularOptions::TreeMode::kDefault;
- stream_options[stream_id].tree_kind = ModularOptions::TreeKind::kLearn;
- }
- if (cparams.decoding_speed_tier >= 1) {
- stream_options[stream_id].tree_kind =
+ size_t stream_id = ModularStreamId::VarDCTDC(group_index).ID(frame_dim_);
+ stream_options_[stream_id].max_chan_size = 0xFFFFFF;
+ stream_options_[stream_id].predictor = Predictor::Weighted;
+ stream_options_[stream_id].wp_tree_mode = ModularOptions::TreeMode::kWPOnly;
+ if (cparams_.speed_tier >= SpeedTier::kSquirrel) {
+ stream_options_[stream_id].tree_kind = ModularOptions::TreeKind::kWPFixedDC;
+ }
+ if (cparams_.speed_tier < SpeedTier::kSquirrel && !nl_dc) {
+ stream_options_[stream_id].predictor =
+ (cparams_.speed_tier < SpeedTier::kKitten ? Predictor::Variable
+ : Predictor::Best);
+ stream_options_[stream_id].wp_tree_mode =
+ ModularOptions::TreeMode::kDefault;
+ stream_options_[stream_id].tree_kind = ModularOptions::TreeKind::kLearn;
+ }
+ if (cparams_.decoding_speed_tier >= 1) {
+ stream_options_[stream_id].tree_kind =
ModularOptions::TreeKind::kGradientFixedDC;
}
- stream_images[stream_id] = Image(r.xsize(), r.ysize(), 8, 3);
- if (nl_dc && stream_options[stream_id].tree_kind ==
+ stream_images_[stream_id] = Image(r.xsize(), r.ysize(), 8, 3);
+ if (nl_dc && stream_options_[stream_id].tree_kind ==
ModularOptions::TreeKind::kGradientFixedDC) {
JXL_ASSERT(enc_state->shared.frame_header.chroma_subsampling.Is444());
for (size_t c : {1, 0, 2}) {
@@ -1521,8 +1535,8 @@ void ModularFrameEncoder::AddVarDCTDC(const Image3F& dc, size_t group_index,
float cfl_factor = enc_state->shared.cmap.DCFactors()[c];
for (size_t y = 0; y < r.ysize(); y++) {
int32_t* quant_row =
- stream_images[stream_id].channel[c < 2 ? c ^ 1 : c].plane.Row(y);
- size_t stride = stream_images[stream_id]
+ stream_images_[stream_id].channel[c < 2 ? c ^ 1 : c].plane.Row(y);
+ size_t stride = stream_images_[stream_id]
.channel[c < 2 ? c ^ 1 : c]
.plane.PixelsPerRow();
const float* row = r.ConstPlaneRow(dc, c, y);
@@ -1533,7 +1547,7 @@ void ModularFrameEncoder::AddVarDCTDC(const Image3F& dc, size_t group_index,
}
} else {
int32_t* quant_row_y =
- stream_images[stream_id].channel[0].plane.Row(y);
+ stream_images_[stream_id].channel[0].plane.Row(y);
for (size_t x = 0; x < r.xsize(); x++) {
quant_row[x] = QuantizeGradient(
quant_row, stride, c, x, y, r.xsize(),
@@ -1552,8 +1566,8 @@ void ModularFrameEncoder::AddVarDCTDC(const Image3F& dc, size_t group_index,
weighted::State wp_state(header, r.xsize(), r.ysize());
for (size_t y = 0; y < r.ysize(); y++) {
int32_t* quant_row =
- stream_images[stream_id].channel[c < 2 ? c ^ 1 : c].plane.Row(y);
- size_t stride = stream_images[stream_id]
+ stream_images_[stream_id].channel[c < 2 ? c ^ 1 : c].plane.Row(y);
+ size_t stride = stream_images_[stream_id]
.channel[c < 2 ? c ^ 1 : c]
.plane.PixelsPerRow();
const float* row = r.ConstPlaneRow(dc, c, y);
@@ -1565,7 +1579,7 @@ void ModularFrameEncoder::AddVarDCTDC(const Image3F& dc, size_t group_index,
}
} else {
int32_t* quant_row_y =
- stream_images[stream_id].channel[0].plane.Row(y);
+ stream_images_[stream_id].channel[0].plane.Row(y);
for (size_t x = 0; x < r.xsize(); x++) {
quant_row[x] = QuantizeWP(
quant_row, stride, c, x, y, r.xsize(), &wp_state,
@@ -1582,7 +1596,7 @@ void ModularFrameEncoder::AddVarDCTDC(const Image3F& dc, size_t group_index,
float cfl_factor = enc_state->shared.cmap.DCFactors()[c];
for (size_t y = 0; y < r.ysize(); y++) {
int32_t* quant_row =
- stream_images[stream_id].channel[c < 2 ? c ^ 1 : c].plane.Row(y);
+ stream_images_[stream_id].channel[c < 2 ? c ^ 1 : c].plane.Row(y);
const float* row = r.ConstPlaneRow(dc, c, y);
if (c == 1) {
for (size_t x = 0; x < r.xsize(); x++) {
@@ -1590,7 +1604,7 @@ void ModularFrameEncoder::AddVarDCTDC(const Image3F& dc, size_t group_index,
}
} else {
int32_t* quant_row_y =
- stream_images[stream_id].channel[0].plane.Row(y);
+ stream_images_[stream_id].channel[0].plane.Row(y);
for (size_t x = 0; x < r.xsize(); x++) {
quant_row[x] =
roundf((row[x] - quant_row_y[x] * (y_factor * cfl_factor)) *
@@ -1611,7 +1625,7 @@ void ModularFrameEncoder::AddVarDCTDC(const Image3F& dc, size_t group_index,
float inv_factor = enc_state->shared.quantizer.GetInvDcStep(c) * mul;
size_t ys = rect.ysize();
size_t xs = rect.xsize();
- Channel& ch = stream_images[stream_id].channel[c < 2 ? c ^ 1 : c];
+ Channel& ch = stream_images_[stream_id].channel[c < 2 ? c ^ 1 : c];
ch.w = xs;
ch.h = ys;
ch.shrink();
@@ -1626,7 +1640,7 @@ void ModularFrameEncoder::AddVarDCTDC(const Image3F& dc, size_t group_index,
}
DequantDC(r, &enc_state->shared.dc_storage, &enc_state->shared.quant_dc,
- stream_images[stream_id], enc_state->shared.quantizer.MulDC(),
+ stream_images_[stream_id], enc_state->shared.quantizer.MulDC(),
1.0 / mul, enc_state->shared.cmap.DCFactors(),
enc_state->shared.frame_header.chroma_subsampling,
enc_state->shared.block_ctx_map);
@@ -1635,26 +1649,26 @@ void ModularFrameEncoder::AddVarDCTDC(const Image3F& dc, size_t group_index,
void ModularFrameEncoder::AddACMetadata(size_t group_index, bool jpeg_transcode,
PassesEncoderState* enc_state) {
const Rect r = enc_state->shared.DCGroupRect(group_index);
- size_t stream_id = ModularStreamId::ACMetadata(group_index).ID(frame_dim);
- stream_options[stream_id].max_chan_size = 0xFFFFFF;
- stream_options[stream_id].wp_tree_mode = ModularOptions::TreeMode::kNoWP;
+ size_t stream_id = ModularStreamId::ACMetadata(group_index).ID(frame_dim_);
+ stream_options_[stream_id].max_chan_size = 0xFFFFFF;
+ stream_options_[stream_id].wp_tree_mode = ModularOptions::TreeMode::kNoWP;
if (jpeg_transcode) {
- stream_options[stream_id].tree_kind =
+ stream_options_[stream_id].tree_kind =
ModularOptions::TreeKind::kJpegTranscodeACMeta;
- } else if (cparams.speed_tier >= SpeedTier::kFalcon) {
- stream_options[stream_id].tree_kind =
+ } else if (cparams_.speed_tier >= SpeedTier::kFalcon) {
+ stream_options_[stream_id].tree_kind =
ModularOptions::TreeKind::kFalconACMeta;
- } else if (cparams.speed_tier > SpeedTier::kKitten) {
- stream_options[stream_id].tree_kind = ModularOptions::TreeKind::kACMeta;
+ } else if (cparams_.speed_tier > SpeedTier::kKitten) {
+ stream_options_[stream_id].tree_kind = ModularOptions::TreeKind::kACMeta;
}
// If we are using a non-constant CfL field, and are in a slow enough mode,
// re-enable tree computation for it.
- if (cparams.speed_tier < SpeedTier::kSquirrel &&
- cparams.force_cfl_jpeg_recompression) {
- stream_options[stream_id].tree_kind = ModularOptions::TreeKind::kLearn;
+ if (cparams_.speed_tier < SpeedTier::kSquirrel &&
+ cparams_.force_cfl_jpeg_recompression) {
+ stream_options_[stream_id].tree_kind = ModularOptions::TreeKind::kLearn;
}
// YToX, YToB, ACS + QF, EPF
- Image& image = stream_images[stream_id];
+ Image& image = stream_images_[stream_id];
image = Image(r.xsize(), r.ysize(), 8, 4);
static_assert(kColorTileDimInBlocks == 8, "Color tile size changed");
Rect cr(r.x0() >> 3, r.y0() >> 3, (r.xsize() + 7) >> 3, (r.ysize() + 7) >> 3);
@@ -1668,11 +1682,11 @@ void ModularFrameEncoder::AddACMetadata(size_t group_index, bool jpeg_transcode,
size_t num = 0;
for (size_t y = 0; y < r.ysize(); y++) {
AcStrategyRow row_acs = enc_state->shared.ac_strategy.ConstRow(r, y);
- const int* row_qf = r.ConstRow(enc_state->shared.raw_quant_field, y);
+ const int32_t* row_qf = r.ConstRow(enc_state->shared.raw_quant_field, y);
const uint8_t* row_epf = r.ConstRow(enc_state->shared.epf_sharpness, y);
- int* out_acs = image.channel[2].plane.Row(0);
- int* out_qf = image.channel[2].plane.Row(1);
- int* row_out_epf = image.channel[3].plane.Row(y);
+ int32_t* out_acs = image.channel[2].plane.Row(0);
+ int32_t* out_qf = image.channel[2].plane.Row(1);
+ int32_t* row_out_epf = image.channel[3].plane.Row(y);
for (size_t x = 0; x < r.xsize(); x++) {
row_out_epf[x] = row_epf[x];
if (!row_acs[x].IsFirstBlock()) continue;
@@ -1700,7 +1714,7 @@ void ModularFrameEncoder::EncodeQuantTable(
Image image(size_x, size_y, 8, 3);
for (size_t c = 0; c < 3; c++) {
for (size_t y = 0; y < size_y; y++) {
- int* JXL_RESTRICT row = image.channel[c].Row(y);
+ int32_t* JXL_RESTRICT row = image.channel[c].Row(y);
for (size_t x = 0; x < size_x; x++) {
row[x] = (*encoding.qraw.qtable)[c * size_x * size_y + y * size_x + x];
}
@@ -1713,14 +1727,14 @@ void ModularFrameEncoder::EncodeQuantTable(
void ModularFrameEncoder::AddQuantTable(size_t size_x, size_t size_y,
const QuantEncoding& encoding,
size_t idx) {
- size_t stream_id = ModularStreamId::QuantTable(idx).ID(frame_dim);
+ size_t stream_id = ModularStreamId::QuantTable(idx).ID(frame_dim_);
JXL_ASSERT(encoding.qraw.qtable != nullptr);
JXL_ASSERT(size_x * size_y * 3 == encoding.qraw.qtable->size());
- Image& image = stream_images[stream_id];
+ Image& image = stream_images_[stream_id];
image = Image(size_x, size_y, 8, 3);
for (size_t c = 0; c < 3; c++) {
for (size_t y = 0; y < size_y; y++) {
- int* JXL_RESTRICT row = image.channel[c].Row(y);
+ int32_t* JXL_RESTRICT row = image.channel[c].Row(y);
for (size_t x = 0; x < size_x; x++) {
row[x] = (*encoding.qraw.qtable)[c * size_x * size_y + y * size_x + x];
}
diff --git a/media/libjxl/src/lib/jxl/enc_modular.h b/media/libjxl/src/lib/jxl/enc_modular.h
index 10287dec88..02477ee65d 100644
--- a/media/libjxl/src/lib/jxl/enc_modular.h
+++ b/media/libjxl/src/lib/jxl/enc_modular.h
@@ -63,29 +63,28 @@ class ModularFrameEncoder {
std::vector<uint8_t> extra_dc_precision;
private:
- Status PrepareEncoding(ThreadPool* pool, const FrameDimensions& frame_dim,
+ Status PrepareEncoding(const FrameHeader& frame_header, ThreadPool* pool,
EncoderHeuristics* heuristics,
AuxOut* aux_out = nullptr);
Status PrepareStreamParams(const Rect& rect, const CompressParams& cparams,
int minShift, int maxShift,
const ModularStreamId& stream, bool do_color);
- std::vector<Image> stream_images;
- std::vector<ModularOptions> stream_options;
+ std::vector<Image> stream_images_;
+ std::vector<ModularOptions> stream_options_;
- Tree tree;
- std::vector<std::vector<Token>> tree_tokens;
- std::vector<GroupHeader> stream_headers;
- std::vector<std::vector<Token>> tokens;
- EntropyEncodingData code;
- std::vector<uint8_t> context_map;
- FrameDimensions frame_dim;
- CompressParams cparams;
- float quality = 100.f;
- std::vector<size_t> tree_splits;
- std::vector<ModularMultiplierInfo> multiplier_info;
- std::vector<std::vector<uint32_t>> gi_channel;
- std::vector<size_t> image_widths;
- Predictor delta_pred = Predictor::Average4;
+ Tree tree_;
+ std::vector<std::vector<Token>> tree_tokens_;
+ std::vector<GroupHeader> stream_headers_;
+ std::vector<std::vector<Token>> tokens_;
+ EntropyEncodingData code_;
+ std::vector<uint8_t> context_map_;
+ FrameDimensions frame_dim_;
+ CompressParams cparams_;
+ std::vector<size_t> tree_splits_;
+ std::vector<ModularMultiplierInfo> multiplier_info_;
+ std::vector<std::vector<uint32_t>> gi_channel_;
+ std::vector<size_t> image_widths_;
+ Predictor delta_pred_ = Predictor::Average4;
};
} // namespace jxl
diff --git a/media/libjxl/src/lib/jxl/enc_params.h b/media/libjxl/src/lib/jxl/enc_params.h
index 9445817f94..2e16fae864 100644
--- a/media/libjxl/src/lib/jxl/enc_params.h
+++ b/media/libjxl/src/lib/jxl/enc_params.h
@@ -202,6 +202,7 @@ struct CompressParams {
// Prints extra information during/after encoding.
bool verbose = false;
+ bool log_search_state = false;
ButteraugliParams ba_params;
@@ -242,8 +243,6 @@ struct CompressParams {
color_transform = jxl::ColorTransform::kNone;
}
- bool use_new_heuristics = false;
-
// Down/upsample the image before encoding / after decoding by this factor.
// The resampling value can also be set to <= 0 to automatically choose based
// on distance, however EncodeFrame doesn't support this, so it is
@@ -253,6 +252,11 @@ struct CompressParams {
int ec_resampling = -1;
// Skip the downsampling before encoding if this is true.
bool already_downsampled = false;
+ // Butteraugli target distance on the original full size image, this can be
+ // different from butteraugli_distance if resampling was used.
+ float original_butteraugli_distance = -1.0f;
+
+ float quant_ac_rescale = 1.0;
// Codestream level to conform to.
// -1: don't care
diff --git a/media/libjxl/src/lib/jxl/enc_patch_dictionary.cc b/media/libjxl/src/lib/jxl/enc_patch_dictionary.cc
index 69c2423083..ff57ff049e 100644
--- a/media/libjxl/src/lib/jxl/enc_patch_dictionary.cc
+++ b/media/libjxl/src/lib/jxl/enc_patch_dictionary.cc
@@ -10,6 +10,7 @@
#include <sys/types.h>
#include <algorithm>
+#include <atomic>
#include <string>
#include <tuple>
#include <utility>
@@ -44,35 +45,37 @@ void PatchDictionaryEncoder::Encode(const PatchDictionary& pdic,
AuxOut* aux_out) {
JXL_ASSERT(pdic.HasAny());
std::vector<std::vector<Token>> tokens(1);
+ size_t num_ec = pdic.shared_->metadata->m.num_extra_channels;
auto add_num = [&](int context, size_t num) {
tokens[0].emplace_back(context, num);
};
size_t num_ref_patch = 0;
for (size_t i = 0; i < pdic.positions_.size();) {
- size_t i_start = i;
+ size_t ref_pos_idx = pdic.positions_[i].ref_pos_idx;
while (i < pdic.positions_.size() &&
- pdic.positions_[i].ref_pos == pdic.positions_[i_start].ref_pos) {
+ pdic.positions_[i].ref_pos_idx == ref_pos_idx) {
i++;
}
num_ref_patch++;
}
add_num(kNumRefPatchContext, num_ref_patch);
+ size_t blend_pos = 0;
for (size_t i = 0; i < pdic.positions_.size();) {
size_t i_start = i;
+ size_t ref_pos_idx = pdic.positions_[i].ref_pos_idx;
+ const auto& ref_pos = pdic.ref_positions_[ref_pos_idx];
while (i < pdic.positions_.size() &&
- pdic.positions_[i].ref_pos == pdic.positions_[i_start].ref_pos) {
+ pdic.positions_[i].ref_pos_idx == ref_pos_idx) {
i++;
}
size_t num = i - i_start;
JXL_ASSERT(num > 0);
- add_num(kReferenceFrameContext, pdic.positions_[i_start].ref_pos.ref);
- add_num(kPatchReferencePositionContext,
- pdic.positions_[i_start].ref_pos.x0);
- add_num(kPatchReferencePositionContext,
- pdic.positions_[i_start].ref_pos.y0);
- add_num(kPatchSizeContext, pdic.positions_[i_start].ref_pos.xsize - 1);
- add_num(kPatchSizeContext, pdic.positions_[i_start].ref_pos.ysize - 1);
+ add_num(kReferenceFrameContext, ref_pos.ref);
+ add_num(kPatchReferencePositionContext, ref_pos.x0);
+ add_num(kPatchReferencePositionContext, ref_pos.y0);
+ add_num(kPatchSizeContext, ref_pos.xsize - 1);
+ add_num(kPatchSizeContext, ref_pos.ysize - 1);
add_num(kPatchCountContext, num - 1);
for (size_t j = i_start; j < i; j++) {
const PatchPosition& pos = pdic.positions_[j];
@@ -85,11 +88,8 @@ void PatchDictionaryEncoder::Encode(const PatchDictionary& pdic,
add_num(kPatchOffsetContext,
PackSigned(pos.y - pdic.positions_[j - 1].y));
}
- JXL_ASSERT(pdic.shared_->metadata->m.extra_channel_info.size() + 1 ==
- pos.blending.size());
- for (size_t i = 0;
- i < pdic.shared_->metadata->m.extra_channel_info.size() + 1; i++) {
- const PatchBlending& info = pos.blending[i];
+ for (size_t j = 0; j < num_ec + 1; ++j, ++blend_pos) {
+ const PatchBlending& info = pdic.blendings_[blend_pos];
add_num(kPatchBlendModeContext, static_cast<uint32_t>(info.mode));
if (UsesAlpha(info.mode) &&
pdic.shared_->metadata->m.extra_channel_info.size() > 1) {
@@ -113,46 +113,48 @@ void PatchDictionaryEncoder::Encode(const PatchDictionary& pdic,
// static
void PatchDictionaryEncoder::SubtractFrom(const PatchDictionary& pdic,
Image3F* opsin) {
+ size_t num_ec = pdic.shared_->metadata->m.num_extra_channels;
// TODO(veluca): this can likely be optimized knowing it runs on full images.
for (size_t y = 0; y < opsin->ysize(); y++) {
- if (y + 1 >= pdic.patch_starts_.size()) continue;
float* JXL_RESTRICT rows[3] = {
opsin->PlaneRow(0, y),
opsin->PlaneRow(1, y),
opsin->PlaneRow(2, y),
};
- for (size_t id = pdic.patch_starts_[y]; id < pdic.patch_starts_[y + 1];
- id++) {
- const PatchPosition& pos = pdic.positions_[pdic.sorted_patches_[id]];
+ for (size_t pos_idx : pdic.GetPatchesForRow(y)) {
+ const size_t blending_idx = pos_idx * (num_ec + 1);
+ const PatchPosition& pos = pdic.positions_[pos_idx];
+ const PatchReferencePosition& ref_pos =
+ pdic.ref_positions_[pos.ref_pos_idx];
+ const PatchBlendMode mode = pdic.blendings_[blending_idx].mode;
size_t by = pos.y;
size_t bx = pos.x;
- size_t xsize = pos.ref_pos.xsize;
+ size_t xsize = ref_pos.xsize;
JXL_DASSERT(y >= by);
- JXL_DASSERT(y < by + pos.ref_pos.ysize);
+ JXL_DASSERT(y < by + ref_pos.ysize);
size_t iy = y - by;
- size_t ref = pos.ref_pos.ref;
+ size_t ref = ref_pos.ref;
const float* JXL_RESTRICT ref_rows[3] = {
pdic.shared_->reference_frames[ref].frame->color()->ConstPlaneRow(
- 0, pos.ref_pos.y0 + iy) +
- pos.ref_pos.x0,
+ 0, ref_pos.y0 + iy) +
+ ref_pos.x0,
pdic.shared_->reference_frames[ref].frame->color()->ConstPlaneRow(
- 1, pos.ref_pos.y0 + iy) +
- pos.ref_pos.x0,
+ 1, ref_pos.y0 + iy) +
+ ref_pos.x0,
pdic.shared_->reference_frames[ref].frame->color()->ConstPlaneRow(
- 2, pos.ref_pos.y0 + iy) +
- pos.ref_pos.x0,
+ 2, ref_pos.y0 + iy) +
+ ref_pos.x0,
};
for (size_t ix = 0; ix < xsize; ix++) {
for (size_t c = 0; c < 3; c++) {
- if (pos.blending[0].mode == PatchBlendMode::kAdd) {
+ if (mode == PatchBlendMode::kAdd) {
rows[c][bx + ix] -= ref_rows[c][ix];
- } else if (pos.blending[0].mode == PatchBlendMode::kReplace) {
+ } else if (mode == PatchBlendMode::kReplace) {
rows[c][bx + ix] = 0;
- } else if (pos.blending[0].mode == PatchBlendMode::kNone) {
+ } else if (mode == PatchBlendMode::kNone) {
// Nothing to do.
} else {
- JXL_ABORT("Blending mode %u not yet implemented",
- (uint32_t)pos.blending[0].mode);
+ JXL_ABORT("Blending mode %u not yet implemented", (uint32_t)mode);
}
}
}
@@ -674,12 +676,15 @@ void FindBestPatchDictionary(const Image3F& opsin,
// TODO(veluca): figure out a better way to fill the image.
ZeroFillImage(&reference_frame);
std::vector<PatchPosition> positions;
+ std::vector<PatchReferencePosition> pref_positions;
+ std::vector<PatchBlending> blendings;
float* JXL_RESTRICT ref_rows[3] = {
reference_frame.PlaneRow(0, 0),
reference_frame.PlaneRow(1, 0),
reference_frame.PlaneRow(2, 0),
};
size_t ref_stride = reference_frame.PixelsPerRow();
+ size_t num_ec = state->shared.metadata->m.num_extra_channels;
for (size_t i = 0; i < info.size(); i++) {
PatchReferencePosition ref_pos;
@@ -696,35 +701,37 @@ void FindBestPatchDictionary(const Image3F& opsin,
}
}
}
- // Add color channels, ignore other channels.
- std::vector<PatchBlending> blending_info(
- state->shared.metadata->m.extra_channel_info.size() + 1,
- PatchBlending{PatchBlendMode::kNone, 0, false});
- blending_info[0].mode = PatchBlendMode::kAdd;
for (const auto& pos : info[i].second) {
positions.emplace_back(
- PatchPosition{pos.first, pos.second, blending_info, ref_pos});
+ PatchPosition{pos.first, pos.second, pref_positions.size()});
+ // Add blending for color channels, ignore other channels.
+ blendings.push_back({PatchBlendMode::kAdd, 0, false});
+ for (size_t j = 0; j < num_ec; ++j) {
+ blendings.push_back({PatchBlendMode::kNone, 0, false});
+ }
}
+ pref_positions.emplace_back(std::move(ref_pos));
}
CompressParams cparams = state->cparams;
// Recursive application of patches could create very weird issues.
cparams.patches = Override::kOff;
- RoundtripPatchFrame(&reference_frame, state, 0, cparams, cms, pool, true);
+ RoundtripPatchFrame(&reference_frame, state, 0, cparams, cms, pool, aux_out,
+ /*subtract=*/true);
// TODO(veluca): this assumes that applying patches is commutative, which is
// not true for all blending modes. This code only produces kAdd patches, so
// this works out.
- std::sort(positions.begin(), positions.end());
- PatchDictionaryEncoder::SetPositions(&state->shared.image_features.patches,
- std::move(positions));
+ PatchDictionaryEncoder::SetPositions(
+ &state->shared.image_features.patches, std::move(positions),
+ std::move(pref_positions), std::move(blendings));
}
void RoundtripPatchFrame(Image3F* reference_frame,
PassesEncoderState* JXL_RESTRICT state, int idx,
CompressParams& cparams, const JxlCmsInterface& cms,
- ThreadPool* pool, bool subtract) {
+ ThreadPool* pool, AuxOut* aux_out, bool subtract) {
FrameInfo patch_frame_info;
cparams.resampling = 1;
cparams.ec_resampling = 1;
@@ -763,29 +770,36 @@ void RoundtripPatchFrame(Image3F* reference_frame,
}
PassesEncoderState roundtrip_state;
auto special_frame = std::unique_ptr<BitWriter>(new BitWriter());
+ AuxOut patch_aux_out;
JXL_CHECK(EncodeFrame(cparams, patch_frame_info, state->shared.metadata, ib,
&roundtrip_state, cms, pool, special_frame.get(),
- nullptr));
+ aux_out ? &patch_aux_out : nullptr));
+ if (aux_out) {
+ for (const auto& l : patch_aux_out.layers) {
+ aux_out->layers[kLayerDictionary].Assimilate(l);
+ }
+ }
const Span<const uint8_t> encoded = special_frame->GetSpan();
state->special_frames.emplace_back(std::move(special_frame));
if (subtract) {
- BitReader br(encoded);
ImageBundle decoded(&state->shared.metadata->m);
PassesDecoderState dec_state;
- JXL_CHECK(dec_state.output_encoding_info.Set(
- *state->shared.metadata,
- ColorEncoding::LinearSRGB(
- state->shared.metadata->m.color_encoding.IsGray())));
- JXL_CHECK(DecodeFrame({}, &dec_state, pool, &br, &decoded,
- *state->shared.metadata, /*constraints=*/nullptr));
+ JXL_CHECK(dec_state.output_encoding_info.SetFromMetadata(
+ *state->shared.metadata));
+ const uint8_t* frame_start = encoded.data();
+ size_t encoded_size = encoded.size();
+ JXL_CHECK(DecodeFrame(&dec_state, pool, frame_start, encoded_size, &decoded,
+ *state->shared.metadata));
+ frame_start += decoded.decoded_bytes();
+ encoded_size -= decoded.decoded_bytes();
size_t ref_xsize =
dec_state.shared_storage.reference_frames[idx].storage.color()->xsize();
// if the frame itself uses patches, we need to decode another frame
if (!ref_xsize) {
- JXL_CHECK(DecodeFrame({}, &dec_state, pool, &br, &decoded,
- *state->shared.metadata, /*constraints=*/nullptr));
+ JXL_CHECK(DecodeFrame(&dec_state, pool, frame_start, encoded_size,
+ &decoded, *state->shared.metadata));
}
- JXL_CHECK(br.Close());
+ JXL_CHECK(encoded_size == 0);
state->shared.reference_frames[idx] =
std::move(dec_state.shared_storage.reference_frames[idx]);
} else {
diff --git a/media/libjxl/src/lib/jxl/enc_patch_dictionary.h b/media/libjxl/src/lib/jxl/enc_patch_dictionary.h
index 8bacf7589a..090827f680 100644
--- a/media/libjxl/src/lib/jxl/enc_patch_dictionary.h
+++ b/media/libjxl/src/lib/jxl/enc_patch_dictionary.h
@@ -81,14 +81,13 @@ class PatchDictionaryEncoder {
size_t layer, AuxOut* aux_out);
static void SetPositions(PatchDictionary* pdic,
- std::vector<PatchPosition> positions) {
- if (pdic->positions_.empty()) {
- pdic->positions_ = std::move(positions);
- } else {
- pdic->positions_.insert(pdic->positions_.end(), positions.begin(),
- positions.end());
- }
- pdic->ComputePatchCache();
+ std::vector<PatchPosition> positions,
+ std::vector<PatchReferencePosition> ref_positions,
+ std::vector<PatchBlending> blendings) {
+ pdic->positions_ = std::move(positions);
+ pdic->ref_positions_ = std::move(ref_positions);
+ pdic->blendings_ = std::move(blendings);
+ pdic->ComputePatchTree();
}
static void SubtractFrom(const PatchDictionary& pdic, Image3F* opsin);
@@ -102,7 +101,7 @@ void FindBestPatchDictionary(const Image3F& opsin,
void RoundtripPatchFrame(Image3F* reference_frame,
PassesEncoderState* JXL_RESTRICT state, int idx,
CompressParams& cparams, const JxlCmsInterface& cms,
- ThreadPool* pool, bool subtract);
+ ThreadPool* pool, AuxOut* aux_out, bool subtract);
} // namespace jxl
diff --git a/media/libjxl/src/lib/jxl/enc_photon_noise_test.cc b/media/libjxl/src/lib/jxl/enc_photon_noise_test.cc
index 3790fdee99..83707255de 100644
--- a/media/libjxl/src/lib/jxl/enc_photon_noise_test.cc
+++ b/media/libjxl/src/lib/jxl/enc_photon_noise_test.cc
@@ -5,7 +5,7 @@
#include "lib/jxl/enc_photon_noise.h"
-#include "gmock/gmock.h"
+#include "lib/jxl/test_utils.h"
namespace jxl {
namespace {
diff --git a/media/libjxl/src/lib/jxl/enc_quant_weights.cc b/media/libjxl/src/lib/jxl/enc_quant_weights.cc
index 33d0e47bae..d8a9931a54 100644
--- a/media/libjxl/src/lib/jxl/enc_quant_weights.cc
+++ b/media/libjxl/src/lib/jxl/enc_quant_weights.cc
@@ -105,10 +105,9 @@ Status EncodeQuant(const QuantEncoding& encoding, size_t idx, size_t size_x,
JXL_RETURN_IF_ERROR(F16Coder::Write(
encoding.afv_weights[c][i] * (i < 6 ? 1.0f / 64 : 1.0f), writer));
}
- JXL_RETURN_IF_ERROR(EncodeDctParams(encoding.dct_params, writer));
- JXL_RETURN_IF_ERROR(
- EncodeDctParams(encoding.dct_params_afv_4x4, writer));
}
+ JXL_RETURN_IF_ERROR(EncodeDctParams(encoding.dct_params, writer));
+ JXL_RETURN_IF_ERROR(EncodeDctParams(encoding.dct_params_afv_4x4, writer));
break;
}
}
@@ -176,6 +175,14 @@ void DequantMatricesSetCustomDC(DequantMatrices* matrices, const float* dc) {
JXL_CHECK(br.Close());
}
+void DequantMatricesScaleDC(DequantMatrices* matrices, const float scale) {
+ float dc[3];
+ for (size_t c = 0; c < 3; ++c) {
+ dc[c] = matrices->InvDCQuant(c) * (1.0f / scale);
+ }
+ DequantMatricesSetCustomDC(matrices, dc);
+}
+
void DequantMatricesSetCustom(DequantMatrices* matrices,
const std::vector<QuantEncoding>& encodings,
ModularFrameEncoder* encoder) {
diff --git a/media/libjxl/src/lib/jxl/enc_quant_weights.h b/media/libjxl/src/lib/jxl/enc_quant_weights.h
index 89033d8cbb..fe5273cf78 100644
--- a/media/libjxl/src/lib/jxl/enc_quant_weights.h
+++ b/media/libjxl/src/lib/jxl/enc_quant_weights.h
@@ -20,6 +20,8 @@ Status DequantMatricesEncodeDC(const DequantMatrices* matrices,
// precision.
void DequantMatricesSetCustomDC(DequantMatrices* matrices, const float* dc);
+void DequantMatricesScaleDC(DequantMatrices* matrices, const float scale);
+
void DequantMatricesSetCustom(DequantMatrices* matrices,
const std::vector<QuantEncoding>& encodings,
ModularFrameEncoder* encoder);
diff --git a/media/libjxl/src/lib/jxl/enc_xyb.cc b/media/libjxl/src/lib/jxl/enc_xyb.cc
index 7421361cf8..577e29686b 100644
--- a/media/libjxl/src/lib/jxl/enc_xyb.cc
+++ b/media/libjxl/src/lib/jxl/enc_xyb.cc
@@ -22,79 +22,23 @@
#include "lib/jxl/color_management.h"
#include "lib/jxl/enc_bit_writer.h"
#include "lib/jxl/enc_image_bundle.h"
+#include "lib/jxl/fast_math-inl.h"
#include "lib/jxl/fields.h"
#include "lib/jxl/image_bundle.h"
#include "lib/jxl/image_ops.h"
#include "lib/jxl/opsin_params.h"
#include "lib/jxl/transfer_functions-inl.h"
+
HWY_BEFORE_NAMESPACE();
namespace jxl {
namespace HWY_NAMESPACE {
// These templates are not found via ADL.
-using hwy::HWY_NAMESPACE::ShiftRight;
-
-// Returns cbrt(x) + add with 6 ulp max error.
-// Modified from vectormath_exp.h, Apache 2 license.
-// https://www.agner.org/optimize/vectorclass.zip
-template <class V>
-V CubeRootAndAdd(const V x, const V add) {
- const HWY_FULL(float) df;
- const HWY_FULL(int32_t) di;
-
- const auto kExpBias = Set(di, 0x54800000); // cast(1.) + cast(1.) / 3
- const auto kExpMul = Set(di, 0x002AAAAA); // shifted 1/3
- const auto k1_3 = Set(df, 1.0f / 3);
- const auto k4_3 = Set(df, 4.0f / 3);
-
- const auto xa = x; // assume inputs never negative
- const auto xa_3 = k1_3 * xa;
-
- // Multiply exponent by -1/3
- const auto m1 = BitCast(di, xa);
- // Special case for 0. 0 is represented with an exponent of 0, so the
- // "kExpBias - 1/3 * exp" below gives the wrong result. The IfThenZeroElse()
- // sets those values as 0, which prevents having NaNs in the computations
- // below.
- const auto m2 =
- IfThenZeroElse(m1 == Zero(di), kExpBias - (ShiftRight<23>(m1)) * kExpMul);
- auto r = BitCast(df, m2);
-
- // Newton-Raphson iterations
- for (int i = 0; i < 3; i++) {
- const auto r2 = r * r;
- r = NegMulAdd(xa_3, r2 * r2, k4_3 * r);
- }
- // Final iteration
- auto r2 = r * r;
- r = MulAdd(k1_3, NegMulAdd(xa, r2 * r2, r), r);
- r2 = r * r;
- r = MulAdd(r2, x, add);
-
- return r;
-}
-
-// Ensures infinity norm is bounded.
-void TestCubeRoot() {
- const HWY_FULL(float) d;
- float max_err = 0.0f;
- for (uint64_t x5 = 0; x5 < 2000000; x5++) {
- const float x = x5 * 1E-5f;
- const float expected = cbrtf(x);
- HWY_ALIGN float approx[MaxLanes(d)];
- Store(CubeRootAndAdd(Set(d, x), Zero(d)), d, approx);
-
- // All lanes are same
- for (size_t i = 1; i < Lanes(d); ++i) {
- JXL_ASSERT(std::abs(approx[0] - approx[i]) <= 1.2E-7f);
- }
-
- const float err = std::abs(approx[0] - expected);
- max_err = std::max(max_err, err);
- }
- // printf("max err %e\n", max_err);
- JXL_ASSERT(max_err < 8E-7f);
-}
+using hwy::HWY_NAMESPACE::Add;
+using hwy::HWY_NAMESPACE::Mul;
+using hwy::HWY_NAMESPACE::MulAdd;
+using hwy::HWY_NAMESPACE::Sub;
+using hwy::HWY_NAMESPACE::ZeroIfNegative;
// 4x3 matrix * 3x1 SIMD vectors
template <class V>
@@ -124,8 +68,8 @@ void StoreXYB(const V r, V g, const V b, float* JXL_RESTRICT valx,
float* JXL_RESTRICT valy, float* JXL_RESTRICT valz) {
const HWY_FULL(float) d;
const V half = Set(d, 0.5f);
- Store(half * (r - g), d, valx);
- Store(half * (r + g), d, valy);
+ Store(Mul(half, Sub(r, g)), d, valx);
+ Store(Mul(half, Add(r, g)), d, valy);
Store(b, d, valz);
}
@@ -354,10 +298,10 @@ Status RgbToYcbcr(const ImageF& r_plane, const ImageF& g_plane,
const auto kB = Set(df, 0.114f);
const auto kAmpR = Set(df, 0.701f);
const auto kAmpB = Set(df, 0.886f);
- const auto kDiffR = kAmpR + kR;
- const auto kDiffB = kAmpB + kB;
- const auto kNormR = Set(df, 1.0f) / (kAmpR + kG + kB);
- const auto kNormB = Set(df, 1.0f) / (kR + kG + kAmpB);
+ const auto kDiffR = Add(kAmpR, kR);
+ const auto kDiffB = Add(kAmpB, kB);
+ const auto kNormR = Div(Set(df, 1.0f), (Add(kAmpR, Add(kG, kB))));
+ const auto kNormB = Div(Set(df, 1.0f), (Add(kR, Add(kG, kAmpB))));
constexpr size_t kGroupArea = kGroupDim * kGroupDim;
const size_t lines_per_group = DivCeil(kGroupArea, xsize);
@@ -376,15 +320,15 @@ Status RgbToYcbcr(const ImageF& r_plane, const ImageF& g_plane,
const auto r = Load(df, r_row + x);
const auto g = Load(df, g_row + x);
const auto b = Load(df, b_row + x);
- const auto r_base = r * kR;
- const auto r_diff = r * kDiffR;
- const auto g_base = g * kG;
- const auto b_base = b * kB;
- const auto b_diff = b * kDiffB;
- const auto y_base = r_base + g_base + b_base;
- const auto y_vec = y_base - k128;
- const auto cb_vec = (b_diff - y_base) * kNormB;
- const auto cr_vec = (r_diff - y_base) * kNormR;
+ const auto r_base = Mul(r, kR);
+ const auto r_diff = Mul(r, kDiffR);
+ const auto g_base = Mul(g, kG);
+ const auto b_base = Mul(b, kB);
+ const auto b_diff = Mul(b, kDiffB);
+ const auto y_base = Add(r_base, Add(g_base, b_base));
+ const auto y_vec = Sub(y_base, k128);
+ const auto cb_vec = Mul(Sub(b_diff, y_base), kNormB);
+ const auto cr_vec = Mul(Sub(r_diff, y_base), kNormR);
Store(y_vec, df, y_row + x);
Store(cb_vec, df, cb_row + x);
Store(cr_vec, df, cr_row + x);
@@ -417,9 +361,6 @@ Status RgbToYcbcr(const ImageF& r_plane, const ImageF& g_plane,
cb_plane, cr_plane, pool);
}
-HWY_EXPORT(TestCubeRoot);
-void TestCubeRoot() { return HWY_DYNAMIC_DISPATCH(TestCubeRoot)(); }
-
// DEPRECATED
Image3F OpsinDynamicsImage(const Image3B& srgb8, const JxlCmsInterface& cms) {
ImageMetadata metadata;
diff --git a/media/libjxl/src/lib/jxl/enc_xyb.h b/media/libjxl/src/lib/jxl/enc_xyb.h
index aaa0ccc920..de8f2e3ff0 100644
--- a/media/libjxl/src/lib/jxl/enc_xyb.h
+++ b/media/libjxl/src/lib/jxl/enc_xyb.h
@@ -37,9 +37,6 @@ Status RgbToYcbcr(const ImageF& r_plane, const ImageF& g_plane,
// DEPRECATED, used by opsin_image_wrapper.
Image3F OpsinDynamicsImage(const Image3B& srgb8, const JxlCmsInterface& cms);
-// For opsin_image_test.
-void TestCubeRoot();
-
} // namespace jxl
#endif // LIB_JXL_ENC_XYB_H_
diff --git a/media/libjxl/src/lib/jxl/encode.cc b/media/libjxl/src/lib/jxl/encode.cc
index a42f9ff4ec..8e02dd6301 100644
--- a/media/libjxl/src/lib/jxl/encode.cc
+++ b/media/libjxl/src/lib/jxl/encode.cc
@@ -14,6 +14,7 @@
#include "jxl/codestream_header.h"
#include "jxl/types.h"
#include "lib/jxl/aux_out.h"
+#include "lib/jxl/base/byte_order.h"
#include "lib/jxl/base/span.h"
#include "lib/jxl/codec_in_out.h"
#include "lib/jxl/enc_color_management.h"
@@ -21,20 +22,29 @@
#include "lib/jxl/enc_file.h"
#include "lib/jxl/enc_icc_codec.h"
#include "lib/jxl/encode_internal.h"
+#include "lib/jxl/exif.h"
#include "lib/jxl/jpeg/enc_jpeg_data.h"
#include "lib/jxl/sanitizers.h"
// Debug-printing failure macro similar to JXL_FAILURE, but for the status code
// JXL_ENC_ERROR
#ifdef JXL_CRASH_ON_ERROR
-#define JXL_API_ERROR(format, ...) \
+#define JXL_API_ERROR(enc, error_code, format, ...) \
+ (enc->error = error_code, \
+ ::jxl::Debug(("%s:%d: " format "\n"), __FILE__, __LINE__, ##__VA_ARGS__), \
+ ::jxl::Abort(), JXL_ENC_ERROR)
+#define JXL_API_ERROR_NOSET(format, ...) \
(::jxl::Debug(("%s:%d: " format "\n"), __FILE__, __LINE__, ##__VA_ARGS__), \
::jxl::Abort(), JXL_ENC_ERROR)
#else // JXL_CRASH_ON_ERROR
-#define JXL_API_ERROR(format, ...) \
- (((JXL_DEBUG_ON_ERROR) && \
+#define JXL_API_ERROR(enc, error_code, format, ...) \
+ (enc->error = error_code, \
+ ((JXL_DEBUG_ON_ERROR) && \
::jxl::Debug(("%s:%d: " format "\n"), __FILE__, __LINE__, ##__VA_ARGS__)), \
JXL_ENC_ERROR)
+#define JXL_API_ERROR_NOSET(format, ...) \
+ (::jxl::Debug(("%s:%d: " format "\n"), __FILE__, __LINE__, ##__VA_ARGS__), \
+ JXL_ENC_ERROR)
#endif // JXL_CRASH_ON_ERROR
namespace jxl {} // namespace jxl
@@ -80,7 +90,7 @@ JxlEncoderStatus BrotliCompress(int quality, const uint8_t* in, size_t in_size,
std::unique_ptr<BrotliEncoderState, decltype(BrotliEncoderDestroyInstance)*>
enc(BrotliEncoderCreateInstance(nullptr, nullptr, nullptr),
BrotliEncoderDestroyInstance);
- if (!enc) return JXL_API_ERROR("BrotliEncoderCreateInstance failed");
+ if (!enc) return JXL_API_ERROR_NOSET("BrotliEncoderCreateInstance failed");
BrotliEncoderSetParameter(enc.get(), BROTLI_PARAM_QUALITY, quality);
BrotliEncoderSetParameter(enc.get(), BROTLI_PARAM_SIZE_HINT, in_size);
@@ -100,7 +110,7 @@ JxlEncoderStatus BrotliCompress(int quality, const uint8_t* in, size_t in_size,
if (!BrotliEncoderCompressStream(enc.get(), BROTLI_OPERATION_FINISH,
&avail_in, &next_in, &avail_out, &next_out,
&total_out)) {
- return JXL_API_ERROR("Brotli compression failed");
+ return JXL_API_ERROR_NOSET("Brotli compression failed");
}
size_t out_size = next_out - temp_buffer.data();
jxl::msan::UnpoisonMemory(next_out - out_size, out_size);
@@ -193,16 +203,93 @@ JxlEncoderStatus CheckValidBitdepth(uint32_t bits_per_sample,
// The spec allows up to 31 for bits_per_sample here, but
// the code does not (yet) support it.
if (!(bits_per_sample > 0 && bits_per_sample <= 24)) {
- return JXL_API_ERROR("Invalid value for bits_per_sample");
+ return JXL_API_ERROR_NOSET("Invalid value for bits_per_sample");
}
} else if ((exponent_bits_per_sample > 8) ||
(bits_per_sample > 24 + exponent_bits_per_sample) ||
(bits_per_sample < 3 + exponent_bits_per_sample)) {
- return JXL_API_ERROR("Invalid float description");
+ return JXL_API_ERROR_NOSET("Invalid float description");
}
return JXL_ENC_SUCCESS;
}
+bool EncodeFrameIndexBox(const jxl::JxlEncoderFrameIndexBox& frame_index_box,
+ jxl::BitWriter& writer) {
+ bool ok = true;
+ int NF = 0;
+ for (size_t i = 0; i < frame_index_box.entries.size(); ++i) {
+ if (i == 0 || frame_index_box.entries[i].to_be_indexed) {
+ ++NF;
+ }
+ }
+ // Frame index box contents varint + 8 bytes
+ // continue with NF * 3 * varint
+ // varint max length is 10 for 64 bit numbers, and these numbers
+ // are limited to 63 bits.
+ static const int kVarintMaxLength = 10;
+ static const int kFrameIndexBoxHeaderLength = kVarintMaxLength + 8;
+ static const int kFrameIndexBoxElementLength = 3 * kVarintMaxLength;
+ const int buffer_size =
+ kFrameIndexBoxHeaderLength + NF * kFrameIndexBoxElementLength;
+ std::vector<uint8_t> buffer_vec(buffer_size);
+ uint8_t* buffer = buffer_vec.data();
+ size_t output_pos = 0;
+ ok &= jxl::EncodeVarInt(NF, buffer_vec.size(), &output_pos, buffer);
+ StoreBE32(frame_index_box.TNUM, &buffer[output_pos]);
+ output_pos += 4;
+ StoreBE32(frame_index_box.TDEN, &buffer[output_pos]);
+ output_pos += 4;
+ // When we record a frame in the index, the record needs to know
+ // how many frames until the next indexed frame. That is why
+ // we store the 'prev' record. That 'prev' record needs to store
+ // the offset byte position to previously recorded indexed frame,
+ // that's why we also trace previous to the previous frame.
+ int prev_prev_ix = -1; // For position offset (OFFi) delta coding.
+ int prev_ix = 0;
+ int T_prev = 0;
+ int T = 0;
+ for (size_t i = 1; i < frame_index_box.entries.size(); ++i) {
+ if (frame_index_box.entries[i].to_be_indexed) {
+ // Now we can record the previous entry, since we need to store
+ // there how many frames until the next one.
+ int64_t OFFi = frame_index_box.entries[prev_ix].OFFi;
+ if (prev_prev_ix != -1) {
+ // Offi needs to be offset of start byte of this frame compared to start
+ // byte of previous frame from this index in the JPEG XL codestream. For
+ // the first frame, this is the offset from the first byte of the JPEG
+ // XL codestream.
+ OFFi -= frame_index_box.entries[prev_prev_ix].OFFi;
+ }
+ int32_t Ti = T_prev;
+ int32_t Fi = i - prev_ix;
+ ok &= jxl::EncodeVarInt(OFFi, buffer_vec.size(), &output_pos, buffer);
+ ok &= jxl::EncodeVarInt(Ti, buffer_vec.size(), &output_pos, buffer);
+ ok &= jxl::EncodeVarInt(Fi, buffer_vec.size(), &output_pos, buffer);
+ prev_prev_ix = prev_ix;
+ prev_ix = i;
+ T_prev = T;
+ T += frame_index_box.entries[i].duration;
+ }
+ }
+ {
+ // Last frame.
+ size_t i = frame_index_box.entries.size();
+ int64_t OFFi = frame_index_box.entries[prev_ix].OFFi;
+ if (prev_prev_ix != -1) {
+ OFFi -= frame_index_box.entries[prev_prev_ix].OFFi;
+ }
+ int32_t Ti = T_prev;
+ int32_t Fi = i - prev_ix;
+ ok &= jxl::EncodeVarInt(OFFi, buffer_vec.size(), &output_pos, buffer);
+ ok &= jxl::EncodeVarInt(Ti, buffer_vec.size(), &output_pos, buffer);
+ ok &= jxl::EncodeVarInt(Fi, buffer_vec.size(), &output_pos, buffer);
+ }
+ // Enough buffer has been allocated, this function should never fail in
+ // writing.
+ JXL_ASSERT(ok);
+ return ok;
+}
+
} // namespace
JxlEncoderStatus JxlEncoderStruct::RefillOutputByteQueue() {
@@ -222,31 +309,36 @@ JxlEncoderStatus JxlEncoderStruct::RefillOutputByteQueue() {
// indicate full incompatibility.
JXL_ASSERT(required_level == -1 || required_level == 5 ||
required_level == 10);
+ // codestream_level == -1 means auto-set to the required level
+ if (codestream_level == -1) codestream_level = required_level;
if (codestream_level == 5 && required_level != 5) {
// If the required level is 10, return error rather than automatically
// setting the level to 10, to avoid inadvertently creating a level 10
// JXL file while intending to target a level 5 decoder.
return JXL_API_ERROR(
- "%s",
+ this, JXL_ENC_ERR_API_USAGE, "%s",
("Codestream level verification for level 5 failed: " + level_message)
.c_str());
}
- if (codestream_level == 10 && required_level == -1) {
+ if (required_level == -1) {
return JXL_API_ERROR(
- "%s", ("Codestream level verification for level 10 failed: " +
- level_message)
- .c_str());
+ this, JXL_ENC_ERR_API_USAGE, "%s",
+ ("Codestream level verification for level 10 failed: " +
+ level_message)
+ .c_str());
}
jxl::BitWriter writer;
if (!WriteHeaders(&metadata, &writer, nullptr)) {
- return JXL_API_ERROR("Failed to write codestream header");
+ return JXL_API_ERROR(this, JXL_ENC_ERR_GENERIC,
+ "Failed to write codestream header");
}
// Only send ICC (at least several hundred bytes) if fields aren't enough.
if (metadata.m.color_encoding.WantICC()) {
if (!jxl::WriteICC(metadata.m.color_encoding.ICC(), &writer,
jxl::kLayerHeader, nullptr)) {
- return JXL_API_ERROR("Failed to write ICC profile");
+ return JXL_API_ERROR(this, JXL_ENC_ERR_GENERIC,
+ "Failed to write ICC profile");
}
}
// TODO(lode): preview should be added here if a preview image is added
@@ -311,7 +403,8 @@ JxlEncoderStatus JxlEncoderStruct::RefillOutputByteQueue() {
num_queued_frames--;
for (unsigned idx = 0; idx < input_frame->ec_initialized.size(); idx++) {
if (!input_frame->ec_initialized[idx]) {
- return JXL_API_ERROR("Extra channel %u is not initialized", idx);
+ return JXL_API_ERROR(this, JXL_ENC_ERR_API_USAGE,
+ "Extra channel %u is not initialized", idx);
}
}
@@ -351,6 +444,8 @@ JxlEncoderStatus JxlEncoderStruct::RefillOutputByteQueue() {
ib.duration = 0;
ib.timecode = 0;
}
+ frame_index_box.AddFrame(codestream_bytes_written_end_of_frame, ib.duration,
+ input_frame->option_values.frame_index_box);
ib.blendmode = static_cast<jxl::BlendMode>(
input_frame->option_values.header.layer_info.blend_info.blendmode);
ib.blend =
@@ -398,7 +493,7 @@ JxlEncoderStatus JxlEncoderStruct::RefillOutputByteQueue() {
&metadata, input_frame->frame, &enc_state, cms,
thread_pool.get(), &writer,
/*aux_out=*/nullptr)) {
- return JXL_API_ERROR("Failed to encode frame");
+ return JXL_API_ERROR(this, JXL_ENC_ERR_GENERIC, "Failed to encode frame");
}
codestream_bytes_written_beginning_of_frame =
codestream_bytes_written_end_of_frame;
@@ -426,6 +521,12 @@ JxlEncoderStatus JxlEncoderStruct::RefillOutputByteQueue() {
bytes.data() + bytes.size());
last_used_cparams = input_frame->option_values.cparams;
+ if (last_frame && frame_index_box.StoreFrameIndexBox()) {
+ bytes.clear();
+ EncodeFrameIndexBox(frame_index_box, writer);
+ jxl::AppendBoxHeader(jxl::MakeBoxType("jxli"), bytes.size(),
+ /*unbounded=*/false, &output_byte_queue);
+ }
} else {
// Not a frame, so is a box instead
jxl::MemoryManagerUniquePtr<jxl::JxlEncoderQueuedBox> box =
@@ -443,7 +544,8 @@ JxlEncoderStatus JxlEncoderStruct::RefillOutputByteQueue() {
BrotliCompress((brotli_effort >= 0 ? brotli_effort : 4),
box->contents.data(), box->contents.size(),
&compressed)) {
- return JXL_API_ERROR("Brotli compression for brob box failed");
+ return JXL_API_ERROR(this, JXL_ENC_ERR_GENERIC,
+ "Brotli compression for brob box failed");
}
jxl::AppendBoxHeader(jxl::MakeBoxType("brob"), compressed.size(), false,
&output_byte_queue);
@@ -462,13 +564,28 @@ JxlEncoderStatus JxlEncoderStruct::RefillOutputByteQueue() {
JxlEncoderStatus JxlEncoderSetColorEncoding(JxlEncoder* enc,
const JxlColorEncoding* color) {
+ if (!enc->basic_info_set) {
+ return JXL_API_ERROR(enc, JXL_ENC_ERR_API_USAGE, "Basic info not yet set");
+ }
if (enc->color_encoding_set) {
- // Already set
- return JXL_ENC_ERROR;
+ return JXL_API_ERROR(enc, JXL_ENC_ERR_API_USAGE,
+ "Color encoding is already set");
}
if (!jxl::ConvertExternalToInternalColorEncoding(
*color, &enc->metadata.m.color_encoding)) {
- return JXL_ENC_ERROR;
+ return JXL_API_ERROR(enc, JXL_ENC_ERR_GENERIC, "Error in color conversion");
+ }
+ if (enc->metadata.m.color_encoding.GetColorSpace() ==
+ jxl::ColorSpace::kGray) {
+ if (enc->basic_info.num_color_channels != 1)
+ return JXL_API_ERROR(
+ enc, JXL_ENC_ERR_API_USAGE,
+ "Cannot use grayscale color encoding with num_color_channels != 1");
+ } else {
+ if (enc->basic_info.num_color_channels != 3)
+ return JXL_API_ERROR(
+ enc, JXL_ENC_ERR_API_USAGE,
+ "Cannot use RGB color encoding with num_color_channels != 3");
}
enc->color_encoding_set = true;
if (!enc->intensity_target_set) {
@@ -480,19 +597,42 @@ JxlEncoderStatus JxlEncoderSetColorEncoding(JxlEncoder* enc,
JxlEncoderStatus JxlEncoderSetICCProfile(JxlEncoder* enc,
const uint8_t* icc_profile,
size_t size) {
+ if (!enc->basic_info_set) {
+ return JXL_API_ERROR(enc, JXL_ENC_ERR_API_USAGE, "Basic info not yet set");
+ }
if (enc->color_encoding_set) {
- // Already set
- return JXL_ENC_ERROR;
+ return JXL_API_ERROR(enc, JXL_ENC_ERR_API_USAGE,
+ "ICC profile is already set");
}
jxl::PaddedBytes icc;
icc.assign(icc_profile, icc_profile + size);
if (!enc->metadata.m.color_encoding.SetICC(std::move(icc))) {
- return JXL_ENC_ERROR;
+ return JXL_API_ERROR(enc, JXL_ENC_ERR_BAD_INPUT,
+ "ICC profile could not be set");
+ }
+ if (enc->metadata.m.color_encoding.GetColorSpace() ==
+ jxl::ColorSpace::kGray) {
+ if (enc->basic_info.num_color_channels != 1)
+ return JXL_API_ERROR(
+ enc, JXL_ENC_ERR_BAD_INPUT,
+ "Cannot use grayscale ICC profile with num_color_channels != 1");
+ } else {
+ if (enc->basic_info.num_color_channels != 3)
+ return JXL_API_ERROR(
+ enc, JXL_ENC_ERR_BAD_INPUT,
+ "Cannot use RGB ICC profile with num_color_channels != 3");
+ // TODO(jon): also check that a kBlack extra channel is provided in the CMYK
+ // case
}
enc->color_encoding_set = true;
if (!enc->intensity_target_set) {
jxl::SetIntensityTarget(&enc->metadata.m);
}
+
+ if (!enc->basic_info.uses_original_profile) {
+ enc->metadata.m.color_encoding.DecideIfWantICC();
+ }
+
return JXL_ENC_SUCCESS;
}
@@ -566,11 +706,11 @@ void JxlEncoderInitBlendInfo(JxlBlendInfo* blend_info) {
JxlEncoderStatus JxlEncoderSetBasicInfo(JxlEncoder* enc,
const JxlBasicInfo* info) {
if (!enc->metadata.size.Set(info->xsize, info->ysize)) {
- return JXL_ENC_ERROR;
+ return JXL_API_ERROR(enc, JXL_ENC_ERR_API_USAGE, "Invalid dimensions");
}
if (JXL_ENC_SUCCESS != CheckValidBitdepth(info->bits_per_sample,
info->exponent_bits_per_sample)) {
- return JXL_ENC_ERROR;
+ return JXL_API_ERROR(enc, JXL_ENC_ERR_API_USAGE, "Invalid bit depth");
}
enc->metadata.m.bit_depth.bits_per_sample = info->bits_per_sample;
enc->metadata.m.bit_depth.exponent_bits_per_sample =
@@ -587,6 +727,7 @@ JxlEncoderStatus JxlEncoderSetBasicInfo(JxlEncoder* enc,
enc->metadata.m.extra_channel_info.resize(enc->metadata.m.num_extra_channels);
if (info->num_extra_channels == 0 && info->alpha_bits) {
return JXL_API_ERROR(
+ enc, JXL_ENC_ERR_API_USAGE,
"when alpha_bits is non-zero, the number of channels must be at least "
"1");
}
@@ -598,7 +739,8 @@ JxlEncoderStatus JxlEncoderSetBasicInfo(JxlEncoder* enc,
channel_info.bits_per_sample = info->alpha_bits;
channel_info.exponent_bits_per_sample = info->alpha_exponent_bits;
if (JxlEncoderSetExtraChannelInfo(enc, 0, &channel_info)) {
- return JXL_ENC_ERROR;
+ return JXL_API_ERROR(enc, JXL_ENC_ERR_API_USAGE,
+ "Problem setting extra channel info for alpha");
}
}
@@ -606,7 +748,12 @@ JxlEncoderStatus JxlEncoderSetBasicInfo(JxlEncoder* enc,
if (info->orientation > 0 && info->orientation <= 8) {
enc->metadata.m.orientation = info->orientation;
} else {
- return JXL_API_ERROR("Invalid value for orientation field");
+ return JXL_API_ERROR(enc, JXL_ENC_ERR_API_USAGE,
+ "Invalid value for orientation field");
+ }
+ if (info->num_color_channels != 1 && info->num_color_channels != 3) {
+ return JXL_API_ERROR(enc, JXL_ENC_ERR_API_USAGE,
+ "Invalid number of color channels");
}
if (info->intensity_target != 0) {
enc->metadata.m.SetIntensityTarget(info->intensity_target);
@@ -622,16 +769,19 @@ JxlEncoderStatus JxlEncoderSetBasicInfo(JxlEncoder* enc,
enc->metadata.m.tone_mapping.relative_to_max_display =
info->relative_to_max_display;
enc->metadata.m.tone_mapping.linear_below = info->linear_below;
+ enc->basic_info = *info;
enc->basic_info_set = true;
enc->metadata.m.have_animation = info->have_animation;
if (info->have_animation) {
if (info->animation.tps_denominator < 1) {
return JXL_API_ERROR(
+ enc, JXL_ENC_ERR_API_USAGE,
"If animation is used, tps_denominator must be >= 1");
}
if (info->animation.tps_numerator < 1) {
- return JXL_API_ERROR("If animation is used, tps_numerator must be >= 1");
+ return JXL_API_ERROR(enc, JXL_ENC_ERR_API_USAGE,
+ "If animation is used, tps_numerator must be >= 1");
}
enc->metadata.m.animation.tps_numerator = info->animation.tps_numerator;
enc->metadata.m.animation.tps_denominator = info->animation.tps_denominator;
@@ -641,11 +791,13 @@ JxlEncoderStatus JxlEncoderSetBasicInfo(JxlEncoder* enc,
std::string level_message;
int required_level = VerifyLevelSettings(enc, &level_message);
if (required_level == -1 ||
- static_cast<int>(enc->codestream_level) < required_level) {
- return JXL_API_ERROR("%s", ("Codestream level verification for level " +
- std::to_string(enc->codestream_level) +
- " failed: " + level_message)
- .c_str());
+ (static_cast<int>(enc->codestream_level) < required_level &&
+ enc->codestream_level != -1)) {
+ return JXL_API_ERROR(
+ enc, JXL_ENC_ERR_API_USAGE, "%s",
+ ("Codestream level verification for level " +
+ std::to_string(enc->codestream_level) + " failed: " + level_message)
+ .c_str());
}
return JXL_ENC_SUCCESS;
}
@@ -668,11 +820,12 @@ void JxlEncoderInitExtraChannelInfo(JxlExtraChannelType type,
JXL_EXPORT JxlEncoderStatus JxlEncoderSetExtraChannelInfo(
JxlEncoder* enc, size_t index, const JxlExtraChannelInfo* info) {
if (index >= enc->metadata.m.num_extra_channels) {
- return JXL_API_ERROR("Invalid value for the index of extra channel");
+ return JXL_API_ERROR(enc, JXL_ENC_ERR_API_USAGE,
+ "Invalid value for the index of extra channel");
}
if (JXL_ENC_SUCCESS != CheckValidBitdepth(info->bits_per_sample,
info->exponent_bits_per_sample)) {
- return JXL_ENC_ERROR;
+ return JXL_API_ERROR(enc, JXL_ENC_ERR_API_USAGE, "Invalid bit depth");
}
jxl::ExtraChannelInfo& channel = enc->metadata.m.extra_channel_info[index];
@@ -693,11 +846,13 @@ JXL_EXPORT JxlEncoderStatus JxlEncoderSetExtraChannelInfo(
std::string level_message;
int required_level = VerifyLevelSettings(enc, &level_message);
if (required_level == -1 ||
- static_cast<int>(enc->codestream_level) < required_level) {
- return JXL_API_ERROR("%s", ("Codestream level verification for level " +
- std::to_string(enc->codestream_level) +
- " failed: " + level_message)
- .c_str());
+ (static_cast<int>(enc->codestream_level) < required_level &&
+ enc->codestream_level != -1)) {
+ return JXL_API_ERROR(
+ enc, JXL_ENC_ERR_API_USAGE, "%s",
+ ("Codestream level verification for level " +
+ std::to_string(enc->codestream_level) + " failed: " + level_message)
+ .c_str());
}
return JXL_ENC_SUCCESS;
}
@@ -707,7 +862,8 @@ JXL_EXPORT JxlEncoderStatus JxlEncoderSetExtraChannelName(JxlEncoder* enc,
const char* name,
size_t size) {
if (index >= enc->metadata.m.num_extra_channels) {
- return JXL_API_ERROR("Invalid value for the index of extra channel");
+ return JXL_API_ERROR(enc, JXL_ENC_ERR_API_USAGE,
+ "Invalid value for the index of extra channel");
}
enc->metadata.m.extra_channel_info[index].name =
std::string(name, name + size);
@@ -741,7 +897,8 @@ JxlEncoderStatus JxlEncoderSetFrameLossless(
JxlEncoderFrameSettings* frame_settings, const JXL_BOOL lossless) {
if (lossless && frame_settings->enc->basic_info_set &&
frame_settings->enc->metadata.m.xyb_encoded) {
- return JXL_API_ERROR("Set use_original_profile=true for lossless encoding");
+ return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_API_USAGE,
+ "Set use_original_profile=true for lossless encoding");
}
frame_settings->values.lossless = lossless;
return JXL_ENC_SUCCESS;
@@ -762,7 +919,8 @@ JxlEncoderStatus JxlEncoderOptionsSetEffort(
JxlEncoderStatus JxlEncoderSetFrameDistance(
JxlEncoderFrameSettings* frame_settings, float distance) {
if (distance < 0.f || distance > 25.f) {
- return JXL_ENC_ERROR;
+ return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_API_USAGE,
+ "Distance has to be in [0.0..25.0]");
}
if (distance > 0.f && distance < 0.01f) {
distance = 0.01f;
@@ -785,18 +943,44 @@ JxlEncoderStatus JxlEncoderOptionsSetDecodingSpeed(
JxlEncoderStatus JxlEncoderFrameSettingsSetOption(
JxlEncoderFrameSettings* frame_settings, JxlEncoderFrameSettingId option,
- int32_t value) {
+ int64_t value) {
+ // check if value is -1, 0 or 1 for Override-type options
+ switch (option) {
+ case JXL_ENC_FRAME_SETTING_NOISE:
+ case JXL_ENC_FRAME_SETTING_DOTS:
+ case JXL_ENC_FRAME_SETTING_PATCHES:
+ case JXL_ENC_FRAME_SETTING_GABORISH:
+ case JXL_ENC_FRAME_SETTING_MODULAR:
+ case JXL_ENC_FRAME_SETTING_KEEP_INVISIBLE:
+ case JXL_ENC_FRAME_SETTING_GROUP_ORDER:
+ case JXL_ENC_FRAME_SETTING_RESPONSIVE:
+ case JXL_ENC_FRAME_SETTING_PROGRESSIVE_AC:
+ case JXL_ENC_FRAME_SETTING_QPROGRESSIVE_AC:
+ case JXL_ENC_FRAME_SETTING_LOSSY_PALETTE:
+ case JXL_ENC_FRAME_SETTING_JPEG_RECON_CFL:
+ if (value < -1 || value > 1) {
+ return JXL_API_ERROR(
+ frame_settings->enc, JXL_ENC_ERR_API_USAGE,
+ "Option value has to be -1 (default), 0 (off) or 1 (on)");
+ }
+ break;
+ default:
+ break;
+ }
+
switch (option) {
case JXL_ENC_FRAME_SETTING_EFFORT:
if (value < 1 || value > 9) {
- return JXL_ENC_ERROR;
+ return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_NOT_SUPPORTED,
+ "Encode effort has to be in [1..9]");
}
frame_settings->values.cparams.speed_tier =
static_cast<jxl::SpeedTier>(10 - value);
return JXL_ENC_SUCCESS;
case JXL_ENC_FRAME_SETTING_BROTLI_EFFORT:
if (value < -1 || value > 11) {
- return JXL_ENC_ERROR;
+ return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_API_USAGE,
+ "Brotli effort has to be in [-1..11]");
}
// set cparams for brotli use in JPEG frames
frame_settings->values.cparams.brotli_effort = value;
@@ -805,13 +989,15 @@ JxlEncoderStatus JxlEncoderFrameSettingsSetOption(
return JXL_ENC_SUCCESS;
case JXL_ENC_FRAME_SETTING_DECODING_SPEED:
if (value < 0 || value > 4) {
- return JXL_ENC_ERROR;
+ return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_NOT_SUPPORTED,
+ "Decoding speed has to be in [0..4]");
}
frame_settings->values.cparams.decoding_speed_tier = value;
return JXL_ENC_SUCCESS;
case JXL_ENC_FRAME_SETTING_RESAMPLING:
if (value != -1 && value != 1 && value != 2 && value != 4 && value != 8) {
- return JXL_ENC_ERROR;
+ return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_API_USAGE,
+ "Resampling factor has to be 1, 2, 4 or 8");
}
frame_settings->values.cparams.resampling = value;
return JXL_ENC_SUCCESS;
@@ -823,7 +1009,8 @@ JxlEncoderStatus JxlEncoderFrameSettingsSetOption(
// JxlEncoderFrameSettingsSetOption due to the extra channel index
// argument required.
if (value != -1 && value != 1 && value != 2 && value != 4 && value != 8) {
- return JXL_ENC_ERROR;
+ return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_API_USAGE,
+ "Resampling factor has to be 1, 2, 4 or 8");
}
frame_settings->values.cparams.ec_resampling = value;
return JXL_ENC_SUCCESS;
@@ -833,98 +1020,72 @@ JxlEncoderStatus JxlEncoderFrameSettingsSetOption(
}
frame_settings->values.cparams.already_downsampled = (value == 1);
return JXL_ENC_SUCCESS;
- case JXL_ENC_FRAME_SETTING_PHOTON_NOISE:
- if (value < 0) return JXL_ENC_ERROR;
- // TODO(lode): add encoder setting to set the 8 floating point values of
- // the noise synthesis parameters per frame for more fine grained control.
- frame_settings->values.cparams.photon_noise_iso =
- static_cast<float>(value);
- return JXL_ENC_SUCCESS;
case JXL_ENC_FRAME_SETTING_NOISE:
- if (value < -1 || value > 1) return JXL_ENC_ERROR;
frame_settings->values.cparams.noise = static_cast<jxl::Override>(value);
return JXL_ENC_SUCCESS;
case JXL_ENC_FRAME_SETTING_DOTS:
- if (value < -1 || value > 1) return JXL_ENC_ERROR;
frame_settings->values.cparams.dots = static_cast<jxl::Override>(value);
return JXL_ENC_SUCCESS;
case JXL_ENC_FRAME_SETTING_PATCHES:
- if (value < -1 || value > 1) return JXL_ENC_ERROR;
frame_settings->values.cparams.patches =
static_cast<jxl::Override>(value);
return JXL_ENC_SUCCESS;
case JXL_ENC_FRAME_SETTING_EPF:
- if (value < -1 || value > 3) return JXL_ENC_ERROR;
+ if (value < -1 || value > 3) {
+ return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_API_USAGE,
+ "EPF value has to be in [-1..3]");
+ }
frame_settings->values.cparams.epf = static_cast<int>(value);
return JXL_ENC_SUCCESS;
case JXL_ENC_FRAME_SETTING_GABORISH:
- if (value < -1 || value > 1) return JXL_ENC_ERROR;
frame_settings->values.cparams.gaborish =
static_cast<jxl::Override>(value);
return JXL_ENC_SUCCESS;
case JXL_ENC_FRAME_SETTING_MODULAR:
- if (value == 1) {
- frame_settings->values.cparams.modular_mode = true;
- } else if (value == -1 || value == 0) {
- frame_settings->values.cparams.modular_mode = false;
- } else {
- return JXL_ENC_ERROR;
- }
+ frame_settings->values.cparams.modular_mode = (value == 1);
return JXL_ENC_SUCCESS;
case JXL_ENC_FRAME_SETTING_KEEP_INVISIBLE:
- if (value < -1 || value > 1) return JXL_ENC_ERROR;
frame_settings->values.cparams.keep_invisible =
static_cast<jxl::Override>(value);
return JXL_ENC_SUCCESS;
case JXL_ENC_FRAME_SETTING_GROUP_ORDER:
- if (value < -1 || value > 1) return JXL_ENC_ERROR;
frame_settings->values.cparams.centerfirst = (value == 1);
return JXL_ENC_SUCCESS;
case JXL_ENC_FRAME_SETTING_GROUP_ORDER_CENTER_X:
- if (value < -1) return JXL_ENC_ERROR;
+ if (value < -1) {
+ return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_API_USAGE,
+ "Center x coordinate has to be -1 or positive");
+ }
frame_settings->values.cparams.center_x = static_cast<size_t>(value);
return JXL_ENC_SUCCESS;
case JXL_ENC_FRAME_SETTING_GROUP_ORDER_CENTER_Y:
- if (value < -1) return JXL_ENC_ERROR;
+ if (value < -1) {
+ return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_API_USAGE,
+ "Center y coordinate has to be -1 or positive");
+ }
frame_settings->values.cparams.center_y = static_cast<size_t>(value);
return JXL_ENC_SUCCESS;
case JXL_ENC_FRAME_SETTING_RESPONSIVE:
- if (value < -1 || value > 1) return JXL_ENC_ERROR;
frame_settings->values.cparams.responsive = value;
return JXL_ENC_SUCCESS;
case JXL_ENC_FRAME_SETTING_PROGRESSIVE_AC:
- if (value < -1 || value > 1) return JXL_ENC_ERROR;
frame_settings->values.cparams.progressive_mode = value;
return JXL_ENC_SUCCESS;
case JXL_ENC_FRAME_SETTING_QPROGRESSIVE_AC:
- if (value < -1 || value > 1) return JXL_ENC_ERROR;
frame_settings->values.cparams.qprogressive_mode = value;
return JXL_ENC_SUCCESS;
case JXL_ENC_FRAME_SETTING_PROGRESSIVE_DC:
- if (value < -1 || value > 2) return JXL_ENC_ERROR;
- frame_settings->values.cparams.progressive_dc = value;
- return JXL_ENC_SUCCESS;
- case JXL_ENC_FRAME_SETTING_CHANNEL_COLORS_GLOBAL_PERCENT:
- if (value < -1 || value > 100) return JXL_ENC_ERROR;
- if (value == -1) {
- frame_settings->values.cparams.channel_colors_pre_transform_percent =
- 95.0f;
- } else {
- frame_settings->values.cparams.channel_colors_pre_transform_percent =
- static_cast<float>(value);
- }
- return JXL_ENC_SUCCESS;
- case JXL_ENC_FRAME_SETTING_CHANNEL_COLORS_GROUP_PERCENT:
- if (value < -1 || value > 100) return JXL_ENC_ERROR;
- if (value == -1) {
- frame_settings->values.cparams.channel_colors_percent = 80.0f;
- } else {
- frame_settings->values.cparams.channel_colors_percent =
- static_cast<float>(value);
+ if (value < -1 || value > 2) {
+ return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_API_USAGE,
+ "Progressive DC has to be in [-1..2]");
}
+ frame_settings->values.cparams.progressive_dc = value;
return JXL_ENC_SUCCESS;
case JXL_ENC_FRAME_SETTING_PALETTE_COLORS:
- if (value < -1 || value > 70913) return JXL_ENC_ERROR;
+ if (value < -1 || value > 70913) {
+ return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_API_USAGE,
+ "Option value has to be in [-1..70913]");
+ }
if (value == -1) {
frame_settings->values.cparams.palette_colors = 1 << 10;
} else {
@@ -932,7 +1093,6 @@ JxlEncoderStatus JxlEncoderFrameSettingsSetOption(
}
return JXL_ENC_SUCCESS;
case JXL_ENC_FRAME_SETTING_LOSSY_PALETTE:
- if (value < -1 || value > 1) return JXL_ENC_ERROR;
// TODO(lode): the defaults of some palette settings depend on others.
// See the logic in cjxl. Similar for other settings. This should be
// handled in the encoder during JxlEncoderProcessOutput (or,
@@ -940,7 +1100,10 @@ JxlEncoderStatus JxlEncoderFrameSettingsSetOption(
frame_settings->values.cparams.lossy_palette = (value == 1);
return JXL_ENC_SUCCESS;
case JXL_ENC_FRAME_SETTING_COLOR_TRANSFORM:
- if (value < -1 || value > 2) return JXL_ENC_ERROR;
+ if (value < -1 || value > 2) {
+ return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_API_USAGE,
+ "Option value has to be in [-1..2]");
+ }
if (value == -1) {
frame_settings->values.cparams.color_transform =
jxl::ColorTransform::kXYB;
@@ -950,11 +1113,17 @@ JxlEncoderStatus JxlEncoderFrameSettingsSetOption(
}
return JXL_ENC_SUCCESS;
case JXL_ENC_FRAME_SETTING_MODULAR_COLOR_SPACE:
- if (value < -1 || value > 35) return JXL_ENC_ERROR;
- frame_settings->values.cparams.colorspace = value + 2;
+ if (value < -1 || value > 41) {
+ return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_API_USAGE,
+ "Option value has to be in [-1..41]");
+ }
+ frame_settings->values.cparams.colorspace = value;
return JXL_ENC_SUCCESS;
case JXL_ENC_FRAME_SETTING_MODULAR_GROUP_SIZE:
- if (value < -1 || value > 3) return JXL_ENC_ERROR;
+ if (value < -1 || value > 3) {
+ return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_API_USAGE,
+ "Option value has to be in [-1..3]");
+ }
// TODO(lode): the default behavior of this parameter for cjxl is
// to choose 1 or 2 depending on the situation. This behavior needs to be
// implemented either in the C++ library by allowing to set this to -1, or
@@ -966,30 +1135,23 @@ JxlEncoderStatus JxlEncoderFrameSettingsSetOption(
}
return JXL_ENC_SUCCESS;
case JXL_ENC_FRAME_SETTING_MODULAR_PREDICTOR:
- if (value < -1 || value > 15) return JXL_ENC_ERROR;
+ if (value < -1 || value > 15) {
+ return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_API_USAGE,
+ "Option value has to be in [-1..15]");
+ }
frame_settings->values.cparams.options.predictor =
static_cast<jxl::Predictor>(value);
return JXL_ENC_SUCCESS;
- case JXL_ENC_FRAME_SETTING_MODULAR_MA_TREE_LEARNING_PERCENT:
- if (value < -1) return JXL_ENC_ERROR;
- // This value is called "iterations" or "nb_repeats" in cjxl, but is in
- // fact a fraction in range 0.0-1.0, with the defautl value 0.5.
- // Convert from integer percentage to floating point fraction here.
- if (value == -1) {
- // TODO(lode): for this and many other settings, avoid duplicating the
- // default values here and in enc_params.h and options.h, have one
- // location where the defaults are specified.
- frame_settings->values.cparams.options.nb_repeats = 0.5f;
- } else {
- frame_settings->values.cparams.options.nb_repeats = value * 0.01f;
- }
- return JXL_ENC_SUCCESS;
case JXL_ENC_FRAME_SETTING_MODULAR_NB_PREV_CHANNELS:
// The max allowed value can in theory be higher. However, it depends on
// the effort setting. 11 is the highest safe value that doesn't cause
// tree_samples to be >= 64 in the encoder. The specification may allow
// more than this. With more fine tuning higher values could be allowed.
- if (value < -1 || value > 11) return JXL_ENC_ERROR;
+ // For N-channel images, the largest useful value is N-1.
+ if (value < -1 || value > 11) {
+ return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_API_USAGE,
+ "Option value has to be in [-1..11]");
+ }
if (value == -1) {
frame_settings->values.cparams.options.max_properties = 0;
} else {
@@ -997,18 +1159,115 @@ JxlEncoderStatus JxlEncoderFrameSettingsSetOption(
}
return JXL_ENC_SUCCESS;
case JXL_ENC_FRAME_SETTING_JPEG_RECON_CFL:
- if (value < -1 || value > 1) return JXL_ENC_ERROR;
if (value == -1) {
frame_settings->values.cparams.force_cfl_jpeg_recompression = true;
} else {
frame_settings->values.cparams.force_cfl_jpeg_recompression = value;
}
return JXL_ENC_SUCCESS;
+ case JXL_ENC_FRAME_INDEX_BOX:
+ frame_settings->values.frame_index_box = true;
+ return JXL_ENC_SUCCESS;
+ case JXL_ENC_FRAME_SETTING_PHOTON_NOISE:
+ return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_NOT_SUPPORTED,
+ "Float option, try setting it with "
+ "JxlEncoderFrameSettingsSetFloatOption");
default:
- return JXL_ENC_ERROR;
+ return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_NOT_SUPPORTED,
+ "Unknown option");
}
}
+JxlEncoderStatus JxlEncoderFrameSettingsSetFloatOption(
+ JxlEncoderFrameSettings* frame_settings, JxlEncoderFrameSettingId option,
+ float value) {
+ switch (option) {
+ case JXL_ENC_FRAME_SETTING_PHOTON_NOISE:
+ if (value < 0) return JXL_ENC_ERROR;
+ // TODO(lode): add encoder setting to set the 8 floating point values of
+ // the noise synthesis parameters per frame for more fine grained control.
+ frame_settings->values.cparams.photon_noise_iso = value;
+ return JXL_ENC_SUCCESS;
+ case JXL_ENC_FRAME_SETTING_MODULAR_MA_TREE_LEARNING_PERCENT:
+ if (value < -1.f || value > 100.f) {
+ return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_API_USAGE,
+ "Option value has to be smaller than 100");
+ }
+ // This value is called "iterations" or "nb_repeats" in cjxl, but is in
+ // fact a fraction in range 0.0-1.0, with the default value 0.5.
+ // Convert from floating point percentage to floating point fraction here.
+ if (value < -.5f) {
+ // TODO(lode): for this and many other settings (also in
+ // JxlEncoderFrameSettingsSetOption), avoid duplicating the default
+ // values here and in enc_params.h and options.h, have one location
+ // where the defaults are specified.
+ frame_settings->values.cparams.options.nb_repeats = 0.5f;
+ } else {
+ frame_settings->values.cparams.options.nb_repeats = value * 0.01f;
+ }
+ return JXL_ENC_SUCCESS;
+ case JXL_ENC_FRAME_SETTING_CHANNEL_COLORS_GLOBAL_PERCENT:
+ if (value < -1.f || value > 100.f) {
+ return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_API_USAGE,
+ "Option value has to be in [-1..100]");
+ }
+ if (value < -.5f) {
+ frame_settings->values.cparams.channel_colors_pre_transform_percent =
+ 95.0f;
+ } else {
+ frame_settings->values.cparams.channel_colors_pre_transform_percent =
+ value;
+ }
+ return JXL_ENC_SUCCESS;
+ case JXL_ENC_FRAME_SETTING_CHANNEL_COLORS_GROUP_PERCENT:
+ if (value < -1.f || value > 100.f) {
+ return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_API_USAGE,
+ "Option value has to be in [-1..100]");
+ }
+ if (value < -.5f) {
+ frame_settings->values.cparams.channel_colors_percent = 80.0f;
+ } else {
+ frame_settings->values.cparams.channel_colors_percent = value;
+ }
+ return JXL_ENC_SUCCESS;
+ case JXL_ENC_FRAME_SETTING_EFFORT:
+ case JXL_ENC_FRAME_SETTING_DECODING_SPEED:
+ case JXL_ENC_FRAME_SETTING_RESAMPLING:
+ case JXL_ENC_FRAME_SETTING_EXTRA_CHANNEL_RESAMPLING:
+ case JXL_ENC_FRAME_SETTING_ALREADY_DOWNSAMPLED:
+ case JXL_ENC_FRAME_SETTING_NOISE:
+ case JXL_ENC_FRAME_SETTING_DOTS:
+ case JXL_ENC_FRAME_SETTING_PATCHES:
+ case JXL_ENC_FRAME_SETTING_EPF:
+ case JXL_ENC_FRAME_SETTING_GABORISH:
+ case JXL_ENC_FRAME_SETTING_MODULAR:
+ case JXL_ENC_FRAME_SETTING_KEEP_INVISIBLE:
+ case JXL_ENC_FRAME_SETTING_GROUP_ORDER:
+ case JXL_ENC_FRAME_SETTING_GROUP_ORDER_CENTER_X:
+ case JXL_ENC_FRAME_SETTING_GROUP_ORDER_CENTER_Y:
+ case JXL_ENC_FRAME_SETTING_RESPONSIVE:
+ case JXL_ENC_FRAME_SETTING_PROGRESSIVE_AC:
+ case JXL_ENC_FRAME_SETTING_QPROGRESSIVE_AC:
+ case JXL_ENC_FRAME_SETTING_PROGRESSIVE_DC:
+ case JXL_ENC_FRAME_SETTING_PALETTE_COLORS:
+ case JXL_ENC_FRAME_SETTING_LOSSY_PALETTE:
+ case JXL_ENC_FRAME_SETTING_COLOR_TRANSFORM:
+ case JXL_ENC_FRAME_SETTING_MODULAR_COLOR_SPACE:
+ case JXL_ENC_FRAME_SETTING_MODULAR_GROUP_SIZE:
+ case JXL_ENC_FRAME_SETTING_MODULAR_PREDICTOR:
+ case JXL_ENC_FRAME_SETTING_MODULAR_NB_PREV_CHANNELS:
+ case JXL_ENC_FRAME_SETTING_JPEG_RECON_CFL:
+ case JXL_ENC_FRAME_INDEX_BOX:
+ case JXL_ENC_FRAME_SETTING_BROTLI_EFFORT:
+ case JXL_ENC_FRAME_SETTING_FILL_ENUM:
+ return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_NOT_SUPPORTED,
+ "Int option, try setting it with "
+ "JxlEncoderFrameSettingsSetOption");
+ default:
+ return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_NOT_SUPPORTED,
+ "Unknown option");
+ }
+}
JxlEncoder* JxlEncoderCreate(const JxlMemoryManager* memory_manager) {
JxlMemoryManager local_memory_manager;
if (!jxl::MemoryManagerInit(&local_memory_manager, memory_manager)) {
@@ -1049,7 +1308,8 @@ void JxlEncoderReset(JxlEncoder* enc) {
enc->intensity_target_set = false;
enc->use_container = false;
enc->use_boxes = false;
- enc->codestream_level = 5;
+ enc->codestream_level = -1;
+ JxlEncoderInitBasicInfo(&enc->basic_info);
}
void JxlEncoderDestroy(JxlEncoder* enc) {
@@ -1061,10 +1321,13 @@ void JxlEncoderDestroy(JxlEncoder* enc) {
}
}
+JxlEncoderError JxlEncoderGetError(JxlEncoder* enc) { return enc->error; }
+
JxlEncoderStatus JxlEncoderUseContainer(JxlEncoder* enc,
JXL_BOOL use_container) {
if (enc->wrote_bytes) {
- return JXL_API_ERROR("this setting can only be set at the beginning");
+ return JXL_API_ERROR(enc, JXL_ENC_ERR_API_USAGE,
+ "this setting can only be set at the beginning");
}
enc->use_container = static_cast<bool>(use_container);
return JXL_ENC_SUCCESS;
@@ -1073,16 +1336,20 @@ JxlEncoderStatus JxlEncoderUseContainer(JxlEncoder* enc,
JxlEncoderStatus JxlEncoderStoreJPEGMetadata(JxlEncoder* enc,
JXL_BOOL store_jpeg_metadata) {
if (enc->wrote_bytes) {
- return JXL_API_ERROR("this setting can only be set at the beginning");
+ return JXL_API_ERROR(enc, JXL_ENC_ERR_API_USAGE,
+ "this setting can only be set at the beginning");
}
enc->store_jpeg_metadata = static_cast<bool>(store_jpeg_metadata);
return JXL_ENC_SUCCESS;
}
JxlEncoderStatus JxlEncoderSetCodestreamLevel(JxlEncoder* enc, int level) {
- if (level != 5 && level != 10) return JXL_API_ERROR("invalid level");
+ if (level != -1 && level != 5 && level != 10) {
+ return JXL_API_ERROR(enc, JXL_ENC_ERR_NOT_SUPPORTED, "invalid level");
+ }
if (enc->wrote_bytes) {
- return JXL_API_ERROR("this setting can only be set at the beginning");
+ return JXL_API_ERROR(enc, JXL_ENC_ERR_API_USAGE,
+ "this setting can only be set at the beginning");
}
enc->codestream_level = level;
return JXL_ENC_SUCCESS;
@@ -1100,11 +1367,15 @@ void JxlEncoderSetCms(JxlEncoder* enc, JxlCmsInterface cms) {
JxlEncoderStatus JxlEncoderSetParallelRunner(JxlEncoder* enc,
JxlParallelRunner parallel_runner,
void* parallel_runner_opaque) {
- if (enc->thread_pool) return JXL_API_ERROR("parallel runner already set");
+ if (enc->thread_pool) {
+ return JXL_API_ERROR(enc, JXL_ENC_ERR_API_USAGE,
+ "parallel runner already set");
+ }
enc->thread_pool = jxl::MemoryManagerMakeUnique<jxl::ThreadPool>(
&enc->memory_manager, parallel_runner, parallel_runner_opaque);
if (!enc->thread_pool) {
- return JXL_ENC_ERROR;
+ return JXL_API_ERROR(enc, JXL_ENC_ERR_GENERIC,
+ "error setting parallel runner");
}
return JXL_ENC_SUCCESS;
}
@@ -1125,7 +1396,8 @@ JxlEncoderStatus GetCurrentDimensions(
ysize = jxl::DivCeil(ysize, factor);
}
if (xsize == 0 || ysize == 0) {
- return JXL_API_ERROR("zero-sized frame is not allowed");
+ return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_API_USAGE,
+ "zero-sized frame is not allowed");
}
return JXL_ENC_SUCCESS;
}
@@ -1135,19 +1407,22 @@ JxlEncoderStatus JxlEncoderAddJPEGFrame(
const JxlEncoderFrameSettings* frame_settings, const uint8_t* buffer,
size_t size) {
if (frame_settings->enc->frames_closed) {
- return JXL_ENC_ERROR;
+ return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_API_USAGE,
+ "Frame input is already closed");
}
jxl::CodecInOut io;
if (!jxl::jpeg::DecodeImageJPG(jxl::Span<const uint8_t>(buffer, size), &io)) {
- return JXL_ENC_ERROR;
+ return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_BAD_INPUT,
+ "Error during decode of input JPEG");
}
if (!frame_settings->enc->color_encoding_set) {
if (!SetColorEncodingFromJpegData(
*io.Main().jpeg_data,
&frame_settings->enc->metadata.m.color_encoding)) {
- return JXL_ENC_ERROR;
+ return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_BAD_INPUT,
+ "Error in input JPEG color space");
}
}
@@ -1159,21 +1434,52 @@ JxlEncoderStatus JxlEncoderAddJPEGFrame(
basic_info.uses_original_profile = true;
if (JxlEncoderSetBasicInfo(frame_settings->enc, &basic_info) !=
JXL_ENC_SUCCESS) {
- return JXL_ENC_ERROR;
+ return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_GENERIC,
+ "Error setting basic info");
}
}
if (frame_settings->enc->metadata.m.xyb_encoded) {
- // Can't XYB encode a lossless JPEG.
- return JXL_ENC_ERROR;
+ return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_API_USAGE,
+ "Can't XYB encode a lossless JPEG");
+ }
+ if (!io.blobs.exif.empty()) {
+ JxlOrientation orientation = static_cast<JxlOrientation>(
+ frame_settings->enc->metadata.m.orientation);
+ jxl::InterpretExif(io.blobs.exif, &orientation);
+ frame_settings->enc->metadata.m.orientation = orientation;
+
+ size_t exif_size = io.blobs.exif.size();
+ // Exif data in JPEG is limited to 64k
+ if (exif_size > 0xFFFF) {
+ return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_GENERIC,
+ "Exif larger than possible in JPEG?");
+ }
+ exif_size += 4; // prefix 4 zero bytes for tiff offset
+ std::vector<uint8_t> exif(exif_size);
+ memcpy(exif.data() + 4, io.blobs.exif.data(), io.blobs.exif.size());
+ JxlEncoderUseBoxes(frame_settings->enc);
+ JxlEncoderAddBox(frame_settings->enc, "Exif", exif.data(), exif_size,
+ /*compress_box=*/JXL_TRUE);
+ }
+ if (!io.blobs.xmp.empty()) {
+ JxlEncoderUseBoxes(frame_settings->enc);
+ JxlEncoderAddBox(frame_settings->enc, "xml ", io.blobs.xmp.data(),
+ io.blobs.xmp.size(), /*compress_box=*/JXL_TRUE);
+ }
+ if (!io.blobs.jumbf.empty()) {
+ JxlEncoderUseBoxes(frame_settings->enc);
+ JxlEncoderAddBox(frame_settings->enc, "jumb", io.blobs.jumbf.data(),
+ io.blobs.jumbf.size(), /*compress_box=*/JXL_TRUE);
}
-
if (frame_settings->enc->store_jpeg_metadata) {
jxl::jpeg::JPEGData data_in = *io.Main().jpeg_data;
jxl::PaddedBytes jpeg_data;
if (!jxl::jpeg::EncodeJPEGData(data_in, &jpeg_data,
frame_settings->values.cparams)) {
- return JXL_ENC_ERROR;
+ return JXL_API_ERROR(
+ frame_settings->enc, JXL_ENC_ERR_JBRD,
+ "JPEG bitstream reconstruction data cannot be encoded");
}
frame_settings->enc->jpeg_metadata = std::vector<uint8_t>(
jpeg_data.data(), jpeg_data.data() + jpeg_data.size());
@@ -1188,17 +1494,21 @@ JxlEncoderStatus JxlEncoderAddJPEGFrame(
jxl::ImageBundle(&frame_settings->enc->metadata.m),
{}});
if (!queued_frame) {
- return JXL_ENC_ERROR;
+ // TODO(jon): when can this happen? is this an API usage error?
+ return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_GENERIC,
+ "No frame queued?");
}
queued_frame->frame.SetFromImage(std::move(*io.Main().color()),
io.Main().c_current());
size_t xsize, ysize;
if (GetCurrentDimensions(frame_settings, xsize, ysize) != JXL_ENC_SUCCESS) {
- return JXL_API_ERROR("bad dimensions");
+ return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_GENERIC,
+ "bad dimensions");
}
if (xsize != static_cast<size_t>(io.Main().jpeg_data->width) ||
ysize != static_cast<size_t>(io.Main().jpeg_data->height)) {
- return JXL_API_ERROR("JPEG dimensions don't match frame dimensions");
+ return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_GENERIC,
+ "JPEG dimensions don't match frame dimensions");
}
std::vector<jxl::ImageF> extra_channels(
frame_settings->enc->metadata.m.num_extra_channels);
@@ -1224,11 +1534,24 @@ JxlEncoderStatus JxlEncoderAddImageFrame(
// Basic Info must be set, and color encoding must be set directly,
// or set to XYB via JxlBasicInfo.uses_original_profile = JXL_FALSE
// Otherwise, this is an API misuse.
- return JXL_ENC_ERROR;
+ return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_API_USAGE,
+ "Basic info or color encoding not set yet");
}
if (frame_settings->enc->frames_closed) {
- return JXL_ENC_ERROR;
+ return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_API_USAGE,
+ "Frame input already closed");
+ }
+ if (pixel_format->num_channels < 3) {
+ if (frame_settings->enc->basic_info.num_color_channels != 1) {
+ return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_API_USAGE,
+ "Grayscale pixel format input for an RGB image");
+ }
+ } else {
+ if (frame_settings->enc->basic_info.num_color_channels != 3) {
+ return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_API_USAGE,
+ "RGB pixel format input for a grayscale image");
+ }
}
auto queued_frame = jxl::MemoryManagerMakeUnique<jxl::JxlEncoderQueuedFrame>(
@@ -1241,7 +1564,9 @@ JxlEncoderStatus JxlEncoderAddImageFrame(
{}});
if (!queued_frame) {
- return JXL_ENC_ERROR;
+ // TODO(jon): when can this happen? is this an API usage error?
+ return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_GENERIC,
+ "No frame queued?");
}
jxl::ColorEncoding c_current;
@@ -1261,11 +1586,14 @@ JxlEncoderStatus JxlEncoderAddImageFrame(
static_cast<size_t>(num_channels == 2 || num_channels == 4);
if (has_interleaved_alpha >
frame_settings->enc->metadata.m.num_extra_channels) {
- return JXL_API_ERROR("number of extra channels mismatch");
+ return JXL_API_ERROR(
+ frame_settings->enc, JXL_ENC_ERR_API_USAGE,
+ "number of extra channels mismatch (need 1 extra channel for alpha)");
}
size_t xsize, ysize;
if (GetCurrentDimensions(frame_settings, xsize, ysize) != JXL_ENC_SUCCESS) {
- return JXL_API_ERROR("bad dimensions");
+ return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_GENERIC,
+ "bad dimensions");
}
std::vector<jxl::ImageF> extra_channels(
frame_settings->enc->metadata.m.num_extra_channels);
@@ -1298,11 +1626,13 @@ JxlEncoderStatus JxlEncoderAddImageFrame(
if (!jxl::BufferToImageBundle(*pixel_format, xsize, ysize, buffer, size,
frame_settings->enc->thread_pool.get(),
c_current, &(queued_frame->frame))) {
- return JXL_ENC_ERROR;
+ return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_API_USAGE,
+ "Invalid input buffer");
}
if (frame_settings->values.lossless &&
frame_settings->enc->metadata.m.xyb_encoded) {
- return JXL_API_ERROR("Set use_original_profile=true for lossless encoding");
+ return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_API_USAGE,
+ "Set use_original_profile=true for lossless encoding");
}
queued_frame->option_values.cparams.level =
frame_settings->enc->codestream_level;
@@ -1313,7 +1643,8 @@ JxlEncoderStatus JxlEncoderAddImageFrame(
JxlEncoderStatus JxlEncoderUseBoxes(JxlEncoder* enc) {
if (enc->wrote_bytes) {
- return JXL_API_ERROR("this setting can only be set at the beginning");
+ return JXL_API_ERROR(enc, JXL_ENC_ERR_API_USAGE,
+ "this setting can only be set at the beginning");
}
enc->use_boxes = true;
return JXL_ENC_SUCCESS;
@@ -1324,21 +1655,25 @@ JxlEncoderStatus JxlEncoderAddBox(JxlEncoder* enc, const JxlBoxType type,
JXL_BOOL compress_box) {
if (!enc->use_boxes) {
return JXL_API_ERROR(
+ enc, JXL_ENC_ERR_API_USAGE,
"must set JxlEncoderUseBoxes at the beginning to add boxes");
}
if (compress_box) {
if (memcmp("jxl", type, 3) == 0) {
return JXL_API_ERROR(
+ enc, JXL_ENC_ERR_API_USAGE,
"brob box may not contain a type starting with \"jxl\"");
}
if (memcmp("jbrd", type, 4) == 0) {
- return JXL_API_ERROR("jbrd box may not be brob compressed");
+ return JXL_API_ERROR(enc, JXL_ENC_ERR_API_USAGE,
+ "jbrd box may not be brob compressed");
}
if (memcmp("brob", type, 4) == 0) {
// The compress_box will compress an existing non-brob box into a brob
// box. If already giving a valid brotli-compressed brob box, set
// compress_box to false since it is already compressed.
- return JXL_API_ERROR("a brob box cannot contain another brob box");
+ return JXL_API_ERROR(enc, JXL_ENC_ERR_API_USAGE,
+ "a brob box cannot contain another brob box");
}
}
@@ -1356,27 +1691,33 @@ JXL_EXPORT JxlEncoderStatus JxlEncoderSetExtraChannelBuffer(
const JxlEncoderOptions* frame_settings, const JxlPixelFormat* pixel_format,
const void* buffer, size_t size, uint32_t index) {
if (index >= frame_settings->enc->metadata.m.num_extra_channels) {
- return JXL_API_ERROR("Invalid value for the index of extra channel");
+ return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_API_USAGE,
+ "Invalid value for the index of extra channel");
}
if (!frame_settings->enc->basic_info_set ||
!frame_settings->enc->color_encoding_set) {
- return JXL_ENC_ERROR;
+ return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_API_USAGE,
+ "Basic info has to be set first");
}
if (frame_settings->enc->input_queue.empty()) {
- return JXL_ENC_ERROR;
+ return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_API_USAGE,
+ "First add image frame, then extra channels");
}
if (frame_settings->enc->frames_closed) {
- return JXL_ENC_ERROR;
+ return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_API_USAGE,
+ "Frame input already closed");
}
size_t xsize, ysize;
if (GetCurrentDimensions(frame_settings, xsize, ysize) != JXL_ENC_SUCCESS) {
- return JXL_API_ERROR("bad dimensions");
+ return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_GENERIC,
+ "bad dimensions");
}
if (!jxl::BufferToImageF(*pixel_format, xsize, ysize, buffer, size,
frame_settings->enc->thread_pool.get(),
&frame_settings->enc->input_queue.back()
.frame->frame.extra_channels()[index])) {
- return JXL_API_ERROR("Failed to set buffer for extra channel");
+ return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_API_USAGE,
+ "Failed to set buffer for extra channel");
}
frame_settings->enc->input_queue.back().frame->ec_initialized[index] = 1;
@@ -1418,13 +1759,15 @@ JxlEncoderStatus JxlEncoderProcessOutput(JxlEncoder* enc, uint8_t** next_out,
JxlEncoderStatus JxlEncoderSetFrameHeader(JxlEncoderOptions* frame_settings,
const JxlFrameHeader* frame_header) {
if (frame_header->layer_info.blend_info.source > 3) {
- return JXL_API_ERROR("invalid blending source index");
+ return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_API_USAGE,
+ "invalid blending source index");
}
// If there are no extra channels, it's ok for the value to be 0.
if (frame_header->layer_info.blend_info.alpha != 0 &&
frame_header->layer_info.blend_info.alpha >=
frame_settings->enc->metadata.m.extra_channel_info.size()) {
- return JXL_API_ERROR("alpha blend channel index out of bounds");
+ return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_API_USAGE,
+ "alpha blend channel index out of bounds");
}
frame_settings->values.header = *frame_header;
@@ -1439,7 +1782,8 @@ JxlEncoderStatus JxlEncoderSetExtraChannelBlendInfo(
JxlEncoderOptions* frame_settings, size_t index,
const JxlBlendInfo* blend_info) {
if (index >= frame_settings->enc->metadata.m.num_extra_channels) {
- return JXL_API_ERROR("Invalid value for the index of extra channel");
+ return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_API_USAGE,
+ "Invalid value for the index of extra channel");
}
if (frame_settings->values.extra_channel_blend_info.size() !=
@@ -1457,7 +1801,8 @@ JxlEncoderStatus JxlEncoderSetFrameName(JxlEncoderFrameSettings* frame_settings,
const char* frame_name) {
std::string str = frame_name ? frame_name : "";
if (str.size() > 1071) {
- return JXL_API_ERROR("frame name can be max 1071 bytes long");
+ return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_API_USAGE,
+ "frame name can be max 1071 bytes long");
}
frame_settings->values.frame_name = str;
frame_settings->values.header.name_length = str.size();
diff --git a/media/libjxl/src/lib/jxl/encode_internal.h b/media/libjxl/src/lib/jxl/encode_internal.h
index 52301327a6..9f82546417 100644
--- a/media/libjxl/src/lib/jxl/encode_internal.h
+++ b/media/libjxl/src/lib/jxl/encode_internal.h
@@ -20,6 +20,84 @@
namespace jxl {
+/* Frame index box 'jxli' will start with Varint() for
+NF: has type Varint(): number of frames listed in the index.
+TNUM: has type u32: numerator of tick unit.
+TDEN: has type u32: denominator of tick unit. Value 0 means the file is
+ill-formed. per frame i listed: OFFi: has type Varint(): offset of start byte of
+this frame compared to start byte of previous frame from this index in the JPEG
+XL codestream. For the first frame, this is the offset from the first byte of
+the JPEG XL codestream. Ti: has type Varint(): duration in ticks between the
+start of this frame and the start of the next frame in the index. If this is the
+last frame in the index, this is the duration in ticks between the start of this
+frame and the end of the stream. A tick lasts TNUM / TDEN seconds. Fi: has type
+Varint(): amount of frames the next frame in the index occurs after this frame.
+If this is the last frame in the index, this is the amount of frames after this
+frame in the remainder of the stream. Only frames that are presented by the
+decoder are counted for this purpose, this excludes frames that are not intended
+for display but for compositing with other frames, such as frames that aren't
+the last frame with a duration of 0 ticks.
+
+All the frames listed in jxli are keyframes and the first frame is
+present in the list.
+There shall be either zero or one Frame Index boxes in a JPEG XL file.
+The offsets OFFi per frame are given as bytes in the codestream, not as
+bytes in the file format using the box structure. This means if JPEG XL Partial
+Codestream boxes are used, the offset is counted within the concatenated
+codestream, bytes from box headers or non-codestream boxes are not counted.
+*/
+
+typedef struct JxlEncoderFrameIndexBoxEntryStruct {
+ bool to_be_indexed;
+ uint32_t duration;
+ uint64_t OFFi;
+} JxlEncoderFrameIndexBoxEntry;
+
+typedef struct JxlEncoderFrameIndexBoxStruct {
+ // We always need to record the first frame entry, so presence of the
+ // first entry alone is not an indication if it was requested to be
+ // stored.
+ bool index_box_requested_through_api = false;
+
+ int64_t NF() const { return entries.size(); }
+ bool StoreFrameIndexBox() {
+ for (auto e : entries) {
+ if (e.to_be_indexed) {
+ return true;
+ }
+ }
+ return false;
+ }
+ int32_t TNUM = 1;
+ int32_t TDEN = 1000;
+
+ std::vector<JxlEncoderFrameIndexBoxEntry> entries;
+
+ // That way we can ensure that every index box will have the first frame.
+ // If the API user decides to mark it as an indexed frame, we call
+ // the AddFrame again, this time with requested.
+ void AddFrame(uint64_t OFFi, uint32_t duration, bool to_be_indexed) {
+ // We call AddFrame to every frame.
+ // Recording the first frame is required by the standard.
+ // Knowing the last frame is required, since the last indexed frame
+ // needs to know how many frames until the end.
+ // To be able to tell how many frames there are between each index
+ // entry we just record every frame here.
+ if (entries.size() == 1) {
+ if (OFFi == entries[0].OFFi) {
+ // API use for the first frame, let's clear the already recorded first
+ // frame.
+ entries.clear();
+ }
+ }
+ JxlEncoderFrameIndexBoxEntry e;
+ e.to_be_indexed = to_be_indexed;
+ e.OFFi = OFFi;
+ e.duration = duration;
+ entries.push_back(e);
+ }
+} JxlEncoderFrameIndexBox;
+
// The encoder options (such as quality, compression speed, ...) for a single
// frame, but not encoder-wide options such as box-related options.
typedef struct JxlEncoderFrameSettingsValuesStruct {
@@ -30,6 +108,7 @@ typedef struct JxlEncoderFrameSettingsValuesStruct {
JxlFrameHeader header;
std::vector<JxlBlendInfo> extra_channel_blend_info;
std::string frame_name;
+ bool frame_index_box = false;
} JxlEncoderFrameSettingsValues;
typedef std::array<uint8_t, 4> BoxType;
@@ -106,6 +185,7 @@ void AppendBoxHeader(const jxl::BoxType& type, size_t size, bool unbounded,
// Internal use only struct, can only be initialized correctly by
// JxlEncoderCreate.
struct JxlEncoderStruct {
+ JxlEncoderError error = JxlEncoderError::JXL_ENC_ERR_OK;
JxlMemoryManager memory_manager;
jxl::MemoryManagerUniquePtr<jxl::ThreadPool> thread_pool{
nullptr, jxl::MemoryManagerDeleteHelper(&memory_manager)};
@@ -126,6 +206,7 @@ struct JxlEncoderStruct {
// or accross multiple jxlp boxes.
size_t codestream_bytes_written_beginning_of_frame;
size_t codestream_bytes_written_end_of_frame;
+ jxl::JxlEncoderFrameIndexBox frame_index_box;
// Force using the container even if not needed
bool use_container;
@@ -135,7 +216,7 @@ struct JxlEncoderStruct {
// TODO(lode): move level into jxl::CompressParams since some C++
// implementation decisions should be based on it: level 10 allows more
// features to be used.
- uint32_t codestream_level;
+ int32_t codestream_level;
bool store_jpeg_metadata;
jxl::CodecMetadata metadata;
std::vector<uint8_t> jpeg_metadata;
@@ -145,6 +226,7 @@ struct JxlEncoderStruct {
// reconstruction box.
bool wrote_bytes;
jxl::CompressParams last_used_cparams;
+ JxlBasicInfo basic_info;
// Encoder wrote a jxlp (partial codestream) box, so any next codestream
// parts must also be written in jxlp boxes, a single jxlc box cannot be
diff --git a/media/libjxl/src/lib/jxl/encode_test.cc b/media/libjxl/src/lib/jxl/encode_test.cc
index e569e212f5..4f1ef0b2b8 100644
--- a/media/libjxl/src/lib/jxl/encode_test.cc
+++ b/media/libjxl/src/lib/jxl/encode_test.cc
@@ -11,7 +11,7 @@
#include "jxl/decode_cxx.h"
#include "jxl/encode_cxx.h"
#include "lib/extras/codec.h"
-#include "lib/jxl/dec_file.h"
+#include "lib/extras/dec/jxl.h"
#include "lib/jxl/enc_butteraugli_pnorm.h"
#include "lib/jxl/encode_internal.h"
#include "lib/jxl/jpeg/dec_jpeg_data.h"
@@ -58,8 +58,7 @@ TEST(EncodeTest, AddJPEGAfterCloseTest) {
JxlEncoderCloseInput(enc.get());
- const std::string jpeg_path =
- "third_party/imagecompression.info/flower_foveon.png.im_q85_420.jpg";
+ const std::string jpeg_path = "jxl/flower/flower.png.im_q85_420.jpg";
const jxl::PaddedBytes orig = jxl::ReadTestData(jpeg_path);
JxlEncoderFrameSettings* frame_settings =
@@ -110,7 +109,7 @@ TEST(EncodeTest, AddFrameBeforeBasicInfoTest) {
JxlColorEncoding color_encoding;
JxlColorEncodingSetToSRGB(&color_encoding,
/*is_gray=*/pixel_format.num_channels < 3);
- EXPECT_EQ(JXL_ENC_SUCCESS,
+ EXPECT_EQ(JXL_ENC_ERROR,
JxlEncoderSetColorEncoding(enc.get(), &color_encoding));
JxlEncoderFrameSettings* frame_settings =
JxlEncoderFrameSettingsCreate(enc.get(), NULL);
@@ -159,7 +158,9 @@ TEST(EncodeTest, DefaultParallelRunnerTest) {
}
void VerifyFrameEncoding(size_t xsize, size_t ysize, JxlEncoder* enc,
- const JxlEncoderFrameSettings* frame_settings) {
+ const JxlEncoderFrameSettings* frame_settings,
+ size_t max_compressed_size,
+ bool lossy_use_original_profile) {
JxlPixelFormat pixel_format = {4, JXL_TYPE_UINT16, JXL_BIG_ENDIAN, 0};
std::vector<uint8_t> pixels = jxl::test::GetSomeTestImage(xsize, ysize, 4, 0);
@@ -170,7 +171,7 @@ void VerifyFrameEncoding(size_t xsize, size_t ysize, JxlEncoder* enc,
jxl::test::JxlBasicInfoSetFromPixelFormat(&basic_info, &pixel_format);
basic_info.xsize = xsize;
basic_info.ysize = ysize;
- if (frame_settings->values.lossless) {
+ if (frame_settings->values.lossless || lossy_use_original_profile) {
basic_info.uses_original_profile = true;
} else {
basic_info.uses_original_profile = false;
@@ -179,9 +180,15 @@ void VerifyFrameEncoding(size_t xsize, size_t ysize, JxlEncoder* enc,
EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetCodestreamLevel(enc, 10));
EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetBasicInfo(enc, &basic_info));
JxlColorEncoding color_encoding;
- JxlColorEncodingSetToSRGB(&color_encoding,
- /*is_gray=*/pixel_format.num_channels < 3);
+ JxlColorEncodingSetToSRGB(&color_encoding, true);
+ EXPECT_EQ(JXL_ENC_ERROR, JxlEncoderSetColorEncoding(enc, &color_encoding));
+ JxlColorEncodingSetToSRGB(&color_encoding, false);
EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetColorEncoding(enc, &color_encoding));
+ pixel_format.num_channels = 1;
+ EXPECT_EQ(JXL_ENC_ERROR,
+ JxlEncoderAddImageFrame(frame_settings, &pixel_format,
+ pixels.data(), pixels.size()));
+ pixel_format.num_channels = 4;
EXPECT_EQ(JXL_ENC_SUCCESS,
JxlEncoderAddImageFrame(frame_settings, &pixel_format,
pixels.data(), pixels.size()));
@@ -201,11 +208,11 @@ void VerifyFrameEncoding(size_t xsize, size_t ysize, JxlEncoder* enc,
}
}
compressed.resize(next_out - compressed.data());
+ EXPECT_LE(compressed.size(), max_compressed_size);
EXPECT_EQ(JXL_ENC_SUCCESS, process_result);
- jxl::DecompressParams dparams;
jxl::CodecInOut decoded_io;
- EXPECT_TRUE(jxl::DecodeFile(
- dparams, jxl::Span<const uint8_t>(compressed.data(), compressed.size()),
+ EXPECT_TRUE(jxl::test::DecodeFile(
+ {}, jxl::Span<const uint8_t>(compressed.data(), compressed.size()),
&decoded_io, /*pool=*/nullptr));
EXPECT_LE(
@@ -213,13 +220,14 @@ void VerifyFrameEncoding(size_t xsize, size_t ysize, JxlEncoder* enc,
#if JXL_HIGH_PRECISION
1.8);
#else
- 4.8);
+ 8.0);
#endif
}
void VerifyFrameEncoding(JxlEncoder* enc,
const JxlEncoderFrameSettings* frame_settings) {
- VerifyFrameEncoding(63, 129, enc, frame_settings);
+ VerifyFrameEncoding(63, 129, enc, frame_settings, 2600,
+ /*lossy_use_original_profile=*/false);
}
TEST(EncodeTest, FrameEncodingTest) {
@@ -233,12 +241,14 @@ TEST(EncodeTest, EncoderResetTest) {
JxlEncoderPtr enc = JxlEncoderMake(nullptr);
EXPECT_NE(nullptr, enc.get());
VerifyFrameEncoding(50, 200, enc.get(),
- JxlEncoderFrameSettingsCreate(enc.get(), nullptr));
+ JxlEncoderFrameSettingsCreate(enc.get(), nullptr), 4300,
+ false);
// Encoder should become reusable for a new image from scratch after using
// reset.
JxlEncoderReset(enc.get());
VerifyFrameEncoding(157, 77, enc.get(),
- JxlEncoderFrameSettingsCreate(enc.get(), nullptr));
+ JxlEncoderFrameSettingsCreate(enc.get(), nullptr), 2300,
+ false);
}
TEST(EncodeTest, CmsTest) {
@@ -311,7 +321,7 @@ TEST(EncodeTest, frame_settingsTest) {
JxlEncoderFrameSettingsCreate(enc.get(), NULL);
EXPECT_EQ(JXL_ENC_SUCCESS,
JxlEncoderSetFrameLossless(frame_settings, JXL_TRUE));
- VerifyFrameEncoding(enc.get(), frame_settings);
+ VerifyFrameEncoding(63, 129, enc.get(), frame_settings, 3600, false);
EXPECT_EQ(true, enc->last_used_cparams.IsLossless());
}
@@ -321,7 +331,7 @@ TEST(EncodeTest, frame_settingsTest) {
JxlEncoderFrameSettings* frame_settings =
JxlEncoderFrameSettingsCreate(enc.get(), NULL);
EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetFrameDistance(frame_settings, 0.5));
- VerifyFrameEncoding(enc.get(), frame_settings);
+ VerifyFrameEncoding(63, 129, enc.get(), frame_settings, 3000, false);
EXPECT_EQ(0.5, enc->last_used_cparams.butteraugli_distance);
}
@@ -393,11 +403,12 @@ TEST(EncodeTest, frame_settingsTest) {
EXPECT_NE(nullptr, enc.get());
JxlEncoderFrameSettings* frame_settings =
JxlEncoderFrameSettingsCreate(enc.get(), NULL);
- EXPECT_EQ(JXL_ENC_SUCCESS,
- JxlEncoderFrameSettingsSetOption(
- frame_settings, JXL_ENC_FRAME_SETTING_PHOTON_NOISE, 1777));
+ EXPECT_EQ(
+ JXL_ENC_SUCCESS,
+ JxlEncoderFrameSettingsSetFloatOption(
+ frame_settings, JXL_ENC_FRAME_SETTING_PHOTON_NOISE, 1777.777));
VerifyFrameEncoding(enc.get(), frame_settings);
- EXPECT_EQ(1777.0f, enc->last_used_cparams.photon_noise_iso);
+ EXPECT_NEAR(1777.777f, enc->last_used_cparams.photon_noise_iso, 1E-6);
}
{
@@ -406,13 +417,13 @@ TEST(EncodeTest, frame_settingsTest) {
JxlEncoderFrameSettings* frame_settings =
JxlEncoderFrameSettingsCreate(enc.get(), NULL);
EXPECT_EQ(JXL_ENC_SUCCESS,
- JxlEncoderFrameSettingsSetOption(
+ JxlEncoderFrameSettingsSetFloatOption(
frame_settings,
- JXL_ENC_FRAME_SETTING_CHANNEL_COLORS_GLOBAL_PERCENT, 55));
+ JXL_ENC_FRAME_SETTING_CHANNEL_COLORS_GLOBAL_PERCENT, 55.0f));
EXPECT_EQ(JXL_ENC_SUCCESS,
- JxlEncoderFrameSettingsSetOption(
+ JxlEncoderFrameSettingsSetFloatOption(
frame_settings,
- JXL_ENC_FRAME_SETTING_CHANNEL_COLORS_GROUP_PERCENT, 25));
+ JXL_ENC_FRAME_SETTING_CHANNEL_COLORS_GROUP_PERCENT, 25.0f));
EXPECT_EQ(JXL_ENC_SUCCESS,
JxlEncoderFrameSettingsSetOption(
frame_settings, JXL_ENC_FRAME_SETTING_PALETTE_COLORS, 70000));
@@ -420,9 +431,10 @@ TEST(EncodeTest, frame_settingsTest) {
JxlEncoderFrameSettingsSetOption(
frame_settings, JXL_ENC_FRAME_SETTING_LOSSY_PALETTE, 1));
VerifyFrameEncoding(enc.get(), frame_settings);
- EXPECT_EQ(55.0f,
- enc->last_used_cparams.channel_colors_pre_transform_percent);
- EXPECT_EQ(25.0f, enc->last_used_cparams.channel_colors_percent);
+ EXPECT_NEAR(55.0f,
+ enc->last_used_cparams.channel_colors_pre_transform_percent,
+ 1E-6);
+ EXPECT_NEAR(25.0f, enc->last_used_cparams.channel_colors_percent, 1E-6);
EXPECT_EQ(70000, enc->last_used_cparams.palette_colors);
EXPECT_EQ(true, enc->last_used_cparams.lossy_palette);
}
@@ -442,22 +454,20 @@ TEST(EncodeTest, frame_settingsTest) {
EXPECT_EQ(JXL_ENC_SUCCESS,
JxlEncoderFrameSettingsSetOption(
frame_settings, JXL_ENC_FRAME_SETTING_MODULAR_PREDICTOR, 14));
- EXPECT_EQ(JXL_ENC_SUCCESS,
- JxlEncoderFrameSettingsSetOption(
- frame_settings,
- JXL_ENC_FRAME_SETTING_MODULAR_MA_TREE_LEARNING_PERCENT, 77));
+ EXPECT_EQ(
+ JXL_ENC_SUCCESS,
+ JxlEncoderFrameSettingsSetFloatOption(
+ frame_settings,
+ JXL_ENC_FRAME_SETTING_MODULAR_MA_TREE_LEARNING_PERCENT, 77.0f));
EXPECT_EQ(
JXL_ENC_SUCCESS,
JxlEncoderFrameSettingsSetOption(
frame_settings, JXL_ENC_FRAME_SETTING_MODULAR_NB_PREV_CHANNELS, 7));
VerifyFrameEncoding(enc.get(), frame_settings);
- // It was set to 30, but becomes 32 because in the C++ implementation, the
- // numerical RCT values are shifted 2 compared to the specification. The
- // API uses the values seen in the JXL specification.
- EXPECT_EQ(32, enc->last_used_cparams.colorspace);
+ EXPECT_EQ(30, enc->last_used_cparams.colorspace);
EXPECT_EQ(2, enc->last_used_cparams.modular_group_size_shift);
EXPECT_EQ(jxl::Predictor::Best, enc->last_used_cparams.options.predictor);
- EXPECT_EQ(0.77f, enc->last_used_cparams.options.nb_repeats);
+ EXPECT_NEAR(0.77f, enc->last_used_cparams.options.nb_repeats, 1E-6);
EXPECT_EQ(7, enc->last_used_cparams.options.max_properties);
}
@@ -486,6 +496,36 @@ TEST(EncodeTest, frame_settingsTest) {
}
}
+TEST(EncodeTest, LossyEncoderUseOriginalProfileTest) {
+ {
+ JxlEncoderPtr enc = JxlEncoderMake(nullptr);
+ ASSERT_NE(nullptr, enc.get());
+ JxlEncoderFrameSettings* frame_settings =
+ JxlEncoderFrameSettingsCreate(enc.get(), NULL);
+ VerifyFrameEncoding(63, 129, enc.get(), frame_settings, 4100, true);
+ }
+ {
+ JxlEncoderPtr enc = JxlEncoderMake(nullptr);
+ ASSERT_NE(nullptr, enc.get());
+ JxlEncoderFrameSettings* frame_settings =
+ JxlEncoderFrameSettingsCreate(enc.get(), NULL);
+ EXPECT_EQ(JXL_ENC_SUCCESS,
+ JxlEncoderFrameSettingsSetOption(
+ frame_settings, JXL_ENC_FRAME_SETTING_PROGRESSIVE_DC, 2));
+ VerifyFrameEncoding(63, 129, enc.get(), frame_settings, 4500, true);
+ }
+ {
+ JxlEncoderPtr enc = JxlEncoderMake(nullptr);
+ ASSERT_NE(nullptr, enc.get());
+ JxlEncoderFrameSettings* frame_settings =
+ JxlEncoderFrameSettingsCreate(enc.get(), NULL);
+ ASSERT_EQ(JXL_ENC_SUCCESS,
+ JxlEncoderFrameSettingsSetOption(
+ frame_settings, JXL_ENC_FRAME_SETTING_EFFORT, 8));
+ VerifyFrameEncoding(63, 129, enc.get(), frame_settings, 3700, true);
+ }
+}
+
namespace {
// Returns a copy of buf from offset to offset+size, or a new zeroed vector if
// the result would have been out of bounds taking integer overflow into
@@ -759,6 +799,7 @@ TEST(EncodeTest, CodestreamLevelVerificationTest) {
// Set an image dimension that is too large for level 5, but fits in level 10
basic_info.xsize = 1ull << 30ull;
+ EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetCodestreamLevel(enc.get(), 5));
EXPECT_EQ(JXL_ENC_ERROR, JxlEncoderSetBasicInfo(enc.get(), &basic_info));
EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetCodestreamLevel(enc.get(), 10));
EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetBasicInfo(enc.get(), &basic_info));
@@ -771,8 +812,7 @@ TEST(EncodeTest, CodestreamLevelVerificationTest) {
}
TEST(EncodeTest, JXL_TRANSCODE_JPEG_TEST(JPEGReconstructionTest)) {
- const std::string jpeg_path =
- "third_party/imagecompression.info/flower_foveon.png.im_q85_420.jpg";
+ const std::string jpeg_path = "jxl/flower/flower.png.im_q85_420.jpg";
const jxl::PaddedBytes orig = jxl::ReadTestData(jpeg_path);
JxlEncoderPtr enc = JxlEncoderMake(nullptr);
@@ -800,61 +840,11 @@ TEST(EncodeTest, JXL_TRANSCODE_JPEG_TEST(JPEGReconstructionTest)) {
compressed.resize(next_out - compressed.data());
EXPECT_EQ(JXL_ENC_SUCCESS, process_result);
- Container container = {};
- jxl::Span<const uint8_t> encoded_span =
- jxl::Span<const uint8_t>(compressed.data(), compressed.size());
- EXPECT_TRUE(container.Decode(&encoded_span));
- EXPECT_EQ(0u, encoded_span.size());
- bool found_jbrd = false;
- bool found_jxlc = false;
- bool found_jxlp = false;
- size_t jbrd_index = 0;
- std::vector<uint8_t> codestream_bytes;
- // The encoder is allowed to either emit a jxlc or one or more jxlp.
- for (size_t i = 0; i < container.boxes.size(); ++i) {
- if (memcmp("jbrd", container.boxes[i].type, 4) == 0) {
- EXPECT_EQ(false, found_jxlc); // Max 1 jbrd
- found_jbrd = true;
- jbrd_index = i;
- }
- if (memcmp("jxlc", container.boxes[i].type, 4) == 0) {
- EXPECT_EQ(false, found_jxlc); // Max 1 jxlc
- EXPECT_EQ(false, found_jxlp); // Can't mix jxlc and jxlp
- found_jxlc = true;
- codestream_bytes.insert(
- codestream_bytes.end(), container.boxes[i].data.data(),
- container.boxes[i].data.data() + container.boxes[i].data.size());
- }
- if (memcmp("jxlp", container.boxes[i].type, 4) == 0) {
- EXPECT_EQ(false, found_jxlc); // Can't mix jxlc and jxlp
- found_jxlp = true;
- // Append all data except the first 4 box content bytes which are the
- // jxpl box counter.
- codestream_bytes.insert(
- codestream_bytes.end(), container.boxes[i].data.data() + 4,
- container.boxes[i].data.data() + container.boxes[i].data.size());
- }
- }
- EXPECT_EQ(true, found_jbrd);
- EXPECT_EQ(true, found_jxlc || found_jxlp);
-
- jxl::CodecInOut decoded_io;
- decoded_io.Main().jpeg_data = jxl::make_unique<jxl::jpeg::JPEGData>();
- EXPECT_TRUE(jxl::jpeg::DecodeJPEGData(container.boxes[jbrd_index].data,
- decoded_io.Main().jpeg_data.get()));
-
- jxl::DecompressParams dparams;
- dparams.keep_dct = true;
- jxl::Span<const uint8_t> codestream_span = jxl::Span<const uint8_t>(
- codestream_bytes.data(), codestream_bytes.size());
- EXPECT_TRUE(jxl::DecodeFile(dparams, codestream_span, &decoded_io, nullptr));
-
+ jxl::extras::JXLDecompressParams dparams;
std::vector<uint8_t> decoded_jpeg_bytes;
- auto write = [&decoded_jpeg_bytes](const uint8_t* buf, size_t len) {
- decoded_jpeg_bytes.insert(decoded_jpeg_bytes.end(), buf, buf + len);
- return len;
- };
- EXPECT_TRUE(jxl::jpeg::WriteJpeg(*decoded_io.Main().jpeg_data, write));
+ jxl::extras::PackedPixelFile ppf;
+ EXPECT_TRUE(DecodeImageJXL(compressed.data(), compressed.size(), dparams,
+ nullptr, &ppf, &decoded_jpeg_bytes));
EXPECT_EQ(decoded_jpeg_bytes.size(), orig.size());
EXPECT_EQ(0, memcmp(decoded_jpeg_bytes.data(), orig.data(), orig.size()));
@@ -1306,8 +1296,9 @@ TEST(EncodeTest, JXL_TRANSCODE_JPEG_TEST(JPEGFrameTest)) {
for (int skip_basic_info = 0; skip_basic_info < 2; skip_basic_info++) {
for (int skip_color_encoding = 0; skip_color_encoding < 2;
skip_color_encoding++) {
- const std::string jpeg_path =
- "third_party/imagecompression.info/flower_foveon_cropped.jpg";
+ // cannot set color encoding if basic info is not set
+ if (skip_basic_info && !skip_color_encoding) continue;
+ const std::string jpeg_path = "jxl/flower/flower_cropped.jpg";
const jxl::PaddedBytes orig = jxl::ReadTestData(jpeg_path);
jxl::CodecInOut orig_io;
ASSERT_TRUE(SetFromBytes(jxl::Span<const uint8_t>(orig), &orig_io,
@@ -1354,11 +1345,9 @@ TEST(EncodeTest, JXL_TRANSCODE_JPEG_TEST(JPEGFrameTest)) {
compressed.resize(next_out - compressed.data());
EXPECT_EQ(JXL_ENC_SUCCESS, process_result);
- jxl::DecompressParams dparams;
jxl::CodecInOut decoded_io;
- EXPECT_TRUE(jxl::DecodeFile(
- dparams,
- jxl::Span<const uint8_t>(compressed.data(), compressed.size()),
+ EXPECT_TRUE(jxl::test::DecodeFile(
+ {}, jxl::Span<const uint8_t>(compressed.data(), compressed.size()),
&decoded_io, /*pool=*/nullptr));
EXPECT_LE(
diff --git a/media/libjxl/src/lib/jxl/epf.cc b/media/libjxl/src/lib/jxl/epf.cc
index 872720b9d0..7288ed9ca6 100644
--- a/media/libjxl/src/lib/jxl/epf.cc
+++ b/media/libjxl/src/lib/jxl/epf.cc
@@ -62,7 +62,7 @@ void ComputeSigma(const Rect& block_rect, PassesDecoderState* state) {
const uint8_t* JXL_RESTRICT sharpness_row =
block_rect.ConstRow(state->shared->epf_sharpness, by);
AcStrategyRow acs_row = ac_strategy.ConstRow(block_rect, by);
- const int* const JXL_RESTRICT row_quant =
+ const int32_t* const JXL_RESTRICT row_quant =
block_rect.ConstRow(state->shared->raw_quant_field, by);
for (size_t bx = 0; bx < block_rect.xsize(); bx++) {
diff --git a/media/libjxl/src/lib/jxl/exif.cc b/media/libjxl/src/lib/jxl/exif.cc
deleted file mode 100644
index ae27dd064b..0000000000
--- a/media/libjxl/src/lib/jxl/exif.cc
+++ /dev/null
@@ -1,89 +0,0 @@
-// Copyright (c) the JPEG XL Project Authors. All rights reserved.
-//
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-#include "lib/jxl/exif.h"
-
-namespace jxl {
-
-constexpr uint16_t kExifOrientationTag = 274;
-
-// Checks if a blob looks like Exif, and if so, sets bigendian
-// according to the tiff endianness
-bool IsExif(const std::vector<uint8_t>& exif, bool* bigendian) {
- if (exif.size() < 12) return false; // not enough bytes for a valid exif blob
- const uint8_t* t = exif.data();
- if (LoadLE32(t) == 0x2A004D4D) {
- *bigendian = true;
- return true;
- } else if (LoadLE32(t) == 0x002A4949) {
- *bigendian = false;
- return true;
- }
- return false; // not a valid tiff header
-}
-
-// Finds the position of an Exif tag, or 0 if it is not found
-size_t FindExifTagPosition(const std::vector<uint8_t>& exif, uint16_t tagname) {
- bool bigendian;
- if (!IsExif(exif, &bigendian)) return 0;
- const uint8_t* t = exif.data() + 4;
- uint32_t offset = (bigendian ? LoadBE32(t) : LoadLE32(t));
- if (exif.size() < 12 + offset + 2 || offset < 8) return 0;
- t += offset - 4;
- uint16_t nb_tags = (bigendian ? LoadBE16(t) : LoadLE16(t));
- t += 2;
- while (nb_tags > 0) {
- if (t + 12 >= exif.data() + exif.size()) return 0;
- uint16_t tag = (bigendian ? LoadBE16(t) : LoadLE16(t));
- t += 2;
- if (tag == tagname) return static_cast<size_t>(t - exif.data());
- t += 10;
- nb_tags--;
- }
- return 0;
-}
-
-// TODO (jon): tag 1 can be used to represent Adobe RGB 1998 if it has value
-// "R03"
-// TODO (jon): set intrinsic dimensions according to
-// https://discourse.wicg.io/t/proposal-exif-image-resolution-auto-and-from-image/4326/24
-void InterpretExif(const std::vector<uint8_t>& exif, CodecMetadata* metadata) {
- bool bigendian;
- if (!IsExif(exif, &bigendian)) return;
- size_t o_pos = FindExifTagPosition(exif, kExifOrientationTag);
- if (o_pos) {
- const uint8_t* t = exif.data() + o_pos;
- uint16_t type = (bigendian ? LoadBE16(t) : LoadLE16(t));
- t += 2;
- uint32_t count = (bigendian ? LoadBE32(t) : LoadLE32(t));
- t += 4;
- uint16_t value = (bigendian ? LoadBE16(t) : LoadLE16(t));
- t += 4;
- if (type == 3 && count == 1 && value >= 1 && value <= 8) {
- metadata->m.orientation = value;
- }
- }
-}
-
-void ResetExifOrientation(std::vector<uint8_t>& exif) {
- bool bigendian;
- if (!IsExif(exif, &bigendian)) return;
- size_t o_pos = FindExifTagPosition(exif, kExifOrientationTag);
- if (o_pos) {
- uint8_t* t = exif.data() + o_pos;
- uint16_t type = (bigendian ? LoadBE16(t) : LoadLE16(t));
- t += 2;
- uint32_t count = (bigendian ? LoadBE32(t) : LoadLE32(t));
- t += 4;
- if (type == 3 && count == 1) {
- if (bigendian)
- StoreBE16(1, t);
- else
- StoreLE16(1, t);
- }
- }
-}
-
-} // namespace jxl
diff --git a/media/libjxl/src/lib/jxl/exif.h b/media/libjxl/src/lib/jxl/exif.h
index 0520cbfd31..06652fc730 100644
--- a/media/libjxl/src/lib/jxl/exif.h
+++ b/media/libjxl/src/lib/jxl/exif.h
@@ -9,18 +9,76 @@
// Basic parsing of Exif (just enough for the render-impacting things
// like orientation)
+#include "jxl/codestream_header.h"
#include "lib/jxl/base/padded_bytes.h"
#include "lib/jxl/image_metadata.h"
namespace jxl {
+constexpr uint16_t kExifOrientationTag = 274;
+
+// Checks if a blob looks like Exif, and if so, sets bigendian
+// according to the tiff endianness
+inline bool IsExif(const std::vector<uint8_t>& exif, bool* bigendian) {
+ if (exif.size() < 12) return false; // not enough bytes for a valid exif blob
+ const uint8_t* t = exif.data();
+ if (LoadLE32(t) == 0x2A004D4D) {
+ *bigendian = true;
+ return true;
+ } else if (LoadLE32(t) == 0x002A4949) {
+ *bigendian = false;
+ return true;
+ }
+ return false; // not a valid tiff header
+}
+
+// Finds the position of an Exif tag, or 0 if it is not found
+inline size_t FindExifTagPosition(const std::vector<uint8_t>& exif,
+ uint16_t tagname) {
+ bool bigendian;
+ if (!IsExif(exif, &bigendian)) return 0;
+ const uint8_t* t = exif.data() + 4;
+ uint32_t offset = (bigendian ? LoadBE32(t) : LoadLE32(t));
+ if (exif.size() < 12 + offset + 2 || offset < 8) return 0;
+ t += offset - 4;
+ uint16_t nb_tags = (bigendian ? LoadBE16(t) : LoadLE16(t));
+ t += 2;
+ while (nb_tags > 0) {
+ if (t + 12 >= exif.data() + exif.size()) return 0;
+ uint16_t tag = (bigendian ? LoadBE16(t) : LoadLE16(t));
+ t += 2;
+ if (tag == tagname) return static_cast<size_t>(t - exif.data());
+ t += 10;
+ nb_tags--;
+ }
+ return 0;
+}
+
+// TODO (jon): tag 1 can be used to represent Adobe RGB 1998 if it has value
+// "R03"
+// TODO (jon): set intrinsic dimensions according to
+// https://discourse.wicg.io/t/proposal-exif-image-resolution-auto-and-from-image/4326/24
// Parses the Exif data just enough to extract any render-impacting info.
// If the Exif data is invalid or could not be parsed, then it is treated
// as a no-op.
-void InterpretExif(const std::vector<uint8_t>& exif, CodecMetadata* metadata);
-
-// Sets the Exif orientation to the identity, to avoid repeated orientation
-void ResetExifOrientation(std::vector<uint8_t>& exif);
+inline void InterpretExif(const std::vector<uint8_t>& exif,
+ JxlOrientation* orientation) {
+ bool bigendian;
+ if (!IsExif(exif, &bigendian)) return;
+ size_t o_pos = FindExifTagPosition(exif, kExifOrientationTag);
+ if (o_pos) {
+ const uint8_t* t = exif.data() + o_pos;
+ uint16_t type = (bigendian ? LoadBE16(t) : LoadLE16(t));
+ t += 2;
+ uint32_t count = (bigendian ? LoadBE32(t) : LoadLE32(t));
+ t += 4;
+ uint16_t value = (bigendian ? LoadBE16(t) : LoadLE16(t));
+ t += 4;
+ if (type == 3 && count == 1 && value >= 1 && value <= 8) {
+ *orientation = static_cast<JxlOrientation>(value);
+ }
+ }
+}
} // namespace jxl
diff --git a/media/libjxl/src/lib/jxl/fast_math-inl.h b/media/libjxl/src/lib/jxl/fast_math-inl.h
index 60be66829a..5c48034290 100644
--- a/media/libjxl/src/lib/jxl/fast_math-inl.h
+++ b/media/libjxl/src/lib/jxl/fast_math-inl.h
@@ -21,9 +21,24 @@ namespace jxl {
namespace HWY_NAMESPACE {
// These templates are not found via ADL.
+using hwy::HWY_NAMESPACE::Abs;
+using hwy::HWY_NAMESPACE::Add;
+using hwy::HWY_NAMESPACE::Eq;
+using hwy::HWY_NAMESPACE::Floor;
+using hwy::HWY_NAMESPACE::Ge;
+using hwy::HWY_NAMESPACE::GetLane;
+using hwy::HWY_NAMESPACE::IfThenElse;
+using hwy::HWY_NAMESPACE::IfThenZeroElse;
+using hwy::HWY_NAMESPACE::Le;
+using hwy::HWY_NAMESPACE::Min;
+using hwy::HWY_NAMESPACE::Mul;
+using hwy::HWY_NAMESPACE::MulAdd;
+using hwy::HWY_NAMESPACE::NegMulAdd;
using hwy::HWY_NAMESPACE::Rebind;
using hwy::HWY_NAMESPACE::ShiftLeft;
using hwy::HWY_NAMESPACE::ShiftRight;
+using hwy::HWY_NAMESPACE::Sub;
+using hwy::HWY_NAMESPACE::Xor;
// Computes base-2 logarithm like std::log2. Undefined if negative / NaN.
// L1 error ~3.9E-6
@@ -41,12 +56,13 @@ V FastLog2f(const DF df, V x) {
const auto x_bits = BitCast(di, x);
// Range reduction to [-1/3, 1/3] - 3 integer, 2 float ops
- const auto exp_bits = x_bits - Set(di, 0x3f2aaaab); // = 2/3
+ const auto exp_bits = Sub(x_bits, Set(di, 0x3f2aaaab)); // = 2/3
// Shifted exponent = log2; also used to clear mantissa.
const auto exp_shifted = ShiftRight<23>(exp_bits);
- const auto mantissa = BitCast(df, x_bits - ShiftLeft<23>(exp_shifted));
+ const auto mantissa = BitCast(df, Sub(x_bits, ShiftLeft<23>(exp_shifted)));
const auto exp_val = ConvertTo(df, exp_shifted);
- return EvalRationalPolynomial(df, mantissa - Set(df, 1.0f), p, q) + exp_val;
+ return Add(EvalRationalPolynomial(df, Sub(mantissa, Set(df, 1.0f)), p, q),
+ exp_val);
}
// max relative error ~3e-7
@@ -54,22 +70,23 @@ template <class DF, class V>
V FastPow2f(const DF df, V x) {
const Rebind<int32_t, DF> di;
auto floorx = Floor(x);
- auto exp = BitCast(df, ShiftLeft<23>(ConvertTo(di, floorx) + Set(di, 127)));
- auto frac = x - floorx;
- auto num = frac + Set(df, 1.01749063e+01);
+ auto exp =
+ BitCast(df, ShiftLeft<23>(Add(ConvertTo(di, floorx), Set(di, 127))));
+ auto frac = Sub(x, floorx);
+ auto num = Add(frac, Set(df, 1.01749063e+01));
num = MulAdd(num, frac, Set(df, 4.88687798e+01));
num = MulAdd(num, frac, Set(df, 9.85506591e+01));
- num = num * exp;
+ num = Mul(num, exp);
auto den = MulAdd(frac, Set(df, 2.10242958e-01), Set(df, -2.22328856e-02));
den = MulAdd(den, frac, Set(df, -1.94414990e+01));
den = MulAdd(den, frac, Set(df, 9.85506633e+01));
- return num / den;
+ return Div(num, den);
}
// max relative error ~3e-5
template <class DF, class V>
V FastPowf(const DF df, V base, V exponent) {
- return FastPow2f(df, FastLog2f(df, base) * exponent);
+ return FastPow2f(df, Mul(FastLog2f(df, base), exponent));
}
// Computes cosine like std::cos.
@@ -79,18 +96,18 @@ V FastCosf(const DF df, V x) {
// Step 1: range reduction to [0, 2pi)
const auto pi2 = Set(df, kPi * 2.0f);
const auto pi2_inv = Set(df, 0.5f / kPi);
- const auto npi2 = Floor(x * pi2_inv) * pi2;
- const auto xmodpi2 = x - npi2;
+ const auto npi2 = Mul(Floor(Mul(x, pi2_inv)), pi2);
+ const auto xmodpi2 = Sub(x, npi2);
// Step 2: range reduction to [0, pi]
- const auto x_pi = Min(xmodpi2, pi2 - xmodpi2);
+ const auto x_pi = Min(xmodpi2, Sub(pi2, xmodpi2));
// Step 3: range reduction to [0, pi/2]
- const auto above_pihalf = x_pi >= Set(df, kPi / 2.0f);
- const auto x_pihalf = IfThenElse(above_pihalf, Set(df, kPi) - x_pi, x_pi);
+ const auto above_pihalf = Ge(x_pi, Set(df, kPi / 2.0f));
+ const auto x_pihalf = IfThenElse(above_pihalf, Sub(Set(df, kPi), x_pi), x_pi);
// Step 4: Taylor-like approximation, scaled by 2**0.75 to make angle
// duplication steps faster, on x/4.
- const auto xs = x_pihalf * Set(df, 0.25f);
- const auto x2 = xs * xs;
- const auto x4 = x2 * x2;
+ const auto xs = Mul(x_pihalf, Set(df, 0.25f));
+ const auto x2 = Mul(xs, xs);
+ const auto x4 = Mul(x2, x2);
const auto cosx_prescaling =
MulAdd(x4, Set(df, 0.06960438),
MulAdd(x2, Set(df, -0.84087373), Set(df, 1.68179268)));
@@ -101,7 +118,7 @@ V FastCosf(const DF df, V x) {
// Step 6: change sign if needed.
const Rebind<uint32_t, DF> du;
auto signbit = ShiftLeft<31>(BitCast(du, VecFromMask(df, above_pihalf)));
- return BitCast(df, signbit ^ BitCast(du, cosx_scale2));
+ return BitCast(df, Xor(signbit, BitCast(du, cosx_scale2)));
}
// Computes the error function like std::erf.
@@ -111,7 +128,7 @@ V FastErff(const DF df, V x) {
// Formula from
// https://en.wikipedia.org/wiki/Error_function#Numerical_approximations
// but constants have been recomputed.
- const auto xle0 = x <= Zero(df);
+ const auto xle0 = Le(x, Zero(df));
const auto absx = Abs(x);
// Compute 1 - 1 / ((((x * a + b) * x + c) * x + d) * x + 1)**4
const auto denom1 =
@@ -119,13 +136,13 @@ V FastErff(const DF df, V x) {
const auto denom2 = MulAdd(denom1, absx, Set(df, 2.32120216e-01));
const auto denom3 = MulAdd(denom2, absx, Set(df, 2.77820801e-01));
const auto denom4 = MulAdd(denom3, absx, Set(df, 1.0f));
- const auto denom5 = denom4 * denom4;
- const auto inv_denom5 = Set(df, 1.0f) / denom5;
+ const auto denom5 = Mul(denom4, denom4);
+ const auto inv_denom5 = Div(Set(df, 1.0f), denom5);
const auto result = NegMulAdd(inv_denom5, inv_denom5, Set(df, 1.0f));
// Change sign if needed.
const Rebind<uint32_t, DF> du;
auto signbit = ShiftLeft<31>(BitCast(du, VecFromMask(df, xle0)));
- return BitCast(df, signbit ^ BitCast(du, result));
+ return BitCast(df, Xor(signbit, BitCast(du, result)));
}
inline float FastLog2f(float f) {
@@ -153,6 +170,47 @@ inline float FastErff(float f) {
return GetLane(FastErff(D, Set(D, f)));
}
+// Returns cbrt(x) + add with 6 ulp max error.
+// Modified from vectormath_exp.h, Apache 2 license.
+// https://www.agner.org/optimize/vectorclass.zip
+template <class V>
+V CubeRootAndAdd(const V x, const V add) {
+ const HWY_FULL(float) df;
+ const HWY_FULL(int32_t) di;
+
+ const auto kExpBias = Set(di, 0x54800000); // cast(1.) + cast(1.) / 3
+ const auto kExpMul = Set(di, 0x002AAAAA); // shifted 1/3
+ const auto k1_3 = Set(df, 1.0f / 3);
+ const auto k4_3 = Set(df, 4.0f / 3);
+
+ const auto xa = x; // assume inputs never negative
+ const auto xa_3 = Mul(k1_3, xa);
+
+ // Multiply exponent by -1/3
+ const auto m1 = BitCast(di, xa);
+ // Special case for 0. 0 is represented with an exponent of 0, so the
+ // "kExpBias - 1/3 * exp" below gives the wrong result. The IfThenZeroElse()
+ // sets those values as 0, which prevents having NaNs in the computations
+ // below.
+ // TODO(eustas): use fused op
+ const auto m2 = IfThenZeroElse(
+ Eq(m1, Zero(di)), Sub(kExpBias, Mul((ShiftRight<23>(m1)), kExpMul)));
+ auto r = BitCast(df, m2);
+
+ // Newton-Raphson iterations
+ for (int i = 0; i < 3; i++) {
+ const auto r2 = Mul(r, r);
+ r = NegMulAdd(xa_3, Mul(r2, r2), Mul(k4_3, r));
+ }
+ // Final iteration
+ auto r2 = Mul(r, r);
+ r = MulAdd(k1_3, NegMulAdd(xa, Mul(r2, r2), r), r);
+ r2 = Mul(r, r);
+ r = MulAdd(r2, x, add);
+
+ return r;
+}
+
// NOLINTNEXTLINE(google-readability-namespace-comments)
} // namespace HWY_NAMESPACE
} // namespace jxl
@@ -161,6 +219,8 @@ HWY_AFTER_NAMESPACE();
#endif // LIB_JXL_FAST_MATH_INL_H_
#if HWY_ONCE
+#ifndef FAST_MATH_ONCE
+#define FAST_MATH_ONCE
namespace jxl {
inline float FastLog2f(float f) { return HWY_STATIC_DISPATCH(FastLog2f)(f); }
@@ -172,4 +232,5 @@ inline float FastCosf(float f) { return HWY_STATIC_DISPATCH(FastCosf)(f); }
inline float FastErff(float f) { return HWY_STATIC_DISPATCH(FastErff)(f); }
} // namespace jxl
+#endif // FAST_MATH_ONCE
#endif // HWY_ONCE
diff --git a/media/libjxl/src/lib/jxl/fast_math_test.cc b/media/libjxl/src/lib/jxl/fast_math_test.cc
index e8958ab12b..897aadc120 100644
--- a/media/libjxl/src/lib/jxl/fast_math_test.cc
+++ b/media/libjxl/src/lib/jxl/fast_math_test.cc
@@ -13,7 +13,6 @@
#include "lib/jxl/dec_xyb-inl.h"
#include "lib/jxl/enc_color_management.h"
#include "lib/jxl/enc_xyb.h"
-#include "lib/jxl/fast_math-inl.h"
#include "lib/jxl/transfer_functions-inl.h"
// Test utils
@@ -51,7 +50,7 @@ HWY_NOINLINE void TestFastPow2() {
const float actual = GetLane(actual_v);
const float expected = std::pow(2, f);
const float rel_err = std::abs(expected - actual) / expected;
- EXPECT_LT(rel_err, 3.1E-7) << "f = " << f;
+ EXPECT_LT(rel_err, 3.1E-6) << "f = " << f;
max_rel_err = std::max(max_rel_err, rel_err);
}
printf("max rel err %e\n", static_cast<double>(max_rel_err));
@@ -107,6 +106,22 @@ HWY_NOINLINE void TestFastErf() {
printf("max abs err %e\n", static_cast<double>(max_abs_err));
}
+HWY_NOINLINE void TestCubeRoot() {
+ const HWY_FULL(float) d;
+ for (uint64_t x5 = 0; x5 < 2000000; x5++) {
+ const float x = x5 * 1E-5f;
+ const float expected = cbrtf(x);
+ HWY_ALIGN float approx[MaxLanes(d)];
+ Store(CubeRootAndAdd(Set(d, x), Zero(d)), d, approx);
+
+ // All lanes are same
+ for (size_t i = 1; i < Lanes(d); ++i) {
+ EXPECT_NEAR(approx[0], approx[i], 5E-7f);
+ }
+ EXPECT_NEAR(approx[0], expected, 8E-7f);
+ }
+}
+
HWY_NOINLINE void TestFastSRGB() {
constexpr size_t kNumTrials = 1 << 23;
Rng rng(1);
@@ -261,6 +276,7 @@ HWY_EXPORT_AND_TEST_P(FastMathTargetTest, TestFastPow2);
HWY_EXPORT_AND_TEST_P(FastMathTargetTest, TestFastPow);
HWY_EXPORT_AND_TEST_P(FastMathTargetTest, TestFastCos);
HWY_EXPORT_AND_TEST_P(FastMathTargetTest, TestFastErf);
+HWY_EXPORT_AND_TEST_P(FastMathTargetTest, TestCubeRoot);
HWY_EXPORT_AND_TEST_P(FastMathTargetTest, TestFastSRGB);
HWY_EXPORT_AND_TEST_P(FastMathTargetTest, TestFastPQDFE);
HWY_EXPORT_AND_TEST_P(FastMathTargetTest, TestFastPQEFD);
diff --git a/media/libjxl/src/lib/jxl/fields_test.cc b/media/libjxl/src/lib/jxl/fields_test.cc
index 50e3ac2212..c11b052301 100644
--- a/media/libjxl/src/lib/jxl/fields_test.cc
+++ b/media/libjxl/src/lib/jxl/fields_test.cc
@@ -228,7 +228,7 @@ TEST(FieldsTest, TestRoundtripSize) {
// Ensure all values can be reached by the encoding.
TEST(FieldsTest, TestCropRect) {
CodecMetadata metadata;
- for (int32_t i = -1000; i < 19000; ++i) {
+ for (int32_t i = -999; i < 19000; ++i) {
FrameHeader f(&metadata);
f.custom_size_or_origin = true;
f.frame_origin.x0 = i;
diff --git a/media/libjxl/src/lib/jxl/frame_header.cc b/media/libjxl/src/lib/jxl/frame_header.cc
index b08c351e81..e69a12c51c 100644
--- a/media/libjxl/src/lib/jxl/frame_header.cc
+++ b/media/libjxl/src/lib/jxl/frame_header.cc
@@ -5,6 +5,8 @@
#include "lib/jxl/frame_header.h"
+#include <sstream>
+
#include "lib/jxl/aux_out.h"
#include "lib/jxl/base/printf_macros.h"
#include "lib/jxl/base/status.h"
@@ -120,10 +122,16 @@ Status Passes::VisitFields(Visitor* JXL_RESTRICT visitor) {
for (uint32_t i = 0; i < num_downsample; ++i) {
JXL_QUIET_RETURN_IF_ERROR(
visitor->U32(Val(1), Val(2), Val(4), Val(8), 1, &downsample[i]));
+ if (i > 0 && downsample[i] >= downsample[i - 1]) {
+ return JXL_FAILURE("downsample sequence should be decreasing");
+ }
}
for (uint32_t i = 0; i < num_downsample; ++i) {
JXL_QUIET_RETURN_IF_ERROR(
visitor->U32(Val(0), Val(1), Val(2), Bits(3), 0, &last_pass[i]));
+ if (i > 0 && last_pass[i] <= last_pass[i - 1]) {
+ return JXL_FAILURE("last_pass sequence should be increasing");
+ }
if (last_pass[i] >= num_passes) {
return JXL_FAILURE("last_pass %u >= num_passes %u", last_pass[i],
num_passes);
@@ -133,6 +141,31 @@ Status Passes::VisitFields(Visitor* JXL_RESTRICT visitor) {
return true;
}
+
+std::string Passes::DebugString() const {
+ std::ostringstream os;
+ os << "p=" << num_passes;
+ if (num_downsample) {
+ os << ",ds=";
+ for (uint32_t i = 0; i < num_downsample; ++i) {
+ os << last_pass[i] << ":" << downsample[i];
+ if (i + 1 < num_downsample) os << ";";
+ }
+ }
+ bool have_shifts = false;
+ for (uint32_t i = 0; i < num_passes; ++i) {
+ if (shift[i]) have_shifts = true;
+ }
+ if (have_shifts) {
+ os << ",shifts=";
+ for (uint32_t i = 0; i < num_passes; ++i) {
+ os << shift[i];
+ if (i + 1 < num_passes) os << ";";
+ }
+ }
+ return os.str();
+}
+
FrameHeader::FrameHeader(const CodecMetadata* metadata)
: animation_frame(metadata), nonserialized_metadata(metadata) {
Bundle::Init(this);
@@ -271,6 +304,11 @@ Status FrameHeader::VisitFields(Visitor* JXL_RESTRICT visitor) {
// Frame size
JXL_QUIET_RETURN_IF_ERROR(visitor->U32(enc, 0, &frame_size.xsize));
JXL_QUIET_RETURN_IF_ERROR(visitor->U32(enc, 0, &frame_size.ysize));
+ if (custom_size_or_origin &&
+ (frame_size.xsize == 0 || frame_size.ysize == 0)) {
+ return JXL_FAILURE(
+ "Invalid crop dimensions for frame: zero width or height");
+ }
int32_t image_xsize = default_xsize();
int32_t image_ysize = default_ysize();
if (frame_type == FrameType::kRegularFrame ||
@@ -368,4 +406,65 @@ Status FrameHeader::VisitFields(Visitor* JXL_RESTRICT visitor) {
return visitor->EndExtensions();
}
+std::string FrameHeader::DebugString() const {
+ std::ostringstream os;
+ os << (encoding == FrameEncoding::kVarDCT ? "VarDCT" : "Modular");
+ os << ",";
+ os << (frame_type == FrameType::kRegularFrame ? "Regular"
+ : frame_type == FrameType::kDCFrame ? "DC"
+ : frame_type == FrameType::kReferenceOnly ? "Reference"
+ : "SkipProgressive");
+ if (frame_type == FrameType::kDCFrame) {
+ os << "(lv" << dc_level << ")";
+ }
+
+ if (flags) {
+ os << ",";
+ uint32_t remaining = flags;
+
+#define TEST_FLAG(name) \
+ if (flags & Flags::k##name) { \
+ remaining &= ~Flags::k##name; \
+ os << #name; \
+ if (remaining) os << "|"; \
+ }
+ TEST_FLAG(Noise);
+ TEST_FLAG(Patches);
+ TEST_FLAG(Splines);
+ TEST_FLAG(UseDcFrame);
+ TEST_FLAG(SkipAdaptiveDCSmoothing);
+#undef TEST_FLAG
+ }
+
+ os << ",";
+ os << (color_transform == ColorTransform::kXYB ? "XYB"
+ : color_transform == ColorTransform::kYCbCr ? "YCbCr"
+ : "None");
+
+ if (encoding == FrameEncoding::kModular) {
+ os << ",shift=" << group_size_shift;
+ } else if (color_transform == ColorTransform::kXYB) {
+ os << ",qm=" << x_qm_scale << ";" << b_qm_scale;
+ }
+ if (frame_type != FrameType::kReferenceOnly) {
+ os << "," << passes.DebugString();
+ }
+ if (custom_size_or_origin) {
+ os << ",xs=" << frame_size.xsize;
+ os << ",ys=" << frame_size.ysize;
+ if (frame_type == FrameType::kRegularFrame ||
+ frame_type == FrameType::kSkipProgressive) {
+ os << ",x0=" << frame_origin.x0;
+ os << ",y0=" << frame_origin.y0;
+ }
+ }
+ if (upsampling > 1) os << ",up=" << upsampling;
+ if (loop_filter.gab) os << ",Gaborish";
+ if (loop_filter.epf_iters > 0) os << ",epf=" << loop_filter.epf_iters;
+ if (animation_frame.duration > 0) os << ",dur=" << animation_frame.duration;
+ if (save_as_reference > 0) os << ",ref=" << save_as_reference;
+ if (is_last) os << ",last";
+ return os.str();
+}
+
} // namespace jxl
diff --git a/media/libjxl/src/lib/jxl/frame_header.h b/media/libjxl/src/lib/jxl/frame_header.h
index e0f2150dea..7eb2f3578b 100644
--- a/media/libjxl/src/lib/jxl/frame_header.h
+++ b/media/libjxl/src/lib/jxl/frame_header.h
@@ -92,8 +92,8 @@ struct YCbCrChromaSubsampling : public Fields {
uint8_t MaxHShift() const { return maxhs_; }
uint8_t MaxVShift() const { return maxvs_; }
- uint8_t RawHShift(size_t c) { return kHShift[channel_mode_[c]]; }
- uint8_t RawVShift(size_t c) { return kVShift[channel_mode_[c]]; }
+ uint8_t RawHShift(size_t c) const { return kHShift[channel_mode_[c]]; }
+ uint8_t RawVShift(size_t c) const { return kVShift[channel_mode_[c]]; }
// Uses JPEG channel order (Y, Cb, Cr).
Status Set(const uint8_t* hsample, const uint8_t* vsample) {
@@ -262,10 +262,10 @@ struct Passes : public Fields {
void GetDownsamplingBracket(size_t pass, int& minShift, int& maxShift) const {
maxShift = 2;
- minShift = 0;
+ minShift = 3;
for (size_t i = 0;; i++) {
for (uint32_t j = 0; j < num_downsample; ++j) {
- if (i <= last_pass[j]) {
+ if (i == last_pass[j]) {
if (downsample[j] == 8) minShift = 3;
if (downsample[j] == 4) minShift = 2;
if (downsample[j] == 2) minShift = 1;
@@ -275,10 +275,22 @@ struct Passes : public Fields {
if (i == num_passes - 1) minShift = 0;
if (i == pass) return;
maxShift = minShift - 1;
- minShift = 0;
}
}
+ uint32_t GetDownsamplingTargetForCompletedPasses(uint32_t num_p) const {
+ if (num_p >= num_passes) return 1;
+ uint32_t retval = 8;
+ for (uint32_t i = 0; i < num_downsample; ++i) {
+ if (num_p > last_pass[i]) {
+ retval = std::min(retval, downsample[i]);
+ }
+ }
+ return retval;
+ }
+
+ std::string DebugString() const;
+
uint32_t num_passes; // <= kMaxNumPasses
uint32_t num_downsample; // <= num_passes
@@ -473,6 +485,8 @@ struct FrameHeader : public Fields {
frame_type == FrameType::kSkipProgressive;
}
+ std::string DebugString() const;
+
uint64_t extensions;
};
diff --git a/media/libjxl/src/lib/jxl/gauss_blur.cc b/media/libjxl/src/lib/jxl/gauss_blur.cc
index d93942b128..930ffb4a39 100644
--- a/media/libjxl/src/lib/jxl/gauss_blur.cc
+++ b/media/libjxl/src/lib/jxl/gauss_blur.cc
@@ -26,7 +26,12 @@ namespace jxl {
namespace HWY_NAMESPACE {
// These templates are not found via ADL.
+using hwy::HWY_NAMESPACE::Add;
using hwy::HWY_NAMESPACE::Broadcast;
+using hwy::HWY_NAMESPACE::GetLane;
+using hwy::HWY_NAMESPACE::Mul;
+using hwy::HWY_NAMESPACE::MulAdd;
+using hwy::HWY_NAMESPACE::NegMulSub;
#if HWY_TARGET != HWY_SCALAR
using hwy::HWY_NAMESPACE::ShiftLeftLanes;
#endif
@@ -72,9 +77,9 @@ void FastGaussian1D(const hwy::AlignedUniquePtr<RecursiveGaussian>& rg,
const V sum = Set(d, left_val + right_val);
// (Only processing a single lane here, no need to broadcast)
- V out_1 = sum * mul_in_1;
- V out_3 = sum * mul_in_3;
- V out_5 = sum * mul_in_5;
+ V out_1 = Mul(sum, mul_in_1);
+ V out_3 = Mul(sum, mul_in_3);
+ V out_5 = Mul(sum, mul_in_5);
out_1 = MulAdd(mul_prev2_1, prev2_1, out_1);
out_3 = MulAdd(mul_prev2_3, prev2_3, out_3);
@@ -91,7 +96,7 @@ void FastGaussian1D(const hwy::AlignedUniquePtr<RecursiveGaussian>& rg,
prev_5 = out_5;
if (n >= 0) {
- out[n] = GetLane(out_1 + out_3 + out_5);
+ out[n] = GetLane(Add(out_1, Add(out_3, out_5)));
}
}
@@ -108,7 +113,7 @@ void FastGaussian1D(const hwy::AlignedUniquePtr<RecursiveGaussian>& rg,
// Unrolled, no bounds checking needed.
for (; n < width - N + 1 - (JXL_GAUSS_MAX_LANES - 1); n += Lanes(d)) {
- const V sum = LoadU(d, in + n - N - 1) + LoadU(d, in + n + N - 1);
+ const V sum = Add(LoadU(d, in + n - N - 1), LoadU(d, in + n + N - 1));
// To get a vector of output(s), we multiply broadcasted vectors (of each
// input plus the two previous outputs) and add them all together.
@@ -116,9 +121,9 @@ void FastGaussian1D(const hwy::AlignedUniquePtr<RecursiveGaussian>& rg,
// horizontal adds or transposing 4x4 values because they run on a different
// port, concurrently with the FMA.
const V in0 = Broadcast<0>(sum);
- V out_1 = in0 * mul_in_1;
- V out_3 = in0 * mul_in_3;
- V out_5 = in0 * mul_in_5;
+ V out_1 = Mul(in0, mul_in_1);
+ V out_3 = Mul(in0, mul_in_3);
+ V out_5 = Mul(in0, mul_in_5);
#if HWY_TARGET != HWY_SCALAR && JXL_GAUSS_MAX_LANES >= 2
const V in1 = Broadcast<1>(sum);
@@ -162,7 +167,7 @@ void FastGaussian1D(const hwy::AlignedUniquePtr<RecursiveGaussian>& rg,
prev_5 = Broadcast<JXL_GAUSS_MAX_LANES - 1>(out_5);
#endif
- Store(out_1 + out_3 + out_5, d, out + n);
+ Store(Add(out_1, Add(out_3, out_5)), d, out + n);
}
// Remainder handling with bounds checks
@@ -174,9 +179,9 @@ void FastGaussian1D(const hwy::AlignedUniquePtr<RecursiveGaussian>& rg,
const V sum = Set(d, left_val + right_val);
// (Only processing a single lane here, no need to broadcast)
- V out_1 = sum * mul_in_1;
- V out_3 = sum * mul_in_3;
- V out_5 = sum * mul_in_5;
+ V out_1 = Mul(sum, mul_in_1);
+ V out_3 = Mul(sum, mul_in_3);
+ V out_5 = Mul(sum, mul_in_5);
out_1 = MulAdd(mul_prev2_1, prev2_1, out_1);
out_3 = MulAdd(mul_prev2_3, prev2_3, out_3);
@@ -192,7 +197,7 @@ void FastGaussian1D(const hwy::AlignedUniquePtr<RecursiveGaussian>& rg,
prev_3 = out_3;
prev_5 = out_5;
- out[n] = GetLane(out_1 + out_3 + out_5);
+ out[n] = GetLane(Add(out_1, Add(out_3, out_5)));
}
}
@@ -235,7 +240,7 @@ class TwoInputs {
Vec<HWY_FULL(float)> operator()(const size_t offset) const {
const auto in1 = Load(HWY_FULL(float)(), pos1_ + offset);
const auto in2 = Load(HWY_FULL(float)(), pos2_ + offset);
- return in1 + in2;
+ return Add(in1, in2);
}
private:
@@ -280,7 +285,7 @@ void VerticalBlock(const V& d1_1, const V& d1_3, const V& d1_5, const V& n2_1,
Store(y1, d, y_1 + kLanes * n_0 + idx_vec * kVN);
Store(y3, d, y_3 + kLanes * n_0 + idx_vec * kVN);
Store(y5, d, y_5 + kLanes * n_0 + idx_vec * kVN);
- output(y1 + y3 + y5, out_pos, idx_vec * kVN);
+ output(Add(y1, Add(y3, y5)), out_pos, idx_vec * kVN);
}
// NOTE: flushing cache line out_pos hurts performance - less so with
// clflushopt than clflush but still a significant slowdown.
@@ -377,10 +382,12 @@ void FastGaussianVertical(const hwy::AlignedUniquePtr<RecursiveGaussian>& rg,
constexpr size_t kCacheLineLanes = 64 / sizeof(float);
constexpr size_t kVN = MaxLanes(HWY_FULL(float)());
- constexpr size_t kCacheLineVectors = kCacheLineLanes / kVN;
+ constexpr size_t kCacheLineVectors =
+ (kVN < kCacheLineLanes) ? (kCacheLineLanes / kVN) : 4;
+ constexpr size_t kFastPace = kCacheLineVectors * kVN;
size_t x = 0;
- for (; x + kCacheLineLanes <= in.xsize(); x += kCacheLineLanes) {
+ for (; x + kFastPace <= in.xsize(); x += kFastPace) {
VerticalStrip<kCacheLineVectors>(rg, in, x, out);
}
for (; x < in.xsize(); x += kVN) {
diff --git a/media/libjxl/src/lib/jxl/gauss_blur_gbench.cc b/media/libjxl/src/lib/jxl/gauss_blur_gbench.cc
index f6aba0c460..b1bb64abc5 100644
--- a/media/libjxl/src/lib/jxl/gauss_blur_gbench.cc
+++ b/media/libjxl/src/lib/jxl/gauss_blur_gbench.cc
@@ -13,7 +13,8 @@
namespace jxl {
namespace {
-ImageF Convolve(const ImageF& in, const std::vector<float>& kernel) {
+JXL_MAYBE_UNUSED ImageF Convolve(const ImageF& in,
+ const std::vector<float>& kernel) {
return ConvolveAndSample(in, kernel, 1);
}
@@ -35,9 +36,8 @@ void BM_GaussBlur1d(benchmark::State& state) {
for (auto _ : state) {
FastGaussian1D(rg, in.Row(0), length, out.Row(0));
// Prevent optimizing out
- const float actual = out.ConstRow(0)[length / 2];
- const float rel_err = std::abs(actual - expected) / expected;
- JXL_ASSERT(rel_err < 9E-5);
+ JXL_ASSERT(std::abs(out.ConstRow(0)[length / 2] - expected) / expected <
+ 9E-5);
}
state.SetItemsProcessed(length * state.iterations());
}
@@ -59,9 +59,9 @@ void BM_GaussBlur2d(benchmark::State& state) {
for (auto _ : state) {
FastGaussian(rg, in, null_pool, &temp, &out);
// Prevent optimizing out
- const float actual = out.ConstRow(ysize / 2)[xsize / 2];
- const float rel_err = std::abs(actual - expected) / expected;
- JXL_ASSERT(rel_err < 9E-5);
+ JXL_ASSERT(std::abs(out.ConstRow(ysize / 2)[xsize / 2] - expected) /
+ expected <
+ 9E-5);
}
state.SetItemsProcessed(xsize * ysize * state.iterations());
}
@@ -81,11 +81,11 @@ void BM_GaussBlurFir(benchmark::State& state) {
const std::vector<float> kernel =
GaussianKernel(static_cast<int>(4 * sigma), static_cast<float>(sigma));
for (auto _ : state) {
- const ImageF out = Convolve(in, kernel);
// Prevent optimizing out
- const float actual = out.ConstRow(ysize / 2)[xsize / 2];
- const float rel_err = std::abs(actual - expected) / expected;
- JXL_ASSERT(rel_err < 9E-5);
+ JXL_ASSERT(std::abs(Convolve(in, kernel).ConstRow(ysize / 2)[xsize / 2] -
+ expected) /
+ expected <
+ 9E-5);
}
state.SetItemsProcessed(xsize * ysize * state.iterations());
}
@@ -110,9 +110,9 @@ void BM_GaussBlurSep7(benchmark::State& state) {
for (auto _ : state) {
Separable7(in, Rect(in), weights, null_pool, &out);
// Prevent optimizing out
- const float actual = out.ConstRow(ysize / 2)[xsize / 2];
- const float rel_err = std::abs(actual - expected) / expected;
- JXL_ASSERT(rel_err < 9E-5);
+ JXL_ASSERT(std::abs(out.ConstRow(ysize / 2)[xsize / 2] - expected) /
+ expected <
+ 9E-5);
}
state.SetItemsProcessed(xsize * ysize * state.iterations());
}
diff --git a/media/libjxl/src/lib/jxl/gradient_test.cc b/media/libjxl/src/lib/jxl/gradient_test.cc
index a64e36ee32..0351904d39 100644
--- a/media/libjxl/src/lib/jxl/gradient_test.cc
+++ b/media/libjxl/src/lib/jxl/gradient_test.cc
@@ -22,8 +22,6 @@
#include "lib/jxl/color_encoding_internal.h"
#include "lib/jxl/color_management.h"
#include "lib/jxl/common.h"
-#include "lib/jxl/dec_file.h"
-#include "lib/jxl/dec_params.h"
#include "lib/jxl/enc_cache.h"
#include "lib/jxl/enc_color_management.h"
#include "lib/jxl/enc_file.h"
@@ -31,6 +29,7 @@
#include "lib/jxl/image.h"
#include "lib/jxl/image_bundle.h"
#include "lib/jxl/image_ops.h"
+#include "lib/jxl/test_utils.h"
namespace jxl {
namespace {
@@ -151,8 +150,6 @@ void TestGradient(ThreadPool* pool, uint32_t color0, uint32_t color1,
if (fast_mode) {
cparams.speed_tier = SpeedTier::kSquirrel;
}
- DecompressParams dparams;
-
Image3F gradient = GenerateTestGradient(color0, color1, angle, xsize, ysize);
CodecInOut io;
@@ -167,7 +164,7 @@ void TestGradient(ThreadPool* pool, uint32_t color0, uint32_t color1,
PassesEncoderState enc_state;
EXPECT_TRUE(EncodeFile(cparams, &io, &enc_state, &compressed, GetJxlCms(),
aux_out, pool));
- EXPECT_TRUE(DecodeFile(dparams, compressed, &io2, pool));
+ EXPECT_TRUE(test::DecodeFile({}, compressed, &io2, pool));
EXPECT_TRUE(
io2.Main().TransformTo(io2.metadata.m.color_encoding, GetJxlCms(), pool));
diff --git a/media/libjxl/src/lib/jxl/headers.cc b/media/libjxl/src/lib/jxl/headers.cc
index 7add681e1a..7c560e52a0 100644
--- a/media/libjxl/src/lib/jxl/headers.cc
+++ b/media/libjxl/src/lib/jxl/headers.cc
@@ -194,20 +194,6 @@ Status ReadSizeHeader(BitReader* JXL_RESTRICT reader,
Status WriteSizeHeader(const SizeHeader& size, BitWriter* JXL_RESTRICT writer,
size_t layer, AuxOut* aux_out) {
- const size_t max_bits = Bundle::MaxBits(size);
- if (max_bits != SizeHeader::kMaxBits) {
- JXL_ABORT("Please update SizeHeader::kMaxBits from %" PRIuS " to %" PRIuS
- "\n",
- SizeHeader::kMaxBits, max_bits);
- }
-
- // Only check the number of non-extension bits (extensions are unbounded).
- // (Bundle::Write will call CanEncode again, but it is fast because SizeHeader
- // is tiny.)
- size_t extension_bits, total_bits;
- JXL_RETURN_IF_ERROR(Bundle::CanEncode(size, &extension_bits, &total_bits));
- JXL_ASSERT(total_bits - extension_bits < SizeHeader::kMaxBits);
-
return Bundle::Write(size, writer, layer, aux_out);
}
diff --git a/media/libjxl/src/lib/jxl/headers.h b/media/libjxl/src/lib/jxl/headers.h
index 56ed72e31f..a9be252c27 100644
--- a/media/libjxl/src/lib/jxl/headers.h
+++ b/media/libjxl/src/lib/jxl/headers.h
@@ -29,10 +29,6 @@ static constexpr uint8_t kCodestreamMarker = 0x0A;
// can preallocate early.
class SizeHeader : public Fields {
public:
- // All fields are valid after reading at most this many bits. WriteSizeHeader
- // verifies this matches Bundle::MaxBits(SizeHeader).
- static constexpr size_t kMaxBits = 78;
-
SizeHeader();
JXL_FIELDS_NAME(SizeHeader)
diff --git a/media/libjxl/src/lib/jxl/image.h b/media/libjxl/src/lib/jxl/image.h
index 5874e3057d..5fe2c558cf 100644
--- a/media/libjxl/src/lib/jxl/image.h
+++ b/media/libjxl/src/lib/jxl/image.h
@@ -14,6 +14,7 @@
#include <string.h>
#include <algorithm>
+#include <sstream>
#include <utility> // std::move
#include "lib/jxl/base/cache_aligned.h"
@@ -242,8 +243,8 @@ class RectT {
JXL_MUST_USE_RESULT RectT Intersection(const RectT& other) const {
return RectT(std::max(x0_, other.x0_), std::max(y0_, other.y0_), xsize_,
- ysize_, std::min(x0_ + xsize_, other.x0_ + other.xsize_),
- std::min(y0_ + ysize_, other.y0_ + other.ysize_));
+ ysize_, std::min(x1(), other.x1()),
+ std::min(y1(), other.y1()));
}
JXL_MUST_USE_RESULT RectT Translate(int64_t x_offset,
@@ -282,8 +283,8 @@ class RectT {
}
bool IsInside(const RectT& other) const {
- return x0_ >= other.x0() && x0_ + xsize_ <= other.x0() + other.xsize_ &&
- y0_ >= other.y0() && y0_ + ysize_ <= other.y0() + other.ysize();
+ return x0_ >= other.x0() && x1() <= other.x1() && y0_ >= other.y0() &&
+ y1() <= other.y1();
}
// Returns true if this Rect fully resides in the given image. ImageT could be
@@ -300,6 +301,32 @@ class RectT {
T x1() const { return x0_ + xsize_; }
T y1() const { return y0_ + ysize_; }
+ RectT<T> ShiftLeft(size_t shiftx, size_t shifty) const {
+ return RectT<T>(x0_ * (1 << shiftx), y0_ * (1 << shifty), xsize_ << shiftx,
+ ysize_ << shifty);
+ }
+ RectT<T> ShiftLeft(size_t shift) const { return ShiftLeft(shift, shift); }
+
+ // Requires x0(), y0() to be multiples of 1<<shiftx, 1<<shifty.
+ RectT<T> CeilShiftRight(size_t shiftx, size_t shifty) const {
+ JXL_ASSERT(x0_ % (1 << shiftx) == 0);
+ JXL_ASSERT(y0_ % (1 << shifty) == 0);
+ return RectT<T>(x0_ / (1 << shiftx), y0_ / (1 << shifty),
+ DivCeil(xsize_, T{1} << shiftx),
+ DivCeil(ysize_, T{1} << shifty));
+ }
+ RectT<T> CeilShiftRight(std::pair<size_t, size_t> shift) const {
+ return CeilShiftRight(shift.first, shift.second);
+ }
+ RectT<T> CeilShiftRight(size_t shift) const {
+ return CeilShiftRight(shift, shift);
+ }
+
+ template <typename U>
+ RectT<U> As() const {
+ return RectT<U>(U(x0_), U(y0_), U(xsize_), U(ysize_));
+ }
+
private:
// Returns size_max, or whatever is left in [begin, end).
static constexpr size_t ClampedSize(T begin, size_t size_max, T end) {
@@ -315,6 +342,14 @@ class RectT {
size_t ysize_;
};
+template <typename T>
+std::string Description(RectT<T> r) {
+ std::ostringstream os;
+ os << "[" << r.x0() << ".." << r.x1() << ")x"
+ << "[" << r.y0() << ".." << r.y1() << ")";
+ return os.str();
+}
+
using Rect = RectT<size_t>;
// Currently, we abuse Image to either refer to an image that owns its storage
diff --git a/media/libjxl/src/lib/jxl/image_bundle.cc b/media/libjxl/src/lib/jxl/image_bundle.cc
index 189b9768c3..dfbc02ddb2 100644
--- a/media/libjxl/src/lib/jxl/image_bundle.cc
+++ b/media/libjxl/src/lib/jxl/image_bundle.cc
@@ -8,7 +8,6 @@
#include <limits>
#include <utility>
-#include "lib/jxl/alpha.h"
#include "lib/jxl/base/byte_order.h"
#include "lib/jxl/base/padded_bytes.h"
#include "lib/jxl/base/printf_macros.h"
@@ -118,32 +117,6 @@ void ImageBundle::SetAlpha(ImageF&& alpha, bool alpha_is_premultiplied) {
// num_extra_channels is automatically set in visitor
VerifySizes();
}
-void ImageBundle::PremultiplyAlpha() {
- if (!HasAlpha()) return;
- if (!HasColor()) return;
- const ExtraChannelInfo* eci = metadata_->Find(ExtraChannel::kAlpha);
- if (eci->alpha_associated) return; // already premultiplied
- JXL_CHECK(color_.ysize() == alpha()->ysize());
- JXL_CHECK(color_.xsize() == alpha()->xsize());
- for (size_t y = 0; y < color_.ysize(); y++) {
- ::jxl::PremultiplyAlpha(color_.PlaneRow(0, y), color_.PlaneRow(1, y),
- color_.PlaneRow(2, y), alpha()->Row(y),
- color_.xsize());
- }
-}
-void ImageBundle::UnpremultiplyAlpha() {
- if (!HasAlpha()) return;
- if (!HasColor()) return;
- const ExtraChannelInfo* eci = metadata_->Find(ExtraChannel::kAlpha);
- if (!eci->alpha_associated) return; // already unpremultiplied
- JXL_CHECK(color_.ysize() == alpha()->ysize());
- JXL_CHECK(color_.xsize() == alpha()->xsize());
- for (size_t y = 0; y < color_.ysize(); y++) {
- ::jxl::UnpremultiplyAlpha(color_.PlaneRow(0, y), color_.PlaneRow(1, y),
- color_.PlaneRow(2, y), alpha()->Row(y),
- color_.xsize());
- }
-}
void ImageBundle::SetExtraChannels(std::vector<ImageF>&& extra_channels) {
for (const ImageF& plane : extra_channels) {
diff --git a/media/libjxl/src/lib/jxl/image_bundle.h b/media/libjxl/src/lib/jxl/image_bundle.h
index de5951ddc2..d233abbbc1 100644
--- a/media/libjxl/src/lib/jxl/image_bundle.h
+++ b/media/libjxl/src/lib/jxl/image_bundle.h
@@ -170,10 +170,6 @@ class ImageBundle {
const ExtraChannelInfo* eci = metadata_->Find(ExtraChannel::kAlpha);
return (eci == nullptr) ? false : eci->alpha_associated;
}
- // Premultiply alpha (if it isn't already premultiplied)
- void PremultiplyAlpha();
- // Unpremultiply alpha (if it isn't already non-premultiplied)
- void UnpremultiplyAlpha();
const ImageF& alpha() const;
ImageF* alpha();
diff --git a/media/libjxl/src/lib/jxl/image_metadata.cc b/media/libjxl/src/lib/jxl/image_metadata.cc
index 409ba732ac..7a1ee1c6b4 100644
--- a/media/libjxl/src/lib/jxl/image_metadata.cc
+++ b/media/libjxl/src/lib/jxl/image_metadata.cc
@@ -59,6 +59,14 @@ Status BitDepth::VisitFields(Visitor* JXL_RESTRICT visitor) {
return true;
}
+std::string BitDepth::DebugString() const {
+ std::ostringstream os;
+ os << (floating_point_sample ? "F" : "U");
+ os << bits_per_sample;
+ if (floating_point_sample) os << "." << exponent_bits_per_sample;
+ return os.str();
+}
+
CustomTransformData::CustomTransformData() { Bundle::Init(this); }
Status CustomTransformData::VisitFields(Visitor* JXL_RESTRICT visitor) {
if (visitor->AllDefault(*this, &all_default)) {
@@ -244,6 +252,22 @@ Status ExtraChannelInfo::VisitFields(Visitor* JXL_RESTRICT visitor) {
return true;
}
+std::string ExtraChannelInfo::DebugString() const {
+ std::ostringstream os;
+ os << (type == ExtraChannel::kAlpha ? "Alpha"
+ : type == ExtraChannel::kDepth ? "Depth"
+ : type == ExtraChannel::kSpotColor ? "Spot"
+ : type == ExtraChannel::kSelectionMask ? "Mask"
+ : type == ExtraChannel::kBlack ? "Black"
+ : type == ExtraChannel::kCFA ? "CFA"
+ : type == ExtraChannel::kThermal ? "Thermal"
+ : "Unknown");
+ if (type == ExtraChannel::kAlpha && alpha_associated) os << "(premul)";
+ os << " " << bit_depth.DebugString();
+ os << " shift: " << dim_shift;
+ return os.str();
+}
+
ImageMetadata::ImageMetadata() { Bundle::Init(this); }
Status ImageMetadata::VisitFields(Visitor* JXL_RESTRICT visitor) {
if (visitor->AllDefault(*this, &all_default)) {
@@ -418,4 +442,36 @@ void ImageMetadata::SetAlphaBits(uint32_t bits, bool alpha_is_premultiplied) {
num_extra_channels = extra_channel_info.size();
if (bits > 12) modular_16_bit_buffer_sufficient = false;
}
+
+std::string ImageMetadata::DebugString() const {
+ std::ostringstream os;
+ os << bit_depth.DebugString();
+ if (modular_16_bit_buffer_sufficient) {
+ os << " (modular 16)";
+ }
+ os << (xyb_encoded ? " xyb encoded" : " orig profile");
+ os << " " << Description(color_encoding);
+ if (num_extra_channels > 0) {
+ os << " extra channels:";
+ for (size_t i = 0; i < num_extra_channels; ++i) {
+ os << " (" << extra_channel_info[i].DebugString() << ")";
+ if (i + 1 < num_extra_channels) os << ",";
+ }
+ }
+ if (have_preview) {
+ os << " preview: " << preview_size.xsize() << "x" << preview_size.ysize();
+ }
+ if (orientation != 1) {
+ os << " orientation: " << orientation;
+ }
+ return os.str();
+}
+
+std::string CodecMetadata::DebugString() const {
+ std::ostringstream os;
+ os << size.xsize() << "x" << size.ysize();
+ os << " " << m.DebugString();
+ return os.str();
+}
+
} // namespace jxl
diff --git a/media/libjxl/src/lib/jxl/image_metadata.h b/media/libjxl/src/lib/jxl/image_metadata.h
index ee6c05b425..9008e42f63 100644
--- a/media/libjxl/src/lib/jxl/image_metadata.h
+++ b/media/libjxl/src/lib/jxl/image_metadata.h
@@ -12,6 +12,7 @@
#include <stddef.h>
#include <stdint.h>
+#include <string>
#include <vector>
#include "jxl/codestream_header.h"
@@ -79,6 +80,8 @@ struct BitDepth : public Fields {
Status VisitFields(Visitor* JXL_RESTRICT visitor) override;
+ std::string DebugString() const;
+
// Whether the original (uncompressed) samples are floating point or
// unsigned integer.
bool floating_point_sample;
@@ -107,6 +110,8 @@ struct ExtraChannelInfo : public Fields {
Status VisitFields(Visitor* JXL_RESTRICT visitor) override;
+ std::string DebugString() const;
+
mutable bool all_default;
ExtraChannel type;
@@ -276,6 +281,8 @@ struct ImageMetadata : public Fields {
bool ExtraFieldsDefault() const;
+ std::string DebugString() const;
+
mutable bool all_default;
BitDepth bit_depth;
@@ -407,6 +414,8 @@ struct CodecMetadata {
return m.preview_size.ysize();
}
}
+
+ std::string DebugString() const;
};
} // namespace jxl
diff --git a/media/libjxl/src/lib/jxl/jpeg/jpeg_data.cc b/media/libjxl/src/lib/jxl/jpeg/jpeg_data.cc
index 50a184e407..a78d77c966 100644
--- a/media/libjxl/src/lib/jxl/jpeg/jpeg_data.cc
+++ b/media/libjxl/src/lib/jxl/jpeg/jpeg_data.cc
@@ -365,6 +365,11 @@ Status JPEGData::VisitFields(Visitor* visitor) {
if (visitor->IsReading()) inter_marker_data_sizes.emplace_back(len);
}
uint32_t tail_data_len = tail_data.size();
+ if (!visitor->IsReading() && tail_data_len > 4260096) {
+ error = JPEGReadError::TAIL_DATA_TOO_LARGE;
+ return JXL_FAILURE("Tail data too large (max size = 4260096, size = %u).",
+ tail_data_len);
+ }
JXL_RETURN_IF_ERROR(visitor->U32(Val(0), BitsOffset(8, 1),
BitsOffset(16, 257), BitsOffset(22, 65793),
0, &tail_data_len));
diff --git a/media/libjxl/src/lib/jxl/jpeg/jpeg_data.h b/media/libjxl/src/lib/jxl/jpeg/jpeg_data.h
index 29aefbcb2c..8fbc8696ec 100644
--- a/media/libjxl/src/lib/jxl/jpeg/jpeg_data.h
+++ b/media/libjxl/src/lib/jxl/jpeg/jpeg_data.h
@@ -105,6 +105,7 @@ enum struct JPEGReadError {
EOB_RUN_TOO_LONG,
IMAGE_TOO_LARGE,
INVALID_QUANT_TBL_PRECISION,
+ TAIL_DATA_TOO_LARGE
};
// Quantization values for an 8x8 pixel block.
diff --git a/media/libjxl/src/lib/jxl/jxl_test.cc b/media/libjxl/src/lib/jxl/jxl_test.cc
index 997dea9643..63ce6125f6 100644
--- a/media/libjxl/src/lib/jxl/jxl_test.cc
+++ b/media/libjxl/src/lib/jxl/jxl_test.cc
@@ -3,11 +3,15 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
+#include "lib/extras/dec/jxl.h"
+
#include <stdint.h>
#include <stdio.h>
#include <array>
+#include <future>
#include <string>
+#include <tuple>
#include <utility>
#include <vector>
@@ -24,8 +28,6 @@
#include "lib/jxl/codec_y4m_testonly.h"
#include "lib/jxl/color_encoding_internal.h"
#include "lib/jxl/color_management.h"
-#include "lib/jxl/dec_file.h"
-#include "lib/jxl/dec_params.h"
#include "lib/jxl/enc_butteraugli_comparator.h"
#include "lib/jxl/enc_butteraugli_pnorm.h"
#include "lib/jxl/enc_cache.h"
@@ -36,8 +38,10 @@
#include "lib/jxl/image_bundle.h"
#include "lib/jxl/image_ops.h"
#include "lib/jxl/image_test_utils.h"
+#include "lib/jxl/jpeg/dec_jpeg_data.h"
#include "lib/jxl/jpeg/dec_jpeg_data_writer.h"
#include "lib/jxl/jpeg/enc_jpeg_data.h"
+#include "lib/jxl/jpeg/jpeg_data.h"
#include "lib/jxl/modular/options.h"
#include "lib/jxl/test_utils.h"
#include "lib/jxl/testdata.h"
@@ -63,14 +67,13 @@ TEST(JxlTest, HeaderSize) {
CompressParams cparams;
cparams.butteraugli_distance = 1.5;
- DecompressParams dparams;
ThreadPool* pool = nullptr;
{
CodecInOut io2;
AuxOut aux_out;
- Roundtrip(&io, cparams, dparams, pool, &io2, &aux_out);
- EXPECT_LE(aux_out.layers[kLayerHeader].total_bits, 34u);
+ Roundtrip(&io, cparams, {}, pool, &io2, &aux_out);
+ EXPECT_LE(aux_out.layers[kLayerHeader].total_bits, 41u);
}
{
@@ -80,8 +83,8 @@ TEST(JxlTest, HeaderSize) {
alpha.Row(0)[0] = 1;
io.Main().SetAlpha(std::move(alpha), /*alpha_is_premultiplied=*/false);
AuxOut aux_out;
- Roundtrip(&io, cparams, dparams, pool, &io2, &aux_out);
- EXPECT_LE(aux_out.layers[kLayerHeader].total_bits, 46u);
+ Roundtrip(&io, cparams, {}, pool, &io2, &aux_out);
+ EXPECT_LE(aux_out.layers[kLayerHeader].total_bits, 49u);
}
}
@@ -91,10 +94,9 @@ TEST(JxlTest, RoundtripSinglePixel) {
CompressParams cparams;
cparams.butteraugli_distance = 1.0;
- DecompressParams dparams;
ThreadPool* pool = nullptr;
CodecInOut io2;
- Roundtrip(&io, cparams, dparams, pool, &io2);
+ Roundtrip(&io, cparams, {}, pool, &io2);
}
// Changing serialized signature causes Decode to fail.
@@ -105,7 +107,6 @@ TEST(JxlTest, RoundtripMarker) {
CompressParams cparams;
cparams.butteraugli_distance = 1.0;
- DecompressParams dparams;
AuxOut* aux_out = nullptr;
ThreadPool* pool = nullptr;
@@ -116,7 +117,7 @@ TEST(JxlTest, RoundtripMarker) {
aux_out, pool));
compressed[i] ^= 0xFF;
CodecInOut io2;
- EXPECT_FALSE(DecodeFile(dparams, compressed, &io2, pool));
+ EXPECT_FALSE(test::DecodeFile({}, compressed, &io2, pool));
}
}
#endif
@@ -124,7 +125,7 @@ TEST(JxlTest, RoundtripMarker) {
TEST(JxlTest, RoundtripTinyFast) {
ThreadPool* pool = nullptr;
const PaddedBytes orig =
- ReadTestData("third_party/wesaturate/500px/u76c0g_bliznaca_srgb8.png");
+ ReadTestData("external/wesaturate/500px/u76c0g_bliznaca_srgb8.png");
CodecInOut io;
ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io, pool));
io.ShrinkTo(32, 32);
@@ -132,20 +133,18 @@ TEST(JxlTest, RoundtripTinyFast) {
CompressParams cparams;
cparams.speed_tier = SpeedTier::kSquirrel;
cparams.butteraugli_distance = 4.0f;
- DecompressParams dparams;
CodecInOut io2;
- const size_t enc_bytes = Roundtrip(&io, cparams, dparams, pool, &io2);
+ const size_t enc_bytes = Roundtrip(&io, cparams, {}, pool, &io2);
printf("32x32 image size %" PRIuS " bytes\n", enc_bytes);
}
TEST(JxlTest, RoundtripSmallD1) {
ThreadPool* pool = nullptr;
const PaddedBytes orig =
- ReadTestData("third_party/wesaturate/500px/u76c0g_bliznaca_srgb8.png");
+ ReadTestData("external/wesaturate/500px/u76c0g_bliznaca_srgb8.png");
CompressParams cparams;
cparams.butteraugli_distance = 1.0;
- DecompressParams dparams;
CodecInOut io_out;
size_t compressed_size;
@@ -155,7 +154,7 @@ TEST(JxlTest, RoundtripSmallD1) {
ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io, pool));
io.ShrinkTo(io.xsize() / 8, io.ysize() / 8);
- compressed_size = Roundtrip(&io, cparams, dparams, pool, &io_out);
+ compressed_size = Roundtrip(&io, cparams, {}, pool, &io_out);
EXPECT_LE(compressed_size, 1000u);
EXPECT_THAT(ButteraugliDistance(io, io_out, cparams.ba_params, GetJxlCms(),
/*distmap=*/nullptr, pool),
@@ -169,12 +168,11 @@ TEST(JxlTest, RoundtripSmallD1) {
ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io_dim, pool));
io_dim.metadata.m.SetIntensityTarget(100);
io_dim.ShrinkTo(io_dim.xsize() / 8, io_dim.ysize() / 8);
- EXPECT_LT(Roundtrip(&io_dim, cparams, dparams, pool, &io_out),
- compressed_size);
+ EXPECT_LT(Roundtrip(&io_dim, cparams, {}, pool, &io_out), compressed_size);
EXPECT_THAT(
ButteraugliDistance(io_dim, io_out, cparams.ba_params, GetJxlCms(),
/*distmap=*/nullptr, pool),
- IsSlightlyBelow(1.5));
+ IsSlightlyBelow(1.1));
EXPECT_EQ(io_dim.metadata.m.IntensityTarget(),
io_out.metadata.m.IntensityTarget());
}
@@ -183,7 +181,7 @@ TEST(JxlTest, RoundtripSmallD1) {
TEST(JxlTest, RoundtripOtherTransforms) {
ThreadPool* pool = nullptr;
const PaddedBytes orig =
- ReadTestData("third_party/wesaturate/64px/a2d1un_nkitzmiller_srgb8.png");
+ ReadTestData("external/wesaturate/64px/a2d1un_nkitzmiller_srgb8.png");
std::unique_ptr<CodecInOut> io = jxl::make_unique<CodecInOut>();
ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), io.get(), pool));
@@ -192,11 +190,10 @@ TEST(JxlTest, RoundtripOtherTransforms) {
cparams.speed_tier = SpeedTier::kKitten;
cparams.color_transform = ColorTransform::kNone;
cparams.butteraugli_distance = 5.0f;
- DecompressParams dparams;
std::unique_ptr<CodecInOut> io2 = jxl::make_unique<CodecInOut>();
const size_t compressed_size =
- Roundtrip(io.get(), cparams, dparams, pool, io2.get());
+ Roundtrip(io.get(), cparams, {}, pool, io2.get());
EXPECT_LE(compressed_size, 23000u);
EXPECT_THAT(ButteraugliDistance(*io, *io2, cparams.ba_params, GetJxlCms(),
/*distmap=*/nullptr, pool),
@@ -205,7 +202,7 @@ TEST(JxlTest, RoundtripOtherTransforms) {
// Check the consistency when performing another roundtrip.
std::unique_ptr<CodecInOut> io3 = jxl::make_unique<CodecInOut>();
const size_t compressed_size2 =
- Roundtrip(io.get(), cparams, dparams, pool, io3.get());
+ Roundtrip(io.get(), cparams, {}, pool, io3.get());
EXPECT_LE(compressed_size2, 23000u);
EXPECT_THAT(ButteraugliDistance(*io, *io3, cparams.ba_params, GetJxlCms(),
/*distmap=*/nullptr, pool),
@@ -215,37 +212,51 @@ TEST(JxlTest, RoundtripOtherTransforms) {
TEST(JxlTest, RoundtripResample2) {
ThreadPool* pool = nullptr;
const PaddedBytes orig =
- ReadTestData("third_party/wesaturate/500px/u76c0g_bliznaca_srgb8.png");
+ ReadTestData("external/wesaturate/500px/u76c0g_bliznaca_srgb8.png");
CodecInOut io;
ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io, pool));
io.ShrinkTo(io.xsize(), io.ysize());
CompressParams cparams;
cparams.resampling = 2;
cparams.speed_tier = SpeedTier::kFalcon;
- DecompressParams dparams;
CodecInOut io2;
- EXPECT_LE(Roundtrip(&io, cparams, dparams, pool, &io2), 17000u);
+ EXPECT_LE(Roundtrip(&io, cparams, {}, pool, &io2), 17000u);
EXPECT_THAT(ComputeDistance2(io.Main(), io2.Main(), GetJxlCms()),
IsSlightlyBelow(90));
}
+TEST(JxlTest, RoundtripResample2Slow) {
+ ThreadPool* pool = nullptr;
+ const PaddedBytes orig =
+ ReadTestData("external/wesaturate/500px/u76c0g_bliznaca_srgb8.png");
+ CodecInOut io;
+ ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io, pool));
+ io.ShrinkTo(io.xsize(), io.ysize());
+ CompressParams cparams;
+ cparams.resampling = 2;
+ cparams.butteraugli_distance = 10;
+ cparams.speed_tier = SpeedTier::kTortoise;
+ CodecInOut io2;
+ EXPECT_LE(Roundtrip(&io, cparams, {}, pool, &io2), 5000u);
+ EXPECT_THAT(ComputeDistance2(io.Main(), io2.Main(), GetJxlCms()),
+ IsSlightlyBelow(250));
+}
+
TEST(JxlTest, RoundtripResample2MT) {
ThreadPoolInternal pool(4);
- const PaddedBytes orig =
- ReadTestData("third_party/imagecompression.info/flower_foveon.png");
+ const PaddedBytes orig = ReadTestData("jxl/flower/flower.png");
// image has to be large enough to have multiple groups after downsampling
CodecInOut io;
ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io, &pool));
CompressParams cparams;
cparams.resampling = 2;
cparams.speed_tier = SpeedTier::kFalcon;
- DecompressParams dparams;
CodecInOut io2;
// TODO(veluca): Figure out why msan and release produce different
// file size.
- EXPECT_LE(Roundtrip(&io, cparams, dparams, &pool, &io2), 64500u);
+ EXPECT_LE(Roundtrip(&io, cparams, {}, &pool, &io2), 200000u);
EXPECT_THAT(ComputeDistance2(io.Main(), io2.Main(), GetJxlCms()),
- IsSlightlyBelow(320));
+ IsSlightlyBelow(340));
}
// Roundtrip the image using a parallel runner that executes single-threaded but
@@ -253,8 +264,7 @@ TEST(JxlTest, RoundtripResample2MT) {
TEST(JxlTest, RoundtripOutOfOrderProcessing) {
FakeParallelRunner fake_pool(/*order_seed=*/123, /*num_threads=*/8);
ThreadPool pool(&JxlFakeParallelRunner, &fake_pool);
- const PaddedBytes orig =
- ReadTestData("third_party/imagecompression.info/flower_foveon.png");
+ const PaddedBytes orig = ReadTestData("jxl/flower/flower.png");
CodecInOut io;
ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io, &pool));
// Image size is selected so that the block border needed is larger than the
@@ -265,9 +275,8 @@ TEST(JxlTest, RoundtripOutOfOrderProcessing) {
// Force epf so we end up needing a lot of border.
cparams.epf = 3;
- DecompressParams dparams;
CodecInOut io2;
- Roundtrip(&io, cparams, dparams, &pool, &io2);
+ Roundtrip(&io, cparams, {}, &pool, &io2);
EXPECT_GE(1.5, ButteraugliDistance(io, io2, cparams.ba_params, GetJxlCms(),
/*distmap=*/nullptr, &pool));
@@ -276,8 +285,7 @@ TEST(JxlTest, RoundtripOutOfOrderProcessing) {
TEST(JxlTest, RoundtripOutOfOrderProcessingBorder) {
FakeParallelRunner fake_pool(/*order_seed=*/47, /*num_threads=*/8);
ThreadPool pool(&JxlFakeParallelRunner, &fake_pool);
- const PaddedBytes orig =
- ReadTestData("third_party/imagecompression.info/flower_foveon.png");
+ const PaddedBytes orig = ReadTestData("jxl/flower/flower.png");
CodecInOut io;
ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io, &pool));
// Image size is selected so that the block border needed is larger than the
@@ -289,9 +297,8 @@ TEST(JxlTest, RoundtripOutOfOrderProcessingBorder) {
cparams.epf = 3;
cparams.resampling = 2;
- DecompressParams dparams;
CodecInOut io2;
- Roundtrip(&io, cparams, dparams, &pool, &io2);
+ Roundtrip(&io, cparams, {}, &pool, &io2);
EXPECT_GE(2.8, ButteraugliDistance(io, io2, cparams.ba_params, GetJxlCms(),
/*distmap=*/nullptr, &pool));
@@ -300,15 +307,14 @@ TEST(JxlTest, RoundtripOutOfOrderProcessingBorder) {
TEST(JxlTest, RoundtripResample4) {
ThreadPool* pool = nullptr;
const PaddedBytes orig =
- ReadTestData("third_party/wesaturate/500px/u76c0g_bliznaca_srgb8.png");
+ ReadTestData("external/wesaturate/500px/u76c0g_bliznaca_srgb8.png");
CodecInOut io;
ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io, pool));
io.ShrinkTo(io.xsize(), io.ysize());
CompressParams cparams;
cparams.resampling = 4;
- DecompressParams dparams;
CodecInOut io2;
- EXPECT_LE(Roundtrip(&io, cparams, dparams, pool, &io2), 6000u);
+ EXPECT_LE(Roundtrip(&io, cparams, {}, pool, &io2), 6000u);
EXPECT_THAT(ButteraugliDistance(io, io2, cparams.ba_params, GetJxlCms(),
/*distmap=*/nullptr, pool),
IsSlightlyBelow(22));
@@ -317,15 +323,14 @@ TEST(JxlTest, RoundtripResample4) {
TEST(JxlTest, RoundtripResample8) {
ThreadPool* pool = nullptr;
const PaddedBytes orig =
- ReadTestData("third_party/wesaturate/500px/u76c0g_bliznaca_srgb8.png");
+ ReadTestData("external/wesaturate/500px/u76c0g_bliznaca_srgb8.png");
CodecInOut io;
ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io, pool));
io.ShrinkTo(io.xsize(), io.ysize());
CompressParams cparams;
cparams.resampling = 8;
- DecompressParams dparams;
CodecInOut io2;
- EXPECT_LE(Roundtrip(&io, cparams, dparams, pool, &io2), 2100u);
+ EXPECT_LE(Roundtrip(&io, cparams, {}, pool, &io2), 2100u);
EXPECT_THAT(ButteraugliDistance(io, io2, cparams.ba_params, GetJxlCms(),
/*distmap=*/nullptr, pool),
IsSlightlyBelow(50));
@@ -334,17 +339,16 @@ TEST(JxlTest, RoundtripResample8) {
TEST(JxlTest, RoundtripUnalignedD2) {
ThreadPool* pool = nullptr;
const PaddedBytes orig =
- ReadTestData("third_party/wesaturate/500px/u76c0g_bliznaca_srgb8.png");
+ ReadTestData("external/wesaturate/500px/u76c0g_bliznaca_srgb8.png");
CodecInOut io;
ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io, pool));
io.ShrinkTo(io.xsize() / 12, io.ysize() / 7);
CompressParams cparams;
cparams.butteraugli_distance = 2.0;
- DecompressParams dparams;
CodecInOut io2;
- EXPECT_LE(Roundtrip(&io, cparams, dparams, pool, &io2), 700u);
+ EXPECT_LE(Roundtrip(&io, cparams, {}, pool, &io2), 700u);
EXPECT_THAT(ButteraugliDistance(io, io2, cparams.ba_params, GetJxlCms(),
/*distmap=*/nullptr, pool),
IsSlightlyBelow(1.7));
@@ -354,26 +358,24 @@ TEST(JxlTest, RoundtripUnalignedD2) {
TEST(JxlTest, RoundtripMultiGroupNL) {
ThreadPoolInternal pool(4);
- const PaddedBytes orig =
- ReadTestData("third_party/imagecompression.info/flower_foveon.png");
+ const PaddedBytes orig = ReadTestData("jxl/flower/flower.png");
CodecInOut io;
ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io, &pool));
io.ShrinkTo(600, 1024); // partial X, full Y group
CompressParams cparams;
- DecompressParams dparams;
cparams.fast_mode = true;
cparams.butteraugli_distance = 1.0f;
CodecInOut io2;
- Roundtrip(&io, cparams, dparams, &pool, &io2);
+ Roundtrip(&io, cparams, {}, &pool, &io2);
EXPECT_THAT(ButteraugliDistance(io, io2, cparams.ba_params, GetJxlCms(),
/*distmap=*/nullptr, &pool),
IsSlightlyBelow(0.9f));
cparams.butteraugli_distance = 2.0f;
CodecInOut io3;
- EXPECT_LE(Roundtrip(&io, cparams, dparams, &pool, &io3), 80000u);
+ EXPECT_LE(Roundtrip(&io, cparams, {}, &pool, &io3), 80000u);
EXPECT_THAT(ButteraugliDistance(io, io3, cparams.ba_params, GetJxlCms(),
/*distmap=*/nullptr, &pool),
IsSlightlyBelow(1.5f));
@@ -382,50 +384,91 @@ TEST(JxlTest, RoundtripMultiGroupNL) {
#endif
TEST(JxlTest, RoundtripMultiGroup) {
+ const PaddedBytes orig = ReadTestData("jxl/flower/flower.png");
+ CodecInOut io;
+ {
+ ThreadPoolInternal pool(4);
+ ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io, &pool));
+ }
+ io.ShrinkTo(600, 1024);
+
+ auto test = [&](jxl::SpeedTier speed_tier, float target_distance,
+ size_t expected_size, float expected_distance) {
+ ThreadPoolInternal pool(4);
+ CompressParams cparams;
+ cparams.butteraugli_distance = target_distance;
+ cparams.speed_tier = speed_tier;
+ CodecInOut io2;
+ EXPECT_LE(Roundtrip(&io, cparams, {}, &pool, &io2), expected_size);
+ EXPECT_THAT(ComputeDistance2(io.Main(), io2.Main(), GetJxlCms()),
+ IsSlightlyBelow(expected_distance));
+ };
+
+ auto run_kitten = std::async(std::launch::async, test, SpeedTier::kKitten,
+ 1.0f, 55000u, 11);
+ auto run_wombat = std::async(std::launch::async, test, SpeedTier::kWombat,
+ 2.0f, 34000u, 18);
+}
+
+TEST(JxlTest, RoundtripRGBToGrayscale) {
ThreadPoolInternal pool(4);
- const PaddedBytes orig =
- ReadTestData("third_party/imagecompression.info/flower_foveon.png");
+ const PaddedBytes orig = ReadTestData("jxl/flower/flower.png");
CodecInOut io;
ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io, &pool));
io.ShrinkTo(600, 1024);
CompressParams cparams;
- DecompressParams dparams;
-
cparams.butteraugli_distance = 1.0f;
- cparams.speed_tier = SpeedTier::kKitten;
+ cparams.speed_tier = SpeedTier::kFalcon;
+
+ extras::JXLDecompressParams dparams;
+ dparams.color_space = "Gra_D65_Rel_SRG";
+
CodecInOut io2;
- EXPECT_LE(Roundtrip(&io, cparams, dparams, &pool, &io2), 40000u);
- EXPECT_THAT(ComputeDistance2(io.Main(), io2.Main(), GetJxlCms()),
- IsSlightlyBelow(15));
+ EXPECT_FALSE(io.Main().IsGray());
+ EXPECT_LE(Roundtrip(&io, cparams, dparams, &pool, &io2), 55000u);
+ EXPECT_TRUE(io2.Main().IsGray());
- cparams.butteraugli_distance = 2.0f;
- cparams.speed_tier = SpeedTier::kWombat;
- CodecInOut io3;
- EXPECT_LE(Roundtrip(&io, cparams, dparams, &pool, &io3), 22100u);
- EXPECT_THAT(ComputeDistance2(io.Main(), io3.Main(), GetJxlCms()),
- IsSlightlyBelow(24));
+ // Convert original to grayscale here, because TransformTo refuses to
+ // convert between grayscale and RGB.
+ ColorEncoding srgb_lin = ColorEncoding::LinearSRGB(/*is_gray=*/false);
+ ASSERT_TRUE(io.TransformTo(srgb_lin, GetJxlCms(), &pool));
+ Image3F* color = io.Main().color();
+ for (size_t y = 0; y < color->ysize(); ++y) {
+ float* row_r = color->PlaneRow(0, y);
+ float* row_g = color->PlaneRow(1, y);
+ float* row_b = color->PlaneRow(2, y);
+ for (size_t x = 0; x < color->xsize(); ++x) {
+ float luma = 0.2126 * row_r[x] + 0.7152 * row_g[x] + 0.0722 * row_b[x];
+ row_r[x] = row_g[x] = row_b[x] = luma;
+ }
+ }
+ ColorEncoding srgb_gamma = ColorEncoding::SRGB(/*is_gray=*/false);
+ ASSERT_TRUE(io.TransformTo(srgb_gamma, GetJxlCms(), &pool));
+ io.metadata.m.color_encoding = io2.Main().c_current();
+ io.Main().OverrideProfile(io2.Main().c_current());
+ EXPECT_THAT(ButteraugliDistance(io, io2, cparams.ba_params, GetJxlCms(),
+ /*distmap=*/nullptr, &pool),
+ IsSlightlyBelow(1.7));
}
TEST(JxlTest, RoundtripLargeFast) {
ThreadPoolInternal pool(8);
- const PaddedBytes orig =
- ReadTestData("third_party/imagecompression.info/flower_foveon.png");
+ const PaddedBytes orig = ReadTestData("jxl/flower/flower.png");
CodecInOut io;
ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io, &pool));
CompressParams cparams;
cparams.speed_tier = SpeedTier::kSquirrel;
- DecompressParams dparams;
CodecInOut io2;
- EXPECT_LE(Roundtrip(&io, cparams, dparams, &pool, &io2), 265000u);
+ EXPECT_LE(Roundtrip(&io, cparams, {}, &pool, &io2), 450800u);
}
TEST(JxlTest, RoundtripDotsForceEpf) {
ThreadPoolInternal pool(8);
const PaddedBytes orig =
- ReadTestData("third_party/wesaturate/500px/cvo9xd_keong_macan_srgb8.png");
+ ReadTestData("external/wesaturate/500px/cvo9xd_keong_macan_srgb8.png");
CodecInOut io;
ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io, &pool));
@@ -433,10 +476,9 @@ TEST(JxlTest, RoundtripDotsForceEpf) {
cparams.epf = 2;
cparams.dots = Override::kOn;
cparams.speed_tier = SpeedTier::kSquirrel;
- DecompressParams dparams;
CodecInOut io2;
- EXPECT_LE(Roundtrip(&io, cparams, dparams, &pool, &io2), 265000u);
+ EXPECT_LE(Roundtrip(&io, cparams, {}, &pool, &io2), 450000u);
}
// Checks for differing size/distance in two consecutive runs of distance 2,
@@ -444,25 +486,23 @@ TEST(JxlTest, RoundtripDotsForceEpf) {
// Failing this may be a sign of race conditions or invalid memory accesses.
TEST(JxlTest, RoundtripD2Consistent) {
ThreadPoolInternal pool(8);
- const PaddedBytes orig =
- ReadTestData("third_party/imagecompression.info/flower_foveon.png");
+ const PaddedBytes orig = ReadTestData("jxl/flower/flower.png");
CodecInOut io;
ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io, &pool));
CompressParams cparams;
cparams.speed_tier = SpeedTier::kSquirrel;
cparams.butteraugli_distance = 2.0;
- DecompressParams dparams;
// Try each xsize mod kBlockDim to verify right border handling.
for (size_t xsize = 48; xsize > 40; --xsize) {
io.ShrinkTo(xsize, 15);
CodecInOut io2;
- const size_t size2 = Roundtrip(&io, cparams, dparams, &pool, &io2);
+ const size_t size2 = Roundtrip(&io, cparams, {}, &pool, &io2);
CodecInOut io3;
- const size_t size3 = Roundtrip(&io, cparams, dparams, &pool, &io3);
+ const size_t size3 = Roundtrip(&io, cparams, {}, &pool, &io3);
// Exact same compressed size.
EXPECT_EQ(size2, size3);
@@ -476,31 +516,37 @@ TEST(JxlTest, RoundtripD2Consistent) {
// Same as above, but for full image, testing multiple groups.
TEST(JxlTest, RoundtripLargeConsistent) {
- ThreadPoolInternal pool(8);
- const PaddedBytes orig =
- ReadTestData("third_party/imagecompression.info/flower_foveon.png");
+ const PaddedBytes orig = ReadTestData("jxl/flower/flower.png");
CodecInOut io;
- ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io, &pool));
+ {
+ ThreadPoolInternal pool(8);
+ ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io, &pool));
+ }
CompressParams cparams;
cparams.speed_tier = SpeedTier::kSquirrel;
cparams.butteraugli_distance = 2.0;
- DecompressParams dparams;
+
+ auto roundtrip_and_compare = [&]() {
+ ThreadPoolInternal pool(8);
+ CodecInOut io2;
+ size_t size = Roundtrip(&io, cparams, {}, &pool, &io2);
+ double dist = ComputeDistance2(io.Main(), io2.Main(), GetJxlCms());
+ return std::tuple<size_t, double>(size, dist);
+ };
// Try each xsize mod kBlockDim to verify right border handling.
- CodecInOut io2;
- const size_t size2 = Roundtrip(&io, cparams, dparams, &pool, &io2);
+ auto future2 = std::async(std::launch::async, roundtrip_and_compare);
+ auto future3 = std::async(std::launch::async, roundtrip_and_compare);
- CodecInOut io3;
- const size_t size3 = Roundtrip(&io, cparams, dparams, &pool, &io3);
+ const auto result2 = future2.get();
+ const auto result3 = future3.get();
// Exact same compressed size.
- EXPECT_EQ(size2, size3);
+ EXPECT_EQ(std::get<0>(result2), std::get<0>(result3));
// Exact same distance.
- const float dist2 = ComputeDistance2(io.Main(), io2.Main(), GetJxlCms());
- const float dist3 = ComputeDistance2(io.Main(), io3.Main(), GetJxlCms());
- EXPECT_EQ(dist2, dist3);
+ EXPECT_EQ(std::get<1>(result2), std::get<1>(result3));
}
#if JXL_TEST_NL
@@ -508,17 +554,16 @@ TEST(JxlTest, RoundtripLargeConsistent) {
TEST(JxlTest, RoundtripSmallNL) {
ThreadPool* pool = nullptr;
const PaddedBytes orig =
- ReadTestData("third_party/wesaturate/500px/u76c0g_bliznaca_srgb8.png");
+ ReadTestData("external/wesaturate/500px/u76c0g_bliznaca_srgb8.png");
CodecInOut io;
ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io, pool));
io.ShrinkTo(io.xsize() / 8, io.ysize() / 8);
CompressParams cparams;
cparams.butteraugli_distance = 1.0;
- DecompressParams dparams;
CodecInOut io2;
- EXPECT_LE(Roundtrip(&io, cparams, dparams, pool, &io2), 1500u);
+ EXPECT_LE(Roundtrip(&io, cparams, {}, pool, &io2), 1500u);
EXPECT_THAT(ButteraugliDistance(io, io2, cparams.ba_params, GetJxlCms(),
/*distmap=*/nullptr, pool),
IsSlightlyBelow(1.7));
@@ -529,7 +574,7 @@ TEST(JxlTest, RoundtripSmallNL) {
TEST(JxlTest, RoundtripNoGaborishNoAR) {
ThreadPool* pool = nullptr;
const PaddedBytes orig =
- ReadTestData("third_party/wesaturate/500px/u76c0g_bliznaca_srgb8.png");
+ ReadTestData("external/wesaturate/500px/u76c0g_bliznaca_srgb8.png");
CodecInOut io;
ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io, pool));
@@ -537,10 +582,9 @@ TEST(JxlTest, RoundtripNoGaborishNoAR) {
cparams.gaborish = Override::kOff;
cparams.epf = 0;
cparams.butteraugli_distance = 1.0;
- DecompressParams dparams;
CodecInOut io2;
- EXPECT_LE(Roundtrip(&io, cparams, dparams, pool, &io2), 40000u);
+ EXPECT_LE(Roundtrip(&io, cparams, {}, pool, &io2), 40000u);
EXPECT_THAT(ButteraugliDistance(io, io2, cparams.ba_params, GetJxlCms(),
/*distmap=*/nullptr, pool),
IsSlightlyBelow(2.0));
@@ -549,7 +593,7 @@ TEST(JxlTest, RoundtripNoGaborishNoAR) {
TEST(JxlTest, RoundtripSmallNoGaborish) {
ThreadPool* pool = nullptr;
const PaddedBytes orig =
- ReadTestData("third_party/wesaturate/500px/u76c0g_bliznaca_srgb8.png");
+ ReadTestData("external/wesaturate/500px/u76c0g_bliznaca_srgb8.png");
CodecInOut io;
ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io, pool));
io.ShrinkTo(io.xsize() / 8, io.ysize() / 8);
@@ -557,10 +601,9 @@ TEST(JxlTest, RoundtripSmallNoGaborish) {
CompressParams cparams;
cparams.gaborish = Override::kOff;
cparams.butteraugli_distance = 1.0;
- DecompressParams dparams;
CodecInOut io2;
- EXPECT_LE(Roundtrip(&io, cparams, dparams, pool, &io2), 900u);
+ EXPECT_LE(Roundtrip(&io, cparams, {}, pool, &io2), 900u);
EXPECT_THAT(ButteraugliDistance(io, io2, cparams.ba_params, GetJxlCms(),
/*distmap=*/nullptr, pool),
IsSlightlyBelow(1.2));
@@ -589,10 +632,9 @@ TEST(JxlTest, RoundtripSmallPatchesAlpha) {
CompressParams cparams;
cparams.speed_tier = SpeedTier::kSquirrel;
cparams.butteraugli_distance = 0.1f;
- DecompressParams dparams;
CodecInOut io2;
- EXPECT_LE(Roundtrip(&io, cparams, dparams, pool, &io2), 2000u);
+ EXPECT_LE(Roundtrip(&io, cparams, {}, pool, &io2), 2000u);
EXPECT_THAT(ButteraugliDistance(io, io2, cparams.ba_params, GetJxlCms(),
/*distmap=*/nullptr, pool),
IsSlightlyBelow(0.04f));
@@ -617,10 +659,9 @@ TEST(JxlTest, RoundtripSmallPatches) {
CompressParams cparams;
cparams.speed_tier = SpeedTier::kSquirrel;
cparams.butteraugli_distance = 0.1f;
- DecompressParams dparams;
CodecInOut io2;
- EXPECT_LE(Roundtrip(&io, cparams, dparams, pool, &io2), 2000u);
+ EXPECT_LE(Roundtrip(&io, cparams, {}, pool, &io2), 2000u);
EXPECT_THAT(ButteraugliDistance(io, io2, cparams.ba_params, GetJxlCms(),
/*distmap=*/nullptr, pool),
IsSlightlyBelow(0.04f));
@@ -638,7 +679,6 @@ TEST(JxlTest, RoundtripImageBundleOriginalBits) {
io.SetFromImage(std::move(image), ColorEncoding::LinearSRGB());
CompressParams cparams;
- DecompressParams dparams;
// Test unsigned integers from 1 to 32 bits
for (uint32_t bit_depth = 1; bit_depth <= 32; bit_depth++) {
@@ -651,7 +691,7 @@ TEST(JxlTest, RoundtripImageBundleOriginalBits) {
io.metadata.m.SetUintSamples(bit_depth);
CodecInOut io2;
- Roundtrip(&io, cparams, dparams, pool, &io2);
+ Roundtrip(&io, cparams, {}, pool, &io2);
EXPECT_EQ(bit_depth, io2.metadata.m.bit_depth.bits_per_sample);
EXPECT_FALSE(io2.metadata.m.bit_depth.floating_point_sample);
@@ -688,7 +728,7 @@ TEST(JxlTest, RoundtripImageBundleOriginalBits) {
io.metadata.m.bit_depth.exponent_bits_per_sample = exponent_bit_depth;
CodecInOut io2;
- Roundtrip(&io, cparams, dparams, pool, &io2);
+ Roundtrip(&io, cparams, {}, pool, &io2);
EXPECT_EQ(bit_depth, io2.metadata.m.bit_depth.bits_per_sample);
EXPECT_TRUE(io2.metadata.m.bit_depth.floating_point_sample);
@@ -701,7 +741,7 @@ TEST(JxlTest, RoundtripImageBundleOriginalBits) {
TEST(JxlTest, RoundtripGrayscale) {
ThreadPool* pool = nullptr;
const PaddedBytes orig = ReadTestData(
- "third_party/wesaturate/500px/cvo9xd_keong_macan_grayscale.png");
+ "external/wesaturate/500px/cvo9xd_keong_macan_grayscale.png");
CodecInOut io;
ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io, pool));
ASSERT_NE(io.xsize(), 0u);
@@ -718,13 +758,12 @@ TEST(JxlTest, RoundtripGrayscale) {
{
CompressParams cparams;
cparams.butteraugli_distance = 1.0;
- DecompressParams dparams;
PaddedBytes compressed;
EXPECT_TRUE(EncodeFile(cparams, &io, &enc_state, &compressed, GetJxlCms(),
aux_out, pool));
CodecInOut io2;
- EXPECT_TRUE(DecodeFile(dparams, compressed, &io2, pool));
+ EXPECT_TRUE(test::DecodeFile({}, compressed, &io2, pool));
EXPECT_TRUE(io2.Main().IsGray());
EXPECT_LE(compressed.size(), 7000u);
@@ -738,13 +777,12 @@ TEST(JxlTest, RoundtripGrayscale) {
{
CompressParams cparams;
cparams.butteraugli_distance = 8.0;
- DecompressParams dparams;
PaddedBytes compressed;
EXPECT_TRUE(EncodeFile(cparams, &io, &enc_state, &compressed, GetJxlCms(),
aux_out, pool));
CodecInOut io2;
- EXPECT_TRUE(DecodeFile(dparams, compressed, &io2, pool));
+ EXPECT_TRUE(test::DecodeFile({}, compressed, &io2, pool));
EXPECT_TRUE(io2.Main().IsGray());
EXPECT_LE(compressed.size(), 1300u);
@@ -752,12 +790,32 @@ TEST(JxlTest, RoundtripGrayscale) {
/*distmap=*/nullptr, pool),
IsSlightlyBelow(6.0));
}
+
+ {
+ CompressParams cparams;
+ cparams.butteraugli_distance = 1.0;
+
+ PaddedBytes compressed;
+ EXPECT_TRUE(EncodeFile(cparams, &io, &enc_state, &compressed, GetJxlCms(),
+ aux_out, pool));
+
+ CodecInOut io2;
+ extras::JXLDecompressParams dparams;
+ dparams.color_space = "RGB_D65_SRG_Rel_SRG";
+ EXPECT_TRUE(test::DecodeFile(dparams, compressed, &io2, pool));
+ EXPECT_FALSE(io2.Main().IsGray());
+
+ EXPECT_LE(compressed.size(), 7000u);
+ EXPECT_THAT(ButteraugliDistance(io, io2, cparams.ba_params, GetJxlCms(),
+ /*distmap=*/nullptr, pool),
+ IsSlightlyBelow(1.6));
+ }
}
TEST(JxlTest, RoundtripAlpha) {
ThreadPool* pool = nullptr;
- const PaddedBytes orig = ReadTestData(
- "third_party/wesaturate/500px/tmshre_riaphotographs_alpha.png");
+ const PaddedBytes orig =
+ ReadTestData("external/wesaturate/500px/tmshre_riaphotographs_alpha.png");
CodecInOut io;
ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io, pool));
@@ -768,7 +826,6 @@ TEST(JxlTest, RoundtripAlpha) {
CompressParams cparams;
cparams.butteraugli_distance = 1.0;
- DecompressParams dparams;
EXPECT_EQ(8u, io.metadata.m.bit_depth.bits_per_sample);
EXPECT_FALSE(io.metadata.m.bit_depth.floating_point_sample);
@@ -779,20 +836,28 @@ TEST(JxlTest, RoundtripAlpha) {
PaddedBytes compressed;
EXPECT_TRUE(EncodeFile(cparams, &io, &enc_state, &compressed, GetJxlCms(),
aux_out, pool));
- CodecInOut io2;
- EXPECT_TRUE(DecodeFile(dparams, compressed, &io2, pool));
- EXPECT_LE(compressed.size(), 10077u);
+ for (bool use_image_callback : {false, true}) {
+ for (bool unpremul_alpha : {false, true}) {
+ CodecInOut io2;
+ extras::JXLDecompressParams dparams;
+ dparams.use_image_callback = use_image_callback;
+ dparams.unpremultiply_alpha = unpremul_alpha;
+ EXPECT_TRUE(test::DecodeFile(dparams, compressed, &io2, pool));
- EXPECT_THAT(ButteraugliDistance(io, io2, cparams.ba_params, GetJxlCms(),
- /*distmap=*/nullptr, pool),
- IsSlightlyBelow(1.2));
+ EXPECT_LE(compressed.size(), 10077u);
+
+ EXPECT_THAT(ButteraugliDistance(io, io2, cparams.ba_params, GetJxlCms(),
+ /*distmap=*/nullptr, pool),
+ IsSlightlyBelow(1.2));
+ }
+ }
}
TEST(JxlTest, RoundtripAlphaPremultiplied) {
ThreadPool* pool = nullptr;
- const PaddedBytes orig = ReadTestData(
- "third_party/wesaturate/500px/tmshre_riaphotographs_alpha.png");
+ const PaddedBytes orig =
+ ReadTestData("external/wesaturate/500px/tmshre_riaphotographs_alpha.png");
CodecInOut io, io_nopremul;
ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io, pool));
ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io_nopremul, pool));
@@ -805,34 +870,61 @@ TEST(JxlTest, RoundtripAlphaPremultiplied) {
CompressParams cparams;
cparams.butteraugli_distance = 1.0;
- DecompressParams dparams;
- io.PremultiplyAlpha();
+ EXPECT_FALSE(io.Main().AlphaIsPremultiplied());
+ EXPECT_TRUE(io.PremultiplyAlpha());
EXPECT_TRUE(io.Main().AlphaIsPremultiplied());
+
+ EXPECT_FALSE(io_nopremul.Main().AlphaIsPremultiplied());
+
PassesEncoderState enc_state;
AuxOut* aux_out = nullptr;
PaddedBytes compressed;
EXPECT_TRUE(EncodeFile(cparams, &io, &enc_state, &compressed, GetJxlCms(),
aux_out, pool));
- CodecInOut io2;
- EXPECT_TRUE(DecodeFile(dparams, compressed, &io2, pool));
- EXPECT_LE(compressed.size(), 10000u);
-
- EXPECT_THAT(ButteraugliDistance(io, io2, cparams.ba_params, GetJxlCms(),
+ for (bool use_image_callback : {false, true}) {
+ for (bool unpremul_alpha : {false, true}) {
+ for (bool use_uint8 : {false, true}) {
+ printf(
+ "Testing premultiplied alpha using %s %s requesting "
+ "%spremultiplied output.\n",
+ use_uint8 ? "uint8" : "float",
+ use_image_callback ? "image callback" : "image_buffer",
+ unpremul_alpha ? "un" : "");
+ CodecInOut io2;
+ extras::JXLDecompressParams dparams;
+ dparams.use_image_callback = use_image_callback;
+ dparams.unpremultiply_alpha = unpremul_alpha;
+ if (use_uint8) {
+ dparams.accepted_formats = {
+ {4, JXL_TYPE_UINT8, JXL_LITTLE_ENDIAN, 0}};
+ }
+ EXPECT_TRUE(test::DecodeFile(dparams, compressed, &io2, pool));
+
+ EXPECT_LE(compressed.size(), 10000u);
+ EXPECT_EQ(unpremul_alpha, !io2.Main().AlphaIsPremultiplied());
+ if (!unpremul_alpha) {
+ EXPECT_THAT(
+ ButteraugliDistance(io, io2, cparams.ba_params, GetJxlCms(),
/*distmap=*/nullptr, pool),
- IsSlightlyBelow(1.2));
- io2.Main().UnpremultiplyAlpha();
- EXPECT_THAT(
- ButteraugliDistance(io_nopremul, io2, cparams.ba_params, GetJxlCms(),
- /*distmap=*/nullptr, pool),
- IsSlightlyBelow(1.35));
+ IsSlightlyBelow(1.25));
+ EXPECT_TRUE(io2.UnpremultiplyAlpha());
+ EXPECT_FALSE(io2.Main().AlphaIsPremultiplied());
+ }
+ EXPECT_THAT(ButteraugliDistance(io_nopremul, io2, cparams.ba_params,
+ GetJxlCms(),
+ /*distmap=*/nullptr, pool),
+ IsSlightlyBelow(1.35));
+ }
+ }
+ }
}
TEST(JxlTest, RoundtripAlphaResampling) {
ThreadPool* pool = nullptr;
- const PaddedBytes orig = ReadTestData(
- "third_party/wesaturate/500px/tmshre_riaphotographs_alpha.png");
+ const PaddedBytes orig =
+ ReadTestData("external/wesaturate/500px/tmshre_riaphotographs_alpha.png");
CodecInOut io;
ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io, pool));
@@ -845,7 +937,6 @@ TEST(JxlTest, RoundtripAlphaResampling) {
cparams.ec_resampling = 2;
cparams.butteraugli_distance = 1.0;
cparams.speed_tier = SpeedTier::kHare;
- DecompressParams dparams;
PassesEncoderState enc_state;
AuxOut* aux_out = nullptr;
@@ -853,7 +944,7 @@ TEST(JxlTest, RoundtripAlphaResampling) {
EXPECT_TRUE(EncodeFile(cparams, &io, &enc_state, &compressed, GetJxlCms(),
aux_out, pool));
CodecInOut io2;
- EXPECT_TRUE(DecodeFile(dparams, compressed, &io2, pool));
+ EXPECT_TRUE(test::DecodeFile({}, compressed, &io2, pool));
EXPECT_LE(compressed.size(), 15000u);
@@ -864,8 +955,8 @@ TEST(JxlTest, RoundtripAlphaResampling) {
TEST(JxlTest, RoundtripAlphaResamplingOnlyAlpha) {
ThreadPool* pool = nullptr;
- const PaddedBytes orig = ReadTestData(
- "third_party/wesaturate/500px/tmshre_riaphotographs_alpha.png");
+ const PaddedBytes orig =
+ ReadTestData("external/wesaturate/500px/tmshre_riaphotographs_alpha.png");
CodecInOut io;
ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io, pool));
@@ -877,7 +968,6 @@ TEST(JxlTest, RoundtripAlphaResamplingOnlyAlpha) {
cparams.ec_resampling = 2;
cparams.butteraugli_distance = 1.0;
cparams.speed_tier = SpeedTier::kFalcon;
- DecompressParams dparams;
PassesEncoderState enc_state;
AuxOut* aux_out = nullptr;
@@ -885,7 +975,7 @@ TEST(JxlTest, RoundtripAlphaResamplingOnlyAlpha) {
EXPECT_TRUE(EncodeFile(cparams, &io, &enc_state, &compressed, GetJxlCms(),
aux_out, pool));
CodecInOut io2;
- EXPECT_TRUE(DecodeFile(dparams, compressed, &io2, pool));
+ EXPECT_TRUE(test::DecodeFile({}, compressed, &io2, pool));
EXPECT_LE(compressed.size(), 34200u);
@@ -896,8 +986,8 @@ TEST(JxlTest, RoundtripAlphaResamplingOnlyAlpha) {
TEST(JxlTest, RoundtripAlphaNonMultipleOf8) {
ThreadPool* pool = nullptr;
- const PaddedBytes orig = ReadTestData(
- "third_party/wesaturate/500px/tmshre_riaphotographs_alpha.png");
+ const PaddedBytes orig =
+ ReadTestData("external/wesaturate/500px/tmshre_riaphotographs_alpha.png");
CodecInOut io;
ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io, pool));
@@ -908,7 +998,6 @@ TEST(JxlTest, RoundtripAlphaNonMultipleOf8) {
CompressParams cparams;
cparams.butteraugli_distance = 1.0;
- DecompressParams dparams;
EXPECT_EQ(8u, io.metadata.m.bit_depth.bits_per_sample);
EXPECT_FALSE(io.metadata.m.bit_depth.floating_point_sample);
@@ -920,7 +1009,7 @@ TEST(JxlTest, RoundtripAlphaNonMultipleOf8) {
EXPECT_TRUE(EncodeFile(cparams, &io, &enc_state, &compressed, GetJxlCms(),
aux_out, pool));
CodecInOut io2;
- EXPECT_TRUE(DecodeFile(dparams, compressed, &io2, pool));
+ EXPECT_TRUE(test::DecodeFile({}, compressed, &io2, pool));
EXPECT_LE(compressed.size(), 180u);
@@ -966,7 +1055,6 @@ TEST(JxlTest, RoundtripAlpha16) {
CompressParams cparams;
cparams.butteraugli_distance = 0.5;
cparams.speed_tier = SpeedTier::kWombat;
- DecompressParams dparams;
io.metadata.m.SetUintSamples(16);
EXPECT_TRUE(io.metadata.m.color_encoding.tf.IsSRGB());
@@ -976,7 +1064,7 @@ TEST(JxlTest, RoundtripAlpha16) {
EXPECT_TRUE(EncodeFile(cparams, &io, &enc_state, &compressed, GetJxlCms(),
aux_out, &pool));
CodecInOut io2;
- EXPECT_TRUE(DecodeFile(dparams, compressed, &io2, &pool));
+ EXPECT_TRUE(test::DecodeFile({}, compressed, &io2, &pool));
EXPECT_THAT(ButteraugliDistance(io, io2, cparams.ba_params, GetJxlCms(),
/*distmap=*/nullptr, &pool),
IsSlightlyBelow(0.8));
@@ -995,16 +1083,15 @@ CompressParams CParamsForLossless() {
TEST(JxlTest, JXL_SLOW_TEST(RoundtripLossless8)) {
ThreadPoolInternal pool(8);
- const PaddedBytes orig = ReadTestData(
- "third_party/wesaturate/500px/tmshre_riaphotographs_srgb8.png");
+ const PaddedBytes orig =
+ ReadTestData("external/wesaturate/500px/tmshre_riaphotographs_srgb8.png");
CodecInOut io;
ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io, &pool));
CompressParams cparams = CParamsForLossless();
- DecompressParams dparams;
CodecInOut io2;
- EXPECT_LE(Roundtrip(&io, cparams, dparams, &pool, &io2), 3500000u);
+ EXPECT_LE(Roundtrip(&io, cparams, {}, &pool, &io2), 3500000u);
// If this test fails with a very close to 0.0 but not exactly 0.0 butteraugli
// distance, then there is likely a floating point issue, that could be
// happening either in io or io2. The values of io are generated by
@@ -1023,25 +1110,24 @@ TEST(JxlTest, JXL_SLOW_TEST(RoundtripLossless8)) {
TEST(JxlTest, JXL_SLOW_TEST(RoundtripLosslessNoEncoderFastPathWP)) {
ThreadPoolInternal pool(8);
- const PaddedBytes orig = ReadTestData(
- "third_party/wesaturate/500px/tmshre_riaphotographs_srgb8.png");
+ const PaddedBytes orig =
+ ReadTestData("external/wesaturate/500px/tmshre_riaphotographs_srgb8.png");
CodecInOut io;
ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io, &pool));
CompressParams cparams = CParamsForLossless();
cparams.speed_tier = SpeedTier::kFalcon;
cparams.options.skip_encoder_fast_path = true;
- DecompressParams dparams;
CodecInOut io2;
- EXPECT_LE(Roundtrip(&io, cparams, dparams, &pool, &io2), 3500000u);
+ EXPECT_LE(Roundtrip(&io, cparams, {}, &pool, &io2), 3500000u);
EXPECT_EQ(ComputeDistance2(io.Main(), io2.Main(), GetJxlCms()), 0.0);
}
TEST(JxlTest, JXL_SLOW_TEST(RoundtripLosslessNoEncoderFastPathGradient)) {
ThreadPoolInternal pool(8);
- const PaddedBytes orig = ReadTestData(
- "third_party/wesaturate/500px/tmshre_riaphotographs_srgb8.png");
+ const PaddedBytes orig =
+ ReadTestData("external/wesaturate/500px/tmshre_riaphotographs_srgb8.png");
CodecInOut io;
ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io, &pool));
@@ -1049,17 +1135,16 @@ TEST(JxlTest, JXL_SLOW_TEST(RoundtripLosslessNoEncoderFastPathGradient)) {
cparams.speed_tier = SpeedTier::kThunder;
cparams.options.skip_encoder_fast_path = true;
cparams.options.predictor = {Predictor::Gradient};
- DecompressParams dparams;
CodecInOut io2;
- EXPECT_LE(Roundtrip(&io, cparams, dparams, &pool, &io2), 3500000u);
+ EXPECT_LE(Roundtrip(&io, cparams, {}, &pool, &io2), 3500000u);
EXPECT_EQ(ComputeDistance2(io.Main(), io2.Main(), GetJxlCms()), 0.0);
}
TEST(JxlTest, JXL_SLOW_TEST(RoundtripLosslessNoEncoderVeryFastPathGradient)) {
ThreadPoolInternal pool(8);
- const PaddedBytes orig = ReadTestData(
- "third_party/wesaturate/500px/tmshre_riaphotographs_srgb8.png");
+ const PaddedBytes orig =
+ ReadTestData("external/wesaturate/500px/tmshre_riaphotographs_srgb8.png");
CodecInOut io;
ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io, &pool));
@@ -1067,37 +1152,35 @@ TEST(JxlTest, JXL_SLOW_TEST(RoundtripLosslessNoEncoderVeryFastPathGradient)) {
cparams.speed_tier = SpeedTier::kLightning;
cparams.options.skip_encoder_fast_path = true;
cparams.options.predictor = {Predictor::Gradient};
- DecompressParams dparams;
CodecInOut io2, io3;
- EXPECT_LE(Roundtrip(&io, cparams, dparams, &pool, &io2), 3500000u);
+ EXPECT_LE(Roundtrip(&io, cparams, {}, &pool, &io2), 3500000u);
EXPECT_EQ(ComputeDistance2(io.Main(), io2.Main(), GetJxlCms()), 0.0);
cparams.options.skip_encoder_fast_path = false;
- EXPECT_LE(Roundtrip(&io, cparams, dparams, &pool, &io3), 3500000u);
+ EXPECT_LE(Roundtrip(&io, cparams, {}, &pool, &io3), 3500000u);
EXPECT_EQ(ComputeDistance2(io.Main(), io3.Main(), GetJxlCms()), 0.0);
}
TEST(JxlTest, JXL_SLOW_TEST(RoundtripLossless8Falcon)) {
ThreadPoolInternal pool(8);
- const PaddedBytes orig = ReadTestData(
- "third_party/wesaturate/500px/tmshre_riaphotographs_srgb8.png");
+ const PaddedBytes orig =
+ ReadTestData("external/wesaturate/500px/tmshre_riaphotographs_srgb8.png");
CodecInOut io;
ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io, &pool));
CompressParams cparams = CParamsForLossless();
cparams.speed_tier = SpeedTier::kFalcon;
- DecompressParams dparams;
CodecInOut io2;
- EXPECT_LE(Roundtrip(&io, cparams, dparams, &pool, &io2), 3500000u);
+ EXPECT_LE(Roundtrip(&io, cparams, {}, &pool, &io2), 3500000u);
EXPECT_EQ(0.0, ButteraugliDistance(io, io2, cparams.ba_params, GetJxlCms(),
/*distmap=*/nullptr, &pool));
}
TEST(JxlTest, RoundtripLossless8Alpha) {
ThreadPool* pool = nullptr;
- const PaddedBytes orig = ReadTestData(
- "third_party/wesaturate/500px/tmshre_riaphotographs_alpha.png");
+ const PaddedBytes orig =
+ ReadTestData("external/wesaturate/500px/tmshre_riaphotographs_alpha.png");
CodecInOut io;
ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io, pool));
EXPECT_EQ(8u, io.metadata.m.GetAlphaBits());
@@ -1106,10 +1189,9 @@ TEST(JxlTest, RoundtripLossless8Alpha) {
EXPECT_EQ(0u, io.metadata.m.bit_depth.exponent_bits_per_sample);
CompressParams cparams = CParamsForLossless();
- DecompressParams dparams;
CodecInOut io2;
- EXPECT_LE(Roundtrip(&io, cparams, dparams, pool, &io2), 350000u);
+ EXPECT_LE(Roundtrip(&io, cparams, {}, pool, &io2), 350000u);
// If fails, see note about floating point in RoundtripLossless8.
EXPECT_EQ(ComputeDistance2(io.Main(), io2.Main(), GetJxlCms()), 0.0);
EXPECT_TRUE(SamePixels(*io.Main().alpha(), *io2.Main().alpha()));
@@ -1149,10 +1231,9 @@ TEST(JxlTest, RoundtripLossless16Alpha) {
EXPECT_EQ(0u, io.metadata.m.bit_depth.exponent_bits_per_sample);
CompressParams cparams = CParamsForLossless();
- DecompressParams dparams;
CodecInOut io2;
- EXPECT_LE(Roundtrip(&io, cparams, dparams, pool, &io2), 7100u);
+ EXPECT_LE(Roundtrip(&io, cparams, {}, pool, &io2), 7100u);
// If this test fails with a very close to 0.0 but not exactly 0.0 butteraugli
// distance, then there is likely a floating point issue, that could be
// happening either in io or io2. The values of io are generated by
@@ -1205,10 +1286,9 @@ TEST(JxlTest, RoundtripLossless16AlphaNotMisdetectedAs8Bit) {
EXPECT_EQ(0u, io.metadata.m.bit_depth.exponent_bits_per_sample);
CompressParams cparams = CParamsForLossless();
- DecompressParams dparams;
CodecInOut io2;
- EXPECT_LE(Roundtrip(&io, cparams, dparams, pool, &io2), 3100u);
+ EXPECT_LE(Roundtrip(&io, cparams, {}, pool, &io2), 3100u);
EXPECT_EQ(16u, io2.metadata.m.GetAlphaBits());
EXPECT_EQ(16u, io2.metadata.m.bit_depth.bits_per_sample);
EXPECT_FALSE(io2.metadata.m.bit_depth.floating_point_sample);
@@ -1220,18 +1300,15 @@ TEST(JxlTest, RoundtripLossless16AlphaNotMisdetectedAs8Bit) {
TEST(JxlTest, RoundtripYCbCr420) {
ThreadPool* pool = nullptr;
- const PaddedBytes orig =
- ReadTestData("third_party/imagecompression.info/flower_foveon.png");
+ const PaddedBytes orig = ReadTestData("jxl/flower/flower.png");
CodecInOut io;
ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io, pool));
- const PaddedBytes yuv420 = ReadTestData(
- "third_party/imagecompression.info/flower_foveon.png.ffmpeg.y4m");
+ const PaddedBytes yuv420 = ReadTestData("jxl/flower/flower.png.ffmpeg.y4m");
CodecInOut io2;
ASSERT_TRUE(test::DecodeImageY4M(Span<const uint8_t>(yuv420), &io2));
CompressParams cparams = CParamsForLossless();
cparams.speed_tier = SpeedTier::kThunder;
- DecompressParams dparams;
PassesEncoderState enc_state;
AuxOut* aux_out = nullptr;
@@ -1239,19 +1316,19 @@ TEST(JxlTest, RoundtripYCbCr420) {
EXPECT_TRUE(EncodeFile(cparams, &io2, &enc_state, &compressed, GetJxlCms(),
aux_out, pool));
CodecInOut io3;
- EXPECT_TRUE(DecodeFile(dparams, compressed, &io3, pool));
+ EXPECT_TRUE(test::DecodeFile({}, compressed, &io3, pool));
- EXPECT_LE(compressed.size(), 1325000u);
+ EXPECT_LE(compressed.size(), 2000000u);
// we're comparing an original PNG with a YCbCr 4:2:0 version
EXPECT_THAT(ComputeDistance2(io.Main(), io3.Main(), GetJxlCms()),
- IsSlightlyBelow(8.5));
+ IsSlightlyBelow(4.3));
}
TEST(JxlTest, RoundtripDots) {
ThreadPool* pool = nullptr;
const PaddedBytes orig =
- ReadTestData("third_party/wesaturate/500px/cvo9xd_keong_macan_srgb8.png");
+ ReadTestData("external/wesaturate/500px/cvo9xd_keong_macan_srgb8.png");
CodecInOut io;
ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io, pool));
@@ -1261,7 +1338,6 @@ TEST(JxlTest, RoundtripDots) {
cparams.dots = Override::kOn;
cparams.butteraugli_distance = 0.04;
cparams.speed_tier = SpeedTier::kSquirrel;
- DecompressParams dparams;
EXPECT_EQ(8u, io.metadata.m.bit_depth.bits_per_sample);
EXPECT_EQ(0u, io.metadata.m.bit_depth.exponent_bits_per_sample);
@@ -1273,7 +1349,7 @@ TEST(JxlTest, RoundtripDots) {
EXPECT_TRUE(EncodeFile(cparams, &io, &enc_state, &compressed, GetJxlCms(),
aux_out, pool));
CodecInOut io2;
- EXPECT_TRUE(DecodeFile(dparams, compressed, &io2, pool));
+ EXPECT_TRUE(test::DecodeFile({}, compressed, &io2, pool));
EXPECT_LE(compressed.size(), 400000u);
EXPECT_THAT(ButteraugliDistance(io, io2, cparams.ba_params, GetJxlCms(),
@@ -1284,7 +1360,7 @@ TEST(JxlTest, RoundtripDots) {
TEST(JxlTest, RoundtripNoise) {
ThreadPool* pool = nullptr;
const PaddedBytes orig =
- ReadTestData("third_party/wesaturate/500px/cvo9xd_keong_macan_srgb8.png");
+ ReadTestData("external/wesaturate/500px/cvo9xd_keong_macan_srgb8.png");
CodecInOut io;
ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io, pool));
@@ -1293,7 +1369,6 @@ TEST(JxlTest, RoundtripNoise) {
CompressParams cparams;
cparams.noise = Override::kOn;
cparams.speed_tier = SpeedTier::kSquirrel;
- DecompressParams dparams;
EXPECT_EQ(8u, io.metadata.m.bit_depth.bits_per_sample);
EXPECT_EQ(0u, io.metadata.m.bit_depth.exponent_bits_per_sample);
@@ -1305,7 +1380,7 @@ TEST(JxlTest, RoundtripNoise) {
EXPECT_TRUE(EncodeFile(cparams, &io, &enc_state, &compressed, GetJxlCms(),
aux_out, pool));
CodecInOut io2;
- EXPECT_TRUE(DecodeFile(dparams, compressed, &io2, pool));
+ EXPECT_TRUE(test::DecodeFile({}, compressed, &io2, pool));
EXPECT_LE(compressed.size(), 40000u);
EXPECT_THAT(ButteraugliDistance(io, io2, cparams.ba_params, GetJxlCms(),
@@ -1316,19 +1391,18 @@ TEST(JxlTest, RoundtripNoise) {
TEST(JxlTest, RoundtripLossless8Gray) {
ThreadPool* pool = nullptr;
const PaddedBytes orig = ReadTestData(
- "third_party/wesaturate/500px/cvo9xd_keong_macan_grayscale.png");
+ "external/wesaturate/500px/cvo9xd_keong_macan_grayscale.png");
CodecInOut io;
ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io, pool));
CompressParams cparams = CParamsForLossless();
- DecompressParams dparams;
EXPECT_TRUE(io.Main().IsGray());
EXPECT_EQ(8u, io.metadata.m.bit_depth.bits_per_sample);
EXPECT_FALSE(io.metadata.m.bit_depth.floating_point_sample);
EXPECT_EQ(0u, io.metadata.m.bit_depth.exponent_bits_per_sample);
CodecInOut io2;
- EXPECT_LE(Roundtrip(&io, cparams, dparams, pool, &io2), 130000u);
+ EXPECT_LE(Roundtrip(&io, cparams, {}, pool, &io2), 130000u);
// If fails, see note about floating point in RoundtripLossless8.
EXPECT_EQ(ComputeDistance2(io.Main(), io2.Main(), GetJxlCms()), 0);
EXPECT_EQ(0.0, ButteraugliDistance(io, io2, cparams.ba_params, GetJxlCms(),
@@ -1349,9 +1423,8 @@ TEST(JxlTest, RoundtripAnimation) {
ASSERT_EQ(4u, io.frames.size());
CompressParams cparams;
- DecompressParams dparams;
CodecInOut io2;
- EXPECT_LE(Roundtrip(&io, cparams, dparams, pool, &io2), 3000u);
+ EXPECT_LE(Roundtrip(&io, cparams, {}, pool, &io2), 3000u);
EXPECT_EQ(io2.frames.size(), io.frames.size());
test::CoalesceGIFAnimationWithAlpha(&io);
@@ -1372,9 +1445,8 @@ TEST(JxlTest, RoundtripLosslessAnimation) {
ASSERT_EQ(4u, io.frames.size());
CompressParams cparams = CParamsForLossless();
- DecompressParams dparams;
CodecInOut io2;
- EXPECT_LE(Roundtrip(&io, cparams, dparams, pool, &io2), 1200u);
+ EXPECT_LE(Roundtrip(&io, cparams, {}, pool, &io2), 1200u);
EXPECT_EQ(io2.frames.size(), io.frames.size());
test::CoalesceGIFAnimationWithAlpha(&io);
@@ -1392,46 +1464,19 @@ TEST(JxlTest, RoundtripAnimationPatches) {
CompressParams cparams;
cparams.patches = Override::kOn;
- DecompressParams dparams;
CodecInOut io2;
// 40k with no patches, 27k with patch frames encoded multiple times.
- EXPECT_LE(Roundtrip(&io, cparams, dparams, pool, &io2), 24000u);
+ EXPECT_LE(Roundtrip(&io, cparams, {}, pool, &io2), 24000u);
EXPECT_EQ(io2.frames.size(), io.frames.size());
// >10 with broken patches
EXPECT_THAT(ButteraugliDistance(io, io2, cparams.ba_params, GetJxlCms(),
/*distmap=*/nullptr, pool),
- IsSlightlyBelow(1.5));
+ IsSlightlyBelow(1.2));
}
#endif // JPEGXL_ENABLE_GIF
-namespace {
-
-jxl::Status DecompressJxlToJPEGForTest(
- const jpegxl::tools::JpegXlContainer& container, jxl::ThreadPool* pool,
- jxl::PaddedBytes* output) {
- output->clear();
- jxl::Span<const uint8_t> compressed(container.codestream);
-
- JXL_RETURN_IF_ERROR(compressed.size() >= 2);
-
- // JXL case
- // Decode to DCT when possible and generate a JPG file.
- jxl::CodecInOut io;
- jxl::DecompressParams params;
- params.keep_dct = true;
- if (!jpegxl::tools::DecodeJpegXlToJpeg(params, container, &io, pool)) {
- return JXL_FAILURE("Failed to decode JXL to JPEG");
- }
- if (!jpeg::EncodeImageJPGCoefficients(&io, output)) {
- return JXL_FAILURE("Failed to generate JPEG");
- }
- return true;
-}
-
-} // namespace
-
size_t RoundtripJpeg(const PaddedBytes& jpeg_in, ThreadPool* pool) {
CodecInOut io;
EXPECT_TRUE(jpeg::DecodeImageJPG(Span<const uint8_t>(jpeg_in), &io));
@@ -1453,11 +1498,13 @@ size_t RoundtripJpeg(const PaddedBytes& jpeg_in, ThreadPool* pool) {
enc_container.jpeg_reconstruction_size = jpeg_data.size();
EXPECT_TRUE(EncodeJpegXlContainerOneShot(enc_container, &compressed));
- jpegxl::tools::JpegXlContainer container;
- EXPECT_TRUE(DecodeJpegXlContainerOneShot(compressed.data(), compressed.size(),
- &container));
- PaddedBytes out;
- EXPECT_TRUE(DecompressJxlToJPEGForTest(container, pool, &out));
+ jxl::extras::JXLDecompressParams dparams;
+ dparams.runner = pool->runner();
+ dparams.runner_opaque = pool->runner_opaque();
+ std::vector<uint8_t> out;
+ jxl::extras::PackedPixelFile ppf;
+ EXPECT_TRUE(DecodeImageJXL(compressed.data(), compressed.size(), dparams,
+ nullptr, &ppf, &out));
EXPECT_EQ(out.size(), jpeg_in.size());
size_t failures = 0;
for (size_t i = 0; i < std::min(out.size(), jpeg_in.size()); i++) {
@@ -1474,18 +1521,16 @@ size_t RoundtripJpeg(const PaddedBytes& jpeg_in, ThreadPool* pool) {
TEST(JxlTest, JXL_TRANSCODE_JPEG_TEST(RoundtripJpegRecompression444)) {
ThreadPoolInternal pool(8);
- const PaddedBytes orig = ReadTestData(
- "third_party/imagecompression.info/flower_foveon.png.im_q85_444.jpg");
- // JPEG size is 326'916 bytes.
- EXPECT_LE(RoundtripJpeg(orig, &pool), 256000u);
+ const PaddedBytes orig = ReadTestData("jxl/flower/flower.png.im_q85_444.jpg");
+ // JPEG size is 696,659 bytes.
+ EXPECT_LE(RoundtripJpeg(orig, &pool), 570000u);
}
#if JPEGXL_ENABLE_JPEG
TEST(JxlTest, JXL_TRANSCODE_JPEG_TEST(RoundtripJpegRecompressionToPixels)) {
ThreadPoolInternal pool(8);
- const PaddedBytes orig = ReadTestData(
- "third_party/imagecompression.info/flower_foveon.png.im_q85_444.jpg");
+ const PaddedBytes orig = ReadTestData("jxl/flower/flower.png.im_q85_444.jpg");
CodecInOut io;
ASSERT_TRUE(jpeg::DecodeImageJPG(Span<const uint8_t>(orig), &io));
@@ -1495,10 +1540,8 @@ TEST(JxlTest, JXL_TRANSCODE_JPEG_TEST(RoundtripJpegRecompressionToPixels)) {
CompressParams cparams;
cparams.color_transform = jxl::ColorTransform::kYCbCr;
- DecompressParams dparams;
-
CodecInOut io3;
- Roundtrip(&io, cparams, dparams, &pool, &io3);
+ Roundtrip(&io, cparams, {}, &pool, &io3);
// TODO(eustas): investigate, why SJPEG and JpegRecompression pixels are
// different.
@@ -1508,8 +1551,7 @@ TEST(JxlTest, JXL_TRANSCODE_JPEG_TEST(RoundtripJpegRecompressionToPixels)) {
TEST(JxlTest, JXL_TRANSCODE_JPEG_TEST(RoundtripJpegRecompressionToPixels420)) {
ThreadPoolInternal pool(8);
- const PaddedBytes orig = ReadTestData(
- "third_party/imagecompression.info/flower_foveon.png.im_q85_420.jpg");
+ const PaddedBytes orig = ReadTestData("jxl/flower/flower.png.im_q85_420.jpg");
CodecInOut io;
ASSERT_TRUE(jpeg::DecodeImageJPG(Span<const uint8_t>(orig), &io));
@@ -1519,10 +1561,8 @@ TEST(JxlTest, JXL_TRANSCODE_JPEG_TEST(RoundtripJpegRecompressionToPixels420)) {
CompressParams cparams;
cparams.color_transform = jxl::ColorTransform::kYCbCr;
- DecompressParams dparams;
-
CodecInOut io3;
- Roundtrip(&io, cparams, dparams, &pool, &io3);
+ Roundtrip(&io, cparams, {}, &pool, &io3);
EXPECT_THAT(ComputeDistance2(io2.Main(), io3.Main(), GetJxlCms()),
IsSlightlyBelow(11));
@@ -1531,8 +1571,7 @@ TEST(JxlTest, JXL_TRANSCODE_JPEG_TEST(RoundtripJpegRecompressionToPixels420)) {
TEST(JxlTest,
JXL_TRANSCODE_JPEG_TEST(RoundtripJpegRecompressionToPixels420EarlyFlush)) {
ThreadPoolInternal pool(8);
- const PaddedBytes orig = ReadTestData(
- "third_party/imagecompression.info/flower_foveon.png.im_q85_420.jpg");
+ const PaddedBytes orig = ReadTestData("jxl/flower/flower.png.im_q85_420.jpg");
CodecInOut io;
ASSERT_TRUE(jpeg::DecodeImageJPG(Span<const uint8_t>(orig), &io));
@@ -1542,21 +1581,20 @@ TEST(JxlTest,
CompressParams cparams;
cparams.color_transform = jxl::ColorTransform::kYCbCr;
- DecompressParams dparams;
+ extras::JXLDecompressParams dparams;
dparams.max_downsampling = 8;
CodecInOut io3;
Roundtrip(&io, cparams, dparams, &pool, &io3);
EXPECT_THAT(ComputeDistance2(io2.Main(), io3.Main(), GetJxlCms()),
- IsSlightlyBelow(650));
+ IsSlightlyBelow(4410));
}
TEST(JxlTest,
JXL_TRANSCODE_JPEG_TEST(RoundtripJpegRecompressionToPixels420Mul16)) {
ThreadPoolInternal pool(8);
- const PaddedBytes orig = ReadTestData(
- "third_party/imagecompression.info/flower_foveon_cropped.jpg");
+ const PaddedBytes orig = ReadTestData("jxl/flower/flower_cropped.jpg");
CodecInOut io;
ASSERT_TRUE(jpeg::DecodeImageJPG(Span<const uint8_t>(orig), &io));
@@ -1566,10 +1604,8 @@ TEST(JxlTest,
CompressParams cparams;
cparams.color_transform = jxl::ColorTransform::kYCbCr;
- DecompressParams dparams;
-
CodecInOut io3;
- Roundtrip(&io, cparams, dparams, &pool, &io3);
+ Roundtrip(&io, cparams, {}, &pool, &io3);
EXPECT_THAT(ComputeDistance2(io2.Main(), io3.Main(), GetJxlCms()),
IsSlightlyBelow(4));
@@ -1578,9 +1614,8 @@ TEST(JxlTest,
TEST(JxlTest,
JXL_TRANSCODE_JPEG_TEST(RoundtripJpegRecompressionToPixels_asymmetric)) {
ThreadPoolInternal pool(8);
- const PaddedBytes orig = ReadTestData(
- "third_party/imagecompression.info/"
- "flower_foveon.png.im_q85_asymmetric.jpg");
+ const PaddedBytes orig =
+ ReadTestData("jxl/flower/flower.png.im_q85_asymmetric.jpg");
CodecInOut io;
ASSERT_TRUE(jpeg::DecodeImageJPG(Span<const uint8_t>(orig), &io));
@@ -1590,10 +1625,8 @@ TEST(JxlTest,
CompressParams cparams;
cparams.color_transform = jxl::ColorTransform::kYCbCr;
- DecompressParams dparams;
-
CodecInOut io3;
- Roundtrip(&io, cparams, dparams, &pool, &io3);
+ Roundtrip(&io, cparams, {}, &pool, &io3);
EXPECT_THAT(ComputeDistance2(io2.Main(), io3.Main(), GetJxlCms()),
IsSlightlyBelow(10));
@@ -1603,94 +1636,108 @@ TEST(JxlTest,
TEST(JxlTest, JXL_TRANSCODE_JPEG_TEST(RoundtripJpegRecompressionGray)) {
ThreadPoolInternal pool(8);
- const PaddedBytes orig = ReadTestData(
- "third_party/imagecompression.info/flower_foveon.png.im_q85_gray.jpg");
- // JPEG size is 167'025 bytes.
- EXPECT_LE(RoundtripJpeg(orig, &pool), 140000u);
+ const PaddedBytes orig =
+ ReadTestData("jxl/flower/flower.png.im_q85_gray.jpg");
+ // JPEG size is 456,528 bytes.
+ EXPECT_LE(RoundtripJpeg(orig, &pool), 390000u);
}
TEST(JxlTest, JXL_TRANSCODE_JPEG_TEST(RoundtripJpegRecompression420)) {
ThreadPoolInternal pool(8);
- const PaddedBytes orig = ReadTestData(
- "third_party/imagecompression.info/flower_foveon.png.im_q85_420.jpg");
- // JPEG size is 226'018 bytes.
- EXPECT_LE(RoundtripJpeg(orig, &pool), 181050u);
+ const PaddedBytes orig = ReadTestData("jxl/flower/flower.png.im_q85_420.jpg");
+ // JPEG size is 546,797 bytes.
+ EXPECT_LE(RoundtripJpeg(orig, &pool), 460000u);
}
TEST(JxlTest,
JXL_TRANSCODE_JPEG_TEST(RoundtripJpegRecompression_luma_subsample)) {
ThreadPoolInternal pool(8);
- const PaddedBytes orig = ReadTestData(
- "third_party/imagecompression.info/"
- "flower_foveon.png.im_q85_luma_subsample.jpg");
- // JPEG size is 216'069 bytes.
- EXPECT_LE(RoundtripJpeg(orig, &pool), 181000u);
+ const PaddedBytes orig =
+ ReadTestData("jxl/flower/flower.png.im_q85_luma_subsample.jpg");
+ // JPEG size is 400,724 bytes.
+ EXPECT_LE(RoundtripJpeg(orig, &pool), 330000u);
}
TEST(JxlTest, JXL_TRANSCODE_JPEG_TEST(RoundtripJpegRecompression444_12)) {
// 444 JPEG that has an interesting sampling-factor (1x2, 1x2, 1x2).
ThreadPoolInternal pool(8);
- const PaddedBytes orig = ReadTestData(
- "third_party/imagecompression.info/flower_foveon.png.im_q85_444_1x2.jpg");
- // JPEG size is 329'942 bytes.
- EXPECT_LE(RoundtripJpeg(orig, &pool), 256000u);
+ const PaddedBytes orig =
+ ReadTestData("jxl/flower/flower.png.im_q85_444_1x2.jpg");
+ // JPEG size is 703,874 bytes.
+ EXPECT_LE(RoundtripJpeg(orig, &pool), 570000u);
}
TEST(JxlTest, JXL_TRANSCODE_JPEG_TEST(RoundtripJpegRecompression422)) {
ThreadPoolInternal pool(8);
- const PaddedBytes orig = ReadTestData(
- "third_party/imagecompression.info/flower_foveon.png.im_q85_422.jpg");
- // JPEG size is 265'590 bytes.
- EXPECT_LE(RoundtripJpeg(orig, &pool), 209000u);
+ const PaddedBytes orig = ReadTestData("jxl/flower/flower.png.im_q85_422.jpg");
+ // JPEG size is 522,057 bytes.
+ EXPECT_LE(RoundtripJpeg(orig, &pool), 500000u);
}
TEST(JxlTest, JXL_TRANSCODE_JPEG_TEST(RoundtripJpegRecompression440)) {
ThreadPoolInternal pool(8);
- const PaddedBytes orig = ReadTestData(
- "third_party/imagecompression.info/flower_foveon.png.im_q85_440.jpg");
- // JPEG size is 262'249 bytes.
- EXPECT_LE(RoundtripJpeg(orig, &pool), 209000u);
+ const PaddedBytes orig = ReadTestData("jxl/flower/flower.png.im_q85_440.jpg");
+ // JPEG size is 603,623 bytes.
+ EXPECT_LE(RoundtripJpeg(orig, &pool), 510000u);
}
TEST(JxlTest, JXL_TRANSCODE_JPEG_TEST(RoundtripJpegRecompression_asymmetric)) {
// 2x vertical downsample of one chroma channel, 2x horizontal downsample of
// the other.
ThreadPoolInternal pool(8);
- const PaddedBytes orig = ReadTestData(
- "third_party/imagecompression.info/"
- "flower_foveon.png.im_q85_asymmetric.jpg");
- // JPEG size is 262'249 bytes.
- EXPECT_LE(RoundtripJpeg(orig, &pool), 209000u);
+ const PaddedBytes orig =
+ ReadTestData("jxl/flower/flower.png.im_q85_asymmetric.jpg");
+ // JPEG size is 604,601 bytes.
+ EXPECT_LE(RoundtripJpeg(orig, &pool), 510000u);
}
TEST(JxlTest, JXL_TRANSCODE_JPEG_TEST(RoundtripJpegRecompression420Progr)) {
ThreadPoolInternal pool(8);
- const PaddedBytes orig = ReadTestData(
- "third_party/imagecompression.info/"
- "flower_foveon.png.im_q85_420_progr.jpg");
- EXPECT_LE(RoundtripJpeg(orig, &pool), 181000u);
+ const PaddedBytes orig =
+ ReadTestData("jxl/flower/flower.png.im_q85_420_progr.jpg");
+ // JPEG size is 522,057 bytes.
+ EXPECT_LE(RoundtripJpeg(orig, &pool), 460000u);
}
TEST(JxlTest, RoundtripProgressive) {
ThreadPoolInternal pool(4);
- const PaddedBytes orig =
- ReadTestData("third_party/imagecompression.info/flower_foveon.png");
+ const PaddedBytes orig = ReadTestData("jxl/flower/flower.png");
+ CodecInOut io;
+ ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io, &pool));
+ io.ShrinkTo(600, 1024);
+
+ CompressParams cparams;
+
+ cparams.butteraugli_distance = 1.0f;
+ cparams.progressive_dc = 1;
+ cparams.responsive = true;
+ cparams.progressive_mode = true;
+ CodecInOut io2;
+ EXPECT_LE(Roundtrip(&io, cparams, {}, &pool, &io2), 61700u);
+ EXPECT_THAT(ButteraugliDistance(io, io2, cparams.ba_params, GetJxlCms(),
+ /*distmap=*/nullptr, &pool),
+ IsSlightlyBelow(1.17f));
+}
+
+TEST(JxlTest, RoundtripProgressiveLevel2Slow) {
+ ThreadPoolInternal pool(8);
+ const PaddedBytes orig = ReadTestData("jxl/flower/flower.png");
CodecInOut io;
ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io, &pool));
io.ShrinkTo(600, 1024);
CompressParams cparams;
- DecompressParams dparams;
cparams.butteraugli_distance = 1.0f;
- cparams.progressive_dc = true;
+ cparams.progressive_dc = 2;
+ cparams.speed_tier = SpeedTier::kTortoise;
cparams.responsive = true;
cparams.progressive_mode = true;
CodecInOut io2;
- EXPECT_LE(Roundtrip(&io, cparams, dparams, &pool, &io2), 40000u);
+ EXPECT_LE(Roundtrip(&io, cparams, {}, &pool, &io2), 71000u);
EXPECT_THAT(ButteraugliDistance(io, io2, cparams.ba_params, GetJxlCms(),
/*distmap=*/nullptr, &pool),
- IsSlightlyBelow(1.1f));
+ IsSlightlyBelow(1.2f));
}
} // namespace
diff --git a/media/libjxl/src/lib/jxl/libjxl.pc.in b/media/libjxl/src/lib/jxl/libjxl.pc.in
index 5dca2ac168..4a7af65b7c 100644
--- a/media/libjxl/src/lib/jxl/libjxl.pc.in
+++ b/media/libjxl/src/lib/jxl/libjxl.pc.in
@@ -1,7 +1,7 @@
prefix=@CMAKE_INSTALL_PREFIX@
exec_prefix=${prefix}
-libdir=${exec_prefix}/@CMAKE_INSTALL_LIBDIR@
-includedir=${prefix}/@CMAKE_INSTALL_INCLUDEDIR@
+libdir=@PKGCONFIG_TARGET_LIBS@
+includedir=@PKGCONFIG_TARGET_INCLUDES@
Name: libjxl
Description: Loads and saves JPEG XL files
@@ -10,3 +10,4 @@ Requires.private: @JPEGXL_LIBRARY_REQUIRES@
Libs: -L${libdir} -ljxl
Libs.private: -lm
Cflags: -I${includedir}
+Cflags.private: -DJXL_STATIC_DEFINE
diff --git a/media/libjxl/src/lib/jxl/modular/encoding/context_predict.h b/media/libjxl/src/lib/jxl/modular/encoding/context_predict.h
index 2865a4dabb..914cd6a4e4 100644
--- a/media/libjxl/src/lib/jxl/modular/encoding/context_predict.h
+++ b/media/libjxl/src/lib/jxl/modular/encoding/context_predict.h
@@ -62,7 +62,7 @@ struct State {
pixel_type_w pred = 0; // *before* removing the added bits.
std::vector<uint32_t> pred_errors[kNumPredictors];
std::vector<int32_t> error;
- Header header;
+ const Header header;
// Allows to approximate division by a number from 1 to 64.
uint32_t divlookup[64];
diff --git a/media/libjxl/src/lib/jxl/modular/encoding/enc_encoding.cc b/media/libjxl/src/lib/jxl/modular/encoding/enc_encoding.cc
index 1934b5e8d6..eeed2aee5c 100644
--- a/media/libjxl/src/lib/jxl/modular/encoding/enc_encoding.cc
+++ b/media/libjxl/src/lib/jxl/modular/encoding/enc_encoding.cc
@@ -87,7 +87,8 @@ void GatherTreeData(const Image &image, pixel_type chan, size_t group_id,
}
uint64_t threshold =
(std::numeric_limits<uint64_t>::max() >> 32) * pixel_fraction;
- uint64_t s[2] = {0x94D049BB133111EBull, 0xBF58476D1CE4E5B9ull};
+ uint64_t s[2] = {static_cast<uint64_t>(0x94D049BB133111EBull),
+ static_cast<uint64_t>(0xBF58476D1CE4E5B9ull)};
// Xorshift128+ adapted from xorshift128+-inl.h
auto use_sample = [&]() {
auto s1 = s[0];
diff --git a/media/libjxl/src/lib/jxl/modular/encoding/enc_ma.cc b/media/libjxl/src/lib/jxl/modular/encoding/enc_ma.cc
index 7700ecc26d..90b11baa06 100644
--- a/media/libjxl/src/lib/jxl/modular/encoding/enc_ma.cc
+++ b/media/libjxl/src/lib/jxl/modular/encoding/enc_ma.cc
@@ -28,6 +28,11 @@ HWY_BEFORE_NAMESPACE();
namespace jxl {
namespace HWY_NAMESPACE {
+// These templates are not found via ADL.
+using hwy::HWY_NAMESPACE::Eq;
+using hwy::HWY_NAMESPACE::IfThenElse;
+using hwy::HWY_NAMESPACE::Lt;
+
const HWY_FULL(float) df;
const HWY_FULL(int32_t) di;
size_t Padded(size_t x) { return RoundUpTo(x, Lanes(df)); }
@@ -40,8 +45,8 @@ float EstimateBits(const int32_t *counts, int32_t *rounded_counts,
const auto zero_i = Zero(di);
for (size_t i = 0; i < num_symbols; i += Lanes(df)) {
auto counts_v = LoadU(di, &counts[i]);
- counts_v = IfThenElse(counts_v == zero_i, zero_i,
- IfThenElse(counts_v < min, min, counts_v));
+ counts_v = IfThenElse(Eq(counts_v, zero_i), zero_i,
+ IfThenElse(Lt(counts_v, min), min, counts_v));
StoreU(counts_v, di, &rounded_counts[i]);
}
// Compute entropy of the "rounded" probabilities.
@@ -54,11 +59,11 @@ float EstimateBits(const int32_t *counts, int32_t *rounded_counts,
for (size_t i = 0; i < num_symbols; i += Lanes(df)) {
const auto counts_v = ConvertTo(df, LoadU(di, &counts[i]));
const auto round_counts_v = LoadU(di, &rounded_counts[i]);
- const auto probs = ConvertTo(df, round_counts_v) * inv_total;
- const auto nbps = IfThenElse(round_counts_v == total_v, BitCast(di, zero),
+ const auto probs = Mul(ConvertTo(df, round_counts_v), inv_total);
+ const auto nbps = IfThenElse(Eq(round_counts_v, total_v), BitCast(di, zero),
BitCast(di, FastLog2f(df, probs)));
- bits_lanes -=
- IfThenElse(counts_v == zero, zero, counts_v * BitCast(df, nbps));
+ bits_lanes = Sub(bits_lanes, IfThenElse(Eq(counts_v, zero), zero,
+ Mul(counts_v, BitCast(df, nbps))));
}
return GetLane(SumOfLanes(df, bits_lanes));
}
@@ -504,7 +509,7 @@ void ComputeBestTree(TreeSamples &tree_samples, float threshold,
tree);
}
-constexpr int TreeSamples::kPropertyRange;
+constexpr int32_t TreeSamples::kPropertyRange;
constexpr uint32_t TreeSamples::kDedupEntryUnused;
Status TreeSamples::SetPredictor(Predictor predictor,
@@ -722,12 +727,12 @@ void TreeSamples::ThreeShuffle(size_t a, size_t b, size_t c) {
}
namespace {
-std::vector<int> QuantizeHistogram(const std::vector<uint32_t> &histogram,
- size_t num_chunks) {
+std::vector<int32_t> QuantizeHistogram(const std::vector<uint32_t> &histogram,
+ size_t num_chunks) {
if (histogram.empty()) return {};
// TODO(veluca): selecting distinct quantiles is likely not the best
// way to go about this.
- std::vector<int> thresholds;
+ std::vector<int32_t> thresholds;
size_t sum = std::accumulate(histogram.begin(), histogram.end(), 0LU);
size_t cumsum = 0;
size_t threshold = 0;
@@ -741,8 +746,8 @@ std::vector<int> QuantizeHistogram(const std::vector<uint32_t> &histogram,
return thresholds;
}
-std::vector<int> QuantizeSamples(const std::vector<int32_t> &samples,
- size_t num_chunks) {
+std::vector<int32_t> QuantizeSamples(const std::vector<int32_t> &samples,
+ size_t num_chunks) {
if (samples.empty()) return {};
int min = *std::min_element(samples.begin(), samples.end());
constexpr int kRange = 512;
@@ -752,7 +757,7 @@ std::vector<int> QuantizeSamples(const std::vector<int32_t> &samples,
uint32_t sample_offset = std::min(std::max(s, -kRange), kRange) - min;
counts[sample_offset]++;
}
- std::vector<int> thresholds = QuantizeHistogram(counts, num_chunks);
+ std::vector<int32_t> thresholds = QuantizeHistogram(counts, num_chunks);
for (auto &v : thresholds) v += min;
return thresholds;
}
@@ -810,15 +815,15 @@ void TreeSamples::PreQuantizeProperties(
return QuantizeHistogram(group_pixel_count, max_property_values);
};
auto quantize_coordinate = [&]() {
- std::vector<int> quantized;
+ std::vector<int32_t> quantized;
quantized.reserve(max_property_values - 1);
for (size_t i = 0; i + 1 < max_property_values; i++) {
quantized.push_back((i + 1) * 256 / max_property_values - 1);
}
return quantized;
};
- std::vector<int> abs_pixel_thr;
- std::vector<int> pixel_thr;
+ std::vector<int32_t> abs_pixel_thr;
+ std::vector<int32_t> pixel_thr;
auto quantize_pixel_property = [&]() {
if (pixel_thr.empty()) {
pixel_thr = QuantizeSamples(pixel_samples, max_property_values);
@@ -833,8 +838,8 @@ void TreeSamples::PreQuantizeProperties(
}
return abs_pixel_thr;
};
- std::vector<int> abs_diff_thr;
- std::vector<int> diff_thr;
+ std::vector<int32_t> abs_diff_thr;
+ std::vector<int32_t> diff_thr;
auto quantize_diff_property = [&]() {
if (diff_thr.empty()) {
diff_thr = QuantizeSamples(diff_samples, max_property_values);
@@ -851,16 +856,16 @@ void TreeSamples::PreQuantizeProperties(
};
auto quantize_wp = [&]() {
if (max_property_values < 32) {
- return std::vector<int>{-127, -63, -31, -15, -7, -3, -1, 0,
- 1, 3, 7, 15, 31, 63, 127};
+ return std::vector<int32_t>{-127, -63, -31, -15, -7, -3, -1, 0,
+ 1, 3, 7, 15, 31, 63, 127};
}
if (max_property_values < 64) {
- return std::vector<int>{-255, -191, -127, -95, -63, -47, -31, -23,
- -15, -11, -7, -5, -3, -1, 0, 1,
- 3, 5, 7, 11, 15, 23, 31, 47,
- 63, 95, 127, 191, 255};
+ return std::vector<int32_t>{-255, -191, -127, -95, -63, -47, -31, -23,
+ -15, -11, -7, -5, -3, -1, 0, 1,
+ 3, 5, 7, 11, 15, 23, 31, 47,
+ 63, 95, 127, 191, 255};
}
- return std::vector<int>{
+ return std::vector<int32_t>{
-255, -223, -191, -159, -127, -111, -95, -79, -63, -55, -47,
-39, -31, -27, -23, -19, -15, -13, -11, -9, -7, -6,
-5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5,
diff --git a/media/libjxl/src/lib/jxl/modular/encoding/enc_ma.h b/media/libjxl/src/lib/jxl/modular/encoding/enc_ma.h
index d0a90cc952..ede37c8023 100644
--- a/media/libjxl/src/lib/jxl/modular/encoding/enc_ma.h
+++ b/media/libjxl/src/lib/jxl/modular/encoding/enc_ma.h
@@ -114,13 +114,13 @@ struct TreeSamples {
// Property values, quantized to at most 256 distinct values.
std::vector<std::vector<uint8_t>> props;
// Decompactification info for `props`.
- std::vector<std::vector<int>> compact_properties;
+ std::vector<std::vector<int32_t>> compact_properties;
// List of properties to use.
std::vector<uint32_t> props_to_use;
// List of predictors to use.
std::vector<Predictor> predictors;
// Mapping property value -> quantized property value.
- static constexpr int kPropertyRange = 511;
+ static constexpr int32_t kPropertyRange = 511;
std::vector<std::vector<uint8_t>> property_mapping;
// Number of samples seen.
size_t num_samples = 0;
diff --git a/media/libjxl/src/lib/jxl/modular/encoding/encoding.cc b/media/libjxl/src/lib/jxl/modular/encoding/encoding.cc
index b34cf78d2a..9d2c3e5cf9 100644
--- a/media/libjxl/src/lib/jxl/modular/encoding/encoding.cc
+++ b/media/libjxl/src/lib/jxl/modular/encoding/encoding.cc
@@ -591,18 +591,19 @@ Status ModularGenericDecompress(BitReader *br, Image &image,
#endif
GroupHeader local_header;
if (header == nullptr) header = &local_header;
+ size_t bit_pos = br->TotalBitsConsumed();
auto dec_status = ModularDecode(br, image, *header, group_id, options, tree,
code, ctx_map, allow_truncated_group);
if (!allow_truncated_group) JXL_RETURN_IF_ERROR(dec_status);
if (dec_status.IsFatalError()) return dec_status;
if (undo_transforms) image.undo_transforms(header->wp_header);
if (image.error) return JXL_FAILURE("Corrupt file. Aborting.");
- size_t bit_pos = br->TotalBitsConsumed();
JXL_DEBUG_V(4,
"Modular-decoded a %" PRIuS "x%" PRIuS " nbchans=%" PRIuS
" image from %" PRIuS " bytes",
image.w, image.h, image.channel.size(),
(br->TotalBitsConsumed() - bit_pos) / 8);
+ JXL_DEBUG_V(5, "Modular image: %s", image.DebugString().c_str());
(void)bit_pos;
#ifdef JXL_ENABLE_ASSERT
// Check that after applying all transforms we are back to the requested image
diff --git a/media/libjxl/src/lib/jxl/modular/modular_image.cc b/media/libjxl/src/lib/jxl/modular/modular_image.cc
index 6cfdf71505..785d0c5443 100644
--- a/media/libjxl/src/lib/jxl/modular/modular_image.cc
+++ b/media/libjxl/src/lib/jxl/modular/modular_image.cc
@@ -5,6 +5,8 @@
#include "lib/jxl/modular/modular_image.h"
+#include <sstream>
+
#include "lib/jxl/base/status.h"
#include "lib/jxl/common.h"
#include "lib/jxl/modular/transform/transform.h"
@@ -58,4 +60,18 @@ Image Image::clone() {
return c;
}
+std::string Image::DebugString() const {
+ std::ostringstream os;
+ os << w << "x" << h << ", depth: " << bitdepth;
+ if (!channel.empty()) {
+ os << ", channels:";
+ for (size_t i = 0; i < channel.size(); ++i) {
+ os << " " << channel[i].w << "x" << channel[i].h
+ << "(shift: " << channel[i].hshift << "," << channel[i].vshift << ")";
+ if (i < nb_meta_channels) os << "*";
+ }
+ }
+ return os.str();
+}
+
} // namespace jxl
diff --git a/media/libjxl/src/lib/jxl/modular/modular_image.h b/media/libjxl/src/lib/jxl/modular/modular_image.h
index 4d11e7abea..3e9b5a8a08 100644
--- a/media/libjxl/src/lib/jxl/modular/modular_image.h
+++ b/media/libjxl/src/lib/jxl/modular/modular_image.h
@@ -11,6 +11,7 @@
#include <stdio.h>
#include <string.h>
+#include <string>
#include <utility>
#include <vector>
@@ -97,10 +98,19 @@ class Image {
Image& operator=(Image&& other) noexcept;
Image(Image&& other) noexcept = default;
+ bool empty() const {
+ for (const auto& ch : channel) {
+ if (ch.w && ch.h) return false;
+ }
+ return true;
+ }
+
Image clone();
void undo_transforms(const weighted::Header& wp_header,
jxl::ThreadPool* pool = nullptr);
+
+ std::string DebugString() const;
};
} // namespace jxl
diff --git a/media/libjxl/src/lib/jxl/modular/transform/enc_palette.cc b/media/libjxl/src/lib/jxl/modular/transform/enc_palette.cc
index 1ae7353276..7065f80817 100644
--- a/media/libjxl/src/lib/jxl/modular/transform/enc_palette.cc
+++ b/media/libjxl/src/lib/jxl/modular/transform/enc_palette.cc
@@ -179,7 +179,43 @@ Status FwdPaletteIteration(Image &input, uint32_t begin_c, uint32_t end_c,
size_t lookup_table_size =
static_cast<int64_t>(maxval) - static_cast<int64_t>(minval) + 1;
if (lookup_table_size > palette_internal::kMaxPaletteLookupTableSize) {
- return false; // too large lookup table
+ // a lookup table would use too much memory, instead use a slower approach
+ // with std::set
+ std::set<pixel_type> chpalette;
+ pixel_type idx = 0;
+ for (size_t y = 0; y < h; y++) {
+ const pixel_type *p = input.channel[begin_c].Row(y);
+ for (size_t x = 0; x < w; x++) {
+ const bool new_color = chpalette.insert(p[x]).second;
+ if (new_color) {
+ idx++;
+ if (idx > (int)nb_colors) return false;
+ }
+ }
+ }
+ JXL_DEBUG_V(6, "Channel %i uses only %i colors.", begin_c, idx);
+ Channel pch(idx, 1);
+ pch.hshift = -1;
+ nb_colors = idx;
+ idx = 0;
+ pixel_type *JXL_RESTRICT p_palette = pch.Row(0);
+ for (pixel_type p : chpalette) {
+ p_palette[idx++] = p;
+ }
+ for (size_t y = 0; y < h; y++) {
+ pixel_type *p = input.channel[begin_c].Row(y);
+ for (size_t x = 0; x < w; x++) {
+ for (idx = 0; p[x] != p_palette[idx] && idx < (int)nb_colors; idx++) {
+ }
+ JXL_DASSERT(idx < (int)nb_colors);
+ p[x] = idx;
+ }
+ }
+ predictor = Predictor::Zero;
+ input.nb_meta_channels++;
+ input.channel.insert(input.channel.begin(), std::move(pch));
+
+ return true;
}
lookup.resize(lookup_table_size, 0);
pixel_type idx = 0;
@@ -304,7 +340,7 @@ Status FwdPaletteIteration(Image &input, uint32_t begin_c, uint32_t end_c,
pixel_type *JXL_RESTRICT p_palette = pch.Row(0);
intptr_t onerow = pch.plane.PixelsPerRow();
intptr_t onerow_image = input.channel[begin_c].plane.PixelsPerRow();
- const int bit_depth = input.bitdepth;
+ const int bit_depth = std::min(input.bitdepth, 24);
if (lossy) {
for (uint32_t i = 0; i < nb_deltas; i++) {
diff --git a/media/libjxl/src/lib/jxl/modular/transform/palette.h b/media/libjxl/src/lib/jxl/modular/transform/palette.h
index 5fd533fb71..ed2d33bed5 100644
--- a/media/libjxl/src/lib/jxl/modular/transform/palette.h
+++ b/media/libjxl/src/lib/jxl/modular/transform/palette.h
@@ -6,6 +6,8 @@
#ifndef LIB_JXL_MODULAR_TRANSFORM_PALETTE_H_
#define LIB_JXL_MODULAR_TRANSFORM_PALETTE_H_
+#include <atomic>
+
#include "lib/jxl/base/data_parallel.h"
#include "lib/jxl/base/status.h"
#include "lib/jxl/common.h"
@@ -139,7 +141,7 @@ static Status InvPalette(Image &input, uint32_t begin_c, uint32_t nb_colors,
const pixel_type *JXL_RESTRICT p_palette = input.channel[0].Row(0);
intptr_t onerow = input.channel[0].plane.PixelsPerRow();
intptr_t onerow_image = input.channel[c0].plane.PixelsPerRow();
- const int bit_depth = input.bitdepth;
+ const int bit_depth = std::min(input.bitdepth, 24);
if (w == 0) {
// Nothing to do.
@@ -152,7 +154,7 @@ static Status InvPalette(Image &input, uint32_t begin_c, uint32_t nb_colors,
const size_t y = task;
pixel_type *p = input.channel[c0].Row(y);
for (size_t x = 0; x < w; x++) {
- const int index = Clamp1(p[x], 0, (pixel_type)palette.w - 1);
+ const int index = Clamp1<int>(p[x], 0, (pixel_type)palette.w - 1);
p[x] = palette_internal::GetPaletteValue(
p_palette, index, /*c=*/0,
/*palette_size=*/palette.w,
diff --git a/media/libjxl/src/lib/jxl/modular/transform/rct.cc b/media/libjxl/src/lib/jxl/modular/transform/rct.cc
index 68f07ea267..f3002a5ac3 100644
--- a/media/libjxl/src/lib/jxl/modular/transform/rct.cc
+++ b/media/libjxl/src/lib/jxl/modular/transform/rct.cc
@@ -12,7 +12,10 @@ HWY_BEFORE_NAMESPACE();
namespace jxl {
namespace HWY_NAMESPACE {
+// These templates are not found via ADL.
+using hwy::HWY_NAMESPACE::Add;
using hwy::HWY_NAMESPACE::ShiftRight;
+using hwy::HWY_NAMESPACE::Sub;
template <int transform_type>
void InvRCTRow(const pixel_type* in0, const pixel_type* in1,
@@ -31,10 +34,10 @@ void InvRCTRow(const pixel_type* in0, const pixel_type* in1,
auto Y = Load(d, in0 + x);
auto Co = Load(d, in1 + x);
auto Cg = Load(d, in2 + x);
- Y -= ShiftRight<1>(Cg);
- auto G = Cg + Y;
- Y -= ShiftRight<1>(Co);
- auto R = Y + Co;
+ Y = Sub(Y, ShiftRight<1>(Cg));
+ auto G = Add(Cg, Y);
+ Y = Sub(Y, ShiftRight<1>(Co));
+ auto R = Add(Y, Co);
Store(R, d, out0 + x);
Store(G, d, out1 + x);
Store(Y, d, out2 + x);
@@ -42,11 +45,11 @@ void InvRCTRow(const pixel_type* in0, const pixel_type* in1,
auto First = Load(d, in0 + x);
auto Second = Load(d, in1 + x);
auto Third = Load(d, in2 + x);
- if (third) Third += First;
+ if (third) Third = Add(Third, First);
if (second == 1) {
- Second += First;
+ Second = Add(Second, First);
} else if (second == 2) {
- Second += ShiftRight<1>(First + Third);
+ Second = Add(Second, ShiftRight<1>(Add(First, Third)));
}
Store(First, d, out0 + x);
Store(Second, d, out1 + x);
diff --git a/media/libjxl/src/lib/jxl/modular/transform/squeeze.cc b/media/libjxl/src/lib/jxl/modular/transform/squeeze.cc
index 3f4689145b..34311895dc 100644
--- a/media/libjxl/src/lib/jxl/modular/transform/squeeze.cc
+++ b/media/libjxl/src/lib/jxl/modular/transform/squeeze.cc
@@ -23,9 +23,23 @@ HWY_BEFORE_NAMESPACE();
namespace jxl {
namespace HWY_NAMESPACE {
+// These templates are not found via ADL.
+using hwy::HWY_NAMESPACE::Abs;
+using hwy::HWY_NAMESPACE::Add;
+using hwy::HWY_NAMESPACE::And;
+using hwy::HWY_NAMESPACE::Gt;
+using hwy::HWY_NAMESPACE::IfThenElse;
+using hwy::HWY_NAMESPACE::IfThenZeroElse;
+using hwy::HWY_NAMESPACE::Lt;
+using hwy::HWY_NAMESPACE::MulEven;
+using hwy::HWY_NAMESPACE::Ne;
+using hwy::HWY_NAMESPACE::Neg;
+using hwy::HWY_NAMESPACE::OddEven;
using hwy::HWY_NAMESPACE::RebindToUnsigned;
using hwy::HWY_NAMESPACE::ShiftLeft;
using hwy::HWY_NAMESPACE::ShiftRight;
+using hwy::HWY_NAMESPACE::Sub;
+using hwy::HWY_NAMESPACE::Xor;
#if HWY_TARGET != HWY_SCALAR
@@ -44,12 +58,12 @@ JXL_INLINE void FastUnsqueeze(const pixel_type *JXL_RESTRICT p_residual,
auto next_avg = Load(d, p_navg + x);
auto top = Load(d, p_pout + x);
// Equivalent to SmoothTendency(top,avg,next_avg), but without branches
- auto Ba = top - avg;
- auto an = avg - next_avg;
- auto nonmono = Ba ^ an;
+ auto Ba = Sub(top, avg);
+ auto an = Sub(avg, next_avg);
+ auto nonmono = Xor(Ba, an);
auto absBa = Abs(Ba);
auto absan = Abs(an);
- auto absBn = Abs(top - next_avg);
+ auto absBn = Abs(Sub(top, next_avg));
// Compute a3 = absBa / 3
auto a3e = BitCast(d, ShiftRight<32>(MulEven(absBa, onethird)));
auto a3oi = MulEven(Reverse(d, absBa), onethird);
@@ -57,26 +71,27 @@ JXL_INLINE void FastUnsqueeze(const pixel_type *JXL_RESTRICT p_residual,
d, Reverse(hwy::HWY_NAMESPACE::Repartition<pixel_type_w, decltype(d)>(),
a3oi));
auto a3 = OddEven(a3o, a3e);
- a3 += absBn + Set(d, 2);
+ a3 = Add(a3, Add(absBn, Set(d, 2)));
auto absdiff = ShiftRight<2>(a3);
- auto skipdiff = Ba != Zero(d);
- skipdiff = And(skipdiff, an != Zero(d));
- skipdiff = And(skipdiff, nonmono < Zero(d));
- auto absBa2 = ShiftLeft<1>(absBa) + (absdiff & Set(d, 1));
- absdiff =
- IfThenElse(absdiff > absBa2, ShiftLeft<1>(absBa) + Set(d, 1), absdiff);
+ auto skipdiff = Ne(Ba, Zero(d));
+ skipdiff = And(skipdiff, Ne(an, Zero(d)));
+ skipdiff = And(skipdiff, Lt(nonmono, Zero(d)));
+ auto absBa2 = Add(ShiftLeft<1>(absBa), And(absdiff, Set(d, 1)));
+ absdiff = IfThenElse(Gt(absdiff, absBa2),
+ Add(ShiftLeft<1>(absBa), Set(d, 1)), absdiff);
auto absan2 = ShiftLeft<1>(absan);
- absdiff =
- IfThenElse(absdiff + (absdiff & Set(d, 1)) > absan2, absan2, absdiff);
- auto diff1 = IfThenElse(top < next_avg, Neg(absdiff), absdiff);
+ absdiff = IfThenElse(Gt(Add(absdiff, And(absdiff, Set(d, 1))), absan2),
+ absan2, absdiff);
+ auto diff1 = IfThenElse(Lt(top, next_avg), Neg(absdiff), absdiff);
auto tendency = IfThenZeroElse(skipdiff, diff1);
auto diff_minus_tendency = Load(d, p_residual + x);
- auto diff = diff_minus_tendency + tendency;
- auto out = avg + ShiftRight<1>(
- diff + BitCast(d, ShiftRight<31>(BitCast(du, diff))));
+ auto diff = Add(diff_minus_tendency, tendency);
+ auto out =
+ Add(avg, ShiftRight<1>(
+ Add(diff, BitCast(d, ShiftRight<31>(BitCast(du, diff))))));
Store(out, d, p_out + x);
- Store(out - diff, d, p_nout + x);
+ Store(Sub(out, diff), d, p_nout + x);
}
}
@@ -114,15 +129,15 @@ Status InvHSqueeze(Image &input, uint32_t c, uint32_t rc, ThreadPool *pool) {
const pixel_type *JXL_RESTRICT p_avg = chin.Row(y);
pixel_type *JXL_RESTRICT p_out = chout.Row(y);
for (size_t x = x0; x < chin_residual.w; x++) {
- pixel_type diff_minus_tendency = p_residual[x];
- pixel_type avg = p_avg[x];
- pixel_type next_avg = (x + 1 < chin.w ? p_avg[x + 1] : avg);
- pixel_type left = (x ? p_out[(x << 1) - 1] : avg);
- pixel_type tendency = SmoothTendency(left, avg, next_avg);
- pixel_type diff = diff_minus_tendency + tendency;
- pixel_type A = avg + (diff / 2);
+ pixel_type_w diff_minus_tendency = p_residual[x];
+ pixel_type_w avg = p_avg[x];
+ pixel_type_w next_avg = (x + 1 < chin.w ? p_avg[x + 1] : avg);
+ pixel_type_w left = (x ? p_out[(x << 1) - 1] : avg);
+ pixel_type_w tendency = SmoothTendency(left, avg, next_avg);
+ pixel_type_w diff = diff_minus_tendency + tendency;
+ pixel_type_w A = avg + (diff / 2);
p_out[(x << 1)] = A;
- pixel_type B = A - diff;
+ pixel_type_w B = A - diff;
p_out[(x << 1) + 1] = B;
}
if (chout.w & 1) p_out[chout.w - 1] = p_avg[chin.w - 1];
@@ -243,13 +258,13 @@ Status InvVSqueeze(Image &input, uint32_t c, uint32_t rc, ThreadPool *pool) {
}
#endif
for (; x < w; x++) {
- pixel_type avg = p_avg[x];
- pixel_type next_avg = p_navg[x];
- pixel_type top = p_pout[x];
- pixel_type tendency = SmoothTendency(top, avg, next_avg);
- pixel_type diff_minus_tendency = p_residual[x];
- pixel_type diff = diff_minus_tendency + tendency;
- pixel_type out = avg + (diff / 2);
+ pixel_type_w avg = p_avg[x];
+ pixel_type_w next_avg = p_navg[x];
+ pixel_type_w top = p_pout[x];
+ pixel_type_w tendency = SmoothTendency(top, avg, next_avg);
+ pixel_type_w diff_minus_tendency = p_residual[x];
+ pixel_type_w diff = diff_minus_tendency + tendency;
+ pixel_type_w out = avg + (diff / 2);
p_out[x] = out;
// If the chin_residual.h == chin.h, the output has an even number
// of rows so the next line is fine. Otherwise, this loop won't
@@ -450,6 +465,8 @@ Status MetaSqueeze(Image &image, std::vector<SqueezeParams> *parameters) {
image.channel.insert(image.channel.begin() + offset + (c - beginc),
std::move(dummy));
+ JXL_DEBUG_V(8, "MetaSqueeze applied, current image: %s",
+ image.DebugString().c_str());
}
}
return true;
@@ -457,4 +474,4 @@ Status MetaSqueeze(Image &image, std::vector<SqueezeParams> *parameters) {
} // namespace jxl
-#endif \ No newline at end of file
+#endif
diff --git a/media/libjxl/src/lib/jxl/modular/transform/transform.cc b/media/libjxl/src/lib/jxl/modular/transform/transform.cc
index 89f4586a56..d9f2b435bf 100644
--- a/media/libjxl/src/lib/jxl/modular/transform/transform.cc
+++ b/media/libjxl/src/lib/jxl/modular/transform/transform.cc
@@ -39,8 +39,7 @@ Status Transform::Inverse(Image &input, const weighted::Header &wp_header,
}
Status Transform::MetaApply(Image &input) {
- JXL_DEBUG_V(6, "Input channels (%" PRIuS ", %" PRIuS " meta): ",
- input.channel.size(), input.nb_meta_channels);
+ JXL_DEBUG_V(6, "MetaApply input: %s", input.DebugString().c_str());
switch (id) {
case TransformId::kRCT:
JXL_DEBUG_V(2, "Transform: kRCT, rct_type=%" PRIu32, rct_type);
@@ -68,9 +67,6 @@ Status Transform::MetaApply(Image &input) {
"Transform: kPalette, begin_c=%" PRIu32 ", num_c=%" PRIu32
", nb_colors=%" PRIu32 ", nb_deltas=%" PRIu32,
begin_c, num_c, nb_colors, nb_deltas);
- if (input.bitdepth > 24) {
- return JXL_FAILURE("Palette is not allowed for bitdepth > 24");
- }
return MetaPalette(input, begin_c, begin_c + num_c - 1, nb_colors,
nb_deltas, lossy_palette);
default:
diff --git a/media/libjxl/src/lib/jxl/modular_test.cc b/media/libjxl/src/lib/jxl/modular_test.cc
index db8c8f31b2..c87be68223 100644
--- a/media/libjxl/src/lib/jxl/modular_test.cc
+++ b/media/libjxl/src/lib/jxl/modular_test.cc
@@ -13,6 +13,7 @@
#include "gtest/gtest.h"
#include "lib/extras/codec.h"
+#include "lib/extras/dec/jxl.h"
#include "lib/jxl/aux_out.h"
#include "lib/jxl/base/compiler_specific.h"
#include "lib/jxl/base/data_parallel.h"
@@ -22,20 +23,20 @@
#include "lib/jxl/codec_in_out.h"
#include "lib/jxl/color_encoding_internal.h"
#include "lib/jxl/color_management.h"
-#include "lib/jxl/dec_file.h"
-#include "lib/jxl/dec_params.h"
#include "lib/jxl/enc_butteraugli_comparator.h"
#include "lib/jxl/enc_butteraugli_pnorm.h"
#include "lib/jxl/enc_cache.h"
#include "lib/jxl/enc_color_management.h"
#include "lib/jxl/enc_file.h"
#include "lib/jxl/enc_params.h"
+#include "lib/jxl/enc_toc.h"
#include "lib/jxl/image.h"
#include "lib/jxl/image_bundle.h"
#include "lib/jxl/image_ops.h"
#include "lib/jxl/image_test_utils.h"
#include "lib/jxl/modular/encoding/enc_encoding.h"
#include "lib/jxl/modular/encoding/encoding.h"
+#include "lib/jxl/modular/encoding/ma_common.h"
#include "lib/jxl/test_utils.h"
#include "lib/jxl/testdata.h"
@@ -45,12 +46,10 @@ using test::Roundtrip;
void TestLosslessGroups(size_t group_size_shift) {
ThreadPool* pool = nullptr;
- const PaddedBytes orig =
- ReadTestData("third_party/imagecompression.info/flower_foveon.png");
+ const PaddedBytes orig = ReadTestData("jxl/flower/flower.png");
CompressParams cparams;
cparams.SetLossless();
cparams.modular_group_size_shift = group_size_shift;
- DecompressParams dparams;
CodecInOut io_out;
size_t compressed_size;
@@ -59,7 +58,7 @@ void TestLosslessGroups(size_t group_size_shift) {
ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io, pool));
io.ShrinkTo(io.xsize() / 4, io.ysize() / 4);
- compressed_size = Roundtrip(&io, cparams, dparams, pool, &io_out);
+ compressed_size = Roundtrip(&io, cparams, {}, pool, &io_out);
EXPECT_LE(compressed_size, 280000u);
EXPECT_LE(ButteraugliDistance(io, io_out, cparams.ba_params, GetJxlCms(),
/*distmap=*/nullptr, pool),
@@ -79,7 +78,7 @@ TEST(ModularTest, JXL_TSAN_SLOW_TEST(RoundtripLosslessGroups1024)) {
TEST(ModularTest, RoundtripLosslessCustomWP_PermuteRCT) {
ThreadPool* pool = nullptr;
const PaddedBytes orig =
- ReadTestData("third_party/wesaturate/500px/u76c0g_bliznaca_srgb8.png");
+ ReadTestData("external/wesaturate/500px/u76c0g_bliznaca_srgb8.png");
CompressParams cparams;
cparams.SetLossless();
// 9 = permute to GBR, to test the special case of permutation-only
@@ -87,7 +86,6 @@ TEST(ModularTest, RoundtripLosslessCustomWP_PermuteRCT) {
// slowest speed so different WP modes are tried
cparams.speed_tier = SpeedTier::kTortoise;
cparams.options.predictor = {Predictor::Weighted};
- DecompressParams dparams;
CodecInOut io_out;
size_t compressed_size;
@@ -96,7 +94,7 @@ TEST(ModularTest, RoundtripLosslessCustomWP_PermuteRCT) {
ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io, pool));
io.ShrinkTo(100, 100);
- compressed_size = Roundtrip(&io, cparams, dparams, pool, &io_out);
+ compressed_size = Roundtrip(&io, cparams, {}, pool, &io_out);
EXPECT_LE(compressed_size, 10150u);
EXPECT_LE(ButteraugliDistance(io, io_out, cparams.ba_params, GetJxlCms(),
/*distmap=*/nullptr, pool),
@@ -106,15 +104,13 @@ TEST(ModularTest, RoundtripLosslessCustomWP_PermuteRCT) {
TEST(ModularTest, RoundtripLossyDeltaPalette) {
ThreadPool* pool = nullptr;
const PaddedBytes orig =
- ReadTestData("third_party/wesaturate/500px/u76c0g_bliznaca_srgb8.png");
+ ReadTestData("external/wesaturate/500px/u76c0g_bliznaca_srgb8.png");
CompressParams cparams;
cparams.modular_mode = true;
cparams.color_transform = jxl::ColorTransform::kNone;
cparams.lossy_palette = true;
cparams.palette_colors = 0;
- DecompressParams dparams;
-
CodecInOut io_out;
size_t compressed_size;
@@ -122,7 +118,7 @@ TEST(ModularTest, RoundtripLossyDeltaPalette) {
ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io, pool));
io.ShrinkTo(300, 100);
- compressed_size = Roundtrip(&io, cparams, dparams, pool, &io_out);
+ compressed_size = Roundtrip(&io, cparams, {}, pool, &io_out);
EXPECT_LE(compressed_size, 6800u);
cparams.ba_params.intensity_target = 80.0f;
EXPECT_THAT(ButteraugliDistance(io, io_out, cparams.ba_params, GetJxlCms(),
@@ -132,15 +128,13 @@ TEST(ModularTest, RoundtripLossyDeltaPalette) {
TEST(ModularTest, RoundtripLossyDeltaPaletteWP) {
ThreadPool* pool = nullptr;
const PaddedBytes orig =
- ReadTestData("third_party/wesaturate/500px/u76c0g_bliznaca_srgb8.png");
+ ReadTestData("external/wesaturate/500px/u76c0g_bliznaca_srgb8.png");
CompressParams cparams;
cparams.SetLossless();
cparams.lossy_palette = true;
cparams.palette_colors = 0;
cparams.options.predictor = jxl::Predictor::Weighted;
- DecompressParams dparams;
-
CodecInOut io_out;
size_t compressed_size;
@@ -148,7 +142,7 @@ TEST(ModularTest, RoundtripLossyDeltaPaletteWP) {
ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io, pool));
io.ShrinkTo(300, 100);
- compressed_size = Roundtrip(&io, cparams, dparams, pool, &io_out);
+ compressed_size = Roundtrip(&io, cparams, {}, pool, &io_out);
EXPECT_LE(compressed_size, 7000u);
cparams.ba_params.intensity_target = 80.0f;
EXPECT_THAT(ButteraugliDistance(io, io_out, cparams.ba_params, GetJxlCms(),
@@ -159,11 +153,10 @@ TEST(ModularTest, RoundtripLossyDeltaPaletteWP) {
TEST(ModularTest, RoundtripLossy) {
ThreadPool* pool = nullptr;
const PaddedBytes orig =
- ReadTestData("third_party/wesaturate/500px/u76c0g_bliznaca_srgb8.png");
+ ReadTestData("external/wesaturate/500px/u76c0g_bliznaca_srgb8.png");
CompressParams cparams;
cparams.modular_mode = true;
cparams.butteraugli_distance = 2.f;
- DecompressParams dparams;
CodecInOut io_out;
size_t compressed_size;
@@ -171,7 +164,7 @@ TEST(ModularTest, RoundtripLossy) {
CodecInOut io;
ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io, pool));
- compressed_size = Roundtrip(&io, cparams, dparams, pool, &io_out);
+ compressed_size = Roundtrip(&io, cparams, {}, pool, &io_out);
EXPECT_LE(compressed_size, 30000u);
cparams.ba_params.intensity_target = 80.0f;
EXPECT_THAT(ButteraugliDistance(io, io_out, cparams.ba_params, GetJxlCms(),
@@ -182,11 +175,10 @@ TEST(ModularTest, RoundtripLossy) {
TEST(ModularTest, RoundtripLossy16) {
ThreadPool* pool = nullptr;
const PaddedBytes orig =
- ReadTestData("third_party/raw.pixls/DJI-FC6310-16bit_709_v4_krita.png");
+ ReadTestData("external/raw.pixls/DJI-FC6310-16bit_709_v4_krita.png");
CompressParams cparams;
cparams.modular_mode = true;
cparams.butteraugli_distance = 2.f;
- DecompressParams dparams;
CodecInOut io_out;
size_t compressed_size;
@@ -196,7 +188,7 @@ TEST(ModularTest, RoundtripLossy16) {
JXL_CHECK(io.TransformTo(ColorEncoding::SRGB(), GetJxlCms(), pool));
io.metadata.m.color_encoding = ColorEncoding::SRGB();
- compressed_size = Roundtrip(&io, cparams, dparams, pool, &io_out);
+ compressed_size = Roundtrip(&io, cparams, {}, pool, &io_out);
EXPECT_LE(compressed_size, 300u);
cparams.ba_params.intensity_target = 80.0f;
EXPECT_THAT(ButteraugliDistance(io, io_out, cparams.ba_params, GetJxlCms(),
@@ -248,8 +240,8 @@ TEST(ModularTest, RoundtripExtraProperties) {
TEST(ModularTest, RoundtripLosslessCustomSqueeze) {
ThreadPool* pool = nullptr;
- const PaddedBytes orig = ReadTestData(
- "third_party/wesaturate/500px/tmshre_riaphotographs_srgb8.png");
+ const PaddedBytes orig =
+ ReadTestData("external/wesaturate/500px/tmshre_riaphotographs_srgb8.png");
CodecInOut io;
ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io, pool));
@@ -271,10 +263,9 @@ TEST(ModularTest, RoundtripLosslessCustomSqueeze) {
p.in_place = true;
p.horizontal = false;
cparams.squeezes.push_back(p);
- DecompressParams dparams;
CodecInOut io2;
- EXPECT_LE(Roundtrip(&io, cparams, dparams, pool, &io2), 265000u);
+ EXPECT_LE(Roundtrip(&io, cparams, {}, pool, &io2), 265000u);
EXPECT_EQ(0.0, ButteraugliDistance(io, io2, cparams.ba_params, GetJxlCms(),
/*distmap=*/nullptr, pool));
}
@@ -316,7 +307,7 @@ TEST_P(ModularTestParam, RoundtripLossless) {
ThreadPool* pool = nullptr;
Rng generator(123);
const PaddedBytes orig =
- ReadTestData("third_party/wesaturate/500px/u76c0g_bliznaca_srgb8.png");
+ ReadTestData("external/wesaturate/500px/u76c0g_bliznaca_srgb8.png");
CodecInOut io1;
ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io1, pool));
@@ -357,9 +348,8 @@ TEST_P(ModularTestParam, RoundtripLossless) {
cparams.options.predictor = {Predictor::Zero};
cparams.speed_tier = SpeedTier::kThunder;
cparams.responsive = responsive;
- DecompressParams dparams;
CodecInOut io2;
- EXPECT_LE(Roundtrip(&io, cparams, dparams, pool, &io2),
+ EXPECT_LE(Roundtrip(&io, cparams, {}, pool, &io2),
bitdepth * xsize * ysize / 3);
EXPECT_LE(0, ComputeDistance2(io.Main(), io2.Main(), GetJxlCms()));
size_t different = 0;
@@ -411,13 +401,145 @@ TEST(ModularTest, RoundtripLosslessCustomFloat) {
cparams.options.predictor = {Predictor::Zero};
cparams.speed_tier = SpeedTier::kThunder;
cparams.decoding_speed_tier = 2;
- DecompressParams dparams;
CodecInOut io2;
- EXPECT_LE(Roundtrip(&io, cparams, dparams, pool, &io2), 23000u);
+ EXPECT_LE(Roundtrip(&io, cparams, {}, pool, &io2), 23000u);
EXPECT_EQ(0.0, ButteraugliDistance(io, io2, cparams.ba_params, GetJxlCms(),
/*distmap=*/nullptr, pool));
}
+void WriteHeaders(BitWriter* writer, size_t xsize, size_t ysize) {
+ BitWriter::Allotment allotment(writer, 16);
+ writer->Write(8, 0xFF);
+ writer->Write(8, kCodestreamMarker);
+ ReclaimAndCharge(writer, &allotment, 0, nullptr);
+ CodecMetadata metadata;
+ EXPECT_TRUE(metadata.size.Set(xsize, ysize));
+ EXPECT_TRUE(WriteSizeHeader(metadata.size, writer, 0, nullptr));
+ metadata.m.color_encoding = ColorEncoding::LinearSRGB(/*is_gray=*/true);
+ metadata.m.xyb_encoded = false;
+ metadata.m.SetUintSamples(31);
+ EXPECT_TRUE(WriteImageMetadata(metadata.m, writer, 0, nullptr));
+ metadata.transform_data.nonserialized_xyb_encoded = metadata.m.xyb_encoded;
+ EXPECT_TRUE(Bundle::Write(metadata.transform_data, writer, 0, nullptr));
+ writer->ZeroPadToByte();
+ FrameHeader frame_header(&metadata);
+ frame_header.encoding = FrameEncoding::kModular;
+ frame_header.loop_filter.gab = false;
+ frame_header.loop_filter.epf_iters = 0;
+ EXPECT_TRUE(WriteFrameHeader(frame_header, writer, nullptr));
+}
+
+// Tree with single node, zero predictor, offset is 1 and multiplier is 1,
+// entropy code is prefix tree with alphabet size 256 and all bits lengths 8.
+void WriteHistograms(BitWriter* writer) {
+ writer->Write(1, 1); // default DC quant
+ writer->Write(1, 1); // has_tree
+ // tree histograms
+ writer->Write(1, 0); // LZ77 disabled
+ writer->Write(3, 1); // simple context map
+ writer->Write(1, 1); // prefix code
+ writer->Write(7, 0x63); // UnintConfig(3, 2, 1)
+ writer->Write(12, 0xfef); // alphabet_size = 256
+ writer->Write(32, 0x10003); // all bit lengths 8
+ // tree tokens
+ writer->Write(8, 0); // tree leaf
+ writer->Write(8, 0); // zero predictor
+ writer->Write(8, 64); // offset = UnpackSigned(ReverseBits(64)) = 1
+ writer->Write(16, 0); // multiplier = 1
+ // histograms
+ writer->Write(1, 0); // LZ77 disabled
+ writer->Write(1, 1); // prefix code
+ writer->Write(7, 0x63); // UnintConfig(3, 2, 1)
+ writer->Write(12, 0xfef); // alphabet_size = 256
+ writer->Write(32, 0x10003); // all bit lengths 8
+}
+
+TEST(ModularTest, PredictorIntegerOverflow) {
+ const size_t xsize = 1;
+ const size_t ysize = 1;
+ BitWriter writer;
+ WriteHeaders(&writer, xsize, ysize);
+ std::vector<BitWriter> group_codes(1);
+ {
+ BitWriter* bw = &group_codes[0];
+ BitWriter::Allotment allotment(bw, 1 << 20);
+ WriteHistograms(bw);
+ GroupHeader header;
+ header.use_global_tree = true;
+ EXPECT_TRUE(Bundle::Write(header, bw, 0, nullptr));
+ // After UnpackSigned this becomes (1 << 31) - 1, the largest pixel_type,
+ // and after adding the offset we get -(1 << 31).
+ bw->Write(8, 119);
+ bw->Write(28, 0xfffffff);
+ bw->ZeroPadToByte();
+ ReclaimAndCharge(bw, &allotment, 0, nullptr);
+ }
+ EXPECT_TRUE(WriteGroupOffsets(group_codes, nullptr, &writer, nullptr));
+ writer.AppendByteAligned(group_codes);
+
+ PaddedBytes compressed = std::move(writer).TakeBytes();
+ extras::PackedPixelFile ppf;
+ extras::JXLDecompressParams params;
+ params.accepted_formats.push_back({1, JXL_TYPE_FLOAT, JXL_LITTLE_ENDIAN, 0});
+ EXPECT_TRUE(DecodeImageJXL(compressed.data(), compressed.size(), params,
+ nullptr, &ppf));
+ ASSERT_EQ(1, ppf.frames.size());
+ const auto& img = ppf.frames[0].color;
+ const auto pixels = reinterpret_cast<const float*>(img.pixels());
+ EXPECT_EQ(-1.0f, pixels[0]);
+}
+
+TEST(ModularTest, UnsqueezeIntegerOverflow) {
+ // Image width is 9 so we can test both the SIMD and non-vector code paths.
+ const size_t xsize = 9;
+ const size_t ysize = 2;
+ BitWriter writer;
+ WriteHeaders(&writer, xsize, ysize);
+ std::vector<BitWriter> group_codes(1);
+ {
+ BitWriter* bw = &group_codes[0];
+ BitWriter::Allotment allotment(bw, 1 << 20);
+ WriteHistograms(bw);
+ GroupHeader header;
+ header.use_global_tree = true;
+ header.transforms.emplace_back();
+ header.transforms[0].id = TransformId::kSqueeze;
+ SqueezeParams params;
+ params.horizontal = false;
+ params.in_place = true;
+ params.begin_c = 0;
+ params.num_c = 1;
+ header.transforms[0].squeezes.emplace_back(params);
+ EXPECT_TRUE(Bundle::Write(header, bw, 0, nullptr));
+ for (size_t i = 0; i < xsize * ysize; ++i) {
+ // After UnpackSigned and adding offset, this becomes (1 << 31) - 1, both
+ // in the image and in the residual channels, and unsqueeze makes them
+ // ~(3 << 30) and (1 << 30) (in pixel_type_w) and the first wraps around
+ // to about -(1 << 30).
+ bw->Write(8, 119);
+ bw->Write(28, 0xffffffe);
+ }
+ bw->ZeroPadToByte();
+ ReclaimAndCharge(bw, &allotment, 0, nullptr);
+ }
+ EXPECT_TRUE(WriteGroupOffsets(group_codes, nullptr, &writer, nullptr));
+ writer.AppendByteAligned(group_codes);
+
+ PaddedBytes compressed = std::move(writer).TakeBytes();
+ extras::PackedPixelFile ppf;
+ extras::JXLDecompressParams params;
+ params.accepted_formats.push_back({1, JXL_TYPE_FLOAT, JXL_LITTLE_ENDIAN, 0});
+ EXPECT_TRUE(DecodeImageJXL(compressed.data(), compressed.size(), params,
+ nullptr, &ppf));
+ ASSERT_EQ(1, ppf.frames.size());
+ const auto& img = ppf.frames[0].color;
+ const auto pixels = reinterpret_cast<const float*>(img.pixels());
+ for (size_t x = 0; x < xsize; ++x) {
+ EXPECT_NEAR(-0.5f, pixels[x], 1e-10);
+ EXPECT_NEAR(0.5f, pixels[xsize + x], 1e-10);
+ }
+}
+
} // namespace
} // namespace jxl
diff --git a/media/libjxl/src/lib/jxl/opsin_image_test.cc b/media/libjxl/src/lib/jxl/opsin_image_test.cc
index 2a95e2917b..7573d6b8bd 100644
--- a/media/libjxl/src/lib/jxl/opsin_image_test.cc
+++ b/media/libjxl/src/lib/jxl/opsin_image_test.cc
@@ -19,11 +19,6 @@
namespace jxl {
namespace {
-class OpsinImageTargetTest : public hwy::TestWithParamTarget {};
-HWY_TARGET_INSTANTIATE_TEST_SUITE_P(OpsinImageTargetTest);
-
-TEST_P(OpsinImageTargetTest, MaxCubeRootError) { TestCubeRoot(); }
-
// Convert a single linear sRGB color to xyb, using the exact image conversion
// procedure that jpeg xl uses.
void LinearSrgbToOpsin(float rgb_r, float rgb_g, float rgb_b,
diff --git a/media/libjxl/src/lib/jxl/passes_test.cc b/media/libjxl/src/lib/jxl/passes_test.cc
index ad44e70b89..a58aadc011 100644
--- a/media/libjxl/src/lib/jxl/passes_test.cc
+++ b/media/libjxl/src/lib/jxl/passes_test.cc
@@ -5,6 +5,7 @@
#include <stddef.h>
+#include <future>
#include <string>
#include <utility>
@@ -18,8 +19,6 @@
#include "lib/jxl/base/thread_pool_internal.h"
#include "lib/jxl/color_encoding_internal.h"
#include "lib/jxl/common.h"
-#include "lib/jxl/dec_file.h"
-#include "lib/jxl/dec_params.h"
#include "lib/jxl/enc_butteraugli_comparator.h"
#include "lib/jxl/enc_cache.h"
#include "lib/jxl/enc_color_management.h"
@@ -37,7 +36,7 @@ using test::Roundtrip;
TEST(PassesTest, RoundtripSmallPasses) {
ThreadPool* pool = nullptr;
const PaddedBytes orig =
- ReadTestData("third_party/wesaturate/500px/u76c0g_bliznaca_srgb8.png");
+ ReadTestData("external/wesaturate/500px/u76c0g_bliznaca_srgb8.png");
CodecInOut io;
ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io, pool));
io.ShrinkTo(io.xsize() / 8, io.ysize() / 8);
@@ -45,10 +44,9 @@ TEST(PassesTest, RoundtripSmallPasses) {
CompressParams cparams;
cparams.butteraugli_distance = 1.0;
cparams.progressive_mode = true;
- DecompressParams dparams;
CodecInOut io2;
- Roundtrip(&io, cparams, dparams, pool, &io2);
+ Roundtrip(&io, cparams, {}, pool, &io2);
EXPECT_THAT(ButteraugliDistance(io, io2, cparams.ba_params, GetJxlCms(),
/*distmap=*/nullptr, pool),
IsSlightlyBelow(1.0));
@@ -57,7 +55,7 @@ TEST(PassesTest, RoundtripSmallPasses) {
TEST(PassesTest, RoundtripUnalignedPasses) {
ThreadPool* pool = nullptr;
const PaddedBytes orig =
- ReadTestData("third_party/wesaturate/500px/u76c0g_bliznaca_srgb8.png");
+ ReadTestData("external/wesaturate/500px/u76c0g_bliznaca_srgb8.png");
CodecInOut io;
ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io, pool));
io.ShrinkTo(io.xsize() / 12, io.ysize() / 7);
@@ -65,56 +63,51 @@ TEST(PassesTest, RoundtripUnalignedPasses) {
CompressParams cparams;
cparams.butteraugli_distance = 2.0;
cparams.progressive_mode = true;
- DecompressParams dparams;
CodecInOut io2;
- Roundtrip(&io, cparams, dparams, pool, &io2);
+ Roundtrip(&io, cparams, {}, pool, &io2);
EXPECT_THAT(ButteraugliDistance(io, io2, cparams.ba_params, GetJxlCms(),
/*distmap=*/nullptr, pool),
IsSlightlyBelow(1.6));
}
TEST(PassesTest, RoundtripMultiGroupPasses) {
- ThreadPoolInternal pool(4);
- const PaddedBytes orig =
- ReadTestData("third_party/imagecompression.info/flower_foveon.png");
+ const PaddedBytes orig = ReadTestData("jxl/flower/flower.png");
CodecInOut io;
- ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io, &pool));
+ {
+ ThreadPoolInternal pool(4);
+ ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io, &pool));
+ }
io.ShrinkTo(600, 1024); // partial X, full Y group
- CompressParams cparams;
- DecompressParams dparams;
+ auto test = [&](float target_distance, float threshold) {
+ ThreadPoolInternal pool(4);
+ CompressParams cparams;
+ cparams.butteraugli_distance = target_distance;
+ cparams.progressive_mode = true;
+ CodecInOut io2;
+ Roundtrip(&io, cparams, {}, &pool, &io2);
+ EXPECT_THAT(ButteraugliDistance(io, io2, cparams.ba_params, GetJxlCms(),
+ /*distmap=*/nullptr, &pool),
+ IsSlightlyBelow(target_distance + threshold));
+ };
- cparams.butteraugli_distance = 1.0f;
- cparams.progressive_mode = true;
- CodecInOut io2;
- Roundtrip(&io, cparams, dparams, &pool, &io2);
- EXPECT_THAT(ButteraugliDistance(io, io2, cparams.ba_params, GetJxlCms(),
- /*distmap=*/nullptr, &pool),
- IsSlightlyBelow(1.2f));
-
- cparams.butteraugli_distance = 2.0f;
- CodecInOut io3;
- Roundtrip(&io, cparams, dparams, &pool, &io3);
- EXPECT_THAT(ButteraugliDistance(io, io3, cparams.ba_params, GetJxlCms(),
- /*distmap=*/nullptr, &pool),
- IsSlightlyBelow(2.0f));
+ auto run1 = std::async(std::launch::async, test, 1.0f, 0.3f);
+ auto run2 = std::async(std::launch::async, test, 2.0f, 0.3f);
}
TEST(PassesTest, RoundtripLargeFastPasses) {
ThreadPoolInternal pool(8);
- const PaddedBytes orig =
- ReadTestData("third_party/imagecompression.info/flower_foveon.png");
+ const PaddedBytes orig = ReadTestData("jxl/flower/flower.png");
CodecInOut io;
ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io, &pool));
CompressParams cparams;
cparams.speed_tier = SpeedTier::kSquirrel;
cparams.progressive_mode = true;
- DecompressParams dparams;
CodecInOut io2;
- Roundtrip(&io, cparams, dparams, &pool, &io2);
+ Roundtrip(&io, cparams, {}, &pool, &io2);
}
// Checks for differing size/distance in two consecutive runs of distance 2,
@@ -122,8 +115,7 @@ TEST(PassesTest, RoundtripLargeFastPasses) {
// Failing this may be a sign of race conditions or invalid memory accesses.
TEST(PassesTest, RoundtripProgressiveConsistent) {
ThreadPoolInternal pool(8);
- const PaddedBytes orig =
- ReadTestData("third_party/imagecompression.info/flower_foveon.png");
+ const PaddedBytes orig = ReadTestData("jxl/flower/flower.png");
CodecInOut io;
ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io, &pool));
@@ -131,17 +123,16 @@ TEST(PassesTest, RoundtripProgressiveConsistent) {
cparams.speed_tier = SpeedTier::kSquirrel;
cparams.progressive_mode = true;
cparams.butteraugli_distance = 2.0;
- DecompressParams dparams;
// Try each xsize mod kBlockDim to verify right border handling.
for (size_t xsize = 48; xsize > 40; --xsize) {
io.ShrinkTo(xsize, 15);
CodecInOut io2;
- const size_t size2 = Roundtrip(&io, cparams, dparams, &pool, &io2);
+ const size_t size2 = Roundtrip(&io, cparams, {}, &pool, &io2);
CodecInOut io3;
- const size_t size3 = Roundtrip(&io, cparams, dparams, &pool, &io3);
+ const size_t size3 = Roundtrip(&io, cparams, {}, &pool, &io3);
// Exact same compressed size.
EXPECT_EQ(size2, size3);
@@ -160,7 +151,7 @@ TEST(PassesTest, RoundtripProgressiveConsistent) {
TEST(PassesTest, AllDownsampleFeasible) {
ThreadPoolInternal pool(8);
const PaddedBytes orig =
- ReadTestData("third_party/wesaturate/500px/u76c0g_bliznaca_srgb8.png");
+ ReadTestData("external/wesaturate/500px/u76c0g_bliznaca_srgb8.png");
CodecInOut io;
ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io, &pool));
@@ -189,10 +180,10 @@ TEST(PassesTest, AllDownsampleFeasible) {
auto check = [&](const uint32_t task, size_t /* thread */) -> void {
const size_t downsampling = downsamplings[task];
- DecompressParams dparams;
+ extras::JXLDecompressParams dparams;
dparams.max_downsampling = downsampling;
CodecInOut output;
- ASSERT_TRUE(DecodeFile(dparams, compressed, &output, nullptr));
+ ASSERT_TRUE(test::DecodeFile(dparams, compressed, &output, nullptr));
EXPECT_EQ(output.xsize(), io.xsize()) << "downsampling = " << downsampling;
EXPECT_EQ(output.ysize(), io.ysize()) << "downsampling = " << downsampling;
EXPECT_LE(ButteraugliDistance(io, output, cparams.ba_params, GetJxlCms(),
@@ -207,7 +198,7 @@ TEST(PassesTest, AllDownsampleFeasible) {
TEST(PassesTest, AllDownsampleFeasibleQProgressive) {
ThreadPoolInternal pool(8);
const PaddedBytes orig =
- ReadTestData("third_party/wesaturate/500px/u76c0g_bliznaca_srgb8.png");
+ ReadTestData("external/wesaturate/500px/u76c0g_bliznaca_srgb8.png");
CodecInOut io;
ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io, &pool));
@@ -236,10 +227,10 @@ TEST(PassesTest, AllDownsampleFeasibleQProgressive) {
auto check = [&](const uint32_t task, size_t /* thread */) -> void {
const size_t downsampling = downsamplings[task];
- DecompressParams dparams;
+ extras::JXLDecompressParams dparams;
dparams.max_downsampling = downsampling;
CodecInOut output;
- ASSERT_TRUE(DecodeFile(dparams, compressed, &output, nullptr));
+ ASSERT_TRUE(test::DecodeFile(dparams, compressed, &output, nullptr));
EXPECT_EQ(output.xsize(), io.xsize()) << "downsampling = " << downsampling;
EXPECT_EQ(output.ysize(), io.ysize()) << "downsampling = " << downsampling;
EXPECT_LE(ButteraugliDistance(io, output, cparams.ba_params, GetJxlCms(),
@@ -254,7 +245,7 @@ TEST(PassesTest, AllDownsampleFeasibleQProgressive) {
TEST(PassesTest, ProgressiveDownsample2DegradesCorrectlyGrayscale) {
ThreadPoolInternal pool(8);
const PaddedBytes orig = ReadTestData(
- "third_party/wesaturate/500px/cvo9xd_keong_macan_grayscale.png");
+ "external/wesaturate/500px/cvo9xd_keong_macan_grayscale.png");
CodecInOut io_orig;
ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io_orig, &pool));
Rect rect(0, 0, io_orig.xsize(), 128);
@@ -281,14 +272,14 @@ TEST(PassesTest, ProgressiveDownsample2DegradesCorrectlyGrayscale) {
EXPECT_LE(compressed.size(), 10000u);
- DecompressParams dparams;
+ extras::JXLDecompressParams dparams;
dparams.max_downsampling = 1;
CodecInOut output;
- ASSERT_TRUE(DecodeFile(dparams, compressed, &output, nullptr));
+ ASSERT_TRUE(test::DecodeFile(dparams, compressed, &output, nullptr));
dparams.max_downsampling = 2;
CodecInOut output_d2;
- ASSERT_TRUE(DecodeFile(dparams, compressed, &output_d2, nullptr));
+ ASSERT_TRUE(test::DecodeFile(dparams, compressed, &output_d2, nullptr));
// 0 if reading all the passes, ~15 if skipping the 8x pass.
float butteraugli_distance_down2_full =
@@ -301,8 +292,7 @@ TEST(PassesTest, ProgressiveDownsample2DegradesCorrectlyGrayscale) {
TEST(PassesTest, ProgressiveDownsample2DegradesCorrectly) {
ThreadPoolInternal pool(8);
- const PaddedBytes orig =
- ReadTestData("third_party/imagecompression.info/flower_foveon.png");
+ const PaddedBytes orig = ReadTestData("jxl/flower/flower.png");
CodecInOut io_orig;
ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io_orig, &pool));
Rect rect(0, 0, io_orig.xsize(), 128);
@@ -328,14 +318,14 @@ TEST(PassesTest, ProgressiveDownsample2DegradesCorrectly) {
EXPECT_LE(compressed.size(), 220000u);
- DecompressParams dparams;
+ extras::JXLDecompressParams dparams;
dparams.max_downsampling = 1;
CodecInOut output;
- ASSERT_TRUE(DecodeFile(dparams, compressed, &output, nullptr));
+ ASSERT_TRUE(test::DecodeFile(dparams, compressed, &output, nullptr));
dparams.max_downsampling = 2;
CodecInOut output_d2;
- ASSERT_TRUE(DecodeFile(dparams, compressed, &output_d2, nullptr));
+ ASSERT_TRUE(test::DecodeFile(dparams, compressed, &output_d2, nullptr));
// 0 if reading all the passes, ~15 if skipping the 8x pass.
float butteraugli_distance_down2_full =
@@ -348,8 +338,7 @@ TEST(PassesTest, ProgressiveDownsample2DegradesCorrectly) {
TEST(PassesTest, NonProgressiveDCImage) {
ThreadPoolInternal pool(8);
- const PaddedBytes orig =
- ReadTestData("third_party/imagecompression.info/flower_foveon.png");
+ const PaddedBytes orig = ReadTestData("jxl/flower/flower.png");
CodecInOut io;
ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io, &pool));
@@ -366,10 +355,10 @@ TEST(PassesTest, NonProgressiveDCImage) {
// Even in non-progressive mode, it should be possible to return a DC-only
// image.
- DecompressParams dparams;
+ extras::JXLDecompressParams dparams;
dparams.max_downsampling = 100;
CodecInOut output;
- ASSERT_TRUE(DecodeFile(dparams, compressed, &output, &pool));
+ ASSERT_TRUE(test::DecodeFile(dparams, compressed, &output, &pool));
EXPECT_EQ(output.xsize(), io.xsize());
EXPECT_EQ(output.ysize(), io.ysize());
}
@@ -377,7 +366,7 @@ TEST(PassesTest, NonProgressiveDCImage) {
TEST(PassesTest, RoundtripSmallNoGaborishPasses) {
ThreadPool* pool = nullptr;
const PaddedBytes orig =
- ReadTestData("third_party/wesaturate/500px/u76c0g_bliznaca_srgb8.png");
+ ReadTestData("external/wesaturate/500px/u76c0g_bliznaca_srgb8.png");
CodecInOut io;
ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io, pool));
io.ShrinkTo(io.xsize() / 8, io.ysize() / 8);
@@ -386,10 +375,9 @@ TEST(PassesTest, RoundtripSmallNoGaborishPasses) {
cparams.gaborish = Override::kOff;
cparams.butteraugli_distance = 1.0;
cparams.progressive_mode = true;
- DecompressParams dparams;
CodecInOut io2;
- Roundtrip(&io, cparams, dparams, pool, &io2);
+ Roundtrip(&io, cparams, {}, pool, &io2);
EXPECT_THAT(ButteraugliDistance(io, io2, cparams.ba_params, GetJxlCms(),
/*distmap=*/nullptr, pool),
IsSlightlyBelow(1.2));
diff --git a/media/libjxl/src/lib/jxl/patch_dictionary_test.cc b/media/libjxl/src/lib/jxl/patch_dictionary_test.cc
index 01dcbd554f..3a34b83d08 100644
--- a/media/libjxl/src/lib/jxl/patch_dictionary_test.cc
+++ b/media/libjxl/src/lib/jxl/patch_dictionary_test.cc
@@ -5,7 +5,6 @@
#include "gtest/gtest.h"
#include "lib/extras/codec.h"
-#include "lib/jxl/dec_params.h"
#include "lib/jxl/enc_butteraugli_comparator.h"
#include "lib/jxl/enc_params.h"
#include "lib/jxl/image_test_utils.h"
@@ -26,11 +25,10 @@ TEST(PatchDictionaryTest, GrayscaleModular) {
CompressParams cparams;
cparams.SetLossless();
cparams.patches = jxl::Override::kOn;
- DecompressParams dparams;
CodecInOut io2;
// Without patches: ~25k
- EXPECT_LE(Roundtrip(&io, cparams, dparams, pool, &io2), 8000u);
+ EXPECT_LE(Roundtrip(&io, cparams, {}, pool, &io2), 8000u);
VerifyRelativeError(*io.Main().color(), *io2.Main().color(), 1e-7f, 0);
}
@@ -42,11 +40,10 @@ TEST(PatchDictionaryTest, GrayscaleVarDCT) {
CompressParams cparams;
cparams.patches = jxl::Override::kOn;
- DecompressParams dparams;
CodecInOut io2;
// Without patches: ~47k
- EXPECT_LE(Roundtrip(&io, cparams, dparams, pool, &io2), 14000u);
+ EXPECT_LE(Roundtrip(&io, cparams, {}, pool, &io2), 14000u);
// Without patches: ~1.2
EXPECT_LE(ButteraugliDistance(io, io2, cparams.ba_params, GetJxlCms(),
/*distmap=*/nullptr, pool),
diff --git a/media/libjxl/src/lib/jxl/preview_test.cc b/media/libjxl/src/lib/jxl/preview_test.cc
index a8342d3a2a..35ec70b57e 100644
--- a/media/libjxl/src/lib/jxl/preview_test.cc
+++ b/media/libjxl/src/lib/jxl/preview_test.cc
@@ -16,8 +16,6 @@
#include "lib/jxl/base/padded_bytes.h"
#include "lib/jxl/codec_in_out.h"
#include "lib/jxl/color_encoding_internal.h"
-#include "lib/jxl/dec_file.h"
-#include "lib/jxl/dec_params.h"
#include "lib/jxl/enc_butteraugli_comparator.h"
#include "lib/jxl/enc_cache.h"
#include "lib/jxl/enc_file.h"
@@ -34,7 +32,7 @@ using test::Roundtrip;
TEST(PreviewTest, RoundtripGivenPreview) {
ThreadPool* pool = nullptr;
const PaddedBytes orig =
- ReadTestData("third_party/wesaturate/500px/u76c0g_bliznaca_srgb8.png");
+ ReadTestData("external/wesaturate/500px/u76c0g_bliznaca_srgb8.png");
CodecInOut io;
ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io, pool));
io.ShrinkTo(io.xsize() / 8, io.ysize() / 8);
@@ -50,10 +48,9 @@ TEST(PreviewTest, RoundtripGivenPreview) {
CompressParams cparams;
cparams.butteraugli_distance = 2.0;
cparams.speed_tier = SpeedTier::kSquirrel;
- DecompressParams dparams;
CodecInOut io2;
- Roundtrip(&io, cparams, dparams, pool, &io2);
+ Roundtrip(&io, cparams, {}, pool, &io2);
EXPECT_EQ(preview_xsize, io2.metadata.m.preview_size.xsize());
EXPECT_EQ(preview_ysize, io2.metadata.m.preview_size.ysize());
EXPECT_EQ(preview_xsize, io2.preview_frame.xsize());
diff --git a/media/libjxl/src/lib/jxl/progressive_split.h b/media/libjxl/src/lib/jxl/progressive_split.h
index 68ab7bc9dc..aeae980447 100644
--- a/media/libjxl/src/lib/jxl/progressive_split.h
+++ b/media/libjxl/src/lib/jxl/progressive_split.h
@@ -115,7 +115,10 @@ class ProgressiveSplitter {
min_downsampling_factor != kNoDownsamplingFactor) {
passes->downsample[passes->num_downsample] = min_downsampling_factor;
passes->last_pass[passes->num_downsample] = i;
- passes->num_downsample += 1;
+ if (mode_.passes[i + 1].suitable_for_downsampling_of_at_least <
+ min_downsampling_factor) {
+ passes->num_downsample += 1;
+ }
}
}
}
diff --git a/media/libjxl/src/lib/jxl/quant_weights.cc b/media/libjxl/src/lib/jxl/quant_weights.cc
index e8d9a10ed6..756a481414 100644
--- a/media/libjxl/src/lib/jxl/quant_weights.cc
+++ b/media/libjxl/src/lib/jxl/quant_weights.cc
@@ -32,6 +32,11 @@ HWY_BEFORE_NAMESPACE();
namespace jxl {
namespace HWY_NAMESPACE {
+// These templates are not found via ADL.
+using hwy::HWY_NAMESPACE::Lt;
+using hwy::HWY_NAMESPACE::MulAdd;
+using hwy::HWY_NAMESPACE::Sqrt;
+
// kQuantWeights[N * N * c + N * y + x] is the relative weight of the (x, y)
// coefficient in component c. Higher weights correspond to finer quantization
// intervals and more bits spent in encoding.
@@ -104,14 +109,14 @@ hwy::HWY_NAMESPACE::Vec<DF4> InterpolateVec(
auto idx = ConvertTo(di, scaled_pos);
- auto frac = scaled_pos - ConvertTo(DF4(), idx);
+ auto frac = Sub(scaled_pos, ConvertTo(DF4(), idx));
// TODO(veluca): in theory, this could be done with 8 TableLookupBytes, but
// it's probably slower.
auto a = GatherIndex(DF4(), array, idx);
auto b = GatherIndex(DF4(), array + 1, idx);
- return a * FastPowf(DF4(), b / a, frac);
+ return Mul(a, FastPowf(DF4(), Div(b, a), frac));
}
// Computes quant weights for a COLS*ROWS-sized transform, using num_bands
@@ -139,7 +144,8 @@ Status GetQuantWeights(
float dy = y * rcprow;
float dy2 = dy * dy;
for (uint32_t x = 0; x < COLS; x += Lanes(DF4())) {
- auto dx = (Set(DF4(), x) + Load(DF4(), l0123)) * Set(DF4(), rcpcol);
+ auto dx =
+ Mul(Add(Set(DF4(), x), Load(DF4(), l0123)), Set(DF4(), rcpcol));
auto scaled_distance = Sqrt(MulAdd(dx, dx, Set(DF4(), dy2)));
auto weight = num_bands == 1 ? Set(DF4(), bands[0])
: InterpolateVec(scaled_distance, bands);
@@ -318,11 +324,11 @@ Status ComputeQuantTable(const QuantEncoding& encoding,
HWY_CAPPED(float, 64) d;
for (size_t i = 0; i < num * 3; i += Lanes(d)) {
auto inv_val = LoadU(d, weights.data() + i);
- if (JXL_UNLIKELY(!AllFalse(d, inv_val >= Set(d, 1.0f / kAlmostZero)) ||
- !AllFalse(d, inv_val < Set(d, kAlmostZero)))) {
+ if (JXL_UNLIKELY(!AllFalse(d, Ge(inv_val, Set(d, 1.0f / kAlmostZero))) ||
+ !AllFalse(d, Lt(inv_val, Set(d, kAlmostZero))))) {
return JXL_FAILURE("Invalid quantization table");
}
- auto val = Set(d, 1.0f) / inv_val;
+ auto val = Div(Set(d, 1.0f), inv_val);
StoreU(val, d, table + *pos + i);
StoreU(inv_val, d, inv_table + *pos + i);
}
@@ -450,9 +456,9 @@ Status Decode(BitReader* br, QuantEncoding* encoding, size_t required_size_x,
for (size_t i = 0; i < 6; i++) {
encoding->afv_weights[c][i] *= 64;
}
- JXL_RETURN_IF_ERROR(DecodeDctParams(br, &encoding->dct_params));
- JXL_RETURN_IF_ERROR(DecodeDctParams(br, &encoding->dct_params_afv_4x4));
}
+ JXL_RETURN_IF_ERROR(DecodeDctParams(br, &encoding->dct_params));
+ JXL_RETURN_IF_ERROR(DecodeDctParams(br, &encoding->dct_params_afv_4x4));
break;
}
case QuantEncoding::kQuantModeDCT: {
diff --git a/media/libjxl/src/lib/jxl/quantizer-inl.h b/media/libjxl/src/lib/jxl/quantizer-inl.h
index 3de8758eac..64d273c552 100644
--- a/media/libjxl/src/lib/jxl/quantizer-inl.h
+++ b/media/libjxl/src/lib/jxl/quantizer-inl.h
@@ -19,8 +19,16 @@ namespace HWY_NAMESPACE {
namespace {
// These templates are not found via ADL.
+using hwy::HWY_NAMESPACE::And;
+using hwy::HWY_NAMESPACE::AndNot;
+using hwy::HWY_NAMESPACE::ApproximateReciprocal;
+using hwy::HWY_NAMESPACE::Gt;
+using hwy::HWY_NAMESPACE::IfThenElse;
+using hwy::HWY_NAMESPACE::IfThenElseZero;
+using hwy::HWY_NAMESPACE::Lt;
using hwy::HWY_NAMESPACE::Rebind;
using hwy::HWY_NAMESPACE::Vec;
+using hwy::HWY_NAMESPACE::Xor;
template <class DI>
HWY_INLINE HWY_MAYBE_UNUSED Vec<Rebind<float, DI>> AdjustQuantBias(
@@ -44,8 +52,8 @@ HWY_INLINE HWY_MAYBE_UNUSED Vec<Rebind<float, DI>> AdjustQuantBias(
// Integer comparison is not helpful because Clang incurs bypass penalties
// from unnecessarily mixing integer and float.
- const auto is_01 = abs_quant < Set(df, 1.125f);
- const auto not_0 = abs_quant > Zero(df);
+ const auto is_01 = Lt(abs_quant, Set(df, 1.125f));
+ const auto not_0 = Gt(abs_quant, Zero(df));
// Bitwise logic is faster than quant * biases[c].
const auto one_bias = IfThenElseZero(not_0, Xor(Set(df, biases[c]), sign));
diff --git a/media/libjxl/src/lib/jxl/quantizer.cc b/media/libjxl/src/lib/jxl/quantizer.cc
index c8c8837477..814aea276b 100644
--- a/media/libjxl/src/lib/jxl/quantizer.cc
+++ b/media/libjxl/src/lib/jxl/quantizer.cc
@@ -19,9 +19,9 @@
namespace jxl {
-static const int kDefaultQuant = 64;
+static const int32_t kDefaultQuant = 64;
-constexpr int Quantizer::kQuantMax;
+constexpr int32_t Quantizer::kQuantMax;
Quantizer::Quantizer(const DequantMatrices* dequant)
: Quantizer(dequant, kDefaultQuant, kGlobalScaleDenom / kDefaultQuant) {}
@@ -39,7 +39,7 @@ Quantizer::Quantizer(const DequantMatrices* dequant, int quant_dc,
void Quantizer::ComputeGlobalScaleAndQuant(float quant_dc, float quant_median,
float quant_median_absd) {
// Target value for the median value in the quant field.
- const float kQuantFieldTarget = 3.80987740592518214386f;
+ const float kQuantFieldTarget = 5;
// We reduce the median of the quant field by the median absolute deviation:
// higher resolution on highly varying quant fields.
float scale = kGlobalScaleDenom * (quant_median - quant_median_absd) /
@@ -49,9 +49,9 @@ void Quantizer::ComputeGlobalScaleAndQuant(float quant_dc, float quant_median,
if (scale > (1 << 15)) scale = 1 << 15;
int new_global_scale = static_cast<int>(scale);
// Ensure that quant_dc_ will always be at least
- // kGlobalScaleDenom/kGlobalScaleNumerator.
+ // 0.625 * kGlobalScaleDenom/kGlobalScaleNumerator = 10.
const int scaled_quant_dc =
- static_cast<int>(quant_dc * kGlobalScaleNumerator);
+ static_cast<int>(quant_dc * kGlobalScaleNumerator * 1.6);
if (new_global_scale > scaled_quant_dc) {
new_global_scale = scaled_quant_dc;
if (new_global_scale <= 0) new_global_scale = 1;
@@ -70,7 +70,7 @@ void Quantizer::ComputeGlobalScaleAndQuant(float quant_dc, float quant_median,
}
void Quantizer::SetQuantFieldRect(const ImageF& qf, const Rect& rect,
- ImageI* JXL_RESTRICT raw_quant_field) {
+ ImageI* JXL_RESTRICT raw_quant_field) const {
for (size_t y = 0; y < rect.ysize(); ++y) {
const float* JXL_RESTRICT row_qf = rect.ConstRow(qf, y);
int32_t* JXL_RESTRICT row_qi = rect.Row(raw_quant_field, y);
@@ -83,7 +83,6 @@ void Quantizer::SetQuantFieldRect(const ImageF& qf, const Rect& rect,
void Quantizer::SetQuantField(const float quant_dc, const ImageF& qf,
ImageI* JXL_RESTRICT raw_quant_field) {
- JXL_CHECK(SameSize(*raw_quant_field, qf));
std::vector<float> data(qf.xsize() * qf.ysize());
for (size_t y = 0; y < qf.ysize(); ++y) {
const float* JXL_RESTRICT row_qf = qf.Row(y);
@@ -103,13 +102,16 @@ void Quantizer::SetQuantField(const float quant_dc, const ImageF& qf,
deviations.end());
const float quant_median_absd = deviations[deviations.size() / 2];
ComputeGlobalScaleAndQuant(quant_dc, quant_median, quant_median_absd);
- SetQuantFieldRect(qf, Rect(qf), raw_quant_field);
+ if (raw_quant_field) {
+ JXL_CHECK(SameSize(*raw_quant_field, qf));
+ SetQuantFieldRect(qf, Rect(qf), raw_quant_field);
+ }
}
void Quantizer::SetQuant(float quant_dc, float quant_ac,
ImageI* JXL_RESTRICT raw_quant_field) {
ComputeGlobalScaleAndQuant(quant_dc, quant_ac, 0);
- int val = ClampVal(quant_ac * inv_global_scale_ + 0.5f);
+ int32_t val = ClampVal(quant_ac * inv_global_scale_ + 0.5f);
FillImage(val, raw_quant_field);
}
diff --git a/media/libjxl/src/lib/jxl/quantizer.h b/media/libjxl/src/lib/jxl/quantizer.h
index 1ff593e5c1..09e2e5e45c 100644
--- a/media/libjxl/src/lib/jxl/quantizer.h
+++ b/media/libjxl/src/lib/jxl/quantizer.h
@@ -68,10 +68,19 @@ class Quantizer {
explicit Quantizer(const DequantMatrices* dequant);
Quantizer(const DequantMatrices* dequant, int quant_dc, int global_scale);
- static constexpr int kQuantMax = 256;
+ static constexpr int32_t kQuantMax = 256;
- static JXL_INLINE int ClampVal(float val) {
- return static_cast<int>(std::max(1.0f, std::min<float>(val, kQuantMax)));
+ static JXL_INLINE int32_t ClampVal(float val) {
+ return static_cast<int32_t>(
+ std::max(1.0f, std::min<float>(val, kQuantMax)));
+ }
+
+ float ScaleGlobalScale(const float scale) {
+ int new_global_scale = static_cast<int>(global_scale_ * scale + 0.5f);
+ float scale_out = new_global_scale * 1.0f / global_scale_;
+ global_scale_ = new_global_scale;
+ RecomputeFromGlobalScale();
+ return scale_out;
}
// Recomputes other derived fields after global_scale_ has changed.
@@ -93,7 +102,7 @@ class Quantizer {
JXL_INLINE float InvGlobalScale() const { return inv_global_scale_; }
void SetQuantFieldRect(const ImageF& qf, const Rect& rect,
- ImageI* JXL_RESTRICT raw_quant_field);
+ ImageI* JXL_RESTRICT raw_quant_field) const;
void SetQuantField(float quant_dc, const ImageF& qf,
ImageI* JXL_RESTRICT raw_quant_field);
diff --git a/media/libjxl/src/lib/jxl/quantizer_test.cc b/media/libjxl/src/lib/jxl/quantizer_test.cc
index 60c9901828..d570bf6d40 100644
--- a/media/libjxl/src/lib/jxl/quantizer_test.cc
+++ b/media/libjxl/src/lib/jxl/quantizer_test.cc
@@ -42,7 +42,7 @@ TEST(QuantizerTest, BitStreamRoundtripSameQuant) {
EXPECT_TRUE(quantizer1.Encode(&writer, 0, nullptr));
writer.ZeroPadToByte();
const size_t bits_written = writer.BitsWritten();
- Quantizer quantizer2(&dequant, qxsize, qysize);
+ Quantizer quantizer2(&dequant);
BitReader reader(writer.GetSpan());
EXPECT_TRUE(quantizer2.Decode(&reader));
EXPECT_TRUE(reader.JumpToByteBoundary());
@@ -66,7 +66,7 @@ TEST(QuantizerTest, BitStreamRoundtripRandomQuant) {
EXPECT_TRUE(quantizer1.Encode(&writer, 0, nullptr));
writer.ZeroPadToByte();
const size_t bits_written = writer.BitsWritten();
- Quantizer quantizer2(&dequant, qxsize, qysize);
+ Quantizer quantizer2(&dequant);
BitReader reader(writer.GetSpan());
EXPECT_TRUE(quantizer2.Decode(&reader));
EXPECT_TRUE(reader.JumpToByteBoundary());
diff --git a/media/libjxl/src/lib/jxl/rational_polynomial-inl.h b/media/libjxl/src/lib/jxl/rational_polynomial-inl.h
index 87bddd1bb2..176e24092c 100644
--- a/media/libjxl/src/lib/jxl/rational_polynomial-inl.h
+++ b/media/libjxl/src/lib/jxl/rational_polynomial-inl.h
@@ -20,6 +20,10 @@ namespace jxl {
namespace HWY_NAMESPACE {
namespace {
+// These templates are not found via ADL.
+using hwy::HWY_NAMESPACE::Div;
+using hwy::HWY_NAMESPACE::MulAdd;
+
// Primary template: default to actual division.
template <typename T, class V>
struct FastDivision {
@@ -31,14 +35,14 @@ struct FastDivision<float, V> {
// One Newton-Raphson iteration.
static HWY_INLINE V ReciprocalNR(const V x) {
const auto rcp = ApproximateReciprocal(x);
- const auto sum = rcp + rcp;
- const auto x_rcp = x * rcp;
+ const auto sum = Add(rcp, rcp);
+ const auto x_rcp = Mul(x, rcp);
return NegMulAdd(x_rcp, rcp, sum);
}
V operator()(const V n, const V d) const {
#if 1 // Faster on SKX
- return n / d;
+ return Div(n, d);
#else
return n * ReciprocalNR(d);
#endif
diff --git a/media/libjxl/src/lib/jxl/rational_polynomial_test.cc b/media/libjxl/src/lib/jxl/rational_polynomial_test.cc
index e0d5a6ee21..13fc044a55 100644
--- a/media/libjxl/src/lib/jxl/rational_polynomial_test.cc
+++ b/media/libjxl/src/lib/jxl/rational_polynomial_test.cc
@@ -26,8 +26,11 @@ using T = float; // required by EvalLog2
using D = HWY_FULL(T);
// These templates are not found via ADL.
+using hwy::HWY_NAMESPACE::Add;
+using hwy::HWY_NAMESPACE::GetLane;
using hwy::HWY_NAMESPACE::ShiftLeft;
using hwy::HWY_NAMESPACE::ShiftRight;
+using hwy::HWY_NAMESPACE::Sub;
// Generic: only computes polynomial
struct EvalPoly {
@@ -50,17 +53,17 @@ struct EvalLog2 {
const HWY_FULL(int32_t) di;
const auto x_bits = BitCast(di, vx);
// Cannot handle negative numbers / NaN.
- JXL_DASSERT(AllTrue(di, Abs(x_bits) == x_bits));
+ JXL_DASSERT(AllTrue(di, Eq(Abs(x_bits), x_bits)));
// Range reduction to [-1/3, 1/3] - 3 integer, 2 float ops
- const auto exp_bits = x_bits - Set(di, 0x3f2aaaab); // = 2/3
+ const auto exp_bits = Sub(x_bits, Set(di, 0x3f2aaaab)); // = 2/3
// Shifted exponent = log2; also used to clear mantissa.
const auto exp_shifted = ShiftRight<23>(exp_bits);
- const auto mantissa = BitCast(d, x_bits - ShiftLeft<23>(exp_shifted));
+ const auto mantissa = BitCast(d, Sub(x_bits, ShiftLeft<23>(exp_shifted)));
const auto exp_val = ConvertTo(d, exp_shifted);
- vx = mantissa - Set(d, 1.0f);
+ vx = Sub(mantissa, Set(d, 1.0f));
- const auto approx = EvalRationalPolynomial(d, vx, p, q) + exp_val;
+ const auto approx = Add(EvalRationalPolynomial(d, vx, p, q), exp_val);
return GetLane(approx);
}
};
diff --git a/media/libjxl/src/lib/jxl/render_pipeline/low_memory_render_pipeline.cc b/media/libjxl/src/lib/jxl/render_pipeline/low_memory_render_pipeline.cc
index 89a636b109..91147303a6 100644
--- a/media/libjxl/src/lib/jxl/render_pipeline/low_memory_render_pipeline.cc
+++ b/media/libjxl/src/lib/jxl/render_pipeline/low_memory_render_pipeline.cc
@@ -397,17 +397,21 @@ void LowMemoryRenderPipeline::PrepareForThreadsInternal(size_t num,
}
}
if (first_image_dim_stage_ != stages_.size()) {
- out_of_frame_data_.resize(num);
- size_t left_padding = std::max<ssize_t>(0, frame_origin_.x0);
+ RectT<ssize_t> image_rect(0, 0, frame_dimensions_.xsize_upsampled,
+ frame_dimensions_.ysize_upsampled);
+ RectT<ssize_t> full_image_rect(0, 0, full_image_xsize_, full_image_ysize_);
+ image_rect = image_rect.Translate(frame_origin_.x0, frame_origin_.y0);
+ image_rect = image_rect.Intersection(full_image_rect);
+ if (image_rect.xsize() == 0 || image_rect.ysize() == 0) {
+ image_rect = RectT<ssize_t>(0, 0, 0, 0);
+ }
+ size_t left_padding = image_rect.x0();
size_t middle_padding = group_dim;
- ssize_t last_x =
- frame_origin_.x0 + std::min(frame_dimensions_.xsize_groups * group_dim,
- frame_dimensions_.xsize_upsampled);
- last_x = Clamp1<ssize_t>(last_x, 0, full_image_xsize_);
- size_t right_padding = full_image_xsize_ - last_x;
+ size_t right_padding = full_image_xsize_ - image_rect.x1();
size_t out_of_frame_xsize =
padding +
std::max(left_padding, std::max(middle_padding, right_padding));
+ out_of_frame_data_.resize(num);
for (size_t t = 0; t < num; t++) {
out_of_frame_data_[t] = ImageF(out_of_frame_xsize, shifts.size());
}
@@ -478,72 +482,131 @@ JXL_INLINE void ApplyXMirroring(float* row, ssize_t borderx, ssize_t group_x0,
}
}
-} // namespace
+// Information about where the *output* of each stage is stored.
+class Rows {
+ public:
+ Rows(const std::vector<std::unique_ptr<RenderPipelineStage>>& stages,
+ const Rect data_max_color_channel_rect, int group_data_x_border,
+ int group_data_y_border,
+ const std::vector<std::pair<size_t, size_t>>& group_data_shift,
+ size_t base_color_shift, std::vector<std::vector<ImageF>>& thread_data,
+ std::vector<ImageF>& input_data) {
+ size_t num_stages = stages.size();
+ size_t num_channels = input_data.size();
+
+ JXL_ASSERT(thread_data.size() == num_channels);
+ JXL_ASSERT(group_data_shift.size() == num_channels);
+
+#if JXL_ENABLE_ASSERT
+ for (const auto& td : thread_data) {
+ JXL_ASSERT(td.size() == num_stages);
+ }
+#endif
-void LowMemoryRenderPipeline::RenderRect(size_t thread_id,
- std::vector<ImageF>& input_data,
- Rect data_max_color_channel_rect,
- Rect image_max_color_channel_rect) {
- // For each stage, the rect corresponding to the image area currently being
- // processed.
- std::vector<Rect> group_rect;
- group_rect.resize(stages_.size());
- for (size_t i = 0; i < stages_.size(); i++) {
- size_t x0 = (image_max_color_channel_rect.x0() << base_color_shift_) >>
- channel_shifts_[i][anyc_[i]].first;
- size_t x1 = DivCeil(std::min(frame_dimensions_.xsize_upsampled,
- (image_max_color_channel_rect.x0() +
- image_max_color_channel_rect.xsize())
- << base_color_shift_),
- 1 << channel_shifts_[i][anyc_[i]].first);
- size_t y0 = (image_max_color_channel_rect.y0() << base_color_shift_) >>
- channel_shifts_[i][anyc_[i]].second;
- size_t y1 = DivCeil(std::min(frame_dimensions_.ysize_upsampled,
- (image_max_color_channel_rect.y0() +
- image_max_color_channel_rect.ysize())
- << base_color_shift_),
- 1 << channel_shifts_[i][anyc_[i]].second);
- group_rect[i] = Rect(x0, y0, x1 - x0, y1 - y0);
+ rows_.resize(num_stages + 1, std::vector<RowInfo>(num_channels));
+
+ for (size_t i = 0; i < num_stages; i++) {
+ for (size_t c = 0; c < input_data.size(); c++) {
+ if (stages[i]->GetChannelMode(c) == RenderPipelineChannelMode::kInOut) {
+ rows_[i + 1][c].ymod_minus_1 = thread_data[c][i].ysize() - 1;
+ rows_[i + 1][c].base_ptr = thread_data[c][i].Row(0);
+ rows_[i + 1][c].stride = thread_data[c][i].PixelsPerRow();
+ }
+ }
+ }
+
+ for (size_t c = 0; c < input_data.size(); c++) {
+ auto channel_group_data_rect =
+ data_max_color_channel_rect.As<ssize_t>()
+ .Translate(-group_data_x_border, -group_data_y_border)
+ .ShiftLeft(base_color_shift)
+ .CeilShiftRight(group_data_shift[c])
+ .Translate(group_data_x_border - ssize_t(kRenderPipelineXOffset),
+ group_data_y_border);
+ rows_[0][c].base_ptr = channel_group_data_rect.Row(&input_data[c], 0);
+ rows_[0][c].stride = input_data[c].PixelsPerRow();
+ rows_[0][c].ymod_minus_1 = -1;
+ }
+ }
+
+ // Stage -1 refers to the input data; all other values must be nonnegative and
+ // refer to the data for the output of that stage.
+ JXL_INLINE float* GetBuffer(int stage, int y, size_t c) const {
+ JXL_DASSERT(stage >= -1);
+ const RowInfo& info = rows_[stage + 1][c];
+ return info.base_ptr + ssize_t(info.stride) * (y & info.ymod_minus_1);
}
+ private:
struct RowInfo {
+ // Pointer to beginning of the first row.
float* base_ptr;
+ // Modulo value for the y axis minus 1 (ymod is guaranteed to be a power of
+ // 2, which allows efficient mod computation by masking).
int ymod_minus_1;
+ // Number of floats per row.
size_t stride;
};
+ std::vector<std::vector<RowInfo>> rows_;
+};
- std::vector<std::vector<RowInfo>> rows(
- stages_.size() + 1, std::vector<RowInfo>(input_data.size()));
+} // namespace
+void LowMemoryRenderPipeline::RenderRect(size_t thread_id,
+ std::vector<ImageF>& input_data,
+ Rect data_max_color_channel_rect,
+ Rect image_max_color_channel_rect) {
+ // For each stage, the rect corresponding to the image area currently being
+ // processed, in the coordinates of that stage (i.e. with the scaling factor
+ // that that stage has).
+ std::vector<Rect> group_rect;
+ group_rect.resize(stages_.size());
+ Rect image_area_rect =
+ image_max_color_channel_rect.ShiftLeft(base_color_shift_)
+ .Crop(frame_dimensions_.xsize_upsampled,
+ frame_dimensions_.ysize_upsampled);
for (size_t i = 0; i < stages_.size(); i++) {
- for (size_t c = 0; c < input_data.size(); c++) {
- if (stages_[i]->GetChannelMode(c) == RenderPipelineChannelMode::kInOut) {
- rows[i + 1][c].ymod_minus_1 = stage_data_[thread_id][c][i].ysize() - 1;
- rows[i + 1][c].base_ptr = stage_data_[thread_id][c][i].Row(0);
- rows[i + 1][c].stride = stage_data_[thread_id][c][i].PixelsPerRow();
- }
- }
- }
-
- for (size_t c = 0; c < input_data.size(); c++) {
- int xoff = int(data_max_color_channel_rect.x0()) -
- int(LowMemoryRenderPipeline::group_data_x_border_);
- xoff = xoff * (1 << base_color_shift_) >> channel_shifts_[0][c].first;
- xoff += LowMemoryRenderPipeline::group_data_x_border_;
- int yoff = int(data_max_color_channel_rect.y0()) -
- int(LowMemoryRenderPipeline::group_data_y_border_);
- yoff = yoff * (1 << base_color_shift_) >> channel_shifts_[0][c].second;
- yoff += LowMemoryRenderPipeline::group_data_y_border_;
- rows[0][c].base_ptr =
- input_data[c].Row(yoff) + xoff - kRenderPipelineXOffset;
- rows[0][c].stride = input_data[c].PixelsPerRow();
- rows[0][c].ymod_minus_1 = -1;
- }
-
- auto get_row_buffer = [&](int stage, int y, size_t c) {
- const RowInfo& info = rows[stage + 1][c];
- return info.base_ptr + ssize_t(info.stride) * (y & info.ymod_minus_1);
- };
+ group_rect[i] =
+ image_area_rect.CeilShiftRight(channel_shifts_[i][anyc_[i]]);
+ }
+
+ ssize_t frame_x0 =
+ first_image_dim_stage_ == stages_.size() ? 0 : frame_origin_.x0;
+ ssize_t frame_y0 =
+ first_image_dim_stage_ == stages_.size() ? 0 : frame_origin_.y0;
+ size_t full_image_xsize = first_image_dim_stage_ == stages_.size()
+ ? frame_dimensions_.xsize_upsampled
+ : full_image_xsize_;
+ size_t full_image_ysize = first_image_dim_stage_ == stages_.size()
+ ? frame_dimensions_.ysize_upsampled
+ : full_image_ysize_;
+
+ // Compute actual x-axis bounds for the current image area in the context of
+ // the full image this frame is part of. As the left boundary may be negative,
+ // we also create the x_pixels_skip value, defined as follows:
+ // - both x_pixels_skip and full_image_x0 are >= 0, and at least one is 0;
+ // - full_image_x0 - x_pixels_skip is the position of the current frame area
+ // in the full image.
+ ssize_t full_image_x0 = frame_x0 + image_area_rect.x0();
+ ssize_t x_pixels_skip = 0;
+ if (full_image_x0 < 0) {
+ x_pixels_skip = -full_image_x0;
+ full_image_x0 = 0;
+ }
+ ssize_t full_image_x1 = frame_x0 + image_area_rect.x1();
+ full_image_x1 = std::min<ssize_t>(full_image_x1, full_image_xsize);
+
+ // If the current image area is entirely outside of the visible image, there
+ // is no point in proceeding. Note: this uses the assumption that if there is
+ // a stage with observable effects (i.e. a kInput stage), it only appears
+ // after the stage that switches to image dimensions.
+ if (full_image_x1 <= full_image_x0) return;
+
+ // Data structures to hold information about input/output rows and their
+ // buffers.
+ Rows rows(stages_, data_max_color_channel_rect, group_data_x_border_,
+ group_data_y_border_, channel_shifts_[0], base_color_shift_,
+ stage_data_[thread_id], input_data);
std::vector<RenderPipelineStage::RowInfo> input_rows(first_trailing_stage_ +
1);
@@ -557,6 +620,51 @@ void LowMemoryRenderPipeline::RenderRect(size_t thread_id,
RenderPipelineStage::RowInfo output_rows(input_data.size(),
std::vector<float*>(8));
+ // Fills in input_rows and output_rows for a given y value (relative to the
+ // start of the group, measured in actual pixels at the appropriate vertical
+ // scaling factor) and a given stage, applying mirroring if necessary. This
+ // function is somewhat inefficient for trailing kInOut or kInput stages,
+ // where just filling the input row once ought to be sufficient.
+ auto prepare_io_rows = [&](int y, size_t i) {
+ ssize_t bordery = stages_[i]->settings_.border_y;
+ size_t shifty = stages_[i]->settings_.shift_y;
+ auto make_row = [&](size_t c, ssize_t iy) {
+ size_t mirrored_y = GetMirroredY(y + iy - bordery, group_rect[i].y0(),
+ image_rect_[i].ysize());
+ input_rows[i][c][iy] =
+ rows.GetBuffer(stage_input_for_channel_[i][c], mirrored_y, c);
+ ApplyXMirroring(input_rows[i][c][iy], stages_[i]->settings_.border_x,
+ group_rect[i].x0(), group_rect[i].xsize(),
+ image_rect_[i].xsize());
+ };
+ for (size_t c = 0; c < input_data.size(); c++) {
+ RenderPipelineChannelMode mode = stages_[i]->GetChannelMode(c);
+ if (mode == RenderPipelineChannelMode::kIgnored) {
+ continue;
+ }
+ // If we already have rows from a previous iteration, we can just shift
+ // the rows by 1 and insert the new one.
+ if (input_rows[i][c].size() == 2 * size_t(bordery) + 1) {
+ for (ssize_t iy = 0; iy < 2 * bordery; iy++) {
+ input_rows[i][c][iy] = input_rows[i][c][iy + 1];
+ }
+ make_row(c, bordery * 2);
+ } else {
+ input_rows[i][c].resize(2 * bordery + 1);
+ for (ssize_t iy = 0; iy < 2 * bordery + 1; iy++) {
+ make_row(c, iy);
+ }
+ }
+
+ // If necessary, get the output buffers.
+ if (mode == RenderPipelineChannelMode::kInOut) {
+ for (size_t iy = 0; iy < (1u << shifty); iy++) {
+ output_rows[c][iy] = rows.GetBuffer(i, y * (1 << shifty) + iy, c);
+ }
+ }
+ }
+ };
+
// We pretend that every stage has a vertical shift of 0, i.e. it is as tall
// as the final image.
// We call each such row a "virtual" row, because it may or may not correspond
@@ -567,10 +675,9 @@ void LowMemoryRenderPipeline::RenderRect(size_t thread_id,
virtual_ypadding_for_output_.end());
for (int vy = -num_extra_rows;
- vy < int(group_rect.back().ysize()) + num_extra_rows; vy++) {
+ vy < int(image_area_rect.ysize()) + num_extra_rows; vy++) {
for (size_t i = 0; i < first_trailing_stage_; i++) {
- int virtual_y_offset = num_extra_rows - virtual_ypadding_for_output_[i];
- int stage_vy = vy - virtual_y_offset;
+ int stage_vy = vy - num_extra_rows + virtual_ypadding_for_output_[i];
if (stage_vy % (1 << channel_shifts_[i][anyc_[i]].second) != 0) {
continue;
@@ -580,8 +687,6 @@ void LowMemoryRenderPipeline::RenderRect(size_t thread_id,
continue;
}
- ssize_t bordery = stages_[i]->settings_.border_y;
- size_t shifty = stages_[i]->settings_.shift_y;
int y = stage_vy >> channel_shifts_[i][anyc_[i]].second;
ssize_t image_y = ssize_t(group_rect[i].y0()) + y;
@@ -590,96 +695,54 @@ void LowMemoryRenderPipeline::RenderRect(size_t thread_id,
continue;
}
- // Get the actual input rows and potentially apply mirroring.
- for (size_t c = 0; c < input_data.size(); c++) {
- RenderPipelineChannelMode mode = stages_[i]->GetChannelMode(c);
- if (mode == RenderPipelineChannelMode::kIgnored) {
- continue;
- }
- auto make_row = [&](ssize_t iy) {
- size_t mirrored_y = GetMirroredY(y + iy - bordery, group_rect[i].y0(),
- image_rect_[i].ysize());
- input_rows[i][c][iy] =
- get_row_buffer(stage_input_for_channel_[i][c], mirrored_y, c);
- ApplyXMirroring(input_rows[i][c][iy], stages_[i]->settings_.border_x,
- group_rect[i].x0(), group_rect[i].xsize(),
- image_rect_[i].xsize());
- };
- // If we already have rows from a previous iteration, we can just shift
- // the rows by 1 and insert the new one.
- if (input_rows[i][c].size() == 2 * size_t(bordery) + 1) {
- for (ssize_t iy = 0; iy < 2 * bordery; iy++) {
- input_rows[i][c][iy] = input_rows[i][c][iy + 1];
- }
- make_row(bordery * 2);
- } else {
- input_rows[i][c].resize(2 * bordery + 1);
- for (ssize_t iy = 0; iy < 2 * bordery + 1; iy++) {
- make_row(iy);
- }
- }
+ // Get the input/output rows and potentially apply mirroring to the input.
+ prepare_io_rows(y, i);
- // If necessary, get the output buffers.
- if (mode == RenderPipelineChannelMode::kInOut) {
- for (size_t iy = 0; iy < (1u << shifty); iy++) {
- output_rows[c][iy] = get_row_buffer(i, y * (1 << shifty) + iy, c);
- }
- }
- }
// Produce output rows.
stages_[i]->ProcessRow(input_rows[i], output_rows,
xpadding_for_output_[i], group_rect[i].xsize(),
- group_rect[i].x0(), group_rect[i].y0() + y,
- thread_id);
+ group_rect[i].x0(), image_y, thread_id);
}
// Process trailing stages, i.e. the final set of non-kInOut stages; they
// all have the same input buffer and no need to use any mirroring.
int y = vy - num_extra_rows;
- if (y < 0 || y >= ssize_t(frame_dimensions_.ysize_upsampled)) continue;
for (size_t c = 0; c < input_data.size(); c++) {
- input_rows[first_trailing_stage_][c][0] = get_row_buffer(
- stage_input_for_channel_[first_trailing_stage_][c], y, c);
- }
-
- for (size_t i = first_trailing_stage_; i < first_image_dim_stage_; i++) {
- stages_[i]->ProcessRow(input_rows[first_trailing_stage_], output_rows,
- /*xextra=*/0, group_rect[i].xsize(),
- group_rect[i].x0(), group_rect[i].y0() + y,
- thread_id);
+ // Skip pixels that are not part of the actual final image area.
+ input_rows[first_trailing_stage_][c][0] =
+ rows.GetBuffer(stage_input_for_channel_[first_trailing_stage_][c], y,
+ c) +
+ x_pixels_skip;
}
- if (first_image_dim_stage_ == stages_.size()) continue;
-
- ssize_t full_image_y =
- y + frame_origin_.y0 + group_rect[first_image_dim_stage_].y0();
- if (full_image_y < 0 || full_image_y >= ssize_t(full_image_ysize_)) {
+ // Check that we are not outside of the bounds for the current rendering
+ // rect. Not doing so might result in overwriting some rows that have been
+ // written (or will be written) by other threads.
+ if (y < 0 || y >= ssize_t(image_area_rect.ysize())) {
continue;
}
- ssize_t full_image_x0 =
- frame_origin_.x0 + group_rect[first_image_dim_stage_].x0();
-
- if (full_image_x0 < 0) {
- // Skip pixels.
- for (size_t c = 0; c < input_data.size(); c++) {
- input_rows[first_trailing_stage_][c][0] -= full_image_x0;
- }
- full_image_x0 = 0;
+ // Avoid running pipeline stages on pixels that are outside the full image
+ // area. As trailing stages have no borders, this is a free optimization
+ // (and may be necessary for correctness, as some stages assume coordinates
+ // are within bounds).
+ ssize_t full_image_y = frame_y0 + image_area_rect.y0() + y;
+ if (full_image_y < 0 || full_image_y >= ssize_t(full_image_ysize)) {
+ continue;
}
- ssize_t full_image_x1 = frame_origin_.x0 +
- group_rect[first_image_dim_stage_].x0() +
- group_rect[first_image_dim_stage_].xsize();
- full_image_x1 = std::min<ssize_t>(full_image_x1, full_image_xsize_);
- if (full_image_x1 <= full_image_x0) continue;
-
- for (size_t i = first_image_dim_stage_; i < stages_.size(); i++) {
+ for (size_t i = first_trailing_stage_; i < stages_.size(); i++) {
+ // Before the first_image_dim_stage_, coordinates are relative to the
+ // current frame.
+ size_t x0 =
+ i < first_image_dim_stage_ ? full_image_x0 - frame_x0 : full_image_x0;
+ size_t y =
+ i < first_image_dim_stage_ ? full_image_y - frame_y0 : full_image_y;
stages_[i]->ProcessRow(input_rows[first_trailing_stage_], output_rows,
- /*xextra=*/0, full_image_x1 - full_image_x0,
- full_image_x0, full_image_y, thread_id);
+ /*xextra=*/0, full_image_x1 - full_image_x0, x0, y,
+ thread_id);
}
}
}
@@ -727,15 +790,27 @@ void LowMemoryRenderPipeline::ProcessBuffers(size_t group_id,
RectT<ssize_t> full_image_rect(0, 0, full_image_xsize_, full_image_ysize_);
group_rect = group_rect.Translate(frame_origin_.x0, frame_origin_.y0);
image_rect = image_rect.Translate(frame_origin_.x0, frame_origin_.y0);
- group_rect =
- group_rect.Intersection(image_rect).Intersection(full_image_rect);
+ image_rect = image_rect.Intersection(full_image_rect);
+ group_rect = group_rect.Intersection(image_rect);
size_t x0 = group_rect.x0();
size_t y0 = group_rect.y0();
size_t x1 = group_rect.x1();
size_t y1 = group_rect.y1();
+ JXL_DEBUG_V(6,
+ "Rendering padding for full image rect %s "
+ "outside group rect %s",
+ Description(full_image_rect).c_str(),
+ Description(group_rect).c_str());
+
+ if (group_id == 0 && (image_rect.xsize() == 0 || image_rect.ysize() == 0)) {
+ // If this frame does not intersect with the full image, we have to
+ // initialize the whole image area with RenderPadding.
+ RenderPadding(thread_id,
+ Rect(0, 0, full_image_xsize_, full_image_ysize_));
+ }
- // Do not render padding if group is empty; if group is empty x0, y0 might
- // have arbitrary values (from frame_origin).
+ // Render padding for groups that intersect with the full image. The case
+ // where no groups intersect was handled above.
if (group_rect.xsize() > 0 && group_rect.ysize() > 0) {
if (gx == 0 && gy == 0) {
RenderPadding(thread_id, Rect(0, 0, x0, y0));
diff --git a/media/libjxl/src/lib/jxl/render_pipeline/render_pipeline.cc b/media/libjxl/src/lib/jxl/render_pipeline/render_pipeline.cc
index 0b50bbb311..68b6ef613f 100644
--- a/media/libjxl/src/lib/jxl/render_pipeline/render_pipeline.cc
+++ b/media/libjxl/src/lib/jxl/render_pipeline/render_pipeline.cc
@@ -108,9 +108,9 @@ void RenderPipeline::InputReady(
const std::vector<std::pair<ImageF*, Rect>>& buffers) {
JXL_DASSERT(group_id < group_completed_passes_.size());
group_completed_passes_[group_id]++;
- for (const auto& buf : buffers) {
- (void)buf;
- JXL_CHECK_IMAGE_INITIALIZED(*buf.first, buf.second);
+ for (size_t i = 0; i < buffers.size(); ++i) {
+ (void)i;
+ JXL_CHECK_PLANE_INITIALIZED(*buffers[i].first, buffers[i].second, i);
}
ProcessBuffers(group_id, thread_id);
diff --git a/media/libjxl/src/lib/jxl/render_pipeline/render_pipeline_stage.h b/media/libjxl/src/lib/jxl/render_pipeline/render_pipeline_stage.h
index dd99ba9f33..d1a0074161 100644
--- a/media/libjxl/src/lib/jxl/render_pipeline/render_pipeline_stage.h
+++ b/media/libjxl/src/lib/jxl/render_pipeline/render_pipeline_stage.h
@@ -31,7 +31,10 @@ enum class RenderPipelineChannelMode {
kInPlace = 1,
// This channel is modified and written to a new buffer.
kInOut = 2,
- // This channel is only read.
+ // This channel is only read. These are the only stages that are assumed to
+ // have observable effects, i.e. calls to ProcessRow for other stages may be
+ // omitted if it can be shown they can't affect any kInput stage ProcessRow
+ // call that happens inside image boundaries.
kInput = 3,
};
@@ -100,6 +103,10 @@ class RenderPipelineStage {
size_t xextra, size_t xsize, size_t xpos, size_t ypos,
size_t thread_id) const = 0;
+ // How each channel will be processed. Channels are numbered starting from
+ // color channels (always 3) and followed by all other channels.
+ virtual RenderPipelineChannelMode GetChannelMode(size_t c) const = 0;
+
protected:
explicit RenderPipelineStage(Settings settings) : settings_(settings) {}
@@ -112,10 +119,6 @@ class RenderPipelineStage {
virtual Status PrepareForThreads(size_t num_threads) { return true; }
- // How each channel will be processed. Channels are numbered starting from
- // color channels (always 3) and followed by all other channels.
- virtual RenderPipelineChannelMode GetChannelMode(size_t c) const = 0;
-
// Returns a pointer to the input row of channel `c` with offset `y`.
// `y` must be in [-settings_.border_y, settings_.border_y]. `c` must be such
// that `GetChannelMode(c) != kIgnored`. The returned pointer points to the
@@ -140,6 +143,8 @@ class RenderPipelineStage {
// Indicates whether, from this stage on, the pipeline will operate on an
// image- rather than frame-sized buffer. Only one stage in the pipeline
// should return true, and it should implement ProcessPaddingRow below too.
+ // It is assumed that, if there is a SwitchToImageDimensions() == true stage,
+ // all kInput stages appear after it.
virtual bool SwitchToImageDimensions() const { return false; }
// If SwitchToImageDimensions returns true, then this should set xsize and
diff --git a/media/libjxl/src/lib/jxl/render_pipeline/render_pipeline_test.cc b/media/libjxl/src/lib/jxl/render_pipeline/render_pipeline_test.cc
index aee171e049..3cece172e2 100644
--- a/media/libjxl/src/lib/jxl/render_pipeline/render_pipeline_test.cc
+++ b/media/libjxl/src/lib/jxl/render_pipeline/render_pipeline_test.cc
@@ -14,8 +14,11 @@
#include "gtest/gtest.h"
#include "lib/extras/codec.h"
+#include "lib/jxl/base/printf_macros.h"
+#include "lib/jxl/dec_frame.h"
#include "lib/jxl/enc_params.h"
#include "lib/jxl/fake_parallel_runner_testonly.h"
+#include "lib/jxl/icc_codec.h"
#include "lib/jxl/image_test_utils.h"
#include "lib/jxl/jpeg/enc_jpeg_data.h"
#include "lib/jxl/render_pipeline/test_render_pipeline_stages.h"
@@ -25,6 +28,61 @@
namespace jxl {
namespace {
+Status DecodeFile(const Span<const uint8_t> file, bool use_slow_pipeline,
+ CodecInOut* io, ThreadPool* pool) {
+ Status ret = true;
+ {
+ BitReader reader(file);
+ BitReaderScopedCloser reader_closer(&reader, &ret);
+ JXL_RETURN_IF_ERROR(reader.ReadFixedBits<16>() == 0x0AFF);
+ JXL_RETURN_IF_ERROR(ReadSizeHeader(&reader, &io->metadata.size));
+ JXL_RETURN_IF_ERROR(ReadImageMetadata(&reader, &io->metadata.m));
+ io->metadata.transform_data.nonserialized_xyb_encoded =
+ io->metadata.m.xyb_encoded;
+ JXL_RETURN_IF_ERROR(Bundle::Read(&reader, &io->metadata.transform_data));
+ size_t xsize = io->metadata.xsize();
+ size_t ysize = io->metadata.ysize();
+ JXL_RETURN_IF_ERROR(VerifyDimensions(&io->constraints, xsize, ysize));
+ if (io->metadata.m.color_encoding.WantICC()) {
+ PaddedBytes icc;
+ JXL_RETURN_IF_ERROR(ReadICC(&reader, &icc));
+ JXL_RETURN_IF_ERROR(io->metadata.m.color_encoding.SetICC(std::move(icc)));
+ }
+ PassesDecoderState dec_state;
+ JXL_RETURN_IF_ERROR(
+ dec_state.output_encoding_info.SetFromMetadata(io->metadata));
+ JXL_RETURN_IF_ERROR(reader.JumpToByteBoundary());
+ io->frames.clear();
+ do {
+ io->frames.emplace_back(&io->metadata.m);
+ // Skip frames that are not displayed.
+ do {
+ size_t frame_start = reader.TotalBitsConsumed() / kBitsPerByte;
+ size_t size_left = file.size() - frame_start;
+ JXL_RETURN_IF_ERROR(
+ DecodeFrame(&dec_state, pool, file.data() + frame_start, size_left,
+ &io->frames.back(), io->metadata, use_slow_pipeline));
+ reader.SkipBits(io->frames.back().decoded_bytes() * kBitsPerByte);
+ } while (dec_state.shared->frame_header.frame_type !=
+ FrameType::kRegularFrame &&
+ dec_state.shared->frame_header.frame_type !=
+ FrameType::kSkipProgressive);
+ } while (!dec_state.shared->frame_header.is_last);
+
+ if (io->frames.empty()) return JXL_FAILURE("Not enough data.");
+
+ if (reader.TotalBitsConsumed() != file.size() * kBitsPerByte) {
+ return JXL_FAILURE("Reader position not at EOF.");
+ }
+ if (!reader.AllReadsWithinBounds()) {
+ return JXL_FAILURE("Reader out of bounds read.");
+ }
+ io->CheckMetadata();
+ // reader is closed here.
+ }
+ return ret;
+}
+
TEST(RenderPipelineTest, Build) {
RenderPipeline::Builder builder(/*num_c=*/1);
builder.AddStage(jxl::make_unique<UpsampleXSlowStage>());
@@ -163,15 +221,13 @@ TEST_P(RenderPipelineTestParam, PipelineTest) {
ASSERT_TRUE(EncodeFile(config.cparams, &io, &enc_state, &compressed,
GetJxlCms(), /*aux_out=*/nullptr, &pool));
- DecompressParams dparams;
-
- dparams.render_spotcolors = true;
CodecInOut io_default;
- ASSERT_TRUE(DecodeFile(dparams, compressed, &io_default, &pool));
+ ASSERT_TRUE(DecodeFile(Span<const uint8_t>(compressed),
+ /*use_slow_pipeline=*/false, &io_default, &pool));
CodecInOut io_slow_pipeline;
- dparams.use_slow_render_pipeline = true;
- ASSERT_TRUE(DecodeFile(dparams, compressed, &io_slow_pipeline, &pool));
+ ASSERT_TRUE(DecodeFile(Span<const uint8_t>(compressed),
+ /*use_slow_pipeline=*/true, &io_slow_pipeline, &pool));
ASSERT_EQ(io_default.frames.size(), io_slow_pipeline.frames.size());
for (size_t i = 0; i < io_default.frames.size(); i++) {
@@ -222,7 +278,7 @@ std::vector<RenderPipelineTestInputSettings> GeneratePipelineTests() {
for (auto size : sizes) {
RenderPipelineTestInputSettings settings;
- settings.input_path = "third_party/imagecompression.info/flower_foveon.png";
+ settings.input_path = "jxl/flower/flower.png";
settings.xsize = size.first;
settings.ysize = size.second;
@@ -354,16 +410,14 @@ std::vector<RenderPipelineTestInputSettings> GeneratePipelineTests() {
{
auto s = settings;
- s.input_path =
- "third_party/imagecompression.info/flower_foveon_alpha.png";
+ s.input_path = "jxl/flower/flower_alpha.png";
s.cparams_descr = "AlphaVarDCT";
all_tests.push_back(s);
}
{
auto s = settings;
- s.input_path =
- "third_party/imagecompression.info/flower_foveon_alpha.png";
+ s.input_path = "jxl/flower/flower_alpha.png";
s.cparams_descr = "AlphaVarDCTUpsamplingEPF";
s.cparams.epf = 1;
s.cparams.ec_resampling = 2;
@@ -374,16 +428,14 @@ std::vector<RenderPipelineTestInputSettings> GeneratePipelineTests() {
auto s = settings;
s.cparams.modular_mode = true;
s.cparams.butteraugli_distance = 0;
- s.input_path =
- "third_party/imagecompression.info/flower_foveon_alpha.png";
+ s.input_path = "jxl/flower/flower_alpha.png";
s.cparams_descr = "AlphaLossless";
all_tests.push_back(s);
}
{
auto s = settings;
- s.input_path =
- "third_party/imagecompression.info/flower_foveon_alpha.png";
+ s.input_path = "jxl/flower/flower_alpha.png";
s.cparams_descr = "AlphaDownsample";
s.cparams.ec_resampling = 2;
all_tests.push_back(s);
@@ -398,11 +450,10 @@ std::vector<RenderPipelineTestInputSettings> GeneratePipelineTests() {
}
#if JPEGXL_ENABLE_TRANSCODE_JPEG
- for (const char* input :
- {"third_party/imagecompression.info/flower_foveon.png.im_q85_444.jpg",
- "third_party/imagecompression.info/flower_foveon.png.im_q85_420.jpg",
- "third_party/imagecompression.info/flower_foveon.png.im_q85_422.jpg",
- "third_party/imagecompression.info/flower_foveon.png.im_q85_440.jpg"}) {
+ for (const char* input : {"jxl/flower/flower.png.im_q85_444.jpg",
+ "jxl/flower/flower.png.im_q85_420.jpg",
+ "jxl/flower/flower.png.im_q85_422.jpg",
+ "jxl/flower/flower.png.im_q85_440.jpg"}) {
RenderPipelineTestInputSettings settings;
settings.input_path = input;
settings.jpeg_transcode = true;
@@ -423,6 +474,26 @@ std::vector<RenderPipelineTestInputSettings> GeneratePipelineTests() {
all_tests.push_back(settings);
}
+ {
+ RenderPipelineTestInputSettings settings;
+ settings.input_path = "jxl/grayscale_patches.png";
+ settings.xsize = 1011;
+ settings.ysize = 277;
+ settings.cparams.photon_noise_iso = 1000;
+ settings.cparams_descr = "PatchesAndNoise";
+ all_tests.push_back(settings);
+ }
+
+ {
+ RenderPipelineTestInputSettings settings;
+ settings.input_path = "jxl/grayscale_patches.png";
+ settings.xsize = 1011;
+ settings.ysize = 277;
+ settings.cparams.resampling = 2;
+ settings.cparams_descr = "PatchesAndUps2";
+ all_tests.push_back(settings);
+ }
+
return all_tests;
}
@@ -461,12 +532,12 @@ TEST(RenderPipelineDecodingTest, Animation) {
PaddedBytes compressed =
ReadTestData("jxl/blending/cropped_traffic_light.jxl");
- DecompressParams dparams;
CodecInOut io_default;
- ASSERT_TRUE(DecodeFile(dparams, compressed, &io_default, &pool));
+ ASSERT_TRUE(DecodeFile(Span<const uint8_t>(compressed),
+ /*use_slow_pipeline=*/false, &io_default, &pool));
CodecInOut io_slow_pipeline;
- dparams.use_slow_render_pipeline = true;
- ASSERT_TRUE(DecodeFile(dparams, compressed, &io_slow_pipeline, &pool));
+ ASSERT_TRUE(DecodeFile(Span<const uint8_t>(compressed),
+ /*use_slow_pipeline=*/true, &io_slow_pipeline, &pool));
ASSERT_EQ(io_default.frames.size(), io_slow_pipeline.frames.size());
for (size_t i = 0; i < io_default.frames.size(); i++) {
diff --git a/media/libjxl/src/lib/jxl/render_pipeline/simple_render_pipeline.cc b/media/libjxl/src/lib/jxl/render_pipeline/simple_render_pipeline.cc
index 9e4e90fa2f..6e6bcb764d 100644
--- a/media/libjxl/src/lib/jxl/render_pipeline/simple_render_pipeline.cc
+++ b/media/libjxl/src/lib/jxl/render_pipeline/simple_render_pipeline.cc
@@ -63,7 +63,7 @@ void SimpleRenderPipeline::ProcessBuffers(size_t group_id, size_t thread_id) {
for (size_t c = 0; c < channel_data_.size(); c++) {
Rect r = MakeChannelRect(group_id, c);
(void)r;
- JXL_CHECK_IMAGE_INITIALIZED(channel_data_[c], r);
+ JXL_CHECK_PLANE_INITIALIZED(channel_data_[c], r, c);
}
if (PassesWithAllInput() <= processed_passes_) return;
@@ -202,9 +202,10 @@ void SimpleRenderPipeline::ProcessBuffers(size_t group_id, size_t thread_id) {
1 << channel_shifts_[next_stage][c].second);
channel_data_[c].ShrinkTo(xsize + 2 * kRenderPipelineXOffset,
ysize + 2 * kRenderPipelineXOffset);
- JXL_CHECK_IMAGE_INITIALIZED(
+ JXL_CHECK_PLANE_INITIALIZED(
channel_data_[c],
- Rect(kRenderPipelineXOffset, kRenderPipelineXOffset, xsize, ysize));
+ Rect(kRenderPipelineXOffset, kRenderPipelineXOffset, xsize, ysize),
+ c);
}
if (stage->SwitchToImageDimensions()) {
diff --git a/media/libjxl/src/lib/jxl/render_pipeline/stage_chroma_upsampling.cc b/media/libjxl/src/lib/jxl/render_pipeline/stage_chroma_upsampling.cc
index 35dfded46e..9b73ee91f1 100644
--- a/media/libjxl/src/lib/jxl/render_pipeline/stage_chroma_upsampling.cc
+++ b/media/libjxl/src/lib/jxl/render_pipeline/stage_chroma_upsampling.cc
@@ -16,6 +16,10 @@ HWY_BEFORE_NAMESPACE();
namespace jxl {
namespace HWY_NAMESPACE {
+// These templates are not found via ADL.
+using hwy::HWY_NAMESPACE::Mul;
+using hwy::HWY_NAMESPACE::MulAdd;
+
class HorizontalChromaUpsamplingStage : public RenderPipelineStage {
public:
explicit HorizontalChromaUpsamplingStage(size_t channel)
@@ -35,7 +39,7 @@ class HorizontalChromaUpsamplingStage : public RenderPipelineStage {
float* row_out = GetOutputRow(output_rows, c_, 0);
for (ssize_t x = -xextra; x < static_cast<ssize_t>(xsize + xextra);
x += Lanes(df)) {
- auto current = LoadU(df, row_in + x) * threefour;
+ auto current = Mul(LoadU(df, row_in + x), threefour);
auto prev = LoadU(df, row_in + x - 1);
auto next = LoadU(df, row_in + x + 1);
auto left = MulAdd(onefour, prev, current);
@@ -80,7 +84,7 @@ class VerticalChromaUpsamplingStage : public RenderPipelineStage {
auto it = LoadU(df, row_top + x);
auto im = LoadU(df, row_mid + x);
auto ib = LoadU(df, row_bot + x);
- auto im_scaled = im * threefour;
+ auto im_scaled = Mul(im, threefour);
Store(MulAdd(it, onefour, im_scaled), df, row_out0 + x);
Store(MulAdd(ib, onefour, im_scaled), df, row_out1 + x);
}
diff --git a/media/libjxl/src/lib/jxl/render_pipeline/stage_epf.cc b/media/libjxl/src/lib/jxl/render_pipeline/stage_epf.cc
index adb9c1fb9c..d59c497843 100644
--- a/media/libjxl/src/lib/jxl/render_pipeline/stage_epf.cc
+++ b/media/libjxl/src/lib/jxl/render_pipeline/stage_epf.cc
@@ -21,7 +21,14 @@ namespace HWY_NAMESPACE {
using DF = HWY_CAPPED(float, 8);
// These templates are not found via ADL.
+using hwy::HWY_NAMESPACE::AbsDiff;
+using hwy::HWY_NAMESPACE::Add;
+using hwy::HWY_NAMESPACE::Div;
+using hwy::HWY_NAMESPACE::Mul;
+using hwy::HWY_NAMESPACE::MulAdd;
using hwy::HWY_NAMESPACE::Vec;
+using hwy::HWY_NAMESPACE::VFromD;
+using hwy::HWY_NAMESPACE::ZeroIfNegative;
JXL_INLINE Vec<DF> Weight(Vec<DF> sad, Vec<DF> inv_sigma, Vec<DF> thres) {
auto v = MulAdd(sad, inv_sigma, Set(DF(), 1.0f));
@@ -52,7 +59,7 @@ class EPF0Stage : public RenderPipelineStage {
: LoadU(DF(), rows[2][3 + row] + x);
auto weight = Weight(sad, inv_sigma, Set(DF(), lf_.epf_pass1_zeroflush));
- *w += weight;
+ *w = Add(*w, weight);
*X = MulAdd(weight, cx, *X);
*Y = MulAdd(weight, cy, *Y);
*B = MulAdd(weight, cb, *B);
@@ -62,6 +69,11 @@ class EPF0Stage : public RenderPipelineStage {
size_t xextra, size_t xsize, size_t xpos, size_t ypos,
size_t thread_id) const final {
DF df;
+
+ using V = decltype(Zero(df));
+ V t0, t1, t2, t3, t4, t5, t6, t7, t8, t9, tA, tB;
+ V* sads[12] = {&t0, &t1, &t2, &t3, &t4, &t5, &t6, &t7, &t8, &t9, &tA, &tB};
+
xextra = RoundUpTo(xextra, Lanes(df));
const float* JXL_RESTRICT row_sigma =
sigma_->Row(ypos / kBlockDim + kSigmaPadding);
@@ -93,16 +105,15 @@ class EPF0Stage : public RenderPipelineStage {
if (row_sigma[bx] < kMinSigma) {
for (size_t c = 0; c < 3; c++) {
auto px = Load(df, rows[c][3 + 0] + x);
- Store(px, df, GetOutputRow(output_rows, c, 0) + x);
+ StoreU(px, df, GetOutputRow(output_rows, c, 0) + x);
}
continue;
}
const auto sm = Load(df, sad_mul + ix);
- const auto inv_sigma = Set(df, row_sigma[bx]) * sm;
+ const auto inv_sigma = Mul(Set(df, row_sigma[bx]), sm);
- decltype(Zero(df)) sads[12];
- for (size_t i = 0; i < 12; i++) sads[i] = Zero(df);
+ for (size_t i = 0; i < 12; i++) *sads[i] = Zero(df);
constexpr std::array<int, 2> sads_off[12] = {
{{-2, 0}}, {{-1, -1}}, {{-1, 0}}, {{-1, 1}}, {{0, -2}}, {{0, -1}},
{{0, 1}}, {{0, 2}}, {{1, -1}}, {{1, 0}}, {{1, 1}}, {{2, 0}},
@@ -122,9 +133,9 @@ class EPF0Stage : public RenderPipelineStage {
const auto c11 =
LoadU(df, rows[c][3 + sads_off[i][0] + plus_off[j][0]] + x +
sads_off[i][1] + plus_off[j][1]);
- sad += AbsDiff(r11, c11);
+ sad = Add(sad, AbsDiff(r11, c11));
}
- sads[i] = MulAdd(sad, scale, sads[i]);
+ *sads[i] = MulAdd(sad, scale, *sads[i]);
}
}
const auto x_cc = Load(df, rows[0][3 + 0] + x);
@@ -138,17 +149,17 @@ class EPF0Stage : public RenderPipelineStage {
for (size_t i = 0; i < 12; i++) {
AddPixel</*aligned=*/false>(/*row=*/sads_off[i][0], rows,
- x + sads_off[i][1], sads[i], inv_sigma, &X,
+ x + sads_off[i][1], *sads[i], inv_sigma, &X,
&Y, &B, &w);
}
#if JXL_HIGH_PRECISION
- auto inv_w = Set(df, 1.0f) / w;
+ auto inv_w = Div(Set(df, 1.0f), w);
#else
auto inv_w = ApproximateReciprocal(w);
#endif
- Store(X * inv_w, df, GetOutputRow(output_rows, 0, 0) + x);
- Store(Y * inv_w, df, GetOutputRow(output_rows, 1, 0) + x);
- Store(B * inv_w, df, GetOutputRow(output_rows, 2, 0) + x);
+ StoreU(Mul(X, inv_w), df, GetOutputRow(output_rows, 0, 0) + x);
+ StoreU(Mul(Y, inv_w), df, GetOutputRow(output_rows, 1, 0) + x);
+ StoreU(Mul(B, inv_w), df, GetOutputRow(output_rows, 2, 0) + x);
}
}
@@ -188,7 +199,7 @@ class EPF1Stage : public RenderPipelineStage {
: LoadU(DF(), rows[2][2 + row] + x);
auto weight = Weight(sad, inv_sigma, Set(DF(), lf_.epf_pass1_zeroflush));
- *w += weight;
+ *w = Add(*w, weight);
*X = MulAdd(weight, cx, *X);
*Y = MulAdd(weight, cy, *Y);
*B = MulAdd(weight, cb, *B);
@@ -236,7 +247,7 @@ class EPF1Stage : public RenderPipelineStage {
}
const auto sm = Load(df, sad_mul + ix);
- const auto inv_sigma = Set(df, row_sigma[bx]) * sm;
+ const auto inv_sigma = Mul(Set(df, row_sigma[bx]), sm);
auto sad0 = Zero(df);
auto sad1 = Zero(df);
auto sad2 = Zero(df);
@@ -259,41 +270,41 @@ class EPF1Stage : public RenderPipelineStage {
const auto p02 = LoadU(df, rows[c][2 + 0] + x - 2);
const auto p12 = LoadU(df, rows[c][2 + 0] + x - 1);
- sad1c += AbsDiff(p02, p12); // SAD 1, 2
- sad0c += AbsDiff(p11, p12); // SAD 2, 1
+ sad1c = Add(sad1c, AbsDiff(p02, p12)); // SAD 1, 2
+ sad0c = Add(sad0c, AbsDiff(p11, p12)); // SAD 2, 1
const auto p22 = LoadU(df, rows[c][2 + 0] + x);
t = AbsDiff(p12, p22);
- sad1c += t; // SAD 1, 2
- sad2c += t; // SAD 3, 2
+ sad1c = Add(sad1c, t); // SAD 1, 2
+ sad2c = Add(sad2c, t); // SAD 3, 2
t = AbsDiff(p22, p21);
auto sad3c = t; // SAD 2, 3
- sad0c += t; // SAD 2, 1
+ sad0c = Add(sad0c, t); // SAD 2, 1
const auto p32 = LoadU(df, rows[c][2 + 0] + x + 1);
- sad0c += AbsDiff(p31, p32); // SAD 2, 1
+ sad0c = Add(sad0c, AbsDiff(p31, p32)); // SAD 2, 1
t = AbsDiff(p22, p32);
- sad1c += t; // SAD 1, 2
- sad2c += t; // SAD 3, 2
+ sad1c = Add(sad1c, t); // SAD 1, 2
+ sad2c = Add(sad2c, t); // SAD 3, 2
const auto p42 = LoadU(df, rows[c][2 + 0] + x + 2);
- sad2c += AbsDiff(p42, p32); // SAD 3, 2
+ sad2c = Add(sad2c, AbsDiff(p42, p32)); // SAD 3, 2
const auto p13 = LoadU(df, rows[c][2 + 1] + x - 1);
- sad3c += AbsDiff(p13, p12); // SAD 2, 3
+ sad3c = Add(sad3c, AbsDiff(p13, p12)); // SAD 2, 3
const auto p23 = Load(df, rows[c][2 + 1] + x);
t = AbsDiff(p22, p23);
- sad0c += t; // SAD 2, 1
- sad3c += t; // SAD 2, 3
- sad1c += AbsDiff(p13, p23); // SAD 1, 2
+ sad0c = Add(sad0c, t); // SAD 2, 1
+ sad3c = Add(sad3c, t); // SAD 2, 3
+ sad1c = Add(sad1c, AbsDiff(p13, p23)); // SAD 1, 2
const auto p33 = LoadU(df, rows[c][2 + 1] + x + 1);
- sad2c += AbsDiff(p33, p23); // SAD 3, 2
- sad3c += AbsDiff(p33, p32); // SAD 2, 3
+ sad2c = Add(sad2c, AbsDiff(p33, p23)); // SAD 3, 2
+ sad3c = Add(sad3c, AbsDiff(p33, p32)); // SAD 2, 3
const auto p24 = Load(df, rows[c][2 + 2] + x);
- sad3c += AbsDiff(p24, p23); // SAD 2, 3
+ sad3c = Add(sad3c, AbsDiff(p24, p23)); // SAD 2, 3
auto scale = Set(df, lf_.epf_channel_scale[c]);
sad0 = MulAdd(sad0c, scale, sad0);
@@ -322,13 +333,13 @@ class EPF1Stage : public RenderPipelineStage {
AddPixel</*aligned=*/true>(/*row=*/1, rows, x, sad3, inv_sigma, &X, &Y,
&B, &w);
#if JXL_HIGH_PRECISION
- auto inv_w = Set(df, 1.0f) / w;
+ auto inv_w = Div(Set(df, 1.0f), w);
#else
auto inv_w = ApproximateReciprocal(w);
#endif
- Store(X * inv_w, df, GetOutputRow(output_rows, 0, 0) + x);
- Store(Y * inv_w, df, GetOutputRow(output_rows, 1, 0) + x);
- Store(B * inv_w, df, GetOutputRow(output_rows, 2, 0) + x);
+ Store(Mul(X, inv_w), df, GetOutputRow(output_rows, 0, 0) + x);
+ Store(Mul(Y, inv_w), df, GetOutputRow(output_rows, 1, 0) + x);
+ Store(Mul(B, inv_w), df, GetOutputRow(output_rows, 2, 0) + x);
}
}
@@ -367,13 +378,13 @@ class EPF2Stage : public RenderPipelineStage {
auto cb = aligned ? Load(DF(), rows[2][1 + row] + x)
: LoadU(DF(), rows[2][1 + row] + x);
- auto sad = AbsDiff(cx, rx) * Set(DF(), lf_.epf_channel_scale[0]);
+ auto sad = Mul(AbsDiff(cx, rx), Set(DF(), lf_.epf_channel_scale[0]));
sad = MulAdd(AbsDiff(cy, ry), Set(DF(), lf_.epf_channel_scale[1]), sad);
sad = MulAdd(AbsDiff(cb, rb), Set(DF(), lf_.epf_channel_scale[2]), sad);
auto weight = Weight(sad, inv_sigma, Set(DF(), lf_.epf_pass2_zeroflush));
- *w += weight;
+ *w = Add(*w, weight);
*X = MulAdd(weight, cx, *X);
*Y = MulAdd(weight, cy, *Y);
*B = MulAdd(weight, cb, *B);
@@ -421,7 +432,7 @@ class EPF2Stage : public RenderPipelineStage {
}
const auto sm = Load(df, sad_mul + ix);
- const auto inv_sigma = Set(df, row_sigma[bx]) * sm;
+ const auto inv_sigma = Mul(Set(df, row_sigma[bx]), sm);
const auto x_cc = Load(df, rows[0][1 + 0] + x);
const auto y_cc = Load(df, rows[1][1 + 0] + x);
@@ -444,13 +455,13 @@ class EPF2Stage : public RenderPipelineStage {
AddPixel</*aligned=*/true>(/*row=*/1, rows, x, x_cc, y_cc, b_cc,
inv_sigma, &X, &Y, &B, &w);
#if JXL_HIGH_PRECISION
- auto inv_w = Set(df, 1.0f) / w;
+ auto inv_w = Div(Set(df, 1.0f), w);
#else
auto inv_w = ApproximateReciprocal(w);
#endif
- Store(X * inv_w, df, GetOutputRow(output_rows, 0, 0) + x);
- Store(Y * inv_w, df, GetOutputRow(output_rows, 1, 0) + x);
- Store(B * inv_w, df, GetOutputRow(output_rows, 2, 0) + x);
+ Store(Mul(X, inv_w), df, GetOutputRow(output_rows, 0, 0) + x);
+ Store(Mul(Y, inv_w), df, GetOutputRow(output_rows, 1, 0) + x);
+ Store(Mul(B, inv_w), df, GetOutputRow(output_rows, 2, 0) + x);
}
}
@@ -507,7 +518,6 @@ std::unique_ptr<RenderPipelineStage> GetEPFStage(const LoopFilter& lf,
default:
JXL_ABORT("Invalid EPF stage");
}
- return nullptr;
}
} // namespace jxl
diff --git a/media/libjxl/src/lib/jxl/render_pipeline/stage_from_linear.cc b/media/libjxl/src/lib/jxl/render_pipeline/stage_from_linear.cc
new file mode 100644
index 0000000000..81f546c6bd
--- /dev/null
+++ b/media/libjxl/src/lib/jxl/render_pipeline/stage_from_linear.cc
@@ -0,0 +1,189 @@
+// Copyright (c) the JPEG XL Project Authors. All rights reserved.
+//
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+#include "lib/jxl/render_pipeline/stage_from_linear.h"
+
+#undef HWY_TARGET_INCLUDE
+#define HWY_TARGET_INCLUDE "lib/jxl/render_pipeline/stage_from_linear.cc"
+#include <hwy/foreach_target.h>
+#include <hwy/highway.h>
+
+#include "lib/jxl/dec_tone_mapping-inl.h"
+#include "lib/jxl/sanitizers.h"
+#include "lib/jxl/transfer_functions-inl.h"
+
+HWY_BEFORE_NAMESPACE();
+namespace jxl {
+namespace HWY_NAMESPACE {
+
+// These templates are not found via ADL.
+using hwy::HWY_NAMESPACE::IfThenZeroElse;
+
+template <typename Op>
+struct PerChannelOp {
+ explicit PerChannelOp(Op op) : op(op) {}
+ template <typename D, typename T>
+ void Transform(D d, T* r, T* g, T* b) const {
+ *r = op.Transform(d, *r);
+ *g = op.Transform(d, *g);
+ *b = op.Transform(d, *b);
+ }
+
+ Op op;
+};
+template <typename Op>
+PerChannelOp<Op> MakePerChannelOp(Op&& op) {
+ return PerChannelOp<Op>(std::forward<Op>(op));
+}
+
+struct OpLinear {
+ template <typename D, typename T>
+ T Transform(D d, const T& linear) const {
+ return linear;
+ }
+};
+
+struct OpRgb {
+ template <typename D, typename T>
+ T Transform(D d, const T& linear) const {
+#if JXL_HIGH_PRECISION
+ return TF_SRGB().EncodedFromDisplay(d, linear);
+#else
+ return FastLinearToSRGB(d, linear);
+#endif
+ }
+};
+
+struct OpPq {
+ template <typename D, typename T>
+ T Transform(D d, const T& linear) const {
+ return TF_PQ().EncodedFromDisplay(d, linear);
+ }
+};
+
+struct OpHlg {
+ explicit OpHlg(const float luminances[3], const float intensity_target)
+ : hlg_ootf_(HlgOOTF::ToSceneLight(/*display_luminance=*/intensity_target,
+ luminances)) {}
+
+ template <typename D, typename T>
+ void Transform(D d, T* r, T* g, T* b) const {
+ hlg_ootf_.Apply(r, g, b);
+ *r = TF_HLG().EncodedFromDisplay(d, *r);
+ *g = TF_HLG().EncodedFromDisplay(d, *g);
+ *b = TF_HLG().EncodedFromDisplay(d, *b);
+ }
+ HlgOOTF hlg_ootf_;
+};
+
+struct Op709 {
+ template <typename D, typename T>
+ T Transform(D d, const T& linear) const {
+ return TF_709().EncodedFromDisplay(d, linear);
+ }
+};
+
+struct OpGamma {
+ const float inverse_gamma;
+ template <typename D, typename T>
+ T Transform(D d, const T& linear) const {
+ return IfThenZeroElse(Le(linear, Set(d, 1e-5f)),
+ FastPowf(d, linear, Set(d, inverse_gamma)));
+ }
+};
+
+template <typename Op>
+class FromLinearStage : public RenderPipelineStage {
+ public:
+ explicit FromLinearStage(Op op)
+ : RenderPipelineStage(RenderPipelineStage::Settings()),
+ op_(std::move(op)) {}
+
+ void ProcessRow(const RowInfo& input_rows, const RowInfo& output_rows,
+ size_t xextra, size_t xsize, size_t xpos, size_t ypos,
+ size_t thread_id) const final {
+ PROFILER_ZONE("FromLinear");
+ const HWY_FULL(float) d;
+ const size_t xsize_v = RoundUpTo(xsize, Lanes(d));
+ float* JXL_RESTRICT row0 = GetInputRow(input_rows, 0, 0);
+ float* JXL_RESTRICT row1 = GetInputRow(input_rows, 1, 0);
+ float* JXL_RESTRICT row2 = GetInputRow(input_rows, 2, 0);
+ // All calculations are lane-wise, still some might require
+ // value-dependent behaviour (e.g. NearestInt). Temporary unpoison last
+ // vector tail.
+ msan::UnpoisonMemory(row0 + xsize, sizeof(float) * (xsize_v - xsize));
+ msan::UnpoisonMemory(row1 + xsize, sizeof(float) * (xsize_v - xsize));
+ msan::UnpoisonMemory(row2 + xsize, sizeof(float) * (xsize_v - xsize));
+ for (ssize_t x = -xextra; x < (ssize_t)(xsize + xextra); x += Lanes(d)) {
+ auto r = LoadU(d, row0 + x);
+ auto g = LoadU(d, row1 + x);
+ auto b = LoadU(d, row2 + x);
+ op_.Transform(d, &r, &g, &b);
+ StoreU(r, d, row0 + x);
+ StoreU(g, d, row1 + x);
+ StoreU(b, d, row2 + x);
+ }
+ msan::PoisonMemory(row0 + xsize, sizeof(float) * (xsize_v - xsize));
+ msan::PoisonMemory(row1 + xsize, sizeof(float) * (xsize_v - xsize));
+ msan::PoisonMemory(row2 + xsize, sizeof(float) * (xsize_v - xsize));
+ }
+
+ RenderPipelineChannelMode GetChannelMode(size_t c) const final {
+ return c < 3 ? RenderPipelineChannelMode::kInPlace
+ : RenderPipelineChannelMode::kIgnored;
+ }
+
+ const char* GetName() const override { return "FromLinear"; }
+
+ private:
+ Op op_;
+};
+
+template <typename Op>
+std::unique_ptr<FromLinearStage<Op>> MakeFromLinearStage(Op&& op) {
+ return jxl::make_unique<FromLinearStage<Op>>(std::forward<Op>(op));
+}
+
+std::unique_ptr<RenderPipelineStage> GetFromLinearStage(
+ const OutputEncodingInfo& output_encoding_info) {
+ if (output_encoding_info.color_encoding.tf.IsLinear()) {
+ return MakeFromLinearStage(MakePerChannelOp(OpLinear()));
+ } else if (output_encoding_info.color_encoding.tf.IsSRGB()) {
+ return MakeFromLinearStage(MakePerChannelOp(OpRgb()));
+ } else if (output_encoding_info.color_encoding.tf.IsPQ()) {
+ return MakeFromLinearStage(MakePerChannelOp(OpPq()));
+ } else if (output_encoding_info.color_encoding.tf.IsHLG()) {
+ return MakeFromLinearStage(
+ OpHlg(output_encoding_info.luminances,
+ output_encoding_info.desired_intensity_target));
+ } else if (output_encoding_info.color_encoding.tf.Is709()) {
+ return MakeFromLinearStage(MakePerChannelOp(Op709()));
+ } else if (output_encoding_info.color_encoding.tf.IsGamma() ||
+ output_encoding_info.color_encoding.tf.IsDCI()) {
+ return MakeFromLinearStage(
+ MakePerChannelOp(OpGamma{output_encoding_info.inverse_gamma}));
+ } else {
+ // This is a programming error.
+ JXL_ABORT("Invalid target encoding");
+ }
+}
+
+// NOLINTNEXTLINE(google-readability-namespace-comments)
+} // namespace HWY_NAMESPACE
+} // namespace jxl
+HWY_AFTER_NAMESPACE();
+
+#if HWY_ONCE
+namespace jxl {
+
+HWY_EXPORT(GetFromLinearStage);
+
+std::unique_ptr<RenderPipelineStage> GetFromLinearStage(
+ const OutputEncodingInfo& output_encoding_info) {
+ return HWY_DYNAMIC_DISPATCH(GetFromLinearStage)(output_encoding_info);
+}
+
+} // namespace jxl
+#endif
diff --git a/media/libjxl/src/lib/jxl/render_pipeline/stage_from_linear.h b/media/libjxl/src/lib/jxl/render_pipeline/stage_from_linear.h
new file mode 100644
index 0000000000..548ab50b8c
--- /dev/null
+++ b/media/libjxl/src/lib/jxl/render_pipeline/stage_from_linear.h
@@ -0,0 +1,20 @@
+// Copyright (c) the JPEG XL Project Authors. All rights reserved.
+//
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+#ifndef LIB_JXL_RENDER_PIPELINE_STAGE_FROM_LINEAR_H_
+#define LIB_JXL_RENDER_PIPELINE_STAGE_FROM_LINEAR_H_
+
+#include "lib/jxl/dec_xyb.h"
+#include "lib/jxl/render_pipeline/render_pipeline_stage.h"
+
+namespace jxl {
+
+// Converts the color channels from linear to the specified output encoding.
+std::unique_ptr<RenderPipelineStage> GetFromLinearStage(
+ const OutputEncodingInfo& output_encoding_info);
+
+} // namespace jxl
+
+#endif // LIB_JXL_RENDER_PIPELINE_STAGE_FROM_LINEAR_H_
diff --git a/media/libjxl/src/lib/jxl/render_pipeline/stage_gaborish.cc b/media/libjxl/src/lib/jxl/render_pipeline/stage_gaborish.cc
index 2450d08cfe..fc90acb476 100644
--- a/media/libjxl/src/lib/jxl/render_pipeline/stage_gaborish.cc
+++ b/media/libjxl/src/lib/jxl/render_pipeline/stage_gaborish.cc
@@ -14,6 +14,11 @@ HWY_BEFORE_NAMESPACE();
namespace jxl {
namespace HWY_NAMESPACE {
+// These templates are not found via ADL.
+using hwy::HWY_NAMESPACE::Add;
+using hwy::HWY_NAMESPACE::Mul;
+using hwy::HWY_NAMESPACE::MulAdd;
+
class GaborishStage : public RenderPipelineStage {
public:
explicit GaborishStage(const LoopFilter& lf)
@@ -74,9 +79,9 @@ class GaborishStage : public RenderPipelineStage {
const auto bl = LoadU(d, row_b + x - 1);
const auto br = LoadU(d, row_b + x + 1);
const auto sum0 = m;
- const auto sum1 = (l + r) + (t + b);
- const auto sum2 = (tl + tr) + (bl + br);
- auto pixels = MulAdd(sum2, w2, MulAdd(sum1, w1, sum0 * w0));
+ const auto sum1 = Add(Add(l, r), Add(t, b));
+ const auto sum2 = Add(Add(tl, tr), Add(bl, br));
+ auto pixels = MulAdd(sum2, w2, MulAdd(sum1, w1, Mul(sum0, w0)));
Store(pixels, d, row_out + x);
}
}
diff --git a/media/libjxl/src/lib/jxl/render_pipeline/stage_noise.cc b/media/libjxl/src/lib/jxl/render_pipeline/stage_noise.cc
index 1ed98bf27e..9f0cee316a 100644
--- a/media/libjxl/src/lib/jxl/render_pipeline/stage_noise.cc
+++ b/media/libjxl/src/lib/jxl/render_pipeline/stage_noise.cc
@@ -10,7 +10,6 @@
#include <hwy/foreach_target.h>
#include <hwy/highway.h>
-#include "lib/jxl/fast_math-inl.h"
#include "lib/jxl/sanitizers.h"
#include "lib/jxl/transfer_functions-inl.h"
@@ -19,11 +18,13 @@ namespace jxl {
namespace HWY_NAMESPACE {
// These templates are not found via ADL.
+using hwy::HWY_NAMESPACE::Max;
using hwy::HWY_NAMESPACE::ShiftRight;
using hwy::HWY_NAMESPACE::Vec;
+using hwy::HWY_NAMESPACE::ZeroIfNegative;
using D = HWY_CAPPED(float, kBlockDim);
-using DI = hwy::HWY_NAMESPACE::Rebind<int, D>;
+using DI = hwy::HWY_NAMESPACE::Rebind<int32_t, D>;
using DI8 = hwy::HWY_NAMESPACE::Repartition<uint8_t, D>;
// [0, max_value]
@@ -64,12 +65,12 @@ class StrengthEvalLut {
V operator()(const V vx) const {
constexpr size_t kScale = NoiseParams::kNumNoisePoints - 2;
- auto scaled_vx = Max(Zero(D()), vx * Set(D(), kScale));
+ auto scaled_vx = Max(Zero(D()), Mul(vx, Set(D(), kScale)));
auto floor_x = Floor(scaled_vx);
- auto frac_x = scaled_vx - floor_x;
- floor_x = IfThenElse(scaled_vx >= Set(D(), kScale), Set(D(), kScale - 1),
+ auto frac_x = Sub(scaled_vx, floor_x);
+ floor_x = IfThenElse(Ge(scaled_vx, Set(D(), kScale)), Set(D(), kScale - 1),
floor_x);
- frac_x = IfThenElse(scaled_vx >= Set(D(), kScale), Set(D(), 1), frac_x);
+ frac_x = IfThenElse(Ge(scaled_vx, Set(D(), kScale)), Set(D(), 1), frac_x);
auto floor_x_int = ConvertTo(DI(), floor_x);
#if HWY_TARGET == HWY_SCALAR
auto low = Set(D(), noise_params_.lut[floor_x_int.raw]);
@@ -77,10 +78,10 @@ class StrengthEvalLut {
#else
// Set each lane's bytes to {0, 0, 2x+1, 2x}.
auto floorx_indices_low =
- floor_x_int * Set(DI(), 0x0202) + Set(DI(), 0x0100);
+ Add(Mul(floor_x_int, Set(DI(), 0x0202)), Set(DI(), 0x0100));
// Set each lane's bytes to {2x+1, 2x, 0, 0}.
auto floorx_indices_hi =
- floor_x_int * Set(DI(), 0x02020000) + Set(DI(), 0x01000000);
+ Add(Mul(floor_x_int, Set(DI(), 0x02020000)), Set(DI(), 0x01000000));
// load LUT
auto low16 = BitCast(DI(), LoadDup128(DI8(), low16_lut));
auto lowm = Set(DI(), 0xFFFF);
@@ -88,16 +89,16 @@ class StrengthEvalLut {
auto him = Set(DI(), 0xFFFF0000);
// low = noise_params.lut[floor_x]
auto low =
- BitCast(D(), (TableLookupBytes(low16, floorx_indices_low) & lowm) |
- (TableLookupBytes(hi16, floorx_indices_hi) & him));
+ BitCast(D(), Or(And(TableLookupBytes(low16, floorx_indices_low), lowm),
+ And(TableLookupBytes(hi16, floorx_indices_hi), him)));
// hi = noise_params.lut[floor_x+1]
- floorx_indices_low += Set(DI(), 0x0202);
- floorx_indices_hi += Set(DI(), 0x02020000);
+ floorx_indices_low = Add(floorx_indices_low, Set(DI(), 0x0202));
+ floorx_indices_hi = Add(floorx_indices_hi, Set(DI(), 0x02020000));
auto hi =
- BitCast(D(), (TableLookupBytes(low16, floorx_indices_low) & lowm) |
- (TableLookupBytes(hi16, floorx_indices_hi) & him));
+ BitCast(D(), Or(And(TableLookupBytes(low16, floorx_indices_low), lowm),
+ And(TableLookupBytes(hi16, floorx_indices_hi), him)));
#endif
- return MulAdd(hi - low, frac_x, low);
+ return MulAdd(Sub(hi, low), frac_x, low);
}
private:
@@ -119,22 +120,25 @@ void AddNoiseToRGB(const D d, const Vec<D> rnd_noise_r,
const auto kRGCorr = Set(d, 0.9921875f); // 127/128
const auto kRGNCorr = Set(d, 0.0078125f); // 1/128
- const auto red_noise = kRGNCorr * rnd_noise_r * noise_strength_r +
- kRGCorr * rnd_noise_cor * noise_strength_r;
- const auto green_noise = kRGNCorr * rnd_noise_g * noise_strength_g +
- kRGCorr * rnd_noise_cor * noise_strength_g;
-
- auto vx = Load(d, out_x);
- auto vy = Load(d, out_y);
- auto vb = Load(d, out_b);
-
- vx += red_noise - green_noise + Set(d, ytox) * (red_noise + green_noise);
- vy += red_noise + green_noise;
- vb += Set(d, ytob) * (red_noise + green_noise);
-
- Store(vx, d, out_x);
- Store(vy, d, out_y);
- Store(vb, d, out_b);
+ const auto red_noise =
+ Mul(noise_strength_r,
+ MulAdd(kRGNCorr, rnd_noise_r, Mul(kRGCorr, rnd_noise_cor)));
+ const auto green_noise =
+ Mul(noise_strength_g,
+ MulAdd(kRGNCorr, rnd_noise_g, Mul(kRGCorr, rnd_noise_cor)));
+
+ auto vx = LoadU(d, out_x);
+ auto vy = LoadU(d, out_y);
+ auto vb = LoadU(d, out_b);
+
+ const auto rg_noise = Add(red_noise, green_noise);
+ vx = Add(MulAdd(Set(d, ytox), rg_noise, Sub(red_noise, green_noise)), vx);
+ vy = Add(vy, rg_noise);
+ vb = MulAdd(Set(d, ytob), rg_noise, vb);
+
+ StoreU(vx, d, out_x);
+ StoreU(vy, d, out_y);
+ StoreU(vb, d, out_b);
}
class AddNoiseStage : public RenderPipelineStage {
@@ -181,16 +185,17 @@ class AddNoiseStage : public RenderPipelineStage {
msan::UnpoisonMemory(row_x + xsize, (xsize_v - xsize) * sizeof(float));
msan::UnpoisonMemory(row_y + xsize, (xsize_v - xsize) * sizeof(float));
for (size_t x = 0; x < xsize_v; x += Lanes(d)) {
- const auto vx = Load(d, row_x + x);
- const auto vy = Load(d, row_y + x);
- const auto in_g = vy - vx;
- const auto in_r = vy + vx;
- const auto noise_strength_g = NoiseStrength(noise_model, in_g * half);
- const auto noise_strength_r = NoiseStrength(noise_model, in_r * half);
- const auto addit_rnd_noise_red = Load(d, row_rnd_r + x) * norm_const;
- const auto addit_rnd_noise_green = Load(d, row_rnd_g + x) * norm_const;
+ const auto vx = LoadU(d, row_x + x);
+ const auto vy = LoadU(d, row_y + x);
+ const auto in_g = Sub(vy, vx);
+ const auto in_r = Add(vy, vx);
+ const auto noise_strength_g = NoiseStrength(noise_model, Mul(in_g, half));
+ const auto noise_strength_r = NoiseStrength(noise_model, Mul(in_r, half));
+ const auto addit_rnd_noise_red = Mul(LoadU(d, row_rnd_r + x), norm_const);
+ const auto addit_rnd_noise_green =
+ Mul(LoadU(d, row_rnd_g + x), norm_const);
const auto addit_rnd_noise_correlated =
- Load(d, row_rnd_c + x) * norm_const;
+ Mul(LoadU(d, row_rnd_c + x), norm_const);
AddNoiseToRGB(D(), addit_rnd_noise_red, addit_rnd_noise_green,
addit_rnd_noise_correlated, noise_strength_g,
noise_strength_r, ytox, ytob, row_x + x, row_y + x,
@@ -242,21 +247,22 @@ class ConvolveNoiseStage : public RenderPipelineStage {
float* JXL_RESTRICT row_out = GetOutputRow(output_rows, c, 0);
for (ssize_t x = -RoundUpTo(xextra, Lanes(d));
x < (ssize_t)(xsize + xextra); x += Lanes(d)) {
- const auto p00 = Load(d, rows[2] + x);
+ const auto p00 = LoadU(d, rows[2] + x);
auto others = Zero(d);
+ // TODO(eustas): sum loaded values to reduce the calculation chain
for (ssize_t i = -2; i <= 2; i++) {
- others += LoadU(d, rows[0] + x + i);
- others += LoadU(d, rows[1] + x + i);
- others += LoadU(d, rows[3] + x + i);
- others += LoadU(d, rows[4] + x + i);
+ others = Add(others, LoadU(d, rows[0] + x + i));
+ others = Add(others, LoadU(d, rows[1] + x + i));
+ others = Add(others, LoadU(d, rows[3] + x + i));
+ others = Add(others, LoadU(d, rows[4] + x + i));
}
- others += LoadU(d, rows[2] + x - 2);
- others += LoadU(d, rows[2] + x - 1);
- others += LoadU(d, rows[2] + x + 1);
- others += LoadU(d, rows[2] + x + 2);
+ others = Add(others, LoadU(d, rows[2] + x - 2));
+ others = Add(others, LoadU(d, rows[2] + x - 1));
+ others = Add(others, LoadU(d, rows[2] + x + 1));
+ others = Add(others, LoadU(d, rows[2] + x + 2));
// 4 * (1 - box kernel)
- auto pixels = MulAdd(others, Set(d, 0.16), p00 * Set(d, -3.84));
- Store(pixels, d, row_out + x);
+ auto pixels = MulAdd(others, Set(d, 0.16), Mul(p00, Set(d, -3.84)));
+ StoreU(pixels, d, row_out + x);
}
}
}
diff --git a/media/libjxl/src/lib/jxl/render_pipeline/stage_patches.cc b/media/libjxl/src/lib/jxl/render_pipeline/stage_patches.cc
index 2c3c300617..527be03839 100644
--- a/media/libjxl/src/lib/jxl/render_pipeline/stage_patches.cc
+++ b/media/libjxl/src/lib/jxl/render_pipeline/stage_patches.cc
@@ -9,36 +9,40 @@ namespace jxl {
namespace {
class PatchDictionaryStage : public RenderPipelineStage {
public:
- explicit PatchDictionaryStage(const PatchDictionary* patches)
+ PatchDictionaryStage(const PatchDictionary* patches, size_t num_channels)
: RenderPipelineStage(RenderPipelineStage::Settings()),
- patches_(*patches) {}
+ patches_(*patches),
+ num_channels_(num_channels) {}
void ProcessRow(const RowInfo& input_rows, const RowInfo& output_rows,
size_t xextra, size_t xsize, size_t xpos, size_t ypos,
size_t thread_id) const final {
PROFILER_ZONE("RenderPatches");
- std::vector<float*> row_ptrs(input_rows.size());
- for (size_t i = 0; i < row_ptrs.size(); i++) {
- row_ptrs[i] = GetInputRow(input_rows, i, 0) - xextra;
+ JXL_ASSERT(xpos == 0 || xpos >= xextra);
+ size_t x0 = xpos ? xpos - xextra : 0;
+ std::vector<float*> row_ptrs(num_channels_);
+ for (size_t i = 0; i < num_channels_; i++) {
+ row_ptrs[i] = GetInputRow(input_rows, i, 0) + x0 - xpos;
}
- patches_.AddOneRow(row_ptrs.data(), ypos, xpos - xextra,
- xsize + 2 * xextra);
+ patches_.AddOneRow(row_ptrs.data(), ypos, x0, xsize + xextra + xpos - x0);
}
RenderPipelineChannelMode GetChannelMode(size_t c) const final {
- return RenderPipelineChannelMode::kInPlace;
+ return c < num_channels_ ? RenderPipelineChannelMode::kInPlace
+ : RenderPipelineChannelMode::kIgnored;
}
const char* GetName() const override { return "Patches"; }
private:
const PatchDictionary& patches_;
+ const size_t num_channels_;
};
} // namespace
std::unique_ptr<RenderPipelineStage> GetPatchesStage(
- const PatchDictionary* patches) {
- return jxl::make_unique<PatchDictionaryStage>(patches);
+ const PatchDictionary* patches, size_t num_channels) {
+ return jxl::make_unique<PatchDictionaryStage>(patches, num_channels);
}
} // namespace jxl
diff --git a/media/libjxl/src/lib/jxl/render_pipeline/stage_patches.h b/media/libjxl/src/lib/jxl/render_pipeline/stage_patches.h
index e4ac8d251c..b35abdc2eb 100644
--- a/media/libjxl/src/lib/jxl/render_pipeline/stage_patches.h
+++ b/media/libjxl/src/lib/jxl/render_pipeline/stage_patches.h
@@ -15,7 +15,7 @@ namespace jxl {
// Draws patches if applicable.
std::unique_ptr<RenderPipelineStage> GetPatchesStage(
- const PatchDictionary* patches);
+ const PatchDictionary* patches, size_t num_channels);
} // namespace jxl
diff --git a/media/libjxl/src/lib/jxl/render_pipeline/stage_to_linear.cc b/media/libjxl/src/lib/jxl/render_pipeline/stage_to_linear.cc
new file mode 100644
index 0000000000..bf79481e4e
--- /dev/null
+++ b/media/libjxl/src/lib/jxl/render_pipeline/stage_to_linear.cc
@@ -0,0 +1,200 @@
+// Copyright (c) the JPEG XL Project Authors. All rights reserved.
+//
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+#include "lib/jxl/render_pipeline/stage_to_linear.h"
+
+#undef HWY_TARGET_INCLUDE
+#define HWY_TARGET_INCLUDE "lib/jxl/render_pipeline/stage_to_linear.cc"
+#include <hwy/foreach_target.h>
+#include <hwy/highway.h>
+
+#include "lib/jxl/dec_tone_mapping-inl.h"
+#include "lib/jxl/sanitizers.h"
+#include "lib/jxl/transfer_functions-inl.h"
+
+HWY_BEFORE_NAMESPACE();
+namespace jxl {
+namespace HWY_NAMESPACE {
+
+// These templates are not found via ADL.
+using hwy::HWY_NAMESPACE::IfThenZeroElse;
+
+template <typename Op>
+struct PerChannelOp {
+ explicit PerChannelOp(Op op) : op(op) {}
+ template <typename D, typename T>
+ void Transform(D d, T* r, T* g, T* b) const {
+ *r = op.Transform(d, *r);
+ *g = op.Transform(d, *g);
+ *b = op.Transform(d, *b);
+ }
+
+ Op op;
+};
+template <typename Op>
+PerChannelOp<Op> MakePerChannelOp(Op&& op) {
+ return PerChannelOp<Op>(std::forward<Op>(op));
+}
+
+struct OpLinear {
+ template <typename D, typename T>
+ T Transform(D d, const T& encoded) const {
+ return encoded;
+ }
+};
+
+struct OpRgb {
+ template <typename D, typename T>
+ T Transform(D d, const T& encoded) const {
+ return TF_SRGB().DisplayFromEncoded(encoded);
+ }
+};
+
+struct OpPq {
+ template <typename D, typename T>
+ T Transform(D d, const T& encoded) const {
+ return TF_PQ().DisplayFromEncoded(d, encoded);
+ }
+};
+
+struct OpHlg {
+ explicit OpHlg(const float luminances[3], const float intensity_target)
+ : hlg_ootf_(HlgOOTF::FromSceneLight(
+ /*display_luminance=*/intensity_target, luminances)) {}
+
+ template <typename D, typename T>
+ void Transform(D d, T* r, T* g, T* b) const {
+ for (T* val : {r, g, b}) {
+ float vals[MaxLanes(d)];
+ Store(*val, d, vals);
+ for (size_t i = 0; i < Lanes(d); ++i) {
+ vals[i] = TF_HLG().DisplayFromEncoded(vals[i]);
+ }
+ *val = Load(d, vals);
+ }
+ hlg_ootf_.Apply(r, g, b);
+ }
+ HlgOOTF hlg_ootf_;
+};
+
+struct Op709 {
+ template <typename D, typename T>
+ T Transform(D d, const T& encoded) const {
+ return TF_709().DisplayFromEncoded(d, encoded);
+ }
+};
+
+struct OpGamma {
+ const float gamma;
+ template <typename D, typename T>
+ T Transform(D d, const T& encoded) const {
+ return IfThenZeroElse(Le(encoded, Set(d, 1e-5f)),
+ FastPowf(d, encoded, Set(d, gamma)));
+ }
+};
+
+struct OpInvalid {
+ template <typename D, typename T>
+ void Transform(D d, T* r, T* g, T* b) const {}
+};
+
+template <typename Op>
+class ToLinearStage : public RenderPipelineStage {
+ public:
+ explicit ToLinearStage(Op op)
+ : RenderPipelineStage(RenderPipelineStage::Settings()),
+ op_(std::move(op)) {}
+
+ explicit ToLinearStage()
+ : RenderPipelineStage(RenderPipelineStage::Settings()), valid_(false) {}
+
+ void ProcessRow(const RowInfo& input_rows, const RowInfo& output_rows,
+ size_t xextra, size_t xsize, size_t xpos, size_t ypos,
+ size_t thread_id) const final {
+ PROFILER_ZONE("ToLinear");
+
+ const HWY_FULL(float) d;
+ const size_t xsize_v = RoundUpTo(xsize, Lanes(d));
+ float* JXL_RESTRICT row0 = GetInputRow(input_rows, 0, 0);
+ float* JXL_RESTRICT row1 = GetInputRow(input_rows, 1, 0);
+ float* JXL_RESTRICT row2 = GetInputRow(input_rows, 2, 0);
+ // All calculations are lane-wise, still some might require
+ // value-dependent behaviour (e.g. NearestInt). Temporary unpoison last
+ // vector tail.
+ msan::UnpoisonMemory(row0 + xsize, sizeof(float) * (xsize_v - xsize));
+ msan::UnpoisonMemory(row1 + xsize, sizeof(float) * (xsize_v - xsize));
+ msan::UnpoisonMemory(row2 + xsize, sizeof(float) * (xsize_v - xsize));
+ for (ssize_t x = -xextra; x < (ssize_t)(xsize + xextra); x += Lanes(d)) {
+ auto r = LoadU(d, row0 + x);
+ auto g = LoadU(d, row1 + x);
+ auto b = LoadU(d, row2 + x);
+ op_.Transform(d, &r, &g, &b);
+ StoreU(r, d, row0 + x);
+ StoreU(g, d, row1 + x);
+ StoreU(b, d, row2 + x);
+ }
+ msan::PoisonMemory(row0 + xsize, sizeof(float) * (xsize_v - xsize));
+ msan::PoisonMemory(row1 + xsize, sizeof(float) * (xsize_v - xsize));
+ msan::PoisonMemory(row2 + xsize, sizeof(float) * (xsize_v - xsize));
+ }
+
+ RenderPipelineChannelMode GetChannelMode(size_t c) const final {
+ return c < 3 ? RenderPipelineChannelMode::kInPlace
+ : RenderPipelineChannelMode::kIgnored;
+ }
+
+ const char* GetName() const override { return "ToLinear"; }
+
+ private:
+ Status IsInitialized() const override { return valid_; }
+
+ Op op_;
+ bool valid_ = true;
+};
+
+template <typename Op>
+std::unique_ptr<ToLinearStage<Op>> MakeToLinearStage(Op&& op) {
+ return jxl::make_unique<ToLinearStage<Op>>(std::forward<Op>(op));
+}
+
+std::unique_ptr<RenderPipelineStage> GetToLinearStage(
+ const OutputEncodingInfo& output_encoding_info) {
+ if (output_encoding_info.color_encoding.tf.IsLinear()) {
+ return MakeToLinearStage(MakePerChannelOp(OpLinear()));
+ } else if (output_encoding_info.color_encoding.tf.IsSRGB()) {
+ return MakeToLinearStage(MakePerChannelOp(OpRgb()));
+ } else if (output_encoding_info.color_encoding.tf.IsPQ()) {
+ return MakeToLinearStage(MakePerChannelOp(OpPq()));
+ } else if (output_encoding_info.color_encoding.tf.IsHLG()) {
+ return MakeToLinearStage(OpHlg(output_encoding_info.luminances,
+ output_encoding_info.orig_intensity_target));
+ } else if (output_encoding_info.color_encoding.tf.Is709()) {
+ return MakeToLinearStage(MakePerChannelOp(Op709()));
+ } else if (output_encoding_info.color_encoding.tf.IsGamma() ||
+ output_encoding_info.color_encoding.tf.IsDCI()) {
+ return MakeToLinearStage(
+ MakePerChannelOp(OpGamma{1.f / output_encoding_info.inverse_gamma}));
+ } else {
+ return jxl::make_unique<ToLinearStage<OpInvalid>>();
+ }
+}
+
+// NOLINTNEXTLINE(google-readability-namespace-comments)
+} // namespace HWY_NAMESPACE
+} // namespace jxl
+HWY_AFTER_NAMESPACE();
+
+#if HWY_ONCE
+namespace jxl {
+
+HWY_EXPORT(GetToLinearStage);
+
+std::unique_ptr<RenderPipelineStage> GetToLinearStage(
+ const OutputEncodingInfo& output_encoding_info) {
+ return HWY_DYNAMIC_DISPATCH(GetToLinearStage)(output_encoding_info);
+}
+
+} // namespace jxl
+#endif
diff --git a/media/libjxl/src/lib/jxl/render_pipeline/stage_to_linear.h b/media/libjxl/src/lib/jxl/render_pipeline/stage_to_linear.h
new file mode 100644
index 0000000000..ccee7b09f0
--- /dev/null
+++ b/media/libjxl/src/lib/jxl/render_pipeline/stage_to_linear.h
@@ -0,0 +1,21 @@
+// Copyright (c) the JPEG XL Project Authors. All rights reserved.
+//
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+#ifndef LIB_JXL_RENDER_PIPELINE_STAGE_TO_LINEAR_H_
+#define LIB_JXL_RENDER_PIPELINE_STAGE_TO_LINEAR_H_
+
+#include "lib/jxl/dec_xyb.h"
+#include "lib/jxl/render_pipeline/render_pipeline_stage.h"
+
+namespace jxl {
+
+// Converts the color channels from `output_encoding_info.color_encoding` to
+// linear.
+std::unique_ptr<RenderPipelineStage> GetToLinearStage(
+ const OutputEncodingInfo& output_encoding_info);
+
+} // namespace jxl
+
+#endif // LIB_JXL_RENDER_PIPELINE_STAGE_TO_LINEAR_H_
diff --git a/media/libjxl/src/lib/jxl/render_pipeline/stage_tone_mapping.cc b/media/libjxl/src/lib/jxl/render_pipeline/stage_tone_mapping.cc
new file mode 100644
index 0000000000..7609534a5b
--- /dev/null
+++ b/media/libjxl/src/lib/jxl/render_pipeline/stage_tone_mapping.cc
@@ -0,0 +1,151 @@
+// Copyright (c) the JPEG XL Project Authors. All rights reserved.
+//
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+#include "lib/jxl/render_pipeline/stage_tone_mapping.h"
+
+#undef HWY_TARGET_INCLUDE
+#define HWY_TARGET_INCLUDE "lib/jxl/render_pipeline/stage_tone_mapping.cc"
+#include <hwy/foreach_target.h>
+#include <hwy/highway.h>
+
+#include "lib/jxl/dec_tone_mapping-inl.h"
+#include "lib/jxl/dec_xyb-inl.h"
+#include "lib/jxl/sanitizers.h"
+#include "lib/jxl/transfer_functions-inl.h"
+
+HWY_BEFORE_NAMESPACE();
+namespace jxl {
+namespace HWY_NAMESPACE {
+
+class ToneMappingStage : public RenderPipelineStage {
+ public:
+ explicit ToneMappingStage(OutputEncodingInfo output_encoding_info)
+ : RenderPipelineStage(RenderPipelineStage::Settings()),
+ output_encoding_info_(std::move(output_encoding_info)) {
+ if (output_encoding_info_.desired_intensity_target ==
+ output_encoding_info_.orig_intensity_target) {
+ // No tone mapping requested.
+ return;
+ }
+ if (output_encoding_info_.orig_color_encoding.tf.IsPQ() &&
+ output_encoding_info_.desired_intensity_target <
+ output_encoding_info_.orig_intensity_target) {
+ tone_mapper_ = jxl::make_unique<ToneMapper>(
+ /*source_range=*/std::pair<float, float>(
+ 0, output_encoding_info_.orig_intensity_target),
+ /*target_range=*/
+ std::pair<float, float>(
+ 0, output_encoding_info_.desired_intensity_target),
+ output_encoding_info_.luminances);
+ } else if (output_encoding_info_.orig_color_encoding.tf.IsHLG() &&
+ !output_encoding_info_.color_encoding.tf.IsHLG()) {
+ hlg_ootf_ = jxl::make_unique<HlgOOTF>(
+ /*source_luminance=*/output_encoding_info_.orig_intensity_target,
+ /*target_luminance=*/output_encoding_info_.desired_intensity_target,
+ output_encoding_info_.luminances);
+ }
+
+ if (output_encoding_info_.color_encoding.tf.IsPQ() &&
+ (tone_mapper_ || hlg_ootf_)) {
+ to_intensity_target_ =
+ 10000.f / output_encoding_info_.orig_intensity_target;
+ from_desired_intensity_target_ =
+ output_encoding_info_.desired_intensity_target / 10000.f;
+ }
+ }
+
+ bool IsNeeded() const { return tone_mapper_ || hlg_ootf_; }
+
+ void ProcessRow(const RowInfo& input_rows, const RowInfo& output_rows,
+ size_t xextra, size_t xsize, size_t xpos, size_t ypos,
+ size_t thread_id) const final {
+ PROFILER_ZONE("ToneMapping");
+
+ if (!(tone_mapper_ || hlg_ootf_)) return;
+
+ const HWY_FULL(float) d;
+ const size_t xsize_v = RoundUpTo(xsize, Lanes(d));
+ float* JXL_RESTRICT row0 = GetInputRow(input_rows, 0, 0);
+ float* JXL_RESTRICT row1 = GetInputRow(input_rows, 1, 0);
+ float* JXL_RESTRICT row2 = GetInputRow(input_rows, 2, 0);
+ // All calculations are lane-wise, still some might require
+ // value-dependent behaviour (e.g. NearestInt). Temporary unpoison last
+ // vector tail.
+ msan::UnpoisonMemory(row0 + xsize, sizeof(float) * (xsize_v - xsize));
+ msan::UnpoisonMemory(row1 + xsize, sizeof(float) * (xsize_v - xsize));
+ msan::UnpoisonMemory(row2 + xsize, sizeof(float) * (xsize_v - xsize));
+ for (ssize_t x = -xextra; x < (ssize_t)(xsize + xextra); x += Lanes(d)) {
+ auto r = LoadU(d, row0 + x);
+ auto g = LoadU(d, row1 + x);
+ auto b = LoadU(d, row2 + x);
+ if (tone_mapper_ || hlg_ootf_) {
+ r = Mul(r, Set(d, to_intensity_target_));
+ g = Mul(g, Set(d, to_intensity_target_));
+ b = Mul(b, Set(d, to_intensity_target_));
+ if (tone_mapper_) {
+ tone_mapper_->ToneMap(&r, &g, &b);
+ } else {
+ JXL_ASSERT(hlg_ootf_);
+ hlg_ootf_->Apply(&r, &g, &b);
+ }
+ if (tone_mapper_ || hlg_ootf_->WarrantsGamutMapping()) {
+ GamutMap(&r, &g, &b, output_encoding_info_.luminances);
+ }
+ r = Mul(r, Set(d, from_desired_intensity_target_));
+ g = Mul(g, Set(d, from_desired_intensity_target_));
+ b = Mul(b, Set(d, from_desired_intensity_target_));
+ }
+ StoreU(r, d, row0 + x);
+ StoreU(g, d, row1 + x);
+ StoreU(b, d, row2 + x);
+ }
+ msan::PoisonMemory(row0 + xsize, sizeof(float) * (xsize_v - xsize));
+ msan::PoisonMemory(row1 + xsize, sizeof(float) * (xsize_v - xsize));
+ msan::PoisonMemory(row2 + xsize, sizeof(float) * (xsize_v - xsize));
+ }
+
+ RenderPipelineChannelMode GetChannelMode(size_t c) const final {
+ return c < 3 ? RenderPipelineChannelMode::kInPlace
+ : RenderPipelineChannelMode::kIgnored;
+ }
+
+ const char* GetName() const override { return "ToneMapping"; }
+
+ private:
+ using ToneMapper = Rec2408ToneMapper<HWY_FULL(float)>;
+ OutputEncodingInfo output_encoding_info_;
+ std::unique_ptr<ToneMapper> tone_mapper_;
+ std::unique_ptr<HlgOOTF> hlg_ootf_;
+ // When the target colorspace is PQ, 1 represents 10000 nits instead of
+ // orig_intensity_target. This temporarily changes this if the tone mappers
+ // require it.
+ float to_intensity_target_ = 1.f;
+ float from_desired_intensity_target_ = 1.f;
+};
+
+std::unique_ptr<RenderPipelineStage> GetToneMappingStage(
+ const OutputEncodingInfo& output_encoding_info) {
+ auto stage = jxl::make_unique<ToneMappingStage>(output_encoding_info);
+ if (!stage->IsNeeded()) return nullptr;
+ return stage;
+}
+
+// NOLINTNEXTLINE(google-readability-namespace-comments)
+} // namespace HWY_NAMESPACE
+} // namespace jxl
+HWY_AFTER_NAMESPACE();
+
+#if HWY_ONCE
+namespace jxl {
+
+HWY_EXPORT(GetToneMappingStage);
+
+std::unique_ptr<RenderPipelineStage> GetToneMappingStage(
+ const OutputEncodingInfo& output_encoding_info) {
+ return HWY_DYNAMIC_DISPATCH(GetToneMappingStage)(output_encoding_info);
+}
+
+} // namespace jxl
+#endif
diff --git a/media/libjxl/src/lib/jxl/render_pipeline/stage_tone_mapping.h b/media/libjxl/src/lib/jxl/render_pipeline/stage_tone_mapping.h
new file mode 100644
index 0000000000..99824f8511
--- /dev/null
+++ b/media/libjxl/src/lib/jxl/render_pipeline/stage_tone_mapping.h
@@ -0,0 +1,37 @@
+// Copyright (c) the JPEG XL Project Authors. All rights reserved.
+//
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+#ifndef LIB_JXL_RENDER_PIPELINE_STAGE_TONE_MAPPING_H_
+#define LIB_JXL_RENDER_PIPELINE_STAGE_TONE_MAPPING_H_
+#include <math.h>
+#include <stdint.h>
+#include <stdio.h>
+
+#include <algorithm>
+#include <utility>
+#include <vector>
+
+#include "lib/jxl/dec_xyb.h"
+#include "lib/jxl/render_pipeline/render_pipeline_stage.h"
+
+namespace jxl {
+
+// Tone maps the image if appropriate. It must be in linear space and
+// `output_encoding_info.luminances` must contain the luminance for the
+// primaries of that space. It must also be encoded such that (1, 1, 1)
+// represents `output_encoding_info.orig_intensity_target` nits, unless
+// `output_encoding_info.color_encoding.tf.IsPQ()`, in which case (1, 1, 1) must
+// represent 10000 nits. This corresponds to what XYBStage outputs. After this
+// stage, (1, 1, 1) will represent
+// `output_encoding_info.desired_intensity_target` nits, except in the PQ
+// special case in which it remains 10000.
+//
+// If no tone mapping is necessary, this will return nullptr.
+std::unique_ptr<RenderPipelineStage> GetToneMappingStage(
+ const OutputEncodingInfo& output_encoding_info);
+
+} // namespace jxl
+
+#endif // LIB_JXL_RENDER_PIPELINE_STAGE_TONE_MAPPING_H_
diff --git a/media/libjxl/src/lib/jxl/render_pipeline/stage_upsampling.cc b/media/libjxl/src/lib/jxl/render_pipeline/stage_upsampling.cc
index 56de46caed..a75e259865 100644
--- a/media/libjxl/src/lib/jxl/render_pipeline/stage_upsampling.cc
+++ b/media/libjxl/src/lib/jxl/render_pipeline/stage_upsampling.cc
@@ -17,6 +17,12 @@ HWY_BEFORE_NAMESPACE();
namespace jxl {
namespace HWY_NAMESPACE {
+// These templates are not found via ADL.
+using hwy::HWY_NAMESPACE::Clamp;
+using hwy::HWY_NAMESPACE::Max;
+using hwy::HWY_NAMESPACE::Min;
+using hwy::HWY_NAMESPACE::MulAdd;
+
class UpsamplingStage : public RenderPipelineStage {
public:
explicit UpsamplingStage(const CustomTransformData& ups_factors, size_t c,
@@ -101,10 +107,27 @@ class UpsamplingStage : public RenderPipelineStage {
void ProcessRowImpl(const RowInfo& input_rows, const RowInfo& output_rows,
ssize_t x0, ssize_t x1) const {
static HWY_FULL(float) df;
+ using V = hwy::HWY_NAMESPACE::Vec<HWY_FULL(float)>;
+ V ups0, ups1, ups2, ups3, ups4, ups5, ups6, ups7;
+ (void)ups2, (void)ups3, (void)ups4, (void)ups5, (void)ups6, (void)ups7;
+ V* ups[N];
+ if (N >= 2) {
+ ups[0] = &ups0;
+ ups[1] = &ups1;
+ }
+ if (N >= 4) {
+ ups[2] = &ups2;
+ ups[3] = &ups3;
+ }
+ if (N == 8) {
+ ups[4] = &ups4;
+ ups[5] = &ups5;
+ ups[6] = &ups6;
+ ups[7] = &ups7;
+ }
for (size_t oy = 0; oy < N; oy++) {
float* dst_row = GetOutputRow(output_rows, c_, oy);
for (ssize_t x = x0; x < x1; x += Lanes(df)) {
- hwy::HWY_NAMESPACE::Vec<HWY_FULL(float)> ups[8];
for (size_t ox = 0; ox < N; ox++) {
auto result = Zero(df);
auto min = LoadU(df, GetInputRow(input_rows, c_, 0) + x);
@@ -118,17 +141,17 @@ class UpsamplingStage : public RenderPipelineStage {
}
}
// Avoid overshooting.
- ups[ox] = Clamp(result, min, max);
+ *ups[ox] = Clamp(result, min, max);
}
if (N == 2) {
- StoreInterleaved(df, ups[0], ups[1], dst_row + x * N);
+ StoreInterleaved(df, ups0, ups1, dst_row + x * N);
}
if (N == 4) {
- StoreInterleaved(df, ups[0], ups[1], ups[2], ups[3], dst_row + x * N);
+ StoreInterleaved(df, ups0, ups1, ups2, ups3, dst_row + x * N);
}
if (N == 8) {
- StoreInterleaved(df, ups[0], ups[1], ups[2], ups[3], ups[4], ups[5],
- ups[6], ups[7], dst_row + x * N);
+ StoreInterleaved(df, ups0, ups1, ups2, ups3, ups4, ups5, ups6, ups7,
+ dst_row + x * N);
}
}
}
diff --git a/media/libjxl/src/lib/jxl/render_pipeline/stage_write.cc b/media/libjxl/src/lib/jxl/render_pipeline/stage_write.cc
index fafa7c409d..71c4d97d55 100644
--- a/media/libjxl/src/lib/jxl/render_pipeline/stage_write.cc
+++ b/media/libjxl/src/lib/jxl/render_pipeline/stage_write.cc
@@ -5,6 +5,7 @@
#include "lib/jxl/render_pipeline/stage_write.h"
+#include "lib/jxl/alpha.h"
#include "lib/jxl/common.h"
#include "lib/jxl/dec_cache.h"
#include "lib/jxl/image_bundle.h"
@@ -19,6 +20,12 @@ HWY_BEFORE_NAMESPACE();
namespace jxl {
namespace HWY_NAMESPACE {
+// These templates are not found via ADL.
+using hwy::HWY_NAMESPACE::Clamp;
+using hwy::HWY_NAMESPACE::Mul;
+using hwy::HWY_NAMESPACE::NearestInt;
+using hwy::HWY_NAMESPACE::U8FromU32;
+
template <typename D, typename V>
void StoreRGBA(D d, V r, V g, V b, V a, bool alpha, size_t n, size_t extra,
uint8_t* buf) {
@@ -53,20 +60,20 @@ void StoreRGBA(D d, V r, V g, V b, V a, bool alpha, size_t n, size_t extra,
// TODO(veluca): implement this for x86.
size_t mul = alpha ? 4 : 3;
HWY_ALIGN uint8_t bytes[16];
- Store(r, d, bytes);
+ StoreU(r, d, bytes);
for (size_t i = 0; i < n; i++) {
buf[mul * i] = bytes[i];
}
- Store(g, d, bytes);
+ StoreU(g, d, bytes);
for (size_t i = 0; i < n; i++) {
buf[mul * i + 1] = bytes[i];
}
- Store(b, d, bytes);
+ StoreU(b, d, bytes);
for (size_t i = 0; i < n; i++) {
buf[mul * i + 2] = bytes[i];
}
if (alpha) {
- Store(a, d, bytes);
+ StoreU(a, d, bytes);
for (size_t i = 0; i < n; i++) {
buf[4 * i + 3] = bytes[i];
}
@@ -115,10 +122,10 @@ class WriteToU8Stage : public RenderPipelineStage {
}
for (ssize_t x = 0; x < x1; x += Lanes(d)) {
- auto rf = Clamp(zero, Load(d, row_in_r + x), one) * mul;
- auto gf = Clamp(zero, Load(d, row_in_g + x), one) * mul;
- auto bf = Clamp(zero, Load(d, row_in_b + x), one) * mul;
- auto af = row_in_a ? Clamp(zero, Load(d, row_in_a + x), one) * mul
+ auto rf = Mul(Clamp(zero, LoadU(d, row_in_r + x), one), mul);
+ auto gf = Mul(Clamp(zero, LoadU(d, row_in_g + x), one), mul);
+ auto bf = Mul(Clamp(zero, LoadU(d, row_in_b + x), one), mul);
+ auto af = row_in_a ? Mul(Clamp(zero, LoadU(d, row_in_a + x), one), mul)
: Set(d, 255.0f);
auto r8 = U8FromU32(BitCast(du, NearestInt(rf)));
auto g8 = U8FromU32(BitCast(du, NearestInt(gf)));
@@ -272,13 +279,14 @@ class WriteToPixelCallbackStage : public RenderPipelineStage {
public:
WriteToPixelCallbackStage(const PixelCallback& pixel_callback, size_t width,
size_t height, bool rgba, bool has_alpha,
- size_t alpha_c)
+ bool unpremul_alpha, size_t alpha_c)
: RenderPipelineStage(RenderPipelineStage::Settings()),
pixel_callback_(pixel_callback),
width_(width),
height_(height),
rgba_(rgba),
has_alpha_(has_alpha),
+ unpremul_alpha_(unpremul_alpha),
alpha_c_(alpha_c),
opaque_alpha_(kMaxPixelsPerCall, 1.0f) {}
@@ -309,6 +317,7 @@ class WriteToPixelCallbackStage : public RenderPipelineStage {
// No xextra offset; opaque_alpha_ is a way to set all values to 1.0f.
line_buffers[3] = opaque_alpha_.data();
}
+
// TODO(veluca): SIMD.
ssize_t limit = std::min(xextra + xsize, width_ - xpos);
for (ssize_t x0 = -xextra; x0 < limit; x0 += kMaxPixelsPerCall) {
@@ -324,6 +333,10 @@ class WriteToPixelCallbackStage : public RenderPipelineStage {
temp[j++] = line_buffers[3][ix];
}
}
+ if (has_alpha_ && rgba_ && unpremul_alpha_) {
+ // TODO(szabadka) SIMDify (possibly in a separate pipeline stage).
+ UnpremultiplyAlpha(temp, ix);
+ }
pixel_callback_.run(run_opaque_, thread_id, xpos + x0, ypos, ix, temp);
for (size_t c = 0; c < 3; c++) line_buffers[c] += kMaxPixelsPerCall;
if (has_alpha_) line_buffers[3] += kMaxPixelsPerCall;
@@ -357,6 +370,7 @@ class WriteToPixelCallbackStage : public RenderPipelineStage {
size_t height_;
bool rgba_;
bool has_alpha_;
+ bool unpremul_alpha_;
size_t alpha_c_;
std::vector<float> opaque_alpha_;
std::vector<CacheAlignedUniquePtr> temp_;
@@ -385,9 +399,9 @@ std::unique_ptr<RenderPipelineStage> GetWriteToU8Stage(uint8_t* rgb,
std::unique_ptr<RenderPipelineStage> GetWriteToPixelCallbackStage(
const PixelCallback& pixel_callback, size_t width, size_t height, bool rgba,
- bool has_alpha, size_t alpha_c) {
+ bool has_alpha, bool unpremul_alpha, size_t alpha_c) {
return jxl::make_unique<WriteToPixelCallbackStage>(
- pixel_callback, width, height, rgba, has_alpha, alpha_c);
+ pixel_callback, width, height, rgba, has_alpha, unpremul_alpha, alpha_c);
}
} // namespace jxl
diff --git a/media/libjxl/src/lib/jxl/render_pipeline/stage_write.h b/media/libjxl/src/lib/jxl/render_pipeline/stage_write.h
index b5e152e9c7..b942fd6645 100644
--- a/media/libjxl/src/lib/jxl/render_pipeline/stage_write.h
+++ b/media/libjxl/src/lib/jxl/render_pipeline/stage_write.h
@@ -30,7 +30,7 @@ std::unique_ptr<RenderPipelineStage> GetWriteToU8Stage(uint8_t* rgb,
// Gets a stage to write to a pixel callback.
std::unique_ptr<RenderPipelineStage> GetWriteToPixelCallbackStage(
const PixelCallback& pixel_callback, size_t width, size_t height, bool rgba,
- bool has_alpha, size_t alpha_c);
+ bool has_alpha, bool unpremul_alpha, size_t alpha_c);
} // namespace jxl
diff --git a/media/libjxl/src/lib/jxl/render_pipeline/stage_xyb.cc b/media/libjxl/src/lib/jxl/render_pipeline/stage_xyb.cc
index c943752109..0022a61127 100644
--- a/media/libjxl/src/lib/jxl/render_pipeline/stage_xyb.cc
+++ b/media/libjxl/src/lib/jxl/render_pipeline/stage_xyb.cc
@@ -11,110 +11,17 @@
#include <hwy/highway.h>
#include "lib/jxl/dec_xyb-inl.h"
-#include "lib/jxl/fast_math-inl.h"
#include "lib/jxl/sanitizers.h"
-#include "lib/jxl/transfer_functions-inl.h"
HWY_BEFORE_NAMESPACE();
namespace jxl {
namespace HWY_NAMESPACE {
-template <typename Op>
-struct PerChannelOp {
- explicit PerChannelOp(Op op) : op(op) {}
- template <typename D, typename T>
- void Transform(D d, T* r, T* g, T* b) const {
- *r = op.Transform(d, *r);
- *g = op.Transform(d, *g);
- *b = op.Transform(d, *b);
- }
-
- Op op;
-};
-template <typename Op>
-PerChannelOp<Op> MakePerChannelOp(Op&& op) {
- return PerChannelOp<Op>(std::forward<Op>(op));
-}
-
-struct OpLinear {
- template <typename D, typename T>
- T Transform(D d, const T& linear) const {
- return linear;
- }
-};
-
-struct OpRgb {
- template <typename D, typename T>
- T Transform(D d, const T& linear) const {
-#if JXL_HIGH_PRECISION
- return TF_SRGB().EncodedFromDisplay(d, linear);
-#else
- return FastLinearToSRGB(d, linear);
-#endif
- }
-};
-
-struct OpPq {
- template <typename D, typename T>
- T Transform(D d, const T& linear) const {
- return TF_PQ().EncodedFromDisplay(d, linear);
- }
-};
-
-struct OpHlg {
- explicit OpHlg(const float luminances[3], const float intensity_target)
- : luminances(luminances), exponent(1.0f) {
- if (295 <= intensity_target && intensity_target <= 305) {
- apply_inverse_ootf = false;
- return;
- }
- exponent =
- (1 / 1.2f) * std::pow(1.111f, -std::log2(intensity_target * 1e-3f)) - 1;
- }
- template <typename D, typename T>
- void Transform(D d, T* r, T* g, T* b) const {
- if (apply_inverse_ootf) {
- const T luminance = Set(d, luminances[0]) * *r +
- Set(d, luminances[1]) * *g +
- Set(d, luminances[2]) * *b;
- const T ratio =
- Min(FastPowf(d, luminance, Set(d, exponent)), Set(d, 1e9));
- *r *= ratio;
- *g *= ratio;
- *b *= ratio;
- }
- *r = TF_HLG().EncodedFromDisplay(d, *r);
- *g = TF_HLG().EncodedFromDisplay(d, *g);
- *b = TF_HLG().EncodedFromDisplay(d, *b);
- }
- bool apply_inverse_ootf = true;
- const float* luminances;
- float exponent;
-};
-
-struct Op709 {
- template <typename D, typename T>
- T Transform(D d, const T& linear) const {
- return TF_709().EncodedFromDisplay(d, linear);
- }
-};
-
-struct OpGamma {
- const float inverse_gamma;
- template <typename D, typename T>
- T Transform(D d, const T& linear) const {
- return IfThenZeroElse(linear <= Set(d, 1e-5f),
- FastPowf(d, linear, Set(d, inverse_gamma)));
- }
-};
-
-template <typename Op>
class XYBStage : public RenderPipelineStage {
public:
- XYBStage(OpsinParams opsin_params, Op op)
+ explicit XYBStage(const OpsinParams& opsin_params)
: RenderPipelineStage(RenderPipelineStage::Settings()),
- opsin_params_(opsin_params),
- op_(op) {}
+ opsin_params_(opsin_params) {}
void ProcessRow(const RowInfo& input_rows, const RowInfo& output_rows,
size_t xextra, size_t xsize, size_t xpos, size_t ypos,
@@ -122,6 +29,7 @@ class XYBStage : public RenderPipelineStage {
PROFILER_ZONE("UndoXYB");
const HWY_FULL(float) d;
+ JXL_ASSERT(xextra == 0);
const size_t xsize_v = RoundUpTo(xsize, Lanes(d));
float* JXL_RESTRICT row0 = GetInputRow(input_rows, 0, 0);
float* JXL_RESTRICT row1 = GetInputRow(input_rows, 1, 0);
@@ -132,19 +40,20 @@ class XYBStage : public RenderPipelineStage {
msan::UnpoisonMemory(row0 + xsize, sizeof(float) * (xsize_v - xsize));
msan::UnpoisonMemory(row1 + xsize, sizeof(float) * (xsize_v - xsize));
msan::UnpoisonMemory(row2 + xsize, sizeof(float) * (xsize_v - xsize));
+ // TODO(eustas): when using frame origin, addresses might be unaligned;
+ // making them aligned will void performance penalty.
for (ssize_t x = -xextra; x < (ssize_t)(xsize + xextra); x += Lanes(d)) {
- const auto in_opsin_x = Load(d, row0 + x);
- const auto in_opsin_y = Load(d, row1 + x);
- const auto in_opsin_b = Load(d, row2 + x);
+ const auto in_opsin_x = LoadU(d, row0 + x);
+ const auto in_opsin_y = LoadU(d, row1 + x);
+ const auto in_opsin_b = LoadU(d, row2 + x);
auto r = Undefined(d);
auto g = Undefined(d);
auto b = Undefined(d);
XybToRgb(d, in_opsin_x, in_opsin_y, in_opsin_b, opsin_params_, &r, &g,
&b);
- op_.Transform(d, &r, &g, &b);
- Store(r, d, row0 + x);
- Store(g, d, row1 + x);
- Store(b, d, row2 + x);
+ StoreU(r, d, row0 + x);
+ StoreU(g, d, row1 + x);
+ StoreU(b, d, row2 + x);
}
msan::PoisonMemory(row0 + xsize, sizeof(float) * (xsize_v - xsize));
msan::PoisonMemory(row1 + xsize, sizeof(float) * (xsize_v - xsize));
@@ -159,43 +68,12 @@ class XYBStage : public RenderPipelineStage {
const char* GetName() const override { return "XYB"; }
private:
- OpsinParams opsin_params_;
- Op op_;
+ const OpsinParams opsin_params_;
};
-template <typename Op>
-std::unique_ptr<XYBStage<Op>> MakeXYBStage(const OpsinParams& opsin_params,
- Op&& op) {
- return jxl::make_unique<XYBStage<Op>>(opsin_params, std::forward<Op>(op));
-}
-
std::unique_ptr<RenderPipelineStage> GetXYBStage(
- const OutputEncodingInfo& output_encoding_info) {
- if (output_encoding_info.color_encoding.tf.IsLinear()) {
- return MakeXYBStage(output_encoding_info.opsin_params,
- MakePerChannelOp(OpLinear()));
- } else if (output_encoding_info.color_encoding.tf.IsSRGB()) {
- return MakeXYBStage(output_encoding_info.opsin_params,
- MakePerChannelOp(OpRgb()));
- } else if (output_encoding_info.color_encoding.tf.IsPQ()) {
- return MakeXYBStage(output_encoding_info.opsin_params,
- MakePerChannelOp(OpPq()));
- } else if (output_encoding_info.color_encoding.tf.IsHLG()) {
- return MakeXYBStage(output_encoding_info.opsin_params,
- OpHlg(output_encoding_info.luminances,
- output_encoding_info.intensity_target));
- } else if (output_encoding_info.color_encoding.tf.Is709()) {
- return MakeXYBStage(output_encoding_info.opsin_params,
- MakePerChannelOp(Op709()));
- } else if (output_encoding_info.color_encoding.tf.IsGamma() ||
- output_encoding_info.color_encoding.tf.IsDCI()) {
- return MakeXYBStage(
- output_encoding_info.opsin_params,
- MakePerChannelOp(OpGamma{output_encoding_info.inverse_gamma}));
- } else {
- // This is a programming error.
- JXL_ABORT("Invalid target encoding");
- }
+ const OpsinParams& opsin_params) {
+ return jxl::make_unique<XYBStage>(opsin_params);
}
// NOLINTNEXTLINE(google-readability-namespace-comments)
@@ -209,8 +87,8 @@ namespace jxl {
HWY_EXPORT(GetXYBStage);
std::unique_ptr<RenderPipelineStage> GetXYBStage(
- const OutputEncodingInfo& output_encoding_info) {
- return HWY_DYNAMIC_DISPATCH(GetXYBStage)(output_encoding_info);
+ const OpsinParams& opsin_params) {
+ return HWY_DYNAMIC_DISPATCH(GetXYBStage)(opsin_params);
}
namespace {
diff --git a/media/libjxl/src/lib/jxl/render_pipeline/stage_xyb.h b/media/libjxl/src/lib/jxl/render_pipeline/stage_xyb.h
index ff4fdd20a4..2bc5075193 100644
--- a/media/libjxl/src/lib/jxl/render_pipeline/stage_xyb.h
+++ b/media/libjxl/src/lib/jxl/render_pipeline/stage_xyb.h
@@ -5,22 +5,16 @@
#ifndef LIB_JXL_RENDER_PIPELINE_STAGE_XYB_H_
#define LIB_JXL_RENDER_PIPELINE_STAGE_XYB_H_
-#include <math.h>
#include <stdint.h>
-#include <stdio.h>
-
-#include <algorithm>
-#include <utility>
-#include <vector>
#include "lib/jxl/dec_xyb.h"
#include "lib/jxl/render_pipeline/render_pipeline_stage.h"
namespace jxl {
-// Converts the color channels from XYB to the specified output encoding.
+// Converts the color channels from XYB to linear with appropriate primaries.
std::unique_ptr<RenderPipelineStage> GetXYBStage(
- const OutputEncodingInfo& output_encoding_info);
+ const OpsinParams& output_encoding_info);
// Gets a stage to convert with fixed point arithmetic from XYB to sRGB8 and
// write to a uint8 buffer.
diff --git a/media/libjxl/src/lib/jxl/render_pipeline/stage_ycbcr.cc b/media/libjxl/src/lib/jxl/render_pipeline/stage_ycbcr.cc
index fa8b2a907b..5cba4a7d41 100644
--- a/media/libjxl/src/lib/jxl/render_pipeline/stage_ycbcr.cc
+++ b/media/libjxl/src/lib/jxl/render_pipeline/stage_ycbcr.cc
@@ -14,6 +14,10 @@ HWY_BEFORE_NAMESPACE();
namespace jxl {
namespace HWY_NAMESPACE {
+// These templates are not found via ADL.
+using hwy::HWY_NAMESPACE::Add;
+using hwy::HWY_NAMESPACE::MulAdd;
+
class kYCbCrStage : public RenderPipelineStage {
public:
kYCbCrStage() : RenderPipelineStage(RenderPipelineStage::Settings()) {}
@@ -36,16 +40,18 @@ class kYCbCrStage : public RenderPipelineStage {
float* JXL_RESTRICT row0 = GetInputRow(input_rows, 0, 0);
float* JXL_RESTRICT row1 = GetInputRow(input_rows, 1, 0);
float* JXL_RESTRICT row2 = GetInputRow(input_rows, 2, 0);
+ // TODO(eustas): when using frame origin, addresses might be unaligned;
+ // making them aligned will void performance penalty.
for (size_t x = 0; x < xsize; x += Lanes(df)) {
- const auto y_vec = Load(df, row1 + x) + c128;
- const auto cb_vec = Load(df, row0 + x);
- const auto cr_vec = Load(df, row2 + x);
- const auto r_vec = crcr * cr_vec + y_vec;
- const auto g_vec = cgcr * cr_vec + cgcb * cb_vec + y_vec;
- const auto b_vec = cbcb * cb_vec + y_vec;
- Store(r_vec, df, row0 + x);
- Store(g_vec, df, row1 + x);
- Store(b_vec, df, row2 + x);
+ const auto y_vec = Add(LoadU(df, row1 + x), c128);
+ const auto cb_vec = LoadU(df, row0 + x);
+ const auto cr_vec = LoadU(df, row2 + x);
+ const auto r_vec = MulAdd(crcr, cr_vec, y_vec);
+ const auto g_vec = MulAdd(cgcr, cr_vec, MulAdd(cgcb, cb_vec, y_vec));
+ const auto b_vec = MulAdd(cbcb, cb_vec, y_vec);
+ StoreU(r_vec, df, row0 + x);
+ StoreU(g_vec, df, row1 + x);
+ StoreU(b_vec, df, row2 + x);
}
}
diff --git a/media/libjxl/src/lib/jxl/roundtrip_test.cc b/media/libjxl/src/lib/jxl/roundtrip_test.cc
index bd3c3f2866..d057172665 100644
--- a/media/libjxl/src/lib/jxl/roundtrip_test.cc
+++ b/media/libjxl/src/lib/jxl/roundtrip_test.cc
@@ -101,7 +101,7 @@ jxl::CodecInOut ConvertTestImage(const std::vector<uint8_t>& buf,
color_encoding, pixel_format.num_channels,
/*alpha_is_premultiplied=*/false,
/*bits_per_sample=*/bitdepth, pixel_format.endianness,
- /*flipped_y=*/false, /*pool=*/nullptr, &io.Main(), float_in,
+ /*pool=*/nullptr, &io.Main(), float_in,
/*align=*/0));
return io;
}
@@ -729,7 +729,7 @@ TEST(RoundtripTest, TestICCProfile) {
jxl::test::JxlBasicInfoSetFromPixelFormat(&basic_info, &format);
basic_info.xsize = xsize;
basic_info.ysize = ysize;
- basic_info.uses_original_profile = JXL_FALSE;
+ basic_info.uses_original_profile = JXL_TRUE;
EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetBasicInfo(enc, &basic_info));
EXPECT_EQ(JXL_ENC_SUCCESS,
@@ -800,8 +800,7 @@ TEST(RoundtripTest, TestICCProfile) {
#if JPEGXL_ENABLE_JPEG // Loading .jpg files requires libjpeg support.
TEST(RoundtripTest, JXL_TRANSCODE_JPEG_TEST(TestJPEGReconstruction)) {
- const std::string jpeg_path =
- "third_party/imagecompression.info/flower_foveon.png.im_q85_420.jpg";
+ const std::string jpeg_path = "jxl/flower/flower.png.im_q85_420.jpg";
const jxl::PaddedBytes orig = jxl::ReadTestData(jpeg_path);
jxl::CodecInOut orig_io;
ASSERT_TRUE(
diff --git a/media/libjxl/src/lib/jxl/sanitizers.h b/media/libjxl/src/lib/jxl/sanitizers.h
index 5cd501798e..ce0bd8dc63 100644
--- a/media/libjxl/src/lib/jxl/sanitizers.h
+++ b/media/libjxl/src/lib/jxl/sanitizers.h
@@ -169,7 +169,7 @@ static JXL_INLINE JXL_MAYBE_UNUSED void PrintImageUninitialized(
// (not poisoned). If any of the values is poisoned it will abort.
template <typename T>
static JXL_INLINE JXL_MAYBE_UNUSED void CheckImageInitialized(
- const Plane<T>& im, const Rect& r, const char* message) {
+ const Plane<T>& im, const Rect& r, size_t c, const char* message) {
JXL_ASSERT(r.x0() <= im.xsize());
JXL_ASSERT(r.x0() + r.xsize() <= im.xsize());
JXL_ASSERT(r.y0() <= im.ysize());
@@ -188,10 +188,11 @@ static JXL_INLINE JXL_MAYBE_UNUSED void CheckImageInitialized(
static_cast<uint64_t>(r.x0()), static_cast<uint64_t>(r.y0()),
static_cast<uint64_t>(r.xsize()), static_cast<uint64_t>(r.ysize()));
size_t x = ret / sizeof(*row);
- JXL_DEBUG(
- 1, "CheckImageInitialized failed at x=%" PRIu64 ", y=%" PRIu64 ": %s",
- static_cast<uint64_t>(r.x0() + x), static_cast<uint64_t>(y),
- message ? message : "");
+ JXL_DEBUG(1,
+ "CheckImageInitialized failed at x=%" PRIu64 ", y=%" PRIu64
+ ", c=%" PRIu64 ": %s",
+ static_cast<uint64_t>(r.x0() + x), static_cast<uint64_t>(y),
+ static_cast<uint64_t>(c), message ? message : "");
PrintImageUninitialized(im);
}
// This will report an error if memory is not initialized.
@@ -205,13 +206,16 @@ static JXL_INLINE JXL_MAYBE_UNUSED void CheckImageInitialized(
for (size_t c = 0; c < 3; c++) {
std::string str_message(message);
str_message += " c=" + std::to_string(c);
- CheckImageInitialized(im.Plane(c), r, str_message.c_str());
+ CheckImageInitialized(im.Plane(c), r, c, str_message.c_str());
}
}
#define JXL_CHECK_IMAGE_INITIALIZED(im, r) \
::jxl::msan::CheckImageInitialized(im, r, "im=" #im ", r=" #r);
+#define JXL_CHECK_PLANE_INITIALIZED(im, r, c) \
+ ::jxl::msan::CheckImageInitialized(im, r, c, "im=" #im ", r=" #r ", c=" #c);
+
#else // JXL_MEMORY_SANITIZER
// In non-msan mode these functions don't use volatile since it is not needed
@@ -228,6 +232,7 @@ template <typename T>
static JXL_INLINE JXL_MAYBE_UNUSED void PoisonImage(const Plane<T>& im) {}
#define JXL_CHECK_IMAGE_INITIALIZED(im, r)
+#define JXL_CHECK_PLANE_INITIALIZED(im, r, c)
#endif
diff --git a/media/libjxl/src/lib/jxl/speed_tier_test.cc b/media/libjxl/src/lib/jxl/speed_tier_test.cc
index 6005f629e7..9c120fe54c 100644
--- a/media/libjxl/src/lib/jxl/speed_tier_test.cc
+++ b/media/libjxl/src/lib/jxl/speed_tier_test.cc
@@ -12,8 +12,6 @@
#include "lib/jxl/base/padded_bytes.h"
#include "lib/jxl/base/thread_pool_internal.h"
#include "lib/jxl/codec_in_out.h"
-#include "lib/jxl/dec_file.h"
-#include "lib/jxl/dec_params.h"
#include "lib/jxl/enc_butteraugli_comparator.h"
#include "lib/jxl/enc_cache.h"
#include "lib/jxl/enc_file.h"
@@ -85,7 +83,7 @@ JXL_GTEST_INSTANTIATE_TEST_SUITE_P(
TEST_P(SpeedTierTest, Roundtrip) {
const PaddedBytes orig =
- ReadTestData("third_party/wesaturate/500px/u76c0g_bliznaca_srgb8.png");
+ ReadTestData("external/wesaturate/500px/u76c0g_bliznaca_srgb8.png");
CodecInOut io;
ThreadPoolInternal pool(8);
ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io, &pool));
@@ -98,10 +96,9 @@ TEST_P(SpeedTierTest, Roundtrip) {
CompressParams cparams;
cparams.speed_tier = params.speed_tier;
- DecompressParams dparams;
CodecInOut io2;
- test::Roundtrip(&io, cparams, dparams, nullptr, &io2);
+ test::Roundtrip(&io, cparams, {}, nullptr, &io2);
// Can be 2.2 in non-hare mode.
EXPECT_LE(ButteraugliDistance(io, io2, cparams.ba_params, GetJxlCms(),
diff --git a/media/libjxl/src/lib/jxl/splines.cc b/media/libjxl/src/lib/jxl/splines.cc
index 11613fc667..edaaf2738d 100644
--- a/media/libjxl/src/lib/jxl/splines.cc
+++ b/media/libjxl/src/lib/jxl/splines.cc
@@ -28,6 +28,13 @@ namespace jxl {
namespace HWY_NAMESPACE {
namespace {
+// These templates are not found via ADL.
+using hwy::HWY_NAMESPACE::Mul;
+using hwy::HWY_NAMESPACE::MulAdd;
+using hwy::HWY_NAMESPACE::MulSub;
+using hwy::HWY_NAMESPACE::Sqrt;
+using hwy::HWY_NAMESPACE::Sub;
+
// Given a set of DCT coefficients, this returns the result of performing cosine
// interpolation on the original samples.
float ContinuousIDCT(const float dct[32], const float t) {
@@ -48,9 +55,9 @@ float ContinuousIDCT(const float dct[32], const float t) {
auto result = Zero(df);
const auto tandhalf = Set(df, t + 0.5f);
for (int i = 0; i < 32; i += Lanes(df)) {
- auto cos_arg = LoadU(df, kMultipliers + i) * tandhalf;
+ auto cos_arg = Mul(LoadU(df, kMultipliers + i), tandhalf);
auto cos = FastCosf(df, cos_arg);
- auto local_res = LoadU(df, dct + i) * cos;
+ auto local_res = Mul(LoadU(df, dct + i), cos);
result = MulAdd(Set(df, kSqrt2), local_res, result);
}
return GetLane(SumOfLanes(df, result));
@@ -65,15 +72,16 @@ void DrawSegment(DF df, const SplineSegment& segment, const bool add,
const auto one_over_2s2 = Set(df, 0.353553391f);
const auto sigma_over_4_times_intensity =
Set(df, segment.sigma_over_4_times_intensity);
- const auto dx = ConvertTo(df, Iota(di, x)) - Set(df, segment.center_x);
+ const auto dx = Sub(ConvertTo(df, Iota(di, x)), Set(df, segment.center_x));
const auto dy = Set(df, y - segment.center_y);
- const auto sqd = MulAdd(dx, dx, dy * dy);
+ const auto sqd = MulAdd(dx, dx, Mul(dy, dy));
const auto distance = Sqrt(sqd);
const auto one_dimensional_factor =
- FastErff(df, MulAdd(distance, half, one_over_2s2) * inv_sigma) -
- FastErff(df, MulSub(distance, half, one_over_2s2) * inv_sigma);
- auto local_intensity = sigma_over_4_times_intensity * one_dimensional_factor *
- one_dimensional_factor;
+ Sub(FastErff(df, Mul(MulAdd(distance, half, one_over_2s2), inv_sigma)),
+ FastErff(df, Mul(MulSub(distance, half, one_over_2s2), inv_sigma)));
+ auto local_intensity =
+ Mul(sigma_over_4_times_intensity,
+ Mul(one_dimensional_factor, one_dimensional_factor));
for (size_t c = 0; c < 3; ++c) {
const auto cm = Set(df, add ? segment.color[c] : -segment.color[c]);
const auto in = LoadU(df, rows[c] + x);
diff --git a/media/libjxl/src/lib/jxl/splines_test.cc b/media/libjxl/src/lib/jxl/splines_test.cc
index 558db32d85..09b2dd5623 100644
--- a/media/libjxl/src/lib/jxl/splines_test.cc
+++ b/media/libjxl/src/lib/jxl/splines_test.cc
@@ -5,15 +5,13 @@
#include "lib/jxl/splines.h"
-#include "gmock/gmock.h"
-#include "gtest/gtest.h"
#include "lib/extras/codec.h"
#include "lib/jxl/base/printf_macros.h"
-#include "lib/jxl/dec_file.h"
#include "lib/jxl/enc_butteraugli_comparator.h"
#include "lib/jxl/enc_color_management.h"
#include "lib/jxl/enc_splines.h"
#include "lib/jxl/image_test_utils.h"
+#include "lib/jxl/test_utils.h"
#include "lib/jxl/testdata.h"
namespace jxl {
@@ -328,8 +326,8 @@ TEST(SplinesTest, ClearedEveryFrame) {
CodecInOut io_actual;
const PaddedBytes bytes_actual =
ReadTestData("jxl/spline_on_first_frame.jxl");
- ASSERT_TRUE(DecodeFile(DecompressParams(), bytes_actual, &io_actual,
- /*pool=*/nullptr));
+ ASSERT_TRUE(test::DecodeFile({}, bytes_actual, &io_actual,
+ /*pool=*/nullptr));
ASSERT_TRUE(io_actual.TransformTo(ColorEncoding::SRGB(), GetJxlCms()));
for (size_t c = 0; c < 3; ++c) {
diff --git a/media/libjxl/src/lib/jxl/test_image.h b/media/libjxl/src/lib/jxl/test_image.h
new file mode 100644
index 0000000000..0093443682
--- /dev/null
+++ b/media/libjxl/src/lib/jxl/test_image.h
@@ -0,0 +1,103 @@
+// Copyright (c) the JPEG XL Project Authors. All rights reserved.
+//
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+#ifndef LIB_JXL_TEST_IMAGE_H_
+#define LIB_JXL_TEST_IMAGE_H_
+
+#include <stdint.h>
+
+#include <vector>
+
+#include "lib/jxl/base/random.h"
+
+namespace jxl {
+namespace test {
+
+// Returns a test image with some autogenerated pixel content, using 16 bits per
+// channel, big endian order, 1 to 4 channels
+// The seed parameter allows to create images with different pixel content.
+std::vector<uint8_t> GetSomeTestImage(size_t xsize, size_t ysize,
+ size_t num_channels, uint16_t seed) {
+ // Cause more significant image difference for successive seeds.
+ Rng generator(seed);
+
+ // Returns random integer in interval [0, max_value)
+ auto rng = [&generator](size_t max_value) -> size_t {
+ return generator.UniformU(0, max_value);
+ };
+
+ // Dark background gradient color
+ uint16_t r0 = rng(32768);
+ uint16_t g0 = rng(32768);
+ uint16_t b0 = rng(32768);
+ uint16_t a0 = rng(32768);
+ uint16_t r1 = rng(32768);
+ uint16_t g1 = rng(32768);
+ uint16_t b1 = rng(32768);
+ uint16_t a1 = rng(32768);
+
+ // Circle with different color
+ size_t circle_x = rng(xsize);
+ size_t circle_y = rng(ysize);
+ size_t circle_r = rng(std::min(xsize, ysize));
+
+ // Rectangle with random noise
+ size_t rect_x0 = rng(xsize);
+ size_t rect_y0 = rng(ysize);
+ size_t rect_x1 = rng(xsize);
+ size_t rect_y1 = rng(ysize);
+ if (rect_x1 < rect_x0) std::swap(rect_x0, rect_y1);
+ if (rect_y1 < rect_y0) std::swap(rect_y0, rect_y1);
+
+ size_t num_pixels = xsize * ysize;
+ // 16 bits per channel, big endian, 4 channels
+ std::vector<uint8_t> pixels(num_pixels * num_channels * 2);
+ // Create pixel content to test, actual content does not matter as long as it
+ // can be compared after roundtrip.
+ for (size_t y = 0; y < ysize; y++) {
+ for (size_t x = 0; x < xsize; x++) {
+ uint16_t r = r0 * (ysize - y - 1) / ysize + r1 * y / ysize;
+ uint16_t g = g0 * (ysize - y - 1) / ysize + g1 * y / ysize;
+ uint16_t b = b0 * (ysize - y - 1) / ysize + b1 * y / ysize;
+ uint16_t a = a0 * (ysize - y - 1) / ysize + a1 * y / ysize;
+ // put some shape in there for visual debugging
+ if ((x - circle_x) * (x - circle_x) + (y - circle_y) * (y - circle_y) <
+ circle_r * circle_r) {
+ r = (65535 - x * y) ^ seed;
+ g = (x << 8) + y + seed;
+ b = (y << 8) + x * seed;
+ a = 32768 + x * 256 - y;
+ } else if (x > rect_x0 && x < rect_x1 && y > rect_y0 && y < rect_y1) {
+ r = rng(65536);
+ g = rng(65536);
+ b = rng(65536);
+ a = rng(65536);
+ }
+ size_t i = (y * xsize + x) * 2 * num_channels;
+ pixels[i + 0] = (r >> 8);
+ pixels[i + 1] = (r & 255);
+ if (num_channels >= 2) {
+ // This may store what is called 'g' in the alpha channel of a 2-channel
+ // image, but that's ok since the content is arbitrary
+ pixels[i + 2] = (g >> 8);
+ pixels[i + 3] = (g & 255);
+ }
+ if (num_channels >= 3) {
+ pixels[i + 4] = (b >> 8);
+ pixels[i + 5] = (b & 255);
+ }
+ if (num_channels >= 4) {
+ pixels[i + 6] = (a >> 8);
+ pixels[i + 7] = (a & 255);
+ }
+ }
+ }
+ return pixels;
+}
+
+} // namespace test
+} // namespace jxl
+
+#endif // LIB_JXL_TEST_IMAGE_H_
diff --git a/media/libjxl/src/lib/jxl/test_utils.h b/media/libjxl/src/lib/jxl/test_utils.h
index b1cc84e87b..b55cc3d205 100644
--- a/media/libjxl/src/lib/jxl/test_utils.h
+++ b/media/libjxl/src/lib/jxl/test_utils.h
@@ -8,22 +8,30 @@
// Macros and functions useful for tests.
+// gmock unconditionally redefines those macros (to wrong values).
+// Lets include it only here and mitigate the problem.
+#pragma push_macro("PRIdS")
+#pragma push_macro("PRIuS")
#include "gmock/gmock.h"
+#pragma pop_macro("PRIuS")
+#pragma pop_macro("PRIdS")
+
#include "gtest/gtest.h"
#include "jxl/codestream_header.h"
#include "jxl/encode.h"
+#include "lib/extras/dec/jxl.h"
+#include "lib/extras/packed_image_convert.h"
#include "lib/jxl/aux_out_fwd.h"
#include "lib/jxl/base/data_parallel.h"
#include "lib/jxl/base/random.h"
#include "lib/jxl/codec_in_out.h"
#include "lib/jxl/color_encoding_internal.h"
#include "lib/jxl/common.h" // JPEGXL_ENABLE_TRANSCODE_JPEG
-#include "lib/jxl/dec_file.h"
-#include "lib/jxl/dec_params.h"
#include "lib/jxl/enc_color_management.h"
#include "lib/jxl/enc_external_image.h"
#include "lib/jxl/enc_file.h"
#include "lib/jxl/enc_params.h"
+#include "lib/jxl/test_image.h"
#ifdef JXL_DISABLE_SLOW_TESTS
#define JXL_SLOW_TEST(X) DISABLED_##X
@@ -81,6 +89,11 @@ void JxlBasicInfoSetFromPixelFormat(JxlBasicInfo* basic_info,
default:
JXL_ABORT("Unhandled JxlDataType");
}
+ if (pixel_format->num_channels < 3) {
+ basic_info->num_color_channels = 1;
+ } else {
+ basic_info->num_color_channels = 3;
+ }
if (pixel_format->num_channels == 2 || pixel_format->num_channels == 4) {
basic_info->alpha_exponent_bits = basic_info->exponent_bits_per_sample;
basic_info->alpha_bits = basic_info->bits_per_sample;
@@ -92,8 +105,9 @@ void JxlBasicInfoSetFromPixelFormat(JxlBasicInfo* basic_info,
}
MATCHER_P(MatchesPrimariesAndTransferFunction, color_encoding, "") {
- return arg.primaries == color_encoding.primaries &&
- arg.tf.IsSame(color_encoding.tf);
+ return (arg.ICC() == color_encoding.ICC() ||
+ (arg.primaries == color_encoding.primaries &&
+ arg.tf.IsSame(color_encoding.tf)));
}
MATCHER(MatchesPrimariesAndTransferFunction, "") {
@@ -102,9 +116,23 @@ MATCHER(MatchesPrimariesAndTransferFunction, "") {
result_listener);
}
+template <typename Source>
+Status DecodeFile(extras::JXLDecompressParams dparams, const Source& file,
+ CodecInOut* JXL_RESTRICT io, ThreadPool* pool) {
+ if (pool && !dparams.runner_opaque) {
+ dparams.runner = pool->runner();
+ dparams.runner_opaque = pool->runner_opaque();
+ }
+ extras::PackedPixelFile ppf;
+ JXL_RETURN_IF_ERROR(DecodeImageJXL(file.data(), file.size(), dparams,
+ /*decoded_bytes=*/nullptr, &ppf));
+ JXL_RETURN_IF_ERROR(ConvertPackedPixelFileToCodecInOut(ppf, pool, io));
+ return true;
+}
+
// Returns compressed size [bytes].
size_t Roundtrip(const CodecInOut* io, const CompressParams& cparams,
- const DecompressParams& dparams, ThreadPool* pool,
+ extras::JXLDecompressParams dparams, ThreadPool* pool,
CodecInOut* JXL_RESTRICT io2, AuxOut* aux_out = nullptr) {
PaddedBytes compressed;
@@ -208,6 +236,7 @@ static inline ColorEncoding ColorEncodingFromDescriptor(
c.primaries = desc.primaries;
c.tf.SetTransferFunction(desc.tf);
c.rendering_intent = desc.rendering_intent;
+ JXL_CHECK(c.CreateICC());
return c;
}
@@ -264,88 +293,6 @@ std::vector<ColorEncodingDescriptor> AllEncodings() {
return all_encodings;
}
-// Returns a test image with some autogenerated pixel content, using 16 bits per
-// channel, big endian order, 1 to 4 channels
-// The seed parameter allows to create images with different pixel content.
-std::vector<uint8_t> GetSomeTestImage(size_t xsize, size_t ysize,
- size_t num_channels, uint16_t seed) {
- // Cause more significant image difference for successive seeds.
- Rng generator(seed);
-
- // Returns random integer in interval [0, max_value)
- auto rng = [&generator](size_t max_value) -> size_t {
- return generator.UniformU(0, max_value);
- };
-
- // Dark background gradient color
- uint16_t r0 = rng(32768);
- uint16_t g0 = rng(32768);
- uint16_t b0 = rng(32768);
- uint16_t a0 = rng(32768);
- uint16_t r1 = rng(32768);
- uint16_t g1 = rng(32768);
- uint16_t b1 = rng(32768);
- uint16_t a1 = rng(32768);
-
- // Circle with different color
- size_t circle_x = rng(xsize);
- size_t circle_y = rng(ysize);
- size_t circle_r = rng(std::min(xsize, ysize));
-
- // Rectangle with random noise
- size_t rect_x0 = rng(xsize);
- size_t rect_y0 = rng(ysize);
- size_t rect_x1 = rng(xsize);
- size_t rect_y1 = rng(ysize);
- if (rect_x1 < rect_x0) std::swap(rect_x0, rect_y1);
- if (rect_y1 < rect_y0) std::swap(rect_y0, rect_y1);
-
- size_t num_pixels = xsize * ysize;
- // 16 bits per channel, big endian, 4 channels
- std::vector<uint8_t> pixels(num_pixels * num_channels * 2);
- // Create pixel content to test, actual content does not matter as long as it
- // can be compared after roundtrip.
- for (size_t y = 0; y < ysize; y++) {
- for (size_t x = 0; x < xsize; x++) {
- uint16_t r = r0 * (ysize - y - 1) / ysize + r1 * y / ysize;
- uint16_t g = g0 * (ysize - y - 1) / ysize + g1 * y / ysize;
- uint16_t b = b0 * (ysize - y - 1) / ysize + b1 * y / ysize;
- uint16_t a = a0 * (ysize - y - 1) / ysize + a1 * y / ysize;
- // put some shape in there for visual debugging
- if ((x - circle_x) * (x - circle_x) + (y - circle_y) * (y - circle_y) <
- circle_r * circle_r) {
- r = (65535 - x * y) ^ seed;
- g = (x << 8) + y + seed;
- b = (y << 8) + x * seed;
- a = 32768 + x * 256 - y;
- } else if (x > rect_x0 && x < rect_x1 && y > rect_y0 && y < rect_y1) {
- r = rng(65536);
- g = rng(65536);
- b = rng(65536);
- a = rng(65536);
- }
- size_t i = (y * xsize + x) * 2 * num_channels;
- pixels[i + 0] = (r >> 8);
- pixels[i + 1] = (r & 255);
- if (num_channels >= 2) {
- // This may store what is called 'g' in the alpha channel of a 2-channel
- // image, but that's ok since the content is arbitrary
- pixels[i + 2] = (g >> 8);
- pixels[i + 3] = (g & 255);
- }
- if (num_channels >= 3) {
- pixels[i + 4] = (b >> 8);
- pixels[i + 5] = (b & 255);
- }
- if (num_channels >= 4) {
- pixels[i + 6] = (a >> 8);
- pixels[i + 7] = (a & 255);
- }
- }
- }
- return pixels;
-}
-
// Returns a CodecInOut based on the buf, xsize, ysize, and the assumption
// that the buffer was created using `GetSomeTestImage`.
jxl::CodecInOut SomeTestImageToCodecInOut(const std::vector<uint8_t>& buf,
@@ -360,7 +307,7 @@ jxl::CodecInOut SomeTestImageToCodecInOut(const std::vector<uint8_t>& buf,
jxl::Span<const uint8_t>(buf.data(), buf.size()), xsize, ysize,
jxl::ColorEncoding::SRGB(/*is_gray=*/num_channels < 3), num_channels,
/*alpha_is_premultiplied=*/false, /*bits_per_sample=*/16, JXL_BIG_ENDIAN,
- /*flipped_y=*/false, /*pool=*/nullptr,
+ /*pool=*/nullptr,
/*ib=*/&io.Main(), /*float_in=*/false, 0));
return io;
}
@@ -454,8 +401,8 @@ size_t GetDataBits(JxlDataType data_type) {
// precisions needed for the maximum data types the API supports: uint32_t
// integers, and, single precision float. The values are in range 0-1 for SDR.
std::vector<double> ConvertToRGBA32(const uint8_t* pixels, size_t xsize,
- size_t ysize,
- const JxlPixelFormat& format) {
+ size_t ysize, const JxlPixelFormat& format,
+ double factor = 0.0) {
std::vector<double> result(xsize * ysize * 4);
size_t num_channels = format.num_channels;
bool gray = num_channels == 1 || num_channels == 2;
@@ -467,7 +414,8 @@ std::vector<double> ConvertToRGBA32(const uint8_t* pixels, size_t xsize,
if (format.align > 1) stride = jxl::RoundUpTo(stride, format.align);
if (format.data_type == JXL_TYPE_UINT8) {
- double mul = 1.0 / 255.0; // Multiplier to bring to 0-1.0 range
+ // Multiplier to bring to 0-1.0 range
+ double mul = factor > 0.0 ? factor : 1.0 / 255.0;
for (size_t y = 0; y < ysize; ++y) {
for (size_t x = 0; x < xsize; ++x) {
size_t j = (y * xsize + x) * 4;
@@ -483,7 +431,8 @@ std::vector<double> ConvertToRGBA32(const uint8_t* pixels, size_t xsize,
}
}
} else if (format.data_type == JXL_TYPE_UINT16) {
- double mul = 1.0 / 65535.0; // Multiplier to bring to 0-1.0 range
+ // Multiplier to bring to 0-1.0 range
+ double mul = factor > 0.0 ? factor : 1.0 / 65535.0;
for (size_t y = 0; y < ysize; ++y) {
for (size_t x = 0; x < xsize; ++x) {
size_t j = (y * xsize + x) * 4;
@@ -624,6 +573,26 @@ size_t ComparePixels(const uint8_t* a, const uint8_t* b, size_t xsize,
}
return numdiff;
}
+double DistanceRMS(const uint8_t* a, const uint8_t* b, size_t xsize,
+ size_t ysize, const JxlPixelFormat& format) {
+ // Convert both images to equal full precision for comparison.
+ std::vector<double> a_full = ConvertToRGBA32(a, xsize, ysize, format);
+ std::vector<double> b_full = ConvertToRGBA32(b, xsize, ysize, format);
+ double sum = 0.0;
+ for (size_t y = 0; y < ysize; y++) {
+ double row_sum = 0.0;
+ for (size_t x = 0; x < xsize; x++) {
+ size_t i = (y * xsize + x) * 4;
+ for (size_t c = 0; c < format.num_channels; ++c) {
+ double diff = a_full[i + c] - b_full[i + c];
+ row_sum += diff * diff;
+ }
+ }
+ sum += row_sum;
+ }
+ sum /= (xsize * ysize);
+ return sqrt(sum);
+}
} // namespace test
bool operator==(const jxl::PaddedBytes& a, const jxl::PaddedBytes& b) {
diff --git a/media/libjxl/src/lib/jxl/testdata.h b/media/libjxl/src/lib/jxl/testdata.h
index 28d1015d0b..d387219bb8 100644
--- a/media/libjxl/src/lib/jxl/testdata.h
+++ b/media/libjxl/src/lib/jxl/testdata.h
@@ -19,39 +19,7 @@ namespace jxl {
static inline PaddedBytes ReadTestData(const std::string& filename) {
std::string full_path = std::string(TEST_DATA_PATH "/") + filename;
PaddedBytes data;
- bool ok = ReadFile(full_path, &data);
-#ifdef __EMSCRIPTEN__
- // Fallback in case FS is not supported in current JS engine.
- if (!ok) {
- // {size_t size, uint8_t* bytes} pair.
- uint32_t size_bytes[2] = {0, 0};
- EM_ASM(
- {
- let buffer = null;
- try {
- buffer = readbuffer(UTF8ToString($0));
- } catch {
- }
- if (!buffer) return;
- let bytes = new Uint8Array(buffer);
- let size = bytes.length;
- let out = _malloc(size);
- if (!out) return;
- HEAP8.set(bytes, out);
- HEAP32[$1 >> 2] = size;
- HEAP32[($1 + 4) >> 2] = out;
- },
- full_path.c_str(), size_bytes);
- size_t size = size_bytes[0];
- uint8_t* bytes = reinterpret_cast<uint8_t*>(size_bytes[1]);
- if (size) {
- data.append(bytes, bytes + size);
- free(reinterpret_cast<void*>(bytes));
- ok = true;
- }
- }
-#endif
- JXL_CHECK(ok);
+ JXL_CHECK(ReadFile(full_path, &data));
return data;
}
diff --git a/media/libjxl/src/lib/jxl/toc.cc b/media/libjxl/src/lib/jxl/toc.cc
index 7d48c1a5e0..24cdd02c65 100644
--- a/media/libjxl/src/lib/jxl/toc.cc
+++ b/media/libjxl/src/lib/jxl/toc.cc
@@ -20,10 +20,9 @@ size_t MaxBits(const size_t num_sizes) {
return 1 + kBitsPerByte + entry_bits + kBitsPerByte;
}
-Status ReadGroupOffsets(size_t toc_entries, BitReader* JXL_RESTRICT reader,
- std::vector<uint64_t>* JXL_RESTRICT offsets,
- std::vector<uint32_t>* JXL_RESTRICT sizes,
- uint64_t* total_size) {
+Status ReadToc(size_t toc_entries, BitReader* JXL_RESTRICT reader,
+ std::vector<uint32_t>* JXL_RESTRICT sizes,
+ std::vector<coeff_order_t>* JXL_RESTRICT permutation) {
if (toc_entries > 65536) {
// Prevent out of memory if invalid JXL codestream causes a bogus amount
// of toc_entries such as 2720436919446 to be computed.
@@ -33,8 +32,6 @@ Status ReadGroupOffsets(size_t toc_entries, BitReader* JXL_RESTRICT reader,
sizes->clear();
sizes->resize(toc_entries);
- offsets->clear();
- offsets->resize(toc_entries);
if (reader->TotalBitsConsumed() >= reader->TotalBytes() * kBitsPerByte) {
return JXL_STATUS(StatusCode::kNotEnoughBytes, "Not enough bytes for TOC");
}
@@ -51,16 +48,13 @@ Status ReadGroupOffsets(size_t toc_entries, BitReader* JXL_RESTRICT reader,
return JXL_STATUS(StatusCode::kNotEnoughBytes, "Not enough bytes for TOC");
};
- JXL_DASSERT(offsets != nullptr && sizes != nullptr);
- std::vector<coeff_order_t> permutation;
- if (reader->ReadFixedBits<1>() == 1 && toc_entries > 0) {
- // Skip permutation description if the toc_entries is 0.
+ JXL_DASSERT(toc_entries > 0);
+ if (reader->ReadFixedBits<1>() == 1) {
JXL_RETURN_IF_ERROR(check_bit_budget(toc_entries));
- permutation.resize(toc_entries);
- JXL_RETURN_IF_ERROR(
- DecodePermutation(/*skip=*/0, toc_entries, permutation.data(), reader));
+ permutation->resize(toc_entries);
+ JXL_RETURN_IF_ERROR(DecodePermutation(/*skip=*/0, toc_entries,
+ permutation->data(), reader));
}
-
JXL_RETURN_IF_ERROR(reader->JumpToByteBoundary());
JXL_RETURN_IF_ERROR(check_bit_budget(toc_entries));
for (size_t i = 0; i < toc_entries; ++i) {
@@ -68,6 +62,18 @@ Status ReadGroupOffsets(size_t toc_entries, BitReader* JXL_RESTRICT reader,
}
JXL_RETURN_IF_ERROR(reader->JumpToByteBoundary());
JXL_RETURN_IF_ERROR(check_bit_budget(0));
+ return true;
+}
+
+Status ReadGroupOffsets(size_t toc_entries, BitReader* JXL_RESTRICT reader,
+ std::vector<uint64_t>* JXL_RESTRICT offsets,
+ std::vector<uint32_t>* JXL_RESTRICT sizes,
+ uint64_t* total_size) {
+ std::vector<coeff_order_t> permutation;
+ JXL_RETURN_IF_ERROR(ReadToc(toc_entries, reader, sizes, &permutation));
+
+ offsets->clear();
+ offsets->resize(toc_entries);
// Prefix sum starting with 0 and ending with the offset of the last group
uint64_t offset = 0;
diff --git a/media/libjxl/src/lib/jxl/toc.h b/media/libjxl/src/lib/jxl/toc.h
index ffebdf9115..a97197ad45 100644
--- a/media/libjxl/src/lib/jxl/toc.h
+++ b/media/libjxl/src/lib/jxl/toc.h
@@ -13,6 +13,7 @@
#include "lib/jxl/base/compiler_specific.h"
#include "lib/jxl/base/status.h"
+#include "lib/jxl/coeff_order_fwd.h"
#include "lib/jxl/dec_bit_reader.h"
#include "lib/jxl/field_encodings.h"
@@ -40,6 +41,10 @@ static JXL_INLINE size_t NumTocEntries(size_t num_groups, size_t num_dc_groups,
num_groups * num_passes;
}
+Status ReadToc(size_t toc_entries, BitReader* JXL_RESTRICT reader,
+ std::vector<uint32_t>* JXL_RESTRICT sizes,
+ std::vector<coeff_order_t>* JXL_RESTRICT permutation);
+
Status ReadGroupOffsets(size_t toc_entries, BitReader* JXL_RESTRICT reader,
std::vector<uint64_t>* JXL_RESTRICT offsets,
std::vector<uint32_t>* JXL_RESTRICT sizes,
diff --git a/media/libjxl/src/lib/jxl/toc_test.cc b/media/libjxl/src/lib/jxl/toc_test.cc
index 62eec5a101..2f3bf5b231 100644
--- a/media/libjxl/src/lib/jxl/toc_test.cc
+++ b/media/libjxl/src/lib/jxl/toc_test.cc
@@ -81,7 +81,7 @@ void Roundtrip(size_t num_entries, bool permute, Rng* rng) {
TEST(TocTest, Test) {
Rng rng(0);
- for (size_t num_entries = 0; num_entries < 10; ++num_entries) {
+ for (size_t num_entries = 1; num_entries < 10; ++num_entries) {
for (bool permute : std::vector<bool>{false, true}) {
Roundtrip(num_entries, permute, &rng);
}
diff --git a/media/libjxl/src/lib/jxl/transfer_functions-inl.h b/media/libjxl/src/lib/jxl/transfer_functions-inl.h
index c40eafca26..9f4c10c76d 100644
--- a/media/libjxl/src/lib/jxl/transfer_functions-inl.h
+++ b/media/libjxl/src/lib/jxl/transfer_functions-inl.h
@@ -18,12 +18,23 @@
#include "lib/jxl/base/compiler_specific.h"
#include "lib/jxl/base/status.h"
+#include "lib/jxl/fast_math-inl.h"
#include "lib/jxl/rational_polynomial-inl.h"
HWY_BEFORE_NAMESPACE();
namespace jxl {
namespace HWY_NAMESPACE {
+// These templates are not found via ADL.
+using hwy::HWY_NAMESPACE::And;
+using hwy::HWY_NAMESPACE::AndNot;
+using hwy::HWY_NAMESPACE::Gt;
+using hwy::HWY_NAMESPACE::IfThenElse;
+using hwy::HWY_NAMESPACE::Lt;
+using hwy::HWY_NAMESPACE::Or;
+using hwy::HWY_NAMESPACE::Sqrt;
+using hwy::HWY_NAMESPACE::TableLookupBytes;
+
// Definitions for BT.2100-2 transfer functions (used inside/outside SIMD):
// "display" is linear light (nits) normalized to [0, 1].
// "encoded" is a nonlinear encoding (e.g. PQ) in [0, 1].
@@ -56,11 +67,11 @@ class TF_HLG {
const V kSign = BitCast(d, Set(du, 0x80000000u));
const V original_sign = And(x, kSign);
x = AndNot(kSign, x); // abs
- const V below_div12 = Sqrt(Set(d, 3.0f) * x);
+ const V below_div12 = Sqrt(Mul(Set(d, 3.0f), x));
const V e =
MulAdd(Set(d, kA * 0.693147181f),
FastLog2f(d, MulAdd(Set(d, 12), x, Set(d, -kB))), Set(d, kC));
- const V magnitude = IfThenElse(x <= Set(d, kDiv12), below_div12, e);
+ const V magnitude = IfThenElse(Le(x, Set(d, kDiv12)), below_div12, e);
return Or(AndNot(kSign, magnitude), original_sign);
}
@@ -123,10 +134,18 @@ class TF_709 {
// Maximum error 1e-6.
template <class D, class V>
JXL_INLINE V EncodedFromDisplay(D d, V x) const {
- auto low = Set(d, kMulLow) * x;
+ auto low = Mul(Set(d, kMulLow), x);
auto hi =
MulAdd(Set(d, kMulHi), FastPowf(d, x, Set(d, kPowHi)), Set(d, kSub));
- return IfThenElse(x <= Set(d, kThresh), low, hi);
+ return IfThenElse(Le(x, Set(d, kThresh)), low, hi);
+ }
+
+ template <class D, class V>
+ JXL_INLINE V DisplayFromEncoded(D d, V x) const {
+ auto low = Mul(Set(d, kInvMulLow), x);
+ auto hi = FastPowf(d, MulAdd(x, Set(d, kInvMulHi), Set(d, kInvAdd)),
+ Set(d, kInvPowHi));
+ return IfThenElse(Lt(x, Set(d, kInvThresh)), low, hi);
}
private:
@@ -135,6 +154,12 @@ class TF_709 {
static constexpr double kMulHi = 1.099;
static constexpr double kPowHi = 0.45;
static constexpr double kSub = -0.099;
+
+ static constexpr double kInvThresh = 0.081;
+ static constexpr double kInvMulLow = 1 / 4.5;
+ static constexpr double kInvMulHi = 1 / 1.099;
+ static constexpr double kInvPowHi = 1 / 0.45;
+ static constexpr double kInvAdd = 0.099 * kInvMulHi;
};
// Perceptual Quantization
@@ -225,7 +250,7 @@ class TF_PQ {
HWY_REP4(-2.072546e+05f),
};
- auto magnitude = IfThenElse(x < Set(d, 1e-4f),
+ auto magnitude = IfThenElse(Lt(x, Set(d, 1e-4f)),
EvalRationalPolynomial(d, xto025, plo, qlo),
EvalRationalPolynomial(d, xto025, p, q));
return Or(AndNot(kSign, magnitude), original_sign);
@@ -268,10 +293,10 @@ class TF_SRGB {
-5.512498495e-02f, 6.521209011e-03f, 6.521209011e-03f,
6.521209011e-03f, 6.521209011e-03f,
};
- const V linear = x * Set(d, kLowDivInv);
+ const V linear = Mul(x, Set(d, kLowDivInv));
const V poly = EvalRationalPolynomial(d, x, p, q);
const V magnitude =
- IfThenElse(x > Set(d, kThreshSRGBToLinear), poly, linear);
+ IfThenElse(Gt(x, Set(d, kThreshSRGBToLinear)), poly, linear);
return Or(AndNot(kSign, magnitude), original_sign);
}
@@ -300,10 +325,10 @@ class TF_SRGB {
9.258482155e-01f, 9.258482155e-01f, 9.258482155e-01f, 9.258482155e-01f,
2.424867759e-02f, 2.424867759e-02f, 2.424867759e-02f, 2.424867759e-02f,
};
- const V linear = x * Set(d, kLowDiv);
+ const V linear = Mul(x, Set(d, kLowDiv));
const V poly = EvalRationalPolynomial(d, Sqrt(x), p, q);
const V magnitude =
- IfThenElse(x > Set(d, kThreshLinearToSRGB), poly, linear);
+ IfThenElse(Gt(x, Set(d, kThreshLinearToSRGB)), poly, linear);
return Or(AndNot(kSign, magnitude), original_sign);
}
@@ -320,8 +345,8 @@ V FastLinearToSRGB(D d, V v) {
const hwy::HWY_NAMESPACE::Rebind<uint32_t, D> du;
const hwy::HWY_NAMESPACE::Rebind<int32_t, D> di;
// Convert to 0.25 - 0.5 range.
- auto v025_05 =
- BitCast(d, (BitCast(du, v) | Set(du, 0x3e800000)) & Set(du, 0x3effffff));
+ auto v025_05 = BitCast(
+ d, And(Or(BitCast(du, v), Set(du, 0x3e800000)), Set(du, 0x3effffff)));
// third degree polynomial approximation between 0.25 and 0.5
// of 1.055/2^(7/2.4) * x^(1/2.4) * 0.5. A degree 4 polynomial only improves
// accuracy by about 3x.
@@ -353,7 +378,7 @@ V FastLinearToSRGB(D d, V v) {
using hwy::HWY_NAMESPACE::ShiftLeft;
using hwy::HWY_NAMESPACE::ShiftRight;
// Every lane of exp is now (if cast to byte) {0, 0, 0, <index for lookup>}.
- auto exp = ShiftRight<23>(BitCast(di, v)) - Set(di, 118);
+ auto exp = Sub(ShiftRight<23>(BitCast(di, v)), Set(di, 118));
auto pow25to18bits = TableLookupBytes(
LoadDup128(di,
reinterpret_cast<const int32_t*>(k2to512powers_25to18bits)),
@@ -366,9 +391,9 @@ V FastLinearToSRGB(D d, V v) {
// we take advantage of the fact that each table has its position 0 equal to
// 0.
// We can now just reassemble the float.
- auto mul =
- BitCast(d, ShiftLeft<18>(pow25to18bits) | ShiftLeft<10>(pow17to10bits) |
- Set(di, k2to512powers_basebits));
+ auto mul = BitCast(
+ d, Or(Or(ShiftLeft<18>(pow25to18bits), ShiftLeft<10>(pow17to10bits)),
+ Set(di, k2to512powers_basebits)));
#else
// Fallback for scalar.
uint32_t exp = ((BitCast(di, v).raw >> 23) - 118) & 0xf;
@@ -376,7 +401,7 @@ V FastLinearToSRGB(D d, V v) {
(k2to512powers_17to10bits[exp] << 10) |
k2to512powers_basebits));
#endif
- return IfThenElse(v < Set(d, 0.0031308f), v * Set(d, 12.92f),
+ return IfThenElse(Lt(v, Set(d, 0.0031308f)), Mul(v, Set(d, 12.92f)),
MulAdd(pow, mul, Set(d, -0.055)));
}
diff --git a/media/libjxl/src/lib/jxl/version.h.in b/media/libjxl/src/lib/jxl/version.h.in
new file mode 100644
index 0000000000..d077abec79
--- /dev/null
+++ b/media/libjxl/src/lib/jxl/version.h.in
@@ -0,0 +1,39 @@
+/* Copyright (c) the JPEG XL Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file.
+ */
+
+/** @addtogroup libjxl_common
+ * @{
+ * @file version.h
+ * @brief libjxl version information
+ */
+
+#ifndef JXL_VERSION_H_
+#define JXL_VERSION_H_
+
+#define JPEGXL_MAJOR_VERSION @JPEGXL_MAJOR_VERSION@ ///< JPEG XL Major version
+#define JPEGXL_MINOR_VERSION @JPEGXL_MINOR_VERSION@ ///< JPEG XL Minor version
+#define JPEGXL_PATCH_VERSION @JPEGXL_PATCH_VERSION@ ///< JPEG XL Patch version
+
+/** Can be used to conditionally compile code for a specific JXL version
+ * @param[maj] major version
+ * @param[min] minor version
+ *
+ * @code
+ * #if JPEGXL_NUMERIC_VERSION < JPEGXL_COMPUTE_NUMERIC_VERSION(0,8,0)
+ * // use old/deprecated api
+ * #else
+ * // use current api
+ * #endif
+ * @endcode
+ */
+#define JPEGXL_COMPUTE_NUMERIC_VERSION(major,minor,patch) ((major<<24) | (minor<<16) | (patch<<8) | 0)
+
+/* Numeric representation of the version */
+#define JPEGXL_NUMERIC_VERSION JPEGXL_COMPUTE_NUMERIC_VERSION(JPEGXL_MAJOR_VERSION,JPEGXL_MINOR_VERSION,JPEGXL_PATCH_VERSION)
+
+#endif /* JXL_VERSION_H_ */
+
+/** @}*/
diff --git a/media/libjxl/src/lib/jxl/xorshift128plus-inl.h b/media/libjxl/src/lib/jxl/xorshift128plus-inl.h
index 9430c175d4..a473d591f2 100644
--- a/media/libjxl/src/lib/jxl/xorshift128plus-inl.h
+++ b/media/libjxl/src/lib/jxl/xorshift128plus-inl.h
@@ -21,8 +21,10 @@ namespace HWY_NAMESPACE {
namespace {
// These templates are not found via ADL.
+using hwy::HWY_NAMESPACE::Add;
using hwy::HWY_NAMESPACE::ShiftLeft;
using hwy::HWY_NAMESPACE::ShiftRight;
+using hwy::HWY_NAMESPACE::Xor;
// Adapted from https://github.com/vpxyz/xorshift/blob/master/xorshift128plus/
// (MIT-license)
@@ -60,11 +62,11 @@ class Xorshift128Plus {
for (size_t i = 0; i < N; i += Lanes(d)) {
auto s1 = Load(d, s0_ + i);
const auto s0 = Load(d, s1_ + i);
- const auto bits = s1 + s0; // b, c
+ const auto bits = Add(s1, s0); // b, c
Store(s0, d, s0_ + i);
- s1 ^= ShiftLeft<23>(s1);
+ s1 = Xor(s1, ShiftLeft<23>(s1));
Store(bits, d, random_bits + i);
- s1 ^= s0 ^ ShiftRight<18>(s1) ^ ShiftRight<5>(s0);
+ s1 = Xor(s1, Xor(s0, Xor(ShiftRight<18>(s1), ShiftRight<5>(s0))));
Store(s1, d, s1_ + i);
}
#else
diff --git a/media/libjxl/src/lib/jxl/xorshift128plus_test.cc b/media/libjxl/src/lib/jxl/xorshift128plus_test.cc
index 4db86e7c71..7514f0e4a8 100644
--- a/media/libjxl/src/lib/jxl/xorshift128plus_test.cc
+++ b/media/libjxl/src/lib/jxl/xorshift128plus_test.cc
@@ -24,7 +24,9 @@ namespace jxl {
namespace HWY_NAMESPACE {
// These templates are not found via ADL.
+using hwy::HWY_NAMESPACE::Or;
using hwy::HWY_NAMESPACE::ShiftRight;
+using hwy::HWY_NAMESPACE::Sub;
// Define to nonzero in order to print the (new) golden outputs.
#define PRINT_RESULTS 0
@@ -310,8 +312,8 @@ void TestFloat() {
Load(du, reinterpret_cast<const uint32_t*>(batch) + i);
// 1.0 + 23 random mantissa bits = [1, 2)
const auto rand12 =
- BitCast(df, ShiftRight<9>(bits) | Set(du, 0x3F800000));
- const auto rand01 = rand12 - Set(df, 1.0f);
+ BitCast(df, Or(ShiftRight<9>(bits), Set(du, 0x3F800000)));
+ const auto rand01 = Sub(rand12, Set(df, 1.0f));
Store(rand01, df, lanes);
for (float lane : lanes) {
sum += lane;
diff --git a/media/libjxl/src/lib/jxl_extras.cmake b/media/libjxl/src/lib/jxl_extras.cmake
index bfb33f9b2f..fd801fc6aa 100644
--- a/media/libjxl/src/lib/jxl_extras.cmake
+++ b/media/libjxl/src/lib/jxl_extras.cmake
@@ -12,51 +12,75 @@ set(JPEGXL_EXTRAS_SOURCES
extras/dec/color_hints.h
extras/dec/decode.cc
extras/dec/decode.h
+ extras/dec/jxl.cc
+ extras/dec/jxl.h
extras/dec/pgx.cc
extras/dec/pgx.h
extras/dec/pnm.cc
extras/dec/pnm.h
+ extras/enc/encode.cc
+ extras/enc/encode.h
+ extras/enc/npy.cc
+ extras/enc/npy.h
extras/enc/pgx.cc
extras/enc/pgx.h
extras/enc/pnm.cc
extras/enc/pnm.h
+ extras/exif.cc
+ extras/exif.h
extras/hlg.cc
extras/hlg.h
extras/packed_image.h
extras/packed_image_convert.cc
extras/packed_image_convert.h
+ extras/render_hdr.cc
+ extras/render_hdr.h
extras/time.cc
extras/time.h
extras/tone_mapping.cc
extras/tone_mapping.h
)
-set(JPEGXL_EXTRAS_DEC_SOURCES
+set(JPEGXL_EXTRAS_CODEC_SOURCES
extras/dec/color_description.cc
extras/dec/color_description.h
extras/dec/color_hints.cc
extras/dec/color_hints.h
extras/dec/decode.cc
extras/dec/decode.h
+ extras/dec/jxl.cc
+ extras/dec/jxl.h
extras/dec/pgx.cc
extras/dec/pgx.h
extras/dec/pnm.cc
extras/dec/pnm.h
+ extras/enc/encode.cc
+ extras/enc/encode.h
+ extras/enc/npy.cc
+ extras/enc/npy.h
+ extras/enc/pgx.cc
+ extras/enc/pgx.h
+ extras/enc/pnm.cc
+ extras/enc/pnm.h
+ extras/exif.cc
+ extras/exif.h
extras/packed_image.h
+ extras/time.cc
+ extras/time.h
)
-add_library(jxl_extras_dec-obj OBJECT "${JPEGXL_EXTRAS_DEC_SOURCES}")
-target_compile_options(jxl_extras_dec-obj PRIVATE "${JPEGXL_INTERNAL_FLAGS}")
-target_compile_definitions(jxl_extras_dec-obj PRIVATE -DJXL_EXPORT=)
-set_property(TARGET jxl_extras_dec-obj PROPERTY POSITION_INDEPENDENT_CODE ON)
-target_include_directories(jxl_extras_dec-obj PUBLIC
+add_library(jxl_extras_codec-obj OBJECT "${JPEGXL_EXTRAS_CODEC_SOURCES}")
+target_compile_options(jxl_extras_codec-obj PRIVATE "${JPEGXL_INTERNAL_FLAGS}")
+target_compile_definitions(jxl_extras_codec-obj PRIVATE -DJXL_EXPORT=)
+set_property(TARGET jxl_extras_codec-obj PROPERTY POSITION_INDEPENDENT_CODE ON)
+target_include_directories(jxl_extras_codec-obj PUBLIC
${PROJECT_SOURCE_DIR}
${CMAKE_CURRENT_SOURCE_DIR}/include
${CMAKE_CURRENT_BINARY_DIR}/include
$<TARGET_PROPERTY:hwy,INTERFACE_INCLUDE_DIRECTORIES>
)
-set(JXL_EXTRAS_DEC_INTERNAL_LIBRARIES)
-set(JXL_EXTRAS_DEC_PUBLIC_COMPILE_DEFINITIONS)
+set(JXL_EXTRAS_CODEC_INTERNAL_LIBRARIES)
+set(JXL_EXTRAS_CODEC_PUBLIC_COMPILE_DEFINITIONS)
# We only define a static library for jxl_extras since it uses internal parts
# of jxl library which are not accessible from outside the library in the
@@ -68,18 +92,18 @@ set_property(TARGET jxl_extras-static PROPERTY POSITION_INDEPENDENT_CODE ON)
target_include_directories(jxl_extras-static PUBLIC "${PROJECT_SOURCE_DIR}")
target_link_libraries(jxl_extras-static PUBLIC
jxl-static
- jxl_extras_dec-static
+ jxl_threads-static
)
find_package(GIF 5.1)
if(GIF_FOUND)
- target_sources(jxl_extras_dec-obj PRIVATE
+ target_sources(jxl_extras_codec-obj PRIVATE
extras/dec/gif.cc
extras/dec/gif.h
)
- target_include_directories(jxl_extras_dec-obj PRIVATE "${GIF_INCLUDE_DIRS}")
- list(APPEND JXL_EXTRAS_DEC_INTERNAL_LIBRARIES ${GIF_LIBRARIES})
- list(APPEND JXL_EXTRAS_DEC_PUBLIC_DEFINITIONS -DJPEGXL_ENABLE_GIF=1)
+ target_include_directories(jxl_extras_codec-obj PRIVATE "${GIF_INCLUDE_DIRS}")
+ list(APPEND JXL_EXTRAS_CODEC_INTERNAL_LIBRARIES ${GIF_LIBRARIES})
+ list(APPEND JXL_EXTRAS_CODEC_PUBLIC_DEFINITIONS -DJPEGXL_ENABLE_GIF=1)
if(JPEGXL_DEP_LICENSE_DIR)
configure_file("${JPEGXL_DEP_LICENSE_DIR}/libgif-dev/copyright"
${PROJECT_BINARY_DIR}/LICENSE.libgif COPYONLY)
@@ -88,14 +112,18 @@ endif()
find_package(JPEG)
if(JPEG_FOUND)
- target_sources(jxl_extras_dec-obj PRIVATE
+ target_sources(jxl_extras_codec-obj PRIVATE
extras/dec/jpg.cc
extras/dec/jpg.h
+ extras/enc/jpg.cc
+ extras/enc/jpg.h
)
- target_include_directories(jxl_extras_dec-obj PRIVATE "${JPEG_INCLUDE_DIRS}")
- list(APPEND JXL_EXTRAS_DEC_INTERNAL_LIBRARIES ${JPEG_LIBRARIES})
- list(APPEND JXL_EXTRAS_DEC_PUBLIC_DEFINITIONS -DJPEGXL_ENABLE_JPEG=1)
+ target_include_directories(jxl_extras_codec-obj PRIVATE "${JPEG_INCLUDE_DIRS}")
+ list(APPEND JXL_EXTRAS_CODEC_INTERNAL_LIBRARIES ${JPEG_LIBRARIES})
+ list(APPEND JXL_EXTRAS_CODEC_PUBLIC_DEFINITIONS -DJPEGXL_ENABLE_JPEG=1)
target_sources(jxl_extras-static PRIVATE
+ extras/dec/jpg.cc
+ extras/dec/jpg.h
extras/enc/jpg.cc
extras/enc/jpg.h
)
@@ -112,14 +140,18 @@ if(NOT JPEGXL_BUNDLE_LIBPNG)
find_package(PNG)
endif()
if(PNG_FOUND)
- target_sources(jxl_extras_dec-obj PRIVATE
+ target_sources(jxl_extras_codec-obj PRIVATE
extras/dec/apng.cc
extras/dec/apng.h
+ extras/enc/apng.cc
+ extras/enc/apng.h
)
- target_include_directories(jxl_extras_dec-obj PRIVATE "${PNG_INCLUDE_DIRS}")
- list(APPEND JXL_EXTRAS_DEC_INTERNAL_LIBRARIES ${PNG_LIBRARIES})
- list(APPEND JXL_EXTRAS_DEC_PUBLIC_DEFINITIONS -DJPEGXL_ENABLE_APNG=1)
+ target_include_directories(jxl_extras_codec-obj PRIVATE "${PNG_INCLUDE_DIRS}")
+ list(APPEND JXL_EXTRAS_CODEC_INTERNAL_LIBRARIES ${PNG_LIBRARIES})
+ list(APPEND JXL_EXTRAS_CODEC_PUBLIC_DEFINITIONS -DJPEGXL_ENABLE_APNG=1)
target_sources(jxl_extras-static PRIVATE
+ extras/dec/apng.cc
+ extras/dec/apng.h
extras/enc/apng.cc
extras/enc/apng.h
)
@@ -138,14 +170,18 @@ endif ()
if (JPEGXL_ENABLE_OPENEXR)
pkg_check_modules(OpenEXR IMPORTED_TARGET OpenEXR)
if (OpenEXR_FOUND)
- target_sources(jxl_extras_dec-obj PRIVATE
+ target_sources(jxl_extras_codec-obj PRIVATE
extras/dec/exr.cc
extras/dec/exr.h
+ extras/enc/exr.cc
+ extras/enc/exr.h
)
- list(APPEND JXL_EXTRAS_DEC_PUBLIC_DEFINITIONS -DJPEGXL_ENABLE_EXR=1)
- target_include_directories(jxl_extras_dec-obj PRIVATE "${OpenEXR_INCLUDE_DIRS}")
- list(APPEND JXL_EXTRAS_DEC_INTERNAL_LIBRARIES PkgConfig::OpenEXR)
+ list(APPEND JXL_EXTRAS_CODEC_PUBLIC_DEFINITIONS -DJPEGXL_ENABLE_EXR=1)
+ target_include_directories(jxl_extras_codec-obj PRIVATE "${OpenEXR_INCLUDE_DIRS}")
+ list(APPEND JXL_EXTRAS_CODEC_INTERNAL_LIBRARIES PkgConfig::OpenEXR)
target_sources(jxl_extras-static PRIVATE
+ extras/dec/exr.cc
+ extras/dec/exr.h
extras/enc/exr.cc
extras/enc/exr.h
)
@@ -159,27 +195,25 @@ if (OpenEXR_FOUND)
# Actually those flags counteract the ones set in JPEGXL_INTERNAL_FLAGS.
if (NOT WIN32)
set_source_files_properties(extras/dec/exr.cc extras/enc/exr.cc PROPERTIES COMPILE_FLAGS -fexceptions)
- if (${CMAKE_CXX_COMPILER_ID} MATCHES "Clang")
+ if (CMAKE_CXX_COMPILER_ID MATCHES "Clang")
set_source_files_properties(extras/dec/exr.cc extras/enc/exr.cc PROPERTIES COMPILE_FLAGS -fcxx-exceptions)
endif()
endif()
endif() # OpenEXR_FOUND
endif() # JPEGXL_ENABLE_OPENEXR
-target_compile_definitions(jxl_extras_dec-obj PRIVATE ${JXL_EXTRAS_DEC_PUBLIC_DEFINITIONS})
+target_compile_definitions(jxl_extras_codec-obj PRIVATE ${JXL_EXTRAS_CODEC_PUBLIC_DEFINITIONS})
### Static library.
-add_library(jxl_extras_dec-static STATIC $<TARGET_OBJECTS:jxl_extras_dec-obj>)
-target_compile_definitions(jxl_extras_dec-static PUBLIC ${JXL_EXTRAS_DEC_PUBLIC_DEFINITIONS})
-target_link_libraries(jxl_extras_dec-static PRIVATE ${JXL_EXTRAS_DEC_INTERNAL_LIBRARIES})
+add_library(jxl_extras_codec-static STATIC $<TARGET_OBJECTS:jxl_extras_codec-obj>)
+target_compile_definitions(jxl_extras_codec-static PUBLIC ${JXL_EXTRAS_CODEC_PUBLIC_DEFINITIONS})
+target_link_libraries(jxl_extras_codec-static PRIVATE ${JXL_EXTRAS_CODEC_INTERNAL_LIBRARIES} jxl)
### Shared library.
-if (((NOT DEFINED "${TARGET_SUPPORTS_SHARED_LIBS}") OR
- TARGET_SUPPORTS_SHARED_LIBS) AND NOT JPEGXL_STATIC AND BUILD_SHARED_LIBS)
-add_library(jxl_extras_dec SHARED $<TARGET_OBJECTS:jxl_extras_dec-obj>)
-target_compile_definitions(jxl_extras_dec PUBLIC ${JXL_EXTRAS_DEC_PUBLIC_DEFINITIONS})
-target_link_libraries(jxl_extras_dec PRIVATE ${JXL_EXTRAS_DEC_INTERNAL_LIBRARIES} jxl)
+if (BUILD_SHARED_LIBS)
+add_library(jxl_extras_codec SHARED $<TARGET_OBJECTS:jxl_extras_codec-obj>)
+target_compile_definitions(jxl_extras_codec PUBLIC ${JXL_EXTRAS_CODEC_PUBLIC_DEFINITIONS})
+target_link_libraries(jxl_extras_codec PRIVATE ${JXL_EXTRAS_CODEC_INTERNAL_LIBRARIES} jxl)
else()
-add_library(jxl_extras_dec ALIAS jxl_extras_dec-static)
-endif() # TARGET_SUPPORTS_SHARED_LIBS AND NOT JPEGXL_STATIC AND
- # BUILD_SHARED_LIBS
+add_library(jxl_extras_codec ALIAS jxl_extras_codec-static)
+endif() # BUILD_SHARED_LIBS
diff --git a/media/libjxl/src/lib/jxl_tests.cmake b/media/libjxl/src/lib/jxl_tests.cmake
index 3fcee6d205..c858ae97b9 100644
--- a/media/libjxl/src/lib/jxl_tests.cmake
+++ b/media/libjxl/src/lib/jxl_tests.cmake
@@ -61,6 +61,7 @@ set(TEST_FILES
### Files before this line are handled by build_cleaner.py
# TODO(deymo): Move this to tools/
../tools/box/box_test.cc
+ ../tools/djxl_fuzzer_test.cc
)
# Test-only library code.
@@ -72,6 +73,7 @@ set(TESTLIB_FILES
jxl/dec_transforms_testonly.h
jxl/fake_parallel_runner_testonly.h
jxl/image_test_utils.h
+ jxl/test_image.h
jxl/test_utils.h
jxl/testdata.h
)
@@ -96,7 +98,11 @@ file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/tests)
foreach (TESTFILE IN LISTS TEST_FILES)
# The TESTNAME is the name without the extension or directory.
get_filename_component(TESTNAME ${TESTFILE} NAME_WE)
- add_executable(${TESTNAME} ${TESTFILE})
+ if(TESTFILE STREQUAL ../tools/djxl_fuzzer_test.cc)
+ add_executable(${TESTNAME} ${TESTFILE} ../tools/djxl_fuzzer.cc)
+ else()
+ add_executable(${TESTNAME} ${TESTFILE})
+ endif()
if(JPEGXL_EMSCRIPTEN)
# The emscripten linking step takes too much memory and crashes during the
# wasm-opt step when using -O2 optimization level
@@ -105,6 +111,10 @@ foreach (TESTFILE IN LISTS TEST_FILES)
-s USE_LIBPNG=1 \
-s TOTAL_MEMORY=1536MB \
-s SINGLE_FILE=1 \
+ -s PROXY_TO_PTHREAD \
+ -s EXIT_RUNTIME=1 \
+ -s USE_PTHREADS=1 \
+ -s NODERAWFS=1 \
")
endif()
target_compile_options(${TESTNAME} PRIVATE
@@ -115,8 +125,6 @@ foreach (TESTFILE IN LISTS TEST_FILES)
)
target_link_libraries(${TESTNAME}
box
- jxl-static
- jxl_threads-static
jxl_extras-static
jxl_testlib-static
gmock
@@ -125,10 +133,10 @@ foreach (TESTFILE IN LISTS TEST_FILES)
)
# Output test targets in the test directory.
set_target_properties(${TESTNAME} PROPERTIES PREFIX "tests/")
- if (WIN32 AND ${CMAKE_CXX_COMPILER_ID} STREQUAL "Clang")
+ if (WIN32 AND CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
set_target_properties(${TESTNAME} PROPERTIES COMPILE_FLAGS "-Wno-error")
endif ()
- if(${CMAKE_VERSION} VERSION_LESS "3.10.3")
+ if(CMAKE_VERSION VERSION_LESS "3.10.3")
gtest_discover_tests(${TESTNAME} TIMEOUT 240)
else ()
gtest_discover_tests(${TESTNAME} DISCOVERY_TIMEOUT 240)
diff --git a/media/libjxl/src/lib/jxl_threads.cmake b/media/libjxl/src/lib/jxl_threads.cmake
index d0d2d00560..006e71eb68 100644
--- a/media/libjxl/src/lib/jxl_threads.cmake
+++ b/media/libjxl/src/lib/jxl_threads.cmake
@@ -40,7 +40,7 @@ set_target_properties(${_target} PROPERTIES
# Always install the library as jxl_threads.{a,so} file without the "-static"
# suffix, except in Windows.
-if (NOT WIN32)
+if (NOT WIN32 OR MINGW)
set_target_properties(${_target} PROPERTIES OUTPUT_NAME "jxl_threads")
endif()
install(TARGETS ${_target}
@@ -63,8 +63,7 @@ target_compile_definitions(jxl_threads-static
### Public shared library.
-if (((NOT DEFINED "${TARGET_SUPPORTS_SHARED_LIBS}") OR
- TARGET_SUPPORTS_SHARED_LIBS) AND NOT JPEGXL_STATIC AND BUILD_SHARED_LIBS)
+if (BUILD_SHARED_LIBS)
add_library(jxl_threads SHARED ${JPEGXL_THREADS_SOURCES})
_set_jxl_threads(jxl_threads)
@@ -104,11 +103,24 @@ add_library(jxl_threads ALIAS jxl_threads-static)
generate_export_header(jxl_threads-static
BASE_NAME JXL_THREADS
EXPORT_FILE_NAME include/jxl/jxl_threads_export.h)
-endif() # TARGET_SUPPORTS_SHARED_LIBS AND NOT JPEGXL_STATIC AND
- # BUILD_SHARED_LIBS
+endif() # BUILD_SHARED_LIBS
### Add a pkg-config file for libjxl_threads.
+
+# Allow adding prefix if CMAKE_INSTALL_INCLUDEDIR not absolute.
+if(IS_ABSOLUTE "${CMAKE_INSTALL_INCLUDEDIR}")
+ set(PKGCONFIG_TARGET_INCLUDES "${CMAKE_INSTALL_INCLUDEDIR}")
+else()
+ set(PKGCONFIG_TARGET_INCLUDES "\${prefix}/${CMAKE_INSTALL_INCLUDEDIR}")
+endif()
+# Allow adding prefix if CMAKE_INSTALL_LIBDIR not absolute.
+if(IS_ABSOLUTE "${CMAKE_INSTALL_LIBDIR}")
+ set(PKGCONFIG_TARGET_LIBS "${CMAKE_INSTALL_LIBDIR}")
+else()
+ set(PKGCONFIG_TARGET_LIBS "\${exec_prefix}/${CMAKE_INSTALL_LIBDIR}")
+endif()
+
set(JPEGXL_THREADS_LIBRARY_REQUIRES "")
configure_file("${CMAKE_CURRENT_SOURCE_DIR}/threads/libjxl_threads.pc.in"
"libjxl_threads.pc" @ONLY)
diff --git a/media/libjxl/src/lib/lib.gni b/media/libjxl/src/lib/lib.gni
index 68afda3a86..2914de9918 100644
--- a/media/libjxl/src/lib/lib.gni
+++ b/media/libjxl/src/lib/lib.gni
@@ -80,8 +80,12 @@ libjxl_dec_sources = [
"jxl/compressed_dc.cc",
"jxl/compressed_dc.h",
"jxl/convolve-inl.h",
- "jxl/convolve.cc",
"jxl/convolve.h",
+ "jxl/convolve_separable5.cc",
+ "jxl/convolve_separable7.cc",
+ "jxl/convolve_slow.cc",
+ "jxl/convolve_symmetric3.cc",
+ "jxl/convolve_symmetric5.cc",
"jxl/dct-inl.h",
"jxl/dct_block-inl.h",
"jxl/dct_scales.cc",
@@ -108,9 +112,9 @@ libjxl_dec_sources = [
"jxl/dec_modular.h",
"jxl/dec_noise.cc",
"jxl/dec_noise.h",
- "jxl/dec_params.h",
"jxl/dec_patch_dictionary.cc",
"jxl/dec_patch_dictionary.h",
+ "jxl/dec_tone_mapping-inl.h",
"jxl/dec_transforms-inl.h",
"jxl/dec_xyb-inl.h",
"jxl/dec_xyb.cc",
@@ -124,7 +128,6 @@ libjxl_dec_sources = [
"jxl/entropy_coder.h",
"jxl/epf.cc",
"jxl/epf.h",
- "jxl/exif.cc",
"jxl/exif.h",
"jxl/fast_dct-inl.h",
"jxl/fast_dct.cc",
@@ -216,6 +219,8 @@ libjxl_dec_sources = [
"jxl/render_pipeline/stage_chroma_upsampling.h",
"jxl/render_pipeline/stage_epf.cc",
"jxl/render_pipeline/stage_epf.h",
+ "jxl/render_pipeline/stage_from_linear.cc",
+ "jxl/render_pipeline/stage_from_linear.h",
"jxl/render_pipeline/stage_gaborish.cc",
"jxl/render_pipeline/stage_gaborish.h",
"jxl/render_pipeline/stage_noise.cc",
@@ -226,6 +231,10 @@ libjxl_dec_sources = [
"jxl/render_pipeline/stage_splines.h",
"jxl/render_pipeline/stage_spot.cc",
"jxl/render_pipeline/stage_spot.h",
+ "jxl/render_pipeline/stage_to_linear.cc",
+ "jxl/render_pipeline/stage_to_linear.h",
+ "jxl/render_pipeline/stage_tone_mapping.cc",
+ "jxl/render_pipeline/stage_tone_mapping.h",
"jxl/render_pipeline/stage_upsampling.cc",
"jxl/render_pipeline/stage_upsampling.h",
"jxl/render_pipeline/stage_write.cc",
@@ -251,8 +260,6 @@ libjxl_enc_sources = [
"jxl/butteraugli/butteraugli.cc",
"jxl/butteraugli/butteraugli.h",
"jxl/butteraugli_wrapper.cc",
- "jxl/dec_file.cc",
- "jxl/dec_file.h",
"jxl/enc_ac_strategy.cc",
"jxl/enc_ac_strategy.h",
"jxl/enc_adaptive_quantization.cc",
@@ -288,7 +295,6 @@ libjxl_enc_sources = [
"jxl/enc_entropy_coder.h",
"jxl/enc_external_image.cc",
"jxl/enc_external_image.h",
- "jxl/enc_fast_heuristics.cc",
"jxl/enc_file.cc",
"jxl/enc_file.h",
"jxl/enc_frame.cc",
@@ -431,6 +437,7 @@ libjxl_testlib_sources = [
"jxl/dec_transforms_testonly.h",
"jxl/fake_parallel_runner_testonly.h",
"jxl/image_test_utils.h",
+ "jxl/test_image.h",
"jxl/test_utils.h",
"jxl/testdata.h",
]
@@ -444,19 +451,29 @@ libjxl_extras_sources = [
"extras/dec/color_hints.h",
"extras/dec/decode.cc",
"extras/dec/decode.h",
+ "extras/dec/jxl.cc",
+ "extras/dec/jxl.h",
"extras/dec/pgx.cc",
"extras/dec/pgx.h",
"extras/dec/pnm.cc",
"extras/dec/pnm.h",
+ "extras/enc/encode.cc",
+ "extras/enc/encode.h",
+ "extras/enc/npy.cc",
+ "extras/enc/npy.h",
"extras/enc/pgx.cc",
"extras/enc/pgx.h",
"extras/enc/pnm.cc",
"extras/enc/pnm.h",
+ "extras/exif.cc",
+ "extras/exif.h",
"extras/hlg.cc",
"extras/hlg.h",
"extras/packed_image.h",
"extras/packed_image_convert.cc",
"extras/packed_image_convert.h",
+ "extras/render_hdr.cc",
+ "extras/render_hdr.h",
"extras/time.cc",
"extras/time.h",
"extras/tone_mapping.cc",
diff --git a/media/libjxl/src/lib/profiler/tsc_timer.h b/media/libjxl/src/lib/profiler/tsc_timer.h
index e3e1fb6ba7..9387f4195b 100644
--- a/media/libjxl/src/lib/profiler/tsc_timer.h
+++ b/media/libjxl/src/lib/profiler/tsc_timer.h
@@ -19,6 +19,9 @@
#ifndef NOMINMAX
#define NOMINMAX
#endif // NOMINMAX
+#ifndef NOGDI
+#define NOGDI
+#endif // NOGDI
#include <windows.h>
// Undef macros to avoid collisions
#undef LoadFence
diff --git a/media/libjxl/src/lib/threads/libjxl_threads.pc.in b/media/libjxl/src/lib/threads/libjxl_threads.pc.in
index 8a3275cf1c..50b937a840 100644
--- a/media/libjxl/src/lib/threads/libjxl_threads.pc.in
+++ b/media/libjxl/src/lib/threads/libjxl_threads.pc.in
@@ -1,7 +1,7 @@
prefix=@CMAKE_INSTALL_PREFIX@
exec_prefix=${prefix}
-libdir=${exec_prefix}/@CMAKE_INSTALL_LIBDIR@
-includedir=${prefix}/@CMAKE_INSTALL_INCLUDEDIR@
+libdir=@PKGCONFIG_TARGET_LIBS@
+includedir=@PKGCONFIG_TARGET_INCLUDES@
Name: libjxl_threads
Description: JPEG XL multi-thread runner using std::threads.
@@ -10,3 +10,4 @@ Requires.private: @JPEGXL_THREADS_LIBRARY_REQUIRES@
Libs: -L${libdir} -ljxl_threads
Libs.private: -lm
Cflags: -I${includedir}
+Cflags.private: -DJXL_THREADS_STATIC_DEFINE
diff --git a/media/libjxl/src/lib/threads/thread_parallel_runner_internal.cc b/media/libjxl/src/lib/threads/thread_parallel_runner_internal.cc
index e868622e9b..2b05ad9928 100644
--- a/media/libjxl/src/lib/threads/thread_parallel_runner_internal.cc
+++ b/media/libjxl/src/lib/threads/thread_parallel_runner_internal.cc
@@ -173,14 +173,8 @@ void ThreadParallelRunner::ThreadFunc(ThreadParallelRunner* self,
}
ThreadParallelRunner::ThreadParallelRunner(const int num_worker_threads)
-#if defined(__EMSCRIPTEN__)
- : num_worker_threads_(0), num_threads_(1) {
- // TODO(eustas): find out if pthreads would work for us.
- (void)num_worker_threads;
-#else
: num_worker_threads_(num_worker_threads),
num_threads_(std::max(num_worker_threads, 1)) {
-#endif
PROFILER_ZONE("ThreadParallelRunner ctor");
threads_.reserve(num_worker_threads_);
diff --git a/media/libjxl/src/lib/threads/thread_parallel_runner_test.cc b/media/libjxl/src/lib/threads/thread_parallel_runner_test.cc
index c68b645df2..2293b5ceb4 100644
--- a/media/libjxl/src/lib/threads/thread_parallel_runner_test.cc
+++ b/media/libjxl/src/lib/threads/thread_parallel_runner_test.cc
@@ -3,6 +3,8 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
+#include <atomic>
+
#include "gtest/gtest.h"
#include "lib/jxl/base/data_parallel.h"
#include "lib/jxl/base/thread_pool_internal.h"
diff --git a/media/libjxl/src/plugins/gdk-pixbuf/CMakeLists.txt b/media/libjxl/src/plugins/gdk-pixbuf/CMakeLists.txt
index 1d595008a5..e56d312b71 100644
--- a/media/libjxl/src/plugins/gdk-pixbuf/CMakeLists.txt
+++ b/media/libjxl/src/plugins/gdk-pixbuf/CMakeLists.txt
@@ -33,7 +33,7 @@ install(TARGETS pixbufloader-jxl LIBRARY DESTINATION "${GDK_PIXBUF_MODULEDIR}")
# /usr/share/thumbnailers/gdk-pixbuf-thumbnailer.thumbnailer
install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/jxl.thumbnailer DESTINATION share/thumbnailers/)
-if(BUILD_TESTING AND NOT MINGW)
+if(BUILD_TESTING AND NOT CMAKE_CROSSCOMPILING)
pkg_check_modules(Gdk IMPORTED_TARGET gdk-2.0)
if (Gdk_FOUND)
# Test for loading a .jxl file using the pixbufloader library via GDK. This
@@ -71,7 +71,7 @@ if(BUILD_TESTING AND NOT MINGW)
COMMAND
${XVFB_PROGRAM_PREFIX} $<TARGET_FILE:pixbufloader_test>
"${CMAKE_CURRENT_SOURCE_DIR}/loaders_test.cache"
- "${CMAKE_SOURCE_DIR}/third_party/testdata/jxl/blending/cropped_traffic_light.jxl"
+ "${CMAKE_SOURCE_DIR}/testdata/jxl/blending/cropped_traffic_light.jxl"
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
)
set_tests_properties(pixbufloader_test_jxl PROPERTIES SKIP_RETURN_CODE 254)
diff --git a/media/libjxl/src/third_party/CMakeLists.txt b/media/libjxl/src/third_party/CMakeLists.txt
index 11cc4ab876..50cc72c92a 100644
--- a/media/libjxl/src/third_party/CMakeLists.txt
+++ b/media/libjxl/src/third_party/CMakeLists.txt
@@ -12,79 +12,17 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-# Enable tests in third_party/ as well.
-enable_testing()
-include(CTest)
-
-if(BUILD_TESTING)
-# Add GTest from source and alias it to what the find_package(GTest) workflow
-# defines. Omitting googletest/ directory would require it to be available in
-# the base system instead, but it would work just fine. This makes packages
-# using GTest and calling find_package(GTest) actually work.
-if (EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/googletest/CMakeLists.txt" AND
- NOT JPEGXL_FORCE_SYSTEM_GTEST)
- add_subdirectory(googletest EXCLUDE_FROM_ALL)
-
- set(GTEST_ROOT "${CMAKE_CURRENT_SOURCE_DIR}/googletest/googletest")
- set(GTEST_INCLUDE_DIR "$<TARGET_PROPERTY:INCLUDE_DIRECTORIES,gtest>"
- CACHE STRING "")
- set(GMOCK_INCLUDE_DIR "$<TARGET_PROPERTY:INCLUDE_DIRECTORIES,gmock>")
- set(GTEST_LIBRARY "$<TARGET_FILE:gtest>")
- set(GTEST_MAIN_LIBRARY "$<TARGET_FILE:gtest_main>")
- add_library(GTest::GTest ALIAS gtest)
- add_library(GTest::Main ALIAS gtest_main)
-
- set_target_properties(gtest PROPERTIES POSITION_INDEPENDENT_CODE TRUE)
- set_target_properties(gmock PROPERTIES POSITION_INDEPENDENT_CODE TRUE)
- set_target_properties(gtest_main PROPERTIES POSITION_INDEPENDENT_CODE TRUE)
- set_target_properties(gmock_main PROPERTIES POSITION_INDEPENDENT_CODE TRUE)
-
- # googletest doesn't compile clean with clang-cl (-Wundef)
- if (WIN32 AND ${CMAKE_CXX_COMPILER_ID} STREQUAL "Clang")
- set_target_properties(gtest PROPERTIES COMPILE_FLAGS "-Wno-error")
- set_target_properties(gmock PROPERTIES COMPILE_FLAGS "-Wno-error")
- set_target_properties(gtest_main PROPERTIES COMPILE_FLAGS "-Wno-error")
- set_target_properties(gmock_main PROPERTIES COMPILE_FLAGS "-Wno-error")
- endif ()
- configure_file("${CMAKE_CURRENT_SOURCE_DIR}/googletest/LICENSE"
- ${PROJECT_BINARY_DIR}/LICENSE.googletest COPYONLY)
-else()
- if(JPEGXL_DEP_LICENSE_DIR)
- configure_file("${JPEGXL_DEP_LICENSE_DIR}/googletest/copyright"
- ${PROJECT_BINARY_DIR}/LICENSE.googletest COPYONLY)
- endif() # JPEGXL_DEP_LICENSE_DIR
+if((SANITIZER STREQUAL "asan") OR (SANITIZER STREQUAL "msan"))
+ set(BUILD_TESTING OFF)
endif()
-find_package(GTest)
-if (NOT GTEST_FOUND)
- set(BUILD_TESTING OFF CACHE BOOL "Build tests" FORCE)
- message(SEND_ERROR "GTest not found. Install googletest package "
- "(libgtest-dev) in the system or download googletest to "
- "third_party/googletest from https://github.com/google/googletest ."
- "To disable tests instead re-run cmake with -DBUILD_TESTING=OFF.")
-endif() # NOT GTEST_FOUND
-
-# Look for gmock in the system too.
-if (NOT DEFINED GMOCK_INCLUDE_DIR)
- find_path(
- GMOCK_INCLUDE_DIR "gmock/gmock.h"
- HINTS ${GTEST_INCLUDE_DIRS})
- if ("${GMOCK_INCLUDE_DIR}" STREQUAL "GMOCK_INCLUDE_DIR-NOTFOUND")
- set(BUILD_TESTING OFF CACHE BOOL "Build tests" FORCE)
- message(SEND_ERROR "GMock not found. Install googletest package "
- "(libgmock-dev) in the system or download googletest to "
- "third_party/googletest from https://github.com/google/googletest ."
- "To disable tests instead re-run cmake with -DBUILD_TESTING=OFF.")
- else()
- message(STATUS "Found GMock: ${GMOCK_INCLUDE_DIR}")
- endif() # GMOCK_INCLUDE_DIR-NOTFOUND
-endif() # NOT DEFINED GMOCK_INCLUDE_DIR
-endif() # BUILD_TESTING
# Highway
set(HWY_SYSTEM_GTEST ON CACHE INTERNAL "")
set(HWY_FORCE_STATIC_LIBS ON CACHE INTERNAL "")
+set(HWY_ENABLE_CONTRIB OFF CACHE INTERNAL "")
+set(HWY_ENABLE_EXAMPLES OFF CACHE INTERNAL "")
if((SANITIZER STREQUAL "asan") OR (SANITIZER STREQUAL "msan"))
- set(HWY_EXAMPLES_TESTS_INSTALL OFF CACHE INTERNAL "")
+ set(HWY_ENABLE_INSTALL OFF CACHE INTERNAL "")
endif()
if (EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/highway/CMakeLists.txt" AND
NOT JPEGXL_FORCE_SYSTEM_HWY)
@@ -123,7 +61,11 @@ if (NOT EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/brotli/c/include/brotli/decode.h" OR
else()
# Compile brotli from sources.
set(BROTLI_DISABLE_TESTS ON CACHE STRING "Disable Brotli tests")
- add_subdirectory(brotli EXCLUDE_FROM_ALL)
+ # Override default "no-install" policy.
+ if((NOT SANITIZER STREQUAL "asan") AND (NOT SANITIZER STREQUAL "msan"))
+ set(BROTLI_BUNDLED_MODE OFF CACHE INTERNAL "")
+ endif()
+ add_subdirectory(brotli)
configure_file("${CMAKE_CURRENT_SOURCE_DIR}/brotli/LICENSE"
${PROJECT_BINARY_DIR}/LICENSE.brotli COPYONLY)
if(BROTLI_EMSCRIPTEN)
@@ -157,37 +99,6 @@ if (JPEGXL_ENABLE_VIEWERS OR NOT JPEGXL_ENABLE_SKCMS)
endif()
endif()
-# gflags
-if(JPEGXL_ENABLE_TOOLS)
- if (JPEGXL_BUNDLE_GFLAGS)
- # Compile gflags from sources.
- set(GFLAGS_BUILD_TESTING OFF)
- add_subdirectory(gflags)
- add_library(jxl_gflags ALIAS gflags_nothreads_static)
- configure_file("${CMAKE_CURRENT_SOURCE_DIR}/gflags/COPYING.txt"
- ${PROJECT_BINARY_DIR}/LICENSE.gflags COPYONLY)
- else()
- # Use sytem libgflags-dev
- find_package(gflags)
- if (NOT gflags_FOUND)
- message(FATAL_ERROR
- "Gflags not found, install libgflags-dev or download gflags source to"
- "third_party/gflags from https://github.com/gflags/gflags. You can use"
- " ${PROJECT_SOURCE_DIR}/deps.sh to download the dependency.")
- endif()
- if(JPEGXL_DEP_LICENSE_DIR)
- configure_file("${JPEGXL_DEP_LICENSE_DIR}/libgflags-dev/copyright"
- ${PROJECT_BINARY_DIR}/LICENSE.gflags COPYONLY)
- endif() # JPEGXL_DEP_LICENSE_DIR
- add_library(jxl_gflags INTERFACE)
- if(JPEGXL_STATIC)
- target_link_libraries(jxl_gflags INTERFACE gflags_static)
- else()
- target_link_libraries(jxl_gflags INTERFACE gflags)
- endif()
- endif()
-endif()
-
# libpng
if (JPEGXL_EMSCRIPTEN)
if (NOT EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/libpng/CMakeLists.txt")
@@ -244,4 +155,3 @@ if (JPEGXL_ENABLE_SJPEG)
configure_file("${CMAKE_CURRENT_SOURCE_DIR}/sjpeg/COPYING"
${PROJECT_BINARY_DIR}/LICENSE.sjpeg COPYONLY)
endif ()
-
diff --git a/media/libjxl/src/third_party/lcms2.cmake b/media/libjxl/src/third_party/lcms2.cmake
index c33f877659..c4551de862 100644
--- a/media/libjxl/src/third_party/lcms2.cmake
+++ b/media/libjxl/src/third_party/lcms2.cmake
@@ -44,7 +44,7 @@ add_library(lcms2 STATIC EXCLUDE_FROM_ALL
target_include_directories(lcms2
PUBLIC "${CMAKE_CURRENT_LIST_DIR}/lcms/include")
# This warning triggers with gcc-8.
-if (${CMAKE_C_COMPILER_ID} MATCHES "GNU")
+if (CMAKE_C_COMPILER_ID MATCHES "GNU")
target_compile_options(lcms2
PRIVATE
# gcc-only flags.
diff --git a/media/libjxl/src/third_party/testing.cmake b/media/libjxl/src/third_party/testing.cmake
new file mode 100644
index 0000000000..9f49737e55
--- /dev/null
+++ b/media/libjxl/src/third_party/testing.cmake
@@ -0,0 +1,83 @@
+# Copyright (c) the JPEG XL Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Enable tests in third_party/ as well.
+enable_testing()
+include(CTest)
+
+set(SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/third_party")
+
+if(BUILD_TESTING)
+# Add GTest from source and alias it to what the find_package(GTest) workflow
+# defines. Omitting googletest/ directory would require it to be available in
+# the base system instead, but it would work just fine. This makes packages
+# using GTest and calling find_package(GTest) actually work.
+if (EXISTS "${SOURCE_DIR}/googletest/CMakeLists.txt" AND
+ NOT JPEGXL_FORCE_SYSTEM_GTEST)
+ add_subdirectory(third_party/googletest EXCLUDE_FROM_ALL)
+
+ set(GTEST_ROOT "${SOURCE_DIR}/googletest/googletest")
+ set(GTEST_INCLUDE_DIR "$<TARGET_PROPERTY:INCLUDE_DIRECTORIES,gtest>"
+ CACHE STRING "")
+ set(GMOCK_INCLUDE_DIR "$<TARGET_PROPERTY:INCLUDE_DIRECTORIES,gmock>")
+ set(GTEST_LIBRARY "$<TARGET_FILE:gtest>")
+ set(GTEST_MAIN_LIBRARY "$<TARGET_FILE:gtest_main>")
+ add_library(GTest::GTest ALIAS gtest)
+ add_library(GTest::Main ALIAS gtest_main)
+
+ set_target_properties(gtest PROPERTIES POSITION_INDEPENDENT_CODE TRUE)
+ set_target_properties(gmock PROPERTIES POSITION_INDEPENDENT_CODE TRUE)
+ set_target_properties(gtest_main PROPERTIES POSITION_INDEPENDENT_CODE TRUE)
+ set_target_properties(gmock_main PROPERTIES POSITION_INDEPENDENT_CODE TRUE)
+
+ # googletest doesn't compile clean with clang-cl (-Wundef)
+ if (WIN32 AND CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
+ set_target_properties(gtest PROPERTIES COMPILE_FLAGS "-Wno-error")
+ set_target_properties(gmock PROPERTIES COMPILE_FLAGS "-Wno-error")
+ set_target_properties(gtest_main PROPERTIES COMPILE_FLAGS "-Wno-error")
+ set_target_properties(gmock_main PROPERTIES COMPILE_FLAGS "-Wno-error")
+ endif ()
+ configure_file("${SOURCE_DIR}/googletest/LICENSE"
+ ${PROJECT_BINARY_DIR}/LICENSE.googletest COPYONLY)
+else()
+ if(JPEGXL_DEP_LICENSE_DIR)
+ configure_file("${JPEGXL_DEP_LICENSE_DIR}/googletest/copyright"
+ ${PROJECT_BINARY_DIR}/LICENSE.googletest COPYONLY)
+ endif() # JPEGXL_DEP_LICENSE_DIR
+endif()
+find_package(GTest)
+if (NOT GTEST_FOUND)
+ set(BUILD_TESTING OFF CACHE BOOL "Build tests" FORCE)
+ message(SEND_ERROR "GTest not found. Install googletest package "
+ "(libgtest-dev) in the system or download googletest to "
+ "third_party/googletest from https://github.com/google/googletest ."
+ "To disable tests instead re-run cmake with -DBUILD_TESTING=OFF.")
+endif() # NOT GTEST_FOUND
+
+# Look for gmock in the system too.
+if (NOT DEFINED GMOCK_INCLUDE_DIR)
+ find_path(
+ GMOCK_INCLUDE_DIR "gmock/gmock.h"
+ HINTS ${GTEST_INCLUDE_DIRS})
+ if (NOT GMOCK_INCLUDE_DIR)
+ set(BUILD_TESTING OFF CACHE BOOL "Build tests" FORCE)
+ message(SEND_ERROR "GMock not found. Install googletest package "
+ "(libgmock-dev) in the system or download googletest to "
+ "third_party/googletest from https://github.com/google/googletest ."
+ "To disable tests instead re-run cmake with -DBUILD_TESTING=OFF.")
+ else()
+ message(STATUS "Found GMock: ${GMOCK_INCLUDE_DIR}")
+ endif() # NOT GMOCK_INCLUDE_DIR
+endif() # NOT DEFINED GMOCK_INCLUDE_DIR
+endif() # BUILD_TESTING
diff --git a/media/libjxl/src/tools/CMakeLists.txt b/media/libjxl/src/tools/CMakeLists.txt
index 9ee2e53fcb..934ed895c0 100644
--- a/media/libjxl/src/tools/CMakeLists.txt
+++ b/media/libjxl/src/tools/CMakeLists.txt
@@ -58,17 +58,19 @@ endif() # JPEGXL_ENABLE_VIEWERS
# Tools are added conditionally below.
set(TOOL_BINARIES)
+# Tools that depend on jxl internal functions.
+set(INTERNAL_TOOL_BINARIES)
add_library(jxl_tool STATIC EXCLUDE_FROM_ALL
cmdline.cc
codec_config.cc
+ speed_stats.cc
+ file_io.cc
tool_version.cc
)
target_compile_options(jxl_tool PUBLIC "${JPEGXL_INTERNAL_FLAGS}")
-target_link_libraries(jxl_tool jxl-static)
-
-target_include_directories(jxl_tool
- PUBLIC "${PROJECT_SOURCE_DIR}")
+target_include_directories(jxl_tool PUBLIC "${PROJECT_SOURCE_DIR}")
+target_link_libraries(jxl_tool hwy)
# The JPEGXL_VERSION is set from the builders.
if(NOT DEFINED JPEGXL_VERSION OR JPEGXL_VERSION STREQUAL "")
@@ -119,85 +121,34 @@ else()
endif()
if(JPEGXL_ENABLE_TOOLS)
- list(APPEND TOOL_BINARIES
- cjxl
- cjxl_ng
- djxl
- djxl_ng
- cjpeg_hdr
- jxlinfo
- )
-
# Main compressor.
- add_executable(cjxl
- cjxl.cc
- speed_stats.cc
- cjxl_main.cc
- )
+ add_executable(cjxl cjxl_main.cc)
target_link_libraries(cjxl
- box
- jxl-static
- jxl_extras-static
- jxl_threads-static
- )
-
-
- add_executable(cjxl_ng
- cjxl_ng_main.cc
- )
- target_link_libraries(cjxl_ng
jxl
- jxl_extras_dec-static
+ jxl_extras_codec-static
jxl_threads
- hwy
- jxl_gflags
- )
- target_include_directories(cjxl_ng PRIVATE "${PROJECT_SOURCE_DIR}")
- if(JPEGXL_EMSCRIPTEN)
- set_target_properties(cjxl_ng PROPERTIES LINK_FLAGS "-s USE_LIBPNG=1")
- endif()
-
- add_executable(djxl_ng
- djxl_ng_main.cc
- )
- target_link_libraries(djxl_ng
- jxl
- jxl_gflags
- )
-
- add_executable(cjpeg_hdr
- cjpeg_hdr.cc
- )
- target_link_libraries(cjpeg_hdr
- box
- jxl-static
- jxl_extras-static
- jxl_threads-static
+ jxl_tool
)
+ list(APPEND TOOL_BINARIES cjxl)
# Main decompressor.
- add_library(djxltool STATIC
- djxl.cc
- speed_stats.cc
- )
- target_link_libraries(djxltool
- box
- jxl-static
- jxl_extras-static
- jxl_threads-static
+ add_executable(djxl djxl_main.cc)
+ target_link_libraries(djxl
+ jxl
+ jxl_extras_codec-static
+ jxl_threads
+ jxl_tool
)
+ list(APPEND TOOL_BINARIES djxl)
- add_executable(djxl
- djxl_main.cc
- )
- target_link_libraries(djxl djxltool)
+ add_executable(cjpeg_hdr cjpeg_hdr.cc)
+ list(APPEND INTERNAL_TOOL_BINARIES cjpeg_hdr)
- add_executable(jxlinfo
- jxlinfo.c
- )
+ add_executable(jxlinfo jxlinfo.c)
target_link_libraries(jxlinfo jxl)
+ list(APPEND TOOL_BINARIES jxlinfo)
- if(NOT ${SANITIZER} STREQUAL "none")
+ if(NOT SANITIZER STREQUAL "none")
# Linking a C test binary with the C++ JPEG XL implementation when using
# address sanitizer is not well supported by clang 9, so force using clang++
# for linking this test if a sanitizer is used.
@@ -207,8 +158,8 @@ if(JPEGXL_ENABLE_TOOLS)
endif() # JPEGXL_ENABLE_TOOLS
# Other developer tools.
-if(${JPEGXL_ENABLE_DEVTOOLS})
- list(APPEND TOOL_BINARIES
+if(JPEGXL_ENABLE_DEVTOOLS)
+ list(APPEND INTERNAL_TOOL_BINARIES
fuzzer_corpus
butteraugli_main
decode_and_encode
@@ -239,8 +190,8 @@ if(${JPEGXL_ENABLE_DEVTOOLS})
endif() # JPEGXL_ENABLE_DEVTOOLS
# Benchmark tools.
-if(${JPEGXL_ENABLE_BENCHMARK})
- list(APPEND TOOL_BINARIES
+if(JPEGXL_ENABLE_BENCHMARK AND JPEGXL_ENABLE_TOOLS)
+ list(APPEND INTERNAL_TOOL_BINARIES
benchmark_xl
)
@@ -256,8 +207,6 @@ if(${JPEGXL_ENABLE_BENCHMARK})
benchmark/benchmark_codec_custom.h
benchmark/benchmark_codec_jxl.cc
benchmark/benchmark_codec_jxl.h
- speed_stats.cc
- speed_stats.h
../third_party/dirent.cc
)
target_link_libraries(benchmark_xl Threads::Threads)
@@ -296,7 +245,7 @@ if(${JPEGXL_ENABLE_BENCHMARK})
# Use the static version of webp if available.
find_library(WebP_STATIC_LINK_LIBRARY NAMES libwebp.a
PATHS "${WebP_LIBDIR}")
- if("${WebP_STATIC_LINK_LIBRARY}" STREQUAL "WebP_STATIC_LINK_LIBRARY-NOTFOUND")
+ if(NOT WebP_STATIC_LINK_LIBRARY)
message(WARNING "Using dynamic libwebp")
target_link_libraries(benchmark_xl PkgConfig::WebP)
else()
@@ -304,7 +253,7 @@ if(${JPEGXL_ENABLE_BENCHMARK})
target_include_directories(benchmark_xl
PRIVATE ${WebP_STATIC_INCLUDE_DIRS})
target_compile_options(benchmark_xl PRIVATE ${WebP_STATIC_CFLAGS_OTHER})
- endif()
+ endif() # NOT WebP_STATIC_LINK_LIBRARY
endif()
pkg_check_modules(AVIF IMPORTED_TARGET libavif)
@@ -319,20 +268,28 @@ if(${JPEGXL_ENABLE_BENCHMARK})
endif() # JPEGXL_ENABLE_BENCHMARK
# All tool binaries depend on "jxl" library and the tool helpers.
+foreach(BINARY IN LISTS INTERNAL_TOOL_BINARIES)
+ target_link_libraries("${BINARY}"
+ jxl_extras-static
+ jxl_tool
+ )
+endforeach()
+
+list(APPEND TOOL_BINARIES ${INTERNAL_TOOL_BINARIES})
+
foreach(BINARY IN LISTS TOOL_BINARIES)
- target_include_directories("${BINARY}" PRIVATE "${PROJECT_SOURCE_DIR}")
- target_link_libraries("${BINARY}" box jxl-static jxl_extras-static jxl_threads-static jxl_tool)
if(JPEGXL_EMSCRIPTEN)
set_target_properties(${BINARY} PROPERTIES LINK_FLAGS "-s USE_LIBPNG=1")
endif()
-
endforeach()
+
install(TARGETS ${TOOL_BINARIES} RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}")
message(STATUS "Building tools: ${TOOL_BINARIES}")
set(FUZZER_BINARIES
color_encoding_fuzzer
decode_basic_info_fuzzer
+ cjxl_fuzzer
djxl_fuzzer
icc_codec_fuzzer
fields_fuzzer
@@ -343,7 +300,7 @@ set(FUZZER_BINARIES
# Fuzzers.
foreach(FUZZER IN LISTS FUZZER_BINARIES)
- if(${JPEGXL_ENABLE_FUZZERS})
+ if(JPEGXL_ENABLE_FUZZERS)
set(BINARY "${FUZZER}")
add_executable("${BINARY}" "${BINARY}.cc")
target_link_libraries("${BINARY}" ${JPEGXL_FUZZER_LINK_FLAGS})
@@ -355,16 +312,14 @@ foreach(FUZZER IN LISTS FUZZER_BINARIES)
"fuzzer_stub.cc" "${FUZZER}.cc")
endif() # JPEGXL_ENABLE_FUZZERS
target_include_directories("${BINARY}" PRIVATE "${CMAKE_SOURCE_DIR}")
- if(${FUZZER} STREQUAL djxl_fuzzer)
+ if(FUZZER STREQUAL djxl_fuzzer)
target_link_libraries("${BINARY}"
jxl_dec-static
jxl_threads-static
)
else()
target_link_libraries("${BINARY}"
- jxl-static
jxl_extras-static
- jxl_threads-static
jxl_tool
)
endif()
@@ -378,7 +333,7 @@ if(BUILD_TESTING AND TARGET jxl AND NOT JPEGXL_EMSCRIPTEN)
file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/tests)
add_executable(libjxl_test libjxl_test.c)
set_property(TARGET libjxl_test PROPERTY C_STANDARD 99)
-if(NOT ${SANITIZER} STREQUAL "none")
+if(NOT SANITIZER STREQUAL "none")
# Linking a C test binary with the C++ JPEG XL implementation when using
# address sanitizer is not well supported by clang 9, so force using clang++
# for linking this test if a sanitizer is used.
@@ -393,12 +348,23 @@ if(NOT WIN32)
endif() # NOT WIN32
endif() # NOT MSVC
-add_test(NAME LibraryCLinkageTest COMMAND libjxl_test)
+add_test(
+ NAME LibraryCLinkageTest
+ COMMAND libjxl_test
+ WORKING_DIRECTORY $<TARGET_FILE_DIR:jxl>
+)
+# if user decide to set CMAKE_SKIP_RPATH:BOOL=ON make sure libjxl.so.0.7 can
+# still be found:
+if(UNIX AND CMAKE_SKIP_RPATH)
+ set_property(TEST LibraryCLinkageTest PROPERTY ENVIRONMENT
+ LD_LIBRARY_PATH=${CMAKE_CURRENT_BINARY_DIR}/..
+ )
+endif()
endif() # BUILD_TESTING AND TARGET jxl AND NOT JPEGXL_EMSCRIPTEN
# Tools defined in subdirectories.
-if(${JPEGXL_ENABLE_VIEWERS})
+if(JPEGXL_ENABLE_VIEWERS)
add_subdirectory(viewer)
add_subdirectory(comparison_viewer)
add_subdirectory(flicker_test)
@@ -408,33 +374,39 @@ add_subdirectory(box)
add_subdirectory(conformance)
-if ("${JPEGXL_EMSCRIPTEN}")
+if (JPEGXL_ENABLE_TOOLS AND JPEGXL_EMSCRIPTEN)
# WASM API facade.
add_executable(jxl_emcc jxl_emcc.cc)
-target_link_libraries(jxl_emcc jxl-static jxl_extras-static)
+target_link_libraries(jxl_emcc
+ jxl_extras-static
+)
set_target_properties(jxl_emcc PROPERTIES LINK_FLAGS "\
-O3\
--closure 1 \
- -s ALLOW_MEMORY_GROWTH=1 \
+ -s TOTAL_MEMORY=75mb \
-s USE_LIBPNG=1 \
-s DISABLE_EXCEPTION_CATCHING=1 \
-s MODULARIZE=1 \
-s FILESYSTEM=0 \
+ -s USE_PTHREADS=1 \
+ -s PTHREAD_POOL_SIZE=4 \
-s EXPORT_NAME=\"JxlCodecModule\"\
-s \"EXPORTED_FUNCTIONS=[\
- _jxlCompress,\
- _jxlDecompress,\
+ _malloc,\
_free,\
- _malloc\
+ _jxlCreateInstance,\
+ _jxlDestroyInstance,\
+ _jxlFlush,\
+ _jxlProcessInput\
]\"\
")
-endif () # JPEGXL_EMSCRIPTEN
+endif () # JPEGXL_ENABLE_TOOLS AND JPEGXL_EMSCRIPTEN
if(JPEGXL_ENABLE_JNI)
find_package(JNI QUIET)
find_package(Java QUIET)
-if ("${JNI_FOUND}" AND "${Java_FOUND}")
+if (JNI_FOUND AND Java_FOUND)
include(UseJava)
# decoder_jni_onload.cc might be necessary for Android; not used yet.
@@ -467,7 +439,7 @@ if ("${JNI_FOUND}" AND "${Java_FOUND}")
)
get_target_property(JXL_JNI_WRAPPER_TEST_JAR jxl_jni_wrapper_test JAR_FILE)
- if(NOT "${SANITIZER}" MATCHES ".san")
+ if(NOT SANITIZER MATCHES ".san")
# NB: Vanilla OpenJDK 8 / 11 are known to work well (i.e. either
# "which java" or JAVA_HOME environment variable point to the path like
# "/usr/lib/jvm/java-xx-openjdk-yyy" on Debian Linux).
@@ -481,3 +453,17 @@ if ("${JNI_FOUND}" AND "${Java_FOUND}")
endif() # JPEGXL_ENABLE_FUZZERS
endif() # JNI_FOUND & Java_FOUND
endif() # JPEGXL_ENABLE_JNI
+
+# End-to-end tests for the tools
+if(BUILD_TESTING AND JPEGXL_ENABLE_TOOLS AND JPEGXL_ENABLE_DEVTOOLS AND JPEGXL_ENABLE_TRANSCODE_JPEG AND (NOT JPEGXL_ENABLE_JNI))
+find_program (BASH_PROGRAM bash)
+if(BASH_PROGRAM AND $<TARGET_EXISTS:cjxl> AND $<TARGET_EXISTS:djxl> AND $<TARGET_EXISTS:ssimulacra_main>)
+ add_test(
+ NAME roundtrip_test
+ COMMAND ${BASH_PROGRAM} ${CMAKE_CURRENT_SOURCE_DIR}/roundtrip_test.sh
+ ${CMAKE_BINARY_DIR})
+ if (CMAKE_CROSSCOMPILING_EMULATOR)
+ set_tests_properties(roundtrip_test PROPERTIES ENVIRONMENT "EMULATOR=${CMAKE_CROSSCOMPILING_EMULATOR}")
+ endif()
+endif()
+endif() # BUILD_TESTING
diff --git a/media/libjxl/src/tools/args.h b/media/libjxl/src/tools/args.h
index cc26a35dc7..7d04ce3a7b 100644
--- a/media/libjxl/src/tools/args.h
+++ b/media/libjxl/src/tools/args.h
@@ -40,43 +40,6 @@ static inline bool ParseOverride(const char* arg, jxl::Override* out) {
return JXL_FAILURE("Args");
}
-static inline bool ParseUnsigned(const char* arg, size_t* out) {
- char* end;
- *out = static_cast<size_t>(strtoull(arg, &end, 0));
- if (end[0] != '\0') {
- fprintf(stderr, "Unable to interpret as unsigned integer: %s.\n", arg);
- return JXL_FAILURE("Args");
- }
- return true;
-}
-
-static inline bool ParseUint32(const char* arg, uint32_t* out) {
- size_t value = 0;
- bool ret = ParseUnsigned(arg, &value);
- if (ret) *out = value;
- return ret;
-}
-
-static inline bool ParseSigned(const char* arg, int* out) {
- char* end;
- *out = static_cast<int>(strtol(arg, &end, 0));
- if (end[0] != '\0') {
- fprintf(stderr, "Unable to interpret as signed integer: %s.\n", arg);
- return JXL_FAILURE("Args");
- }
- return true;
-}
-
-static inline bool ParseFloat(const char* arg, float* out) {
- char* end;
- *out = static_cast<float>(strtod(arg, &end));
- if (end[0] != '\0') {
- fprintf(stderr, "Unable to interpret as float: %s.\n", arg);
- return JXL_FAILURE("Args");
- }
- return true;
-}
-
static inline bool ParseFloatPair(const char* arg,
std::pair<float, float>* out) {
int parsed = sscanf(arg, "%f,%f", &out->first, &out->second);
@@ -91,16 +54,6 @@ static inline bool ParseFloatPair(const char* arg,
return true;
}
-static inline bool ParseDouble(const char* arg, double* out) {
- char* end;
- *out = static_cast<double>(strtod(arg, &end));
- if (end[0] != '\0') {
- fprintf(stderr, "Unable to interpret as double: %s.\n", arg);
- return JXL_FAILURE("Args");
- }
- return true;
-}
-
static inline bool ParseAndAppendKeyValue(const char* arg,
jxl::extras::ColorHints* out) {
const char* eq = strchr(arg, '=');
@@ -132,26 +85,11 @@ static inline bool ParsePredictor(const char* arg, jxl::Predictor* out) {
return true;
}
-static inline bool ParseString(const char* arg, std::string* out) {
- out->assign(arg);
- return true;
-}
-
static inline bool ParseCString(const char* arg, const char** out) {
*out = arg;
return true;
}
-static inline bool SetBooleanTrue(bool* out) {
- *out = true;
- return true;
-}
-
-static inline bool SetBooleanFalse(bool* out) {
- *out = false;
- return true;
-}
-
static inline bool IncrementUnsigned(size_t* out) {
(*out)++;
return true;
diff --git a/media/libjxl/src/tools/benchmark/benchmark_codec.cc b/media/libjxl/src/tools/benchmark/benchmark_codec.cc
index 2deb7409dd..230665bbae 100644
--- a/media/libjxl/src/tools/benchmark/benchmark_codec.cc
+++ b/media/libjxl/src/tools/benchmark/benchmark_codec.cc
@@ -94,7 +94,6 @@ Status ImageCodec::ParseParam(const std::string& param) {
}
return true;
} else if (param[0] == 'r') {
- butteraugli_target_ = -1.0;
ba_params_.hf_asymmetry = args_.ba_params.hf_asymmetry;
bitrate_target_ = strtof(param.substr(1).c_str(), nullptr);
return true;
@@ -109,7 +108,7 @@ class NoneCodec : public ImageCodec {
Status ParseParam(const std::string& param) override { return true; }
Status Compress(const std::string& filename, const CodecInOut* io,
- ThreadPoolInternal* pool, PaddedBytes* compressed,
+ ThreadPoolInternal* pool, std::vector<uint8_t>* compressed,
jpegxl::tools::SpeedStats* speed_stats) override {
PROFILER_ZONE("NoneCompress");
const double start = Now();
diff --git a/media/libjxl/src/tools/benchmark/benchmark_codec.h b/media/libjxl/src/tools/benchmark/benchmark_codec.h
index abc5c1a618..e554fc2802 100644
--- a/media/libjxl/src/tools/benchmark/benchmark_codec.h
+++ b/media/libjxl/src/tools/benchmark/benchmark_codec.h
@@ -59,7 +59,8 @@ class ImageCodec {
virtual bool IsJpegTranscoder() const { return false; }
virtual Status Compress(const std::string& filename, const CodecInOut* io,
- ThreadPoolInternal* pool, PaddedBytes* compressed,
+ ThreadPoolInternal* pool,
+ std::vector<uint8_t>* compressed,
jpegxl::tools::SpeedStats* speed_stats) = 0;
virtual Status Decompress(const std::string& filename,
@@ -72,7 +73,7 @@ class ImageCodec {
virtual Status CanRecompressJpeg() const { return false; }
virtual Status RecompressJpeg(const std::string& filename,
const std::string& data,
- PaddedBytes* compressed,
+ std::vector<uint8_t>* compressed,
jpegxl::tools::SpeedStats* speed_stats) {
return false;
}
diff --git a/media/libjxl/src/tools/benchmark/benchmark_codec_avif.cc b/media/libjxl/src/tools/benchmark/benchmark_codec_avif.cc
index 68f47e69a4..fbe36b5a05 100644
--- a/media/libjxl/src/tools/benchmark/benchmark_codec_avif.cc
+++ b/media/libjxl/src/tools/benchmark/benchmark_codec_avif.cc
@@ -213,7 +213,7 @@ class AvifCodec : public ImageCodec {
}
Status Compress(const std::string& filename, const CodecInOut* io,
- ThreadPoolInternal* pool, PaddedBytes* compressed,
+ ThreadPoolInternal* pool, std::vector<uint8_t>* compressed,
jpegxl::tools::SpeedStats* speed_stats) override {
double elapsed_convert_image = 0;
const double start = Now();
@@ -324,7 +324,7 @@ class AvifCodec : public ImageCodec {
rgb_image.height * rgb_image.rowBytes),
rgb_image.width, rgb_image.height, color, (has_alpha ? 4 : 3),
/*alpha_is_premultiplied=*/false, rgb_image.depth,
- JXL_NATIVE_ENDIAN, /*flipped_y=*/false, pool, &ib,
+ JXL_NATIVE_ENDIAN, pool, &ib,
/*float_in=*/false, /*align=*/0));
io->frames.push_back(std::move(ib));
io->dec_pixels += rgb_image.width * rgb_image.height;
diff --git a/media/libjxl/src/tools/benchmark/benchmark_codec_custom.cc b/media/libjxl/src/tools/benchmark/benchmark_codec_custom.cc
index a93fd50193..eefae6e65c 100644
--- a/media/libjxl/src/tools/benchmark/benchmark_codec_custom.cc
+++ b/media/libjxl/src/tools/benchmark/benchmark_codec_custom.cc
@@ -86,7 +86,7 @@ class CustomCodec : public ImageCodec {
}
Status Compress(const std::string& filename, const CodecInOut* io,
- ThreadPoolInternal* pool, PaddedBytes* compressed,
+ ThreadPoolInternal* pool, std::vector<uint8_t>* compressed,
jpegxl::tools::SpeedStats* speed_stats) override {
JXL_RETURN_IF_ERROR(param_index_ > 2);
@@ -98,10 +98,8 @@ class CustomCodec : public ImageCodec {
saved_intensity_target_ = io->metadata.m.IntensityTarget();
const size_t bits = io->metadata.m.bit_depth.bits_per_sample;
- PaddedBytes png;
JXL_RETURN_IF_ERROR(
- extras::EncodeImageAPNG(io, io->Main().c_current(), bits, pool, &png));
- JXL_RETURN_IF_ERROR(WriteFile(png, png_filename));
+ EncodeToFile(*io, io->Main().c_current(), bits, png_filename, pool));
std::vector<std::string> arguments = compress_args_;
arguments.push_back(png_filename);
arguments.push_back(encoded_filename);
diff --git a/media/libjxl/src/tools/benchmark/benchmark_codec_jpeg.cc b/media/libjxl/src/tools/benchmark/benchmark_codec_jpeg.cc
index 78b304e832..ae3215a32f 100644
--- a/media/libjxl/src/tools/benchmark/benchmark_codec_jpeg.cc
+++ b/media/libjxl/src/tools/benchmark/benchmark_codec_jpeg.cc
@@ -24,51 +24,31 @@
#include "lib/jxl/codec_in_out.h"
#include "tools/cmdline.h"
-using jxl::extras::JpegEncoder;
-
namespace jxl {
namespace {
struct JPEGArgs {
- JpegEncoder encoder = JpegEncoder::kLibJpeg;
- YCbCrChromaSubsampling chroma_subsampling;
+ std::string jpeg_encoder = "libjpeg";
+ std::string chroma_subsampling = "444";
};
JPEGArgs* const jpegargs = new JPEGArgs;
-bool ParseChromaSubsampling(const char* param,
- YCbCrChromaSubsampling* subsampling) {
- std::vector<std::pair<
- std::string, std::pair<std::array<uint8_t, 3>, std::array<uint8_t, 3>>>>
- options = {{"444", {{{1, 1, 1}}, {{1, 1, 1}}}},
- {"420", {{{2, 1, 1}}, {{2, 1, 1}}}},
- {"422", {{{2, 1, 1}}, {{1, 1, 1}}}},
- {"440", {{{1, 1, 1}}, {{2, 1, 1}}}}};
- for (const auto& option : options) {
- if (param == option.first) {
- JXL_CHECK(subsampling->Set(option.second.first.data(),
- option.second.second.data()));
- return true;
- }
- }
- return false;
-}
-
} // namespace
Status AddCommandLineOptionsJPEGCodec(BenchmarkArgs* args) {
args->cmdline.AddOptionValue(
'\0', "chroma_subsampling", "444/422/420/411",
"default JPEG chroma subsampling (default: 444).",
- &jpegargs->chroma_subsampling, &ParseChromaSubsampling);
+ &jpegargs->chroma_subsampling, &jpegxl::tools::ParseString);
return true;
}
class JPEGCodec : public ImageCodec {
public:
explicit JPEGCodec(const BenchmarkArgs& args) : ImageCodec(args) {
- encoder_ = jpegargs->encoder;
+ jpeg_encoder_ = jpegargs->jpeg_encoder;
chroma_subsampling_ = jpegargs->chroma_subsampling;
}
@@ -77,24 +57,35 @@ class JPEGCodec : public ImageCodec {
return true;
}
if (param == "sjpeg") {
- encoder_ = JpegEncoder::kSJpeg;
+ jpeg_encoder_ = param;
return true;
}
if (param.compare(0, 3, "yuv") == 0) {
if (param.size() != 6) return false;
- return ParseChromaSubsampling(param.c_str() + 3, &chroma_subsampling_);
+ chroma_subsampling_ = param.substr(3);
+ return true;
}
return false;
}
Status Compress(const std::string& filename, const CodecInOut* io,
- ThreadPoolInternal* pool, PaddedBytes* compressed,
+ ThreadPoolInternal* pool, std::vector<uint8_t>* compressed,
jpegxl::tools::SpeedStats* speed_stats) override {
+ extras::PackedPixelFile ppf;
+ JxlPixelFormat format = {0, JXL_TYPE_UINT8, JXL_BIG_ENDIAN, 0};
+ JXL_RETURN_IF_ERROR(ConvertCodecInOutToPackedPixelFile(
+ *io, format, io->metadata.m.color_encoding, pool, &ppf));
+ extras::EncodedImage encoded;
+ std::unique_ptr<extras::Encoder> encoder = extras::GetJPEGEncoder();
+ std::ostringstream os;
+ os << static_cast<int>(std::round(q_target_));
+ encoder->SetOption("q", os.str());
+ encoder->SetOption("jpeg_encoder", jpeg_encoder_);
+ encoder->SetOption("chroma_subsampling", chroma_subsampling_);
const double start = Now();
- JXL_RETURN_IF_ERROR(EncodeImageJPG(io, encoder_,
- static_cast<int>(std::round(q_target_)),
- chroma_subsampling_, pool, compressed));
+ JXL_RETURN_IF_ERROR(encoder->Encode(ppf, &encoded, pool));
const double end = Now();
+ *compressed = encoded.bitstreams.back();
speed_stats->NotifyElapsed(end - start);
return true;
}
@@ -114,8 +105,8 @@ class JPEGCodec : public ImageCodec {
}
protected:
- JpegEncoder encoder_;
- YCbCrChromaSubsampling chroma_subsampling_;
+ std::string jpeg_encoder_;
+ std::string chroma_subsampling_;
};
ImageCodec* CreateNewJPEGCodec(const BenchmarkArgs& args) {
diff --git a/media/libjxl/src/tools/benchmark/benchmark_codec_jxl.cc b/media/libjxl/src/tools/benchmark/benchmark_codec_jxl.cc
index 6dd1ad016e..6557858320 100644
--- a/media/libjxl/src/tools/benchmark/benchmark_codec_jxl.cc
+++ b/media/libjxl/src/tools/benchmark/benchmark_codec_jxl.cc
@@ -12,9 +12,13 @@
#include <utility>
#include <vector>
-#include "jxl/decode_cxx.h"
#include "jxl/thread_parallel_runner_cxx.h"
#include "lib/extras/codec.h"
+#include "lib/extras/dec/jxl.h"
+#if JPEGXL_ENABLE_JPEG
+#include "lib/extras/enc/jpg.h"
+#endif
+#include "lib/extras/packed_image_convert.h"
#include "lib/extras/time.h"
#include "lib/jxl/aux_out.h"
#include "lib/jxl/base/data_parallel.h"
@@ -22,8 +26,6 @@
#include "lib/jxl/base/padded_bytes.h"
#include "lib/jxl/base/span.h"
#include "lib/jxl/codec_in_out.h"
-#include "lib/jxl/dec_file.h"
-#include "lib/jxl/dec_params.h"
#include "lib/jxl/enc_cache.h"
#include "lib/jxl/enc_color_management.h"
#include "lib/jxl/enc_external_image.h"
@@ -58,6 +60,7 @@ struct JxlArgs {
Override dots;
Override patches;
+ bool log_search_state;
std::string debug_image_dir;
};
@@ -85,6 +88,9 @@ Status AddCommandLineOptionsJxlCodec(BenchmarkArgs* args) {
args->AddOverride(&jxlargs->patches, "patches",
"Enable(1)/disable(0) patch dictionary.");
+ args->AddFlag(&jxlargs->log_search_state, "log_search_state",
+ "Print out debug info for tortoise mode AQ loop.", false);
+
args->AddString(
&jxlargs->debug_image_dir, "debug_image_dir",
"If not empty, saves debug images for each "
@@ -113,7 +119,9 @@ class JxlCodec : public ImageCodec {
std::istringstream parser(param.substr(kEcResamplingPrefix.size()));
parser >> cparams_.ec_resampling;
} else if (ImageCodec::ParseParam(param)) {
- // Nothing to do.
+ if (param[0] == 'd' && butteraugli_target_ == 0.0) {
+ cparams_.SetLossless();
+ }
} else if (param == "uint8") {
uint8_ = true;
} else if (param[0] == 'u') {
@@ -178,8 +186,6 @@ class JxlCodec : public ImageCodec {
return JXL_FAILURE("Invalid group size shift value");
}
cparams_.modular_group_size_shift = gsize;
- } else if (param == "new_heuristics") {
- cparams_.use_new_heuristics = true;
} else if (param == "plt") {
cparams_.options.max_properties = 0;
cparams_.options.nb_repeats = 0;
@@ -193,6 +199,8 @@ class JxlCodec : public ImageCodec {
if (cparams_.epf > 3) {
return JXL_FAILURE("Invalid epf value");
}
+ } else if (param.substr(0, 2) == "nr") {
+ normalize_bitrate_ = true;
} else if (param.substr(0, 16) == "faster_decoding=") {
cparams_.decoding_speed_tier =
strtol(param.substr(16).c_str(), nullptr, 10);
@@ -205,6 +213,7 @@ class JxlCodec : public ImageCodec {
bool IsColorAware() const override {
// Can't deal with negative values from color space conversion.
if (cparams_.modular_mode) return false;
+ if (normalize_bitrate_) return false;
// Otherwise, input may be in any color space.
return true;
}
@@ -215,7 +224,7 @@ class JxlCodec : public ImageCodec {
}
Status Compress(const std::string& filename, const CodecInOut* io,
- ThreadPoolInternal* pool, PaddedBytes* compressed,
+ ThreadPoolInternal* pool, std::vector<uint8_t>* compressed,
jpegxl::tools::SpeedStats* speed_stats) override {
if (!jxlargs->debug_image_dir.empty()) {
cinfo_.dump_image = [](const CodecInOut& io, const std::string& path) {
@@ -248,15 +257,34 @@ class JxlCodec : public ImageCodec {
cparams_.color_transform = ColorTransform::kXYB;
}
+ cparams_.log_search_state = jxlargs->log_search_state;
+
+#if JPEGXL_ENABLE_JPEG
+ if (normalize_bitrate_ && cparams_.butteraugli_distance > 0.0f) {
+ extras::PackedPixelFile ppf;
+ JxlPixelFormat format = {0, JXL_TYPE_UINT8, JXL_BIG_ENDIAN, 0};
+ JXL_RETURN_IF_ERROR(ConvertCodecInOutToPackedPixelFile(
+ *io, format, io->metadata.m.color_encoding, pool, &ppf));
+ extras::EncodedImage encoded;
+ std::unique_ptr<extras::Encoder> encoder = extras::GetJPEGEncoder();
+ encoder->SetOption("q", "95");
+ JXL_RETURN_IF_ERROR(encoder->Encode(ppf, &encoded, pool));
+ float jpeg_bits = encoded.bitstreams.back().size() * kBitsPerByte;
+ float jpeg_bitrate = jpeg_bits / (io->xsize() * io->ysize());
+ // Formula fitted on jyrki31 corpus for distances between 1.0 and 8.0.
+ cparams_.target_bitrate = (jpeg_bitrate * 0.36f /
+ (0.6f * cparams_.butteraugli_distance + 0.4f));
+ }
+#endif
+
const double start = Now();
PassesEncoderState passes_encoder_state;
- if (cparams_.use_new_heuristics) {
- passes_encoder_state.heuristics =
- jxl::make_unique<jxl::FastEncoderHeuristics>();
- }
+ PaddedBytes compressed_padded;
JXL_RETURN_IF_ERROR(EncodeFile(cparams_, io, &passes_encoder_state,
- compressed, GetJxlCms(), &cinfo_, pool));
+ &compressed_padded, GetJxlCms(), &cinfo_,
+ pool));
const double end = Now();
+ compressed->assign(compressed_padded.begin(), compressed_padded.end());
speed_stats->NotifyElapsed(end - start);
return true;
}
@@ -265,121 +293,25 @@ class JxlCodec : public ImageCodec {
const Span<const uint8_t> compressed,
ThreadPoolInternal* pool, CodecInOut* io,
jpegxl::tools::SpeedStats* speed_stats) override {
- io->frames.clear();
- if (dparams_.max_passes != DecompressParams().max_passes ||
- dparams_.max_downsampling != DecompressParams().max_downsampling) {
- // Must use the C++ API to honor non-default dparams.
- if (uint8_) {
- return JXL_FAILURE(
- "trying to use decompress params that are not all available in "
- "either decoding API");
- }
- const double start = Now();
- JXL_RETURN_IF_ERROR(DecodeFile(dparams_, compressed, io, pool));
- const double end = Now();
- speed_stats->NotifyElapsed(end - start);
- return true;
- }
-
- double elapsed_convert_image = 0;
+ dparams_.runner = pool->runner();
+ dparams_.runner_opaque = pool->runner_opaque();
+ JxlDataType data_type = uint8_ ? JXL_TYPE_UINT8 : JXL_TYPE_FLOAT;
+ dparams_.accepted_formats = {{3, data_type, JXL_NATIVE_ENDIAN, 0},
+ {4, data_type, JXL_NATIVE_ENDIAN, 0}};
+ // By default, the decoder will undo exif orientation, giving an image
+ // with identity exif rotation as result. However, the benchmark does
+ // not undo exif orientation of the originals, and compares against the
+ // originals, so we must set the option to keep the original orientation
+ // instead.
+ dparams_.keep_orientation = true;
+ extras::PackedPixelFile ppf;
+ size_t decoded_bytes;
const double start = Now();
- {
- std::vector<uint8_t> pixel_data;
- PaddedBytes icc_profile;
- auto runner = JxlThreadParallelRunnerMake(nullptr, pool->NumThreads());
- auto dec = JxlDecoderMake(nullptr);
- // By default, the decoder will undo exif orientation, giving an image
- // with identity exif rotation as result. However, the benchmark does
- // not undo exif orientation of the originals, and compares against the
- // originals, so we must set the option to keep the original orientation
- // instead.
- JxlDecoderSetKeepOrientation(dec.get(), JXL_TRUE);
- JXL_RETURN_IF_ERROR(
- JXL_DEC_SUCCESS ==
- JxlDecoderSubscribeEvents(dec.get(), JXL_DEC_BASIC_INFO |
- JXL_DEC_COLOR_ENCODING |
- JXL_DEC_FULL_IMAGE));
- JxlBasicInfo info{};
- JxlPixelFormat format = {/*num_channels=*/3,
- /*data_type=*/JXL_TYPE_FLOAT,
- /*endianness=*/JXL_NATIVE_ENDIAN,
- /*align=*/0};
- if (uint8_) {
- format.data_type = JXL_TYPE_UINT8;
- }
- JxlDecoderSetInput(dec.get(), compressed.data(), compressed.size());
- JxlDecoderStatus status;
- while ((status = JxlDecoderProcessInput(dec.get())) != JXL_DEC_SUCCESS) {
- switch (status) {
- case JXL_DEC_ERROR:
- return JXL_FAILURE("decoder error");
- case JXL_DEC_NEED_MORE_INPUT:
- return JXL_FAILURE("decoder requests more input");
- case JXL_DEC_BASIC_INFO:
- JXL_RETURN_IF_ERROR(JXL_DEC_SUCCESS ==
- JxlDecoderGetBasicInfo(dec.get(), &info));
- format.num_channels = info.num_color_channels;
- if (info.alpha_bits != 0) {
- ++format.num_channels;
- io->metadata.m.extra_channel_info.resize(1);
- io->metadata.m.extra_channel_info[0].type =
- jxl::ExtraChannel::kAlpha;
- }
- break;
- case JXL_DEC_COLOR_ENCODING: {
- size_t icc_size;
- JXL_RETURN_IF_ERROR(JXL_DEC_SUCCESS ==
- JxlDecoderGetICCProfileSize(
- dec.get(), &format,
- JXL_COLOR_PROFILE_TARGET_DATA, &icc_size));
- icc_profile.resize(icc_size);
- JXL_RETURN_IF_ERROR(JXL_DEC_SUCCESS ==
- JxlDecoderGetColorAsICCProfile(
- dec.get(), &format,
- JXL_COLOR_PROFILE_TARGET_DATA,
- icc_profile.data(), icc_profile.size()));
- break;
- }
- case JXL_DEC_NEED_IMAGE_OUT_BUFFER: {
- size_t buffer_size;
- JXL_RETURN_IF_ERROR(
- JXL_DEC_SUCCESS ==
- JxlDecoderImageOutBufferSize(dec.get(), &format, &buffer_size));
- JXL_RETURN_IF_ERROR(buffer_size ==
- info.xsize * info.ysize * format.num_channels *
- (uint8_ ? sizeof(uint8_t) : sizeof(float)));
- pixel_data.resize(buffer_size);
- JXL_RETURN_IF_ERROR(JXL_DEC_SUCCESS ==
- JxlDecoderSetImageOutBuffer(dec.get(), &format,
- pixel_data.data(),
- buffer_size));
- break;
- }
- case JXL_DEC_FULL_IMAGE: {
- const double start_convert_image = Now();
- {
- ColorEncoding color_encoding;
- JXL_RETURN_IF_ERROR(
- color_encoding.SetICC(PaddedBytes(icc_profile)));
- ImageBundle frame(&io->metadata.m);
- JXL_RETURN_IF_ERROR(BufferToImageBundle(
- format, info.xsize, info.ysize, pixel_data.data(),
- pixel_data.size(), pool, color_encoding, &frame));
- io->frames.push_back(std::move(frame));
- io->dec_pixels += info.xsize * info.ysize;
- }
- const double end_convert_image = Now();
- elapsed_convert_image += end_convert_image - start_convert_image;
- break;
- }
- default:
- return JXL_FAILURE("unrecognized status %d",
- static_cast<int>(status));
- }
- }
- }
+ JXL_RETURN_IF_ERROR(DecodeImageJXL(compressed.data(), compressed.size(),
+ dparams_, &decoded_bytes, &ppf));
const double end = Now();
- speed_stats->NotifyElapsed(end - start - elapsed_convert_image);
+ speed_stats->NotifyElapsed(end - start);
+ JXL_RETURN_IF_ERROR(ConvertPackedPixelFileToCodecInOut(ppf, pool, io));
return true;
}
@@ -394,8 +326,9 @@ class JxlCodec : public ImageCodec {
AuxOut cinfo_;
CompressParams cparams_;
bool has_ctransform_ = false;
- DecompressParams dparams_;
+ extras::JXLDecompressParams dparams_;
bool uint8_ = false;
+ bool normalize_bitrate_ = false;
};
ImageCodec* CreateNewJxlCodec(const BenchmarkArgs& args) {
diff --git a/media/libjxl/src/tools/benchmark/benchmark_codec_png.cc b/media/libjxl/src/tools/benchmark/benchmark_codec_png.cc
index e479d7ad24..b310b11bd9 100644
--- a/media/libjxl/src/tools/benchmark/benchmark_codec_png.cc
+++ b/media/libjxl/src/tools/benchmark/benchmark_codec_png.cc
@@ -12,6 +12,7 @@
#include <string>
+#include "lib/extras/codec.h"
#include "lib/extras/dec/apng.h"
#include "lib/extras/enc/apng.h"
#include "lib/extras/packed_image.h"
@@ -40,12 +41,12 @@ class PNGCodec : public ImageCodec {
Status ParseParam(const std::string& param) override { return true; }
Status Compress(const std::string& filename, const CodecInOut* io,
- ThreadPoolInternal* pool, PaddedBytes* compressed,
+ ThreadPoolInternal* pool, std::vector<uint8_t>* compressed,
jpegxl::tools::SpeedStats* speed_stats) override {
const size_t bits = io->metadata.m.bit_depth.bits_per_sample;
const double start = Now();
- JXL_RETURN_IF_ERROR(extras::EncodeImageAPNG(io, io->Main().c_current(),
- bits, pool, compressed));
+ JXL_RETURN_IF_ERROR(Encode(*io, extras::Codec::kPNG, io->Main().c_current(),
+ bits, compressed, pool));
const double end = Now();
speed_stats->NotifyElapsed(end - start);
return true;
@@ -72,4 +73,4 @@ ImageCodec* CreateNewPNGCodec(const BenchmarkArgs& args) {
} // namespace jxl
-#endif \ No newline at end of file
+#endif
diff --git a/media/libjxl/src/tools/benchmark/benchmark_codec_webp.cc b/media/libjxl/src/tools/benchmark/benchmark_codec_webp.cc
index 376018c364..3b1bb264d3 100644
--- a/media/libjxl/src/tools/benchmark/benchmark_codec_webp.cc
+++ b/media/libjxl/src/tools/benchmark/benchmark_codec_webp.cc
@@ -40,11 +40,10 @@ Status FromSRGB(const size_t xsize, const size_t ysize, const bool is_gray,
const ColorEncoding& c = ColorEncoding::SRGB(is_gray);
const size_t bits_per_sample = (is_16bit ? 2 : 1) * kBitsPerByte;
const Span<const uint8_t> span(pixels, end - pixels);
- return ConvertFromExternal(span, xsize, ysize, c,
- (is_gray ? 1 : 3) + (has_alpha ? 1 : 0),
- alpha_is_premultiplied, bits_per_sample,
- endianness, /*flipped_y=*/false, pool, ib,
- /*float_in=*/false, /*align=*/0);
+ return ConvertFromExternal(
+ span, xsize, ysize, c, (is_gray ? 1 : 3) + (has_alpha ? 1 : 0),
+ alpha_is_premultiplied, bits_per_sample, endianness, pool, ib,
+ /*float_in=*/false, /*align=*/0);
}
struct WebPArgs {
@@ -86,7 +85,7 @@ class WebPCodec : public ImageCodec {
}
Status Compress(const std::string& filename, const CodecInOut* io,
- ThreadPoolInternal* pool, PaddedBytes* compressed,
+ ThreadPoolInternal* pool, std::vector<uint8_t>* compressed,
jpegxl::tools::SpeedStats* speed_stats) override {
const double start = Now();
const ImageBundle& ib = io->Main();
@@ -105,7 +104,7 @@ class WebPCodec : public ImageCodec {
size_t xsize = ib.oriented_xsize();
size_t ysize = ib.oriented_ysize();
size_t stride = xsize * num_chans;
- PaddedBytes srgb(stride * ysize);
+ std::vector<uint8_t> srgb(stride * ysize);
JXL_RETURN_IF_ERROR(ConvertToExternal(
*transformed, 8, /*float_out=*/false, num_chans, JXL_BIG_ENDIAN, stride,
pool, srgb.data(), srgb.size(),
@@ -216,17 +215,18 @@ class WebPCodec : public ImageCodec {
static int WebPStringWrite(const uint8_t* data, size_t data_size,
const WebPPicture* const picture) {
if (data_size) {
- PaddedBytes* const out = static_cast<PaddedBytes*>(picture->custom_ptr);
+ std::vector<uint8_t>* const out =
+ static_cast<std::vector<uint8_t>*>(picture->custom_ptr);
const size_t pos = out->size();
out->resize(pos + data_size);
memcpy(out->data() + pos, data, data_size);
}
return 1;
}
- Status CompressInternal(const PaddedBytes& srgb, size_t xsize, size_t ysize,
- size_t num_chans, int quality,
- PaddedBytes* compressed) {
- *compressed = PaddedBytes();
+ Status CompressInternal(const std::vector<uint8_t>& srgb, size_t xsize,
+ size_t ysize, size_t num_chans, int quality,
+ std::vector<uint8_t>* compressed) {
+ compressed->clear();
WebPConfig config;
WebPConfigInit(&config);
JXL_ASSERT(!lossless_ || !near_lossless_); // can't have both
diff --git a/media/libjxl/src/tools/benchmark/benchmark_stats.cc b/media/libjxl/src/tools/benchmark/benchmark_stats.cc
index ef5932aa9d..f22e89c84f 100644
--- a/media/libjxl/src/tools/benchmark/benchmark_stats.cc
+++ b/media/libjxl/src/tools/benchmark/benchmark_stats.cc
@@ -166,6 +166,13 @@ void BenchmarkStats::Assimilate(const BenchmarkStats& victim) {
void BenchmarkStats::PrintMoreStats() const {
if (Args()->print_more_stats) {
jxl_stats.Print();
+ size_t total_bits = jxl_stats.aux_out.TotalBits();
+ size_t compressed_bits = total_compressed_size * kBitsPerByte;
+ if (total_bits != compressed_bits) {
+ printf("Total layer bits: %" PRIuS " vs total compressed bits: %" PRIuS
+ " (%.2f%% accounted for)\n",
+ total_bits, compressed_bits, total_bits * 100.0 / compressed_bits);
+ }
}
if (Args()->print_distance_percentiles) {
std::vector<float> sorted = distances;
diff --git a/media/libjxl/src/tools/benchmark/benchmark_xl.cc b/media/libjxl/src/tools/benchmark/benchmark_xl.cc
index e91fbb8c5a..fed5e9b1ba 100644
--- a/media/libjxl/src/tools/benchmark/benchmark_xl.cc
+++ b/media/libjxl/src/tools/benchmark/benchmark_xl.cc
@@ -72,7 +72,7 @@ Status ReadPNG(const std::string& filename, Image3F* image) {
void DoCompress(const std::string& filename, const CodecInOut& io,
const std::vector<std::string>& extra_metrics_commands,
ImageCodec* codec, ThreadPoolInternal* inner_pool,
- PaddedBytes* compressed, BenchmarkStats* s) {
+ std::vector<uint8_t>* compressed, BenchmarkStats* s) {
PROFILER_FUNC;
++s->total_input_files;
@@ -131,10 +131,9 @@ void DoCompress(const std::string& filename, const CodecInOut& io,
}
if (valid && Args()->decode_only) {
- std::string data_in;
+ std::vector<uint8_t> data_in;
JXL_CHECK(ReadFile(filename, &data_in));
- compressed->append((uint8_t*)data_in.data(),
- (uint8_t*)data_in.data() + data_in.size());
+ compressed->insert(compressed->end(), data_in.begin(), data_in.end());
}
// Decompress
@@ -266,9 +265,16 @@ void DoCompress(const std::string& filename, const CodecInOut& io,
std::string dir = FileDirName(filename);
std::string outdir =
Args()->output_dir.empty() ? dir + "/out" : Args()->output_dir;
- // Make compatible for filename
- std::replace(codec_name.begin(), codec_name.end(), ':', '_');
- std::string compressed_fn = outdir + "/" + name + "." + codec_name;
+ std::string compressed_fn = outdir + "/" + name;
+ // Add in the parameters of the codec_name in reverse order, so that the
+ // name of the file format (e.g. jxl) is last.
+ int pos = static_cast<int>(codec_name.size()) - 1;
+ while (pos > 0) {
+ int prev = codec_name.find_last_of(':', pos);
+ if (prev > pos) prev = -1;
+ compressed_fn += '.' + codec_name.substr(prev + 1, pos - prev);
+ pos = prev - 1;
+ }
std::string decompressed_fn = compressed_fn + Args()->output_extension;
#if JPEGXL_ENABLE_APNG
std::string heatmap_fn = compressed_fn + ".heatmap.png";
@@ -1043,7 +1049,7 @@ class Benchmark {
Task& t = (*tasks)[i];
const CodecInOut& image = loaded_images[t.idx_image];
t.image = &image;
- PaddedBytes compressed;
+ std::vector<uint8_t> compressed;
DoCompress(fnames[t.idx_image], image, extra_metrics_commands,
t.codec.get(), inner_pools[thread].get(), &compressed,
&t.stats);
diff --git a/media/libjxl/src/tools/box/CMakeLists.txt b/media/libjxl/src/tools/box/CMakeLists.txt
index 3072cfef0b..c79add000b 100644
--- a/media/libjxl/src/tools/box/CMakeLists.txt
+++ b/media/libjxl/src/tools/box/CMakeLists.txt
@@ -18,7 +18,7 @@ target_include_directories(box
"${PROJECT_SOURCE_DIR}"
)
-if(${JPEGXL_ENABLE_DEVTOOLS})
+if(JPEGXL_ENABLE_DEVTOOLS)
add_executable(box_list
box_list_main.cc
)
diff --git a/media/libjxl/src/tools/box/box.cc b/media/libjxl/src/tools/box/box.cc
index a60af5b14b..db73c7ca72 100644
--- a/media/libjxl/src/tools/box/box.cc
+++ b/media/libjxl/src/tools/box/box.cc
@@ -38,17 +38,15 @@ jxl::Status ParseBoxHeader(const uint8_t** next_in, size_t* available_in,
// Total box_size including this header itself.
uint64_t box_size = LoadBE32(in + pos);
- memcpy(box->type, in + pos + 4, 4);
-
- pos += 8;
-
+ pos += 4;
if (box_size == 1) {
// If the size is 1, it indicates extended size read from 64-bit integer.
if (OutOfBounds(pos, 8, size)) return JXL_FAILURE("out of bounds");
box_size = LoadBE64(in + pos);
pos += 8;
}
-
+ memcpy(box->type, in + pos, 4);
+ pos += 4;
if (!memcmp("uuid", box->type, 4)) {
if (OutOfBounds(pos, 16, size)) return JXL_FAILURE("out of bounds");
memcpy(box->extended_type, in + pos, 16);
@@ -282,53 +280,6 @@ jxl::Status EncodeJpegXlContainerOneShot(const JpegXlContainer& container,
// TODO(veluca): the format defined here encode some things multiple times. Fix
// that.
-jxl::Status DecodeJpegXlToJpeg(jxl::DecompressParams params,
- const JpegXlContainer& container,
- jxl::CodecInOut* io, jxl::ThreadPool* pool) {
- params.keep_dct = true;
- if (container.jpeg_reconstruction == nullptr) {
- return JXL_FAILURE(
- "Cannot decode to JPEG without a JPEG reconstruction box");
- }
-
- io->Main().jpeg_data = jxl::make_unique<jxl::jpeg::JPEGData>();
-
- JXL_RETURN_IF_ERROR(DecodeJPEGData(
- jxl::Span<const uint8_t>(container.jpeg_reconstruction,
- container.jpeg_reconstruction_size),
- io->Main().jpeg_data.get()));
-
- auto& jpeg_data = io->Main().jpeg_data;
- bool have_exif = false, have_xmp = false;
- for (size_t i = 0; i < jpeg_data->app_data.size(); i++) {
- if (jpeg_data->app_marker_type[i] == jxl::jpeg::AppMarkerType::kExif) {
- if (have_exif)
- return JXL_FAILURE("Unexpected: more than one Exif box required?");
- if (jpeg_data->app_data[i].size() != container.exif_size + 9) {
- return JXL_FAILURE(
- "Exif box size does not match JPEG reconstruction data");
- }
- have_exif = true;
- memcpy(&jpeg_data->app_data[i][3 + 6], container.exif,
- container.exif_size);
- }
- if (jpeg_data->app_marker_type[i] == jxl::jpeg::AppMarkerType::kXMP) {
- if (have_xmp)
- return JXL_FAILURE("Unexpected: more than one XMP box required?");
- if (jpeg_data->app_data[i].size() != container.xml[0].second + 32) {
- return JXL_FAILURE(
- "XMP box size does not match JPEG reconstruction data");
- }
- have_xmp = true;
- memcpy(&jpeg_data->app_data[i][3 + 29], container.xml[0].first,
- container.xml[0].second);
- }
- }
-
- JXL_RETURN_IF_ERROR(DecodeFile(
- params, jxl::Span<const uint8_t>(container.codestream), io, pool));
- return true;
-}
} // namespace tools
} // namespace jpegxl
diff --git a/media/libjxl/src/tools/box/box.h b/media/libjxl/src/tools/box/box.h
index d6fd34fb67..4cc3058982 100644
--- a/media/libjxl/src/tools/box/box.h
+++ b/media/libjxl/src/tools/box/box.h
@@ -14,7 +14,6 @@
#include "lib/jxl/base/padded_bytes.h"
#include "lib/jxl/base/status.h"
#include "lib/jxl/codec_in_out.h"
-#include "lib/jxl/dec_file.h"
#include "lib/jxl/enc_file.h"
namespace jpegxl {
@@ -108,12 +107,6 @@ jxl::Status DecodeJpegXlContainerOneShot(const uint8_t* data, size_t size,
jxl::Status EncodeJpegXlContainerOneShot(const JpegXlContainer& container,
jxl::PaddedBytes* out);
-// TODO(veluca): this doesn't really belong here.
-jxl::Status DecodeJpegXlToJpeg(jxl::DecompressParams params,
- const JpegXlContainer& container,
- jxl::CodecInOut* io,
- jxl::ThreadPool* pool = nullptr);
-
} // namespace tools
} // namespace jpegxl
diff --git a/media/libjxl/src/tools/build_cleaner.py b/media/libjxl/src/tools/build_cleaner.py
index 0a0df75635..76857d7995 100755
--- a/media/libjxl/src/tools/build_cleaner.py
+++ b/media/libjxl/src/tools/build_cleaner.py
@@ -54,7 +54,7 @@ def SplitLibFiles(repo_files):
"""
testonly = (
- 'testdata.h', 'test_utils.h', '_test.h', '_test.cc',
+ 'testdata.h', 'test_utils.h', 'test_image.h', '_test.h', '_test.cc',
# _testonly.* files are library code used in tests only.
'_testonly.h', '_testonly.cc'
)
@@ -100,10 +100,6 @@ def SplitLibFiles(repo_files):
"lib/jxl/progressive_split.h",
# TODO(deymo): Add luminance.cc and luminance.h here too. Currently used
# by aux_out.h.
- # dec_file is not intended to be part of the decoder library, so move it
- # to the encoder source set
- "lib/jxl/dec_file.cc",
- "lib/jxl/dec_file.h",
])
# Temporarily remove enc_bit_writer from the encoder sources: a lot of
# decoder source code still needs to be split up into encoder and decoder.
diff --git a/media/libjxl/src/tools/butteraugli_main.cc b/media/libjxl/src/tools/butteraugli_main.cc
index f212aadd7c..247ade8d47 100644
--- a/media/libjxl/src/tools/butteraugli_main.cc
+++ b/media/libjxl/src/tools/butteraugli_main.cc
@@ -100,9 +100,11 @@ Status RunButteraugli(const char* pathname1, const char* pathname2,
int main(int argc, char** argv) {
if (argc < 3) {
fprintf(stderr,
- "Usage: %s <reference> <distorted> [--distmap <distmap>] "
- "[--intensity_target <intensity_target>]\n"
- "[--colorspace <colorspace_hint>]\n"
+ "Usage: %s <reference> <distorted>\n"
+ " [--distmap <distmap>]\n"
+ " [--intensity_target <intensity_target>]\n"
+ " [--colorspace <colorspace_hint>]\n"
+ " [--pnorm <pth norm>]\n"
"NOTE: images get converted to linear sRGB for butteraugli. Images"
" without attached profiles (such as ppm or pfm) are interpreted"
" as nonlinear sRGB. The hint format is RGB_D65_SRG_Rel_Lin for"
@@ -121,7 +123,7 @@ int main(int argc, char** argv) {
} else if (std::string(argv[i]) == "--colorspace" && i + 1 < argc) {
colorspace = argv[++i];
} else if (std::string(argv[i]) == "--intensity_target" && i + 1 < argc) {
- intensity_target = std::stof(std::string(argv[i + 1]));
+ intensity_target = std::stof(std::string(argv[++i]));
} else if (std::string(argv[i]) == "--pnorm" && i + 1 < argc) {
char* end;
p = strtod(argv[++i], &end);
diff --git a/media/libjxl/src/tools/cjxl.cc b/media/libjxl/src/tools/cjxl.cc
deleted file mode 100644
index 7d61feba64..0000000000
--- a/media/libjxl/src/tools/cjxl.cc
+++ /dev/null
@@ -1,769 +0,0 @@
-// Copyright (c) the JPEG XL Project Authors. All rights reserved.
-//
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-#include "tools/cjxl.h"
-
-#include <math.h>
-#include <stdint.h>
-#include <stdio.h>
-
-#include <algorithm>
-#include <string>
-#include <utility>
-#include <vector>
-
-#include "lib/extras/codec.h"
-#include "lib/extras/time.h"
-#include "lib/jxl/aux_out.h"
-#include "lib/jxl/base/cache_aligned.h"
-#include "lib/jxl/base/compiler_specific.h"
-#include "lib/jxl/base/data_parallel.h"
-#include "lib/jxl/base/file_io.h"
-#include "lib/jxl/base/padded_bytes.h"
-#include "lib/jxl/base/printf_macros.h"
-#include "lib/jxl/base/profiler.h"
-#include "lib/jxl/base/status.h"
-#include "lib/jxl/base/thread_pool_internal.h"
-#include "lib/jxl/codec_in_out.h"
-#include "lib/jxl/common.h"
-#include "lib/jxl/enc_cache.h"
-#include "lib/jxl/enc_color_management.h"
-#include "lib/jxl/enc_file.h"
-#include "lib/jxl/enc_params.h"
-#include "lib/jxl/frame_header.h"
-#include "lib/jxl/image.h"
-#include "lib/jxl/image_bundle.h"
-#include "lib/jxl/jpeg/enc_jpeg_data.h"
-#include "lib/jxl/modular/encoding/encoding.h"
-#include "tools/args.h"
-#include "tools/box/box.h"
-#include "tools/speed_stats.h"
-
-namespace jpegxl {
-namespace tools {
-namespace {
-
-static inline bool ParseSpeedTier(const char* arg, jxl::SpeedTier* out) {
- return jxl::ParseSpeedTier(arg, out);
-}
-static inline bool ParseColorTransform(const char* arg,
- jxl::ColorTransform* out) {
- size_t value = 0;
- bool ret = ParseUnsigned(arg, &value);
- if (ret && value > 2) ret = false;
- if (ret) *out = jxl::ColorTransform(value);
- return ret;
-}
-static inline bool ParseIntensityTarget(const char* arg, float* out) {
- return ParseFloat(arg, out) && *out > 0;
-}
-static inline bool ParsePhotonNoiseParameter(const char* arg, float* out) {
- return strncmp(arg, "ISO", 3) == 0 && ParseFloat(arg + 3, out) && *out > 0;
-}
-
-// Proposes a distance to try for a given bpp target. This could depend
-// on the entropy in the image, too, but let's start with something.
-static double ApproximateDistanceForBPP(double bpp) {
- return 1.704 * pow(bpp, -0.804);
-}
-
-jxl::Status LoadSaliencyMap(const std::string& filename_heatmap,
- jxl::ThreadPool* pool, jxl::ImageF* out_map) {
- jxl::CodecInOut io_heatmap;
- if (!SetFromFile(filename_heatmap, jxl::extras::ColorHints(), &io_heatmap,
- pool)) {
- return JXL_FAILURE("Could not load heatmap.");
- }
- *out_map = std::move(io_heatmap.Main().color()->Plane(0));
- return true;
-}
-
-void SetParametersForSizeOrBitrate(jxl::ThreadPoolInternal* pool,
- const size_t pixels, CompressArgs* args) {
- CompressArgs s = *args; // Args for search.
-
- // If fixed size, convert to bitrate.
- if (s.params.target_size > 0) {
- s.params.target_bitrate = s.params.target_size * 8.0 / pixels;
- s.params.target_size = 0;
- }
- const double target_size = s.params.target_bitrate * (1 / 8.) * pixels;
-
- double dist = ApproximateDistanceForBPP(s.params.target_bitrate);
- s.params.target_bitrate = 0;
- double best_dist = 1.0;
- double best_loss = 1e99;
-
- jxl::CodecInOut io;
- double decode_mps = 0;
- if (!LoadAll(*args, pool, &io, &decode_mps)) {
- s.params.butteraugli_distance = static_cast<float>(dist);
- printf("couldn't load image\n");
- return;
- }
-
- for (int i = 0; i < 7; ++i) {
- s.params.butteraugli_distance = static_cast<float>(dist);
- jxl::PaddedBytes candidate;
- bool ok =
- CompressJxl(io, decode_mps, pool, s, &candidate, /*print_stats=*/false);
- if (!ok) {
- printf(
- "Compression error occurred during the search for best size. "
- "Trying with butteraugli distance %.15g\n",
- best_dist);
- break;
- }
- printf("Butteraugli distance %.3f yields %6" PRIuS " bytes, %.3f bpp.\n",
- dist, candidate.size(), candidate.size() * 8.0 / pixels);
- const double ratio = static_cast<double>(candidate.size()) / target_size;
- const double loss = std::max(ratio, 1.0 / std::max(ratio, 1e-30));
- if (best_loss > loss) {
- best_dist = dist;
- best_loss = loss;
- }
- dist *= ratio;
- if (dist < 0.01) {
- dist = 0.01;
- }
- if (dist >= 16.0) {
- dist = 16.0;
- }
- }
- args->params.butteraugli_distance = static_cast<float>(best_dist);
- args->params.target_bitrate = 0;
- args->params.target_size = 0;
-}
-
-const char* ModeFromArgs(const CompressArgs& args) {
- if (args.jpeg_transcode) return "JPEG";
- if (args.params.modular_mode) return "Modular";
- return "VarDCT";
-}
-
-std::string QualityFromArgs(const CompressArgs& args) {
- char buf[100];
- if (args.jpeg_transcode) {
- snprintf(buf, sizeof(buf), "lossless transcode");
- } else if (args.params.IsLossless()) {
- snprintf(buf, sizeof(buf), "lossless");
- } else {
- snprintf(buf, sizeof(buf), "d%.3f", args.params.butteraugli_distance);
- }
- return buf;
-}
-
-void PrintMode(jxl::ThreadPoolInternal* pool, const jxl::CodecInOut& io,
- const double decode_mps, const CompressArgs& args) {
- const char* mode = ModeFromArgs(args);
- const char* speed = SpeedTierName(args.params.speed_tier);
- const std::string quality = QualityFromArgs(args);
- fprintf(stderr,
- "Read %" PRIuS "x%" PRIuS
- " image, %.1f MP/s\n"
- "Encoding [%s%s, %s, %s",
- io.xsize(), io.ysize(), decode_mps,
- (args.use_container ? "Container | " : ""), mode, quality.c_str(),
- speed);
- if (args.use_container) {
- if (args.jpeg_transcode && args.store_jpeg_metadata)
- fprintf(stderr, " | JPEG reconstruction data");
- if (!io.blobs.exif.empty())
- fprintf(stderr, " | %" PRIuS "-byte Exif", io.blobs.exif.size());
- if (!io.blobs.xmp.empty())
- fprintf(stderr, " | %" PRIuS "-byte XMP", io.blobs.xmp.size());
- if (!io.blobs.jumbf.empty())
- fprintf(stderr, " | %" PRIuS "-byte JUMBF", io.blobs.jumbf.size());
- }
- fprintf(stderr, "], %" PRIuS " threads.\n", pool->NumWorkerThreads());
-}
-
-} // namespace
-
-void CompressArgs::AddCommandLineOptions(CommandLineParser* cmdline) {
- // Positional arguments.
- cmdline->AddPositionalOption("INPUT", /* required = */ true,
- "the input can be "
-#if JPEGXL_ENABLE_APNG
- "PNG, APNG, "
-#endif
-#if JPEGXL_ENABLE_GIF
- "GIF, "
-#endif
-#if JPEGXL_ENABLE_JPEG
- "JPEG, "
-#else
- "JPEG (lossless recompression only), "
-#endif
-#if JPEGXL_ENABLE_EXR
- "EXR, "
-#endif
- "PPM, PFM, or PGX",
- &file_in);
- cmdline->AddPositionalOption(
- "OUTPUT", /* required = */ true,
- "the compressed JXL output file (can be omitted for benchmarking)",
- &file_out);
-
- // Flags.
- // TODO(lode): also add options to add exif/xmp/other metadata in the
- // container.
- // TODO(lode): decide on good name for this flag: box, container, bmff, ...
- cmdline->AddOptionFlag(
- '\0', "container",
- "Always encode using container format (default: only if needed)",
- &use_container, &SetBooleanTrue, 1);
-
- cmdline->AddOptionFlag('\0', "strip",
- "Do not encode using container format (strips "
- "Exif/XMP/JPEG bitstream reconstruction data)",
- &no_container, &SetBooleanTrue, 2);
-
- cmdline->AddOptionFlag('\0', "strip_jpeg_metadata",
- "Do not encode JPEG bitstream reconstruction data",
- &store_jpeg_metadata, &SetBooleanFalse, 2);
-
- // Target distance/size/bpp
- opt_distance_id = cmdline->AddOptionValue(
- 'd', "distance", "maxError",
- ("Max. butteraugli distance, lower = higher quality. Range: 0 .. 25.\n"
- " 0.0 = mathematically lossless. Default for already-lossy input "
- "(JPEG/GIF).\n"
- " 1.0 = visually lossless. Default for other input.\n"
- " Recommended range: 0.5 .. 3.0."),
- &params.butteraugli_distance, &ParseFloat);
- opt_target_size_id = cmdline->AddOptionValue(
- '\0', "target_size", "N",
- ("Aim at file size of N bytes.\n"
- " Compresses to 1 % of the target size in ideal conditions.\n"
- " Runs the same algorithm as --target_bpp"),
- &params.target_size, &ParseUnsigned, 1);
- opt_target_bpp_id = cmdline->AddOptionValue(
- '\0', "target_bpp", "BPP",
- ("Aim at file size that has N bits per pixel.\n"
- " Compresses to 1 % of the target BPP in ideal conditions."),
- &params.target_bitrate, &ParseFloat, 1);
-
- // High-level options
- opt_quality_id = cmdline->AddOptionValue(
- 'q', "quality", "QUALITY",
- "Quality setting (is remapped to --distance). Range: -inf .. 100.\n"
- " 100 = mathematically lossless. Default for already-lossy input "
- "(JPEG/GIF).\n Positive quality values roughly match libjpeg quality.",
- &quality, &ParseFloat);
-
- cmdline->AddOptionValue(
- 'e', "effort", "EFFORT",
- "Encoder effort setting. Range: 1 .. 9.\n"
- " Default: 7. Higher number is more effort (slower).",
- &params.speed_tier, &ParseSpeedTier);
-
- cmdline->AddOptionValue('\0', "brotli_effort", "B_EFFORT",
- "Brotli effort setting. Range: 0 .. 11.\n"
- " Default: -1 (based on EFFORT). Higher number is "
- "more effort (slower).",
- &params.brotli_effort, &ParseSigned, -1);
-
- cmdline->AddOptionValue(
- 's', "speed", "ANIMAL",
- "Deprecated synonym for --effort. Valid values are:\n"
- " lightning (1), thunder, falcon, cheetah, hare, wombat, squirrel, "
- "kitten, tortoise (9)\n"
- " Default: squirrel. Values are in order from faster to slower.\n",
- &params.speed_tier, &ParseSpeedTier, 2);
-
- cmdline->AddOptionValue('\0', "faster_decoding", "AMOUNT",
- "Favour higher decoding speed. 0 = default, higher "
- "values give higher speed at the expense of quality",
- &params.decoding_speed_tier, &ParseUnsigned, 2);
-
- cmdline->AddOptionFlag('p', "progressive",
- "Enable progressive/responsive decoding.",
- &progressive, &SetBooleanTrue);
-
- cmdline->AddOptionFlag('\0', "premultiply",
- "Force premultiplied (associated) alpha.",
- &force_premultiplied, &SetBooleanTrue, 1);
- cmdline->AddOptionValue('\0', "keep_invisible", "0|1",
- "force disable/enable preserving color of invisible "
- "pixels (default: 1 if lossless, 0 if lossy).",
- &params.keep_invisible, &ParseOverride, 1);
-
- cmdline->AddOptionFlag('\0', "centerfirst",
- "Put center groups first in the compressed file.",
- &params.centerfirst, &SetBooleanTrue, 1);
-
- cmdline->AddOptionValue('\0', "center_x", "0..XSIZE",
- "Put center groups first in the compressed file.",
- &params.center_x, &ParseUnsigned, 1);
- cmdline->AddOptionValue('\0', "center_y", "0..YSIZE",
- "Put center groups first in the compressed file.",
- &params.center_y, &ParseUnsigned, 1);
-
- // Flags.
- cmdline->AddOptionFlag('\0', "progressive_ac",
- "Use the progressive mode for AC.",
- &params.progressive_mode, &SetBooleanTrue, 1);
- cmdline->AddOptionFlag('\0', "qprogressive_ac",
- "Use the progressive mode for AC.",
- &params.qprogressive_mode, &SetBooleanTrue, 1);
- cmdline->AddOptionValue('\0', "progressive_dc", "num_dc_frames",
- "Use progressive mode for DC.",
- &params.progressive_dc, &ParseSigned, 1);
- cmdline->AddOptionFlag('m', "modular",
- "Use the modular mode (lossy / lossless).",
- &params.modular_mode, &SetBooleanTrue, 1);
- cmdline->AddOptionFlag('\0', "use_new_heuristics",
- "use new and not yet ready encoder heuristics",
- &params.use_new_heuristics, &SetBooleanTrue, 2);
-
- // JPEG modes: parallel Brunsli, pixels to JPEG, or JPEG to Brunsli
- cmdline->AddOptionFlag('j', "jpeg_transcode",
- "Do lossy transcode of input JPEG file (decode to "
- "pixels instead of doing lossless transcode).",
- &jpeg_transcode, &SetBooleanFalse, 1);
- cmdline->AddOptionFlag('\0', "jpeg_transcode_disable_cfl",
- "Disable CFL for lossless JPEG recompression",
- &params.force_cfl_jpeg_recompression, &SetBooleanFalse,
- 2);
-
- cmdline->AddOptionValue('\0', "num_threads", "N",
- "number of worker threads (zero = none).",
- &num_threads, &ParseUnsigned, 1);
- cmdline->AddOptionValue('\0', "num_reps", "N", "how many times to compress.",
- &num_reps, &ParseUnsigned, 1);
-
- cmdline->AddOptionValue('\0', "noise", "0|1",
- "force disable/enable noise generation.",
- &params.noise, &ParseOverride, 1);
- cmdline->AddOptionValue(
- '\0', "photon_noise", "ISO3200",
- "Set the noise to approximately what it would be at a given nominal "
- "exposure on a 35mm camera. For formats other than 35mm, or when the "
- "whole sensor was not used, you can multiply the ISO value by the "
- "equivalence ratio squared, for example by 2.25 for an APS-C camera.",
- &params.photon_noise_iso, &ParsePhotonNoiseParameter, 1);
- cmdline->AddOptionValue('\0', "dots", "0|1",
- "force disable/enable dots generation.", &params.dots,
- &ParseOverride, 1);
- cmdline->AddOptionValue('\0', "patches", "0|1",
- "force disable/enable patches generation.",
- &params.patches, &ParseOverride, 1);
- cmdline->AddOptionValue(
- '\0', "resampling", "-1|1|2|4|8",
- "Subsample all color channels by this factor, or use -1 to choose the "
- "resampling factor based on distance.",
- &params.resampling, &ParseSigned, 0);
- cmdline->AddOptionValue(
- '\0', "ec_resampling", "1|2|4|8",
- "Subsample all extra channels by this factor. If this value is smaller "
- "than the resampling of color channels, it will be increased to match.",
- &params.ec_resampling, &ParseSigned, 2);
- cmdline->AddOptionFlag('\0', "already_downsampled",
- "Do not downsample the given input before encoding, "
- "but still signal that the decoder should upsample.",
- &params.already_downsampled, &SetBooleanTrue, 2);
-
- cmdline->AddOptionValue(
- '\0', "epf", "-1..3",
- "Edge preserving filter level (-1 = choose based on quality, default)",
- &params.epf, &ParseSigned, 1);
-
- cmdline->AddOptionValue('\0', "gaborish", "0|1",
- "force disable/enable gaborish.", &params.gaborish,
- &ParseOverride, 1);
-
- opt_intensity_target_id = cmdline->AddOptionValue(
- '\0', "intensity_target", "N",
- ("Intensity target of monitor in nits, higher\n"
- " results in higher quality image. Must be strictly positive.\n"
- " Default is 255 for standard images, 4000 for input images known to\n"
- " to have PQ or HLG transfer function."),
- &intensity_target, &ParseIntensityTarget, 1);
-
- cmdline->AddOptionValue('\0', "saliency_num_progressive_steps", "N", nullptr,
- &params.saliency_num_progressive_steps,
- &ParseUnsigned, 2);
- cmdline->AddOptionValue('\0', "saliency_map_filename", "STRING", nullptr,
- &saliency_map_filename, &ParseString, 2);
- cmdline->AddOptionValue('\0', "saliency_threshold", "0..1", nullptr,
- &params.saliency_threshold, &ParseFloat, 2);
-
- cmdline->AddOptionValue(
- 'x', "dec-hints", "key=value",
- "color_space indicates the ColorEncoding, see Description();\n"
- "icc_pathname refers to a binary file containing an ICC profile.",
- &color_hints, &ParseAndAppendKeyValue, 1);
-
- cmdline->AddOptionValue(
- '\0', "override_bitdepth", "0=use from image, 1-32=override",
- "If nonzero, store the given bit depth in the JPEG XL file metadata"
- " (1-32), instead of using the bit depth from the original input"
- " image.",
- &override_bitdepth, &ParseUnsigned, 2);
-
- opt_color_id = cmdline->AddOptionValue(
- 'c', "colortransform", "0..2", "0=XYB, 1=None, 2=YCbCr",
- &params.color_transform, &ParseColorTransform, 2);
-
- // modular mode options
- cmdline->AddOptionValue(
- 'I', "iterations", "F",
- "[modular encoding] fraction of pixels used to learn MA trees "
- "(default=0.5, try 0 for no MA and fast decode)",
- &params.options.nb_repeats, &ParseFloat, 2);
-
- cmdline->AddOptionValue(
- 'C', "colorspace", "K",
- ("[modular encoding] color transform: 0=RGB, 1=YCoCg, "
- "2-37=RCT (default: try several, depending on speed)"),
- &params.colorspace, &ParseSigned, 1);
-
- opt_m_group_size_id = cmdline->AddOptionValue(
- 'g', "group-size", "K",
- ("[modular encoding] set group size to 128 << K "
- "(default: 1 or 2)"),
- &params.modular_group_size_shift, &ParseUnsigned, 1);
-
- cmdline->AddOptionValue(
- 'P', "predictor", "K",
- "[modular encoding] predictor(s) to use: 0=zero, "
- "1=left, 2=top, 3=avg0, 4=select, 5=gradient, 6=weighted, "
- "7=topright, 8=topleft, 9=leftleft, 10=avg1, 11=avg2, 12=avg3, "
- "13=toptop predictive average "
- "14=mix 5 and 6, 15=mix everything. Default 14, at slowest speed "
- "default 15",
- &params.options.predictor, &ParsePredictor, 1);
-
- cmdline->AddOptionValue(
- 'E', "extra-properties", "K",
- "[modular encoding] number of extra MA tree properties to use",
- &params.options.max_properties, &ParseSigned, 2);
-
- cmdline->AddOptionValue('\0', "palette", "K",
- "[modular encoding] use a palette if image has at "
- "most K colors (default: 1024)",
- &params.palette_colors, &ParseSigned, 1);
-
- cmdline->AddOptionFlag(
- '\0', "lossy-palette",
- "[modular encoding] quantize to a palette that has fewer entries than "
- "would be necessary for perfect preservation; for the time being, it is "
- "recommended to set --palette=0 with this option to use the default "
- "palette only",
- &params.lossy_palette, &SetBooleanTrue, 1);
-
- cmdline->AddOptionValue(
- 'X', "pre-compact", "PERCENT",
- ("[modular encoding] compact channels (globally) if ratio "
- "used/range is below this (default: 80%)"),
- &params.channel_colors_pre_transform_percent, &ParseFloat, 2);
-
- cmdline->AddOptionValue(
- 'Y', "post-compact", "PERCENT",
- ("[modular encoding] compact channels (per-group) if ratio "
- "used/range is below this (default: 80%)"),
- &params.channel_colors_percent, &ParseFloat, 2);
-
- cmdline->AddOptionValue('R', "responsive", "K",
- "[modular encoding] do Squeeze transform, 0=false, "
- "1=true (default: true if lossy, false if lossless)",
- &params.responsive, &ParseSigned, 1);
-
- cmdline->AddOptionFlag('V', "version", "Print version number and exit",
- &version, &SetBooleanTrue, 1);
- cmdline->AddOptionFlag('\0', "quiet", "Be more silent", &quiet,
- &SetBooleanTrue, 1);
- cmdline->AddOptionValue('\0', "print_profile", "0|1",
- "Print timing information before exiting",
- &print_profile, &ParseOverride, 1);
-
- cmdline->AddOptionFlag(
- 'v', "verbose",
- "Verbose output; can be repeated, also applies to help (!).",
- &params.verbose, &SetBooleanTrue);
-}
-
-jxl::Status CompressArgs::ValidateArgs(const CommandLineParser& cmdline) {
- params.file_in = file_in;
- params.file_out = file_out;
-
- if (file_in == nullptr) {
- fprintf(stderr, "Missing INPUT filename.\n");
- return false;
- }
-
- bool got_distance = cmdline.GetOption(opt_distance_id)->matched();
- bool got_target_size = cmdline.GetOption(opt_target_size_id)->matched();
- bool got_target_bpp = cmdline.GetOption(opt_target_bpp_id)->matched();
- bool got_quality = cmdline.GetOption(opt_quality_id)->matched();
- bool got_intensity_target =
- cmdline.GetOption(opt_intensity_target_id)->matched();
-
- if (got_quality) {
- default_settings = false;
- if (quality < 100) jpeg_transcode = false;
- if (quality < 7 || quality == 100 || params.modular_mode) {
- if (jpeg_transcode == false) params.modular_mode = true;
- }
- // Quality settings roughly match libjpeg qualities.
- if (quality >= 100) {
- params.butteraugli_distance = 0;
- } else if (quality >= 30) {
- params.butteraugli_distance = 0.1 + (100 - quality) * 0.09;
- } else {
- params.butteraugli_distance =
- 6.4 + pow(2.5, (30 - quality) / 5.0f) / 6.25f;
- }
- }
-
- if (params.resampling > 1 && !params.already_downsampled)
- jpeg_transcode = false;
-
- if (progressive) {
- params.qprogressive_mode = true;
- params.responsive = 1;
- default_settings = false;
- }
- if (got_target_size || got_target_bpp || got_intensity_target) {
- default_settings = false;
- }
-
- if (params.progressive_dc < -1 || params.progressive_dc > 2) {
- fprintf(stderr, "Invalid/out of range progressive_dc (%d), try -1 to 2.\n",
- params.progressive_dc);
- return false;
- }
-
- if (got_distance) {
- constexpr float butteraugli_min_dist = 0.1f;
- constexpr float butteraugli_max_dist = 25.0f;
- if (!(0 <= params.butteraugli_distance &&
- params.butteraugli_distance <= butteraugli_max_dist)) {
- fprintf(stderr, "Invalid/out of range distance, try 0 to %g.\n",
- butteraugli_max_dist);
- return false;
- }
- if (params.butteraugli_distance > 0) jpeg_transcode = false;
- if (params.butteraugli_distance == 0) {
- // Use modular for lossless.
- if (jpeg_transcode == false) params.modular_mode = true;
- } else if (params.butteraugli_distance < butteraugli_min_dist) {
- params.butteraugli_distance = butteraugli_min_dist;
- }
- default_settings = false;
- }
-
- if (got_target_bpp || got_target_size) {
- jpeg_transcode = false;
- }
- if (params.brotli_effort > 11) {
- fprintf(stderr, "Invalid --brotli_effort value\n");
- return false;
- }
- if (got_target_bpp + got_target_size + got_distance + got_quality > 1) {
- fprintf(stderr,
- "You can specify only one of '--distance', '-q', "
- "'--target_bpp' and '--target_size'. They are all different ways"
- " to specify the image quality. When in doubt, use --distance."
- " It gives the most visually consistent results.\n");
- return false;
- }
-
- if (!saliency_map_filename.empty()) {
- if (!params.progressive_mode) {
- saliency_map_filename.clear();
- fprintf(stderr,
- "Warning: Specifying --saliency_map_filename only makes sense "
- "for --progressive_ac mode.\n");
- }
- }
-
- if (!params.file_in) {
- fprintf(stderr, "Missing input filename.\n");
- return false;
- }
-
- if (!cmdline.GetOption(opt_color_id)->matched()) {
- // default to RGB for lossless modular
- if (params.modular_mode) {
- if (params.butteraugli_distance > 0.f) {
- params.color_transform = jxl::ColorTransform::kXYB;
- } else {
- params.color_transform = jxl::ColorTransform::kNone;
- }
- }
- }
-
- if (override_bitdepth > 32) {
- fprintf(stderr, "override_bitdepth must be <= 32\n");
- return false;
- }
-
- if (params.epf > 3) {
- fprintf(stderr, "--epf must be in the 0..3 range\n");
- return false;
- }
-
- return true;
-}
-
-jxl::Status CompressArgs::ValidateArgsAfterLoad(
- const CommandLineParser& cmdline, const jxl::CodecInOut& io) {
- if (!ValidateArgs(cmdline)) return false;
- bool got_m_group_size = cmdline.GetOption(opt_m_group_size_id)->matched();
- if (params.modular_mode && !got_m_group_size) {
- // Default modular group size: set to 512 if 256 would be silly
- const size_t kThinImageThr = 256 + 64;
- const size_t kSmallImageThr = 256 + 128;
- if (io.xsize() < kThinImageThr || io.ysize() < kThinImageThr ||
- (io.xsize() < kSmallImageThr && io.ysize() < kSmallImageThr)) {
- params.modular_group_size_shift = 2;
- }
- }
- if (!io.blobs.exif.empty() || !io.blobs.xmp.empty() ||
- !io.blobs.jumbf.empty() || !io.blobs.iptc.empty() ||
- (jpeg_transcode && store_jpeg_metadata)) {
- use_container = true;
- }
- if (no_container) use_container = false;
- if (jpeg_transcode && params.modular_mode) {
- fprintf(stderr,
- "Error: cannot do lossless JPEG transcode in modular mode.\n");
- return false;
- }
- if (jpeg_transcode) {
- if (params.progressive_mode || params.qprogressive_mode ||
- params.progressive_dc > 0) {
- fprintf(stderr,
- "Error: progressive lossless JPEG transcode is not yet "
- "implemented.\n");
- return false;
- }
- }
- return true;
-}
-
-jxl::Status LoadAll(CompressArgs& args, jxl::ThreadPoolInternal* pool,
- jxl::CodecInOut* io, double* decode_mps) {
- const double t0 = jxl::Now();
-
- jxl::PaddedBytes encoded;
- JXL_RETURN_IF_ERROR(jxl::ReadFile(args.params.file_in, &encoded));
- jxl::extras::Codec input_codec;
- bool ok;
- if (args.jpeg_transcode && encoded.size() >= 2 && encoded[0] == 0xFF &&
- encoded[1] == 0xD8) {
- input_codec = jxl::extras::Codec::kJPG;
- ok = jxl::jpeg::DecodeImageJPG(jxl::Span<const uint8_t>(encoded), io);
- } else {
- ok = jxl::SetFromBytes(jxl::Span<const uint8_t>(encoded), args.color_hints,
- io, nullptr, &input_codec);
- }
- if (!ok) {
- fprintf(stderr, "Failed to read image %s.\n", args.params.file_in);
- return false;
- }
- if (args.intensity_target != 0) {
- io->metadata.m.SetIntensityTarget(args.intensity_target);
- }
- if (input_codec != jxl::extras::Codec::kJPG) args.jpeg_transcode = false;
- if (args.jpeg_transcode) args.params.butteraugli_distance = 0;
-
- if (input_codec == jxl::extras::Codec::kGIF && args.default_settings) {
- args.params.modular_mode = true;
- args.params.butteraugli_distance = 0;
- }
- if (args.override_bitdepth != 0) {
- if (args.override_bitdepth == 32) {
- io->metadata.m.SetFloat32Samples();
- } else {
- io->metadata.m.SetUintSamples(args.override_bitdepth);
- }
- }
- if (args.force_premultiplied) {
- io->PremultiplyAlpha();
- }
-
- jxl::ImageF saliency_map;
- if (!args.saliency_map_filename.empty()) {
- if (!LoadSaliencyMap(args.saliency_map_filename, pool, &saliency_map)) {
- fprintf(stderr, "Failed to read saliency map %s.\n",
- args.saliency_map_filename.c_str());
- return false;
- }
- args.params.saliency_map = &saliency_map;
- }
-
- const double t1 = jxl::Now();
- const size_t pixels = io->xsize() * io->ysize();
- *decode_mps = pixels * io->frames.size() * 1E-6 / (t1 - t0);
-
- return true;
-}
-
-jxl::Status CompressJxl(jxl::CodecInOut& io, double decode_mps,
- jxl::ThreadPoolInternal* pool, CompressArgs& args,
- jxl::PaddedBytes* compressed, bool print_stats) {
- JXL_CHECK(pool);
-
- const size_t pixels = io.xsize() * io.ysize();
-
- if (args.params.target_size > 0 || args.params.target_bitrate > 0) {
- // Slow iterative search for parameters that reach target bpp / size.
- SetParametersForSizeOrBitrate(pool, pixels, &args);
- }
-
- if (print_stats) PrintMode(pool, io, decode_mps, args);
-
- // Final/actual compression run (possibly repeated for benchmarking).
- jxl::AuxOut aux_out;
- if (args.inspector_image3f) {
- aux_out.SetInspectorImage3F(args.inspector_image3f);
- }
- SpeedStats stats;
- jxl::PassesEncoderState passes_encoder_state;
- if (args.params.use_new_heuristics) {
- passes_encoder_state.heuristics =
- jxl::make_unique<jxl::FastEncoderHeuristics>();
- }
- for (size_t i = 0; i < args.num_reps; ++i) {
- const double t0 = jxl::Now();
- jxl::Status ok = false;
- if (io.Main().IsJPEG()) {
- // TODO(lode): automate this in the encoder. The encoder must in the
- // beginning choose to either do all in xyb, or all in non-xyb, write
- // that in the xyb_encoded header flag, and persistently keep that state
- // to check if every frame uses an allowed color transform.
- args.params.color_transform = io.Main().color_transform;
- }
- ok = EncodeFile(args.params, &io, &passes_encoder_state, compressed,
- jxl::GetJxlCms(), &aux_out, pool);
- if (!ok) {
- fprintf(stderr, "Failed to compress to %s.\n", ModeFromArgs(args));
- return false;
- }
- const double t1 = jxl::Now();
- stats.NotifyElapsed(t1 - t0);
- stats.SetImageSize(io.xsize(), io.ysize());
- }
-
- if (print_stats) {
- const double bpp =
- static_cast<double>(compressed->size() * jxl::kBitsPerByte) / pixels;
- fprintf(stderr, "Compressed to %" PRIuS " bytes (%.3f bpp%s).\n",
- compressed->size(), bpp / io.frames.size(),
- io.frames.size() == 1 ? "" : "/frame");
- JXL_CHECK(stats.Print(args.num_threads));
- if (args.params.verbose) {
- aux_out.Print(1);
- }
- }
-
- return true;
-}
-
-} // namespace tools
-} // namespace jpegxl
diff --git a/media/libjxl/src/tools/cjxl.h b/media/libjxl/src/tools/cjxl.h
deleted file mode 100644
index 1be3500dfd..0000000000
--- a/media/libjxl/src/tools/cjxl.h
+++ /dev/null
@@ -1,109 +0,0 @@
-// Copyright (c) the JPEG XL Project Authors. All rights reserved.
-//
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-#ifndef TOOLS_CJXL_H_
-#define TOOLS_CJXL_H_
-
-#include <stddef.h>
-
-#include <string>
-#include <thread>
-#include <utility>
-
-#include "lib/extras/dec/color_hints.h"
-#include "lib/jxl/base/data_parallel.h"
-#include "lib/jxl/base/override.h"
-#include "lib/jxl/base/padded_bytes.h"
-#include "lib/jxl/base/status.h"
-#include "lib/jxl/base/thread_pool_internal.h"
-#include "lib/jxl/codec_in_out.h"
-#include "lib/jxl/enc_params.h"
-#include "lib/jxl/jxl_inspection.h"
-#include "tools/cmdline.h"
-
-namespace jpegxl {
-namespace tools {
-
-struct CompressArgs {
- void SetInspectorImage3F(const jxl::InspectorImage3F& inspector) {
- inspector_image3f = inspector;
- }
-
- // Add all the command line options to the CommandLineParser. Note that the
- // options are tied to the instance that this was called on.
- void AddCommandLineOptions(CommandLineParser* cmdline);
-
- // Post-processes and validates the passed arguments, checking whether all
- // passed options are compatible. Returns whether the validation was
- // successful.
- jxl::Status ValidateArgs(const CommandLineParser& cmdline);
-
- // Validates the arguments again, having loaded the input so sensible defaults
- // can be chosen based on e.g. dimensions.
- jxl::Status ValidateArgsAfterLoad(const CommandLineParser& cmdline,
- const jxl::CodecInOut& io);
-
- // Common flags.
- bool version = false;
- bool use_container = false;
- bool no_container = false;
- bool quiet = false;
-
- const char* file_in = nullptr;
- const char* file_out = nullptr;
- jxl::Override print_profile = jxl::Override::kDefault;
-
- // Decoding source image flags
- jxl::extras::ColorHints color_hints;
-
- // JXL flags
- size_t override_bitdepth = 0;
- jxl::CompressParams params;
- size_t num_threads = std::thread::hardware_concurrency();
- size_t num_reps = 1;
- float intensity_target = 0;
-
- // Filename for the user provided saliency-map.
- std::string saliency_map_filename;
-
- // Whether to perform lossless transcoding with kVarDCT or kJPEG encoding.
- // If true, attempts to load JPEG coefficients instead of pixels.
- // Reset to false if input image is not a JPEG.
- bool jpeg_transcode = true;
-
- bool store_jpeg_metadata = true;
-
- float quality = -1001.f; // Default to lossless if input is already lossy,
- // or to VarDCT otherwise.
- bool progressive = false;
- bool default_settings = true;
- bool force_premultiplied = false;
-
- // Will get passed on to AuxOut.
- jxl::InspectorImage3F inspector_image3f;
-
- // References (ids) of specific options to check if they were matched.
- CommandLineParser::OptionId opt_distance_id = -1;
- CommandLineParser::OptionId opt_target_size_id = -1;
- CommandLineParser::OptionId opt_target_bpp_id = -1;
- CommandLineParser::OptionId opt_quality_id = -1;
- CommandLineParser::OptionId opt_near_lossless_id = -1;
- CommandLineParser::OptionId opt_intensity_target_id = -1;
- CommandLineParser::OptionId opt_color_id = -1;
- CommandLineParser::OptionId opt_m_group_size_id = -1;
-};
-
-jxl::Status LoadAll(CompressArgs& args, jxl::ThreadPoolInternal* pool,
- jxl::CodecInOut* io, double* decode_mps);
-
-// The input image must already have been loaded into io using LoadAll.
-jxl::Status CompressJxl(jxl::CodecInOut& io, double decode_mps,
- jxl::ThreadPoolInternal* pool, CompressArgs& args,
- jxl::PaddedBytes* compressed, bool print_stats = true);
-
-} // namespace tools
-} // namespace jpegxl
-
-#endif // TOOLS_CJXL_H_
diff --git a/media/libjxl/src/tools/cjxl_fuzzer.cc b/media/libjxl/src/tools/cjxl_fuzzer.cc
new file mode 100644
index 0000000000..f3a1d9f9d8
--- /dev/null
+++ b/media/libjxl/src/tools/cjxl_fuzzer.cc
@@ -0,0 +1,231 @@
+// Copyright (c) the JPEG XL Project Authors. All rights reserved.
+//
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+#include <limits.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <algorithm>
+#include <functional>
+#include <random>
+#include <vector>
+
+#include "hwy/targets.h"
+#include "jxl/encode.h"
+#include "jxl/encode_cxx.h"
+#include "jxl/thread_parallel_runner.h"
+#include "jxl/thread_parallel_runner_cxx.h"
+#include "lib/jxl/base/status.h"
+#include "lib/jxl/test_image.h"
+
+namespace {
+
+#define TRY(expr) \
+ do { \
+ if (JXL_ENC_SUCCESS != (expr)) return false; \
+ } while (0)
+
+struct FuzzSpec {
+ size_t xsize;
+ size_t ysize;
+ struct OptionSpec {
+ JxlEncoderFrameSettingId id;
+ int32_t value;
+ };
+ std::vector<OptionSpec> options;
+ bool is_jpeg = false;
+ bool lossless = false;
+ bool have_alpha = false;
+ bool premultiply = false;
+ bool orig_profile = true;
+ uint16_t pixels_seed = 0;
+ uint16_t alpha_seed = 0;
+ size_t bit_depth = 8;
+ size_t alpha_bit_depth = 8;
+ int32_t codestream_level = -1;
+ std::vector<uint8_t> icc;
+ JxlColorEncoding color_encoding;
+ size_t num_frames = 1;
+ size_t output_buffer_size = 1;
+};
+
+bool EncodeJpegXl(const FuzzSpec& spec) {
+ // Multi-threaded parallel runner. Limit to max 2 threads since the fuzzer
+ // itself is already multithreaded.
+ size_t num_threads =
+ std::min<size_t>(2, JxlThreadParallelRunnerDefaultNumWorkerThreads());
+ auto runner = JxlThreadParallelRunnerMake(nullptr, num_threads);
+ JxlEncoderPtr enc_ptr = JxlEncoderMake(/*memory_manager=*/nullptr);
+ JxlEncoder* enc = enc_ptr.get();
+ for (size_t num_rep = 0; num_rep < 2; ++num_rep) {
+ JxlEncoderReset(enc);
+ TRY(JxlEncoderSetParallelRunner(enc, JxlThreadParallelRunner,
+ runner.get()));
+ JxlEncoderFrameSettings* frame_settings =
+ JxlEncoderFrameSettingsCreate(enc, nullptr);
+
+ for (auto option : spec.options) {
+ TRY(JxlEncoderFrameSettingsSetOption(frame_settings, option.id,
+ option.value));
+ }
+
+ TRY(JxlEncoderSetCodestreamLevel(enc, spec.codestream_level));
+ JxlBasicInfo basic_info;
+ JxlEncoderInitBasicInfo(&basic_info);
+ basic_info.xsize = spec.xsize;
+ basic_info.ysize = spec.ysize;
+ basic_info.bits_per_sample = spec.bit_depth;
+ basic_info.uses_original_profile = spec.orig_profile;
+ if (spec.have_alpha) {
+ basic_info.alpha_bits = spec.alpha_bit_depth;
+ basic_info.num_extra_channels = 1;
+ }
+ TRY(JxlEncoderSetBasicInfo(enc, &basic_info));
+ if (spec.lossless) {
+ TRY(JxlEncoderSetFrameLossless(frame_settings, JXL_TRUE));
+ }
+
+ // TODO(szabadka) Add icc color profiles.
+ TRY(JxlEncoderSetColorEncoding(enc, &spec.color_encoding));
+
+ // TODO(szabadka) Add jpeg frames.
+ for (size_t i = 0; i < spec.num_frames; ++i) {
+ JxlFrameHeader frame_header;
+ JxlEncoderInitFrameHeader(&frame_header);
+ // TODO(szabadka) Add more frame header options.
+ TRY(JxlEncoderSetFrameHeader(frame_settings, &frame_header));
+ if (spec.have_alpha) {
+ JxlExtraChannelInfo extra_channel_info;
+ JxlEncoderInitExtraChannelInfo(JXL_CHANNEL_ALPHA, &extra_channel_info);
+ TRY(JxlEncoderSetExtraChannelInfo(enc, 0, &extra_channel_info));
+ extra_channel_info.alpha_premultiplied = spec.premultiply;
+ }
+ JxlPixelFormat pixelformat = {3, JXL_TYPE_UINT16, JXL_LITTLE_ENDIAN, 0};
+ std::vector<uint8_t> pixels = jxl::test::GetSomeTestImage(
+ spec.xsize, spec.ysize, 3, spec.pixels_seed);
+ TRY(JxlEncoderAddImageFrame(frame_settings, &pixelformat, pixels.data(),
+ pixels.size()));
+ if (spec.have_alpha) {
+ std::vector<uint8_t> alpha_pixels = jxl::test::GetSomeTestImage(
+ spec.xsize, spec.ysize, 1, spec.alpha_seed);
+ TRY(JxlEncoderSetExtraChannelBuffer(frame_settings, &pixelformat,
+ alpha_pixels.data(),
+ alpha_pixels.size(), 0));
+ }
+ }
+ // Reading compressed output
+ JxlEncoderStatus process_result = JXL_ENC_NEED_MORE_OUTPUT;
+ while (process_result == JXL_ENC_NEED_MORE_OUTPUT) {
+ std::vector<uint8_t> buf(spec.output_buffer_size);
+ uint8_t* next_out = buf.data();
+ size_t avail_out = buf.size();
+ process_result = JxlEncoderProcessOutput(enc, &next_out, &avail_out);
+ }
+ if (JXL_ENC_SUCCESS != process_result) {
+ return false;
+ }
+ }
+ return true;
+}
+
+template <typename T>
+T Select(const std::vector<T>& vec, std::function<uint32_t(size_t)> get_index) {
+ return vec[get_index(vec.size() - 1)];
+}
+
+int TestOneInput(const uint8_t* data, size_t size) {
+ uint64_t flags = 0;
+ size_t flag_bits = 0;
+
+ const auto consume_data = [&]() {
+ if (size < 4) abort();
+ uint32_t buf = 0;
+ memcpy(&buf, data, 4);
+ data += 4;
+ size -= 4;
+ flags = (flags << 32) | buf;
+ flag_bits += 32;
+ };
+
+ const auto get_flag = [&](size_t max_value) {
+ size_t limit = 1;
+ while (limit <= max_value) {
+ limit <<= 1;
+ --flag_bits;
+ if (flag_bits <= 16) {
+ consume_data();
+ }
+ }
+ uint32_t result = flags % limit;
+ flags /= limit;
+ return result % (max_value + 1);
+ };
+
+ std::vector<JxlColorSpace> colorspaces = {
+ JXL_COLOR_SPACE_RGB, JXL_COLOR_SPACE_GRAY, JXL_COLOR_SPACE_XYB,
+ JXL_COLOR_SPACE_UNKNOWN};
+ std::vector<JxlWhitePoint> whitepoints = {
+ JXL_WHITE_POINT_D65, JXL_WHITE_POINT_CUSTOM, JXL_WHITE_POINT_E,
+ JXL_WHITE_POINT_DCI};
+ std::vector<JxlPrimaries> primaries = {JXL_PRIMARIES_SRGB,
+ JXL_PRIMARIES_CUSTOM,
+ JXL_PRIMARIES_2100, JXL_PRIMARIES_P3};
+ std::vector<JxlTransferFunction> transfer_functions = {
+ JXL_TRANSFER_FUNCTION_709, JXL_TRANSFER_FUNCTION_UNKNOWN,
+ JXL_TRANSFER_FUNCTION_LINEAR, JXL_TRANSFER_FUNCTION_SRGB,
+ JXL_TRANSFER_FUNCTION_PQ, JXL_TRANSFER_FUNCTION_DCI,
+ JXL_TRANSFER_FUNCTION_HLG, JXL_TRANSFER_FUNCTION_GAMMA};
+ std::vector<JxlRenderingIntent> rendering_intents = {
+ JXL_RENDERING_INTENT_PERCEPTUAL,
+ JXL_RENDERING_INTENT_RELATIVE,
+ JXL_RENDERING_INTENT_SATURATION,
+ JXL_RENDERING_INTENT_ABSOLUTE,
+ };
+
+ FuzzSpec spec;
+ // Randomly set some options.
+ // TODO(szabadka) Make value bounds option specific.
+ size_t num_options = get_flag(32);
+ for (size_t i = 0; i < num_options; ++i) {
+ FuzzSpec::OptionSpec option;
+ option.id = static_cast<JxlEncoderFrameSettingId>(get_flag(32));
+ option.value = static_cast<int32_t>(get_flag(16)) - 1;
+ spec.options.push_back(option);
+ }
+
+ spec.xsize = get_flag(4095) + 1;
+ spec.ysize = get_flag(4095) + 1;
+ spec.lossless = get_flag(1);
+ if (!spec.lossless) {
+ spec.orig_profile = get_flag(1);
+ }
+ spec.have_alpha = get_flag(1);
+ spec.premultiply = get_flag(1);
+ spec.pixels_seed = get_flag((1 << 16) - 1);
+ spec.alpha_seed = get_flag((1 << 16) - 1);
+ spec.bit_depth = get_flag(15) + 1;
+ spec.alpha_bit_depth = get_flag(15) + 1;
+ spec.color_encoding.color_space = Select(colorspaces, get_flag);
+ spec.color_encoding.white_point = Select(whitepoints, get_flag);
+ spec.color_encoding.primaries = Select(primaries, get_flag);
+ spec.color_encoding.transfer_function = Select(transfer_functions, get_flag);
+ spec.color_encoding.rendering_intent = Select(rendering_intents, get_flag);
+ spec.output_buffer_size = get_flag(4095) + 1;
+
+ const auto targets = hwy::SupportedAndGeneratedTargets();
+ hwy::SetSupportedTargetsForTest(Select(targets, get_flag));
+ EncodeJpegXl(spec);
+ hwy::SetSupportedTargetsForTest(0);
+
+ return 0;
+}
+
+} // namespace
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+ return TestOneInput(data, size);
+}
diff --git a/media/libjxl/src/tools/cjxl_main.cc b/media/libjxl/src/tools/cjxl_main.cc
index 157400a13d..e43bb27075 100644
--- a/media/libjxl/src/tools/cjxl_main.cc
+++ b/media/libjxl/src/tools/cjxl_main.cc
@@ -3,19 +3,63 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
-#include <stdio.h>
+// Note: This encoder binary does extensive flag-validity checking (in
+// order to produce meaningful error messages), and on top of that
+// checks all libjxl C API call return values. The downside of this
+// vs. libjxl providing meaningful error messages is that a change to
+// the accepted range of a flag-specified parameter in libjxl will
+// also require a change to the range-check here. The advantage is
+// that this minimizes the size of libjxl.
+#include <stdint.h>
+
+#include <cmath>
+#include <cstdlib>
+#include <functional>
+#include <iostream>
+#include <sstream>
+#include <string>
+#include <thread>
+#include <type_traits>
+#include <vector>
+
+#include "jxl/codestream_header.h"
#include "jxl/encode.h"
-#include "lib/jxl/base/file_io.h"
-#include "lib/jxl/base/profiler.h"
-#include "lib/jxl/jpeg/enc_jpeg_data.h"
-#include "tools/box/box.h"
-#include "tools/cjxl.h"
+#include "jxl/encode_cxx.h"
+#include "jxl/thread_parallel_runner.h"
+#include "jxl/thread_parallel_runner_cxx.h"
+#include "jxl/types.h"
+#include "lib/extras/dec/apng.h"
+#include "lib/extras/dec/color_hints.h"
+#include "lib/extras/dec/gif.h"
+#include "lib/extras/dec/jpg.h"
+#include "lib/extras/dec/pgx.h"
+#include "lib/extras/dec/pnm.h"
+#include "lib/extras/time.h"
+#include "lib/jxl/base/override.h"
+#include "lib/jxl/base/printf_macros.h"
+#include "lib/jxl/base/status.h"
+#include "lib/jxl/exif.h"
+#include "lib/jxl/size_constraints.h"
+#include "tools/args.h"
+#include "tools/cmdline.h"
#include "tools/codec_config.h"
+#include "tools/file_io.h"
+#include "tools/speed_stats.h"
namespace jpegxl {
namespace tools {
+namespace {
+inline bool ParsePhotonNoiseParameter(const char* arg, float* out) {
+ return strncmp(arg, "ISO", 3) == 0 && ParseFloat(arg + 3, out) && *out > 0;
+}
+inline bool ParseIntensityTarget(const char* arg, float* out) {
+ return ParseFloat(arg, out) && *out > 0;
+}
+
+} // namespace
+
enum CjxlRetCode : int {
OK = 0,
ERR_PARSE,
@@ -28,124 +72,1144 @@ enum CjxlRetCode : int {
DROPPED_JBRD,
};
-int CompressJpegXlMain(int argc, const char* argv[]) {
- CommandLineParser cmdline;
- CompressArgs args;
- args.AddCommandLineOptions(&cmdline);
+struct CompressArgs {
+ // CompressArgs() = default;
+ void AddCommandLineOptions(CommandLineParser* cmdline) {
+ // Positional arguments.
+ cmdline->AddPositionalOption("INPUT", /* required = */ true,
+ "the input can be "
+#if JPEGXL_ENABLE_APNG
+ "PNG, APNG, "
+#endif
+#if JPEGXL_ENABLE_GIF
+ "GIF, "
+#endif
+#if JPEGXL_ENABLE_JPEG
+ "JPEG, "
+#else
+ "JPEG (lossless recompression only), "
+#endif
+#if JPEGXL_ENABLE_EXR
+ "EXR, "
+#endif
+ "PPM, PFM, or PGX",
+ &file_in);
+ cmdline->AddPositionalOption(
+ "OUTPUT", /* required = */ true,
+ "the compressed JXL output file (can be omitted for benchmarking)",
+ &file_out);
- if (!cmdline.Parse(argc, argv)) {
- // Parse already printed the actual error cause.
- fprintf(stderr, "Use '%s -h' for more information\n", argv[0]);
- return CjxlRetCode::ERR_PARSE;
+ // Flags.
+ // TODO(lode): also add options to add exif/xmp/other metadata in the
+ // container.
+ cmdline->AddOptionValue('\0', "container", "0|1",
+ "0 = Do not encode using container format (strip "
+ "Exif/XMP/JPEG bitstream reconstruction data)."
+ "1 = Force using container format \n"
+ "(default: use only if needed).\n",
+ &container, &ParseOverride, 1);
+
+ cmdline->AddOptionValue(
+ '\0', "jpeg_store_metadata", "0|1",
+ ("If --lossless_jpeg=1, store JPEG reconstruction "
+ "metadata in the JPEG XL container "
+ "(for lossless reconstruction of the JPEG codestream)."
+ "(default: 1)"),
+ &jpeg_store_metadata, &ParseUnsigned, 2);
+
+ // Target distance/size/bpp
+ opt_distance_id = cmdline->AddOptionValue(
+ 'd', "distance", "maxError",
+ "Max. butteraugli distance, lower = higher quality.\n"
+ " 0.0 = mathematically lossless. Default for already-lossy input "
+ "(JPEG/GIF).\n"
+ " 1.0 = visually lossless. Default for other input.\n"
+ " Recommended range: 0.5 .. 3.0. Mutually exclusive with --quality.",
+ &distance, &ParseFloat);
+
+ // High-level options
+ opt_quality_id = cmdline->AddOptionValue(
+ 'q', "quality", "QUALITY",
+ "Quality setting (is remapped to --distance). Range: -inf .. 100.\n"
+ " 100 = mathematically lossless. Default for already-lossy input "
+ "(JPEG/GIF).\n"
+ " Other input gets encoded as per --distance default.\n"
+ " Positive quality values roughly match libjpeg quality.\n"
+ " Mutually exclusive with --distance.",
+ &quality, &ParseFloat);
+
+ cmdline->AddOptionValue(
+ 'e', "effort", "EFFORT",
+ "Encoder effort setting. Range: 1 .. 9.\n"
+ " Default: 7. Higher number is more effort (slower).",
+ &effort, &ParseUnsigned, -1);
+
+ cmdline->AddOptionValue(
+ '\0', "brotli_effort", "B_EFFORT",
+ "Brotli effort setting. Range: 0 .. 11.\n"
+ " Default: 9. Higher number is more effort (slower).",
+ &brotli_effort, &ParseUnsigned, -1);
+
+ cmdline->AddOptionValue(
+ '\0', "faster_decoding", "0|1|2|3|4",
+ "Favour higher decoding speed. 0 = default, higher "
+ "values give higher speed at the expense of quality",
+ &faster_decoding, &ParseUnsigned, 2);
+
+ cmdline->AddOptionFlag('p', "progressive",
+ "Enable progressive/responsive decoding.",
+ &progressive, &SetBooleanTrue);
+
+ cmdline->AddOptionValue('\0', "premultiply", "-1|0|1",
+ "Force premultiplied (associated) alpha.",
+ &premultiply, &ParseSigned, 1);
+
+ cmdline->AddOptionValue(
+ '\0', "keep_invisible", "0|1",
+ "force disable/enable preserving color of invisible "
+ "pixels (default: 1 if lossless, 0 if lossy).",
+ &keep_invisible, &ParseOverride, 1);
+
+ cmdline->AddOptionValue(
+ '\0', "group_order", "0|1",
+ "Order in which 256x256 groups are stored "
+ "in the codestream for progressive rendering. "
+ "Value not provided means 'encoder default', 0 means 'scanline order', "
+ "1 means 'center-first order'.",
+ &group_order, &ParseOverride, 1);
+
+ cmdline->AddOptionValue(
+ '\0', "center_x", "0..XSIZE",
+ "Determines the horizontal position of center for the center-first "
+ "group order. The value -1 means 'use the middle of the image', "
+ "other values [0..xsize) set this to a particular coordinate.",
+ &center_x, &ParseInt64, 1);
+
+ cmdline->AddOptionValue(
+ '\0', "center_y", "0..YSIZE",
+ "Determines the vertical position of center for the center-first "
+ "group order. The value -1 means 'use the middle of the image', "
+ "other values [0..ysize) set this to a particular coordinate.",
+ &center_y, &ParseInt64, 1);
+
+ // Flags.
+ cmdline->AddOptionFlag('\0', "progressive_ac",
+ "Use the progressive mode for AC.", &progressive_ac,
+ &SetBooleanTrue, 1);
+
+ opt_qprogressive_ac_id = cmdline->AddOptionFlag(
+ '\0', "qprogressive_ac",
+ "Use the progressive mode for AC with shift quantization.",
+ &qprogressive_ac, &SetBooleanTrue, 1);
+
+ cmdline->AddOptionValue(
+ '\0', "progressive_dc", "num_dc_frames",
+ "Progressive-DC setting. Valid values are: -1, 0, 1, 2.",
+ &progressive_dc, &ParseSigned, 1);
+
+ cmdline->AddOptionValue(
+ 'm', "modular", "0|1",
+ "Use modular mode (not provided = encoder chooses, 0 = enforce VarDCT, "
+ "1 = enforce modular mode).",
+ &modular, &ParseOverride, 1);
+
+ // JPEG modes: parallel Brunsli, pixels to JPEG, or JPEG to Brunsli
+ opt_lossless_jpeg_id = cmdline->AddOptionValue(
+ 'j', "lossless_jpeg", "0|1",
+ "If the input is JPEG, losslessly transcode JPEG, "
+ "rather than using reencode pixels.",
+ &lossless_jpeg, &ParseUnsigned, 1);
+
+ cmdline->AddOptionValue(
+ '\0', "jpeg_reconstruction_cfl", "0|1",
+ "Enable/disable chroma-from-luma (CFL) for lossless "
+ "JPEG reconstruction.",
+ &jpeg_reconstruction_cfl, &ParseOverride, 2);
+
+ cmdline->AddOptionValue(
+ '\0', "num_threads", "N",
+ "Number of worker threads (-1 == use machine default, "
+ "0 == do not use multithreading).",
+ &num_threads, &ParseSigned, 1);
+
+ cmdline->AddOptionValue('\0', "num_reps", "N",
+ "How many times to compress. (For benchmarking).",
+ &num_reps, &ParseUnsigned, 1);
+
+ cmdline->AddOptionValue(
+ '\0', "photon_noise", "ISO3200",
+ "Adds noise to the image emulating photographic film noise. "
+ "The higher the given number, the grainier the image will be. "
+ "As an example, a value of 100 gives low noise whereas a value "
+ "of 3200 gives a lot of noise. The default value is 0.",
+ &photon_noise_iso, &ParsePhotonNoiseParameter, 1);
+
+ cmdline->AddOptionValue(
+ '\0', "dots", "0|1",
+ "Force disable/enable dots generation. "
+ "(not provided = default, 0 = disable, 1 = enable).",
+ &dots, &ParseOverride, 1);
+
+ cmdline->AddOptionValue(
+ '\0', "patches", "0|1",
+ "Force disable/enable patches generation. "
+ "(not provided = default, 0 = disable, 1 = enable).",
+ &patches, &ParseOverride, 1);
+
+ cmdline->AddOptionValue(
+ '\0', "resampling", "-1|1|2|4|8",
+ "Resampling for extra channels. Default of -1 applies resampling only "
+ "for low quality. Value 1 does no downsampling (1x1), 2 does 2x2 "
+ "downsampling, 4 is for 4x4 downsampling, and 8 for 8x8 downsampling.",
+ &resampling, &ParseSigned, 0);
+
+ cmdline->AddOptionValue(
+ '\0', "ec_resampling", "-1|1|2|4|8",
+ "Resampling for extra channels. Default of -1 applies resampling only "
+ "for low quality. Value 1 does no downsampling (1x1), 2 does 2x2 "
+ "downsampling, 4 is for 4x4 downsampling, and 8 for 8x8 downsampling.",
+ &ec_resampling, &ParseSigned, 2);
+
+ cmdline->AddOptionFlag('\0', "already_downsampled",
+ "Do not downsample the given input before encoding, "
+ "but still signal that the decoder should upsample.",
+ &already_downsampled, &SetBooleanTrue, 2);
+
+ cmdline->AddOptionValue(
+ '\0', "epf", "-1|0|1|2|3",
+ "Edge preserving filter level, -1 to 3. "
+ "Value -1 means: default (encoder chooses), 0 to 3 set a strength.",
+ &epf, &ParseSigned, 1);
+
+ cmdline->AddOptionValue(
+ '\0', "gaborish", "0|1",
+ "Force disable/enable the gaborish filter. "
+ "(not provided = default, 0 = disable, 1 = enable).",
+ &gaborish, &ParseOverride, 1);
+
+ cmdline->AddOptionValue(
+ '\0', "intensity_target", "N",
+ "Upper bound on the intensity level present in the image in nits. "
+ "Leaving this set to its default of 0 lets libjxl choose a sensible "
+ "default "
+ "value based on the color encoding.",
+ &intensity_target, &ParseIntensityTarget, 1);
+
+ cmdline->AddOptionValue(
+ 'x', "dec-hints", "key=value",
+ "color_space indicates the ColorEncoding, see Description();\n"
+ "icc_pathname refers to a binary file containing an ICC profile.",
+ &color_hints, &ParseAndAppendKeyValue, 1);
+
+ cmdline->AddOptionValue(
+ '\0', "override_bitdepth", "0=use from image, 1-32=override",
+ "If nonzero, store the given bit depth in the JPEG XL file metadata"
+ " (1-32), instead of using the bit depth from the original input"
+ " image.",
+ &override_bitdepth, &ParseUnsigned, 2);
+
+ // modular mode options
+ cmdline->AddOptionValue(
+ 'I', "iterations", "F",
+ "[modular encoding] Fraction of pixels used to learn MA trees as "
+ "a percentage. -1 = default, 0 = no MA and fast decode, 50 = "
+ "default value, 100 = all."
+ "Higher values use more encoder memory.",
+ &modular_ma_tree_learning_percent, &ParseFloat, 2);
+
+ cmdline->AddOptionValue(
+ 'C', "modular_colorspace", "K",
+ ("[modular encoding] color transform: -1=default, 0=RGB (none), "
+ "1-41=RCT (6=YCoCg, default: try several, depending on speed)"),
+ &modular_colorspace, &ParseSigned, 1);
+
+ opt_modular_group_size_id = cmdline->AddOptionValue(
+ 'g', "modular_group_size", "K",
+ "[modular encoding] group size: -1 == default. 0 => 128, "
+ "1 => 256, 2 => 512, 3 => 1024",
+ &modular_group_size, &ParseSigned, 1);
+
+ cmdline->AddOptionValue(
+ 'P', "modular_predictor", "K",
+ "[modular encoding] predictor(s) to use: 0=zero, "
+ "1=left, 2=top, 3=avg0, 4=select, 5=gradient, 6=weighted, "
+ "7=topright, 8=topleft, 9=leftleft, 10=avg1, 11=avg2, 12=avg3, "
+ "13=toptop predictive average "
+ "14=mix 5 and 6, 15=mix everything. If unset, uses default 14, "
+ "at slowest speed default 15.",
+ &modular_predictor, &ParseSigned, 1);
+
+ cmdline->AddOptionValue(
+ 'E', "modular_nb_prev_channels", "K",
+ "[modular encoding] number of extra MA tree properties to use",
+ &modular_nb_prev_channels, &ParseSigned, 2);
+
+ cmdline->AddOptionValue(
+ '\0', "modular_palette_colors", "K",
+ "[modular encoding] Use color palette if number of colors is smaller "
+ "than or equal to this, or -1 to use the encoder default.",
+ &modular_palette_colors, &ParseSigned, 1);
+
+ cmdline->AddOptionFlag(
+ '\0', "modular_lossy_palette",
+ "[modular encoding] quantize to a palette that has fewer entries than "
+ "would be necessary for perfect preservation; for the time being, it "
+ "is "
+ "recommended to set --palette=0 with this option to use the default "
+ "palette only",
+ &modular_lossy_palette, &SetBooleanTrue, 1);
+
+ cmdline->AddOptionValue(
+ 'X', "pre-compact", "PERCENT",
+ "[modular encoding] Use Global channel palette if the number of "
+ "colors is smaller than this percentage of range. "
+ "Use 0-100 to set an explicit percentage, -1 to use the encoder "
+ "default.",
+ &modular_channel_colors_global_percent, &ParseFloat, 2);
+
+ cmdline->AddOptionValue(
+ 'Y', "post-compact", "PERCENT",
+ "[modular encoding] Use Local (per-group) channel palette if the "
+ "number "
+ "of colors is smaller than this percentage of range. Use 0-100 to set "
+ "an explicit percentage, -1 to use the encoder default.",
+ &modular_channel_colors_group_percent, &ParseFloat, 2);
+
+ cmdline->AddOptionValue('\0', "codestream_level", "K",
+ "The codestream level. Either `-1`, `5` or `10`.",
+ &codestream_level, &ParseSigned, 2);
+
+ opt_responsive_id = cmdline->AddOptionValue(
+ 'R', "responsive", "K",
+ "[modular encoding] do Squeeze transform, 0=false, "
+ "1=true (default: true if lossy, false if lossless)",
+ &responsive, &ParseSigned, 1);
+
+ cmdline->AddOptionFlag('V', "version",
+ "Print encoder library version number and exit.",
+ &version, &SetBooleanTrue, 1);
+
+ cmdline->AddOptionFlag('\0', "quiet", "Be more silent", &quiet,
+ &SetBooleanTrue, 1);
+
+ cmdline->AddOptionValue(
+ '\0', "frame_indexing", "string",
+ // TODO(tfish): Add a more convenient vanilla alternative.
+ "If non-empty, a string matching '^(0*|1[01]*)'. If this string has a "
+ "'1' in i-th position, then the i-th frame will be indexed in "
+ "the frame index box.",
+ &frame_indexing, &ParseString, 1);
+
+ cmdline->AddOptionFlag(
+ 'v', "verbose",
+ "Verbose output; can be repeated, also applies to help (!).", &verbose,
+ &SetBooleanTrue);
}
- if (args.version) {
- fprintf(stdout, "cjxl %s\n",
- CodecConfigString(JxlEncoderVersion()).c_str());
- fprintf(stdout, "Copyright (c) the JPEG XL Project\n");
- return CjxlRetCode::OK;
+ // Common flags.
+ bool version = false;
+ jxl::Override container = jxl::Override::kDefault;
+ bool quiet = false;
+
+ const char* file_in = nullptr;
+ const char* file_out = nullptr;
+ jxl::Override print_profile = jxl::Override::kDefault;
+
+ // Decoding source image flags
+ jxl::extras::ColorHints color_hints;
+
+ // JXL flags
+ size_t override_bitdepth = 0;
+ int32_t num_threads = -1;
+ size_t num_reps = 1;
+ float intensity_target = 0;
+
+ // Filename for the user provided saliency-map.
+ std::string saliency_map_filename;
+
+ // Whether to perform lossless transcoding with kVarDCT or kJPEG encoding.
+ // If true, attempts to load JPEG coefficients instead of pixels.
+ // Reset to false if input image is not a JPEG.
+ size_t lossless_jpeg = 1;
+
+ size_t jpeg_store_metadata = 1;
+
+ float quality = -1001.f; // Default to lossless if input is already lossy,
+ // or to VarDCT otherwise.
+ bool verbose = false;
+ bool progressive = false;
+ bool progressive_ac = false;
+ bool qprogressive_ac = false;
+ int32_t progressive_dc = -1;
+ bool modular_lossy_palette = false;
+ int32_t premultiply = -1;
+ bool already_downsampled = false;
+ jxl::Override jpeg_reconstruction_cfl = jxl::Override::kDefault;
+ jxl::Override modular = jxl::Override::kDefault;
+ jxl::Override keep_invisible = jxl::Override::kDefault;
+ jxl::Override dots = jxl::Override::kDefault;
+ jxl::Override patches = jxl::Override::kDefault;
+ jxl::Override gaborish = jxl::Override::kDefault;
+ jxl::Override group_order = jxl::Override::kDefault;
+
+ size_t faster_decoding = 0;
+ int32_t resampling = -1;
+ int32_t ec_resampling = -1;
+ int32_t epf = -1;
+ int64_t center_x = -1;
+ int64_t center_y = -1;
+ int32_t modular_group_size = -1;
+ int32_t modular_predictor = -1;
+ int32_t modular_colorspace = -1;
+ float modular_channel_colors_global_percent = -1.f;
+ float modular_channel_colors_group_percent = -1.f;
+ int32_t modular_palette_colors = -1;
+ int32_t modular_nb_prev_channels = -1;
+ float modular_ma_tree_learning_percent = -1.f;
+ float photon_noise_iso = 0;
+ int32_t codestream_level = -1;
+ int32_t responsive = -1;
+ float distance = 1.0;
+ size_t effort = 7;
+ size_t brotli_effort = 9;
+ std::string frame_indexing;
+
+ // Will get passed on to AuxOut.
+ // jxl::InspectorImage3F inspector_image3f;
+
+ // References (ids) of specific options to check if they were matched.
+ CommandLineParser::OptionId opt_lossless_jpeg_id = -1;
+ CommandLineParser::OptionId opt_responsive_id = -1;
+ CommandLineParser::OptionId opt_distance_id = -1;
+ CommandLineParser::OptionId opt_quality_id = -1;
+ CommandLineParser::OptionId opt_qprogressive_ac_id = -1;
+ CommandLineParser::OptionId opt_modular_group_size_id = -1;
+};
+
+const char* ModeFromArgs(const CompressArgs& args) {
+ if (args.lossless_jpeg) return "JPEG";
+ if (args.modular == jxl::Override::kOn || args.distance == 0)
+ return "Modular";
+ return "VarDCT";
+}
+
+std::string DistanceFromArgs(const CompressArgs& args) {
+ char buf[100];
+ if (args.lossless_jpeg) {
+ snprintf(buf, sizeof(buf), "lossless transcode");
+ } else if (args.distance == 0) {
+ snprintf(buf, sizeof(buf), "lossless");
+ } else {
+ snprintf(buf, sizeof(buf), "d%.3f", args.distance);
}
+ return buf;
+}
- if (!args.quiet) {
- fprintf(stderr, "JPEG XL encoder %s\n",
- CodecConfigString(JxlEncoderVersion()).c_str());
+void PrintMode(jxl::extras::PackedPixelFile& ppf, const double decode_mps,
+ size_t num_bytes, const CompressArgs& args) {
+ const char* mode = ModeFromArgs(args);
+ const std::string distance = DistanceFromArgs(args);
+ if (args.lossless_jpeg) {
+ fprintf(stderr, "Read JPEG image with %" PRIuS " bytes.\n", num_bytes);
+ } else {
+ fprintf(stderr,
+ "Read %" PRIuS "x%" PRIuS " image, %" PRIuS " bytes, %.1f MP/s\n",
+ static_cast<size_t>(ppf.info.xsize),
+ static_cast<size_t>(ppf.info.ysize), num_bytes, decode_mps);
}
+ fprintf(stderr, "Encoding [%s%s, %s, effort: %" PRIuS,
+ (args.container == jxl::Override::kOn ? "Container | " : ""), mode,
+ distance.c_str(), args.effort);
+ if (args.container == jxl::Override::kOn) {
+ if (args.lossless_jpeg && args.jpeg_store_metadata)
+ fprintf(stderr, " | JPEG reconstruction data");
+ if (!ppf.metadata.exif.empty())
+ fprintf(stderr, " | %" PRIuS "-byte Exif", ppf.metadata.exif.size());
+ if (!ppf.metadata.xmp.empty())
+ fprintf(stderr, " | %" PRIuS "-byte XMP", ppf.metadata.xmp.size());
+ if (!ppf.metadata.jumbf.empty())
+ fprintf(stderr, " | %" PRIuS "-byte JUMBF", ppf.metadata.jumbf.size());
+ }
+ fprintf(stderr, "], \n");
+}
- if (cmdline.HelpFlagPassed()) {
- cmdline.PrintHelp();
- return CjxlRetCode::OK;
+} // namespace tools
+} // namespace jpegxl
+
+namespace {
+
+template <typename T>
+void SetFlagFrameOptionOrDie(const char* flag_name, T flag_value,
+ JxlEncoderFrameSettings* frame_settings,
+ JxlEncoderFrameSettingId encoder_option) {
+ if (JXL_ENC_SUCCESS !=
+ (std::is_same<T, float>::value
+ ? JxlEncoderFrameSettingsSetFloatOption(frame_settings,
+ encoder_option, flag_value)
+ : JxlEncoderFrameSettingsSetOption(frame_settings, encoder_option,
+ flag_value))) {
+ std::cerr << "Setting encoder option from flag --" << flag_name
+ << " failed." << std::endl;
+ exit(EXIT_FAILURE);
}
+}
- if (!args.ValidateArgs(cmdline)) {
- // ValidateArgs already printed the actual error cause.
- fprintf(stderr, "Use '%s -h' for more information\n", argv[0]);
- return CjxlRetCode::ERR_INVALID_ARG;
+void SetDistanceFromFlags(JxlEncoderFrameSettings* jxl_encoder_frame_settings,
+ jpegxl::tools::CommandLineParser* cmdline,
+ jpegxl::tools::CompressArgs* args,
+ const jxl::extras::Codec& codec) {
+ bool distance_set = cmdline->GetOption(args->opt_distance_id)->matched();
+ bool quality_set = cmdline->GetOption(args->opt_quality_id)->matched();
+ if (quality_set) {
+ if (distance_set) {
+ std::cerr << "Must not set both --distance and --quality." << std::endl;
+ exit(EXIT_FAILURE);
+ }
+ double distance = args->quality >= 100 ? 0.0
+ : args->quality >= 30
+ ? 0.1 + (100 - args->quality) * 0.09
+ : 6.4 + pow(2.5, (30 - args->quality) / 5.0) / 6.25;
+ args->distance = distance;
+ distance_set = true;
+ }
+ if (!distance_set) {
+ bool lossy_input = (codec == jxl::extras::Codec::kJPG ||
+ codec == jxl::extras::Codec::kGIF);
+ args->distance = lossy_input ? 0.0 : 1.0;
+ }
+ if (JXL_ENC_SUCCESS !=
+ JxlEncoderSetFrameDistance(jxl_encoder_frame_settings, args->distance)) {
+ std::cerr << "Setting frame distance failed." << std::endl;
+ exit(EXIT_FAILURE);
}
+}
- jxl::PaddedBytes compressed;
+using flag_check_fn = std::function<std::string(int64_t)>;
+using flag_check_float_fn = std::function<std::string(float)>;
- jxl::ThreadPoolInternal pool(args.num_threads);
- jxl::CodecInOut io;
- double decode_mps = 0;
- if (!LoadAll(args, &pool, &io, &decode_mps)) {
- return CjxlRetCode::ERR_LOAD_INPUT;
+bool IsJPG(const std::vector<uint8_t>& image_data) {
+ return (image_data.size() >= 2 && image_data[0] == 0xFF &&
+ image_data[1] == 0xD8);
+}
+
+// TODO(tfish): Replace with non-C-API library function.
+// Implementation is in extras/.
+jxl::Status GetPixeldata(const std::vector<uint8_t>& image_data,
+ const jxl::extras::ColorHints& color_hints,
+ jxl::extras::PackedPixelFile& ppf,
+ jxl::extras::Codec& codec) {
+ // Any valid encoding is larger (ensures codecs can read the first few bytes).
+ constexpr size_t kMinBytes = 9;
+
+ if (image_data.size() < kMinBytes) return JXL_FAILURE("Input too small.");
+ jxl::Span<const uint8_t> encoded(image_data);
+
+ ppf.info.orientation = JXL_ORIENT_IDENTITY;
+ jxl::SizeConstraints size_constraints;
+
+ const auto choose_codec = [&]() {
+#if JPEGXL_ENABLE_APNG
+ if (jxl::extras::DecodeImageAPNG(encoded, color_hints, size_constraints,
+ &ppf)) {
+ return jxl::extras::Codec::kPNG;
+ }
+#endif
+ if (jxl::extras::DecodeImagePGX(encoded, color_hints, size_constraints,
+ &ppf)) {
+ return jxl::extras::Codec::kPGX;
+ } else if (jxl::extras::DecodeImagePNM(encoded, color_hints,
+ size_constraints, &ppf)) {
+ return jxl::extras::Codec::kPNM;
+ }
+#if JPEGXL_ENABLE_GIF
+ if (jxl::extras::DecodeImageGIF(encoded, color_hints, size_constraints,
+ &ppf)) {
+ return jxl::extras::Codec::kGIF;
+ }
+#endif
+#if JPEGXL_ENABLE_JPEG
+ if (jxl::extras::DecodeImageJPG(encoded, color_hints, size_constraints,
+ &ppf)) {
+ return jxl::extras::Codec::kJPG;
+ }
+#endif
+ // TODO(tfish): Bring back EXR and PSD.
+ return jxl::extras::Codec::kUnknown;
+ };
+ codec = choose_codec();
+ if (codec == jxl::extras::Codec::kUnknown) {
+ return JXL_FAILURE("Codecs failed to decode input.");
}
+ return true;
+}
+
+} // namespace
+
+int main(int argc, char** argv) {
+ std::string version = jpegxl::tools::CodecConfigString(JxlEncoderVersion());
+ jpegxl::tools::CompressArgs args;
+ jpegxl::tools::CommandLineParser cmdline;
+ args.AddCommandLineOptions(&cmdline);
- // need to validate again because now we know the input
- if (!args.ValidateArgsAfterLoad(cmdline, io)) {
+ if (!cmdline.Parse(argc, const_cast<const char**>(argv))) {
+ // Parse already printed the actual error cause.
fprintf(stderr, "Use '%s -h' for more information\n", argv[0]);
- return CjxlRetCode::ERR_INVALID_INPUT;
+ return jpegxl::tools::CjxlRetCode::ERR_PARSE;
}
+
+ if (args.version) {
+ fprintf(stdout, "cjxl %s\n", version.c_str());
+ fprintf(stdout, "Copyright (c) the JPEG XL Project\n");
+ return jpegxl::tools::CjxlRetCode::OK;
+ }
+
+ if (!args.quiet) {
+ fprintf(stderr, "JPEG XL encoder %s\n", version.c_str());
+ }
+
+ if (cmdline.HelpFlagPassed() || !args.file_in) {
+ cmdline.PrintHelp();
+ return jpegxl::tools::CjxlRetCode::OK;
+ }
+
if (!args.file_out && !args.quiet) {
fprintf(stderr,
"No output file specified.\n"
"Encoding will be performed, but the result will be discarded.\n");
}
- if (!CompressJxl(io, decode_mps, &pool, args, &compressed, !args.quiet)) {
- return CjxlRetCode::ERR_ENCODING;
+
+ // Loading the input.
+ // Depending on flags-settings, we want to either load a JPEG and
+ // faithfully convert it to JPEG XL, or load (JPEG or non-JPEG)
+ // pixel data.
+ std::vector<uint8_t> image_data;
+ jxl::extras::PackedPixelFile ppf;
+ jxl::extras::Codec codec = jxl::extras::Codec::kUnknown;
+ double decode_mps = 0;
+ size_t pixels = 0;
+ if (!jpegxl::tools::ReadFile(args.file_in, &image_data)) {
+ std::cerr << "Reading image data failed." << std::endl;
+ exit(EXIT_FAILURE);
+ }
+ if (!IsJPG(image_data)) args.lossless_jpeg = 0;
+ if (!args.lossless_jpeg) {
+ const double t0 = jxl::Now();
+ jxl::Status status = GetPixeldata(image_data, args.color_hints, ppf, codec);
+ if (!status) {
+ std::cerr << "Getting pixel data." << std::endl;
+ exit(EXIT_FAILURE);
+ }
+ if (ppf.frames.empty()) {
+ std::cerr << "No frames on input file." << std::endl;
+ exit(EXIT_FAILURE);
+ }
+
+ const double t1 = jxl::Now();
+ pixels = ppf.info.xsize * ppf.info.ysize;
+ decode_mps = pixels * ppf.info.num_color_channels * 1E-6 / (t1 - t0);
}
- int ret = CjxlRetCode::OK;
- if (args.use_container) {
- JpegXlContainer container;
- container.codestream = std::move(compressed);
- if (!io.blobs.exif.empty()) {
- container.exif = io.blobs.exif.data();
- container.exif_size = io.blobs.exif.size();
+ JxlEncoderPtr enc = JxlEncoderMake(/*memory_manager=*/nullptr);
+ JxlEncoder* jxl_encoder = enc.get();
+ JxlThreadParallelRunnerPtr runner;
+ std::vector<uint8_t> compressed;
+ size_t num_worker_threads;
+ jpegxl::tools::SpeedStats stats;
+ for (size_t num_rep = 0; num_rep < args.num_reps; ++num_rep) {
+ const double t0 = jxl::Now();
+ JxlEncoderReset(jxl_encoder);
+ if (args.num_threads != 0) {
+ num_worker_threads = JxlThreadParallelRunnerDefaultNumWorkerThreads();
+ {
+ int64_t flag_num_worker_threads = args.num_threads;
+ if (flag_num_worker_threads > -1) {
+ num_worker_threads = flag_num_worker_threads;
+ }
+ }
+ if (runner == nullptr) {
+ runner = JxlThreadParallelRunnerMake(
+ /*memory_manager=*/nullptr, num_worker_threads);
+ }
+ if (JXL_ENC_SUCCESS !=
+ JxlEncoderSetParallelRunner(jxl_encoder, JxlThreadParallelRunner,
+ runner.get())) {
+ std::cerr << "JxlEncoderSetParallelRunner failed." << std::endl;
+ return EXIT_FAILURE;
+ }
}
- auto append_xml = [&container](const std::vector<uint8_t>& bytes) {
- if (bytes.empty()) return;
- container.xml.emplace_back(bytes.data(), bytes.size());
+ JxlEncoderFrameSettings* jxl_encoder_frame_settings =
+ JxlEncoderFrameSettingsCreate(jxl_encoder, nullptr);
+
+ auto process_flag = [&jxl_encoder_frame_settings](
+ const char* flag_name, int64_t flag_value,
+ JxlEncoderFrameSettingId encoder_option,
+ const flag_check_fn& flag_check) {
+ std::string error = flag_check(flag_value);
+ if (!error.empty()) {
+ std::cerr << "Invalid flag value for --" << flag_name << ": " << error
+ << std::endl;
+ exit(EXIT_FAILURE);
+ }
+ SetFlagFrameOptionOrDie(flag_name, flag_value, jxl_encoder_frame_settings,
+ encoder_option);
+ };
+ auto process_float_flag = [&jxl_encoder_frame_settings](
+ const char* flag_name, float flag_value,
+ JxlEncoderFrameSettingId encoder_option,
+ const flag_check_float_fn& flag_check) {
+ std::string error = flag_check(flag_value);
+ if (!error.empty()) {
+ std::cerr << "Invalid flag value for --" << flag_name << ": " << error
+ << std::endl;
+ exit(EXIT_FAILURE);
+ }
+ SetFlagFrameOptionOrDie(flag_name, flag_value, jxl_encoder_frame_settings,
+ encoder_option);
+ };
+
+ auto process_bool_flag = [&jxl_encoder_frame_settings](
+ const char* flag_name,
+ jxl::Override flag_value,
+ JxlEncoderFrameSettingId encoder_option) {
+ if (flag_value != jxl::Override::kDefault) {
+ SetFlagFrameOptionOrDie(flag_name,
+ flag_value == jxl::Override::kOn ? 1 : 0,
+ jxl_encoder_frame_settings, encoder_option);
+ }
};
- append_xml(io.blobs.xmp);
- if (!io.blobs.jumbf.empty()) {
- container.jumb = io.blobs.jumbf.data();
- container.jumb_size = io.blobs.jumbf.size();
- }
- jxl::PaddedBytes jpeg_data;
- if (args.store_jpeg_metadata && io.Main().IsJPEG()) {
- jxl::jpeg::JPEGData data_in = *io.Main().jpeg_data;
- if (EncodeJPEGData(data_in, &jpeg_data, args.params)) {
- container.jpeg_reconstruction = jpeg_data.data();
- container.jpeg_reconstruction_size = jpeg_data.size();
+
+ { // Processing tuning flags.
+ process_bool_flag("modular", args.modular, JXL_ENC_FRAME_SETTING_MODULAR);
+ process_bool_flag("keep_invisible", args.keep_invisible,
+ JXL_ENC_FRAME_SETTING_KEEP_INVISIBLE);
+ process_bool_flag("dots", args.dots, JXL_ENC_FRAME_SETTING_DOTS);
+ process_bool_flag("patches", args.patches, JXL_ENC_FRAME_SETTING_PATCHES);
+ process_bool_flag("gaborish", args.gaborish,
+ JXL_ENC_FRAME_SETTING_GABORISH);
+ process_bool_flag("group_order", args.group_order,
+ JXL_ENC_FRAME_SETTING_GROUP_ORDER);
+
+ if (!args.frame_indexing.empty()) {
+ bool must_be_all_zeros = args.frame_indexing[0] != '1';
+ for (char c : args.frame_indexing) {
+ if (c == '1') {
+ if (must_be_all_zeros) {
+ std::cerr
+ << "Invalid --frame_indexing. If the first character is "
+ "'0', all must be '0'."
+ << std::endl;
+ return EXIT_FAILURE;
+ }
+ } else if (c != '0') {
+ std::cerr << "Invalid --frame_indexing. Must match the pattern "
+ "'^(0*|1[01]*)$'."
+ << std::endl;
+ return EXIT_FAILURE;
+ }
+ }
+ }
+
+ process_flag(
+ "effort", args.effort, JXL_ENC_FRAME_SETTING_EFFORT,
+ [](int64_t x) -> std::string {
+ return (1 <= x && x <= 9) ? "" : "Valid range is {1, 2, ..., 9}.";
+ });
+ process_flag(
+ "brotli_effort", args.brotli_effort,
+ JXL_ENC_FRAME_SETTING_BROTLI_EFFORT, [](int64_t x) -> std::string {
+ return (-1 <= x && x <= 11) ? ""
+ : "Valid range is {-1, 0, 1, ..., 11}.";
+ });
+ process_flag("epf", args.epf, JXL_ENC_FRAME_SETTING_EPF,
+ [](int64_t x) -> std::string {
+ return (-1 <= x && x <= 3)
+ ? ""
+ : "Valid range is {-1, 0, 1, 2, 3}.\n";
+ });
+ process_flag(
+ "faster_decoding", args.faster_decoding,
+ JXL_ENC_FRAME_SETTING_DECODING_SPEED, [](int64_t x) -> std::string {
+ return (0 <= x && x <= 4) ? ""
+ : "Valid range is {0, 1, 2, 3, 4}.\n";
+ });
+ process_flag("resampling", args.resampling,
+ JXL_ENC_FRAME_SETTING_RESAMPLING,
+ [](int64_t x) -> std::string {
+ return (x == -1 || x == 1 || x == 4 || x == 8)
+ ? ""
+ : "Valid values are {-1, 1, 2, 4, 8}.\n";
+ });
+ process_flag("ec_resampling", args.ec_resampling,
+ JXL_ENC_FRAME_SETTING_EXTRA_CHANNEL_RESAMPLING,
+ [](int64_t x) -> std::string {
+ return (x == -1 || x == 1 || x == 4 || x == 8)
+ ? ""
+ : "Valid values are {-1, 1, 2, 4, 8}.\n";
+ });
+ SetFlagFrameOptionOrDie("photon_noise_iso", args.photon_noise_iso,
+ jxl_encoder_frame_settings,
+ JXL_ENC_FRAME_SETTING_PHOTON_NOISE);
+ SetFlagFrameOptionOrDie("already_downsampled",
+ static_cast<int32_t>(args.already_downsampled),
+ jxl_encoder_frame_settings,
+ JXL_ENC_FRAME_SETTING_ALREADY_DOWNSAMPLED);
+ SetDistanceFromFlags(jxl_encoder_frame_settings, &cmdline, &args, codec);
+
+ if (args.group_order != jxl::Override::kOn &&
+ (args.center_x != -1 || args.center_y != -1)) {
+ std::cerr
+ << "Invalid flag combination. Setting --center_x or --center_y "
+ << "requires setting --group_order=1" << std::endl;
+ return EXIT_FAILURE;
+ }
+ process_flag("center_x", args.center_x,
+ JXL_ENC_FRAME_SETTING_GROUP_ORDER_CENTER_X,
+ [](int64_t x) -> std::string {
+ if (x < -1) {
+ return "Valid values are: -1 or [0 .. xsize).";
+ }
+ return "";
+ });
+ process_flag("center_y", args.center_y,
+ JXL_ENC_FRAME_SETTING_GROUP_ORDER_CENTER_Y,
+ [](int64_t x) -> std::string {
+ if (x < -1) {
+ return "Valid values are: -1 or [0 .. ysize).";
+ }
+ return "";
+ });
+ }
+ { // Progressive/responsive mode settings.
+ bool qprogressive_ac_set =
+ cmdline.GetOption(args.opt_qprogressive_ac_id)->matched();
+ int32_t qprogressive_ac = args.qprogressive_ac ? 1 : 0;
+ bool responsive_set =
+ cmdline.GetOption(args.opt_responsive_id)->matched();
+ int32_t responsive = args.responsive ? 1 : 0;
+
+ process_flag(
+ "progressive_dc", args.progressive_dc,
+ JXL_ENC_FRAME_SETTING_PROGRESSIVE_DC, [](int64_t x) -> std::string {
+ return (-1 <= x && x <= 2) ? "" : "Valid range is {-1, 0, 1, 2}.\n";
+ });
+ SetFlagFrameOptionOrDie(
+ "progressive_ac", static_cast<int32_t>(args.progressive_ac),
+ jxl_encoder_frame_settings, JXL_ENC_FRAME_SETTING_PROGRESSIVE_AC);
+
+ if (args.progressive) {
+ qprogressive_ac = 1;
+ qprogressive_ac_set = true;
+ responsive = 1;
+ responsive_set = true;
+ }
+ if (responsive_set) {
+ SetFlagFrameOptionOrDie("responsive", responsive,
+ jxl_encoder_frame_settings,
+ JXL_ENC_FRAME_SETTING_RESPONSIVE);
+ }
+ if (qprogressive_ac_set) {
+ SetFlagFrameOptionOrDie("qprogressive_ac", qprogressive_ac,
+ jxl_encoder_frame_settings,
+ JXL_ENC_FRAME_SETTING_QPROGRESSIVE_AC);
+ }
+ }
+ { // Modular mode related.
+ // TODO(firsching): consider doing more validation after image size is
+ // known, i.e. set to 512 if 256 would be silly using
+ // opt_modular_group_size_id.
+ process_flag("modular_group_size", args.modular_group_size,
+ JXL_ENC_FRAME_SETTING_MODULAR_GROUP_SIZE,
+ [](int64_t x) -> std::string {
+ return (-1 <= x && x <= 3)
+ ? ""
+ : "Invalid --modular_group_size. Valid "
+ "range is {-1, 0, 1, 2, 3}.\n";
+ });
+ process_flag("modular_predictor", args.modular_predictor,
+ JXL_ENC_FRAME_SETTING_MODULAR_PREDICTOR,
+ [](int64_t x) -> std::string {
+ return (-1 <= x && x <= 15)
+ ? ""
+ : "Invalid --modular_predictor. Valid "
+ "range is {-1, 0, 1, ..., 15}.\n";
+ });
+ process_flag(
+ "modular_colorspace", args.modular_colorspace,
+ JXL_ENC_FRAME_SETTING_MODULAR_COLOR_SPACE,
+ [](int64_t x) -> std::string {
+ return (-1 <= x && x <= 41)
+ ? ""
+ : "Invalid --modular_colorspace. Valid range is "
+ "{-1, 0, 1, ..., 41}.\n";
+ });
+ process_float_flag(
+ "modular_ma_tree_learning_percent",
+ args.modular_ma_tree_learning_percent,
+ JXL_ENC_FRAME_SETTING_MODULAR_MA_TREE_LEARNING_PERCENT,
+ [](float x) -> std::string {
+ return -1 <= x && x <= 100
+ ? ""
+ : "Invalid --modular_ma_tree_learning_percent, Valid"
+ "rang is [-1, 100].\n";
+ });
+ process_flag("modular_nb_prev_channels", args.modular_nb_prev_channels,
+ JXL_ENC_FRAME_SETTING_MODULAR_NB_PREV_CHANNELS,
+ [](int64_t x) -> std::string {
+ return (-1 <= x && x <= 11)
+ ? ""
+ : "Invalid --modular_nb_prev_channels. Valid "
+ "range is {-1, 0, 1, ..., 11}.\n";
+ });
+ SetFlagFrameOptionOrDie("modular_lossy_palette",
+ static_cast<int32_t>(args.modular_lossy_palette),
+ jxl_encoder_frame_settings,
+ JXL_ENC_FRAME_SETTING_LOSSY_PALETTE);
+ process_flag("modular_palette_colors", args.modular_palette_colors,
+ JXL_ENC_FRAME_SETTING_PALETTE_COLORS,
+ [](int64_t x) -> std::string {
+ return -1 <= x ? ""
+ : "Invalid --modular_palette_colors, must "
+ "be -1 or non-negative\n";
+ });
+ process_float_flag(
+ "modular_channel_colors_global_percent",
+ args.modular_channel_colors_global_percent,
+ JXL_ENC_FRAME_SETTING_CHANNEL_COLORS_GLOBAL_PERCENT,
+ [](float x) -> std::string {
+ return (-1 <= x && x <= 100)
+ ? ""
+ : "Invalid --modular_channel_colors_global_percent. "
+ "Valid "
+ "range is [-1, 100].\n";
+ });
+ process_float_flag(
+ "modular_channel_colors_group_percent",
+ args.modular_channel_colors_group_percent,
+ JXL_ENC_FRAME_SETTING_CHANNEL_COLORS_GROUP_PERCENT,
+ [](float x) -> std::string {
+ return (-1 <= x && x <= 100)
+ ? ""
+ : "Invalid --modular_channel_colors_group_percent. "
+ "Valid "
+ "range is [-1, 100].\n";
+ });
+ }
+
+ bool use_container = args.container == jxl::Override::kOn;
+ if (!ppf.metadata.exif.empty() || !ppf.metadata.xmp.empty() ||
+ !ppf.metadata.jumbf.empty() || !ppf.metadata.iptc.empty() ||
+ (args.lossless_jpeg && args.jpeg_store_metadata)) {
+ use_container = true;
+ }
+ if (use_container) args.container = jxl::Override::kOn;
+
+ if (!ppf.metadata.exif.empty()) {
+ jxl::InterpretExif(ppf.metadata.exif, &ppf.info.orientation);
+ }
+
+ if (JXL_ENC_SUCCESS !=
+ JxlEncoderUseContainer(jxl_encoder, static_cast<int>(use_container))) {
+ std::cerr << "JxlEncoderUseContainer failed." << std::endl;
+ return EXIT_FAILURE;
+ }
+
+ if (num_rep == 0 && !args.quiet)
+ PrintMode(ppf, decode_mps, image_data.size(), args);
+
+ if (args.lossless_jpeg && IsJPG(image_data)) {
+ if (!cmdline.GetOption(args.opt_lossless_jpeg_id)->matched()) {
+ std::cerr << "Note: Implicit-default for JPEG is lossless-transcoding. "
+ << "To silence this message, set --lossless_jpeg=(1|0)."
+ << std::endl;
+ }
+ if (args.jpeg_store_metadata) {
+ if (JXL_ENC_SUCCESS !=
+ JxlEncoderStoreJPEGMetadata(jxl_encoder, JXL_TRUE)) {
+ std::cerr << "Storing JPEG metadata failed. " << std::endl;
+ return EXIT_FAILURE;
+ }
+ }
+ process_bool_flag("jpeg_reconstruction_cfl", args.jpeg_reconstruction_cfl,
+ JXL_ENC_FRAME_SETTING_JPEG_RECON_CFL);
+ if (JXL_ENC_SUCCESS != JxlEncoderAddJPEGFrame(jxl_encoder_frame_settings,
+ image_data.data(),
+ image_data.size())) {
+ std::cerr << "JxlEncoderAddJPEGFrame() failed." << std::endl;
+ return EXIT_FAILURE;
+ }
+ } else { // Do JxlEncoderAddImageFrame().
+ size_t num_alpha_channels = 0; // Adjusted below.
+ {
+ JxlBasicInfo basic_info = ppf.info;
+ if (basic_info.alpha_bits > 0) num_alpha_channels = 1;
+ basic_info.intensity_target = args.intensity_target;
+ basic_info.num_extra_channels = num_alpha_channels;
+ basic_info.num_color_channels = ppf.info.num_color_channels;
+ const bool lossless = args.distance == 0;
+ basic_info.uses_original_profile = lossless;
+ if (args.override_bitdepth != 0) {
+ basic_info.bits_per_sample = args.override_bitdepth;
+ basic_info.exponent_bits_per_sample =
+ args.override_bitdepth == 32 ? 8 : 0;
+ }
+ if (JXL_ENC_SUCCESS !=
+ JxlEncoderSetCodestreamLevel(jxl_encoder, args.codestream_level)) {
+ std::cerr << "Setting --codestream_level failed." << std::endl;
+ return EXIT_FAILURE;
+ }
+ if (JXL_ENC_SUCCESS !=
+ JxlEncoderSetBasicInfo(jxl_encoder, &basic_info)) {
+ std::cerr << "JxlEncoderSetBasicInfo() failed." << std::endl;
+ return EXIT_FAILURE;
+ }
+ if (lossless &&
+ JXL_ENC_SUCCESS != JxlEncoderSetFrameLossless(
+ jxl_encoder_frame_settings, JXL_TRUE)) {
+ std::cerr << "JxlEncoderSetFrameLossless() failed." << std::endl;
+ return EXIT_FAILURE;
+ }
+ }
+
+ if (!ppf.icc.empty()) {
+ if (JXL_ENC_SUCCESS != JxlEncoderSetICCProfile(jxl_encoder,
+ ppf.icc.data(),
+ ppf.icc.size())) {
+ std::cerr << "JxlEncoderSetICCProfile() failed." << std::endl;
+ return EXIT_FAILURE;
+ }
} else {
- fprintf(stderr, "Warning: failed to create JPEG reconstruction data\n");
- ret = CjxlRetCode::DROPPED_JBRD;
+ if (JXL_ENC_SUCCESS !=
+ JxlEncoderSetColorEncoding(jxl_encoder, &ppf.color_encoding)) {
+ std::cerr << "JxlEncoderSetColorEncoding() failed." << std::endl;
+ return EXIT_FAILURE;
+ }
+ }
+
+ for (size_t num_frame = 0; num_frame < ppf.frames.size(); ++num_frame) {
+ const jxl::extras::PackedFrame& pframe = ppf.frames[num_frame];
+ const jxl::extras::PackedImage& pimage = pframe.color;
+ JxlPixelFormat ppixelformat = pimage.format;
+ {
+ if (JXL_ENC_SUCCESS !=
+ JxlEncoderSetFrameHeader(jxl_encoder_frame_settings,
+ &pframe.frame_info)) {
+ std::cerr << "JxlEncoderSetFrameHeader() failed." << std::endl;
+ return EXIT_FAILURE;
+ }
+ }
+ if (num_frame < args.frame_indexing.size() &&
+ args.frame_indexing[num_frame] == '1') {
+ if (JXL_ENC_SUCCESS !=
+ JxlEncoderFrameSettingsSetOption(jxl_encoder_frame_settings,
+ JXL_ENC_FRAME_INDEX_BOX, 1)) {
+ std::cerr << "Setting option JXL_ENC_FRAME_INDEX_BOX failed."
+ << std::endl;
+ return EXIT_FAILURE;
+ }
+ }
+ JxlEncoderStatus enc_status;
+ {
+ if (num_alpha_channels > 0) {
+ JxlExtraChannelInfo extra_channel_info;
+ JxlEncoderInitExtraChannelInfo(JXL_CHANNEL_ALPHA,
+ &extra_channel_info);
+ enc_status = JxlEncoderSetExtraChannelInfo(jxl_encoder, 0,
+ &extra_channel_info);
+ if (JXL_ENC_SUCCESS != enc_status) {
+ std::cerr << "JxlEncoderSetExtraChannelInfo() failed."
+ << std::endl;
+ return EXIT_FAILURE;
+ }
+ if (args.premultiply != -1) {
+ if (args.premultiply != 0 && args.premultiply != 1) {
+ std::cerr << "Flag --premultiply must be one of: -1, 0, 1."
+ << std::endl;
+ return EXIT_FAILURE;
+ }
+ extra_channel_info.alpha_premultiplied = args.premultiply;
+ }
+ // We take the extra channel blend info frame_info, but don't do
+ // clamping.
+ JxlBlendInfo extra_channel_blend_info =
+ pframe.frame_info.layer_info.blend_info;
+ extra_channel_blend_info.clamp = JXL_FALSE;
+ JxlEncoderSetExtraChannelBlendInfo(jxl_encoder_frame_settings, 0,
+ &extra_channel_blend_info);
+ }
+ enc_status =
+ JxlEncoderAddImageFrame(jxl_encoder_frame_settings, &ppixelformat,
+ pimage.pixels(), pimage.pixels_size);
+ if (JXL_ENC_SUCCESS != enc_status) {
+ std::cerr << "JxlEncoderAddImageFrame() failed." << std::endl;
+ return EXIT_FAILURE;
+ }
+ // Only set extra channel buffer if is is provided non-interleaved.
+ if (!pframe.extra_channels.empty()) {
+ enc_status = JxlEncoderSetExtraChannelBuffer(
+ jxl_encoder_frame_settings, &ppixelformat,
+ pframe.extra_channels[0].pixels(),
+ pframe.extra_channels[0].stride *
+ pframe.extra_channels[0].ysize,
+ 0);
+ if (JXL_ENC_SUCCESS != enc_status) {
+ std::cerr << "JxlEncoderSetExtraChannelBuffer() failed."
+ << std::endl;
+ return EXIT_FAILURE;
+ }
+ }
+ }
}
}
- compressed = {};
- if (!EncodeJpegXlContainerOneShot(container, &compressed)) {
- fprintf(stderr, "Failed to encode container format\n");
- return CjxlRetCode::ERR_CONTAINER;
+ JxlEncoderCloseInput(jxl_encoder);
+ // Reading compressed output
+ compressed.clear();
+ compressed.resize(4096);
+ uint8_t* next_out = compressed.data();
+ size_t avail_out = compressed.size() - (next_out - compressed.data());
+ JxlEncoderStatus process_result = JXL_ENC_NEED_MORE_OUTPUT;
+ while (process_result == JXL_ENC_NEED_MORE_OUTPUT) {
+ process_result =
+ JxlEncoderProcessOutput(jxl_encoder, &next_out, &avail_out);
+ if (process_result == JXL_ENC_NEED_MORE_OUTPUT) {
+ size_t offset = next_out - compressed.data();
+ compressed.resize(compressed.size() * 2);
+ next_out = compressed.data() + offset;
+ avail_out = compressed.size() - offset;
+ }
}
- if (!args.quiet) {
- const size_t pixels = io.xsize() * io.ysize();
- const double bpp =
- static_cast<double>(compressed.size() * jxl::kBitsPerByte) / pixels;
- fprintf(stderr, "Including container: %llu bytes (%.3f bpp%s).\n",
- static_cast<long long unsigned>(compressed.size()),
- bpp / io.frames.size(), io.frames.size() == 1 ? "" : "/frame");
+ compressed.resize(next_out - compressed.data());
+ if (JXL_ENC_SUCCESS != process_result) {
+ std::cerr << "JxlEncoderProcessOutput failed." << std::endl;
+ return EXIT_FAILURE;
}
+
+ const double t1 = jxl::Now();
+ stats.NotifyElapsed(t1 - t0);
+ stats.SetImageSize(ppf.info.xsize, ppf.info.ysize);
}
+
if (args.file_out) {
- if (!jxl::WriteFile(compressed, args.file_out)) {
- fprintf(stderr, "Failed to write to \"%s\"\n", args.file_out);
- return CjxlRetCode::ERR_WRITE;
+ if (!jpegxl::tools::WriteFile(args.file_out, compressed)) {
+ std::cerr << "Could not write jxl file." << std::endl;
+ return EXIT_FAILURE;
}
}
-
- if (args.print_profile == jxl::Override::kOn) {
- PROFILER_PRINT_RESULTS();
- }
- if (!args.quiet && cmdline.verbosity > 0) {
- jxl::CacheAligned::PrintStats();
+ if (!args.quiet) {
+ const double bpp =
+ static_cast<double>(compressed.size() * jxl::kBitsPerByte) / pixels;
+ fprintf(stderr, "Compressed to %" PRIuS " bytes ", compressed.size());
+ // For lossless jpeg-reconstruction, we don't print some stats, since we
+ // don't have easy access to the image dimensions.
+ if (args.container == jxl::Override::kOn) {
+ fprintf(stderr, "including container ");
+ }
+ if (!args.lossless_jpeg) {
+ fprintf(stderr, "(%.3f bpp%s).\n", bpp / ppf.frames.size(),
+ ppf.frames.size() == 1 ? "" : "/frame");
+ JXL_CHECK(stats.Print(num_worker_threads));
+ } else {
+ fprintf(stderr, "\n");
+ }
}
- return ret;
-}
-
-} // namespace tools
-} // namespace jpegxl
-
-int main(int argc, const char** argv) {
- return jpegxl::tools::CompressJpegXlMain(argc, argv);
+ return EXIT_SUCCESS;
}
diff --git a/media/libjxl/src/tools/cjxl_ng_main.cc b/media/libjxl/src/tools/cjxl_ng_main.cc
deleted file mode 100644
index 403ee82cfd..0000000000
--- a/media/libjxl/src/tools/cjxl_ng_main.cc
+++ /dev/null
@@ -1,922 +0,0 @@
-// Copyright (c) the JPEG XL Project Authors. All rights reserved.
-//
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-// Note: This encoder binary does extensive flag-validity checking (in
-// order to produce meaningful error messages), and on top of that
-// checks all libjxl C API call return values. The downside of this
-// vs. libjxl providing meaningful error messages is that a change to
-// the accepted range of a flag-specified parameter in libjxl will
-// also require a change to the range-check here. The advantage is
-// that this minimizes the size of libjxl.
-
-#include <stdint.h>
-
-#include <cmath>
-#include <cstdlib>
-#include <functional>
-#include <iostream>
-#include <sstream>
-#include <string>
-#include <vector>
-
-#include "gflags/gflags.h"
-#include "jxl/codestream_header.h"
-#include "jxl/encode.h"
-#include "jxl/encode_cxx.h"
-#include "jxl/thread_parallel_runner.h"
-#include "jxl/thread_parallel_runner_cxx.h"
-#include "jxl/types.h"
-#include "lib/extras/codec.h"
-#include "lib/extras/dec/apng.h"
-#include "lib/extras/dec/color_hints.h"
-#include "lib/extras/dec/gif.h"
-#include "lib/extras/dec/jpg.h"
-#include "lib/extras/dec/pgx.h"
-#include "lib/extras/dec/pnm.h"
-#include "lib/jxl/base/file_io.h"
-#include "lib/jxl/base/status.h"
-#include "lib/jxl/size_constraints.h"
-
-DECLARE_bool(help);
-DECLARE_bool(helpshort);
-// The flag --version is owned by gflags itself.
-DEFINE_bool(encoder_version, false,
- "Print encoder library version number and exit.");
-
-DEFINE_bool(lossless_jpeg, true,
- "If the input is JPEG, use JxlEncoderAddJPEGFrame "
- "to add a JPEG frame (i.e. losslessly transcoding JPEG), "
- "rather than using JxlEncoderAddImageFrame to reencode pixels.");
-
-DEFINE_bool(jpeg_store_metadata, true,
- "If --lossless_jpeg is set, store JPEG reconstruction "
- "metadata in the JPEG XL container "
- "(for lossless reconstruction of the JPEG codestream).");
-
-DEFINE_bool(jpeg_reconstruction_cfl, true,
- "Enable/disable chroma-from-luma (CFL) for lossless "
- "JPEG reconstruction.");
-
-DEFINE_bool(container, false,
- "Force using container format (default: use only if needed).");
-
-DEFINE_bool(strip, false,
- "Do not encode using container format (strips "
- "Exif/XMP/JPEG bitstream reconstruction data).");
-
-DEFINE_bool(responsive, false, "[modular encoding] Do Squeeze transform");
-
-DEFINE_bool(progressive, false, "Enable progressive/responsive decoding.");
-
-DEFINE_bool(progressive_ac, false, "Use progressive mode for AC.");
-
-DEFINE_bool(qprogressive_ac, false, "Use progressive mode for AC.");
-
-DEFINE_bool(modular_lossy_palette, false, "Use delta-palette.");
-
-DEFINE_int32(premultiply, -1,
- "Force premultiplied (associated) alpha. "
- "-1 = Do what the input does, 0 = Do not premultiply, "
- "1 = force premultiply.");
-
-DEFINE_bool(already_downsampled, false,
- "Do not downsample the given input before encoding, "
- "but still signal that the decoder should upsample.");
-
-DEFINE_bool(
- modular, false,
- "Use modular mode (not provided = encoder chooses, 0 = enforce VarDCT, "
- "1 = enforce modular mode).");
-
-DEFINE_bool(keep_invisible, false,
- "Force disable/enable preserving color of invisible "
- "pixels. (not provided = default, 0 = disable, 1 = enable).");
-
-DEFINE_bool(dots, false,
- "Force disable/enable dots generation. "
- "(not provided = default, 0 = disable, 1 = enable).");
-
-DEFINE_bool(patches, false,
- "Force disable/enable patches generation. "
- "(not provided = default, 0 = disable, 1 = enable).");
-
-DEFINE_bool(gaborish, false,
- "Force disable/enable the gaborish filter. "
- "(not provided = default, 0 = disable, 1 = enable).");
-
-DEFINE_bool(
- group_order, false,
- "Order in which 256x256 regions are stored "
- "in the codestream for progressive rendering. "
- "Value not provided means 'encoder default', 0 means 'scanline order', "
- "1 means 'center-first order'.");
-
-DEFINE_double(
- intensity_target, 0.0,
- "Upper bound on the intensity level present in the image in nits. "
- "Leaving this set to its default of 0 lets libjxl choose a sensible "
- "default "
- "value based on the color encoding.");
-
-// TODO(tfish):
-// --dec-hints, -- NEED (passed to image decoders, via extras, tweaks decoding)
-// --override_bitdepth, -- NEED
-
-DEFINE_int32(progressive_dc, -1,
- "Progressive-DC setting. Valid values are: -1, 0, 1, 2.");
-
-DEFINE_int32(faster_decoding, 0,
- "Favour higher decoding speed. 0 = default, higher "
- "values give higher speed at the expense of quality");
-
-DEFINE_int32(
- resampling, -1,
- "Resampling. Default of -1 applies resampling only for low quality. "
- "Value 1 does no downsampling (1x1), 2 does 2x2 downsampling, "
- "4 is for 4x4 downsampling, and 8 for 8x8 downsampling.");
-
-DEFINE_int32(
- ec_resampling, -1,
- "Resampling for extra channels. Default of -1 applies resampling only "
- "for low quality. Value 1 does no downsampling (1x1), 2 does 2x2 "
- "downsampling, 4 is for 4x4 downsampling, and 8 for 8x8 downsampling.");
-
-DEFINE_int32(
- epf, -1,
- "Edge preserving filter level, -1 to 3. "
- "Value -1 means: default (encoder chooses), 0 to 3 set a strength.");
-
-DEFINE_int64(
- center_x, -1,
- "Determines the horizontal position of center for the center-first "
- "group order. The value -1 means 'use the middle of the image', "
- "other values [0..xsize) set this to a particular coordinate.");
-
-DEFINE_int64(center_y, -1,
- "Determines the vertical position of center for the center-first "
- "group order. The value -1 means 'use the middle of the image', "
- "other values [0..ysize) set this to a particular coordinate.");
-
-DEFINE_int64(num_threads, -1,
- "Number of worker threads (-1 == use machine default, "
- "0 == do not use multithreading).");
-
-DEFINE_int64(num_reps, 1, "How many times to compress. (For benchmarking).");
-
-DEFINE_int32(modular_group_size, -1,
- "[modular encoding] group size: -1 == default. 0 => 128, "
- "1 => 256, 2 => 512, 3 => 1024");
-
-DEFINE_int32(modular_predictor, -1,
- "[modular encoding] predictor(s) to use: 0=zero, "
- "1=left, 2=top, 3=avg0, 4=select, 5=gradient, 6=weighted, "
- "7=topright, 8=topleft, 9=leftleft, 10=avg1, 11=avg2, 12=avg3, "
- "13=toptop predictive average "
- "14=mix 5 and 6, 15=mix everything. If unset, uses default 14, "
- "at slowest speed default 15.");
-
-DEFINE_int32(modular_colorspace, -1,
- "[modular encoding] color transform: 0=RGB, 1=YCoCg, "
- "2-37=RCT (default: try several, depending on speed)");
-
-DEFINE_int32(
- modular_channel_colors_global_percent, -1,
- "[modular encoding] Use Global channel palette if the number of "
- "colors is smaller than this percentage of range. "
- "Use 0-100 to set an explicit percentage, -1 to use the encoder default.");
-
-DEFINE_int32(
- modular_channel_colors_group_percent, -1,
- "[modular encoding] Use Local (per-group) channel palette if the number "
- "of colors is smaller than this percentage of range. Use 0-100 to set "
- "an explicit percentage, -1 to use the encoder default.");
-
-DEFINE_int32(
- modular_palette_colors, -1,
- "[modular encoding] Use color palette if number of colors is smaller "
- "than or equal to this, or -1 to use the encoder default.");
-
-DEFINE_int32(modular_nb_prev_channels, -1,
- "[modular encoding] number of extra MA tree properties to use");
-
-DEFINE_int32(modular_ma_tree_learning_percent, -1,
- "[modular encoding] Fraction of pixels used to learn MA trees as "
- "a percentage. -1 = default, 0 = no MA and fast decode, 50 = "
- "default value, 100 = all, values above 100 are also permitted. "
- "Higher values use more encoder memory.");
-
-DEFINE_int32(photon_noise_iso, 0,
- "Adds noise to the image emulating photographic film noise. "
- "The higher the given number, the grainier the image will be. "
- "As an example, a value of 100 gives low noise whereas a value "
- "of 3200 gives a lot of noise. The default value is 0.");
-
-DEFINE_int32(codestream_level, 5, "The codestream level. Either `5` or `10`.");
-
-DEFINE_double(
- distance, 1.0,
- "Max. butteraugli distance, lower = higher quality.\n"
- " 0.0 = mathematically lossless. Default for already-lossy input "
- "(JPEG/GIF).\n"
- " 1.0 = visually lossless. Default for other input.\n"
- " Recommended range: 0.5 .. 3.0. Mutually exclusive with --quality.");
-
-DEFINE_double(
- quality, 100.0,
- "Quality setting (is remapped to --distance). Range: -inf .. 100.\n"
- " 100 = mathematically lossless. Default for already-lossy input "
- "(JPEG/GIF).\n"
- " Other input gets encoded as per --distance default.\n"
- " Positive quality values roughly match libjpeg quality.\n"
- " Mutually exclusive with --distance.");
-
-DEFINE_int64(effort, 3,
- "Encoder effort setting. Range: 1 .. 9.\n"
- " Higher number is more effort (slower).");
-
-DEFINE_int32(brotli_effort, 9,
- "Brotli effort setting. Range: 0 .. 11.\n"
- " Default: 9. Higher number is more effort (slower).");
-
-DEFINE_string(frame_indexing, "",
- // TODO(tfish): Add a more convenient vanilla alternative.
- "If non-empty, a string matching '^[01]*$'. If this string has a "
- "'1' in i-th position, then the i-th frame will be indexed in "
- "the frame index box.");
-
-namespace {
-/**
- * Writes bytes to file.
- */
-bool WriteFile(const std::vector<uint8_t>& bytes, const char* filename) {
- FILE* file = fopen(filename, "wb");
- if (!file) {
- std::cerr << "Could not open file: " << filename << " for writing"
- << std::endl
- << "Error: " << strerror(errno) << std::endl;
- return false;
- }
- if (fwrite(bytes.data(), sizeof(uint8_t), bytes.size(), file) !=
- bytes.size()) {
- std::cerr << "Could not write bytes to file: " << filename << std::endl
- << "Error: " << strerror(errno) << std::endl;
- return false;
- }
- if (fclose(file) != 0) {
- std::cerr << "Could not close file: " << filename << std::endl
- << "Error: " << strerror(errno) << std::endl;
- return false;
- }
- return true;
-}
-
-void SetFlagFrameOptionOrDie(const char* flag_name, int32_t flag_value,
- JxlEncoderFrameSettings* frame_settings,
- JxlEncoderFrameSettingId encoder_option) {
- if (JXL_ENC_SUCCESS != JxlEncoderFrameSettingsSetOption(
- frame_settings, encoder_option, flag_value)) {
- std::cerr << "Setting encoder option from flag -- " << flag_name
- << "failed." << std::endl;
- exit(EXIT_FAILURE);
- }
-}
-
-void SetDistanceFromFlags(JxlEncoderFrameSettings* jxl_encoder_frame_settings,
- const jxl::extras::Codec& codec) {
- bool distance_set =
- !gflags::GetCommandLineFlagInfoOrDie("distance").is_default;
- bool quality_set = !gflags::GetCommandLineFlagInfoOrDie("quality").is_default;
-
- if (distance_set && quality_set) {
- std::cerr << "Must not set both --distance and --quality." << std::endl;
- exit(EXIT_FAILURE);
- }
- if (distance_set) {
- if (JXL_ENC_SUCCESS != JxlEncoderSetFrameDistance(
- jxl_encoder_frame_settings, FLAGS_distance)) {
- std::cerr << "Setting --distance parameter failed." << std::endl;
- exit(EXIT_FAILURE);
- }
- return;
- }
- if (quality_set) {
- double distance = FLAGS_quality >= 100 ? 0.0
- : FLAGS_quality >= 30
- ? 0.1 + (100 - FLAGS_quality) * 0.09
- : 6.4 + pow(2.5, (30 - FLAGS_quality) / 5.0) / 6.25;
- if (JXL_ENC_SUCCESS !=
- JxlEncoderSetFrameDistance(jxl_encoder_frame_settings, distance)) {
- std::cerr << "Setting --quality parameter failed." << std::endl;
- exit(EXIT_FAILURE);
- }
- return;
- }
- // No flag set, but input is JPG or GIF: Use distance 0 default.
- if (codec == jxl::extras::Codec::kJPG || codec == jxl::extras::Codec::kGIF) {
- if (JXL_ENC_SUCCESS ==
- JxlEncoderSetFrameDistance(jxl_encoder_frame_settings, 0.0)) {
- std::cerr << "Setting 'lossless' default for GIF or JPEG input."
- << std::endl;
- }
- }
-}
-
-typedef std::function<std::string(int32_t)> flag_check_fn;
-
-bool IsJPG(const jxl::PaddedBytes& image_data) {
- return (image_data.size() >= 2 && image_data[0] == 0xFF &&
- image_data[1] == 0xD8);
-}
-
-void SetCodestreamLevel(JxlEncoder* jxl_encoder, bool for_lossless_jpeg) {
- bool flag_set =
- !gflags::GetCommandLineFlagInfoOrDie("codestream_level").is_default;
- int32_t codestream_level = FLAGS_codestream_level;
- auto set_codestream_level = [&jxl_encoder, &codestream_level]() {
- if (JXL_ENC_SUCCESS !=
- JxlEncoderSetCodestreamLevel(jxl_encoder, codestream_level)) {
- std::cerr << "Setting --codestream_level failed." << std::endl;
- exit(EXIT_FAILURE);
- }
- };
- if (for_lossless_jpeg) {
- if (!flag_set) {
- set_codestream_level();
- }
- } else {
- if (!flag_set) {
- codestream_level = static_cast<int32_t>(
- JxlEncoderGetRequiredCodestreamLevel(jxl_encoder));
- if (codestream_level == -1) {
- std::cerr << "No codestream_level supports the given image parameters."
- << std::endl;
- exit(EXIT_FAILURE);
- }
- }
- set_codestream_level();
- }
-}
-
-// TODO(tfish): Replace with non-C-API library function.
-// Implementation is in extras/.
-jxl::Status GetPixeldata(const jxl::PaddedBytes& image_data,
- jxl::extras::PackedPixelFile& ppf,
- jxl::extras::Codec& codec) {
- // Any valid encoding is larger (ensures codecs can read the first few bytes).
- constexpr size_t kMinBytes = 9;
-
- if (image_data.size() < kMinBytes) return JXL_FAILURE("Input too small.");
- jxl::Span<const uint8_t> encoded(image_data);
-
- ppf.info.orientation = JXL_ORIENT_IDENTITY;
- jxl::extras::ColorHints color_hints;
- jxl::SizeConstraints size_constraints;
-
-#if JPEGXL_ENABLE_APNG
- if (jxl::extras::DecodeImageAPNG(encoded, color_hints, size_constraints,
- &ppf)) {
- codec = jxl::extras::Codec::kPNG;
- } else
-#endif
- if (jxl::extras::DecodeImagePGX(encoded, color_hints, size_constraints,
- &ppf)) {
- codec = jxl::extras::Codec::kPGX;
- } else if (jxl::extras::DecodeImagePNM(encoded, color_hints, size_constraints,
- &ppf)) {
- codec = jxl::extras::Codec::kPNM;
- }
-#if JPEGXL_ENABLE_GIF
- else if (jxl::extras::DecodeImageGIF(encoded, color_hints, size_constraints,
- &ppf)) {
- codec = jxl::extras::Codec::kGIF;
- }
-#endif
-#if JPEGXL_ENABLE_JPEG
- else if (jxl::extras::DecodeImageJPG(encoded, color_hints, size_constraints,
- &ppf)) {
- codec = jxl::extras::Codec::kJPG;
- }
-#endif
- else { // TODO(tfish): Bring back EXR and PSD.
- return JXL_FAILURE("Codecs failed to decode input.");
- }
- // TODO(tfish): Migrate this:
- // if (!skip_ppf_conversion) {
- // JXL_RETURN_IF_ERROR(ConvertPackedPixelFileToCodecInOut(ppf, pool, io));
- // }
- return true;
-}
-
-void set_usage_message_and_version(const char* argv0) {
- gflags::SetUsageMessage(
- "JPEG XL-encodes an image.\n"
- " Input format can be one of: "
-#if JPEGXL_ENABLE_APNG
- "PNG, APNG, "
-#endif
-#if JPEGXL_ENABLE_GIF
- "GIF, "
-#endif
-#if JPEGXL_ENABLE_JPEG
- "JPEG, "
-#endif
- "PPM, PFM, PGX.\n Sample usage:\n" +
- std::string(argv0) + " <source_image_filename> <target_image_filename>");
- uint32_t version = JxlEncoderVersion();
-
- gflags::SetVersionString(std::to_string(version / 1000000) + "." +
- std::to_string((version / 1000) % 1000) + "." +
- std::to_string(version % 1000));
-}
-
-} // namespace
-
-int main(int argc, char** argv) {
- std::cerr << "Warning: This is work in progress, consider using cjxl instead!"
- << std::endl;
- set_usage_message_and_version(argv[0]);
- // TODO(firsching): rethink --help handling
- gflags::ParseCommandLineNonHelpFlags(&argc, &argv, /*remove_flags=*/true);
- if (FLAGS_help) {
- FLAGS_help = false;
- FLAGS_helpshort = true;
- }
- gflags::HandleCommandLineHelpFlags();
-
- if (argc != 3) {
- FLAGS_help = false;
- FLAGS_helpshort = true;
- gflags::HandleCommandLineHelpFlags();
- return EXIT_FAILURE;
- }
- const char* filename_in = argv[1];
- const char* filename_out = argv[2];
-
- // Loading the input.
- // Depending on flags-settings, we want to either load a JPEG and
- // faithfully convert it to JPEG XL, or load (JPEG or non-JPEG)
- // pixel data. For benchmarking, we want to be able to do
- // N repetitions of image-compression, but the input should
- // not get reloaded as part of that.
- // Since we do not want to load the input before we decided that
- // flag-settings are valid, we need a mechanism to lazy-load the image.
- bool input_image_loaded = false;
- jxl::PaddedBytes image_data;
- jxl::extras::PackedPixelFile ppf;
- jxl::extras::Codec codec = jxl::extras::Codec::kUnknown;
- auto ensure_image_loaded = [&filename_in, &input_image_loaded, &image_data,
- &ppf, &codec]() {
- if (input_image_loaded) return;
- if (!ReadFile(filename_in, &image_data)) {
- std::cerr << "Reading image data failed." << std::endl;
- exit(EXIT_FAILURE);
- }
- if (!(FLAGS_lossless_jpeg && IsJPG(image_data))) {
- jxl::Status status = GetPixeldata(image_data, ppf, codec);
- if (!status) {
- std::cerr << "Getting pixel data." << std::endl;
- exit(EXIT_FAILURE);
- }
- if (ppf.frames.size() < 1) {
- std::cerr << "No frames on input file." << std::endl;
- exit(EXIT_FAILURE);
- }
- }
- input_image_loaded = true;
- };
-
- JxlEncoderPtr enc = JxlEncoderMake(/*memory_manager=*/nullptr);
- JxlEncoder* jxl_encoder = enc.get();
- JxlThreadParallelRunnerPtr runner;
- for (int num_rep = 0; num_rep < FLAGS_num_reps; ++num_rep) {
- JxlEncoderReset(jxl_encoder);
- if (FLAGS_num_threads != 0) {
- size_t num_worker_threads =
- JxlThreadParallelRunnerDefaultNumWorkerThreads();
- {
- int64_t flag_num_worker_threads = FLAGS_num_threads;
- if (flag_num_worker_threads != -1) {
- num_worker_threads = flag_num_worker_threads;
- }
- }
- if (runner == nullptr) {
- runner = JxlThreadParallelRunnerMake(
- /*memory_manager=*/nullptr, num_worker_threads);
- }
- if (JXL_ENC_SUCCESS !=
- JxlEncoderSetParallelRunner(jxl_encoder, JxlThreadParallelRunner,
- runner.get())) {
- std::cerr << "JxlEncoderSetParallelRunner failed." << std::endl;
- return EXIT_FAILURE;
- }
- }
-
- JxlEncoderFrameSettings* jxl_encoder_frame_settings =
- JxlEncoderFrameSettingsCreate(jxl_encoder, nullptr);
-
- auto process_flag = [&jxl_encoder_frame_settings](
- const char* flag_name, int32_t flag_value,
- JxlEncoderFrameSettingId encoder_option,
- flag_check_fn flag_check) {
- gflags::CommandLineFlagInfo flag_info =
- gflags::GetCommandLineFlagInfoOrDie(flag_name);
- if (!flag_info.is_default) {
- std::string error = flag_check(flag_value);
- if (!error.empty()) {
- std::cerr << "Invalid flag value for --" << flag_name << ": " << error
- << std::endl;
- exit(EXIT_FAILURE);
- }
- SetFlagFrameOptionOrDie(flag_name, flag_value,
- jxl_encoder_frame_settings, encoder_option);
- }
- };
-
- auto process_bool_flag = [&process_flag](
- const char* flag_name, int32_t flag_value,
- JxlEncoderFrameSettingId encoder_option) {
- process_flag(flag_name, static_cast<int32_t>(flag_value), encoder_option,
- [](int32_t x) { return ""; });
- };
-
- { // Processing tuning flags.
- bool use_container = FLAGS_container;
- // TODO(tfish): Set use_container according to need of encoded data.
- // This will likely require moving this piece out of flags-processing.
- if (FLAGS_strip) {
- use_container = false;
- }
- JxlEncoderUseContainer(jxl_encoder, use_container);
-
- process_bool_flag("modular", FLAGS_modular,
- JXL_ENC_FRAME_SETTING_MODULAR);
- process_bool_flag("keep_invisible", FLAGS_keep_invisible,
- JXL_ENC_FRAME_SETTING_KEEP_INVISIBLE);
- process_bool_flag("dots", FLAGS_dots, JXL_ENC_FRAME_SETTING_DOTS);
- process_bool_flag("patches", FLAGS_patches,
- JXL_ENC_FRAME_SETTING_PATCHES);
- process_bool_flag("gaborish", FLAGS_gaborish,
- JXL_ENC_FRAME_SETTING_GABORISH);
- process_bool_flag("group_order", FLAGS_group_order,
- JXL_ENC_FRAME_SETTING_GROUP_ORDER);
-
- if (!FLAGS_frame_indexing.empty()) {
- bool must_be_all_zeros = FLAGS_frame_indexing[0] != '1';
- for (char c : FLAGS_frame_indexing) {
- if (c == '1') {
- if (must_be_all_zeros) {
- std::cerr
- << "Invalid --frame_indexing. If the first character is "
- "'0', all must be '0'."
- << std::endl;
- return EXIT_FAILURE;
- }
- } else if (c != '0') {
- std::cerr << "Invalid --frame_indexing. Must match the pattern "
- "'^(0*|1[01]*)$'."
- << std::endl;
- return EXIT_FAILURE;
- }
- }
- }
-
- process_flag(
- "effort", FLAGS_effort, JXL_ENC_FRAME_SETTING_EFFORT,
- [](int32_t x) -> std::string {
- return (1 <= x && x <= 9) ? "" : "Valid range is {1, 2, ..., 9}.";
- });
- process_flag(
- "brotli_effort", FLAGS_brotli_effort,
- JXL_ENC_FRAME_SETTING_BROTLI_EFFORT, [](int32_t x) -> std::string {
- return (-1 <= x && x <= 11) ? ""
- : "Valid range is {-1, 0, 1, ..., 11}.";
- });
- process_flag("epf", FLAGS_epf, JXL_ENC_FRAME_SETTING_EPF,
- [](int32_t x) -> std::string {
- return (-1 <= x && x <= 3)
- ? ""
- : "Valid range is {-1, 0, 1, 2, 3}.\n";
- });
- process_flag(
- "faster_decoding", FLAGS_faster_decoding,
- JXL_ENC_FRAME_SETTING_DECODING_SPEED, [](int32_t x) -> std::string {
- return (0 <= x && x <= 4) ? ""
- : "Valid range is {0, 1, 2, 3, 4}.\n";
- });
- process_flag("resampling", FLAGS_resampling,
- JXL_ENC_FRAME_SETTING_RESAMPLING,
- [](int32_t x) -> std::string {
- return (x == -1 || x == 1 || x == 4 || x == 8)
- ? ""
- : "Valid values are {-1, 1, 2, 4, 8}.\n";
- });
- process_flag("ec_resampling", FLAGS_ec_resampling,
- JXL_ENC_FRAME_SETTING_EXTRA_CHANNEL_RESAMPLING,
- [](int32_t x) -> std::string {
- return (x == -1 || x == 1 || x == 4 || x == 8)
- ? ""
- : "Valid values are {-1, 1, 2, 4, 8}.\n";
- });
- process_flag("photon_noise_iso", FLAGS_photon_noise_iso,
- JXL_ENC_FRAME_SETTING_PHOTON_NOISE,
- [](int32_t x) -> std::string {
- return x >= 0 ? "" : "Must be >= 0.";
- });
- process_bool_flag("already_downsampled", FLAGS_already_downsampled,
- JXL_ENC_FRAME_SETTING_ALREADY_DOWNSAMPLED);
- SetDistanceFromFlags(jxl_encoder_frame_settings, codec);
-
- if (!FLAGS_group_order &&
- (FLAGS_center_x != -1 || FLAGS_center_y != -1)) {
- std::cerr
- << "Invalid flag combination. Setting --center_x or --center_y "
- << "requires setting --group_order=1" << std::endl;
- return EXIT_FAILURE;
- }
- process_flag("center_x", FLAGS_center_x,
- JXL_ENC_FRAME_SETTING_GROUP_ORDER_CENTER_X,
- [](int32_t x) -> std::string {
- if (x < -1) {
- return "Valid values are: -1 or [0 .. xsize).";
- }
- return "";
- });
- process_flag("center_y", FLAGS_center_y,
- JXL_ENC_FRAME_SETTING_GROUP_ORDER_CENTER_Y,
- [](int32_t x) -> std::string {
- if (x < -1) {
- return "Valid values are: -1 or [0 .. ysize).";
- }
- return "";
- });
- }
- { // Progressive/responsive mode settings.
- bool qprogressive_ac_set =
- !gflags::GetCommandLineFlagInfoOrDie("qprogressive_ac").is_default;
- int32_t qprogressive_ac = FLAGS_qprogressive_ac ? 1 : 0;
- bool responsive_set =
- !gflags::GetCommandLineFlagInfoOrDie("responsive").is_default;
- int32_t responsive = FLAGS_responsive ? 1 : 0;
-
- process_flag(
- "progressive_dc", FLAGS_progressive_dc,
- JXL_ENC_FRAME_SETTING_PROGRESSIVE_DC, [](int32_t x) -> std::string {
- return (-1 <= x && x <= 2) ? "" : "Valid range is {-1, 0, 1, 2}.\n";
- });
- process_bool_flag("progressive_ac", FLAGS_progressive_ac,
- JXL_ENC_FRAME_SETTING_PROGRESSIVE_AC);
-
- if (FLAGS_progressive) {
- qprogressive_ac = 1;
- qprogressive_ac_set = true;
- responsive = 1;
- responsive_set = true;
- }
- if (responsive_set) {
- SetFlagFrameOptionOrDie("responsive", responsive,
- jxl_encoder_frame_settings,
- JXL_ENC_FRAME_SETTING_RESPONSIVE);
- }
- if (qprogressive_ac_set) {
- SetFlagFrameOptionOrDie("qprogressive_ac", qprogressive_ac,
- jxl_encoder_frame_settings,
- JXL_ENC_FRAME_SETTING_QPROGRESSIVE_AC);
- }
- }
- { // Modular mode related.
- process_flag("modular_group_size", FLAGS_modular_group_size,
- JXL_ENC_FRAME_SETTING_MODULAR_GROUP_SIZE,
- [](int32_t x) -> std::string {
- return (-1 <= x && x <= 3)
- ? ""
- : "Invalid --modular_group_size. Valid "
- "range is {-1, 0, 1, 2, 3}.\n";
- });
- process_flag("modular_predictor", FLAGS_modular_predictor,
- JXL_ENC_FRAME_SETTING_MODULAR_PREDICTOR,
- [](int32_t x) -> std::string {
- return (0 <= x && x <= 15)
- ? ""
- : "Invalid --modular_predictor. Valid "
- "range is {-1, 0, 1, ..., 15}.\n";
- });
- process_flag(
- "modular_colorspace", FLAGS_modular_colorspace,
- JXL_ENC_FRAME_SETTING_MODULAR_COLOR_SPACE,
- [](int32_t x) -> std::string {
- return (0 <= x && x <= 15)
- ? ""
- : "Invalid --modular_colorspace. Valid range is "
- "{-1, 0, 1, ..., 37}.\n";
- });
- process_flag("modular_ma_tree_learning_percent",
- FLAGS_modular_ma_tree_learning_percent,
- JXL_ENC_FRAME_SETTING_MODULAR_MA_TREE_LEARNING_PERCENT,
- [](int32_t x) -> std::string {
- return (-1 <= x && x <= 100)
- ? ""
- : "Invalid --modular_ma_tree_learning_percent. "
- "Valid range is {-1, 0, 1, ..., 100}.\n";
- });
- process_flag("modular_nb_prev_channels", FLAGS_modular_nb_prev_channels,
- JXL_ENC_FRAME_SETTING_MODULAR_NB_PREV_CHANNELS,
- [](int32_t x) -> std::string {
- return (-1 <= x && x <= 11)
- ? ""
- : "Invalid --modular_nb_prev_channels. Valid "
- "range is {-1, 0, 1, ..., 11}.\n";
- });
- process_bool_flag("modular_lossy_palette", FLAGS_modular_lossy_palette,
- JXL_ENC_FRAME_SETTING_LOSSY_PALETTE);
- process_flag("modular_palette_colors", FLAGS_modular_palette_colors,
- JXL_ENC_FRAME_SETTING_PALETTE_COLORS,
- [](int32_t x) -> std::string { return ""; });
- process_flag(
- "modular_channel_colors_global_percent",
- FLAGS_modular_channel_colors_global_percent,
- JXL_ENC_FRAME_SETTING_CHANNEL_COLORS_GLOBAL_PERCENT,
- [](int32_t x) -> std::string {
- return (-1 <= x && x <= 100)
- ? ""
- : "Invalid --modular_channel_colors_global_percent. "
- "Valid "
- "range is {-1, 0, 1, ..., 100}.\n";
- });
- process_flag(
- "modular_channel_colors_group_percent",
- FLAGS_modular_channel_colors_group_percent,
- JXL_ENC_FRAME_SETTING_CHANNEL_COLORS_GROUP_PERCENT,
- [](int32_t x) -> std::string {
- return (-1 <= x && x <= 100)
- ? ""
- : "Invalid --modular_channel_colors_group_percent. "
- "Valid "
- "range is {-1, 0, 1, ..., 100}.\n";
- });
- }
- ensure_image_loaded();
- if (FLAGS_lossless_jpeg && IsJPG(image_data)) {
- if (gflags::GetCommandLineFlagInfoOrDie("lossless_jpeg").is_default) {
- std::cerr << "Note: Implicit-default for JPEG is lossless-transcoding. "
- << "To silence this message, set --lossless_jpeg=(1|0)."
- << std::endl;
- }
- if (FLAGS_jpeg_store_metadata) {
- if (JXL_ENC_SUCCESS != JxlEncoderStoreJPEGMetadata(jxl_encoder, true)) {
- std::cerr << "Storing JPEG metadata failed. " << std::endl;
- return EXIT_FAILURE;
- }
- }
- process_bool_flag("jpeg_reconstruction_cfl",
- FLAGS_jpeg_reconstruction_cfl,
- JXL_ENC_FRAME_SETTING_JPEG_RECON_CFL);
- SetCodestreamLevel(jxl_encoder, /*for_lossless_jpeg=*/true);
- if (JXL_ENC_SUCCESS != JxlEncoderAddJPEGFrame(jxl_encoder_frame_settings,
- image_data.data(),
- image_data.size())) {
- std::cerr << "JxlEncoderAddJPEGFrame() failed." << std::endl;
- return EXIT_FAILURE;
- }
- } else { // Do JxlEncoderAddImageFrame().
- size_t num_alpha_channels = 0; // Adjusted below.
- {
- JxlBasicInfo basic_info = ppf.info;
- if (basic_info.alpha_bits > 0) num_alpha_channels = 1;
- basic_info.intensity_target =
- static_cast<float>(FLAGS_intensity_target);
- basic_info.num_extra_channels = num_alpha_channels;
- basic_info.num_color_channels = ppf.info.num_color_channels;
- basic_info.uses_original_profile = JXL_FALSE;
- if (JXL_ENC_SUCCESS !=
- JxlEncoderSetBasicInfo(jxl_encoder, &basic_info)) {
- std::cerr << "JxlEncoderSetBasicInfo() failed." << std::endl;
- return EXIT_FAILURE;
- }
- SetCodestreamLevel(jxl_encoder, /*for_lossless_jpeg=*/false);
- }
-
- if (!ppf.icc.empty()) {
- JxlEncoderSetICCProfile(jxl_encoder, ppf.icc.data(), ppf.icc.size());
- if (JXL_ENC_SUCCESS != JxlEncoderSetICCProfile(jxl_encoder,
- ppf.icc.data(),
- ppf.icc.size())) {
- std::cerr << "JxlEncoderSetICCProfile() failed." << std::endl;
- return EXIT_FAILURE;
- }
- } else {
- if (JXL_ENC_SUCCESS !=
- JxlEncoderSetColorEncoding(jxl_encoder, &ppf.color_encoding)) {
- std::cerr << "JxlEncoderSetColorEncoding() failed." << std::endl;
- return EXIT_FAILURE;
- }
- }
-
- for (size_t num_frame = 0; num_frame < ppf.frames.size(); ++num_frame) {
- const jxl::extras::PackedFrame& pframe = ppf.frames[num_frame];
- const jxl::extras::PackedImage& pimage = pframe.color;
- JxlPixelFormat ppixelformat = pimage.format;
- {
- if (JXL_ENC_SUCCESS !=
- JxlEncoderSetFrameHeader(jxl_encoder_frame_settings,
- &pframe.frame_info)) {
- std::cerr << "JxlEncoderSetFrameHeader() failed." << std::endl;
- return EXIT_FAILURE;
- }
- }
- if (num_frame < FLAGS_frame_indexing.size() &&
- FLAGS_frame_indexing[num_frame] == '1') {
- if (JXL_ENC_SUCCESS !=
- JxlEncoderFrameSettingsSetOption(jxl_encoder_frame_settings,
- JXL_ENC_FRAME_INDEX_BOX, 1)) {
- std::cerr << "Setting option JXL_ENC_FRAME_INDEX_BOX failed."
- << std::endl;
- return EXIT_FAILURE;
- }
- }
- jxl::Status enc_status(true);
- {
- if (num_alpha_channels > 0) {
- JxlExtraChannelInfo extra_channel_info;
- JxlEncoderInitExtraChannelInfo(JXL_CHANNEL_ALPHA,
- &extra_channel_info);
- enc_status = JxlEncoderSetExtraChannelInfo(jxl_encoder, 0,
- &extra_channel_info);
- if (JXL_ENC_SUCCESS != enc_status) {
- std::cerr << "JxlEncoderSetExtraChannelInfo() failed."
- << std::endl;
- return EXIT_FAILURE;
- }
- if (FLAGS_premultiply != -1) {
- if (!(FLAGS_premultiply == 0 || FLAGS_premultiply == 1)) {
- std::cerr << "Flag --premultiply must be one of: -1, 0, 1."
- << std::endl;
- return EXIT_FAILURE;
- }
- extra_channel_info.alpha_premultiplied = FLAGS_premultiply;
- }
- // We take the extra channel blend info frame_info, but don't do
- // clamping.
- JxlBlendInfo extra_channel_blend_info =
- pframe.frame_info.layer_info.blend_info;
- extra_channel_blend_info.clamp = JXL_FALSE;
- JxlEncoderSetExtraChannelBlendInfo(jxl_encoder_frame_settings, 0,
- &extra_channel_blend_info);
- }
- enc_status =
- JxlEncoderAddImageFrame(jxl_encoder_frame_settings, &ppixelformat,
- pimage.pixels(), pimage.pixels_size);
- if (JXL_ENC_SUCCESS != enc_status) {
- std::cerr << "JxlEncoderAddImageFrame() failed." << std::endl;
- return EXIT_FAILURE;
- }
- // Only set extra channel buffer if is is provided non-interleaved.
- if (!pframe.extra_channels.empty()) {
- enc_status = JxlEncoderSetExtraChannelBuffer(
- jxl_encoder_frame_settings, &ppixelformat,
- pframe.extra_channels[0].pixels(),
- pframe.extra_channels[0].stride *
- pframe.extra_channels[0].ysize,
- 0);
- if (JXL_ENC_SUCCESS != enc_status) {
- std::cerr << "JxlEncoderSetExtraChannelBuffer() failed."
- << std::endl;
- return EXIT_FAILURE;
- }
- }
- }
- }
- }
- JxlEncoderCloseInput(jxl_encoder);
- }
- // Reading compressed output
- std::vector<uint8_t> compressed;
- compressed.resize(4096);
- uint8_t* next_out = compressed.data();
- size_t avail_out = compressed.size() - (next_out - compressed.data());
- JxlEncoderStatus process_result = JXL_ENC_NEED_MORE_OUTPUT;
- while (process_result == JXL_ENC_NEED_MORE_OUTPUT) {
- process_result =
- JxlEncoderProcessOutput(jxl_encoder, &next_out, &avail_out);
- if (process_result == JXL_ENC_NEED_MORE_OUTPUT) {
- size_t offset = next_out - compressed.data();
- compressed.resize(compressed.size() * 2);
- next_out = compressed.data() + offset;
- avail_out = compressed.size() - offset;
- }
- }
- compressed.resize(next_out - compressed.data());
- if (JXL_ENC_SUCCESS != process_result) {
- std::cerr << "JxlEncoderProcessOutput failed." << std::endl;
- return EXIT_FAILURE;
- }
-
- // TODO(firsching): print info about compressed size and other image stats
- // here and in the beginning, like is done in current cjxl.
- if (!WriteFile(compressed, filename_out)) {
- std::cerr << "Could not write jxl file." << std::endl;
- return EXIT_FAILURE;
- }
- return EXIT_SUCCESS;
-}
diff --git a/media/libjxl/src/tools/cmdline.h b/media/libjxl/src/tools/cmdline.h
index ed5d847050..9b730e67e3 100644
--- a/media/libjxl/src/tools/cmdline.h
+++ b/media/libjxl/src/tools/cmdline.h
@@ -9,12 +9,11 @@
#include <stdio.h>
#include <string.h>
+#include <cstdint>
#include <memory>
#include <string>
#include <vector>
-#include "lib/jxl/base/status.h"
-
namespace jpegxl {
namespace tools {
@@ -97,7 +96,6 @@ class CommandLineParser {
}
const CmdOptionInterface* GetOption(OptionId id) const {
- JXL_ASSERT(id < options_.size());
return options_[id].get();
}
@@ -316,6 +314,82 @@ class CommandLineParser {
bool help_ = false;
};
+//
+// Common parsers for AddOptionValue and AddOptionFlag
+//
+
+static inline bool ParseSigned(const char* arg, int* out) {
+ char* end;
+ *out = static_cast<int>(strtol(arg, &end, 0));
+ if (end[0] != '\0') {
+ fprintf(stderr, "Unable to interpret as signed integer: %s.\n", arg);
+ return false;
+ }
+ return true;
+}
+
+static inline bool ParseUnsigned(const char* arg, size_t* out) {
+ char* end;
+ *out = static_cast<size_t>(strtoull(arg, &end, 0));
+ if (end[0] != '\0') {
+ fprintf(stderr, "Unable to interpret as unsigned integer: %s.\n", arg);
+ return false;
+ }
+ return true;
+}
+
+static inline bool ParseInt64(const char* arg, int64_t* out) {
+ char* end;
+ *out = strtol(arg, &end, 0);
+ if (end[0] != '\0') {
+ fprintf(stderr, "Unable to interpret as signed integer: %s.\n", arg);
+ return false;
+ }
+ return true;
+}
+
+static inline bool ParseUint32(const char* arg, uint32_t* out) {
+ size_t value = 0;
+ bool ret = ParseUnsigned(arg, &value);
+ if (ret) *out = value;
+ return ret;
+}
+
+static inline bool ParseFloat(const char* arg, float* out) {
+ char* end;
+ *out = static_cast<float>(strtod(arg, &end));
+ if (end[0] != '\0') {
+ fprintf(stderr, "Unable to interpret as float: %s.\n", arg);
+ return false;
+ }
+ return true;
+}
+
+static inline bool ParseDouble(const char* arg, double* out) {
+ char* end;
+ *out = static_cast<double>(strtod(arg, &end));
+ if (end[0] != '\0') {
+ fprintf(stderr, "Unable to interpret as double: %s.\n", arg);
+ return false;
+ }
+ return true;
+}
+
+static inline bool ParseString(const char* arg, std::string* out) {
+ out->assign(arg);
+ return true;
+}
+
+static inline bool SetBooleanTrue(bool* out) {
+ *out = true;
+ return true;
+}
+
+static inline bool SetBooleanFalse(bool* out) {
+ *out = false;
+ return true;
+}
+
} // namespace tools
} // namespace jpegxl
diff --git a/media/libjxl/src/tools/codec_config.cc b/media/libjxl/src/tools/codec_config.cc
index 9ed64a23c8..8efc26c221 100644
--- a/media/libjxl/src/tools/codec_config.cc
+++ b/media/libjxl/src/tools/codec_config.cc
@@ -7,7 +7,6 @@
#include <hwy/targets.h>
-#include "lib/jxl/base/status.h"
#include "tools/tool_version.h"
namespace jpegxl {
@@ -45,8 +44,9 @@ std::string CodecConfigString(uint32_t lib_version) {
config += ',';
saw_target = true;
}
- JXL_ASSERT(saw_target);
- (void)saw_target;
+ if (!saw_target) {
+ config += "no targets found,";
+ }
config.resize(config.size() - 1); // remove trailing comma
config += "]";
diff --git a/media/libjxl/src/tools/codec_config.h b/media/libjxl/src/tools/codec_config.h
index 729c96d4a8..a4f79a66b8 100644
--- a/media/libjxl/src/tools/codec_config.h
+++ b/media/libjxl/src/tools/codec_config.h
@@ -6,6 +6,7 @@
#ifndef TOOLS_CODEC_CONFIG_H_
#define TOOLS_CODEC_CONFIG_H_
+#include <stdint.h>
#include <string>
namespace jpegxl {
diff --git a/media/libjxl/src/tools/conformance/CMakeLists.txt b/media/libjxl/src/tools/conformance/CMakeLists.txt
index 9bc7e317f7..5766612abf 100644
--- a/media/libjxl/src/tools/conformance/CMakeLists.txt
+++ b/media/libjxl/src/tools/conformance/CMakeLists.txt
@@ -3,9 +3,6 @@
# Use of this source code is governed by a BSD-style
# license that can be found in the LICENSE file.
-add_executable(djxl_conformance djxl_conformance.cc)
-target_link_libraries(djxl_conformance jxl_dec)
-
if(BUILD_TESTING AND CMAKE_EXECUTABLE_SUFFIX STREQUAL "")
# Script to validate the tooling.
find_program (BASH_PROGRAM bash)
diff --git a/media/libjxl/src/tools/conformance/conformance.py b/media/libjxl/src/tools/conformance/conformance.py
index b012716d2e..15158bcc37 100755
--- a/media/libjxl/src/tools/conformance/conformance.py
+++ b/media/libjxl/src/tools/conformance/conformance.py
@@ -18,16 +18,14 @@ import tempfile
import lcms2
+def Failure(message):
+ print(f"\033[91m{message}\033[0m", flush=True)
+ return False
-class ConformanceTestError(Exception):
- """General conformance test error."""
-
-
-def CompareNPY(ref, ref_icc, dec, dec_icc, frame_idx, rmse, peak_error):
+def CompareNPY(ref, ref_icc, dec, dec_icc, frame_idx, rmse_limit, peak_error):
"""Compare a decoded numpy against the reference one."""
if ref.shape != dec.shape:
- raise ConformanceTestError(
- f'Expected shape {ref.shape} but found {dec.shape}')
+ return Failure(f'Expected shape {ref.shape} but found {dec.shape}')
ref_frame = ref[frame_idx]
dec_frame = dec[frame_idx]
num_channels = ref_frame.shape[2]
@@ -35,23 +33,25 @@ def CompareNPY(ref, ref_icc, dec, dec_icc, frame_idx, rmse, peak_error):
if ref_icc != dec_icc:
# Transform colors before comparison.
if num_channels < 3:
- raise ConformanceTestError(f"Only RGB images are supported")
- ref_clr = ref_frame[:, :, 0:3]
+ return Failure(f"Only RGB images are supported")
dec_clr = dec_frame[:, :, 0:3]
dec_frame[:, :, 0:3] = lcms2.convert_pixels(dec_icc, ref_icc, dec_clr)
error = numpy.abs(ref_frame - dec_frame)
- for ch in range(num_channels):
- error_ch = error[:, :, ch]
- actual_rmse = numpy.sqrt(numpy.mean(error_ch * error_ch))
- if actual_rmse > rmse:
- raise ConformanceTestError(
- f"RMSE too large: {actual_rmse} > {rmse}")
-
actual_peak_error = error.max()
+ error_by_channel = [error[:, :, ch] for ch in range(num_channels)]
+ actual_rmses = [numpy.sqrt(numpy.mean(error_ch * error_ch)) for error_ch in error_by_channel]
+ actual_rmse = max(actual_rmses)
+
+ print(f"RMSE: {actual_rmses}, peak error: {actual_peak_error}", flush=True)
+
+ if actual_rmse > rmse_limit:
+ return Failure(f"RMSE too large: {actual_rmse} > {rmse_limit}")
+
if actual_peak_error > peak_error:
- raise ConformanceTestError(
+ return Failure(
f"Peak error too large: {actual_peak_error} > {peak_error}")
+ return True
def CompareBinaries(ref_bin, dec_bin):
@@ -63,8 +63,9 @@ def CompareBinaries(ref_bin, dec_bin):
dec_data = decf.read()
if ref_data != dec_data:
- raise ConformanceTestError(
+ return Failure(
f'Binary files mismatch: {ref_bin} {dec_bin}')
+ return True
TEST_KEYS = set(
@@ -74,31 +75,33 @@ TEST_KEYS = set(
def CheckMeta(dec, ref):
if isinstance(ref, dict):
if not isinstance(dec, dict):
- raise ConformanceTestError("Malformed metadata file")
+ return Failure("Malformed metadata file")
for k, v in ref.items():
if k in TEST_KEYS:
continue
if k not in dec:
- raise ConformanceTestError(
+ return Failure(
f"Malformed metadata file: key {k} not found")
vv = dec[k]
- CheckMeta(vv, v)
+ return CheckMeta(vv, v)
elif isinstance(ref, list):
if not isinstance(dec, list) or len(dec) != len(ref):
- raise ConformanceTestError("Malformed metadata file")
+ return Failure("Malformed metadata file")
for vv, v in zip(dec, ref):
- CheckMeta(vv, v)
+ return CheckMeta(vv, v)
elif isinstance(ref, float):
if not isinstance(dec, float):
- raise ConformanceTestError("Malformed metadata file")
+ return Failure("Malformed metadata file")
if abs(dec - ref) > 0.0001:
- raise ConformanceTestError(
+ return Failure(
f"Metadata: Expected {ref}, found {dec}")
elif dec != ref:
- raise ConformanceTestError(f"Metadata: Expected {ref}, found {dec}")
+ return Failure(f"Metadata: Expected {ref}, found {dec}")
+ return True
def ConformanceTestRunner(args):
+ ok = True
# We can pass either the .txt file or the directory which defaults to the
# full corpus. This is useful to run a subset of the corpus in other .txt
# files.
@@ -112,7 +115,7 @@ def ConformanceTestRunner(args):
with open(corpus_txt, 'r') as f:
for test_id in f:
test_id = test_id.rstrip('\n')
- print('Testing %s' % test_id)
+ print(f"\033[94m\033[1mTesting {test_id}\033[0m", flush=True)
test_dir = os.path.join(corpus_dir, test_id)
with open(os.path.join(test_dir, 'test.json'), 'r') as f:
@@ -123,38 +126,53 @@ def ConformanceTestRunner(args):
exact_tests = []
with tempfile.TemporaryDirectory(prefix=test_id) as work_dir:
- cmd = [args.decoder, os.path.join(test_dir, 'input.jxl')]
- # Select the parameters to run.
+ input_filename = os.path.join(test_dir, 'input.jxl')
pixel_prefix = os.path.join(work_dir, 'decoded')
- cmd.extend(['-p', pixel_prefix])
+ output_filename = pixel_prefix + '_image.npy'
+ cmd = [args.decoder, input_filename, output_filename]
+ cmd_jpeg = []
+ if 'preview' in descriptor:
+ preview_filename = os.path.join(work_dir,
+ 'decoded_preview.npy')
+ cmd.extend(['--preview_out', preview_filename])
if 'reconstructed_jpeg' in descriptor:
jpeg_filename = os.path.join(work_dir, 'reconstructed.jpg')
- cmd.extend(['-j', jpeg_filename])
+ cmd_jpeg = [args.decoder, input_filename, jpeg_filename]
exact_tests.append(('reconstructed.jpg', jpeg_filename))
if 'original_icc' in descriptor:
decoded_original_icc = os.path.join(
work_dir, 'decoded_org.icc')
- cmd.extend(['-i', decoded_original_icc])
+ cmd.extend(['--orig_icc_out', decoded_original_icc])
exact_tests.append(('original.icc', decoded_original_icc))
meta_filename = os.path.join(work_dir, 'meta.json')
- cmd.extend(['-m', meta_filename])
+ cmd.extend(['--metadata_out', meta_filename])
+ cmd.extend(['--icc_out', pixel_prefix + '.icc'])
+ cmd.extend(['--norender_spotcolors'])
+ print(f"Running: {cmd}", flush=True)
if subprocess.call(cmd) != 0:
- raise ConformanceTestError(
- 'Running the decoder (%s) returned error' %
- ' '.join(cmd))
+ ok = Failure('Running the decoder (%s) returned error' %
+ ' '.join(cmd))
+ continue
+ if cmd_jpeg:
+ print(f"Running: {cmd_jpeg}", flush=True)
+ if subprocess.call(cmd_jpeg) != 0:
+ ok = Failure(
+ 'Running the decoder (%s) returned error' %
+ ' '.join(cmd_jpeg))
+ continue
# Run validation of exact files.
for reference_basename, decoded_filename in exact_tests:
reference_filename = os.path.join(test_dir,
reference_basename)
- CompareBinaries(reference_filename, decoded_filename)
+ ok = ok & CompareBinaries(reference_filename, decoded_filename)
# Validate metadata.
with open(meta_filename, 'r') as f:
meta = json.load(f)
- CheckMeta(meta, descriptor)
+ ok = ok & CheckMeta(meta, descriptor)
# Pixel data.
decoded_icc = pixel_prefix + '.icc'
@@ -168,16 +186,16 @@ def ConformanceTestRunner(args):
decoded_npy = os.path.join(work_dir, 'decoded_image.npy')
if not os.path.exists(decoded_npy):
- raise ConformanceTestError(
- 'File not decoded: decoded_image.npy')
+ ok = Failure('File not decoded: decoded_image.npy')
+ continue
reference_npy = numpy.load(reference_npy)
decoded_npy = numpy.load(decoded_npy)
for i, fd in enumerate(descriptor['frames']):
- CompareNPY(reference_npy, reference_icc, decoded_npy,
- decoded_icc, i, fd['rms_error'],
- fd['peak_error'])
+ ok = ok & CompareNPY(reference_npy, reference_icc, decoded_npy,
+ decoded_icc, i, fd['rms_error'],
+ fd['peak_error'])
if 'preview' in descriptor:
reference_npy = os.path.join(test_dir,
@@ -185,17 +203,17 @@ def ConformanceTestRunner(args):
decoded_npy = os.path.join(work_dir, 'decoded_preview.npy')
if not os.path.exists(decoded_npy):
- raise ConformanceTestError(
+ ok = Failure(
'File not decoded: decoded_preview.npy')
reference_npy = numpy.load(reference_npy)
decoded_npy = numpy.load(decoded_npy)
- CompareNPY(reference_npy, reference_icc, decoded_npy,
- decoded_icc, 0,
- descriptor['preview']['rms_error'],
- descriptor['preview']['peak_error'])
+ ok = ok & CompareNPY(reference_npy, reference_icc, decoded_npy,
+ decoded_icc, 0,
+ descriptor['preview']['rms_error'],
+ descriptor['preview']['peak_error'])
- return True
+ return ok
def main():
diff --git a/media/libjxl/src/tools/conformance/djxl_conformance.cc b/media/libjxl/src/tools/conformance/djxl_conformance.cc
deleted file mode 100644
index 77d0c68722..0000000000
--- a/media/libjxl/src/tools/conformance/djxl_conformance.cc
+++ /dev/null
@@ -1,669 +0,0 @@
-
-// Copyright (c) the JPEG XL Project Authors. All rights reserved.
-//
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-#include <stdint.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-
-#include <algorithm>
-#include <fstream>
-#include <iostream>
-#include <iterator>
-#include <memory>
-#include <sstream>
-#include <vector>
-
-#include "jxl/decode.h"
-#include "jxl/decode_cxx.h"
-#include "jxl/thread_parallel_runner.h"
-#include "jxl/thread_parallel_runner_cxx.h"
-
-namespace {
-
-struct DecodeOptions {
- // Path to the input .jxl file.
- const char* input = nullptr;
-
- // Prefix of the output path where to generate the pixel data or nullptr if
- // no pixel data should be save to disk.
- const char* pixel_prefix = nullptr;
-
- // Path to the original ICC profile to be generated, if requested.
- const char* icc_path = nullptr;
-
- // Path to JPEG reconstruction file to be generated, if requested.
- const char* jpeg_path = nullptr;
-
- // Path to the metadata text file to be generated, if requested.
- const char* metadata_path = nullptr;
-};
-
-bool LoadFile(const char* filename, std::vector<uint8_t>* data) {
- std::ifstream ifs(filename, std::ios::binary);
- std::vector<uint8_t> contents((std::istreambuf_iterator<char>(ifs)),
- std::istreambuf_iterator<char>());
- ifs.close();
- *data = std::move(contents);
- return ifs.good();
-}
-
-bool SaveFile(const char* filename, std::vector<uint8_t> data) {
- std::ofstream ofs(filename, std::ios::binary);
- ofs.write(reinterpret_cast<const char*>(data.data()), data.size());
- ofs.close();
- return ofs.good();
-}
-
-struct ImageArray {
- uint32_t xsize, ysize;
- // amount of color channels: 1 for grayscale, 3 for RGB
- uint32_t num_color_channels;
- // amount of extra channels, including alpha channels, spot colors, ...
- uint32_t num_extra_channels;
-
- // Both frames and ec_frames are filled in by the JXL decoder, and will be
- // converted into a numpy array of the form (frame, ysize, xsize, channel)
-
- // Array of the color channels of the frames. This is an array of frames,
- // where each frame is an array of pixels. The pixels in a frame are laid
- // out per scanline, then per channel, and finally individual pixels as
- // little endian 32-bit floating point.
- std::vector<std::vector<uint8_t>> frames;
-
- // Array of the extra channels of the frames. This is an array of frames,
- // where each frame is an array of extra channels. The pixels in an extra
- // channel are laid out per scanline, then individual pixels as
- // little endian 32-bit floating point.
- std::vector<std::vector<std::vector<uint8_t>>> ec_frames;
-};
-
-// Saves an ImageArray as a numpy 4D ndarray in binary format.
-bool SaveNPYArray(const char* filename, const ImageArray& arr) {
- size_t image_size =
- sizeof(float) * arr.xsize * arr.ysize * arr.num_color_channels;
- size_t ec_size = sizeof(float) * arr.xsize * arr.ysize;
- for (const auto& frame : arr.frames) {
- if (frame.size() != image_size) {
- fprintf(stderr, "Invalid frame size\n");
- return false;
- }
- }
- for (const auto& frame : arr.ec_frames) {
- if (frame.size() != arr.num_extra_channels) {
- fprintf(stderr, "Invalid extra channel count\n");
- return false;
- }
- for (const auto& ch : frame) {
- if (ch.size() != ec_size) {
- fprintf(stderr, "Invalid extra channel size\n");
- return false;
- }
- }
- }
-
- FILE* file = fopen(filename, "wb");
- if (!file) {
- fprintf(stderr, "Could not open %s for writing", filename);
- return false;
- }
-#define WRITE_TO_FILE(ptr, len) \
- do { \
- if (fwrite((ptr), (len), 1, file) != 1) { \
- fprintf(stderr, "Error writing " #ptr " to file %s\n", filename); \
- fclose(file); \
- return false; \
- } \
- } while (0)
-
- const uint8_t header[] = "\x93NUMPY\x01\x00";
- WRITE_TO_FILE(header, 8);
-
- {
- uint32_t num_channels = arr.num_color_channels + arr.num_extra_channels;
- std::stringstream ss;
- ss << "{'descr': '<f4', 'fortran_order': False, 'shape': ("
- << arr.frames.size() << ", " << arr.ysize << ", " << arr.xsize << ", "
- << num_channels << "), }\n";
- // 16-bit little endian header length.
- uint8_t header_len[2] = {static_cast<uint8_t>(ss.str().size() % 256),
- static_cast<uint8_t>(ss.str().size() / 256)};
- WRITE_TO_FILE(header_len, 2);
- WRITE_TO_FILE(ss.str().data(), ss.str().size());
- }
-
- // interleave the samples from color and extra channels
- for (size_t f = 0; f < arr.frames.size(); ++f) {
- size_t pos = 0;
- for (size_t y = 0; y < arr.ysize; ++y) {
- for (size_t x = 0; x < arr.xsize; ++x, pos += sizeof(float)) {
- WRITE_TO_FILE(arr.frames[f].data() + pos * arr.num_color_channels,
- arr.num_color_channels * sizeof(float));
- for (size_t i = 0; i < arr.num_extra_channels; i++) {
- WRITE_TO_FILE(arr.ec_frames[f][i].data() + pos, sizeof(float));
- }
- }
- }
- }
-
- return fclose(file) == 0;
-#undef WRITE_TO_FILE
-}
-
-// JSON value writing
-
-class JSONField {
- public:
- virtual ~JSONField() = default;
- virtual void Write(std::ostream& o, uint32_t indent) const = 0;
-
- protected:
- JSONField() = default;
-};
-
-class JSONValue : public JSONField {
- public:
- template <typename T>
- explicit JSONValue(const T& value) : value_(std::to_string(value)) {}
-
- explicit JSONValue(const std::string& value) : value_("\"" + value + "\"") {}
-
- explicit JSONValue(bool value) : value_(value ? "true" : "false") {}
-
- void Write(std::ostream& o, uint32_t indent) const override { o << value_; }
-
- private:
- std::string value_;
-};
-
-class JSONDict : public JSONField {
- public:
- JSONDict() = default;
-
- template <typename T>
- T* AddEmpty(const std::string& key) {
- static_assert(std::is_convertible<T*, JSONField*>::value,
- "T must be a JSONField");
- T* ret = new T();
- values_.emplace_back(
- key, std::unique_ptr<JSONField>(static_cast<JSONField*>(ret)));
- return ret;
- }
-
- template <typename T>
- void Add(const std::string& key, const T& value) {
- values_.emplace_back(key, std::unique_ptr<JSONField>(new JSONValue(value)));
- }
-
- void Write(std::ostream& o, uint32_t indent) const override {
- std::string indent_str(indent, ' ');
- o << "{";
- bool is_first = true;
- for (const auto& key_value : values_) {
- if (!is_first) {
- o << ",";
- }
- is_first = false;
- o << std::endl << indent_str << " \"" << key_value.first << "\": ";
- key_value.second->Write(o, indent + 2);
- }
- if (!values_.empty()) {
- o << std::endl << indent_str;
- }
- o << "}";
- }
-
- private:
- // Dictionary with order.
- std::vector<std::pair<std::string, std::unique_ptr<JSONField>>> values_;
-};
-
-class JSONArray : public JSONField {
- public:
- JSONArray() = default;
-
- template <typename T>
- T* AddEmpty() {
- static_assert(std::is_convertible<T*, JSONField*>::value,
- "T must be a JSONField");
- T* ret = new T();
- values_.emplace_back(ret);
- return ret;
- }
-
- template <typename T>
- void Add(const T& value) {
- values_.emplace_back(new JSONValue(value));
- }
-
- void Write(std::ostream& o, uint32_t indent) const override {
- std::string indent_str(indent, ' ');
- o << "[";
- bool is_first = true;
- for (const auto& value : values_) {
- if (!is_first) {
- o << ",";
- }
- is_first = false;
- o << std::endl << indent_str << " ";
- value->Write(o, indent + 2);
- }
- if (!values_.empty()) {
- o << std::endl << indent_str;
- }
- o << "]";
- }
-
- private:
- std::vector<std::unique_ptr<JSONField>> values_;
-};
-
-#define EXPECT_TRUE(X) \
- do { \
- if (!(X)) { \
- fprintf(stderr, "Failed: %s\n", #X); \
- return false; \
- } \
- } while (false)
-
-// Helper macro for decoder error checking.
-#define EXPECT_SUCCESS(X) EXPECT_TRUE((X) == JXL_DEC_SUCCESS)
-
-// TODO(veluca): merge this back in DecodeJXL once/if the API supports decoding
-// to JPEG and to pixels at the same time.
-bool DecodeJXLToJpeg(const char* input_path, const char* output_path) {
- // JPEG output buffer when reconstructing a JPEG file.
- std::vector<uint8_t> jpeg_data;
- std::vector<uint8_t> jpeg_data_chunk(16 * 1024);
- auto dec = JxlDecoderMake(nullptr);
-
- uint32_t events = JXL_DEC_JPEG_RECONSTRUCTION | JXL_DEC_FULL_IMAGE;
- EXPECT_SUCCESS(JxlDecoderSubscribeEvents(dec.get(), events));
-
- // TODO(deymo): Consider using a multi-threading decoder for conformance
- // testing as well.
-
- // Load and set input all at oncee.
- std::vector<uint8_t> jxl_input;
- EXPECT_TRUE(LoadFile(input_path, &jxl_input));
- EXPECT_SUCCESS(
- JxlDecoderSetInput(dec.get(), jxl_input.data(), jxl_input.size()));
-
- bool has_jpeg_reconstruction = false;
-
- while (true) {
- JxlDecoderStatus status = JxlDecoderProcessInput(dec.get());
- if (status == JXL_DEC_ERROR) {
- fprintf(stderr, "Error decoding.\n");
- return false;
- } else if (status == JXL_DEC_NEED_MORE_INPUT) {
- fprintf(stderr, "Error decoding: expected more input.\n");
- return false;
- } else if (status == JXL_DEC_JPEG_RECONSTRUCTION) {
- has_jpeg_reconstruction = true;
- // Decoding to JPEG.
- EXPECT_SUCCESS(JxlDecoderSetJPEGBuffer(dec.get(), jpeg_data_chunk.data(),
- jpeg_data_chunk.size()));
- } else if (status == JXL_DEC_JPEG_NEED_MORE_OUTPUT) {
- // Decoded a chunk to JPEG.
- size_t used_jpeg_output =
- jpeg_data_chunk.size() - JxlDecoderReleaseJPEGBuffer(dec.get());
- jpeg_data.insert(jpeg_data.end(), jpeg_data_chunk.data(),
- jpeg_data_chunk.data() + used_jpeg_output);
- if (used_jpeg_output == 0) {
- // Chunk is too small.
- jpeg_data_chunk.resize(jpeg_data_chunk.size() * 2);
- }
- EXPECT_SUCCESS(JxlDecoderSetJPEGBuffer(dec.get(), jpeg_data_chunk.data(),
- jpeg_data_chunk.size()));
- } else if (status == JXL_DEC_SUCCESS) {
- break;
- } else if (status == JXL_DEC_FULL_IMAGE) {
- } else if (status == JXL_DEC_NEED_IMAGE_OUT_BUFFER) {
- return true;
- } else {
- fprintf(stderr, "Error: unexpected status: %d\n",
- static_cast<int>(status));
- return false;
- }
- }
-
- if (has_jpeg_reconstruction) {
- size_t used_jpeg_output =
- jpeg_data_chunk.size() - JxlDecoderReleaseJPEGBuffer(dec.get());
- jpeg_data.insert(jpeg_data.end(), jpeg_data_chunk.data(),
- jpeg_data_chunk.data() + used_jpeg_output);
- EXPECT_TRUE(SaveFile(output_path, jpeg_data));
- }
- return true;
-}
-
-bool DecodeJXL(const DecodeOptions& opts) {
- auto dec = JxlDecoderMake(nullptr);
-
- uint32_t events =
- JXL_DEC_BASIC_INFO | JXL_DEC_COLOR_ENCODING | JXL_DEC_PREVIEW_IMAGE;
- if (opts.pixel_prefix) events |= JXL_DEC_FRAME | JXL_DEC_FULL_IMAGE;
- // We need to output the frame header info in the metadata.
- if (opts.metadata_path) events |= JXL_DEC_FRAME;
-
- if (opts.jpeg_path) {
- EXPECT_TRUE(DecodeJXLToJpeg(opts.input, opts.jpeg_path));
- }
-
- EXPECT_SUCCESS(JxlDecoderSubscribeEvents(dec.get(), events));
- EXPECT_SUCCESS(JxlDecoderSetRenderSpotcolors(dec.get(), JXL_FALSE));
-
- // TODO(deymo): Consider using a multi-threading decoder for conformance
- // testing as well.
-
- // Load and set input all at oncee.
- std::vector<uint8_t> jxl_input;
- EXPECT_TRUE(LoadFile(opts.input, &jxl_input));
- EXPECT_SUCCESS(
- JxlDecoderSetInput(dec.get(), jxl_input.data(), jxl_input.size()));
-
- JxlBasicInfo info{};
-
- // Pixel data when decoding a frame or a preview frame.
- std::vector<uint8_t> pixels;
- std::vector<uint8_t> preview_pixels;
-
- std::vector<JxlExtraChannelInfo> extra_channels;
- std::vector<std::vector<uint8_t>> extra_channel_pixels;
-
- std::vector<JxlFrameHeader> frame_headers;
- std::vector<std::string> frame_names;
-
- JxlPixelFormat format;
-
- ImageArray image, preview;
-
- while (true) {
- JxlDecoderStatus status = JxlDecoderProcessInput(dec.get());
- if (status == JXL_DEC_ERROR) {
- fprintf(stderr, "Error decoding.\n");
- return false;
- } else if (status == JXL_DEC_NEED_MORE_INPUT) {
- fprintf(stderr, "Error decoding: expected more input.\n");
- return false;
- } else if (status == JXL_DEC_BASIC_INFO) {
- // Basic info.
- EXPECT_SUCCESS(JxlDecoderGetBasicInfo(dec.get(), &info));
- extra_channels.resize(info.num_extra_channels);
- for (uint32_t i = 0; i < info.num_extra_channels; ++i) {
- EXPECT_SUCCESS(
- JxlDecoderGetExtraChannelInfo(dec.get(), i, &extra_channels[i]));
- std::vector<char> name(extra_channels[i].name_length + 1);
- EXPECT_SUCCESS(JxlDecoderGetExtraChannelName(dec.get(), i, name.data(),
- name.size()));
- }
-
- // Select the output pixel format based on the basic info.
- format = JxlPixelFormat{info.num_color_channels, JXL_TYPE_FLOAT,
- JXL_LITTLE_ENDIAN, 0};
- image.num_color_channels = info.num_color_channels;
- image.num_extra_channels = info.num_extra_channels;
- image.xsize = info.xsize;
- image.ysize = info.ysize;
-
- if (info.have_preview) {
- preview.num_color_channels = info.num_color_channels;
- preview.num_extra_channels = info.num_extra_channels;
- preview.xsize = info.preview.xsize;
- preview.ysize = info.preview.ysize;
- }
- } else if (status == JXL_DEC_COLOR_ENCODING) {
- // ICC profiles.
- if (opts.icc_path) {
- // Store the original ICC if requested.
- size_t icc_size;
- EXPECT_SUCCESS(JxlDecoderGetICCProfileSize(
- dec.get(), nullptr, JXL_COLOR_PROFILE_TARGET_ORIGINAL, &icc_size));
- std::vector<uint8_t> icc_original(icc_size);
- EXPECT_SUCCESS(JxlDecoderGetColorAsICCProfile(
- dec.get(), nullptr, JXL_COLOR_PROFILE_TARGET_ORIGINAL,
- icc_original.data(), icc_original.size()));
- EXPECT_TRUE(SaveFile(opts.icc_path, icc_original));
- }
-
- if (opts.pixel_prefix) {
- // Get the ICC color profile of the pixel data and store it.
- size_t icc_size;
- EXPECT_SUCCESS(JxlDecoderGetICCProfileSize(
- dec.get(), &format, JXL_COLOR_PROFILE_TARGET_DATA, &icc_size));
- std::vector<uint8_t> icc_data(icc_size);
- EXPECT_SUCCESS(JxlDecoderGetColorAsICCProfile(
- dec.get(), &format, JXL_COLOR_PROFILE_TARGET_DATA, icc_data.data(),
- icc_data.size()));
- std::string icc_data_filename = std::string(opts.pixel_prefix) + ".icc";
- EXPECT_TRUE(SaveFile(icc_data_filename.c_str(), icc_data));
- }
- } else if (status == JXL_DEC_FRAME) {
- // Capture the frame header information.
- JxlFrameHeader frame_header;
- EXPECT_SUCCESS(JxlDecoderGetFrameHeader(dec.get(), &frame_header));
- std::vector<char> frame_name(frame_header.name_length + 1);
- EXPECT_SUCCESS(JxlDecoderGetFrameName(dec.get(), frame_name.data(),
- frame_name.size()));
- EXPECT_TRUE(frame_name[frame_name.size() - 1] == '\0');
- frame_headers.emplace_back(frame_header);
- frame_names.emplace_back(frame_name.begin(), frame_name.end() - 1);
- } else if (status == JXL_DEC_NEED_IMAGE_OUT_BUFFER) {
- // Set pixel output buffer.
- size_t buffer_size;
- EXPECT_SUCCESS(
- JxlDecoderImageOutBufferSize(dec.get(), &format, &buffer_size));
- pixels.resize(buffer_size);
- memset(pixels.data(), 0, pixels.size());
- EXPECT_SUCCESS(JxlDecoderSetImageOutBuffer(dec.get(), &format,
- pixels.data(), pixels.size()));
- extra_channel_pixels.resize(info.num_extra_channels);
- for (uint32_t i = 0; i < info.num_extra_channels; ++i) {
- EXPECT_SUCCESS(JxlDecoderExtraChannelBufferSize(dec.get(), &format,
- &buffer_size, i));
- extra_channel_pixels[i].resize(buffer_size);
- memset(extra_channel_pixels[i].data(), 0,
- extra_channel_pixels[i].size());
- EXPECT_SUCCESS(JxlDecoderSetExtraChannelBuffer(
- dec.get(), &format, extra_channel_pixels[i].data(),
- extra_channel_pixels[i].size(), i));
- }
- } else if (status == JXL_DEC_NEED_PREVIEW_OUT_BUFFER) {
- // Set preview pixel output buffer.
- size_t buffer_size;
- EXPECT_SUCCESS(
- JxlDecoderPreviewOutBufferSize(dec.get(), &format, &buffer_size));
- preview_pixels.resize(buffer_size);
- memset(preview_pixels.data(), 0, preview_pixels.size());
- EXPECT_SUCCESS(JxlDecoderSetPreviewOutBuffer(
- dec.get(), &format, preview_pixels.data(), preview_pixels.size()));
- } else if (status == JXL_DEC_FULL_IMAGE) {
- // Pixel output buffer is set.
- if (opts.pixel_prefix) {
- image.frames.emplace_back();
- swap(image.frames.back(), pixels);
- image.ec_frames.emplace_back();
- for (uint32_t i = 0; i < info.num_extra_channels; ++i) {
- image.ec_frames.back().emplace_back();
- swap(image.ec_frames.back().back(), extra_channel_pixels[i]);
- }
- }
-
- // TODO(deymo): Get the extra channel pixel data an store it.
- } else if (status == JXL_DEC_PREVIEW_IMAGE) {
- // Preview pixel output buffer is set.
- if (opts.pixel_prefix && info.have_preview) {
- preview.frames.emplace_back();
- swap(preview.frames.back(), preview_pixels);
- }
- } else if (status == JXL_DEC_SUCCESS) {
- break;
- } else {
- fprintf(stderr, "Error: unexpected status: %d\n",
- static_cast<int>(status));
- return false;
- }
- }
-
- if (opts.pixel_prefix) {
- std::string name = std::string(opts.pixel_prefix) + "_image.npy";
- EXPECT_TRUE(SaveNPYArray(name.c_str(), image));
- }
-
- if (opts.pixel_prefix && info.have_preview) {
- std::string name = std::string(opts.pixel_prefix) + "_preview.npy";
- EXPECT_TRUE(SaveNPYArray(name.c_str(), preview));
- }
-
- if (opts.metadata_path) {
- JSONDict meta;
- // Same order as in 18181-3 CD.
-
- // Frames.
- auto* meta_frames = meta.AddEmpty<JSONArray>("frames");
- for (size_t i = 0; i < frame_headers.size(); i++) {
- auto* frame_i = meta_frames->AddEmpty<JSONDict>();
- if (info.have_animation) {
- frame_i->Add("duration", JSONValue(frame_headers[i].duration * 1.0f *
- info.animation.tps_denominator /
- info.animation.tps_numerator));
- }
-
- frame_i->Add("name", JSONValue(frame_names[i]));
-
- if (info.animation.have_timecodes) {
- frame_i->Add("timecode", JSONValue(frame_headers[i].timecode));
- }
- }
-
-#define METADATA(FIELD) meta.Add(#FIELD, info.FIELD)
-
- METADATA(intensity_target);
- METADATA(min_nits);
- METADATA(relative_to_max_display);
- METADATA(linear_below);
-
- if (info.have_preview) {
- meta.AddEmpty<JSONDict>("preview");
- // TODO(veluca): can we have duration/name/timecode here?
- }
-
- {
- auto ectype = meta.AddEmpty<JSONArray>("extra_channel_type");
- auto bps = meta.AddEmpty<JSONArray>("bits_per_sample");
- auto ebps = meta.AddEmpty<JSONArray>("exp_bits_per_sample");
- bps->Add(info.bits_per_sample);
- ebps->Add(info.exponent_bits_per_sample);
- for (size_t i = 0; i < extra_channels.size(); i++) {
- switch (extra_channels[i].type) {
- case JXL_CHANNEL_ALPHA: {
- ectype->Add(std::string("Alpha"));
- break;
- }
- case JXL_CHANNEL_DEPTH: {
- ectype->Add(std::string("Depth"));
- break;
- }
- case JXL_CHANNEL_SPOT_COLOR: {
- ectype->Add(std::string("SpotColor"));
- break;
- }
- case JXL_CHANNEL_SELECTION_MASK: {
- ectype->Add(std::string("SelectionMask"));
- break;
- }
- case JXL_CHANNEL_BLACK: {
- ectype->Add(std::string("Black"));
- break;
- }
- case JXL_CHANNEL_CFA: {
- ectype->Add(std::string("CFA"));
- break;
- }
- case JXL_CHANNEL_THERMAL: {
- ectype->Add(std::string("Thermal"));
- break;
- }
- default: {
- ectype->Add(std::string("UNKNOWN"));
- break;
- }
- }
- bps->Add(extra_channels[i].bits_per_sample);
- ebps->Add(extra_channels[i].exponent_bits_per_sample);
- }
- }
-
- std::ofstream ofs(opts.metadata_path);
- meta.Write(ofs, 0);
- ofs << std::endl;
- ofs.close();
- EXPECT_TRUE(ofs.good());
- }
- return true;
-}
-
-int Usage(const char* program) {
- fprintf(
- stderr,
- "Usage: %s INPUT_JXL [-i ORG_ICC] [-p PREFIX] [-m METADATA]\n"
- "\n"
- " INPUT_JXL: Path to the input .jxl file.\n"
- " -i ORG_ICC: Path to the output \"original\" ICC profile.\n"
- " -p PREFIX: Prefix path to generate the pixel numpy data image (with\n"
- " suffix \".npy\") and ICC profile (with suffix \".icc\"). The \n"
- " image data will be a 4D numpy array with dimensions (number of \n"
- " frames, height, width, number of channels).\n"
- " -j JPEG: Path to the output reconstructed JPEG file.\n"
- " -m METADATA: Path to the output JSON text metadata file.\n",
- program);
- return 1;
-}
-
-} // namespace
-
-// Helper macro to check that an extra argument was passed to ARG.
-#define EXPECT_ARG(ARG) \
- if (optind >= argc) { \
- fprintf(stderr, "%s needs an argument value.\n", ARG); \
- return Usage(argv[0]); \
- }
-
-int main(int argc, char* argv[]) {
- DecodeOptions opts;
-
- for (int optind = 1; optind < argc;) {
- if (!strcmp(argv[optind], "-i")) {
- optind++;
- EXPECT_ARG("-i");
- opts.icc_path = argv[optind++];
- } else if (!strcmp(argv[optind], "-p")) {
- optind++;
- EXPECT_ARG("-p");
- opts.pixel_prefix = argv[optind++];
- } else if (!strcmp(argv[optind], "-j")) {
- optind++;
- EXPECT_ARG("-j");
- opts.jpeg_path = argv[optind++];
- } else if (!strcmp(argv[optind], "-m")) {
- optind++;
- EXPECT_ARG("-m");
- opts.metadata_path = argv[optind++];
- } else if (opts.input == nullptr) {
- opts.input = argv[optind++];
- } else {
- fprintf(stderr, "Unknown parameter: \"%s\".\n", argv[optind]);
- return Usage(argv[0]);
- }
- }
- if (!opts.input) {
- fprintf(stderr, "JXL decoder for conformance testing.\n");
- return Usage(argv[0]);
- }
-
- return DecodeJXL(opts) ? 0 : 1;
-}
diff --git a/media/libjxl/src/tools/conformance/generator.py b/media/libjxl/src/tools/conformance/generator.py
index e230d48b3c..e2a9b2e66a 100755
--- a/media/libjxl/src/tools/conformance/generator.py
+++ b/media/libjxl/src/tools/conformance/generator.py
@@ -57,15 +57,14 @@ def GenerateConformanceCorpus(args):
descriptor = {}
descriptor['jxl'] = 'input.jxl'
- cmd = [args.decoder, input_file]
original_icc_filename = os.path.join(test_dir, 'original.icc')
reconstructed_filename = os.path.join(test_dir, 'reconstructed.jpg')
pixel_prefix = os.path.join(test_dir, 'reference')
- cmd.extend(['-p', pixel_prefix])
- cmd.extend(['-i', original_icc_filename])
- cmd.extend(['-j', reconstructed_filename])
+ output_file = pixel_prefix + '_image.npy'
+ cmd = [args.decoder, input_file, output_file]
metadata_filename = os.path.join(test_dir, 'test.json')
- cmd.extend(['-m', metadata_filename])
+ cmd.extend(['--metadata_out', metadata_filename])
+ cmd.extend(['--icc_out', pixel_prefix + '.icc'])
# Decode and generate the reference files.
subprocess.check_call(cmd)
diff --git a/media/libjxl/src/tools/conformance/tooling_test.sh b/media/libjxl/src/tools/conformance/tooling_test.sh
index 4366599838..95adefb1eb 100755
--- a/media/libjxl/src/tools/conformance/tooling_test.sh
+++ b/media/libjxl/src/tools/conformance/tooling_test.sh
@@ -13,7 +13,7 @@ MYDIR=$(dirname $(realpath "$0"))
if [[ $# -eq 2 ]]; then
JPEGXL_TEST_DATA_PATH="$2"
else
- JPEGXL_TEST_DATA_PATH="${MYDIR}/../../third_party/testdata"
+ JPEGXL_TEST_DATA_PATH="${MYDIR}/../../testdata"
fi
set -eux
@@ -41,7 +41,7 @@ main() {
build_dir=$(realpath "${MYDIR}/../../build")
fi
- local decoder="${build_dir}/tools/conformance/djxl_conformance"
+ local decoder="${build_dir}/tools/djxl"
"${MYDIR}/generator.py" \
--decoder="${decoder}" \
--output="${tmpdir}" \
diff --git a/media/libjxl/src/tools/djxl.cc b/media/libjxl/src/tools/djxl.cc
deleted file mode 100644
index 8487fcdf09..0000000000
--- a/media/libjxl/src/tools/djxl.cc
+++ /dev/null
@@ -1,329 +0,0 @@
-// Copyright (c) the JPEG XL Project Authors. All rights reserved.
-//
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-#include "tools/djxl.h"
-
-#include <stdio.h>
-
-#include "lib/extras/codec.h"
-#include "lib/extras/dec/color_description.h"
-#include "lib/extras/time.h"
-#include "lib/extras/tone_mapping.h"
-#include "lib/jxl/alpha.h"
-#include "lib/jxl/base/data_parallel.h"
-#include "lib/jxl/base/file_io.h"
-#include "lib/jxl/base/override.h"
-#include "lib/jxl/base/printf_macros.h"
-#include "lib/jxl/color_encoding_internal.h"
-#include "lib/jxl/color_management.h"
-#include "lib/jxl/dec_file.h"
-#include "lib/jxl/frame_header.h"
-#include "lib/jxl/image.h"
-#include "lib/jxl/image_bundle.h"
-#include "lib/jxl/image_ops.h"
-#include "lib/jxl/jpeg/dec_jpeg_data_writer.h"
-#include "tools/args.h"
-#include "tools/box/box.h"
-
-namespace jpegxl {
-namespace tools {
-
-static inline bool ParseLuminanceRange(const char* arg,
- std::pair<float, float>* out) {
- char* end;
- out->first = static_cast<float>(strtod(arg, &end));
- if (*end == '\0') {
- // That was actually the upper bound.
- out->second = out->first;
- out->first = 0;
- return true;
- }
- if (*end != '-') {
- fprintf(stderr, "Unable to interpret as luminance range: %s.\n", arg);
- return JXL_FAILURE("Args");
- }
- const char* second = end + 1;
- out->second = static_cast<float>(strtod(second, &end));
- if (*end != '\0') {
- fprintf(stderr, "Unable to interpret as luminance range: %s.\n", arg);
- return JXL_FAILURE("Args");
- }
- return true;
-}
-
-void DecompressArgs::AddCommandLineOptions(CommandLineParser* cmdline) {
- // Positional arguments.
- cmdline->AddPositionalOption("INPUT", /* required = */ true,
- "the compressed input file", &file_in);
-
- cmdline->AddPositionalOption(
- "OUTPUT", /* required = */ true,
- "the output can be PNG with ICC, JPG, or PPM/PFM.", &file_out);
-
- cmdline->AddOptionFlag('V', "version", "print version number and exit",
- &version, &SetBooleanTrue);
-
- cmdline->AddOptionValue('\0', "num_reps", "N", nullptr, &num_reps,
- &ParseUnsigned);
-
- cmdline->AddOptionValue('\0', "num_threads", "N",
- "The number of threads to use", &num_threads,
- &ParseUnsigned);
-
- cmdline->AddOptionValue('\0', "print_profile", "0|1",
- "print timing information before exiting",
- &print_profile, &ParseOverride);
-
- cmdline->AddOptionValue('\0', "bits_per_sample", "N",
- "defaults to original (input) bit depth",
- &bits_per_sample, &ParseUnsigned);
-
- cmdline->AddOptionFlag(
- '\0', "tone_map",
- "tone map the image to the luminance range indicated by --display_nits "
- "instead of performing a naive 0-1 -> 0-1 conversion",
- &tone_map, &SetBooleanTrue);
-
- cmdline->AddOptionValue('\0', "display_nits", "0.3-250",
- "luminance range of the display to which to "
- "tone-map; the lower bound can be omitted",
- &display_nits, &ParseLuminanceRange);
- cmdline->AddOptionValue(
- '\0', "preserve_saturation", "0..1",
- "with --tone_map, how much to favor saturation over luminance",
- &preserve_saturation, &ParseFloat);
-
- cmdline->AddOptionValue('\0', "color_space", "RGB_D65_SRG_Rel_Lin",
- "defaults to original (input) color space",
- &color_space, &ParseString);
-
- cmdline->AddOptionValue('s', "downsampling", "1,2,4,8,16",
- "maximum permissible downsampling factor (values "
- "greater than 16 will return the LQIP if available)",
- &params.max_downsampling, &ParseUnsigned);
-
- cmdline->AddOptionFlag('\0', "allow_partial_files",
- "allow decoding of truncated files",
- &params.allow_partial_files, &SetBooleanTrue);
-
- cmdline->AddOptionFlag('\0', "allow_more_progressive_steps",
- "allow decoding more progressive steps in truncated "
- "files. No effect without --allow_partial_files",
- &params.allow_more_progressive_steps, &SetBooleanTrue);
-
-#if JPEGXL_ENABLE_JPEG
- cmdline->AddOptionFlag(
- 'j', "pixels_to_jpeg",
- "By default, if the input JPEG XL contains a recompressed JPEG file, "
- "djxl "
- "reconstructs the exact original JPEG file. This flag causes the decoder "
- "to instead decode the image to pixels and encode a new (lossy) JPEG. "
- "The output file if provided must be a .jpg or .jpeg file.",
- &decode_to_pixels, &SetBooleanTrue);
-
- opt_jpeg_quality_id =
- cmdline->AddOptionValue('q', "jpeg_quality", "N",
- "JPEG output quality. Setting an output quality "
- "implies --pixels_to_jpeg.",
- &jpeg_quality, &ParseUnsigned);
-#endif
-
-#if JPEGXL_ENABLE_SJPEG
- cmdline->AddOptionFlag('\0', "use_sjpeg",
- "use sjpeg instead of libjpeg for JPEG output",
- &use_sjpeg, &SetBooleanTrue);
-#endif
-
- cmdline->AddOptionFlag('\0', "print_read_bytes",
- "print total number of decoded bytes",
- &print_read_bytes, &SetBooleanTrue);
-
- cmdline->AddOptionFlag('\0', "quiet", "silence output (except for errors)",
- &quiet, &SetBooleanTrue);
-}
-
-jxl::Status DecompressArgs::ValidateArgs(const CommandLineParser& cmdline) {
- if (file_in == nullptr) {
- fprintf(stderr, "Missing INPUT filename.\n");
- return false;
- }
-
-#if JPEGXL_ENABLE_JPEG
- if (cmdline.GetOption(opt_jpeg_quality_id)->matched()) {
- decode_to_pixels = true;
- }
-#endif
- if (file_out) {
- const std::string extension = jxl::Extension(file_out);
- const jxl::extras::Codec codec =
- jxl::extras::CodecFromExtension(extension, &bits_per_sample);
- if (codec != jxl::extras::Codec::kJPG) {
- // when decoding to anything-but-JPEG, we'll need pixels
- decode_to_pixels = true;
- }
- } else {
- decode_to_pixels = true;
- }
- return true;
-}
-
-jxl::Status DecompressJxlToPixels(const jxl::Span<const uint8_t> compressed,
- const jxl::DecompressParams& params,
- jxl::ThreadPool* pool,
- jxl::CodecInOut* JXL_RESTRICT io,
- SpeedStats* JXL_RESTRICT stats) {
- const double t0 = jxl::Now();
- if (!jxl::DecodeFile(params, compressed, io, pool)) {
- fprintf(stderr, "Failed to decompress to pixels.\n");
- return false;
- }
- const double t1 = jxl::Now();
- stats->NotifyElapsed(t1 - t0);
- stats->SetImageSize(io->xsize(), io->ysize());
- return true;
-}
-
-jxl::Status DecompressJxlToJPEG(const JpegXlContainer& container,
- const DecompressArgs& args,
- jxl::ThreadPool* pool, jxl::PaddedBytes* output,
- SpeedStats* JXL_RESTRICT stats) {
- output->clear();
- const double t0 = jxl::Now();
-
- jxl::Span<const uint8_t> compressed(container.codestream);
-
- JXL_RETURN_IF_ERROR(compressed.size() >= 2);
-
- // JXL case
- // Decode to DCT when possible and generate a JPG file.
- jxl::CodecInOut io;
- // Set JPEG quality.
- // TODO(deymo): We should probably fail to give a JPEG file if the
- // original image can't be transcoded to a JPEG file without passing
- // through pixels, or at least signal this to the user.
- io.use_sjpeg = args.use_sjpeg;
- io.jpeg_quality = args.jpeg_quality;
-
- if (!DecodeJpegXlToJpeg(args.params, container, &io, pool)) {
- return JXL_FAILURE("Failed to decode JXL to JPEG");
- }
- if (!jxl::jpeg::EncodeImageJPGCoefficients(&io, output)) {
- return JXL_FAILURE("Failed to generate JPEG");
- }
- stats->SetImageSize(io.xsize(), io.ysize());
-
- const double t1 = jxl::Now();
- stats->NotifyElapsed(t1 - t0);
- stats->SetFileSize(output->size());
- return true;
-}
-
-jxl::Status WriteJxlOutput(const DecompressArgs& args, const char* file_out,
- jxl::CodecInOut& io, jxl::ThreadPool* pool) {
- // Can only write if we decoded and have an output filename.
- // (Writing large PNGs is slow, so allow skipping it for benchmarks.)
- if (file_out == nullptr) return true;
-
- // Stay in original color space unless something else is needed.
- jxl::ColorEncoding c_out = io.metadata.m.color_encoding;
- // Override original color space with sRGB if input is CMYK.
- if (io.Main().HasBlack()) c_out = jxl::ColorEncoding::SRGB(false);
- // Override original color space with arg if specified.
- if (!args.color_space.empty()) {
- bool color_space_applied = false;
- JxlColorEncoding c_out_external;
- if (jxl::ParseDescription(args.color_space, &c_out_external) &&
- ConvertExternalToInternalColorEncoding(c_out_external, &c_out) &&
- c_out.CreateICC()) {
- color_space_applied = true;
- } else {
- jxl::PaddedBytes icc;
- if (jxl::ReadFile(args.color_space, &icc) &&
- c_out.SetICC(std::move(icc))) {
- color_space_applied = true;
- }
- }
-
- if (!color_space_applied) {
- fprintf(stderr, "Failed to apply color_space.\n");
- return false;
- }
- }
-
- // Override original #bits with arg if specified.
- size_t bits_per_sample = io.metadata.m.bit_depth.bits_per_sample;
- if (args.bits_per_sample != 0) bits_per_sample = args.bits_per_sample;
-
- if (args.tone_map) {
- jxl::Status status = jxl::ToneMapTo(args.display_nits, &io, pool);
- if (!status) fprintf(stderr, "Failed to map tones.\n");
- JXL_RETURN_IF_ERROR(status);
- status = jxl::GamutMap(&io, args.preserve_saturation, pool);
- if (!status) fprintf(stderr, "Failed to map gamut.\n");
- JXL_RETURN_IF_ERROR(status);
- if (c_out.tf.IsPQ() && args.color_space.empty()) {
- // Prevent writing the tone-mapped image to PQ output unless explicitly
- // requested. The result would look even dimmer than it would have without
- // tone mapping.
- c_out.tf.SetTransferFunction(jxl::TransferFunction::k709);
- status = c_out.CreateICC();
- if (!status) fprintf(stderr, "Failed to create ICC\n");
- JXL_RETURN_IF_ERROR(c_out.CreateICC());
- }
- }
-
- const char* extension = strrchr(file_out, '.');
- std::string base = extension == nullptr
- ? std::string(file_out)
- : std::string(file_out, extension - file_out);
- if (extension == nullptr) extension = "";
- const jxl::extras::Codec codec = jxl::extras::CodecFromExtension(extension);
- if (!io.metadata.m.have_animation || codec == jxl::extras::Codec::kPNG) {
- bool ok;
- if (io.Main().IsJPEG() && codec == jxl::extras::Codec::kJPG) {
- jxl::PaddedBytes encoded;
- ok = jxl::jpeg::EncodeImageJPGCoefficients(&io, &encoded) &&
- jxl::WriteFile(encoded, file_out);
- } else {
- ok = jxl::EncodeToFile(io, c_out, bits_per_sample, file_out, pool);
- }
- if (!ok) {
- fprintf(stderr, "Failed to write decoded image.\n");
- return false;
- }
- } else {
- const int digits = 1 + static_cast<int>(std::log10(std::max(
- 1, static_cast<int>(io.frames.size() - 1))));
- std::vector<char> output_filename;
- output_filename.resize(base.size() + 1 + digits + strlen(extension) + 1);
-
- for (size_t i = 0; i < io.frames.size(); ++i) {
- jxl::CodecInOut frame_io;
- frame_io.SetFromImage(jxl::CopyImage(*io.frames[i].color()),
- io.frames[i].c_current());
- frame_io.metadata.m = *io.frames[i].metadata();
- frame_io.jpeg_quality = io.jpeg_quality;
- frame_io.use_sjpeg = io.use_sjpeg;
- if (io.frames[i].HasAlpha()) {
- frame_io.Main().SetAlpha(
- jxl::CopyImage(*io.frames[i].alpha()),
- /*alpha_is_premultiplied=*/io.frames[i].AlphaIsPremultiplied());
- }
- snprintf(output_filename.data(), output_filename.size(), "%s-%0*zu%s",
- base.c_str(), digits, i, extension);
- if (!EncodeToFile(frame_io, c_out, bits_per_sample,
- output_filename.data(), pool)) {
- fprintf(stderr,
- "Failed to write decoded image for frame %" PRIuS "/%" PRIuS
- ".\n",
- i + 1, io.frames.size());
- }
- }
- }
- return true;
-}
-
-} // namespace tools
-} // namespace jpegxl
diff --git a/media/libjxl/src/tools/djxl.h b/media/libjxl/src/tools/djxl.h
deleted file mode 100644
index d091ed7a3f..0000000000
--- a/media/libjxl/src/tools/djxl.h
+++ /dev/null
@@ -1,91 +0,0 @@
-// Copyright (c) the JPEG XL Project Authors. All rights reserved.
-//
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-#ifndef TOOLS_DJXL_H_
-#define TOOLS_DJXL_H_
-
-#include <stddef.h>
-
-#include <thread>
-
-#include "jxl/decode.h"
-#include "lib/jxl/aux_out.h"
-#include "lib/jxl/base/data_parallel.h"
-#include "lib/jxl/base/override.h"
-#include "lib/jxl/base/padded_bytes.h"
-#include "lib/jxl/base/status.h"
-#include "lib/jxl/codec_in_out.h"
-#include "lib/jxl/dec_params.h"
-#include "tools/args.h"
-#include "tools/box/box.h"
-#include "tools/cmdline.h"
-#include "tools/speed_stats.h"
-
-namespace jpegxl {
-namespace tools {
-
-// Common JPEG XL decompress arguments.
-struct DecompressArgs {
- // Initialize non-static default options.
- DecompressArgs() = default;
-
- // Add all the command line options to the CommandLineParser. Note that the
- // options are tied to the instance that this was called on.
- void AddCommandLineOptions(CommandLineParser* cmdline);
-
- // Validate the passed arguments, checking whether all passed options are
- // compatible. Returns whether the validation was successful.
- jxl::Status ValidateArgs(const CommandLineParser& cmdline);
-
- // Common djxl parameters.
- const char* file_in = nullptr;
- const char* file_out = nullptr;
- size_t num_threads = std::thread::hardware_concurrency();
- bool use_sjpeg = false;
- size_t jpeg_quality = 95;
- bool decode_to_pixels = false;
- bool version = false;
- jxl::Override print_profile = jxl::Override::kDefault;
-
- size_t num_reps = 1;
-
- // Format parameters:
-
- size_t bits_per_sample = 0;
- bool tone_map = false;
- std::pair<float, float> display_nits = {0.f, jxl::kDefaultIntensityTarget};
- float preserve_saturation = .1f;
- std::string color_space; // description or path to ICC profile
-
- jxl::DecompressParams params;
-
- // If true, print the effective amount of bytes read from the bitstream.
- bool print_read_bytes = false;
- bool quiet = false;
-
- // References (ids) of specific options to check if they were matched.
- CommandLineParser::OptionId opt_jpeg_quality_id = -1;
-};
-
-// Decompresses and notifies SpeedStats of elapsed time.
-jxl::Status DecompressJxlToPixels(const jxl::Span<const uint8_t> compressed,
- const jxl::DecompressParams& params,
- jxl::ThreadPool* pool,
- jxl::CodecInOut* JXL_RESTRICT io,
- SpeedStats* JXL_RESTRICT stats);
-
-jxl::Status DecompressJxlToJPEG(const JpegXlContainer& container,
- const DecompressArgs& args,
- jxl::ThreadPool* pool, jxl::PaddedBytes* output,
- SpeedStats* JXL_RESTRICT stats);
-
-jxl::Status WriteJxlOutput(const DecompressArgs& args, const char* file_out,
- jxl::CodecInOut& io,
- jxl::ThreadPool* pool = nullptr);
-
-} // namespace tools
-} // namespace jpegxl
-
-#endif // TOOLS_DJXL_H_
diff --git a/media/libjxl/src/tools/djxl_fuzzer_test.cc b/media/libjxl/src/tools/djxl_fuzzer_test.cc
new file mode 100644
index 0000000000..e5b35c9cdf
--- /dev/null
+++ b/media/libjxl/src/tools/djxl_fuzzer_test.cc
@@ -0,0 +1,44 @@
+// Copyright (c) the JPEG XL Project Authors. All rights reserved.
+//
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+#include <sstream>
+#include <string>
+#include <vector>
+
+#include "gtest/gtest.h"
+#include "jxl/thread_parallel_runner.h"
+#include "jxl/thread_parallel_runner_cxx.h"
+#include "lib/jxl/test_utils.h"
+#include "lib/jxl/testdata.h"
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size);
+
+std::vector<uint64_t> AllTestIds() {
+ return {
+ 4546077333782528, 4716049045520384, 4718378999218176, 4729306868219904,
+ 4787817341911040, 4816304719134720, 4848606801166336, 4859247059402752,
+ 4887504894951424, 4984529666834432, 5014934495297536, 5112097090961408,
+ 5189497920290816, 5381727462227968, 5382562858532864, 5392074930782208,
+ 5467620336336896, 5473482434019328, 5489367788945408, 5556400888086528,
+ 5582808628723712, 5631220790198272, 5685623166468096, 5737500246671360,
+ 5785438255710208, 5800733037953024, 5849986531721216, 5858549672050688,
+ 5899664422993920, 5900921718046720, 5906295376445440, 5914266367557632,
+ 6013780411154432, 6165169006313472, 6277573962760192, 6329817929220096,
+ 6355777170833408, 6375307931680768, 6448658097242112, 6515680276512768,
+ 6569981946494976, 6735607318052864, 6737321070821376, 6748486320652288,
+ };
+}
+
+class DjxlFuzzerTest : public ::testing::TestWithParam<uint64_t> {};
+JXL_GTEST_INSTANTIATE_TEST_SUITE_P(DjxlFuzzerTestInstantiation, DjxlFuzzerTest,
+ ::testing::ValuesIn(AllTestIds()));
+TEST_P(DjxlFuzzerTest, TestOne) {
+ uint64_t id = GetParam();
+ std::ostringstream os;
+ os << "oss-fuzz/clusterfuzz-testcase-minimized-djxl_fuzzer-" << id;
+ printf("Testing %s\n", os.str().c_str());
+ const jxl::PaddedBytes input = jxl::ReadTestData(os.str());
+ LLVMFuzzerTestOneInput(input.data(), input.size());
+}
diff --git a/media/libjxl/src/tools/djxl_main.cc b/media/libjxl/src/tools/djxl_main.cc
index d1442d949c..44971c08eb 100644
--- a/media/libjxl/src/tools/djxl_main.cc
+++ b/media/libjxl/src/tools/djxl_main.cc
@@ -3,193 +3,463 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
-#include <stdint.h>
-#include <stdio.h>
-#include <string.h>
-
+#include <climits>
+#include <cstddef>
+#include <cstdint>
+#include <cstdio>
+#include <cstdlib>
+#include <cstring>
+#include <iostream>
+#include <sstream>
+#include <string>
#include <vector>
#include "jxl/decode.h"
-#include "lib/jxl/aux_out.h"
-#include "lib/jxl/base/cache_aligned.h"
-#include "lib/jxl/base/data_parallel.h"
-#include "lib/jxl/base/file_io.h"
-#include "lib/jxl/base/override.h"
-#include "lib/jxl/base/padded_bytes.h"
+#include "jxl/thread_parallel_runner.h"
+#include "jxl/thread_parallel_runner_cxx.h"
+#include "jxl/types.h"
+#include "lib/extras/dec/decode.h"
+#include "lib/extras/dec/jxl.h"
+#include "lib/extras/enc/encode.h"
+#include "lib/extras/enc/pnm.h"
+#include "lib/extras/packed_image.h"
+#include "lib/extras/time.h"
#include "lib/jxl/base/printf_macros.h"
-#include "lib/jxl/base/profiler.h"
-#include "lib/jxl/base/span.h"
-#include "lib/jxl/base/status.h"
-#include "lib/jxl/base/thread_pool_internal.h"
-#include "lib/jxl/codec_in_out.h"
-#include "tools/box/box.h"
#include "tools/cmdline.h"
#include "tools/codec_config.h"
-#include "tools/djxl.h"
+#include "tools/file_io.h"
#include "tools/speed_stats.h"
namespace jpegxl {
namespace tools {
+
+struct DecompressArgs {
+ DecompressArgs() = default;
+
+ void AddCommandLineOptions(CommandLineParser* cmdline) {
+ cmdline->AddPositionalOption("INPUT", /* required = */ true,
+ "The compressed input file.", &file_in);
+
+ cmdline->AddPositionalOption("OUTPUT", /* required = */ true,
+ "The output can be (A)PNG with ICC, JPG, or "
+ "PPM/PFM.",
+ &file_out);
+
+ cmdline->AddOptionFlag('V', "version", "Print version number and exit.",
+ &version, &SetBooleanTrue);
+
+ cmdline->AddOptionValue('\0', "num_reps", "N",
+ "Sets the number of times to decompress the image. "
+ "Used for benchmarking, the default is 1.",
+ &num_reps, &ParseUnsigned);
+
+ cmdline->AddOptionValue('\0', "num_threads", "N",
+ "Sets the number of threads to use. The default 0 "
+ "value means the machine default.",
+ &num_threads, &ParseUnsigned);
+
+ cmdline->AddOptionValue('\0', "bits_per_sample", "N",
+ "Sets the output bit depth. The default 0 value "
+ "means the original (input) bit depth.",
+ &bits_per_sample, &ParseUnsigned);
+
+ cmdline->AddOptionValue('\0', "display_nits", "N",
+ "If set to a non-zero value, tone maps the image "
+ "the given peak display luminance.",
+ &display_nits, &ParseDouble);
+
+ cmdline->AddOptionValue('\0', "color_space", "COLORSPACE_DESC",
+ "Sets the output color space of the image. This "
+ "flag has no effect if the image is not XYB "
+ "encoded.",
+ &color_space, &ParseString);
+
+ cmdline->AddOptionValue('s', "downsampling", "N",
+ "If set and the input JXL stream is progressive "
+ "and contains hints for target downsampling "
+ "ratios, the decoder will skip any progressive "
+ "passes that are not needed to produce a partially "
+ "decoded image intended for this downsampling "
+ "ratio.",
+ &downsampling, &ParseUint32);
+
+ cmdline->AddOptionFlag('\0', "allow_partial_files",
+ "Allow decoding of truncated files.",
+ &allow_partial_files, &SetBooleanTrue);
+
+#if JPEGXL_ENABLE_JPEG
+ cmdline->AddOptionFlag(
+ 'j', "pixels_to_jpeg",
+ "By default, if the input JPEG XL contains a recompressed JPEG file, "
+ "djxl reconstructs the exact original JPEG file. This flag causes the "
+ "decoder to instead decode the image to pixels and encode a new "
+ "(lossy) JPEG. The output file if provided must be a .jpg or .jpeg "
+ "file.",
+ &pixels_to_jpeg, &SetBooleanTrue);
+
+ opt_jpeg_quality_id = cmdline->AddOptionValue(
+ 'q', "jpeg_quality", "N",
+ "Sets the JPEG output quality, default is 95. Setting an output "
+ "quality implies --pixels_to_jpeg.",
+ &jpeg_quality, &ParseUnsigned);
+#endif
+
+#if JPEGXL_ENABLE_SJPEG
+ cmdline->AddOptionFlag('\0', "use_sjpeg",
+ "Use sjpeg instead of libjpeg for JPEG output.",
+ &use_sjpeg, &SetBooleanTrue);
+#endif
+
+ cmdline->AddOptionFlag('\0', "norender_spotcolors",
+ "Disables rendering spot colors.",
+ &render_spotcolors, &SetBooleanFalse);
+
+ cmdline->AddOptionValue('\0', "preview_out", "FILENAME",
+ "If specified, writes the preview image to this "
+ "file.",
+ &preview_out, &ParseString);
+
+ cmdline->AddOptionValue(
+ '\0', "icc_out", "FILENAME",
+ "If specified, writes the ICC profile of the decoded image to "
+ "this file.",
+ &icc_out, &ParseString);
+
+ cmdline->AddOptionValue(
+ '\0', "orig_icc_out", "FILENAME",
+ "If specified, writes the ICC profile of the original image to "
+ "this file. This can be different from the ICC profile of the "
+ "decoded image if --color_space was specified, or if the image "
+ "was XYB encoded and the color conversion to the original "
+ "profile was not supported by the decoder.",
+ &orig_icc_out, &ParseString);
+
+ cmdline->AddOptionValue(
+ '\0', "metadata_out", "FILENAME",
+ "If specified, writes decoded metadata info to this file in "
+ "JSON format. Used by the conformance test script",
+ &metadata_out, &ParseString);
+
+ cmdline->AddOptionFlag('\0', "print_read_bytes",
+ "Print total number of decoded bytes.",
+ &print_read_bytes, &SetBooleanTrue);
+
+ cmdline->AddOptionFlag('\0', "quiet", "Silence output (except for errors).",
+ &quiet, &SetBooleanTrue);
+ }
+
+ // Validate the passed arguments, checking whether all passed options are
+ // compatible. Returns whether the validation was successful.
+ bool ValidateArgs(const CommandLineParser& cmdline) {
+ if (file_in == nullptr) {
+ fprintf(stderr, "Missing INPUT filename.\n");
+ return false;
+ }
+ return true;
+ }
+
+ const char* file_in = nullptr;
+ const char* file_out = nullptr;
+ bool version = false;
+ size_t num_reps = 1;
+ size_t num_threads = 0;
+ size_t bits_per_sample = 0;
+ double display_nits = 0.0;
+ std::string color_space;
+ uint32_t downsampling = 0;
+ bool allow_partial_files = false;
+ bool pixels_to_jpeg = false;
+ size_t jpeg_quality = 95;
+ bool use_sjpeg = false;
+ bool render_spotcolors = true;
+ std::string preview_out;
+ std::string icc_out;
+ std::string orig_icc_out;
+ std::string metadata_out;
+ bool print_read_bytes = false;
+ bool quiet = false;
+ // References (ids) of specific options to check if they were matched.
+ CommandLineParser::OptionId opt_jpeg_quality_id = -1;
+};
+
+} // namespace tools
+} // namespace jpegxl
+
namespace {
-int DecompressMain(int argc, const char* argv[]) {
- DecompressArgs args;
- CommandLineParser cmdline;
+bool WriteOptionalOutput(const std::string& filename,
+ const std::vector<uint8_t>& bytes) {
+ if (filename.empty() || bytes.empty()) {
+ return true;
+ }
+ return jpegxl::tools::WriteFile(filename.data(), bytes);
+}
+
+std::string Filename(const std::string& base, const std::string& extension,
+ int layer_index, int frame_index, int num_layers,
+ int num_frames) {
+ auto digits = [](int n) { return 1 + static_cast<int>(std::log10(n)); };
+ std::string out = base;
+ if (num_frames > 1) {
+ std::vector<char> buf(2 + digits(num_frames));
+ snprintf(buf.data(), buf.size(), "-%0*d", digits(num_frames), frame_index);
+ out.append(buf.data());
+ }
+ if (num_layers > 1) {
+ std::vector<char> buf(4 + digits(num_layers));
+ snprintf(buf.data(), buf.size(), "-ec%0*d", digits(num_layers),
+ layer_index);
+ out.append(buf.data());
+ }
+ if (extension == ".ppm" && layer_index > 0) {
+ out.append(".pgm");
+ } else {
+ out.append(extension);
+ }
+ return out;
+}
+
+bool DecompressJxlReconstructJPEG(const jpegxl::tools::DecompressArgs& args,
+ const std::vector<uint8_t>& compressed,
+ void* runner,
+ std::vector<uint8_t>* jpeg_bytes,
+ jpegxl::tools::SpeedStats* stats) {
+ const double t0 = jxl::Now();
+ jxl::extras::PackedPixelFile ppf; // for JxlBasicInfo
+ jxl::extras::JXLDecompressParams dparams;
+ dparams.runner = JxlThreadParallelRunner;
+ dparams.runner_opaque = runner;
+ if (!jxl::extras::DecodeImageJXL(compressed.data(), compressed.size(),
+ dparams, nullptr, &ppf, jpeg_bytes)) {
+ return false;
+ }
+ const double t1 = jxl::Now();
+ if (stats) {
+ stats->NotifyElapsed(t1 - t0);
+ stats->SetImageSize(ppf.info.xsize, ppf.info.ysize);
+ stats->SetFileSize(jpeg_bytes->size());
+ }
+ return true;
+}
+
+bool DecompressJxlToPackedPixelFile(
+ const jpegxl::tools::DecompressArgs& args,
+ const std::vector<uint8_t>& compressed,
+ const std::vector<JxlPixelFormat>& accepted_formats, void* runner,
+ jxl::extras::PackedPixelFile* ppf, size_t* decoded_bytes,
+ jpegxl::tools::SpeedStats* stats) {
+ jxl::extras::JXLDecompressParams dparams;
+ dparams.max_downsampling = args.downsampling;
+ dparams.accepted_formats = accepted_formats;
+ dparams.display_nits = args.display_nits;
+ dparams.color_space = args.color_space;
+ dparams.render_spotcolors = args.render_spotcolors;
+ dparams.runner = JxlThreadParallelRunner;
+ dparams.runner_opaque = runner;
+ dparams.allow_partial_input = args.allow_partial_files;
+ const double t0 = jxl::Now();
+ if (!jxl::extras::DecodeImageJXL(compressed.data(), compressed.size(),
+ dparams, decoded_bytes, ppf)) {
+ return false;
+ }
+ const double t1 = jxl::Now();
+ if (stats) {
+ stats->NotifyElapsed(t1 - t0);
+ stats->SetImageSize(ppf->info.xsize, ppf->info.ysize);
+ }
+ return true;
+}
+
+} // namespace
+
+int main(int argc, const char* argv[]) {
+ std::string version = jpegxl::tools::CodecConfigString(JxlDecoderVersion());
+ jpegxl::tools::DecompressArgs args;
+ jpegxl::tools::CommandLineParser cmdline;
args.AddCommandLineOptions(&cmdline);
if (!cmdline.Parse(argc, argv)) {
- // ValidateArgs already printed the actual error cause.
+ // Parse already printed the actual error cause.
fprintf(stderr, "Use '%s -h' for more information\n", argv[0]);
- return 1;
+ return EXIT_FAILURE;
}
if (args.version) {
- fprintf(stdout, "djxl %s\n",
- CodecConfigString(JxlDecoderVersion()).c_str());
+ fprintf(stdout, "djxl %s\n", version.c_str());
fprintf(stdout, "Copyright (c) the JPEG XL Project\n");
- return 0;
+ return EXIT_SUCCESS;
}
if (!args.quiet) {
- fprintf(stderr, "JPEG XL decoder %s\n",
- CodecConfigString(JxlDecoderVersion()).c_str());
+ fprintf(stderr, "JPEG XL decoder %s\n", version.c_str());
}
if (cmdline.HelpFlagPassed()) {
cmdline.PrintHelp();
- return 0;
+ return EXIT_SUCCESS;
}
if (!args.ValidateArgs(cmdline)) {
// ValidateArgs already printed the actual error cause.
fprintf(stderr, "Use '%s -h' for more information\n", argv[0]);
- return 1;
+ return EXIT_FAILURE;
}
- jxl::PaddedBytes compressed;
- if (!jxl::ReadFile(args.file_in, &compressed)) {
- fprintf(stderr, "Failed to read file: %s.\n", args.file_in);
- return 1;
+ std::vector<uint8_t> compressed;
+ // Reading compressed JPEG XL input
+ if (!jpegxl::tools::ReadFile(args.file_in, &compressed)) {
+ fprintf(stderr, "couldn't load %s\n", args.file_in);
+ return EXIT_FAILURE;
}
if (!args.quiet) {
fprintf(stderr, "Read %" PRIuS " compressed bytes.\n", compressed.size());
}
- // If the file uses the box format container, unpack the boxes into
- // `container`. Otherwise, fill `container.codestream` accordingly.
- JpegXlContainer container;
- if (IsContainerHeader(compressed.data(), compressed.size())) {
- if (!DecodeJpegXlContainerOneShot(compressed.data(), compressed.size(),
- &container)) {
- fprintf(stderr, "Decoding container format failed.\n");
- return 1;
- }
- } else {
- container.codestream = std::move(compressed);
- }
-
- jxl::ThreadPoolInternal pool(args.num_threads);
- SpeedStats stats;
-
- // Quick test that this looks like a valid JXL file.
- JxlSignature signature = JxlSignatureCheck(container.codestream.data(),
- container.codestream.size());
- if (signature == JXL_SIG_NOT_ENOUGH_BYTES || signature == JXL_SIG_INVALID) {
- fprintf(stderr, "Unknown compressed image format (%u)\n", signature);
- return 1;
- }
-
if (!args.file_out && !args.quiet) {
fprintf(stderr,
"No output file specified.\n"
"Decoding will be performed, but the result will be discarded.\n");
}
- jxl::AuxOut aux_out;
-
- if (!args.decode_to_pixels) {
- args.params.keep_dct = true;
+ std::string filename_out;
+ std::string base;
+ std::string extension;
+ if (args.file_out) {
+ filename_out = std::string(args.file_out);
+ size_t pos = filename_out.find_last_of('.');
+ if (pos < filename_out.size()) {
+ base = filename_out.substr(0, pos);
+ extension = filename_out.substr(pos);
+ } else {
+ base = filename_out;
+ }
+ }
+ const jxl::extras::Codec codec = jxl::extras::CodecFromExtension(extension);
+ if (codec == jxl::extras::Codec::kEXR) {
+ std::string force_colorspace = "RGB_D65_SRG_Rel_Lin";
+ if (!args.color_space.empty() && args.color_space != force_colorspace) {
+ fprintf(stderr, "Warning: colorspace ignored for EXR output\n");
+ }
+ args.color_space = force_colorspace;
+ }
- jxl::PaddedBytes jpg_output;
- bool success = true;
- for (size_t i = 0; i < args.num_reps; ++i) {
- success = success && DecompressJxlToJPEG(container, args, &pool,
- &jpg_output, &stats);
+ jpegxl::tools::SpeedStats stats;
+ size_t num_worker_threads = JxlThreadParallelRunnerDefaultNumWorkerThreads();
+ {
+ int64_t flag_num_worker_threads = args.num_threads;
+ if (flag_num_worker_threads != 0) {
+ num_worker_threads = flag_num_worker_threads;
}
- if (!args.quiet && success) fprintf(stderr, "Reconstructed to JPEG.\n");
+ }
+ auto runner = JxlThreadParallelRunnerMake(
+ /*memory_manager=*/nullptr, num_worker_threads);
+
+ bool decode_to_pixels = (codec != jxl::extras::Codec::kJPG);
+#if JPEGXL_ENABLE_JPEG
+ if (args.pixels_to_jpeg ||
+ cmdline.GetOption(args.opt_jpeg_quality_id)->matched()) {
+ decode_to_pixels = true;
+ }
+#endif
- if (success && args.file_out != nullptr) {
- if (!jxl::WriteFile(jpg_output, args.file_out)) {
- fprintf(stderr, "Failed to write to \"%s\"\n", args.file_out);
- return 1;
+ size_t num_reps = args.num_reps;
+ if (!decode_to_pixels) {
+ std::vector<uint8_t> bytes;
+ for (size_t i = 0; i < num_reps; ++i) {
+ if (!DecompressJxlReconstructJPEG(args, compressed, runner.get(), &bytes,
+ &stats)) {
+ if (bytes.empty()) {
+ if (!args.quiet) {
+ fprintf(stderr,
+ "Warning: could not decode losslessly to JPEG. Retrying "
+ "with --pixels_to_jpeg...\n");
+ }
+ decode_to_pixels = true;
+ break;
+ }
+ return EXIT_FAILURE;
}
}
- if (!success) {
- if (!args.quiet) {
- fprintf(stderr,
- "Warning: could not decode losslessly to JPEG. Retrying with "
- "--pixels_to_jpeg...\n");
+ if (!bytes.empty()) {
+ if (!args.quiet) fprintf(stderr, "Reconstructed to JPEG.\n");
+ if (!filename_out.empty() &&
+ !jpegxl::tools::WriteFile(filename_out.c_str(), bytes)) {
+ return EXIT_FAILURE;
}
- args.decode_to_pixels = true;
}
}
- if (args.decode_to_pixels) {
- args.params.keep_dct = false;
- jxl::CodecInOut io;
- auto assign = [](const uint8_t* bytes, size_t size,
- std::vector<uint8_t>& target) {
- target.assign(bytes, bytes + size);
- };
- if (container.exif_size) {
- assign(container.exif, container.exif_size, io.blobs.exif);
+ if (decode_to_pixels) {
+ std::vector<JxlPixelFormat> accepted_formats;
+ std::unique_ptr<jxl::extras::Encoder> encoder;
+ if (!filename_out.empty()) {
+ encoder = jxl::extras::Encoder::FromExtension(extension);
+ if (encoder == nullptr) {
+ fprintf(stderr, "can't decode to the file extension '%s'\n",
+ extension.c_str());
+ return EXIT_FAILURE;
+ }
+ accepted_formats = encoder->AcceptedFormats();
+ }
+ jxl::extras::PackedPixelFile ppf;
+ size_t decoded_bytes = 0;
+ for (size_t i = 0; i < num_reps; ++i) {
+ if (!DecompressJxlToPackedPixelFile(args, compressed, accepted_formats,
+ runner.get(), &ppf, &decoded_bytes,
+ &stats)) {
+ fprintf(stderr, "DecompressJxlToPackedPixelFile failed\n");
+ return EXIT_FAILURE;
+ }
}
- if (!container.xml.empty()) {
- assign(container.xml[0].first, container.xml[0].second, io.blobs.xmp);
+ if (!args.quiet) fprintf(stderr, "Decoded to pixels.\n");
+ if (args.print_read_bytes) {
+ fprintf(stderr, "Decoded bytes: %" PRIuS "\n", decoded_bytes);
}
- if (container.xml.size() > 1) {
- fprintf(stderr,
- "Warning: more than one XML box found, assuming first one is XMP "
- "and ignoring others\n");
+ if (extension == ".pfm") {
+ ppf.info.bits_per_sample = 32;
+ } else if (args.bits_per_sample > 0) {
+ ppf.info.bits_per_sample = args.bits_per_sample;
}
- // Set JPEG quality.
- // TODO(veluca): the decoder should set this value, and the argument should
- // be an override.
- // TODO(veluca): the decoder should directly produce a JPEG file, and this
- // should not be necessary.
- io.use_sjpeg = args.use_sjpeg;
- io.jpeg_quality = args.jpeg_quality;
-
- // Decode to pixels.
- for (size_t i = 0; i < args.num_reps; ++i) {
- if (!DecompressJxlToPixels(jxl::Span<const uint8_t>(container.codestream),
- args.params, &pool, &io, &stats)) {
- // Error is already reported by DecompressJxlToPixels.
- return 1;
+#if JPEGXL_ENABLE_JPEG
+ if (encoder) {
+ std::ostringstream os;
+ os << args.jpeg_quality;
+ encoder->SetOption("q", os.str());
+ }
+#endif
+#if JPEGXL_ENABLE_SJPEG
+ if (encoder && args.use_sjpeg) {
+ encoder->SetOption("jpeg_encoder", "sjpeg");
+ }
+#endif
+ jxl::extras::EncodedImage encoded_image;
+ if (encoder) {
+ if (!encoder->Encode(ppf, &encoded_image)) {
+ fprintf(stderr, "Encode failed\n");
+ return EXIT_FAILURE;
}
}
- if (!args.quiet) fprintf(stderr, "Decoded to pixels.\n");
- if (!WriteJxlOutput(args, args.file_out, io, &pool)) {
- // Error is already reported by WriteJxlOutput.
- return 1;
+ size_t nlayers = 1 + encoded_image.extra_channel_bitstreams.size();
+ size_t nframes = encoded_image.bitstreams.size();
+ for (size_t i = 0; i < nlayers; ++i) {
+ for (size_t j = 0; j < nframes; ++j) {
+ const std::vector<uint8_t>& bitstream =
+ (i == 0 ? encoded_image.bitstreams[j]
+ : encoded_image.extra_channel_bitstreams[i - 1][j]);
+ std::string fn = Filename(base, extension, i, j, nlayers, nframes);
+ if (!jpegxl::tools::WriteFile(fn.c_str(), bitstream)) {
+ return EXIT_FAILURE;
+ }
+ }
}
-
- if (args.print_read_bytes) {
- fprintf(stderr, "Decoded bytes: %" PRIuS "\n", io.Main().decoded_bytes());
+ if (!WriteOptionalOutput(args.preview_out,
+ encoded_image.preview_bitstream) ||
+ !WriteOptionalOutput(args.icc_out, ppf.icc) ||
+ !WriteOptionalOutput(args.orig_icc_out, ppf.orig_icc) ||
+ !WriteOptionalOutput(args.metadata_out, encoded_image.metadata)) {
+ return EXIT_FAILURE;
}
}
-
- if (!args.quiet) JXL_CHECK(stats.Print(pool.NumWorkerThreads()));
-
- if (args.print_profile == jxl::Override::kOn) {
- PROFILER_PRINT_RESULTS();
+ if (!args.quiet) {
+ stats.Print(num_worker_threads);
}
- if (!args.quiet) jxl::CacheAligned::PrintStats();
- return 0;
-}
-
-} // namespace
-} // namespace tools
-} // namespace jpegxl
-
-int main(int argc, const char* argv[]) {
- return jpegxl::tools::DecompressMain(argc, argv);
+ return EXIT_SUCCESS;
}
diff --git a/media/libjxl/src/tools/djxl_ng_main.cc b/media/libjxl/src/tools/djxl_ng_main.cc
deleted file mode 100644
index b2eec302a8..0000000000
--- a/media/libjxl/src/tools/djxl_ng_main.cc
+++ /dev/null
@@ -1,476 +0,0 @@
-// Copyright (c) the JPEG XL Project Authors. All rights reserved.
-//
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-#include <climits>
-#include <cstddef>
-#include <cstdint>
-#include <cstdio>
-#include <cstdlib>
-#include <cstring>
-#include <iostream>
-#include <string>
-#include <vector>
-
-#include "gflags/gflags.h"
-#include "jxl/codestream_header.h"
-#include "jxl/decode.h"
-#include "jxl/decode_cxx.h"
-#include "jxl/resizable_parallel_runner_cxx.h"
-#include "jxl/thread_parallel_runner.h"
-#include "jxl/thread_parallel_runner_cxx.h"
-#include "jxl/types.h"
-#include "lib/extras/dec/decode.h"
-#include "lib/extras/enc/pnm.h"
-#include "lib/extras/packed_image.h"
-#include "lib/jxl/base/printf_macros.h"
-#include "lib/jxl/base/status.h"
-
-DECLARE_bool(help);
-DECLARE_bool(helpshort);
-
-DEFINE_int64(num_reps, 1, "How many times to decompress.");
-
-DEFINE_int64(num_threads, 0,
- // TODO(firsching): Sync with team about changed meaning of 0 -
- // was: No multithreaded workers. Is: use default number.
- "Number of worker threads (0 == use machine default).");
-
-// TODO(firsching): wire this up.
-DEFINE_int32(bits_per_sample, 0, "0 = original (input) bit depth");
-
-// TODO(firsching): wire this up.
-DEFINE_bool(
- tone_map, true,
- "tone map the image to the luminance range indicated by --display_nits "
- "instead of performing a naive 0-1 -> 0-1 conversion");
-
-// TODO(firsching): wire this up.
-DEFINE_string(display_nits, "0.f-255.",
- "luminance range of the display to which to "
- "tone-map; the lower bound can be omitted");
-
-// TODO(firsching): wire this up.
-DEFINE_double(preserve_saturation, 0.1,
- "with --tone_map, how much to favor saturation over luminance");
-
-// TODO(firsching): wire this up; consider making empty string the default.
-DEFINE_string(color_space, "RGB_D65_SRG_Rel_Lin",
- "defaults to original (input) color space");
-
-// TODO(firsching): wire this up.
-DEFINE_uint32(downsampling, 0,
- "maximum permissible downsampling factor (values "
- "greater than 16 will return the LQIP if available");
-
-// TODO(firsching): wire this up.
-DEFINE_bool(allow_partial_files, false, "allow decoding of truncated files");
-
-// TODO(firsching): wire this up.
-DEFINE_bool(allow_more_progressive_steps, false,
- "allow decoding more progressive steps in truncated "
- "files. No effect without --allow_partial_files");
-
-#if JPEGXL_ENABLE_JPEG
-// TODO(firsching): wire this up.
-DEFINE_bool(
- pixels_to_jpeg, false,
- "By default, if the input JPEG XL contains a recompressed JPEG file, djxl "
- "reconstructs the exact original JPEG file. This flag causes the decoder "
- "to instead decode the image to pixels and encode a new (lossy) JPEG. "
- "The output file if provided must be a .jpg or .jpeg file.");
-
-// TODO(firsching): wire this up.
-DEFINE_uint32(jpeg_quality, 95,
- "JPEG output quality. Setting an output quality "
- "implies --pixels_to_jpeg.");
-#endif
-
-#if JPEGXL_ENABLE_SJPEG
-// TODO(firsching): wire this up.
-DEFINE_bool(use_sjpeg, false, "use sjpeg instead of libjpeg for JPEG output");
-#endif
-
-// TODO(firsching): wire this up.
-DEFINE_bool(print_read_bytes, false, "print total number of decoded bytes");
-
-// TODO(firsching): wire this up.
-DEFINE_bool(quiet, false, "silence output (except for errors)");
-
-bool ReadFile(const char* filename, std::vector<uint8_t>* out) {
- FILE* file = fopen(filename, "rb");
- if (!file) {
- return false;
- }
-
- if (fseek(file, 0, SEEK_END) != 0) {
- fclose(file);
- return false;
- }
-
- long size = ftell(file);
- // Avoid invalid file or directory.
- if (size >= LONG_MAX || size < 0) {
- fclose(file);
- return false;
- }
-
- if (fseek(file, 0, SEEK_SET) != 0) {
- fclose(file);
- return false;
- }
-
- out->resize(size);
- size_t readsize = fread(out->data(), 1, size, file);
- if (fclose(file) != 0) {
- return false;
- }
-
- return readsize == static_cast<size_t>(size);
-}
-
-bool WriteFile(const char* filename, const std::vector<uint8_t>& bytes) {
- FILE* file = fopen(filename, "wb");
- if (!file) {
- fprintf(stderr,
- "Could not open %s for writing\n"
- "Error: %s",
- filename, strerror(errno));
- return false;
- }
- if (fwrite(bytes.data(), 1, bytes.size(), file) != bytes.size()) {
- fprintf(stderr,
- "Could not write to file\n"
- "Error: %s",
- strerror(errno));
- return false;
- }
- if (fclose(file) != 0) {
- fprintf(stderr,
- "Could not close file\n"
- "Error: %s",
- strerror(errno));
- return false;
- }
- return true;
-}
-
-int DecompressJxlReconstructJPEG(const std::vector<uint8_t>& compressed,
- std::vector<uint8_t>& jpeg_bytes,
- JxlDecoderPtr dec,
- JxlThreadParallelRunnerPtr runner) {
- if (JXL_DEC_SUCCESS != JxlDecoderSetParallelRunner(dec.get(),
- JxlThreadParallelRunner,
- runner.get())) {
- fprintf(stderr, "JxlEncoderSetParallelRunner failed\n");
- return EXIT_FAILURE;
- }
-
- if (JXL_DEC_SUCCESS !=
- JxlDecoderSubscribeEvents(
- dec.get(), JXL_DEC_JPEG_RECONSTRUCTION | JXL_DEC_FULL_IMAGE)) {
- fprintf(stderr, "JxlDecoderSubscribeEvents failed\n");
- return EXIT_FAILURE;
- }
- bool can_reconstruct_jpeg = false;
- std::vector<uint8_t> jpeg_data_chunk(16384);
- jpeg_bytes.resize(0);
- if (JXL_DEC_SUCCESS !=
- JxlDecoderSetInput(dec.get(), compressed.data(), compressed.size())) {
- fprintf(stderr, "Decoder failed to set input\n");
- return EXIT_FAILURE;
- }
- JxlDecoderCloseInput(dec.get());
-
- for (;;) {
- JxlDecoderStatus status = JxlDecoderProcessInput(dec.get());
- if (status == JXL_DEC_ERROR) {
- fprintf(stderr, "Failed to decode image\n");
- return EXIT_FAILURE;
- } else if (status == JXL_DEC_NEED_MORE_INPUT) {
- fprintf(stderr, "Error, already provided all input\n");
- return EXIT_FAILURE;
- } else if (status == JXL_DEC_JPEG_RECONSTRUCTION) {
- can_reconstruct_jpeg = true;
- // Decoding to JPEG.
- if (JXL_DEC_SUCCESS != JxlDecoderSetJPEGBuffer(dec.get(),
- jpeg_data_chunk.data(),
- jpeg_data_chunk.size())) {
- fprintf(stderr, "Decoder failed to set JPEG Buffer\n");
- return EXIT_FAILURE;
- }
- } else if (status == JXL_DEC_JPEG_NEED_MORE_OUTPUT) {
- // Decoded a chunk to JPEG.
- size_t used_jpeg_output =
- jpeg_data_chunk.size() - JxlDecoderReleaseJPEGBuffer(dec.get());
- jpeg_bytes.insert(jpeg_bytes.end(), jpeg_data_chunk.data(),
- jpeg_data_chunk.data() + used_jpeg_output);
- if (used_jpeg_output == 0) {
- // Chunk is too small.
- jpeg_data_chunk.resize(jpeg_data_chunk.size() * 2);
- }
- if (JXL_DEC_SUCCESS != JxlDecoderSetJPEGBuffer(dec.get(),
- jpeg_data_chunk.data(),
- jpeg_data_chunk.size())) {
- fprintf(stderr, "Decoder failed to set JPEG Buffer\n");
- return EXIT_FAILURE;
- }
- } else if (status == JXL_DEC_SUCCESS) {
- // Decoding finished successfully.
- break;
- } else if (status == JXL_DEC_FULL_IMAGE) {
- } else if (status == JXL_DEC_NEED_IMAGE_OUT_BUFFER) {
- break;
- } else {
- fprintf(stderr, "Error: unexpected status: %d\n",
- static_cast<int>(status));
- return EXIT_FAILURE;
- }
- }
- if (!can_reconstruct_jpeg) return EXIT_FAILURE;
- size_t used_jpeg_output =
- jpeg_data_chunk.size() - JxlDecoderReleaseJPEGBuffer(dec.get());
- jpeg_bytes.insert(jpeg_bytes.end(), jpeg_data_chunk.data(),
- jpeg_data_chunk.data() + used_jpeg_output);
- return EXIT_SUCCESS;
-}
-
-int DecompressJxlToPackedPixelFile(const std::vector<uint8_t>& compressed,
- jxl::extras::PackedPixelFile& ppf,
- JxlPixelFormat& format, JxlDecoderPtr dec,
- JxlThreadParallelRunnerPtr runner) {
- if (JXL_DEC_SUCCESS != JxlDecoderSetParallelRunner(dec.get(),
- JxlThreadParallelRunner,
- runner.get())) {
- fprintf(stderr, "JxlEncoderSetParallelRunner failed\n");
- return EXIT_FAILURE;
- }
- if (JXL_DEC_SUCCESS !=
- JxlDecoderSubscribeEvents(dec.get(),
- JXL_DEC_BASIC_INFO | JXL_DEC_COLOR_ENCODING |
- JXL_DEC_FRAME | JXL_DEC_FULL_IMAGE)) {
- fprintf(stderr, "JxlDecoderSubscribeEvents failed\n");
- return EXIT_FAILURE;
- }
-
- // Reading compressed JPEG XL input and decoding to pixels
- if (JXL_DEC_SUCCESS !=
- JxlDecoderSetInput(dec.get(), compressed.data(), compressed.size())) {
- fprintf(stderr, "Decoder failed to set input\n");
- return EXIT_FAILURE;
- }
- // TODO(firsching): handle boxes as well (exif, iptc, jumbf and xmp).
- for (;;) {
- JxlDecoderStatus status = JxlDecoderProcessInput(dec.get());
- if (status == JXL_DEC_ERROR) {
- fprintf(stderr, "Failed to decode image\n");
- return EXIT_FAILURE;
- } else if (status == JXL_DEC_NEED_MORE_INPUT) {
- fprintf(stderr, "Error, already provided all input\n");
- return EXIT_FAILURE;
- } else if (status == JXL_DEC_BASIC_INFO) {
- if (JXL_DEC_SUCCESS != JxlDecoderGetBasicInfo(dec.get(), &ppf.info)) {
- fprintf(stderr, "JxlDecoderGetBasicInfo failed\n");
- return EXIT_FAILURE;
- }
- // Make some modifications to the format if the decoded data requires it.
- if (ppf.info.num_color_channels != format.num_channels) {
- format.num_channels = ppf.info.num_color_channels;
- }
- if (ppf.info.bits_per_sample > 8 &&
- ppf.info.exponent_bits_per_sample == 0) {
- format.data_type = JXL_TYPE_UINT16;
- }
- // TODO(firsching): handle extra channels
- } else if (status == JXL_DEC_COLOR_ENCODING) {
- size_t icc_size = 0;
- // TODO(firsching) handle other targets as well.
- JxlColorProfileTarget target = JXL_COLOR_PROFILE_TARGET_ORIGINAL;
- if (JXL_DEC_SUCCESS !=
- JxlDecoderGetICCProfileSize(dec.get(), &format, target, &icc_size)) {
- fprintf(stderr, "JxlDecoderGetICCProfileSize failed\n");
- }
- if (icc_size != 0) {
- ppf.icc.resize(icc_size);
- if (JXL_DEC_SUCCESS !=
- JxlDecoderGetColorAsICCProfile(dec.get(), &format, target,
- ppf.icc.data(), icc_size)) {
- fprintf(stderr, "JxlDecoderGetColorAsICCProfile failed\n");
- return EXIT_FAILURE;
- }
- } else {
- if (JXL_DEC_SUCCESS !=
- JxlDecoderGetColorAsEncodedProfile(dec.get(), &format, target,
- &ppf.color_encoding)) {
- fprintf(stderr, "JxlDecoderGetColorAsEncodedProfile failed\n");
- return EXIT_FAILURE;
- }
- }
- } else if (status == JXL_DEC_FRAME) {
- jxl::extras::PackedFrame frame(ppf.info.xsize, ppf.info.ysize, format);
- ppf.frames.emplace_back(std::move(frame));
- } else if (status == JXL_DEC_NEED_IMAGE_OUT_BUFFER) {
- size_t buffer_size;
- if (JXL_DEC_SUCCESS !=
- JxlDecoderImageOutBufferSize(dec.get(), &format, &buffer_size)) {
- fprintf(stderr, "JxlDecoderImageOutBufferSize failed\n");
- return EXIT_FAILURE;
- }
- if (buffer_size != ppf.frames.back().color.pixels_size) {
- fprintf(stderr, "Invalid out buffer size %" PRIuS " %" PRIuS "\n",
- buffer_size, ppf.frames.back().color.pixels_size);
- return EXIT_FAILURE;
- }
-
- auto callback = [](void* opaque, size_t x, size_t y, size_t num_pixels,
- const void* pixels) {
- jxl::extras::PackedPixelFile* ppf =
- reinterpret_cast<jxl::extras::PackedPixelFile*>(opaque);
- uint8_t* pixels_buffer =
- reinterpret_cast<uint8_t*>(ppf->frames.back().color.pixels());
- size_t sample_size = ppf->frames.back().color.format.num_channels *
- ppf->frames.back().color.BitsPerChannel(
- ppf->frames.back().color.format.data_type) /
- 8;
- // TODO(firsching): take color profile into account and transform if
- // needed here.
- memcpy(pixels_buffer +
- (ppf->frames.back().color.stride * y + sample_size * x),
- pixels, num_pixels * sample_size);
- };
- if (JXL_DEC_SUCCESS !=
- JxlDecoderSetImageOutCallback(dec.get(), &format, callback, &ppf)) {
- fprintf(stderr, "JxlDecoderSetImageOutCallback failed\n");
- return EXIT_FAILURE;
- }
- } else if (status == JXL_DEC_SUCCESS) {
- // Decoding finished successfully.
- break;
- } else if (status == JXL_DEC_FULL_IMAGE) {
- } else {
- fprintf(stderr, "Error: unexpected status: %d\n",
- static_cast<int>(status));
- return EXIT_FAILURE;
- }
- }
- return EXIT_SUCCESS;
-}
-
-int main(int argc, char** argv) {
- std::cerr << "Warning: This is work in progress, consider using djxl "
- "instead!\n";
-
- gflags::SetUsageMessage("JPEG XL decoder");
- uint32_t version = JxlDecoderVersion();
- gflags::SetVersionString(std::to_string(version / 1000000) + "." +
- std::to_string((version / 1000) % 1000) + "." +
- std::to_string(version % 1000));
- // TODO(firsching): rethink --help handling
- gflags::ParseCommandLineNonHelpFlags(&argc, &argv, /*remove_flags=*/true);
- if (FLAGS_help) {
- FLAGS_help = false;
- FLAGS_helpshort = true;
- }
- gflags::HandleCommandLineHelpFlags();
-
- if (argc != 3) {
- FLAGS_help = false;
- FLAGS_helpshort = true;
- gflags::HandleCommandLineHelpFlags();
- return EXIT_FAILURE;
- }
- const char* filename_in = argv[1];
- const char* filename_out = argv[2];
- size_t num_reps = FLAGS_num_reps;
-
- const char* extension = strrchr(filename_out, '.');
- std::string base = extension == nullptr
- ? std::string(filename_out)
- : std::string(filename_out, extension - filename_out);
- if (extension == nullptr) extension = "";
- const jxl::extras::Codec codec = jxl::extras::CodecFromExtension(extension);
-
- std::vector<uint8_t> compressed;
- // Reading compressed JPEG XL input
- if (!ReadFile(filename_in, &compressed)) {
- fprintf(stderr, "couldn't load %s\n", filename_in);
- return EXIT_FAILURE;
- }
-
- size_t num_worker_threads = JxlThreadParallelRunnerDefaultNumWorkerThreads();
- {
- int64_t flag_num_worker_threads = FLAGS_num_threads;
- if (flag_num_worker_threads != 0) {
- num_worker_threads = flag_num_worker_threads;
- }
- }
- auto dec = JxlDecoderMake(/*memory_manager=*/nullptr);
- auto runner = JxlThreadParallelRunnerMake(
- /*memory_manager=*/nullptr, num_worker_threads);
- std::vector<uint8_t> bytes;
- if (codec == jxl::extras::Codec::kJPG
-#if JPEGXL_ENABLE_JPEG
- && !FLAGS_pixels_to_jpeg
-#endif
- ) {
- std::vector<uint8_t> bytes;
- for (size_t i = 0; i < num_reps; ++i) {
- if (DecompressJxlReconstructJPEG(compressed, bytes, std::move(dec),
- std::move(runner)) != 0) {
- return EXIT_FAILURE;
- }
- }
- if (WriteFile(filename_out, bytes)) {
- return EXIT_FAILURE;
- };
- // TODO(firsching): handle non-reconstruct JPEG
- } else if (codec == jxl::extras::Codec::kPNM) {
- JxlDataType datatype = JXL_TYPE_UINT8;
- uint32_t num_channels = 3;
- if (std::string(extension) == ".pfm") {
- datatype = JXL_TYPE_FLOAT;
- }
- if (std::string(extension) == ".pgm") {
- num_channels = 1;
- }
-
- JxlPixelFormat format = {num_channels, datatype, JXL_NATIVE_ENDIAN, 0};
- jxl::extras::PackedPixelFile ppf;
- if (DecompressJxlToPackedPixelFile(compressed, ppf, format, std::move(dec),
- std::move(runner)) != 0) {
- return EXIT_FAILURE;
- }
- if (ppf.info.exponent_bits_per_sample != 0) {
- if (num_channels == 1 && ppf.info.num_color_channels == 3) {
- JXL_WARNING("For color images, the filename should end with .ppm.\n");
- }
- if (num_channels == 3 && ppf.info.num_color_channels == 1) {
- JXL_WARNING(
- "For grayscale images, the filename should end with .pgm.\n");
- }
- if (ppf.info.bits_per_sample > 16) {
- JXL_WARNING("PPM only supports up to 16 bits per sample");
- }
- }
- const int digits = 1 + static_cast<int>(std::log10(std::max(
- 1, static_cast<int>(ppf.frames.size() - 1))));
- std::vector<char> output_filename;
- output_filename.resize(base.size() + 1 + digits + strlen(extension) + 1);
- for (size_t i = 0; i < ppf.frames.size(); i++) {
- JXL_RETURN_IF_ERROR(jxl::extras::EncodeImagePNM(
- ppf, ppf.frames[i].color.BitsPerChannel(format.data_type), nullptr, i,
- &bytes));
- snprintf(output_filename.data(), output_filename.size(), "%s-%0*zu%s",
- base.c_str(), digits, i, extension);
- if (!WriteFile(
- ppf.frames.size() > 1 ? output_filename.data() : filename_out,
- bytes)) {
- return EXIT_FAILURE;
- }
- }
- } else {
- // TODO(firsching): handle other formats
- }
- return EXIT_SUCCESS;
-}
diff --git a/media/libjxl/src/tools/file_io.cc b/media/libjxl/src/tools/file_io.cc
new file mode 100644
index 0000000000..bc7f3b16fb
--- /dev/null
+++ b/media/libjxl/src/tools/file_io.cc
@@ -0,0 +1,75 @@
+// Copyright (c) the JPEG XL Project Authors. All rights reserved.
+//
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+#include "tools/file_io.h"
+
+#include <errno.h>
+#include <limits.h>
+#include <stdio.h>
+#include <string.h>
+
+namespace jpegxl {
+namespace tools {
+
+bool ReadFile(const char* filename, std::vector<uint8_t>* out) {
+ FILE* file = fopen(filename, "rb");
+ if (!file) {
+ return false;
+ }
+
+ if (fseek(file, 0, SEEK_END) != 0) {
+ fclose(file);
+ return false;
+ }
+
+ long size = ftell(file);
+ // Avoid invalid file or directory.
+ if (size >= LONG_MAX || size < 0) {
+ fclose(file);
+ return false;
+ }
+
+ if (fseek(file, 0, SEEK_SET) != 0) {
+ fclose(file);
+ return false;
+ }
+
+ out->resize(size);
+ size_t readsize = fread(out->data(), 1, size, file);
+ if (fclose(file) != 0) {
+ return false;
+ }
+
+ return readsize == static_cast<size_t>(size);
+}
+
+bool WriteFile(const char* filename, const std::vector<uint8_t>& bytes) {
+ FILE* file = fopen(filename, "wb");
+ if (!file) {
+ fprintf(stderr,
+ "Could not open %s for writing\n"
+ "Error: %s",
+ filename, strerror(errno));
+ return false;
+ }
+ if (fwrite(bytes.data(), 1, bytes.size(), file) != bytes.size()) {
+ fprintf(stderr,
+ "Could not write to file\n"
+ "Error: %s",
+ strerror(errno));
+ return false;
+ }
+ if (fclose(file) != 0) {
+ fprintf(stderr,
+ "Could not close file\n"
+ "Error: %s",
+ strerror(errno));
+ return false;
+ }
+ return true;
+}
+
+} // namespace tools
+} // namespace jpegxl
diff --git a/media/libjxl/src/tools/file_io.h b/media/libjxl/src/tools/file_io.h
new file mode 100644
index 0000000000..959b79d492
--- /dev/null
+++ b/media/libjxl/src/tools/file_io.h
@@ -0,0 +1,23 @@
+// Copyright (c) the JPEG XL Project Authors. All rights reserved.
+//
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+#ifndef TOOLS_FILE_IO_H_
+#define TOOLS_FILE_IO_H_
+
+#include <stdint.h>
+
+#include <vector>
+
+namespace jpegxl {
+namespace tools {
+
+bool ReadFile(const char* filename, std::vector<uint8_t>* out);
+
+bool WriteFile(const char* filename, const std::vector<uint8_t>& bytes);
+
+} // namespace tools
+} // namespace jpegxl
+
+#endif // TOOLS_FILE_IO_H_
diff --git a/media/libjxl/src/tools/fuzzer_corpus.cc b/media/libjxl/src/tools/fuzzer_corpus.cc
index 0d66bd816e..159256cb6f 100644
--- a/media/libjxl/src/tools/fuzzer_corpus.cc
+++ b/media/libjxl/src/tools/fuzzer_corpus.cc
@@ -23,7 +23,7 @@
#include <vector>
#if JPEGXL_ENABLE_JPEG
-#include "lib/extras/enc/jpg.h"
+#include "lib/extras/codec.h"
#endif
#include "lib/jxl/aux_out.h"
#include "lib/jxl/base/data_parallel.h"
@@ -220,8 +220,8 @@ bool GenerateFile(const char* output_dir, const ImageSpec& spec,
span, spec.width, spec.height, io.metadata.m.color_encoding,
bytes_per_pixel / bytes_per_sample,
/*alpha_is_premultiplied=*/spec.alpha_is_premultiplied,
- io.metadata.m.bit_depth.bits_per_sample, JXL_LITTLE_ENDIAN,
- false /* flipped_y */, nullptr, &ib, /*float_in=*/false, /*align=*/0));
+ io.metadata.m.bit_depth.bits_per_sample, JXL_LITTLE_ENDIAN, nullptr,
+ &ib, /*float_in=*/false, /*align=*/0));
io.frames.push_back(std::move(ib));
}
@@ -232,10 +232,12 @@ bool GenerateFile(const char* output_dir, const ImageSpec& spec,
if (spec.is_reconstructible_jpeg) {
// If this image is supposed to be a reconstructible JPEG, collect the JPEG
// metadata and encode it in the beginning of the compressed bytes.
- jxl::PaddedBytes jpeg_bytes;
- JXL_RETURN_IF_ERROR(EncodeImageJPG(
- &io, jxl::extras::JpegEncoder::kLibJpeg, /*quality=*/70,
- jxl::YCbCrChromaSubsampling(), /*pool=*/nullptr, &jpeg_bytes));
+ std::vector<uint8_t> jpeg_bytes;
+ io.jpeg_quality = 70;
+ JXL_RETURN_IF_ERROR(jxl::Encode(io, jxl::extras::Codec::kJPG,
+ io.metadata.m.color_encoding,
+ /*bits_per_sample=*/8, &jpeg_bytes,
+ /*pool=*/nullptr));
JXL_RETURN_IF_ERROR(jxl::jpeg::DecodeImageJPG(
jxl::Span<const uint8_t>(jpeg_bytes.data(), jpeg_bytes.size()), &io));
jxl::PaddedBytes jpeg_data;
diff --git a/media/libjxl/src/tools/hdr/generate_lut_template.cc b/media/libjxl/src/tools/hdr/generate_lut_template.cc
index 45fc27ab56..626d54fd2e 100644
--- a/media/libjxl/src/tools/hdr/generate_lut_template.cc
+++ b/media/libjxl/src/tools/hdr/generate_lut_template.cc
@@ -53,6 +53,7 @@ int main(int argc, const char** argv) {
"GenerateTemplate"));
jxl::CodecInOut output;
+ output.metadata.m.bit_depth.bits_per_sample = 16;
output.SetFromImage(std::move(image), jxl::ColorEncoding::SRGB());
JXL_CHECK(jxl::EncodeToFile(output, jxl::ColorEncoding::SRGB(), 16,
output_filename, &pool));
diff --git a/media/libjxl/src/tools/jxl_emcc.cc b/media/libjxl/src/tools/jxl_emcc.cc
index 1951b30a5a..c4c855a1d8 100644
--- a/media/libjxl/src/tools/jxl_emcc.cc
+++ b/media/libjxl/src/tools/jxl_emcc.cc
@@ -4,63 +4,240 @@
// license that can be found in the LICENSE file.
#include <cstring>
+#include <memory>
+#include <vector>
-#include "lib/extras/codec.h"
-#include "lib/jxl/base/span.h"
-#include "lib/jxl/codec_in_out.h"
-#include "lib/jxl/dec_file.h"
-#include "lib/jxl/enc_cache.h"
-#include "lib/jxl/enc_color_management.h"
-#include "lib/jxl/enc_file.h"
+#include "jxl/decode.h"
+#include "jxl/decode_cxx.h"
+#include "jxl/thread_parallel_runner_cxx.h"
+
+#if !defined(__wasm__)
+#include "lib/jxl/base/file_io.h"
+#endif
+
+namespace {
+
+struct DecoderInstance {
+ uint32_t width = 0;
+ uint32_t height = 0;
+ uint8_t* pixels = nullptr;
+ uint32_t color_space = 0;
+
+ size_t pixels_size = 0;
+ bool want_sdr;
+ uint32_t display_nits;
+ JxlPixelFormat format;
+ JxlDecoderPtr decoder;
+ JxlThreadParallelRunnerPtr thread_pool;
+
+ std::vector<uint8_t> tail;
+};
+
+} // namespace
extern "C" {
-/* NOTA BENE: see file history to uncover how to decode HDR JPEGs to pixels. */
+void* jxlCreateInstance(bool want_sdr, uint32_t display_nits) {
+ DecoderInstance* instance = new DecoderInstance();
+ instance->want_sdr = want_sdr;
+ instance->display_nits = display_nits;
+ JxlDataType storageFormat = want_sdr ? JXL_TYPE_UINT8 : JXL_TYPE_UINT16;
+ instance->format = {4, storageFormat, JXL_NATIVE_ENDIAN, 0};
+ instance->decoder = JxlDecoderMake(nullptr);
+
+ JxlDecoder* dec = instance->decoder.get();
+
+ auto report_error = [&](uint32_t code, const char* text) {
+ fprintf(stderr, "%s\n", text);
+ // instance->result = code;
+ return instance;
+ };
+
+ instance->thread_pool = JxlThreadParallelRunnerMake(nullptr, 4);
+ void* runner = instance->thread_pool.get();
+
+ auto status =
+ JxlDecoderSetParallelRunner(dec, JxlThreadParallelRunner, runner);
+
+ if (status != JXL_DEC_SUCCESS) {
+ return report_error(1, "JxlDecoderSetParallelRunner failed");
+ }
+
+ status = JxlDecoderSubscribeEvents(
+ dec, JXL_DEC_BASIC_INFO | JXL_DEC_COLOR_ENCODING | JXL_DEC_FULL_IMAGE |
+ JXL_DEC_FRAME_PROGRESSION);
+ if (JXL_DEC_SUCCESS != status) {
+ return report_error(2, "JxlDecoderSubscribeEvents failed");
+ }
+
+ status = JxlDecoderSetProgressiveDetail(dec, kPasses);
+ if (JXL_DEC_SUCCESS != status) {
+ return report_error(3, "JxlDecoderSetProgressiveDetail failed");
+ }
+ return instance;
+}
+
+void jxlDestroyInstance(void* opaque_instance) {
+ if (opaque_instance == nullptr) return;
+ DecoderInstance* instance =
+ reinterpret_cast<DecoderInstance*>(opaque_instance);
+ if (instance->pixels) {
+ free(instance->pixels);
+ }
+ delete instance;
+}
+
+uint32_t jxlProcessInput(void* opaque_instance, const uint8_t* input,
+ size_t input_size) {
+ if (opaque_instance == nullptr) return static_cast<uint32_t>(-1);
+ DecoderInstance* instance =
+ reinterpret_cast<DecoderInstance*>(opaque_instance);
+ JxlDecoder* dec = instance->decoder.get();
+
+ auto report_error = [&](int code, const char* text) {
+ fprintf(stderr, "%s\n", text);
+ // instance->result = code;
+ return static_cast<uint32_t>(code);
+ };
-/** Result: uint32_t 'size' followed by compressed image (JXL). */
-uint8_t* jxlCompress(const uint8_t* data, size_t size) {
- jxl::PaddedBytes compressed;
- jxl::CodecInOut io;
- jxl::extras::Codec input_codec;
- if (!jxl::SetFromBytes(jxl::Span<const uint8_t>(data, size), &io, nullptr,
- &input_codec)) {
- return nullptr;
+ std::vector<uint8_t>& tail = instance->tail;
+ if (!tail.empty()) {
+ tail.reserve(tail.size() + input_size);
+ tail.insert(tail.end(), input, input + input_size);
+ input = tail.data();
+ input_size = tail.size();
}
- jxl::CompressParams params;
- jxl::PassesEncoderState passes_encoder_state;
- if (!jxl::EncodeFile(params, &io, &passes_encoder_state, &compressed,
- jxl::GetJxlCms(), nullptr, nullptr)) {
- return nullptr;
+
+ auto status = JxlDecoderSetInput(dec, input, input_size);
+ if (JXL_DEC_SUCCESS != status) {
+ return report_error(-2, "JxlDecoderSetInput failed");
+ }
+
+ auto release_input = [&]() {
+ size_t unused_input = JxlDecoderReleaseInput(dec);
+ if (unused_input == 0) {
+ tail.clear();
+ return;
+ }
+ if (tail.empty()) {
+ tail.insert(tail.end(), input + input_size - unused_input,
+ input + input_size);
+ } else {
+ memmove(tail.data(), tail.data() + tail.size() - unused_input,
+ unused_input);
+ tail.resize(unused_input);
+ }
+ };
+
+ while (true) {
+ status = JxlDecoderProcessInput(dec);
+ if (JXL_DEC_SUCCESS == status) {
+ release_input();
+ return 0; // ¯\_(ツ)_/¯
+ } else if (JXL_DEC_FRAME_PROGRESSION == status) {
+ release_input();
+ return 1; // ready to flush; client will decide whether it is necessary
+ } else if (JXL_DEC_NEED_MORE_INPUT == status) {
+ release_input();
+ return 2;
+ } else if (JXL_DEC_FULL_IMAGE == status) {
+ release_input();
+ return 0; // final image is ready
+ } else if (JXL_DEC_BASIC_INFO == status) {
+ JxlBasicInfo info;
+ status = JxlDecoderGetBasicInfo(dec, &info);
+ if (status != JXL_DEC_SUCCESS) {
+ release_input();
+ return report_error(-4, "JxlDecoderGetBasicInfo failed");
+ }
+ instance->width = info.xsize;
+ instance->height = info.ysize;
+ status = JxlDecoderImageOutBufferSize(dec, &instance->format,
+ &instance->pixels_size);
+ if (status != JXL_DEC_SUCCESS) {
+ release_input();
+ return report_error(-6, "JxlDecoderImageOutBufferSize failed");
+ }
+ if (instance->pixels) {
+ release_input();
+ return report_error(-7, "Tried to realloc pixels");
+ }
+ instance->pixels =
+ reinterpret_cast<uint8_t*>(malloc(instance->pixels_size));
+ } else if (JXL_DEC_NEED_IMAGE_OUT_BUFFER == status) {
+ if (!instance->pixels) {
+ release_input();
+ return report_error(-8, "Out buffer not allocated");
+ }
+ status = JxlDecoderSetImageOutBuffer(
+ dec, &instance->format, instance->pixels, instance->pixels_size);
+ if (status != JXL_DEC_SUCCESS) {
+ release_input();
+ return report_error(-9, "JxlDecoderSetImageOutBuffer failed");
+ }
+ } else if (JXL_DEC_COLOR_ENCODING == status) {
+ JxlColorEncoding color_encoding;
+ color_encoding.color_space = JXL_COLOR_SPACE_RGB;
+ color_encoding.white_point = JXL_WHITE_POINT_D65;
+ color_encoding.primaries =
+ instance->want_sdr ? JXL_PRIMARIES_SRGB : JXL_PRIMARIES_2100;
+ color_encoding.transfer_function = instance->want_sdr
+ ? JXL_TRANSFER_FUNCTION_SRGB
+ : JXL_TRANSFER_FUNCTION_PQ;
+ color_encoding.rendering_intent = JXL_RENDERING_INTENT_PERCEPTUAL;
+ status = JxlDecoderSetPreferredColorProfile(dec, &color_encoding);
+ if (status != JXL_DEC_SUCCESS) {
+ release_input();
+ return report_error(-5, "JxlDecoderSetPreferredColorProfile failed");
+ }
+ } else {
+ release_input();
+ return report_error(-3, "Unexpected decoder status");
+ }
}
- size_t compressed_size = compressed.size();
- uint8_t* result = reinterpret_cast<uint8_t*>(malloc(compressed_size + 4));
- uint32_t* meta = reinterpret_cast<uint32_t*>(result);
- meta[0] = compressed_size;
- memcpy(result + 4, compressed.data(), compressed_size);
- return result;
+
+ release_input();
+ return 0;
}
-/** Result: uint32_t 'size' followed by decompressed image (JPG). */
-uint8_t* jxlDecompress(const uint8_t* data, size_t size) {
- jxl::PaddedBytes decompressed;
- jxl::CodecInOut io;
- jxl::DecompressParams params;
- if (!jxl::DecodeFile(params, jxl::Span<const uint8_t>(data, size), &io,
- nullptr)) {
- return nullptr;
+uint32_t jxlFlush(void* opaque_instance) {
+ if (opaque_instance == nullptr) return static_cast<uint32_t>(-1);
+ DecoderInstance* instance =
+ reinterpret_cast<DecoderInstance*>(opaque_instance);
+ JxlDecoder* dec = instance->decoder.get();
+
+ auto report_error = [&](int code, const char* text) {
+ fprintf(stderr, "%s\n", text);
+ // instance->result = code;
+ return static_cast<uint32_t>(code);
+ };
+
+ if (!instance->pixels) {
+ return report_error(-2, "Not ready to flush");
}
- io.use_sjpeg = false;
- io.jpeg_quality = 100;
- if (!jxl::Encode(io, jxl::extras::Codec::kJPG, io.Main().c_current(), 8,
- &decompressed, nullptr)) {
- return nullptr;
+
+ auto status = JxlDecoderFlushImage(dec);
+ if (status != JXL_DEC_SUCCESS) {
+ return report_error(-3, "Failed to flush");
}
- size_t decompressed_size = decompressed.size();
- uint8_t* result = reinterpret_cast<uint8_t*>(malloc(decompressed_size + 4));
- uint32_t* meta = reinterpret_cast<uint32_t*>(result);
- meta[0] = decompressed_size;
- memcpy(result + 4, decompressed.data(), decompressed_size);
- return result;
+
+ return 0;
+}
+
+#if !defined(__wasm__)
+int main(int argc, char* argv[]) {
+ std::vector<uint8_t> data;
+ JXL_RETURN_IF_ERROR(jxl::ReadFile(argv[1], &data));
+ fprintf(stderr, "File size: %d\n", (int)data.size());
+
+ void* instance = jxlCreateInstance(true, 100);
+ uint32_t status = jxlProcessInput(instance, data.data(), data.size());
+ fprintf(stderr, "Process result: %d\n", status);
+ jxlFlush(instance);
+ status = jxlProcessInput(instance, nullptr, 0);
+ fprintf(stderr, "Process result: %d\n", status);
+ jxlDestroyInstance(instance);
}
+#endif
} // extern "C"
diff --git a/media/libjxl/src/tools/jxlinfo.c b/media/libjxl/src/tools/jxlinfo.c
index 0cced1740c..d8d67e7fad 100644
--- a/media/libjxl/src/tools/jxlinfo.c
+++ b/media/libjxl/src/tools/jxlinfo.c
@@ -91,9 +91,13 @@ int PrintBasicInfo(FILE* file, int verbose) {
printf("float (%d exponent bits) ", info.exponent_bits_per_sample);
}
int cmyk = 0, alpha = 0;
- const char* const ec_type_names[7] = {"Alpha", "Depth", "Spotcolor",
- "Selection", "Black", "CFA",
- "Thermal"};
+ const char* const ec_type_names[] = {
+ "Alpha", "Depth", "Spotcolor", "Selection", "Black",
+ "CFA", "Thermal", "Reserved0", "Reserved1", "Reserved2",
+ "Reserved3", "Reserved4", "Reserved5", "Reserved6", "Reserved7",
+ "Unknown", "Optional"};
+ const size_t ec_type_names_size =
+ sizeof(ec_type_names) / sizeof(ec_type_names[0]);
for (uint32_t i = 0; i < info.num_extra_channels; i++) {
JxlExtraChannelInfo extra;
if (JXL_DEC_SUCCESS != JxlDecoderGetExtraChannelInfo(dec, i, &extra)) {
@@ -131,10 +135,9 @@ int PrintBasicInfo(FILE* file, int verbose) {
continue;
}
- printf("+%s", (extra.type < 7 ? ec_type_names[extra.type]
- : (extra.type == JXL_CHANNEL_OPTIONAL
- ? "UnknownOptional"
- : "Unknown(OUTDATED libjxl!)")));
+ printf("+%s", (extra.type < ec_type_names_size
+ ? ec_type_names[extra.type]
+ : "Unknown, please update your libjxl"));
}
printf("\n");
if (verbose) {
@@ -149,12 +152,9 @@ int PrintBasicInfo(FILE* file, int verbose) {
break;
}
printf("extra channel %u:\n", i);
- printf(
- " type: %s\n",
- (extra.type < 7 ? ec_type_names[extra.type]
- : (extra.type == JXL_CHANNEL_OPTIONAL
- ? "Unknown but can be ignored"
- : "Unknown, please update your libjxl")));
+ printf(" type: %s\n", (extra.type < ec_type_names_size
+ ? ec_type_names[extra.type]
+ : "Unknown, please update your libjxl"));
printf(" bits_per_sample: %u\n", extra.bits_per_sample);
if (extra.exponent_bits_per_sample > 0) {
printf(" float, with exponent_bits_per_sample: %u\n",
diff --git a/media/libjxl/src/tools/reference_zip.sh b/media/libjxl/src/tools/reference_zip.sh
index 6a87344d2d..6a284b43f7 100755
--- a/media/libjxl/src/tools/reference_zip.sh
+++ b/media/libjxl/src/tools/reference_zip.sh
@@ -61,8 +61,8 @@ cd build
cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTING=OFF -DJPEGXL_ENABLE_SJPEG=OFF ..
cmake --build . -- -j\$(nproc)
-tools/djxl ../third_party/testdata/jxl/blending/cropped_traffic_light.jxl test.png
-tools/cjxl ../third_party/testdata/third_party/imagecompression.info/flower_foveon.png.im_q85_444.jpg test.jxl
+tools/djxl ../testdata/jxl/blending/cropped_traffic_light.jxl test.png
+tools/cjxl ../testdata/jxl/flower/flower.png.im_q85_444.jpg test.jxl
tools/djxl test.jxl test.jpg
EOF
set +x
diff --git a/media/libjxl/src/tools/roundtrip_test.sh b/media/libjxl/src/tools/roundtrip_test.sh
new file mode 100644
index 0000000000..46b775645a
--- /dev/null
+++ b/media/libjxl/src/tools/roundtrip_test.sh
@@ -0,0 +1,91 @@
+#!/bin/bash
+# Copyright (c) the JPEG XL Project Authors. All rights reserved.
+#
+# Use of this source code is governed by a BSD-style
+# license that can be found in the LICENSE file.
+
+# End-to-end roundtrip tests for cjxl and djxl tools.
+
+MYDIR=$(dirname $(realpath "$0"))
+JPEGXL_TEST_DATA_PATH="${MYDIR}/../testdata"
+
+set -eux
+
+EMULATOR=${EMULATOR:-}
+
+# Temporary files cleanup hooks.
+CLEANUP_FILES=()
+cleanup() {
+ if [[ ${#CLEANUP_FILES[@]} -ne 0 ]]; then
+ rm -rf "${CLEANUP_FILES[@]}"
+ fi
+}
+trap 'retcode=$?; { set +x; } 2>/dev/null; cleanup' INT TERM EXIT
+
+roundtrip_test() {
+ local infn="${JPEGXL_TEST_DATA_PATH}/$1"
+ local encargs="$2"
+ local maxdist="$3"
+
+ local encoder="${EMULATOR} ${build_dir}/tools/cjxl"
+ local decoder="${EMULATOR} ${build_dir}/tools/djxl"
+ local comparator="${EMULATOR} ${build_dir}/tools/ssimulacra_main"
+ local jxlfn="$(mktemp -p "$tmpdir")"
+
+ "${encoder}" "${infn}" "${jxlfn}" $encargs
+
+ if [ "${infn: -3}" == "jpg" ]; then
+ local outfn="$(mktemp -p "$tmpdir").jpg"
+
+ # Test losless jpeg reconstruction.
+ "${decoder}" "${jxlfn}" "${outfn}" --num_reps 2
+ diff "${infn}" "${outfn}"
+
+ # Test decoding to pixels.
+ "${decoder}" "${jxlfn}" "${outfn}" --num_reps 2 --pixels_to_jpeg
+ local dist="$("${comparator}" "${infn}" "${outfn}")"
+ python3 -c "import sys; sys.exit(not ${dist} > 0.0)"
+ python3 -c "import sys; sys.exit(not ${dist} < 0.005)"
+
+ # Test decoding to pixels by setting the --jpeg_quality flag.
+ "${decoder}" "${jxlfn}" "${outfn}" --num_reps 2 --jpeg_quality 100
+ local dist="$("${comparator}" "${infn}" "${outfn}")"
+ python3 -c "import sys; sys.exit(not ${dist} > 0.0)"
+ python3 -c "import sys; sys.exit(not ${dist} < 0.005)"
+
+ # Test decoding to pixels by writing to a png.
+ outfn="$(mktemp -p "$tmpdir").png"
+ "${decoder}" "${jxlfn}" "${outfn}" --num_reps 2
+ local dist="$("${comparator}" "${infn}" "${outfn}")"
+ python3 -c "import sys; sys.exit(not ${dist} > 0.0)"
+ python3 -c "import sys; sys.exit(not ${dist} < 0.005)"
+ else
+ # Test decoding to png.
+ local outfn="$(mktemp -p "$tmpdir").png"
+ "${decoder}" "${jxlfn}" "${outfn}" --num_reps 2
+ local dist="$("${comparator}" "${infn}" "${outfn}")"
+ python3 -c "import sys; sys.exit(not ${dist} <= ${maxdist})"
+
+ # Test decoding to jpg.
+ outfn="$(mktemp -p "$tmpdir").jpg"
+ "${decoder}" "${jxlfn}" "${outfn}" --num_reps 2
+ local dist="$("${comparator}" "${infn}" "${outfn}")"
+ python3 -c "import sys; sys.exit(not ${dist} <= ${maxdist} + 0.05)"
+ fi
+}
+
+main() {
+ local tmpdir=$(mktemp -d)
+ CLEANUP_FILES+=("${tmpdir}")
+
+ local build_dir="${1:-}"
+ if [[ -z "${build_dir}" ]]; then
+ build_dir=$(realpath "${MYDIR}/../../build")
+ fi
+
+ roundtrip_test "jxl/flower/flower.png" "-e 1" 0.02
+ roundtrip_test "jxl/flower/flower.png" "-e 1 -d 0.0" 0.0
+ roundtrip_test "jxl/flower/flower_cropped.jpg" "-e 1" 0.0
+}
+
+main "$@"
diff --git a/media/libjxl/src/tools/speed_stats.cc b/media/libjxl/src/tools/speed_stats.cc
index 3ab271f964..cdef814df4 100644
--- a/media/libjxl/src/tools/speed_stats.cc
+++ b/media/libjxl/src/tools/speed_stats.cc
@@ -17,12 +17,13 @@ namespace jpegxl {
namespace tools {
void SpeedStats::NotifyElapsed(double elapsed_seconds) {
- JXL_ASSERT(elapsed_seconds > 0.0);
- elapsed_.push_back(elapsed_seconds);
+ if (elapsed_seconds > 0.0) {
+ elapsed_.push_back(elapsed_seconds);
+ }
}
-jxl::Status SpeedStats::GetSummary(SpeedStats::Summary* s) {
- if (elapsed_.empty()) return JXL_FAILURE("Didn't call NotifyElapsed");
+bool SpeedStats::GetSummary(SpeedStats::Summary* s) {
+ if (elapsed_.empty()) return false;
s->min = *std::min_element(elapsed_.begin(), elapsed_.end());
s->max = *std::max_element(elapsed_.begin(), elapsed_.end());
@@ -83,18 +84,18 @@ std::string SummaryStat(double value, const char* unit,
const double value_min = value / s.max;
const double value_max = value / s.min;
- int ret = snprintf(stat_str, sizeof(stat_str), ",%s %.2f %s/s [%.2f, %.2f]",
- s.type, value_tendency, unit, value_min, value_max);
- (void)ret; // ret is unused when JXL_ASSERT is disabled.
- JXL_ASSERT(ret < static_cast<int>(sizeof(stat_str)));
+ snprintf(stat_str, sizeof(stat_str), ",%s %.2f %s/s [%.2f, %.2f]", s.type,
+ value_tendency, unit, value_min, value_max);
return stat_str;
}
} // namespace
-jxl::Status SpeedStats::Print(size_t worker_threads) {
+bool SpeedStats::Print(size_t worker_threads) {
Summary s;
- JXL_RETURN_IF_ERROR(GetSummary(&s));
+ if (!GetSummary(&s)) {
+ return false;
+ }
std::string mps_stats = SummaryStat(xsize_ * ysize_ * 1e-6, "MP", s);
std::string mbs_stats = SummaryStat(file_size_ * 1e-6, "MB", s);
diff --git a/media/libjxl/src/tools/speed_stats.h b/media/libjxl/src/tools/speed_stats.h
index eec8a58586..870523f6f1 100644
--- a/media/libjxl/src/tools/speed_stats.h
+++ b/media/libjxl/src/tools/speed_stats.h
@@ -11,8 +11,6 @@
#include <vector>
-#include "lib/jxl/base/status.h"
-
namespace jpegxl {
namespace tools {
@@ -32,7 +30,7 @@ class SpeedStats {
};
// Non-const, may sort elapsed_.
- jxl::Status GetSummary(Summary* summary);
+ bool GetSummary(Summary* summary);
// Sets the image size to allow computing MP/s values.
void SetImageSize(size_t xsize, size_t ysize) {
@@ -45,7 +43,7 @@ class SpeedStats {
// Calls GetSummary and prints megapixels/sec. SetImageSize() must be called
// once before this can be used.
- jxl::Status Print(size_t worker_threads);
+ bool Print(size_t worker_threads);
private:
std::vector<double> elapsed_;