diff --git a/media/libjxl/moz.build b/media/libjxl/moz.build --- a/media/libjxl/moz.build +++ b/media/libjxl/moz.build @@ -17,11 +17,10 @@ "/third_party/jpeg-xl/lib/jxl/aux_out.cc", "/third_party/jpeg-xl/lib/jxl/base/cache_aligned.cc", "/third_party/jpeg-xl/lib/jxl/base/data_parallel.cc", "/third_party/jpeg-xl/lib/jxl/base/padded_bytes.cc", "/third_party/jpeg-xl/lib/jxl/base/random.cc", - "/third_party/jpeg-xl/lib/jxl/base/status.cc", "/third_party/jpeg-xl/lib/jxl/blending.cc", "/third_party/jpeg-xl/lib/jxl/box_content_decoder.cc", "/third_party/jpeg-xl/lib/jxl/chroma_from_luma.cc", "/third_party/jpeg-xl/lib/jxl/coeff_order.cc", "/third_party/jpeg-xl/lib/jxl/color_encoding_internal.cc", @@ -44,10 +43,11 @@ "/third_party/jpeg-xl/lib/jxl/decode.cc", "/third_party/jpeg-xl/lib/jxl/decode_to_jpeg.cc", "/third_party/jpeg-xl/lib/jxl/enc_bit_writer.cc", "/third_party/jpeg-xl/lib/jxl/entropy_coder.cc", "/third_party/jpeg-xl/lib/jxl/epf.cc", + "/third_party/jpeg-xl/lib/jxl/exif.cc", "/third_party/jpeg-xl/lib/jxl/fast_dct.cc", "/third_party/jpeg-xl/lib/jxl/fields.cc", "/third_party/jpeg-xl/lib/jxl/frame_header.cc", "/third_party/jpeg-xl/lib/jxl/gauss_blur.cc", "/third_party/jpeg-xl/lib/jxl/headers.cc", diff --git a/media/libjxl/moz.yaml b/media/libjxl/moz.yaml --- a/media/libjxl/moz.yaml +++ b/media/libjxl/moz.yaml @@ -8,13 +8,13 @@ name: libjxl description: JPEG XL image format reference implementation url: https://github.com/libjxl/libjxl - release: commit 318c592d98b97d103941b90d47107f06a10c71da (2022-03-21T20:44:46Z). + release: 4da1c812f47ea4ca3a09ca135099e494e85e9656 (2022-04-26T11:25:32Z). - revision: 318c592d98b97d103941b90d47107f06a10c71da + revision: 4da1c812f47ea4ca3a09ca135099e494e85e9656 license: Apache-2.0 license-file: LICENSE diff --git a/third_party/jpeg-xl/.github/workflows/build_test.yml b/third_party/jpeg-xl/.github/workflows/build_test.yml --- a/third_party/jpeg-xl/.github/workflows/build_test.yml +++ b/third_party/jpeg-xl/.github/workflows/build_test.yml @@ -44,11 +44,11 @@ # test on AVX3-enabled CPUs when changing this value. env_test_stack_size: 4000 # Build scalar-only hwy instructions. - name: scalar mode: release - cxxflags: -DHWY_DISABLED_TARGETS=~HWY_SCALAR + cxxflags: -DHWY_COMPILE_ONLY_SCALAR # Disabling optional features to speed up msan build a little bit. - name: msan skip_install: true cmake_args: >- -DJPEGXL_ENABLE_DEVTOOLS=OFF -DJPEGXL_ENABLE_PLUGINS=OFF @@ -57,12 +57,10 @@ skip_install: true - name: coverage apt_pkgs: gcovr # Coverage builds require a bit more RAM. env_test_stack_size: 2048 - # Exclude roundtrip tests from the unittest coverage. - ctest_args: -E '^JxlTest' # Build with support for decoding to JPEG bytes disabled. Produces a # smaller build if only decoding to pixels is needed. - name: release-nojpeg mode: release cxxflags: -DJXL_DEBUG_ON_ABORT=0 @@ -147,10 +145,11 @@ # hash as the starting point from the restore-keys entry. key: ${{ runner.os }}-${{ github.sha }}-${{ matrix.name }} restore-keys: | ${{ runner.os }}-${{ steps.git-env.outputs.parent }}-${{ matrix.name }} - name: Build + if: matrix.name != 'coverage' || env.WILL_RUN_TESTS == 'true' run: | mkdir -p ${CCACHE_DIR} echo "max_size = 200M" > ${CCACHE_DIR}/ccache.conf mode="${{ matrix.mode }}" build_tests=$([ "$WILL_RUN_TESTS" == "true" ] && echo "ON" || echo "OFF") @@ -214,14 +213,14 @@ with: flags: unittests files: build/coverage.xml - name: Fast benchmark ${{ matrix.mode }} if: | - github.event_name == 'push' || + matrix.name != 'coverage' && (github.event_name == 'push' || (github.event_name == 'pull_request' && ( matrix.test_in_pr || - contains(github.event.pull_request.labels.*.names, 'CI:full'))) + contains(github.event.pull_request.labels.*.names, 'CI:full')))) run: | STORE_IMAGES=0 ./ci.sh fast_benchmark # Run gbench once, just to make sure it runs, not for actual benchmarking. # This doesn't work on msan because we use gbench library from the system # which is not instrumented by msan. @@ -308,11 +307,10 @@ sudo apt update pkgs=( # Build dependencies cmake doxygen - libgtest-dev:${{ matrix.arch }} ninja-build pkg-config qemu-user-static xvfb diff --git a/third_party/jpeg-xl/.github/workflows/conformance.yml b/third_party/jpeg-xl/.github/workflows/conformance.yml --- a/third_party/jpeg-xl/.github/workflows/conformance.yml +++ b/third_party/jpeg-xl/.github/workflows/conformance.yml @@ -96,12 +96,11 @@ run: needs: build runs-on: ubuntu-latest strategy: matrix: - # 'main_level10' currently fails - name: [main_level5] + name: [main_level5, main_level10] steps: - name: Install deps run: | pip install numpy - name: Checkout the conformance source diff --git a/third_party/jpeg-xl/.github/workflows/release.yaml b/third_party/jpeg-xl/.github/workflows/release.yaml --- a/third_party/jpeg-xl/.github/workflows/release.yaml +++ b/third_party/jpeg-xl/.github/workflows/release.yaml @@ -144,10 +144,15 @@ "https://github.com/git/git/archive/refs/tags/v${git_version}.tar.gz" tar -zxf "v${git_version}.tar.gz" cd "git-${git_version}" make prefix=/usr -j4 install + - name: Set git safe dir + run: | + export GIT_CEILING_DIRECTORIES=/__w # only work before git v2.35.2 + git config --global --add safe.directory /__w/libjxl/libjxl + - name: Checkout the source uses: actions/checkout@v2 with: submodules: true fetch-depth: 1 diff --git a/third_party/jpeg-xl/AUTHORS b/third_party/jpeg-xl/AUTHORS --- a/third_party/jpeg-xl/AUTHORS +++ b/third_party/jpeg-xl/AUTHORS @@ -23,19 +23,23 @@ CanadianBaconBoi Daniel Novomeský David Burnett Dirk Lemstra Don Olmstead +Heiko Becker Jon Sneyers Kleis Auke Wolthuizen +L. E. Segovia Leo Izen Lovell Fuller Marcin Konicki +Martin Strunz Mathieu Malaterre Misaki Kasumi Petr Diblík Pieter Wuille Samuel Leong +Stephan T. Lavavej Vincent Torri xiota Yonatan Nebenzhal Ziemowit Zabawa diff --git a/third_party/jpeg-xl/CHANGELOG.md b/third_party/jpeg-xl/CHANGELOG.md --- a/third_party/jpeg-xl/CHANGELOG.md +++ b/third_party/jpeg-xl/CHANGELOG.md @@ -21,10 +21,13 @@ - decoder API: field added to `JxlFrameHeader`: a `JxlLayerInfo` struct that contains crop dimensions and offsets and blending information for the non-coalesced case. - decoder API: new function `JxlDecoderGetExtraChannelBlendInfo` to get the blending information for extra channels in the non-coalesced case. + - decoder API: new function `JxlDecoderSetMultithreadedImageOutCallback`, + allowing output callbacks to receive more information about the number of + threads on which they are running. - encoder API: added ability to set several encoder options to frames using `JxlEncoderFrameSettingsSetOption` - encoder API: new functions `JxlEncoderSetFrameHeader` and `JxlEncoderSetExtraChannelBlendInfo` to set animation and blending parameters of the frame, and `JxlEncoderInitFrameHeader` and diff --git a/third_party/jpeg-xl/CMakeLists.txt b/third_party/jpeg-xl/CMakeLists.txt --- a/third_party/jpeg-xl/CMakeLists.txt +++ b/third_party/jpeg-xl/CMakeLists.txt @@ -189,10 +189,19 @@ # Threads set(THREADS_PREFER_PTHREAD_FLAG YES) find_package(Threads REQUIRED) +# These settings are important to drive check_cxx_source_compiles +# See CMP0067 (min cmake version is 3.10 anyway) +set(CMAKE_CXX_STANDARD 11) +set(CMAKE_CXX_EXTENSIONS OFF) +set(CMAKE_CXX_STANDARD_REQUIRED YES) + +# Atomics +find_package(Atomics REQUIRED) + if(JPEGXL_STATIC) if (MINGW) # In MINGW libstdc++ uses pthreads directly. When building statically a # program (regardless of whether the source code uses pthread or not) the # toolchain will add stdc++ and pthread to the linking step but stdc++ will @@ -296,23 +305,23 @@ endif () endif () # !MSVC include(GNUInstallDirs) -set(CMAKE_CXX_STANDARD 11) -set(CMAKE_CXX_EXTENSIONS OFF) -set(CMAKE_CXX_STANDARD_REQUIRED YES) - add_subdirectory(third_party) # Copy the JXL license file to the output build directory. configure_file("${CMAKE_CURRENT_SOURCE_DIR}/LICENSE" ${PROJECT_BINARY_DIR}/LICENSE.jpeg-xl COPYONLY) # Enable tests regardless of where they are defined. enable_testing() include(CTest) +# Specify default location of `testdata`: +if(NOT DEFINED JPEGXL_TEST_DATA_PATH) + set(JPEGXL_TEST_DATA_PATH "${PROJECT_SOURCE_DIR}/third_party/testdata") +endif() # Libraries. add_subdirectory(lib) if(BUILD_TESTING) @@ -417,11 +426,11 @@ MAIN_DEPENDENCY "${CMAKE_CURRENT_SOURCE_DIR}/doc/man/${PAGE}.txt") list(APPEND MANPAGE_FILES "${CMAKE_CURRENT_BINARY_DIR}/${PAGE}.1") list(APPEND MANPAGES "${PAGE}.1") endforeach() add_custom_target(manpages ALL DEPENDS ${MANPAGES}) - install(FILES ${MANPAGE_FILES} DESTINATION share/man/man1) + install(FILES ${MANPAGE_FILES} DESTINATION ${CMAKE_INSTALL_MANDIR}/man1) endif() # ASCIIDOC_PY_FOUND else() message(WARNING "asciidoc was not found, the man pages will not be installed.") endif() # ASCIIDOC != "ASCIIDOC-NOTFOUND" endif() # JPEGXL_ENABLE_MANPAGES diff --git a/third_party/jpeg-xl/ci.sh b/third_party/jpeg-xl/ci.sh --- a/third_party/jpeg-xl/ci.sh +++ b/third_party/jpeg-xl/ci.sh @@ -903,11 +903,11 @@ num_threads=1 fi local benchmark_args=( --input "${src_img_dir}/*.png" - --codec=jpeg:yuv420:q85,webp:q80,jxl:fast:d1,jxl:fast:d1:downsampling=8,jxl:fast:d4,jxl:fast:d4:downsampling=8,jxl:cheetah:m,jxl:m:cheetah:P6,jxl:m:falcon:q80 + --codec=jpeg:yuv420:q85,webp:q80,jxl:d1:6,jxl:d1:6:downsampling=8,jxl:d5:6,jxl:d5:6:downsampling=8,jxl:m:d0:2,jxl:m:d0:3,jxl:m:d2:2 --output_dir "${output_dir}" --noprofiler --show_progress --num_threads="${num_threads}" ) if [[ "${STORE_IMAGES}" == "1" ]]; then @@ -1018,15 +1018,15 @@ local jxl_jpeg_benchmarks=( "--num_reps=3" ) local images=( - "third_party/testdata/imagecompression.info/flower_foveon.png" + "third_party/testdata/third_party/imagecompression.info/flower_foveon.png" ) local jpg_images=( - "third_party/testdata/imagecompression.info/flower_foveon.png.im_q85_420.jpg" + "third_party/testdata/third_party/imagecompression.info/flower_foveon.png.im_q85_420.jpg" ) if [[ "${SKIP_CPUSET:-}" == "1" ]]; then # Use a single cpu config in this case. local cpu_confs=("?") diff --git a/third_party/jpeg-xl/cmake/FindAtomics.cmake b/third_party/jpeg-xl/cmake/FindAtomics.cmake new file mode 100644 --- /dev/null +++ b/third_party/jpeg-xl/cmake/FindAtomics.cmake @@ -0,0 +1,53 @@ +# Original issue: +# * https://gitlab.kitware.com/cmake/cmake/-/issues/23021#note_1098733 +# +# For reference: +# * https://gcc.gnu.org/wiki/Atomic/GCCMM +# +# riscv64 specific: +# * https://lists.debian.org/debian-riscv/2022/01/msg00009.html +# +# ATOMICS_FOUND - system has c++ atomics +# ATOMICS_LIBRARIES - libraries needed to use c++ atomics + +include(CheckCXXSourceCompiles) + +# RISC-V only has 32-bit and 64-bit atomic instructions. GCC is supposed +# to convert smaller atomics to those larger ones via masking and +# shifting like LLVM, but it’s a known bug that it does not. This means +# anything that wants to use atomics on 1-byte or 2-byte types needs +# -latomic, but not 4-byte or 8-byte (though it does no harm). +set(atomic_code + " + #include + #include + std::atomic n8 (0); // riscv64 + std::atomic n64 (0); // armel, mipsel, powerpc + int main() { + ++n8; + ++n64; + return 0; + }") + +check_cxx_source_compiles("${atomic_code}" ATOMICS_LOCK_FREE_INSTRUCTIONS) + +if(ATOMICS_LOCK_FREE_INSTRUCTIONS) + set(ATOMICS_FOUND TRUE) + set(ATOMICS_LIBRARIES) +else() + set(CMAKE_REQUIRED_LIBRARIES "-latomic") + check_cxx_source_compiles("${atomic_code}" ATOMICS_IN_LIBRARY) + set(CMAKE_REQUIRED_LIBRARIES) + if(ATOMICS_IN_LIBRARY) + set(ATOMICS_LIBRARY atomic) + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(Atomics DEFAULT_MSG ATOMICS_LIBRARY) + set(ATOMICS_LIBRARIES ${ATOMICS_LIBRARY}) + unset(ATOMICS_LIBRARY) + else() + if(Atomics_FIND_REQUIRED) + message(FATAL_ERROR "Neither lock free instructions nor -latomic found.") + endif() + endif() +endif() +unset(atomic_code) diff --git a/third_party/jpeg-xl/debian/copyright b/third_party/jpeg-xl/debian/copyright --- a/third_party/jpeg-xl/debian/copyright +++ b/third_party/jpeg-xl/debian/copyright @@ -36,11 +36,11 @@ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -Files: third_party/testdata/imagecompression.info/* +Files: third_party/testdata/third_party/imagecompression.info/* Copyright: their respective owners. License: License without any prohibitive copyright restrictions. See https://imagecompression.info/test_images/ for details. . These Images are available without any prohibitive copyright restrictions. @@ -56,11 +56,11 @@ 3. No payment is required for distribution of this material, it must be available freely under the conditions stated here. That is, it is prohibited to sell the material. 4. This notice may not be removed or altered from any distribution. -Files: third_party/testdata/pngsuite/* +Files: third_party/testdata/third_party/pngsuite/* Copyright: Willem van Schaik, 1996, 2011 License: PngSuite License See http://www.schaik.com/pngsuite/ for details. . Permission to use, copy, modify and distribute these images for any @@ -72,11 +72,11 @@ Files: third_party/testdata/raw.pixls/* Copyright: their respective owners listed in https://www.wesaturate.com/ License: CC0-1.0 -Files: third_party/testdata/wide-gamut-tests/ +Files: third_party/testdata/third_party/wide-gamut-tests/ Copyright: github.com/codelogic/wide-gamut-tests authors. License: Apache-2.0 License: Apache-2.0 Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/third_party/jpeg-xl/deps.sh b/third_party/jpeg-xl/deps.sh --- a/third_party/jpeg-xl/deps.sh +++ b/third_party/jpeg-xl/deps.sh @@ -11,10 +11,11 @@ MYDIR=$(dirname $(realpath "$0")) # Git revisions we use for the given submodules. Update these whenever you # update a git submodule. +THIRD_PARTY_BROTLI="35ef5c554d888bef217d449346067de05e269b30" THIRD_PARTY_GFLAGS="827c769e5fc98e0f2a34c47cef953cc6328abced" THIRD_PARTY_HIGHWAY="f13e3b956eb226561ac79427893ec0afd66f91a8" THIRD_PARTY_SKCMS="64374756e03700d649f897dbd98c95e78c30c7da" THIRD_PARTY_SJPEG="868ab558fad70fcbe8863ba4e85179eeb81cc840" THIRD_PARTY_ZLIB="cacf7f1d4e3d44d871b605da3b647f07d718623f" @@ -69,10 +70,11 @@ git -C "${MYDIR}" submodule update --init --recursive --depth 1 --recommend-shallow return 0 fi # Sources downloaded from a tarball. + download_github third_party/brotli google/brotli download_github third_party/gflags gflags/gflags download_github third_party/highway google/highway download_github third_party/sjpeg webmproject/sjpeg download_github third_party/skcms \ "https://skia.googlesource.com/skcms/+archive/" diff --git a/third_party/jpeg-xl/examples/CMakeLists.txt b/third_party/jpeg-xl/examples/CMakeLists.txt --- a/third_party/jpeg-xl/examples/CMakeLists.txt +++ b/third_party/jpeg-xl/examples/CMakeLists.txt @@ -23,13 +23,10 @@ target_link_libraries(decode_progressive PkgConfig::Jxl PkgConfig::JxlThreads) add_executable(encode_oneshot encode_oneshot.cc) target_link_libraries(encode_oneshot PkgConfig::Jxl PkgConfig::JxlThreads) -add_executable(jxlinfo jxlinfo.c) -target_link_libraries(jxlinfo PkgConfig::Jxl) - # Building a static binary with the static libjxl dependencies. How to load # static library configs from pkg-config and how to build static binaries # depends on the platform, and building static binaries in general has problems. # If you don't need static binaries you can remove this section. diff --git a/third_party/jpeg-xl/examples/examples.cmake b/third_party/jpeg-xl/examples/examples.cmake --- a/third_party/jpeg-xl/examples/examples.cmake +++ b/third_party/jpeg-xl/examples/examples.cmake @@ -7,15 +7,5 @@ target_link_libraries(decode_oneshot jxl_dec jxl_threads) add_executable(decode_progressive ${CMAKE_CURRENT_LIST_DIR}/decode_progressive.cc) target_link_libraries(decode_progressive jxl_dec jxl_threads) add_executable(encode_oneshot ${CMAKE_CURRENT_LIST_DIR}/encode_oneshot.cc) target_link_libraries(encode_oneshot jxl jxl_threads) - -add_executable(jxlinfo ${CMAKE_CURRENT_LIST_DIR}/jxlinfo.c) -target_link_libraries(jxlinfo jxl) - -if(NOT ${SANITIZER} STREQUAL "none") - # Linking a C test binary with the C++ JPEG XL implementation when using - # address sanitizer is not well supported by clang 9, so force using clang++ - # for linking this test if a sanitizer is used. - set_target_properties(jxlinfo PROPERTIES LINKER_LANGUAGE CXX) -endif() # SANITIZER != "none" diff --git a/third_party/jpeg-xl/examples/jxlinfo.c b/third_party/jpeg-xl/examples/jxlinfo.c deleted file mode 100644 --- a/third_party/jpeg-xl/examples/jxlinfo.c +++ /dev/null @@ -1,332 +0,0 @@ -// Copyright (c) the JPEG XL Project Authors. All rights reserved. -// -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// This example prints information from the main codestream header. - -#include -#include -#include -#include -#include - -#include "jxl/decode.h" - -int PrintBasicInfo(FILE* file) { - uint8_t* data = NULL; - size_t data_size = 0; - // In how large chunks to read from the file and try decoding the basic info. - const size_t chunk_size = 64; - - JxlDecoder* dec = JxlDecoderCreate(NULL); - if (!dec) { - fprintf(stderr, "JxlDecoderCreate failed\n"); - return 0; - } - - JxlDecoderSetKeepOrientation(dec, 1); - - if (JXL_DEC_SUCCESS != JxlDecoderSubscribeEvents( - dec, JXL_DEC_BASIC_INFO | JXL_DEC_COLOR_ENCODING | - JXL_DEC_FRAME | JXL_DEC_BOX)) { - fprintf(stderr, "JxlDecoderSubscribeEvents failed\n"); - JxlDecoderDestroy(dec); - return 0; - } - - JxlBasicInfo info; - int seen_basic_info = 0; - JxlFrameHeader frame_header; - - for (;;) { - // The first time, this will output JXL_DEC_NEED_MORE_INPUT because no - // input is set yet, this is ok since the input is set when handling this - // event. - JxlDecoderStatus status = JxlDecoderProcessInput(dec); - - if (status == JXL_DEC_ERROR) { - fprintf(stderr, "Decoder error\n"); - break; - } else if (status == JXL_DEC_NEED_MORE_INPUT) { - // The first time there is nothing to release and it returns 0, but that - // is ok. - size_t remaining = JxlDecoderReleaseInput(dec); - // move any remaining bytes to the front if necessary - if (remaining != 0) { - memmove(data, data + data_size - remaining, remaining); - } - // resize the buffer to append one more chunk of data - // TODO(lode): avoid unnecessary reallocations - data = (uint8_t*)realloc(data, remaining + chunk_size); - // append bytes read from the file behind the remaining bytes - size_t read_size = fread(data + remaining, 1, chunk_size, file); - if (read_size == 0 && feof(file)) { - fprintf(stderr, "Unexpected EOF\n"); - break; - } - data_size = remaining + read_size; - JxlDecoderSetInput(dec, data, data_size); - if (feof(file)) JxlDecoderCloseInput(dec); - } else if (status == JXL_DEC_SUCCESS) { - // Finished all processing. - break; - } else if (status == JXL_DEC_BASIC_INFO) { - if (JXL_DEC_SUCCESS != JxlDecoderGetBasicInfo(dec, &info)) { - fprintf(stderr, "JxlDecoderGetBasicInfo failed\n"); - break; - } - - seen_basic_info = 1; - - printf("dimensions: %ux%u\n", info.xsize, info.ysize); - printf("have_container: %d\n", info.have_container); - printf("uses_original_profile: %d\n", info.uses_original_profile); - printf("bits_per_sample: %d\n", info.bits_per_sample); - if (info.exponent_bits_per_sample) - printf("float, with exponent_bits_per_sample: %d\n", - info.exponent_bits_per_sample); - if (info.intensity_target != 255.f || info.min_nits != 0.f || - info.relative_to_max_display != 0 || - info.relative_to_max_display != 0.f) { - printf("intensity_target: %f\n", info.intensity_target); - printf("min_nits: %f\n", info.min_nits); - printf("relative_to_max_display: %d\n", info.relative_to_max_display); - printf("linear_below: %f\n", info.linear_below); - } - printf("have_preview: %d\n", info.have_preview); - if (info.have_preview) { - printf("preview xsize: %u\n", info.preview.xsize); - printf("preview ysize: %u\n", info.preview.ysize); - } - printf("have_animation: %d\n", info.have_animation); - if (info.have_animation) { - printf("ticks per second (numerator / denominator): %u / %u\n", - info.animation.tps_numerator, info.animation.tps_denominator); - printf("num_loops: %u\n", info.animation.num_loops); - printf("have_timecodes: %d\n", info.animation.have_timecodes); - } - printf("intrinsic xsize: %u\n", info.intrinsic_xsize); - printf("intrinsic ysize: %u\n", info.intrinsic_ysize); - const char* const orientation_string[8] = { - "Normal", "Flipped horizontally", - "Upside down", "Flipped vertically", - "Transposed", "90 degrees clockwise", - "Anti-Transposed", "90 degrees counter-clockwise"}; - if (info.orientation > 0 && info.orientation < 9) { - printf("orientation: %d (%s)\n", info.orientation, - orientation_string[info.orientation - 1]); - } else { - fprintf(stderr, "Invalid orientation\n"); - } - printf("num_color_channels: %d\n", info.num_color_channels); - printf("num_extra_channels: %d\n", info.num_extra_channels); - - const char* const ec_type_names[7] = {"Alpha", "Depth", - "Spot color", "Selection mask", - "K (of CMYK)", "CFA (Bayer data)", - "Thermal"}; - for (uint32_t i = 0; i < info.num_extra_channels; i++) { - JxlExtraChannelInfo extra; - if (JXL_DEC_SUCCESS != JxlDecoderGetExtraChannelInfo(dec, i, &extra)) { - fprintf(stderr, "JxlDecoderGetExtraChannelInfo failed\n"); - break; - } - printf("extra channel %u:\n", i); - printf(" type: %s\n", - (extra.type < 7 ? ec_type_names[extra.type] - : (extra.type == JXL_CHANNEL_OPTIONAL - ? "Unknown but can be ignored" - : "Unknown, please update your libjxl"))); - printf(" bits_per_sample: %u\n", extra.bits_per_sample); - if (extra.exponent_bits_per_sample > 0) { - printf(" float, with exponent_bits_per_sample: %u\n", - extra.exponent_bits_per_sample); - } - if (extra.dim_shift > 0) { - printf(" dim_shift: %u (upsampled %ux)\n", extra.dim_shift, - 1 << extra.dim_shift); - } - if (extra.name_length) { - char* name = malloc(extra.name_length + 1); - if (JXL_DEC_SUCCESS != JxlDecoderGetExtraChannelName( - dec, i, name, extra.name_length + 1)) { - fprintf(stderr, "JxlDecoderGetExtraChannelName failed\n"); - free(name); - break; - } - printf(" name: %s\n", name); - free(name); - } - if (extra.type == JXL_CHANNEL_ALPHA) - printf(" alpha_premultiplied: %d (%s)\n", extra.alpha_premultiplied, - extra.alpha_premultiplied ? "Premultiplied" - : "Non-premultiplied"); - if (extra.type == JXL_CHANNEL_SPOT_COLOR) { - printf(" spot_color: (%f, %f, %f) with opacity %f\n", - extra.spot_color[0], extra.spot_color[1], extra.spot_color[2], - extra.spot_color[3]); - } - if (extra.type == JXL_CHANNEL_CFA) - printf(" cfa_channel: %u\n", extra.cfa_channel); - } - } else if (status == JXL_DEC_COLOR_ENCODING) { - JxlPixelFormat format = {4, JXL_TYPE_FLOAT, JXL_LITTLE_ENDIAN, 0}; - printf("color profile:\n"); - - JxlColorEncoding color_encoding; - if (JXL_DEC_SUCCESS == - JxlDecoderGetColorAsEncodedProfile(dec, &format, - JXL_COLOR_PROFILE_TARGET_ORIGINAL, - &color_encoding)) { - printf(" format: JPEG XL encoded color profile\n"); - const char* const cs_string[4] = {"RGB color", "Grayscale", "XYB", - "Unknown"}; - const char* const wp_string[12] = {"", "D65", "Custom", "", "", "", - "", "", "", "", "E", "P3"}; - const char* const pr_string[12] = { - "", "sRGB", "Custom", "", "", "", "", "", "", "Rec.2100", "", "P3"}; - const char* const tf_string[19] = { - "", "709", "Unknown", "", "", "", "", "", "Linear", "", - "", "", "", "sRGB", "", "", "PQ", "DCI", "HLG"}; - const char* const ri_string[4] = {"Perceptual", "Relative", - "Saturation", "Absolute"}; - printf(" color_space: %d (%s)\n", color_encoding.color_space, - cs_string[color_encoding.color_space]); - printf(" white_point: %d (%s)\n", color_encoding.white_point, - wp_string[color_encoding.white_point]); - if (color_encoding.white_point == JXL_WHITE_POINT_CUSTOM) { - printf(" white_point XY: %f %f\n", color_encoding.white_point_xy[0], - color_encoding.white_point_xy[1]); - } - if (color_encoding.color_space == JXL_COLOR_SPACE_RGB || - color_encoding.color_space == JXL_COLOR_SPACE_UNKNOWN) { - printf(" primaries: %d (%s)\n", color_encoding.primaries, - pr_string[color_encoding.primaries]); - if (color_encoding.primaries == JXL_PRIMARIES_CUSTOM) { - printf(" red primaries XY: %f %f\n", - color_encoding.primaries_red_xy[0], - color_encoding.primaries_red_xy[1]); - printf(" green primaries XY: %f %f\n", - color_encoding.primaries_green_xy[0], - color_encoding.primaries_green_xy[1]); - printf(" blue primaries XY: %f %f\n", - color_encoding.primaries_blue_xy[0], - color_encoding.primaries_blue_xy[1]); - } - } - if (color_encoding.transfer_function == JXL_TRANSFER_FUNCTION_GAMMA) { - printf(" transfer_function: gamma: %f\n", color_encoding.gamma); - } else { - printf(" transfer_function: %d (%s)\n", - color_encoding.transfer_function, - tf_string[color_encoding.transfer_function]); - } - printf(" rendering_intent: %d (%s)\n", color_encoding.rendering_intent, - ri_string[color_encoding.rendering_intent]); - - } else { - // The profile is not in JPEG XL encoded form, get as ICC profile - // instead. - printf(" format: ICC profile\n"); - size_t profile_size; - if (JXL_DEC_SUCCESS != - JxlDecoderGetICCProfileSize(dec, &format, - JXL_COLOR_PROFILE_TARGET_ORIGINAL, - &profile_size)) { - fprintf(stderr, "JxlDecoderGetICCProfileSize failed\n"); - continue; - } - printf(" ICC profile size: %" PRIu64 "\n", (uint64_t)profile_size); - if (profile_size < 132) { - fprintf(stderr, "ICC profile too small\n"); - continue; - } - uint8_t* profile = (uint8_t*)malloc(profile_size); - if (JXL_DEC_SUCCESS != - JxlDecoderGetColorAsICCProfile(dec, &format, - JXL_COLOR_PROFILE_TARGET_ORIGINAL, - profile, profile_size)) { - fprintf(stderr, "JxlDecoderGetColorAsICCProfile failed\n"); - free(profile); - continue; - } - printf(" CMM type: \"%.4s\"\n", profile + 4); - printf(" color space: \"%.4s\"\n", profile + 16); - printf(" rendering intent: %d\n", (int)profile[67]); - free(profile); - } - } else if (status == JXL_DEC_FRAME) { - if (JXL_DEC_SUCCESS != JxlDecoderGetFrameHeader(dec, &frame_header)) { - fprintf(stderr, "JxlDecoderGetFrameHeader failed\n"); - break; - } - printf("frame:\n"); - if (frame_header.name_length) { - char* name = malloc(frame_header.name_length + 1); - if (JXL_DEC_SUCCESS != - JxlDecoderGetFrameName(dec, name, frame_header.name_length + 1)) { - fprintf(stderr, "JxlDecoderGetFrameName failed\n"); - free(name); - break; - } - printf(" name: %s\n", name); - free(name); - } - float ms = frame_header.duration * 1000.f * - info.animation.tps_denominator / info.animation.tps_numerator; - if (info.have_animation) { - printf(" duration: %u ticks (%f ms)\n", frame_header.duration, ms); - if (info.animation.have_timecodes) { - printf(" time code: %X\n", frame_header.timecode); - } - } - if (!frame_header.name_length && !info.have_animation) { - printf(" still frame, unnamed\n"); - } - } else if (status == JXL_DEC_BOX) { - JxlBoxType type; - uint64_t size; - JxlDecoderGetBoxType(dec, type, JXL_FALSE); - JxlDecoderGetBoxSizeRaw(dec, &size); - printf("box: type: \"%c%c%c%c\" size: %" PRIu64 "\n", type[0], type[1], - type[2], type[3], (uint64_t)size); - } else { - fprintf(stderr, "Unexpected decoder status\n"); - break; - } - } - - JxlDecoderDestroy(dec); - free(data); - - return seen_basic_info; -} - -int main(int argc, char* argv[]) { - if (argc != 2) { - fprintf(stderr, - "Usage: %s \n" - "Where:\n" - " jxl = input JPEG XL image filename\n", - argv[0]); - return 1; - } - - const char* jxl_filename = argv[1]; - - FILE* file = fopen(jxl_filename, "rb"); - if (!file) { - fprintf(stderr, "Failed to read file %s\n", jxl_filename); - return 1; - } - - if (!PrintBasicInfo(file)) { - fclose(file); - fprintf(stderr, "Couldn't print basic info\n"); - return 1; - } - - fclose(file); - return 0; -} diff --git a/third_party/jpeg-xl/lib/CMakeLists.txt b/third_party/jpeg-xl/lib/CMakeLists.txt --- a/third_party/jpeg-xl/lib/CMakeLists.txt +++ b/third_party/jpeg-xl/lib/CMakeLists.txt @@ -124,11 +124,11 @@ endif() # WIN32 # Internal flags for coverage builds: if(JPEGXL_ENABLE_COVERAGE) set(JPEGXL_COVERAGE_FLAGS - -g -O0 -fprofile-arcs -ftest-coverage -DJXL_DISABLE_SLOW_TESTS + -g -O0 -fprofile-arcs -ftest-coverage -DJXL_ENABLE_ASSERT=0 -DJXL_ENABLE_CHECK=0 ) endif() # JPEGXL_ENABLE_COVERAGE endif() #!MSVC diff --git a/third_party/jpeg-xl/lib/extras/codec.cc b/third_party/jpeg-xl/lib/extras/codec.cc --- a/third_party/jpeg-xl/lib/extras/codec.cc +++ b/third_party/jpeg-xl/lib/extras/codec.cc @@ -19,11 +19,10 @@ #endif #if JPEGXL_ENABLE_EXR #include "lib/extras/enc/exr.h" #endif -#include "lib/extras/codec_psd.h" #include "lib/extras/dec/decode.h" #include "lib/extras/enc/pgx.h" #include "lib/extras/enc/pnm.h" #include "lib/extras/packed_image_convert.h" #include "lib/jxl/base/file_io.h" @@ -41,17 +40,13 @@ const extras::ColorHints& color_hints, CodecInOut* io, ThreadPool* pool, extras::Codec* orig_codec) { if (bytes.size() < kMinBytes) return JXL_FAILURE("Too few bytes"); extras::PackedPixelFile ppf; - if (extras::DecodeBytes(bytes, color_hints, io->constraints, &ppf, pool, + if (extras::DecodeBytes(bytes, color_hints, io->constraints, &ppf, orig_codec)) { return ConvertPackedPixelFileToCodecInOut(ppf, pool, io); - } else if (extras::DecodeImagePSD(bytes, color_hints, pool, io)) { - // TODO(deymo): Migrate PSD codec too. - if (orig_codec) *orig_codec = extras::Codec::kPSD; - return true; } return JXL_FAILURE("Codecs failed to decode"); } Status SetFromFile(const std::string& pathname, @@ -122,13 +117,10 @@ case extras::Codec::kPGX: return extras::EncodeImagePGX(&io, c_desired, bits_per_sample, pool, bytes); case extras::Codec::kGIF: return JXL_FAILURE("Encoding to GIF is not implemented"); - case extras::Codec::kPSD: - return extras::EncodeImagePSD(&io, c_desired, bits_per_sample, pool, - bytes); case extras::Codec::kEXR: #if JPEGXL_ENABLE_EXR return extras::EncodeImageEXR(&io, c_desired, pool, bytes); #else return JXL_FAILURE("JPEG XL was built without OpenEXR support"); @@ -145,14 +137,17 @@ ThreadPool* pool) { const std::string extension = Extension(pathname); const extras::Codec codec = extras::CodecFromExtension(extension, &bits_per_sample); - // Warn about incorrect usage of PBM/PGM/PGX/PPM - only the latter supports + // Warn about incorrect usage of PGM/PGX/PPM - only the latter supports // color, but CodecFromExtension lumps them all together. if (codec == extras::Codec::kPNM && extension != ".pfm") { - if (!io.Main().IsGray() && extension != ".ppm") { + if (io.Main().HasAlpha() && extension != ".pam") { + JXL_WARNING( + "For images with alpha, the filename should end with .pam.\n"); + } else if (!io.Main().IsGray() && extension == ".pgm") { JXL_WARNING("For color images, the filename should end with .ppm.\n"); } else if (io.Main().IsGray() && extension == ".ppm") { JXL_WARNING( "For grayscale images, the filename should not end with .ppm.\n"); } diff --git a/third_party/jpeg-xl/lib/extras/codec_psd.h b/third_party/jpeg-xl/lib/extras/codec_psd.h deleted file mode 100644 --- a/third_party/jpeg-xl/lib/extras/codec_psd.h +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright (c) the JPEG XL Project Authors. All rights reserved. -// -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -#ifndef LIB_EXTRAS_CODEC_PSD_H_ -#define LIB_EXTRAS_CODEC_PSD_H_ - -// Decodes Photoshop PSD/PSB, preserving the layers - -#include -#include - -#include "lib/extras/dec/color_hints.h" -#include "lib/jxl/base/padded_bytes.h" -#include "lib/jxl/base/span.h" -#include "lib/jxl/base/status.h" -#include "lib/jxl/codec_in_out.h" -#include "lib/jxl/color_encoding_internal.h" - -namespace jxl { -namespace extras { - -// Decodes `bytes` into `io`. -Status DecodeImagePSD(const Span bytes, - const extras::ColorHints& color_hints, ThreadPool* pool, - CodecInOut* io); - -// Not implemented yet -Status EncodeImagePSD(const CodecInOut* io, const ColorEncoding& c_desired, - size_t bits_per_sample, ThreadPool* pool, - PaddedBytes* bytes); - -} // namespace extras -} // namespace jxl - -#endif // LIB_EXTRAS_CODEC_PSD_H_ diff --git a/third_party/jpeg-xl/lib/extras/codec_psd.cc b/third_party/jpeg-xl/lib/extras/codec_psd.cc deleted file mode 100644 --- a/third_party/jpeg-xl/lib/extras/codec_psd.cc +++ /dev/null @@ -1,621 +0,0 @@ -// Copyright (c) the JPEG XL Project Authors. All rights reserved. -// -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -#include "lib/extras/codec_psd.h" - -#include -#include -#include - -#include -#include -#include -#include -#include - -#include "lib/jxl/base/bits.h" -#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 -#include "lib/jxl/image.h" -#include "lib/jxl/image_bundle.h" -#include "lib/jxl/image_ops.h" -#include "lib/jxl/luminance.h" - -namespace jxl { -namespace extras { -namespace { - -uint64_t get_be_int(int bytes, const uint8_t*& pos, const uint8_t* maxpos) { - uint64_t r = 0; - if (pos + bytes <= maxpos) { - if (bytes == 1) { - r = *pos; - } else if (bytes == 2) { - r = LoadBE16(pos); - } else if (bytes == 4) { - r = LoadBE32(pos); - } else if (bytes == 8) { - r = LoadBE64(pos); - } - } - pos += bytes; - return r; -} - -// Copies up to n bytes, without reading from maxpos (the STL-style end). -void safe_copy(const uint8_t* JXL_RESTRICT pos, - const uint8_t* JXL_RESTRICT maxpos, char* JXL_RESTRICT out, - size_t n) { - for (size_t i = 0; i < n; ++i) { - if (pos + i >= maxpos) return; - out[i] = pos[i]; - } -} - -// maxpos is the STL-style end! The valid range is up to [pos, maxpos). -int safe_strncmp(const uint8_t* pos, const uint8_t* maxpos, const char* s2, - size_t n) { - if (pos + n > maxpos) return 1; - return strncmp((const char*)pos, s2, n); -} -constexpr int PSD_VERBOSITY = 1; - -Status decode_layer(const uint8_t*& pos, const uint8_t* maxpos, - ImageBundle& layer, std::vector chans, - std::vector invert, int w, int h, int version, - int colormodel, bool is_layer, int depth) { - int compression_method = 2; - int nb_channels = chans.size(); - JXL_DEBUG_V(PSD_VERBOSITY, - "Trying to decode layer with dimensions %ix%i and %i channels", w, - h, nb_channels); - if (w <= 0 || h <= 0) return JXL_FAILURE("PSD: empty layer"); - for (int c = 0; c < nb_channels; c++) { - // skip nop byte padding - while (pos < maxpos && *pos == 128) pos++; - JXL_DEBUG_V(PSD_VERBOSITY, "Channel %i (pos %" PRIuS ")", c, (size_t)pos); - // Merged image stores all channels together (same compression method) - // Layers store channel per channel - if (is_layer || c == 0) { - compression_method = get_be_int(2, pos, maxpos); - JXL_DEBUG_V(PSD_VERBOSITY, "compression method: %i", compression_method); - if (compression_method > 1 || compression_method < 0) { - return JXL_FAILURE("PSD: can't handle compression method %i", - compression_method); - } - } - - if (!is_layer && c < colormodel) { - // skip to the extra channels - if (compression_method == 0) { - pos += w * h * (depth >> 3) * colormodel; - c = colormodel - 1; - continue; - } - size_t skip_amount = 0; - for (int i = 0; i < nb_channels; i++) { - if (i < colormodel) { - for (int y = 0; y < h; y++) { - skip_amount += get_be_int(2 * version, pos, maxpos); - } - } else { - pos += h * 2 * version; - } - } - pos += skip_amount; - c = colormodel - 1; - continue; - } - if (is_layer || c == 0) { - // skip the line-counts, we don't need them - if (compression_method == 1) { - pos += h * (is_layer ? 1 : nb_channels) * 2 * - version; // PSB uses 4 bytes per rowsize instead of 2 - } - } - int c_id = chans[c]; - if (c_id < 0) continue; // skip - if (static_cast(c_id) >= 3 + layer.extra_channels().size()) - return JXL_FAILURE("PSD: can't handle channel id %i", c_id); - ImageF& ch = (c_id < 3 ? layer.color()->Plane(c_id) - : layer.extra_channels()[c_id - 3]); - - for (int y = 0; y < h; y++) { - if (pos > maxpos) return JXL_FAILURE("PSD: premature end of input"); - float* const JXL_RESTRICT row = ch.Row(y); - if (compression_method == 0) { - // uncompressed is easy - if (depth == 8) { - for (int x = 0; x < w; x++) { - row[x] = get_be_int(1, pos, maxpos) * (1.f / 255.f); - } - } else if (depth == 16) { - for (int x = 0; x < w; x++) { - row[x] = get_be_int(2, pos, maxpos) * (1.f / 65535.f); - } - } else if (depth == 32) { - for (int x = 0; x < w; x++) { - uint32_t f = get_be_int(4, pos, maxpos); - memcpy(&row[x], &f, 4); - } - } - } else { - // RLE is not that hard - if (depth != 8) - return JXL_FAILURE("PSD: did not expect RLE with depth>1"); - for (int x = 0; x < w;) { - if (pos >= maxpos) return JXL_FAILURE("PSD: out of bounds"); - int8_t rle = *pos++; - if (rle <= 0) { - if (rle == -128) continue; // nop - int count = 1 - rle; - float v = get_be_int(1, pos, maxpos) * (1.f / 255.f); - while (count && x < w) { - row[x] = v; - count--; - x++; - } - if (count) return JXL_FAILURE("PSD: row overflow"); - } else { - int count = 1 + rle; - while (count && x < w) { - row[x] = get_be_int(1, pos, maxpos) * (1.f / 255.f); - count--; - x++; - } - if (count) return JXL_FAILURE("PSD: row overflow"); - } - } - } - if (invert[c]) { - // sometimes 0 means full ink - for (int x = 0; x < w; x++) { - row[x] = 1.f - row[x]; - } - } - } - JXL_DEBUG_V(PSD_VERBOSITY, "Channel %i read.", c); - } - - return true; -} - -} // namespace - -Status DecodeImagePSD(const Span bytes, - const ColorHints& /*color_hints*/, ThreadPool* pool, - CodecInOut* io) { - const uint8_t* pos = bytes.data(); - const uint8_t* maxpos = bytes.data() + bytes.size(); - if (safe_strncmp(pos, maxpos, "8BPS", 4)) return false; // not a PSD file - JXL_DEBUG_V(PSD_VERBOSITY, "trying psd decode"); - pos += 4; - int version = get_be_int(2, pos, maxpos); - JXL_DEBUG_V(PSD_VERBOSITY, "Version=%i", version); - if (version < 1 || version > 2) - return JXL_FAILURE("PSD: unknown format version"); - // PSD = version 1, PSB = version 2 - pos += 6; - int nb_channels = get_be_int(2, pos, maxpos); - size_t ysize = get_be_int(4, pos, maxpos); - size_t xsize = get_be_int(4, pos, maxpos); - const SizeConstraints* constraints = &io->constraints; - JXL_RETURN_IF_ERROR(VerifyDimensions(constraints, xsize, ysize)); - uint64_t total_pixel_count = static_cast(xsize) * ysize; - int bitdepth = get_be_int(2, pos, maxpos); - if (bitdepth != 8 && bitdepth != 16 && bitdepth != 32) { - return JXL_FAILURE("PSD: bit depth %i invalid or not supported", bitdepth); - } - if (bitdepth == 32) { - io->metadata.m.SetFloat32Samples(); - } else { - io->metadata.m.SetUintSamples(bitdepth); - } - int colormodel = get_be_int(2, pos, maxpos); - // 1 = Grayscale, 3 = RGB, 4 = CMYK - if (colormodel != 1 && colormodel != 3 && colormodel != 4) - return JXL_FAILURE("PSD: unsupported color model"); - - int real_nb_channels = colormodel; - std::vector> spotcolor; - - if (get_be_int(4, pos, maxpos)) - return JXL_FAILURE("PSD: Unsupported color mode section"); - - bool hasmergeddata = true; - bool have_alpha = false; - bool merged_has_alpha = false; - bool color_already_set = false; - size_t metalength = get_be_int(4, pos, maxpos); - const uint8_t* metaoffset = pos; - while (pos < metaoffset + metalength) { - char header[5] = "????"; - safe_copy(pos, maxpos, header, 4); - if (memcmp(header, "8BIM", 4) != 0) { - return JXL_FAILURE("PSD: Unexpected image resource header: %s", header); - } - pos += 4; - int id = get_be_int(2, pos, maxpos); - int namelength = get_be_int(1, pos, maxpos); - pos += namelength; - if (!(namelength & 1)) pos++; // padding to even length - size_t blocklength = get_be_int(4, pos, maxpos); - // JXL_DEBUG_V(PSD_VERBOSITY, "block id: %i | block length: %" PRIuS,id, - // blocklength); - if (pos > maxpos) return JXL_FAILURE("PSD: Unexpected end of file"); - if (id == 1039) { // ICC profile - size_t delta = maxpos - pos; - if (delta < blocklength) { - return JXL_FAILURE("PSD: Invalid block length"); - } - PaddedBytes icc; - icc.resize(blocklength); - memcpy(icc.data(), pos, blocklength); - if (!io->metadata.m.color_encoding.SetICC(std::move(icc))) { - return JXL_FAILURE("PSD: Invalid color profile"); - } - color_already_set = true; - } else if (id == 1057) { // compatibility mode or not? - if (get_be_int(4, pos, maxpos) != 1) { - return JXL_FAILURE("PSD: expected version=1 in id=1057 resource block"); - } - hasmergeddata = get_be_int(1, pos, maxpos); - pos++; - blocklength -= 6; // already skipped these bytes - } else if (id == 1077) { // spot colors - int version = get_be_int(4, pos, maxpos); - if (version != 1) { - return JXL_FAILURE( - "PSD: expected DisplayInfo version 1, got version %i", version); - } - int spotcolorcount = nb_channels - colormodel; - JXL_DEBUG_V(PSD_VERBOSITY, "Reading %i spot colors. %" PRIuS, - spotcolorcount, blocklength); - for (int k = 0; k < spotcolorcount; k++) { - int colorspace = get_be_int(2, pos, maxpos); - if ((colormodel == 3 && colorspace != 0) || - (colormodel == 4 && colorspace != 2)) { - return JXL_FAILURE( - "PSD: cannot handle spot colors in different color spaces than " - "image itself"); - } - if (colorspace == 2) JXL_WARNING("PSD: K ignored in CMYK spot color"); - std::vector color; - color.push_back(get_be_int(2, pos, maxpos) / 65535.f); // R or C - color.push_back(get_be_int(2, pos, maxpos) / 65535.f); // G or M - color.push_back(get_be_int(2, pos, maxpos) / 65535.f); // B or Y - color.push_back(get_be_int(2, pos, maxpos) / 65535.f); // ignored or K - color.push_back(get_be_int(2, pos, maxpos) / - 100.f); // solidity (alpha, basically) - int kind = get_be_int(1, pos, maxpos); - JXL_DEBUG_V(PSD_VERBOSITY, "Kind=%i", kind); - color.push_back(kind); - spotcolor.push_back(color); - if (kind == 2) { - JXL_DEBUG_V(PSD_VERBOSITY, "Actual spot color"); - } else if (kind == 1) { - JXL_DEBUG_V(PSD_VERBOSITY, "Mask (alpha) channel"); - } else if (kind == 0) { - JXL_DEBUG_V(PSD_VERBOSITY, "Selection (alpha) channel"); - } else { - return JXL_FAILURE("PSD: Unknown extra channel type"); - } - } - if (blocklength & 1) pos++; - blocklength = 0; - } - pos += blocklength; - if (blocklength & 1) pos++; // padding again - } - - // TODO(deymo): Apply color hints when PSD is converted to PackedPixelFile. - (void)color_already_set; - // JXL_RETURN_IF_ERROR(ApplyColorHints(color_hints, color_already_set, - // /*is_gray=*/false, io)); - - size_t layerlength = get_be_int(4 * version, pos, maxpos); - const uint8_t* after_layers_pos = pos + layerlength; - if (after_layers_pos < pos) return JXL_FAILURE("PSD: invalid layer length"); - if (layerlength) { - pos += 4 * version; // don't care about layerinfolength - JXL_DEBUG_V(PSD_VERBOSITY, "Layer section length: %" PRIuS, layerlength); - int layercount = static_cast(get_be_int(2, pos, maxpos)); - JXL_DEBUG_V(PSD_VERBOSITY, "Layer count: %i", layercount); - io->frames.clear(); - - if (layercount == 0) { - if (get_be_int(2, pos, maxpos) != 0) { - return JXL_FAILURE( - "PSD: Expected zero padding before additional layer info"); - } - while (pos < after_layers_pos) { - if (safe_strncmp(pos, maxpos, "8BIM", 4) && - safe_strncmp(pos, maxpos, "8B64", 4)) - return JXL_FAILURE("PSD: Unexpected layer info signature"); - pos += 4; - const uint8_t* tpos = pos; - pos += 4; - size_t blocklength = get_be_int(4 * version, pos, maxpos); - JXL_DEBUG_V(PSD_VERBOSITY, "Length=%" PRIuS, blocklength); - if (blocklength > 0) { - if (pos >= maxpos) return JXL_FAILURE("PSD: Unexpected end of file"); - size_t delta = maxpos - pos; - if (delta < blocklength) { - return JXL_FAILURE("PSD: Invalid block length"); - } - } - if (!safe_strncmp(tpos, maxpos, "Layr", 4) || - !safe_strncmp(tpos, maxpos, "Lr16", 4) || - !safe_strncmp(tpos, maxpos, "Lr32", 4)) { - layercount = static_cast(get_be_int(2, pos, maxpos)); - if (layercount < 0) { - return JXL_FAILURE("PSD: Invalid layer count"); - } - JXL_DEBUG_V(PSD_VERBOSITY, "Real layer count: %i", layercount); - if (layercount > 1) have_alpha = true; - break; - } - if (!safe_strncmp(tpos, maxpos, "Mtrn", 4) || - !safe_strncmp(tpos, maxpos, "Mt16", 4) || - !safe_strncmp(tpos, maxpos, "Mt32", 4)) { - JXL_DEBUG_V(PSD_VERBOSITY, "Merged layer has transparency channel"); - if (nb_channels > real_nb_channels) { - have_alpha = true; - merged_has_alpha = true; - } - } - pos += blocklength; - } - } else if (layercount < 0) { - // negative layer count indicates merged has alpha and it is to be shown - if (nb_channels > real_nb_channels) { - have_alpha = true; - merged_has_alpha = true; - } - layercount = -layercount; - } else { - // multiple layers implies there is alpha - have_alpha = true; - } - - ExtraChannelInfo info; - info.bit_depth.bits_per_sample = bitdepth; - info.dim_shift = 0; - - if (colormodel == 4) { // cmyk - info.type = ExtraChannel::kBlack; - io->metadata.m.extra_channel_info.push_back(info); - } - if (have_alpha) { - JXL_DEBUG_V(PSD_VERBOSITY, "Have alpha"); - real_nb_channels++; - info.type = ExtraChannel::kAlpha; - info.alpha_associated = - false; // true? PSD is not consistent with this, need to check - io->metadata.m.extra_channel_info.push_back(info); - } - if (merged_has_alpha && !spotcolor.empty() && spotcolor[0][5] == 1) { - // first alpha channel - spotcolor.erase(spotcolor.begin()); - } - for (size_t i = 0; i < spotcolor.size(); i++) { - real_nb_channels++; - if (spotcolor[i][5] == 2) { - info.type = ExtraChannel::kSpotColor; - info.spot_color[0] = spotcolor[i][0]; - info.spot_color[1] = spotcolor[i][1]; - info.spot_color[2] = spotcolor[i][2]; - info.spot_color[3] = spotcolor[i][4]; - } else if (spotcolor[i][5] == 1) { - info.type = ExtraChannel::kAlpha; - } else if (spotcolor[i][5] == 0) { - info.type = ExtraChannel::kSelectionMask; - } else - return JXL_FAILURE("PSD: unhandled extra channel"); - io->metadata.m.extra_channel_info.push_back(info); - } - std::vector> layer_chan_id; - std::vector layer_offsets(layercount + 1, 0); - std::vector is_real_layer(layercount, false); - for (int l = 0; l < layercount; l++) { - ImageBundle layer(&io->metadata.m); - layer.duration = 0; - layer.blend = (l > 0); - - layer.use_for_next_frame = (l + 1 < layercount); - layer.origin.y0 = get_be_int(4, pos, maxpos); - layer.origin.x0 = get_be_int(4, pos, maxpos); - size_t height = get_be_int(4, pos, maxpos) - layer.origin.y0; - size_t width = get_be_int(4, pos, maxpos) - layer.origin.x0; - JXL_DEBUG_V(PSD_VERBOSITY, - "Layer %i: %" PRIuS " x %" PRIuS " at origin (%i, %i)", l, - width, height, layer.origin.x0, layer.origin.y0); - int nb_chs = get_be_int(2, pos, maxpos); - JXL_DEBUG_V(PSD_VERBOSITY, " channels: %i", nb_chs); - std::vector chan_ids; - layer_offsets[l + 1] = layer_offsets[l]; - for (int lc = 0; lc < nb_chs; lc++) { - int id = get_be_int(2, pos, maxpos); - JXL_DEBUG_V(PSD_VERBOSITY, " id=%i", id); - if (id == 65535) { - chan_ids.push_back(colormodel); // alpha - } else if (id == 65534) { - chan_ids.push_back(-1); // layer mask, ignored - } else { - chan_ids.push_back(id); // color channel - } - layer_offsets[l + 1] += get_be_int(4 * version, pos, maxpos); - } - layer_chan_id.push_back(chan_ids); - if (safe_strncmp(pos, maxpos, "8BIM", 4)) - return JXL_FAILURE("PSD: Layer %i: Unexpected signature (not 8BIM)", l); - pos += 4; - if (safe_strncmp(pos, maxpos, "norm", 4)) { - return JXL_FAILURE( - "PSD: Layer %i: Cannot handle non-default blend mode", l); - } - pos += 4; - int opacity = get_be_int(1, pos, maxpos); - if (opacity < 100) { - JXL_WARNING( - "PSD: ignoring opacity of semi-transparent layer %i (opacity=%i)", - l, opacity); - } - pos++; // clipping - int flags = get_be_int(1, pos, maxpos); - pos++; - bool invisible = (flags & 2); - if (invisible) { - if (l + 1 < layercount) { - layer.blend = false; - layer.use_for_next_frame = false; - } else { - // TODO: instead add dummy last frame? - JXL_WARNING("PSD: invisible top layer was made visible"); - } - } - size_t extradata = get_be_int(4, pos, maxpos); - JXL_DEBUG_V(PSD_VERBOSITY, " extradata: %" PRIuS " bytes", extradata); - const uint8_t* after_extra = pos + extradata; - // TODO: deal with non-empty layer masks - pos += get_be_int(4, pos, maxpos); // skip layer mask data - pos += get_be_int(4, pos, maxpos); // skip layer blend range data - size_t namelength = get_be_int(1, pos, maxpos); - size_t delta = maxpos - pos; - if (delta < namelength) return JXL_FAILURE("PSD: Invalid block length"); - char lname[256] = {}; - memcpy(lname, pos, namelength); - lname[namelength] = 0; - JXL_DEBUG_V(PSD_VERBOSITY, " name: %s", lname); - pos = after_extra; - if (width == 0 || height == 0) { - JXL_DEBUG_V(PSD_VERBOSITY, - " NOT A REAL LAYER"); // probably layer group - continue; - } - is_real_layer[l] = true; - JXL_RETURN_IF_ERROR(VerifyDimensions(constraints, width, height)); - uint64_t pixel_count = static_cast(width) * height; - if (!SafeAdd(total_pixel_count, pixel_count, total_pixel_count)) { - return JXL_FAILURE("Image too big"); - } - if (total_pixel_count > constraints->dec_max_pixels) { - return JXL_FAILURE("Image too big"); - } - Image3F rgb(width, height); - layer.SetFromImage(std::move(rgb), io->metadata.m.color_encoding); - std::vector ec; - for (const auto& ec_meta : layer.metadata()->extra_channel_info) { - ImageF extra(width, height); - if (ec_meta.type == ExtraChannel::kAlpha) { - FillPlane(1.0f, &extra, Rect(extra)); // opaque - } else { - ZeroFillPlane(&extra, Rect(extra)); // zeroes - } - ec.push_back(std::move(extra)); - } - if (!ec.empty()) layer.SetExtraChannels(std::move(ec)); - layer.name = lname; - io->dec_pixels += layer.xsize() * layer.ysize(); - io->frames.push_back(std::move(layer)); - } - - std::vector invert(real_nb_channels, false); - int il = 0; - const uint8_t* bpos = pos; - for (int l = 0; l < layercount; l++) { - if (!is_real_layer[l]) continue; - pos = bpos + layer_offsets[l]; - if (pos < bpos) return JXL_FAILURE("PSD: invalid layer offset"); - JXL_DEBUG_V(PSD_VERBOSITY, "At position %i (%" PRIuS ")", - (int)(pos - bytes.data()), (size_t)pos); - ImageBundle& layer = io->frames[il++]; - std::vector& chan_id = layer_chan_id[l]; - if (chan_id.size() > invert.size()) invert.resize(chan_id.size(), false); - JXL_RETURN_IF_ERROR(decode_layer(pos, maxpos, layer, chan_id, invert, - layer.xsize(), layer.ysize(), version, - colormodel, true, bitdepth)); - } - } else - return JXL_FAILURE("PSD: no layer data found"); - - if (!hasmergeddata && !spotcolor.empty()) { - return JXL_FAILURE("PSD: extra channel data declared but not found"); - } - - if (!spotcolor.empty() || (hasmergeddata && io->frames.empty())) { - // PSD only has spot colors / extra alpha/mask data in the merged image - // We don't redundantly store the merged image, so we put it in the first - // layer (the next layers will kAdd zeroes to it) - pos = after_layers_pos; - bool have_only_merged = false; - if (io->frames.empty()) { - // There is only the merged image, no layers - ImageBundle nlayer(&io->metadata.m); - Image3F rgb(xsize, ysize); - nlayer.SetFromImage(std::move(rgb), io->metadata.m.color_encoding); - std::vector ec; - for (const auto& ec_meta : nlayer.metadata()->extra_channel_info) { - ImageF extra(xsize, ysize); - if (ec_meta.type == ExtraChannel::kAlpha) { - FillPlane(1.0f, &extra, Rect(extra)); // opaque - } else { - ZeroFillPlane(&extra, Rect(extra)); // zeroes - } - ec.push_back(std::move(extra)); - } - if (!ec.empty()) nlayer.SetExtraChannels(std::move(ec)); - io->dec_pixels += nlayer.xsize() * nlayer.ysize(); - io->frames.push_back(std::move(nlayer)); - have_only_merged = true; - } - ImageBundle& layer = io->frames[0]; - std::vector chan_id(real_nb_channels); - std::iota(chan_id.begin(), chan_id.end(), 0); - std::vector invert(real_nb_channels, false); - if (static_cast(spotcolor.size()) + colormodel + 1 < - real_nb_channels) { - return JXL_FAILURE("Inconsistent layer configuration"); - } - if (!merged_has_alpha) { - if (colormodel >= real_nb_channels) { - return JXL_FAILURE("Inconsistent layer configuration"); - } - chan_id.erase(chan_id.begin() + colormodel); - invert.erase(invert.begin() + colormodel); - } else { - colormodel++; - } - for (size_t i = colormodel; i < invert.size(); i++) { - if (spotcolor[i - colormodel][5] == 2) invert[i] = true; - if (spotcolor[i - colormodel][5] == 0) invert[i] = true; - } - JXL_RETURN_IF_ERROR(decode_layer( - pos, maxpos, layer, chan_id, invert, layer.xsize(), layer.ysize(), - version, (have_only_merged ? 0 : colormodel), false, bitdepth)); - } - - if (io->frames.empty()) return JXL_FAILURE("PSD: no layers"); - - io->SetSize(xsize, ysize); - - SetIntensityTarget(io); - - return true; -} - -Status EncodeImagePSD(const CodecInOut* io, const ColorEncoding& c_desired, - size_t bits_per_sample, ThreadPool* pool, - PaddedBytes* bytes) { - return JXL_FAILURE("PSD encoding not yet implemented"); -} - -} // namespace extras -} // namespace jxl diff --git a/third_party/jpeg-xl/lib/extras/codec_test.cc b/third_party/jpeg-xl/lib/extras/codec_test.cc --- a/third_party/jpeg-xl/lib/extras/codec_test.cc +++ b/third_party/jpeg-xl/lib/extras/codec_test.cc @@ -27,10 +27,35 @@ namespace jxl { namespace extras { namespace { +std::string ExtensionFromCodec(Codec codec, const bool is_gray, + const bool has_alpha, + const size_t bits_per_sample) { + switch (codec) { + case Codec::kJPG: + return ".jpg"; + case Codec::kPGX: + return ".pgx"; + case Codec::kPNG: + return ".png"; + case Codec::kPNM: + if (has_alpha) return ".pam"; + if (is_gray) return ".pgm"; + return (bits_per_sample == 32) ? ".pfm" : ".ppm"; + case Codec::kGIF: + return ".gif"; + case Codec::kEXR: + return ".exr"; + case Codec::kUnknown: + return std::string(); + } + JXL_UNREACHABLE; + return std::string(); +} + CodecInOut CreateTestImage(const size_t xsize, const size_t ysize, const bool is_gray, const bool add_alpha, const size_t bits_per_sample, const ColorEncoding& c_native) { Image3F image(xsize, ysize); @@ -74,11 +99,11 @@ if (codec == Codec::kPNM && add_alpha) return; // Our EXR codec always uses 16-bit premultiplied alpha, does not support // grayscale, and somehow does not have sufficient precision for this test. if (codec == Codec::kEXR) return; printf("Codec %s bps:%" PRIuS " gr:%d al:%d\n", - ExtensionFromCodec(codec, is_gray, bits_per_sample).c_str(), + ExtensionFromCodec(codec, is_gray, add_alpha, bits_per_sample).c_str(), bits_per_sample, is_gray, add_alpha); ColorEncoding c_native; c_native.SetColorSpace(is_gray ? ColorSpace::kGray : ColorSpace::kRGB); // Note: this must not be wider than c_external, otherwise gamut clipping @@ -229,15 +254,15 @@ #if 0 TEST(CodecTest, TestMetadataSRGB) { ThreadPoolInternal pool(12); - const char* paths[] = {"raw.pixls/DJI-FC6310-16bit_srgb8_v4_krita.png", - "raw.pixls/Google-Pixel2XL-16bit_srgb8_v4_krita.png", - "raw.pixls/HUAWEI-EVA-L09-16bit_srgb8_dt.png", - "raw.pixls/Nikon-D300-12bit_srgb8_dt.png", - "raw.pixls/Sony-DSC-RX1RM2-14bit_srgb8_v4_krita.png"}; + const char* paths[] = {"third_party/raw.pixls/DJI-FC6310-16bit_srgb8_v4_krita.png", + "third_party/raw.pixls/Google-Pixel2XL-16bit_srgb8_v4_krita.png", + "third_party/raw.pixls/HUAWEI-EVA-L09-16bit_srgb8_dt.png", + "third_party/raw.pixls/Nikon-D300-12bit_srgb8_dt.png", + "third_party/raw.pixls/Sony-DSC-RX1RM2-14bit_srgb8_v4_krita.png"}; for (const char* relative_pathname : paths) { const CodecInOut io = DecodeRoundtrip(relative_pathname, Codec::kPNG, &pool); EXPECT_EQ(8, io.metadata.m.bit_depth.bits_per_sample); EXPECT_FALSE(io.metadata.m.bit_depth.floating_point_sample); @@ -258,13 +283,13 @@ TEST(CodecTest, TestMetadataLinear) { ThreadPoolInternal pool(12); const char* paths[3] = { - "raw.pixls/Google-Pixel2XL-16bit_acescg_g1_v4_krita.png", - "raw.pixls/HUAWEI-EVA-L09-16bit_709_g1_dt.png", - "raw.pixls/Nikon-D300-12bit_2020_g1_dt.png", + "third_party/raw.pixls/Google-Pixel2XL-16bit_acescg_g1_v4_krita.png", + "third_party/raw.pixls/HUAWEI-EVA-L09-16bit_709_g1_dt.png", + "third_party/raw.pixls/Nikon-D300-12bit_2020_g1_dt.png", }; const WhitePoint white_points[3] = {WhitePoint::kCustom, WhitePoint::kD65, WhitePoint::kD65}; const Primaries primaries[3] = {Primaries::kCustom, Primaries::kSRGB, Primaries::k2100}; @@ -290,12 +315,12 @@ TEST(CodecTest, TestMetadataICC) { ThreadPoolInternal pool(12); const char* paths[] = { - "raw.pixls/DJI-FC6310-16bit_709_v4_krita.png", - "raw.pixls/Sony-DSC-RX1RM2-14bit_709_v4_krita.png", + "third_party/raw.pixls/DJI-FC6310-16bit_709_v4_krita.png", + "third_party/raw.pixls/Sony-DSC-RX1RM2-14bit_709_v4_krita.png", }; for (const char* relative_pathname : paths) { const CodecInOut io = DecodeRoundtrip(relative_pathname, Codec::kPNG, &pool); EXPECT_GE(16, io.metadata.m.bit_depth.bits_per_sample); @@ -313,32 +338,32 @@ EXPECT_EQ(Primaries::kSRGB, c_original.primaries); EXPECT_EQ(TransferFunction::k709, c_original.tf.GetTransferFunction()); } } -TEST(CodecTest, TestPNGSuite) { +TEST(CodecTest, Testthird_party/pngsuite) { ThreadPoolInternal pool(12); // Ensure we can load PNG with text, japanese UTF-8, compressed text. - (void)DecodeRoundtrip("pngsuite/ct1n0g04.png", Codec::kPNG, &pool); - (void)DecodeRoundtrip("pngsuite/ctjn0g04.png", Codec::kPNG, &pool); - (void)DecodeRoundtrip("pngsuite/ctzn0g04.png", Codec::kPNG, &pool); + (void)DecodeRoundtrip("third_party/pngsuite/ct1n0g04.png", Codec::kPNG, &pool); + (void)DecodeRoundtrip("third_party/pngsuite/ctjn0g04.png", Codec::kPNG, &pool); + (void)DecodeRoundtrip("third_party/pngsuite/ctzn0g04.png", Codec::kPNG, &pool); // Extract gAMA const CodecInOut b1 = - DecodeRoundtrip("pngsuite/g10n3p04.png", Codec::kPNG, &pool); + DecodeRoundtrip("third_party/pngsuite/g10n3p04.png", Codec::kPNG, &pool); EXPECT_TRUE(b1.metadata.color_encoding.tf.IsLinear()); // Extract cHRM const CodecInOut b_p = - DecodeRoundtrip("pngsuite/ccwn2c08.png", Codec::kPNG, &pool); + DecodeRoundtrip("third_party/pngsuite/ccwn2c08.png", Codec::kPNG, &pool); EXPECT_EQ(Primaries::kSRGB, b_p.metadata.color_encoding.primaries); EXPECT_EQ(WhitePoint::kD65, b_p.metadata.color_encoding.white_point); // Extract EXIF from (new-style) dedicated chunk const CodecInOut b_exif = - DecodeRoundtrip("pngsuite/exif2c08.png", Codec::kPNG, &pool); + DecodeRoundtrip("third_party/pngsuite/exif2c08.png", Codec::kPNG, &pool); EXPECT_EQ(978, b_exif.blobs.exif.size()); } #endif void VerifyWideGamutMetadata(const std::string& relative_pathname, @@ -357,17 +382,17 @@ EXPECT_EQ(primaries, c_original.primaries); } TEST(CodecTest, TestWideGamut) { ThreadPoolInternal pool(12); - // VerifyWideGamutMetadata("wide-gamut-tests/P3-sRGB-color-bars.png", + // VerifyWideGamutMetadata("third_party/wide-gamut-tests/P3-sRGB-color-bars.png", // Primaries::kP3, &pool); - VerifyWideGamutMetadata("wide-gamut-tests/P3-sRGB-color-ring.png", + VerifyWideGamutMetadata("third_party/wide-gamut-tests/P3-sRGB-color-ring.png", Primaries::kP3, &pool); - // VerifyWideGamutMetadata("wide-gamut-tests/R2020-sRGB-color-bars.png", + // VerifyWideGamutMetadata("third_party/wide-gamut-tests/R2020-sRGB-color-bars.png", // Primaries::k2100, &pool); - // VerifyWideGamutMetadata("wide-gamut-tests/R2020-sRGB-color-ring.png", + // VerifyWideGamutMetadata("third_party/wide-gamut-tests/R2020-sRGB-color-ring.png", // Primaries::k2100, &pool); } TEST(CodecTest, TestPNM) { TestCodecPNM(); } diff --git a/third_party/jpeg-xl/lib/extras/dec/apng.cc b/third_party/jpeg-xl/lib/extras/dec/apng.cc --- a/third_party/jpeg-xl/lib/extras/dec/apng.cc +++ b/third_party/jpeg-xl/lib/extras/dec/apng.cc @@ -41,10 +41,11 @@ #include #include #include +#include "jxl/codestream_header.h" #include "jxl/encode.h" #include "lib/jxl/base/compiler_specific.h" #include "lib/jxl/base/printf_macros.h" #include "lib/jxl/base/scope_guard.h" #include "lib/jxl/common.h" @@ -54,10 +55,16 @@ namespace jxl { namespace extras { namespace { +/* hIST chunk tail is not proccesed properly; skip this chunk completely; + see https://github.com/glennrp/libpng/pull/413 */ +const png_byte kIgnoredPngChunks[] = { + 104, 73, 83, 84, '\0' /* hIST */ +}; + // Returns floating-point value from the PNG encoding (times 10^5). static double F64FromU32(const uint32_t x) { return static_cast(x) * 1E-5; } @@ -161,10 +168,35 @@ } JXL_ASSERT(*nibble < 16); return true; } + // Returns false if invalid. + static JXL_INLINE Status DecodeDecimal(const char** pos, const char* end, + uint32_t* JXL_RESTRICT value) { + size_t len = 0; + *value = 0; + while (*pos < end) { + char next = **pos; + if (next >= '0' && next <= '9') { + *value = (*value * 10) + static_cast(next - '0'); + len++; + if (len > 8) { + break; + } + } else { + // Do not consume terminator (non-decimal digit). + break; + } + (*pos)++; + } + if (len == 0 || len > 8) { + return JXL_FAILURE("Failed to parse decimal"); + } + return true; + } + // Parses a PNG text chunk with key of the form "Raw profile type ####", with // #### a type. // Returns whether it could successfully parse the content. // We trust key and encoded are null-terminated because they come from // libpng. @@ -194,21 +226,19 @@ } if (pos == encoded_end) return false; // We parsed so far a \n, some number of non \n characters and are now // pointing at a \n. if (*(pos++) != '\n') return false; - unsigned long bytes_to_decode; - const int fields = sscanf(pos, "%8lu", &bytes_to_decode); - if (fields != 1) return false; // Failed to decode metadata header - JXL_ASSERT(pos + 8 <= encoded_end); - pos += 8; // read %8lu + uint32_t bytes_to_decode = 0; + JXL_RETURN_IF_ERROR(DecodeDecimal(&pos, encoded_end, &bytes_to_decode)); - // We need 2*bytes for the hex values plus 1 byte every 36 values. + // We need 2*bytes for the hex values plus 1 byte every 36 values, + // plus terminal \n for length. const unsigned long needed_bytes = bytes_to_decode * 2 + 1 + DivCeil(bytes_to_decode, 36); if (needed_bytes != static_cast(encoded_end - pos)) { - return JXL_FAILURE("Not enough bytes to parse %lu bytes in hex", + return JXL_FAILURE("Not enough bytes to parse %d bytes in hex", bytes_to_decode); } JXL_ASSERT(bytes->empty()); bytes->reserve(bytes_to_decode); @@ -249,11 +279,11 @@ constexpr uint32_t kId_gAMA = 0x414D4167; constexpr uint32_t kId_cHRM = 0x4D524863; constexpr uint32_t kId_eXIf = 0x66495865; struct APNGFrame { - PaddedBytes pixels; + std::vector pixels; std::vector rows; unsigned int w, h, delay_num, delay_den; }; struct Reader { @@ -268,11 +298,11 @@ } bool Eof() { return next == last; } }; const unsigned long cMaxPNGSize = 1000000UL; -const size_t kMaxPNGChunkSize = 100000000; // 100 MB +const size_t kMaxPNGChunkSize = 1lu << 30; // 1 GB void info_fn(png_structp png_ptr, png_infop info_ptr) { png_set_expand(png_ptr); png_set_palette_to_rgb(png_ptr); png_set_tRNS_to_alpha(png_ptr); @@ -282,15 +312,16 @@ void row_fn(png_structp png_ptr, png_bytep new_row, png_uint_32 row_num, int pass) { APNGFrame* frame = (APNGFrame*)png_get_progressive_ptr(png_ptr); JXL_CHECK(frame); + JXL_CHECK(row_num < frame->rows.size()); JXL_CHECK(frame->rows[row_num] < frame->pixels.data() + frame->pixels.size()); png_progressive_combine_row(png_ptr, frame->rows[row_num], new_row); } -inline unsigned int read_chunk(Reader* r, PaddedBytes* pChunk) { +inline unsigned int read_chunk(Reader* r, std::vector* pChunk) { unsigned char len[4]; if (r->Read(&len, 4)) { const auto size = png_get_uint_32(len); // Check first, to avoid overflow. if (size > kMaxPNGChunkSize) { @@ -305,22 +336,25 @@ } return 0; } int processing_start(png_structp& png_ptr, png_infop& info_ptr, void* frame_ptr, - bool hasInfo, PaddedBytes& chunkIHDR, - std::vector& chunksInfo) { + bool hasInfo, std::vector& chunkIHDR, + std::vector>& chunksInfo) { unsigned char header[8] = {137, 80, 78, 71, 13, 10, 26, 10}; png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); info_ptr = png_create_info_struct(png_ptr); if (!png_ptr || !info_ptr) return 1; if (setjmp(png_jmpbuf(png_ptr))) { return 1; } + png_set_keep_unknown_chunks(png_ptr, 1, kIgnoredPngChunks, + (int)sizeof(kIgnoredPngChunks) / 5); + png_set_crc_action(png_ptr, PNG_CRC_QUIET_USE, PNG_CRC_QUIET_USE); png_set_progressive_read_fn(png_ptr, frame_ptr, info_fn, row_fn, NULL); png_process_data(png_ptr, info_ptr, header, 8); png_process_data(png_ptr, info_ptr, chunkIHDR.data(), chunkIHDR.size()); @@ -378,13 +412,13 @@ unsigned int id, j, w, h, w0, h0, x0, y0; unsigned int delay_num, delay_den, dop, bop, rowbytes, imagesize; unsigned char sig[8]; png_structp png_ptr = nullptr; png_infop info_ptr = nullptr; - PaddedBytes chunk; - PaddedBytes chunkIHDR; - std::vector chunksInfo; + std::vector chunk; + std::vector chunkIHDR; + std::vector> chunksInfo; bool isAnimated = false; bool hasInfo = false; APNGFrame frameRaw = {}; uint32_t num_channels; JxlPixelFormat format; @@ -592,12 +626,11 @@ png_charp name; png_uint_32 proflen = 0; auto ok = png_get_iCCP(png_ptr, info_ptr, &name, &compression_type, &profile, &proflen); if (ok && proflen) { - ppf->icc.resize(proflen); - memcpy(ppf->icc.data(), profile, proflen); + ppf->icc.assign(profile, profile + proflen); have_color = true; } else { // TODO(eustas): JXL_WARNING? } } else if (id == kId_sRGB) { @@ -668,11 +701,12 @@ bool should_blend = frame.blend_op == BLEND_OP_OVER; bool use_for_next_frame = has_nontrivial_background && frame.dispose_op != DISPOSE_OP_PREVIOUS; size_t x0 = frame.x0; size_t y0 = frame.y0; - + size_t xsize = frame.data.xsize; + size_t ysize = frame.data.ysize; if (previous_frame_should_be_cleared) { size_t xs = frame.data.xsize; size_t ys = frame.data.ysize; size_t px0 = frames[i - 1].x0; size_t py0 = frames[i - 1].y0; @@ -708,102 +742,56 @@ xs * bytes_per_pixel); } x0 = px0; y0 = py0; + xsize = pxs; + ysize = pys; should_blend = false; ppf->frames.emplace_back(std::move(new_data)); } else { // If all else fails, insert a dummy blank frame with kReplace. PackedImage blank(pxs, pys, frame.data.format); memset(blank.pixels(), 0, blank.pixels_size); ppf->frames.emplace_back(std::move(blank)); auto& pframe = ppf->frames.back(); - pframe.x0 = px0; - pframe.y0 = py0; + pframe.frame_info.layer_info.crop_x0 = px0; + pframe.frame_info.layer_info.crop_y0 = py0; + pframe.frame_info.layer_info.xsize = frame.xsize; + pframe.frame_info.layer_info.ysize = frame.ysize; pframe.frame_info.duration = 0; - pframe.blend = false; - pframe.use_for_next_frame = true; - + pframe.frame_info.layer_info.have_crop = 0; + pframe.frame_info.layer_info.blend_info.blendmode = JXL_BLEND_REPLACE; + pframe.frame_info.layer_info.blend_info.source = 0; + pframe.frame_info.layer_info.save_as_reference = 1; ppf->frames.emplace_back(std::move(frame.data)); } } else { ppf->frames.emplace_back(std::move(frame.data)); } auto& pframe = ppf->frames.back(); - pframe.x0 = x0; - pframe.y0 = y0; + pframe.frame_info.layer_info.crop_x0 = x0; + pframe.frame_info.layer_info.crop_y0 = y0; + pframe.frame_info.layer_info.xsize = xsize; + pframe.frame_info.layer_info.ysize = ysize; pframe.frame_info.duration = frame.duration; - pframe.blend = should_blend; - pframe.use_for_next_frame = use_for_next_frame; + pframe.frame_info.layer_info.blend_info.blendmode = + should_blend ? JXL_BLEND_BLEND : JXL_BLEND_REPLACE; + bool is_full_size = x0 == 0 && y0 == 0 && xsize == ppf->info.xsize && + ysize == ppf->info.ysize; + pframe.frame_info.layer_info.have_crop = is_full_size ? 0 : 1; + pframe.frame_info.layer_info.blend_info.source = should_blend ? 1 : 0; + pframe.frame_info.layer_info.blend_info.alpha = 0; + pframe.frame_info.layer_info.save_as_reference = use_for_next_frame ? 1 : 0; - if (has_nontrivial_background && - frame.dispose_op == DISPOSE_OP_BACKGROUND) { - previous_frame_should_be_cleared = true; - } else { - previous_frame_should_be_cleared = false; - } + previous_frame_should_be_cleared = + has_nontrivial_background && frame.dispose_op == DISPOSE_OP_BACKGROUND; } if (ppf->frames.empty()) return JXL_FAILURE("No frames decoded"); ppf->frames.back().frame_info.is_last = true; return true; } -static void PngWrite(png_structp png_ptr, png_bytep data, png_size_t length) { - PaddedBytes* bytes = static_cast(png_get_io_ptr(png_ptr)); - bytes->append(data, data + length); -} - -// Stores XMP and EXIF/IPTC into key/value strings for PNG -class BlobsWriterPNG { - public: - static Status Encode(const Blobs& blobs, std::vector* strings) { - if (!blobs.exif.empty()) { - JXL_RETURN_IF_ERROR(EncodeBase16("exif", blobs.exif, strings)); - } - if (!blobs.iptc.empty()) { - JXL_RETURN_IF_ERROR(EncodeBase16("iptc", blobs.iptc, strings)); - } - if (!blobs.xmp.empty()) { - JXL_RETURN_IF_ERROR(EncodeBase16("xmp", blobs.xmp, strings)); - } - return true; - } - - private: - static JXL_INLINE char EncodeNibble(const uint8_t nibble) { - JXL_ASSERT(nibble < 16); - return (nibble < 10) ? '0' + nibble : 'a' + nibble - 10; - } - - static Status EncodeBase16(const std::string& type, const PaddedBytes& bytes, - std::vector* strings) { - // Encoding: base16 with newline after 72 chars. - const size_t base16_size = - 2 * bytes.size() + DivCeil(bytes.size(), size_t(36)) + 1; - std::string base16; - base16.reserve(base16_size); - for (size_t i = 0; i < bytes.size(); ++i) { - if (i % 36 == 0) base16.push_back('\n'); - base16.push_back(EncodeNibble(bytes[i] >> 4)); - base16.push_back(EncodeNibble(bytes[i] & 0x0F)); - } - base16.push_back('\n'); - JXL_ASSERT(base16.length() == base16_size); - - char key[30]; - snprintf(key, sizeof(key), "Raw profile type %s", type.c_str()); - - char header[30]; - snprintf(header, sizeof(header), "\n%s\n%8" PRIuS, type.c_str(), - bytes.size()); - - strings->push_back(std::string(key)); - strings->push_back(std::string(header) + base16); - return true; - } -}; - } // namespace extras } // namespace jxl diff --git a/third_party/jpeg-xl/lib/extras/dec/color_hints.cc b/third_party/jpeg-xl/lib/extras/dec/color_hints.cc --- a/third_party/jpeg-xl/lib/extras/dec/color_hints.cc +++ b/third_party/jpeg-xl/lib/extras/dec/color_hints.cc @@ -40,13 +40,11 @@ return JXL_FAILURE("mismatch between file and color_space hint"); } got_color_space = true; } else if (key == "icc_pathname") { - PaddedBytes icc; - JXL_RETURN_IF_ERROR(ReadFile(value, &icc)); - ppf->icc = std::vector{icc.data(), icc.data() + icc.size()}; + JXL_RETURN_IF_ERROR(ReadFile(value, &ppf->icc)); got_color_space = true; } else { JXL_WARNING("Ignoring %s hint", key.c_str()); } return true; diff --git a/third_party/jpeg-xl/lib/extras/dec/decode.h b/third_party/jpeg-xl/lib/extras/dec/decode.h --- a/third_party/jpeg-xl/lib/extras/dec/decode.h +++ b/third_party/jpeg-xl/lib/extras/dec/decode.h @@ -31,12 +31,11 @@ kPNG, kPNM, kPGX, kJPG, kGIF, - kEXR, - kPSD + kEXR }; static inline constexpr uint64_t EnumBits(Codec /*unused*/) { // Return only fully-supported codecs (kGIF is decode-only). return MakeBit(Codec::kPNM) @@ -47,28 +46,23 @@ | MakeBit(Codec::kJPG) #endif #if JPEGXL_ENABLE_EXR | MakeBit(Codec::kEXR) #endif - | MakeBit(Codec::kPSD); + ; } -// Lower case ASCII including dot, e.g. ".png". -std::string ExtensionFromCodec(Codec codec, bool is_gray, - size_t bits_per_sample); - // If and only if extension is ".pfm", *bits_per_sample is updated to 32 so // that Encode() would encode to PFM instead of PPM. Codec CodecFromExtension(std::string extension, size_t* JXL_RESTRICT bits_per_sample = nullptr); // Decodes "bytes" and sets io->metadata.m. // color_space_hint may specify the color space, otherwise, defaults to sRGB. Status DecodeBytes(Span bytes, const ColorHints& color_hints, const SizeConstraints& constraints, - extras::PackedPixelFile* ppf, ThreadPool* pool = nullptr, - Codec* orig_codec = nullptr); + extras::PackedPixelFile* ppf, Codec* orig_codec = nullptr); } // namespace extras } // namespace jxl #endif // LIB_EXTRAS_DEC_DECODE_H_ diff --git a/third_party/jpeg-xl/lib/extras/dec/decode.cc b/third_party/jpeg-xl/lib/extras/dec/decode.cc --- a/third_party/jpeg-xl/lib/extras/dec/decode.cc +++ b/third_party/jpeg-xl/lib/extras/dec/decode.cc @@ -29,35 +29,10 @@ // Any valid encoding is larger (ensures codecs can read the first few bytes) constexpr size_t kMinBytes = 9; } // namespace -std::string ExtensionFromCodec(Codec codec, const bool is_gray, - const size_t bits_per_sample) { - switch (codec) { - case Codec::kJPG: - return ".jpg"; - case Codec::kPGX: - return ".pgx"; - case Codec::kPNG: - return ".png"; - case Codec::kPNM: - if (is_gray) return ".pgm"; - return (bits_per_sample == 32) ? ".pfm" : ".ppm"; - case Codec::kGIF: - return ".gif"; - case Codec::kEXR: - return ".exr"; - case Codec::kPSD: - return ".psd"; - case Codec::kUnknown: - return std::string(); - } - JXL_UNREACHABLE; - return std::string(); -} - Codec CodecFromExtension(std::string extension, size_t* JXL_RESTRICT bits_per_sample) { std::transform( extension.begin(), extension.end(), extension.begin(), [](char c) { return std::tolower(c, std::locale::classic()); }); @@ -66,14 +41,12 @@ if (extension == ".jpg") return Codec::kJPG; if (extension == ".jpeg") return Codec::kJPG; if (extension == ".pgx") return Codec::kPGX; - if (extension == ".pbm") { - if (bits_per_sample != nullptr) *bits_per_sample = 1; - return Codec::kPNM; - } + if (extension == ".pam") return Codec::kPNM; + if (extension == ".pnm") return Codec::kPNM; if (extension == ".pgm") return Codec::kPNM; if (extension == ".ppm") return Codec::kPNM; if (extension == ".pfm") { if (bits_per_sample != nullptr) *bits_per_sample = 32; return Codec::kPNM; @@ -81,20 +54,17 @@ if (extension == ".gif") return Codec::kGIF; if (extension == ".exr") return Codec::kEXR; - if (extension == ".psd") return Codec::kPSD; - return Codec::kUnknown; } Status DecodeBytes(const Span bytes, const ColorHints& color_hints, const SizeConstraints& constraints, - extras::PackedPixelFile* ppf, ThreadPool* pool, - Codec* orig_codec) { + extras::PackedPixelFile* ppf, Codec* orig_codec) { if (bytes.size() < kMinBytes) return JXL_FAILURE("Too few bytes"); *ppf = extras::PackedPixelFile(); // Default values when not set by decoders. @@ -121,11 +91,11 @@ else if (DecodeImageJPG(bytes, color_hints, constraints, ppf)) { codec = Codec::kJPG; } #endif #if JPEGXL_ENABLE_EXR - else if (DecodeImageEXR(bytes, color_hints, constraints, pool, ppf)) { + else if (DecodeImageEXR(bytes, color_hints, constraints, ppf)) { codec = Codec::kEXR; } #endif else { return JXL_FAILURE("Codecs failed to decode"); diff --git a/third_party/jpeg-xl/lib/extras/dec/exr.h b/third_party/jpeg-xl/lib/extras/dec/exr.h --- a/third_party/jpeg-xl/lib/extras/dec/exr.h +++ b/third_party/jpeg-xl/lib/extras/dec/exr.h @@ -19,12 +19,11 @@ namespace jxl { namespace extras { // Decodes `bytes` into `ppf`. color_hints are ignored. Status DecodeImageEXR(Span bytes, const ColorHints& color_hints, - const SizeConstraints& constraints, ThreadPool* pool, - PackedPixelFile* ppf); + const SizeConstraints& constraints, PackedPixelFile* ppf); } // namespace extras } // namespace jxl #endif // LIB_EXTRAS_DEC_EXR_H_ diff --git a/third_party/jpeg-xl/lib/extras/dec/exr.cc b/third_party/jpeg-xl/lib/extras/dec/exr.cc --- a/third_party/jpeg-xl/lib/extras/dec/exr.cc +++ b/third_party/jpeg-xl/lib/extras/dec/exr.cc @@ -27,22 +27,10 @@ using ExrInt64 = decltype(std::declval().tellg()); constexpr int kExrBitsPerSample = 16; constexpr int kExrAlphaBits = 16; -size_t GetNumThreads(ThreadPool* pool) { - size_t exr_num_threads = 1; - JXL_CHECK(RunOnPool( - pool, 0, 1, - [&](size_t num_threads) { - exr_num_threads = num_threads; - return true; - }, - [&](uint32_t /* task */, size_t /*thread*/) {}, "DecodeImageEXRThreads")); - return exr_num_threads; -} - class InMemoryIStream : public OpenEXR::IStream { public: // The data pointed to by `bytes` must outlive the InMemoryIStream. explicit InMemoryIStream(const Span bytes) : IStream(/*fileName=*/""), bytes_(bytes) {} @@ -72,19 +60,12 @@ }; } // namespace Status DecodeImageEXR(Span bytes, const ColorHints& color_hints, - const SizeConstraints& constraints, ThreadPool* pool, + const SizeConstraints& constraints, PackedPixelFile* ppf) { - // Get the number of threads we should be using for OpenEXR. - // OpenEXR creates its own set of threads, independent from ours. `pool` is - // only used for converting from a buffer of OpenEXR::Rgba to Image3F. - // TODO(sboukortt): look into changing that with OpenEXR 2.3 which allows - // custom thread pools according to its changelog. - OpenEXR::setGlobalThreadCount(GetNumThreads(pool)); - InMemoryIStream is(bytes); #ifdef __EXCEPTIONS std::unique_ptr input_ptr; try { @@ -147,31 +128,28 @@ std::min(input.dataWindow().max.y, input.displayWindow().max.y)); input.setFrameBuffer( input_rows.data() - input.dataWindow().min.x - start_y * row_size, /*xStride=*/1, /*yStride=*/row_size); input.readPixels(start_y, end_y); - JXL_RETURN_IF_ERROR(RunOnPool( - pool, start_y, end_y + 1, ThreadPool::NoInit, - [&](const uint32_t exr_y, size_t /* thread */) { - const int image_y = exr_y - input.displayWindow().min.y; - const OpenEXR::Rgba* const JXL_RESTRICT input_row = - &input_rows[(exr_y - start_y) * row_size]; - uint8_t* row = static_cast(frame.color.pixels()) + - frame.color.stride * image_y; - const uint32_t pixel_size = - (3 + (has_alpha ? 1 : 0)) * kExrBitsPerSample / 8; - for (int exr_x = std::max(input.dataWindow().min.x, - input.displayWindow().min.x); - exr_x <= - std::min(input.dataWindow().max.x, input.displayWindow().max.x); - ++exr_x) { - const int image_x = exr_x - input.displayWindow().min.x; - memcpy(row + image_x * pixel_size, - input_row + (exr_x - input.dataWindow().min.x), pixel_size); - } - }, - "DecodeImageEXR")); + for (int exr_y = start_y; exr_y <= end_y; ++exr_y) { + const int image_y = exr_y - input.displayWindow().min.y; + const OpenEXR::Rgba* const JXL_RESTRICT input_row = + &input_rows[(exr_y - start_y) * row_size]; + uint8_t* row = static_cast(frame.color.pixels()) + + frame.color.stride * image_y; + const uint32_t pixel_size = + (3 + (has_alpha ? 1 : 0)) * kExrBitsPerSample / 8; + for (int exr_x = + std::max(input.dataWindow().min.x, input.displayWindow().min.x); + exr_x <= + std::min(input.dataWindow().max.x, input.displayWindow().max.x); + ++exr_x) { + const int image_x = exr_x - input.displayWindow().min.x; + memcpy(row + image_x * pixel_size, + input_row + (exr_x - input.dataWindow().min.x), pixel_size); + } + } } ppf->color_encoding.transfer_function = JXL_TRANSFER_FUNCTION_LINEAR; ppf->color_encoding.color_space = JXL_COLOR_SPACE_RGB; ppf->color_encoding.primaries = JXL_PRIMARIES_SRGB; diff --git a/third_party/jpeg-xl/lib/extras/dec/gif.cc b/third_party/jpeg-xl/lib/extras/dec/gif.cc --- a/third_party/jpeg-xl/lib/extras/dec/gif.cc +++ b/third_party/jpeg-xl/lib/extras/dec/gif.cc @@ -10,10 +10,11 @@ #include #include #include +#include "jxl/codestream_header.h" #include "lib/jxl/base/compiler_specific.h" #include "lib/jxl/sanitizers.h" namespace jxl { namespace extras { @@ -255,19 +256,30 @@ msan::UnpoisonMemory(color_map->Colors, sizeof(*color_map->Colors) * color_map->ColorCount); GraphicsControlBlock gcb; DGifSavedExtensionToGCB(gif.get(), i, &gcb); msan::UnpoisonMemory(&gcb, sizeof(gcb)); - + bool is_full_size = total_rect.x0() == 0 && total_rect.y0() == 0 && + total_rect.xsize() == canvas.color.xsize && + total_rect.ysize() == canvas.color.ysize; if (ppf->info.have_animation) { frame->frame_info.duration = gcb.DelayTime; - frame->x0 = total_rect.x0(); - frame->y0 = total_rect.y0(); + frame->frame_info.layer_info.have_crop = static_cast(!is_full_size); + frame->frame_info.layer_info.crop_x0 = total_rect.x0(); + frame->frame_info.layer_info.crop_y0 = total_rect.y0(); + frame->frame_info.layer_info.xsize = frame->color.xsize; + frame->frame_info.layer_info.ysize = frame->color.ysize; if (last_base_was_none) { replace = true; } - frame->blend = !replace; + frame->frame_info.layer_info.blend_info.blendmode = + replace ? JXL_BLEND_REPLACE : JXL_BLEND_BLEND; + // We always only reference at most the last frame + frame->frame_info.layer_info.blend_info.source = + last_base_was_none ? 0u : 1u; + frame->frame_info.layer_info.blend_info.clamp = 1; + frame->frame_info.layer_info.blend_info.alpha = 0; // TODO(veluca): this could in principle be implemented. if (last_base_was_none && (total_rect.x0() != 0 || total_rect.y0() != 0 || total_rect.xsize() != canvas.color.xsize || total_rect.ysize() != canvas.color.ysize || !replace)) { @@ -276,18 +288,18 @@ "blended frames"); } switch (gcb.DisposalMode) { case DISPOSE_DO_NOT: case DISPOSE_BACKGROUND: - frame->use_for_next_frame = true; + frame->frame_info.layer_info.save_as_reference = 1u; last_base_was_none = false; break; case DISPOSE_PREVIOUS: - frame->use_for_next_frame = false; + frame->frame_info.layer_info.save_as_reference = 0u; break; default: - frame->use_for_next_frame = false; + frame->frame_info.layer_info.save_as_reference = 0u; last_base_was_none = true; } } // Update the canvas by creating a copy first. diff --git a/third_party/jpeg-xl/lib/extras/dec/pgx.cc b/third_party/jpeg-xl/lib/extras/dec/pgx.cc --- a/third_party/jpeg-xl/lib/extras/dec/pgx.cc +++ b/third_party/jpeg-xl/lib/extras/dec/pgx.cc @@ -115,23 +115,23 @@ JXL_RETURN_IF_ERROR(SkipSingleWhitespace()); JXL_RETURN_IF_ERROR(ParseUnsigned(&header->ysize)); // 0xa, or 0xd 0xa. JXL_RETURN_IF_ERROR(SkipLineBreak()); + // TODO(jon): could do up to 24-bit by converting the values to + // JXL_TYPE_FLOAT. if (header->bits_per_sample > 16) { return JXL_FAILURE("PGX: >16 bits not yet supported"); } // TODO(lode): support signed integers. This may require changing the way // external_image works. if (header->is_signed) { return JXL_FAILURE("PGX: signed not yet supported"); } size_t numpixels = header->xsize * header->ysize; - size_t bytes_per_pixel = header->bits_per_sample <= 8 ? 1 - : header->bits_per_sample <= 16 ? 2 - : 4; + size_t bytes_per_pixel = header->bits_per_sample <= 8 ? 1 : 2; if (pos_ + numpixels * bytes_per_pixel > end_) { return JXL_FAILURE("PGX: data too small"); } *pos = pos_; @@ -172,13 +172,11 @@ ppf->info.alpha_exponent_bits = 0; ppf->info.num_color_channels = 1; // Always grayscale ppf->info.orientation = JXL_ORIENT_IDENTITY; JxlDataType data_type; - if (header.bits_per_sample > 16) { - data_type = JXL_TYPE_UINT32; - } else if (header.bits_per_sample > 8) { + if (header.bits_per_sample > 8) { data_type = JXL_TYPE_UINT16; } else { data_type = JXL_TYPE_UINT8; } diff --git a/third_party/jpeg-xl/lib/extras/dec/pnm.cc b/third_party/jpeg-xl/lib/extras/dec/pnm.cc --- a/third_party/jpeg-xl/lib/extras/dec/pnm.cc +++ b/third_party/jpeg-xl/lib/extras/dec/pnm.cc @@ -17,11 +17,10 @@ namespace { struct HeaderPNM { size_t xsize; size_t ysize; - bool is_bit; // PBM bool is_gray; // PGM bool has_alpha; // PAM size_t bits_per_sample; bool floating_point; bool big_endian; @@ -37,18 +36,13 @@ // codec.cc ensures we have at least two bytes => no range check here. if (pos_[0] != 'P') return false; const uint8_t type = pos_[1]; pos_ += 2; - header->is_bit = false; - switch (type) { case '4': - header->is_bit = true; - header->is_gray = true; - header->bits_per_sample = 1; - return ParseHeaderPNM(header, pos); + return JXL_FAILURE("pbm not supported"); case '5': header->is_gray = true; return ParseHeaderPNM(header, pos); @@ -167,27 +161,31 @@ ++pos_; } return true; } - Status MatchString(const char* keyword) { + Status MatchString(const char* keyword, bool skipws = true) { const uint8_t* ppos = pos_; while (*keyword) { if (ppos >= end_) return JXL_FAILURE("PAM: unexpected end of input"); if (*keyword != *ppos) return false; ppos++; keyword++; } pos_ = ppos; - JXL_RETURN_IF_ERROR(SkipWhitespace()); + if (skipws) { + JXL_RETURN_IF_ERROR(SkipWhitespace()); + } else { + JXL_RETURN_IF_ERROR(SkipSingleWhitespace()); + } return true; } Status ParseHeaderPAM(HeaderPNM* header, const uint8_t** pos) { size_t depth = 3; size_t max_val = 255; - while (!MatchString("ENDHDR")) { + while (!MatchString("ENDHDR", /*skipws=*/false)) { JXL_RETURN_IF_ERROR(SkipWhitespace()); if (MatchString("WIDTH")) { JXL_RETURN_IF_ERROR(ParseUnsigned(&header->xsize)); } else if (MatchString("HEIGHT")) { JXL_RETURN_IF_ERROR(ParseUnsigned(&header->ysize)); @@ -229,11 +227,15 @@ return JXL_FAILURE("PAM: bad DEPTH"); } if (max_val == 0 || max_val >= 65536) { return JXL_FAILURE("PAM: bad MAXVAL"); } - header->bits_per_sample = CeilLog2Nonzero(max_val); + // e.g When `max_val` is 1 , we want 1 bit: + header->bits_per_sample = FloorLog2Nonzero(max_val) + 1; + if ((1u << header->bits_per_sample) - 1 != max_val) + return JXL_FAILURE("PNM: unsupported MaxVal (expected 2^n - 1)"); + // PAM does not pack bits as in PBM. header->floating_point = false; header->big_endian = true; *pos = pos_; return true; @@ -244,19 +246,19 @@ JXL_RETURN_IF_ERROR(ParseUnsigned(&header->xsize)); JXL_RETURN_IF_ERROR(SkipWhitespace()); JXL_RETURN_IF_ERROR(ParseUnsigned(&header->ysize)); - if (!header->is_bit) { - JXL_RETURN_IF_ERROR(SkipWhitespace()); - size_t max_val; - JXL_RETURN_IF_ERROR(ParseUnsigned(&max_val)); - if (max_val == 0 || max_val >= 65536) { - return JXL_FAILURE("PNM: bad MaxVal"); - } - header->bits_per_sample = CeilLog2Nonzero(max_val); + JXL_RETURN_IF_ERROR(SkipWhitespace()); + size_t max_val; + JXL_RETURN_IF_ERROR(ParseUnsigned(&max_val)); + if (max_val == 0 || max_val >= 65536) { + return JXL_FAILURE("PNM: bad MaxVal"); } + header->bits_per_sample = FloorLog2Nonzero(max_val) + 1; + if ((1u << header->bits_per_sample) - 1 != max_val) + return JXL_FAILURE("PNM: unsupported MaxVal (expected 2^n - 1)"); header->floating_point = false; header->big_endian = true; JXL_RETURN_IF_ERROR(SkipSingleWhitespace()); @@ -274,11 +276,16 @@ JXL_RETURN_IF_ERROR(SkipSingleWhitespace()); // The scale has no meaning as multiplier, only its sign is used to // indicate endianness. All software expects nominal range 0..1. double scale; JXL_RETURN_IF_ERROR(ParseSigned(&scale)); - header->big_endian = scale >= 0.0; + if (scale == 0.0) { + return JXL_FAILURE("PFM: bad scale factor value."); + } else if (std::abs(scale) != 1.0) { + JXL_WARNING("PFM: Discarding non-unit scale factor"); + } + header->big_endian = scale > 0.0; header->bits_per_sample = 32; header->floating_point = true; JXL_RETURN_IF_ERROR(SkipSingleWhitespace()); @@ -339,16 +346,12 @@ JxlDataType data_type; if (header.floating_point) { // There's no float16 pnm version. data_type = JXL_TYPE_FLOAT; } else { - if (header.bits_per_sample > 16) { - data_type = JXL_TYPE_UINT32; - } else if (header.bits_per_sample > 8) { + if (header.bits_per_sample > 8) { data_type = JXL_TYPE_UINT16; - } else if (header.is_bit) { - data_type = JXL_TYPE_BOOLEAN; } else { data_type = JXL_TYPE_UINT8; } } @@ -361,10 +364,11 @@ }; ppf->frames.clear(); ppf->frames.emplace_back(header.xsize, header.ysize, format); auto* frame = &ppf->frames.back(); + frame->color.bitdepth_from_format = false; frame->color.flipped_y = header.bits_per_sample == 32; // PFMs are flipped size_t pnm_remaining_size = bytes.data() + bytes.size() - pos; if (pnm_remaining_size < frame->color.pixels_size) { return JXL_FAILURE("PNM file too small"); } diff --git a/third_party/jpeg-xl/lib/extras/enc/apng.cc b/third_party/jpeg-xl/lib/extras/enc/apng.cc --- a/third_party/jpeg-xl/lib/extras/enc/apng.cc +++ b/third_party/jpeg-xl/lib/extras/enc/apng.cc @@ -47,10 +47,11 @@ #include "lib/jxl/base/printf_macros.h" #include "lib/jxl/color_encoding_internal.h" #include "lib/jxl/dec_external_image.h" #include "lib/jxl/enc_color_management.h" #include "lib/jxl/enc_image_bundle.h" +#include "lib/jxl/exif.h" #include "lib/jxl/frame_header.h" #include "lib/jxl/headers.h" #include "lib/jxl/image.h" #include "lib/jxl/image_bundle.h" #include "png.h" /* original (unpatched) libpng is ok */ @@ -68,11 +69,16 @@ // Stores XMP and EXIF/IPTC into key/value strings for PNG class BlobsWriterPNG { public: static Status Encode(const Blobs& blobs, std::vector* strings) { if (!blobs.exif.empty()) { - JXL_RETURN_IF_ERROR(EncodeBase16("exif", blobs.exif, strings)); + // PNG viewers typically ignore Exif orientation but not all of them do + // (and e.g. cjxl doesn't), so we overwrite the Exif orientation to the + // identity to avoid repeated orientation. + std::vector exif = blobs.exif; + ResetExifOrientation(exif); + JXL_RETURN_IF_ERROR(EncodeBase16("exif", exif, strings)); } if (!blobs.iptc.empty()) { JXL_RETURN_IF_ERROR(EncodeBase16("iptc", blobs.iptc, strings)); } if (!blobs.xmp.empty()) { @@ -85,11 +91,12 @@ static JXL_INLINE char EncodeNibble(const uint8_t nibble) { JXL_ASSERT(nibble < 16); return (nibble < 10) ? '0' + nibble : 'a' + nibble - 10; } - static Status EncodeBase16(const std::string& type, const PaddedBytes& bytes, + static Status EncodeBase16(const std::string& type, + const std::vector& bytes, std::vector* strings) { // Encoding: base16 with newline after 72 chars. const size_t base16_size = 2 * bytes.size() + DivCeil(bytes.size(), size_t(36)) + 1; std::string base16; @@ -156,16 +163,16 @@ JXL_RETURN_IF_ERROR(TransformIfNeeded(ib, c_desired, GetJxlCms(), pool, &store, &transformed)); size_t stride = ib.oriented_xsize() * DivCeil(c_desired.Channels() * bits_per_sample + alpha_bits, kBitsPerByte); - PaddedBytes raw_bytes(stride * ib.oriented_ysize()); + std::vector raw_bytes(stride * ib.oriented_ysize()); JXL_RETURN_IF_ERROR(ConvertToExternal( *transformed, bits_per_sample, /*float_out=*/false, c_desired.Channels() + (ib.HasAlpha() ? 1 : 0), JXL_BIG_ENDIAN, stride, - pool, raw_bytes.data(), raw_bytes.size(), /*out_callback=*/nullptr, - /*out_opaque=*/nullptr, metadata.GetOrientation())); + pool, raw_bytes.data(), raw_bytes.size(), + /*out_callback=*/{}, metadata.GetOrientation())); int width = ib.oriented_xsize(); int height = ib.oriented_ysize(); png_byte color_type = @@ -186,14 +193,14 @@ png_set_iCCP(png_ptr, info_ptr, "1", 0, c_desired.ICC().data(), c_desired.ICC().size()); std::vector textstrings; JXL_RETURN_IF_ERROR(BlobsWriterPNG::Encode(io->blobs, &textstrings)); - for (size_t i = 0; i + 1 < textstrings.size(); i += 2) { + for (size_t kk = 0; kk + 1 < textstrings.size(); kk += 2) { png_text text; - text.key = const_cast(textstrings[i].c_str()); - text.text = const_cast(textstrings[i + 1].c_str()); + text.key = const_cast(textstrings[kk].c_str()); + text.text = const_cast(textstrings[kk + 1].c_str()); text.compression = PNG_TEXT_COMPRESSION_zTXt; png_set_text(png_ptr, info_ptr, &text, 1); } png_write_info(png_ptr, info_ptr); @@ -239,20 +246,21 @@ png_write_flush(png_ptr); const size_t pos = bytes->size(); png_write_image(png_ptr, &rows[0]); png_write_flush(png_ptr); if (count > 0) { - PaddedBytes fdata(4); + std::vector fdata(4); png_save_uint_32(fdata.data(), anim_chunks++); size_t p = pos; while (p + 8 < bytes->size()) { size_t len = png_get_uint_32(bytes->data() + p); JXL_ASSERT(bytes->operator[](p + 4) == 'I'); JXL_ASSERT(bytes->operator[](p + 5) == 'D'); JXL_ASSERT(bytes->operator[](p + 6) == 'A'); JXL_ASSERT(bytes->operator[](p + 7) == 'T'); - fdata.append(bytes->data() + p + 8, bytes->data() + p + 8 + len); + fdata.insert(fdata.end(), bytes->data() + p + 8, + bytes->data() + p + 8 + len); p += len + 12; } bytes->resize(pos); png_byte fdat[5] = "fdAT"; diff --git a/third_party/jpeg-xl/lib/extras/enc/jpg.cc b/third_party/jpeg-xl/lib/extras/enc/jpg.cc --- a/third_party/jpeg-xl/lib/extras/enc/jpg.cc +++ b/third_party/jpeg-xl/lib/extras/enc/jpg.cc @@ -17,12 +17,14 @@ #include "lib/jxl/base/compiler_specific.h" #include "lib/jxl/base/status.h" #include "lib/jxl/color_encoding_internal.h" #include "lib/jxl/common.h" +#include "lib/jxl/dec_external_image.h" #include "lib/jxl/enc_color_management.h" #include "lib/jxl/enc_image_bundle.h" +#include "lib/jxl/exif.h" #include "lib/jxl/image.h" #include "lib/jxl/image_bundle.h" #include "lib/jxl/sanitizers.h" #if JPEGXL_ENABLE_SJPEG #include "sjpeg.h" @@ -31,27 +33,19 @@ namespace jxl { namespace extras { namespace { -constexpr float kJPEGSampleMultiplier = MAXJSAMPLE; constexpr unsigned char kICCSignature[12] = { 0x49, 0x43, 0x43, 0x5F, 0x50, 0x52, 0x4F, 0x46, 0x49, 0x4C, 0x45, 0x00}; constexpr int kICCMarker = JPEG_APP0 + 2; constexpr size_t kMaxBytesInMarker = 65533; constexpr unsigned char kExifSignature[6] = {0x45, 0x78, 0x69, 0x66, 0x00, 0x00}; constexpr int kExifMarker = JPEG_APP0 + 1; -constexpr float kJPEGSampleMin = 0; -constexpr float kJPEGSampleMax = MAXJSAMPLE; - -// TODO (jon): take orientation into account when writing jpeg output -// TODO (jon): write Exif blob also in sjpeg encoding -// TODO (jon): overwrite orientation in Exif blob to avoid double orientation - void WriteICCProfile(jpeg_compress_struct* const cinfo, const PaddedBytes& icc) { constexpr size_t kMaxIccBytesInMarker = kMaxBytesInMarker - sizeof kICCSignature - 2; const int num_markers = @@ -71,19 +65,19 @@ jpeg_write_m_byte(cinfo, icc[begin]); ++begin; } } } -void WriteExif(jpeg_compress_struct* const cinfo, const PaddedBytes& exif) { - if (exif.size() < 4) return; +void WriteExif(jpeg_compress_struct* const cinfo, + const std::vector& exif) { jpeg_write_m_header( cinfo, kExifMarker, - static_cast(exif.size() - 4 + sizeof kExifSignature)); + static_cast(exif.size() + sizeof kExifSignature)); for (const unsigned char c : kExifSignature) { jpeg_write_m_byte(cinfo, c); } - for (size_t i = 4; i < exif.size(); ++i) { + for (size_t i = 0; i < exif.size(); ++i) { jpeg_write_m_byte(cinfo, exif[i]); } } Status SetChromaSubsampling(const YCbCrChromaSubsampling& chroma_subsampling, @@ -113,12 +107,12 @@ cinfo.err = jpeg_std_error(&jerr); jpeg_create_compress(&cinfo); unsigned char* buffer = nullptr; unsigned long size = 0; jpeg_mem_dest(&cinfo, &buffer, &size); - cinfo.image_width = ib->xsize(); - cinfo.image_height = ib->ysize(); + cinfo.image_width = ib->oriented_xsize(); + cinfo.image_height = ib->oriented_ysize(); if (ib->IsGray()) { cinfo.input_components = 1; cinfo.in_color_space = JCS_GRAYSCALE; } else { cinfo.input_components = 3; @@ -132,31 +126,30 @@ jpeg_set_quality(&cinfo, quality, TRUE); jpeg_start_compress(&cinfo, TRUE); if (!ib->IsSRGB()) { WriteICCProfile(&cinfo, ib->c_current().ICC()); } - WriteExif(&cinfo, io->blobs.exif); + if (!io->blobs.exif.empty()) { + std::vector exif = io->blobs.exif; + ResetExifOrientation(exif); + WriteExif(&cinfo, exif); + } if (cinfo.input_components > 3 || cinfo.input_components < 0) return JXL_FAILURE("invalid numbers of components"); - std::unique_ptr row( - new JSAMPLE[cinfo.input_components * cinfo.image_width]); - for (size_t y = 0; y < ib->ysize(); ++y) { - const float* const JXL_RESTRICT input_row[3] = { - ib->color().ConstPlaneRow(0, y), ib->color().ConstPlaneRow(1, y), - ib->color().ConstPlaneRow(2, y)}; - for (size_t x = 0; x < ib->xsize(); ++x) { - for (size_t c = 0; c < static_cast(cinfo.input_components); ++c) { - JXL_RETURN_IF_ERROR(c < 3); - row[cinfo.input_components * x + c] = static_cast( - std::max(std::min(kJPEGSampleMultiplier * input_row[c][x] + .5f, - kJPEGSampleMax), - kJPEGSampleMin)); - } - } - JSAMPROW rows[] = {row.get()}; - jpeg_write_scanlines(&cinfo, rows, 1); + size_t stride = + ib->oriented_xsize() * cinfo.input_components * sizeof(JSAMPLE); + PaddedBytes raw_bytes(stride * ib->oriented_ysize()); + JXL_RETURN_IF_ERROR(ConvertToExternal( + *ib, BITS_IN_JSAMPLE, /*float_out=*/false, cinfo.input_components, + JXL_BIG_ENDIAN, stride, nullptr, raw_bytes.data(), raw_bytes.size(), + /*out_callback=*/{}, ib->metadata()->GetOrientation())); + + for (size_t y = 0; y < ib->oriented_ysize(); ++y) { + JSAMPROW row[] = {raw_bytes.data() + y * stride}; + + jpeg_write_scanlines(&cinfo, row, 1); } jpeg_finish_compress(&cinfo); jpeg_destroy_compress(&cinfo); bytes->resize(size); // Compressed image data is initialized by libjpeg, which we are not @@ -165,46 +158,45 @@ std::copy_n(buffer, size, bytes->data()); std::free(buffer); return true; } -Status EncodeWithSJpeg(const ImageBundle* ib, size_t quality, +Status EncodeWithSJpeg(const ImageBundle* ib, const CodecInOut* io, + size_t quality, const YCbCrChromaSubsampling& chroma_subsampling, PaddedBytes* bytes) { #if !JPEGXL_ENABLE_SJPEG return JXL_FAILURE("JPEG XL was built without sjpeg support"); #else sjpeg::EncoderParam param(quality); if (!ib->IsSRGB()) { param.iccp.assign(ib->metadata()->color_encoding.ICC().begin(), ib->metadata()->color_encoding.ICC().end()); } + std::vector exif = io->blobs.exif; + if (!exif.empty()) { + ResetExifOrientation(exif); + param.exif.assign(exif.begin(), exif.end()); + } if (chroma_subsampling.Is444()) { param.yuv_mode = SJPEG_YUV_444; } else if (chroma_subsampling.Is420()) { param.yuv_mode = SJPEG_YUV_SHARP; } else { return JXL_FAILURE("sjpeg does not support this chroma subsampling mode"); } - std::vector rgb; - rgb.reserve(ib->xsize() * ib->ysize() * 3); - for (size_t y = 0; y < ib->ysize(); ++y) { - const float* const rows[] = { - ib->color().ConstPlaneRow(0, y), - ib->color().ConstPlaneRow(1, y), - ib->color().ConstPlaneRow(2, y), - }; - for (size_t x = 0; x < ib->xsize(); ++x) { - for (const float* const row : rows) { - rgb.push_back(static_cast( - std::max(0.f, std::min(255.f, roundf(255.f * row[x]))))); - } - } - } + size_t stride = ib->oriented_xsize() * 3; + PaddedBytes rgb(ib->xsize() * ib->ysize() * 3); + JXL_RETURN_IF_ERROR( + ConvertToExternal(*ib, 8, /*float_out=*/false, 3, JXL_BIG_ENDIAN, stride, + nullptr, rgb.data(), rgb.size(), + /*out_callback=*/{}, ib->metadata()->GetOrientation())); + std::string output; - JXL_RETURN_IF_ERROR(sjpeg::Encode(rgb.data(), ib->xsize(), ib->ysize(), - ib->xsize() * 3, param, &output)); + JXL_RETURN_IF_ERROR(sjpeg::Encode(rgb.data(), ib->oriented_xsize(), + ib->oriented_ysize(), stride, param, + &output)); bytes->assign( reinterpret_cast(output.data()), reinterpret_cast(output.data() + output.size())); return true; #endif @@ -232,11 +224,11 @@ JXL_RETURN_IF_ERROR( EncodeWithLibJpeg(ib, io, quality, chroma_subsampling, bytes)); break; case JpegEncoder::kSJpeg: JXL_RETURN_IF_ERROR( - EncodeWithSJpeg(ib, quality, chroma_subsampling, bytes)); + EncodeWithSJpeg(ib, io, quality, chroma_subsampling, bytes)); break; default: return JXL_FAILURE("tried to use an unknown JPEG encoder"); } diff --git a/third_party/jpeg-xl/lib/extras/enc/pgx.cc b/third_party/jpeg-xl/lib/extras/enc/pgx.cc --- a/third_party/jpeg-xl/lib/extras/enc/pgx.cc +++ b/third_party/jpeg-xl/lib/extras/enc/pgx.cc @@ -73,12 +73,12 @@ size_t stride = ib.xsize() * (bits_per_sample / kBitsPerByte); JXL_RETURN_IF_ERROR( ConvertToExternal(*transformed, bits_per_sample, /*float_out=*/false, /*num_channels=*/1, JXL_BIG_ENDIAN, stride, pool, - pixels.data(), pixels.size(), /*out_callback=*/nullptr, - /*out_opaque=*/nullptr, metadata.GetOrientation())); + pixels.data(), pixels.size(), + /*out_callback=*/{}, metadata.GetOrientation())); char header[kMaxHeaderSize]; int header_size = 0; JXL_RETURN_IF_ERROR(EncodeHeader(ib, bits_per_sample, header, &header_size)); diff --git a/third_party/jpeg-xl/lib/extras/enc/pnm.cc b/third_party/jpeg-xl/lib/extras/enc/pnm.cc --- a/third_party/jpeg-xl/lib/extras/enc/pnm.cc +++ b/third_party/jpeg-xl/lib/extras/enc/pnm.cc @@ -33,34 +33,34 @@ constexpr size_t kMaxHeaderSize = 200; Status EncodeHeader(const PackedPixelFile& ppf, const size_t bits_per_sample, const bool little_endian, char* header, int* JXL_RESTRICT chars_written) { - if (ppf.info.alpha_bits > 0) return JXL_FAILURE("PNM: can't store alpha"); bool is_gray = ppf.info.num_color_channels <= 2; size_t oriented_xsize = ppf.info.orientation <= 4 ? ppf.info.xsize : ppf.info.ysize; size_t oriented_ysize = ppf.info.orientation <= 4 ? ppf.info.ysize : ppf.info.xsize; - - if (bits_per_sample == 32) { // PFM + if (ppf.info.alpha_bits > 0) { // PAM + if (bits_per_sample > 16) return JXL_FAILURE("PNM cannot have > 16 bits"); + const uint32_t max_val = (1U << bits_per_sample) - 1; + *chars_written = + snprintf(header, kMaxHeaderSize, + "P7\nWIDTH %" PRIuS "\nHEIGHT %" PRIuS + "\nDEPTH %u\nMAXVAL %u\nTUPLTYPE %s\nENDHDR\n", + oriented_xsize, oriented_ysize, is_gray ? 2 : 4, max_val, + is_gray ? "GRAYSCALE_ALPHA" : "RGB_ALPHA"); + JXL_RETURN_IF_ERROR(static_cast(*chars_written) < + kMaxHeaderSize); + } else if (bits_per_sample == 32) { // PFM const char type = is_gray ? 'f' : 'F'; const double scale = little_endian ? -1.0 : 1.0; *chars_written = snprintf(header, kMaxHeaderSize, "P%c\n%" PRIuS " %" PRIuS "\n%.1f\n", type, oriented_xsize, oriented_ysize, scale); JXL_RETURN_IF_ERROR(static_cast(*chars_written) < kMaxHeaderSize); - } else if (bits_per_sample == 1) { // PBM - if (is_gray) { - return JXL_FAILURE("Cannot encode color as PBM"); - } - *chars_written = - snprintf(header, kMaxHeaderSize, "P4\n%" PRIuS " %" PRIuS "\n", - oriented_xsize, oriented_ysize); - JXL_RETURN_IF_ERROR(static_cast(*chars_written) < - kMaxHeaderSize); } else { // PGM/PPM const uint32_t max_val = (1U << bits_per_sample) - 1; if (max_val >= 65536) return JXL_FAILURE("PNM cannot have > 16 bits"); const char type = is_gray ? '5' : '6'; *chars_written = @@ -98,11 +98,11 @@ Status EncodeImagePNM(const PackedPixelFile& ppf, size_t bits_per_sample, ThreadPool* pool, size_t frame_index, std::vector* bytes) { const bool floating_point = bits_per_sample > 16; - // Choose native for PFM; PGM/PPM require big-endian (N/A for PBM) + // Choose native for PFM; PGM/PPM require big-endian const JxlEndianness endianness = floating_point ? JXL_NATIVE_ENDIAN : JXL_BIG_ENDIAN; if (!ppf.metadata.exif.empty() || !ppf.metadata.iptc.empty() || !ppf.metadata.jumbf.empty() || !ppf.metadata.xmp.empty()) { JXL_WARNING("PNM encoder ignoring metadata - use a different codec"); diff --git a/third_party/jpeg-xl/lib/extras/packed_image.h b/third_party/jpeg-xl/lib/extras/packed_image.h --- a/third_party/jpeg-xl/lib/extras/packed_image.h +++ b/third_party/jpeg-xl/lib/extras/packed_image.h @@ -62,34 +62,36 @@ size_t ysize; // Whether the y coordinate is flipped (y=0 is the last row). bool flipped_y = false; + // Whether the range is determined by format or by JxlBasicInfo + // e.g. if format is UINT16 and JxlBasicInfo bits_per_sample is 10, + // then if bitdepth_from_format == true, the range is 0..65535 + // while if bitdepth_from_format == false, the range is 0..1023. + bool bitdepth_from_format = true; + // The number of bytes per row. size_t stride; // Pixel storage format and buffer size of the pixels_ pointer. JxlPixelFormat format; size_t pixels_size; static size_t BitsPerChannel(JxlDataType data_type) { switch (data_type) { - case JXL_TYPE_BOOLEAN: - return 1; case JXL_TYPE_UINT8: return 8; case JXL_TYPE_UINT16: return 16; - case JXL_TYPE_UINT32: - return 32; case JXL_TYPE_FLOAT: return 32; case JXL_TYPE_FLOAT16: return 16; - // No default, give compiler error if new type not handled. + default: + JXL_ABORT("Unhandled JxlDataType"); } - return 0; // Indicate invalid data type. } private: static size_t CalcStride(const JxlPixelFormat& format, size_t xsize) { size_t stride = xsize * (BitsPerChannel(format.data_type) * @@ -114,20 +116,10 @@ // The Frame metadata. JxlFrameHeader frame_info = {}; std::string name; - // Offset of the frame in the image. - // TODO(deymo): Add support in the API for this. - size_t x0 = 0; - size_t y0 = 0; - - // Whether this frame should be blended with the previous one. - // TODO(deymo): Maybe add support for this in the API. - bool blend = false; - bool use_for_next_frame = false; - // The pixel data for the color (or grayscale) channels. PackedImage color; // Extra channel image data. std::vector extra_channels; }; diff --git a/third_party/jpeg-xl/lib/extras/packed_image_convert.cc b/third_party/jpeg-xl/lib/extras/packed_image_convert.cc --- a/third_party/jpeg-xl/lib/extras/packed_image_convert.cc +++ b/third_party/jpeg-xl/lib/extras/packed_image_convert.cc @@ -63,25 +63,29 @@ PaddedBytes icc; icc.append(ppf.icc); if (!io->metadata.m.color_encoding.SetICC(std::move(icc))) { fprintf(stderr, "Warning: error setting ICC profile, assuming SRGB"); io->metadata.m.color_encoding = ColorEncoding::SRGB(is_gray); + } else { + if (io->metadata.m.color_encoding.IsGray() != is_gray) { + // E.g. JPG image has 3 channels, but gray ICC. + return JXL_FAILURE("Embedded ICC does not match image color type"); + } } } else { JXL_RETURN_IF_ERROR(ConvertExternalToInternalColorEncoding( ppf.color_encoding, &io->metadata.m.color_encoding)); + if (io->metadata.m.color_encoding.ICC().empty()) { + return JXL_FAILURE("Failed to serialize ICC"); + } } // Convert the extra blobs - io->blobs.exif.clear(); - io->blobs.exif.append(ppf.metadata.exif); - io->blobs.iptc.clear(); - io->blobs.iptc.append(ppf.metadata.iptc); - io->blobs.jumbf.clear(); - io->blobs.jumbf.append(ppf.metadata.jumbf); - io->blobs.xmp.clear(); - io->blobs.xmp.append(ppf.metadata.xmp); + io->blobs.exif = ppf.metadata.exif; + io->blobs.iptc = ppf.metadata.iptc; + io->blobs.jumbf = ppf.metadata.jumbf; + io->blobs.xmp = ppf.metadata.xmp; // Append all other extra channels. for (const PackedPixelFile::PackedExtraChannel& info : ppf.extra_channels_info) { ExtraChannelInfo out; @@ -105,34 +109,37 @@ io->dec_pixels = 0; io->frames.clear(); for (const auto& frame : ppf.frames) { JXL_ASSERT(frame.color.pixels() != nullptr); size_t frame_bits_per_sample = - frame.color.BitsPerChannel(frame.color.format.data_type); + (frame.color.bitdepth_from_format + ? frame.color.BitsPerChannel(frame.color.format.data_type) + : ppf.info.bits_per_sample); JXL_ASSERT(frame_bits_per_sample != 0); // It is ok for the frame.color.format.num_channels to not match the // number of channels on the image. JXL_ASSERT(1 <= frame.color.format.num_channels && frame.color.format.num_channels <= 4); const Span span( static_cast(frame.color.pixels()), frame.color.pixels_size); - Rect frame_rect = - Rect(frame.x0, frame.y0, frame.color.xsize, frame.color.ysize); + Rect frame_rect = Rect(frame.frame_info.layer_info.crop_x0, + frame.frame_info.layer_info.crop_y0, + frame.frame_info.layer_info.xsize, + frame.frame_info.layer_info.ysize); JXL_ASSERT(frame_rect.IsInside(Rect(0, 0, ppf.info.xsize, ppf.info.ysize))); - ImageBundle bundle(&io->metadata.m); if (ppf.info.have_animation) { bundle.duration = frame.frame_info.duration; - bundle.blend = frame.blend; - bundle.use_for_next_frame = frame.use_for_next_frame; + bundle.blend = frame.frame_info.layer_info.blend_info.blendmode > 0; + bundle.use_for_next_frame = + frame.frame_info.layer_info.save_as_reference > 0; + bundle.origin.x0 = frame.frame_info.layer_info.crop_x0; + bundle.origin.y0 = frame.frame_info.layer_info.crop_y0; } bundle.name = frame.name; // frame.frame_info.name_length is ignored here. - bundle.origin.x0 = frame.x0; - bundle.origin.y0 = frame.y0; - JXL_ASSERT(io->metadata.m.color_encoding.IsGray() == (frame.color.format.num_channels <= 2)); const bool float_in = frame.color.format.data_type == JXL_TYPE_FLOAT16 || frame.color.format.data_type == JXL_TYPE_FLOAT; @@ -142,15 +149,17 @@ /*alpha_is_premultiplied=*/ppf.info.alpha_premultiplied, frame_bits_per_sample, frame.color.format.endianness, /*flipped_y=*/frame.color.flipped_y, pool, &bundle, /*float_in=*/float_in, /*align=*/0)); - for (const auto& ppf_ec : frame.extra_channels) { - bundle.extra_channels().emplace_back(ppf_ec.xsize, ppf_ec.ysize); + bundle.extra_channels().resize(io->metadata.m.extra_channel_info.size()); + for (size_t i = 0; i < frame.extra_channels.size(); i++) { + const auto& ppf_ec = frame.extra_channels[i]; + bundle.extra_channels()[i] = ImageF(ppf_ec.xsize, ppf_ec.ysize); JXL_CHECK(BufferToImageF(ppf_ec.format, ppf_ec.xsize, ppf_ec.ysize, ppf_ec.pixels(), ppf_ec.pixels_size, pool, - &bundle.extra_channels().back())); + &bundle.extra_channels()[i])); } io->frames.push_back(std::move(bundle)); io->dec_pixels += frame.color.xsize * frame.color.ysize; } @@ -216,14 +225,14 @@ if (ppf->icc.empty()) { ConvertInternalToExternalColorEncoding(c_desired, &ppf->color_encoding); } // Convert the extra blobs - ppf->metadata.exif.assign(io.blobs.exif.begin(), io.blobs.exif.end()); - ppf->metadata.iptc.assign(io.blobs.iptc.begin(), io.blobs.iptc.end()); - ppf->metadata.jumbf.assign(io.blobs.jumbf.begin(), io.blobs.jumbf.end()); - ppf->metadata.xmp.assign(io.blobs.xmp.begin(), io.blobs.xmp.end()); + ppf->metadata.exif = io.blobs.exif; + ppf->metadata.iptc = io.blobs.iptc; + ppf->metadata.jumbf = io.blobs.jumbf; + ppf->metadata.xmp = io.blobs.xmp; const bool float_out = pixel_format.data_type == JXL_TYPE_FLOAT || pixel_format.data_type == JXL_TYPE_FLOAT16; // Convert the pixels ppf->frames.clear(); for (const auto& frame : io.frames) { @@ -238,12 +247,15 @@ /*endianness=*/pixel_format.endianness, /*align=*/pixel_format.align}; PackedFrame packed_frame(frame.oriented_xsize(), frame.oriented_ysize(), format); + packed_frame.color.bitdepth_from_format = float_out; const size_t bits_per_sample = - packed_frame.color.BitsPerChannel(pixel_format.data_type); + packed_frame.color.bitdepth_from_format + ? packed_frame.color.BitsPerChannel(pixel_format.data_type) + : ppf->info.bits_per_sample; packed_frame.name = frame.name; packed_frame.frame_info.name_length = frame.name.size(); // Color transform ImageBundle ib = frame.Copy(); const ImageBundle* to_color_transform = &ib; @@ -262,12 +274,11 @@ JXL_RETURN_IF_ERROR(ConvertToExternal( *transformed, bits_per_sample, float_out, format.num_channels, format.endianness, /* stride_out=*/packed_frame.color.stride, pool, packed_frame.color.pixels(), packed_frame.color.pixels_size, - /*out_callback=*/nullptr, /*out_opaque=*/nullptr, - frame.metadata()->GetOrientation())); + /*out_callback=*/{}, frame.metadata()->GetOrientation())); // TODO(firsching): Convert the extra channels, beside one potential alpha // channel. FIXME! JXL_CHECK(frame.extra_channels().size() <= has_alpha); ppf->frames.push_back(std::move(packed_frame)); diff --git a/third_party/jpeg-xl/lib/extras/tone_mapping_gbench.cc b/third_party/jpeg-xl/lib/extras/tone_mapping_gbench.cc --- a/third_party/jpeg-xl/lib/extras/tone_mapping_gbench.cc +++ b/third_party/jpeg-xl/lib/extras/tone_mapping_gbench.cc @@ -12,11 +12,11 @@ namespace jxl { static void BM_ToneMapping(benchmark::State& state) { CodecInOut image; const PaddedBytes image_bytes = - ReadTestData("imagecompression.info/flower_foveon.png"); + ReadTestData("third_party/imagecompression.info/flower_foveon.png"); JXL_CHECK(SetFromBytes(Span(image_bytes), &image)); // Convert to linear Rec. 2020 so that `ToneMapTo` doesn't have to and we // mainly measure the tone mapping itself. ColorEncoding linear_rec2020; diff --git a/third_party/jpeg-xl/lib/include/jxl/decode.h b/third_party/jpeg-xl/lib/include/jxl/decode.h --- a/third_party/jpeg-xl/lib/include/jxl/decode.h +++ b/third_party/jpeg-xl/lib/include/jxl/decode.h @@ -916,10 +916,60 @@ */ typedef void (*JxlImageOutCallback)(void* opaque, size_t x, size_t y, size_t num_pixels, const void* pixels); /** + * Initialization callback for @c JxlDecoderSetMultithreadedImageOutCallback. + * + * @see JxlDecoderSetMultithreadedImageOutCallback + * + * @param init_opaque optional user data, as given to + * @c JxlDecoderSetMultithreadedImageOutCallback. + * @param num_threads maximum number of threads that will call the @c run + * callback concurrently. + * @param num_pixels_per_thread maximum number of pixels that will be passed in + * one call to @c run. + * @return a pointer to data that will be passed to the @c run callback, or + * @c NULL if initialization failed. + */ +typedef void* (*JxlImageOutInitCallback)(void* init_opaque, size_t num_threads, + size_t num_pixels_per_thread); + +/** + * Worker callback for @c JxlDecoderSetMultithreadedImageOutCallback. + * + * @see JxlDecoderSetMultithreadedImageOutCallback + * + * @param run_opaque user data returned by the @c init callback. + * @param thread_id number in `[0, num_threads)` identifying the thread of the + * current invocation of the callback. + * @param x horizontal position of the first (leftmost) pixel of the pixel data. + * @param y vertical position of the pixel data. + * @param num_pixels number of pixels in the pixel data. May be less than the + * full @c xsize of the image, and will be at most equal to the @c + * num_pixels_per_thread that was passed to @c init. + * @param pixels pixel data as a horizontal stripe, in the format passed to @c + * JxlDecoderSetMultithreadedImageOutCallback. The data pointed to remains owned + * by the caller and is only guaranteed to outlive the current callback + * invocation. + */ +typedef void (*JxlImageOutRunCallback)(void* run_opaque, size_t thread_id, + size_t x, size_t y, size_t num_pixels, + const void* pixels); + +/** + * Destruction callback for @c JxlDecoderSetMultithreadedImageOutCallback, + * called after all invocations of the @c run callback to perform any + * appropriate clean-up of the @c run_opaque data returned by @c init. + * + * @see JxlDecoderSetMultithreadedImageOutCallback + * + * @param run_opaque user data returned by the @c init callback. + */ +typedef void (*JxlImageOutDestroyCallback)(void* run_opaque); + +/** * Sets pixel output callback. This is an alternative to * JxlDecoderSetImageOutBuffer. This can be set when the JXL_DEC_FRAME event * occurs, must be set when the JXL_DEC_NEED_IMAGE_OUT_BUFFER event occurs, and * applies only for the current frame. Only one of JxlDecoderSetImageOutBuffer * or JxlDecoderSetImageOutCallback may be used for the same frame, not both at @@ -948,12 +998,12 @@ * this pixel. A pixel may be visited zero times if it cannot be decoded yet * or if it was already decoded to full precision (this behavior is not * guaranteed). * * @param dec decoder object - * @param format format of the pixels. Object owned by user and its contents - * are copied internally. + * @param format format of the pixels. Object owned by user; its contents are + * copied internally. * @param callback the callback function receiving partial scanlines of pixel * data. * @param opaque optional user data, which will be passed on to the callback, * may be NULL. * @return JXL_DEC_SUCCESS on success, JXL_DEC_ERROR on error, such as @@ -961,10 +1011,34 @@ */ JXL_EXPORT JxlDecoderStatus JxlDecoderSetImageOutCallback(JxlDecoder* dec, const JxlPixelFormat* format, JxlImageOutCallback callback, void* opaque); +/** Similar to @c JxlDecoderSetImageOutCallback except that the callback is + * allowed an initialization phase during which it is informed of how many + * threads will call it concurrently, and those calls are further informed of + * which thread they are occurring in. + * + * @param dec decoder object + * @param format format of the pixels. Object owned by user; its contents are + * copied internally. + * @param init_callback initialization callback. + * @param run_callback the callback function receiving partial scanlines of + * pixel data. + * @param destroy_callback clean-up callback invoked after all calls to @c + * run_callback. May be NULL if no clean-up is necessary. + * @param init_opaque optional user data passed to @c init_callback, may be NULL + * (unlike the return value from @c init_callback which may only be NULL if + * initialization failed). + * @return JXL_DEC_SUCCESS on success, JXL_DEC_ERROR on error, such as @c + * JxlDecoderSetImageOutBuffer having already been called. + */ +JXL_EXPORT JxlDecoderStatus JxlDecoderSetMultithreadedImageOutCallback( + JxlDecoder* dec, const JxlPixelFormat* format, + JxlImageOutInitCallback init_callback, JxlImageOutRunCallback run_callback, + JxlImageOutDestroyCallback destroy_callback, void* init_opaque); + /** * Returns the minimum size in bytes of an extra channel pixel buffer for the * given format. This is the buffer for JxlDecoderSetExtraChannelBuffer. * Requires the basic image information is available in the decoder. * @@ -1123,12 +1197,12 @@ * box, this will return "brob" if the decompressed argument is JXL_FALSE, or * the underlying box type if the decompressed argument is JXL_TRUE. * * @param dec decoder object * @param type buffer to copy the type into - * @param decompressed which box type to get: JXL_TRUE to get the raw box type, - * which can be "brob", JXL_FALSE, get the underlying box type. + * @param decompressed which box type to get: JXL_FALSE to get the raw box type, + * which can be "brob", JXL_TRUE, get the underlying box type. * @return JXL_DEC_SUCCESS if the value is available, JXL_DEC_ERROR if not, for * example the JXL file does not use the container format. */ JXL_EXPORT JxlDecoderStatus JxlDecoderGetBoxType(JxlDecoder* dec, JxlBoxType type, diff --git a/third_party/jpeg-xl/lib/include/jxl/encode.h b/third_party/jpeg-xl/lib/include/jxl/encode.h --- a/third_party/jpeg-xl/lib/include/jxl/encode.h +++ b/third_party/jpeg-xl/lib/include/jxl/encode.h @@ -281,10 +281,17 @@ * If non-keyframes, i.e., frames with cropping, blending or patches are * attempted to be indexed, JXL_ENC_ERROR will occur. */ JXL_ENC_FRAME_INDEX_BOX = 31, + /** Sets brotli encode effort for use in JPEG recompression and compressed + * metadata boxes (brob). Can be -1 (default) or 0 (fastest) to 11 (slowest). + * Default is based on the general encode effort in case of JPEG + * recompression, and 4 for brob boxes. + */ + JXL_ENC_FRAME_SETTING_BROTLI_EFFORT = 32, + /** 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. */ JXL_ENC_FRAME_SETTING_FILL_ENUM = 65535, @@ -507,10 +514,12 @@ * - trichromatic, e.g. RGB * - trichromatic + alpha * * Extra channels not handled here need to be set by @ref * JxlEncoderSetExtraChannelBuffer. + * If the image has alpha, and alpha is not passed here, it will implicitly be + * set to all-opaque (an alpha value of 1.0 everywhere). * * The color profile of the pixels depends on the value of uses_original_profile * in the JxlBasicInfo. If true, the pixels are assumed to be encoded in the * original profile that is set with JxlEncoderSetColorEncoding or * JxlEncoderSetICCProfile. If false, the pixels are assumed to be nonlinear @@ -1047,11 +1056,11 @@ JXL_EXPORT JxlEncoderStatus JxlEncoderSetFrameDistance( JxlEncoderFrameSettings* frame_settings, float distance); /** DEPRECATED: use JxlEncoderSetFrameDistance instead. */ -JXL_EXPORT JxlEncoderStatus +JXL_EXPORT JXL_DEPRECATED JxlEncoderStatus JxlEncoderOptionsSetDistance(JxlEncoderFrameSettings*, float); /** * 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. @@ -1070,11 +1079,11 @@ JXL_EXPORT JxlEncoderFrameSettings* JxlEncoderFrameSettingsCreate( JxlEncoder* enc, const JxlEncoderFrameSettings* source); /** DEPRECATED: use JxlEncoderFrameSettingsCreate instead. */ -JXL_EXPORT JxlEncoderFrameSettings* JxlEncoderOptionsCreate( +JXL_EXPORT JXL_DEPRECATED JxlEncoderFrameSettings* JxlEncoderOptionsCreate( JxlEncoder*, const JxlEncoderFrameSettings*); /** * Sets a color encoding to be sRGB. * diff --git a/third_party/jpeg-xl/lib/include/jxl/types.h b/third_party/jpeg-xl/lib/include/jxl/types.h --- a/third_party/jpeg-xl/lib/include/jxl/types.h +++ b/third_party/jpeg-xl/lib/include/jxl/types.h @@ -14,10 +14,12 @@ #define JXL_TYPES_H_ #include #include +#include "jxl/jxl_export.h" + #if defined(__cplusplus) || defined(c_plusplus) extern "C" { #endif /** @@ -39,32 +41,30 @@ * (within gamut, may go outside this range for wide color gamut). Floating * point output, either JXL_TYPE_FLOAT or JXL_TYPE_FLOAT16, is recommended * for HDR and wide gamut images when color profile conversion is required. */ JXL_TYPE_FLOAT = 0, - /** Use 1-bit packed in uint8_t, first pixel in LSB, padded to uint8_t per - * row. - * TODO(lode): support first in MSB, other padding. - */ - JXL_TYPE_BOOLEAN, - /** Use type uint8_t. May clip wide color gamut data. */ - JXL_TYPE_UINT8, + JXL_TYPE_UINT8 = 2, /** Use type uint16_t. May clip wide color gamut data. */ - JXL_TYPE_UINT16, - - /** Use type uint32_t. May clip wide color gamut data. - */ - JXL_TYPE_UINT32, + JXL_TYPE_UINT16 = 3, /** Use 16-bit IEEE 754 half-precision floating point values */ - JXL_TYPE_FLOAT16, + JXL_TYPE_FLOAT16 = 5, } JxlDataType; +/* DEPRECATED: bit-packed 1-bit data type. Use JXL_TYPE_UINT8 instead. + */ +static const int JXL_DEPRECATED JXL_TYPE_BOOLEAN = 1; + +/* DEPRECATED: uint32_t data type. Use JXL_TYPE_FLOAT instead. + */ +static const int JXL_DEPRECATED JXL_TYPE_UINT32 = 4; + /** Ordering of multi-byte data. */ typedef enum { /** Use the endianness of the system, either little endian or big endian, * without forcing either specific endianness. Do not use if pixel data diff --git a/third_party/jpeg-xl/lib/jxl.cmake b/third_party/jpeg-xl/lib/jxl.cmake --- a/third_party/jpeg-xl/lib/jxl.cmake +++ b/third_party/jpeg-xl/lib/jxl.cmake @@ -37,13 +37,13 @@ jxl/base/padded_bytes.h jxl/base/printf_macros.h jxl/base/profiler.h jxl/base/random.cc jxl/base/random.h + jxl/base/sanitizer_definitions.h jxl/base/scope_guard.h jxl/base/span.h - jxl/base/status.cc jxl/base/status.h jxl/base/thread_pool_internal.h jxl/blending.cc jxl/blending.h jxl/box_content_decoder.cc @@ -104,10 +104,12 @@ jxl/enc_bit_writer.h jxl/entropy_coder.cc jxl/entropy_coder.h jxl/epf.cc jxl/epf.h + jxl/exif.cc + jxl/exif.h jxl/fast_dct-inl.h jxl/fast_dct.cc jxl/fast_dct.h jxl/fast_dct128-inl.h jxl/fast_dct16-inl.h @@ -342,20 +344,21 @@ set(JPEGXL_DEC_INTERNAL_LIBS brotlidec-static brotlicommon-static hwy + Threads::Threads + ${ATOMICS_LIBRARIES} ) if(JPEGXL_ENABLE_PROFILER) list(APPEND JPEGXL_DEC_INTERNAL_LIBS jxl_profiler) endif() set(JPEGXL_INTERNAL_LIBS ${JPEGXL_DEC_INTERNAL_LIBS} brotlienc-static - Threads::Threads ) # strips the -static suffix from all the elements in LIST function(strip_static OUTPUT_VAR LIB_LIST) foreach(lib IN LISTS ${LIB_LIST}) @@ -463,11 +466,11 @@ # for tests. add_library(jxl_dec-static STATIC $ ) target_link_libraries(jxl_dec-static - PUBLIC ${JPEGXL_COVERAGE_FLAGS} ${JPEGXL_DEC_INTERNAL_LIBS} hwy) + PUBLIC ${JPEGXL_COVERAGE_FLAGS} ${JPEGXL_DEC_INTERNAL_LIBS}) target_include_directories(jxl_dec-static PUBLIC "${PROJECT_SOURCE_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}/include" "${CMAKE_CURRENT_BINARY_DIR}/include") @@ -484,11 +487,11 @@ # for tests. # TODO(lode): once the source files are correctly split so that it is possible # to do, remove $ here and depend on jxl_dec-static add_library(jxl-static STATIC ${JPEGXL_INTERNAL_OBJECTS}) target_link_libraries(jxl-static - PUBLIC ${JPEGXL_COVERAGE_FLAGS} ${JPEGXL_INTERNAL_LIBS} hwy) + PUBLIC ${JPEGXL_COVERAGE_FLAGS} ${JPEGXL_INTERNAL_LIBS}) target_include_directories(jxl-static PUBLIC "${PROJECT_SOURCE_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}/include" "${CMAKE_CURRENT_BINARY_DIR}/include") diff --git a/third_party/jpeg-xl/lib/jxl/aux_out.h b/third_party/jpeg-xl/lib/jxl/aux_out.h --- a/third_party/jpeg-xl/lib/jxl/aux_out.h +++ b/third_party/jpeg-xl/lib/jxl/aux_out.h @@ -218,11 +218,11 @@ std::array min; std::array max; Image3MinMax(image, &min, &max); Image3B normalized(image.xsize(), image.ysize()); for (size_t c = 0; c < 3; ++c) { - float mul = min[c] == max[c] ? 0 : (1.0f / (max[c] - min[c])); + float mul = min[c] == max[c] ? 0 : (255.0f / (max[c] - min[c])); for (size_t y = 0; y < image.ysize(); ++y) { const T* JXL_RESTRICT row_in = image.ConstPlaneRow(c, y); uint8_t* JXL_RESTRICT row_out = normalized.PlaneRow(c, y); for (size_t x = 0; x < image.xsize(); ++x) { row_out[x] = static_cast((row_in[x] - min[c]) * mul); diff --git a/third_party/jpeg-xl/lib/jxl/base/sanitizer_definitions.h b/third_party/jpeg-xl/lib/jxl/base/sanitizer_definitions.h new file mode 100644 --- /dev/null +++ b/third_party/jpeg-xl/lib/jxl/base/sanitizer_definitions.h @@ -0,0 +1,44 @@ +// Copyright (c) the JPEG XL Project Authors. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#ifndef LIB_JXL_BASE_SANITIZER_DEFINITIONS_H_ +#define LIB_JXL_BASE_SANITIZER_DEFINITIONS_H_ + +#ifdef MEMORY_SANITIZER +#define JXL_MEMORY_SANITIZER 1 +#elif defined(__has_feature) +#if __has_feature(memory_sanitizer) +#define JXL_MEMORY_SANITIZER 1 +#else +#define JXL_MEMORY_SANITIZER 0 +#endif +#else +#define JXL_MEMORY_SANITIZER 0 +#endif + +#ifdef ADDRESS_SANITIZER +#define JXL_ADDRESS_SANITIZER 1 +#elif defined(__has_feature) +#if __has_feature(address_sanitizer) +#define JXL_ADDRESS_SANITIZER 1 +#else +#define JXL_ADDRESS_SANITIZER 0 +#endif +#else +#define JXL_ADDRESS_SANITIZER 0 +#endif + +#ifdef THREAD_SANITIZER +#define JXL_THREAD_SANITIZER 1 +#elif defined(__has_feature) +#if __has_feature(thread_sanitizer) +#define JXL_THREAD_SANITIZER 1 +#else +#define JXL_THREAD_SANITIZER 0 +#endif +#else +#define JXL_THREAD_SANITIZER 0 +#endif +#endif // LIB_JXL_BASE_SANITIZER_DEFINITIONS_H diff --git a/third_party/jpeg-xl/lib/jxl/base/status.h b/third_party/jpeg-xl/lib/jxl/base/status.h --- a/third_party/jpeg-xl/lib/jxl/base/status.h +++ b/third_party/jpeg-xl/lib/jxl/base/status.h @@ -12,10 +12,15 @@ #include #include #include #include "lib/jxl/base/compiler_specific.h" +#include "lib/jxl/base/sanitizer_definitions.h" + +#if JXL_ADDRESS_SANITIZER || JXL_MEMORY_SANITIZER || JXL_THREAD_SANITIZER +#include "sanitizer/common_interface_defs.h" // __sanitizer_print_stack_trace +#endif // defined(*_SANITIZER) namespace jxl { // Uncomment to abort when JXL_FAILURE or JXL_STATUS with a fatal error is // reached: @@ -68,11 +73,17 @@ // Print a debug message on standard error. You should use the JXL_DEBUG macro // instead of calling Debug directly. This function returns false, so it can be // used as a return value in JXL_FAILURE. JXL_FORMAT(1, 2) -bool Debug(const char* format, ...); +inline JXL_NOINLINE bool Debug(const char* format, ...) { + va_list args; + va_start(args, format); + vfprintf(stderr, format, args); + va_end(args); + return false; +} // Print a debug message on standard error if "enabled" is true. "enabled" is // normally a macro that evaluates to 0 or 1 at compile time, so the Debug // function is never called and optimized out in release builds. Note that the // arguments are compiled but not evaluated when enabled is false. The format @@ -111,11 +122,25 @@ #endif // JXL_DEBUG_WARNING #define JXL_WARNING(format, ...) \ JXL_DEBUG(JXL_DEBUG_WARNING, format, ##__VA_ARGS__) // Exits the program after printing a stack trace when possible. -JXL_NORETURN bool Abort(); +JXL_NORETURN inline JXL_NOINLINE bool Abort() { +#if JXL_ADDRESS_SANITIZER || JXL_MEMORY_SANITIZER || JXL_THREAD_SANITIZER + // If compiled with any sanitizer print a stack trace. This call doesn't crash + // the program, instead the trap below will crash it also allowing gdb to + // break there. + __sanitizer_print_stack_trace(); +#endif // *_SANITIZER) + +#if JXL_COMPILER_MSVC + __debugbreak(); + abort(); +#else + __builtin_trap(); +#endif +} // Exits the program after printing file/line plus a formatted string. #define JXL_ABORT(format, ...) \ ((JXL_DEBUG_ON_ABORT) && ::jxl::Debug(("%s:%d: JXL_ABORT: " format "\n"), \ __FILE__, __LINE__, ##__VA_ARGS__), \ diff --git a/third_party/jpeg-xl/lib/jxl/base/status.cc b/third_party/jpeg-xl/lib/jxl/base/status.cc deleted file mode 100644 --- a/third_party/jpeg-xl/lib/jxl/base/status.cc +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright (c) the JPEG XL Project Authors. All rights reserved. -// -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -#include "lib/jxl/base/status.h" - -#include -#include -#include - -#include - -#include "lib/jxl/sanitizers.h" - -#if JXL_ADDRESS_SANITIZER || JXL_MEMORY_SANITIZER || JXL_THREAD_SANITIZER -#include "sanitizer/common_interface_defs.h" // __sanitizer_print_stack_trace -#endif // defined(*_SANITIZER) - -namespace jxl { - -bool Debug(const char* format, ...) { - va_list args; - va_start(args, format); - vfprintf(stderr, format, args); - va_end(args); - return false; -} - -bool Abort() { -#if JXL_ADDRESS_SANITIZER || JXL_MEMORY_SANITIZER || JXL_THREAD_SANITIZER - // If compiled with any sanitizer print a stack trace. This call doesn't crash - // the program, instead the trap below will crash it also allowing gdb to - // break there. - __sanitizer_print_stack_trace(); -#endif // *_SANITIZER) - -#if JXL_COMPILER_MSVC - __debugbreak(); - abort(); -#else - __builtin_trap(); -#endif -} - -} // namespace jxl diff --git a/third_party/jpeg-xl/lib/jxl/bits_test.cc b/third_party/jpeg-xl/lib/jxl/bits_test.cc --- a/third_party/jpeg-xl/lib/jxl/bits_test.cc +++ b/third_party/jpeg-xl/lib/jxl/bits_test.cc @@ -39,10 +39,14 @@ for (uint32_t i = 1; i <= 7; ++i) { EXPECT_EQ(expected[i - 1], FloorLog2Nonzero(i)) << " " << i; EXPECT_EQ(expected[i - 1], FloorLog2Nonzero(uint64_t(i))) << " " << i; } + EXPECT_EQ(11u, FloorLog2Nonzero(0x00000fffu)); // 4095 + EXPECT_EQ(12u, FloorLog2Nonzero(0x00001000u)); // 4096 + EXPECT_EQ(12u, FloorLog2Nonzero(0x00001001u)); // 4097 + EXPECT_EQ(31u, FloorLog2Nonzero(0x80000000u)); EXPECT_EQ(31u, FloorLog2Nonzero(0x80000001u)); EXPECT_EQ(31u, FloorLog2Nonzero(0xFFFFFFFFu)); EXPECT_EQ(31u, FloorLog2Nonzero(0x80000000ull)); @@ -60,10 +64,14 @@ for (uint32_t i = 1; i <= 7; ++i) { EXPECT_EQ(expected[i - 1], CeilLog2Nonzero(i)) << " " << i; EXPECT_EQ(expected[i - 1], CeilLog2Nonzero(uint64_t(i))) << " " << i; } + EXPECT_EQ(12u, CeilLog2Nonzero(0x00000fffu)); // 4095 + EXPECT_EQ(12u, CeilLog2Nonzero(0x00001000u)); // 4096 + EXPECT_EQ(13u, CeilLog2Nonzero(0x00001001u)); // 4097 + EXPECT_EQ(31u, CeilLog2Nonzero(0x80000000u)); EXPECT_EQ(32u, CeilLog2Nonzero(0x80000001u)); EXPECT_EQ(32u, CeilLog2Nonzero(0xFFFFFFFFu)); EXPECT_EQ(31u, CeilLog2Nonzero(0x80000000ull)); diff --git a/third_party/jpeg-xl/lib/jxl/blending.cc b/third_party/jpeg-xl/lib/jxl/blending.cc --- a/third_party/jpeg-xl/lib/jxl/blending.cc +++ b/third_party/jpeg-xl/lib/jxl/blending.cc @@ -76,11 +76,11 @@ PerformMulBlending(bg[3 + i] + x0, fg[3 + i] + x0, tmp.Row(3 + i), xsize, ec_blending[i].clamp); } else if (ec_blending[i].mode == PatchBlendMode::kReplace) { memcpy(tmp.Row(3 + i), fg[3 + i] + x0, xsize * sizeof(**fg)); } else if (ec_blending[i].mode == PatchBlendMode::kNone) { - memcpy(tmp.Row(3 + i), bg[3 + i] + x0, xsize * sizeof(**fg)); + if (xsize) memcpy(tmp.Row(3 + i), bg[3 + i] + x0, xsize * sizeof(**fg)); } else { JXL_ABORT("Unreachable"); } } size_t alpha = color_blending.alpha_channel; diff --git a/third_party/jpeg-xl/lib/jxl/butteraugli_wrapper.cc b/third_party/jpeg-xl/lib/jxl/butteraugli_wrapper.cc --- a/third_party/jpeg-xl/lib/jxl/butteraugli_wrapper.cc +++ b/third_party/jpeg-xl/lib/jxl/butteraugli_wrapper.cc @@ -34,26 +34,20 @@ break; case JXL_TYPE_FLOAT16: metadata->SetFloat16Samples(); potential_alpha_bits = 16; break; - case JXL_TYPE_UINT32: - metadata->SetUintSamples(32); - potential_alpha_bits = 16; - break; case JXL_TYPE_UINT16: metadata->SetUintSamples(16); potential_alpha_bits = 16; break; case JXL_TYPE_UINT8: metadata->SetUintSamples(8); potential_alpha_bits = 8; break; - case JXL_TYPE_BOOLEAN: - metadata->SetUintSamples(2); - potential_alpha_bits = 2; - break; + default: + JXL_ABORT("Unhandled JxlDataType"); } if (pixel_format->num_channels == 2 || pixel_format->num_channels == 4) { metadata->SetAlphaBits(potential_alpha_bits); } } @@ -114,13 +108,14 @@ api->intensity_target = v; } void JxlButteraugliApiDestroy(JxlButteraugliApi* api) { if (api) { + JxlMemoryManager local_memory_manager = api->memory_manager; // Call destructor directly since custom free function is used. api->~JxlButteraugliApi(); - jxl::MemoryManagerFree(&api->memory_manager, api); + jxl::MemoryManagerFree(&local_memory_manager, api); } } JxlButteraugliResult* JxlButteraugliCompute( const JxlButteraugliApi* api, uint32_t xsize, uint32_t ysize, @@ -198,10 +193,11 @@ return max_distance; } void JxlButteraugliResultDestroy(JxlButteraugliResult* result) { if (result) { + JxlMemoryManager local_memory_manager = result->memory_manager; // Call destructor directly since custom free function is used. result->~JxlButteraugliResult(); - jxl::MemoryManagerFree(&result->memory_manager, result); + jxl::MemoryManagerFree(&local_memory_manager, result); } } diff --git a/third_party/jpeg-xl/lib/jxl/codec_in_out.h b/third_party/jpeg-xl/lib/jxl/codec_in_out.h --- a/third_party/jpeg-xl/lib/jxl/codec_in_out.h +++ b/third_party/jpeg-xl/lib/jxl/codec_in_out.h @@ -54,14 +54,14 @@ using CodecIntervals = std::array; // RGB[A] or Y[A] // Optional text/EXIF metadata. struct Blobs { - PaddedBytes exif; - PaddedBytes iptc; - PaddedBytes jumbf; - PaddedBytes xmp; + std::vector exif; + std::vector iptc; + std::vector jumbf; + std::vector xmp; }; // Holds a preview, a main image or one or more frames, plus the inputs/outputs // to/from decoding/encoding. class CodecInOut { diff --git a/third_party/jpeg-xl/lib/jxl/common.h b/third_party/jpeg-xl/lib/jxl/common.h --- a/third_party/jpeg-xl/lib/jxl/common.h +++ b/third_party/jpeg-xl/lib/jxl/common.h @@ -15,10 +15,11 @@ #include // numeric_limits #include // unique_ptr #include #include "lib/jxl/base/compiler_specific.h" +#include "lib/jxl/base/padded_bytes.h" #ifndef JXL_HIGH_PRECISION #define JXL_HIGH_PRECISION 1 #endif @@ -190,8 +191,48 @@ // signed snprintf(data, sizeof(data), "%lld", static_cast(n)); } return data; } + +namespace { +static inline uint64_t DecodeVarInt(const uint8_t* input, size_t inputSize, + size_t* pos) { + size_t i; + uint64_t ret = 0; + for (i = 0; *pos + i < inputSize && i < 10; ++i) { + ret |= uint64_t(input[*pos + i] & 127) << uint64_t(7 * i); + // If the next-byte flag is not set, stop + if ((input[*pos + i] & 128) == 0) break; + } + // TODO: Return a decoding error if i == 10. + *pos += i + 1; + return ret; +} + +static inline bool EncodeVarInt(uint64_t value, size_t output_size, + size_t* output_pos, uint8_t* output) { + // While more than 7 bits of data are left, + // store 7 bits and set the next byte flag + while (value > 127) { + if (*output_pos > output_size) return false; + // |128: Set the next byte flag + output[(*output_pos)++] = ((uint8_t)(value & 127)) | 128; + // Remove the seven bits we just wrote + value >>= 7; + } + if (*output_pos > output_size) return false; + output[(*output_pos)++] = ((uint8_t)value) & 127; + return true; +} + +static inline void EncodeVarInt(uint64_t value, PaddedBytes* data) { + size_t pos = data->size(); + data->resize(data->size() + 9); + JXL_CHECK(EncodeVarInt(value, data->size(), &pos, data->data())); + data->resize(pos); +} +} // namespace + } // namespace jxl #endif // LIB_JXL_COMMON_H_ diff --git a/third_party/jpeg-xl/lib/jxl/convolve-inl.h b/third_party/jpeg-xl/lib/jxl/convolve-inl.h --- a/third_party/jpeg-xl/lib/jxl/convolve-inl.h +++ b/third_party/jpeg-xl/lib/jxl/convolve-inl.h @@ -42,11 +42,11 @@ return TableLookupLanes(c, indices); // ONML'KJII #elif HWY_TARGET == HWY_SCALAR return c; // Same (the first mirrored value is the last valid one) #else // 128 bit // c = LKJI -#if HWY_ARCH_X86 +#if HWY_TARGET <= (1 << HWY_HIGHEST_TARGET_BIT_X86) return V{_mm_shuffle_ps(c.raw, c.raw, _MM_SHUFFLE(2, 1, 0, 0))}; // KJII #else const D d; // TODO(deymo): Figure out if this can be optimized using a single vsri // instruction to convert LKJI to KJII. @@ -70,11 +70,11 @@ const D d; JXL_ASSERT(false); // unsupported, avoid calling this. return Zero(d); #else // 128 bit // c = LKJI -#if HWY_ARCH_X86 +#if HWY_TARGET <= (1 << HWY_HIGHEST_TARGET_BIT_X86) return V{_mm_shuffle_ps(c.raw, c.raw, _MM_SHUFFLE(1, 0, 0, 1))}; // JIIJ #else const D d; HWY_ALIGN constexpr int lanes[4] = {1, 0, 0, 1}; // JIIJ const auto indices = SetTableIndices(d, lanes); @@ -96,11 +96,11 @@ const D d; JXL_ASSERT(false); // unsupported, avoid calling this. return Zero(d); #else // 128 bit // c = LKJI -#if HWY_ARCH_X86 +#if HWY_TARGET <= (1 << HWY_HIGHEST_TARGET_BIT_X86) return V{_mm_shuffle_ps(c.raw, c.raw, _MM_SHUFFLE(0, 0, 1, 2))}; // IIJK #else const D d; HWY_ALIGN constexpr int lanes[4] = {2, 1, 0, 0}; // IIJK const auto indices = SetTableIndices(d, lanes); diff --git a/third_party/jpeg-xl/lib/jxl/dec_ans.cc b/third_party/jpeg-xl/lib/jxl/dec_ans.cc --- a/third_party/jpeg-xl/lib/jxl/dec_ans.cc +++ b/third_party/jpeg-xl/lib/jxl/dec_ans.cc @@ -72,13 +72,10 @@ } } else { int is_flat = input->ReadBits(1); if (is_flat == 1) { int alphabet_size = DecodeVarLenUint8(input) + 1; - if (alphabet_size == 0) { - return JXL_FAILURE("Invalid alphabet size for flat histogram."); - } *counts = CreateFlatHistogram(alphabet_size, 1 << precision_bits); return true; } uint32_t shift; diff --git a/third_party/jpeg-xl/lib/jxl/dec_cache.h b/third_party/jpeg-xl/lib/jxl/dec_cache.h --- a/third_party/jpeg-xl/lib/jxl/dec_cache.h +++ b/third_party/jpeg-xl/lib/jxl/dec_cache.h @@ -8,10 +8,11 @@ #include #include // HWY_ALIGN_MAX +#include "jxl/decode.h" #include "lib/jxl/ac_strategy.h" #include "lib/jxl/base/profiler.h" #include "lib/jxl/coeff_order.h" #include "lib/jxl/common.h" #include "lib/jxl/convolve.h" @@ -27,10 +28,35 @@ namespace jxl { constexpr size_t kSigmaBorder = 1; constexpr size_t kSigmaPadding = 2; +struct PixelCallback { + PixelCallback() = default; + PixelCallback(JxlImageOutInitCallback init, JxlImageOutRunCallback run, + JxlImageOutDestroyCallback destroy, void* init_opaque) + : init(init), run(run), destroy(destroy), init_opaque(init_opaque) { +#if JXL_ENABLE_ASSERT + const bool has_init = init != nullptr; + const bool has_run = run != nullptr; + const bool has_destroy = destroy != nullptr; + JXL_ASSERT(has_init == has_run && has_run == has_destroy); +#endif + } + + bool IsPresent() const { return run != nullptr; } + + void* Init(size_t num_threads, size_t num_pixels) const { + return init(init_opaque, num_threads, num_pixels); + } + + JxlImageOutInitCallback init = nullptr; + JxlImageOutRunCallback run = nullptr; + JxlImageOutDestroyCallback destroy = nullptr; + void* init_opaque = nullptr; +}; + // Per-frame decoder state. All the images here should be accessed through a // group rect (either with block units or pixel units). struct PassesDecoderState { PassesSharedState shared_storage; // Allows avoiding copies for encoder loop. @@ -63,11 +89,12 @@ // If true, rgb_output or callback output is RGBA using 4 instead of 3 bytes // per pixel. bool rgb_output_is_rgba; // Callback for line-by-line output. - std::function pixel_callback; + PixelCallback pixel_callback; + // Buffer of upsampling * kApplyImageFeaturesTileDim ones. std::vector opaque_alpha; // One row per thread std::vector> pixel_callback_rows; @@ -104,11 +131,10 @@ std::pow(1 / (1.25f), shared->frame_header.x_qm_scale - 2.0f); b_dm_multiplier = std::pow(1 / (1.25f), shared->frame_header.b_qm_scale - 2.0f); rgb_output = nullptr; - pixel_callback = nullptr; rgb_output_is_rgba = false; fast_xyb_srgb8_conversion = false; used_acs = 0; upsampler8x = GetUpsamplingStage(shared->metadata->transform_data, 0, 3); diff --git a/third_party/jpeg-xl/lib/jxl/dec_cache.cc b/third_party/jpeg-xl/lib/jxl/dec_cache.cc --- a/third_party/jpeg-xl/lib/jxl/dec_cache.cc +++ b/third_party/jpeg-xl/lib/jxl/dec_cache.cc @@ -178,16 +178,16 @@ builder.AddStage(GetSpotColorStage(3 + i, eci.spot_color)); } } } - if (pixel_callback) { + if (pixel_callback.IsPresent()) { builder.AddStage(GetWriteToPixelCallbackStage(pixel_callback, width, height, rgb_output_is_rgba, has_alpha, alpha_c)); } else if (rgb_output) { - builder.AddStage(GetWriteToU8Stage(rgb_output, rgb_stride, width, height, + builder.AddStage(GetWriteToU8Stage(rgb_output, rgb_stride, height, rgb_output_is_rgba, has_alpha, alpha_c)); } else { builder.AddStage(GetWriteToImageBundleStage( decoded, output_encoding_info.color_encoding)); diff --git a/third_party/jpeg-xl/lib/jxl/dec_external_image.h b/third_party/jpeg-xl/lib/jxl/dec_external_image.h --- a/third_party/jpeg-xl/lib/jxl/dec_external_image.h +++ b/third_party/jpeg-xl/lib/jxl/dec_external_image.h @@ -13,49 +13,43 @@ #include "jxl/decode.h" #include "jxl/types.h" #include "lib/jxl/base/status.h" #include "lib/jxl/color_encoding_internal.h" +#include "lib/jxl/dec_cache.h" #include "lib/jxl/image.h" #include "lib/jxl/image_bundle.h" namespace jxl { // Converts ib to interleaved void* pixel buffer with the given format. -// bits_per_sample: must be 8, 16 or 32, and must be 32 if float_out -// is true. 1 and 32 int are not yet implemented. +// bits_per_sample: must be 16 or 32 if float_out is true, and at most 16 +// if it is false. No bit packing is done. // num_channels: must be 1, 2, 3 or 4 for gray, gray+alpha, RGB, RGB+alpha. // This supports the features needed for the C API and does not perform // color space conversion. -// TODO(lode): support 1-bit output (bits_per_sample == 1) // TODO(lode): support rectangle crop. // stride_out is output scanline size in bytes, must be >= // output_xsize * output_bytes_per_pixel. // undo_orientation is an EXIF orientation to undo. Depending on the // orientation, the output xsize and ysize are swapped compared to input // xsize and ysize. Status ConvertToExternal(const jxl::ImageBundle& ib, size_t bits_per_sample, bool float_out, size_t num_channels, JxlEndianness endianness, size_t stride_out, jxl::ThreadPool* thread_pool, void* out_image, - size_t out_size, JxlImageOutCallback out_callback, - void* out_opaque, jxl::Orientation undo_orientation); + size_t out_size, const PixelCallback& out_callback, + jxl::Orientation undo_orientation); // Converts single-channel image to interleaved void* pixel buffer with the // given format, with a single channel. -// bits_per_sample: must be 8, 16 or 32, and must be 32 if float_out -// is true. 1 and 32 int are not yet implemented. // This supports the features needed for the C API to get extra channels. -// stride_out is output scanline size in bytes, must be >= -// output_xsize * output_bytes_per_pixel. -// undo_orientation is an EXIF orientation to undo. Depending on the -// orientation, the output xsize and ysize are swapped compared to input -// xsize and ysize. +// Arguments are similar to the multi-channel function above. Status ConvertToExternal(const jxl::ImageF& channel, size_t bits_per_sample, bool float_out, JxlEndianness endianness, size_t stride_out, jxl::ThreadPool* thread_pool, void* out_image, size_t out_size, - JxlImageOutCallback out_callback, void* out_opaque, + const PixelCallback& out_callback, jxl::Orientation undo_orientation); } // namespace jxl #endif // LIB_JXL_DEC_EXTERNAL_IMAGE_H_ diff --git a/third_party/jpeg-xl/lib/jxl/dec_external_image.cc b/third_party/jpeg-xl/lib/jxl/dec_external_image.cc --- a/third_party/jpeg-xl/lib/jxl/dec_external_image.cc +++ b/third_party/jpeg-xl/lib/jxl/dec_external_image.cc @@ -30,32 +30,13 @@ HWY_BEFORE_NAMESPACE(); namespace jxl { namespace HWY_NAMESPACE { +// TODO(jon): check if this can be replaced by a FloatToU16 function void FloatToU32(const float* in, uint32_t* out, size_t num, float mul, size_t bits_per_sample) { - // TODO(eustas): investigate 24..31 bpp cases. - if (bits_per_sample == 32) { - // Conversion to real 32-bit *unsigned* integers requires more intermediate - // precision that what is given by the usual f32 -> i32 conversion - // instructions, so we run the non-SIMD path for those. - const uint32_t cap = (1ull << bits_per_sample) - 1; - for (size_t x = 0; x < num; x++) { - float v = in[x]; - if (v >= 1.0f) { - out[x] = cap; - } else if (v >= 0.0f) { // Inverted condition => NaN -> 0. - out[x] = static_cast(v * mul + 0.5f); - } else { - out[x] = 0; - } - } - return; - } - - // General SIMD case for less than 32 bits output. const HWY_FULL(float) d; const hwy::HWY_NAMESPACE::Rebind du; // Unpoison accessing partially-uninitialized vectors with memory sanitizer. // This is because we run NearestInt() on the vector, which triggers msan even @@ -265,43 +246,40 @@ Status ConvertChannelsToExternal(const ImageF* channels[], size_t num_channels, size_t bits_per_sample, bool float_out, JxlEndianness endianness, size_t stride, jxl::ThreadPool* pool, void* out_image, size_t out_size, - JxlImageOutCallback out_callback, - void* out_opaque, + const PixelCallback& out_callback, jxl::Orientation undo_orientation) { JXL_DASSERT(num_channels != 0 && num_channels <= kConvertMaxChannels); JXL_DASSERT(channels[0] != nullptr); - - if (bits_per_sample < 1 || bits_per_sample > 32) { - return JXL_FAILURE("Invalid bits_per_sample value."); - } - if (!!out_image == !!out_callback) { + JXL_CHECK(float_out ? bits_per_sample == 16 || bits_per_sample == 32 + : bits_per_sample > 0 && bits_per_sample <= 16); + if (!!out_image == out_callback.IsPresent()) { return JXL_FAILURE( "Must provide either an out_image or an out_callback, but not both."); } - // TODO(deymo): Implement 1-bit per pixel packed in 8 samples per byte. - if (bits_per_sample == 1) { - return JXL_FAILURE("packed 1-bit per sample is not yet supported"); - } - if (bits_per_sample > 16 && bits_per_sample < 32) { - return JXL_FAILURE("not supported, try bits_per_sample=32"); - } - // bytes_per_channel and is only valid for bits_per_sample > 1. const size_t bytes_per_channel = DivCeil(bits_per_sample, jxl::kBitsPerByte); const size_t bytes_per_pixel = num_channels * bytes_per_channel; std::vector> row_out_callback; - auto InitOutCallback = [&](size_t num_threads) { - if (out_callback) { + const auto FreeCallbackOpaque = [&out_callback](void* p) { + out_callback.destroy(p); + }; + std::unique_ptr out_run_opaque( + nullptr, FreeCallbackOpaque); + auto InitOutCallback = [&](size_t num_threads) -> Status { + if (out_callback.IsPresent()) { + out_run_opaque.reset(out_callback.Init(num_threads, stride)); + JXL_RETURN_IF_ERROR(out_run_opaque != nullptr); row_out_callback.resize(num_threads); for (size_t i = 0; i < num_threads; ++i) { row_out_callback[i].resize(stride); } } + return true; }; // Channels used to store the transformed original channels if needed. ImageF temp_channels[kConvertMaxChannels]; if (undo_orientation != Orientation::kIdentity) { @@ -320,11 +298,11 @@ if (stride < bytes_per_pixel * xsize) { return JXL_FAILURE("stride is smaller than scanline width in bytes: %" PRIuS " vs %" PRIuS, stride, bytes_per_pixel * xsize); } - if (!out_callback && + if (!out_callback.IsPresent() && out_size < (ysize - 1) * stride + bytes_per_pixel * xsize) { return JXL_FAILURE("out_size is too small to store image"); } const bool little_endian = @@ -349,12 +327,11 @@ JXL_RETURN_IF_ERROR(RunOnPool( pool, 0, static_cast(ysize), [&](size_t num_threads) { f16_cache = Plane(xsize, num_channels * num_threads); - InitOutCallback(num_threads); - return true; + return InitOutCallback(num_threads); }, [&](const uint32_t task, const size_t thread) { const int64_t y = task; const float* JXL_RESTRICT row_in[kConvertMaxChannels]; for (size_t c = 0; c < num_channels; c++) { @@ -365,11 +342,11 @@ row_f16[c] = f16_cache.Row(c + thread * num_channels); HWY_DYNAMIC_DISPATCH(FloatToF16) (row_in[c], row_f16[c], xsize); } uint8_t* row_out = - out_callback + out_callback.IsPresent() ? row_out_callback[thread].data() : &(reinterpret_cast(out_image))[stride * y]; // interleave the one scanline hwy::float16_t* row_f16_out = reinterpret_cast(row_out); @@ -382,26 +359,24 @@ size_t size = xsize * num_channels * 2; for (size_t i = 0; i < size; i += 2) { std::swap(row_out[i + 0], row_out[i + 1]); } } - if (out_callback) { - (*out_callback)(out_opaque, 0, y, xsize, row_out); + if (out_callback.IsPresent()) { + out_callback.run(out_run_opaque.get(), thread, 0, y, xsize, + row_out); } }, "ConvertF16")); } else if (bits_per_sample == 32) { JXL_RETURN_IF_ERROR(RunOnPool( pool, 0, static_cast(ysize), - [&](size_t num_threads) { - InitOutCallback(num_threads); - return true; - }, + [&](size_t num_threads) { return InitOutCallback(num_threads); }, [&](const uint32_t task, const size_t thread) { const int64_t y = task; uint8_t* row_out = - out_callback + out_callback.IsPresent() ? row_out_callback[thread].data() : &(reinterpret_cast(out_image))[stride * y]; const float* JXL_RESTRICT row_in[kConvertMaxChannels]; for (size_t c = 0; c < num_channels; c++) { row_in[c] = channels[c] ? channels[c]->Row(y) : ones.Row(0); @@ -409,12 +384,13 @@ if (little_endian) { StoreFloatRow(row_in, num_channels, xsize, row_out); } else { StoreFloatRow(row_in, num_channels, xsize, row_out); } - if (out_callback) { - (*out_callback)(out_opaque, 0, y, xsize, row_out); + if (out_callback.IsPresent()) { + out_callback.run(out_run_opaque.get(), thread, 0, y, xsize, + row_out); } }, "ConvertFloat")); } else { return JXL_FAILURE("float other than 16-bit and 32-bit not supported"); @@ -426,17 +402,16 @@ Plane u32_cache; JXL_RETURN_IF_ERROR(RunOnPool( pool, 0, static_cast(ysize), [&](size_t num_threads) { u32_cache = Plane(xsize, num_channels * num_threads); - InitOutCallback(num_threads); - return true; + return InitOutCallback(num_threads); }, [&](const uint32_t task, const size_t thread) { const int64_t y = task; uint8_t* row_out = - out_callback + out_callback.IsPresent() ? row_out_callback[thread].data() : &(reinterpret_cast(out_image))[stride * y]; const float* JXL_RESTRICT row_in[kConvertMaxChannels]; for (size_t c = 0; c < num_channels; c++) { row_in[c] = channels[c] ? channels[c]->Row(y) : ones.Row(0); @@ -448,28 +423,22 @@ // intended to be initialized on a previous run. msan::PoisonMemory(row_u32[c], xsize * sizeof(row_u32[c][0])); HWY_DYNAMIC_DISPATCH(FloatToU32) (row_in[c], row_u32[c], xsize, mul, bits_per_sample); } - // TODO(deymo): add bits_per_sample == 1 case here. if (bits_per_sample <= 8) { StoreUintRow(row_u32, num_channels, xsize, 1, row_out); - } else if (bits_per_sample <= 16) { + } else { if (little_endian) { StoreUintRow(row_u32, num_channels, xsize, 2, row_out); } else { StoreUintRow(row_u32, num_channels, xsize, 2, row_out); } - } else { - if (little_endian) { - StoreUintRow(row_u32, num_channels, xsize, 4, row_out); - } else { - StoreUintRow(row_u32, num_channels, xsize, 4, row_out); - } } - if (out_callback) { - (*out_callback)(out_opaque, 0, y, xsize, row_out); + if (out_callback.IsPresent()) { + out_callback.run(out_run_opaque.get(), thread, 0, y, xsize, + row_out); } }, "ConvertUint")); } return true; @@ -479,12 +448,12 @@ Status ConvertToExternal(const jxl::ImageBundle& ib, size_t bits_per_sample, bool float_out, size_t num_channels, JxlEndianness endianness, size_t stride, jxl::ThreadPool* pool, void* out_image, - size_t out_size, JxlImageOutCallback out_callback, - void* out_opaque, jxl::Orientation undo_orientation) { + size_t out_size, const PixelCallback& out_callback, + jxl::Orientation undo_orientation) { bool want_alpha = num_channels == 2 || num_channels == 4; size_t color_channels = num_channels <= 2 ? 1 : 3; const Image3F* color = &ib.color(); // Undo premultiplied alpha. @@ -510,22 +479,22 @@ } JXL_ASSERT(num_channels == c); return ConvertChannelsToExternal( channels, num_channels, bits_per_sample, float_out, endianness, stride, - pool, out_image, out_size, out_callback, out_opaque, undo_orientation); + pool, out_image, out_size, out_callback, undo_orientation); } Status ConvertToExternal(const jxl::ImageF& channel, size_t bits_per_sample, bool float_out, JxlEndianness endianness, size_t stride, jxl::ThreadPool* pool, void* out_image, - size_t out_size, JxlImageOutCallback out_callback, - void* out_opaque, jxl::Orientation undo_orientation) { + size_t out_size, const PixelCallback& out_callback, + jxl::Orientation undo_orientation) { const ImageF* channels[1]; channels[0] = &channel; - return ConvertChannelsToExternal( - channels, 1, bits_per_sample, float_out, endianness, stride, pool, - out_image, out_size, out_callback, out_opaque, undo_orientation); + return ConvertChannelsToExternal(channels, 1, bits_per_sample, float_out, + endianness, stride, pool, out_image, + out_size, out_callback, undo_orientation); } } // namespace jxl #endif // HWY_ONCE diff --git a/third_party/jpeg-xl/lib/jxl/dec_external_image_gbench.cc b/third_party/jpeg-xl/lib/jxl/dec_external_image_gbench.cc --- a/third_party/jpeg-xl/lib/jxl/dec_external_image_gbench.cc +++ b/third_party/jpeg-xl/lib/jxl/dec_external_image_gbench.cc @@ -36,11 +36,11 @@ ib, /*bits_per_sample=*/8, /*float_out=*/false, num_channels, JXL_NATIVE_ENDIAN, /*stride*/ bytes_per_row, /*thread_pool=*/nullptr, interleaved.data(), interleaved.size(), - /*out_callback=*/nullptr, /*out_opaque=*/nullptr, + /*out_callback=*/{}, /*undo_orientation=*/jxl::Orientation::kIdentity)); } } // Pixels per second. diff --git a/third_party/jpeg-xl/lib/jxl/dec_file.cc b/third_party/jpeg-xl/lib/jxl/dec_file.cc --- a/third_party/jpeg-xl/lib/jxl/dec_file.cc +++ b/third_party/jpeg-xl/lib/jxl/dec_file.cc @@ -64,11 +64,27 @@ Status ret = true; { BitReader reader(file); BitReaderScopedCloser reader_closer(&reader, &ret); - (void)reader.ReadFixedBits<16>(); // skip marker + if (reader.ReadFixedBits<16>() != 0x0AFF) { + // We don't have a naked codestream. Make a quick & dirty attempt to find + // the codestream. + // TODO(jon): get rid of this whole function + const unsigned char* begin = file.data(); + const unsigned char* end = file.data() + file.size() - 4; + while (begin < end) { + if (!memcmp(begin, "jxlc", 4)) break; + begin++; + } + if (begin >= end) return JXL_FAILURE("Couldn't find jxl codestream"); + reader.SkipBits(8 * (begin - file.data() + 2)); + unsigned int firstbytes = reader.ReadFixedBits<16>(); + if (firstbytes != 0x0AFF) + return JXL_FAILURE("Codestream didn't start with FF0A but with %X", + firstbytes); + } { JXL_RETURN_IF_ERROR(DecodeHeaders(&reader, io)); size_t xsize = io->metadata.xsize(); size_t ysize = io->metadata.ysize(); diff --git a/third_party/jpeg-xl/lib/jxl/dec_frame.h b/third_party/jpeg-xl/lib/jxl/dec_frame.h --- a/third_party/jpeg-xl/lib/jxl/dec_frame.h +++ b/third_party/jpeg-xl/lib/jxl/dec_frame.h @@ -6,10 +6,11 @@ #ifndef LIB_JXL_DEC_FRAME_H_ #define LIB_JXL_DEC_FRAME_H_ #include +#include "jxl/decode.h" #include "lib/jxl/base/compiler_specific.h" #include "lib/jxl/base/data_parallel.h" #include "lib/jxl/base/span.h" #include "lib/jxl/base/status.h" #include "lib/jxl/blending.h" @@ -157,11 +158,11 @@ bool is_rgba, bool undo_orientation) const { if (!CanDoLowMemoryPath(undo_orientation)) return; dec_state_->rgb_output = rgb_output; dec_state_->rgb_output_is_rgba = is_rgba; dec_state_->rgb_stride = stride; - JXL_ASSERT(dec_state_->pixel_callback == nullptr); + JXL_ASSERT(!dec_state_->pixel_callback.IsPresent()); #if !JXL_HIGH_PRECISION if (decoded_->metadata()->xyb_encoded && dec_state_->output_encoding_info.color_encoding.IsSRGB() && dec_state_->output_encoding_info.all_default_opsin && HasFastXYBTosRGB8() && frame_header_.needs_color_transform()) { @@ -179,26 +180,24 @@ // @param undo_orientation: if true, indicates the frame decoder should apply // the exif orientation to bring the image to the intended display // orientation. Performing this operation is not yet supported, so this // results in not setting the buffer if the image has a non-identity EXIF // orientation. When outputting to the ImageBundle, no orientation is undone. - void MaybeSetFloatCallback( - const std::function& cb, - bool is_rgba, bool undo_orientation) const { + void MaybeSetFloatCallback(const PixelCallback& pixel_callback, bool is_rgba, + bool undo_orientation) const { if (!CanDoLowMemoryPath(undo_orientation)) return; - dec_state_->pixel_callback = cb; + dec_state_->pixel_callback = pixel_callback; dec_state_->rgb_output_is_rgba = is_rgba; JXL_ASSERT(dec_state_->rgb_output == nullptr); } // Returns true if the rgb output buffer passed by MaybeSetRGB8OutputBuffer // has been/will be populated by Flush() / FinalizeFrame(), or if a pixel // callback has been used. bool HasRGBBuffer() const { return dec_state_->rgb_output != nullptr || - dec_state_->pixel_callback != nullptr; + dec_state_->pixel_callback.IsPresent(); } private: Status ProcessDCGlobal(BitReader* br); Status ProcessDCGroup(size_t dc_group_id, BitReader* br); @@ -214,22 +213,23 @@ // Allocates storage for parallel decoding using up to `num_threads` threads // of up to `num_tasks` tasks. The value of `thread` passed to // `GetStorageLocation` must be smaller than the `num_threads` value passed // here. The value of `task` passed to `GetStorageLocation` must be smaller // than the value of `num_tasks` passed here. - void PrepareStorage(size_t num_threads, size_t num_tasks) { + Status PrepareStorage(size_t num_threads, size_t num_tasks) { size_t storage_size = std::min(num_threads, num_tasks); if (storage_size > group_dec_caches_.size()) { group_dec_caches_.resize(storage_size); } use_task_id_ = num_threads > num_tasks; if (dec_state_->render_pipeline) { - dec_state_->render_pipeline->PrepareForThreads( + JXL_RETURN_IF_ERROR(dec_state_->render_pipeline->PrepareForThreads( storage_size, /*use_group_ids=*/modular_frame_decoder_.UsesFullImage() && - frame_header_.encoding == FrameEncoding::kVarDCT); + frame_header_.encoding == FrameEncoding::kVarDCT)); } + return true; } size_t GetStorageLocation(size_t thread, size_t task) { if (use_task_id_) return task; return thread; @@ -240,22 +240,12 @@ // future frames, there are no spot colors to be rendered, and alpha is not // premultiplied, then low memory options can be used // (uint8 output buffer or float pixel callback). // TODO(veluca): reduce this set of restrictions. bool CanDoLowMemoryPath(bool undo_orientation) const { - if (undo_orientation && - decoded_->metadata()->GetOrientation() != Orientation::kIdentity) { - return false; - } - if (NeedsBlending(dec_state_)) return false; - if (frame_header_.CanBeReferenced()) return false; - if (render_spotcolors_ && - decoded_->metadata()->Find(ExtraChannel::kSpotColor)) { - return false; - } - if (decoded_->AlphaIsPremultiplied()) return false; - return true; + return !(undo_orientation && + decoded_->metadata()->GetOrientation() != Orientation::kIdentity); } PassesDecoderState* dec_state_; ThreadPool* pool_; std::vector section_offsets_; diff --git a/third_party/jpeg-xl/lib/jxl/dec_frame.cc b/third_party/jpeg-xl/lib/jxl/dec_frame.cc --- a/third_party/jpeg-xl/lib/jxl/dec_frame.cc +++ b/third_party/jpeg-xl/lib/jxl/dec_frame.cc @@ -263,10 +263,11 @@ decoded->ClearExtraChannels(); decoded->duration = frame_header_.animation_frame.duration; if (!frame_header_.nonserialized_is_preview && + (frame_header_.is_last || frame_header_.animation_frame.duration > 0) && (frame_header_.frame_type == kRegularFrame || frame_header_.frame_type == kSkipProgressive)) { ++dec_state_->visible_frame_index; dec_state_->nonvisible_frame_index = 0; } else { @@ -554,14 +555,15 @@ Status FrameDecoder::ProcessACGroup(size_t ac_group_id, BitReader* JXL_RESTRICT* br, size_t num_passes, size_t thread, bool force_draw, bool dc_only) { PROFILER_ZONE("process_group"); + size_t group_dim = frame_dim_.group_dim; const size_t gx = ac_group_id % frame_dim_.xsize_groups; const size_t gy = ac_group_id / frame_dim_.xsize_groups; - const size_t x = gx * frame_dim_.group_dim; - const size_t y = gy * frame_dim_.group_dim; + const size_t x = gx * group_dim; + const size_t y = gy * group_dim; RenderPipelineInput render_pipeline_input = dec_state_->render_pipeline->GetInputBuffers(ac_group_id, thread); bool should_run_pipeline = true; @@ -575,11 +577,11 @@ decoded_passes_per_ac_group_[ac_group_id], force_draw, dc_only, &should_run_pipeline)); } // don't limit to image dimensions here (is done in DecodeGroup) - const Rect mrect(x, y, frame_dim_.group_dim, frame_dim_.group_dim); + const Rect mrect(x, y, group_dim, group_dim); for (size_t i = 0; i < frame_header_.passes.num_passes; i++) { int minShift, maxShift; frame_header_.passes.GetDownsamplingBracket(i, minShift, maxShift); if (i >= decoded_passes_per_ac_group_[ac_group_id] && i < decoded_passes_per_ac_group_[ac_group_id] + num_passes) { @@ -610,18 +612,18 @@ for (size_t c = 0; c < 3; c++) { auto r = render_pipeline_input.GetBuffer(noise_c_start + c); rects[c].first = r.first; size_t x1 = r.second.x0() + r.second.xsize(); size_t y1 = r.second.y0() + r.second.ysize(); - rects[c].second = Rect(r.second.x0() + ix * kGroupDim, - r.second.y0() + iy * kGroupDim, kGroupDim, - kGroupDim, x1, y1); + rects[c].second = Rect(r.second.x0() + ix * group_dim, + r.second.y0() + iy * group_dim, group_dim, + group_dim, x1, y1); } Random3Planes(dec_state_->visible_frame_index, dec_state_->nonvisible_frame_index, - (gx * frame_header_.upsampling + ix) * kGroupDim, - (gy * frame_header_.upsampling + iy) * kGroupDim, + (gx * frame_header_.upsampling + ix) * group_dim, + (gy * frame_header_.upsampling + iy) * group_dim, rects[0], rects[1], rects[2]); } } } @@ -790,12 +792,12 @@ } JXL_RETURN_IF_ERROR(RunOnPool( pool_, 0, ac_group_sec.size(), [this](size_t num_threads) { - PrepareStorage(num_threads, decoded_passes_per_ac_group_.size()); - return true; + return PrepareStorage(num_threads, + decoded_passes_per_ac_group_.size()); }, [this, &ac_group_sec, &num_ac_passes, &num, §ions, §ion_status, &has_error](size_t g, size_t thread) { if (num_ac_passes[g] == 0) { // no new AC pass, nothing to do. return; @@ -861,12 +863,12 @@ } std::atomic has_error{false}; JXL_RETURN_IF_ERROR(RunOnPool( pool_, 0, decoded_passes_per_ac_group_.size(), [this](const size_t num_threads) { - PrepareStorage(num_threads, decoded_passes_per_ac_group_.size()); - return true; + return PrepareStorage(num_threads, + decoded_passes_per_ac_group_.size()); }, [this, &has_error](const uint32_t g, size_t thread) { if (decoded_passes_per_ac_group_[g] == frame_header_.passes.num_passes) { // This group was drawn already, nothing to do. diff --git a/third_party/jpeg-xl/lib/jxl/dec_group.cc b/third_party/jpeg-xl/lib/jxl/dec_group.cc --- a/third_party/jpeg-xl/lib/jxl/dec_group.cc +++ b/third_party/jpeg-xl/lib/jxl/dec_group.cc @@ -738,11 +738,11 @@ kRenderPipelineXOffset; } // Arguments set to 0/nullptr are not used. dec_state->upsampler8x->ProcessRow(input_rows, output_rows, /*xextra=*/0, src_rect.xsize(), 0, 0, - nullptr); + thread); } } return true; } diff --git a/third_party/jpeg-xl/lib/jxl/dec_modular.cc b/third_party/jpeg-xl/lib/jxl/dec_modular.cc --- a/third_party/jpeg-xl/lib/jxl/dec_modular.cc +++ b/third_party/jpeg-xl/lib/jxl/dec_modular.cc @@ -87,10 +87,20 @@ namespace jxl { HWY_EXPORT(MultiplySum); // Local function HWY_EXPORT(RgbFromSingle); // Local function HWY_EXPORT(SingleFromSingle); // Local function +// Slow conversion using double precision multiplication, only +// needed when the bit depth is too high for single precision +void SingleFromSingleAccurate(const size_t xsize, + const pixel_type* const JXL_RESTRICT row_in, + const double factor, float* row_out) { + for (size_t x = 0; x < xsize; x++) { + row_out[x] = row_in[x] * factor; + } +} + // convert custom [bits]-bit float (with [exp_bits] exponent bits) stored as int // back to binary32 float void int_to_float(const pixel_type* const JXL_RESTRICT row_in, float* const JXL_RESTRICT row_out, const size_t xsize, const int bits, const int exp_bits) { @@ -455,26 +465,26 @@ RenderPipelineInput& render_pipeline_input, Rect modular_rect) { const auto& frame_header = dec_state->shared->frame_header; const auto* metadata = frame_header.nonserialized_metadata; JXL_CHECK(gi.transform.empty()); - auto get_row = [&](Rect r, size_t c, size_t y) { - return render_pipeline_input.GetBuffer(c).second.Row( - render_pipeline_input.GetBuffer(c).first, y); + auto get_row = [&](size_t c, size_t y) { + const auto& buffer = render_pipeline_input.GetBuffer(c); + return buffer.second.Row(buffer.first, y); }; size_t c = 0; if (do_color) { const bool rgb_from_gray = metadata->m.color_encoding.IsGray() && frame_header.color_transform == ColorTransform::kNone; const bool fp = metadata->m.bit_depth.floating_point_sample && frame_header.color_transform != ColorTransform::kXYB; for (; c < 3; c++) { - float factor = full_image.bitdepth < 32 - ? 1.f / ((1u << full_image.bitdepth) - 1) - : 0; + double factor = full_image.bitdepth < 32 + ? 1.0 / ((1u << full_image.bitdepth) - 1) + : 0; size_t c_in = c; if (frame_header.color_transform == ColorTransform::kXYB) { factor = dec_state->shared->matrices.DCQuants()[c]; // XYB is encoded as YX(B-Y) if (c < 2) c_in = 1 - c; @@ -511,11 +521,11 @@ const size_t y = task; const pixel_type* const JXL_RESTRICT row_in = mr.Row(&ch_in.plane, y); const pixel_type* const JXL_RESTRICT row_in_Y = mr.Row(&gi.channel[0].plane, y); - float* const JXL_RESTRICT row_out = get_row(r, c, y); + float* const JXL_RESTRICT row_out = get_row(c, y); HWY_DYNAMIC_DISPATCH(MultiplySum) (xsize_shifted, row_in, row_in_Y, factor, row_out); }, "ModularIntToFloat")); } else if (fp) { @@ -527,15 +537,15 @@ const size_t y = task; const pixel_type* const JXL_RESTRICT row_in = mr.Row(&ch_in.plane, y); if (rgb_from_gray) { for (size_t cc = 0; cc < 3; cc++) { - float* const JXL_RESTRICT row_out = get_row(r, cc, y); + float* const JXL_RESTRICT row_out = get_row(cc, y); int_to_float(row_in, row_out, xsize_shifted, bits, exp_bits); } } else { - float* const JXL_RESTRICT row_out = get_row(r, c, y); + float* const JXL_RESTRICT row_out = get_row(c, y); int_to_float(row_in, row_out, xsize_shifted, bits, exp_bits); } }, "ModularIntToFloat_losslessfloat")); } else { @@ -544,17 +554,31 @@ [&](const uint32_t task, size_t /* thread */) { const size_t y = task; const pixel_type* const JXL_RESTRICT row_in = mr.Row(&ch_in.plane, y); if (rgb_from_gray) { - HWY_DYNAMIC_DISPATCH(RgbFromSingle) - (xsize_shifted, row_in, factor, get_row(r, 0, y), - get_row(r, 1, y), get_row(r, 2, y)); + if (full_image.bitdepth < 23) { + HWY_DYNAMIC_DISPATCH(RgbFromSingle) + (xsize_shifted, row_in, factor, get_row(0, y), get_row(1, y), + get_row(2, y)); + } else { + SingleFromSingleAccurate(xsize_shifted, row_in, factor, + get_row(0, y)); + SingleFromSingleAccurate(xsize_shifted, row_in, factor, + get_row(1, y)); + SingleFromSingleAccurate(xsize_shifted, row_in, factor, + get_row(2, y)); + } } else { - float* const JXL_RESTRICT row_out = get_row(r, c, y); - HWY_DYNAMIC_DISPATCH(SingleFromSingle) - (xsize_shifted, row_in, factor, row_out); + float* const JXL_RESTRICT row_out = get_row(c, y); + if (full_image.bitdepth < 23) { + HWY_DYNAMIC_DISPATCH(SingleFromSingle) + (xsize_shifted, row_in, factor, row_out); + } else { + SingleFromSingleAccurate(xsize_shifted, row_in, factor, + row_out); + } } }, "ModularIntToFloat")); } if (rgb_from_gray) { @@ -570,11 +594,11 @@ const ExtraChannelInfo& eci = metadata->m.extra_channel_info[ec]; int bits = eci.bit_depth.bits_per_sample; int exp_bits = eci.bit_depth.exponent_bits_per_sample; bool fp = eci.bit_depth.floating_point_sample; JXL_ASSERT(fp || bits < 32); - const float mul = fp ? 0 : (1.0f / ((1u << bits) - 1)); + const double factor = fp ? 0 : (1.0 / ((1u << bits) - 1)); JXL_ASSERT(c < gi.channel.size()); Channel& ch_in = gi.channel[c]; Rect r = render_pipeline_input.GetBuffer(3 + ec).second; Rect mr(modular_rect.x0() >> ch_in.hshift, modular_rect.y0() >> ch_in.vshift, @@ -587,12 +611,15 @@ r.Row(render_pipeline_input.GetBuffer(3 + ec).first, y); const pixel_type* const JXL_RESTRICT row_in = mr.Row(&ch_in.plane, y); if (fp) { int_to_float(row_in, row_out, r.xsize(), bits, exp_bits); } else { - for (size_t x = 0; x < r.xsize(); ++x) { - row_out[x] = row_in[x] * mul; + if (full_image.bitdepth < 23) { + HWY_DYNAMIC_DISPATCH(SingleFromSingle) + (r.xsize(), row_in, factor, row_out); + } else { + SingleFromSingleAccurate(r.xsize(), row_in, factor, row_out); } } } } return true; @@ -619,15 +646,14 @@ std::atomic has_error{false}; JXL_RETURN_IF_ERROR(RunOnPool( pool, 0, dec_state->shared->frame_dim.num_groups, [&](size_t num_threads) { - dec_state->render_pipeline->PrepareForThreads( + return dec_state->render_pipeline->PrepareForThreads( num_threads, /*use_group_ids=*/dec_state->shared->frame_header.encoding == FrameEncoding::kVarDCT); - return true; }, [&](const uint32_t group, size_t thread_id) { RenderPipelineInput input = dec_state->render_pipeline->GetInputBuffers(group, thread_id); if (!ModularImageToDecodedRect(gi, dec_state, nullptr, input, diff --git a/third_party/jpeg-xl/lib/jxl/dec_noise.h b/third_party/jpeg-xl/lib/jxl/dec_noise.h --- a/third_party/jpeg-xl/lib/jxl/dec_noise.h +++ b/third_party/jpeg-xl/lib/jxl/dec_noise.h @@ -18,20 +18,10 @@ #include "lib/jxl/image.h" #include "lib/jxl/noise.h" namespace jxl { -// Add a noise to Opsin image, loading generated random noise from `noise_rect` -// in `noise`. -void AddNoise(const NoiseParams& noise_params, const Rect& noise_rect, - const Image3F& noise, const Rect& opsin_rect, - const ColorCorrelationMap& cmap, Image3F* opsin); - -void RandomImage3(size_t visible_frame_index, size_t nonvisible_frame_index, - size_t x0, size_t y0, const Rect& rect, - Image3F* JXL_RESTRICT noise); - void Random3Planes(size_t visible_frame_index, size_t nonvisible_frame_index, size_t x0, size_t y0, const std::pair& plane0, const std::pair& plane1, const std::pair& plane2); diff --git a/third_party/jpeg-xl/lib/jxl/dec_noise.cc b/third_party/jpeg-xl/lib/jxl/dec_noise.cc --- a/third_party/jpeg-xl/lib/jxl/dec_noise.cc +++ b/third_party/jpeg-xl/lib/jxl/dec_noise.cc @@ -83,186 +83,10 @@ row + x); batch_pos += N; } } } - -// [0, max_value] -template -static HWY_INLINE V Clamp0ToMax(D d, const V x, const V max_value) { - const auto clamped = Min(x, max_value); - return ZeroIfNegative(clamped); -} - -// x is in [0+delta, 1+delta], delta ~= 0.06 -template -typename StrengthEval::V NoiseStrength(const StrengthEval& eval, - const typename StrengthEval::V x) { - return Clamp0ToMax(D(), eval(x), Set(D(), 1.0f)); -} - -// TODO(veluca): SIMD-fy. -class StrengthEvalLut { - public: - using V = Vec; - - explicit StrengthEvalLut(const NoiseParams& noise_params) -#if HWY_TARGET == HWY_SCALAR - : noise_params_(noise_params) -#endif - { -#if HWY_TARGET != HWY_SCALAR - uint32_t lut[8]; - memcpy(lut, noise_params.lut, sizeof(lut)); - for (size_t i = 0; i < 8; i++) { - low16_lut[2 * i] = (lut[i] >> 0) & 0xFF; - low16_lut[2 * i + 1] = (lut[i] >> 8) & 0xFF; - high16_lut[2 * i] = (lut[i] >> 16) & 0xFF; - high16_lut[2 * i + 1] = (lut[i] >> 24) & 0xFF; - } -#endif - } - - V operator()(const V vx) const { - constexpr size_t kScale = NoiseParams::kNumNoisePoints - 2; - auto scaled_vx = Max(Zero(D()), vx * Set(D(), kScale)); - auto floor_x = Floor(scaled_vx); - auto frac_x = scaled_vx - floor_x; - floor_x = IfThenElse(scaled_vx >= Set(D(), kScale + 1), Set(D(), kScale), - floor_x); - frac_x = IfThenElse(scaled_vx >= Set(D(), kScale + 1), Set(D(), 1), frac_x); - auto floor_x_int = ConvertTo(DI(), floor_x); -#if HWY_TARGET == HWY_SCALAR - auto low = Set(D(), noise_params_.lut[floor_x_int.raw]); - auto hi = Set(D(), noise_params_.lut[floor_x_int.raw + 1]); -#else - // Set each lane's bytes to {0, 0, 2x+1, 2x}. - auto floorx_indices_low = - floor_x_int * Set(DI(), 0x0202) + Set(DI(), 0x0100); - // Set each lane's bytes to {2x+1, 2x, 0, 0}. - auto floorx_indices_hi = - floor_x_int * Set(DI(), 0x02020000) + Set(DI(), 0x01000000); - // load LUT - auto low16 = BitCast(DI(), LoadDup128(DI8(), low16_lut)); - auto lowm = Set(DI(), 0xFFFF); - auto hi16 = BitCast(DI(), LoadDup128(DI8(), high16_lut)); - auto him = Set(DI(), 0xFFFF0000); - // low = noise_params.lut[floor_x] - auto low = - BitCast(D(), (TableLookupBytes(low16, floorx_indices_low) & lowm) | - (TableLookupBytes(hi16, floorx_indices_hi) & him)); - // hi = noise_params.lut[floor_x+1] - floorx_indices_low += Set(DI(), 0x0202); - floorx_indices_hi += Set(DI(), 0x02020000); - auto hi = - BitCast(D(), (TableLookupBytes(low16, floorx_indices_low) & lowm) | - (TableLookupBytes(hi16, floorx_indices_hi) & him)); -#endif - return MulAdd(hi - low, frac_x, low); - } - - private: -#if HWY_TARGET != HWY_SCALAR - // noise_params.lut transformed into two 16-bit lookup tables. - HWY_ALIGN uint8_t high16_lut[16]; - HWY_ALIGN uint8_t low16_lut[16]; -#else - const NoiseParams& noise_params_; -#endif -}; - -template -void AddNoiseToRGB(const D d, const Vec rnd_noise_r, - const Vec rnd_noise_g, const Vec rnd_noise_cor, - const Vec noise_strength_g, const Vec noise_strength_r, - float ytox, float ytob, float* JXL_RESTRICT out_x, - float* JXL_RESTRICT out_y, float* JXL_RESTRICT out_b) { - const auto kRGCorr = Set(d, 0.9921875f); // 127/128 - const auto kRGNCorr = Set(d, 0.0078125f); // 1/128 - - const auto red_noise = kRGNCorr * rnd_noise_r * noise_strength_r + - kRGCorr * rnd_noise_cor * noise_strength_r; - const auto green_noise = kRGNCorr * rnd_noise_g * noise_strength_g + - kRGCorr * rnd_noise_cor * noise_strength_g; - - auto vx = Load(d, out_x); - auto vy = Load(d, out_y); - auto vb = Load(d, out_b); - - vx += red_noise - green_noise + Set(d, ytox) * (red_noise + green_noise); - vy += red_noise + green_noise; - vb += Set(d, ytob) * (red_noise + green_noise); - - Store(vx, d, out_x); - Store(vy, d, out_y); - Store(vb, d, out_b); -} - -void AddNoise(const NoiseParams& noise_params, const Rect& noise_rect, - const Image3F& noise, const Rect& opsin_rect, - const ColorCorrelationMap& cmap, Image3F* opsin) { - if (!noise_params.HasAny()) return; - const StrengthEvalLut noise_model(noise_params); - D d; - const auto half = Set(d, 0.5f); - - const size_t xsize = opsin_rect.xsize(); - const size_t ysize = opsin_rect.ysize(); - - // With the prior subtract-random Laplacian approximation, rnd_* ranges were - // about [-1.5, 1.6]; Laplacian3 about doubles this to [-3.6, 3.6], so the - // normalizer is half of what it was before (0.5). - const auto norm_const = Set(d, 0.22f); - - float ytox = cmap.YtoXRatio(0); - float ytob = cmap.YtoBRatio(0); - - const size_t xsize_v = RoundUpTo(xsize, Lanes(d)); - - for (size_t y = 0; y < ysize; ++y) { - float* JXL_RESTRICT row_x = opsin_rect.PlaneRow(opsin, 0, y); - float* JXL_RESTRICT row_y = opsin_rect.PlaneRow(opsin, 1, y); - float* JXL_RESTRICT row_b = opsin_rect.PlaneRow(opsin, 2, y); - const float* JXL_RESTRICT row_rnd_r = noise_rect.ConstPlaneRow(noise, 0, y); - const float* JXL_RESTRICT row_rnd_g = noise_rect.ConstPlaneRow(noise, 1, y); - const float* JXL_RESTRICT row_rnd_c = noise_rect.ConstPlaneRow(noise, 2, y); - // Needed by the calls to Floor() in StrengthEvalLut. Only arithmetic and - // shuffles are otherwise done on the data, so this is safe. - msan::UnpoisonMemory(row_x + xsize, (xsize_v - xsize) * sizeof(float)); - msan::UnpoisonMemory(row_y + xsize, (xsize_v - xsize) * sizeof(float)); - for (size_t x = 0; x < xsize; x += Lanes(d)) { - const auto vx = Load(d, row_x + x); - const auto vy = Load(d, row_y + x); - const auto in_g = vy - vx; - const auto in_r = vy + vx; - const auto noise_strength_g = NoiseStrength(noise_model, in_g * half); - const auto noise_strength_r = NoiseStrength(noise_model, in_r * half); - const auto addit_rnd_noise_red = Load(d, row_rnd_r + x) * norm_const; - const auto addit_rnd_noise_green = Load(d, row_rnd_g + x) * norm_const; - const auto addit_rnd_noise_correlated = - Load(d, row_rnd_c + x) * norm_const; - AddNoiseToRGB(D(), addit_rnd_noise_red, addit_rnd_noise_green, - addit_rnd_noise_correlated, noise_strength_g, - noise_strength_r, ytox, ytob, row_x + x, row_y + x, - row_b + x); - } - msan::PoisonMemory(row_x + xsize, (xsize_v - xsize) * sizeof(float)); - msan::PoisonMemory(row_y + xsize, (xsize_v - xsize) * sizeof(float)); - msan::PoisonMemory(row_b + xsize, (xsize_v - xsize) * sizeof(float)); - } -} - -void RandomImage3(size_t visible_frame_index, size_t nonvisible_frame_index, - size_t x0, size_t y0, const Rect& rect, - Image3F* JXL_RESTRICT noise) { - HWY_ALIGN Xorshift128Plus rng(visible_frame_index, nonvisible_frame_index, x0, - y0); - RandomImage(&rng, rect, &noise->Plane(0)); - RandomImage(&rng, rect, &noise->Plane(1)); - RandomImage(&rng, rect, &noise->Plane(2)); -} - void Random3Planes(size_t visible_frame_index, size_t nonvisible_frame_index, size_t x0, size_t y0, const std::pair& plane0, const std::pair& plane1, const std::pair& plane2) { HWY_ALIGN Xorshift128Plus rng(visible_frame_index, nonvisible_frame_index, x0, @@ -278,26 +102,10 @@ HWY_AFTER_NAMESPACE(); #if HWY_ONCE namespace jxl { -HWY_EXPORT(AddNoise); -void AddNoise(const NoiseParams& noise_params, const Rect& noise_rect, - const Image3F& noise, const Rect& opsin_rect, - const ColorCorrelationMap& cmap, Image3F* opsin) { - return HWY_DYNAMIC_DISPATCH(AddNoise)(noise_params, noise_rect, noise, - opsin_rect, cmap, opsin); -} - -HWY_EXPORT(RandomImage3); -void RandomImage3(size_t visible_frame_index, size_t nonvisible_frame_index, - size_t x0, size_t y0, const Rect& rect, - Image3F* JXL_RESTRICT noise) { - return HWY_DYNAMIC_DISPATCH(RandomImage3)( - visible_frame_index, nonvisible_frame_index, x0, y0, rect, noise); -} - HWY_EXPORT(Random3Planes); void Random3Planes(size_t visible_frame_index, size_t nonvisible_frame_index, size_t x0, size_t y0, const std::pair& plane0, const std::pair& plane1, const std::pair& plane2) { diff --git a/third_party/jpeg-xl/lib/jxl/dec_patch_dictionary.h b/third_party/jpeg-xl/lib/jxl/dec_patch_dictionary.h --- a/third_party/jpeg-xl/lib/jxl/dec_patch_dictionary.h +++ b/third_party/jpeg-xl/lib/jxl/dec_patch_dictionary.h @@ -124,15 +124,10 @@ // Adds patches to a segment of `xsize` pixels, starting at `inout`, assumed // to be located at position (x0, y) in the frame. void AddOneRow(float* const* inout, size_t y, size_t x0, size_t xsize) const; - // Only adds patches that belong to the `image_rect` area of the decoded - // image, writing them to the `opsin_rect` area of `opsin`. - void AddTo(Image3F* opsin, const Rect& opsin_rect, - float* const* extra_channels, const Rect& image_rect) const; - // Returns dependencies of this patch dictionary on reference frame ids as a // bit mask: bits 0-3 indicate reference frame 0-3. int GetReferences() const; private: diff --git a/third_party/jpeg-xl/lib/jxl/dec_patch_dictionary.cc b/third_party/jpeg-xl/lib/jxl/dec_patch_dictionary.cc --- a/third_party/jpeg-xl/lib/jxl/dec_patch_dictionary.cc +++ b/third_party/jpeg-xl/lib/jxl/dec_patch_dictionary.cc @@ -154,13 +154,10 @@ positions_.shrink_to_fit(); if (!decoder.CheckANSFinalState()) { return JXL_FAILURE("ANS checksum failure."); } - if (!HasAny()) { - return JXL_FAILURE("Decoded patch dictionary but got none"); - } ComputePatchCache(); return true; } @@ -235,53 +232,6 @@ patch_x1 - patch_x0, pos.blending[0], pos.blending.data() + 1, shared_->metadata->m.extra_channel_info); } } - -void PatchDictionary::AddTo(Image3F* opsin, const Rect& opsin_rect, - float* const* extra_channels, - const Rect& image_rect) const { - JXL_CHECK(SameSize(opsin_rect, image_rect)); - if (patch_starts_.empty()) return; - size_t num_ec = shared_->metadata->m.num_extra_channels; - std::vector fg_ptrs(3 + num_ec); - std::vector bg_ptrs(3 + num_ec); - for (size_t y = image_rect.y0(); y < image_rect.y0() + image_rect.ysize(); - y++) { - if (y + 1 >= patch_starts_.size()) continue; - for (size_t id = patch_starts_[y]; id < patch_starts_[y + 1]; id++) { - const PatchPosition& pos = positions_[sorted_patches_[id]]; - size_t by = pos.y; - size_t bx = pos.x; - size_t xsize = pos.ref_pos.xsize; - JXL_DASSERT(y >= by); - JXL_DASSERT(y < by + pos.ref_pos.ysize); - size_t iy = y - by; - size_t ref = pos.ref_pos.ref; - if (bx >= image_rect.x0() + image_rect.xsize()) continue; - if (bx + xsize < image_rect.x0()) continue; - size_t x0 = std::max(bx, image_rect.x0()); - size_t x1 = std::min(bx + xsize, image_rect.x0() + image_rect.xsize()); - for (size_t c = 0; c < 3; c++) { - fg_ptrs[c] = - shared_->reference_frames[ref].frame->color()->ConstPlaneRow( - c, pos.ref_pos.y0 + iy) + - pos.ref_pos.x0 + x0 - bx; - bg_ptrs[c] = opsin_rect.PlaneRow(opsin, c, y - image_rect.y0()) + x0 - - image_rect.x0(); - } - for (size_t i = 0; i < num_ec; i++) { - fg_ptrs[3 + i] = - shared_->reference_frames[ref].frame->extra_channels()[i].ConstRow( - pos.ref_pos.y0 + iy) + - pos.ref_pos.x0 + x0 - bx; - bg_ptrs[3 + i] = extra_channels[i] + x0 - image_rect.x0(); - } - PerformBlending(bg_ptrs.data(), fg_ptrs.data(), bg_ptrs.data(), 0, - x1 - x0, pos.blending[0], pos.blending.data() + 1, - shared_->metadata->m.extra_channel_info); - } - } -} - } // namespace jxl diff --git a/third_party/jpeg-xl/lib/jxl/decode.cc b/third_party/jpeg-xl/lib/jxl/decode.cc --- a/third_party/jpeg-xl/lib/jxl/decode.cc +++ b/third_party/jpeg-xl/lib/jxl/decode.cc @@ -137,25 +137,21 @@ namespace { size_t BitsPerChannel(JxlDataType data_type) { switch (data_type) { - case JXL_TYPE_BOOLEAN: - return 1; case JXL_TYPE_UINT8: return 8; case JXL_TYPE_UINT16: return 16; - case JXL_TYPE_UINT32: - return 32; case JXL_TYPE_FLOAT: return 32; case JXL_TYPE_FLOAT16: return 16; - // No default, give compiler error if new type not handled. + default: + return 0; // signals unhandled JxlDataType } - return 0; // Indicate invalid data type. } enum class DecoderStage : uint32_t { kInited, // Decoder created, no JxlDecoderProcessInput called yet kStarted, // Running JxlDecoderProcessInput calls @@ -463,12 +459,19 @@ bool image_out_buffer_set; // Owned by the caller, buffers for DC image and full resolution images void* preview_out_buffer; void* image_out_buffer; - JxlImageOutCallback image_out_callback; - void* image_out_opaque; + JxlImageOutInitCallback image_out_init_callback; + JxlImageOutRunCallback image_out_run_callback; + JxlImageOutDestroyCallback image_out_destroy_callback; + void* image_out_init_opaque; + struct SimpleImageOutCallback { + JxlImageOutCallback callback; + void* opaque; + }; + SimpleImageOutCallback simple_image_out_callback; size_t preview_out_size; size_t image_out_size; JxlPixelFormat preview_out_format; @@ -607,10 +610,15 @@ namespace { bool CheckSizeLimit(JxlDecoder* dec, size_t xsize, size_t ysize) { if (!dec->memory_limit_base) return true; if (xsize == 0 || ysize == 0) return true; + if (xsize >= dec->memory_limit_base || ysize >= dec->memory_limit_base) { + return false; + } + // Rough estimate of real row length. + xsize = jxl::DivCeil(xsize, 32) * 32; size_t num_pixels = xsize * ysize; if (num_pixels / xsize != ysize) return false; // overflow if (num_pixels > dec->memory_limit_base) return false; return true; } @@ -668,12 +676,14 @@ dec->box_count = 0; dec->preview_out_buffer_set = false; dec->image_out_buffer_set = false; dec->preview_out_buffer = nullptr; dec->image_out_buffer = nullptr; - dec->image_out_callback = nullptr; - dec->image_out_opaque = nullptr; + dec->image_out_init_callback = nullptr; + dec->image_out_run_callback = nullptr; + dec->image_out_destroy_callback = nullptr; + dec->image_out_init_opaque = nullptr; dec->preview_out_size = 0; dec->image_out_size = 0; dec->extra_channel_output.clear(); dec->dec_pixels = 0; dec->next_in = 0; @@ -729,11 +739,11 @@ JxlDecoder* dec = new (alloc) JxlDecoder(); dec->memory_manager = local_memory_manager; #ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION if (!memory_manager) { - dec->memory_limit_base = 1 << 21; + dec->memory_limit_base = 53 << 16; // Allow 5 x max_image_size processing units; every frame is accounted // as W x H CPU processing units, so there could be numerous small frames // or few larger ones. dec->cpu_limit_base = 5 * dec->memory_limit_base; } @@ -744,13 +754,14 @@ return dec; } void JxlDecoderDestroy(JxlDecoder* dec) { if (dec) { + JxlMemoryManager local_memory_manager = dec->memory_manager; // Call destructor directly since custom free function is used. dec->~JxlDecoder(); - jxl::MemoryManagerFree(&dec->memory_manager, dec); + jxl::MemoryManagerFree(&local_memory_manager, dec); } } void JxlDecoderRewind(JxlDecoder* dec) { JxlDecoderRewindDecodingState(dec); } @@ -1049,11 +1060,11 @@ // and treated as if it is 1. static JxlDecoderStatus ConvertImageInternal( const JxlDecoder* dec, const jxl::ImageBundle& frame, const JxlPixelFormat& format, bool want_extra_channel, size_t extra_channel_index, void* out_image, size_t out_size, - JxlImageOutCallback out_callback, void* out_opaque) { + const PixelCallback& out_callback) { // TODO(lode): handle mismatch of RGB/grayscale color profiles and pixel data // color/grayscale format const size_t stride = GetStride(dec, format); bool float_format = format.data_type == JXL_TYPE_FLOAT || @@ -1063,23 +1074,21 @@ ? jxl::Orientation::kIdentity : dec->metadata.m.GetOrientation(); jxl::Status status(true); if (want_extra_channel) { - status = jxl::ConvertToExternal( - frame.extra_channels()[extra_channel_index], - BitsPerChannel(format.data_type), float_format, format.endianness, - stride, dec->thread_pool.get(), out_image, out_size, - /*out_callback=*/out_callback, - /*out_opaque=*/out_opaque, undo_orientation); + JXL_ASSERT(extra_channel_index < frame.extra_channels().size()); + status = jxl::ConvertToExternal(frame.extra_channels()[extra_channel_index], + BitsPerChannel(format.data_type), + float_format, format.endianness, stride, + dec->thread_pool.get(), out_image, out_size, + out_callback, undo_orientation); } else { status = jxl::ConvertToExternal( frame, BitsPerChannel(format.data_type), float_format, format.num_channels, format.endianness, stride, dec->thread_pool.get(), - out_image, out_size, - /*out_callback=*/out_callback, - /*out_opaque=*/out_opaque, undo_orientation); + out_image, out_size, out_callback, undo_orientation); } return status ? JXL_DEC_SUCCESS : JXL_DEC_ERROR; } @@ -1250,12 +1259,12 @@ if (want_preview) { if (dec->preview_out_buffer) { JxlDecoderStatus status = ConvertImageInternal( dec, ib, dec->preview_out_format, /*want_extra_channel=*/false, /*extra_channel_index=*/0, dec->preview_out_buffer, - dec->preview_out_size, /*out_callback=*/nullptr, - /*out_opaque=*/nullptr); + dec->preview_out_size, + /*out_callback=*/{}); if (status != JXL_DEC_SUCCESS) return status; } return JXL_DEC_PREVIEW_IMAGE; } } @@ -1451,21 +1460,20 @@ IsLittleEndian()); bool swap_endianness = little_endian != IsLittleEndian(); // TODO(lode): Support more formats than just native endian float32 for // the low-memory callback path - if (dec->image_out_buffer_set && !!dec->image_out_callback && + if (dec->image_out_buffer_set && !!dec->image_out_init_callback && + !!dec->image_out_run_callback && dec->image_out_format.data_type == JXL_TYPE_FLOAT && dec->image_out_format.num_channels >= 3 && !swap_endianness && dec->frame_dec_in_progress) { bool is_rgba = dec->image_out_format.num_channels == 4; dec->frame_dec->MaybeSetFloatCallback( - [dec](const float* pixels, size_t x, size_t y, size_t num_pixels) { - JXL_DASSERT(num_pixels > 0); - dec->image_out_callback(dec->image_out_opaque, x, y, num_pixels, - pixels); - }, + PixelCallback{ + dec->image_out_init_callback, dec->image_out_run_callback, + dec->image_out_destroy_callback, dec->image_out_init_opaque}, is_rgba, !dec->keep_orientation); } size_t pos = dec->frame_start - dec->codestream_pos; if (pos >= size) { @@ -1555,25 +1563,34 @@ // Copy pixels if desired. JxlDecoderStatus status = ConvertImageInternal( dec, *dec->ib, dec->image_out_format, /*want_extra_channel=*/false, /*extra_channel_index=*/0, dec->image_out_buffer, - dec->image_out_size, dec->image_out_callback, - dec->image_out_opaque); + dec->image_out_size, + PixelCallback{dec->image_out_init_callback, + dec->image_out_run_callback, + dec->image_out_destroy_callback, + dec->image_out_init_opaque}); if (status != JXL_DEC_SUCCESS) return status; } dec->image_out_buffer_set = false; + bool has_ec = !dec->ib->extra_channels().empty(); for (size_t i = 0; i < dec->extra_channel_output.size(); ++i) { void* buffer = dec->extra_channel_output[i].buffer; // buffer nullptr indicates this extra channel is not requested if (!buffer) continue; + if (!has_ec) { + JXL_WARNING( + "Extra channels are not supported when callback is used"); + return JXL_DEC_ERROR; + } const JxlPixelFormat* format = &dec->extra_channel_output[i].format; JxlDecoderStatus status = ConvertImageInternal( dec, *dec->ib, *format, - /*want_extra_channel=*/true, i, buffer, - dec->extra_channel_output[i].buffer_size, nullptr, nullptr); + /*want_extra_channel=*/true, /*extra_channel_index=*/i, buffer, + dec->extra_channel_output[i].buffer_size, /*out_callback=*/{}); if (status != JXL_DEC_SUCCESS) return status; } dec->extra_channel_output.clear(); } @@ -2383,21 +2400,15 @@ return JXL_API_ERROR("Don't know frame dimensions yet"); } if (format->num_channels > 4) { return JXL_API_ERROR("More than 4 channels not supported"); } - if (format->data_type == JXL_TYPE_BOOLEAN) { - return JXL_API_ERROR("Boolean data type not yet supported"); - } - if (format->data_type == JXL_TYPE_UINT32) { - return JXL_API_ERROR("uint32 data type not yet supported"); - } *bits = BitsPerChannel(format->data_type); if (*bits == 0) { - return JXL_API_ERROR("Invalid data type"); + return JXL_API_ERROR("Invalid/unsupported data type"); } return JXL_DEC_SUCCESS; } @@ -2438,11 +2449,11 @@ dec->ib->ShrinkTo(xsize_nopadding, ysize_nopadding); JxlDecoderStatus status = jxl::ConvertImageInternal( dec, *dec->ib, dec->image_out_format, /*want_extra_channel=*/false, /*extra_channel_index=*/0, dec->image_out_buffer, dec->image_out_size, - /*out_callback=*/nullptr, /*out_opaque=*/nullptr); + /*out_callback=*/{}); dec->ib->ShrinkTo(xsize, ysize); if (status != JXL_DEC_SUCCESS) return status; return JXL_DEC_SUCCESS; } @@ -2546,11 +2557,11 @@ const JxlPixelFormat* format, void* buffer, size_t size) { if (!dec->got_basic_info || !(dec->orig_events_wanted & JXL_DEC_FULL_IMAGE)) { return JXL_API_ERROR("No image out buffer needed at this time"); } - if (dec->image_out_buffer_set && !!dec->image_out_callback) { + if (dec->image_out_buffer_set && !!dec->image_out_run_callback) { return JXL_API_ERROR( "Cannot change from image out callback to image out buffer"); } if (format->num_channels < 3 && !dec->metadata.m.color_encoding.IsGray()) { return JXL_API_ERROR("Grayscale output not possible for color image"); @@ -2632,23 +2643,55 @@ JxlDecoderStatus JxlDecoderSetImageOutCallback(JxlDecoder* dec, const JxlPixelFormat* format, JxlImageOutCallback callback, void* opaque) { + dec->simple_image_out_callback.callback = callback; + dec->simple_image_out_callback.opaque = opaque; + const auto init_callback = + +[](void* init_opaque, size_t num_threads, size_t num_pixels_per_thread) { + // No initialization to do, just reuse init_opaque as run_opaque. + return init_opaque; + }; + const auto run_callback = + +[](void* run_opaque, size_t thread_id, size_t x, size_t y, + size_t num_pixels, const void* pixels) { + const auto* const simple_callback = + static_cast(run_opaque); + simple_callback->callback(simple_callback->opaque, x, y, num_pixels, + pixels); + }; + const auto destroy_callback = +[](void* run_opaque) {}; + return JxlDecoderSetMultithreadedImageOutCallback( + dec, format, init_callback, run_callback, + /*destroy_callback=*/destroy_callback, &dec->simple_image_out_callback); +} + +JxlDecoderStatus JxlDecoderSetMultithreadedImageOutCallback( + JxlDecoder* dec, const JxlPixelFormat* format, + JxlImageOutInitCallback init_callback, JxlImageOutRunCallback run_callback, + JxlImageOutDestroyCallback destroy_callback, void* init_opaque) { if (dec->image_out_buffer_set && !!dec->image_out_buffer) { return JXL_API_ERROR( "Cannot change from image out buffer to image out callback"); } + if (init_callback == nullptr || run_callback == nullptr || + destroy_callback == nullptr) { + return JXL_API_ERROR("All callbacks are required"); + } + // Perform error checking for invalid format. size_t bits_dummy; JxlDecoderStatus status = PrepareSizeCheck(dec, format, &bits_dummy); if (status != JXL_DEC_SUCCESS) return status; dec->image_out_buffer_set = true; - dec->image_out_callback = callback; - dec->image_out_opaque = opaque; + dec->image_out_init_callback = init_callback; + dec->image_out_run_callback = run_callback; + dec->image_out_destroy_callback = destroy_callback; + dec->image_out_init_opaque = init_opaque; dec->image_out_format = *format; return JXL_DEC_SUCCESS; } diff --git a/third_party/jpeg-xl/lib/jxl/decode_test.cc b/third_party/jpeg-xl/lib/jxl/decode_test.cc --- a/third_party/jpeg-xl/lib/jxl/decode_test.cc +++ b/third_party/jpeg-xl/lib/jxl/decode_test.cc @@ -209,11 +209,11 @@ &pool, &jpeg_bytes)); jpeg_codestream->append(jpeg_bytes.data(), jpeg_bytes.data() + jpeg_bytes.size()); EXPECT_TRUE(jxl::jpeg::DecodeImageJPG( jxl::Span(jpeg_bytes.data(), jpeg_bytes.size()), &io)); - EXPECT_TRUE(EncodeJPEGData(*io.Main().jpeg_data, &jpeg_data)); + EXPECT_TRUE(EncodeJPEGData(*io.Main().jpeg_data, &jpeg_data, cparams)); io.metadata.m.xyb_encoded = false; #else // JPEGXL_ENABLE_JPEG JXL_ABORT( "unable to create reconstructible JPEG without JPEG support enabled"); #endif // JPEGXL_ENABLE_JPEG @@ -1232,11 +1232,12 @@ for (size_t i = 0; i < pixels.size(); i++) pixels[i] = 0; EXPECT_TRUE(ConvertToExternal( io.Main(), 16, /*float_out=*/false, orig_channels, JXL_BIG_ENDIAN, xsize * 2 * orig_channels, nullptr, pixels.data(), pixels.size(), - nullptr, nullptr, static_cast(config.orientation))); + /*out_callback=*/{}, + static_cast(config.orientation))); } if (config.upsampling == 1) { EXPECT_EQ(0u, jxl::test::ComparePixels(pixels.data(), pixels2.data(), xsize, ysize, format_orig, format)); } else { @@ -1404,16 +1405,12 @@ os << "f32"; break; case JXL_TYPE_FLOAT16: os << "f16"; break; - case JXL_TYPE_UINT32: - os << "u32"; - break; - case JXL_TYPE_BOOLEAN: - os << "b"; - break; + default: + JXL_ASSERT(false); }; if (jxl::test::GetDataBits(c.data_type) > jxl::kBitsPerByte) { if (c.endianness == JXL_NATIVE_ENDIAN) { // add nothing } else if (c.endianness == JXL_BIG_ENDIAN) { @@ -3566,11 +3563,11 @@ } #endif // JPEGXL_ENABLE_JPEG TEST(DecodeTest, JXL_TRANSCODE_JPEG_TEST(JPEGReconstructionTest)) { const std::string jpeg_path = - "imagecompression.info/flower_foveon.png.im_q85_420.jpg"; + "third_party/imagecompression.info/flower_foveon.png.im_q85_420.jpg"; const jxl::PaddedBytes orig = jxl::ReadTestData(jpeg_path); jxl::CodecInOut orig_io; ASSERT_TRUE( jxl::jpeg::DecodeImageJPG(jxl::Span(orig), &orig_io)); orig_io.metadata.m.xyb_encoded = false; @@ -3584,11 +3581,12 @@ orig_io.Main(), &enc_state, jxl::GetJxlCms(), /*pool=*/nullptr, &writer, /*aux_out=*/nullptr)); jxl::PaddedBytes jpeg_data; - ASSERT_TRUE(EncodeJPEGData(*orig_io.Main().jpeg_data.get(), &jpeg_data)); + ASSERT_TRUE( + EncodeJPEGData(*orig_io.Main().jpeg_data.get(), &jpeg_data, cparams)); jxl::PaddedBytes container; container.append(jxl::kContainerHeader, jxl::kContainerHeader + sizeof(jxl::kContainerHeader)); jxl::AppendBoxHeader(jxl::MakeBoxType("jbrd"), jpeg_data.size(), false, &container); @@ -4098,11 +4096,11 @@ jxl::CompressParams cparams; cparams.speed_tier = jxl::SpeedTier::kLightning; cparams.modular_mode = true; cparams.color_transform = jxl::ColorTransform::kNone; - cparams.quality_pair = {100, 100}; + cparams.butteraugli_distance = 0.f; jxl::PaddedBytes compressed; std::unique_ptr enc_state = jxl::make_unique(); EXPECT_TRUE(jxl::EncodeFile(cparams, &io, enc_state.get(), &compressed, diff --git a/third_party/jpeg-xl/lib/jxl/enc_adaptive_quantization.cc b/third_party/jpeg-xl/lib/jxl/enc_adaptive_quantization.cc --- a/third_party/jpeg-xl/lib/jxl/enc_adaptive_quantization.cc +++ b/third_party/jpeg-xl/lib/jxl/enc_adaptive_quantization.cc @@ -1100,13 +1100,14 @@ const ImageMetadata& metadata = *decoded.metadata(); JXL_CHECK(dec_state->PreparePipeline(&decoded, options)); hwy::AlignedUniquePtr group_dec_caches; - const auto allocate_storage = [&](const size_t num_threads) { - dec_state->render_pipeline->PrepareForThreads(num_threads, - /*use_group_ids=*/false); + const auto allocate_storage = [&](const size_t num_threads) -> Status { + JXL_RETURN_IF_ERROR( + dec_state->render_pipeline->PrepareForThreads(num_threads, + /*use_group_ids=*/false)); group_dec_caches = hwy::MakeUniqueAlignedArray(num_threads); return true; }; const auto process_group = [&](const uint32_t group_index, const size_t thread) { diff --git a/third_party/jpeg-xl/lib/jxl/enc_cache.cc b/third_party/jpeg-xl/lib/jxl/enc_cache.cc --- a/third_party/jpeg-xl/lib/jxl/enc_cache.cc +++ b/third_party/jpeg-xl/lib/jxl/enc_cache.cc @@ -96,12 +96,14 @@ cparams.gaborish = Override::kOff; // Use kVarDCT in max_error_mode for intermediate progressive DC, // and kModular for the smallest DC (first in the bitstream) if (cparams.progressive_dc == 0) { cparams.modular_mode = true; - cparams.quality_pair.first = cparams.quality_pair.second = - 99.f - enc_state->cparams.butteraugli_distance * 0.2f; + // TODO(jon): tweak mapping from image dist to dist for modular DC + cparams.butteraugli_distance = + std::max(kMinButteraugliDistance, + enc_state->cparams.butteraugli_distance * 0.03f); } ImageBundle ib(&shared.metadata->m); // This is a lie - dc is in XYB // (but EncodeFrame will skip RGB->XYB conversion anyway) ib.SetFromImage( diff --git a/third_party/jpeg-xl/lib/jxl/enc_external_image.cc b/third_party/jpeg-xl/lib/jxl/enc_external_image.cc --- a/third_party/jpeg-xl/lib/jxl/enc_external_image.cc +++ b/third_party/jpeg-xl/lib/jxl/enc_external_image.cc @@ -84,11 +84,10 @@ uint32_t JXL_INLINE Load8(const uint8_t* p) { return *p; } Status PixelFormatToExternal(const JxlPixelFormat& pixel_format, size_t* bitdepth, bool* float_in) { - // TODO(zond): Make this accept uint32. if (pixel_format.data_type == JXL_TYPE_FLOAT) { *bitdepth = 32; *float_in = true; } else if (pixel_format.data_type == JXL_TYPE_FLOAT16) { *bitdepth = 16; @@ -98,35 +97,24 @@ *float_in = false; } else if (pixel_format.data_type == JXL_TYPE_UINT16) { *bitdepth = 16; *float_in = false; } else { - return JXL_FAILURE("unsupported bitdepth"); + return JXL_FAILURE("unsupported pixel format data type"); } return true; } } // namespace Status ConvertFromExternal(Span bytes, size_t xsize, size_t ysize, size_t bits_per_sample, JxlEndianness endianness, ThreadPool* pool, ImageF* channel, bool float_in, size_t align) { // TODO(firsching): Avoid code duplication with the function below. - if (bits_per_sample < 1 || bits_per_sample > 32) { - return JXL_FAILURE("Invalid bits_per_sample value."); - } - // TODO(deymo): Implement 1-bit per sample as 8 samples per byte. In - // any other case we use DivCeil(bits_per_sample, 8) bytes per pixel per - // channel. - if (bits_per_sample == 1) { - return JXL_FAILURE("packed 1-bit per sample is not yet supported"); - } - - // bytes_per_pixel are only valid for - // bits_per_sample > 1. + JXL_CHECK(float_in ? bits_per_sample == 16 || bits_per_sample == 32 + : bits_per_sample > 0 && bits_per_sample <= 16); const size_t bytes_per_pixel = DivCeil(bits_per_sample, jxl::kBitsPerByte); - const size_t last_row_size = xsize * bytes_per_pixel; const size_t row_size = (align > 1 ? jxl::DivCeil(last_row_size, align) * align : last_row_size); const size_t bytes_to_read = row_size * (ysize - 1) + last_row_size; if (xsize == 0 || ysize == 0) return JXL_FAILURE("Empty image"); @@ -151,11 +139,11 @@ pool, 0, static_cast(ysize), ThreadPool::NoInit, [&](const uint32_t task, size_t /*thread*/) { const size_t y = task; size_t i = row_size * task; float* JXL_RESTRICT row_out = channel->Row(y); - if (bits_per_sample <= 16) { + if (bits_per_sample == 16) { if (little_endian) { for (size_t x = 0; x < xsize; ++x) { row_out[x] = LoadLEFloat16(in + i); i += bytes_per_pixel; } @@ -186,30 +174,20 @@ pool, 0, static_cast(ysize), ThreadPool::NoInit, [&](const uint32_t task, size_t /*thread*/) { const size_t y = task; size_t i = row_size * task; float* JXL_RESTRICT row_out = channel->Row(y); - // TODO(deymo): add bits_per_sample == 1 case here. Also maybe - // implement masking if bits_per_sample is not a multiple of 8. if (bits_per_sample <= 8) { LoadFloatRow(row_out, in + i, mul, xsize, bytes_per_pixel); - } else if (bits_per_sample <= 16) { + } else { if (little_endian) { LoadFloatRow(row_out, in + i, mul, xsize, bytes_per_pixel); } else { LoadFloatRow(row_out, in + i, mul, xsize, bytes_per_pixel); } - } else { - if (little_endian) { - LoadFloatRow(row_out, in + i, mul, xsize, - bytes_per_pixel); - } else { - LoadFloatRow(row_out, in + i, mul, xsize, - bytes_per_pixel); - } } }, "ConvertExtraChannelUint")); } @@ -219,30 +197,21 @@ size_t ysize, const ColorEncoding& c_current, size_t channels, bool alpha_is_premultiplied, size_t bits_per_sample, JxlEndianness endianness, bool flipped_y, ThreadPool* pool, ImageBundle* ib, bool float_in, size_t align) { - if (bits_per_sample < 1 || bits_per_sample > 32) { - return JXL_FAILURE("Invalid bits_per_sample value."); - } - // TODO(deymo): Implement 1-bit per sample as 8 samples per byte. In - // any other case we use DivCeil(bits_per_sample, 8) bytes per pixel per - // channel. - if (bits_per_sample == 1) { - return JXL_FAILURE("packed 1-bit per sample is not yet supported"); - } + JXL_CHECK(float_in ? bits_per_sample == 16 || bits_per_sample == 32 + : bits_per_sample > 0 && bits_per_sample <= 16); const size_t color_channels = c_current.Channels(); bool has_alpha = channels == 2 || channels == 4; if (channels < color_channels) { return JXL_FAILURE("Expected %" PRIuS " color channels, received only %" PRIuS " channels", color_channels, channels); } - // bytes_per_channel and bytes_per_pixel are only valid for - // bits_per_sample > 1. const size_t bytes_per_channel = DivCeil(bits_per_sample, jxl::kBitsPerByte); const size_t bytes_per_pixel = channels * bytes_per_channel; if (bits_per_sample > 16 && bits_per_sample < 32) { return JXL_FAILURE("not supported, try bits_per_sample=32"); } @@ -285,11 +254,11 @@ [&](const uint32_t task, size_t /*thread*/) { const size_t y = get_y(task); size_t i = row_size * task + (c * bits_per_sample / jxl::kBitsPerByte); float* JXL_RESTRICT row_out = color.PlaneRow(c, y); - if (bits_per_sample <= 16) { + if (bits_per_sample == 16) { if (little_endian) { for (size_t x = 0; x < xsize; ++x) { row_out[x] = LoadLEFloat16(in + i); i += bytes_per_pixel; } @@ -323,30 +292,20 @@ pool, 0, static_cast(ysize), ThreadPool::NoInit, [&](const uint32_t task, size_t /*thread*/) { const size_t y = get_y(task); size_t i = row_size * task + c * bytes_per_channel; float* JXL_RESTRICT row_out = color.PlaneRow(c, y); - // TODO(deymo): add bits_per_sample == 1 case here. Also maybe - // implement masking if bits_per_sample is not a multiple of 8. if (bits_per_sample <= 8) { LoadFloatRow(row_out, in + i, mul, xsize, bytes_per_pixel); - } else if (bits_per_sample <= 16) { + } else { if (little_endian) { LoadFloatRow(row_out, in + i, mul, xsize, bytes_per_pixel); } else { LoadFloatRow(row_out, in + i, mul, xsize, bytes_per_pixel); } - } else { - if (little_endian) { - LoadFloatRow(row_out, in + i, mul, xsize, - bytes_per_pixel); - } else { - LoadFloatRow(row_out, in + i, mul, xsize, - bytes_per_pixel); - } } }, "ConvertRGBUint")); } } @@ -369,11 +328,11 @@ [&](const uint32_t task, size_t /*thread*/) { const size_t y = get_y(task); size_t i = row_size * task + ((channels - 1) * bits_per_sample / jxl::kBitsPerByte); float* JXL_RESTRICT row_out = alpha.Row(y); - if (bits_per_sample <= 16) { + if (bits_per_sample == 16) { if (little_endian) { for (size_t x = 0; x < xsize; ++x) { row_out[x] = LoadLEFloat16(in + i); i += bytes_per_pixel; } @@ -404,36 +363,32 @@ pool, 0, static_cast(ysize), ThreadPool::NoInit, [&](const uint32_t task, size_t /*thread*/) { const size_t y = get_y(task); size_t i = row_size * task + (channels - 1) * bytes_per_channel; float* JXL_RESTRICT row_out = alpha.Row(y); - // TODO(deymo): add bits_per_sample == 1 case here. Also maybe - // implement masking if bits_per_sample is not a multiple of 8. if (bits_per_sample <= 8) { LoadFloatRow(row_out, in + i, mul, xsize, bytes_per_pixel); - } else if (bits_per_sample <= 16) { + } else { if (little_endian) { LoadFloatRow(row_out, in + i, mul, xsize, bytes_per_pixel); } else { LoadFloatRow(row_out, in + i, mul, xsize, bytes_per_pixel); } - } else { - if (little_endian) { - LoadFloatRow(row_out, in + i, mul, xsize, - bytes_per_pixel); - } else { - LoadFloatRow(row_out, in + i, mul, xsize, - bytes_per_pixel); - } } }, "ConvertAlphaUint")); } ib->SetAlpha(std::move(alpha), alpha_is_premultiplied); + } else if (!has_alpha && ib->HasAlpha()) { + // if alpha is not passed, but it is expected, then assume + // it is all-opaque + ImageF alpha(xsize, ysize); + FillImage(1.0f, &alpha); + ib->SetAlpha(std::move(alpha), alpha_is_premultiplied); } return true; } diff --git a/third_party/jpeg-xl/lib/jxl/enc_file.cc b/third_party/jpeg-xl/lib/jxl/enc_file.cc --- a/third_party/jpeg-xl/lib/jxl/enc_file.cc +++ b/third_party/jpeg-xl/lib/jxl/enc_file.cc @@ -18,10 +18,11 @@ #include "lib/jxl/color_encoding_internal.h" #include "lib/jxl/enc_bit_writer.h" #include "lib/jxl/enc_cache.h" #include "lib/jxl/enc_frame.h" #include "lib/jxl/enc_icc_codec.h" +#include "lib/jxl/exif.h" #include "lib/jxl/frame_header.h" #include "lib/jxl/headers.h" #include "lib/jxl/image_bundle.h" namespace jxl { @@ -62,55 +63,10 @@ /*suitable_for_downsampling_of_at_least=*/2}, {/*num_coefficients=*/8, /*shift=*/0, /*salient_only=*/false, /*suitable_for_downsampling_of_at_least=*/0}, }; -constexpr uint16_t kExifOrientationTag = 274; - -// Parses the Exif data just enough to extract any render-impacting info. -// If the Exif data is invalid or could not be parsed, then it is treated -// as a no-op. -// TODO (jon): tag 1 can be used to represent Adobe RGB 1998 if it has value -// "R03" -// TODO (jon): set intrinsic dimensions according to -// https://discourse.wicg.io/t/proposal-exif-image-resolution-auto-and-from-image/4326/24 -void InterpretExif(const PaddedBytes& exif, CodecMetadata* metadata) { - if (exif.size() < 12) return; // not enough bytes for a valid exif blob - const uint8_t* t = exif.data(); - bool bigendian = false; - if (LoadLE32(t) == 0x2A004D4D) { - bigendian = true; - } else if (LoadLE32(t) != 0x002A4949) { - return; // not a valid tiff header - } - t += 4; - uint32_t offset = (bigendian ? LoadBE32(t) : LoadLE32(t)); - if (exif.size() < 12 + offset + 2 || offset < 8) return; - t += offset - 4; - uint16_t nb_tags = (bigendian ? LoadBE16(t) : LoadLE16(t)); - t += 2; - while (nb_tags > 0) { - if (t + 12 >= exif.data() + exif.size()) return; - uint16_t tag = (bigendian ? LoadBE16(t) : LoadLE16(t)); - t += 2; - uint16_t type = (bigendian ? LoadBE16(t) : LoadLE16(t)); - t += 2; - uint32_t count = (bigendian ? LoadBE32(t) : LoadLE32(t)); - t += 4; - uint16_t value = (bigendian ? LoadBE16(t) : LoadLE16(t)); - t += 4; - if (tag == kExifOrientationTag) { - if (type == 3 && count == 1) { - if (value >= 1 && value <= 8) { - metadata->m.orientation = value; - } - } - } - nb_tags--; - } -} - Status PrepareCodecMetadataFromIO(const CompressParams& cparams, const CodecInOut* io, CodecMetadata* metadata) { *metadata = io->metadata; size_t ups = 1; @@ -119,13 +75,11 @@ JXL_RETURN_IF_ERROR(metadata->size.Set(io->xsize() * ups, io->ysize() * ups)); // Keep ICC profile in lossless modes because a reconstructed profile may be // slightly different (quantization). // Also keep ICC in JPEG reconstruction mode as we need byte-exact profiles. - const bool lossless_modular = - cparams.modular_mode && cparams.quality_pair.first == 100.0f; - if (!lossless_modular && !io->Main().IsJPEG()) { + if (!cparams.IsLossless() && !io->Main().IsJPEG()) { metadata->m.color_encoding.DecideIfWantICC(); } metadata->m.xyb_encoded = cparams.color_transform == ColorTransform::kXYB ? true : false; diff --git a/third_party/jpeg-xl/lib/jxl/enc_frame.cc b/third_party/jpeg-xl/lib/jxl/enc_frame.cc --- a/third_party/jpeg-xl/lib/jxl/enc_frame.cc +++ b/third_party/jpeg-xl/lib/jxl/enc_frame.cc @@ -252,14 +252,13 @@ } } } // Strength of EPF in modular mode. if (frame_header->encoding == FrameEncoding::kModular && - cparams.quality_pair.first < 100) { + !cparams.IsLossless()) { // TODO(veluca): this formula is nonsense. - loop_filter->epf_sigma_for_modular = - 20.0f * (1.0f - cparams.quality_pair.first / 100); + loop_filter->epf_sigma_for_modular = cparams.butteraugli_distance; } if (frame_header->encoding == FrameEncoding::kModular && cparams.lossy_palette) { loop_filter->epf_sigma_for_modular = 1.0f; } @@ -1103,16 +1102,10 @@ if (cparams.butteraugli_distance != 0 && cparams.butteraugli_distance < kMinButteraugliDistance) { return JXL_FAILURE("Butteraugli distance is too low (%f)", cparams.butteraugli_distance); } - if (cparams.butteraugli_distance > 0.9f && cparams.modular_mode == false && - cparams.quality_pair.first == 100) { - // in case the color image is lossy, make the alpha slightly lossy too - cparams.quality_pair.first = - std::max(90.f, 99.f - 0.3f * cparams.butteraugli_distance); - } if (ib.IsJPEG()) { cparams.gaborish = Override::kOff; cparams.epf = 0; cparams.modular_mode = false; @@ -1226,12 +1219,11 @@ // YCbCr) // If encoding a special DC or reference frame, don't do anything: // input is already in XYB. CopyImageTo(ib.color(), &opsin); } - bool lossless = (frame_header->encoding == FrameEncoding::kModular && - cparams.quality_pair.first == 100); + bool lossless = cparams.IsLossless(); if (ib.HasAlpha() && !ib.AlphaIsPremultiplied() && frame_header->frame_type == FrameType::kRegularFrame && !ApplyOverride(cparams.keep_invisible, lossless) && cparams.ec_resampling == cparams.resampling) { // simplify invisible pixels diff --git a/third_party/jpeg-xl/lib/jxl/enc_icc_codec.cc b/third_party/jpeg-xl/lib/jxl/enc_icc_codec.cc --- a/third_party/jpeg-xl/lib/jxl/enc_icc_codec.cc +++ b/third_party/jpeg-xl/lib/jxl/enc_icc_codec.cc @@ -20,33 +20,10 @@ #include "lib/jxl/icc_codec_common.h" namespace jxl { namespace { -bool EncodeVarInt(uint64_t value, size_t output_size, size_t* output_pos, - uint8_t* output) { - // While more than 7 bits of data are left, - // store 7 bits and set the next byte flag - while (value > 127) { - if (*output_pos > output_size) return false; - // |128: Set the next byte flag - output[(*output_pos)++] = ((uint8_t)(value & 127)) | 128; - // Remove the seven bits we just wrote - value >>= 7; - } - if (*output_pos > output_size) return false; - output[(*output_pos)++] = ((uint8_t)value) & 127; - return true; -} - -void EncodeVarInt(uint64_t value, PaddedBytes* data) { - size_t pos = data->size(); - data->resize(data->size() + 9); - JXL_CHECK(EncodeVarInt(value, data->size(), &pos, data->data())); - data->resize(pos); -} - // Unshuffles or de-interleaves bytes, for example with width 2, turns // "AaBbCcDc" into "ABCDabcd", this for example de-interleaves UTF-16 bytes into // first all the high order bytes, then all the low order bytes. // Transposes a matrix of width columns and ceil(size / width) rows. There are // size elements, size may be < width * height, if so the @@ -157,13 +134,13 @@ if (tag == kRtrcTag && pos + 24 < size) { bool ok = true; ok &= DecodeKeyword(icc, size, pos + 0) == kGtrcTag; ok &= DecodeKeyword(icc, size, pos + 12) == kBtrcTag; if (ok) { - for (size_t i = 0; i < 8; i++) { - if (icc[pos - 8 + i] != icc[pos + 4 + i]) ok = false; - if (icc[pos - 8 + i] != icc[pos + 16 + i]) ok = false; + for (size_t kk = 0; kk < 8; kk++) { + if (icc[pos - 8 + kk] != icc[pos + 4 + kk]) ok = false; + if (icc[pos - 8 + kk] != icc[pos + 16 + kk]) ok = false; } } if (ok) { tagcode = kCommandTagTRC; pos += 24; diff --git a/third_party/jpeg-xl/lib/jxl/enc_modular.h b/third_party/jpeg-xl/lib/jxl/enc_modular.h --- a/third_party/jpeg-xl/lib/jxl/enc_modular.h +++ b/third_party/jpeg-xl/lib/jxl/enc_modular.h @@ -78,12 +78,11 @@ std::vector> tokens; EntropyEncodingData code; std::vector context_map; FrameDimensions frame_dim; CompressParams cparams; - float quality = cparams.quality_pair.first; - float cquality = cparams.quality_pair.second; + float quality = 100.f; std::vector tree_splits; std::vector multiplier_info; std::vector> gi_channel; std::vector image_widths; Predictor delta_pred = Predictor::Average4; diff --git a/third_party/jpeg-xl/lib/jxl/enc_modular.cc b/third_party/jpeg-xl/lib/jxl/enc_modular.cc --- a/third_party/jpeg-xl/lib/jxl/enc_modular.cc +++ b/third_party/jpeg-xl/lib/jxl/enc_modular.cc @@ -226,15 +226,22 @@ // convert binary32 float that corresponds to custom [bits]-bit float (with // [exp_bits] exponent bits) to a [bits]-bit integer representation that should // fit in pixel_type Status float_to_int(const float* const row_in, pixel_type* const row_out, size_t xsize, unsigned int bits, unsigned int exp_bits, - bool fp, float factor) { + bool fp, double dfactor) { JXL_ASSERT(sizeof(pixel_type) * 8 >= bits); if (!fp) { - for (size_t x = 0; x < xsize; ++x) { - row_out[x] = row_in[x] * factor + (row_in[x] < 0 ? -0.5f : 0.5f); + if (bits > 22) { + for (size_t x = 0; x < xsize; ++x) { + row_out[x] = row_in[x] * dfactor + (row_in[x] < 0 ? -0.5 : 0.5); + } + } else { + float factor = dfactor; + for (size_t x = 0; x < xsize; ++x) { + row_out[x] = row_in[x] * factor + (row_in[x] < 0 ? -0.5f : 0.5f); + } } return true; } if (bits == 32 && fp) { JXL_ASSERT(exp_bits == 8); @@ -294,12 +301,11 @@ ModularFrameEncoder::ModularFrameEncoder(const FrameHeader& frame_header, const CompressParams& cparams_orig) : frame_dim(frame_header.ToFrameDimensions()), cparams(cparams_orig) { size_t num_streams = ModularStreamId::Num(frame_dim, frame_header.passes.num_passes); - if (cparams.modular_mode && - cparams.quality_pair == std::pair{100.0, 100.0}) { + if (cparams.IsLossless()) { switch (cparams.decoding_speed_tier) { case 0: break; case 1: cparams.options.wp_tree_mode = ModularOptions::TreeMode::kWPOnly; @@ -320,22 +326,21 @@ break; } } } if (cparams.decoding_speed_tier >= 1 && cparams.responsive && - cparams.quality_pair == std::make_pair(100.f, 100.f)) { + cparams.IsLossless()) { cparams.options.tree_kind = ModularOptions::TreeKind::kTrivialTreeNoPredictor; cparams.options.nb_repeats = 0; } stream_images.resize(num_streams); - if (cquality > 100) cquality = quality; // use a sensible default if nothing explicit is specified: // Squeeze for lossy, no squeeze for lossless if (cparams.responsive < 0) { - if (quality == 100) { + if (cparams.IsLossless()) { cparams.responsive = 0; } else { cparams.responsive = 1; } } @@ -394,18 +399,18 @@ if (cparams.options.predictor == static_cast(-1)) { // no explicit predictor(s) given, set a good default if ((cparams.speed_tier <= SpeedTier::kTortoise || cparams.modular_mode == false) && - quality == 100 && cparams.responsive == false) { + cparams.IsLossless() && cparams.responsive == false) { // TODO(veluca): allow all predictors that don't break residual // multipliers in lossy mode. cparams.options.predictor = Predictor::Variable; } else if (cparams.responsive || cparams.lossy_palette) { // zero predictor for Squeeze residues and lossy palette cparams.options.predictor = Predictor::Zero; - } else if (quality < 100) { + } else if (!cparams.IsLossless()) { // If not responsive and lossy. TODO(veluca): use near_lossless instead? cparams.options.predictor = Predictor::Gradient; } else if (cparams.speed_tier < SpeedTier::kFalcon) { // try median and weighted predictor for anything else cparams.options.predictor = Predictor::Best; @@ -418,10 +423,16 @@ } } else { delta_pred = cparams.options.predictor; if (cparams.lossy_palette) cparams.options.predictor = Predictor::Zero; } + if (!cparams.IsLossless()) { + if (cparams.options.predictor == Predictor::Weighted || + cparams.options.predictor == Predictor::Variable || + cparams.options.predictor == Predictor::Best) + cparams.options.predictor = Predictor::Zero; + } tree_splits.push_back(0); if (cparams.modular_mode == false) { cparams.options.fast_decode_multiplier = 1.0f; tree_splits.push_back(ModularStreamId::VarDCTDC(0).ID(frame_dim)); tree_splits.push_back(ModularStreamId::ModularDC(0).ID(frame_dim)); @@ -497,21 +508,33 @@ } else if (metadata.bit_depth.bits_per_sample > 32) { return JXL_FAILURE("bits_per_sample > 32 not supported"); } } + // in the non-float case, there is an implicit 0 sign bit + int max_bitdepth = + do_color ? metadata.bit_depth.bits_per_sample + (fp ? 0 : 1) : 0; Image& gi = stream_images[0]; gi = Image(xsize, ysize, metadata.bit_depth.bits_per_sample, nb_chans); int c = 0; if (cparams.color_transform == ColorTransform::kXYB && cparams.modular_mode == true) { - static const float enc_factors[3] = {32768.0f, 2048.0f, 2048.0f}; + float enc_factors[3] = {32768.0f, 2048.0f, 2048.0f}; + if (cparams.butteraugli_distance > 0 && !cparams.responsive) { + // quantize XYB here and then treat it as a lossless image + enc_factors[0] *= 1.f / (1.f + 23.f * cparams.butteraugli_distance); + enc_factors[1] *= 1.f / (1.f + 14.f * cparams.butteraugli_distance); + enc_factors[2] *= 1.f / (1.f + 14.f * cparams.butteraugli_distance); + cparams.butteraugli_distance = 0; + } if (cparams.manual_xyb_factors.size() == 3) { DequantMatricesSetCustomDC(&enc_state->shared.matrices, cparams.manual_xyb_factors.data()); + // TODO(jon): update max_bitdepth in this case } else { DequantMatricesSetCustomDC(&enc_state->shared.matrices, enc_factors); + max_bitdepth = 12; } } pixel_type maxval = gi.bitdepth < 32 ? (1u << gi.bitdepth) - 1 : 0; if (do_color) { for (; c < 3; c++) { @@ -521,11 +544,11 @@ continue; int c_out = c; // XYB is encoded as YX(B-Y) if (cparams.color_transform == ColorTransform::kXYB && c < 2) c_out = 1 - c_out; - float factor = maxval; + double factor = maxval; if (cparams.color_transform == ColorTransform::kXYB) factor = enc_state->shared.matrices.InvDCQuant(c); if (c == 2 && cparams.color_transform == ColorTransform::kXYB) { JXL_ASSERT(!fp); for (size_t y = 0; y < ysize; ++y) { @@ -533,10 +556,12 @@ pixel_type* const JXL_RESTRICT row_out = gi.channel[c_out].Row(y); pixel_type* const JXL_RESTRICT row_Y = gi.channel[0].Row(y); for (size_t x = 0; x < xsize; ++x) { row_out[x] = row_in[x] * factor + 0.5f; row_out[x] -= row_Y[x]; + // zero the lsb of B + row_out[x] = row_out[x] / 2 * 2; } } } else { int bits = metadata.bit_depth.bits_per_sample; int exp_bits = metadata.bit_depth.exponent_bits_per_sample; @@ -579,11 +604,12 @@ CeilLog2Nonzero(ecups) - CeilLog2Nonzero(frame_header.upsampling); int bits = eci.bit_depth.bits_per_sample; int exp_bits = eci.bit_depth.exponent_bits_per_sample; bool fp = eci.bit_depth.floating_point_sample; - float factor = (fp ? 1 : ((1u << eci.bit_depth.bits_per_sample) - 1)); + double factor = (fp ? 1 : ((1u << eci.bit_depth.bits_per_sample) - 1)); + if (bits + (fp ? 0 : 1) > max_bitdepth) max_bitdepth = bits + (fp ? 0 : 1); std::atomic has_error{false}; JXL_RETURN_IF_ERROR(RunOnPool( pool, 0, gi.channel[c].plane.ysize(), ThreadPool::NoInit, [&](const int task, const int thread) { const size_t y = task; @@ -597,13 +623,20 @@ "float2int")); if (has_error) return JXL_FAILURE("Error in float to integer conversion"); } JXL_ASSERT(c == nb_chans); + int level_max_bitdepth = (cparams.level == 5 ? 16 : 32); + if (max_bitdepth > level_max_bitdepth) + return JXL_FAILURE( + "Bitdepth too high for level %i (need %i bits, have only %i in this " + "level)", + cparams.level, max_bitdepth, level_max_bitdepth); + // Set options and apply transformations - if (quality < 100) { + if (cparams.butteraugli_distance > 0) { if (cparams.palette_colors != 0) { JXL_DEBUG_V(3, "Lossy encode, not doing palette transforms"); } if (cparams.color_transform == ColorTransform::kXYB) { cparams.channel_colors_pre_transform_percent = 0; @@ -702,90 +735,85 @@ do_transform(gi, maybe_palette_3, weighted::Header(), pool, cparams.options.zero_tokens); } } + // don't do an RCT if we're short on bits if (cparams.color_transform == ColorTransform::kNone && do_color && !fp && - gi.channel.size() - gi.nb_meta_channels >= 3) { + gi.channel.size() - gi.nb_meta_channels >= 3 && + max_bitdepth + 1 < level_max_bitdepth) { if (cparams.colorspace == 1 || (cparams.colorspace < 0 && - (quality < 100 || cparams.speed_tier > SpeedTier::kHare))) { + (!cparams.IsLossless() || cparams.speed_tier > SpeedTier::kHare))) { Transform ycocg{TransformId::kRCT}; ycocg.rct_type = 6; ycocg.begin_c = gi.nb_meta_channels; do_transform(gi, ycocg, weighted::Header(), pool); + max_bitdepth++; } else if (cparams.colorspace >= 2) { Transform sg(TransformId::kRCT); sg.begin_c = gi.nb_meta_channels; sg.rct_type = cparams.colorspace - 2; do_transform(gi, sg, weighted::Header(), pool); + max_bitdepth++; } } - if (cparams.responsive && !gi.channel.empty()) { + // don't do squeeze if we don't have some spare bits + if (cparams.responsive && !gi.channel.empty() && + max_bitdepth + 2 < level_max_bitdepth) { Transform t(TransformId::kSqueeze); t.squeezes = cparams.squeezes; do_transform(gi, t, weighted::Header(), pool); + max_bitdepth += 2; } + if (max_bitdepth + 1 > level_max_bitdepth) { + // force no group RCTs if we don't have a spare bit + cparams.colorspace = 0; + } + JXL_ASSERT(max_bitdepth <= level_max_bitdepth); + std::vector quants; - if (quality < 100 || cquality < 100) { + if (cparams.butteraugli_distance > 0) { quants.resize(gi.channel.size(), 1); - JXL_DEBUG_V( - 2, - "Adding quantization constants corresponding to luma quality %.2f " - "and chroma quality %.2f", - quality, cquality); + float quality = 0.25f * cparams.butteraugli_distance; + JXL_DEBUG_V(2, + "Adding quantization constants corresponding to distance %.3f ", + quality); if (!cparams.responsive) { JXL_DEBUG_V(1, "Warning: lossy compression without Squeeze " "transform is just color quantization."); - quality = (400 + quality) / 5; - cquality = (400 + cquality) / 5; - } - - // convert 'quality' to quantization scaling factor - if (quality > 50) { - quality = 200.0 - quality * 2.0; - } else { - quality = 900.0 - quality * 16.0; - } - if (cquality > 50) { - cquality = 200.0 - cquality * 2.0; - } else { - cquality = 900.0 - cquality * 16.0; + quality *= 0.1f; } if (cparams.color_transform != ColorTransform::kXYB) { - quality *= 0.01f * maxval / 255.f; - cquality *= 0.01f * maxval / 255.f; - } else { - quality *= 0.01f; - cquality *= 0.01f; + quality *= maxval / 255.f; } - if (cparams.options.nb_repeats == 0) { return JXL_FAILURE("nb_repeats = 0 not supported with modular lossy!"); } for (uint32_t i = gi.nb_meta_channels; i < gi.channel.size(); i++) { Channel& ch = gi.channel[i]; int shift = ch.hshift + ch.vshift; // number of pixel halvings if (shift > 16) shift = 16; if (shift > 0) shift--; int q; // assuming default Squeeze here - int component = ((i - gi.nb_meta_channels) % nb_chans); + int component = + (do_color ? 0 : 3) + ((i - gi.nb_meta_channels) % nb_chans); // last 4 channels are final chroma residuals if (nb_chans > 2 && i >= gi.channel.size() - 4 && cparams.responsive) { component = 1; } if (cparams.color_transform == ColorTransform::kXYB && component < 3) { - q = (component == 0 ? quality : cquality) * squeeze_quality_factor_xyb * + q = quality * squeeze_quality_factor_xyb * squeeze_xyb_qtable[component][shift]; } else { if (cparams.colorspace != 0 && component > 0 && component < 3) { - q = cquality * squeeze_quality_factor * squeeze_chroma_qtable[shift]; + q = quality * squeeze_quality_factor * squeeze_chroma_qtable[shift]; } else { q = quality * squeeze_quality_factor * squeeze_luma_factor * squeeze_luma_qtable[shift]; } } @@ -1286,16 +1314,14 @@ } if (gi.channel.empty()) return true; // Do some per-group transforms - float quality = cparams.quality_pair.first; - // Local palette // TODO(veluca): make this work with quantize-after-prediction in lossy // mode. - if (quality == 100 && cparams.palette_colors != 0 && + if (cparams.butteraugli_distance == 0.f && cparams.palette_colors != 0 && cparams.speed_tier < SpeedTier::kCheetah) { // all-channel palette (e.g. RGBA) if (gi.channel.size() - gi.nb_meta_channels > 1) { Transform maybe_palette(TransformId::kPalette); maybe_palette.begin_c = gi.nb_meta_channels; @@ -1319,12 +1345,13 @@ do_transform(gi, maybe_palette_3, weighted::Header()); } } // Local channel palette - if (cparams.channel_colors_percent > 0 && quality == 100 && - !cparams.lossy_palette && cparams.speed_tier < SpeedTier::kCheetah && + if (cparams.channel_colors_percent > 0 && + cparams.butteraugli_distance == 0.f && !cparams.lossy_palette && + cparams.speed_tier < SpeedTier::kCheetah && !(cparams.responsive && cparams.decoding_speed_tier >= 1)) { // single channel palette (like FLIF's ChannelCompact) size_t nb_channels = gi.channel.size() - gi.nb_meta_channels; for (size_t i = 0; i < nb_channels; i++) { int min, max; @@ -1346,12 +1373,13 @@ } } // lossless and no specific color transform specified: try Nothing, YCoCg, // and 17 RCTs - if (cparams.color_transform == ColorTransform::kNone && quality == 100 && - cparams.colorspace < 0 && gi.channel.size() - gi.nb_meta_channels >= 3 && + if (cparams.color_transform == ColorTransform::kNone && + cparams.IsLossless() && cparams.colorspace < 0 && + gi.channel.size() - gi.nb_meta_channels >= 3 && cparams.responsive == false && do_color && cparams.speed_tier <= SpeedTier::kHare) { Transform sg(TransformId::kRCT); sg.begin_c = gi.nb_meta_channels; size_t nb_rcts_to_try = 0; diff --git a/third_party/jpeg-xl/lib/jxl/enc_params.h b/third_party/jpeg-xl/lib/jxl/enc_params.h --- a/third_party/jpeg-xl/lib/jxl/enc_params.h +++ b/third_party/jpeg-xl/lib/jxl/enc_params.h @@ -125,10 +125,11 @@ // Try to achieve a maximum pixel-by-pixel error on each channel. bool max_error_mode = false; float max_error[3] = {0.0, 0.0, 0.0}; SpeedTier speed_tier = SpeedTier::kSquirrel; + int brotli_effort = -1; // 0 = default. // 1 = slightly worse quality. // 4 = fastest speed, lowest quality // TODO(veluca): hook this up to the C API. @@ -216,32 +217,30 @@ // modular mode options below ModularOptions options; int responsive = -1; // empty for default squeeze std::vector squeezes; - // A pair of . - std::pair quality_pair{100.f, 100.f}; int colorspace = -1; // Use Global channel palette if #colors < this percentage of range float channel_colors_pre_transform_percent = 95.f; // Use Local channel palette if #colors < this percentage of range float channel_colors_percent = 80.f; int palette_colors = 1 << 10; // up to 10-bit palette is probably worthwhile bool lossy_palette = false; // Returns whether these params are lossless as defined by SetLossless(); bool IsLossless() const { - return modular_mode && quality_pair.first == 100 && - quality_pair.second == 100 && - color_transform == jxl::ColorTransform::kNone; + // YCbCr is also considered lossless here since it's intended for + // source material that is already YCbCr (we don't do the fwd transform) + return modular_mode && butteraugli_distance == 0.0f && + color_transform != jxl::ColorTransform::kXYB; } // Sets the parameters required to make the codec lossless. void SetLossless() { modular_mode = true; - quality_pair.first = 100; - quality_pair.second = 100; + butteraugli_distance = 0.0f; color_transform = jxl::ColorTransform::kNone; } bool use_new_heuristics = false; @@ -253,10 +252,14 @@ int resampling = -1; int ec_resampling = -1; // Skip the downsampling before encoding if this is true. bool already_downsampled = false; + // Codestream level to conform to. + // -1: don't care + int level = -1; + std::vector manual_noise; std::vector manual_xyb_factors; }; static constexpr float kMinButteraugliForDynamicAR = 0.5f; diff --git a/third_party/jpeg-xl/lib/jxl/enc_patch_dictionary.cc b/third_party/jpeg-xl/lib/jxl/enc_patch_dictionary.cc --- a/third_party/jpeg-xl/lib/jxl/enc_patch_dictionary.cc +++ b/third_party/jpeg-xl/lib/jxl/enc_patch_dictionary.cc @@ -708,15 +708,10 @@ } CompressParams cparams = state->cparams; // Recursive application of patches could create very weird issues. cparams.patches = Override::kOff; - // TODO(veluca): possibly change heuristics here. - if (!cparams.modular_mode) { - cparams.quality_pair.first = cparams.quality_pair.second = - 90.f - cparams.butteraugli_distance * 5.f; - } RoundtripPatchFrame(&reference_frame, state, 0, cparams, cms, pool, true); // TODO(veluca): this assumes that applying patches is commutative, which is // not true for all blending modes. This code only produces kAdd patches, so diff --git a/third_party/jpeg-xl/lib/jxl/encode.cc b/third_party/jpeg-xl/lib/jxl/encode.cc --- a/third_party/jpeg-xl/lib/jxl/encode.cc +++ b/third_party/jpeg-xl/lib/jxl/encode.cc @@ -144,10 +144,14 @@ return -1; } // Level 5 checks + if (!m.modular_16_bit_buffer_sufficient) { + if (debug_string) *debug_string = "Too high modular bit depth"; + return 10; + } if (xsize > (1ull << 18ull) || ysize > (1ull << 18ull) || xsize * ysize > (1ull << 28ull)) { if (debug_string) *debug_string = "Too large image dimensions"; return 10; } @@ -246,10 +250,16 @@ } } // TODO(lode): preview should be added here if a preview image is added writer.ZeroPadToByte(); + + // Not actually the end of frame, but the end of metadata/ICC, but helps + // the next frame to start here for indexing purposes. + codestream_bytes_written_end_of_frame += + jxl::DivCeil(writer.BitsWritten(), 8); + bytes = std::move(writer).TakeBytes(); if (MustUseContainer()) { // Add "JXL " and ftyp box. output_byte_queue.insert( @@ -361,39 +371,45 @@ input_frame->option_values.header.layer_info.blend_info.clamp; frame_info.alpha_channel = input_frame->option_values.header.layer_info.blend_info.alpha; frame_info.extra_channel_blending_info.resize( metadata.m.num_extra_channels); - // If extra channel blend info has not been set, use the default values. - JxlBlendInfo default_blend_info; - JxlEncoderInitBlendInfo(&default_blend_info); + // If extra channel blend info has not been set, use the blend mode from the + // layer_info. + JxlBlendInfo default_blend_info = + input_frame->option_values.header.layer_info.blend_info; for (size_t i = 0; i < metadata.m.num_extra_channels; ++i) { auto& to = frame_info.extra_channel_blending_info[i]; const auto& from = i < input_frame->option_values.extra_channel_blend_info.size() ? input_frame->option_values.extra_channel_blend_info[i] : default_blend_info; to.mode = static_cast(from.blendmode); to.source = from.source; to.alpha_channel = from.alpha; + to.clamp = (from.clamp != 0); } if (input_frame->option_values.header.layer_info.have_crop) { ib.origin.x0 = input_frame->option_values.header.layer_info.crop_x0; ib.origin.y0 = input_frame->option_values.header.layer_info.crop_y0; } + JXL_ASSERT(writer.BitsWritten() == 0); if (!jxl::EncodeFrame(input_frame->option_values.cparams, frame_info, &metadata, input_frame->frame, &enc_state, cms, thread_pool.get(), &writer, /*aux_out=*/nullptr)) { return JXL_API_ERROR("Failed to encode frame"); } + codestream_bytes_written_beginning_of_frame = + codestream_bytes_written_end_of_frame; + codestream_bytes_written_end_of_frame += + jxl::DivCeil(writer.BitsWritten(), 8); // Possibly bytes already contains the codestream header: in case this is // the first frame, and the codestream header was not encoded as jxlp above. bytes.append(std::move(writer).TakeBytes()); - if (MustUseContainer()) { if (last_frame && jxlp_counter == 0) { // If this is the last frame and no jxlp boxes were used yet, it's // slighly more efficient to write a jxlc box since it has 4 bytes less // overhead. @@ -421,13 +437,14 @@ jxl::PaddedBytes compressed(4); // Prepend the original box type in the brob box contents for (size_t i = 0; i < 4; i++) { compressed[i] = static_cast(box->type[i]); } - if (JXL_ENC_SUCCESS != BrotliCompress(9, box->contents.data(), - box->contents.size(), - &compressed)) { + if (JXL_ENC_SUCCESS != + BrotliCompress((brotli_effort >= 0 ? brotli_effort : 4), + box->contents.data(), box->contents.size(), + &compressed)) { return JXL_API_ERROR("Brotli compression for brob box failed"); } jxl::AppendBoxHeader(jxl::MakeBoxType("brob"), compressed.size(), false, &output_byte_queue); output_byte_queue.insert(output_byte_queue.end(), compressed.data(), @@ -559,11 +576,12 @@ enc->metadata.m.bit_depth.exponent_bits_per_sample = info->exponent_bits_per_sample; enc->metadata.m.bit_depth.floating_point_sample = (info->exponent_bits_per_sample != 0u); enc->metadata.m.modular_16_bit_buffer_sufficient = - (info->exponent_bits_per_sample == 0u) && info->bits_per_sample <= 12; + (!info->uses_original_profile || info->bits_per_sample <= 12) && + info->alpha_bits <= 12; // The number of extra channels includes the alpha channel, so for example and // RGBA with no other extra channels, has exactly num_extra_channels == 1 enc->metadata.m.num_extra_channels = info->num_extra_channels; enc->metadata.m.extra_channel_info.resize(enc->metadata.m.num_extra_channels); @@ -618,11 +636,19 @@ enc->metadata.m.animation.tps_numerator = info->animation.tps_numerator; enc->metadata.m.animation.tps_denominator = info->animation.tps_denominator; enc->metadata.m.animation.num_loops = info->animation.num_loops; enc->metadata.m.animation.have_timecodes = info->animation.have_timecodes; } - + std::string level_message; + int required_level = VerifyLevelSettings(enc, &level_message); + if (required_level == -1 || + static_cast(enc->codestream_level) < required_level) { + return JXL_API_ERROR("%s", ("Codestream level verification for level " + + std::to_string(enc->codestream_level) + + " failed: " + level_message) + .c_str()); + } return JXL_ENC_SUCCESS; } void JxlEncoderInitExtraChannelInfo(JxlExtraChannelType type, JxlExtraChannelInfo* info) { @@ -650,20 +676,31 @@ } jxl::ExtraChannelInfo& channel = enc->metadata.m.extra_channel_info[index]; channel.type = static_cast(info->type); channel.bit_depth.bits_per_sample = info->bits_per_sample; + enc->metadata.m.modular_16_bit_buffer_sufficient &= + info->bits_per_sample <= 12; channel.bit_depth.exponent_bits_per_sample = info->exponent_bits_per_sample; channel.bit_depth.floating_point_sample = info->exponent_bits_per_sample != 0; channel.dim_shift = info->dim_shift; channel.name = ""; channel.alpha_associated = (info->alpha_premultiplied != 0); channel.cfa_channel = info->cfa_channel; channel.spot_color[0] = info->spot_color[0]; channel.spot_color[1] = info->spot_color[1]; channel.spot_color[2] = info->spot_color[2]; channel.spot_color[3] = info->spot_color[3]; + std::string level_message; + int required_level = VerifyLevelSettings(enc, &level_message); + if (required_level == -1 || + static_cast(enc->codestream_level) < required_level) { + return JXL_API_ERROR("%s", ("Codestream level verification for level " + + std::to_string(enc->codestream_level) + + " failed: " + level_message) + .c_str()); + } return JXL_ENC_SUCCESS; } JXL_EXPORT JxlEncoderStatus JxlEncoderSetExtraChannelName(JxlEncoder* enc, size_t index, @@ -686,10 +723,11 @@ if (source != nullptr) { opts->values = source->values; } else { opts->values.lossless = false; } + opts->values.cparams.level = enc->codestream_level; JxlEncoderFrameSettings* ret = opts.get(); enc->encoder_options.emplace_back(std::move(opts)); return ret; } @@ -699,10 +737,14 @@ return JxlEncoderFrameSettingsCreate(enc, source); } JxlEncoderStatus JxlEncoderSetFrameLossless( JxlEncoderFrameSettings* frame_settings, const JXL_BOOL lossless) { + if (lossless && frame_settings->enc->basic_info_set && + frame_settings->enc->metadata.m.xyb_encoded) { + return JXL_API_ERROR("Set use_original_profile=true for lossless encoding"); + } frame_settings->values.lossless = lossless; return JXL_ENC_SUCCESS; } JxlEncoderStatus JxlEncoderOptionsSetLossless( @@ -724,33 +766,10 @@ } if (distance > 0.f && distance < 0.01f) { distance = 0.01f; } frame_settings->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.f) { - quality = std::min(35.f + (jpeg_quality - 7.f) * 3.0f, 100.0f); - } else { - quality = - std::min(35.f + (jpeg_quality - 7.f) * 65.f / 93.f, 100.0f); - } - frame_settings->values.cparams.quality_pair.first = - frame_settings->values.cparams.quality_pair.second = quality; return JXL_ENC_SUCCESS; } JxlEncoderStatus JxlEncoderOptionsSetDistance( JxlEncoderFrameSettings* frame_settings, float distance) { @@ -773,10 +792,19 @@ return JXL_ENC_ERROR; } frame_settings->values.cparams.speed_tier = static_cast(10 - value); return JXL_ENC_SUCCESS; + case JXL_ENC_FRAME_SETTING_BROTLI_EFFORT: + if (value < -1 || value > 11) { + return JXL_ENC_ERROR; + } + // set cparams for brotli use in JPEG frames + frame_settings->values.cparams.brotli_effort = value; + // set enc option for brotli use in brob boxes + frame_settings->enc->brotli_effort = value; + return JXL_ENC_SUCCESS; case JXL_ENC_FRAME_SETTING_DECODING_SPEED: if (value < 0 || value > 4) { return JXL_ENC_ERROR; } frame_settings->values.cparams.decoding_speed_tier = value; @@ -1006,11 +1034,12 @@ enc->input_queue.clear(); enc->num_queued_frames = 0; enc->num_queued_boxes = 0; enc->encoder_options.clear(); enc->output_byte_queue.clear(); - enc->output_bytes_flushed = 0; + enc->codestream_bytes_written_beginning_of_frame = 0; + enc->codestream_bytes_written_end_of_frame = 0; enc->wrote_bytes = false; enc->jxlp_counter = 0; enc->metadata = jxl::CodecMetadata(); enc->last_used_cparams = jxl::CompressParams(); enc->frames_closed = false; @@ -1023,13 +1052,14 @@ enc->codestream_level = 5; } void JxlEncoderDestroy(JxlEncoder* enc) { if (enc) { + JxlMemoryManager local_memory_manager = enc->memory_manager; // Call destructor directly since custom free function is used. enc->~JxlEncoder(); - jxl::MemoryManagerFree(&enc->memory_manager, enc); + jxl::MemoryManagerFree(&local_memory_manager, enc); } } JxlEncoderStatus JxlEncoderUseContainer(JxlEncoder* enc, JXL_BOOL use_container) { @@ -1060,11 +1090,14 @@ int JxlEncoderGetRequiredCodestreamLevel(const JxlEncoder* enc) { return VerifyLevelSettings(enc, nullptr); } -void JxlEncoderSetCms(JxlEncoder* enc, JxlCmsInterface cms) { enc->cms = cms; } +void JxlEncoderSetCms(JxlEncoder* enc, JxlCmsInterface cms) { + jxl::msan::MemoryIsInitialized(&cms, sizeof(cms)); + enc->cms = cms; +} JxlEncoderStatus JxlEncoderSetParallelRunner(JxlEncoder* enc, JxlParallelRunner parallel_runner, void* parallel_runner_opaque) { if (enc->thread_pool) return JXL_API_ERROR("parallel runner already set"); @@ -1136,11 +1169,12 @@ } if (frame_settings->enc->store_jpeg_metadata) { jxl::jpeg::JPEGData data_in = *io.Main().jpeg_data; jxl::PaddedBytes jpeg_data; - if (!jxl::jpeg::EncodeJPEGData(data_in, &jpeg_data)) { + if (!jxl::jpeg::EncodeJPEGData(data_in, &jpeg_data, + frame_settings->values.cparams)) { return JXL_ENC_ERROR; } frame_settings->enc->jpeg_metadata = std::vector( jpeg_data.data(), jpeg_data.data() + jpeg_data.size()); } @@ -1245,19 +1279,35 @@ has_interleaved_alpha = 0; // only first Alpha is initialized } else { queued_frame->ec_initialized.push_back(0); } } + queued_frame->frame.origin.x0 = + frame_settings->values.header.layer_info.crop_x0; + queued_frame->frame.origin.y0 = + frame_settings->values.header.layer_info.crop_y0; + queued_frame->frame.use_for_next_frame = + (frame_settings->values.header.layer_info.save_as_reference != 0u); + queued_frame->frame.blendmode = + frame_settings->values.header.layer_info.blend_info.blendmode == + JXL_BLEND_REPLACE + ? jxl::BlendMode::kReplace + : jxl::BlendMode::kBlend; + queued_frame->frame.blend = + frame_settings->values.header.layer_info.blend_info.source > 0; if (!jxl::BufferToImageBundle(*pixel_format, xsize, ysize, buffer, size, frame_settings->enc->thread_pool.get(), c_current, &(queued_frame->frame))) { return JXL_ENC_ERROR; } - if (frame_settings->values.lossless) { - queued_frame->option_values.cparams.SetLossless(); + if (frame_settings->values.lossless && + frame_settings->enc->metadata.m.xyb_encoded) { + return JXL_API_ERROR("Set use_original_profile=true for lossless encoding"); } + queued_frame->option_values.cparams.level = + frame_settings->enc->codestream_level; QueueFrame(frame_settings, queued_frame); return JXL_ENC_SUCCESS; } @@ -1348,11 +1398,10 @@ if (!enc->output_byte_queue.empty()) { size_t to_copy = std::min(*avail_out, enc->output_byte_queue.size()); std::copy_n(enc->output_byte_queue.begin(), to_copy, *next_out); *next_out += to_copy; *avail_out -= to_copy; - enc->output_bytes_flushed += to_copy; enc->output_byte_queue.erase(enc->output_byte_queue.begin(), enc->output_byte_queue.begin() + to_copy); } else if (!enc->input_queue.empty()) { if (enc->RefillOutputByteQueue() != JXL_ENC_SUCCESS) { return JXL_ENC_ERROR; @@ -1404,11 +1453,11 @@ return JXL_ENC_SUCCESS; } JxlEncoderStatus JxlEncoderSetFrameName(JxlEncoderFrameSettings* frame_settings, const char* frame_name) { - std::string str = frame_name; + std::string str = frame_name ? frame_name : ""; if (str.size() > 1071) { return JXL_API_ERROR("frame name can be max 1071 bytes long"); } frame_settings->values.frame_name = str; frame_settings->values.header.name_length = str.size(); diff --git a/third_party/jpeg-xl/lib/jxl/encode_internal.h b/third_party/jpeg-xl/lib/jxl/encode_internal.h --- a/third_party/jpeg-xl/lib/jxl/encode_internal.h +++ b/third_party/jpeg-xl/lib/jxl/encode_internal.h @@ -115,16 +115,19 @@ size_t num_queued_frames; size_t num_queued_boxes; std::vector input_queue; std::deque output_byte_queue; - size_t output_bytes_flushed; - // Get the current write position in the stream (for indexing use). - size_t BytePosition() const { - return output_bytes_flushed + output_byte_queue.size(); - } + // How many codestream bytes have been written, i.e., + // content of jxlc and jxlp boxes. Frame index box jxli + // requires position indices to point to codestream bytes, + // so we need to keep track of the total of flushed or queue + // codestream bytes. These bytes may be in a single jxlc box + // or accross multiple jxlp boxes. + size_t codestream_bytes_written_beginning_of_frame; + size_t codestream_bytes_written_end_of_frame; // Force using the container even if not needed bool use_container; // User declared they will add metadata boxes bool use_boxes; @@ -151,10 +154,11 @@ bool frames_closed; bool boxes_closed; bool basic_info_set; bool color_encoding_set; bool intensity_target_set; + int brotli_effort = -1; // Takes the first frame in the input_queue, encodes it, and appends // the bytes to the output_byte_queue. JxlEncoderStatus RefillOutputByteQueue(); diff --git a/third_party/jpeg-xl/lib/jxl/encode_test.cc b/third_party/jpeg-xl/lib/jxl/encode_test.cc --- a/third_party/jpeg-xl/lib/jxl/encode_test.cc +++ b/third_party/jpeg-xl/lib/jxl/encode_test.cc @@ -36,10 +36,11 @@ JxlBasicInfo basic_info; jxl::test::JxlBasicInfoSetFromPixelFormat(&basic_info, &pixel_format); basic_info.xsize = xsize; basic_info.ysize = ysize; basic_info.uses_original_profile = false; + EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetCodestreamLevel(enc.get(), 10)); 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, @@ -56,11 +57,11 @@ EXPECT_NE(nullptr, enc.get()); JxlEncoderCloseInput(enc.get()); const std::string jpeg_path = - "imagecompression.info/flower_foveon.png.im_q85_420.jpg"; + "third_party/imagecompression.info/flower_foveon.png.im_q85_420.jpg"; const jxl::PaddedBytes orig = jxl::ReadTestData(jpeg_path); JxlEncoderFrameSettings* frame_settings = JxlEncoderFrameSettingsCreate(enc.get(), NULL); @@ -83,10 +84,11 @@ JxlBasicInfo basic_info; jxl::test::JxlBasicInfoSetFromPixelFormat(&basic_info, &pixel_format); basic_info.xsize = xsize; basic_info.ysize = ysize; basic_info.uses_original_profile = true; + EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetCodestreamLevel(enc.get(), 10)); EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetBasicInfo(enc.get(), &basic_info)); JxlEncoderFrameSettings* frame_settings = JxlEncoderFrameSettingsCreate(enc.get(), NULL); EXPECT_EQ(JXL_ENC_ERROR, JxlEncoderAddImageFrame(frame_settings, &pixel_format, @@ -171,10 +173,12 @@ if (frame_settings->values.lossless) { basic_info.uses_original_profile = true; } else { basic_info.uses_original_profile = false; } + // 16-bit alpha means this requires level 10 + EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetCodestreamLevel(enc, 10)); EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetBasicInfo(enc, &basic_info)); JxlColorEncoding color_encoding; JxlColorEncodingSetToSRGB(&color_encoding, /*is_gray=*/pixel_format.num_channels < 3); EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetColorEncoding(enc, &color_encoding)); @@ -196,11 +200,10 @@ avail_out = compressed.size() - offset; } } compressed.resize(next_out - compressed.data()); EXPECT_EQ(JXL_ENC_SUCCESS, process_result); - jxl::DecompressParams dparams; jxl::CodecInOut decoded_io; EXPECT_TRUE(jxl::DecodeFile( dparams, jxl::Span(compressed.data(), compressed.size()), &decoded_io, /*pool=*/nullptr)); @@ -628,10 +631,11 @@ JxlBasicInfo basic_info; jxl::test::JxlBasicInfoSetFromPixelFormat(&basic_info, &pixel_format); basic_info.xsize = xsize; basic_info.ysize = ysize; basic_info.uses_original_profile = false; + EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetCodestreamLevel(enc.get(), 10)); EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetBasicInfo(enc.get(), &basic_info)); JxlColorEncoding color_encoding; JxlColorEncodingSetToSRGB(&color_encoding, /*is_gray=*/false); EXPECT_EQ(JXL_ENC_SUCCESS, @@ -737,11 +741,11 @@ EXPECT_EQ(0u, encoded_span.size()); EXPECT_EQ(0, memcmp("jxll", container.boxes[0].type, 4)); } TEST(EncodeTest, CodestreamLevelVerificationTest) { - JxlPixelFormat pixel_format = {4, JXL_TYPE_UINT16, JXL_BIG_ENDIAN, 0}; + JxlPixelFormat pixel_format = {4, JXL_TYPE_UINT8, JXL_BIG_ENDIAN, 0}; JxlBasicInfo basic_info; jxl::test::JxlBasicInfoSetFromPixelFormat(&basic_info, &pixel_format); basic_info.xsize = 64; basic_info.ysize = 64; @@ -753,25 +757,24 @@ EXPECT_EQ(5, JxlEncoderGetRequiredCodestreamLevel(enc.get())); // Set an image dimension that is too large for level 5, but fits in level 10 basic_info.xsize = 1ull << 30ull; + EXPECT_EQ(JXL_ENC_ERROR, JxlEncoderSetBasicInfo(enc.get(), &basic_info)); + EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetCodestreamLevel(enc.get(), 10)); EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetBasicInfo(enc.get(), &basic_info)); - EXPECT_EQ(10, JxlEncoderGetRequiredCodestreamLevel(enc.get())); // Set an image dimension that is too large even for level 10 basic_info.xsize = 1ull << 31ull; - EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetBasicInfo(enc.get(), &basic_info)); - - EXPECT_EQ(-1, JxlEncoderGetRequiredCodestreamLevel(enc.get())); + EXPECT_EQ(JXL_ENC_ERROR, JxlEncoderSetBasicInfo(enc.get(), &basic_info)); } TEST(EncodeTest, JXL_TRANSCODE_JPEG_TEST(JPEGReconstructionTest)) { const std::string jpeg_path = - "imagecompression.info/flower_foveon.png.im_q85_420.jpg"; + "third_party/imagecompression.info/flower_foveon.png.im_q85_420.jpg"; const jxl::PaddedBytes orig = jxl::ReadTestData(jpeg_path); JxlEncoderPtr enc = JxlEncoderMake(nullptr); JxlEncoderFrameSettings* frame_settings = JxlEncoderFrameSettingsCreate(enc.get(), NULL); @@ -898,10 +901,11 @@ basic_info.orientation = JXL_ORIENT_ROTATE_90_CW; basic_info.animation.tps_numerator = 55; basic_info.animation.tps_denominator = 77; basic_info.animation.num_loops = 10; basic_info.animation.have_timecodes = JXL_TRUE; + EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetCodestreamLevel(enc.get(), 10)); EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetBasicInfo(enc.get(), &basic_info)); JxlColorEncoding color_encoding; JxlColorEncodingSetToSRGB(&color_encoding, /*is_gray=*/false); EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetColorEncoding(enc.get(), &color_encoding)); @@ -999,10 +1003,11 @@ basic_info.ysize = ysize; basic_info.have_animation = true; basic_info.animation.tps_numerator = 1000; basic_info.animation.tps_denominator = 1; basic_info.animation.have_timecodes = JXL_TRUE; + EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetCodestreamLevel(enc.get(), 10)); EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetBasicInfo(enc.get(), &basic_info)); JxlColorEncoding color_encoding; JxlColorEncodingSetToSRGB(&color_encoding, /*is_gray=*/false); EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetColorEncoding(enc.get(), &color_encoding)); @@ -1100,10 +1105,11 @@ jxl::test::JxlBasicInfoSetFromPixelFormat(&basic_info, &pixel_format); // Encoding a 300x300 frame in an image that is only 100x100 basic_info.xsize = 100; basic_info.ysize = 100; basic_info.uses_original_profile = JXL_TRUE; + EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetCodestreamLevel(enc.get(), 10)); EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetBasicInfo(enc.get(), &basic_info)); JxlColorEncoding color_encoding; JxlColorEncodingSetToSRGB(&color_encoding, /*is_gray=*/false); EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetColorEncoding(enc.get(), &color_encoding)); @@ -1192,10 +1198,11 @@ JxlBasicInfo basic_info; jxl::test::JxlBasicInfoSetFromPixelFormat(&basic_info, &pixel_format); basic_info.xsize = xsize; basic_info.ysize = ysize; basic_info.uses_original_profile = false; + EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetCodestreamLevel(enc.get(), 10)); EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetBasicInfo(enc.get(), &basic_info)); JxlColorEncoding color_encoding; JxlColorEncodingSetToSRGB(&color_encoding, /*is_gray=*/false); EXPECT_EQ(JXL_ENC_SUCCESS, @@ -1298,11 +1305,11 @@ TEST(EncodeTest, JXL_TRANSCODE_JPEG_TEST(JPEGFrameTest)) { for (int skip_basic_info = 0; skip_basic_info < 2; skip_basic_info++) { for (int skip_color_encoding = 0; skip_color_encoding < 2; skip_color_encoding++) { const std::string jpeg_path = - "imagecompression.info/flower_foveon_cropped.jpg"; + "third_party/imagecompression.info/flower_foveon_cropped.jpg"; const jxl::PaddedBytes orig = jxl::ReadTestData(jpeg_path); jxl::CodecInOut orig_io; ASSERT_TRUE(SetFromBytes(jxl::Span(orig), &orig_io, /*pool=*/nullptr)); diff --git a/third_party/jpeg-xl/lib/jxl/exif.h b/third_party/jpeg-xl/lib/jxl/exif.h new file mode 100644 --- /dev/null +++ b/third_party/jpeg-xl/lib/jxl/exif.h @@ -0,0 +1,27 @@ +// Copyright (c) the JPEG XL Project Authors. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#ifndef LIB_JXL_EXIF_H_ +#define LIB_JXL_EXIF_H_ + +// Basic parsing of Exif (just enough for the render-impacting things +// like orientation) + +#include "lib/jxl/base/padded_bytes.h" +#include "lib/jxl/image_metadata.h" + +namespace jxl { + +// Parses the Exif data just enough to extract any render-impacting info. +// If the Exif data is invalid or could not be parsed, then it is treated +// as a no-op. +void InterpretExif(const std::vector& exif, CodecMetadata* metadata); + +// Sets the Exif orientation to the identity, to avoid repeated orientation +void ResetExifOrientation(std::vector& exif); + +} // namespace jxl + +#endif // LIB_JXL_EXIF_H_ diff --git a/third_party/jpeg-xl/lib/jxl/exif.cc b/third_party/jpeg-xl/lib/jxl/exif.cc new file mode 100644 --- /dev/null +++ b/third_party/jpeg-xl/lib/jxl/exif.cc @@ -0,0 +1,89 @@ +// Copyright (c) the JPEG XL Project Authors. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#include "lib/jxl/exif.h" + +namespace jxl { + +constexpr uint16_t kExifOrientationTag = 274; + +// Checks if a blob looks like Exif, and if so, sets bigendian +// according to the tiff endianness +bool IsExif(const std::vector& exif, bool* bigendian) { + if (exif.size() < 12) return false; // not enough bytes for a valid exif blob + const uint8_t* t = exif.data(); + if (LoadLE32(t) == 0x2A004D4D) { + *bigendian = true; + return true; + } else if (LoadLE32(t) == 0x002A4949) { + *bigendian = false; + return true; + } + return false; // not a valid tiff header +} + +// Finds the position of an Exif tag, or 0 if it is not found +size_t FindExifTagPosition(const std::vector& exif, uint16_t tagname) { + bool bigendian; + if (!IsExif(exif, &bigendian)) return 0; + const uint8_t* t = exif.data() + 4; + uint32_t offset = (bigendian ? LoadBE32(t) : LoadLE32(t)); + if (exif.size() < 12 + offset + 2 || offset < 8) return 0; + t += offset - 4; + uint16_t nb_tags = (bigendian ? LoadBE16(t) : LoadLE16(t)); + t += 2; + while (nb_tags > 0) { + if (t + 12 >= exif.data() + exif.size()) return 0; + uint16_t tag = (bigendian ? LoadBE16(t) : LoadLE16(t)); + t += 2; + if (tag == tagname) return static_cast(t - exif.data()); + t += 10; + nb_tags--; + } + return 0; +} + +// TODO (jon): tag 1 can be used to represent Adobe RGB 1998 if it has value +// "R03" +// TODO (jon): set intrinsic dimensions according to +// https://discourse.wicg.io/t/proposal-exif-image-resolution-auto-and-from-image/4326/24 +void InterpretExif(const std::vector& exif, CodecMetadata* metadata) { + bool bigendian; + if (!IsExif(exif, &bigendian)) return; + size_t o_pos = FindExifTagPosition(exif, kExifOrientationTag); + if (o_pos) { + const uint8_t* t = exif.data() + o_pos; + uint16_t type = (bigendian ? LoadBE16(t) : LoadLE16(t)); + t += 2; + uint32_t count = (bigendian ? LoadBE32(t) : LoadLE32(t)); + t += 4; + uint16_t value = (bigendian ? LoadBE16(t) : LoadLE16(t)); + t += 4; + if (type == 3 && count == 1 && value >= 1 && value <= 8) { + metadata->m.orientation = value; + } + } +} + +void ResetExifOrientation(std::vector& exif) { + bool bigendian; + if (!IsExif(exif, &bigendian)) return; + size_t o_pos = FindExifTagPosition(exif, kExifOrientationTag); + if (o_pos) { + uint8_t* t = exif.data() + o_pos; + uint16_t type = (bigendian ? LoadBE16(t) : LoadLE16(t)); + t += 2; + uint32_t count = (bigendian ? LoadBE32(t) : LoadLE32(t)); + t += 4; + if (type == 3 && count == 1) { + if (bigendian) + StoreBE16(1, t); + else + StoreLE16(1, t); + } + } +} + +} // namespace jxl diff --git a/third_party/jpeg-xl/lib/jxl/frame_header.h b/third_party/jpeg-xl/lib/jxl/frame_header.h --- a/third_party/jpeg-xl/lib/jxl/frame_header.h +++ b/third_party/jpeg-xl/lib/jxl/frame_header.h @@ -38,11 +38,11 @@ BitsOffset(10, 48), 0, &name_length)); if (visitor->IsReading()) { name->resize(name_length); } for (size_t i = 0; i < name_length; i++) { - uint32_t c = (*name)[i]; + uint32_t c = static_cast((*name)[i]); JXL_QUIET_RETURN_IF_ERROR(visitor->Bits(8, 0, &c)); (*name)[i] = static_cast(c); } return true; } diff --git a/third_party/jpeg-xl/lib/jxl/icc_codec.cc b/third_party/jpeg-xl/lib/jxl/icc_codec.cc --- a/third_party/jpeg-xl/lib/jxl/icc_codec.cc +++ b/third_party/jpeg-xl/lib/jxl/icc_codec.cc @@ -20,23 +20,10 @@ #include "lib/jxl/icc_codec_common.h" namespace jxl { namespace { -uint64_t DecodeVarInt(const uint8_t* input, size_t inputSize, size_t* pos) { - size_t i; - uint64_t ret = 0; - for (i = 0; *pos + i < inputSize && i < 10; ++i) { - ret |= uint64_t(input[*pos + i] & 127) << uint64_t(7 * i); - // If the next-byte flag is not set, stop - if ((input[*pos + i] & 128) == 0) break; - } - // TODO: Return a decoding error if i == 10. - *pos += i + 1; - return ret; -} - // Shuffles or interleaves bytes, for example with width 2, turns "ABCDabcd" // into "AaBbCcDc". Transposes a matrix of ceil(size / width) columns and // width rows. There are size elements, size may be < width * height, if so the // last elements of the rightmost column are missing, the missing spots are // transposed along with the filled spots, and the result has the missing diff --git a/third_party/jpeg-xl/lib/jxl/image.h b/third_party/jpeg-xl/lib/jxl/image.h --- a/third_party/jpeg-xl/lib/jxl/image.h +++ b/third_party/jpeg-xl/lib/jxl/image.h @@ -192,122 +192,133 @@ // Rectangular region in image(s). Factoring this out of Image instead of // shifting the pointer by x0/y0 allows this to apply to multiple images with // different resolutions (e.g. color transform and quantization field). // Can compare using SameSize(rect1, rect2). -class Rect { +template +class RectT { public: // Most windows are xsize_max * ysize_max, except those on the borders where // begin + size_max > end. - constexpr Rect(size_t xbegin, size_t ybegin, size_t xsize_max, - size_t ysize_max, size_t xend, size_t yend) + constexpr RectT(T xbegin, T ybegin, size_t xsize_max, size_t ysize_max, + T xend, T yend) : x0_(xbegin), y0_(ybegin), xsize_(ClampedSize(xbegin, xsize_max, xend)), ysize_(ClampedSize(ybegin, ysize_max, yend)) {} // Construct with origin and known size (typically from another Rect). - constexpr Rect(size_t xbegin, size_t ybegin, size_t xsize, size_t ysize) + constexpr RectT(T xbegin, T ybegin, size_t xsize, size_t ysize) : x0_(xbegin), y0_(ybegin), xsize_(xsize), ysize_(ysize) {} // Construct a rect that covers a whole image/plane/ImageBundle etc. - template - explicit Rect(const Image& image) - : Rect(0, 0, image.xsize(), image.ysize()) {} + template + explicit RectT(const ImageT& image) + : RectT(0, 0, image.xsize(), image.ysize()) {} - Rect() : Rect(0, 0, 0, 0) {} + RectT() : RectT(0, 0, 0, 0) {} - Rect(const Rect&) = default; - Rect& operator=(const Rect&) = default; + RectT(const RectT&) = default; + RectT& operator=(const RectT&) = default; // Construct a subrect that resides in an image/plane/ImageBundle etc. - template - Rect Crop(const Image& image) const { - return Rect(x0_, y0_, xsize_, ysize_, image.xsize(), image.ysize()); + template + RectT Crop(const ImageT& image) const { + return Intersection(RectT(image)); } // 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); + RectT Crop(size_t area_xsize, size_t area_ysize) const { + return Intersection(RectT(0, 0, 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 { + RectT Lines(size_t y, size_t num) const { JXL_DASSERT(y + num <= ysize_); - return Rect(x0_, y0_ + y, xsize_, num); + return RectT(x0_, y0_ + y, xsize_, num); } - Rect Line(size_t y) const { return Lines(y, 1); } + RectT Line(size_t y) const { return Lines(y, 1); } - JXL_MUST_USE_RESULT Rect Intersection(const Rect& other) const { - return Rect(std::max(x0_, other.x0_), std::max(y0_, other.y0_), xsize_, - ysize_, std::min(x0_ + xsize_, other.x0_ + other.xsize_), - std::min(y0_ + ysize_, other.y0_ + other.ysize_)); + JXL_MUST_USE_RESULT RectT Intersection(const RectT& other) const { + return RectT(std::max(x0_, other.x0_), std::max(y0_, other.y0_), xsize_, + ysize_, std::min(x0_ + xsize_, other.x0_ + other.xsize_), + std::min(y0_ + ysize_, other.y0_ + other.ysize_)); } - JXL_MUST_USE_RESULT Rect Translate(int64_t x_offset, int64_t y_offset) const { - return Rect(x0_ + x_offset, y0_ + y_offset, xsize_, ysize_); + JXL_MUST_USE_RESULT RectT Translate(int64_t x_offset, + int64_t y_offset) const { + return RectT(x0_ + x_offset, y0_ + y_offset, xsize_, ysize_); } - template - T* Row(Plane* image, size_t y) const { + template + V* Row(Plane* image, size_t y) const { + JXL_DASSERT(y + y0_ >= 0); return image->Row(y + y0_) + x0_; } - template - const T* Row(const Plane* image, size_t y) const { + template + const V* Row(const Plane* image, size_t y) const { + JXL_DASSERT(y + y0_ >= 0); return image->Row(y + y0_) + x0_; } - template - T* PlaneRow(Image3* image, const size_t c, size_t y) const { + template + V* PlaneRow(Image3* image, const size_t c, size_t y) const { + JXL_DASSERT(y + y0_ >= 0); return image->PlaneRow(c, y + y0_) + x0_; } - template - const T* ConstRow(const Plane& image, size_t y) const { + template + const V* ConstRow(const Plane& image, size_t y) const { + JXL_DASSERT(y + y0_ >= 0); return image.ConstRow(y + y0_) + x0_; } - template - const T* ConstPlaneRow(const Image3& image, size_t c, size_t y) const { + template + const V* ConstPlaneRow(const Image3& image, size_t c, size_t y) const { + JXL_DASSERT(y + y0_ >= 0); return image.ConstPlaneRow(c, y + y0_) + x0_; } - bool IsInside(const Rect& other) const { + bool IsInside(const RectT& other) const { return x0_ >= other.x0() && x0_ + xsize_ <= other.x0() + other.xsize_ && y0_ >= other.y0() && y0_ + ysize_ <= other.y0() + other.ysize(); } // Returns true if this Rect fully resides in the given image. ImageT could be // Plane or Image3; however if ImageT is Rect, results are nonsensical. template bool IsInside(const ImageT& image) const { - return (x0_ + xsize_ <= image.xsize()) && (y0_ + ysize_ <= image.ysize()); + return IsInside(RectT(image)); } - size_t x0() const { return x0_; } - size_t y0() const { return y0_; } + T x0() const { return x0_; } + T y0() const { return y0_; } size_t xsize() const { return xsize_; } size_t ysize() const { return ysize_; } + T x1() const { return x0_ + xsize_; } + T y1() const { return y0_ + ysize_; } private: // Returns size_max, or whatever is left in [begin, end). - static constexpr size_t ClampedSize(size_t begin, size_t size_max, - size_t end) { - return (begin + size_max <= end) ? size_max - : (end > begin ? end - begin : 0); + static constexpr size_t ClampedSize(T begin, size_t size_max, T end) { + return (static_cast(begin + size_max) <= end) + ? size_max + : (end > begin ? end - begin : 0); } - size_t x0_; - size_t y0_; + T x0_; + T y0_; size_t xsize_; size_t ysize_; }; +using Rect = RectT; + // Currently, we abuse Image to either refer to an image that owns its storage // or one that doesn't. In similar vein, we abuse Image* function parameters to // either mean "assign to me" or "fill the provided image with data". // Hopefully, the "assign to me" meaning will go away and most images in the // codebase will not be backed by own storage. When this happens we can redesign diff --git a/third_party/jpeg-xl/lib/jxl/image.cc b/third_party/jpeg-xl/lib/jxl/image.cc --- a/third_party/jpeg-xl/lib/jxl/image.cc +++ b/third_party/jpeg-xl/lib/jxl/image.cc @@ -131,51 +131,10 @@ std::swap(orig_ysize_, other.orig_ysize_); std::swap(bytes_per_row_, other.bytes_per_row_); std::swap(bytes_, other.bytes_); } -ImageB ImageFromPacked(const uint8_t* packed, const size_t xsize, - const size_t ysize, const size_t bytes_per_row) { - JXL_ASSERT(bytes_per_row >= xsize); - ImageB image(xsize, ysize); - PROFILER_FUNC; - for (size_t y = 0; y < ysize; ++y) { - uint8_t* const JXL_RESTRICT row = image.Row(y); - const uint8_t* const JXL_RESTRICT packed_row = packed + y * bytes_per_row; - memcpy(row, packed_row, xsize); - } - return image; -} - -// Note that using mirroring here gives slightly worse results. -ImageF PadImage(const ImageF& in, const size_t xsize, const size_t ysize) { - JXL_ASSERT(xsize >= in.xsize()); - JXL_ASSERT(ysize >= in.ysize()); - ImageF out(xsize, ysize); - size_t y = 0; - for (; y < in.ysize(); ++y) { - const float* JXL_RESTRICT row_in = in.ConstRow(y); - float* JXL_RESTRICT row_out = out.Row(y); - memcpy(row_out, row_in, in.xsize() * sizeof(row_in[0])); - const int lastcol = in.xsize() - 1; - const float lastval = row_out[lastcol]; - for (size_t x = in.xsize(); x < xsize; ++x) { - row_out[x] = lastval; - } - } - - // TODO(janwas): no need to copy if we can 'extend' image: if rows are - // pointers to any memory? Or allocate larger image before IO? - const int lastrow = in.ysize() - 1; - for (; y < ysize; ++y) { - const float* JXL_RESTRICT row_in = out.ConstRow(lastrow); - float* JXL_RESTRICT row_out = out.Row(y); - memcpy(row_out, row_in, xsize * sizeof(row_out[0])); - } - return out; -} - Image3F PadImageMirror(const Image3F& in, const size_t xborder, const size_t yborder) { size_t xsize = in.xsize(); size_t ysize = in.ysize(); Image3F out(xsize + 2 * xborder, ysize + 2 * yborder); @@ -214,23 +173,10 @@ } } return out; } -Image3F PadImageToMultiple(const Image3F& in, const size_t N) { - PROFILER_FUNC; - const size_t xsize_blocks = DivCeil(in.xsize(), N); - const size_t ysize_blocks = DivCeil(in.ysize(), N); - const size_t xsize = N * xsize_blocks; - const size_t ysize = N * ysize_blocks; - ImageF out[3]; - for (size_t c = 0; c < 3; ++c) { - out[c] = PadImage(in.Plane(c), xsize, ysize); - } - return Image3F(std::move(out[0]), std::move(out[1]), std::move(out[2])); -} - void PadImageToBlockMultipleInPlace(Image3F* JXL_RESTRICT in) { PROFILER_FUNC; const size_t xsize_orig = in->xsize(); const size_t ysize_orig = in->ysize(); const size_t xsize = RoundUpToBlockDim(xsize_orig); diff --git a/third_party/jpeg-xl/lib/jxl/image_ops.h b/third_party/jpeg-xl/lib/jxl/image_ops.h --- a/third_party/jpeg-xl/lib/jxl/image_ops.h +++ b/third_party/jpeg-xl/lib/jxl/image_ops.h @@ -787,23 +787,14 @@ T* JXL_RESTRICT row = rect.Row(image, y); memset(row, 0, rect.xsize() * sizeof(T)); } } -// First, image is padded horizontally, with the rightmost value. -// Next, image is padded vertically, by repeating the last line. -ImageF PadImage(const ImageF& in, size_t xsize, size_t ysize); - // Pad an image with xborder columns on each vertical side and yboder rows // above and below, mirroring the image. Image3F PadImageMirror(const Image3F& in, size_t xborder, size_t yborder); -// First, image is padded horizontally, with the rightmost value. -// Next, image is padded vertically, by repeating the last line. -// Prefer PadImageToBlockMultipleInPlace if padding to kBlockDim. -Image3F PadImageToMultiple(const Image3F& in, size_t N); - // Same as above, but operates in-place. Assumes that the `in` image was // allocated large enough. void PadImageToBlockMultipleInPlace(Image3F* JXL_RESTRICT in); // Downsamples an image by a given factor. diff --git a/third_party/jpeg-xl/lib/jxl/jpeg/enc_jpeg_data.h b/third_party/jpeg-xl/lib/jxl/jpeg/enc_jpeg_data.h --- a/third_party/jpeg-xl/lib/jxl/jpeg/enc_jpeg_data.h +++ b/third_party/jpeg-xl/lib/jxl/jpeg/enc_jpeg_data.h @@ -6,15 +6,17 @@ #ifndef LIB_JXL_JPEG_ENC_JPEG_DATA_H_ #define LIB_JXL_JPEG_ENC_JPEG_DATA_H_ #include "lib/jxl/base/padded_bytes.h" #include "lib/jxl/codec_in_out.h" +#include "lib/jxl/enc_params.h" #include "lib/jxl/jpeg/jpeg_data.h" namespace jxl { namespace jpeg { -Status EncodeJPEGData(JPEGData& jpeg_data, PaddedBytes* bytes); +Status EncodeJPEGData(JPEGData& jpeg_data, PaddedBytes* bytes, + const CompressParams& cparams); Status SetColorEncodingFromJpegData(const jpeg::JPEGData& jpg, ColorEncoding* color_encoding); /** diff --git a/third_party/jpeg-xl/lib/jxl/jpeg/enc_jpeg_data.cc b/third_party/jpeg-xl/lib/jxl/jpeg/enc_jpeg_data.cc --- a/third_party/jpeg-xl/lib/jxl/jpeg/enc_jpeg_data.cc +++ b/third_party/jpeg-xl/lib/jxl/jpeg/enc_jpeg_data.cc @@ -228,22 +228,25 @@ } return color_encoding->SetICC(std::move(icc_profile)); } -Status EncodeJPEGData(JPEGData& jpeg_data, PaddedBytes* bytes) { +Status EncodeJPEGData(JPEGData& jpeg_data, PaddedBytes* bytes, + const CompressParams& cparams) { jpeg_data.app_marker_type.resize(jpeg_data.app_data.size(), AppMarkerType::kUnknown); JXL_RETURN_IF_ERROR(DetectIccProfile(jpeg_data)); JXL_RETURN_IF_ERROR(DetectBlobs(jpeg_data)); BitWriter writer; JXL_RETURN_IF_ERROR(Bundle::Write(jpeg_data, &writer, 0, nullptr)); writer.ZeroPadToByte(); *bytes = std::move(writer).TakeBytes(); BrotliEncoderState* brotli_enc = BrotliEncoderCreateInstance(nullptr, nullptr, nullptr); - BrotliEncoderSetParameter(brotli_enc, BROTLI_PARAM_QUALITY, 11); + int effort = cparams.brotli_effort; + if (effort < 0) effort = 11 - static_cast(cparams.speed_tier); + BrotliEncoderSetParameter(brotli_enc, BROTLI_PARAM_QUALITY, effort); size_t total_data = 0; for (size_t i = 0; i < jpeg_data.app_data.size(); i++) { if (jpeg_data.app_marker_type[i] != AppMarkerType::kUnknown) { continue; } 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 --- 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 @@ -93,10 +93,11 @@ size_t marker_len = ReadUint16(data, pos); int precision = ReadUint8(data, pos); int height = ReadUint16(data, pos); int width = ReadUint16(data, pos); int num_components = ReadUint8(data, pos); + // 'jbrd' is hardcoded for 8bits: JXL_JPEG_VERIFY_INPUT(precision, 8, 8, PRECISION); JXL_JPEG_VERIFY_INPUT(height, 1, kMaxDimPixels, HEIGHT); JXL_JPEG_VERIFY_INPUT(width, 1, kMaxDimPixels, WIDTH); JXL_JPEG_VERIFY_INPUT(num_components, 1, kMaxComponents, NUMCOMP); JXL_JPEG_VERIFY_LEN(3 * num_components); diff --git a/third_party/jpeg-xl/lib/jxl/jxl_test.cc b/third_party/jpeg-xl/lib/jxl/jxl_test.cc --- a/third_party/jpeg-xl/lib/jxl/jxl_test.cc +++ b/third_party/jpeg-xl/lib/jxl/jxl_test.cc @@ -122,11 +122,11 @@ #endif TEST(JxlTest, RoundtripTinyFast) { ThreadPool* pool = nullptr; const PaddedBytes orig = - ReadTestData("wesaturate/500px/u76c0g_bliznaca_srgb8.png"); + ReadTestData("third_party/wesaturate/500px/u76c0g_bliznaca_srgb8.png"); CodecInOut io; ASSERT_TRUE(SetFromBytes(Span(orig), &io, pool)); io.ShrinkTo(32, 32); CompressParams cparams; @@ -140,11 +140,11 @@ } TEST(JxlTest, RoundtripSmallD1) { ThreadPool* pool = nullptr; const PaddedBytes orig = - ReadTestData("wesaturate/500px/u76c0g_bliznaca_srgb8.png"); + ReadTestData("third_party/wesaturate/500px/u76c0g_bliznaca_srgb8.png"); CompressParams cparams; cparams.butteraugli_distance = 1.0; DecompressParams dparams; CodecInOut io_out; @@ -181,11 +181,11 @@ } TEST(JxlTest, RoundtripOtherTransforms) { ThreadPool* pool = nullptr; const PaddedBytes orig = - ReadTestData("wesaturate/64px/a2d1un_nkitzmiller_srgb8.png"); + ReadTestData("third_party/wesaturate/64px/a2d1un_nkitzmiller_srgb8.png"); std::unique_ptr io = jxl::make_unique(); ASSERT_TRUE(SetFromBytes(Span(orig), io.get(), pool)); CompressParams cparams; // Slow modes access linear image for adaptive quant search @@ -213,11 +213,11 @@ } TEST(JxlTest, RoundtripResample2) { ThreadPool* pool = nullptr; const PaddedBytes orig = - ReadTestData("wesaturate/500px/u76c0g_bliznaca_srgb8.png"); + ReadTestData("third_party/wesaturate/500px/u76c0g_bliznaca_srgb8.png"); CodecInOut io; ASSERT_TRUE(SetFromBytes(Span(orig), &io, pool)); io.ShrinkTo(io.xsize(), io.ysize()); CompressParams cparams; cparams.resampling = 2; @@ -230,11 +230,11 @@ } TEST(JxlTest, RoundtripResample2MT) { ThreadPoolInternal pool(4); const PaddedBytes orig = - ReadTestData("imagecompression.info/flower_foveon.png"); + ReadTestData("third_party/imagecompression.info/flower_foveon.png"); // image has to be large enough to have multiple groups after downsampling CodecInOut io; ASSERT_TRUE(SetFromBytes(Span(orig), &io, &pool)); CompressParams cparams; cparams.resampling = 2; @@ -252,11 +252,11 @@ // 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"); + ReadTestData("third_party/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); @@ -275,11 +275,11 @@ TEST(JxlTest, RoundtripOutOfOrderProcessingBorder) { FakeParallelRunner fake_pool(/*order_seed=*/47, /*num_threads=*/8); ThreadPool pool(&JxlFakeParallelRunner, &fake_pool); const PaddedBytes orig = - ReadTestData("imagecompression.info/flower_foveon.png"); + ReadTestData("third_party/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); @@ -298,11 +298,11 @@ } TEST(JxlTest, RoundtripResample4) { ThreadPool* pool = nullptr; const PaddedBytes orig = - ReadTestData("wesaturate/500px/u76c0g_bliznaca_srgb8.png"); + ReadTestData("third_party/wesaturate/500px/u76c0g_bliznaca_srgb8.png"); CodecInOut io; ASSERT_TRUE(SetFromBytes(Span(orig), &io, pool)); io.ShrinkTo(io.xsize(), io.ysize()); CompressParams cparams; cparams.resampling = 4; @@ -315,11 +315,11 @@ } TEST(JxlTest, RoundtripResample8) { ThreadPool* pool = nullptr; const PaddedBytes orig = - ReadTestData("wesaturate/500px/u76c0g_bliznaca_srgb8.png"); + ReadTestData("third_party/wesaturate/500px/u76c0g_bliznaca_srgb8.png"); CodecInOut io; ASSERT_TRUE(SetFromBytes(Span(orig), &io, pool)); io.ShrinkTo(io.xsize(), io.ysize()); CompressParams cparams; cparams.resampling = 8; @@ -332,11 +332,11 @@ } TEST(JxlTest, RoundtripUnalignedD2) { ThreadPool* pool = nullptr; const PaddedBytes orig = - ReadTestData("wesaturate/500px/u76c0g_bliznaca_srgb8.png"); + ReadTestData("third_party/wesaturate/500px/u76c0g_bliznaca_srgb8.png"); CodecInOut io; ASSERT_TRUE(SetFromBytes(Span(orig), &io, pool)); io.ShrinkTo(io.xsize() / 12, io.ysize() / 7); CompressParams cparams; @@ -353,11 +353,11 @@ #if JXL_TEST_NL TEST(JxlTest, RoundtripMultiGroupNL) { ThreadPoolInternal pool(4); const PaddedBytes orig = - ReadTestData("imagecompression.info/flower_foveon.png"); + ReadTestData("third_party/imagecompression.info/flower_foveon.png"); CodecInOut io; ASSERT_TRUE(SetFromBytes(Span(orig), &io, &pool)); io.ShrinkTo(600, 1024); // partial X, full Y group CompressParams cparams; @@ -382,11 +382,11 @@ #endif TEST(JxlTest, RoundtripMultiGroup) { ThreadPoolInternal pool(4); const PaddedBytes orig = - ReadTestData("imagecompression.info/flower_foveon.png"); + ReadTestData("third_party/imagecompression.info/flower_foveon.png"); CodecInOut io; ASSERT_TRUE(SetFromBytes(Span(orig), &io, &pool)); io.ShrinkTo(600, 1024); CompressParams cparams; @@ -408,11 +408,11 @@ } TEST(JxlTest, RoundtripLargeFast) { ThreadPoolInternal pool(8); const PaddedBytes orig = - ReadTestData("imagecompression.info/flower_foveon.png"); + ReadTestData("third_party/imagecompression.info/flower_foveon.png"); CodecInOut io; ASSERT_TRUE(SetFromBytes(Span(orig), &io, &pool)); CompressParams cparams; cparams.speed_tier = SpeedTier::kSquirrel; @@ -423,11 +423,11 @@ } TEST(JxlTest, RoundtripDotsForceEpf) { ThreadPoolInternal pool(8); const PaddedBytes orig = - ReadTestData("wesaturate/500px/cvo9xd_keong_macan_srgb8.png"); + ReadTestData("third_party/wesaturate/500px/cvo9xd_keong_macan_srgb8.png"); CodecInOut io; ASSERT_TRUE(SetFromBytes(Span(orig), &io, &pool)); CompressParams cparams; cparams.epf = 2; @@ -443,11 +443,11 @@ // which involves additional processing including adaptive reconstruction. // Failing this may be a sign of race conditions or invalid memory accesses. TEST(JxlTest, RoundtripD2Consistent) { ThreadPoolInternal pool(8); const PaddedBytes orig = - ReadTestData("imagecompression.info/flower_foveon.png"); + ReadTestData("third_party/imagecompression.info/flower_foveon.png"); CodecInOut io; ASSERT_TRUE(SetFromBytes(Span(orig), &io, &pool)); CompressParams cparams; cparams.speed_tier = SpeedTier::kSquirrel; @@ -476,11 +476,11 @@ // Same as above, but for full image, testing multiple groups. TEST(JxlTest, RoundtripLargeConsistent) { ThreadPoolInternal pool(8); const PaddedBytes orig = - ReadTestData("imagecompression.info/flower_foveon.png"); + ReadTestData("third_party/imagecompression.info/flower_foveon.png"); CodecInOut io; ASSERT_TRUE(SetFromBytes(Span(orig), &io, &pool)); CompressParams cparams; cparams.speed_tier = SpeedTier::kSquirrel; @@ -506,11 +506,11 @@ #if JXL_TEST_NL TEST(JxlTest, RoundtripSmallNL) { ThreadPool* pool = nullptr; const PaddedBytes orig = - ReadTestData("wesaturate/500px/u76c0g_bliznaca_srgb8.png"); + ReadTestData("third_party/wesaturate/500px/u76c0g_bliznaca_srgb8.png"); CodecInOut io; ASSERT_TRUE(SetFromBytes(Span(orig), &io, pool)); io.ShrinkTo(io.xsize() / 8, io.ysize() / 8); CompressParams cparams; @@ -527,11 +527,11 @@ #endif TEST(JxlTest, RoundtripNoGaborishNoAR) { ThreadPool* pool = nullptr; const PaddedBytes orig = - ReadTestData("wesaturate/500px/u76c0g_bliznaca_srgb8.png"); + ReadTestData("third_party/wesaturate/500px/u76c0g_bliznaca_srgb8.png"); CodecInOut io; ASSERT_TRUE(SetFromBytes(Span(orig), &io, pool)); CompressParams cparams; cparams.gaborish = Override::kOff; @@ -547,11 +547,11 @@ } TEST(JxlTest, RoundtripSmallNoGaborish) { ThreadPool* pool = nullptr; const PaddedBytes orig = - ReadTestData("wesaturate/500px/u76c0g_bliznaca_srgb8.png"); + ReadTestData("third_party/wesaturate/500px/u76c0g_bliznaca_srgb8.png"); CodecInOut io; ASSERT_TRUE(SetFromBytes(Span(orig), &io, pool)); io.ShrinkTo(io.xsize() / 8, io.ysize() / 8); CompressParams cparams; @@ -593,11 +593,11 @@ CodecInOut io2; EXPECT_LE(Roundtrip(&io, cparams, dparams, pool, &io2), 2000u); EXPECT_THAT(ButteraugliDistance(io, io2, cparams.ba_params, GetJxlCms(), /*distmap=*/nullptr, pool), - IsSlightlyBelow(0.2f)); + IsSlightlyBelow(0.04f)); } TEST(JxlTest, RoundtripSmallPatches) { ThreadPool* pool = nullptr; CodecInOut io; @@ -621,11 +621,11 @@ CodecInOut io2; EXPECT_LE(Roundtrip(&io, cparams, dparams, pool, &io2), 2000u); EXPECT_THAT(ButteraugliDistance(io, io2, cparams.ba_params, GetJxlCms(), /*distmap=*/nullptr, pool), - IsSlightlyBelow(0.2f)); + IsSlightlyBelow(0.04f)); } // Test header encoding of original bits per sample TEST(JxlTest, RoundtripImageBundleOriginalBits) { ThreadPool* pool = nullptr; @@ -698,12 +698,12 @@ } } TEST(JxlTest, RoundtripGrayscale) { ThreadPool* pool = nullptr; - const PaddedBytes orig = - ReadTestData("wesaturate/500px/cvo9xd_keong_macan_grayscale.png"); + const PaddedBytes orig = ReadTestData( + "third_party/wesaturate/500px/cvo9xd_keong_macan_grayscale.png"); CodecInOut io; ASSERT_TRUE(SetFromBytes(Span(orig), &io, pool)); ASSERT_NE(io.xsize(), 0u); io.ShrinkTo(128, 128); EXPECT_TRUE(io.Main().IsGray()); @@ -754,12 +754,12 @@ } } TEST(JxlTest, RoundtripAlpha) { ThreadPool* pool = nullptr; - const PaddedBytes orig = - ReadTestData("wesaturate/500px/tmshre_riaphotographs_alpha.png"); + const PaddedBytes orig = ReadTestData( + "third_party/wesaturate/500px/tmshre_riaphotographs_alpha.png"); CodecInOut io; ASSERT_TRUE(SetFromBytes(Span(orig), &io, pool)); ASSERT_NE(io.xsize(), 0u); ASSERT_TRUE(io.metadata.m.HasAlpha()); @@ -784,17 +784,17 @@ EXPECT_LE(compressed.size(), 10077u); EXPECT_THAT(ButteraugliDistance(io, io2, cparams.ba_params, GetJxlCms(), /*distmap=*/nullptr, pool), - IsSlightlyBelow(1.4)); + IsSlightlyBelow(1.2)); } TEST(JxlTest, RoundtripAlphaPremultiplied) { ThreadPool* pool = nullptr; - const PaddedBytes orig = - ReadTestData("wesaturate/500px/tmshre_riaphotographs_alpha.png"); + const PaddedBytes orig = ReadTestData( + "third_party/wesaturate/500px/tmshre_riaphotographs_alpha.png"); CodecInOut io, io_nopremul; ASSERT_TRUE(SetFromBytes(Span(orig), &io, pool)); ASSERT_TRUE(SetFromBytes(Span(orig), &io_nopremul, pool)); ASSERT_NE(io.xsize(), 0u); @@ -819,22 +819,22 @@ EXPECT_LE(compressed.size(), 10000u); EXPECT_THAT(ButteraugliDistance(io, io2, cparams.ba_params, GetJxlCms(), /*distmap=*/nullptr, pool), - IsSlightlyBelow(1.4)); + IsSlightlyBelow(1.2)); io2.Main().UnpremultiplyAlpha(); EXPECT_THAT( ButteraugliDistance(io_nopremul, io2, cparams.ba_params, GetJxlCms(), /*distmap=*/nullptr, pool), - IsSlightlyBelow(1.8)); + IsSlightlyBelow(1.35)); } TEST(JxlTest, RoundtripAlphaResampling) { ThreadPool* pool = nullptr; - const PaddedBytes orig = - ReadTestData("wesaturate/500px/tmshre_riaphotographs_alpha.png"); + const PaddedBytes orig = ReadTestData( + "third_party/wesaturate/500px/tmshre_riaphotographs_alpha.png"); CodecInOut io; ASSERT_TRUE(SetFromBytes(Span(orig), &io, pool)); ASSERT_NE(io.xsize(), 0u); ASSERT_TRUE(io.metadata.m.HasAlpha()); @@ -862,12 +862,12 @@ IsSlightlyBelow(4.7)); } TEST(JxlTest, RoundtripAlphaResamplingOnlyAlpha) { ThreadPool* pool = nullptr; - const PaddedBytes orig = - ReadTestData("wesaturate/500px/tmshre_riaphotographs_alpha.png"); + const PaddedBytes orig = ReadTestData( + "third_party/wesaturate/500px/tmshre_riaphotographs_alpha.png"); CodecInOut io; ASSERT_TRUE(SetFromBytes(Span(orig), &io, pool)); ASSERT_NE(io.xsize(), 0u); ASSERT_TRUE(io.metadata.m.HasAlpha()); @@ -894,12 +894,12 @@ IsSlightlyBelow(1.85)); } TEST(JxlTest, RoundtripAlphaNonMultipleOf8) { ThreadPool* pool = nullptr; - const PaddedBytes orig = - ReadTestData("wesaturate/500px/tmshre_riaphotographs_alpha.png"); + const PaddedBytes orig = ReadTestData( + "third_party/wesaturate/500px/tmshre_riaphotographs_alpha.png"); CodecInOut io; ASSERT_TRUE(SetFromBytes(Span(orig), &io, pool)); ASSERT_NE(io.xsize(), 0u); ASSERT_TRUE(io.metadata.m.HasAlpha()); @@ -920,19 +920,19 @@ EXPECT_TRUE(EncodeFile(cparams, &io, &enc_state, &compressed, GetJxlCms(), aux_out, pool)); CodecInOut io2; EXPECT_TRUE(DecodeFile(dparams, compressed, &io2, pool)); - EXPECT_LE(compressed.size(), 200u); + EXPECT_LE(compressed.size(), 180u); // TODO(robryk): Fix the following line in presence of different alpha_bits in // the two contexts. // EXPECT_TRUE(SamePixels(io.Main().alpha(), io2.Main().alpha())); // TODO(robryk): Fix the distance estimate used in the encoder. EXPECT_THAT(ButteraugliDistance(io, io2, cparams.ba_params, GetJxlCms(), /*distmap=*/nullptr, pool), - IsSlightlyBelow(0.8)); + IsSlightlyBelow(0.9)); } TEST(JxlTest, RoundtripAlpha16) { ThreadPoolInternal pool(4); @@ -963,12 +963,11 @@ ASSERT_TRUE(io.metadata.m.HasAlpha()); ASSERT_TRUE(io.Main().HasAlpha()); CompressParams cparams; cparams.butteraugli_distance = 0.5; - // Prevent the test to be too slow, does not affect alpha - cparams.speed_tier = SpeedTier::kSquirrel; + cparams.speed_tier = SpeedTier::kWombat; DecompressParams dparams; io.metadata.m.SetUintSamples(16); EXPECT_TRUE(io.metadata.m.color_encoding.tf.IsSRGB()); PassesEncoderState enc_state; @@ -976,29 +975,30 @@ PaddedBytes compressed; EXPECT_TRUE(EncodeFile(cparams, &io, &enc_state, &compressed, GetJxlCms(), aux_out, &pool)); CodecInOut io2; EXPECT_TRUE(DecodeFile(dparams, compressed, &io2, &pool)); - - EXPECT_TRUE(SamePixels(*io.Main().alpha(), *io2.Main().alpha())); + EXPECT_THAT(ButteraugliDistance(io, io2, cparams.ba_params, GetJxlCms(), + /*distmap=*/nullptr, &pool), + IsSlightlyBelow(0.8)); } namespace { CompressParams CParamsForLossless() { CompressParams cparams; cparams.modular_mode = true; cparams.color_transform = jxl::ColorTransform::kNone; - cparams.quality_pair = {100, 100}; + cparams.butteraugli_distance = 0.f; cparams.options.predictor = {Predictor::Weighted}; return cparams; } } // namespace TEST(JxlTest, JXL_SLOW_TEST(RoundtripLossless8)) { ThreadPoolInternal pool(8); - const PaddedBytes orig = - ReadTestData("wesaturate/500px/tmshre_riaphotographs_srgb8.png"); + const PaddedBytes orig = ReadTestData( + "third_party/wesaturate/500px/tmshre_riaphotographs_srgb8.png"); CodecInOut io; ASSERT_TRUE(SetFromBytes(Span(orig), &io, &pool)); CompressParams cparams = CParamsForLossless(); DecompressParams dparams; @@ -1021,12 +1021,12 @@ EXPECT_EQ(ComputeDistance2(io.Main(), io2.Main(), GetJxlCms()), 0.0); } TEST(JxlTest, JXL_SLOW_TEST(RoundtripLosslessNoEncoderFastPathWP)) { ThreadPoolInternal pool(8); - const PaddedBytes orig = - ReadTestData("wesaturate/500px/tmshre_riaphotographs_srgb8.png"); + const PaddedBytes orig = ReadTestData( + "third_party/wesaturate/500px/tmshre_riaphotographs_srgb8.png"); CodecInOut io; ASSERT_TRUE(SetFromBytes(Span(orig), &io, &pool)); CompressParams cparams = CParamsForLossless(); cparams.speed_tier = SpeedTier::kFalcon; @@ -1038,12 +1038,12 @@ EXPECT_EQ(ComputeDistance2(io.Main(), io2.Main(), GetJxlCms()), 0.0); } TEST(JxlTest, JXL_SLOW_TEST(RoundtripLosslessNoEncoderFastPathGradient)) { ThreadPoolInternal pool(8); - const PaddedBytes orig = - ReadTestData("wesaturate/500px/tmshre_riaphotographs_srgb8.png"); + const PaddedBytes orig = ReadTestData( + "third_party/wesaturate/500px/tmshre_riaphotographs_srgb8.png"); CodecInOut io; ASSERT_TRUE(SetFromBytes(Span(orig), &io, &pool)); CompressParams cparams = CParamsForLossless(); cparams.speed_tier = SpeedTier::kThunder; @@ -1056,12 +1056,12 @@ EXPECT_EQ(ComputeDistance2(io.Main(), io2.Main(), GetJxlCms()), 0.0); } TEST(JxlTest, JXL_SLOW_TEST(RoundtripLosslessNoEncoderVeryFastPathGradient)) { ThreadPoolInternal pool(8); - const PaddedBytes orig = - ReadTestData("wesaturate/500px/tmshre_riaphotographs_srgb8.png"); + const PaddedBytes orig = ReadTestData( + "third_party/wesaturate/500px/tmshre_riaphotographs_srgb8.png"); CodecInOut io; ASSERT_TRUE(SetFromBytes(Span(orig), &io, &pool)); CompressParams cparams = CParamsForLossless(); cparams.speed_tier = SpeedTier::kLightning; @@ -1077,12 +1077,12 @@ EXPECT_EQ(ComputeDistance2(io.Main(), io3.Main(), GetJxlCms()), 0.0); } TEST(JxlTest, JXL_SLOW_TEST(RoundtripLossless8Falcon)) { ThreadPoolInternal pool(8); - const PaddedBytes orig = - ReadTestData("wesaturate/500px/tmshre_riaphotographs_srgb8.png"); + const PaddedBytes orig = ReadTestData( + "third_party/wesaturate/500px/tmshre_riaphotographs_srgb8.png"); CodecInOut io; ASSERT_TRUE(SetFromBytes(Span(orig), &io, &pool)); CompressParams cparams = CParamsForLossless(); cparams.speed_tier = SpeedTier::kFalcon; @@ -1094,12 +1094,12 @@ /*distmap=*/nullptr, &pool)); } TEST(JxlTest, RoundtripLossless8Alpha) { ThreadPool* pool = nullptr; - const PaddedBytes orig = - ReadTestData("wesaturate/500px/tmshre_riaphotographs_alpha.png"); + const PaddedBytes orig = ReadTestData( + "third_party/wesaturate/500px/tmshre_riaphotographs_alpha.png"); CodecInOut io; ASSERT_TRUE(SetFromBytes(Span(orig), &io, pool)); EXPECT_EQ(8u, io.metadata.m.GetAlphaBits()); EXPECT_EQ(8u, io.metadata.m.bit_depth.bits_per_sample); EXPECT_FALSE(io.metadata.m.bit_depth.floating_point_sample); @@ -1219,15 +1219,15 @@ } TEST(JxlTest, RoundtripYCbCr420) { ThreadPool* pool = nullptr; const PaddedBytes orig = - ReadTestData("imagecompression.info/flower_foveon.png"); + ReadTestData("third_party/imagecompression.info/flower_foveon.png"); CodecInOut io; ASSERT_TRUE(SetFromBytes(Span(orig), &io, pool)); - const PaddedBytes yuv420 = - ReadTestData("imagecompression.info/flower_foveon.png.ffmpeg.y4m"); + const PaddedBytes yuv420 = ReadTestData( + "third_party/imagecompression.info/flower_foveon.png.ffmpeg.y4m"); CodecInOut io2; ASSERT_TRUE(test::DecodeImageY4M(Span(yuv420), &io2)); CompressParams cparams = CParamsForLossless(); cparams.speed_tier = SpeedTier::kThunder; @@ -1249,11 +1249,11 @@ } TEST(JxlTest, RoundtripDots) { ThreadPool* pool = nullptr; const PaddedBytes orig = - ReadTestData("wesaturate/500px/cvo9xd_keong_macan_srgb8.png"); + ReadTestData("third_party/wesaturate/500px/cvo9xd_keong_macan_srgb8.png"); CodecInOut io; ASSERT_TRUE(SetFromBytes(Span(orig), &io, pool)); ASSERT_NE(io.xsize(), 0u); @@ -1282,11 +1282,11 @@ } TEST(JxlTest, RoundtripNoise) { ThreadPool* pool = nullptr; const PaddedBytes orig = - ReadTestData("wesaturate/500px/cvo9xd_keong_macan_srgb8.png"); + ReadTestData("third_party/wesaturate/500px/cvo9xd_keong_macan_srgb8.png"); CodecInOut io; ASSERT_TRUE(SetFromBytes(Span(orig), &io, pool)); ASSERT_NE(io.xsize(), 0u); @@ -1313,12 +1313,12 @@ IsSlightlyBelow(1.6)); } TEST(JxlTest, RoundtripLossless8Gray) { ThreadPool* pool = nullptr; - const PaddedBytes orig = - ReadTestData("wesaturate/500px/cvo9xd_keong_macan_grayscale.png"); + const PaddedBytes orig = ReadTestData( + "third_party/wesaturate/500px/cvo9xd_keong_macan_grayscale.png"); CodecInOut io; ASSERT_TRUE(SetFromBytes(Span(orig), &io, pool)); CompressParams cparams = CParamsForLossless(); DecompressParams dparams; @@ -1446,11 +1446,11 @@ /*aux_out=*/nullptr, pool)); jpegxl::tools::JpegXlContainer enc_container; enc_container.codestream = std::move(codestream); jpeg::JPEGData data_in = *io.Main().jpeg_data; jxl::PaddedBytes jpeg_data; - EXPECT_TRUE(EncodeJPEGData(data_in, &jpeg_data)); + EXPECT_TRUE(EncodeJPEGData(data_in, &jpeg_data, cparams)); enc_container.jpeg_reconstruction = jpeg_data.data(); enc_container.jpeg_reconstruction_size = jpeg_data.size(); EXPECT_TRUE(EncodeJpegXlContainerOneShot(enc_container, &compressed)); jpegxl::tools::JpegXlContainer container; @@ -1472,22 +1472,22 @@ return compressed.size(); } TEST(JxlTest, JXL_TRANSCODE_JPEG_TEST(RoundtripJpegRecompression444)) { ThreadPoolInternal pool(8); - const PaddedBytes orig = - ReadTestData("imagecompression.info/flower_foveon.png.im_q85_444.jpg"); + const PaddedBytes orig = ReadTestData( + "third_party/imagecompression.info/flower_foveon.png.im_q85_444.jpg"); // JPEG size is 326'916 bytes. EXPECT_LE(RoundtripJpeg(orig, &pool), 256000u); } #if JPEGXL_ENABLE_JPEG TEST(JxlTest, JXL_TRANSCODE_JPEG_TEST(RoundtripJpegRecompressionToPixels)) { ThreadPoolInternal pool(8); - const PaddedBytes orig = - ReadTestData("imagecompression.info/flower_foveon.png.im_q85_444.jpg"); + const PaddedBytes orig = ReadTestData( + "third_party/imagecompression.info/flower_foveon.png.im_q85_444.jpg"); CodecInOut io; ASSERT_TRUE(jpeg::DecodeImageJPG(Span(orig), &io)); CodecInOut io2; ASSERT_TRUE(SetFromBytes(Span(orig), &io2, &pool)); @@ -1506,12 +1506,12 @@ IsSlightlyBelow(12)); } TEST(JxlTest, JXL_TRANSCODE_JPEG_TEST(RoundtripJpegRecompressionToPixels420)) { ThreadPoolInternal pool(8); - const PaddedBytes orig = - ReadTestData("imagecompression.info/flower_foveon.png.im_q85_420.jpg"); + const PaddedBytes orig = ReadTestData( + "third_party/imagecompression.info/flower_foveon.png.im_q85_420.jpg"); CodecInOut io; ASSERT_TRUE(jpeg::DecodeImageJPG(Span(orig), &io)); CodecInOut io2; ASSERT_TRUE(SetFromBytes(Span(orig), &io2, &pool)); @@ -1529,12 +1529,12 @@ } TEST(JxlTest, JXL_TRANSCODE_JPEG_TEST(RoundtripJpegRecompressionToPixels420EarlyFlush)) { ThreadPoolInternal pool(8); - const PaddedBytes orig = - ReadTestData("imagecompression.info/flower_foveon.png.im_q85_420.jpg"); + const PaddedBytes orig = ReadTestData( + "third_party/imagecompression.info/flower_foveon.png.im_q85_420.jpg"); CodecInOut io; ASSERT_TRUE(jpeg::DecodeImageJPG(Span(orig), &io)); CodecInOut io2; ASSERT_TRUE(SetFromBytes(Span(orig), &io2, &pool)); @@ -1553,12 +1553,12 @@ } TEST(JxlTest, JXL_TRANSCODE_JPEG_TEST(RoundtripJpegRecompressionToPixels420Mul16)) { ThreadPoolInternal pool(8); - const PaddedBytes orig = - ReadTestData("imagecompression.info/flower_foveon_cropped.jpg"); + const PaddedBytes orig = ReadTestData( + "third_party/imagecompression.info/flower_foveon_cropped.jpg"); CodecInOut io; ASSERT_TRUE(jpeg::DecodeImageJPG(Span(orig), &io)); CodecInOut io2; ASSERT_TRUE(SetFromBytes(Span(orig), &io2, &pool)); @@ -1577,11 +1577,12 @@ TEST(JxlTest, JXL_TRANSCODE_JPEG_TEST(RoundtripJpegRecompressionToPixels_asymmetric)) { ThreadPoolInternal pool(8); const PaddedBytes orig = ReadTestData( - "imagecompression.info/flower_foveon.png.im_q85_asymmetric.jpg"); + "third_party/imagecompression.info/" + "flower_foveon.png.im_q85_asymmetric.jpg"); CodecInOut io; ASSERT_TRUE(jpeg::DecodeImageJPG(Span(orig), &io)); CodecInOut io2; ASSERT_TRUE(SetFromBytes(Span(orig), &io2, &pool)); @@ -1600,79 +1601,82 @@ #endif TEST(JxlTest, JXL_TRANSCODE_JPEG_TEST(RoundtripJpegRecompressionGray)) { ThreadPoolInternal pool(8); - const PaddedBytes orig = - ReadTestData("imagecompression.info/flower_foveon.png.im_q85_gray.jpg"); + const PaddedBytes orig = ReadTestData( + "third_party/imagecompression.info/flower_foveon.png.im_q85_gray.jpg"); // JPEG size is 167'025 bytes. EXPECT_LE(RoundtripJpeg(orig, &pool), 140000u); } TEST(JxlTest, JXL_TRANSCODE_JPEG_TEST(RoundtripJpegRecompression420)) { ThreadPoolInternal pool(8); - const PaddedBytes orig = - ReadTestData("imagecompression.info/flower_foveon.png.im_q85_420.jpg"); + const PaddedBytes orig = ReadTestData( + "third_party/imagecompression.info/flower_foveon.png.im_q85_420.jpg"); // JPEG size is 226'018 bytes. EXPECT_LE(RoundtripJpeg(orig, &pool), 181050u); } TEST(JxlTest, JXL_TRANSCODE_JPEG_TEST(RoundtripJpegRecompression_luma_subsample)) { ThreadPoolInternal pool(8); const PaddedBytes orig = ReadTestData( - "imagecompression.info/flower_foveon.png.im_q85_luma_subsample.jpg"); + "third_party/imagecompression.info/" + "flower_foveon.png.im_q85_luma_subsample.jpg"); // JPEG size is 216'069 bytes. EXPECT_LE(RoundtripJpeg(orig, &pool), 181000u); } TEST(JxlTest, JXL_TRANSCODE_JPEG_TEST(RoundtripJpegRecompression444_12)) { // 444 JPEG that has an interesting sampling-factor (1x2, 1x2, 1x2). ThreadPoolInternal pool(8); const PaddedBytes orig = ReadTestData( - "imagecompression.info/flower_foveon.png.im_q85_444_1x2.jpg"); + "third_party/imagecompression.info/flower_foveon.png.im_q85_444_1x2.jpg"); // JPEG size is 329'942 bytes. EXPECT_LE(RoundtripJpeg(orig, &pool), 256000u); } TEST(JxlTest, JXL_TRANSCODE_JPEG_TEST(RoundtripJpegRecompression422)) { ThreadPoolInternal pool(8); - const PaddedBytes orig = - ReadTestData("imagecompression.info/flower_foveon.png.im_q85_422.jpg"); + const PaddedBytes orig = ReadTestData( + "third_party/imagecompression.info/flower_foveon.png.im_q85_422.jpg"); // JPEG size is 265'590 bytes. EXPECT_LE(RoundtripJpeg(orig, &pool), 209000u); } TEST(JxlTest, JXL_TRANSCODE_JPEG_TEST(RoundtripJpegRecompression440)) { ThreadPoolInternal pool(8); - const PaddedBytes orig = - ReadTestData("imagecompression.info/flower_foveon.png.im_q85_440.jpg"); + const PaddedBytes orig = ReadTestData( + "third_party/imagecompression.info/flower_foveon.png.im_q85_440.jpg"); // JPEG size is 262'249 bytes. EXPECT_LE(RoundtripJpeg(orig, &pool), 209000u); } TEST(JxlTest, JXL_TRANSCODE_JPEG_TEST(RoundtripJpegRecompression_asymmetric)) { // 2x vertical downsample of one chroma channel, 2x horizontal downsample of // the other. ThreadPoolInternal pool(8); const PaddedBytes orig = ReadTestData( - "imagecompression.info/flower_foveon.png.im_q85_asymmetric.jpg"); + "third_party/imagecompression.info/" + "flower_foveon.png.im_q85_asymmetric.jpg"); // JPEG size is 262'249 bytes. EXPECT_LE(RoundtripJpeg(orig, &pool), 209000u); } TEST(JxlTest, JXL_TRANSCODE_JPEG_TEST(RoundtripJpegRecompression420Progr)) { ThreadPoolInternal pool(8); const PaddedBytes orig = ReadTestData( - "imagecompression.info/flower_foveon.png.im_q85_420_progr.jpg"); + "third_party/imagecompression.info/" + "flower_foveon.png.im_q85_420_progr.jpg"); EXPECT_LE(RoundtripJpeg(orig, &pool), 181000u); } TEST(JxlTest, RoundtripProgressive) { ThreadPoolInternal pool(4); const PaddedBytes orig = - ReadTestData("imagecompression.info/flower_foveon.png"); + ReadTestData("third_party/imagecompression.info/flower_foveon.png"); CodecInOut io; ASSERT_TRUE(SetFromBytes(Span(orig), &io, &pool)); io.ShrinkTo(600, 1024); CompressParams cparams; @@ -1684,10 +1688,10 @@ cparams.progressive_mode = true; CodecInOut io2; EXPECT_LE(Roundtrip(&io, cparams, dparams, &pool, &io2), 40000u); EXPECT_THAT(ButteraugliDistance(io, io2, cparams.ba_params, GetJxlCms(), /*distmap=*/nullptr, &pool), - IsSlightlyBelow(2.5f)); + IsSlightlyBelow(1.1f)); } } // namespace } // namespace jxl diff --git a/third_party/jpeg-xl/lib/jxl/modular/encoding/context_predict.h b/third_party/jpeg-xl/lib/jxl/modular/encoding/context_predict.h --- a/third_party/jpeg-xl/lib/jxl/modular/encoding/context_predict.h +++ b/third_party/jpeg-xl/lib/jxl/modular/encoding/context_predict.h @@ -302,11 +302,11 @@ uint32_t context; Predictor predictor; int64_t offset; int32_t multiplier; }; - LookupResult Lookup(const Properties &properties) const { + JXL_INLINE LookupResult Lookup(const Properties &properties) const { uint32_t pos = 0; while (true) { const FlatDecisionNode &node = nodes_[pos]; if (node.property0 < 0) { return {node.childID, node.predictor, node.predictor_offset, @@ -414,10 +414,11 @@ enum PredictorMode { kUseTree = 1, kUseWP = 2, kForceComputeProperties = 4, kAllPredictions = 8, + kNoEdgeCases = 16 }; JXL_INLINE pixel_type_w PredictOne(Predictor p, pixel_type_w left, pixel_type_w top, pixel_type_w toptop, pixel_type_w topleft, pixel_type_w topright, @@ -459,26 +460,28 @@ return pixel_type_w{0}; } } template -inline PredictionResult Predict( +JXL_INLINE PredictionResult Predict( Properties *p, size_t w, const pixel_type *JXL_RESTRICT pp, const intptr_t onerow, const size_t x, const size_t y, Predictor predictor, const MATreeLookup *lookup, const Channel *references, weighted::State *wp_state, pixel_type_w *predictions) { // We start in position 3 because of 2 static properties + y. size_t offset = 3; constexpr bool compute_properties = mode & kUseTree || mode & kForceComputeProperties; - pixel_type_w left = (x ? pp[-1] : (y ? pp[-onerow] : 0)); - pixel_type_w top = (y ? pp[-onerow] : left); - pixel_type_w topleft = (x && y ? pp[-1 - onerow] : left); - pixel_type_w topright = (x + 1 < w && y ? pp[1 - onerow] : top); - pixel_type_w leftleft = (x > 1 ? pp[-2] : left); - pixel_type_w toptop = (y > 1 ? pp[-onerow - onerow] : top); - pixel_type_w toprightright = (x + 2 < w && y ? pp[2 - onerow] : topright); + constexpr bool nec = mode & kNoEdgeCases; + pixel_type_w left = (nec || x ? pp[-1] : (y ? pp[-onerow] : 0)); + pixel_type_w top = (nec || y ? pp[-onerow] : left); + pixel_type_w topleft = (nec || (x && y) ? pp[-1 - onerow] : left); + pixel_type_w topright = (nec || (x + 1 < w && y) ? pp[1 - onerow] : top); + pixel_type_w leftleft = (nec || x > 1 ? pp[-2] : left); + pixel_type_w toptop = (nec || y > 1 ? pp[-onerow - onerow] : top); + pixel_type_w toprightright = + (nec || (x + 2 < w && y) ? pp[2 - onerow] : topright); if (compute_properties) { // location (*p)[offset++] = x; // neighbors @@ -504,11 +507,11 @@ pixel_type_w wp_pred = 0; if (mode & kUseWP) { wp_pred = wp_state->Predict( x, y, w, top, left, topright, topleft, toptop, p, offset); } - if (compute_properties) { + if (!nec && compute_properties) { offset += weighted::kNumProperties; // Extra properties. const pixel_type *JXL_RESTRICT rp = references->Row(x); for (size_t i = 0; i < references->w; i++) { (*p)[offset++] = rp[i]; @@ -563,10 +566,19 @@ const Channel &references) { return detail::Predict( p, w, pp, onerow, x, y, Predictor::Zero, &tree_lookup, &references, /*wp_state=*/nullptr, /*predictions=*/nullptr); } +// Only use for y > 1, x > 1, x < w-2, and empty references +JXL_INLINE PredictionResult +PredictTreeNoWPNEC(Properties *p, size_t w, const pixel_type *JXL_RESTRICT pp, + const intptr_t onerow, const int x, const int y, + const MATreeLookup &tree_lookup, const Channel &references) { + return detail::Predict( + p, w, pp, onerow, x, y, Predictor::Zero, &tree_lookup, &references, + /*wp_state=*/nullptr, /*predictions=*/nullptr); +} inline PredictionResult PredictTreeWP(Properties *p, size_t w, const pixel_type *JXL_RESTRICT pp, const intptr_t onerow, const int x, const int y, diff --git a/third_party/jpeg-xl/lib/jxl/modular/encoding/encoding.cc b/third_party/jpeg-xl/lib/jxl/modular/encoding/encoding.cc --- a/third_party/jpeg-xl/lib/jxl/modular/encoding/encoding.cc +++ b/third_party/jpeg-xl/lib/jxl/modular/encoding/encoding.cc @@ -184,18 +184,27 @@ pixel_type v = make_pixel(value, multiplier, offset); for (size_t y = 0; y < channel.h; y++) { pixel_type *JXL_RESTRICT r = channel.Row(y); std::fill(r, r + channel.w, v); } - } else { JXL_DEBUG_V(8, "Fast track."); - for (size_t y = 0; y < channel.h; y++) { - pixel_type *JXL_RESTRICT r = channel.Row(y); - for (size_t x = 0; x < channel.w; x++) { - uint32_t v = reader->ReadHybridUintClustered(ctx_id, br); - r[x] = make_pixel(v, multiplier, offset); + if (multiplier == 1 && offset == 0) { + for (size_t y = 0; y < channel.h; y++) { + pixel_type *JXL_RESTRICT r = channel.Row(y); + for (size_t x = 0; x < channel.w; x++) { + uint32_t v = reader->ReadHybridUintClustered(ctx_id, br); + r[x] = UnpackSigned(v); + } + } + } else { + for (size_t y = 0; y < channel.h; y++) { + pixel_type *JXL_RESTRICT r = channel.Row(y); + for (size_t x = 0; x < channel.w; x++) { + uint32_t v = reader->ReadHybridUintClustered(ctx_id, br); + r[x] = make_pixel(v, multiplier, offset); + } } } } } else if (predictor == Predictor::Gradient && offset == 0 && multiplier == 1 && reader->HuffRleOnly()) { @@ -353,16 +362,40 @@ Channel references(properties.size() - kNumNonrefProperties, channel.w); for (size_t y = 0; y < channel.h; y++) { pixel_type *JXL_RESTRICT p = channel.Row(y); PrecomputeReferences(channel, y, *image, chan, &references); InitPropsRow(&properties, static_props, y); - for (size_t x = 0; x < channel.w; x++) { - PredictionResult res = - PredictTreeNoWP(&properties, channel.w, p + x, onerow, x, y, - tree_lookup, references); - uint64_t v = reader->ReadHybridUintClustered(res.context, br); - p[x] = make_pixel(v, res.multiplier, res.guess); + if (y > 1 && channel.w > 8 && references.w == 0) { + for (size_t x = 0; x < 2; x++) { + PredictionResult res = + PredictTreeNoWP(&properties, channel.w, p + x, onerow, x, y, + tree_lookup, references); + uint64_t v = reader->ReadHybridUintClustered(res.context, br); + p[x] = make_pixel(v, res.multiplier, res.guess); + } + for (size_t x = 2; x < channel.w - 2; x++) { + PredictionResult res = + PredictTreeNoWPNEC(&properties, channel.w, p + x, onerow, x, y, + tree_lookup, references); + uint64_t v = reader->ReadHybridUintClustered(res.context, br); + p[x] = make_pixel(v, res.multiplier, res.guess); + } + for (size_t x = channel.w - 2; x < channel.w; x++) { + PredictionResult res = + PredictTreeNoWP(&properties, channel.w, p + x, onerow, x, y, + tree_lookup, references); + uint64_t v = reader->ReadHybridUintClustered(res.context, br); + p[x] = make_pixel(v, res.multiplier, res.guess); + } + } else { + for (size_t x = 0; x < channel.w; x++) { + PredictionResult res = + PredictTreeNoWP(&properties, channel.w, p + x, onerow, x, y, + tree_lookup, references); + uint64_t v = reader->ReadHybridUintClustered(res.context, br); + p[x] = make_pixel(v, res.multiplier, res.guess); + } } } } else { JXL_DEBUG_V(8, "Slowest track."); MATreeLookup tree_lookup(tree); diff --git a/third_party/jpeg-xl/lib/jxl/modular/transform/enc_palette.cc b/third_party/jpeg-xl/lib/jxl/modular/transform/enc_palette.cc --- a/third_party/jpeg-xl/lib/jxl/modular/transform/enc_palette.cc +++ b/third_party/jpeg-xl/lib/jxl/modular/transform/enc_palette.cc @@ -548,22 +548,19 @@ Status FwdPalette(Image &input, uint32_t begin_c, uint32_t end_c, uint32_t &nb_colors, uint32_t &nb_deltas, bool ordered, bool lossy, Predictor &predictor, const weighted::Header &wp_header) { PaletteIterationData palette_iteration_data; - uint32_t nb = end_c - begin_c + 1; uint32_t nb_colors_orig = nb_colors; uint32_t nb_deltas_orig = nb_deltas; - // if no channel palette special case - if ((lossy || nb != 1) && input.bitdepth >= 8) { + // preprocessing pass in case of lossy palette + if (lossy && input.bitdepth >= 8) { JXL_RETURN_IF_ERROR(FwdPaletteIteration( - input, begin_c, end_c, nb_colors, nb_deltas, ordered, lossy, predictor, - wp_header, palette_iteration_data)); + input, begin_c, end_c, nb_colors_orig, nb_deltas_orig, ordered, lossy, + predictor, wp_header, palette_iteration_data)); } palette_iteration_data.final_run = true; - nb_colors = nb_colors_orig; - nb_deltas = nb_deltas_orig; return FwdPaletteIteration(input, begin_c, end_c, nb_colors, nb_deltas, ordered, lossy, predictor, wp_header, palette_iteration_data); } diff --git a/third_party/jpeg-xl/lib/jxl/modular/transform/squeeze.h b/third_party/jpeg-xl/lib/jxl/modular/transform/squeeze.h --- a/third_party/jpeg-xl/lib/jxl/modular/transform/squeeze.h +++ b/third_party/jpeg-xl/lib/jxl/modular/transform/squeeze.h @@ -73,14 +73,10 @@ if (diff - (diff & 1) < 2 * (a - n)) diff = 2 * (a - n); } return diff; } -void InvHSqueeze(Image &input, int c, int rc, ThreadPool *pool); - -void InvVSqueeze(Image &input, int c, int rc, ThreadPool *pool); - void DefaultSqueezeParameters(std::vector *parameters, const Image &image); Status CheckMetaSqueezeParams(const SqueezeParams ¶meter, int num_channels); diff --git a/third_party/jpeg-xl/lib/jxl/modular/transform/squeeze.cc b/third_party/jpeg-xl/lib/jxl/modular/transform/squeeze.cc --- a/third_party/jpeg-xl/lib/jxl/modular/transform/squeeze.cc +++ b/third_party/jpeg-xl/lib/jxl/modular/transform/squeeze.cc @@ -10,17 +10,84 @@ #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" +#undef HWY_TARGET_INCLUDE +#define HWY_TARGET_INCLUDE "lib/jxl/modular/transform/squeeze.cc" +#include +#include +#include "lib/jxl/simd_util-inl.h" + +HWY_BEFORE_NAMESPACE(); namespace jxl { +namespace HWY_NAMESPACE { + +using hwy::HWY_NAMESPACE::RebindToUnsigned; +using hwy::HWY_NAMESPACE::ShiftLeft; +using hwy::HWY_NAMESPACE::ShiftRight; + +#if HWY_TARGET != HWY_SCALAR + +JXL_INLINE void FastUnsqueeze(const pixel_type *JXL_RESTRICT p_residual, + const pixel_type *JXL_RESTRICT p_avg, + const pixel_type *JXL_RESTRICT p_navg, + const pixel_type *p_pout, + pixel_type *JXL_RESTRICT p_out, + pixel_type *p_nout) { + const HWY_CAPPED(pixel_type, 8) d; + const RebindToUnsigned du; + const size_t N = Lanes(d); + auto onethird = Set(d, 0x55555556); + for (size_t x = 0; x < 8; x += N) { + auto avg = Load(d, p_avg + x); + auto next_avg = Load(d, p_navg + x); + auto top = Load(d, p_pout + x); + // Equivalent to SmoothTendency(top,avg,next_avg), but without branches + auto Ba = top - avg; + auto an = avg - next_avg; + auto nonmono = Ba ^ an; + auto absBa = Abs(Ba); + auto absan = Abs(an); + auto absBn = Abs(top - next_avg); + // Compute a3 = absBa / 3 + auto a3e = BitCast(d, ShiftRight<32>(MulEven(absBa, onethird))); + auto a3oi = MulEven(Reverse(d, absBa), onethird); + auto a3o = BitCast( + d, Reverse(hwy::HWY_NAMESPACE::Repartition(), + a3oi)); + auto a3 = OddEven(a3o, a3e); + a3 += absBn + Set(d, 2); + auto absdiff = ShiftRight<2>(a3); + auto skipdiff = Ba != Zero(d); + skipdiff = And(skipdiff, an != Zero(d)); + skipdiff = And(skipdiff, nonmono < Zero(d)); + auto absBa2 = ShiftLeft<1>(absBa) + (absdiff & Set(d, 1)); + absdiff = + IfThenElse(absdiff > absBa2, ShiftLeft<1>(absBa) + Set(d, 1), absdiff); + auto absan2 = ShiftLeft<1>(absan); + absdiff = + IfThenElse(absdiff + (absdiff & Set(d, 1)) > absan2, absan2, absdiff); + auto diff1 = IfThenElse(top < next_avg, Neg(absdiff), absdiff); + auto tendency = IfThenZeroElse(skipdiff, diff1); + + auto diff_minus_tendency = Load(d, p_residual + x); + auto diff = diff_minus_tendency + tendency; + auto out = avg + ShiftRight<1>( + diff + BitCast(d, ShiftRight<31>(BitCast(du, diff)))); + Store(out, d, p_out + x); + Store(out - diff, d, p_nout + x); + } +} + +#endif Status InvHSqueeze(Image &input, uint32_t c, uint32_t rc, ThreadPool *pool) { JXL_ASSERT(c < input.channel.size()); JXL_ASSERT(rc < input.channel.size()); - const Channel &chin = input.channel[c]; + Channel &chin = input.channel[c]; const Channel &chin_residual = input.channel[rc]; // These must be valid since we ran MetaApply already. JXL_ASSERT(chin.w == DivCeil(chin.w + chin_residual.w, 2)); JXL_ASSERT(chin.h == chin_residual.h); @@ -40,46 +107,88 @@ if (chin_residual.h == 0) { // Short-circuit: channel with no pixels. input.channel[c] = std::move(chout); return true; } + auto unsqueeze_row = [&](size_t y, size_t x0) { + const pixel_type *JXL_RESTRICT p_residual = chin_residual.Row(y); + const pixel_type *JXL_RESTRICT p_avg = chin.Row(y); + pixel_type *JXL_RESTRICT p_out = chout.Row(y); + for (size_t x = x0; x < chin_residual.w; x++) { + pixel_type diff_minus_tendency = p_residual[x]; + pixel_type avg = p_avg[x]; + pixel_type next_avg = (x + 1 < chin.w ? p_avg[x + 1] : avg); + pixel_type left = (x ? p_out[(x << 1) - 1] : avg); + pixel_type tendency = SmoothTendency(left, avg, next_avg); + pixel_type diff = diff_minus_tendency + tendency; + pixel_type A = avg + (diff / 2); + p_out[(x << 1)] = A; + pixel_type B = A - diff; + p_out[(x << 1) + 1] = B; + } + if (chout.w & 1) p_out[chout.w - 1] = p_avg[chin.w - 1]; + }; - JXL_RETURN_IF_ERROR(RunOnPool( - pool, 0, chin.h, ThreadPool::NoInit, - [&](const uint32_t task, size_t /* thread */) { - const size_t y = task; - const pixel_type *JXL_RESTRICT p_residual = chin_residual.Row(y); - const pixel_type *JXL_RESTRICT p_avg = chin.Row(y); - pixel_type *JXL_RESTRICT p_out = chout.Row(y); + // somewhat complicated trickery just to be able to SIMD this. + // Horizontal unsqueeze has horizontal data dependencies, so we do + // 8 rows at a time and treat it as a vertical unsqueeze of a + // transposed 8x8 block (or 9x8 for one input). + static constexpr const size_t kRowsPerThread = 8; + const auto unsqueeze_span = [&](const uint32_t task, size_t /* thread */) { + const size_t y0 = task * kRowsPerThread; + const size_t rows = std::min(kRowsPerThread, chin.h - y0); + size_t x = 0; - // special case for x=0 so we don't have to check x>0 - pixel_type_w avg = p_avg[0]; - pixel_type_w next_avg = (1 < chin.w ? p_avg[1] : avg); - pixel_type_w tendency = SmoothTendency(avg, avg, next_avg); - pixel_type_w diff = p_residual[0] + tendency; - pixel_type_w A = - ((avg * 2) + diff + (diff > 0 ? -(diff & 1) : (diff & 1))) >> 1; - pixel_type_w B = A - diff; - p_out[0] = A; - p_out[1] = B; +#if HWY_TARGET != HWY_SCALAR + intptr_t onerow_in = chin.plane.PixelsPerRow(); + intptr_t onerow_inr = chin_residual.plane.PixelsPerRow(); + intptr_t onerow_out = chout.plane.PixelsPerRow(); + const pixel_type *JXL_RESTRICT p_residual = chin_residual.Row(y0); + const pixel_type *JXL_RESTRICT p_avg = chin.Row(y0); + pixel_type *JXL_RESTRICT p_out = chout.Row(y0); + HWY_ALIGN pixel_type b_p_avg[9 * kRowsPerThread]; + HWY_ALIGN pixel_type b_p_residual[8 * kRowsPerThread]; + HWY_ALIGN pixel_type b_p_out_even[8 * kRowsPerThread]; + HWY_ALIGN pixel_type b_p_out_odd[8 * kRowsPerThread]; + HWY_ALIGN pixel_type b_p_out_evenT[8 * kRowsPerThread]; + HWY_ALIGN pixel_type b_p_out_oddT[8 * kRowsPerThread]; + const HWY_CAPPED(pixel_type, 8) d; + const size_t N = Lanes(d); + if (chin_residual.w > 16 && rows == kRowsPerThread) { + for (; x < chin_residual.w - 9; x += 8) { + Transpose8x8Block(p_residual + x, b_p_residual, onerow_inr); + Transpose8x8Block(p_avg + x, b_p_avg, onerow_in); + for (size_t y = 0; y < kRowsPerThread; y++) { + b_p_avg[8 * 8 + y] = p_avg[x + 8 + onerow_in * y]; + } + for (size_t i = 0; i < 8; i++) { + FastUnsqueeze( + b_p_residual + 8 * i, b_p_avg + 8 * i, b_p_avg + 8 * (i + 1), + (x + i ? b_p_out_odd + 8 * ((x + i - 1) & 7) : b_p_avg + 8 * i), + b_p_out_even + 8 * i, b_p_out_odd + 8 * i); + } - for (size_t x = 1; x < chin_residual.w; x++) { - pixel_type_w diff_minus_tendency = p_residual[x]; - pixel_type_w avg = p_avg[x]; - pixel_type_w next_avg = (x + 1 < chin.w ? p_avg[x + 1] : avg); - pixel_type_w left = p_out[(x << 1) - 1]; - pixel_type_w tendency = SmoothTendency(left, avg, next_avg); - pixel_type_w diff = diff_minus_tendency + tendency; - pixel_type_w A = - ((avg * 2) + diff + (diff > 0 ? -(diff & 1) : (diff & 1))) >> 1; - p_out[x << 1] = A; - pixel_type_w B = A - diff; - p_out[(x << 1) + 1] = B; + Transpose8x8Block(b_p_out_even, b_p_out_evenT, 8); + Transpose8x8Block(b_p_out_odd, b_p_out_oddT, 8); + for (size_t y = 0; y < kRowsPerThread; y++) { + for (size_t i = 0; i < kRowsPerThread; i += N) { + auto even = Load(d, b_p_out_evenT + 8 * y + i); + auto odd = Load(d, b_p_out_oddT + 8 * y + i); + StoreInterleaved(d, even, odd, + p_out + ((x + i) << 1) + onerow_out * y); + } } - if (chout.w & 1) p_out[chout.w - 1] = p_avg[chin.w - 1]; - }, - "InvHorizontalSqueeze")); + } + } +#endif + for (size_t y = 0; y < rows; y++) { + unsqueeze_row(y0 + y, x); + } + }; + JXL_RETURN_IF_ERROR(RunOnPool(pool, 0, DivCeil(chin.h, kRowsPerThread), + ThreadPool::NoInit, unsqueeze_span, + "InvHorizontalSqueeze")); input.channel[c] = std::move(chout); return true; } Status InvVSqueeze(Image &input, uint32_t c, uint32_t rc, ThreadPool *pool) { @@ -109,46 +218,51 @@ // Short-circuit: channel with no pixels. input.channel[c] = std::move(chout); return true; } - intptr_t onerow_in = chin.plane.PixelsPerRow(); - intptr_t onerow_out = chout.plane.PixelsPerRow(); - constexpr int kColsPerThread = 64; - JXL_RETURN_IF_ERROR(RunOnPool( - pool, 0, DivCeil(chin.w, kColsPerThread), ThreadPool::NoInit, - [&](const uint32_t task, size_t /* thread */) { - const size_t x0 = task * kColsPerThread; - const size_t x1 = std::min((size_t)(task + 1) * kColsPerThread, chin.w); - // We only iterate up to std::min(chin_residual.h, chin.h) which is - // always chin_residual.h. - for (size_t y = 0; y < chin_residual.h; y++) { - const pixel_type *JXL_RESTRICT p_residual = chin_residual.Row(y); - const pixel_type *JXL_RESTRICT p_avg = chin.Row(y); - pixel_type *JXL_RESTRICT p_out = chout.Row(y << 1); - for (size_t x = x0; x < x1; x++) { - pixel_type_w diff_minus_tendency = p_residual[x]; - pixel_type_w avg = p_avg[x]; - - pixel_type_w next_avg = avg; - if (y + 1 < chin.h) next_avg = p_avg[x + onerow_in]; - pixel_type_w top = - (y > 0 ? p_out[static_cast(x) - onerow_out] : avg); - pixel_type_w tendency = SmoothTendency(top, avg, next_avg); - pixel_type_w diff = diff_minus_tendency + tendency; - pixel_type_w out = - ((avg * 2) + diff + (diff > 0 ? -(diff & 1) : (diff & 1))) >> 1; - - p_out[x] = out; - // If the chin_residual.h == chin.h, the output has an even number - // of rows so the next line is fine. Otherwise, this loop won't - // write to the last output row which is handled separately. - p_out[x + onerow_out] = p_out[x] - diff; - } - } - }, - "InvVertSqueeze")); + static constexpr const int kColsPerThread = 64; + const auto unsqueeze_slice = [&](const uint32_t task, size_t /* thread */) { + const size_t x0 = task * kColsPerThread; + const size_t x1 = std::min((size_t)(task + 1) * kColsPerThread, chin.w); + const size_t w = x1 - x0; + // We only iterate up to std::min(chin_residual.h, chin.h) which is + // always chin_residual.h. + for (size_t y = 0; y < chin_residual.h; y++) { + const pixel_type *JXL_RESTRICT p_residual = chin_residual.Row(y) + x0; + const pixel_type *JXL_RESTRICT p_avg = chin.Row(y) + x0; + const pixel_type *JXL_RESTRICT p_navg = + chin.Row(y + 1 < chin.h ? y + 1 : y) + x0; + pixel_type *JXL_RESTRICT p_out = chout.Row(y << 1) + x0; + pixel_type *JXL_RESTRICT p_nout = chout.Row((y << 1) + 1) + x0; + const pixel_type *p_pout = y > 0 ? chout.Row((y << 1) - 1) + x0 : p_avg; + size_t x = 0; +#if HWY_TARGET != HWY_SCALAR + for (; x + 7 < w; x += 8) { + FastUnsqueeze(p_residual + x, p_avg + x, p_navg + x, p_pout + x, + p_out + x, p_nout + x); + } +#endif + for (; x < w; x++) { + pixel_type avg = p_avg[x]; + pixel_type next_avg = p_navg[x]; + pixel_type top = p_pout[x]; + pixel_type tendency = SmoothTendency(top, avg, next_avg); + pixel_type diff_minus_tendency = p_residual[x]; + pixel_type diff = diff_minus_tendency + tendency; + pixel_type out = avg + (diff / 2); + p_out[x] = out; + // If the chin_residual.h == chin.h, the output has an even number + // of rows so the next line is fine. Otherwise, this loop won't + // write to the last output row which is handled separately. + p_nout[x] = out - diff; + } + } + }; + JXL_RETURN_IF_ERROR(RunOnPool(pool, 0, DivCeil(chin.w, kColsPerThread), + ThreadPool::NoInit, unsqueeze_slice, + "InvVertSqueeze")); if (chout.h & 1) { size_t y = chin.h - 1; const pixel_type *p_avg = chin.Row(y); pixel_type *p_out = chout.Row(y << 1); @@ -158,10 +272,66 @@ } input.channel[c] = std::move(chout); return true; } +Status InvSqueeze(Image &input, std::vector parameters, + ThreadPool *pool) { + for (int i = parameters.size() - 1; i >= 0; i--) { + JXL_RETURN_IF_ERROR( + CheckMetaSqueezeParams(parameters[i], input.channel.size())); + bool horizontal = parameters[i].horizontal; + bool in_place = parameters[i].in_place; + uint32_t beginc = parameters[i].begin_c; + uint32_t endc = parameters[i].begin_c + parameters[i].num_c - 1; + uint32_t offset; + if (in_place) { + offset = endc + 1; + } else { + offset = input.channel.size() + beginc - endc - 1; + } + if (beginc < input.nb_meta_channels) { + // This is checked in MetaSqueeze. + JXL_ASSERT(input.nb_meta_channels > parameters[i].num_c); + input.nb_meta_channels -= parameters[i].num_c; + } + + for (uint32_t c = beginc; c <= endc; c++) { + uint32_t rc = offset + c - beginc; + // MetaApply should imply that `rc` is within range, otherwise there's a + // programming bug. + JXL_ASSERT(rc < input.channel.size()); + if ((input.channel[c].w < input.channel[rc].w) || + (input.channel[c].h < input.channel[rc].h)) { + return JXL_FAILURE("Corrupted squeeze transform"); + } + if (horizontal) { + JXL_RETURN_IF_ERROR(InvHSqueeze(input, c, rc, pool)); + } else { + JXL_RETURN_IF_ERROR(InvVSqueeze(input, c, rc, pool)); + } + } + input.channel.erase(input.channel.begin() + offset, + input.channel.begin() + offset + (endc - beginc + 1)); + } + return true; +} + +} // namespace HWY_NAMESPACE +} // namespace jxl +HWY_AFTER_NAMESPACE(); + +#if HWY_ONCE + +namespace jxl { + +HWY_EXPORT(InvSqueeze); +Status InvSqueeze(Image &input, std::vector parameters, + ThreadPool *pool) { + return HWY_DYNAMIC_DISPATCH(InvSqueeze)(input, parameters, pool); +} + void DefaultSqueezeParameters(std::vector *parameters, const Image &image) { int nb_channels = image.channel.size() - image.nb_meta_channels; parameters->clear(); @@ -283,48 +453,8 @@ } } return true; } -Status InvSqueeze(Image &input, std::vector parameters, - ThreadPool *pool) { - for (int i = parameters.size() - 1; i >= 0; i--) { - JXL_RETURN_IF_ERROR( - CheckMetaSqueezeParams(parameters[i], input.channel.size())); - bool horizontal = parameters[i].horizontal; - bool in_place = parameters[i].in_place; - uint32_t beginc = parameters[i].begin_c; - uint32_t endc = parameters[i].begin_c + parameters[i].num_c - 1; - uint32_t offset; - if (in_place) { - offset = endc + 1; - } else { - offset = input.channel.size() + beginc - endc - 1; - } - if (beginc < input.nb_meta_channels) { - // This is checked in MetaSqueeze. - JXL_ASSERT(input.nb_meta_channels > parameters[i].num_c); - input.nb_meta_channels -= parameters[i].num_c; - } +} // namespace jxl - for (uint32_t c = beginc; c <= endc; c++) { - uint32_t rc = offset + c - beginc; - // MetaApply should imply that `rc` is within range, otherwise there's a - // programming bug. - JXL_ASSERT(rc < input.channel.size()); - if ((input.channel[c].w < input.channel[rc].w) || - (input.channel[c].h < input.channel[rc].h)) { - return JXL_FAILURE("Corrupted squeeze transform"); - } - if (horizontal) { - JXL_RETURN_IF_ERROR(InvHSqueeze(input, c, rc, pool)); - } else { - JXL_RETURN_IF_ERROR(InvVSqueeze(input, c, rc, pool)); - } - } - input.channel.erase(input.channel.begin() + offset, - input.channel.begin() + offset + (endc - beginc + 1)); - } - return true; -} - -} // namespace jxl +#endif \ No newline at end of file diff --git a/third_party/jpeg-xl/lib/jxl/modular_test.cc b/third_party/jpeg-xl/lib/jxl/modular_test.cc --- a/third_party/jpeg-xl/lib/jxl/modular_test.cc +++ b/third_party/jpeg-xl/lib/jxl/modular_test.cc @@ -23,10 +23,11 @@ #include "lib/jxl/color_encoding_internal.h" #include "lib/jxl/color_management.h" #include "lib/jxl/dec_file.h" #include "lib/jxl/dec_params.h" #include "lib/jxl/enc_butteraugli_comparator.h" +#include "lib/jxl/enc_butteraugli_pnorm.h" #include "lib/jxl/enc_cache.h" #include "lib/jxl/enc_color_management.h" #include "lib/jxl/enc_file.h" #include "lib/jxl/enc_params.h" #include "lib/jxl/image.h" @@ -43,15 +44,14 @@ using test::Roundtrip; void TestLosslessGroups(size_t group_size_shift) { ThreadPool* pool = nullptr; const PaddedBytes orig = - ReadTestData("imagecompression.info/flower_foveon.png"); + ReadTestData("third_party/imagecompression.info/flower_foveon.png"); CompressParams cparams; - cparams.modular_mode = true; + cparams.SetLossless(); cparams.modular_group_size_shift = group_size_shift; - cparams.color_transform = jxl::ColorTransform::kNone; DecompressParams dparams; CodecInOut io_out; size_t compressed_size; @@ -77,19 +77,18 @@ } TEST(ModularTest, RoundtripLosslessCustomWP_PermuteRCT) { ThreadPool* pool = nullptr; const PaddedBytes orig = - ReadTestData("wesaturate/500px/u76c0g_bliznaca_srgb8.png"); + ReadTestData("third_party/wesaturate/500px/u76c0g_bliznaca_srgb8.png"); CompressParams cparams; - cparams.modular_mode = true; + cparams.SetLossless(); // 9 = permute to GBR, to test the special case of permutation-only cparams.colorspace = 9; // slowest speed so different WP modes are tried cparams.speed_tier = SpeedTier::kTortoise; cparams.options.predictor = {Predictor::Weighted}; - cparams.color_transform = jxl::ColorTransform::kNone; DecompressParams dparams; CodecInOut io_out; size_t compressed_size; @@ -105,11 +104,11 @@ } TEST(ModularTest, RoundtripLossyDeltaPalette) { ThreadPool* pool = nullptr; const PaddedBytes orig = - ReadTestData("wesaturate/500px/u76c0g_bliznaca_srgb8.png"); + ReadTestData("third_party/wesaturate/500px/u76c0g_bliznaca_srgb8.png"); CompressParams cparams; cparams.modular_mode = true; cparams.color_transform = jxl::ColorTransform::kNone; cparams.lossy_palette = true; cparams.palette_colors = 0; @@ -131,14 +130,13 @@ IsSlightlyBelow(1.5)); } TEST(ModularTest, RoundtripLossyDeltaPaletteWP) { ThreadPool* pool = nullptr; const PaddedBytes orig = - ReadTestData("wesaturate/500px/u76c0g_bliznaca_srgb8.png"); + ReadTestData("third_party/wesaturate/500px/u76c0g_bliznaca_srgb8.png"); CompressParams cparams; - cparams.modular_mode = true; - cparams.color_transform = jxl::ColorTransform::kNone; + cparams.SetLossless(); cparams.lossy_palette = true; cparams.palette_colors = 0; cparams.options.predictor = jxl::Predictor::Weighted; DecompressParams dparams; @@ -159,37 +157,37 @@ } TEST(ModularTest, RoundtripLossy) { ThreadPool* pool = nullptr; const PaddedBytes orig = - ReadTestData("wesaturate/500px/u76c0g_bliznaca_srgb8.png"); + ReadTestData("third_party/wesaturate/500px/u76c0g_bliznaca_srgb8.png"); CompressParams cparams; cparams.modular_mode = true; - cparams.quality_pair = {80.0f, 80.0f}; + cparams.butteraugli_distance = 2.f; DecompressParams dparams; CodecInOut io_out; size_t compressed_size; CodecInOut io; ASSERT_TRUE(SetFromBytes(Span(orig), &io, pool)); compressed_size = Roundtrip(&io, cparams, dparams, pool, &io_out); - EXPECT_LE(compressed_size, 40000u); + EXPECT_LE(compressed_size, 30000u); cparams.ba_params.intensity_target = 80.0f; EXPECT_THAT(ButteraugliDistance(io, io_out, cparams.ba_params, GetJxlCms(), /*distmap=*/nullptr, pool), - IsSlightlyBelow(2.0)); + IsSlightlyBelow(2.3)); } TEST(ModularTest, RoundtripLossy16) { ThreadPool* pool = nullptr; const PaddedBytes orig = - ReadTestData("raw.pixls/DJI-FC6310-16bit_709_v4_krita.png"); + ReadTestData("third_party/raw.pixls/DJI-FC6310-16bit_709_v4_krita.png"); CompressParams cparams; cparams.modular_mode = true; - cparams.quality_pair = {80.0f, 80.0f}; + cparams.butteraugli_distance = 2.f; DecompressParams dparams; CodecInOut io_out; size_t compressed_size; @@ -197,15 +195,15 @@ ASSERT_TRUE(SetFromBytes(Span(orig), &io, pool)); JXL_CHECK(io.TransformTo(ColorEncoding::SRGB(), GetJxlCms(), pool)); io.metadata.m.color_encoding = ColorEncoding::SRGB(); compressed_size = Roundtrip(&io, cparams, dparams, pool, &io_out); - EXPECT_LE(compressed_size, 400u); + EXPECT_LE(compressed_size, 300u); cparams.ba_params.intensity_target = 80.0f; EXPECT_THAT(ButteraugliDistance(io, io_out, cparams.ba_params, GetJxlCms(), /*distmap=*/nullptr, pool), - IsSlightlyBelow(1.5)); + IsSlightlyBelow(1.6)); } TEST(ModularTest, RoundtripExtraProperties) { constexpr size_t kSize = 250; Image image(kSize, kSize, /*bitdepth=*/8, 3); @@ -248,19 +246,19 @@ } } TEST(ModularTest, RoundtripLosslessCustomSqueeze) { ThreadPool* pool = nullptr; - const PaddedBytes orig = - ReadTestData("wesaturate/500px/tmshre_riaphotographs_srgb8.png"); + const PaddedBytes orig = ReadTestData( + "third_party/wesaturate/500px/tmshre_riaphotographs_srgb8.png"); CodecInOut io; ASSERT_TRUE(SetFromBytes(Span(orig), &io, pool)); CompressParams cparams; cparams.modular_mode = true; cparams.color_transform = jxl::ColorTransform::kNone; - cparams.quality_pair = {100, 100}; + cparams.butteraugli_distance = 0.f; cparams.options.predictor = {Predictor::Zero}; cparams.speed_tier = SpeedTier::kThunder; cparams.responsive = 1; // Custom squeeze params, atm just for testing SqueezeParams p; @@ -279,10 +277,109 @@ EXPECT_LE(Roundtrip(&io, cparams, dparams, pool, &io2), 265000u); EXPECT_EQ(0.0, ButteraugliDistance(io, io2, cparams.ba_params, GetJxlCms(), /*distmap=*/nullptr, pool)); } +struct RoundtripLosslessConfig { + int bitdepth; + int responsive; +}; +class ModularTestParam + : public ::testing::TestWithParam {}; + +std::vector GenerateLosslessTests() { + std::vector all; + for (int responsive = 0; responsive <= 1; responsive++) { + for (int bitdepth = 1; bitdepth < 32; bitdepth++) { + if (responsive && bitdepth > 30) continue; + all.push_back({bitdepth, responsive}); + } + } + return all; +} +std::string LosslessTestDescription( + const testing::TestParamInfo& info) { + std::stringstream name; + name << info.param.bitdepth << "bit"; + if (info.param.responsive) name << "Squeeze"; + return name.str(); +} + +JXL_GTEST_INSTANTIATE_TEST_SUITE_P(RoundtripLossless, ModularTestParam, + testing::ValuesIn(GenerateLosslessTests()), + LosslessTestDescription); + +TEST_P(ModularTestParam, RoundtripLossless) { + RoundtripLosslessConfig config = GetParam(); + int bitdepth = config.bitdepth; + int responsive = config.responsive; + + ThreadPool* pool = nullptr; + Rng generator(123); + const PaddedBytes orig = + ReadTestData("third_party/wesaturate/500px/u76c0g_bliznaca_srgb8.png"); + CodecInOut io1; + ASSERT_TRUE(SetFromBytes(Span(orig), &io1, pool)); + + // vary the dimensions a bit, in case of bugs related to + // even vs odd width or height. + size_t xsize = 423 + bitdepth; + size_t ysize = 467 + bitdepth; + + CodecInOut io; + io.SetSize(xsize, ysize); + io.metadata.m.color_encoding = jxl::ColorEncoding::SRGB(false); + io.metadata.m.SetUintSamples(bitdepth); + + double factor = ((1lu << bitdepth) - 1lu); + double ifactor = 1.0 / factor; + Image3F noise_added(xsize, ysize); + + for (size_t c = 0; c < 3; c++) { + for (size_t y = 0; y < ysize; y++) { + const float* in = io1.Main().color()->PlaneRow(c, y); + float* out = noise_added.PlaneRow(c, y); + for (size_t x = 0; x < xsize; x++) { + // make the least significant bits random + float f = in[x] + generator.UniformF(0.0f, 1.f / 255.f); + if (f > 1.f) f = 1.f; + // quantize to the bitdepth we're testing + unsigned int u = f * factor + 0.5; + out[x] = u * ifactor; + } + } + } + io.SetFromImage(std::move(noise_added), jxl::ColorEncoding::SRGB(false)); + + CompressParams cparams; + cparams.modular_mode = true; + cparams.color_transform = jxl::ColorTransform::kNone; + cparams.butteraugli_distance = 0.f; + cparams.options.predictor = {Predictor::Zero}; + cparams.speed_tier = SpeedTier::kThunder; + cparams.responsive = responsive; + DecompressParams dparams; + CodecInOut io2; + EXPECT_LE(Roundtrip(&io, cparams, dparams, pool, &io2), + bitdepth * xsize * ysize / 3); + EXPECT_LE(0, ComputeDistance2(io.Main(), io2.Main(), GetJxlCms())); + size_t different = 0; + for (size_t c = 0; c < 3; c++) { + for (size_t y = 0; y < ysize; y++) { + const float* in = io.Main().color()->PlaneRow(c, y); + const float* out = io2.Main().color()->PlaneRow(c, y); + for (size_t x = 0; x < xsize; x++) { + uint32_t uin = in[x] * factor + 0.5; + uint32_t uout = out[x] * factor + 0.5; + // check that the integer values are identical + if (uin != uout) different++; + } + } + } + EXPECT_EQ(different, 0); +} + TEST(ModularTest, RoundtripLosslessCustomFloat) { ThreadPool* pool = nullptr; CodecInOut io; size_t xsize = 100, ysize = 300; io.SetSize(xsize, ysize); @@ -308,11 +405,11 @@ io.metadata.m.SetIntensityTarget(255); CompressParams cparams; cparams.modular_mode = true; cparams.color_transform = jxl::ColorTransform::kNone; - cparams.quality_pair = {100, 100}; + cparams.butteraugli_distance = 0.f; cparams.options.predictor = {Predictor::Zero}; cparams.speed_tier = SpeedTier::kThunder; cparams.decoding_speed_tier = 2; DecompressParams dparams; diff --git a/third_party/jpeg-xl/lib/jxl/passes_test.cc b/third_party/jpeg-xl/lib/jxl/passes_test.cc --- a/third_party/jpeg-xl/lib/jxl/passes_test.cc +++ b/third_party/jpeg-xl/lib/jxl/passes_test.cc @@ -35,11 +35,11 @@ using test::Roundtrip; TEST(PassesTest, RoundtripSmallPasses) { ThreadPool* pool = nullptr; const PaddedBytes orig = - ReadTestData("wesaturate/500px/u76c0g_bliznaca_srgb8.png"); + ReadTestData("third_party/wesaturate/500px/u76c0g_bliznaca_srgb8.png"); CodecInOut io; ASSERT_TRUE(SetFromBytes(Span(orig), &io, pool)); io.ShrinkTo(io.xsize() / 8, io.ysize() / 8); CompressParams cparams; @@ -55,11 +55,11 @@ } TEST(PassesTest, RoundtripUnalignedPasses) { ThreadPool* pool = nullptr; const PaddedBytes orig = - ReadTestData("wesaturate/500px/u76c0g_bliznaca_srgb8.png"); + ReadTestData("third_party/wesaturate/500px/u76c0g_bliznaca_srgb8.png"); CodecInOut io; ASSERT_TRUE(SetFromBytes(Span(orig), &io, pool)); io.ShrinkTo(io.xsize() / 12, io.ysize() / 7); CompressParams cparams; @@ -75,11 +75,11 @@ } TEST(PassesTest, RoundtripMultiGroupPasses) { ThreadPoolInternal pool(4); const PaddedBytes orig = - ReadTestData("imagecompression.info/flower_foveon.png"); + ReadTestData("third_party/imagecompression.info/flower_foveon.png"); CodecInOut io; ASSERT_TRUE(SetFromBytes(Span(orig), &io, &pool)); io.ShrinkTo(600, 1024); // partial X, full Y group CompressParams cparams; @@ -102,11 +102,11 @@ } TEST(PassesTest, RoundtripLargeFastPasses) { ThreadPoolInternal pool(8); const PaddedBytes orig = - ReadTestData("imagecompression.info/flower_foveon.png"); + ReadTestData("third_party/imagecompression.info/flower_foveon.png"); CodecInOut io; ASSERT_TRUE(SetFromBytes(Span(orig), &io, &pool)); CompressParams cparams; cparams.speed_tier = SpeedTier::kSquirrel; @@ -121,11 +121,11 @@ // which involves additional processing including adaptive reconstruction. // Failing this may be a sign of race conditions or invalid memory accesses. TEST(PassesTest, RoundtripProgressiveConsistent) { ThreadPoolInternal pool(8); const PaddedBytes orig = - ReadTestData("imagecompression.info/flower_foveon.png"); + ReadTestData("third_party/imagecompression.info/flower_foveon.png"); CodecInOut io; ASSERT_TRUE(SetFromBytes(Span(orig), &io, &pool)); CompressParams cparams; cparams.speed_tier = SpeedTier::kSquirrel; @@ -158,11 +158,11 @@ } TEST(PassesTest, AllDownsampleFeasible) { ThreadPoolInternal pool(8); const PaddedBytes orig = - ReadTestData("wesaturate/500px/u76c0g_bliznaca_srgb8.png"); + ReadTestData("third_party/wesaturate/500px/u76c0g_bliznaca_srgb8.png"); CodecInOut io; ASSERT_TRUE(SetFromBytes(Span(orig), &io, &pool)); PaddedBytes compressed; AuxOut aux; @@ -205,11 +205,11 @@ } TEST(PassesTest, AllDownsampleFeasibleQProgressive) { ThreadPoolInternal pool(8); const PaddedBytes orig = - ReadTestData("wesaturate/500px/u76c0g_bliznaca_srgb8.png"); + ReadTestData("third_party/wesaturate/500px/u76c0g_bliznaca_srgb8.png"); CodecInOut io; ASSERT_TRUE(SetFromBytes(Span(orig), &io, &pool)); PaddedBytes compressed; AuxOut aux; @@ -251,12 +251,12 @@ check, "TestQProgressive")); } TEST(PassesTest, ProgressiveDownsample2DegradesCorrectlyGrayscale) { ThreadPoolInternal pool(8); - const PaddedBytes orig = - ReadTestData("wesaturate/500px/cvo9xd_keong_macan_grayscale.png"); + const PaddedBytes orig = ReadTestData( + "third_party/wesaturate/500px/cvo9xd_keong_macan_grayscale.png"); CodecInOut io_orig; ASSERT_TRUE(SetFromBytes(Span(orig), &io_orig, &pool)); Rect rect(0, 0, io_orig.xsize(), 128); // need 2 DC groups for the DC frame to actually be progressive. Image3F large(4242, rect.ysize()); @@ -300,11 +300,11 @@ } TEST(PassesTest, ProgressiveDownsample2DegradesCorrectly) { ThreadPoolInternal pool(8); const PaddedBytes orig = - ReadTestData("imagecompression.info/flower_foveon.png"); + ReadTestData("third_party/imagecompression.info/flower_foveon.png"); CodecInOut io_orig; ASSERT_TRUE(SetFromBytes(Span(orig), &io_orig, &pool)); Rect rect(0, 0, io_orig.xsize(), 128); // need 2 DC groups for the DC frame to actually be progressive. Image3F large(4242, rect.ysize()); @@ -347,11 +347,11 @@ } TEST(PassesTest, NonProgressiveDCImage) { ThreadPoolInternal pool(8); const PaddedBytes orig = - ReadTestData("imagecompression.info/flower_foveon.png"); + ReadTestData("third_party/imagecompression.info/flower_foveon.png"); CodecInOut io; ASSERT_TRUE(SetFromBytes(Span(orig), &io, &pool)); PaddedBytes compressed; AuxOut aux; @@ -375,11 +375,11 @@ } TEST(PassesTest, RoundtripSmallNoGaborishPasses) { ThreadPool* pool = nullptr; const PaddedBytes orig = - ReadTestData("wesaturate/500px/u76c0g_bliznaca_srgb8.png"); + ReadTestData("third_party/wesaturate/500px/u76c0g_bliznaca_srgb8.png"); CodecInOut io; ASSERT_TRUE(SetFromBytes(Span(orig), &io, pool)); io.ShrinkTo(io.xsize() / 8, io.ysize() / 8); CompressParams cparams; diff --git a/third_party/jpeg-xl/lib/jxl/patch_dictionary_test.cc b/third_party/jpeg-xl/lib/jxl/patch_dictionary_test.cc --- a/third_party/jpeg-xl/lib/jxl/patch_dictionary_test.cc +++ b/third_party/jpeg-xl/lib/jxl/patch_dictionary_test.cc @@ -22,12 +22,11 @@ const PaddedBytes orig = ReadTestData("jxl/grayscale_patches.png"); CodecInOut io; ASSERT_TRUE(SetFromBytes(Span(orig), &io, pool)); CompressParams cparams; - cparams.color_transform = jxl::ColorTransform::kNone; - cparams.modular_mode = true; + cparams.SetLossless(); cparams.patches = jxl::Override::kOn; DecompressParams dparams; CodecInOut io2; // Without patches: ~25k diff --git a/third_party/jpeg-xl/lib/jxl/preview_test.cc b/third_party/jpeg-xl/lib/jxl/preview_test.cc --- a/third_party/jpeg-xl/lib/jxl/preview_test.cc +++ b/third_party/jpeg-xl/lib/jxl/preview_test.cc @@ -32,11 +32,11 @@ using test::Roundtrip; TEST(PreviewTest, RoundtripGivenPreview) { ThreadPool* pool = nullptr; const PaddedBytes orig = - ReadTestData("wesaturate/500px/u76c0g_bliznaca_srgb8.png"); + ReadTestData("third_party/wesaturate/500px/u76c0g_bliznaca_srgb8.png"); CodecInOut io; ASSERT_TRUE(SetFromBytes(Span(orig), &io, pool)); io.ShrinkTo(io.xsize() / 8, io.ysize() / 8); // Same as main image io.preview_frame = io.Main().Copy(); diff --git a/third_party/jpeg-xl/lib/jxl/quantizer-inl.h b/third_party/jpeg-xl/lib/jxl/quantizer-inl.h --- a/third_party/jpeg-xl/lib/jxl/quantizer-inl.h +++ b/third_party/jpeg-xl/lib/jxl/quantizer-inl.h @@ -26,11 +26,10 @@ HWY_INLINE HWY_MAYBE_UNUSED Vec> AdjustQuantBias( DI di, const size_t c, const Vec quant_i, const float* HWY_RESTRICT biases) { const Rebind df; -#if JXL_HIGH_PRECISION const auto quant = ConvertTo(df, quant_i); // Compare |quant|, keep sign bit for negating result. const auto kSign = BitCast(df, Set(di, INT32_MIN)); const auto sign = And(quant, kSign); // TODO(janwas): = abs ^ orig @@ -54,16 +53,10 @@ // About 2E-5 worse than ReciprocalNR or division. const auto bias = NegMulAdd(Set(df, biases[3]), ApproximateReciprocal(quant), quant); return IfThenElse(is_01, one_bias, bias); -#else - auto sign = IfThenElseZero(quant_i < Zero(di), Set(di, INT32_MIN)); - return BitCast(df, IfThenElse(Abs(quant_i) == Set(di, 1), - sign | BitCast(di, Set(df, biases[c])), - BitCast(di, ConvertTo(df, quant_i)))); -#endif } } // namespace // NOLINTNEXTLINE(google-readability-namespace-comments) } // namespace HWY_NAMESPACE diff --git a/third_party/jpeg-xl/lib/jxl/render_pipeline/low_memory_render_pipeline.h b/third_party/jpeg-xl/lib/jxl/render_pipeline/low_memory_render_pipeline.h --- a/third_party/jpeg-xl/lib/jxl/render_pipeline/low_memory_render_pipeline.h +++ b/third_party/jpeg-xl/lib/jxl/render_pipeline/low_memory_render_pipeline.h @@ -44,15 +44,13 @@ std::pair BorderToStore(size_t c) const; bool use_group_ids_; - // Storage for borders between groups. - // We also store the extremal borders for simplicity. Horizontal borders are - // stored in an image as wide as the main frame, in top-to-bottom order (top - // border of a group first, followed by the bottom border, followed by top - // border of the next group). Vertical borders are similarly stored. + // Storage for borders between groups. Borders of adjacent groups are stacked + // together, e.g. bottom border of current group is followed by top border + // of next group. std::vector borders_horizontal_; std::vector borders_vertical_; // Manages the status of borders. GroupBorderAssigner group_border_assigner_; diff --git a/third_party/jpeg-xl/lib/jxl/render_pipeline/low_memory_render_pipeline.cc b/third_party/jpeg-xl/lib/jxl/render_pipeline/low_memory_render_pipeline.cc --- a/third_party/jpeg-xl/lib/jxl/render_pipeline/low_memory_render_pipeline.cc +++ b/third_party/jpeg-xl/lib/jxl/render_pipeline/low_memory_render_pipeline.cc @@ -48,28 +48,35 @@ auto borders = BorderToStore(c); size_t borderx_write = borders.first; size_t bordery_write = borders.second; - CopyImageTo( - Rect(group_data_x_border_, group_data_y_border_, x1 - x0, bordery_write), - in, Rect(x0, (gy * 2) * bordery_write, x1 - x0, bordery_write), - &borders_horizontal_[c]); - CopyImageTo( - Rect(group_data_x_border_, group_data_y_border_ + y1 - y0 - bordery_write, - x1 - x0, bordery_write), - in, Rect(x0, (gy * 2 + 1) * bordery_write, x1 - x0, bordery_write), - &borders_horizontal_[c]); - CopyImageTo( - Rect(group_data_x_border_, group_data_y_border_, borderx_write, y1 - y0), - in, Rect((gx * 2) * borderx_write, y0, borderx_write, y1 - y0), - &borders_vertical_[c]); - CopyImageTo(Rect(group_data_x_border_ + x1 - x0 - borderx_write, - group_data_y_border_, borderx_write, y1 - y0), - in, - Rect((gx * 2 + 1) * borderx_write, y0, borderx_write, y1 - y0), - &borders_vertical_[c]); + if (gy > 0) { + Rect from(group_data_x_border_, group_data_y_border_, x1 - x0, + bordery_write); + Rect to(x0, (gy * 2 - 1) * bordery_write, x1 - x0, bordery_write); + CopyImageTo(from, in, to, &borders_horizontal_[c]); + } + if (gy + 1 < frame_dimensions_.ysize_groups) { + Rect from(group_data_x_border_, + group_data_y_border_ + y1 - y0 - bordery_write, x1 - x0, + bordery_write); + Rect to(x0, (gy * 2) * bordery_write, x1 - x0, bordery_write); + CopyImageTo(from, in, to, &borders_horizontal_[c]); + } + if (gx > 0) { + Rect from(group_data_x_border_, group_data_y_border_, borderx_write, + y1 - y0); + Rect to((gx * 2 - 1) * borderx_write, y0, borderx_write, y1 - y0); + CopyImageTo(from, in, to, &borders_vertical_[c]); + } + if (gx + 1 < frame_dimensions_.xsize_groups) { + Rect from(group_data_x_border_ + x1 - x0 - borderx_write, + group_data_y_border_, borderx_write, y1 - y0); + Rect to((gx * 2) * borderx_write, y0, borderx_write, y1 - y0); + CopyImageTo(from, in, to, &borders_vertical_[c]); + } } void LowMemoryRenderPipeline::LoadBorders(size_t group_id, size_t c, const Rect& r, ImageF* out) { size_t gy = group_id / frame_dimensions_.xsize_groups; @@ -117,41 +124,41 @@ // 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), + Rect(x0src, (gy * 2 - 2) * bordery_write, x1src - x0src, bordery_write), borders_horizontal_[c], Rect(group_data_x_border_ + x0src - x0, group_data_y_border_ - bordery_write, x1src - x0src, bordery_write), out); } if (y1src > y1) { // When copying the bottom border we must not be on the bottom groups. JXL_DASSERT(gy + 1 < frame_dimensions_.ysize_groups); CopyImageTo( - Rect(x0src, (gy * 2 + 2) * bordery_write, x1src - x0src, bordery_write), + Rect(x0src, (gy * 2 + 1) * bordery_write, x1src - x0src, bordery_write), borders_horizontal_[c], Rect(group_data_x_border_ + x0src - x0, group_data_y_border_ + y1 - y0, x1src - x0src, bordery_write), out); } if (x0src < x0) { JXL_DASSERT(gx > 0); CopyImageTo( - Rect((gx * 2 - 1) * borderx_write, y0src, borderx_write, y1src - y0src), + Rect((gx * 2 - 2) * borderx_write, y0src, borderx_write, y1src - y0src), borders_vertical_[c], Rect(group_data_x_border_ - borderx_write, group_data_y_border_ + y0src - y0, borderx_write, y1src - y0src), out); } if (x1src > x1) { // When copying the right border we must not be on the rightmost groups. JXL_DASSERT(gx + 1 < frame_dimensions_.xsize_groups); CopyImageTo( - Rect((gx * 2 + 2) * borderx_write, y0src, borderx_write, y1src - y0src), + Rect((gx * 2 + 1) * borderx_write, y0src, borderx_write, y1src - y0src), borders_vertical_[c], Rect(group_data_x_border_ + x1 - x0, group_data_y_border_ + y0src - y0, borderx_write, y1src - y0src), out); } @@ -175,20 +182,23 @@ } for (size_t c = 0; c < shifts.size(); c++) { auto borders = BorderToStore(c); size_t borderx = borders.first; size_t bordery = borders.second; - Rect horizontal = Rect( - 0, 0, - DivCeil(frame_dimensions_.xsize_upsampled_padded, 1 << shifts[c].first), - bordery * frame_dimensions_.ysize_groups * 2); + JXL_DASSERT(frame_dimensions_.xsize_groups > 0); + size_t num_xborders = (frame_dimensions_.xsize_groups - 1) * 2; + JXL_DASSERT(frame_dimensions_.ysize_groups > 0); + size_t num_yborders = (frame_dimensions_.ysize_groups - 1) * 2; + size_t downsampled_xsize = + DivCeil(frame_dimensions_.xsize_upsampled_padded, 1 << shifts[c].first); + size_t downsampled_ysize = DivCeil(frame_dimensions_.ysize_upsampled_padded, + 1 << shifts[c].second); + Rect horizontal = Rect(0, 0, downsampled_xsize, bordery * num_yborders); if (!SameSize(horizontal, borders_horizontal_[c])) { borders_horizontal_[c] = ImageF(horizontal.xsize(), horizontal.ysize()); } - Rect vertical = Rect(0, 0, borderx * frame_dimensions_.xsize_groups * 2, - DivCeil(frame_dimensions_.ysize_upsampled_padded, - 1 << shifts[c].second)); + Rect vertical = Rect(0, 0, borderx * num_xborders, downsampled_ysize); if (!SameSize(vertical, borders_vertical_[c])) { borders_vertical_[c] = ImageF(vertical.xsize(), vertical.ysize()); } } } @@ -361,14 +371,16 @@ GroupInputYSize(c) + group_data_y_border_ * 2); } } // TODO(veluca): avoid reallocating buffers if not needed. stage_data_.resize(num); - size_t stage_buffer_xsize = - (frame_dimensions_.group_dim << base_color_shift_) + - 2 * group_data_x_border_ + // maximum size of a rect - 2 * kRenderPipelineXOffset; // extra padding for processing + size_t upsampling = 1u << base_color_shift_; + size_t group_dim = frame_dimensions_.group_dim * upsampling; + size_t padding = + 2 * group_data_x_border_ * upsampling + // maximum size of a rect + 2 * kRenderPipelineXOffset; // extra padding for processing + size_t stage_buffer_xsize = group_dim + padding; for (size_t t = 0; t < num; t++) { stage_data_[t].resize(shifts.size()); for (size_t c = 0; c < shifts.size(); c++) { stage_data_[t][c].resize(stages_.size()); size_t next_y_border = 0; @@ -384,19 +396,20 @@ } } } if (first_image_dim_stage_ != stages_.size()) { out_of_frame_data_.resize(num); + size_t left_padding = std::max(0, frame_origin_.x0); + size_t middle_padding = group_dim; + ssize_t last_x = + frame_origin_.x0 + std::min(frame_dimensions_.xsize_groups * group_dim, + frame_dimensions_.xsize_upsampled); + last_x = Clamp1(last_x, 0, full_image_xsize_); + size_t right_padding = full_image_xsize_ - last_x; size_t out_of_frame_xsize = - 2 * kRenderPipelineXOffset + - std::max( - 0, - std::max( - frame_origin_.x0, - static_cast(full_image_xsize_) - frame_origin_.x0 - - static_cast(frame_dimensions_.xsize_upsampled))); - out_of_frame_xsize = std::max(out_of_frame_xsize, stage_buffer_xsize); + padding + + std::max(left_padding, std::max(middle_padding, right_padding)); for (size_t t = 0; t < num; t++) { out_of_frame_data_[t] = ImageF(out_of_frame_xsize, shifts.size()); } } } @@ -612,14 +625,14 @@ output_rows[c][iy] = get_row_buffer(i, y * (1 << shifty) + iy, c); } } } // Produce output rows. - stages_[i]->ProcessRow( - input_rows[i], output_rows, xpadding_for_output_[i], - group_rect[i].xsize(), group_rect[i].x0(), group_rect[i].y0() + y, - reinterpret_cast(temp_buffers_[thread_id].get())); + stages_[i]->ProcessRow(input_rows[i], output_rows, + xpadding_for_output_[i], group_rect[i].xsize(), + group_rect[i].x0(), group_rect[i].y0() + y, + thread_id); } // Process trailing stages, i.e. the final set of non-kInOut stages; they // all have the same input buffer and no need to use any mirroring. @@ -630,15 +643,14 @@ input_rows[first_trailing_stage_][c][0] = get_row_buffer( stage_input_for_channel_[first_trailing_stage_][c], y, c); } for (size_t i = first_trailing_stage_; i < first_image_dim_stage_; i++) { - stages_[i]->ProcessRow( - input_rows[first_trailing_stage_], output_rows, - /*xextra=*/0, group_rect[i].xsize(), group_rect[i].x0(), - group_rect[i].y0() + y, - reinterpret_cast(temp_buffers_[thread_id].get())); + stages_[i]->ProcessRow(input_rows[first_trailing_stage_], output_rows, + /*xextra=*/0, group_rect[i].xsize(), + group_rect[i].x0(), group_rect[i].y0() + y, + thread_id); } if (first_image_dim_stage_ == stages_.size()) continue; ssize_t full_image_y = @@ -663,15 +675,13 @@ full_image_x1 = std::min(full_image_x1, full_image_xsize_); if (full_image_x1 <= full_image_x0) continue; for (size_t i = first_image_dim_stage_; i < stages_.size(); i++) { - stages_[i]->ProcessRow( - input_rows[first_trailing_stage_], output_rows, - /*xextra=*/0, full_image_x1 - full_image_x0, full_image_x0, - full_image_y, - reinterpret_cast(temp_buffers_[thread_id].get())); + stages_[i]->ProcessRow(input_rows[first_trailing_stage_], output_rows, + /*xextra=*/0, full_image_x1 - full_image_x0, + full_image_x0, full_image_y, thread_id); } } } void LowMemoryRenderPipeline::RenderPadding(size_t thread_id, Rect rect) { @@ -686,14 +696,13 @@ for (size_t y = 0; y < rect.ysize(); y++) { stages_[first_image_dim_stage_ - 1]->ProcessPaddingRow( input_rows, rect.xsize(), rect.x0(), rect.y0() + y); for (size_t i = first_image_dim_stage_; i < stages_.size(); i++) { - stages_[i]->ProcessRow( - input_rows, output_rows, - /*xextra=*/0, rect.xsize(), rect.x0(), rect.y0() + y, - reinterpret_cast(temp_buffers_[thread_id].get())); + stages_[i]->ProcessRow(input_rows, output_rows, + /*xextra=*/0, rect.xsize(), rect.x0(), + rect.y0() + y, thread_id); } } } void LowMemoryRenderPipeline::ProcessBuffers(size_t group_id, @@ -708,54 +717,54 @@ size_t gy = group_id / frame_dimensions_.xsize_groups; size_t gx = group_id % frame_dimensions_.xsize_groups; if (first_image_dim_stage_ != stages_.size()) { - size_t x0 = std::max( - 0, (gx * frame_dimensions_.group_dim << base_color_shift_) + - frame_origin_.x0); - size_t y0 = std::max( - 0, (gy * frame_dimensions_.group_dim << base_color_shift_) + - frame_origin_.y0); - size_t x1 = std::min( - frame_origin_.x0 + std::min((gx + 1) * frame_dimensions_.group_dim - << base_color_shift_, - frame_dimensions_.xsize_upsampled), - full_image_xsize_); - size_t y1 = std::min( - frame_origin_.y0 + std::min((gy + 1) * frame_dimensions_.group_dim - << base_color_shift_, - frame_dimensions_.ysize_upsampled), - full_image_ysize_); - x0 = std::min(x1, x0); - y0 = std::min(y1, y0); + size_t group_dim = frame_dimensions_.group_dim << base_color_shift_; + RectT group_rect(gx * group_dim, gy * group_dim, group_dim, + group_dim); + RectT image_rect(0, 0, frame_dimensions_.xsize_upsampled, + frame_dimensions_.ysize_upsampled); + RectT full_image_rect(0, 0, full_image_xsize_, full_image_ysize_); + group_rect = group_rect.Translate(frame_origin_.x0, frame_origin_.y0); + image_rect = image_rect.Translate(frame_origin_.x0, frame_origin_.y0); + group_rect = + group_rect.Intersection(image_rect).Intersection(full_image_rect); + size_t x0 = group_rect.x0(); + size_t y0 = group_rect.y0(); + size_t x1 = group_rect.x1(); + size_t y1 = group_rect.y1(); - if (gx == 0 && gy == 0) { - RenderPadding(thread_id, Rect(0, 0, x0, y0)); - } - if (gy == 0) { - RenderPadding(thread_id, Rect(x0, 0, x1 - x0, y0)); - } - if (gx == 0) { - RenderPadding(thread_id, Rect(0, y0, x0, y1 - y0)); - } - if (gx == 0 && gy + 1 == frame_dimensions_.ysize_groups) { - RenderPadding(thread_id, Rect(0, y1, x0, full_image_ysize_ - y1)); - } - if (gy + 1 == frame_dimensions_.ysize_groups) { - RenderPadding(thread_id, Rect(x0, y1, x1 - x0, full_image_ysize_ - y1)); - } - if (gy == 0 && gx + 1 == frame_dimensions_.xsize_groups) { - RenderPadding(thread_id, Rect(x1, 0, full_image_xsize_ - x1, y0)); - } - if (gx + 1 == frame_dimensions_.xsize_groups) { - RenderPadding(thread_id, Rect(x1, y0, full_image_xsize_ - x1, y1 - y0)); - } - if (gy + 1 == frame_dimensions_.ysize_groups && - gx + 1 == frame_dimensions_.xsize_groups) { - RenderPadding(thread_id, Rect(x1, y1, full_image_xsize_ - x1, - full_image_ysize_ - y1)); + // Do not render padding if group is empty; if group is empty x0, y0 might + // have arbitrary values (from frame_origin). + if (group_rect.xsize() > 0 && group_rect.ysize() > 0) { + if (gx == 0 && gy == 0) { + RenderPadding(thread_id, Rect(0, 0, x0, y0)); + } + if (gy == 0) { + RenderPadding(thread_id, Rect(x0, 0, x1 - x0, y0)); + } + if (gx == 0) { + RenderPadding(thread_id, Rect(0, y0, x0, y1 - y0)); + } + if (gx == 0 && gy + 1 == frame_dimensions_.ysize_groups) { + RenderPadding(thread_id, Rect(0, y1, x0, full_image_ysize_ - y1)); + } + if (gy + 1 == frame_dimensions_.ysize_groups) { + RenderPadding(thread_id, Rect(x0, y1, x1 - x0, full_image_ysize_ - y1)); + } + if (gy == 0 && gx + 1 == frame_dimensions_.xsize_groups) { + RenderPadding(thread_id, Rect(x1, 0, full_image_xsize_ - x1, y0)); + } + if (gx + 1 == frame_dimensions_.xsize_groups) { + RenderPadding(thread_id, Rect(x1, y0, full_image_xsize_ - x1, y1 - y0)); + } + if (gy + 1 == frame_dimensions_.ysize_groups && + gx + 1 == frame_dimensions_.xsize_groups) { + RenderPadding(thread_id, Rect(x1, y1, full_image_xsize_ - x1, + full_image_ysize_ - y1)); + } } } Rect ready_rects[GroupBorderAssigner::kMaxToFinalize]; size_t num_ready_rects = 0; diff --git a/third_party/jpeg-xl/lib/jxl/render_pipeline/render_pipeline.h b/third_party/jpeg-xl/lib/jxl/render_pipeline/render_pipeline.h --- a/third_party/jpeg-xl/lib/jxl/render_pipeline/render_pipeline.h +++ b/third_party/jpeg-xl/lib/jxl/render_pipeline/render_pipeline.h @@ -85,11 +85,11 @@ // Allocates storage to run with `num` threads. If `use_group_ids` is true, // storage is allocated for each group, not each thread. The behaviour is // undefined if calling this function multiple times with a different value // for `use_group_ids`. - void PrepareForThreads(size_t num, bool use_group_ids); + Status PrepareForThreads(size_t num, bool use_group_ids); // Retrieves a buffer where input data should be stored by the callee. When // input has been provided for all buffers, the pipeline will complete its // processing. This method may be called multiple times concurrently from // different threads, provided that a different `thread_id` is given. @@ -113,13 +113,10 @@ FrameDimensions frame_dimensions_; std::vector group_completed_passes_; - // Indexed by thread_id - std::vector temp_buffers_; - friend class RenderPipelineInput; private: void InputReady(size_t group_id, size_t thread_id, const std::vector>& buffers); diff --git a/third_party/jpeg-xl/lib/jxl/render_pipeline/render_pipeline.cc b/third_party/jpeg-xl/lib/jxl/render_pipeline/render_pipeline.cc --- a/third_party/jpeg-xl/lib/jxl/render_pipeline/render_pipeline.cc +++ b/third_party/jpeg-xl/lib/jxl/render_pipeline/render_pipeline.cc @@ -114,22 +114,16 @@ } ProcessBuffers(group_id, thread_id); } -void RenderPipeline::PrepareForThreads(size_t num, bool use_group_ids) { - temp_buffers_.resize(num); - for (auto& thread_buffer : temp_buffers_) { - size_t size = 0; - for (const auto& stage : stages_) { - size = std::max(stage->settings_.temp_buffer_size, size); - } - if (size) { - thread_buffer = AllocateArray(sizeof(float) * size); - } +Status RenderPipeline::PrepareForThreads(size_t num, bool use_group_ids) { + for (const auto& stage : stages_) { + JXL_RETURN_IF_ERROR(stage->PrepareForThreads(num)); } PrepareForThreadsInternal(num, use_group_ids); + return true; } void RenderPipelineInput::Done() { JXL_ASSERT(pipeline_); pipeline_->InputReady(group_id_, thread_id_, buffers_); diff --git a/third_party/jpeg-xl/lib/jxl/render_pipeline/render_pipeline_stage.h b/third_party/jpeg-xl/lib/jxl/render_pipeline/render_pipeline_stage.h --- a/third_party/jpeg-xl/lib/jxl/render_pipeline/render_pipeline_stage.h +++ b/third_party/jpeg-xl/lib/jxl/render_pipeline/render_pipeline_stage.h @@ -53,14 +53,10 @@ // Log2 of the number of columns/rows of output that this stage will produce // for every input row for kInOut channels. size_t shift_x = 0; size_t shift_y = 0; - // Size (in floats) of the (aligned) per-thread temporary buffer to pass to - // ProcessRow. - size_t temp_buffer_size = 0; - static Settings ShiftX(size_t shift, size_t border) { Settings settings; settings.border_x = border; settings.shift_x = shift; return settings; @@ -71,12 +67,11 @@ settings.border_y = border; settings.shift_y = shift; return settings; } - static Settings Symmetric(size_t shift, size_t border, - size_t temp_buffer_size = 0) { + static Settings Symmetric(size_t shift, size_t border) { Settings settings; settings.border_x = settings.border_y = border; settings.shift_x = settings.shift_y = shift; return settings; } @@ -101,11 +96,11 @@ // `GroupBorderAssigner::kPaddingXRound`. If `settings_.temp_buffer_size` is // nonzero, `temp` will point to an HWY-aligned buffer of at least that number // of floats; concurrent calls will have different buffers. virtual void ProcessRow(const RowInfo& input_rows, const RowInfo& output_rows, size_t xextra, size_t xsize, size_t xpos, size_t ypos, - float* JXL_RESTRICT temp) const = 0; + size_t thread_id) const = 0; protected: explicit RenderPipelineStage(Settings settings) : settings_(settings) {} virtual Status IsInitialized() const { return true; } @@ -113,10 +108,12 @@ // Informs the stage about the total size of each channel. Few stages will // actually need to use this information. virtual void SetInputSizes( const std::vector>& input_sizes) {} + virtual Status PrepareForThreads(size_t num_threads) { return true; } + // How each channel will be processed. Channels are numbered starting from // color channels (always 3) and followed by all other channels. virtual RenderPipelineChannelMode GetChannelMode(size_t c) const = 0; // Returns a pointer to the input row of channel `c` with offset `y`. diff --git a/third_party/jpeg-xl/lib/jxl/render_pipeline/render_pipeline_test.cc b/third_party/jpeg-xl/lib/jxl/render_pipeline/render_pipeline_test.cc --- a/third_party/jpeg-xl/lib/jxl/render_pipeline/render_pipeline_test.cc +++ b/third_party/jpeg-xl/lib/jxl/render_pipeline/render_pipeline_test.cc @@ -47,20 +47,20 @@ FrameDimensions frame_dimensions; frame_dimensions.Set(/*xsize=*/1024, /*ysize=*/1024, /*group_size_shift=*/0, /*max_hshift=*/0, /*max_vshift=*/0, /*modular_mode=*/false, /*upsampling=*/1); auto pipeline = std::move(builder).Finalize(frame_dimensions); - pipeline->PrepareForThreads(1, /*use_group_ids=*/false); + ASSERT_TRUE(pipeline->PrepareForThreads(1, /*use_group_ids=*/false)); for (size_t i = 0; i < frame_dimensions.num_groups; i++) { auto input_buffers = pipeline->GetInputBuffers(i, 0); FillPlane(0.0f, input_buffers.GetBuffer(0).first, input_buffers.GetBuffer(0).second); input_buffers.Done(); } - EXPECT_TRUE(pipeline->PassesWithAllInput() == 1); + EXPECT_EQ(pipeline->PassesWithAllInput(), 1); } TEST(RenderPipelineTest, BuildFast) { RenderPipeline::Builder builder(/*num_c=*/1); builder.AddStage(jxl::make_unique()); @@ -82,20 +82,20 @@ FrameDimensions frame_dimensions; frame_dimensions.Set(/*xsize=*/1024, /*ysize=*/1024, /*group_size_shift=*/0, /*max_hshift=*/0, /*max_vshift=*/0, /*modular_mode=*/false, /*upsampling=*/1); auto pipeline = std::move(builder).Finalize(frame_dimensions); - pipeline->PrepareForThreads(1, /*use_group_ids=*/false); + ASSERT_TRUE(pipeline->PrepareForThreads(1, /*use_group_ids=*/false)); for (size_t i = 0; i < frame_dimensions.num_groups; i++) { auto input_buffers = pipeline->GetInputBuffers(i, 0); FillPlane(0.0f, input_buffers.GetBuffer(0).first, input_buffers.GetBuffer(0).second); input_buffers.Done(); } - EXPECT_TRUE(pipeline->PassesWithAllInput() == 1); + EXPECT_EQ(pipeline->PassesWithAllInput(), 1); } struct RenderPipelineTestInputSettings { // Input image. std::string input_path; @@ -220,11 +220,11 @@ {3, 8}, {128, 128}, {256, 256}, {258, 258}, {533, 401}, {777, 777}, }; for (auto size : sizes) { RenderPipelineTestInputSettings settings; - settings.input_path = "imagecompression.info/flower_foveon.png"; + settings.input_path = "third_party/imagecompression.info/flower_foveon.png"; settings.xsize = size.first; settings.ysize = size.second; // Base settings. settings.cparams.butteraugli_distance = 1.0; @@ -346,42 +346,46 @@ { auto s = settings; s.cparams_descr = "ModularLossy"; s.cparams.modular_mode = true; - s.cparams.quality_pair = {90, 90}; + s.cparams.butteraugli_distance = 1.f; all_tests.push_back(s); } { auto s = settings; - s.input_path = "imagecompression.info/flower_foveon_alpha.png"; + s.input_path = + "third_party/imagecompression.info/flower_foveon_alpha.png"; s.cparams_descr = "AlphaVarDCT"; all_tests.push_back(s); } { auto s = settings; - s.input_path = "imagecompression.info/flower_foveon_alpha.png"; + s.input_path = + "third_party/imagecompression.info/flower_foveon_alpha.png"; s.cparams_descr = "AlphaVarDCTUpsamplingEPF"; s.cparams.epf = 1; s.cparams.ec_resampling = 2; all_tests.push_back(s); } { auto s = settings; s.cparams.modular_mode = true; s.cparams.butteraugli_distance = 0; - s.input_path = "imagecompression.info/flower_foveon_alpha.png"; + s.input_path = + "third_party/imagecompression.info/flower_foveon_alpha.png"; s.cparams_descr = "AlphaLossless"; all_tests.push_back(s); } { auto s = settings; - s.input_path = "imagecompression.info/flower_foveon_alpha.png"; + s.input_path = + "third_party/imagecompression.info/flower_foveon_alpha.png"; s.cparams_descr = "AlphaDownsample"; s.cparams.ec_resampling = 2; all_tests.push_back(s); } @@ -393,14 +397,14 @@ } } #if JPEGXL_ENABLE_TRANSCODE_JPEG for (const char* input : - {"imagecompression.info/flower_foveon.png.im_q85_444.jpg", - "imagecompression.info/flower_foveon.png.im_q85_420.jpg", - "imagecompression.info/flower_foveon.png.im_q85_422.jpg", - "imagecompression.info/flower_foveon.png.im_q85_440.jpg"}) { + {"third_party/imagecompression.info/flower_foveon.png.im_q85_444.jpg", + "third_party/imagecompression.info/flower_foveon.png.im_q85_420.jpg", + "third_party/imagecompression.info/flower_foveon.png.im_q85_422.jpg", + "third_party/imagecompression.info/flower_foveon.png.im_q85_440.jpg"}) { RenderPipelineTestInputSettings settings; settings.input_path = input; settings.jpeg_transcode = true; settings.xsize = 2268; settings.ysize = 1512; diff --git a/third_party/jpeg-xl/lib/jxl/render_pipeline/simple_render_pipeline.cc b/third_party/jpeg-xl/lib/jxl/render_pipeline/simple_render_pipeline.cc --- a/third_party/jpeg-xl/lib/jxl/render_pipeline/simple_render_pipeline.cc +++ b/third_party/jpeg-xl/lib/jxl/render_pipeline/simple_render_pipeline.cc @@ -180,14 +180,12 @@ for (size_t iy = 0; iy < output_rows[c].size(); iy++) { output_rows[c][iy] = output_channels[c]->Row( (y << stage->settings_.shift_y) + iy + kRenderPipelineXOffset); } } - stage->ProcessRow( - input_rows, output_rows, /*xextra=*/0, xsize, - /*xpos=*/0, y, - reinterpret_cast(temp_buffers_[thread_id].get())); + stage->ProcessRow(input_rows, output_rows, /*xextra=*/0, xsize, + /*xpos=*/0, y, thread_id); } } // Move new channels to current channels. for (size_t c = 0; c < channel_data_.size(); c++) { diff --git a/third_party/jpeg-xl/lib/jxl/render_pipeline/stage_blending.cc b/third_party/jpeg-xl/lib/jxl/render_pipeline/stage_blending.cc --- a/third_party/jpeg-xl/lib/jxl/render_pipeline/stage_blending.cc +++ b/third_party/jpeg-xl/lib/jxl/render_pipeline/stage_blending.cc @@ -101,11 +101,11 @@ Status IsInitialized() const override { return initialized_; } void ProcessRow(const RowInfo& input_rows, const RowInfo& output_rows, size_t xextra, size_t xsize, size_t xpos, size_t ypos, - float* JXL_RESTRICT temp) const final { + size_t thread_id) const final { PROFILER_ZONE("Blend"); JXL_ASSERT(initialized_); const FrameOrigin& frame_origin = state_.frame_header.frame_origin; ssize_t bg_xpos = frame_origin.x0 + static_cast(xpos); ssize_t bg_ypos = frame_origin.y0 + static_cast(ypos); diff --git a/third_party/jpeg-xl/lib/jxl/render_pipeline/stage_chroma_upsampling.cc b/third_party/jpeg-xl/lib/jxl/render_pipeline/stage_chroma_upsampling.cc --- a/third_party/jpeg-xl/lib/jxl/render_pipeline/stage_chroma_upsampling.cc +++ b/third_party/jpeg-xl/lib/jxl/render_pipeline/stage_chroma_upsampling.cc @@ -23,11 +23,11 @@ /*shift=*/1, /*border=*/1)), c_(channel) {} void ProcessRow(const RowInfo& input_rows, const RowInfo& output_rows, size_t xextra, size_t xsize, size_t xpos, size_t ypos, - float* JXL_RESTRICT temp) const final { + size_t thread_id) const final { PROFILER_ZONE("HorizontalChromaUpsampling"); HWY_FULL(float) df; xextra = RoundUpTo(xextra, Lanes(df)); auto threefour = Set(df, 0.75f); auto onefour = Set(df, 0.25f); @@ -62,11 +62,11 @@ /*shift=*/1, /*border=*/1)), c_(channel) {} void ProcessRow(const RowInfo& input_rows, const RowInfo& output_rows, size_t xextra, size_t xsize, size_t xpos, size_t ypos, - float* JXL_RESTRICT temp) const final { + size_t thread_id) const final { PROFILER_ZONE("VerticalChromaUpsampling"); HWY_FULL(float) df; xextra = RoundUpTo(xextra, Lanes(df)); auto threefour = Set(df, 0.75f); auto onefour = Set(df, 0.25f); diff --git a/third_party/jpeg-xl/lib/jxl/render_pipeline/stage_epf.cc b/third_party/jpeg-xl/lib/jxl/render_pipeline/stage_epf.cc --- a/third_party/jpeg-xl/lib/jxl/render_pipeline/stage_epf.cc +++ b/third_party/jpeg-xl/lib/jxl/render_pipeline/stage_epf.cc @@ -58,11 +58,11 @@ *B = MulAdd(weight, cb, *B); } void ProcessRow(const RowInfo& input_rows, const RowInfo& output_rows, size_t xextra, size_t xsize, size_t xpos, size_t ypos, - float* JXL_RESTRICT temp) const final { + size_t thread_id) const final { DF df; xextra = RoundUpTo(xextra, Lanes(df)); const float* JXL_RESTRICT row_sigma = sigma_->Row(ypos / kBlockDim + kSigmaPadding); @@ -194,11 +194,11 @@ *B = MulAdd(weight, cb, *B); } void ProcessRow(const RowInfo& input_rows, const RowInfo& output_rows, size_t xextra, size_t xsize, size_t xpos, size_t ypos, - float* JXL_RESTRICT temp) const final { + size_t thread_id) const final { DF df; xextra = RoundUpTo(xextra, Lanes(df)); const float* JXL_RESTRICT row_sigma = sigma_->Row(ypos / kBlockDim + kSigmaPadding); @@ -379,11 +379,11 @@ *B = MulAdd(weight, cb, *B); } void ProcessRow(const RowInfo& input_rows, const RowInfo& output_rows, size_t xextra, size_t xsize, size_t xpos, size_t ypos, - float* JXL_RESTRICT temp) const final { + size_t thread_id) const final { DF df; xextra = RoundUpTo(xextra, Lanes(df)); const float* JXL_RESTRICT row_sigma = sigma_->Row(ypos / kBlockDim + kSigmaPadding); diff --git a/third_party/jpeg-xl/lib/jxl/render_pipeline/stage_gaborish.cc b/third_party/jpeg-xl/lib/jxl/render_pipeline/stage_gaborish.cc --- a/third_party/jpeg-xl/lib/jxl/render_pipeline/stage_gaborish.cc +++ b/third_party/jpeg-xl/lib/jxl/render_pipeline/stage_gaborish.cc @@ -39,11 +39,11 @@ } } void ProcessRow(const RowInfo& input_rows, const RowInfo& output_rows, size_t xextra, size_t xsize, size_t xpos, size_t ypos, - float* JXL_RESTRICT temp) const final { + size_t thread_id) const final { PROFILER_ZONE("Gaborish"); const HWY_FULL(float) d; for (size_t c = 0; c < 3; c++) { float* JXL_RESTRICT row_t = GetInputRow(input_rows, c, -1); diff --git a/third_party/jpeg-xl/lib/jxl/render_pipeline/stage_noise.cc b/third_party/jpeg-xl/lib/jxl/render_pipeline/stage_noise.cc --- a/third_party/jpeg-xl/lib/jxl/render_pipeline/stage_noise.cc +++ b/third_party/jpeg-xl/lib/jxl/render_pipeline/stage_noise.cc @@ -147,11 +147,11 @@ cmap_(cmap), first_c_(first_c) {} void ProcessRow(const RowInfo& input_rows, const RowInfo& output_rows, size_t xextra, size_t xsize, size_t xpos, size_t ypos, - float* JXL_RESTRICT temp) const final { + size_t thread_id) const final { PROFILER_ZONE("Noise apply"); if (!noise_params_.HasAny()) return; const StrengthEvalLut noise_model(noise_params_); D d; @@ -228,11 +228,11 @@ /*shift=*/0, /*border=*/2)), first_c_(first_c) {} void ProcessRow(const RowInfo& input_rows, const RowInfo& output_rows, size_t xextra, size_t xsize, size_t xpos, size_t ypos, - float* JXL_RESTRICT temp) const final { + size_t thread_id) const final { PROFILER_ZONE("Noise convolve"); const HWY_FULL(float) d; for (size_t c = first_c_; c < first_c_ + 3; c++) { float* JXL_RESTRICT rows[5]; diff --git a/third_party/jpeg-xl/lib/jxl/render_pipeline/stage_patches.cc b/third_party/jpeg-xl/lib/jxl/render_pipeline/stage_patches.cc --- a/third_party/jpeg-xl/lib/jxl/render_pipeline/stage_patches.cc +++ b/third_party/jpeg-xl/lib/jxl/render_pipeline/stage_patches.cc @@ -13,11 +13,11 @@ : RenderPipelineStage(RenderPipelineStage::Settings()), patches_(*patches) {} void ProcessRow(const RowInfo& input_rows, const RowInfo& output_rows, size_t xextra, size_t xsize, size_t xpos, size_t ypos, - float* JXL_RESTRICT temp) const final { + size_t thread_id) const final { PROFILER_ZONE("RenderPatches"); std::vector row_ptrs(input_rows.size()); for (size_t i = 0; i < row_ptrs.size(); i++) { row_ptrs[i] = GetInputRow(input_rows, i, 0) - xextra; } diff --git a/third_party/jpeg-xl/lib/jxl/render_pipeline/stage_splines.cc b/third_party/jpeg-xl/lib/jxl/render_pipeline/stage_splines.cc --- a/third_party/jpeg-xl/lib/jxl/render_pipeline/stage_splines.cc +++ b/third_party/jpeg-xl/lib/jxl/render_pipeline/stage_splines.cc @@ -20,11 +20,11 @@ : RenderPipelineStage(RenderPipelineStage::Settings()), splines_(*splines) {} void ProcessRow(const RowInfo& input_rows, const RowInfo& output_rows, size_t xextra, size_t xsize, size_t xpos, size_t ypos, - float* JXL_RESTRICT temp) const final { + size_t thread_id) const final { PROFILER_ZONE("RenderSplines"); float* row_x = GetInputRow(input_rows, 0, 0); float* row_y = GetInputRow(input_rows, 1, 0); float* row_b = GetInputRow(input_rows, 2, 0); splines_.AddToRow(row_x, row_y, row_b, Rect(xpos, ypos, xsize, 1)); diff --git a/third_party/jpeg-xl/lib/jxl/render_pipeline/stage_spot.cc b/third_party/jpeg-xl/lib/jxl/render_pipeline/stage_spot.cc --- a/third_party/jpeg-xl/lib/jxl/render_pipeline/stage_spot.cc +++ b/third_party/jpeg-xl/lib/jxl/render_pipeline/stage_spot.cc @@ -15,11 +15,11 @@ JXL_ASSERT(spot_c_ >= 3); } void ProcessRow(const RowInfo& input_rows, const RowInfo& output_rows, size_t xextra, size_t xsize, size_t xpos, size_t ypos, - float* JXL_RESTRICT temp) const final { + size_t thread_id) const final { // TODO(veluca): add SIMD. PROFILER_ZONE("RenderSpotColors"); float scale = spot_color_[3]; for (size_t c = 0; c < 3; c++) { float* JXL_RESTRICT p = GetInputRow(input_rows, c, 0); diff --git a/third_party/jpeg-xl/lib/jxl/render_pipeline/stage_upsampling.cc b/third_party/jpeg-xl/lib/jxl/render_pipeline/stage_upsampling.cc --- a/third_party/jpeg-xl/lib/jxl/render_pipeline/stage_upsampling.cc +++ b/third_party/jpeg-xl/lib/jxl/render_pipeline/stage_upsampling.cc @@ -38,11 +38,11 @@ } } void ProcessRow(const RowInfo& input_rows, const RowInfo& output_rows, size_t xextra, size_t xsize, size_t xpos, size_t ypos, - float* JXL_RESTRICT temp) const final { + size_t thread_id) const final { PROFILER_ZONE("Upsampling"); static HWY_FULL(float) df; size_t shift = settings_.shift_x; size_t N = 1 << shift; const size_t xsize_v = RoundUpTo(xsize, Lanes(df)); diff --git a/third_party/jpeg-xl/lib/jxl/render_pipeline/stage_write.h b/third_party/jpeg-xl/lib/jxl/render_pipeline/stage_write.h --- a/third_party/jpeg-xl/lib/jxl/render_pipeline/stage_write.h +++ b/third_party/jpeg-xl/lib/jxl/render_pipeline/stage_write.h @@ -6,10 +6,11 @@ #ifndef LIB_JXL_RENDER_PIPELINE_STAGE_WRITE_H_ #define LIB_JXL_RENDER_PIPELINE_STAGE_WRITE_H_ #include +#include "lib/jxl/dec_cache.h" #include "lib/jxl/image_bundle.h" #include "lib/jxl/render_pipeline/render_pipeline_stage.h" namespace jxl { @@ -18,18 +19,19 @@ // Gets a stage to write color channels to an Image3F. std::unique_ptr GetWriteToImage3FStage(Image3F* image); // Gets a stage to write to a uint8 buffer. -std::unique_ptr GetWriteToU8Stage( - uint8_t* rgb, size_t stride, size_t width, size_t height, bool rgba, - bool has_alpha, size_t alpha_c); +std::unique_ptr GetWriteToU8Stage(uint8_t* rgb, + size_t stride, + size_t height, bool rgba, + bool has_alpha, + size_t alpha_c); // Gets a stage to write to a pixel callback. std::unique_ptr GetWriteToPixelCallbackStage( - const std::function& - pixel_callback, - size_t width, size_t height, bool rgba, bool has_alpha, size_t alpha_c); + const PixelCallback& pixel_callback, size_t width, size_t height, bool rgba, + bool has_alpha, size_t alpha_c); } // namespace jxl #endif // LIB_JXL_RENDER_PIPELINE_STAGE_WRITE_H_ diff --git a/third_party/jpeg-xl/lib/jxl/render_pipeline/stage_write.cc b/third_party/jpeg-xl/lib/jxl/render_pipeline/stage_write.cc --- a/third_party/jpeg-xl/lib/jxl/render_pipeline/stage_write.cc +++ b/third_party/jpeg-xl/lib/jxl/render_pipeline/stage_write.cc @@ -4,10 +4,11 @@ // license that can be found in the LICENSE file. #include "lib/jxl/render_pipeline/stage_write.h" #include "lib/jxl/common.h" +#include "lib/jxl/dec_cache.h" #include "lib/jxl/image_bundle.h" #include "lib/jxl/sanitizers.h" #undef HWY_TARGET_INCLUDE #define HWY_TARGET_INCLUDE "lib/jxl/render_pipeline/stage_write.cc" @@ -73,24 +74,23 @@ #endif } class WriteToU8Stage : public RenderPipelineStage { public: - WriteToU8Stage(uint8_t* rgb, size_t stride, size_t width, size_t height, - bool rgba, bool has_alpha, size_t alpha_c) + WriteToU8Stage(uint8_t* rgb, size_t stride, size_t height, bool rgba, + bool has_alpha, size_t alpha_c) : RenderPipelineStage(RenderPipelineStage::Settings()), rgb_(rgb), stride_(stride), - width_(width), height_(height), rgba_(rgba), has_alpha_(has_alpha), alpha_c_(alpha_c) {} void ProcessRow(const RowInfo& input_rows, const RowInfo& output_rows, size_t xextra, size_t xsize, size_t xpos, size_t ypos, - float* JXL_RESTRICT temp) const final { + size_t thread_id) const final { if (ypos >= height_) return; JXL_DASSERT(xextra == 0); size_t bytes = rgba_ ? 4 : 3; const float* JXL_RESTRICT row_in_r = GetInputRow(input_rows, 0, 0); const float* JXL_RESTRICT row_in_g = GetInputRow(input_rows, 1, 0); @@ -122,11 +122,11 @@ : Set(d, 255.0f); auto r8 = U8FromU32(BitCast(du, NearestInt(rf))); auto g8 = U8FromU32(BitCast(du, NearestInt(gf))); auto b8 = U8FromU32(BitCast(du, NearestInt(bf))); auto a8 = U8FromU32(BitCast(du, NearestInt(af))); - size_t n = width_ - xpos - x; + size_t n = xsize - x; if (JXL_LIKELY(n >= Lanes(d))) { StoreRGBA(D::Rebind(), r8, g8, b8, a8, rgba_, Lanes(d), n, rgb_ + base_ptr + bytes * x); } else { StoreRGBA(D::Rebind(), r8, g8, b8, a8, rgba_, n, n, @@ -144,23 +144,24 @@ const char* GetName() const override { return "WriteToU8"; } private: uint8_t* rgb_; size_t stride_; - size_t width_; size_t height_; bool rgba_; bool has_alpha_; size_t alpha_c_; std::vector opaque_alpha_; }; -std::unique_ptr GetWriteToU8Stage( - uint8_t* rgb, size_t stride, size_t width, size_t height, bool rgba, - bool has_alpha, size_t alpha_c) { - return jxl::make_unique(rgb, stride, width, height, rgba, - has_alpha, alpha_c); +std::unique_ptr GetWriteToU8Stage(uint8_t* rgb, + size_t stride, + size_t height, bool rgba, + bool has_alpha, + size_t alpha_c) { + return jxl::make_unique(rgb, stride, height, rgba, has_alpha, + alpha_c); } // NOLINTNEXTLINE(google-readability-namespace-comments) } // namespace HWY_NAMESPACE } // namespace jxl @@ -188,10 +189,11 @@ for (size_t c = 1; c < input_sizes.size(); c++) { JXL_ASSERT(input_sizes[c].first == input_sizes[0].first); JXL_ASSERT(input_sizes[c].second == input_sizes[0].second); } #endif + // TODO(eustas): what should we do in the case of "want only ECs"? image_bundle_->SetFromImage( Image3F(input_sizes[0].first, input_sizes[0].second), color_encoding_); // TODO(veluca): consider not reallocating ECs if not needed. image_bundle_->extra_channels().clear(); for (size_t c = 3; c < input_sizes.size(); c++) { @@ -200,11 +202,11 @@ } } void ProcessRow(const RowInfo& input_rows, const RowInfo& output_rows, size_t xextra, size_t xsize, size_t xpos, size_t ypos, - float* JXL_RESTRICT temp) const final { + size_t thread_id) const final { for (size_t c = 0; c < 3; c++) { memcpy(image_bundle_->color()->PlaneRow(c, ypos) + xpos - xextra, GetInputRow(input_rows, c, 0) - xextra, sizeof(float) * (xsize + 2 * xextra)); } @@ -245,11 +247,11 @@ *image_ = Image3F(input_sizes[0].first, input_sizes[0].second); } void ProcessRow(const RowInfo& input_rows, const RowInfo& output_rows, size_t xextra, size_t xsize, size_t xpos, size_t ypos, - float* JXL_RESTRICT temp) const final { + size_t thread_id) const final { for (size_t c = 0; c < 3; c++) { memcpy(image_->PlaneRow(c, ypos) + xpos - xextra, GetInputRow(input_rows, c, 0) - xextra, sizeof(float) * (xsize + 2 * xextra)); } @@ -266,28 +268,38 @@ Image3F* image_; }; class WriteToPixelCallbackStage : public RenderPipelineStage { public: - WriteToPixelCallbackStage( - const std::function& - pixel_callback, - size_t width, size_t height, bool rgba, bool has_alpha, size_t alpha_c) + WriteToPixelCallbackStage(const PixelCallback& pixel_callback, size_t width, + size_t height, bool rgba, bool has_alpha, + size_t alpha_c) : RenderPipelineStage(RenderPipelineStage::Settings()), pixel_callback_(pixel_callback), width_(width), height_(height), rgba_(rgba), has_alpha_(has_alpha), alpha_c_(alpha_c), - opaque_alpha_(kMaxPixelsPerCall, 1.0f) { - settings_.temp_buffer_size = kMaxPixelsPerCall * (rgba_ ? 4 : 3); + opaque_alpha_(kMaxPixelsPerCall, 1.0f) {} + + WriteToPixelCallbackStage(const WriteToPixelCallbackStage&) = delete; + WriteToPixelCallbackStage& operator=(const WriteToPixelCallbackStage&) = + delete; + WriteToPixelCallbackStage(WriteToPixelCallbackStage&&) = delete; + WriteToPixelCallbackStage& operator=(WriteToPixelCallbackStage&&) = delete; + + ~WriteToPixelCallbackStage() override { + if (run_opaque_) { + pixel_callback_.destroy(run_opaque_); + } } void ProcessRow(const RowInfo& input_rows, const RowInfo& output_rows, size_t xextra, size_t xsize, size_t xpos, size_t ypos, - float* JXL_RESTRICT temp) const final { + size_t thread_id) const final { + JXL_DASSERT(run_opaque_); if (ypos >= height_) return; const float* line_buffers[4]; for (size_t c = 0; c < 3; c++) { line_buffers[c] = GetInputRow(input_rows, c, 0) - xextra; } @@ -300,19 +312,21 @@ // TODO(veluca): SIMD. ssize_t limit = std::min(xextra + xsize, width_ - xpos); for (ssize_t x0 = -xextra; x0 < limit; x0 += kMaxPixelsPerCall) { size_t j = 0; size_t ix = 0; + float* JXL_RESTRICT temp = + reinterpret_cast(temp_[thread_id].get()); for (; ix < kMaxPixelsPerCall && ssize_t(ix) + x0 < limit; ix++) { temp[j++] = line_buffers[0][ix]; temp[j++] = line_buffers[1][ix]; temp[j++] = line_buffers[2][ix]; if (rgba_) { temp[j++] = line_buffers[3][ix]; } } - pixel_callback_(temp, xpos + x0, ypos, ix); + pixel_callback_.run(run_opaque_, thread_id, xpos + x0, ypos, ix, temp); for (size_t c = 0; c < 3; c++) line_buffers[c] += kMaxPixelsPerCall; if (has_alpha_) line_buffers[3] += kMaxPixelsPerCall; } } @@ -323,19 +337,31 @@ } const char* GetName() const override { return "WritePixelCB"; } private: + Status PrepareForThreads(size_t num_threads) override { + run_opaque_ = + pixel_callback_.Init(num_threads, /*num_pixels=*/kMaxPixelsPerCall); + JXL_RETURN_IF_ERROR(run_opaque_ != nullptr); + temp_.resize(num_threads); + for (CacheAlignedUniquePtr& temp : temp_) { + temp = AllocateArray(sizeof(float) * kMaxPixelsPerCall * (rgba_ ? 4 : 3)); + } + return true; + } + static constexpr size_t kMaxPixelsPerCall = 1024; - const std::function& - pixel_callback_; + PixelCallback pixel_callback_; + void* run_opaque_ = nullptr; size_t width_; size_t height_; bool rgba_; bool has_alpha_; size_t alpha_c_; std::vector opaque_alpha_; + std::vector temp_; }; } // namespace std::unique_ptr GetWriteToImageBundleStage( @@ -346,21 +372,22 @@ std::unique_ptr GetWriteToImage3FStage(Image3F* image) { return jxl::make_unique(image); } -std::unique_ptr GetWriteToU8Stage( - uint8_t* rgb, size_t stride, size_t width, size_t height, bool rgba, - bool has_alpha, size_t alpha_c) { - return HWY_DYNAMIC_DISPATCH(GetWriteToU8Stage)(rgb, stride, width, height, - rgba, has_alpha, alpha_c); +std::unique_ptr GetWriteToU8Stage(uint8_t* rgb, + size_t stride, + size_t height, bool rgba, + bool has_alpha, + size_t alpha_c) { + return HWY_DYNAMIC_DISPATCH(GetWriteToU8Stage)(rgb, stride, height, rgba, + has_alpha, alpha_c); } std::unique_ptr GetWriteToPixelCallbackStage( - const std::function& - pixel_callback, - size_t width, size_t height, bool rgba, bool has_alpha, size_t alpha_c) { + const PixelCallback& pixel_callback, size_t width, size_t height, bool rgba, + bool has_alpha, size_t alpha_c) { return jxl::make_unique( pixel_callback, width, height, rgba, has_alpha, alpha_c); } } // namespace jxl diff --git a/third_party/jpeg-xl/lib/jxl/render_pipeline/stage_xyb.cc b/third_party/jpeg-xl/lib/jxl/render_pipeline/stage_xyb.cc --- a/third_party/jpeg-xl/lib/jxl/render_pipeline/stage_xyb.cc +++ b/third_party/jpeg-xl/lib/jxl/render_pipeline/stage_xyb.cc @@ -116,11 +116,11 @@ opsin_params_(opsin_params), op_(op) {} void ProcessRow(const RowInfo& input_rows, const RowInfo& output_rows, size_t xextra, size_t xsize, size_t xpos, size_t ypos, - float* JXL_RESTRICT temp) const final { + size_t thread_id) const final { PROFILER_ZONE("UndoXYB"); const HWY_FULL(float) d; const size_t xsize_v = RoundUpTo(xsize, Lanes(d)); float* JXL_RESTRICT row0 = GetInputRow(input_rows, 0, 0); @@ -227,11 +227,11 @@ has_alpha_(has_alpha), alpha_c_(alpha_c) {} void ProcessRow(const RowInfo& input_rows, const RowInfo& output_rows, size_t xextra, size_t xsize, size_t xpos, size_t ypos, - float* JXL_RESTRICT temp) const final { + size_t thread_id) const final { if (ypos >= height_) return; JXL_ASSERT(xextra == 0); const float* xyba[4] = { GetInputRow(input_rows, 0, 0), GetInputRow(input_rows, 1, 0), GetInputRow(input_rows, 2, 0), diff --git a/third_party/jpeg-xl/lib/jxl/render_pipeline/stage_ycbcr.cc b/third_party/jpeg-xl/lib/jxl/render_pipeline/stage_ycbcr.cc --- a/third_party/jpeg-xl/lib/jxl/render_pipeline/stage_ycbcr.cc +++ b/third_party/jpeg-xl/lib/jxl/render_pipeline/stage_ycbcr.cc @@ -18,11 +18,11 @@ public: kYCbCrStage() : RenderPipelineStage(RenderPipelineStage::Settings()) {} void ProcessRow(const RowInfo& input_rows, const RowInfo& output_rows, size_t xextra, size_t xsize, size_t xpos, size_t ypos, - float* JXL_RESTRICT temp) const final { + size_t thread_id) const final { PROFILER_ZONE("UndoYCbCr"); const HWY_FULL(float) df; // Full-range BT.601 as defined by JFIF Clause 7: diff --git a/third_party/jpeg-xl/lib/jxl/render_pipeline/test_render_pipeline_stages.h b/third_party/jpeg-xl/lib/jxl/render_pipeline/test_render_pipeline_stages.h --- a/third_party/jpeg-xl/lib/jxl/render_pipeline/test_render_pipeline_stages.h +++ b/third_party/jpeg-xl/lib/jxl/render_pipeline/test_render_pipeline_stages.h @@ -20,11 +20,11 @@ UpsampleXSlowStage() : RenderPipelineStage(RenderPipelineStage::Settings::ShiftX(1, 1)) {} void ProcessRow(const RowInfo& input_rows, const RowInfo& output_rows, size_t xextra, size_t xsize, size_t xpos, size_t ypos, - float* JXL_RESTRICT temp) const final { + size_t thread_id) const final { for (size_t c = 0; c < input_rows.size(); c++) { const float* row = GetInputRow(input_rows, c, 0); float* row_out = GetOutputRow(output_rows, c, 0); for (int64_t x = -xextra; x < (int64_t)(xsize + xextra); x++) { float xp = *(row + x - 1); @@ -50,11 +50,11 @@ UpsampleYSlowStage() : RenderPipelineStage(RenderPipelineStage::Settings::ShiftY(1, 1)) {} void ProcessRow(const RowInfo& input_rows, const RowInfo& output_rows, size_t xextra, size_t xsize, size_t xpos, size_t ypos, - float* JXL_RESTRICT temp) const final { + size_t thread_id) const final { for (size_t c = 0; c < input_rows.size(); c++) { const float* rowp = GetInputRow(input_rows, c, -1); const float* rowc = GetInputRow(input_rows, c, 0); const float* rown = GetInputRow(input_rows, c, 1); float* row_out0 = GetOutputRow(output_rows, c, 0); @@ -82,11 +82,11 @@ public: Check0FinalStage() : RenderPipelineStage(RenderPipelineStage::Settings()) {} void ProcessRow(const RowInfo& input_rows, const RowInfo& output_rows, size_t xextra, size_t xsize, size_t xpos, size_t ypos, - float* JXL_RESTRICT temp) const final { + size_t thread_id) const final { for (size_t c = 0; c < input_rows.size(); c++) { for (size_t x = 0; x < xsize; x++) { JXL_CHECK(fabsf(GetInputRow(input_rows, c, 0)[x]) < 1e-8); } } diff --git a/third_party/jpeg-xl/lib/jxl/roundtrip_test.cc b/third_party/jpeg-xl/lib/jxl/roundtrip_test.cc --- a/third_party/jpeg-xl/lib/jxl/roundtrip_test.cc +++ b/third_party/jpeg-xl/lib/jxl/roundtrip_test.cc @@ -49,11 +49,10 @@ switch (pixel_format.data_type) { case JXL_TYPE_UINT8: io.metadata.m.SetAlphaBits(8); break; case JXL_TYPE_UINT16: - case JXL_TYPE_UINT32: case JXL_TYPE_FLOAT: case JXL_TYPE_FLOAT16: io.metadata.m.SetAlphaBits(16); break; default: @@ -265,12 +264,13 @@ std::vector channel_infos; for (const auto& extra_channel : extra_channels) { auto channel_type = extra_channel.first; JxlExtraChannelInfo channel_info; JxlEncoderInitExtraChannelInfo(channel_type, &channel_info); - channel_info.bits_per_sample = basic_info.bits_per_sample; - channel_info.exponent_bits_per_sample = basic_info.exponent_bits_per_sample; + channel_info.bits_per_sample = (lossless ? basic_info.bits_per_sample : 8); + channel_info.exponent_bits_per_sample = + (lossless ? basic_info.exponent_bits_per_sample : 0); channel_infos.push_back(channel_info); } for (size_t index = 0; index < channel_infos.size(); index++) { EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetExtraChannelInfo(enc, index + has_interleaved_alpha, @@ -444,16 +444,10 @@ extra_channel_bytes.data(), xsize, ysize, extra_channel_pixel_format, extra_channel_output_pixel_format), 0u); EXPECT_EQ(extra_channel, extra_channel_bytes); - } else { - EXPECT_EQ(jxl::test::ComparePixels( - extra_channel.data(), extra_channel_bytes.data(), xsize, - ysize, extra_channel_pixel_format, - extra_channel_output_pixel_format, 16.0), - 0u); } } } } // namespace @@ -586,10 +580,11 @@ JxlBasicInfo basic_info; jxl::test::JxlBasicInfoSetFromPixelFormat(&basic_info, &pixel_format); basic_info.xsize = xsize; basic_info.ysize = ysize; basic_info.uses_original_profile = false; + EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetCodestreamLevel(enc, 10)); EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetBasicInfo(enc, &basic_info)); JxlColorEncoding color_encoding; if (pixel_format.data_type == JXL_TYPE_FLOAT) { JxlColorEncodingSetToLinearSRGB(&color_encoding, /*is_gray=*/pixel_format.num_channels < 3); @@ -804,11 +799,11 @@ } #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"; + "third_party/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)); diff --git a/third_party/jpeg-xl/lib/jxl/sanitizers.h b/third_party/jpeg-xl/lib/jxl/sanitizers.h --- a/third_party/jpeg-xl/lib/jxl/sanitizers.h +++ b/third_party/jpeg-xl/lib/jxl/sanitizers.h @@ -8,48 +8,13 @@ #include #include #include "lib/jxl/base/compiler_specific.h" +#include "lib/jxl/base/sanitizer_definitions.h" #include "lib/jxl/image.h" -#ifdef MEMORY_SANITIZER -#define JXL_MEMORY_SANITIZER 1 -#elif defined(__has_feature) -#if __has_feature(memory_sanitizer) -#define JXL_MEMORY_SANITIZER 1 -#else -#define JXL_MEMORY_SANITIZER 0 -#endif -#else -#define JXL_MEMORY_SANITIZER 0 -#endif - -#ifdef ADDRESS_SANITIZER -#define JXL_ADDRESS_SANITIZER 1 -#elif defined(__has_feature) -#if __has_feature(address_sanitizer) -#define JXL_ADDRESS_SANITIZER 1 -#else -#define JXL_ADDRESS_SANITIZER 0 -#endif -#else -#define JXL_ADDRESS_SANITIZER 0 -#endif - -#ifdef THREAD_SANITIZER -#define JXL_THREAD_SANITIZER 1 -#elif defined(__has_feature) -#if __has_feature(thread_sanitizer) -#define JXL_THREAD_SANITIZER 1 -#else -#define JXL_THREAD_SANITIZER 0 -#endif -#else -#define JXL_THREAD_SANITIZER 0 -#endif - #if JXL_MEMORY_SANITIZER #include #include #include diff --git a/third_party/jpeg-xl/lib/jxl/simd_util-inl.h b/third_party/jpeg-xl/lib/jxl/simd_util-inl.h --- a/third_party/jpeg-xl/lib/jxl/simd_util-inl.h +++ b/third_party/jpeg-xl/lib/jxl/simd_util-inl.h @@ -12,10 +12,12 @@ #define LIB_JXL_SIMD_UTIL_INL_H_ #endif #include +#include "lib/jxl/base/compiler_specific.h" + HWY_BEFORE_NAMESPACE(); namespace jxl { namespace HWY_NAMESPACE { #if HWY_CAP_GE512 @@ -37,39 +39,45 @@ #endif // Stores v0[0], v1[0], v0[1], v1[1], ... to mem, in this order. Mem must be // aligned. -template -void StoreInterleaved(const DF df, V v0, V v1, float* mem) { +template +void StoreInterleaved(const DF df, V v0, V v1, T* mem) { + static_assert(sizeof(T) == 4, "only use StoreInterleaved for 4-byte types"); #if HWY_TARGET == HWY_SCALAR Store(v0, df, mem); Store(v1, df, mem + 1); #elif !HWY_CAP_GE256 Store(InterleaveLower(df, v0, v1), df, mem); Store(InterleaveUpper(df, v0, v1), df, mem + Lanes(df)); -#elif !HWY_CAP_GE512 - auto t0 = InterleaveLower(df, v0, v1); - auto t1 = InterleaveUpper(df, v0, v1); - Store(ConcatLowerLower(df, t1, t0), df, mem); - Store(ConcatUpperUpper(df, t1, t0), df, mem + Lanes(df)); #else - auto t0 = InterleaveLower(df, v0, v1); - auto t1 = InterleaveUpper(df, v0, v1); - Store(Concat4(df, Quarter<0>(df, t0), Quarter<0>(df, t1), Quarter<1>(df, t0), - Quarter<1>(df, t1)), - df, mem); - Store(Concat4(df, Quarter<2>(df, t0), Quarter<2>(df, t1), Quarter<3>(df, t0), - Quarter<3>(df, t1)), - df, mem + Lanes(df)); + if (!HWY_CAP_GE512 || Lanes(df) == 8) { + auto t0 = InterleaveLower(df, v0, v1); + auto t1 = InterleaveUpper(df, v0, v1); + Store(ConcatLowerLower(df, t1, t0), df, mem); + Store(ConcatUpperUpper(df, t1, t0), df, mem + Lanes(df)); + } else { +#if HWY_CAP_GE512 + auto t0 = InterleaveLower(df, v0, v1); + auto t1 = InterleaveUpper(df, v0, v1); + Store(Concat4(df, Quarter<0>(df, t0), Quarter<0>(df, t1), + Quarter<1>(df, t0), Quarter<1>(df, t1)), + df, mem); + Store(Concat4(df, Quarter<2>(df, t0), Quarter<2>(df, t1), + Quarter<3>(df, t0), Quarter<3>(df, t1)), + df, mem + Lanes(df)); +#endif + } #endif } // Stores v0[0], v1[0], v2[0], v3[0], v0[1] ... to mem, in this order. Mem must // be aligned. -template -void StoreInterleaved(const DF df, V v0, V v1, V v2, V v3, float* mem) { +template +void StoreInterleaved(const DF df, V v0, V v1, V v2, V v3, T* mem) { + static_assert(sizeof(T) == 4, "only use StoreInterleaved for 4-byte types"); #if HWY_TARGET == HWY_SCALAR Store(v0, df, mem); Store(v1, df, mem + 1); Store(v2, df, mem + 2); Store(v3, df, mem + 3); @@ -253,10 +261,88 @@ Quarter<3>(df, m7)), df, mem + 7 * Lanes(df)); #endif } +#if HWY_CAP_GE256 +JXL_INLINE void Transpose8x8Block(const int32_t* JXL_RESTRICT from, + int32_t* JXL_RESTRICT to, size_t fromstride) { + const HWY_CAPPED(int32_t, 8) d; + auto i0 = Load(d, from); + auto i1 = Load(d, from + 1 * fromstride); + auto i2 = Load(d, from + 2 * fromstride); + auto i3 = Load(d, from + 3 * fromstride); + auto i4 = Load(d, from + 4 * fromstride); + auto i5 = Load(d, from + 5 * fromstride); + auto i6 = Load(d, from + 6 * fromstride); + auto i7 = Load(d, from + 7 * fromstride); + + const auto q0 = InterleaveLower(d, i0, i2); + const auto q1 = InterleaveLower(d, i1, i3); + const auto q2 = InterleaveUpper(d, i0, i2); + const auto q3 = InterleaveUpper(d, i1, i3); + const auto q4 = InterleaveLower(d, i4, i6); + const auto q5 = InterleaveLower(d, i5, i7); + const auto q6 = InterleaveUpper(d, i4, i6); + const auto q7 = InterleaveUpper(d, i5, i7); + + const auto r0 = InterleaveLower(d, q0, q1); + const auto r1 = InterleaveUpper(d, q0, q1); + const auto r2 = InterleaveLower(d, q2, q3); + const auto r3 = InterleaveUpper(d, q2, q3); + const auto r4 = InterleaveLower(d, q4, q5); + const auto r5 = InterleaveUpper(d, q4, q5); + const auto r6 = InterleaveLower(d, q6, q7); + const auto r7 = InterleaveUpper(d, q6, q7); + + i0 = ConcatLowerLower(d, r4, r0); + i1 = ConcatLowerLower(d, r5, r1); + i2 = ConcatLowerLower(d, r6, r2); + i3 = ConcatLowerLower(d, r7, r3); + i4 = ConcatUpperUpper(d, r4, r0); + i5 = ConcatUpperUpper(d, r5, r1); + i6 = ConcatUpperUpper(d, r6, r2); + i7 = ConcatUpperUpper(d, r7, r3); + + Store(i0, d, to); + Store(i1, d, to + 1 * 8); + Store(i2, d, to + 2 * 8); + Store(i3, d, to + 3 * 8); + Store(i4, d, to + 4 * 8); + Store(i5, d, to + 5 * 8); + Store(i6, d, to + 6 * 8); + Store(i7, d, to + 7 * 8); +} +#elif HWY_TARGET != HWY_SCALAR +JXL_INLINE void Transpose8x8Block(const int32_t* JXL_RESTRICT from, + int32_t* JXL_RESTRICT to, size_t fromstride) { + const HWY_CAPPED(int32_t, 4) d; + for (size_t n = 0; n < 8; n += 4) { + for (size_t m = 0; m < 8; m += 4) { + auto p0 = Load(d, from + n * fromstride + m); + auto p1 = Load(d, from + (n + 1) * fromstride + m); + auto p2 = Load(d, from + (n + 2) * fromstride + m); + auto p3 = Load(d, from + (n + 3) * fromstride + m); + const auto q0 = InterleaveLower(d, p0, p2); + const auto q1 = InterleaveLower(d, p1, p3); + const auto q2 = InterleaveUpper(d, p0, p2); + const auto q3 = InterleaveUpper(d, p1, p3); + + const auto r0 = InterleaveLower(d, q0, q1); + const auto r1 = InterleaveUpper(d, q0, q1); + const auto r2 = InterleaveLower(d, q2, q3); + const auto r3 = InterleaveUpper(d, q2, q3); + Store(r0, d, to + m * 8 + n); + Store(r1, d, to + (1 + m) * 8 + n); + Store(r2, d, to + (2 + m) * 8 + n); + Store(r3, d, to + (3 + m) * 8 + n); + } + } +} + +#endif + // NOLINTNEXTLINE(google-readability-namespace-comments) } // namespace HWY_NAMESPACE } // namespace jxl HWY_AFTER_NAMESPACE(); diff --git a/third_party/jpeg-xl/lib/jxl/speed_tier_test.cc b/third_party/jpeg-xl/lib/jxl/speed_tier_test.cc --- a/third_party/jpeg-xl/lib/jxl/speed_tier_test.cc +++ b/third_party/jpeg-xl/lib/jxl/speed_tier_test.cc @@ -83,11 +83,11 @@ SpeedTierTestParams{SpeedTier::kTortoise, /*shrink8=*/true})); TEST_P(SpeedTierTest, Roundtrip) { const PaddedBytes orig = - ReadTestData("wesaturate/500px/u76c0g_bliznaca_srgb8.png"); + ReadTestData("third_party/wesaturate/500px/u76c0g_bliznaca_srgb8.png"); CodecInOut io; ThreadPoolInternal pool(8); ASSERT_TRUE(SetFromBytes(Span(orig), &io, &pool)); const SpeedTierTestParams& params = GetParam(); diff --git a/third_party/jpeg-xl/lib/jxl/test_utils.h b/third_party/jpeg-xl/lib/jxl/test_utils.h --- a/third_party/jpeg-xl/lib/jxl/test_utils.h +++ b/third_party/jpeg-xl/lib/jxl/test_utils.h @@ -76,28 +76,17 @@ break; case JXL_TYPE_UINT16: basic_info->bits_per_sample = 16; basic_info->exponent_bits_per_sample = 0; break; - case JXL_TYPE_UINT32: - basic_info->bits_per_sample = 32; - basic_info->exponent_bits_per_sample = 0; - break; - case JXL_TYPE_BOOLEAN: - basic_info->bits_per_sample = 1; - basic_info->exponent_bits_per_sample = 0; - break; + default: + JXL_ABORT("Unhandled JxlDataType"); } if (pixel_format->num_channels == 2 || pixel_format->num_channels == 4) { - basic_info->alpha_exponent_bits = 0; - if (basic_info->bits_per_sample == 32) { - basic_info->alpha_bits = 16; - basic_info->num_extra_channels = 1; - } else { - basic_info->alpha_bits = basic_info->bits_per_sample; - basic_info->num_extra_channels = 1; - } + basic_info->alpha_exponent_bits = basic_info->exponent_bits_per_sample; + basic_info->alpha_bits = basic_info->bits_per_sample; + basic_info->num_extra_channels = 1; } else { basic_info->alpha_exponent_bits = 0; basic_info->alpha_bits = 0; } } @@ -429,43 +418,37 @@ return LoadFloat16(bits16); } size_t GetPrecision(JxlDataType data_type) { switch (data_type) { - case JXL_TYPE_BOOLEAN: - return 1; case JXL_TYPE_UINT8: return 8; case JXL_TYPE_UINT16: return 16; - case JXL_TYPE_UINT32: - return 32; case JXL_TYPE_FLOAT: // Floating point mantissa precision return 24; case JXL_TYPE_FLOAT16: return 11; + default: + JXL_ABORT("Unhandled JxlDataType"); } - JXL_ASSERT(false); // unknown type } size_t GetDataBits(JxlDataType data_type) { switch (data_type) { - case JXL_TYPE_BOOLEAN: - return 1; case JXL_TYPE_UINT8: return 8; case JXL_TYPE_UINT16: return 16; - case JXL_TYPE_UINT32: - return 32; case JXL_TYPE_FLOAT: return 32; case JXL_TYPE_FLOAT16: return 16; + default: + JXL_ABORT("Unhandled JxlDataType"); } - JXL_ASSERT(false); // unknown type } // Procedure to convert pixels to double precision, not efficient, but // well-controlled for testing. It uses double, to be able to represent all // precisions needed for the maximum data types the API supports: uint32_t @@ -481,27 +464,11 @@ size_t stride = xsize * jxl::DivCeil(GetDataBits(format.data_type) * num_channels, jxl::kBitsPerByte); if (format.align > 1) stride = jxl::RoundUpTo(stride, format.align); - if (format.data_type == JXL_TYPE_BOOLEAN) { - for (size_t y = 0; y < ysize; ++y) { - jxl::BitReader br(jxl::Span(pixels + stride * y, stride)); - for (size_t x = 0; x < xsize; ++x) { - size_t j = (y * xsize + x) * 4; - double r = br.ReadBits(1); - double g = gray ? r : br.ReadBits(1); - double b = gray ? r : br.ReadBits(1); - double a = alpha ? br.ReadBits(1) : 1; - result[j + 0] = r; - result[j + 1] = g; - result[j + 2] = b; - result[j + 3] = a; - } - JXL_CHECK(br.Close()); - } - } else if (format.data_type == JXL_TYPE_UINT8) { + if (format.data_type == JXL_TYPE_UINT8) { double mul = 1.0 / 255.0; // Multiplier to bring to 0-1.0 range for (size_t y = 0; y < ysize; ++y) { for (size_t x = 0; x < xsize; ++x) { size_t j = (y * xsize + x) * 4; size_t i = y * stride + x * num_channels; @@ -541,35 +508,10 @@ result[j + 1] = g * mul; result[j + 2] = b * mul; result[j + 3] = a * mul; } } - } else if (format.data_type == JXL_TYPE_UINT32) { - double mul = 1.0 / 4294967295.0; // Multiplier to bring to 0-1.0 range - for (size_t y = 0; y < ysize; ++y) { - for (size_t x = 0; x < xsize; ++x) { - size_t j = (y * xsize + x) * 4; - size_t i = y * stride + x * num_channels * 4; - double r, g, b, a; - if (format.endianness == JXL_BIG_ENDIAN) { - r = LoadBE32(pixels + i); - g = gray ? r : LoadBE32(pixels + i + 4); - b = gray ? r : LoadBE32(pixels + i + 8); - a = alpha ? LoadBE32(pixels + i + num_channels * 2 - 4) : 4294967295; - - } else { - r = LoadLE32(pixels + i); - g = gray ? r : LoadLE32(pixels + i + 4); - b = gray ? r : LoadLE32(pixels + i + 8); - a = alpha ? LoadLE32(pixels + i + num_channels * 2 - 4) : 4294967295; - } - result[j + 0] = r * mul; - result[j + 1] = g * mul; - result[j + 2] = b * mul; - result[j + 3] = a * mul; - } - } } else if (format.data_type == JXL_TYPE_FLOAT) { for (size_t y = 0; y < ysize; ++y) { for (size_t x = 0; x < xsize; ++x) { size_t j = (y * xsize + x) * 4; size_t i = y * stride + x * num_channels * 4; diff --git a/third_party/jpeg-xl/lib/jxl_benchmark.cmake b/third_party/jpeg-xl/lib/jxl_benchmark.cmake --- a/third_party/jpeg-xl/lib/jxl_benchmark.cmake +++ b/third_party/jpeg-xl/lib/jxl_benchmark.cmake @@ -32,11 +32,11 @@ # 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}" gbench_main.cc) target_compile_definitions(jxl_gbench PRIVATE - -DTEST_DATA_PATH="${PROJECT_SOURCE_DIR}/third_party/testdata") + -DTEST_DATA_PATH="${JPEGXL_TEST_DATA_PATH}") target_link_libraries(jxl_gbench jxl_extras-static jxl-static benchmark::benchmark ) diff --git a/third_party/jpeg-xl/lib/jxl_extras.cmake b/third_party/jpeg-xl/lib/jxl_extras.cmake --- a/third_party/jpeg-xl/lib/jxl_extras.cmake +++ b/third_party/jpeg-xl/lib/jxl_extras.cmake @@ -4,12 +4,10 @@ # license that can be found in the LICENSE file. set(JPEGXL_EXTRAS_SOURCES extras/codec.cc extras/codec.h - extras/codec_psd.cc - extras/codec_psd.h extras/dec/color_description.cc extras/dec/color_description.h extras/dec/color_hints.cc extras/dec/color_hints.h extras/dec/decode.cc @@ -42,24 +40,25 @@ extras/dec/decode.h extras/dec/pgx.cc extras/dec/pgx.h extras/dec/pnm.cc extras/dec/pnm.h + extras/packed_image.h ) -# TODO(sboukortt): Make the code in dec/ sufficiently independent from the rest that it links on its own as a shared -# library. -add_library(jxl_extras_dec-static STATIC EXCLUDE_FROM_ALL - "${JPEGXL_EXTRAS_DEC_SOURCES}") -target_compile_options(jxl_extras_dec-static PRIVATE "${JPEGXL_INTERNAL_FLAGS}") -set_property(TARGET jxl_extras_dec-static PROPERTY POSITION_INDEPENDENT_CODE ON) -target_include_directories(jxl_extras_dec-static PUBLIC +add_library(jxl_extras_dec-obj OBJECT "${JPEGXL_EXTRAS_DEC_SOURCES}") +target_compile_options(jxl_extras_dec-obj PRIVATE "${JPEGXL_INTERNAL_FLAGS}") +target_compile_definitions(jxl_extras_dec-obj PRIVATE -DJXL_EXPORT=) +set_property(TARGET jxl_extras_dec-obj PROPERTY POSITION_INDEPENDENT_CODE ON) +target_include_directories(jxl_extras_dec-obj PUBLIC ${PROJECT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/include ${CMAKE_CURRENT_BINARY_DIR}/include $ ) +set(JXL_EXTRAS_DEC_INTERNAL_LIBRARIES) +set(JXL_EXTRAS_DEC_PUBLIC_COMPILE_DEFINITIONS) # We only define a static library for jxl_extras since it uses internal parts # of jxl library which are not accessible from outside the library in the # shared library case. add_library(jxl_extras-static STATIC EXCLUDE_FROM_ALL @@ -72,32 +71,32 @@ jxl_extras_dec-static ) find_package(GIF 5.1) if(GIF_FOUND) - target_sources(jxl_extras_dec-static PRIVATE + target_sources(jxl_extras_dec-obj PRIVATE extras/dec/gif.cc extras/dec/gif.h ) - target_include_directories(jxl_extras_dec-static PRIVATE "${GIF_INCLUDE_DIRS}") - target_link_libraries(jxl_extras_dec-static PRIVATE ${GIF_LIBRARIES}) - target_compile_definitions(jxl_extras_dec-static PUBLIC -DJPEGXL_ENABLE_GIF=1) + target_include_directories(jxl_extras_dec-obj PRIVATE "${GIF_INCLUDE_DIRS}") + list(APPEND JXL_EXTRAS_DEC_INTERNAL_LIBRARIES ${GIF_LIBRARIES}) + list(APPEND JXL_EXTRAS_DEC_PUBLIC_DEFINITIONS -DJPEGXL_ENABLE_GIF=1) if(JPEGXL_DEP_LICENSE_DIR) configure_file("${JPEGXL_DEP_LICENSE_DIR}/libgif-dev/copyright" ${PROJECT_BINARY_DIR}/LICENSE.libgif COPYONLY) endif() # JPEGXL_DEP_LICENSE_DIR endif() find_package(JPEG) if(JPEG_FOUND) - target_sources(jxl_extras_dec-static PRIVATE + target_sources(jxl_extras_dec-obj PRIVATE extras/dec/jpg.cc extras/dec/jpg.h ) - target_include_directories(jxl_extras_dec-static PRIVATE "${JPEG_INCLUDE_DIRS}") - target_link_libraries(jxl_extras_dec-static PRIVATE ${JPEG_LIBRARIES}) - target_compile_definitions(jxl_extras_dec-static PUBLIC -DJPEGXL_ENABLE_JPEG=1) + target_include_directories(jxl_extras_dec-obj PRIVATE "${JPEG_INCLUDE_DIRS}") + list(APPEND JXL_EXTRAS_DEC_INTERNAL_LIBRARIES ${JPEG_LIBRARIES}) + list(APPEND JXL_EXTRAS_DEC_PUBLIC_DEFINITIONS -DJPEGXL_ENABLE_JPEG=1) target_sources(jxl_extras-static PRIVATE extras/enc/jpg.cc extras/enc/jpg.h ) target_include_directories(jxl_extras-static PRIVATE "${JPEG_INCLUDE_DIRS}") @@ -111,17 +110,17 @@ if(NOT JPEGXL_BUNDLE_LIBPNG) find_package(PNG) endif() if(PNG_FOUND) - target_sources(jxl_extras_dec-static PRIVATE + target_sources(jxl_extras_dec-obj PRIVATE extras/dec/apng.cc extras/dec/apng.h ) - target_include_directories(jxl_extras_dec-static PRIVATE "${PNG_INCLUDE_DIRS}") - target_link_libraries(jxl_extras_dec-static PRIVATE ${PNG_LIBRARIES}) - target_compile_definitions(jxl_extras_dec-static PUBLIC -DJPEGXL_ENABLE_APNG=1) + target_include_directories(jxl_extras_dec-obj PRIVATE "${PNG_INCLUDE_DIRS}") + list(APPEND JXL_EXTRAS_DEC_INTERNAL_LIBRARIES ${PNG_LIBRARIES}) + list(APPEND JXL_EXTRAS_DEC_PUBLIC_DEFINITIONS -DJPEGXL_ENABLE_APNG=1) target_sources(jxl_extras-static PRIVATE extras/enc/apng.cc extras/enc/apng.h ) target_include_directories(jxl_extras-static PUBLIC "${PNG_INCLUDE_DIRS}") @@ -137,16 +136,17 @@ endif () if (JPEGXL_ENABLE_OPENEXR) pkg_check_modules(OpenEXR IMPORTED_TARGET OpenEXR) if (OpenEXR_FOUND) - target_sources(jxl_extras_dec-static PRIVATE + target_sources(jxl_extras_dec-obj PRIVATE extras/dec/exr.cc extras/dec/exr.h ) - target_compile_definitions(jxl_extras_dec-static PUBLIC -DJPEGXL_ENABLE_EXR=1) - target_link_libraries(jxl_extras_dec-static PRIVATE PkgConfig::OpenEXR) + list(APPEND JXL_EXTRAS_DEC_PUBLIC_DEFINITIONS -DJPEGXL_ENABLE_EXR=1) + target_include_directories(jxl_extras_dec-obj PRIVATE "${OpenEXR_INCLUDE_DIRS}") + list(APPEND JXL_EXTRAS_DEC_INTERNAL_LIBRARIES PkgConfig::OpenEXR) target_sources(jxl_extras-static PRIVATE extras/enc/exr.cc extras/enc/exr.h ) target_compile_definitions(jxl_extras-static PUBLIC -DJPEGXL_ENABLE_EXR=1) @@ -163,5 +163,23 @@ set_source_files_properties(extras/dec/exr.cc extras/enc/exr.cc PROPERTIES COMPILE_FLAGS -fcxx-exceptions) endif() endif() endif() # OpenEXR_FOUND endif() # JPEGXL_ENABLE_OPENEXR + +target_compile_definitions(jxl_extras_dec-obj PRIVATE ${JXL_EXTRAS_DEC_PUBLIC_DEFINITIONS}) + +### Static library. +add_library(jxl_extras_dec-static STATIC $) +target_compile_definitions(jxl_extras_dec-static PUBLIC ${JXL_EXTRAS_DEC_PUBLIC_DEFINITIONS}) +target_link_libraries(jxl_extras_dec-static PRIVATE ${JXL_EXTRAS_DEC_INTERNAL_LIBRARIES}) + +### Shared library. +if (((NOT DEFINED "${TARGET_SUPPORTS_SHARED_LIBS}") OR + TARGET_SUPPORTS_SHARED_LIBS) AND NOT JPEGXL_STATIC AND BUILD_SHARED_LIBS) +add_library(jxl_extras_dec SHARED $) +target_compile_definitions(jxl_extras_dec PUBLIC ${JXL_EXTRAS_DEC_PUBLIC_DEFINITIONS}) +target_link_libraries(jxl_extras_dec PRIVATE ${JXL_EXTRAS_DEC_INTERNAL_LIBRARIES} jxl) +else() +add_library(jxl_extras_dec ALIAS jxl_extras_dec-static) +endif() # TARGET_SUPPORTS_SHARED_LIBS AND NOT JPEGXL_STATIC AND + # BUILD_SHARED_LIBS diff --git a/third_party/jpeg-xl/lib/jxl_tests.cmake b/third_party/jpeg-xl/lib/jxl_tests.cmake --- a/third_party/jpeg-xl/lib/jxl_tests.cmake +++ b/third_party/jpeg-xl/lib/jxl_tests.cmake @@ -83,11 +83,11 @@ target_compile_options(jxl_testlib-static PRIVATE ${JPEGXL_INTERNAL_FLAGS} ${JPEGXL_COVERAGE_FLAGS} ) target_compile_definitions(jxl_testlib-static PUBLIC - -DTEST_DATA_PATH="${PROJECT_SOURCE_DIR}/third_party/testdata") + -DTEST_DATA_PATH="${JPEGXL_TEST_DATA_PATH}") target_include_directories(jxl_testlib-static PUBLIC "${PROJECT_SOURCE_DIR}" ) target_link_libraries(jxl_testlib-static hwy jxl-static) diff --git a/third_party/jpeg-xl/lib/lib.gni b/third_party/jpeg-xl/lib/lib.gni --- a/third_party/jpeg-xl/lib/lib.gni +++ b/third_party/jpeg-xl/lib/lib.gni @@ -55,13 +55,13 @@ "jxl/base/padded_bytes.h", "jxl/base/printf_macros.h", "jxl/base/profiler.h", "jxl/base/random.cc", "jxl/base/random.h", + "jxl/base/sanitizer_definitions.h", "jxl/base/scope_guard.h", "jxl/base/span.h", - "jxl/base/status.cc", "jxl/base/status.h", "jxl/base/thread_pool_internal.h", "jxl/blending.cc", "jxl/blending.h", "jxl/box_content_decoder.cc", @@ -122,10 +122,12 @@ "jxl/enc_bit_writer.h", "jxl/entropy_coder.cc", "jxl/entropy_coder.h", "jxl/epf.cc", "jxl/epf.h", + "jxl/exif.cc", + "jxl/exif.h", "jxl/fast_dct-inl.h", "jxl/fast_dct.cc", "jxl/fast_dct.h", "jxl/fast_dct128-inl.h", "jxl/fast_dct16-inl.h", @@ -434,12 +436,10 @@ ] libjxl_extras_sources = [ "extras/codec.cc", "extras/codec.h", - "extras/codec_psd.cc", - "extras/codec_psd.h", "extras/dec/color_description.cc", "extras/dec/color_description.h", "extras/dec/color_hints.cc", "extras/dec/color_hints.h", "extras/dec/decode.cc", diff --git a/third_party/jpeg-xl/lib/threads/thread_parallel_runner.cc b/third_party/jpeg-xl/lib/threads/thread_parallel_runner.cc --- a/third_party/jpeg-xl/lib/threads/thread_parallel_runner.cc +++ b/third_party/jpeg-xl/lib/threads/thread_parallel_runner.cc @@ -86,13 +86,14 @@ void JxlThreadParallelRunnerDestroy(void* runner_opaque) { jpegxl::ThreadParallelRunner* runner = reinterpret_cast(runner_opaque); if (runner) { + JxlMemoryManager local_memory_manager = runner->memory_manager; // Call destructor directly since custom free function is used. runner->~ThreadParallelRunner(); - ThreadMemoryManagerFree(&runner->memory_manager, runner); + ThreadMemoryManagerFree(&local_memory_manager, runner); } } // Get default value for num_worker_threads parameter of // InitJxlThreadParallelRunner. diff --git a/third_party/jpeg-xl/plugins/gdk-pixbuf/CMakeLists.txt b/third_party/jpeg-xl/plugins/gdk-pixbuf/CMakeLists.txt --- a/third_party/jpeg-xl/plugins/gdk-pixbuf/CMakeLists.txt +++ b/third_party/jpeg-xl/plugins/gdk-pixbuf/CMakeLists.txt @@ -61,16 +61,20 @@ set(XVFB_PROGRAM_PREFIX "${XVFB_PROGRAM};-a") else() set(XVFB_PROGRAM_PREFIX "") endif() - add_test( - NAME pixbufloader_test_jxl - COMMAND - ${XVFB_PROGRAM_PREFIX} $ - "${CMAKE_CURRENT_SOURCE_DIR}/loaders_test.cache" - "${CMAKE_SOURCE_DIR}/third_party/testdata/jxl/blending/cropped_traffic_light.jxl" - WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} - ) - set_tests_properties(pixbufloader_test_jxl PROPERTIES SKIP_RETURN_CODE 254) + # libX11.so and libgdk-x11-2.0.so are not compiled with MSAN -> report + # use-of-uninitialized-value for string some internal string value. + if (NOT (SANITIZER STREQUAL "msan")) + add_test( + NAME pixbufloader_test_jxl + COMMAND + ${XVFB_PROGRAM_PREFIX} $ + "${CMAKE_CURRENT_SOURCE_DIR}/loaders_test.cache" + "${CMAKE_SOURCE_DIR}/third_party/testdata/jxl/blending/cropped_traffic_light.jxl" + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + ) + set_tests_properties(pixbufloader_test_jxl PROPERTIES SKIP_RETURN_CODE 254) + endif() endif() # Gdk_FOUND endif() # BUILD_TESTING diff --git a/third_party/jpeg-xl/third_party/CMakeLists.txt b/third_party/jpeg-xl/third_party/CMakeLists.txt --- a/third_party/jpeg-xl/third_party/CMakeLists.txt +++ b/third_party/jpeg-xl/third_party/CMakeLists.txt @@ -161,25 +161,32 @@ if(JPEGXL_ENABLE_TOOLS) if (JPEGXL_BUNDLE_GFLAGS) # Compile gflags from sources. set(GFLAGS_BUILD_TESTING OFF) add_subdirectory(gflags) + add_library(jxl_gflags ALIAS gflags_nothreads_static) configure_file("${CMAKE_CURRENT_SOURCE_DIR}/gflags/COPYING.txt" ${PROJECT_BINARY_DIR}/LICENSE.gflags COPYONLY) else() # Use sytem libgflags-dev - find_package(gflags REQUIRED) + find_package(gflags) if (NOT gflags_FOUND) message(FATAL_ERROR "Gflags not found, install libgflags-dev or download gflags source to" "third_party/gflags from https://github.com/gflags/gflags. You can use" " ${PROJECT_SOURCE_DIR}/deps.sh to download the dependency.") endif() if(JPEGXL_DEP_LICENSE_DIR) configure_file("${JPEGXL_DEP_LICENSE_DIR}/libgflags-dev/copyright" ${PROJECT_BINARY_DIR}/LICENSE.gflags COPYONLY) endif() # JPEGXL_DEP_LICENSE_DIR + add_library(jxl_gflags INTERFACE) + if(JPEGXL_STATIC) + target_link_libraries(jxl_gflags INTERFACE gflags_static) + else() + target_link_libraries(jxl_gflags INTERFACE gflags) + endif() endif() endif() # libpng if (JPEGXL_EMSCRIPTEN) @@ -209,10 +216,12 @@ set(ZLIB_LIBRARY zlibstatic) add_subdirectory(libpng EXCLUDE_FROM_ALL) set(PNG_FOUND YES PARENT_SCOPE) set(PNG_INCLUDE_DIRS "${CMAKE_CURRENT_SOURCE_DIR}/libpng/" PARENT_SCOPE) set(PNG_LIBRARIES png_static PARENT_SCOPE) + set_property(TARGET png_static PROPERTY POSITION_INDEPENDENT_CODE ON) + set_property(TARGET zlibstatic PROPERTY POSITION_INDEPENDENT_CODE ON) if(JPEGXL_DEP_LICENSE_DIR) configure_file("${CMAKE_CURRENT_SOURCE_DIR}/libpng/LICENSE" ${PROJECT_BINARY_DIR}/LICENSE.libpng COPYONLY) endif() else()