diff --git a/media/libjxl/moz.yaml b/media/libjxl/moz.yaml index 0ede3e76095d..4a7cd8f2e1fb 100644 --- a/media/libjxl/moz.yaml +++ b/media/libjxl/moz.yaml @@ -1,54 +1,55 @@ -# Version of this schema -schema: 1 - -bugzilla: - # Bugzilla product and component for this directory and subdirectories - product: Core - component: "ImageLib" - -# Document the source of externally hosted code -origin: - - # Short name of the package/library - name: libjxl - - description: JPEG XL image format reference implementation - - # Full URL for the package's homepage/etc - # Usually different from repository url - url: https://github.com/libjxl/libjxl - - # Human-readable identifier for this version/release - # Generally "version NNN", "tag SSS", "bookmark SSS" - release: commit 0eff04c3a04e72e78d35f0965f17f54a98d61830 (2021-10-20T17:45:50Z). - - # Revision to pull in - # Must be a long or short commit SHA (long preferred) - # NOTE(krosylight): Update highway together when updating this! - revision: 0eff04c3a04e72e78d35f0965f17f54a98d61830 - - # The package's license, where possible using the mnemonic from - # https://spdx.org/licenses/ - # Multiple licenses can be specified (as a YAML list) - # A "LICENSE" file must exist containing the full license text - license: Apache-2.0 - - license-file: LICENSE - -updatebot: - maintainer-phab: saschanaz - maintainer-bz: krosylight@mozilla.com - tasks: - - type: vendoring - enabled: True - frequency: 3 weeks - -vendoring: - url: https://github.com/libjxl/libjxl.git - source-hosting: github - vendor-directory: third_party/jpeg-xl - - exclude: - - doc/ - - third_party/testdata/ - - tools/ +# Version of this schema +schema: 1 + +bugzilla: + # Bugzilla product and component for this directory and subdirectories + product: Core + component: "ImageLib" + +# Document the source of externally hosted code +origin: + + # Short name of the package/library + name: libjxl + + description: JPEG XL image format reference implementation + + # Full URL for the package's homepage/etc + # Usually different from repository url + url: https://github.com/libjxl/libjxl + + # Human-readable identifier for this version/release + # Generally "version NNN", "tag SSS", "bookmark SSS" + release: commit 9e8c5766ba32c008ddcaf11f0fac591aa7964f7b (2021-11-10T07:51:06Z). + + # Revision to pull in + # Must be a long or short commit SHA (long preferred) + # NOTE(krosylight): Update highway together when updating this! + revision: 9e8c5766ba32c008ddcaf11f0fac591aa7964f7b + + # The package's license, where possible using the mnemonic from + # https://spdx.org/licenses/ + # Multiple licenses can be specified (as a YAML list) + # A "LICENSE" file must exist containing the full license text + license: Apache-2.0 + + license-file: LICENSE + +updatebot: + maintainer-phab: saschanaz + maintainer-bz: krosylight@mozilla.com + tasks: + - type: vendoring + enabled: True + frequency: 3 weeks + +vendoring: + url: https://github.com/libjxl/libjxl.git + source-hosting: github + vendor-directory: third_party/jpeg-xl + + exclude: + - doc/ + - third_party/testdata/ + - tools/ + diff --git a/third_party/jpeg-xl/AUTHORS b/third_party/jpeg-xl/AUTHORS index a2b570809b75..dc396a784821 100644 --- a/third_party/jpeg-xl/AUTHORS +++ b/third_party/jpeg-xl/AUTHORS @@ -20,6 +20,7 @@ Alexander Sago Dirk Lemstra Jon Sneyers Lovell Fuller +Kleis Auke Wolthuizen Marcin Konicki Petr DiblĂ­k Pieter Wuille @@ -29,4 +30,5 @@ Andrius Lukas Narbutas Misaki Kasumi Samuel Leong Alex Xu (Hello71) -Vincent Torri \ No newline at end of file +Vincent Torri +Artem Selishchev diff --git a/third_party/jpeg-xl/CHANGELOG.md b/third_party/jpeg-xl/CHANGELOG.md index 80c2ed3ddaac..d67ba764d5e4 100644 --- a/third_party/jpeg-xl/CHANGELOG.md +++ b/third_party/jpeg-xl/CHANGELOG.md @@ -5,6 +5,28 @@ All notable changes to this project will be documented in this file. 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 +### Added + - decoder API: Ability to decode the content of metadata boxes: + `JXL_DEC_BOX`, `JXL_DEC_BOX_NEED_MORE_OUTPUT`, `JxlDecoderSetBoxBuffer`, + `JxlDecoderGetBoxType`, `JxlDecoderGetBoxSizeRaw` and + `JxlDecoderSetDecompressBoxes` + - decoder API: ability to mark the input is finished: `JxlDecoderCloseInput` + +### Changed +- decoder API: `JxlDecoderCloseInput` is required when using JXL_DEC_BOX, and + also encouraged, but not required for backwards compatiblity, for any other + usage of the decoder. + +## [0.6.1] - 2021-10-29 +### Changed + - Security: Fix OOB read in splines rendering (#735 - + [CVE-2021-22563](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2021-22563)) + - Security: Fix OOB copy (read/write) in out-of-order/multi-threaded decoding + (#708 - [CVE-2021-22564](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2021-22564)) + - Fix segfault in `djxl` tool with `--allow_partial_files` flag (#781). + - Fix border in extra channels when using upsampling (#796) + ## [0.6] - 2021-10-04 ### Added - API: New functions to decode extra channels: diff --git a/third_party/jpeg-xl/CMakeLists.txt b/third_party/jpeg-xl/CMakeLists.txt index 6157f33e56b4..41b9f3dd5ac1 100644 --- a/third_party/jpeg-xl/CMakeLists.txt +++ b/third_party/jpeg-xl/CMakeLists.txt @@ -355,7 +355,12 @@ if(JPEGXL_ENABLE_MANPAGES) find_program(ASCIIDOC a2x) if(NOT "${ASCIIDOC}" STREQUAL "ASCIIDOC-NOTFOUND") file(STRINGS "${ASCIIDOC}" ASCIIDOC_SHEBANG LIMIT_COUNT 1) -if(ASCIIDOC_SHEBANG MATCHES "python2") +if(ASCIIDOC_SHEBANG MATCHES "/sh") + set(ASCIIDOC_PY_FOUND ON) + # Run the program directly and set ASCIIDOC as empty. + set(ASCIIDOC_PY "${ASCIIDOC}") + set(ASCIIDOC "") +elseif(ASCIIDOC_SHEBANG MATCHES "python2") find_package(Python2 COMPONENTS Interpreter) set(ASCIIDOC_PY_FOUND "${Python2_Interpreter_FOUND}") set(ASCIIDOC_PY Python2::Interpreter) @@ -386,7 +391,7 @@ if (ASCIIDOC_PY_FOUND) add_custom_command( OUTPUT "${PAGE}.1" COMMAND "${ASCIIDOC_PY}" - ARGS "${ASCIIDOC}" + ARGS ${ASCIIDOC} --format manpage --destination-dir="${CMAKE_CURRENT_BINARY_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}/doc/man/${PAGE}.txt" MAIN_DEPENDENCY "${CMAKE_CURRENT_SOURCE_DIR}/doc/man/${PAGE}.txt") diff --git a/third_party/jpeg-xl/bash_test.sh b/third_party/jpeg-xl/bash_test.sh index 263fe5cc2a95..d5791767fe1d 100644 --- a/third_party/jpeg-xl/bash_test.sh +++ b/third_party/jpeg-xl/bash_test.sh @@ -86,13 +86,34 @@ test_copyright() { # Check that we don't use "%zu" or "%zd" in format string for size_t. test_printf_size_t() { + local ret=0 if grep -n -E '%[0-9]*z[udx]' \ $(git ls-files | grep -E '(\.c|\.cc|\.cpp|\.h)$'); then echo "Don't use '%zu' or '%zd' in a format string, instead use " \ "'%\" PRIuS \"' or '%\" PRIdS \"'." >&2 - return 1 + ret=1 fi - return 0 + + local f + for f in $(git ls-files | grep -E "\.cc$" | xargs grep 'PRI[udx]S' | + cut -f 1 -d : | uniq); do + if ! grep -F printf_macros.h "$f" >/dev/null; then + echo "$f: Add lib/jxl/base/printf_macros.h for PRI.S, or use other " \ + "types for code outside lib/jxl library." >&2 + ret=1 + fi + done + + for f in $(git ls-files | grep -E "\.h$" | grep -v -F printf_macros.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. + # those may re-define PRIuS unconditionally causing a compile error. + echo "$f: Don't use PRI.S in header files. Sorry." + ret=1 + done + + return ${ret} } # Check that "dec_" code doesn't depend on "enc_" headers. diff --git a/third_party/jpeg-xl/ci.sh b/third_party/jpeg-xl/ci.sh index 715443438162..dfd6041fda45 100644 --- a/third_party/jpeg-xl/ci.sh +++ b/third_party/jpeg-xl/ci.sh @@ -468,6 +468,8 @@ cmake_build_and_test() { if [[ "${PACK_TEST:-}" == "1" ]]; then (cd "${BUILD_DIR}" ${FIND_BIN} -name '*.cmake' -a '!' -path '*CMakeFiles*' + # gtest / gmock / gtest_main shared libs + ${FIND_BIN} lib/ -name 'libg*.so*' ${FIND_BIN} -type d -name tests -a '!' -path '*CMakeFiles*' ) | tar -C "${BUILD_DIR}" -cf "${BUILD_DIR}/tests.tar.xz" -T - \ --use-compress-program="xz --threads=$(nproc --all || echo 1) -6" diff --git a/third_party/jpeg-xl/examples/decode_exif_metadata.cc b/third_party/jpeg-xl/examples/decode_exif_metadata.cc index 84c2f42718dd..adfe5f84244a 100644 --- a/third_party/jpeg-xl/examples/decode_exif_metadata.cc +++ b/third_party/jpeg-xl/examples/decode_exif_metadata.cc @@ -36,6 +36,7 @@ bool DecodeJpegXlExif(const uint8_t* jxl, size_t size, } JxlDecoderSetInput(dec.get(), jxl, size); + JxlDecoderCloseInput(dec.get()); const constexpr size_t kChunkSize = 65536; size_t output_pos = 0; diff --git a/third_party/jpeg-xl/examples/decode_oneshot.cc b/third_party/jpeg-xl/examples/decode_oneshot.cc index d146b764533d..932193fd17e1 100644 --- a/third_party/jpeg-xl/examples/decode_oneshot.cc +++ b/third_party/jpeg-xl/examples/decode_oneshot.cc @@ -52,6 +52,7 @@ bool DecodeJpegXlOneShot(const uint8_t* jxl, size_t size, JxlPixelFormat format = {4, JXL_TYPE_FLOAT, JXL_NATIVE_ENDIAN, 0}; JxlDecoderSetInput(dec.get(), jxl, size); + JxlDecoderCloseInput(dec.get()); for (;;) { JxlDecoderStatus status = JxlDecoderProcessInput(dec.get()); diff --git a/third_party/jpeg-xl/lib/extras/codec_pgx.cc b/third_party/jpeg-xl/lib/extras/codec_pgx.cc index bed505cc4610..f71dff7ea0e3 100644 --- a/third_party/jpeg-xl/lib/extras/codec_pgx.cc +++ b/third_party/jpeg-xl/lib/extras/codec_pgx.cc @@ -18,6 +18,7 @@ #include "lib/jxl/base/byte_order.h" #include "lib/jxl/base/compiler_specific.h" #include "lib/jxl/base/file_io.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_external_image.h" diff --git a/third_party/jpeg-xl/lib/extras/codec_png.cc b/third_party/jpeg-xl/lib/extras/codec_png.cc index 2efcd95f92db..0ee27196395f 100644 --- a/third_party/jpeg-xl/lib/extras/codec_png.cc +++ b/third_party/jpeg-xl/lib/extras/codec_png.cc @@ -22,6 +22,7 @@ #include "lib/jxl/base/byte_order.h" #include "lib/jxl/base/compiler_specific.h" #include "lib/jxl/base/file_io.h" +#include "lib/jxl/base/printf_macros.h" #include "lib/jxl/color_management.h" #include "lib/jxl/common.h" #include "lib/jxl/dec_external_image.h" diff --git a/third_party/jpeg-xl/lib/extras/codec_pnm.cc b/third_party/jpeg-xl/lib/extras/codec_pnm.cc index 8bea05bade83..2c254db8171c 100644 --- a/third_party/jpeg-xl/lib/extras/codec_pnm.cc +++ b/third_party/jpeg-xl/lib/extras/codec_pnm.cc @@ -18,6 +18,7 @@ #include "lib/jxl/base/byte_order.h" #include "lib/jxl/base/compiler_specific.h" #include "lib/jxl/base/file_io.h" +#include "lib/jxl/base/printf_macros.h" #include "lib/jxl/base/status.h" #include "lib/jxl/color_management.h" #include "lib/jxl/dec_external_image.h" diff --git a/third_party/jpeg-xl/lib/extras/codec_psd.cc b/third_party/jpeg-xl/lib/extras/codec_psd.cc index 52a08b41be01..fc064c4455a7 100644 --- a/third_party/jpeg-xl/lib/extras/codec_psd.cc +++ b/third_party/jpeg-xl/lib/extras/codec_psd.cc @@ -19,6 +19,7 @@ #include "lib/jxl/base/byte_order.h" #include "lib/jxl/base/compiler_specific.h" #include "lib/jxl/base/file_io.h" +#include "lib/jxl/base/printf_macros.h" #include "lib/jxl/color_management.h" #include "lib/jxl/common.h" #include "lib/jxl/fields.h" // AllDefault diff --git a/third_party/jpeg-xl/lib/extras/codec_test.cc b/third_party/jpeg-xl/lib/extras/codec_test.cc index b2a7648ea987..276b2f4be1af 100644 --- a/third_party/jpeg-xl/lib/extras/codec_test.cc +++ b/third_party/jpeg-xl/lib/extras/codec_test.cc @@ -15,6 +15,7 @@ #include "gtest/gtest.h" #include "lib/extras/codec_pgx.h" #include "lib/extras/codec_pnm.h" +#include "lib/jxl/base/printf_macros.h" #include "lib/jxl/base/random.h" #include "lib/jxl/base/thread_pool_internal.h" #include "lib/jxl/color_management.h" diff --git a/third_party/jpeg-xl/lib/extras/tone_mapping.cc b/third_party/jpeg-xl/lib/extras/tone_mapping.cc index 9bb1c0559c6f..673915e9c15b 100644 --- a/third_party/jpeg-xl/lib/extras/tone_mapping.cc +++ b/third_party/jpeg-xl/lib/extras/tone_mapping.cc @@ -98,30 +98,13 @@ Status ToneMapFrame(const std::pair display_nits, TF_PQ().DisplayFromEncoded(df, e4))); const V ratio = new_luminance / luminance; - const V multiplier = ratio * - Set(df, ib->metadata()->IntensityTarget()) * - inv_max_display_nits; + const V normalizer = + Set(df, ib->metadata()->IntensityTarget()) * inv_max_display_nits; - red *= multiplier; - green *= multiplier; - blue *= multiplier; - - const V gray = new_luminance * inv_max_display_nits; - - // Desaturate out-of-gamut pixels. - V gray_mix = Zero(df); - for (const V val : {red, green, blue}) { - const V inv_val_minus_gray = Set(df, 1) / (val - gray); - const V bound1 = val * inv_val_minus_gray; - const V bound2 = bound1 - inv_val_minus_gray; - const V min_bound = Min(bound1, bound2); - const V max_bound = Max(bound1, bound2); - gray_mix = Clamp(gray_mix, min_bound, max_bound); - } - gray_mix = Clamp(gray_mix, Zero(df), Set(df, 1)); for (V* const val : {&red, &green, &blue}) { - *val = IfThenElse(luminance < Set(df, 1e-6), gray, - MulAdd(gray_mix, gray - *val, *val)); + *val = IfThenElse(luminance <= Set(df, 1e-6f), new_luminance, + *val * ratio) * + normalizer; } Store(red, df, row_r + x); @@ -134,6 +117,77 @@ Status ToneMapFrame(const std::pair display_nits, return true; } +Status GamutMapFrame(ImageBundle* const ib, float preserve_saturation, + ThreadPool* const pool) { + HWY_FULL(float) df; + using V = decltype(Zero(df)); + + ColorEncoding linear_rec2020; + linear_rec2020.SetColorSpace(ColorSpace::kRGB); + linear_rec2020.primaries = Primaries::k2100; + linear_rec2020.white_point = WhitePoint::kD65; + linear_rec2020.tf.SetTransferFunction(TransferFunction::kLinear); + JXL_RETURN_IF_ERROR(linear_rec2020.CreateICC()); + JXL_RETURN_IF_ERROR(ib->TransformTo(linear_rec2020, pool)); + + JXL_RETURN_IF_ERROR(RunOnPool( + pool, 0, ib->ysize(), ThreadPool::SkipInit(), + [&](const int y, const int thread) { + float* const JXL_RESTRICT row_r = ib->color()->PlaneRow(0, y); + float* const JXL_RESTRICT row_g = ib->color()->PlaneRow(1, y); + float* const JXL_RESTRICT row_b = ib->color()->PlaneRow(2, y); + for (size_t x = 0; x < ib->xsize(); x += Lanes(df)) { + 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; + } + + Store(red, df, row_r + x); + Store(green, df, row_g + x); + Store(blue, df, row_b + x); + } + }, + "GamutMap")); + + return true; +} + // NOLINTNEXTLINE(google-readability-namespace-comments) } // namespace HWY_NAMESPACE } // namespace jxl @@ -144,7 +198,8 @@ namespace jxl { namespace { HWY_EXPORT(ToneMapFrame); -} +HWY_EXPORT(GamutMapFrame); +} // namespace Status ToneMapTo(const std::pair display_nits, CodecInOut* const io, ThreadPool* const pool) { @@ -156,5 +211,14 @@ Status ToneMapTo(const std::pair display_nits, return true; } +Status GamutMap(CodecInOut* const io, float preserve_saturation, + ThreadPool* const pool) { + const auto gamut_map_frame = HWY_DYNAMIC_DISPATCH(GamutMapFrame); + for (ImageBundle& ib : io->frames) { + JXL_RETURN_IF_ERROR(gamut_map_frame(&ib, preserve_saturation, pool)); + } + return true; +} + } // namespace jxl #endif diff --git a/third_party/jpeg-xl/lib/extras/tone_mapping.h b/third_party/jpeg-xl/lib/extras/tone_mapping.h index 4f9feeccc6cd..1f474101eb44 100644 --- a/third_party/jpeg-xl/lib/extras/tone_mapping.h +++ b/third_party/jpeg-xl/lib/extras/tone_mapping.h @@ -10,9 +10,21 @@ namespace jxl { +// Important: after calling this, the result will contain many out-of-gamut +// colors. It is very strongly recommended to call GamutMap afterwards to +// rectify this. Status ToneMapTo(std::pair display_nits, CodecInOut* io, ThreadPool* pool = nullptr); +// `preserve_saturation` indicates to what extent to favor saturation over +// luminance when mapping out-of-gamut colors to Rec. 2020. 0 preserves +// luminance at the complete expense of saturation, while 1 gives the most +// saturated color with the same hue that Rec. 2020 can represent even if it +// means lowering the luminance. Values in between correspond to linear mixtures +// of those two extremes. +Status GamutMap(CodecInOut* io, float preserve_saturation, + ThreadPool* pool = nullptr); + } // namespace jxl #endif // LIB_EXTRAS_TONE_MAPPING_H_ diff --git a/third_party/jpeg-xl/lib/gbench_main.cc b/third_party/jpeg-xl/lib/gbench_main.cc new file mode 100644 index 000000000000..1cc17720177d --- /dev/null +++ b/third_party/jpeg-xl/lib/gbench_main.cc @@ -0,0 +1,8 @@ +// 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 "benchmark/benchmark.h" + +BENCHMARK_MAIN(); diff --git a/third_party/jpeg-xl/lib/include/jxl/decode.h b/third_party/jpeg-xl/lib/include/jxl/decode.h index b2d0e55b1b8d..675c522936c3 100644 --- a/third_party/jpeg-xl/lib/include/jxl/decode.h +++ b/third_party/jpeg-xl/lib/include/jxl/decode.h @@ -405,6 +405,19 @@ JXL_EXPORT JxlDecoderStatus JxlDecoderSubscribeEvents(JxlDecoder* dec, JXL_EXPORT JxlDecoderStatus JxlDecoderSetKeepOrientation(JxlDecoder* dec, JXL_BOOL keep_orientation); +/** 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. + * + * @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. + */ +JXL_EXPORT JxlDecoderStatus +JxlDecoderSetRenderSpotcolors(JxlDecoder* dec, JXL_BOOL render_spotcolors); + /** * Decodes JPEG XL file using the available bytes. Requires input has been * set with JxlDecoderSetInput. After JxlDecoderProcessInput, input can @@ -454,11 +467,14 @@ 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 + * 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, - * JXL_DEC_SUCCESS otherwise. + * @return JXL_DEC_ERROR if input was already set without releasing or + * JxlDecoderCloseInput was already called, JXL_DEC_SUCCESS otherwise. */ JXL_EXPORT JxlDecoderStatus JxlDecoderSetInput(JxlDecoder* dec, const uint8_t* data, @@ -483,6 +499,23 @@ JXL_EXPORT JxlDecoderStatus JxlDecoderSetInput(JxlDecoder* dec, */ 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 + * 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. + * @param dec decoder object + */ +JXL_EXPORT void JxlDecoderCloseInput(JxlDecoder* dec); + /** * Outputs the basic image information, such as image dimensions, bit depth and * all other JxlBasicInfo fields, if available. diff --git a/third_party/jpeg-xl/lib/include/jxl/encode.h b/third_party/jpeg-xl/lib/include/jxl/encode.h index 7be7aa86d272..ba9f062ab92c 100644 --- a/third_party/jpeg-xl/lib/include/jxl/encode.h +++ b/third_party/jpeg-xl/lib/include/jxl/encode.h @@ -13,6 +13,7 @@ #ifndef JXL_ENCODE_H_ #define JXL_ENCODE_H_ +#include "jxl/codestream_header.h" #include "jxl/decode.h" #include "jxl/jxl_export.h" #include "jxl/memory_manager.h" @@ -73,42 +74,163 @@ typedef enum { } JxlEncoderStatus; /** - * Id of options to set to JxlEncoderOptions. + * Id of per-frame options to set to JxlEncoderOptions with + * JxlEncoderOptionsSetInteger. + * NOTE: this enum includes most but not all encoder options. The image quality + * is a frame option that can be set with JxlEncoderOptionsSetDistance instead. + * Options that apply globally, rather than per-frame, are set with their own + * functions and do not use the per-frame JxlEncoderOptions. */ typedef enum { + /** Sets encoder effort/speed level without affecting decoding speed. Valid + * values are, from faster to slower speed: 1:lightning 2:thunder 3:falcon + * 4:cheetah 5:hare 6:wombat 7:squirrel 8:kitten 9:tortoise. + * Default: squirrel (7). + */ + JXL_ENC_OPTION_EFFORT = 0, + + /** Sets the decoding speed tier for the provided options. Minimum is 0 + * (slowest to decode, best quality/density), and maximum is 4 (fastest to + * decode, at the cost of some quality/density). Default is 0. + */ + JXL_ENC_OPTION_DECODING_SPEED = 1, + /** Sets resampling option. If enabled, the image is downsampled before * compression, and upsampled to original size in the decoder. Integer option, - * use 0 for the default behavior (resampling only applied for low quality), + * use -1 for the default behavior (resampling only applied for low quality), * 1 for no downsampling (1x1), 2 for 2x2 downsampling, 4 for 4x4 - * downsampling, 8 for 8x8 downsampling. The default value is 0. + * downsampling, 8 for 8x8 downsampling. */ - JXL_ENC_OPTION_RESAMPLING = 0, + JXL_ENC_OPTION_RESAMPLING = 2, /** Similar to JXL_ENC_OPTION_RESAMPLING, but for extra channels. Integer - * option, use 1 for no downsampling (1x1), 2 for 2x2 downsampling, 4 for 4x4 - * downsampling, 8 for 8x8 downsampling. The default value is 1. + * option, use -1 for the default behavior (depends on encoder + * implementation), 1 for no downsampling (1x1), 2 for 2x2 downsampling, 4 for + * 4x4 downsampling, 8 for 8x8 downsampling. */ - JXL_ENC_OPTION_EXTRA_CHANNEL_RESAMPLING = 1, + JXL_ENC_OPTION_EXTRA_CHANNEL_RESAMPLING = 3, - /** Enables or disables noise generation. Integer option, use -1 for the - * encoder default, 0 to disable, 1 to enable. The + /** 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. */ - JXL_ENC_OPTION_NOISE = 2, + JXL_ENC_OPTION_PHOTON_NOISE = 4, - /** Enables or disables dots generation. Integer option, use -1 for the + /** Enables adaptive noise generation. This setting is not recommended for + * use, please use JXL_ENC_OPTION_PHOTON_NOISE instead. Use -1 for the default + * (encoder chooses), 0 to disable, 1 to enable. + */ + JXL_ENC_OPTION_NOISE = 5, + + /** Enables or disables dots generation. Use -1 for the default (encoder + * chooses), 0 to disable, 1 to enable. + */ + JXL_ENC_OPTION_DOTS = 6, + + /** Enables or disables patches generation. Use -1 for the default (encoder + * chooses), 0 to disable, 1 to enable. + */ + JXL_ENC_OPTION_PATCHES = 7, + + /** Edge preserving filter level, -1 to 3. Use -1 for the default (encoder + * chooses), 0 to 3 to set a strength. + */ + JXL_ENC_OPTION_EPF = 8, + + /** Enables or disables the gaborish filter. Use -1 for the default (encoder + * chooses), 0 to disable, 1 to enable. + */ + JXL_ENC_OPTION_GABORISH = 9, + + /** Enables modular encoding. Use -1 for default (encoder + * chooses), 0 to enforce VarDCT mode (e.g. for photographic images), 1 to + * enforce modular mode (e.g. for lossless images). + */ + JXL_ENC_OPTION_MODULAR = 10, + + /** Enables or disables preserving color of invisible pixels. Use -1 for the + * default (1 if lossless, 0 if lossy), 0 to disable, 1 to enable. + */ + JXL_ENC_OPTION_KEEP_INVISIBLE = 11, + + /** Determines the order in which 256x256 regions are stored in the codestream + * for progressive rendering. Use -1 for the encoder + * default, 0 for scanline order, 1 for center-first order. + */ + JXL_ENC_OPTION_GROUP_ORDER = 12, + + /** Determines the horizontal position of center for the center-first group + * order. Use -1 to automatically use the middle of the image, 0..xsize to + * specifically set it. + */ + JXL_ENC_OPTION_GROUP_ORDER_CENTER_X = 13, + + /** Determines the center for the center-first group order. Use -1 to + * automatically use the middle of the image, 0..ysize to specifically set it. + */ + JXL_ENC_OPTION_GROUP_ORDER_CENTER_Y = 14, + + /** Enables or disables progressive encoding for modular mode. Use -1 for the * encoder default, 0 to disable, 1 to enable. */ - JXL_ENC_OPTION_DOTS = 3, + JXL_ENC_OPTION_RESPONSIVE = 15, - /** Enables or disables patches generation. Integer option, use -1 for the - * encoder default, 0 to disable, 1 to enable. + /** Set the progressive mode for the AC coefficients of VarDCT, using spectral + * progression from the DCT coefficients. Use -1 for the encoder default, 0 to + * disable, 1 to enable. */ - JXL_ENC_OPTION_PATCHES = 4, + JXL_ENC_OPTION_PROGRESSIVE_AC = 16, - /** Enables or disables the gaborish filter. Integer option, use -1 for the - * encoder default, 0 to disable, 1 to enable. + /** Set the progressive mode for the AC coefficients of VarDCT, using + * quantization of the least significant bits. Use -1 for the encoder default, + * 0 to disable, 1 to enable. */ - JXL_ENC_OPTION_GABORISH = 5, + JXL_ENC_OPTION_QPROGRESSIVE_AC = 17, + + /** Set the progressive mode using lower-resolution DC images for VarDCT. Use + * -1 for the encoder default, 0 to disable, 1 to have an extra 64x64 lower + * resolution pass, 2 to have a 512x512 and 64x64 lower resolution pass. + */ + JXL_ENC_OPTION_PROGRESSIVE_DC = 18, + + /** Use Global channel palette if the amount of colors is smaller than this + * percentage of range. Use 0-100 to set an explicit percentage, -1 to use the + * encoder default. Used for modular encoding. + */ + JXL_ENC_OPTION_CHANNEL_COLORS_PRE_TRANSFORM_PERCENT = 19, + + /** Use Local channel palette if the amount of colors is smaller than this + * percentage of range. Use 0-100 to set an explicit percentage, -1 to use the + * encoder default. Used for modular encoding. + */ + JXL_ENC_OPTION_CHANNEL_COLORS_PERCENT = 20, + + /** Use color palette if amount of colors is smaller than or equal to this + * amount, or -1 to use the encoder default. Used for modular encoding. + */ + JXL_ENC_OPTION_PALETTE_COLORS = 21, + + /** Enables or disables delta palette. Use -1 for the default (encoder + * chooses), 0 to disable, 1 to enable. Used in modular mode. + */ + JXL_ENC_OPTION_LOSSY_PALETTE = 22, + + /** Color space for modular encoding: 0=RGB, 1=YCoCg, 2-37=RCT, -1=default: + * try several, depending on speed. + */ + JXL_ENC_OPTION_MODULAR_COLOR_SPACE = 23, + + /** Group size for modular encoding: -1=default, 0=128, 1=256, 2=512, 3=1024. + */ + JXL_ENC_OPTION_MODULAR_GROUP_SIZE = 24, + + /** Predictor for modular encoding. -1 = default, 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. + */ + JXL_ENC_OPTION_MODULAR_PREDICTOR = 25, /** Enum value not to be used as an option. This value is added to force the * C compiler to have the enum to take a known size. @@ -214,12 +336,21 @@ JXL_EXPORT JxlEncoderStatus JxlEncoderAddJPEGFrame( * Sets the buffer to read pixels from for the next image to encode. Must call * JxlEncoderSetBasicInfo before JxlEncoderAddImageFrame. * - * Currently only some pixel formats are supported: + * Currently only some data types for pixel formats are supported: * - JXL_TYPE_UINT8 * - JXL_TYPE_UINT16 * - JXL_TYPE_FLOAT16, with nominal range 0..1 * - JXL_TYPE_FLOAT, with nominal range 0..1 * + * We support interleaved channels as described by the JxlPixelFormat: + * - single-channel data, e.g. grayscale + * - single-channel + alpha + * - trichromatic, e.g. RGB + * - trichromatic + alpha + * + * Extra channels not handled here need to be set by @ref + * JxlEncoderSetExtraChannelBuffer. + * * 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 @@ -240,7 +371,152 @@ JXL_EXPORT JxlEncoderStatus JxlEncoderAddImageFrame( const void* buffer, size_t size); /** - * Declares that this encoder will not encode anything further. + * Sets the buffer to read pixels from for an extra channel at a given index. + * The index must be smaller than the num_extra_channels in the associated + * JxlBasicInfo. Must call @ref JxlEncoderSetExtraChannelInfo before + * JxlEncoderSetExtraChannelBuffer. + * + * TODO(firsching): mention what data types in pixel formats are supported. + * + * It is required to call this function for every extra channel, except for the + * alpha channel if that was already set through @ref JxlEncoderAddImageFrame. + * + * @param options set of encoder options to use when encoding the extra channel. + * @param pixel_format format for pixels. Object owned by the caller and its + * contents are copied internally. The num_channels value is ignored, since the + * number of channels for an extra channel is always assumed to be one. + * @param buffer buffer type to input the pixel data from. Owned by the caller + * and its contents are copied internally. + * @param size size of buffer in bytes. + * @param index index of the extra channel to use. + * @return JXL_ENC_SUCCESS on success, JXL_ENC_ERROR on error + */ +JXL_EXPORT JxlEncoderStatus JxlEncoderSetExtraChannelBuffer( + const JxlEncoderOptions* options, const JxlPixelFormat* pixel_format, + const void* buffer, size_t size, uint32_t index); + +/** Adds a metadata box to the file format. JxlEncoderProcessOutput must be used + * to effectively write the box to the output. @ref JxlEncoderUseContainer must + * be enabled before using this function. + * + * Background information about the container format and boxes follows here: + * + * For users of libjxl, boxes allow inserting application-specific data and + * metadata (Exif, XML, JUMBF and user defined boxes). + * + * The box format follows ISO BMFF and shares features and box types with other + * image and video formats, including the Exif, XML and JUMBF boxes. The box + * format for JPEG XL is specified in ISO/IEC 18181-2. + * + * Boxes in general don't contain other boxes inside, except a JUMBF superbox. + * Boxes follow each other sequentially and are byte-aligned. If the container + * format is used, the JXL stream exists out of 3 or more concatenated boxes. + * It is also possible to use a direct codestream without boxes, but in that + * case metadata cannot be added. + * + * Each box generally has the following byte structure in the file: + * - 4 bytes: box size including box header (Big endian. If set to 0, an + * 8-byte 64-bit size follows instead). + * - 4 bytes: type, e.g. "JXL " for the signature box, "jxlc" for a codestream + * box. + * - N bytes: box contents. + * + * Only the box contents are provided to the contents argument of this function, + * the encoder encodes the size header itself. + * + * Box types are given by 4 characters. A list of known types follows: + * - "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 + * encoder will automatically do this if necessary. The contents are the same + * as in case of a jxlc box, when concatenated. + * - "Exif": a box with EXIF metadata, can be added by libjxl users, or is + * automatically added when needed for JPEG reconstruction. The contents of + * this box must be prepended by a 4-byte tiff header offset, which may + * be 4 zero bytes. + * - "XML ": a box with XMP or IPTC metadata, can be added by libjxl users, or + * is automatically added when needed for JPEG reconstruction + * - "jumb": a JUMBF superbox, which can contain boxes with different types of + * metadata inside. This box type can be added by the encoder transparently, + * and other libraries to create and handle JUMBF content exist. + * - "brob": a Brotli-compressed box, which otherwise represents an existing + * type of box such as Exif or XML. The encoder creates these when enabled and + * users of libjxl don't need to create them directly. Some box types are not + * allowed to be compressed: any of the signature, jxl* and jbrd boxes. + * - "jxli": frame index box, can list the keyframes in case of a JXL animation, + * allowing the decoder to jump to individual frames more efficiently. This + * box type is specified, but not currently supported by the encoder or + * decoder. + * - "jbrd": JPEG reconstruction box, contains the information required to + * byte-for-byte losslessly recontruct a JPEG-1 image. The JPEG coefficients + * (pixel content) themselves are encoded in the JXL codestream (jxlc or jxlp) + * itself. Exif and XMP metadata will be encoded in Exif and XMP boxes. The + * jbrd box itself contains information such as the app markers of the JPEG-1 + * file and everything else required to fit the information together into the + * exact original JPEG file. This box is added automatically by the encoder + * when needed, and only when JPEG reconstruction is used. + * - other: other application-specific boxes can be added. Their typename should + * not begin with "jxl" or "JXL" or conflict with other existing typenames. + * + * Most boxes are automatically added by the encoder and should not be added + * with JxlEncoderAddBox. Boxes that one may wish to add with JxlEncoderAddBox + * are: Exif and XML (but not when using JPEG reconstruction since if the + * JPEG has those, these boxes are already added automatically), jumb, and + * application-specific boxes. + * + * Adding metadata boxes increases the filesize. When adding Exif metadata, the + * data must be in sync with what is encoded in the JPEG XL codestream, + * specifically the image orientation. While this is not recommended in + * practice, in case of conflicting metadata, the JPEG XL codestream takes + * precedence. + * + * It is possible to create a codestream without boxes, then what would be in + * the jxlc box is written directly to the output + * + * It is possible to split the codestream across multiple boxes, in that case + * multiple boxes of type jxlp are used. This is handled by the encoder when + * needed. + * + * For now metadata boxes can only be added before or after the codestream with + * all frames, so using JxlEncoderAddBox is only possible before the first + * JxlEncoderAddImageFrame call, and/or after the last JxlEncoderAddImageFrame + * call and JxlEncoderCloseInput. Support for adding boxes in-between the + * codestream, and/or in-between image frames may be added later, and would + * cause the encoder to use jxlp boxes for the codestream. + * + * @param enc encoder object. + * @param type the box type, e.g. "Exif" for EXIF metadata, "XML " for XMP or + * IPTC metadata, "jumb" for JUMBF metadata. + * @param contents the full contents of the box, for example EXIF + * data. For an "Exif" box, the EXIF data must be prepended by a 4-byte tiff + * header offset, which may be 4 zero-bytes. The ISO BMFF box header must not + * be included, only the contents. + * @param size size of the box contents. + * @param compress_box Whether to compress this box as a "brob" box. Requires + * Brotli support. + * @return JXL_ENC_SUCCESS on success, JXL_ENC_ERROR on error, such as when + * using this function without JxlEncoderUseContainer, or adding a box type + * that would result in an invalid file format. + */ +JXL_EXPORT JxlEncoderStatus JxlEncoderAddBox(JxlEncoder* enc, JxlBoxType type, + const uint8_t* contents, + size_t size, + JXL_BOOL compress_box); + +/** + * Declares that this encoder will not encode any further frames. Further + * metadata boxes may still be added. * * Must be called between JxlEncoderAddImageFrame/JPEGFrame of the last frame * and the next call to JxlEncoderProcessOutput, or JxlEncoderProcessOutput @@ -295,6 +571,10 @@ JXL_EXPORT void JxlEncoderInitBasicInfo(JxlBasicInfo* info); /** * Sets the global metadata of the image encoded by this encoder. * + * If the JxlBasicInfo contains information of extra channels beyond an alpha + * channel, then @ref JxlEncoderSetExtraChannelInfo must be called between + * JxlEncoderSetBasicInfo and @ref JxlEncoderAddImageFrame. + * * @param enc encoder object. * @param info global image metadata. Object owned by the caller and its * contents are copied internally. @@ -305,103 +585,50 @@ JXL_EXPORT JxlEncoderStatus JxlEncoderSetBasicInfo(JxlEncoder* enc, const JxlBasicInfo* info); /** - * Configure the encoder to store JPEG reconstruction metadata in the JPEG XL - * container. + * Initializes a JxlExtraChannelInfo struct to default values. + * For forwards-compatibility, this function has to be called before values + * are assigned to the struct fields. + * The default values correspond to an 8-bit channel of the provided type. * - * The encoder must be configured to use the JPEG XL container format using @ref - * JxlEncoderUseContainer for this to have any effect. - * - * If this is set to true and a single JPEG frame is added, it will be - * possible to losslessly reconstruct the JPEG codestream. - * - * @param enc encoder object. - * @param store_jpeg_metadata true if the encoder should store JPEG metadata. - * @return JXL_ENC_SUCCESS if the operation was successful, JXL_ENC_ERROR - * otherwise. + * @param type type of the extra channel. + * @param info global extra channel metadata. Object owned by the caller and its + * contents are copied internally. + * @return JXL_ENC_SUCCESS on success, JXL_ENC_ERROR on error */ -JXL_EXPORT JxlEncoderStatus -JxlEncoderStoreJPEGMetadata(JxlEncoder* enc, JXL_BOOL store_jpeg_metadata); +JXL_EXPORT void JxlEncoderInitExtraChannelInfo(JxlExtraChannelType type, + JxlExtraChannelInfo* info); /** - * Configure the encoder to use the JPEG XL container format. + * Sets information for the extra channel at the given index. The index + * must be smaller than num_extra_channels in the associated JxlBasicInfo. * - * Using the JPEG XL container format allows to store metadata such as JPEG - * reconstruction (@ref JxlEncoderStoreJPEGMetadata) or other metadata like - * EXIF; but it adds a few bytes to the encoded file for container headers even - * if there is no extra metadata. - * - * @param enc encoder object. - * @param use_container true if the encoder should output the JPEG XL container - * format. - * @return JXL_ENC_SUCCESS if the operation was successful, JXL_ENC_ERROR - * otherwise. + * @param enc encoder object + * @param index index of the extra channel to set. + * @param info global extra channel metadata. Object owned by the caller and its + * contents are copied internally. + * @return JXL_ENC_SUCCESS on success, JXL_ENC_ERROR on error */ -JXL_EXPORT JxlEncoderStatus JxlEncoderUseContainer(JxlEncoder* enc, - JXL_BOOL use_container); +JXL_EXPORT JxlEncoderStatus JxlEncoderSetExtraChannelInfo( + JxlEncoder* enc, size_t index, const JxlExtraChannelInfo* info); /** - * Sets lossless/lossy mode for the provided options. Default is lossy. + * Sets the name for the extra channel at the given index in UTF-8. The index + * must be smaller than the num_extra_channels in the associated JxlBasicInfo. * - * @param options set of encoder options to update with the new mode - * @param lossless whether the options should be lossless - * @return JXL_ENC_SUCCESS if the operation was successful, JXL_ENC_ERROR - * otherwise. + * @param enc encoder object + * @param index index of the extra channel to set. + * @param name buffer with the name of the extra channel. + * @param size size of the name buffer in bytes. + * @return JXL_ENC_SUCCESS on success, JXL_ENC_ERROR on error */ -JXL_EXPORT JxlEncoderStatus -JxlEncoderOptionsSetLossless(JxlEncoderOptions* options, JXL_BOOL lossless); +JXL_EXPORT JxlEncoderStatus JxlEncoderSetExtraChannelName(JxlEncoder* enc, + size_t index, + const char* name, + size_t size); /** - * Set the decoding speed tier for the provided options. Minimum is 0 (highest - * quality), and maximum is 4 (lowest quality). Default is 0. - * - * @param options set of encoder options to update with the new decoding speed - * tier. - * @param tier the decoding speed tier to set. - * @return JXL_ENC_SUCCESS if the operation was successful, JXL_ENC_ERROR - * otherwise. - */ -JXL_EXPORT JxlEncoderStatus -JxlEncoderOptionsSetDecodingSpeed(JxlEncoderOptions* options, int tier); - -/** - * Sets encoder effort/speed level without affecting decoding speed. Valid - * values are, from faster to slower speed: 1:lightning 2:thunder 3:falcon - * 4:cheetah 5:hare 6:wombat 7:squirrel 8:kitten 9:tortoise. - * Default: squirrel (7). - * - * @param options set of encoder options to update with the new mode. - * @param effort the effort value to set. - * @return JXL_ENC_SUCCESS if the operation was successful, JXL_ENC_ERROR - * otherwise. - */ -JXL_EXPORT JxlEncoderStatus -JxlEncoderOptionsSetEffort(JxlEncoderOptions* options, int effort); - -/** - * Sets the distance level for lossy compression: target max butteraugli - * distance, lower = higher quality. Range: 0 .. 15. - * 0.0 = mathematically lossless (however, use JxlEncoderOptionsSetLossless to - * use true lossless). - * 1.0 = visually lossless. - * Recommended range: 0.5 .. 3.0. - * Default value: 1.0. - * If JxlEncoderOptionsSetLossless is used, this value is unused and implied - * to be 0. - * - * @param options set of encoder options to update with the new mode. - * @param distance the distance value to set. - * @return JXL_ENC_SUCCESS if the operation was successful, JXL_ENC_ERROR - * otherwise. - */ -JXL_EXPORT JxlEncoderStatus -JxlEncoderOptionsSetDistance(JxlEncoderOptions* options, float distance); - -/** - * Sets an option of integer type to the encoder option. The JxlEncoderOptionId - * argument determines which option is set. - * - * TODO(lode): also use the option enum for the container, lossless, speed, - * effort and distance options. + * Sets a frame-specific option of integer type to the encoder options. + * The JxlEncoderOptionId argument determines which option is set. * * @param options set of encoder options to update with the new mode. * @param option ID of the option to set. @@ -412,9 +639,139 @@ JxlEncoderOptionsSetDistance(JxlEncoderOptions* options, float distance); * JxlEncoderOptions object is still valid and is the same as before this * function was called. */ -JXL_EXPORT JxlEncoderStatus JxlEncoderOptionsSetAsInteger( +JXL_EXPORT JxlEncoderStatus JxlEncoderOptionsSetInteger( JxlEncoderOptions* options, JxlEncoderOptionId option, int32_t value); +/** Indicates the encoder should use the box-based JPEG XL container format + * (BMFF) instead of outputting the codestream bytes directly. Both with and + * without container are valid JPEG XL files, but the container is necessary + * when metadata, level 10 features or JPEG reconstruction is used. + * + * If enabled, the encoder always uses the container format, even if not + * necessary. If disabled, the encoder will still use the container format if + * required (such as for JPEG metadata @ref JxlEncoderStoreJPEGMetadata). + * + * This setting must be explicitely enabled before using @ref JxlEncoderAddBox. + * + * By default this setting is disabled. + * + * This setting can only be set at the beginning, before encoding starts. + * + * @param enc encoder object. + * @param use_container true if the encoder should always output the JPEG XL + * container format. + * @return JXL_ENC_SUCCESS if the operation was successful, JXL_ENC_ERROR + * otherwise. + */ +JXL_EXPORT JxlEncoderStatus JxlEncoderUseContainer(JxlEncoder* enc, + JXL_BOOL use_container); + +/** + * Configure the encoder to store JPEG reconstruction metadata in the JPEG XL + * container. + * + * If this is set to true and a single JPEG frame is added, it will be + * possible to losslessly reconstruct the JPEG codestream. + * + * This setting can only be set at the beginning, before encoding starts. + * + * @param enc encoder object. + * @param store_jpeg_metadata true if the encoder should store JPEG metadata. + * @return JXL_ENC_SUCCESS if the operation was successful, JXL_ENC_ERROR + * otherwise. + */ +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. + * + * 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 + * level 10 feature is absolutely necessary. Supports a maximum resolution + * 268435456 pixels total with a maximum width or height of 262144 pixels, + * maximum 16-bit color channel depth, maximum 120 frames per second for + * animation, maximum ICC color profile size of 4 MiB, it allows all color + * models and extra channel types except CMYK and the JXL_CHANNEL_BLACK extra + * channel, and a maximum of 4 extra channels in addition to the 3 color + * channels. It also sets boundaries to certain internally used coding tools. + * + * Level 10: this level removes or increases the bounds of most of the level + * 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. + * + * This setting can only be set at the beginning, before encoding starts. + */ +JXL_EXPORT JxlEncoderStatus JxlEncoderSetCodestreamLevel(JxlEncoder* enc, + int level); + +/** + * Enables lossless encoding. + * + * This is not an option like the others on itself, but rather while enabled it + * overrides a set of existing options (such as distance and modular mode) that + * enables bit-for-bit lossless encoding. + * + * When disabled, those options are not overridden, but since those options + * could still have been manually set to a combination that operates losslessly, + * using this function with lossless set to JXL_DEC_FALSE does not guarantee + * lossy encoding, though the default set of options is lossy. + * + * @param options set of encoder options to update with the new mode + * @param lossless whether to override options for lossless mode + * @return JXL_ENC_SUCCESS if the operation was successful, JXL_ENC_ERROR + * otherwise. + */ +JXL_EXPORT JxlEncoderStatus +JxlEncoderOptionsSetLossless(JxlEncoderOptions* options, JXL_BOOL lossless); + +/** + * @param options set of encoder options to update with the new mode. + * @param effort the effort value to set. + * @return JXL_ENC_SUCCESS if the operation was successful, JXL_ENC_ERROR + * otherwise. + * + * DEPRECATED: use JxlEncoderOptionsSetInteger(options, JXL_ENC_OPTION_EFFORT, + * effort)) instead. + */ +JXL_EXPORT JXL_DEPRECATED JxlEncoderStatus +JxlEncoderOptionsSetEffort(JxlEncoderOptions* options, int effort); + +/** + * @param options set of encoder options to update with the new decoding speed + * tier. + * @param tier the decoding speed tier to set. + * @return JXL_ENC_SUCCESS if the operation was successful, JXL_ENC_ERROR + * otherwise. + * + * DEPRECATED: use JxlEncoderOptionsSetInteger(options, + * JXL_ENC_OPTION_DECODING_SPEED, tier)) instead. + */ +JXL_EXPORT JXL_DEPRECATED JxlEncoderStatus +JxlEncoderOptionsSetDecodingSpeed(JxlEncoderOptions* options, int tier); + +/** + * Sets the distance level for lossy compression: target max butteraugli + * distance, lower = higher quality. Range: 0 .. 15. + * 0.0 = mathematically lossless (however, use JxlEncoderOptionsSetLossless + * instead to use true lossless, as setting distance to 0 alone is not the only + * requirement). 1.0 = visually lossless. Recommended range: 0.5 .. 3.0. Default + * value: 1.0. + * + * @param options set of encoder options to update with the new mode. + * @param distance the distance value to set. + * @return JXL_ENC_SUCCESS if the operation was successful, JXL_ENC_ERROR + * otherwise. + */ +JXL_EXPORT JxlEncoderStatus +JxlEncoderOptionsSetDistance(JxlEncoderOptions* options, float distance); + /** * Create a new set of encoder options, with all values initially copied from * the @p source options, or set to default if @p source is NULL. diff --git a/third_party/jpeg-xl/lib/include/jxl/memory_manager.h b/third_party/jpeg-xl/lib/include/jxl/memory_manager.h index 4f3382a6d0df..52640a8beba5 100644 --- a/third_party/jpeg-xl/lib/include/jxl/memory_manager.h +++ b/third_party/jpeg-xl/lib/include/jxl/memory_manager.h @@ -27,8 +27,8 @@ extern "C" { * * @param opaque custom memory manager handle provided by the caller. * @param size in bytes of the requested memory region. - * @returns @c NULL if the memory can not be allocated, - * @returns pointer to the memory otherwise. + * @return @c NULL if the memory can not be allocated, + * @return pointer to the memory otherwise. */ typedef void* (*jpegxl_alloc_func)(void* opaque, size_t size); diff --git a/third_party/jpeg-xl/lib/include/jxl/parallel_runner.h b/third_party/jpeg-xl/lib/include/jxl/parallel_runner.h index 88b97254c341..45394e972c56 100644 --- a/third_party/jpeg-xl/lib/include/jxl/parallel_runner.h +++ b/third_party/jpeg-xl/lib/include/jxl/parallel_runner.h @@ -70,8 +70,8 @@ typedef int JxlParallelRetCode; * JxlParallelRunner() must be passed here. * @param num_threads the maximum number of threads. This value must be * positive. - * @returns 0 if the initialization process was successful. - * @returns an error code if there was an error, which should be returned by + * @return 0 if the initialization process was successful. + * @return an error code if there was an error, which should be returned by * JxlParallelRunner(). */ typedef JxlParallelRetCode (*JxlParallelRunInit)(void* jpegxl_opaque, @@ -110,9 +110,9 @@ typedef void (*JxlParallelRunFunction)(void* jpegxl_opaque, uint32_t value, * or encoding instance may call the provided JxlParallelRunner multiple * times for different parts of the decoding or encoding process. * - * @returns 0 if the @p init call succeeded (returned 0) and no other error + * @return 0 if the @p init call succeeded (returned 0) and no other error * occurred in the runner code. - * @returns JXL_PARALLEL_RET_RUNNER_ERROR if an error occurred in the runner + * @return JXL_PARALLEL_RET_RUNNER_ERROR if an error occurred in the runner * code, for example, setting up the threads. * @return the return value of @p init() if non-zero. */ diff --git a/third_party/jpeg-xl/lib/jxl.cmake b/third_party/jpeg-xl/lib/jxl.cmake index c2011060b91a..83f071582551 100644 --- a/third_party/jpeg-xl/lib/jxl.cmake +++ b/third_party/jpeg-xl/lib/jxl.cmake @@ -35,6 +35,7 @@ set(JPEGXL_INTERNAL_SOURCES_DEC jxl/base/override.h jxl/base/padded_bytes.cc jxl/base/padded_bytes.h + jxl/base/printf_macros.h jxl/base/profiler.h jxl/base/random.cc jxl/base/random.h diff --git a/third_party/jpeg-xl/lib/jxl/aux_out.cc b/third_party/jpeg-xl/lib/jxl/aux_out.cc index 523c016c8a2b..00e03216ae4c 100644 --- a/third_party/jpeg-xl/lib/jxl/aux_out.cc +++ b/third_party/jpeg-xl/lib/jxl/aux_out.cc @@ -10,6 +10,7 @@ #include // accumulate #include "lib/jxl/aux_out_fwd.h" +#include "lib/jxl/base/printf_macros.h" #include "lib/jxl/enc_bit_writer.h" namespace jxl { diff --git a/third_party/jpeg-xl/lib/jxl/aux_out.h b/third_party/jpeg-xl/lib/jxl/aux_out.h index 0d79b88265a9..7fe812d3c677 100644 --- a/third_party/jpeg-xl/lib/jxl/aux_out.h +++ b/third_party/jpeg-xl/lib/jxl/aux_out.h @@ -8,6 +8,7 @@ // Optional output information for debugging and analyzing size usage. +#include #include #include #include @@ -103,7 +104,7 @@ static inline const char* LayerName(size_t layer) { case kLayerExtraChannels: return "extra channels"; default: - JXL_ABORT("Invalid layer %" PRIuS "\n", layer); + JXL_ABORT("Invalid layer %d\n", static_cast(layer)); } } @@ -119,12 +120,13 @@ struct AuxOut { clustered_entropy += victim.clustered_entropy; } void Print(size_t num_inputs) const { - printf("%10" PRIdS, total_bits); + printf("%10" PRId64, static_cast(total_bits)); if (histogram_bits != 0) { - printf(" [c/i:%6.2f | hst:%8" PRIdS " | ex:%8" PRIdS + printf(" [c/i:%6.2f | hst:%8" PRId64 " | ex:%8" PRId64 " | h+c+e:%12.3f", - num_clustered_histograms * 1.0 / num_inputs, histogram_bits >> 3, - extra_bits >> 3, + num_clustered_histograms * 1.0 / num_inputs, + static_cast(histogram_bits >> 3), + static_cast(extra_bits >> 3), (histogram_bits + clustered_entropy + extra_bits) / 8.0); printf("]"); } diff --git a/third_party/jpeg-xl/lib/jxl/base/cache_aligned.cc b/third_party/jpeg-xl/lib/jxl/base/cache_aligned.cc index e296e58c0269..411d41452109 100644 --- a/third_party/jpeg-xl/lib/jxl/base/cache_aligned.cc +++ b/third_party/jpeg-xl/lib/jxl/base/cache_aligned.cc @@ -20,8 +20,8 @@ #include // kMaxVectorSize #include +#include "lib/jxl/base/printf_macros.h" #include "lib/jxl/base/status.h" -#include "lib/jxl/common.h" namespace jxl { namespace { diff --git a/third_party/jpeg-xl/lib/jxl/base/compiler_specific.h b/third_party/jpeg-xl/lib/jxl/base/compiler_specific.h index b279fa0c82f6..88eb4b096976 100644 --- a/third_party/jpeg-xl/lib/jxl/base/compiler_specific.h +++ b/third_party/jpeg-xl/lib/jxl/base/compiler_specific.h @@ -55,6 +55,8 @@ #define JXL_NORETURN __declspec(noreturn) #elif JXL_COMPILER_GCC || JXL_COMPILER_CLANG #define JXL_NORETURN __attribute__((noreturn)) +#else +#define JXL_NORETURN #endif #if JXL_COMPILER_MSVC diff --git a/third_party/jpeg-xl/lib/jxl/base/printf_macros.h b/third_party/jpeg-xl/lib/jxl/base/printf_macros.h new file mode 100644 index 000000000000..3215052afdec --- /dev/null +++ b/third_party/jpeg-xl/lib/jxl/base/printf_macros.h @@ -0,0 +1,34 @@ +// 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_BASE_PRINTF_MACROS_H_ +#define LIB_JXL_BASE_PRINTF_MACROS_H_ + +// Format string macros. These should be included after any other system +// library since those may unconditionally define these, depending on the +// platform. + +// PRIuS and PRIdS macros to print size_t and ssize_t respectively. +#if !defined(PRIdS) +#if defined(_WIN64) +#define PRIdS "lld" +#elif defined(_WIN32) +#define PRIdS "d" +#else +#define PRIdS "zd" +#endif +#endif // PRIdS + +#if !defined(PRIuS) +#if defined(_WIN64) +#define PRIuS "llu" +#elif defined(_WIN32) +#define PRIuS "u" +#else +#define PRIuS "zu" +#endif +#endif // PRIuS + +#endif // LIB_JXL_BASE_PRINTF_MACROS_H_ diff --git a/third_party/jpeg-xl/lib/jxl/blending.cc b/third_party/jpeg-xl/lib/jxl/blending.cc index b41983e6a8fe..3186830f3dee 100644 --- a/third_party/jpeg-xl/lib/jxl/blending.cc +++ b/third_party/jpeg-xl/lib/jxl/blending.cc @@ -6,6 +6,7 @@ #include "lib/jxl/blending.h" #include "lib/jxl/alpha.h" +#include "lib/jxl/base/printf_macros.h" #include "lib/jxl/image_ops.h" namespace jxl { diff --git a/third_party/jpeg-xl/lib/jxl/box_content_decoder.cc b/third_party/jpeg-xl/lib/jxl/box_content_decoder.cc index 262dac539444..c4cba3a31aee 100644 --- a/third_party/jpeg-xl/lib/jxl/box_content_decoder.cc +++ b/third_party/jpeg-xl/lib/jxl/box_content_decoder.cc @@ -83,7 +83,6 @@ JxlDecoderStatus JxlBoxContentDecoder::Process(const uint8_t* next_in, size_t can_read = avail_in; if (!box_until_eof_) can_read = std::min(can_read, remaining_); size_t to_write = std::min(can_read, *avail_out); - memcpy(*next_out, next_in, to_write); *next_out += to_write; diff --git a/third_party/jpeg-xl/lib/jxl/butteraugli/butteraugli.cc b/third_party/jpeg-xl/lib/jxl/butteraugli/butteraugli.cc index 55f171a57a99..c08dd0536c43 100644 --- a/third_party/jpeg-xl/lib/jxl/butteraugli/butteraugli.cc +++ b/third_party/jpeg-xl/lib/jxl/butteraugli/butteraugli.cc @@ -41,6 +41,7 @@ #define HWY_TARGET_INCLUDE "lib/jxl/butteraugli/butteraugli.cc" #include +#include "lib/jxl/base/printf_macros.h" #include "lib/jxl/base/profiler.h" #include "lib/jxl/base/status.h" #include "lib/jxl/convolve.h" @@ -1136,7 +1137,7 @@ void Mask(const ImageF& mask0, const ImageF& mask1, FuzzyErosion(blurred1, &diff1); for (size_t y = 0; y < ysize; ++y) { for (size_t x = 0; x < xsize; ++x) { - mask->Row(y)[x] = diff1.Row(y)[x]; + mask->Row(y)[x] = diff0.Row(y)[x]; if (diff_ac != nullptr) { static const float kMaskToErrorMul = 10.0; float diff = blurred0.Row(y)[x] - blurred1.Row(y)[x]; @@ -1796,9 +1797,9 @@ bool ButteraugliDiffmap(const Image3F& rgb0, const Image3F& rgb1, for (size_t y = 0; y < yscaled; ++y) { for (size_t x = 0; x < xscaled; ++x) { size_t x2 = - std::min(xsize - 1, std::max(0, x - xborder)); + std::min(xsize - 1, x > xborder ? x - xborder : 0); size_t y2 = - std::min(ysize - 1, std::max(0, y - yborder)); + std::min(ysize - 1, y > yborder ? y - yborder : 0); scaled0.PlaneRow(i, y)[x] = rgb0.PlaneRow(i, y2)[x2]; scaled1.PlaneRow(i, y)[x] = rgb1.PlaneRow(i, y2)[x2]; } diff --git a/third_party/jpeg-xl/lib/jxl/coeff_order_test.cc b/third_party/jpeg-xl/lib/jxl/coeff_order_test.cc index dc56d53f136b..2a226c3c3520 100644 --- a/third_party/jpeg-xl/lib/jxl/coeff_order_test.cc +++ b/third_party/jpeg-xl/lib/jxl/coeff_order_test.cc @@ -13,6 +13,7 @@ #include #include "gtest/gtest.h" +#include "lib/jxl/base/printf_macros.h" #include "lib/jxl/base/random.h" #include "lib/jxl/base/span.h" #include "lib/jxl/coeff_order_fwd.h" diff --git a/third_party/jpeg-xl/lib/jxl/common.h b/third_party/jpeg-xl/lib/jxl/common.h index 1fa6e2349267..98c98d338746 100644 --- a/third_party/jpeg-xl/lib/jxl/common.h +++ b/third_party/jpeg-xl/lib/jxl/common.h @@ -27,27 +27,6 @@ #define JPEGXL_ENABLE_TRANSCODE_JPEG 1 #endif // JPEGXL_ENABLE_TRANSCODE_JPEG -// PRIuS and PRIdS macros to print size_t and ssize_t respectively. -#if !defined(PRIdS) -#if defined(_WIN64) -#define PRIdS "lld" -#elif defined(_WIN32) -#define PRIdS "d" -#else -#define PRIdS "zd" -#endif -#endif // PRIdS - -#if !defined(PRIuS) -#if defined(_WIN64) -#define PRIuS "llu" -#elif defined(_WIN32) -#define PRIuS "u" -#else -#define PRIuS "zu" -#endif -#endif // PRIuS - namespace jxl { // Some enums and typedefs used by more than one header file. diff --git a/third_party/jpeg-xl/lib/jxl/convolve_test.cc b/third_party/jpeg-xl/lib/jxl/convolve_test.cc index aec8767177ed..f9191f088615 100644 --- a/third_party/jpeg-xl/lib/jxl/convolve_test.cc +++ b/third_party/jpeg-xl/lib/jxl/convolve_test.cc @@ -17,6 +17,7 @@ #include "lib/jxl/base/compiler_specific.h" #include "lib/jxl/base/data_parallel.h" +#include "lib/jxl/base/printf_macros.h" #include "lib/jxl/base/thread_pool_internal.h" #include "lib/jxl/image_ops.h" #include "lib/jxl/image_test_utils.h" diff --git a/third_party/jpeg-xl/lib/jxl/dct-inl.h b/third_party/jpeg-xl/lib/jxl/dct-inl.h index ecc3935a5d79..53f60e09b1b4 100644 --- a/third_party/jpeg-xl/lib/jxl/dct-inl.h +++ b/third_party/jpeg-xl/lib/jxl/dct-inl.h @@ -273,42 +273,8 @@ struct IDCT1D MaxLanes(FV<0>()))>::type> { } }; -// Computes the in-place NxN transposed-scaled-DCT (tsDCT) of block. -// Requires that block is HWY_ALIGN'ed. -// -// See also DCTSlow, ComputeDCT -template -struct ComputeTransposedScaledDCT { - // scratch_space must be aligned, and should have space for N*N floats. - template - HWY_MAYBE_UNUSED void operator()(const From& from, float* JXL_RESTRICT to, - float* JXL_RESTRICT scratch_space) { - float* JXL_RESTRICT block = scratch_space; - DCT1D()(from, DCTTo(to, N)); - Transpose::Run(DCTFrom(to, N), DCTTo(block, N)); - DCT1D()(DCTFrom(block, N), DCTTo(to, N)); - } -}; - -// Computes the in-place NxN transposed-scaled-iDCT (tsIDCT)of block. -// Requires that block is HWY_ALIGN'ed. -// -// See also IDCTSlow, ComputeIDCT. - -template -struct ComputeTransposedScaledIDCT { - // scratch_space must be aligned, and should have space for N*N floats. - template - HWY_MAYBE_UNUSED void operator()(float* JXL_RESTRICT from, const To& to, - float* JXL_RESTRICT scratch_space) { - float* JXL_RESTRICT block = scratch_space; - IDCT1D()(DCTFrom(from, N), DCTTo(block, N)); - Transpose::Run(DCTFrom(block, N), DCTTo(from, N)); - IDCT1D()(DCTFrom(from, N), to); - } -}; -// Computes the non-transposed, scaled DCT of a block, that needs to be -// HWY_ALIGN'ed. Used for rectangular blocks. +// Computes the maybe-transposed, scaled DCT of a block, that needs to be +// HWY_ALIGN'ed. template struct ComputeScaledDCT { // scratch_space must be aligned, and should have space for ROWS*COLS @@ -329,8 +295,8 @@ struct ComputeScaledDCT { } } }; -// Computes the non-transposed, scaled DCT of a block, that needs to be -// HWY_ALIGN'ed. Used for rectangular blocks. +// Computes the maybe-transposed, scaled IDCT of a block, that needs to be +// HWY_ALIGN'ed. template struct ComputeScaledIDCT { // scratch_space must be aligned, and should have space for ROWS*COLS diff --git a/third_party/jpeg-xl/lib/jxl/dct_test.cc b/third_party/jpeg-xl/lib/jxl/dct_test.cc index a51a3178c9be..606d5fefa749 100644 --- a/third_party/jpeg-xl/lib/jxl/dct_test.cc +++ b/third_party/jpeg-xl/lib/jxl/dct_test.cc @@ -35,7 +35,7 @@ template void ComputeDCT(float block[N * N]) { HWY_ALIGN float tmp_block[N * N]; HWY_ALIGN float scratch_space[N * N]; - ComputeTransposedScaledDCT()(DCTFrom(block, N), tmp_block, scratch_space); + ComputeScaledDCT()(DCTFrom(block, N), tmp_block, scratch_space); // Untranspose. Transpose::Run(DCTFrom(tmp_block, N), DCTTo(block, N)); @@ -50,7 +50,7 @@ void ComputeIDCT(float block[N * N]) { // Untranspose. Transpose::Run(DCTFrom(block, N), DCTTo(tmp_block, N)); - ComputeTransposedScaledIDCT()(tmp_block, DCTTo(block, N), scratch_space); + ComputeScaledIDCT()(tmp_block, DCTTo(block, N), scratch_space); } template diff --git a/third_party/jpeg-xl/lib/jxl/dec_ans.cc b/third_party/jpeg-xl/lib/jxl/dec_ans.cc index a31de56c4499..8c082b9e76e7 100644 --- a/third_party/jpeg-xl/lib/jxl/dec_ans.cc +++ b/third_party/jpeg-xl/lib/jxl/dec_ans.cc @@ -12,6 +12,7 @@ #include "lib/jxl/ans_common.h" #include "lib/jxl/ans_params.h" #include "lib/jxl/base/bits.h" +#include "lib/jxl/base/printf_macros.h" #include "lib/jxl/base/profiler.h" #include "lib/jxl/base/status.h" #include "lib/jxl/common.h" diff --git a/third_party/jpeg-xl/lib/jxl/dec_cache.cc b/third_party/jpeg-xl/lib/jxl/dec_cache.cc index e40a97fcb97f..0bd2a0ac39cf 100644 --- a/third_party/jpeg-xl/lib/jxl/dec_cache.cc +++ b/third_party/jpeg-xl/lib/jxl/dec_cache.cc @@ -87,18 +87,20 @@ void LoadBorders(const Rect& block_rect, size_t hshift, size_t vshift, // Limits of the area to copy from, in image coordinates. JXL_DASSERT(r.x0() == 0 || r.x0() >= borderx); size_t x0src = DivCeil(r.x0() == 0 ? r.x0() : r.x0() - borderx, 1 << hshift); + // r may be such that r.x1 (namely x0() + xsize()) is within borderx of the + // right side of the image, so we use min() here. size_t x1src = - DivCeil(r.x0() + r.xsize() + - (r.x0() + r.xsize() == frame_dim.xsize_padded ? 0 : borderx), + DivCeil(std::min(r.x0() + r.xsize() + borderx, frame_dim.xsize_padded), 1 << hshift); JXL_DASSERT(r.y0() == 0 || r.y0() >= bordery); size_t y0src = DivCeil(r.y0() == 0 ? r.y0() : r.y0() - bordery, 1 << vshift); + // Similar to x1, y1 might be closer than bordery from the bottom. size_t y1src = - DivCeil(r.y0() + r.ysize() + - (r.y0() + r.ysize() == frame_dim.ysize_padded ? 0 : bordery), + DivCeil(std::min(r.y0() + r.ysize() + bordery, frame_dim.ysize_padded), 1 << vshift); // Copy other groups' borders from the border storage. if (y0src < y0) { + JXL_DASSERT(gy > 0); CopyImageTo( Rect(x0src, (gy * 2 - 1) * bordery_write, x1src - x0src, bordery_write), border_storage_h, @@ -107,6 +109,8 @@ void LoadBorders(const Rect& block_rect, size_t hshift, size_t vshift, plane_out); } if (y1src > y1) { + // When copying the bottom border we must not be on the bottom groups. + JXL_DASSERT(gy + 1 < frame_dim.ysize_groups); CopyImageTo( Rect(x0src, (gy * 2 + 2) * bordery_write, x1src - x0src, bordery_write), border_storage_h, @@ -115,6 +119,7 @@ void LoadBorders(const Rect& block_rect, size_t hshift, size_t vshift, plane_out); } if (x0src < x0) { + JXL_DASSERT(gx > 0); CopyImageTo( Rect((gx * 2 - 1) * borderx_write, y0src, borderx_write, y1src - y0src), border_storage_v, @@ -123,6 +128,8 @@ void LoadBorders(const Rect& block_rect, size_t hshift, size_t vshift, plane_out); } if (x1src > x1) { + // When copying the right border we must not be on the rightmost groups. + JXL_DASSERT(gx + 1 < frame_dim.xsize_groups); CopyImageTo( Rect((gx * 2 + 2) * borderx_write, y0src, borderx_write, y1src - y0src), border_storage_v, diff --git a/third_party/jpeg-xl/lib/jxl/dec_external_image.cc b/third_party/jpeg-xl/lib/jxl/dec_external_image.cc index 6f3ccf091dad..b4765dfc060c 100644 --- a/third_party/jpeg-xl/lib/jxl/dec_external_image.cc +++ b/third_party/jpeg-xl/lib/jxl/dec_external_image.cc @@ -22,6 +22,7 @@ #include "lib/jxl/base/byte_order.h" #include "lib/jxl/base/cache_aligned.h" #include "lib/jxl/base/compiler_specific.h" +#include "lib/jxl/base/printf_macros.h" #include "lib/jxl/color_management.h" #include "lib/jxl/common.h" #include "lib/jxl/sanitizers.h" diff --git a/third_party/jpeg-xl/lib/jxl/dec_file.cc b/third_party/jpeg-xl/lib/jxl/dec_file.cc index 2ee1f66ffd43..841e223c4ec4 100644 --- a/third_party/jpeg-xl/lib/jxl/dec_file.cc +++ b/third_party/jpeg-xl/lib/jxl/dec_file.cc @@ -147,6 +147,7 @@ Status DecodeFile(const DecompressParams& dparams, 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(), @@ -155,13 +156,19 @@ Status DecodeFile(const DecompressParams& dparams, 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); - io->dec_pixels += io->frames.back().xsize() * io->frames.back().ysize(); + 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."); diff --git a/third_party/jpeg-xl/lib/jxl/dec_frame.cc b/third_party/jpeg-xl/lib/jxl/dec_frame.cc index 9ce100c47461..ababa43c4f43 100644 --- a/third_party/jpeg-xl/lib/jxl/dec_frame.cc +++ b/third_party/jpeg-xl/lib/jxl/dec_frame.cc @@ -21,6 +21,7 @@ #include "lib/jxl/base/bits.h" #include "lib/jxl/base/compiler_specific.h" #include "lib/jxl/base/data_parallel.h" +#include "lib/jxl/base/printf_macros.h" #include "lib/jxl/base/profiler.h" #include "lib/jxl/base/status.h" #include "lib/jxl/chroma_from_luma.h" @@ -541,11 +542,13 @@ Status FrameDecoder::ProcessACGlobal(BitReader* br) { size_t num_components = jpeg_data->components.size(); bool is_gray = (num_components == 1); auto jpeg_c_map = JpegOrder(frame_header_.color_transform, is_gray); + size_t qt_set = 0; for (size_t c = 0; c < num_components; c++) { // TODO(eustas): why 1-st quant table for gray? size_t quant_c = is_gray ? 1 : c; size_t qpos = jpeg_data->components[jpeg_c_map[c]].quant_idx; JXL_CHECK(qpos != jpeg_data->quant.size()); + qt_set |= 1 << qpos; for (size_t x = 0; x < 8; x++) { for (size_t y = 0; y < 8; y++) { jpeg_data->quant[qpos].values[x * 8 + y] = @@ -553,6 +556,14 @@ Status FrameDecoder::ProcessACGlobal(BitReader* br) { } } } + for (size_t i = 0; i < jpeg_data->quant.size(); i++) { + if (qt_set & (1 << i)) continue; + if (i == 0) return JXL_FAILURE("First quant table unused."); + // Unused quant table is set to copy of previous quant table + for (size_t j = 0; j < 64; j++) { + jpeg_data->quant[i].values[j] = jpeg_data->quant[i - 1].values[j]; + } + } } // Set memory buffer for pre-color-transform frame, if needed. if (frame_header_.needs_color_transform() && diff --git a/third_party/jpeg-xl/lib/jxl/dec_group.cc b/third_party/jpeg-xl/lib/jxl/dec_group.cc index 086ff1b5e677..d4abcb84e010 100644 --- a/third_party/jpeg-xl/lib/jxl/dec_group.cc +++ b/third_party/jpeg-xl/lib/jxl/dec_group.cc @@ -23,6 +23,7 @@ #include "lib/jxl/ac_strategy.h" #include "lib/jxl/aux_out.h" #include "lib/jxl/base/bits.h" +#include "lib/jxl/base/printf_macros.h" #include "lib/jxl/base/profiler.h" #include "lib/jxl/base/status.h" #include "lib/jxl/coeff_order.h" diff --git a/third_party/jpeg-xl/lib/jxl/dec_modular.cc b/third_party/jpeg-xl/lib/jxl/dec_modular.cc index a23fa4140e2b..32303e044026 100644 --- a/third_party/jpeg-xl/lib/jxl/dec_modular.cc +++ b/third_party/jpeg-xl/lib/jxl/dec_modular.cc @@ -18,6 +18,7 @@ #include "lib/jxl/alpha.h" #include "lib/jxl/base/compiler_specific.h" +#include "lib/jxl/base/printf_macros.h" #include "lib/jxl/base/span.h" #include "lib/jxl/base/status.h" #include "lib/jxl/compressed_dc.h" @@ -158,17 +159,18 @@ Status ModularFrameDecoder::DecodeGlobalInfo(BitReader* reader, if (is_gray && frame_header.color_transform == ColorTransform::kNone) { nb_chans = 1; } + do_color = decode_color; + if (!do_color) nb_chans = 0; + size_t nb_extra = metadata.extra_channel_info.size(); bool has_tree = reader->ReadBits(1); if (has_tree) { - size_t tree_size_limit = - 1024 + frame_dim.xsize * frame_dim.ysize * nb_chans / 16; + size_t tree_size_limit = std::min( + static_cast(1 << 22), + 1024 + frame_dim.xsize * frame_dim.ysize * (nb_chans + nb_extra) / 16); JXL_RETURN_IF_ERROR(DecodeTree(reader, &tree, tree_size_limit)); JXL_RETURN_IF_ERROR( DecodeHistograms(reader, (tree.size() + 1) / 2, &code, &context_map)); } - do_color = decode_color; - if (!do_color) nb_chans = 0; - size_t nb_extra = metadata.extra_channel_info.size(); bool fp = metadata.bit_depth.floating_point_sample; diff --git a/third_party/jpeg-xl/lib/jxl/dec_patch_dictionary.cc b/third_party/jpeg-xl/lib/jxl/dec_patch_dictionary.cc index 043a21add7c6..483ed41ea66c 100644 --- a/third_party/jpeg-xl/lib/jxl/dec_patch_dictionary.cc +++ b/third_party/jpeg-xl/lib/jxl/dec_patch_dictionary.cc @@ -18,6 +18,7 @@ #include "lib/jxl/ans_params.h" #include "lib/jxl/base/compiler_specific.h" #include "lib/jxl/base/override.h" +#include "lib/jxl/base/printf_macros.h" #include "lib/jxl/base/status.h" #include "lib/jxl/blending.h" #include "lib/jxl/chroma_from_luma.h" diff --git a/third_party/jpeg-xl/lib/jxl/dec_reconstruct.cc b/third_party/jpeg-xl/lib/jxl/dec_reconstruct.cc index 8d5467b87512..9d1eb6102820 100644 --- a/third_party/jpeg-xl/lib/jxl/dec_reconstruct.cc +++ b/third_party/jpeg-xl/lib/jxl/dec_reconstruct.cc @@ -140,7 +140,7 @@ Status UndoXYBInPlace(Image3F* idct, const Rect& rect, DoUndoXYBInPlace(idct, rect, Op709(), output_encoding_info); } else if (output_encoding_info.color_encoding.tf.IsGamma() || output_encoding_info.color_encoding.tf.IsDCI()) { - OpGamma op = {output_encoding_info.inverse_gamma}; + OpGamma op{output_encoding_info.inverse_gamma}; DoUndoXYBInPlace(idct, rect, op, output_encoding_info); } else { // This is a programming error. @@ -418,10 +418,13 @@ HWY_EXPORT(DoYCbCrUpsampling); void UndoXYB(const Image3F& src, Image3F* dst, const OutputEncodingInfo& output_info, ThreadPool* pool) { CopyImageTo(src, dst); - pool->Run(0, src.ysize(), ThreadPool::SkipInit(), [&](int y, int /*thread*/) { - JXL_CHECK(HWY_DYNAMIC_DISPATCH(UndoXYBInPlace)(dst, Rect(*dst).Line(y), - output_info)); - }); + RunOnPool( + pool, 0, src.ysize(), ThreadPool::SkipInit(), + [&](int y, int /*thread*/) { + JXL_CHECK(HWY_DYNAMIC_DISPATCH(UndoXYBInPlace)(dst, Rect(*dst).Line(y), + output_info)); + }, + "UndoXYB"); } namespace { @@ -441,7 +444,9 @@ class EnsurePaddingInPlaceRowByRow { size_t image_ysize, size_t xpadding, size_t ypadding, ssize_t* y0, ssize_t* y1) { // coordinates relative to rect. - JXL_DASSERT(SameSize(rect, image_rect)); + JXL_ASSERT(SameSize(rect, image_rect)); + JXL_ASSERT(image_rect.x0() + image_rect.xsize() <= image_xsize); + JXL_ASSERT(image_rect.y0() + image_rect.ysize() <= image_ysize); *y0 = -std::min(image_rect.y0(), ypadding); *y1 = rect.ysize() + std::min(ypadding, image_ysize - image_rect.ysize() - image_rect.y0()); @@ -455,7 +460,7 @@ class EnsurePaddingInPlaceRowByRow { strategy_ = kSlow; } y0_ = rect.y0(); - JXL_DASSERT(rect.x0() >= xpadding); + JXL_ASSERT(rect.x0() >= xpadding); x0_ = x1_ = rect.x0() - xpadding; // If close to the left border - do mirroring. if (image_rect.x0() < xpadding) x1_ = rect.x0() - image_rect.x0(); @@ -464,8 +469,11 @@ class EnsurePaddingInPlaceRowByRow { if (image_rect.x0() + image_rect.xsize() + xpadding > image_xsize) { x2_ = rect.x0() + image_xsize - image_rect.x0(); } - JXL_DASSERT(image_xsize == (x2_ - x1_) || - (x1_ - x0_ <= x2_ - x1_ && x3_ - x2_ <= x2_ - x1_)); + JXL_ASSERT(x0_ <= x1_); + JXL_ASSERT(x1_ <= x2_); + JXL_ASSERT(x2_ <= x3_); + JXL_ASSERT(image_xsize == (x2_ - x1_) || + (x1_ - x0_ <= x2_ - x1_ && x3_ - x2_ <= x2_ - x1_)); } public: @@ -793,7 +801,9 @@ Status FinalizeImageRect( } ssize_t ensure_padding_y0, ensure_padding_y1; EnsurePaddingInPlaceRowByRow ensure_padding; - Rect ec_image_rect = ScaleRectForEC(frame_rect, frame_header, ec); + // frame_rect can go up to frame_dim.xsize_padded, in VarDCT mode. + Rect ec_image_rect = ScaleRectForEC( + frame_rect.Crop(frame_dim.xsize, frame_dim.ysize), frame_header, ec); size_t ecxs = DivCeil(frame_dim.xsize_upsampled, frame_header.extra_channel_upsampling[ec]); size_t ecys = DivCeil(frame_dim.ysize_upsampled, @@ -836,8 +846,10 @@ Status FinalizeImageRect( extra_channels[ec].second.ysize() + rect_for_if_storage.ysize() - rect_for_upsampling.ysize()); extra_channels_for_patches.emplace_back(extra_channels[ec].first, r); + // frame_rect can go up to frame_dim.xsize_padded, in VarDCT mode. ec_padding[ec].Init(extra_channels[ec].first, extra_channels[ec].second, - frame_rect, frame_dim.xsize, frame_dim.ysize, 2, 2, + frame_rect.Crop(frame_dim.xsize, frame_dim.ysize), + frame_dim.xsize, frame_dim.ysize, 2, 2, &ensure_padding_upsampling_ec_y0, &ensure_padding_upsampling_ec_y1); } @@ -1048,7 +1060,8 @@ Status FinalizeImageRect( dec_state->rgb_output_is_rgba, alpha, alpha_rect.Lines(available_y, num_ys), upsampled_frame_rect.Lines(available_y, num_ys) - .Crop(Rect(0, 0, frame_dim.xsize, frame_dim.ysize)), + .Crop(Rect(0, 0, frame_dim.xsize_upsampled, + frame_dim.ysize_upsampled)), dec_state->rgb_output, dec_state->rgb_stride); } if (dec_state->pixel_callback != nullptr) { @@ -1157,7 +1170,9 @@ Status FinalizeFrameDecoding(ImageBundle* decoded, std::vector> ec_rects; ec_rects.reserve(decoded->extra_channels().size()); for (size_t i = 0; i < decoded->extra_channels().size(); i++) { - Rect r = ScaleRectForEC(rects_to_process[rect_id], frame_header, i); + Rect r = ScaleRectForEC( + rects_to_process[rect_id].Crop(frame_dim.xsize, frame_dim.ysize), + frame_header, i); if (frame_header.extra_channel_upsampling[i] != 1) { Rect ec_input_rect(kBlockDim, 2, r.xsize(), r.ysize()); auto eti = diff --git a/third_party/jpeg-xl/lib/jxl/dec_reconstruct_gbench.cc b/third_party/jpeg-xl/lib/jxl/dec_reconstruct_gbench.cc new file mode 100644 index 000000000000..1eba9ba8555b --- /dev/null +++ b/third_party/jpeg-xl/lib/jxl/dec_reconstruct_gbench.cc @@ -0,0 +1,46 @@ +// 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 "benchmark/benchmark.h" +#include "lib/jxl/dec_reconstruct.h" +#include "lib/jxl/image_ops.h" + +namespace jxl { + +static void BM_UndoXYB(benchmark::State& state, TransferFunction tf) { + const size_t xsize = state.range(); + const size_t ysize = xsize; + Image3F src(xsize, ysize); + Image3F dst(xsize, ysize); + FillImage(1.f, &src); + + OutputEncodingInfo output_info = {}; + CodecMetadata metadata = {}; + JXL_CHECK(output_info.Set(metadata, ColorEncoding::LinearSRGB(false))); + output_info.inverse_gamma = 1.; + // Set the TransferFunction since the code executed depends on this parameter. + output_info.color_encoding.tf.SetTransferFunction(tf); + + ThreadPool* null_pool = nullptr; + for (auto _ : state) { + UndoXYB(src, &dst, output_info, null_pool); + } + state.SetItemsProcessed(xsize * ysize * state.iterations()); +} + +BENCHMARK_CAPTURE(BM_UndoXYB, Linear, TransferFunction::kLinear) + ->RangeMultiplier(4) + ->Range(512, 2048); +BENCHMARK_CAPTURE(BM_UndoXYB, SRGB, TransferFunction::kSRGB) + ->RangeMultiplier(4) + ->Range(512, 2048); +BENCHMARK_CAPTURE(BM_UndoXYB, PQ, TransferFunction::kPQ) + ->RangeMultiplier(4) + ->Range(512, 2048); +BENCHMARK_CAPTURE(BM_UndoXYB, DCI, TransferFunction::kDCI) + ->RangeMultiplier(4) + ->Range(512, 2048); + +} // namespace jxl diff --git a/third_party/jpeg-xl/lib/jxl/dec_transforms-inl.h b/third_party/jpeg-xl/lib/jxl/dec_transforms-inl.h index c9aebc6b995f..37f3cb7a4d98 100644 --- a/third_party/jpeg-xl/lib/jxl/dec_transforms-inl.h +++ b/third_party/jpeg-xl/lib/jxl/dec_transforms-inl.h @@ -23,24 +23,6 @@ namespace jxl { namespace HWY_NAMESPACE { namespace { -template -struct DoDCT { - template - void operator()(const From& from, float* JXL_RESTRICT to, - float* JXL_RESTRICT scratch_space) { - ComputeScaledDCT()(from, to, scratch_space); - } -}; - -template -struct DoDCT { - template - void operator()(const From& from, float* JXL_RESTRICT to, - float* JXL_RESTRICT scratch_space) { - ComputeTransposedScaledDCT()(from, to, scratch_space); - } -}; - // 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. @@ -56,7 +38,8 @@ JXL_INLINE void ReinterpretingDCT(const float* input, const size_t input_stride, // ROWS, COLS <= 8, so we can put scratch space on the stack. HWY_ALIGN float scratch_space[ROWS * COLS]; - DoDCT()(DCTFrom(input, input_stride), block, scratch_space); + ComputeScaledDCT()(DCTFrom(input, input_stride), block, + scratch_space); if (ROWS < COLS) { for (size_t y = 0; y < LF_ROWS; y++) { for (size_t x = 0; x < LF_COLS; x++) { @@ -447,7 +430,7 @@ void AFVTransformToPixels(const float* JXL_RESTRICT coefficients, block[iy * 4 + ix] = coefficients[iy * 2 * 8 + ix * 2 + 1]; } } - ComputeTransposedScaledIDCT<4>()( + ComputeScaledIDCT<4, 4>()( block, DCTTo(pixels + afv_y * 4 * pixels_stride + (afv_x == 1 ? 0 : 4), pixels_stride), @@ -575,7 +558,7 @@ HWY_MAYBE_UNUSED void TransformToPixels(const AcStrategy::Type strategy, block[iy * 4 + ix] = coefficients[(y + iy * 2) * 8 + x + ix * 2]; } } - ComputeTransposedScaledIDCT<4>()( + ComputeScaledIDCT<4, 4>()( block, DCTTo(pixels + y * 4 * pixels_stride + x * 4, pixels_stride), scratch_space); @@ -599,8 +582,8 @@ HWY_MAYBE_UNUSED void TransformToPixels(const AcStrategy::Type strategy, } case Type::DCT16X16: { PROFILER_ZONE("IDCT 16"); - ComputeTransposedScaledIDCT<16>()( - coefficients, DCTTo(pixels, pixels_stride), scratch_space); + ComputeScaledIDCT<16, 16>()(coefficients, DCTTo(pixels, pixels_stride), + scratch_space); break; } case Type::DCT16X8: { @@ -641,14 +624,14 @@ HWY_MAYBE_UNUSED void TransformToPixels(const AcStrategy::Type strategy, } case Type::DCT32X32: { PROFILER_ZONE("IDCT 32"); - ComputeTransposedScaledIDCT<32>()( - coefficients, DCTTo(pixels, pixels_stride), scratch_space); + ComputeScaledIDCT<32, 32>()(coefficients, DCTTo(pixels, pixels_stride), + scratch_space); break; } case Type::DCT: { PROFILER_ZONE("IDCT 8"); - ComputeTransposedScaledIDCT<8>()( - coefficients, DCTTo(pixels, pixels_stride), scratch_space); + ComputeScaledIDCT<8, 8>()(coefficients, DCTTo(pixels, pixels_stride), + scratch_space); break; } case Type::AFV0: { @@ -685,8 +668,8 @@ HWY_MAYBE_UNUSED void TransformToPixels(const AcStrategy::Type strategy, } case Type::DCT64X64: { PROFILER_ZONE("IDCT 64"); - ComputeTransposedScaledIDCT<64>()( - coefficients, DCTTo(pixels, pixels_stride), scratch_space); + ComputeScaledIDCT<64, 64>()(coefficients, DCTTo(pixels, pixels_stride), + scratch_space); break; } case Type::DCT128X64: { @@ -703,8 +686,8 @@ HWY_MAYBE_UNUSED void TransformToPixels(const AcStrategy::Type strategy, } case Type::DCT128X128: { PROFILER_ZONE("IDCT 128"); - ComputeTransposedScaledIDCT<128>()( - coefficients, DCTTo(pixels, pixels_stride), scratch_space); + ComputeScaledIDCT<128, 128>()(coefficients, DCTTo(pixels, pixels_stride), + scratch_space); break; } case Type::DCT256X128: { @@ -721,8 +704,8 @@ HWY_MAYBE_UNUSED void TransformToPixels(const AcStrategy::Type strategy, } case Type::DCT256X256: { PROFILER_ZONE("IDCT 256"); - ComputeTransposedScaledIDCT<256>()( - coefficients, DCTTo(pixels, pixels_stride), scratch_space); + ComputeScaledIDCT<256, 256>()(coefficients, DCTTo(pixels, pixels_stride), + scratch_space); break; } case Type::kNumValidStrategies: diff --git a/third_party/jpeg-xl/lib/jxl/decode.cc b/third_party/jpeg-xl/lib/jxl/decode.cc index 601d83b58502..489100ad775b 100644 --- a/third_party/jpeg-xl/lib/jxl/decode.cc +++ b/third_party/jpeg-xl/lib/jxl/decode.cc @@ -192,12 +192,20 @@ enum class FrameStage : uint32_t { enum class BoxStage : uint32_t { kHeader, // Parsing box header of the next box, or start of non-container // stream + kFtyp, // The ftyp box kSkip, // Box whose contents are skipped kCodestream, // Handling codestream box contents, or non-container stream kPartialCodestream, // Handling the extra header of partial codestream box kJpegRecon, // Handling jpeg reconstruction box }; +enum class JpegReconStage : uint32_t { + kNone, // Not outputting + kSettingMetadata, // Ready to output, must set metadata to the jpeg_data + kOutputting, // Currently outputting the JPEG bytes + 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 @@ -444,6 +452,7 @@ struct JxlDecoderStruct { // Settings bool keep_orientation; + bool render_spotcolors; // 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, @@ -455,6 +464,7 @@ struct JxlDecoderStruct { // Fields for reading the basic info from the header. size_t basic_info_size_hint; bool have_container; + size_t box_count; // 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 @@ -551,12 +561,33 @@ struct JxlDecoderStruct { jxl::JxlToJpegDecoder jpeg_decoder; jxl::JxlBoxContentDecoder box_content_decoder; + // Decodes Exif or XMP metadata for JPEG reconstruction + jxl::JxlBoxContentDecoder metadata_decoder; + std::vector exif_metadata; + std::vector xmp_metadata; + // must store JPEG reconstruction metadata from the current box + // 0 = not stored, 1 = currently storing, 2 = finished + int store_exif; + int store_xmp; + size_t recon_out_buffer_pos; + size_t recon_exif_size; // Expected exif size as read from the jbrd box + size_t recon_xmp_size; // Expected exif size as read from the jbrd box + JpegReconStage recon_output_jpeg; + + bool JbrdNeedMoreBoxes() const { + // jbrd box wants exif but exif box not yet seen + if (store_exif < 2 && recon_exif_size > 0) return true; + // jbrd box wants xmp but xmp box not yet seen + if (store_xmp < 2 && recon_xmp_size > 0) return true; + return false; + } // Statistics which CodecInOut can keep uint64_t dec_pixels; const uint8_t* next_in; size_t avail_in; + bool input_closed; void AdvanceInput(size_t size) { next_in += size; @@ -601,10 +632,19 @@ void JxlDecoderRewindDecodingState(JxlDecoder* dec) { dec->box_out_buffer_size = 0; dec->box_out_buffer_begin = 0; dec->box_out_buffer_pos = 0; + dec->exif_metadata.clear(); + dec->xmp_metadata.clear(); + dec->store_exif = 0; + dec->store_xmp = 0; + dec->recon_out_buffer_pos = 0; + dec->recon_exif_size = 0; + dec->recon_xmp_size = 0; + dec->recon_output_jpeg = JpegReconStage::kNone; dec->events_wanted = 0; dec->basic_info_size_hint = InitialBasicInfoSizeHint(); dec->have_container = 0; + dec->box_count = 0; dec->preview_out_buffer_set = false; dec->image_out_buffer_set = false; dec->preview_out_buffer = nullptr; @@ -617,6 +657,7 @@ void JxlDecoderRewindDecodingState(JxlDecoder* dec) { dec->dec_pixels = 0; dec->next_in = 0; dec->avail_in = 0; + dec->input_closed = false; dec->passes_state.reset(nullptr); dec->frame_dec.reset(nullptr); @@ -645,6 +686,7 @@ void JxlDecoderReset(JxlDecoder* dec) { dec->thread_pool.reset(); dec->keep_orientation = false; + dec->render_spotcolors = true; dec->orig_events_wanted = 0; dec->frame_references.clear(); dec->frame_saved_as.clear(); @@ -746,6 +788,15 @@ JxlDecoderStatus JxlDecoderSetKeepOrientation(JxlDecoder* dec, return JXL_DEC_SUCCESS; } +JxlDecoderStatus JxlDecoderSetRenderSpotcolors(JxlDecoder* dec, + JXL_BOOL render_spotcolors) { + if (dec->stage != DecoderStage::kInited) { + return JXL_API_ERROR("Must set render_spotcolors option before starting"); + } + dec->render_spotcolors = !!render_spotcolors; + return JXL_DEC_SUCCESS; +} + namespace jxl { namespace { @@ -1119,6 +1170,7 @@ JxlDecoderStatus JxlDecoderProcessCodestream(JxlDecoder* dec, const uint8_t* in, auto reader = GetBitReader(compressed); jxl::DecompressParams dparams; dparams.preview = want_preview ? jxl::Override::kOn : jxl::Override::kOff; + dparams.render_spotcolors = dec->render_spotcolors; jxl::ImageBundle ib(&dec->metadata.m); PassesDecoderState preview_dec_state; JXL_API_RETURN_IF_ERROR(preview_dec_state.output_encoding_info.Set( @@ -1159,6 +1211,15 @@ JxlDecoderStatus JxlDecoderProcessCodestream(JxlDecoder* dec, const uint8_t* in, } if (dec->frame_stage == FrameStage::kHeader) { + if (dec->recon_output_jpeg == JpegReconStage::kSettingMetadata || + dec->recon_output_jpeg == JpegReconStage::kOutputting) { + // The image bundle contains the JPEG reconstruction frame, but the + // decoder is still waiting to decode an EXIF or XMP box. It's not + // implemented to decode additional frames during this, and a JPEG + // reconstruction image should have only one frame. + 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; @@ -1257,6 +1318,7 @@ JxlDecoderStatus JxlDecoderProcessCodestream(JxlDecoder* dec, const uint8_t* in, dec->frame_dec.reset(new FrameDecoder( dec->passes_state.get(), dec->metadata, dec->thread_pool.get())); + dec->frame_dec->SetRenderSpotcolors(dec->render_spotcolors); // If JPEG reconstruction is wanted and possible, set the jpeg_data of // the ImageBundle. @@ -1381,10 +1443,17 @@ JxlDecoderStatus JxlDecoderProcessCodestream(JxlDecoder* dec, const uint8_t* in, 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; } + bool output_jpeg_reconstruction = false; + if (dec->frame_stage == FrameStage::kFullOutput) { if (dec->is_last_of_still) { if (dec->events_wanted & JXL_DEC_FULL_IMAGE) { @@ -1400,9 +1469,7 @@ JxlDecoderStatus JxlDecoderProcessCodestream(JxlDecoder* dec, const uint8_t* in, // If no output buffer was set, we merely return the JXL_DEC_FULL_IMAGE // status without outputting pixels. if (dec->jpeg_decoder.IsOutputSet() && dec->ib->jpeg_data != nullptr) { - JxlDecoderStatus status = - dec->jpeg_decoder.WriteOutput(*dec->ib->jpeg_data); - if (status != JXL_DEC_SUCCESS) return status; + output_jpeg_reconstruction = true; } else if (return_full_image && dec->image_out_buffer_set) { if (!dec->frame_dec->HasRGBBuffer()) { // Copy pixels if desired. @@ -1433,13 +1500,19 @@ 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(); dec->frame_stage = FrameStage::kHeader; dec->frame_start += dec->frame_size; - if (return_full_image && !dec->skipping_frame) { + + if (output_jpeg_reconstruction) { + dec->recon_output_jpeg = JpegReconStage::kSettingMetadata; return JXL_DEC_FULL_IMAGE; + } else { + // 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) { + return JXL_DEC_FULL_IMAGE; + } } } @@ -1453,7 +1526,12 @@ JxlDecoderStatus JxlDecoderProcessCodestream(JxlDecoder* dec, const uint8_t* in, JxlDecoderStatus JxlDecoderSetInput(JxlDecoder* dec, const uint8_t* data, size_t size) { - if (dec->next_in) return JXL_DEC_ERROR; + if (dec->next_in) { + return JXL_API_ERROR("already set input, use JxlDecoderReleaseInput first"); + } + if (dec->input_closed) { + return JXL_API_ERROR("input already closed"); + } dec->next_in = data; dec->avail_in = size; @@ -1467,8 +1545,19 @@ size_t JxlDecoderReleaseInput(JxlDecoder* dec) { return result; } +void JxlDecoderCloseInput(JxlDecoder* dec) { dec->input_closed = true; } + JxlDecoderStatus JxlDecoderSetJPEGBuffer(JxlDecoder* dec, uint8_t* data, size_t size) { + // JPEG reconstruction buffer can only set and updated before or during the + // first frame, the reconstruction box refers to the first frame and in + // theory multi-frame images should not be used with a jbrd box. + if (dec->internal_frames > 1) { + return JXL_API_ERROR("JPEG reconstruction only works for the first frame"); + } + if (dec->jpeg_decoder.IsOutputSet()) { + return JXL_API_ERROR("Already set JPEG buffer"); + } return dec->jpeg_decoder.SetOutputBuffer(data, size); } @@ -1521,29 +1610,8 @@ static JxlDecoderStatus ParseBoxHeader(const uint8_t* in, size_t size, return JXL_DEC_SUCCESS; } - -JxlDecoderStatus JxlDecoderProcessInput(JxlDecoder* dec) { - if (dec->stage == DecoderStage::kInited) { - dec->stage = DecoderStage::kStarted; - } - if (dec->stage == DecoderStage::kError) { - return JXL_API_ERROR( - "Cannot keep using decoder after it encountered an error, use " - "JxlDecoderReset to reset it"); - } - - if (!dec->got_signature) { - JxlSignature sig = JxlSignatureCheck(dec->next_in, dec->avail_in); - if (sig == JXL_SIG_INVALID) return JXL_API_ERROR("invalid signature"); - if (sig == JXL_SIG_NOT_ENOUGH_BYTES) return JXL_DEC_NEED_MORE_INPUT; - - dec->got_signature = true; - - if (sig == JXL_SIG_CONTAINER) { - dec->have_container = 1; - } - } - +// This includes handling the codestream if it is not a box-based jxl file. +static JxlDecoderStatus HandleBoxes(JxlDecoder* dec) { // Box handling loop for (;;) { if (dec->box_stage != BoxStage::kHeader) { @@ -1567,6 +1635,77 @@ JxlDecoderStatus JxlDecoderProcessInput(JxlDecoder* dec) { return box_result; } } + + if (dec->store_exif == 1 || dec->store_xmp == 1) { + std::vector& metadata = + (dec->store_exif == 1) ? dec->exif_metadata : dec->xmp_metadata; + for (;;) { + if (metadata.empty()) metadata.resize(64); + uint8_t* orig_next_out = metadata.data() + dec->recon_out_buffer_pos; + uint8_t* next_out = orig_next_out; + size_t avail_out = metadata.size() - dec->recon_out_buffer_pos; + JxlDecoderStatus box_result = dec->metadata_decoder.Process( + dec->next_in, dec->avail_in, + dec->file_pos - dec->box_contents_begin, &next_out, &avail_out); + size_t produced = next_out - orig_next_out; + dec->recon_out_buffer_pos += produced; + if (box_result == JXL_DEC_BOX_NEED_MORE_OUTPUT) { + metadata.resize(metadata.size() * 2); + } else if (box_result == JXL_DEC_NEED_MORE_INPUT) { + break; // box stage handling below will handle this instead + } else if (box_result == JXL_DEC_SUCCESS) { + size_t needed_size = (dec->store_exif == 1) ? dec->recon_exif_size + : dec->recon_xmp_size; + if (dec->box_contents_unbounded && + dec->recon_out_buffer_pos < needed_size) { + // Unbounded box, but we know the expected size due to the jbrd + // box's data. Treat this as the JXL_DEC_NEED_MORE_INPUT case. + break; + } else { + metadata.resize(dec->recon_out_buffer_pos); + if (dec->store_exif == 1) dec->store_exif = 2; + if (dec->store_xmp == 1) dec->store_xmp = 2; + break; + } + } else { + // error + return box_result; + } + } + } + } + + if (dec->recon_output_jpeg == JpegReconStage::kSettingMetadata && + !dec->JbrdNeedMoreBoxes()) { + using namespace jxl; + jpeg::JPEGData* jpeg_data = dec->ib->jpeg_data.get(); + if (dec->recon_exif_size) { + JxlDecoderStatus status = JxlToJpegDecoder::SetExif( + dec->exif_metadata.data(), dec->exif_metadata.size(), jpeg_data); + if (status != JXL_DEC_SUCCESS) return status; + } + if (dec->recon_xmp_size) { + JxlDecoderStatus status = JxlToJpegDecoder::SetXmp( + dec->xmp_metadata.data(), dec->xmp_metadata.size(), jpeg_data); + if (status != JXL_DEC_SUCCESS) return status; + } + dec->recon_output_jpeg = JpegReconStage::kOutputting; + } + + if (dec->recon_output_jpeg == JpegReconStage::kOutputting && + !dec->JbrdNeedMoreBoxes()) { + using namespace jxl; + JxlDecoderStatus status = + dec->jpeg_decoder.WriteOutput(*dec->ib->jpeg_data); + if (status != JXL_DEC_SUCCESS) return status; + dec->recon_output_jpeg = JpegReconStage::kFinished; + dec->ib.reset(); + if (dec->events_wanted & JXL_DEC_FULL_IMAGE) { + // Return the full image event here now, this may be delayed if this + // could only be done after decoding an exif or xmp box after the + // codestream. + return JXL_DEC_FULL_IMAGE; + } } if (dec->box_stage == BoxStage::kHeader) { @@ -1577,12 +1716,27 @@ JxlDecoderStatus JxlDecoderProcessInput(JxlDecoder* dec) { continue; } if (dec->avail_in == 0) { - if (dec->stage == DecoderStage::kFinished) { - // All codestream boxes done, return success. However, if the user - // still has more input, which could be a next metadata box, it's - // still possible to continue next JxlDecoderProcessInput calls. + if (dec->stage != DecoderStage::kFinished) { + // Not yet seen (all) codestream boxes. + return JXL_DEC_NEED_MORE_INPUT; + } + if (dec->JbrdNeedMoreBoxes()) { + return JXL_DEC_NEED_MORE_INPUT; + } + if (dec->input_closed) { return JXL_DEC_SUCCESS; } + if (!(dec->events_wanted & JXL_DEC_BOX)) { + // All codestream and jbrd metadata boxes finished, and no individual + // boxes requested by user, so no need to request any more input. + // This returns success for backwards compatibility, when + // JxlDecoderCloseInput and JXL_DEC_BOX did not exist, as well + // as for efficiency. + return JXL_DEC_SUCCESS; + } + // Even though we are exactly at a box end, there still may be more + // boxes. The user may call JxlDecoderCloseInput to indicate the input + // is finished and get success instead. return JXL_DEC_NEED_MORE_INPUT; } @@ -1597,7 +1751,6 @@ JxlDecoderStatus JxlDecoderProcessInput(JxlDecoder* dec) { } return status; } - if (memcmp(dec->box_type, "brob", 4) == 0) { if (dec->avail_in < header_size + 4) { return JXL_DEC_NEED_MORE_INPUT; @@ -1609,6 +1762,17 @@ JxlDecoderStatus JxlDecoderProcessInput(JxlDecoder* dec) { sizeof(dec->box_decoded_type)); } + // Box order validity checks + // The signature box at box_count == 1 is not checked here since that's + // already done at the beginning. + dec->box_count++; + if (dec->box_count == 2 && memcmp(dec->box_type, "ftyp", 4) != 0) { + return JXL_API_ERROR("the second box must be the ftyp box"); + } + if (memcmp(dec->box_type, "ftyp", 4) == 0 && dec->box_count != 2) { + return JXL_API_ERROR("the ftyp box must come second"); + } + dec->AdvanceInput(header_size); dec->box_contents_unbounded = (box_size == 0); @@ -1620,18 +1784,44 @@ JxlDecoderStatus JxlDecoderProcessInput(JxlDecoder* dec) { dec->box_contents_unbounded ? 0 : (box_size - header_size); dec->box_size = box_size; + if (dec->orig_events_wanted & JXL_DEC_JPEG_RECONSTRUCTION) { + // Initiate storing of Exif or XMP data for JPEG reconstruction + if (dec->store_exif == 0 && + memcmp(dec->box_decoded_type, "Exif", 4) == 0) { + dec->store_exif = 1; + dec->recon_out_buffer_pos = 0; + } + if (dec->store_xmp == 0 && + memcmp(dec->box_decoded_type, "xml ", 4) == 0) { + dec->store_xmp = 1; + dec->recon_out_buffer_pos = 0; + } + } + if (dec->events_wanted & JXL_DEC_BOX) { bool decompress = dec->decompress_boxes && memcmp(dec->box_type, "brob", 4) == 0; dec->box_content_decoder.StartBox( decompress, dec->box_contents_unbounded, dec->box_contents_size); } + if (dec->store_exif == 1 || dec->store_xmp == 1) { + bool brob = memcmp(dec->box_type, "brob", 4) == 0; + dec->metadata_decoder.StartBox(brob, dec->box_contents_unbounded, + dec->box_contents_size); + } - if (memcmp(dec->box_type, "jxlc", 4) == 0) { + if (memcmp(dec->box_type, "ftyp", 4) == 0) { + dec->box_stage = BoxStage::kFtyp; + } else if (memcmp(dec->box_type, "jxlc", 4) == 0) { dec->box_stage = BoxStage::kCodestream; } else if (memcmp(dec->box_type, "jxlp", 4) == 0) { dec->box_stage = BoxStage::kPartialCodestream; - } else if (memcmp(dec->box_type, "jbrd", 4) == 0) { + } else if ((dec->orig_events_wanted & JXL_DEC_JPEG_RECONSTRUCTION) && + memcmp(dec->box_type, "jbrd", 4) == 0) { + if (!(dec->events_wanted & JXL_DEC_JPEG_RECONSTRUCTION)) { + return JXL_API_ERROR( + "multiple JPEG reconstruction boxes not supported"); + } dec->box_stage = BoxStage::kJpegRecon; } else { dec->box_stage = BoxStage::kSkip; @@ -1642,6 +1832,16 @@ JxlDecoderStatus JxlDecoderProcessInput(JxlDecoder* dec) { dec->box_out_buffer_set_current_box = false; return JXL_DEC_BOX; } + } else if (dec->box_stage == BoxStage::kFtyp) { + if (dec->box_contents_size < 12) { + return JXL_API_ERROR("file type box too small"); + } + if (dec->avail_in < 4) return JXL_DEC_NEED_MORE_INPUT; + if (memcmp(dec->next_in, "jxl ", 4) != 0) { + return JXL_API_ERROR("file type box major brand must be \"jxl \""); + } + dec->AdvanceInput(4); + dec->box_stage = BoxStage::kSkip; } else if (dec->box_stage == BoxStage::kPartialCodestream) { if (dec->last_codestream_seen) { return JXL_API_ERROR("cannot have codestream after last codestream"); @@ -1681,19 +1881,29 @@ JxlDecoderStatus JxlDecoderProcessInput(JxlDecoder* dec) { JxlDecoderStatus status = jxl::JxlDecoderProcessCodestream(dec, codestream, avail_codestream); - if (!have_copy && status == JXL_DEC_NEED_MORE_INPUT) { - dec->codestream_copy.insert(dec->codestream_copy.end(), dec->next_in, - dec->next_in + avail_codestream); - dec->AdvanceInput(avail_codestream); + 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 (status == JXL_DEC_NEED_MORE_INPUT && - dec->file_pos == dec->box_contents_end) { - dec->box_stage = BoxStage::kHeader; - continue; + if (dec->file_pos == dec->box_contents_end) { + dec->box_stage = BoxStage::kHeader; + continue; + } } if (status == JXL_DEC_SUCCESS) { + if (dec->JbrdNeedMoreBoxes()) { + dec->box_stage = BoxStage::kSkip; + continue; + } if (dec->box_contents_unbounded) { // Last box reached and codestream done, nothing more to do. dec->AdvanceInput(dec->avail_in); @@ -1704,7 +1914,7 @@ JxlDecoderStatus JxlDecoderProcessInput(JxlDecoder* dec) { dec->box_stage = BoxStage::kSkip; continue; } else { - // Codestreaam decoded, and no box output requested, skip all further + // Codestream decoded, and no box output requested, skip all further // input and return success. dec->AdvanceInput(dec->avail_in); break; @@ -1724,6 +1934,30 @@ JxlDecoderStatus JxlDecoderProcessInput(JxlDecoder* dec) { size_t consumed = next_in - dec->next_in; dec->AdvanceInput(consumed); if (recon_result == JXL_DEC_JPEG_RECONSTRUCTION) { + jxl::jpeg::JPEGData* jpeg_data = dec->jpeg_decoder.GetJpegData(); + size_t num_exif = jxl::JxlToJpegDecoder::NumExifMarkers(*jpeg_data); + size_t num_xmp = jxl::JxlToJpegDecoder::NumXmpMarkers(*jpeg_data); + if (num_exif) { + if (num_exif > 1) { + return JXL_API_ERROR( + "multiple exif markers for JPEG reconstruction not supported"); + } + if (JXL_DEC_SUCCESS != jxl::JxlToJpegDecoder::ExifBoxContentSize( + *jpeg_data, &dec->recon_exif_size)) { + return JXL_API_ERROR("invalid jbrd exif size"); + } + } + if (num_xmp) { + if (num_xmp > 1) { + return JXL_API_ERROR( + "multiple XMP markers for JPEG reconstruction not supported"); + } + if (JXL_DEC_SUCCESS != jxl::JxlToJpegDecoder::XmlBoxContentSize( + *jpeg_data, &dec->recon_xmp_size)) { + return JXL_API_ERROR("invalid jbrd XMP size"); + } + } + dec->box_stage = BoxStage::kHeader; // If successful JPEG reconstruction, return the success if the user // cares about it, otherwise continue. @@ -1737,9 +1971,19 @@ JxlDecoderStatus JxlDecoderProcessInput(JxlDecoder* dec) { } } else if (dec->box_stage == BoxStage::kSkip) { if (dec->box_contents_unbounded) { - // Nothing further to do, an unbounded box is the last box, - // can end early. - break; + if (dec->input_closed) { + return JXL_DEC_SUCCESS; + } + if (!(dec->box_out_buffer_set)) { + // An unbounded box is always the last box. Not requesting box data, + // so return success even if JxlDecoderCloseInput was not called for + // backwards compatibility as well as efficiency since this box is + // being skipped. + return JXL_DEC_SUCCESS; + } + // Arbitrarily more bytes may follow, only JxlDecoderCloseInput can + // mark the end. + 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; @@ -1760,11 +2004,58 @@ JxlDecoderStatus JxlDecoderProcessInput(JxlDecoder* dec) { } } - if (dec->stage != DecoderStage::kFinished) { - return JXL_API_ERROR("codestream never finished"); + return JXL_DEC_SUCCESS; +} + +JxlDecoderStatus JxlDecoderProcessInput(JxlDecoder* dec) { + if (dec->stage == DecoderStage::kInited) { + dec->stage = DecoderStage::kStarted; + } + if (dec->stage == DecoderStage::kError) { + return JXL_API_ERROR( + "Cannot keep using decoder after it encountered an error, use " + "JxlDecoderReset to reset it"); } - return JXL_DEC_SUCCESS; + if (!dec->got_signature) { + JxlSignature sig = JxlSignatureCheck(dec->next_in, dec->avail_in); + if (sig == JXL_SIG_INVALID) return JXL_API_ERROR("invalid signature"); + if (sig == JXL_SIG_NOT_ENOUGH_BYTES) { + if (dec->input_closed) { + return JXL_API_ERROR("file too small for signature"); + } + return JXL_DEC_NEED_MORE_INPUT; + } + + dec->got_signature = true; + + if (sig == JXL_SIG_CONTAINER) { + dec->have_container = 1; + } + } + + JxlDecoderStatus status = HandleBoxes(dec); + + if (status == JXL_DEC_NEED_MORE_INPUT && dec->input_closed) { + return JXL_API_ERROR("missing input"); + } + + // Even if the box handling returns success, certain types of + // data may be missing. + if (status == JXL_DEC_SUCCESS) { + if (dec->stage != DecoderStage::kFinished) { + // TODO(lode): consider not returning this error if only subscribed to + // the JXL_DEC_BOX event and so finishing the image frames is not + // required. + return JXL_API_ERROR("codestream never finished"); + } + + if (dec->JbrdNeedMoreBoxes()) { + return JXL_API_ERROR("missing metadata boxes for jpeg reconstruction"); + } + } + + return status; } // To ensure ABI forward-compatibility, this struct has a constant size. diff --git a/third_party/jpeg-xl/lib/jxl/decode_test.cc b/third_party/jpeg-xl/lib/jxl/decode_test.cc index fb019e467e95..2169d7e6a469 100644 --- a/third_party/jpeg-xl/lib/jxl/decode_test.cc +++ b/third_party/jpeg-xl/lib/jxl/decode_test.cc @@ -1474,6 +1474,7 @@ struct PixelTestConfig { // Exif orientation, 1-8 JxlOrientation orientation; bool keep_orientation; + size_t upsampling; }; class DecodeTestParam : public ::testing::TestWithParam {}; @@ -1495,6 +1496,8 @@ TEST_P(DecodeTestParam, PixelTest) { 0}; jxl::CompressParams cparams; cparams.SetLossless(); // Lossless to verify pixels exactly after roundtrip. + cparams.resampling = config.upsampling; + cparams.ec_resampling = config.upsampling; jxl::PaddedBytes compressed = jxl::CreateTestJXLCodestream( jxl::Span(pixels.data(), pixels.size()), config.xsize, config.ysize, orig_channels, cparams, config.add_container, @@ -1540,9 +1543,19 @@ TEST_P(DecodeTestParam, PixelTest) { xsize * 2 * orig_channels, nullptr, pixels.data(), pixels.size(), nullptr, nullptr, static_cast(config.orientation))); } - - EXPECT_EQ(0u, ComparePixels(pixels.data(), pixels2.data(), xsize, ysize, - format_orig, format)); + if (config.upsampling == 1) { + EXPECT_EQ(0u, ComparePixels(pixels.data(), pixels2.data(), xsize, ysize, + format_orig, format)); + } else { + // resampling is of course not lossless, so as a rough check: + // count pixels that are more than off-by-25 in the 8-bit value of one of + // the channels + EXPECT_LE( + ComparePixels( + pixels.data(), pixels2.data(), xsize, ysize, format_orig, format, + 50.0 * (config.data_type == JXL_TYPE_UINT8 ? 1.0 : 256.0)), + 300u); + } JxlDecoderDestroy(dec); } @@ -1581,7 +1594,7 @@ std::vector GeneratePixelTests() { CodeStreamBoxFormat box, JxlOrientation orientation, bool keep_orientation, OutputFormat format, bool use_callback, bool set_buffer_early, - bool resizable_runner) { + bool resizable_runner, size_t upsampling) { PixelTestConfig c; c.grayscale = ch.grayscale; c.include_alpha = ch.include_alpha; @@ -1597,17 +1610,21 @@ std::vector GeneratePixelTests() { c.use_resizable_runner = resizable_runner; c.orientation = orientation; c.keep_orientation = keep_orientation; + c.upsampling = upsampling; all_tests.push_back(c); }; // Test output formats and methods. for (ChannelInfo ch : ch_info) { for (int use_callback = 0; use_callback <= 1; use_callback++) { - for (OutputFormat fmt : out_formats) { - make_test(ch, 301, 33, /*add_preview=*/false, - CodeStreamBoxFormat::kCSBF_None, JXL_ORIENT_IDENTITY, - /*keep_orientation=*/false, fmt, use_callback, - /*set_buffer_early=*/false, /*resizable_runner=*/false); + for (size_t upsampling : {1, 2, 4, 8}) { + for (OutputFormat fmt : out_formats) { + make_test(ch, 301, 33, /*add_preview=*/false, + CodeStreamBoxFormat::kCSBF_None, JXL_ORIENT_IDENTITY, + /*keep_orientation=*/false, fmt, use_callback, + /*set_buffer_early=*/false, /*resizable_runner=*/false, + upsampling); + } } } } @@ -1617,21 +1634,21 @@ std::vector GeneratePixelTests() { (CodeStreamBoxFormat)box, JXL_ORIENT_IDENTITY, /*keep_orientation=*/false, out_formats[0], /*use_callback=*/false, - /*set_buffer_early=*/false, /*resizable_runner=*/false); + /*set_buffer_early=*/false, /*resizable_runner=*/false, 1); } // Test previews. for (int add_preview = 0; add_preview <= 1; add_preview++) { make_test(ch_info[0], 77, 33, add_preview, CodeStreamBoxFormat::kCSBF_None, JXL_ORIENT_IDENTITY, /*keep_orientation=*/false, out_formats[0], /*use_callback=*/false, /*set_buffer_early=*/false, - /*resizable_runner=*/false); + /*resizable_runner=*/false, 1); } // Test setting buffers early. make_test(ch_info[0], 300, 33, /*add_preview=*/false, CodeStreamBoxFormat::kCSBF_None, JXL_ORIENT_IDENTITY, /*keep_orientation=*/false, out_formats[0], /*use_callback=*/false, /*set_buffer_early=*/true, - /*resizable_runner=*/false); + /*resizable_runner=*/false, 1); // Test using the resizable runner for (size_t i = 0; i < 4; i++) { @@ -1639,7 +1656,7 @@ std::vector GeneratePixelTests() { CodeStreamBoxFormat::kCSBF_None, JXL_ORIENT_IDENTITY, /*keep_orientation=*/false, out_formats[0], /*use_callback=*/false, /*set_buffer_early=*/false, - /*resizable_runner=*/true); + /*resizable_runner=*/true, 1); } // Test orientations. @@ -1649,13 +1666,13 @@ std::vector GeneratePixelTests() { static_cast(orientation), /*keep_orientation=*/false, out_formats[0], /*use_callback=*/false, /*set_buffer_early=*/true, - /*resizable_runner=*/false); + /*resizable_runner=*/false, 1); make_test(ch_info[0], 280, 12, /*add_preview=*/false, CodeStreamBoxFormat::kCSBF_None, static_cast(orientation), /*keep_orientation=*/true, out_formats[0], /*use_callback=*/false, /*set_buffer_early=*/true, - /*resizable_runner=*/false); + /*resizable_runner=*/false, 1); } return all_tests; @@ -1706,6 +1723,7 @@ std::ostream& operator<<(std::ostream& os, const PixelTestConfig& c) { if (c.use_resizable_runner) os << "ResizableRunner"; if (c.orientation != 1) os << "O" << c.orientation; if (c.keep_orientation) os << "Keep"; + if (c.upsampling > 1) os << "x" << c.upsampling; return os; } @@ -3315,6 +3333,14 @@ TEST(DecodeTest, JXL_TRANSCODE_JPEG_TEST(JPEGReconstructionTest)) { VerifyJPEGReconstruction(container, orig); } +TEST(DecodeTest, JXL_TRANSCODE_JPEG_TEST(JPEGReconstructionMetadataTest)) { + const std::string jpeg_path = "jxl/jpeg_reconstruction/1x1_exif_xmp.jpg"; + const std::string jxl_path = "jxl/jpeg_reconstruction/1x1_exif_xmp.jxl"; + const jxl::PaddedBytes jpeg = jxl::ReadTestData(jpeg_path); + const jxl::PaddedBytes jxl = jxl::ReadTestData(jxl_path); + VerifyJPEGReconstruction(jxl, jpeg); +} + TEST(DecodeTest, ContinueFinalNonEssentialBoxTest) { size_t xsize = 80, ysize = 90; std::vector pixels = jxl::test::GetSomeTestImage(xsize, ysize, 4, 0); @@ -3426,6 +3452,10 @@ TEST(DecodeTest, BoxTest) { } } + // 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_NEED_MORE_INPUT, JxlDecoderProcessInput(dec)); + JxlDecoderCloseInput(dec); EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderProcessInput(dec)); JxlDecoderDestroy(dec); @@ -3448,6 +3478,7 @@ TEST(DecodeTest, ExifBrobBoxTest) { if (!streaming) { EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderSetInput(dec, compressed.data(), compressed.size())); + JxlDecoderCloseInput(dec); } // for streaming input case const uint8_t* next_in = compressed.data(); @@ -3476,6 +3507,7 @@ TEST(DecodeTest, ExifBrobBoxTest) { total_in += amount; EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderSetInput(dec, next_in, avail_in)); + if (total_in == compressed.size()) JxlDecoderCloseInput(dec); } else { FAIL(); break; @@ -3528,6 +3560,7 @@ TEST(DecodeTest, ExifBrobBoxTest) { if (!streaming) { EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderSetInput(dec, compressed.data(), compressed.size())); + JxlDecoderCloseInput(dec); } // for streaming input case const uint8_t* next_in = compressed.data(); @@ -3558,6 +3591,7 @@ TEST(DecodeTest, ExifBrobBoxTest) { total_in += amount; EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderSetInput(dec, next_in, avail_in)); + if (total_in == compressed.size()) JxlDecoderCloseInput(dec); } else { FAIL(); break; @@ -3628,6 +3662,7 @@ TEST(DecodeTest, PartialCodestreamBoxTest) { dec, JXL_DEC_BASIC_INFO | JXL_DEC_FULL_IMAGE | JXL_DEC_BOX)); EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderSetInput(dec, compressed.data(), compressed.size())); + JxlDecoderCloseInput(dec); size_t num_jxlp = 0; @@ -3707,6 +3742,7 @@ TEST(DecodeTest, PartialCodestreamBoxTest) { EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderSetInput(dec, extracted_codestream.data(), extracted_codestream.size())); + JxlDecoderCloseInput(dec); size_t num_boxes = 0; @@ -3756,3 +3792,140 @@ TEST(DecodeTest, PartialCodestreamBoxTest) { JxlDecoderDestroy(dec); } } + +TEST(DecodeTest, SpotColorTest) { + jxl::ThreadPool* pool = nullptr; + jxl::CodecInOut io; + size_t xsize = 55, ysize = 257; + io.metadata.m.color_encoding = jxl::ColorEncoding::LinearSRGB(); + jxl::Image3F main(xsize, ysize); + jxl::ImageF spot(xsize, ysize); + jxl::ZeroFillImage(&main); + jxl::ZeroFillImage(&spot); + + for (size_t y = 0; y < ysize; y++) { + float* JXL_RESTRICT rowm = main.PlaneRow(1, y); + float* JXL_RESTRICT rows = spot.Row(y); + for (size_t x = 0; x < xsize; x++) { + rowm[x] = (x + y) * (1.f / 255.f); + rows[x] = ((x ^ y) & 255) * (1.f / 255.f); + } + } + io.SetFromImage(std::move(main), jxl::ColorEncoding::LinearSRGB()); + jxl::ExtraChannelInfo info; + info.bit_depth.bits_per_sample = 8; + info.dim_shift = 0; + info.type = jxl::ExtraChannel::kSpotColor; + info.spot_color[0] = 0.5f; + info.spot_color[1] = 0.2f; + info.spot_color[2] = 1.f; + info.spot_color[3] = 0.5f; + + io.metadata.m.extra_channel_info.push_back(info); + std::vector ec; + ec.push_back(std::move(spot)); + io.frames[0].SetExtraChannels(std::move(ec)); + + jxl::CompressParams cparams; + cparams.speed_tier = jxl::SpeedTier::kLightning; + cparams.modular_mode = true; + cparams.color_transform = jxl::ColorTransform::kNone; + cparams.quality_pair = {100, 100}; + + jxl::PaddedBytes compressed; + std::unique_ptr enc_state = + jxl::make_unique(); + EXPECT_TRUE(jxl::EncodeFile(cparams, &io, enc_state.get(), &compressed, + nullptr, pool)); + + for (size_t render_spot = 0; render_spot < 2; render_spot++) { + JxlPixelFormat format = {3, JXL_TYPE_UINT8, JXL_LITTLE_ENDIAN, 0}; + + JxlDecoder* dec = JxlDecoderCreate(NULL); + + EXPECT_EQ(JXL_DEC_SUCCESS, + JxlDecoderSubscribeEvents( + dec, JXL_DEC_BASIC_INFO | JXL_DEC_FULL_IMAGE)); + if (!render_spot) { + EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderSetRenderSpotcolors(dec, JXL_FALSE)); + } + + EXPECT_EQ(JXL_DEC_SUCCESS, + JxlDecoderSetInput(dec, compressed.data(), compressed.size())); + EXPECT_EQ(JXL_DEC_BASIC_INFO, JxlDecoderProcessInput(dec)); + JxlBasicInfo binfo; + EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderGetBasicInfo(dec, &binfo)); + EXPECT_EQ(1u, binfo.num_extra_channels); + EXPECT_EQ(xsize, binfo.xsize); + EXPECT_EQ(ysize, binfo.ysize); + + JxlExtraChannelInfo extra_info; + EXPECT_EQ(JXL_DEC_SUCCESS, + JxlDecoderGetExtraChannelInfo(dec, 0, &extra_info)); + EXPECT_EQ((unsigned int)jxl::ExtraChannel::kSpotColor, extra_info.type); + + EXPECT_EQ(JXL_DEC_NEED_IMAGE_OUT_BUFFER, JxlDecoderProcessInput(dec)); + size_t buffer_size; + EXPECT_EQ(JXL_DEC_SUCCESS, + JxlDecoderImageOutBufferSize(dec, &format, &buffer_size)); + size_t extra_size; + EXPECT_EQ(JXL_DEC_SUCCESS, + JxlDecoderExtraChannelBufferSize(dec, &format, &extra_size, 0)); + + std::vector image(buffer_size); + std::vector extra(extra_size); + size_t bytes_per_pixel = + format.num_channels * GetDataBits(format.data_type) / jxl::kBitsPerByte; + size_t stride = bytes_per_pixel * binfo.xsize; + + EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderSetImageOutBuffer( + dec, &format, image.data(), image.size())); + EXPECT_EQ(JXL_DEC_SUCCESS, + JxlDecoderSetExtraChannelBuffer(dec, &format, extra.data(), + extra.size(), 0)); + + EXPECT_EQ(JXL_DEC_FULL_IMAGE, JxlDecoderProcessInput(dec)); + + // After the full image was output, JxlDecoderProcessInput should return + // success to indicate all is done. + EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderProcessInput(dec)); + JxlDecoderDestroy(dec); + + for (size_t y = 0; y < ysize; y++) { + uint8_t* JXL_RESTRICT rowm = image.data() + stride * y; + uint8_t* JXL_RESTRICT rows = extra.data() + xsize * y; + for (size_t x = 0; x < xsize; x++) { + if (!render_spot) { + // if spot color isn't rendered, main image should be as we made it + // (red and blue are all zeroes) + + EXPECT_EQ(rowm[x * 3 + 0], 0); + EXPECT_EQ(rowm[x * 3 + 1], (x + y > 255 ? 255 : x + y)); + EXPECT_EQ(rowm[x * 3 + 2], 0); + } + if (render_spot) { + // if spot color is rendered, expect red and blue to look like the + // spot color channel + EXPECT_LT(abs(rowm[x * 3 + 0] - (rows[x] * 0.25f)), 1); + EXPECT_LT(abs(rowm[x * 3 + 2] - (rows[x] * 0.5f)), 1); + } + EXPECT_EQ(rows[x], ((x ^ y) & 255)); + } + } + } +} + +TEST(DecodeTest, CloseInput) { + std::vector partial_file = {0xff}; + + JxlDecoderPtr dec = JxlDecoderMake(nullptr); + EXPECT_EQ(JXL_DEC_SUCCESS, + JxlDecoderSubscribeEvents(dec.get(), + JXL_DEC_BASIC_INFO | JXL_DEC_FULL_IMAGE)); + EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderSetInput(dec.get(), partial_file.data(), + partial_file.size())); + EXPECT_EQ(JXL_DEC_NEED_MORE_INPUT, JxlDecoderProcessInput(dec.get())); + EXPECT_EQ(JXL_DEC_NEED_MORE_INPUT, JxlDecoderProcessInput(dec.get())); + JxlDecoderCloseInput(dec.get()); + EXPECT_EQ(JXL_DEC_ERROR, JxlDecoderProcessInput(dec.get())); +} diff --git a/third_party/jpeg-xl/lib/jxl/decode_to_jpeg.cc b/third_party/jpeg-xl/lib/jxl/decode_to_jpeg.cc index 4bab82abb36b..aa57b2723f73 100644 --- a/third_party/jpeg-xl/lib/jxl/decode_to_jpeg.cc +++ b/third_party/jpeg-xl/lib/jxl/decode_to_jpeg.cc @@ -72,6 +72,98 @@ JxlDecoderStatus JxlToJpegDecoder::Process(const uint8_t** next_in, return JXL_DEC_NEED_MORE_INPUT; } +size_t JxlToJpegDecoder::NumExifMarkers(const jpeg::JPEGData& jpeg_data) { + size_t num = 0; + for (size_t i = 0; i < jpeg_data.app_data.size(); ++i) { + if (jpeg_data.app_marker_type[i] == jxl::jpeg::AppMarkerType::kExif) { + num++; + } + } + return num; +} + +size_t JxlToJpegDecoder::NumXmpMarkers(const jpeg::JPEGData& jpeg_data) { + size_t num = 0; + for (size_t i = 0; i < jpeg_data.app_data.size(); ++i) { + if (jpeg_data.app_marker_type[i] == jxl::jpeg::AppMarkerType::kXMP) { + num++; + } + } + return num; +} + +JxlDecoderStatus JxlToJpegDecoder::ExifBoxContentSize( + const jpeg::JPEGData& jpeg_data, size_t* size) { + for (size_t i = 0; i < jpeg_data.app_data.size(); ++i) { + if (jpeg_data.app_marker_type[i] == jxl::jpeg::AppMarkerType::kExif) { + if (jpeg_data.app_data[i].size() < 3 + sizeof(jpeg::kExifTag)) { + // too small for app marker header + return JXL_DEC_ERROR; + } + // The first 4 bytes are the TIFF header from the box contents, and are + // not included in the JPEG + *size = jpeg_data.app_data[i].size() + 4 - 3 - sizeof(jpeg::kExifTag); + return JXL_DEC_SUCCESS; + } + } + return JXL_DEC_ERROR; +} + +JxlDecoderStatus JxlToJpegDecoder::XmlBoxContentSize( + const jpeg::JPEGData& jpeg_data, size_t* size) { + for (size_t i = 0; i < jpeg_data.app_data.size(); ++i) { + if (jpeg_data.app_marker_type[i] == jxl::jpeg::AppMarkerType::kXMP) { + if (jpeg_data.app_data[i].size() < 3 + sizeof(jpeg::kXMPTag)) { + // too small for app marker header + return JXL_DEC_ERROR; + } + *size = jpeg_data.app_data[i].size() - 3 - sizeof(jpeg::kXMPTag); + return JXL_DEC_SUCCESS; + } + } + return JXL_DEC_ERROR; +} + +JxlDecoderStatus JxlToJpegDecoder::SetExif(const uint8_t* data, size_t size, + jpeg::JPEGData* jpeg_data) { + for (size_t i = 0; i < jpeg_data->app_data.size(); ++i) { + if (jpeg_data->app_marker_type[i] == jxl::jpeg::AppMarkerType::kExif) { + if (jpeg_data->app_data[i].size() != + size + 3 + sizeof(jpeg::kExifTag) - 4) + return JXL_DEC_ERROR; + // The first 9 bytes are used for JPEG marker header. + jpeg_data->app_data[i][0] = 0xE1; + // The second and third byte are already filled in correctly + memcpy(jpeg_data->app_data[i].data() + 3, jpeg::kExifTag, + sizeof(jpeg::kExifTag)); + // The first 4 bytes are the TIFF header from the box contents, and are + // not included in the JPEG + memcpy(jpeg_data->app_data[i].data() + 3 + sizeof(jpeg::kExifTag), + data + 4, size - 4); + return JXL_DEC_SUCCESS; + } + } + return JXL_DEC_ERROR; +} +JxlDecoderStatus JxlToJpegDecoder::SetXmp(const uint8_t* data, size_t size, + jpeg::JPEGData* jpeg_data) { + for (size_t i = 0; i < jpeg_data->app_data.size(); ++i) { + if (jpeg_data->app_marker_type[i] == jxl::jpeg::AppMarkerType::kXMP) { + if (jpeg_data->app_data[i].size() != size + 3 + sizeof(jpeg::kXMPTag)) + return JXL_DEC_ERROR; + // The first 9 bytes are used for JPEG marker header. + jpeg_data->app_data[i][0] = 0xE1; + // The second and third byte are already filled in correctly + memcpy(jpeg_data->app_data[i].data() + 3, jpeg::kXMPTag, + sizeof(jpeg::kXMPTag)); + memcpy(jpeg_data->app_data[i].data() + 3 + sizeof(jpeg::kXMPTag), data, + size); + return JXL_DEC_SUCCESS; + } + } + return JXL_DEC_ERROR; +} + #endif // JPEGXL_ENABLE_TRANSCODE_JPEG } // namespace jxl diff --git a/third_party/jpeg-xl/lib/jxl/decode_to_jpeg.h b/third_party/jpeg-xl/lib/jxl/decode_to_jpeg.h index 83d361dac016..f4e1ae9a8f57 100644 --- a/third_party/jpeg-xl/lib/jxl/decode_to_jpeg.h +++ b/third_party/jpeg-xl/lib/jxl/decode_to_jpeg.h @@ -37,12 +37,6 @@ class JxlToJpegDecoder { // Returns whether the decoder is parsing a boxa JPEG box was parsed. bool IsParsingBox() const { return inside_box_; } - const jpeg::JPEGData* JpegData() const { return jpeg_data_.get(); } - - // Return the parsed jpeg::JPEGData object and removes it from the - // JxlToJpegDecoder. - jpeg::JPEGData* ReleaseJpegData() { return jpeg_data_.release(); } - // Sets the output buffer used when producing JPEG output. JxlDecoderStatus SetOutputBuffer(uint8_t* data, size_t size) { if (next_out_) return JXL_DEC_ERROR; @@ -74,8 +68,39 @@ class JxlToJpegDecoder { // Uses box_size_, inside_box_ and box_until_eof_ to calculate how much to // consume. Potentially stores unparsed data in buffer_. // Potentially populates jpeg_data_. Potentially updates inside_box_. + // Returns JXL_DEC_JPEG_RECONSTRUCTION when finished, JXL_DEC_NEED_MORE_INPUT + // if more input is needed, JXL_DEC_ERROR on parsing error. JxlDecoderStatus Process(const uint8_t** next_in, size_t* avail_in); + // Returns non-owned copy of the JPEGData, only after Process finished and + // the JPEGData was not yet moved to an image bundle with + // SetImageBundleJpegData. + jpeg::JPEGData* GetJpegData() { return jpeg_data_.get(); } + + // Returns how many exif or xmp app markers are present in the JPEG data. A + // return value higher than 1 would require multiple exif boxes or multiple + // xmp boxes in the container format, and this is not supported by the API and + // considered an error. May only be called after Process returned success. + static size_t NumExifMarkers(const jpeg::JPEGData& jpeg_data); + static size_t NumXmpMarkers(const jpeg::JPEGData& jpeg_data); + + // Returns box content size for metadata, using the known data from the app + // markers. + static JxlDecoderStatus ExifBoxContentSize(const jpeg::JPEGData& jpeg_data, + size_t* size); + static JxlDecoderStatus XmlBoxContentSize(const jpeg::JPEGData& jpeg_data, + size_t* size); + + // Returns JXL_DEC_ERROR if there is no exif/XMP marker or the data size + // does not match, or this function is called before Process returned + // success, JXL_DEC_SUCCESS otherwise. As input, provide the full box contents + // but not the box header. In case of exif, this includes the 4-byte TIFF + // header, even though it won't be copied into the JPEG. + static JxlDecoderStatus SetExif(const uint8_t* data, size_t size, + jpeg::JPEGData* jpeg_data); + static JxlDecoderStatus SetXmp(const uint8_t* data, size_t size, + jpeg::JPEGData* jpeg_data); + // Sets the JpegData of the ImageBundle passed if there is anything to set. // Releases the JpegData from this decoder if set. Status SetImageBundleJpegData(ImageBundle* ib) { @@ -145,9 +170,6 @@ class JxlToJpegDecoder { bool IsOutputSet() const { return false; } bool IsParsingBox() const { return false; } - const jpeg::JPEGData* JpegData() const { return nullptr; } - jpeg::JPEGData* ReleaseJpegData() { return nullptr; } - JxlDecoderStatus SetOutputBuffer(uint8_t* /* data */, size_t /* size */) { return JXL_DEC_ERROR; } @@ -158,9 +180,31 @@ class JxlToJpegDecoder { JxlDecoderStatus Process(const uint8_t** next_in, size_t* avail_in) { return JXL_DEC_ERROR; } + jpeg::JPEGData* GetJpegData() { return nullptr; } Status SetImageBundleJpegData(ImageBundle* /* ib */) { return true; } + static size_t NumExifMarkers(const jpeg::JPEGData& /*jpeg_data*/) { + return 0; + } + static size_t NumXmpMarkers(const jpeg::JPEGData& /*jpeg_data*/) { return 0; } + static size_t ExifBoxContentSize(const jpeg::JPEGData& /*jpeg_data*/, + size_t* /*size*/) { + return JXL_DEC_ERROR; + } + static size_t XmlBoxContentSize(const jpeg::JPEGData& /*jpeg_data*/, + size_t* /*size*/) { + return JXL_DEC_ERROR; + } + static JxlDecoderStatus SetExif(const uint8_t* /*data*/, size_t /*size*/, + jpeg::JPEGData* /*jpeg_data*/) { + return JXL_DEC_ERROR; + } + static JxlDecoderStatus SetXmp(const uint8_t* /*data*/, size_t /*size*/, + jpeg::JPEGData* /*jpeg_data*/) { + return JXL_DEC_ERROR; + } + JxlDecoderStatus WriteOutput(const jpeg::JPEGData& /* jpeg_data */) { return JXL_DEC_SUCCESS; } diff --git a/third_party/jpeg-xl/lib/jxl/enc_ans.cc b/third_party/jpeg-xl/lib/jxl/enc_ans.cc index 48bc745f6543..16d28139f3e5 100644 --- a/third_party/jpeg-xl/lib/jxl/enc_ans.cc +++ b/third_party/jpeg-xl/lib/jxl/enc_ans.cc @@ -541,6 +541,7 @@ void ChooseUintConfigs(const HistogramParams& params, std::vector* clustered_histograms, EntropyEncodingData* codes, size_t* log_alpha_size) { codes->uint_config.resize(clustered_histograms->size()); + if (params.uint_method == HistogramParams::HybridUintMethod::kNone) return; if (params.uint_method == HistogramParams::HybridUintMethod::kContextMap) { codes->uint_config.clear(); @@ -622,6 +623,9 @@ void ChooseUintConfigs(const HistogramParams& params, for (size_t i = 0; i < clustered_histograms->size(); i++) { if (!is_valid[i]) continue; float cost = (*clustered_histograms)[i].PopulationCost() + extra_bits[i]; + // add signaling cost of the hybriduintconfig itself + cost += CeilLog2Nonzero(cfg.split_exponent + 1); + cost += CeilLog2Nonzero(cfg.split_exponent - cfg.msb_in_token + 1); if (cost < costs[i]) { codes->uint_config[i] = cfg; costs[i] = cost; diff --git a/third_party/jpeg-xl/lib/jxl/enc_color_management.cc b/third_party/jpeg-xl/lib/jxl/enc_color_management.cc index 6a8179dd6112..a7218c0f39ba 100644 --- a/third_party/jpeg-xl/lib/jxl/enc_color_management.cc +++ b/third_party/jpeg-xl/lib/jxl/enc_color_management.cc @@ -31,6 +31,7 @@ #include "lib/jxl/base/compiler_specific.h" #include "lib/jxl/base/data_parallel.h" +#include "lib/jxl/base/printf_macros.h" #include "lib/jxl/base/status.h" #include "lib/jxl/field_encodings.h" #include "lib/jxl/linalg.h" diff --git a/third_party/jpeg-xl/lib/jxl/enc_detect_dots.cc b/third_party/jpeg-xl/lib/jxl/enc_detect_dots.cc index 60ccccf1ced7..cecb2b37e13e 100644 --- a/third_party/jpeg-xl/lib/jxl/enc_detect_dots.cc +++ b/third_party/jpeg-xl/lib/jxl/enc_detect_dots.cc @@ -21,6 +21,7 @@ #include "lib/jxl/base/compiler_specific.h" #include "lib/jxl/base/data_parallel.h" +#include "lib/jxl/base/printf_macros.h" #include "lib/jxl/base/profiler.h" #include "lib/jxl/base/status.h" #include "lib/jxl/codec_in_out.h" diff --git a/third_party/jpeg-xl/lib/jxl/enc_modular.cc b/third_party/jpeg-xl/lib/jxl/enc_modular.cc index ebaf1e95f0a5..0904ccbbac94 100644 --- a/third_party/jpeg-xl/lib/jxl/enc_modular.cc +++ b/third_party/jpeg-xl/lib/jxl/enc_modular.cc @@ -17,6 +17,7 @@ #include "lib/jxl/aux_out.h" #include "lib/jxl/base/compiler_specific.h" #include "lib/jxl/base/padded_bytes.h" +#include "lib/jxl/base/printf_macros.h" #include "lib/jxl/base/status.h" #include "lib/jxl/compressed_dc.h" #include "lib/jxl/dec_ans.h" diff --git a/third_party/jpeg-xl/lib/jxl/enc_transforms-inl.h b/third_party/jpeg-xl/lib/jxl/enc_transforms-inl.h index c2f8e61105b9..ef6dc2bbd7ec 100644 --- a/third_party/jpeg-xl/lib/jxl/enc_transforms-inl.h +++ b/third_party/jpeg-xl/lib/jxl/enc_transforms-inl.h @@ -23,24 +23,6 @@ namespace jxl { namespace HWY_NAMESPACE { namespace { -template -struct DoIDCT { - template - void operator()(float* JXL_RESTRICT from, const To& to, - float* JXL_RESTRICT scratch_space) { - ComputeScaledIDCT()(from, to, scratch_space); - } -}; - -template -struct DoIDCT { - template - void operator()(float* JXL_RESTRICT from, const To& to, - float* JXL_RESTRICT scratch_space) const { - ComputeTransposedScaledIDCT()(from, to, scratch_space); - } -}; - // Inverse of ReinterpretingDCT. template @@ -68,7 +50,8 @@ HWY_INLINE void ReinterpretingIDCT(const float* input, // ROWS, COLS <= 8, so we can put scratch space on the stack. HWY_ALIGN float scratch_space[ROWS * COLS]; - DoIDCT()(block, DCTTo(output, output_stride), scratch_space); + ComputeScaledIDCT()(block, DCTTo(output, output_stride), + scratch_space); } template @@ -435,7 +418,7 @@ void AFVTransformFromPixels(const float* JXL_RESTRICT pixels, } } // 4x4 DCT of the block with same y and different x. - ComputeTransposedScaledDCT<4>()( + ComputeScaledDCT<4, 4>()( DCTFrom(pixels + afv_y * 4 * pixels_stride + (afv_x == 1 ? 0 : 4), pixels_stride), block, scratch_space); @@ -545,7 +528,7 @@ HWY_MAYBE_UNUSED void TransformFromPixels(const AcStrategy::Type strategy, for (size_t y = 0; y < 2; y++) { for (size_t x = 0; x < 2; x++) { HWY_ALIGN float block[4 * 4]; - ComputeTransposedScaledDCT<4>()( + ComputeScaledDCT<4, 4>()( DCTFrom(pixels + y * 4 * pixels_stride + x * 4, pixels_stride), block, scratch_space); for (size_t iy = 0; iy < 4; iy++) { @@ -574,8 +557,8 @@ HWY_MAYBE_UNUSED void TransformFromPixels(const AcStrategy::Type strategy, } case Type::DCT16X16: { PROFILER_ZONE("DCT 16"); - ComputeTransposedScaledDCT<16>()(DCTFrom(pixels, pixels_stride), - coefficients, scratch_space); + ComputeScaledDCT<16, 16>()(DCTFrom(pixels, pixels_stride), coefficients, + scratch_space); break; } case Type::DCT16X8: { @@ -616,14 +599,14 @@ HWY_MAYBE_UNUSED void TransformFromPixels(const AcStrategy::Type strategy, } case Type::DCT32X32: { PROFILER_ZONE("DCT 32"); - ComputeTransposedScaledDCT<32>()(DCTFrom(pixels, pixels_stride), - coefficients, scratch_space); + ComputeScaledDCT<32, 32>()(DCTFrom(pixels, pixels_stride), coefficients, + scratch_space); break; } case Type::DCT: { PROFILER_ZONE("DCT 8"); - ComputeTransposedScaledDCT<8>()(DCTFrom(pixels, pixels_stride), - coefficients, scratch_space); + ComputeScaledDCT<8, 8>()(DCTFrom(pixels, pixels_stride), coefficients, + scratch_space); break; } case Type::AFV0: { @@ -648,8 +631,8 @@ HWY_MAYBE_UNUSED void TransformFromPixels(const AcStrategy::Type strategy, } case Type::DCT64X64: { PROFILER_ZONE("DCT 64x64"); - ComputeTransposedScaledDCT<64>()(DCTFrom(pixels, pixels_stride), - coefficients, scratch_space); + ComputeScaledDCT<64, 64>()(DCTFrom(pixels, pixels_stride), coefficients, + scratch_space); break; } case Type::DCT64X32: { @@ -666,8 +649,8 @@ HWY_MAYBE_UNUSED void TransformFromPixels(const AcStrategy::Type strategy, } case Type::DCT128X128: { PROFILER_ZONE("DCT 128x128"); - ComputeTransposedScaledDCT<128>()(DCTFrom(pixels, pixels_stride), - coefficients, scratch_space); + ComputeScaledDCT<128, 128>()(DCTFrom(pixels, pixels_stride), coefficients, + scratch_space); break; } case Type::DCT128X64: { @@ -684,8 +667,8 @@ HWY_MAYBE_UNUSED void TransformFromPixels(const AcStrategy::Type strategy, } case Type::DCT256X256: { PROFILER_ZONE("DCT 256x256"); - ComputeTransposedScaledDCT<256>()(DCTFrom(pixels, pixels_stride), - coefficients, scratch_space); + ComputeScaledDCT<256, 256>()(DCTFrom(pixels, pixels_stride), coefficients, + scratch_space); break; } case Type::DCT256X128: { diff --git a/third_party/jpeg-xl/lib/jxl/encode.cc b/third_party/jpeg-xl/lib/jxl/encode.cc index 62cc28a88fb2..45c177b2ab30 100644 --- a/third_party/jpeg-xl/lib/jxl/encode.cc +++ b/third_party/jpeg-xl/lib/jxl/encode.cc @@ -48,10 +48,18 @@ JxlEncoderStatus JxlEncoderStruct::RefillOutputByteQueue() { jxl::BitWriter writer; if (!wrote_bytes) { - if (use_container) { + if (MustUseContainer()) { + // Add "JXL " and ftyp box. output_byte_queue.insert( output_byte_queue.end(), jxl::kContainerHeader, jxl::kContainerHeader + sizeof(jxl::kContainerHeader)); + if (codestream_level != 5) { + // Add jxll box. + output_byte_queue.insert( + output_byte_queue.end(), jxl::kLevelBoxHeader, + jxl::kLevelBoxHeader + sizeof(jxl::kLevelBoxHeader)); + output_byte_queue.push_back(codestream_level); + } if (store_jpeg_metadata && jpeg_metadata.size() > 0) { jxl::AppendBoxHeader(jxl::MakeBoxType("jbrd"), jpeg_metadata.size(), false, &output_byte_queue); @@ -100,7 +108,7 @@ JxlEncoderStatus JxlEncoderStruct::RefillOutputByteQueue() { jxl::PaddedBytes bytes = std::move(writer).TakeBytes(); - if (use_container && !wrote_bytes) { + if (MustUseContainer() && !wrote_bytes) { if (input_closed && input_frame_queue.empty()) { jxl::AppendBoxHeader(jxl::MakeBoxType("jxlc"), bytes.size(), /*unbounded=*/false, &output_byte_queue); @@ -251,11 +259,7 @@ JxlEncoderStatus JxlEncoderOptionsSetLossless(JxlEncoderOptions* options, JxlEncoderStatus JxlEncoderOptionsSetEffort(JxlEncoderOptions* options, const int effort) { - if (effort < 1 || effort > 9) { - return JXL_ENC_ERROR; - } - options->values.cparams.speed_tier = static_cast(10 - effort); - return JXL_ENC_SUCCESS; + return JxlEncoderOptionsSetInteger(options, JXL_ENC_OPTION_EFFORT, effort); } JxlEncoderStatus JxlEncoderOptionsSetDistance(JxlEncoderOptions* options, @@ -264,40 +268,193 @@ JxlEncoderStatus JxlEncoderOptionsSetDistance(JxlEncoderOptions* options, return JXL_ENC_ERROR; } options->values.cparams.butteraugli_distance = distance; + float jpeg_quality; + // Formula to translate butteraugli distance roughly into JPEG 0-100 quality. + // This is the inverse of the formula in cjxl.cc to translate JPEG quality + // into butteraugli distance. + if (distance < 6.56f) { + jpeg_quality = -5.456783f * std::log(0.0256f * distance - 0.16384f); + } else { + jpeg_quality = -11.11111f * distance + 101.11111f; + } + // Translate JPEG quality into the quality_pair setting for modular encoding. + // This is the formula also used in cjxl.cc to convert the command line JPEG + // quality parameter to the quality_pair setting. + // TODO(lode): combine the distance -> quality_pair conversion into a single + // formula, possibly altering it to a more suitable heuristic. + float quality; + if (jpeg_quality < 7) { + quality = std::min(35 + (jpeg_quality - 7) * 3.0f, 100.0f); + } else { + quality = std::min(35 + (jpeg_quality - 7) * 65.f / 93.f, 100.0f); + } + options->values.cparams.quality_pair.first = + options->values.cparams.quality_pair.second = quality; return JXL_ENC_SUCCESS; } -JxlEncoderStatus JxlEncoderOptionsSetAsInteger(JxlEncoderOptions* options, - JxlEncoderOptionId option, - int32_t value) { +JxlEncoderStatus JxlEncoderOptionsSetDecodingSpeed(JxlEncoderOptions* options, + int tier) { + return JxlEncoderOptionsSetInteger(options, JXL_ENC_OPTION_DECODING_SPEED, + tier); +} + +JxlEncoderStatus JxlEncoderOptionsSetInteger(JxlEncoderOptions* options, + JxlEncoderOptionId option, + int32_t value) { switch (option) { + case JXL_ENC_OPTION_EFFORT: + if (value < 1 || value > 9) { + return JXL_ENC_ERROR; + } + options->values.cparams.speed_tier = + static_cast(10 - value); + return JXL_ENC_SUCCESS; + case JXL_ENC_OPTION_DECODING_SPEED: + if (value < 0 || value > 4) { + return JXL_ENC_ERROR; + } + options->values.cparams.decoding_speed_tier = value; + return JXL_ENC_SUCCESS; case JXL_ENC_OPTION_RESAMPLING: - if (value != 0 && value != 1 && value != 2 && value != 4 && value != 8) { + if (value != -1 && value != 1 && value != 2 && value != 4 && value != 8) { return JXL_ENC_ERROR; } options->values.cparams.resampling = value; return JXL_ENC_SUCCESS; case JXL_ENC_OPTION_EXTRA_CHANNEL_RESAMPLING: - if (value != 0 && value != 1 && value != 2 && value != 4 && value != 8) { + if (value != -1 && value != 1 && value != 2 && value != 4 && value != 8) { return JXL_ENC_ERROR; } // The implementation doesn't support the default choice between 1x1 and // 2x2 for extra channels, so 1x1 is set as the default. - if (value == 0) value = 1; + if (value == -1) value = 1; options->values.cparams.ec_resampling = value; return JXL_ENC_SUCCESS; + case JXL_ENC_OPTION_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. + options->values.cparams.photon_noise_iso = static_cast(value); + return JXL_ENC_SUCCESS; case JXL_ENC_OPTION_NOISE: + if (value < -1 || value > 1) return JXL_ENC_ERROR; options->values.cparams.noise = static_cast(value); return JXL_ENC_SUCCESS; case JXL_ENC_OPTION_DOTS: + if (value < -1 || value > 1) return JXL_ENC_ERROR; options->values.cparams.dots = static_cast(value); return JXL_ENC_SUCCESS; case JXL_ENC_OPTION_PATCHES: + if (value < -1 || value > 1) return JXL_ENC_ERROR; options->values.cparams.patches = static_cast(value); return JXL_ENC_SUCCESS; + case JXL_ENC_OPTION_EPF: + if (value < -1 || value > 3) return JXL_ENC_ERROR; + options->values.cparams.epf = static_cast(value); + return JXL_ENC_SUCCESS; case JXL_ENC_OPTION_GABORISH: + if (value < -1 || value > 1) return JXL_ENC_ERROR; options->values.cparams.gaborish = static_cast(value); return JXL_ENC_SUCCESS; + case JXL_ENC_OPTION_MODULAR: + if (value == 1) { + options->values.cparams.modular_mode = true; + } else if (value == -1 || value == 0) { + options->values.cparams.modular_mode = false; + } else { + return JXL_ENC_ERROR; + } + return JXL_ENC_SUCCESS; + case JXL_ENC_OPTION_KEEP_INVISIBLE: + if (value < -1 || value > 1) return JXL_ENC_ERROR; + options->values.cparams.keep_invisible = + static_cast(value); + return JXL_ENC_SUCCESS; + case JXL_ENC_OPTION_GROUP_ORDER: + if (value < -1 || value > 1) return JXL_ENC_ERROR; + options->values.cparams.centerfirst = (value == 1); + return JXL_ENC_SUCCESS; + case JXL_ENC_OPTION_GROUP_ORDER_CENTER_X: + if (value < -1) return JXL_ENC_ERROR; + options->values.cparams.center_x = static_cast(value); + return JXL_ENC_SUCCESS; + case JXL_ENC_OPTION_GROUP_ORDER_CENTER_Y: + if (value < -1) return JXL_ENC_ERROR; + options->values.cparams.center_y = static_cast(value); + return JXL_ENC_SUCCESS; + case JXL_ENC_OPTION_RESPONSIVE: + if (value < -1 || value > 1) return JXL_ENC_ERROR; + options->values.cparams.responsive = value; + return JXL_ENC_SUCCESS; + case JXL_ENC_OPTION_PROGRESSIVE_AC: + if (value < -1 || value > 1) return JXL_ENC_ERROR; + options->values.cparams.progressive_mode = value; + return JXL_ENC_SUCCESS; + case JXL_ENC_OPTION_QPROGRESSIVE_AC: + if (value < -1 || value > 1) return JXL_ENC_ERROR; + options->values.cparams.qprogressive_mode = value; + return JXL_ENC_SUCCESS; + case JXL_ENC_OPTION_PROGRESSIVE_DC: + if (value < -1 || value > 2) return JXL_ENC_ERROR; + options->values.cparams.progressive_dc = value; + return JXL_ENC_SUCCESS; + case JXL_ENC_OPTION_CHANNEL_COLORS_PRE_TRANSFORM_PERCENT: + if (value < -1 || value > 100) return JXL_ENC_ERROR; + if (value == -1) { + options->values.cparams.channel_colors_pre_transform_percent = 95.0f; + } else { + options->values.cparams.channel_colors_pre_transform_percent = + static_cast(value); + } + return JXL_ENC_SUCCESS; + case JXL_ENC_OPTION_CHANNEL_COLORS_PERCENT: + if (value < -1 || value > 100) return JXL_ENC_ERROR; + if (value == -1) { + options->values.cparams.channel_colors_percent = 80.0f; + } else { + options->values.cparams.channel_colors_percent = + static_cast(value); + } + return JXL_ENC_SUCCESS; + case JXL_ENC_OPTION_PALETTE_COLORS: + if (value < -1 || value > 70913) return JXL_ENC_ERROR; + if (value == -1) { + options->values.cparams.palette_colors = 1 << 10; + } else { + options->values.cparams.palette_colors = value; + } + return JXL_ENC_SUCCESS; + case JXL_ENC_OPTION_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, + // alternatively, in the cjxl binary like now) + options->values.cparams.lossy_palette = (value == 1); + return JXL_ENC_SUCCESS; + case JXL_ENC_OPTION_MODULAR_COLOR_SPACE: + // TODO(lode): also add color transform option (xyb, none, ycbcr) + if (value < -1 || value > 37) return JXL_ENC_ERROR; + options->values.cparams.colorspace = value; + return JXL_ENC_SUCCESS; + case JXL_ENC_OPTION_MODULAR_GROUP_SIZE: + if (value < -1 || value > 3) return JXL_ENC_ERROR; + // 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 + // kept in cjxl and set it to 1 or 2 using this API. + if (value == -1) { + options->values.cparams.modular_group_size_shift = 1; + } else { + options->values.cparams.modular_group_size_shift = value; + } + return JXL_ENC_SUCCESS; + case JXL_ENC_OPTION_MODULAR_PREDICTOR: + if (value < -1 || value > 15) return JXL_ENC_ERROR; + options->values.cparams.options.predictor = + static_cast(value); + return JXL_ENC_SUCCESS; default: return JXL_ENC_ERROR; } @@ -329,6 +486,8 @@ void JxlEncoderReset(JxlEncoder* enc) { enc->input_closed = false; enc->basic_info_set = false; enc->color_encoding_set = false; + enc->use_container = false; + enc->codestream_level = 5; } void JxlEncoderDestroy(JxlEncoder* enc) { @@ -341,16 +500,31 @@ void JxlEncoderDestroy(JxlEncoder* enc) { 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"); + } enc->use_container = static_cast(use_container); return JXL_ENC_SUCCESS; } 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"); + } enc->store_jpeg_metadata = static_cast(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 (enc->wrote_bytes) { + return JXL_API_ERROR("this setting can only be set at the beginning"); + } + enc->codestream_level = level; + return JXL_ENC_SUCCESS; +} + JxlEncoderStatus JxlEncoderSetParallelRunner(JxlEncoder* enc, JxlParallelRunner parallel_runner, void* parallel_runner_opaque) { @@ -507,15 +681,6 @@ JxlEncoderStatus JxlEncoderProcessOutput(JxlEncoder* enc, uint8_t** next_out, return JXL_ENC_SUCCESS; } -JxlEncoderStatus JxlEncoderOptionsSetDecodingSpeed(JxlEncoderOptions* options, - int tier) { - if (tier < 0 || tier > 4) { - return JXL_ENC_ERROR; - } - options->values.cparams.decoding_speed_tier = tier; - return JXL_ENC_SUCCESS; -} - void JxlColorEncodingSetToSRGB(JxlColorEncoding* color_encoding, JXL_BOOL is_gray) { ConvertInternalToExternalColorEncoding(jxl::ColorEncoding::SRGB(is_gray), diff --git a/third_party/jpeg-xl/lib/jxl/encode_internal.h b/third_party/jpeg-xl/lib/jxl/encode_internal.h index 4c144bfc2914..a7f4b76b3cd5 100644 --- a/third_party/jpeg-xl/lib/jxl/encode_internal.h +++ b/third_party/jpeg-xl/lib/jxl/encode_internal.h @@ -19,6 +19,8 @@ namespace jxl { +// Options per-frame, this is not used for codestream-wide settings or global +// encoder settings. typedef struct JxlEncoderOptionsValuesStruct { // lossless is a separate setting from cparams because it is a combination // setting that overrides multiple settings inside of cparams. @@ -45,6 +47,8 @@ constexpr unsigned char kContainerHeader[] = { 0xa, 0, 0, 0, 0x14, 'f', 't', 'y', 'p', 'j', 'x', 'l', ' ', 0, 0, 0, 0, 'j', 'x', 'l', ' '}; +constexpr unsigned char kLevelBoxHeader[] = {0, 0, 0, 0x9, 'j', 'x', 'l', 'l'}; + namespace { template uint8_t* Extend(T* vec, size_t size) { @@ -91,6 +95,11 @@ struct JxlEncoderStruct { std::vector output_byte_queue; bool use_container = false; + + // 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 = 5; bool store_jpeg_metadata = false; jxl::CodecMetadata metadata; std::vector jpeg_metadata; @@ -106,6 +115,10 @@ struct JxlEncoderStruct { // bytes to the output_byte_queue. JxlEncoderStatus RefillOutputByteQueue(); + bool MustUseContainer() const { + return use_container || codestream_level != 5 || store_jpeg_metadata; + } + // Appends the bytes of a JXL box header with the provided type and size to // the end of the output_byte_queue. If unbounded is true, the size won't be // added to the header and the box will be assumed to continue until EOF. diff --git a/third_party/jpeg-xl/lib/jxl/encode_test.cc b/third_party/jpeg-xl/lib/jxl/encode_test.cc index bd9aaabf3180..a0ea84f8e9a4 100644 --- a/third_party/jpeg-xl/lib/jxl/encode_test.cc +++ b/third_party/jpeg-xl/lib/jxl/encode_test.cc @@ -56,9 +56,6 @@ TEST(EncodeTest, AddJPEGAfterCloseTest) { const std::string jpeg_path = "imagecompression.info/flower_foveon.png.im_q85_420.jpg"; const jxl::PaddedBytes orig = jxl::ReadTestData(jpeg_path); - jxl::CodecInOut orig_io; - ASSERT_TRUE( - SetFromBytes(jxl::Span(orig), &orig_io, /*pool=*/nullptr)); JxlEncoderOptions* options = JxlEncoderOptionsCreate(enc.get(), NULL); @@ -234,7 +231,8 @@ TEST(EncodeTest, OptionsTest) { JxlEncoderPtr enc = JxlEncoderMake(nullptr); EXPECT_NE(nullptr, enc.get()); JxlEncoderOptions* options = JxlEncoderOptionsCreate(enc.get(), NULL); - EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderOptionsSetEffort(options, 5)); + EXPECT_EQ(JXL_ENC_SUCCESS, + JxlEncoderOptionsSetInteger(options, JXL_ENC_OPTION_EFFORT, 5)); VerifyFrameEncoding(enc.get(), options); EXPECT_EQ(jxl::SpeedTier::kHare, enc->last_used_cparams.speed_tier); } @@ -244,9 +242,11 @@ TEST(EncodeTest, OptionsTest) { EXPECT_NE(nullptr, enc.get()); JxlEncoderOptions* options = JxlEncoderOptionsCreate(enc.get(), NULL); // Lower than currently supported values - EXPECT_EQ(JXL_ENC_ERROR, JxlEncoderOptionsSetEffort(options, 0)); + EXPECT_EQ(JXL_ENC_ERROR, + JxlEncoderOptionsSetInteger(options, JXL_ENC_OPTION_EFFORT, 0)); // Higher than currently supported values - EXPECT_EQ(JXL_ENC_ERROR, JxlEncoderOptionsSetEffort(options, 10)); + EXPECT_EQ(JXL_ENC_ERROR, + JxlEncoderOptionsSetInteger(options, JXL_ENC_OPTION_EFFORT, 10)); } { @@ -278,10 +278,99 @@ TEST(EncodeTest, OptionsTest) { JxlEncoderPtr enc = JxlEncoderMake(nullptr); EXPECT_NE(nullptr, enc.get()); JxlEncoderOptions* options = JxlEncoderOptionsCreate(enc.get(), NULL); - EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderOptionsSetDecodingSpeed(options, 2)); + EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderOptionsSetInteger( + options, JXL_ENC_OPTION_DECODING_SPEED, 2)); VerifyFrameEncoding(enc.get(), options); EXPECT_EQ(2u, enc->last_used_cparams.decoding_speed_tier); } + + { + JxlEncoderPtr enc = JxlEncoderMake(nullptr); + EXPECT_NE(nullptr, enc.get()); + JxlEncoderOptions* options = JxlEncoderOptionsCreate(enc.get(), NULL); + EXPECT_EQ(JXL_ENC_ERROR, JxlEncoderOptionsSetInteger( + options, JXL_ENC_OPTION_GROUP_ORDER, 100)); + EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderOptionsSetInteger( + options, JXL_ENC_OPTION_GROUP_ORDER, 1)); + EXPECT_EQ(JXL_ENC_SUCCESS, + JxlEncoderOptionsSetInteger( + options, JXL_ENC_OPTION_GROUP_ORDER_CENTER_X, 5)); + VerifyFrameEncoding(enc.get(), options); + EXPECT_EQ(true, enc->last_used_cparams.centerfirst); + EXPECT_EQ(5, enc->last_used_cparams.center_x); + } + + { + JxlEncoderPtr enc = JxlEncoderMake(nullptr); + EXPECT_NE(nullptr, enc.get()); + JxlEncoderOptions* options = JxlEncoderOptionsCreate(enc.get(), NULL); + EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderOptionsSetInteger( + options, JXL_ENC_OPTION_RESPONSIVE, 0)); + EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderOptionsSetInteger( + options, JXL_ENC_OPTION_PROGRESSIVE_AC, 1)); + EXPECT_EQ(JXL_ENC_SUCCESS, + JxlEncoderOptionsSetInteger(options, + JXL_ENC_OPTION_QPROGRESSIVE_AC, -1)); + EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderOptionsSetInteger( + options, JXL_ENC_OPTION_PROGRESSIVE_DC, 2)); + VerifyFrameEncoding(enc.get(), options); + EXPECT_EQ(false, enc->last_used_cparams.responsive); + EXPECT_EQ(true, enc->last_used_cparams.progressive_mode); + EXPECT_EQ(2, enc->last_used_cparams.progressive_dc); + } + + { + JxlEncoderPtr enc = JxlEncoderMake(nullptr); + EXPECT_NE(nullptr, enc.get()); + JxlEncoderOptions* options = JxlEncoderOptionsCreate(enc.get(), NULL); + EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderOptionsSetInteger( + options, JXL_ENC_OPTION_PHOTON_NOISE, 1777)); + VerifyFrameEncoding(enc.get(), options); + EXPECT_EQ(1777.0f, enc->last_used_cparams.photon_noise_iso); + } + + { + JxlEncoderPtr enc = JxlEncoderMake(nullptr); + EXPECT_NE(nullptr, enc.get()); + JxlEncoderOptions* options = JxlEncoderOptionsCreate(enc.get(), NULL); + EXPECT_EQ( + JXL_ENC_SUCCESS, + JxlEncoderOptionsSetInteger( + options, JXL_ENC_OPTION_CHANNEL_COLORS_PRE_TRANSFORM_PERCENT, 55)); + EXPECT_EQ(JXL_ENC_SUCCESS, + JxlEncoderOptionsSetInteger( + options, JXL_ENC_OPTION_CHANNEL_COLORS_PERCENT, 25)); + EXPECT_EQ(JXL_ENC_SUCCESS, + JxlEncoderOptionsSetInteger( + options, JXL_ENC_OPTION_PALETTE_COLORS, 70000)); + EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderOptionsSetInteger( + options, JXL_ENC_OPTION_LOSSY_PALETTE, 1)); + VerifyFrameEncoding(enc.get(), options); + 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_EQ(70000, enc->last_used_cparams.palette_colors); + EXPECT_EQ(true, enc->last_used_cparams.lossy_palette); + } + + { + JxlEncoderPtr enc = JxlEncoderMake(nullptr); + EXPECT_NE(nullptr, enc.get()); + JxlEncoderOptions* options = JxlEncoderOptionsCreate(enc.get(), NULL); + EXPECT_EQ(JXL_ENC_SUCCESS, + JxlEncoderOptionsSetInteger( + options, JXL_ENC_OPTION_MODULAR_COLOR_SPACE, 30)); + EXPECT_EQ(JXL_ENC_SUCCESS, + JxlEncoderOptionsSetInteger( + options, JXL_ENC_OPTION_MODULAR_GROUP_SIZE, 2)); + EXPECT_EQ(JXL_ENC_SUCCESS, + JxlEncoderOptionsSetInteger( + options, JXL_ENC_OPTION_MODULAR_PREDICTOR, 14)); + VerifyFrameEncoding(enc.get(), options); + 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); + } } namespace { @@ -466,18 +555,68 @@ TEST(EncodeTest, SingleFrameBoundedJXLCTest) { EXPECT_EQ(true, container.boxes[0].data_size_given); } +TEST(EncodeTest, CodestreamLevelTest) { + size_t xsize = 64; + size_t ysize = 64; + JxlPixelFormat pixel_format = {4, JXL_TYPE_UINT16, JXL_BIG_ENDIAN, 0}; + std::vector pixels = jxl::test::GetSomeTestImage(xsize, ysize, 4, 0); + + jxl::CodecInOut input_io = + jxl::test::SomeTestImageToCodecInOut(pixels, 4, xsize, ysize); + + JxlBasicInfo basic_info; + jxl::test::JxlBasicInfoSetFromPixelFormat(&basic_info, &pixel_format); + basic_info.xsize = xsize; + basic_info.ysize = ysize; + basic_info.uses_original_profile = false; + + JxlEncoderPtr enc = JxlEncoderMake(nullptr); + EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetCodestreamLevel(enc.get(), 10)); + JxlEncoderOptions* options = JxlEncoderOptionsCreate(enc.get(), NULL); + + EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetBasicInfo(enc.get(), &basic_info)); + JxlColorEncoding color_encoding; + JxlColorEncodingSetToSRGB(&color_encoding, + /*is_gray=*/pixel_format.num_channels < 3); + EXPECT_EQ(JXL_ENC_SUCCESS, + JxlEncoderSetColorEncoding(enc.get(), &color_encoding)); + EXPECT_EQ(JXL_ENC_SUCCESS, + JxlEncoderAddImageFrame(options, &pixel_format, pixels.data(), + pixels.size())); + JxlEncoderCloseInput(enc.get()); + + std::vector compressed = std::vector(64); + 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(enc.get(), &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()); + EXPECT_EQ(JXL_ENC_SUCCESS, process_result); + + Container container = {}; + jxl::Span encoded_span = + jxl::Span(compressed.data(), compressed.size()); + EXPECT_TRUE(container.Decode(&encoded_span)); + EXPECT_EQ(0u, encoded_span.size()); + EXPECT_EQ(0, memcmp("jxll", container.boxes[0].type, 4)); +} + TEST(EncodeTest, JXL_TRANSCODE_JPEG_TEST(JPEGReconstructionTest)) { const std::string jpeg_path = "imagecompression.info/flower_foveon.png.im_q85_420.jpg"; const jxl::PaddedBytes orig = jxl::ReadTestData(jpeg_path); - jxl::CodecInOut orig_io; - ASSERT_TRUE( - SetFromBytes(jxl::Span(orig), &orig_io, /*pool=*/nullptr)); JxlEncoderPtr enc = JxlEncoderMake(nullptr); JxlEncoderOptions* options = JxlEncoderOptionsCreate(enc.get(), NULL); - EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderUseContainer(enc.get(), JXL_TRUE)); EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderStoreJPEGMetadata(enc.get(), JXL_TRUE)); EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderAddJPEGFrame(options, orig.data(), orig.size())); @@ -528,6 +667,42 @@ TEST(EncodeTest, JXL_TRANSCODE_JPEG_TEST(JPEGReconstructionTest)) { EXPECT_EQ(0, memcmp(decoded_jpeg_bytes.data(), orig.data(), orig.size())); } +// This test is commented out until JxlEncoderAddBox is implemented, and is a +// prototype of JxlEncoderAddBox usage, not a finished test implementation. +#if 0 +TEST(EncodeTest, BoxTest) { + JxlEncoderPtr enc = JxlEncoderMake(nullptr); + EXPECT_NE(nullptr, enc.get()); + + // TODO(lode): create a test image, initialize encoder and options, prepare + // next_out and avail_out, and handle status and output buffer after the + // JxlEncoderProcessOutput calls below. + + // Add an early metadata box + JxlEncoderAddBox("Exif", exif_data, exif_size); + + // Write to output + status = JxlEncoderProcessOutput(enc.get(), &next_out, &avail_out); + + // Add image frame + EXPECT_EQ(JXL_ENC_ERROR, + JxlEncoderAddImageFrame(options, &pixel_format, pixels.data(), + pixels.size())); + // Indicate this is the last frame + JxlEncoderCloseInput(enc.get()); + + // Write to output + status = JxlEncoderProcessOutput(enc.get(), &next_out, &avail_out); + + // Add a late metadata box + JxlEncoderAddBox("XML ", xml_data, xml_size); + + // Write to output + status = JxlEncoderProcessOutput(enc.get(), &next_out, &avail_out); +} +#endif + +#if JPEGXL_ENABLE_JPEG // Loading .jpg files requires libjpeg support. 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; @@ -592,3 +767,4 @@ TEST(EncodeTest, JXL_TRANSCODE_JPEG_TEST(JPEGFrameTest)) { } } } +#endif // JPEGXL_ENABLE_JPEG diff --git a/third_party/jpeg-xl/lib/jxl/fake_parallel_runner_testonly.h b/third_party/jpeg-xl/lib/jxl/fake_parallel_runner_testonly.h new file mode 100644 index 000000000000..3b5c16b54324 --- /dev/null +++ b/third_party/jpeg-xl/lib/jxl/fake_parallel_runner_testonly.h @@ -0,0 +1,79 @@ +// 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_FAKE_PARALLEL_RUNNER_TESTONLY_H_ +#define LIB_JXL_FAKE_PARALLEL_RUNNER_TESTONLY_H_ + +#include + +#include + +#include "jxl/parallel_runner.h" +#include "lib/jxl/base/compiler_specific.h" +#include "lib/jxl/base/random.h" + +namespace jxl { + +// A parallel runner implementation that runs all the jobs in a single thread +// (the caller thread) but runs them pretending to use multiple threads and +// potentially out of order. This is useful for testing conditions that only +// occur under heavy load where the order of operations is different. +class FakeParallelRunner { + public: + FakeParallelRunner(uint32_t order_seed, uint32_t num_threads) + : order_seed_(order_seed), rng_(order_seed), num_threads_(num_threads) { + if (num_threads_ < 1) num_threads_ = 1; + } + + JxlParallelRetCode Run(void* jxl_opaque, JxlParallelRunInit init, + JxlParallelRunFunction func, uint32_t start, + uint32_t end) { + JxlParallelRetCode ret = init(jxl_opaque, num_threads_); + if (ret != 0) return ret; + + if (order_seed_ == 0) { + for (uint32_t i = start; i < end; i++) { + func(jxl_opaque, i, i % num_threads_); + } + } else { + std::vector order(end - start); + for (uint32_t i = start; i < end; i++) { + order[i - start] = i; + } + rng_.Shuffle(order.data(), order.size()); + for (uint32_t i = start; i < end; i++) { + func(jxl_opaque, order[i - start], i % num_threads_); + } + } + return ret; + } + + private: + // Seed for the RNG for defining the execution order. A value of 0 means + // sequential order from start to end. + uint32_t order_seed_; + + // The PRNG object, initialized with the order_seed_. Only used if the seed is + // not 0. + Rng rng_; + + // Number of fake threads. All the tasks are run on the same thread, but using + // different thread_id values based on this num_threads. + uint32_t num_threads_; +}; + +} // namespace jxl + +extern "C" { +// Function to pass as the parallel runner. +JXL_INLINE JxlParallelRetCode JxlFakeParallelRunner( + void* runner_opaque, void* jpegxl_opaque, JxlParallelRunInit init, + JxlParallelRunFunction func, uint32_t start_range, uint32_t end_range) { + return static_cast(runner_opaque) + ->Run(jpegxl_opaque, init, func, start_range, end_range); +} +} + +#endif // LIB_JXL_FAKE_PARALLEL_RUNNER_TESTONLY_H_ diff --git a/third_party/jpeg-xl/lib/jxl/fields.cc b/third_party/jpeg-xl/lib/jxl/fields.cc index 8648efba6e39..e941598c9021 100644 --- a/third_party/jpeg-xl/lib/jxl/fields.cc +++ b/third_party/jpeg-xl/lib/jxl/fields.cc @@ -12,6 +12,7 @@ #include "hwy/base.h" #include "lib/jxl/base/bits.h" +#include "lib/jxl/base/printf_macros.h" namespace jxl { diff --git a/third_party/jpeg-xl/lib/jxl/fields.h b/third_party/jpeg-xl/lib/jxl/fields.h index fce90ad3d657..18a57cfca294 100644 --- a/third_party/jpeg-xl/lib/jxl/fields.h +++ b/third_party/jpeg-xl/lib/jxl/fields.h @@ -8,6 +8,7 @@ // Forward/backward-compatible 'bundles' with auto-serialized 'fields'. +#include #include #include #include @@ -40,7 +41,8 @@ class BitsCoder { size_t* JXL_RESTRICT encoded_bits) { *encoded_bits = bits; if (value >= (1ULL << bits)) { - return JXL_FAILURE("Value %u too large for %" PRIuS " bits", value, bits); + return JXL_FAILURE("Value %u too large for %" PRIu64 " bits", value, + static_cast(bits)); } return true; } @@ -53,8 +55,8 @@ class BitsCoder { static Status Write(const size_t bits, const uint32_t value, BitWriter* JXL_RESTRICT writer) { if (value >= (1ULL << bits)) { - return JXL_FAILURE("Value %d too large to encode in %" PRIuS " bits", - value, bits); + return JXL_FAILURE("Value %d too large to encode in %" PRIu64 " bits", + value, static_cast(bits)); } writer->Write(bits, value); return true; diff --git a/third_party/jpeg-xl/lib/jxl/frame_header.cc b/third_party/jpeg-xl/lib/jxl/frame_header.cc index e4a6ab595eac..b08c351e81f8 100644 --- a/third_party/jpeg-xl/lib/jxl/frame_header.cc +++ b/third_party/jpeg-xl/lib/jxl/frame_header.cc @@ -6,6 +6,7 @@ #include "lib/jxl/frame_header.h" #include "lib/jxl/aux_out.h" +#include "lib/jxl/base/printf_macros.h" #include "lib/jxl/base/status.h" #include "lib/jxl/fields.h" diff --git a/third_party/jpeg-xl/lib/jxl/gauss_blur_test.cc b/third_party/jpeg-xl/lib/jxl/gauss_blur_test.cc index 14f93ef6e9a3..2aa94f786e9b 100644 --- a/third_party/jpeg-xl/lib/jxl/gauss_blur_test.cc +++ b/third_party/jpeg-xl/lib/jxl/gauss_blur_test.cc @@ -11,6 +11,7 @@ #include "gtest/gtest.h" #include "lib/extras/time.h" +#include "lib/jxl/base/printf_macros.h" #include "lib/jxl/convolve.h" #include "lib/jxl/image_ops.h" #include "lib/jxl/image_test_utils.h" diff --git a/third_party/jpeg-xl/lib/jxl/headers.cc b/third_party/jpeg-xl/lib/jxl/headers.cc index e9606f124f72..7add681e1aff 100644 --- a/third_party/jpeg-xl/lib/jxl/headers.cc +++ b/third_party/jpeg-xl/lib/jxl/headers.cc @@ -5,6 +5,7 @@ #include "lib/jxl/headers.h" +#include "lib/jxl/base/printf_macros.h" #include "lib/jxl/common.h" #include "lib/jxl/fields.h" diff --git a/third_party/jpeg-xl/lib/jxl/image.h b/third_party/jpeg-xl/lib/jxl/image.h index 6a1c389d4891..996fa6d93b53 100644 --- a/third_party/jpeg-xl/lib/jxl/image.h +++ b/third_party/jpeg-xl/lib/jxl/image.h @@ -8,6 +8,7 @@ // SIMD/multicore-friendly planar image representation with row accessors. +#include #include #include #include @@ -84,7 +85,7 @@ struct PlaneBase { #if defined(ADDRESS_SANITIZER) || defined(MEMORY_SANITIZER) || \ defined(THREAD_SANITIZER) if (y >= ysize_) { - JXL_ABORT("Row(%" PRIuS ") in (%u x %u) image\n", y, xsize_, ysize_); + JXL_ABORT("Row(%" PRIu64 ") in (%u x %u) image\n", y, xsize_, ysize_); } #endif @@ -223,6 +224,12 @@ class Rect { return Rect(x0_, y0_, xsize_, ysize_, image.xsize(), image.ysize()); } + // Construct a subrect that resides in the [0, ysize) x [0, xsize) region of + // the current rect. + Rect Crop(size_t area_xsize, size_t area_ysize) const { + return Rect(x0_, y0_, xsize_, ysize_, area_xsize, area_ysize); + } + // Returns a rect that only contains `num` lines with offset `y` from `y0()`. Rect Lines(size_t y, size_t num) const { JXL_DASSERT(y + num <= ysize_); @@ -416,9 +423,10 @@ class Image3 { #if defined(ADDRESS_SANITIZER) || defined(MEMORY_SANITIZER) || \ defined(THREAD_SANITIZER) if (c >= kNumPlanes || y >= ysize()) { - JXL_ABORT("PlaneRow(%" PRIuS ", %" PRIuS ") in (%" PRIuS " x %" PRIuS + JXL_ABORT("PlaneRow(%" PRIu64 ", %" PRIu64 ") in (%" PRIu64 " x %" PRIu64 ") image\n", - c, y, xsize(), ysize()); + static_cast(c), static_cast(y), + static_cast(xsize()), static_cast(ysize())); } #endif } diff --git a/third_party/jpeg-xl/lib/jxl/image_bundle.cc b/third_party/jpeg-xl/lib/jxl/image_bundle.cc index 48a631c58d9e..63470f3aed2b 100644 --- a/third_party/jpeg-xl/lib/jxl/image_bundle.cc +++ b/third_party/jpeg-xl/lib/jxl/image_bundle.cc @@ -11,6 +11,7 @@ #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" #include "lib/jxl/base/profiler.h" #include "lib/jxl/codec_in_out.h" #include "lib/jxl/color_management.h" diff --git a/third_party/jpeg-xl/lib/jxl/image_ops_test.cc b/third_party/jpeg-xl/lib/jxl/image_ops_test.cc index 3ed9d0d1fb13..8937364e8cd9 100644 --- a/third_party/jpeg-xl/lib/jxl/image_ops_test.cc +++ b/third_party/jpeg-xl/lib/jxl/image_ops_test.cc @@ -12,6 +12,7 @@ #include #include "gtest/gtest.h" +#include "lib/jxl/base/printf_macros.h" #include "lib/jxl/image.h" #include "lib/jxl/image_test_utils.h" diff --git a/third_party/jpeg-xl/lib/jxl/image_test_utils.h b/third_party/jpeg-xl/lib/jxl/image_test_utils.h index 46a7b3c1f3c8..3d6c00618f29 100644 --- a/third_party/jpeg-xl/lib/jxl/image_test_utils.h +++ b/third_party/jpeg-xl/lib/jxl/image_test_utils.h @@ -121,12 +121,13 @@ void VerifyRelativeError(const Plane& expected, const Plane& actual, if (any_bad) { // Never had a valid relative value, don't print it. if (max_relative < 0) { - fprintf(stderr, "c=%" PRIuS ": max +/- %E exceeds +/- %.2E\n", c, max_l1, - threshold_l1); + fprintf(stderr, "c=%" PRIu64 ": max +/- %E exceeds +/- %.2E\n", + static_cast(c), max_l1, threshold_l1); } else { fprintf(stderr, - "c=%" PRIuS ": max +/- %E, x %E exceeds +/- %.2E, x %.2E\n", c, - max_l1, max_relative, threshold_l1, threshold_relative); + "c=%" PRIu64 ": max +/- %E, x %E exceeds +/- %.2E, x %.2E\n", + static_cast(c), max_l1, max_relative, threshold_l1, + threshold_relative); } // Dump the expected image and actual image if the region is small enough. const intptr_t kMaxTestDumpSize = 16; @@ -230,7 +231,7 @@ typename std::enable_if::value>::type RandomFillImage( int64_t(std::numeric_limits::max()) + 1); } -void RandomFillImage(Plane* image) { +JXL_INLINE void RandomFillImage(Plane* image) { Rng rng(129); GenerateImage(rng, image, 0.0f, std::numeric_limits::max()); } @@ -250,7 +251,7 @@ typename std::enable_if::value>::type RandomFillImage( int64_t(std::numeric_limits::max()) + 1); } -void RandomFillImage(Image3F* image) { +JXL_INLINE void RandomFillImage(Image3F* image) { Rng rng(129); GenerateImage(rng, image, 0.0f, std::numeric_limits::max()); } diff --git a/third_party/jpeg-xl/lib/jxl/jpeg/enc_jpeg_data_reader.cc b/third_party/jpeg-xl/lib/jxl/jpeg/enc_jpeg_data_reader.cc index e2e8ee6fd075..f4aed1b1a0df 100644 --- a/third_party/jpeg-xl/lib/jxl/jpeg/enc_jpeg_data_reader.cc +++ b/third_party/jpeg-xl/lib/jxl/jpeg/enc_jpeg_data_reader.cc @@ -12,6 +12,7 @@ #include #include +#include "lib/jxl/base/printf_macros.h" #include "lib/jxl/base/status.h" #include "lib/jxl/common.h" #include "lib/jxl/jpeg/enc_jpeg_huffman_decode.h" diff --git a/third_party/jpeg-xl/lib/jxl/jpeg/jpeg_data.cc b/third_party/jpeg-xl/lib/jxl/jpeg/jpeg_data.cc index b405393e0483..50a184e407cc 100644 --- a/third_party/jpeg-xl/lib/jxl/jpeg/jpeg_data.cc +++ b/third_party/jpeg-xl/lib/jxl/jpeg/jpeg_data.cc @@ -5,6 +5,7 @@ #include "lib/jxl/jpeg/jpeg_data.h" +#include "lib/jxl/base/printf_macros.h" #include "lib/jxl/base/status.h" namespace jxl { @@ -143,15 +144,14 @@ Status JPEGData::VisitFields(Visitor* visitor) { } JPEGComponentType component_type = - components.size() == 1 && components[0].id == 1 - ? JPEGComponentType::kGray - : components.size() == 3 && components[0].id == 1 && - components[1].id == 2 && components[2].id == 3 - ? JPEGComponentType::kYCbCr - : components.size() == 3 && components[0].id == 'R' && - components[1].id == 'G' && components[2].id == 'B' - ? JPEGComponentType::kRGB - : JPEGComponentType::kCustom; + components.size() == 1 && components[0].id == 1 ? JPEGComponentType::kGray + : components.size() == 3 && components[0].id == 1 && + components[1].id == 2 && components[2].id == 3 + ? JPEGComponentType::kYCbCr + : components.size() == 3 && components[0].id == 'R' && + components[1].id == 'G' && components[2].id == 'B' + ? JPEGComponentType::kRGB + : JPEGComponentType::kCustom; JXL_RETURN_IF_ERROR( visitor->Bits(2, JPEGComponentType::kYCbCr, reinterpret_cast(&component_type))); @@ -195,10 +195,15 @@ Status JPEGData::VisitFields(Visitor* visitor) { } used_tables |= 1U << components[i].quant_idx; } - if (used_tables + 1 != 1U << quant.size()) { - return JXL_FAILURE("Not all quant tables are used (%" PRIuS - " tables, %" PRIx64 " used table mask)", - quant.size(), static_cast(used_tables)); + for (size_t i = 0; i < quant.size(); i++) { + if (used_tables & (1 << i)) continue; + if (i == 0) return JXL_FAILURE("First quant table unused."); + // Unused quant table has to be set to copy of previous quant table + for (size_t j = 0; j < 64; j++) { + if (quant[i].values[j] != quant[i - 1].values[j]) { + return JXL_FAILURE("Non-trivial unused quant table"); + } + } } uint32_t num_huff = huffman_code.size(); diff --git a/third_party/jpeg-xl/lib/jxl/jxl_test.cc b/third_party/jpeg-xl/lib/jxl/jxl_test.cc index 567eeaacb93a..b742c54a85fa 100644 --- a/third_party/jpeg-xl/lib/jxl/jxl_test.cc +++ b/third_party/jpeg-xl/lib/jxl/jxl_test.cc @@ -19,6 +19,7 @@ #include "lib/jxl/base/data_parallel.h" #include "lib/jxl/base/override.h" #include "lib/jxl/base/padded_bytes.h" +#include "lib/jxl/base/printf_macros.h" #include "lib/jxl/base/thread_pool_internal.h" #include "lib/jxl/codec_in_out.h" #include "lib/jxl/codec_y4m_testonly.h" @@ -30,6 +31,7 @@ #include "lib/jxl/enc_cache.h" #include "lib/jxl/enc_file.h" #include "lib/jxl/enc_params.h" +#include "lib/jxl/fake_parallel_runner_testonly.h" #include "lib/jxl/image.h" #include "lib/jxl/image_bundle.h" #include "lib/jxl/image_ops.h" @@ -196,7 +198,7 @@ TEST(JxlTest, RoundtripOtherTransforms) { EXPECT_LE(compressed_size, 23000u); EXPECT_THAT(ButteraugliDistance(*io, *io2, cparams.ba_params, /*distmap=*/nullptr, pool), - IsSlightlyBelow(4.0)); + IsSlightlyBelow(3.0)); // Check the consistency when performing another roundtrip. std::unique_ptr io3 = jxl::make_unique(); @@ -205,7 +207,7 @@ TEST(JxlTest, RoundtripOtherTransforms) { EXPECT_LE(compressed_size2, 23000u); EXPECT_THAT(ButteraugliDistance(*io, *io3, cparams.ba_params, /*distmap=*/nullptr, pool), - IsSlightlyBelow(4.0)); + IsSlightlyBelow(3.0)); } TEST(JxlTest, RoundtripResample2) { @@ -222,7 +224,7 @@ TEST(JxlTest, RoundtripResample2) { EXPECT_LE(Roundtrip(&io, cparams, dparams, pool, &io2), 17000u); EXPECT_THAT(ButteraugliDistance(io, io2, cparams.ba_params, /*distmap=*/nullptr, pool), - IsSlightlyBelow(10)); + IsSlightlyBelow(8)); } TEST(JxlTest, RoundtripResample2MT) { @@ -248,6 +250,31 @@ TEST(JxlTest, RoundtripResample2MT) { #endif } +// Roundtrip the image using a parallel runner that executes single-threaded but +// in random order. +TEST(JxlTest, RoundtripOutOfOrderProcessing) { + FakeParallelRunner fake_pool(/*order_seed=*/123, /*num_threads=*/8); + ThreadPool pool(&JxlFakeParallelRunner, &fake_pool); + const PaddedBytes orig = + ReadTestData("imagecompression.info/flower_foveon.png"); + CodecInOut io; + ASSERT_TRUE(SetFromBytes(Span(orig), &io, &pool)); + // Image size is selected so that the block border needed is larger than the + // amount of pixels available on the next block. + io.ShrinkTo(513, 515); + + CompressParams cparams; + // Force epf so we end up needing a lot of border. + cparams.epf = 3; + + DecompressParams dparams; + CodecInOut io2; + Roundtrip(&io, cparams, dparams, &pool, &io2); + + EXPECT_GE(1.5, ButteraugliDistance(io, io2, cparams.ba_params, + /*distmap=*/nullptr, &pool)); +} + TEST(JxlTest, RoundtripResample4) { ThreadPool* pool = nullptr; const PaddedBytes orig = @@ -262,7 +289,7 @@ TEST(JxlTest, RoundtripResample4) { EXPECT_LE(Roundtrip(&io, cparams, dparams, pool, &io2), 6000u); EXPECT_THAT(ButteraugliDistance(io, io2, cparams.ba_params, /*distmap=*/nullptr, pool), - IsSlightlyBelow(28)); + IsSlightlyBelow(22)); } TEST(JxlTest, RoundtripResample8) { @@ -279,7 +306,7 @@ TEST(JxlTest, RoundtripResample8) { EXPECT_LE(Roundtrip(&io, cparams, dparams, pool, &io2), 2100u); EXPECT_THAT(ButteraugliDistance(io, io2, cparams.ba_params, /*distmap=*/nullptr, pool), - IsSlightlyBelow(80)); + IsSlightlyBelow(50)); } TEST(JxlTest, RoundtripUnalignedD2) { @@ -706,7 +733,7 @@ TEST(JxlTest, RoundtripGrayscale) { EXPECT_LE(compressed.size(), 1300u); EXPECT_THAT(ButteraugliDistance(io, io2, cparams.ba_params, /*distmap=*/nullptr, pool), - IsSlightlyBelow(7.0)); + IsSlightlyBelow(6.0)); } } @@ -1202,7 +1229,7 @@ TEST(JxlTest, RoundtripYCbCr420) { // we're comparing an original PNG with a YCbCr 4:2:0 version EXPECT_THAT(ButteraugliDistance(io, io3, cparams.ba_params, /*distmap=*/nullptr, pool), - IsSlightlyBelow(2.8)); + IsSlightlyBelow(3.0)); } TEST(JxlTest, RoundtripDots) { diff --git a/third_party/jpeg-xl/lib/jxl/modular/encoding/enc_debug_tree.cc b/third_party/jpeg-xl/lib/jxl/modular/encoding/enc_debug_tree.cc index 1bca290734a7..f2a1705e4b87 100644 --- a/third_party/jpeg-xl/lib/jxl/modular/encoding/enc_debug_tree.cc +++ b/third_party/jpeg-xl/lib/jxl/modular/encoding/enc_debug_tree.cc @@ -9,6 +9,7 @@ #include #include "lib/jxl/base/os_macros.h" +#include "lib/jxl/base/printf_macros.h" #include "lib/jxl/base/status.h" #include "lib/jxl/modular/encoding/context_predict.h" #include "lib/jxl/modular/encoding/dec_ma.h" @@ -22,9 +23,7 @@ namespace jxl { -namespace { - -inline const char *PredictorName(Predictor p) { +const char *PredictorName(Predictor p) { switch (p) { case Predictor::Zero: return "Zero"; @@ -59,7 +58,7 @@ inline const char *PredictorName(Predictor p) { }; } -inline std::string PropertyName(size_t i) { +std::string PropertyName(size_t i) { static_assert(kNumNonrefProperties == 16, "Update this function"); switch (i) { case 0: @@ -99,8 +98,6 @@ inline std::string PropertyName(size_t i) { } } -} // namespace - void PrintTree(const Tree &tree, const std::string &path) { FILE *f = fopen((path + ".dot").c_str(), "w"); fprintf(f, "graph{\n"); diff --git a/third_party/jpeg-xl/lib/jxl/modular/encoding/enc_debug_tree.h b/third_party/jpeg-xl/lib/jxl/modular/encoding/enc_debug_tree.h index dd29204c6c59..78deaab1b869 100644 --- a/third_party/jpeg-xl/lib/jxl/modular/encoding/enc_debug_tree.h +++ b/third_party/jpeg-xl/lib/jxl/modular/encoding/enc_debug_tree.h @@ -9,6 +9,7 @@ #include #include +#include #include #include "lib/jxl/modular/encoding/dec_ma.h" @@ -16,6 +17,9 @@ namespace jxl { +const char *PredictorName(Predictor p); +std::string PropertyName(size_t i); + void PrintTree(const Tree &tree, const std::string &path); } // namespace jxl diff --git a/third_party/jpeg-xl/lib/jxl/modular/encoding/enc_encoding.cc b/third_party/jpeg-xl/lib/jxl/modular/encoding/enc_encoding.cc index 7c40c3001da4..f8780ea1db31 100644 --- a/third_party/jpeg-xl/lib/jxl/modular/encoding/enc_encoding.cc +++ b/third_party/jpeg-xl/lib/jxl/modular/encoding/enc_encoding.cc @@ -14,6 +14,7 @@ #include #include +#include "lib/jxl/base/printf_macros.h" #include "lib/jxl/base/status.h" #include "lib/jxl/common.h" #include "lib/jxl/dec_ans.h" diff --git a/third_party/jpeg-xl/lib/jxl/modular/encoding/encoding.cc b/third_party/jpeg-xl/lib/jxl/modular/encoding/encoding.cc index 0c9e00a16ec0..1e6a3e53fe48 100644 --- a/third_party/jpeg-xl/lib/jxl/modular/encoding/encoding.cc +++ b/third_party/jpeg-xl/lib/jxl/modular/encoding/encoding.cc @@ -10,6 +10,7 @@ #include +#include "lib/jxl/base/printf_macros.h" #include "lib/jxl/modular/encoding/context_predict.h" #include "lib/jxl/modular/options.h" @@ -446,7 +447,7 @@ Status ModularDecode(BitReader *br, Image &image, GroupHeader &header, max_tree_size += pixels; if (max_tree_size < pixels) return JXL_FAILURE("Tree size overflow"); } - + max_tree_size = std::min(static_cast(1 << 20), max_tree_size); JXL_RETURN_IF_ERROR(DecodeTree(br, &tree_storage, max_tree_size)); JXL_RETURN_IF_ERROR(DecodeHistograms(br, (tree_storage.size() + 1) / 2, &code_storage, &context_map_storage)); diff --git a/third_party/jpeg-xl/lib/jxl/modular/modular_image.cc b/third_party/jpeg-xl/lib/jxl/modular/modular_image.cc index 943261b3e96c..d66ca978f46f 100644 --- a/third_party/jpeg-xl/lib/jxl/modular/modular_image.cc +++ b/third_party/jpeg-xl/lib/jxl/modular/modular_image.cc @@ -13,7 +13,7 @@ namespace jxl { void Image::undo_transforms(const weighted::Header &wp_header, jxl::ThreadPool *pool) { - while (transform.size() > 0) { + while (!transform.empty()) { Transform t = transform.back(); JXL_DEBUG_V(4, "Undoing transform"); Status result = t.Inverse(*this, wp_header, pool); diff --git a/third_party/jpeg-xl/lib/jxl/modular/transform/squeeze.cc b/third_party/jpeg-xl/lib/jxl/modular/transform/squeeze.cc index 7353556e2869..7e3029bd8aca 100644 --- a/third_party/jpeg-xl/lib/jxl/modular/transform/squeeze.cc +++ b/third_party/jpeg-xl/lib/jxl/modular/transform/squeeze.cc @@ -8,6 +8,7 @@ #include #include "lib/jxl/base/data_parallel.h" +#include "lib/jxl/base/printf_macros.h" #include "lib/jxl/common.h" #include "lib/jxl/modular/modular_image.h" #include "lib/jxl/modular/transform/transform.h" diff --git a/third_party/jpeg-xl/lib/jxl/modular/transform/transform.cc b/third_party/jpeg-xl/lib/jxl/modular/transform/transform.cc index 4f557ea318c5..28d5e5be4dd8 100644 --- a/third_party/jpeg-xl/lib/jxl/modular/transform/transform.cc +++ b/third_party/jpeg-xl/lib/jxl/modular/transform/transform.cc @@ -5,6 +5,7 @@ #include "lib/jxl/modular/transform/transform.h" +#include "lib/jxl/base/printf_macros.h" #include "lib/jxl/fields.h" #include "lib/jxl/modular/modular_image.h" #include "lib/jxl/modular/transform/palette.h" diff --git a/third_party/jpeg-xl/lib/jxl/quant_weights.cc b/third_party/jpeg-xl/lib/jxl/quant_weights.cc index 74f288812f89..3fd3c245b235 100644 --- a/third_party/jpeg-xl/lib/jxl/quant_weights.cc +++ b/third_party/jpeg-xl/lib/jxl/quant_weights.cc @@ -13,6 +13,7 @@ #include #include "lib/jxl/base/bits.h" +#include "lib/jxl/base/printf_macros.h" #include "lib/jxl/base/status.h" #include "lib/jxl/common.h" #include "lib/jxl/dct_scales.h" diff --git a/third_party/jpeg-xl/lib/jxl/roundtrip_test.cc b/third_party/jpeg-xl/lib/jxl/roundtrip_test.cc index dd99ad61ae8e..898707e0477f 100644 --- a/third_party/jpeg-xl/lib/jxl/roundtrip_test.cc +++ b/third_party/jpeg-xl/lib/jxl/roundtrip_test.cc @@ -567,6 +567,7 @@ TEST(RoundtripTest, TestICCProfile) { JxlDecoderDestroy(dec); } +#if JPEGXL_ENABLE_JPEG // Loading .jpg files requires libjpeg support. TEST(RoundtripTest, JXL_TRANSCODE_JPEG_TEST(TestJPEGReconstruction)) { const std::string jpeg_path = "imagecompression.info/flower_foveon.png.im_q85_420.jpg"; @@ -613,3 +614,4 @@ TEST(RoundtripTest, JXL_TRANSCODE_JPEG_TEST(TestJPEGReconstruction)) { ASSERT_EQ(used, orig.size()); EXPECT_EQ(0, memcmp(reconstructed_buffer.data(), orig.data(), used)); } +#endif // JPEGXL_ENABLE_JPEG diff --git a/third_party/jpeg-xl/lib/jxl/sanitizers.h b/third_party/jpeg-xl/lib/jxl/sanitizers.h index 72cffc6f7726..4b6615d7f6d6 100644 --- a/third_party/jpeg-xl/lib/jxl/sanitizers.h +++ b/third_party/jpeg-xl/lib/jxl/sanitizers.h @@ -6,6 +6,7 @@ #ifndef LIB_JXL_SANITIZERS_H_ #define LIB_JXL_SANITIZERS_H_ +#include #include #include "lib/jxl/base/compiler_specific.h" @@ -99,8 +100,8 @@ template static JXL_INLINE JXL_MAYBE_UNUSED void PrintImageUninitialized( const Plane& im) { fprintf(stderr, - "Uninitialized regions for image of size %" PRIuS "x%" PRIuS ":\n", - im.xsize(), im.ysize()); + "Uninitialized regions for image of size %" PRIu64 "x%" PRIu64 ":\n", + static_cast(im.xsize()), static_cast(im.ysize())); // A segment of uninitialized pixels in a row, in the format [first, second). typedef std::pair PixelSegment; @@ -138,15 +139,18 @@ static JXL_INLINE JXL_MAYBE_UNUSED void PrintImageUninitialized( return; } if (end_y - start_y_ > 1) { - fprintf(stderr, " y=[%" PRIdS ", %" PRIuS "):", start_y_, end_y); + fprintf(stderr, " y=[%" PRId64 ", %" PRIu64 "):", + static_cast(start_y_), static_cast(end_y)); } else { - fprintf(stderr, " y=[%" PRIdS "]:", start_y_); + fprintf(stderr, " y=[%" PRId64 "]:", static_cast(start_y_)); } for (const auto& seg : segments_) { if (seg.first + 1 == seg.second) { - fprintf(stderr, " [%" PRIdS "]", seg.first); + fprintf(stderr, " [%" PRId64 "]", static_cast(seg.first)); } else { - fprintf(stderr, " [%" PRIdS ", %" PRIuS ")", seg.first, seg.second); + fprintf(stderr, " [%" PRId64 ", %" PRIu64 ")", + static_cast(seg.first), + static_cast(seg.second)); } } fprintf(stderr, "\n"); @@ -203,16 +207,20 @@ static JXL_INLINE JXL_MAYBE_UNUSED void CheckImageInitialized( const auto* row = im.Row(y); intptr_t ret = __msan_test_shadow(row + r.x0(), sizeof(*row) * r.xsize()); if (ret != -1) { - JXL_DEBUG(1, - "Checking an image of %" PRIuS " x %" PRIuS ", rect x0=%" PRIuS - ", y0=%" PRIuS - ", " - "xsize=%" PRIuS ", ysize=%" PRIuS, - im.xsize(), im.ysize(), r.x0(), r.y0(), r.xsize(), r.ysize()); + JXL_DEBUG( + 1, + "Checking an image of %" PRIu64 " x %" PRIu64 ", rect x0=%" PRIu64 + ", y0=%" PRIu64 + ", " + "xsize=%" PRIu64 ", ysize=%" PRIu64, + static_cast(im.xsize()), static_cast(im.ysize()), + static_cast(r.x0()), static_cast(r.y0()), + static_cast(r.xsize()), static_cast(r.ysize())); size_t x = ret / sizeof(*row); JXL_DEBUG( - 1, "CheckImageInitialized failed at x=%" PRIuS ", y=%" PRIuS ": %s", - r.x0() + x, y, message ? message : ""); + 1, "CheckImageInitialized failed at x=%" PRIu64 ", y=%" PRIu64 ": %s", + static_cast(r.x0() + x), static_cast(y), + message ? message : ""); PrintImageUninitialized(im); } // This will report an error if memory is not initialized. diff --git a/third_party/jpeg-xl/lib/jxl/splines.cc b/third_party/jpeg-xl/lib/jxl/splines.cc index 34308fd17e16..58ebfd68113d 100644 --- a/third_party/jpeg-xl/lib/jxl/splines.cc +++ b/third_party/jpeg-xl/lib/jxl/splines.cc @@ -9,6 +9,7 @@ #include #include "lib/jxl/ans_params.h" +#include "lib/jxl/base/printf_macros.h" #include "lib/jxl/base/status.h" #include "lib/jxl/chroma_from_luma.h" #include "lib/jxl/common.h" diff --git a/third_party/jpeg-xl/lib/jxl/splines_test.cc b/third_party/jpeg-xl/lib/jxl/splines_test.cc index 9e29b40d1cc4..8b6d6854347f 100644 --- a/third_party/jpeg-xl/lib/jxl/splines_test.cc +++ b/third_party/jpeg-xl/lib/jxl/splines_test.cc @@ -8,6 +8,7 @@ #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_splines.h" diff --git a/third_party/jpeg-xl/lib/jxl_benchmark.cmake b/third_party/jpeg-xl/lib/jxl_benchmark.cmake index 7aa15b7b2c1a..5b658d7a31b3 100644 --- a/third_party/jpeg-xl/lib/jxl_benchmark.cmake +++ b/third_party/jpeg-xl/lib/jxl_benchmark.cmake @@ -8,6 +8,7 @@ set(JPEGXL_INTERNAL_SOURCES_GBENCH extras/tone_mapping_gbench.cc jxl/dec_external_image_gbench.cc + jxl/dec_reconstruct_gbench.cc jxl/enc_external_image_gbench.cc jxl/gauss_blur_gbench.cc jxl/splines_gbench.cc @@ -31,7 +32,7 @@ if(benchmark_FOUND) # Compiles all the benchmark files into a single binary. Individual benchmarks # can be run with --benchmark_filter. - add_executable(jxl_gbench "${JPEGXL_INTERNAL_SOURCES_GBENCH}") + add_executable(jxl_gbench "${JPEGXL_INTERNAL_SOURCES_GBENCH}" gbench_main.cc) target_compile_definitions(jxl_gbench PRIVATE -DTEST_DATA_PATH="${PROJECT_SOURCE_DIR}/third_party/testdata") @@ -39,7 +40,6 @@ if(benchmark_FOUND) jxl_extras-static jxl-static benchmark::benchmark - benchmark::benchmark_main ) endif() # benchmark_FOUND diff --git a/third_party/jpeg-xl/lib/jxl_tests.cmake b/third_party/jpeg-xl/lib/jxl_tests.cmake index 5bd00e3bed50..8f155daaee60 100644 --- a/third_party/jpeg-xl/lib/jxl_tests.cmake +++ b/third_party/jpeg-xl/lib/jxl_tests.cmake @@ -70,6 +70,7 @@ set(TESTLIB_FILES jxl/dct_for_test.h jxl/dec_transforms_testonly.cc jxl/dec_transforms_testonly.h + jxl/fake_parallel_runner_testonly.h jxl/image_test_utils.h jxl/test_utils.h jxl/testdata.h diff --git a/third_party/jpeg-xl/lib/lib.gni b/third_party/jpeg-xl/lib/lib.gni index 56fd1d2441db..34268902bff1 100644 --- a/third_party/jpeg-xl/lib/lib.gni +++ b/third_party/jpeg-xl/lib/lib.gni @@ -52,6 +52,7 @@ libjxl_dec_sources = [ "jxl/base/override.h", "jxl/base/padded_bytes.cc", "jxl/base/padded_bytes.h", + "jxl/base/printf_macros.h", "jxl/base/profiler.h", "jxl/base/random.cc", "jxl/base/random.h", @@ -321,6 +322,7 @@ libjxl_enc_sources = [ libjxl_gbench_sources = [ "extras/tone_mapping_gbench.cc", "jxl/dec_external_image_gbench.cc", + "jxl/dec_reconstruct_gbench.cc", "jxl/enc_external_image_gbench.cc", "jxl/gauss_blur_gbench.cc", "jxl/splines_gbench.cc", @@ -389,6 +391,7 @@ libjxl_testlib_sources = [ "jxl/dct_for_test.h", "jxl/dec_transforms_testonly.cc", "jxl/dec_transforms_testonly.h", + "jxl/fake_parallel_runner_testonly.h", "jxl/image_test_utils.h", "jxl/test_utils.h", "jxl/testdata.h", diff --git a/third_party/jpeg-xl/lib/profiler/profiler.cc b/third_party/jpeg-xl/lib/profiler/profiler.cc index d12589e2d251..63d8569df9e2 100644 --- a/third_party/jpeg-xl/lib/profiler/profiler.cc +++ b/third_party/jpeg-xl/lib/profiler/profiler.cc @@ -432,7 +432,7 @@ void ThreadSpecific::ComputeOverhead() { std::sort(samples, samples + kNumSamples); self_overhead = samples[kNumSamples / 2]; #if PROFILER_PRINT_OVERHEAD - printf("Overhead: %" PRIuS "\n", self_overhead); + printf("Overhead: %" PRIu64 "\n", static_cast(self_overhead)); #endif results_->SetSelfOverhead(self_overhead); } @@ -468,7 +468,8 @@ void ThreadSpecific::ComputeOverhead() { std::sort(samples, samples + kNumSamples); const uint64_t child_overhead = samples[9 * kNumSamples / 10]; #if PROFILER_PRINT_OVERHEAD - printf("Child overhead: %" PRIuS "\n", child_overhead); + printf("Child overhead: %" PRIu64 "\n", + static_cast(child_overhead)); #endif results_->SetChildOverhead(child_overhead); } diff --git a/third_party/jpeg-xl/lib/profiler/tsc_timer.h b/third_party/jpeg-xl/lib/profiler/tsc_timer.h index 27db276f70bc..d3c1bee453b2 100644 --- a/third_party/jpeg-xl/lib/profiler/tsc_timer.h +++ b/third_party/jpeg-xl/lib/profiler/tsc_timer.h @@ -10,20 +10,43 @@ // ensure exactly the desired regions are measured. #include +#include // clock_gettime + +#if defined(_WIN32) || defined(_WIN64) +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif // WIN32_LEAN_AND_MEAN +#ifndef NOMINMAX +#define NOMINMAX +#endif // NOMINMAX +#include +// Undef macros to avoid collisions +#undef LoadFence +#undef StoreFence +#endif + +#if defined(__MACH__) +#include +#include +#endif + +#if defined(__HAIKU__) +#include +#endif #include #include #include // LoadFence -#if HWY_COMPILER_MSVC -#include -#endif // HWY_COMPILER_MSVC - namespace profiler { +// Ticks := platform-specific timer values (CPU cycles on x86). Must be +// unsigned to guarantee wraparound on overflow. +using Ticks = uint64_t; + // TicksBefore/After return absolute timestamps and must be placed immediately -// before and after the region to measure. The functions are distinct because -// they use different fences. +// before and after the region to measure. We provide separate Before/After +// functions because they use different fences. // // Background: RDTSC is not 'serializing'; earlier instructions may complete // after it, and/or later instructions may complete before it. 'Fences' ensure @@ -59,7 +82,7 @@ namespace profiler { // Using Before+Before leads to higher variance and overhead than After+After. // However, After+After includes an LFENCE in the region measurements, which // adds a delay dependent on earlier loads. The combination of Before+After -// is faster than Before+Before and more consistent than Stop+Stop because +// is faster than Before+Before and more consistent than After+After because // the first LFENCE already delayed subsequent loads before the measured // region. This combination seems not to have been considered in prior work: // http://akaros.cs.berkeley.edu/lxr/akaros/kern/arch/x86/rdtsc_test.c @@ -71,19 +94,18 @@ namespace profiler { // by several under/over-count errata, so we use the TSC instead. // Returns a 64-bit timestamp in unit of 'ticks'; to convert to seconds, -// divide by InvariantTicksPerSecond. Although 32-bit ticks are faster to read, -// they overflow too quickly to measure long regions. -static HWY_INLINE HWY_MAYBE_UNUSED uint64_t TicksBefore() { - uint64_t t; -#if HWY_ARCH_PPC +// divide by InvariantTicksPerSecond. +static HWY_INLINE HWY_MAYBE_UNUSED Ticks TicksBefore() { + Ticks t; +#if HWY_ARCH_PPC && defined(__GLIBC__) asm volatile("mfspr %0, %1" : "=r"(t) : "i"(268)); -#elif HWY_ARCH_X86_64 && HWY_COMPILER_MSVC +#elif HWY_ARCH_X86 && HWY_COMPILER_MSVC hwy::LoadFence(); HWY_FENCE; t = __rdtsc(); hwy::LoadFence(); HWY_FENCE; -#elif HWY_ARCH_X86_64 && (HWY_COMPILER_CLANG || HWY_COMPILER_GCC) +#elif HWY_ARCH_X86_64 asm volatile( "lfence\n\t" "rdtsc\n\t" @@ -95,30 +117,35 @@ static HWY_INLINE HWY_MAYBE_UNUSED uint64_t TicksBefore() { // "memory" avoids reordering. rdx = TSC >> 32. // "cc" = flags modified by SHL. : "rdx", "memory", "cc"); -#elif HWY_COMPILER_MSVC - // Use std::chrono in MSVC 32-bit. - t = std::chrono::time_point_cast( - std::chrono::steady_clock::now()) - .time_since_epoch() - .count(); -#else - // Fall back to OS - unsure how to reliably query cntvct_el0 frequency. +#elif HWY_ARCH_RVV + asm volatile("rdcycle %0" : "=r"(t)); +#elif defined(_WIN32) || defined(_WIN64) + LARGE_INTEGER counter; + (void)QueryPerformanceCounter(&counter); + t = counter.QuadPart; +#elif defined(__MACH__) + t = mach_absolute_time(); +#elif defined(__HAIKU__) + t = system_time_nsecs(); // since boot +#else // POSIX timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); - t = ts.tv_sec * 1000000000LL + ts.tv_nsec; + t = static_cast(ts.tv_sec * 1000000000LL + ts.tv_nsec); #endif return t; } -static HWY_INLINE HWY_MAYBE_UNUSED uint64_t TicksAfter() { - uint64_t t; -#if HWY_ARCH_X86_64 && HWY_COMPILER_MSVC +static HWY_INLINE HWY_MAYBE_UNUSED Ticks TicksAfter() { + Ticks t; +#if HWY_ARCH_PPC && defined(__GLIBC__) + asm volatile("mfspr %0, %1" : "=r"(t) : "i"(268)); +#elif HWY_ARCH_X86 && HWY_COMPILER_MSVC HWY_FENCE; unsigned aux; t = __rdtscp(&aux); hwy::LoadFence(); HWY_FENCE; -#elif HWY_ARCH_X86_64 && (HWY_COMPILER_CLANG || HWY_COMPILER_GCC) +#elif HWY_ARCH_X86_64 // Use inline asm because __rdtscp generates code to store TSC_AUX (ecx). asm volatile( "rdtscp\n\t" diff --git a/third_party/jpeg-xl/plugins/gimp/file-jxl-save.cc b/third_party/jpeg-xl/plugins/gimp/file-jxl-save.cc index 21ac7e2ab02a..9301f0d75e26 100644 --- a/third_party/jpeg-xl/plugins/gimp/file-jxl-save.cc +++ b/third_party/jpeg-xl/plugins/gimp/file-jxl-save.cc @@ -761,8 +761,10 @@ bool SaveJpegXlImage(const gint32 image_id, const gint32 drawable_id, JxlEncoderOptions* enc_opts; enc_opts = JxlEncoderOptionsCreate(enc.get(), nullptr); - JxlEncoderOptionsSetEffort(enc_opts, jxl_save_opts.encoding_effort); - JxlEncoderOptionsSetDecodingSpeed(enc_opts, jxl_save_opts.faster_decoding); + JxlEncoderOptionsSetInteger(enc_opts, JXL_ENC_OPTION_EFFORT, + jxl_save_opts.encoding_effort); + JxlEncoderOptionsSetInteger(enc_opts, JXL_ENC_OPTION_DECODING_SPEED, + jxl_save_opts.faster_decoding); // lossless mode if (jxl_save_opts.lossless || jxl_save_opts.distance < 0.01) {