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 89875cba4d18485ec9692c80b747b59b73ce712e (2022-02-28T16:03:42Z). + release: commit 42b66f16faaa465a6fc61eb9e89f4f3327cfa810 (2022-03-22T17:46:33Z). - revision: 89875cba4d18485ec9692c80b747b59b73ce712e + revision: 42b66f16faaa465a6fc61eb9e89f4f3327cfa810 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 @@ -254,14 +254,16 @@ build_target: arm-linux-gnueabihf cmake_args: -DCMAKE_CROSSCOMPILING_EMULATOR=/usr/bin/qemu-arm-static - arch: i386 lowprecision: + test_in_pr: true build_target: i686-linux-gnu env: BUILD_DIR: build + WILL_RUN_TESTS: ${{ github.event_name == 'push' || (github.event_name == 'pull_request' && (matrix.test_in_pr || contains(github.event.pull_request.labels.*.names, 'CI:full'))) }} steps: - name: Setup apt shell: bash run: | @@ -379,17 +381,11 @@ --binutils ${{ matrix.build_target }}- \ --max-stack ${{ matrix.max_stack || '0' }} \ cjxl djxl libjxl.so libjxl_dec.so # Run the tests on push and when requested in pull_request. - name: Test - # Some tests have a small floating point error on i686. - # TODO(deymo): Re-enable i686 tests. - if: | - matrix.build_target != 'i686-linux-gnu' && ( - github.event_name == 'push' || - (github.event_name == 'pull_request' && - contains(github.event.pull_request.labels.*.names, 'CI:full'))) + if: env.WILL_RUN_TESTS == 'true' run: | ./ci.sh test env: BUILD_TARGET: ${{ matrix.build_target }} 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 @@ -106,10 +106,11 @@ pip install numpy - name: Checkout the conformance source uses: actions/checkout@v2 with: repository: libjxl/conformance + ref: ce5a804a3ff834097b93c39d2f93e5445f6bf340 path: conformance - name: Download and link conformance files run: | ${{ github.workspace }}/conformance/scripts/download_and_symlink.sh - uses: actions/download-artifact@v2 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 @@ -89,10 +89,12 @@ "Build JPEGXL fuzzer targets.") set(JPEGXL_ENABLE_DEVTOOLS false CACHE BOOL "Build JPEGXL developer tools.") set(JPEGXL_ENABLE_TOOLS true CACHE BOOL "Build JPEGXL user tools: cjxl and djxl.") +set(JPEGXL_ENABLE_DOXYGEN true CACHE BOOL + "Generate C API documentation using Doxygen.") set(JPEGXL_ENABLE_MANPAGES true CACHE BOOL "Build and install man pages for the command-line tools.") set(JPEGXL_ENABLE_BENCHMARK true CACHE BOOL "Build JPEGXL benchmark tools.") set(JPEGXL_ENABLE_EXAMPLES true CACHE BOOL @@ -114,11 +116,11 @@ set(JPEGXL_ENABLE_VIEWERS false CACHE BOOL "Build JPEGXL viewer tools for evaluation.") set(JPEGXL_ENABLE_TCMALLOC ${ENABLE_TCMALLOC_DEFAULT} CACHE BOOL "Build JPEGXL using gperftools (tcmalloc) allocator.") set(JPEGXL_ENABLE_PLUGINS false CACHE BOOL - "Build third-party plugings to support JPEG XL in other applications.") + "Build third-party plugins to support JPEG XL in other applications.") set(JPEGXL_ENABLE_COVERAGE false CACHE BOOL "Enable code coverage tracking for libjxl. This also enables debug and disables optimizations.") set(JPEGXL_ENABLE_PROFILER false CACHE BOOL "Builds in support for profiling (printed by tools if extra flags given") set(JPEGXL_ENABLE_TRANSCODE_JPEG true CACHE BOOL @@ -304,11 +306,11 @@ # 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 are they defined. +# Enable tests regardless of where they are defined. enable_testing() include(CTest) # Libraries. add_subdirectory(lib) @@ -322,10 +324,11 @@ COMMAND ${BASH_PROGRAM} ${CMAKE_CURRENT_SOURCE_DIR}/bash_test.sh) endif() endif() # BUILD_TESTING # Documentation generated by Doxygen +if(JPEGXL_ENABLE_DOXYGEN) find_package(Doxygen) if(DOXYGEN_FOUND) set(DOXYGEN_GENERATE_HTML "YES") set(DOXYGEN_GENERATE_XML "YES") set(DOXYGEN_STRIP_FROM_PATH "${CMAKE_CURRENT_SOURCE_DIR}/lib/include") @@ -364,10 +367,11 @@ # Create a "doc" target for compatibility since "doc" is not otherwise added to # the build when doxygen is not installed. add_custom_target(doc false COMMENT "Error: Can't generate doc since Doxygen not installed.") endif() # DOXYGEN_FOUND +endif() # JPEGXL_ENABLE_DOXYGEN if(JPEGXL_ENABLE_MANPAGES) find_program(ASCIIDOC a2x) if(NOT "${ASCIIDOC}" STREQUAL "ASCIIDOC-NOTFOUND") file(STRINGS "${ASCIIDOC}" ASCIIDOC_SHEBANG LIMIT_COUNT 1) diff --git a/third_party/jpeg-xl/bash_test.sh b/third_party/jpeg-xl/bash_test.sh --- a/third_party/jpeg-xl/bash_test.sh +++ b/third_party/jpeg-xl/bash_test.sh @@ -13,10 +13,13 @@ test_includes() { local ret=0 local f for f in $(git ls-files | grep -E '(\.cc|\.cpp|\.h)$'); do + if [ ! -e "$f" ]; then + continue + fi # Check that the public files (in lib/include/ directory) don't use the full # path to the public header since users of the library will include the # library as: #include "jxl/foobar.h". if [[ "${f#lib/include/}" != "${f}" ]]; then if grep -i -H -n -E '#include\s*[<"]lib/include/jxl' "$f" >&2; then @@ -49,10 +52,13 @@ test_include_collision() { local ret=0 local f for f in $(git ls-files | grep -E '^lib/include/'); do + if [ ! -e "$f" ]; then + continue + fi local base=${f#lib/include/} if [[ -e "lib/${base}" ]]; then echo "$f: Name collision, both $f and lib/${base} exist." >&2 ret=1 fi @@ -64,10 +70,13 @@ local ret=0 local f for f in $( git ls-files | grep -E \ '(Dockerfile.*|\.c|\.cc|\.cpp|\.gni|\.h|\.java|\.sh|\.m|\.py|\.ui|\.yml)$'); do + if [ ! -e "$f" ]; then + continue + fi if [[ "${f#third_party/}" == "$f" ]]; then # $f is not in third_party/ if ! head -n 10 "$f" | grep -F 'Copyright (c) the JPEG XL Project Authors.' >/dev/null ; then echo "$f: Missing Copyright blob near the top of the file." >&2 @@ -95,10 +104,13 @@ fi local f for f in $(git ls-files | grep -E "\.cc$" | xargs grep 'PRI[udx]S' | cut -f 1 -d : | uniq); do + if [ ! -e "$f" ]; then + continue + fi if ! grep -F printf_macros.h "$f" >/dev/null; then echo "$f: Add lib/jxl/base/printf_macros.h for PRI.S, or use other " \ "types for code outside lib/jxl library." >&2 ret=1 fi @@ -119,10 +131,13 @@ # Check that "dec_" code doesn't depend on "enc_" headers. test_dec_enc_deps() { local ret=0 local f for f in $(git ls-files | grep -E '/dec_'); do + if [ ! -e "$f" ]; then + continue + fi if [[ "${f#third_party/}" == "$f" ]]; then # $f is not in third_party/ if grep -n -H -E "#include.*/enc_" "$f" >&2; then echo "$f: Don't include \"enc_*\" files from \"dec_*\" files." >&2 ret=1 @@ -135,10 +150,13 @@ # Check for git merge conflict markers. test_merge_conflict() { local ret=0 TEXT_FILES='(\.cc|\.cpp|\.h|\.sh|\.m|\.py|\.md|\.txt|\.cmake)$' for f in $(git ls-files | grep -E "${TEXT_FILES}"); do + if [ ! -e "$f" ]; then + continue + fi if grep -E '^<<<<<<< ' "$f"; then echo "$f: Found git merge conflict marker. Please resolve." >&2 ret=1 fi done @@ -228,10 +246,13 @@ local field_classes=$( git ls-files | grep -E '\.(cc|h)' | grep -v 'test\.cc$' | xargs grep -h -o -E '\b[^ ]+ : public Fields' | cut -f 1 -d ' ') local classname for classname in ${field_classes}; do + if [ ! -e "$classname" ]; then + continue + fi if ! grep -E "\\b${classname}\\b" tools/fields_fuzzer.cc >/dev/null; then cat >&2 <&2; then echo "Don't use \"%n\"." >&2 ret=1 fi done diff --git a/third_party/jpeg-xl/experimental/fast_lossless/fast_lossless.cc b/third_party/jpeg-xl/experimental/fast_lossless/fast_lossless.cc --- a/third_party/jpeg-xl/experimental/fast_lossless/fast_lossless.cc +++ b/third_party/jpeg-xl/experimental/fast_lossless/fast_lossless.cc @@ -761,11 +761,10 @@ } inline void Chunk(size_t run, uint16_t* residuals) { // Run is broken. Encode the run and encode the individual vector. Rle(run, lz77_counts); - run = 0; for (size_t ix = 0; ix < kChunkSize; ix++) { unsigned token, nbits, bits; EncodeHybridUint000(residuals[ix], &token, &nbits, &bits); raw_counts[token]++; } @@ -1126,10 +1125,11 @@ size_t bytes_per_sample = (bitdepth > 8 ? 2 : 1); assert(bytedepth == bytes_per_sample); assert(width != 0); assert(height != 0); assert(stride >= nb_chans * bytes_per_sample * width); + (void)bytes_per_sample; // Count colors to try palette std::vector palette(kHashSize); palette[0] = 1; int16_t lookup[kHashSize]; @@ -1323,32 +1323,40 @@ *output = writer.data.release(); return writer.bytes_written; } size_t FastLosslessEncode(const unsigned char* rgba, size_t width, - size_t stride, size_t height, size_t c, + size_t stride, size_t height, size_t nb_chans, size_t bitdepth, int effort, unsigned char** output) { assert(bitdepth <= 12); assert(bitdepth > 0); - assert(c <= 4); - assert(c != 0); + assert(nb_chans <= 4); + assert(nb_chans != 0); if (bitdepth <= 8) { - if (c == 1) + if (nb_chans == 1) { return LLEnc<1, 1>(rgba, width, stride, height, bitdepth, effort, output); - if (c == 2) + } + if (nb_chans == 2) { return LLEnc<2, 1>(rgba, width, stride, height, bitdepth, effort, output); - if (c == 3) + } + if (nb_chans == 3) { return LLEnc<3, 1>(rgba, width, stride, height, bitdepth, effort, output); - if (c == 4) + } + if (nb_chans == 4) { return LLEnc<4, 1>(rgba, width, stride, height, bitdepth, effort, output); + } } else { - if (c == 1) + if (nb_chans == 1) { return LLEnc<1, 2>(rgba, width, stride, height, bitdepth, effort, output); - if (c == 2) + } + if (nb_chans == 2) { return LLEnc<2, 2>(rgba, width, stride, height, bitdepth, effort, output); - if (c == 3) + } + if (nb_chans == 3) { return LLEnc<3, 2>(rgba, width, stride, height, bitdepth, effort, output); - if (c == 4) + } + if (nb_chans == 4) { return LLEnc<4, 2>(rgba, width, stride, height, bitdepth, effort, output); + } } return 0; } 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 @@ -112,12 +112,12 @@ "PNM encoder cannot store custom ICC profile; decoder\n" "will need hint key=color_space to get the same values"); } JXL_RETURN_IF_ERROR(extras::ConvertCodecInOutToPackedPixelFile( io, format, c_desired, pool, &ppf)); - JXL_RETURN_IF_ERROR( - extras::EncodeImagePNM(ppf, bits_per_sample, pool, &bytes_vector)); + JXL_RETURN_IF_ERROR(extras::EncodeImagePNM( + ppf, bits_per_sample, pool, /*frame_index=*/0, &bytes_vector)); bytes->assign(bytes_vector.data(), bytes_vector.data() + bytes_vector.size()); return true; case extras::Codec::kPGX: return extras::EncodeImagePGX(&io, c_desired, bits_per_sample, pool, 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" @@ -588,16 +589,20 @@ // TODO(jon): catch special case of PQ and synthesize color encoding // in that case int compression_type; png_bytep profile; png_charp name; - png_uint_32 proflen; - png_get_iCCP(png_ptr, info_ptr, &name, &compression_type, &profile, - &proflen); - ppf->icc.resize(proflen); - memcpy(ppf->icc.data(), profile, proflen); - have_color = true; + 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); + have_color = true; + } else { + // TODO(eustas): JXL_WARNING? + } } else if (id == kId_sRGB) { JXL_RETURN_IF_ERROR(DecodeSRGB(chunk.data() + 8, chunk.size() - 12, &ppf->color_encoding)); have_srgb = true; have_color = true; @@ -664,11 +669,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; @@ -704,43 +710,50 @@ 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; + pframe.frame_info.layer_info.have_crop = 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; 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/jpg.cc b/third_party/jpeg-xl/lib/extras/dec/jpg.cc --- a/third_party/jpeg-xl/lib/extras/dec/jpg.cc +++ b/third_party/jpeg-xl/lib/extras/dec/jpg.cc @@ -151,12 +151,13 @@ longjmp(*env, 1); } void MyOutputMessage(j_common_ptr cinfo) { #if JXL_DEBUG_WARNING == 1 - char buf[JMSG_LENGTH_MAX]; + char buf[JMSG_LENGTH_MAX + 1]; (*cinfo->err->format_message)(cinfo, buf); + buf[JMSG_LENGTH_MAX] = 0; JXL_WARNING("%s", buf); #endif } } // namespace 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,12 +17,13 @@ namespace { struct HeaderPNM { size_t xsize; size_t ysize; - bool is_bit; // PBM - bool is_gray; // PGM + bool is_bit; // PBM + bool is_gray; // PGM + bool has_alpha; // PAM size_t bits_per_sample; bool floating_point; bool big_endian; }; @@ -53,11 +54,12 @@ case '6': header->is_gray = false; return ParseHeaderPNM(header, pos); - // TODO(jon): P7 (PAM) + case '7': + return ParseHeaderPAM(header, pos); case 'F': header->is_gray = false; return ParseHeaderPFM(header, pos); @@ -165,19 +167,77 @@ ++pos_; } return true; } - Status ReadChar(char* out) { - // Unlikely to happen. - if (pos_ + 1 < pos_) return JXL_FAILURE("Y4M: overflow"); + Status MatchString(const char* keyword) { + 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()); + return true; + } - if (pos_ >= end_) { - return JXL_FAILURE("Y4M: unexpected end of input"); + Status ParseHeaderPAM(HeaderPNM* header, const uint8_t** pos) { + size_t depth = 3; + size_t max_val = 255; + while (!MatchString("ENDHDR")) { + 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)); + } else if (MatchString("DEPTH")) { + JXL_RETURN_IF_ERROR(ParseUnsigned(&depth)); + } else if (MatchString("MAXVAL")) { + JXL_RETURN_IF_ERROR(ParseUnsigned(&max_val)); + } else if (MatchString("TUPLTYPE")) { + if (MatchString("RGB_ALPHA")) { + header->has_alpha = true; + } else if (MatchString("RGB")) { + } else if (MatchString("GRAYSCALE_ALPHA")) { + header->has_alpha = true; + header->is_gray = true; + } else if (MatchString("GRAYSCALE")) { + header->is_gray = true; + } else if (MatchString("BLACKANDWHITE_ALPHA")) { + header->has_alpha = true; + header->is_gray = true; + max_val = 1; + } else if (MatchString("BLACKANDWHITE")) { + header->is_gray = true; + max_val = 1; + } else { + return JXL_FAILURE("PAM: unknown TUPLTYPE"); + } + } else { + constexpr size_t kMaxHeaderLength = 20; + char unknown_header[kMaxHeaderLength + 1]; + size_t len = std::min(kMaxHeaderLength, end_ - pos_); + strncpy(unknown_header, reinterpret_cast(pos_), len); + unknown_header[len] = 0; + return JXL_FAILURE("PAM: unknown header keyword: %s", unknown_header); + } } - *out = *pos_; - pos_++; + size_t num_channels = header->is_gray ? 1 : 3; + if (header->has_alpha) num_channels++; + if (num_channels != depth) { + 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); + + header->floating_point = false; + header->big_endian = true; + *pos = pos_; return true; } Status ParseHeaderPNM(HeaderPNM* header, const uint8_t** pos) { JXL_RETURN_IF_ERROR(SkipWhitespace()); @@ -250,10 +310,13 @@ if (header.bits_per_sample == 0 || header.bits_per_sample > 32) { return JXL_FAILURE("PNM: bits_per_sample invalid"); } + // PPM specify that in the raster, the sample values are "nonlinear" (BP.709, + // with gamma number of 2.2). Deviate from the specification and assume + // `sRGB` in our implementation. JXL_RETURN_IF_ERROR(ApplyColorHints(color_hints, /*color_already_set=*/false, header.is_gray, ppf)); ppf->info.xsize = header.xsize; ppf->info.ysize = header.ysize; @@ -265,14 +328,15 @@ ppf->info.exponent_bits_per_sample = 0; } ppf->info.orientation = JXL_ORIENT_IDENTITY; - // No alpha in PNM - ppf->info.alpha_bits = 0; + // No alpha in PNM and PFM + ppf->info.alpha_bits = (header.has_alpha ? ppf->info.bits_per_sample : 0); ppf->info.alpha_exponent_bits = 0; - ppf->info.num_color_channels = header.is_gray ? 1 : 3; + ppf->info.num_color_channels = (header.is_gray ? 1 : 3); + ppf->info.num_extra_channels = (header.has_alpha ? 1 : 0); JxlDataType data_type; if (header.floating_point) { // There's no float16 pnm version. data_type = JXL_TYPE_FLOAT; @@ -287,11 +351,12 @@ data_type = JXL_TYPE_UINT8; } } const JxlPixelFormat format{ - /*num_channels=*/ppf->info.num_color_channels, + /*num_channels=*/ppf->info.num_color_channels + + ppf->info.num_extra_channels, /*data_type=*/data_type, /*endianness=*/header.big_endian ? JXL_BIG_ENDIAN : JXL_LITTLE_ENDIAN, /*align=*/0, }; ppf->frames.clear(); 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 @@ -258,11 +258,11 @@ png_byte fdat[5] = "fdAT"; png_write_chunk(png_ptr, fdat, fdata.data(), fdata.size()); } count++; - if (count == io->frames.size()) png_write_end(png_ptr, NULL); + if (count == io->frames.size() || !have_anim) png_write_end(png_ptr, NULL); png_destroy_write_struct(&png_ptr, &info_ptr); } return true; diff --git a/third_party/jpeg-xl/lib/extras/enc/pnm.h b/third_party/jpeg-xl/lib/extras/enc/pnm.h --- a/third_party/jpeg-xl/lib/extras/enc/pnm.h +++ b/third_party/jpeg-xl/lib/extras/enc/pnm.h @@ -21,11 +21,12 @@ namespace jxl { namespace extras { // Transforms from io->c_current to `c_desired` and encodes into `bytes`. Status EncodeImagePNM(const PackedPixelFile& ppf, size_t bits_per_sample, - ThreadPool* pool, std::vector* bytes); + ThreadPool* pool, size_t frame_index, + std::vector* bytes); } // namespace extras } // namespace jxl #endif // LIB_EXTRAS_ENC_PNM_H_ 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 @@ -95,11 +95,12 @@ } } // namespace Status EncodeImagePNM(const PackedPixelFile& ppf, size_t bits_per_sample, - ThreadPool* pool, std::vector* bytes) { + 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) const JxlEndianness endianness = floating_point ? JXL_NATIVE_ENDIAN : JXL_BIG_ENDIAN; if (!ppf.metadata.exif.empty() || !ppf.metadata.iptc.empty() || @@ -112,17 +113,18 @@ bool is_little_endian = endianness == JXL_LITTLE_ENDIAN || (endianness == JXL_NATIVE_ENDIAN && IsLittleEndian()); JXL_RETURN_IF_ERROR(EncodeHeader(ppf, bits_per_sample, is_little_endian, header, &header_size)); bytes->resize(static_cast(header_size) + - ppf.frames[0].color.pixels_size); + ppf.frames[frame_index].color.pixels_size); memcpy(bytes->data(), header, static_cast(header_size)); - memcpy(bytes->data() + header_size, ppf.frames[0].color.pixels(), - ppf.frames[0].color.pixels_size); + memcpy(bytes->data() + header_size, ppf.frames[frame_index].color.pixels(), + ppf.frames[frame_index].color.pixels_size); if (floating_point) { VerticallyFlipImage(reinterpret_cast(bytes->data() + header_size), - ppf.frames[0].color.xsize, ppf.frames[0].color.ysize, + ppf.frames[frame_index].color.xsize, + ppf.frames[frame_index].color.ysize, ppf.info.num_color_channels); } return true; } 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 @@ -12,10 +12,11 @@ #include #include #include #include +#include #include #include #include #include "jxl/codestream_header.h" @@ -37,11 +38,11 @@ : xsize(xsize), ysize(ysize), stride(stride), format(format), pixels_size(ysize * stride), - pixels_(malloc(pixels_size), free) {} + pixels_(malloc(std::max(1, pixels_size)), free) {} // Construct the image using the passed pixel buffer. The buffer is owned by // this object and released with free(). PackedImage(size_t xsize, size_t ysize, const JxlPixelFormat& format, void* pixels, size_t pixels_size) : xsize(xsize), @@ -113,20 +114,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 @@ -115,44 +115,45 @@ 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; JXL_RETURN_IF_ERROR(ConvertFromExternal( span, frame.color.xsize, frame.color.ysize, - io->metadata.m.color_encoding, - /*has_alpha=*/frame.color.format.num_channels == 2 || - frame.color.format.num_channels == 4, + io->metadata.m.color_encoding, frame.color.format.num_channels, /*alpha_is_premultiplied=*/ppf.info.alpha_premultiplied, frame_bits_per_sample, frame.color.format.endianness, /*flipped_y=*/frame.color.flipped_y, pool, &bundle, /*float_in=*/float_in, /*align=*/0)); - 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; } diff --git a/third_party/jpeg-xl/lib/include/jxl/codestream_header.h b/third_party/jpeg-xl/lib/include/jxl/codestream_header.h --- a/third_party/jpeg-xl/lib/include/jxl/codestream_header.h +++ b/third_party/jpeg-xl/lib/include/jxl/codestream_header.h @@ -141,10 +141,12 @@ /** Upper bound on the intensity level present in the image in nits. For * unsigned integer pixel encodings, this is the brightness of the largest * representable value. The image does not necessarily contain a pixel * actually this bright. An encoder is allowed to set 255 for SDR images * without computing a histogram. + * Leaving this set to its default of 0 lets libjxl choose a sensible default + * value based on the color encoding. */ float intensity_target; /** Lower bound on the intensity level present in the image. This may be * loose, i.e. lower than the actual darkest pixel. When tone mapping, a 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 @@ -507,10 +507,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 @@ -529,11 +531,12 @@ * includes reference to the encoder object. * @param pixel_format format for pixels. Object owned by the caller and its * contents are copied internally. * @param buffer buffer type to input the pixel data from. Owned by the caller * and its contents are copied internally. - * @param size size of buffer in bytes. + * @param size size of buffer in bytes. This size should match what is implied + * by the frame dimensions and the pixel format. * @return JXL_ENC_SUCCESS on success, JXL_ENC_ERROR on error */ JXL_EXPORT JxlEncoderStatus JxlEncoderAddImageFrame( const JxlEncoderFrameSettings* frame_settings, const JxlPixelFormat* pixel_format, const void* buffer, size_t size); @@ -554,11 +557,12 @@ * @param pixel_format format for pixels. Object owned by the caller and its * contents are copied internally. The num_channels value is ignored, since the * number of channels for an extra channel is always assumed to be one. * @param buffer buffer type to input the pixel data from. Owned by the caller * and its contents are copied internally. - * @param size size of buffer in bytes. + * @param size size of buffer in bytes. This size should match what is implied + * by the frame dimensions and the pixel format. * @param index index of the extra channel to use. * @return JXL_ENC_SUCCESS on success, JXL_ENC_ERROR on error */ JXL_EXPORT JxlEncoderStatus JxlEncoderSetExtraChannelBuffer( const JxlEncoderFrameSettings* frame_settings, 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 @@ -91,15 +91,11 @@ jxl/dec_noise.cc jxl/dec_noise.h jxl/dec_params.h jxl/dec_patch_dictionary.cc jxl/dec_patch_dictionary.h - jxl/dec_reconstruct.cc - jxl/dec_reconstruct.h jxl/dec_transforms-inl.h - jxl/dec_upsample.cc - jxl/dec_upsample.h jxl/dec_xyb-inl.h jxl/dec_xyb.cc jxl/dec_xyb.h jxl/decode.cc jxl/decode_to_jpeg.cc @@ -121,13 +117,10 @@ jxl/fast_dct8-inl.h jxl/fast_math-inl.h jxl/field_encodings.h jxl/fields.cc jxl/fields.h - jxl/filters.cc - jxl/filters.h - jxl/filters_internal.h jxl/frame_header.cc jxl/frame_header.h jxl/gauss_blur.cc jxl/gauss_blur.h jxl/headers.cc diff --git a/third_party/jpeg-xl/lib/jxl/base/scope_guard.h b/third_party/jpeg-xl/lib/jxl/base/scope_guard.h --- a/third_party/jpeg-xl/lib/jxl/base/scope_guard.h +++ b/third_party/jpeg-xl/lib/jxl/base/scope_guard.h @@ -22,11 +22,11 @@ ScopeGuard(ScopeGuard &&other) : callback_(std::move(other.callback_)) { other.armed_ = false; } template - ScopeGuard(CallbackParam &&callback) + explicit ScopeGuard(CallbackParam &&callback) : callback_(std::forward(callback)), armed_(true) {} ~ScopeGuard() { if (armed_) callback_(); } @@ -38,11 +38,11 @@ bool armed_; }; template ScopeGuard MakeScopeGuard(Callback &&callback) { - return {std::forward(callback)}; + return ScopeGuard{std::forward(callback)}; } } // namespace jxl #endif // LIB_JXL_BASE_SCOPE_GUARD_H_ diff --git a/third_party/jpeg-xl/lib/jxl/base/span.h b/third_party/jpeg-xl/lib/jxl/base/span.h --- a/third_party/jpeg-xl/lib/jxl/base/span.h +++ b/third_party/jpeg-xl/lib/jxl/base/span.h @@ -35,10 +35,12 @@ constexpr T* data() const noexcept { return ptr_; } constexpr size_t size() const noexcept { return len_; } + constexpr bool empty() const noexcept { return len_ == 0; } + constexpr T& operator[](size_t i) const noexcept { // MSVC 2015 accepts this as constexpr, but not ptr_[i] return *(data() + i); } diff --git a/third_party/jpeg-xl/lib/jxl/blending.h b/third_party/jpeg-xl/lib/jxl/blending.h --- a/third_party/jpeg-xl/lib/jxl/blending.h +++ b/third_party/jpeg-xl/lib/jxl/blending.h @@ -9,88 +9,16 @@ #include "lib/jxl/dec_patch_dictionary.h" #include "lib/jxl/image_bundle.h" namespace jxl { +bool NeedsBlending(PassesDecoderState* dec_state); + void PerformBlending(const float* const* bg, const float* const* fg, float* const* out, size_t x0, size_t xsize, const PatchBlending& color_blending, const PatchBlending* ec_blending, const std::vector& extra_channel_info); -class ImageBlender { - public: - class RectBlender { - public: - // Does the blending for a given row of the rect passed to - // ImageBlender::PrepareRect. - Status DoBlending(size_t y); - - // If this returns true, then nothing needs to be done for this rect and - // DoBlending can be skipped (but does not have to). - bool done() const { return done_; } - - private: - friend class ImageBlender; - explicit RectBlender(bool done) : done_(done) {} - - bool done_; - Rect current_overlap_; - Rect current_cropbox_; - const std::vector* extra_channel_info_; - std::vector fg_ptrs_; - std::vector fg_strides_; - std::vector bg_ptrs_; - std::vector bg_strides_; - std::vector out_ptrs_; - std::vector out_strides_; - std::vector fg_row_ptrs_; - std::vector bg_row_ptrs_; - std::vector out_row_ptrs_; - std::vector blending_info_; - }; - - static bool NeedsBlending(PassesDecoderState* dec_state); - - Status PrepareBlending( - PassesDecoderState* dec_state, FrameOrigin foreground_origin, - size_t foreground_xsize, size_t foreground_ysize, - const std::vector* extra_channel_info, - const ColorEncoding& frame_color_encoding, const Rect& frame_rect, - Image3F* output, const Rect& output_rect, - std::vector* output_extra_channels, - std::vector output_extra_channels_rects); - // rect is relative to the full decoded foreground. - // But foreground here can be a subset of the full foreground, and input_rect - // indicates where that rect is in that subset. For example, if rect = - // Rect(10, 10, 20, 20), and foreground is subrect (7, 7, 30, 30) of the full - // foreground, then input_rect should be (3, 3, 20, 20), because that is where - // rect is relative to the foreground crop. - ImageBlender::RectBlender PrepareRect( - const Rect& rect, const Image3F& foreground, - const std::vector& extra_channels, const Rect& input_rect) const; - - // If this returns true, then it is not necessary to call further methods on - // this ImageBlender to achieve blending, although it is not forbidden either - // (those methods will just return immediately in that case). - bool done() const { return done_; } - - private: - BlendingInfo info_; - const std::vector* extra_channel_info_; - Rect frame_rect_; - // Destination, as well as background before DoBlending is called. - Image3F* output_; - ImageBundle* bg_; - Rect output_rect_; - std::vector* output_extra_channels_; - std::vector output_extra_channels_rects_; - Rect cropbox_; - Rect overlap_; - bool done_ = false; - const std::vector* ec_info_; - FrameOrigin o_{}; -}; - } // namespace jxl #endif // LIB_JXL_BLENDING_H_ 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 @@ -4,76 +4,15 @@ // license that can be found in the LICENSE file. #include "lib/jxl/blending.h" #include "lib/jxl/alpha.h" -#include "lib/jxl/base/printf_macros.h" #include "lib/jxl/image_ops.h" namespace jxl { -namespace { - -// Given two rects A and B, returns a set of rects whose union is A \ B. This -// may require from 0 to 4 rects, one for each non-empty side of B. `storage` -// must have room to accommodate that many rects. The order is consistent when -// called successively with "parallel" rects. -// -// +----------------------+ -// | top | -// +------+-------+-------+ -// | left | inner | right | -// +------+-------+-------+ -// | bottom | -// +----------------------+ -Span SubtractRect(const Rect& outer, const Rect& inner, - Rect* storage) { - size_t num_rects = 0; - - const Rect intersection = inner.Intersection(outer); - if (intersection.xsize() == 0 && intersection.ysize() == 0) { - storage[num_rects++] = outer; - return Span(storage, num_rects); - } - - // Left, same height as inner - if (outer.x0() < inner.x0()) { - storage[num_rects++] = - Rect(outer.x0(), inner.y0(), - std::min(outer.xsize(), inner.x0() - outer.x0()), inner.ysize()); - } - - // Right, same height as inner - if (outer.x0() + outer.xsize() > inner.x0() + inner.xsize()) { - storage[num_rects++] = - Rect(inner.x0() + inner.xsize(), inner.y0(), - std::min(outer.xsize(), outer.x0() + outer.xsize() - - (inner.x0() + inner.xsize())), - inner.ysize()); - } - - // Top, full width - if (outer.y0() < inner.y0()) { - storage[num_rects++] = - Rect(outer.x0(), outer.y0(), outer.xsize(), - std::min(outer.ysize(), inner.y0() - outer.y0())); - } - - // Bottom, full width - if (outer.y0() + outer.ysize() > inner.y0() + inner.ysize()) { - storage[num_rects++] = - Rect(outer.x0(), inner.y0() + inner.ysize(), outer.xsize(), - std::min(outer.ysize(), outer.y0() + outer.ysize() - - (inner.y0() + inner.ysize()))); - } - - return Span(storage, num_rects); -} - -} // namespace - -bool ImageBlender::NeedsBlending(PassesDecoderState* dec_state) { +bool NeedsBlending(PassesDecoderState* dec_state) { const PassesSharedState& state = *dec_state->shared; if (!(state.frame_header.frame_type == FrameType::kRegularFrame || state.frame_header.frame_type == FrameType::kSkipProgressive)) { return false; } @@ -89,257 +28,10 @@ return false; } return true; } -Status ImageBlender::PrepareBlending( - PassesDecoderState* dec_state, FrameOrigin foreground_origin, - size_t foreground_xsize, size_t foreground_ysize, - const std::vector* extra_channel_info, - const ColorEncoding& frame_color_encoding, const Rect& frame_rect, - Image3F* output, const Rect& output_rect, - std::vector* output_extra_channels, - std::vector output_extra_channels_rects) { - const PassesSharedState& state = *dec_state->shared; - info_ = state.frame_header.blending_info; - - ec_info_ = &state.frame_header.extra_channel_blending_info; - - frame_rect_ = frame_rect; - extra_channel_info_ = extra_channel_info; - output_ = output; - output_rect_ = output_rect; - output_extra_channels_ = output_extra_channels; - output_extra_channels_rects_ = std::move(output_extra_channels_rects); - - size_t image_xsize = state.frame_header.nonserialized_metadata->xsize(); - size_t image_ysize = state.frame_header.nonserialized_metadata->ysize(); - - // the rect in the canvas that needs to be updated - cropbox_ = frame_rect; - // the rect of the foreground that overlaps with the canvas - overlap_ = cropbox_; - o_ = foreground_origin; - o_.x0 -= frame_rect.x0(); - o_.y0 -= frame_rect.y0(); - int x0 = (o_.x0 >= 0 ? o_.x0 : 0); - int y0 = (o_.y0 >= 0 ? o_.y0 : 0); - int xsize = foreground_xsize; - if (o_.x0 < 0) xsize += o_.x0; - int ysize = foreground_ysize; - if (o_.y0 < 0) ysize += o_.y0; - xsize = Clamp1(xsize, 0, (int)cropbox_.xsize() - x0); - ysize = Clamp1(ysize, 0, (int)cropbox_.ysize() - y0); - cropbox_ = Rect(x0, y0, xsize, ysize); - x0 = (o_.x0 < 0 ? -o_.x0 : 0); - y0 = (o_.y0 < 0 ? -o_.y0 : 0); - overlap_ = Rect(x0, y0, xsize, ysize); - - // Image to write to. - ImageBundle& bg = *state.reference_frames[info_.source].frame; - bg_ = &bg; - if (bg.xsize() == 0 && bg.ysize() == 0) { - // there is no background, assume it to be all zeroes - ImageBundle empty(&state.metadata->m); - Image3F color(image_xsize, image_ysize); - ZeroFillImage(&color); - empty.SetFromImage(std::move(color), frame_color_encoding); - if (!output_extra_channels_->empty()) { - std::vector ec; - for (size_t i = 0; i < output_extra_channels_->size(); ++i) { - ImageF eci(image_xsize, image_ysize); - ZeroFillImage(&eci); - ec.push_back(std::move(eci)); - } - empty.SetExtraChannels(std::move(ec)); - } - bg = std::move(empty); - } else if (state.reference_frames[info_.source].ib_is_in_xyb) { - return JXL_FAILURE( - "Trying to blend XYB reference frame %i and non-XYB frame", - info_.source); - } - - if (bg.xsize() < image_xsize || bg.ysize() < image_ysize || - bg.origin.x0 != 0 || bg.origin.y0 != 0) { - return JXL_FAILURE("Trying to use a %" PRIuS "x%" PRIuS - " crop as a background", - bg.xsize(), bg.ysize()); - } - if (state.metadata->m.xyb_encoded) { - if (!dec_state->output_encoding_info.color_encoding_is_original) { - return JXL_FAILURE("Blending in unsupported color space"); - } - } - - if (!overlap_.IsInside(Rect(0, 0, foreground_xsize, foreground_ysize))) { - return JXL_FAILURE("Trying to use a %" PRIuS "x%" PRIuS - " crop as a foreground", - foreground_xsize, foreground_ysize); - } - - if (!cropbox_.IsInside(bg)) { - return JXL_FAILURE("Trying blend %" PRIuS "x%" PRIuS " to (%" PRIuS - ",%" PRIuS "), but background is %" PRIuS "x%" PRIuS, - cropbox_.xsize(), cropbox_.ysize(), cropbox_.x0(), - cropbox_.y0(), bg.xsize(), bg.ysize()); - } - - Rect frame_rects_storage[4], output_rects_storage[4]; - Span frame_rects = SubtractRect( - frame_rect, cropbox_.Translate(frame_rect.x0(), frame_rect.y0()), - frame_rects_storage); - Span output_rects = SubtractRect( - output_rect, cropbox_.Translate(output_rect.x0(), output_rect.y0()), - output_rects_storage); - JXL_ASSERT(frame_rects.size() == output_rects.size()); - for (size_t i = 0; i < frame_rects.size(); ++i) { - CopyImageTo(frame_rects[i], *bg.color(), output_rects[i], output); - } - for (size_t i = 0; i < ec_info_->size(); ++i) { - const auto& eci = (*ec_info_)[i]; - const auto& src = *state.reference_frames[eci.source].frame; - output_rects = - SubtractRect(output_extra_channels_rects_[i], - cropbox_.Translate(output_extra_channels_rects_[i].x0(), - output_extra_channels_rects_[i].y0()), - output_rects_storage); - if (src.xsize() == 0 && src.ysize() == 0) { - for (size_t j = 0; j < output_rects.size(); ++j) { - ZeroFillPlane(&(*output_extra_channels_)[i], output_rects[j]); - } - } else { - if (src.extra_channels()[i].xsize() < image_xsize || - src.extra_channels()[i].ysize() < image_ysize || src.origin.x0 != 0 || - src.origin.y0 != 0) { - return JXL_FAILURE( - "Invalid size %" PRIuS "x%" PRIuS - " or origin %+d%+d for extra channel %" PRIuS - " of " - "reference frame %" PRIuS ", expected at least %" PRIuS "x%" PRIuS - "+0+0", - src.extra_channels()[i].xsize(), src.extra_channels()[i].ysize(), - static_cast(src.origin.x0), static_cast(src.origin.y0), i, - static_cast(eci.source), image_xsize, image_ysize); - } - for (size_t j = 0; j < frame_rects.size(); ++j) { - CopyImageTo(frame_rects[j], src.extra_channels()[i], output_rects[j], - &(*output_extra_channels_)[i]); - } - } - } - - return true; -} - -ImageBlender::RectBlender ImageBlender::PrepareRect( - const Rect& rect, const Image3F& foreground, - const std::vector& extra_channels, const Rect& input_rect) const { - JXL_DASSERT(rect.xsize() == input_rect.xsize()); - JXL_DASSERT(rect.ysize() == input_rect.ysize()); - JXL_DASSERT(input_rect.IsInside(foreground)); - - RectBlender blender(false); - blender.extra_channel_info_ = extra_channel_info_; - - blender.current_overlap_ = rect.Intersection(overlap_); - if (blender.current_overlap_.xsize() == 0 || - blender.current_overlap_.ysize() == 0) { - blender.done_ = true; - return blender; - } - - blender.current_cropbox_ = - Rect(o_.x0 + blender.current_overlap_.x0(), - o_.y0 + blender.current_overlap_.y0(), - blender.current_overlap_.xsize(), blender.current_overlap_.ysize()); - - // Turn current_overlap_ from being relative to the full foreground to being - // relative to the rect or input_rect. - blender.current_overlap_ = - Rect(blender.current_overlap_.x0() - rect.x0(), - blender.current_overlap_.y0() - rect.y0(), - blender.current_overlap_.xsize(), blender.current_overlap_.ysize()); - - // And this one is relative to the `foreground` subimage. - const Rect input_overlap(blender.current_overlap_.x0() + input_rect.x0(), - blender.current_overlap_.y0() + input_rect.y0(), - blender.current_overlap_.xsize(), - blender.current_overlap_.ysize()); - - blender.blending_info_.resize(extra_channels.size() + 1); - auto make_blending = [&](const BlendingInfo& info, PatchBlending* pb) { - pb->alpha_channel = info.alpha_channel; - pb->clamp = info.clamp; - switch (info.mode) { - case BlendMode::kReplace: { - pb->mode = PatchBlendMode::kReplace; - break; - } - case BlendMode::kAdd: { - pb->mode = PatchBlendMode::kAdd; - break; - } - case BlendMode::kMul: { - pb->mode = PatchBlendMode::kMul; - break; - } - case BlendMode::kBlend: { - pb->mode = PatchBlendMode::kBlendAbove; - break; - } - case BlendMode::kAlphaWeightedAdd: { - pb->mode = PatchBlendMode::kAlphaWeightedAddAbove; - break; - } - default: { - JXL_ABORT("Invalid blend mode"); // should have failed to decode - } - } - }; - make_blending(info_, &blender.blending_info_[0]); - for (size_t i = 0; i < extra_channels.size(); i++) { - make_blending((*ec_info_)[i], &blender.blending_info_[1 + i]); - } - - Rect cropbox_row = blender.current_cropbox_.Line(0); - Rect overlap_row = input_overlap.Line(0); - const auto num_ptrs = 3 + extra_channels.size(); - blender.fg_ptrs_.reserve(num_ptrs); - blender.fg_strides_.reserve(num_ptrs); - blender.bg_ptrs_.reserve(num_ptrs); - blender.bg_strides_.reserve(num_ptrs); - for (size_t c = 0; c < 3; c++) { - blender.fg_ptrs_.push_back(overlap_row.ConstPlaneRow(foreground, c, 0)); - blender.fg_strides_.push_back(foreground.PixelsPerRow()); - blender.bg_ptrs_.push_back( - cropbox_row.Translate(frame_rect_.x0(), frame_rect_.y0()) - .PlaneRow(bg_->color(), c, 0)); - blender.bg_strides_.push_back(bg_->color()->PixelsPerRow()); - blender.out_ptrs_.push_back( - cropbox_row.Translate(output_rect_.x0(), output_rect_.y0()) - .PlaneRow(output_, c, 0)); - blender.out_strides_.push_back(output_->PixelsPerRow()); - } - for (size_t c = 0; c < extra_channels.size(); c++) { - blender.fg_ptrs_.push_back(overlap_row.ConstRow(extra_channels[c], 0)); - blender.fg_strides_.push_back(extra_channels[c].PixelsPerRow()); - blender.bg_ptrs_.push_back( - cropbox_row.Translate(frame_rect_.x0(), frame_rect_.y0()) - .Row(&bg_->extra_channels()[c], 0)); - blender.bg_strides_.push_back(bg_->extra_channels()[c].PixelsPerRow()); - blender.out_ptrs_.push_back( - cropbox_row - .Translate(output_extra_channels_rects_[c].x0(), - output_extra_channels_rects_[c].y0()) - .Row(&(*output_extra_channels_)[c], 0)); - blender.out_strides_.push_back((*output_extra_channels_)[c].PixelsPerRow()); - } - - return blender; -} - void PerformBlending(const float* const* bg, const float* const* fg, float* const* out, size_t x0, size_t xsize, const PatchBlending& color_blending, const PatchBlending* ec_blending, const std::vector& extra_channel_info) { @@ -455,27 +147,6 @@ for (size_t i = 0; i < 3 + num_ec; i++) { if (xsize != 0) memcpy(out[i] + x0, tmp.Row(i), xsize * sizeof(**out)); } } -Status ImageBlender::RectBlender::DoBlending(size_t y) { - if (done_ || y < current_overlap_.y0() || - y >= current_overlap_.y0() + current_overlap_.ysize()) { - return true; - } - y -= current_overlap_.y0(); - fg_row_ptrs_.resize(fg_ptrs_.size()); - bg_row_ptrs_.resize(bg_ptrs_.size()); - out_row_ptrs_.resize(out_ptrs_.size()); - for (size_t c = 0; c < fg_row_ptrs_.size(); c++) { - fg_row_ptrs_[c] = fg_ptrs_[c] + y * fg_strides_[c]; - bg_row_ptrs_[c] = bg_ptrs_[c] + y * bg_strides_[c]; - out_row_ptrs_[c] = out_ptrs_[c] + y * out_strides_[c]; - } - PerformBlending(bg_row_ptrs_.data(), fg_row_ptrs_.data(), - out_row_ptrs_.data(), 0, current_overlap_.xsize(), - blending_info_[0], blending_info_.data() + 1, - *extra_channel_info_); - return true; -} - } // namespace jxl diff --git a/third_party/jpeg-xl/lib/jxl/blending_test.cc b/third_party/jpeg-xl/lib/jxl/blending_test.cc --- a/third_party/jpeg-xl/lib/jxl/blending_test.cc +++ b/third_party/jpeg-xl/lib/jxl/blending_test.cc @@ -1,12 +1,10 @@ // 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/blending.h" - #include "gmock/gmock.h" #include "gtest/gtest.h" #include "lib/extras/codec.h" #include "lib/jxl/dec_file.h" #include "lib/jxl/image_test_utils.h" @@ -37,62 +35,7 @@ EXPECT_TRUE(SamePixels(ib.color(), *frame.Main().color())); ++i; } } -TEST(BlendingTest, Offset) { - const PaddedBytes background_bytes = ReadTestData("jxl/splines.pfm"); - CodecInOut background; - ASSERT_TRUE(SetFromBytes(Span(background_bytes), &background)); - const PaddedBytes foreground_bytes = - ReadTestData("jxl/grayscale_patches.png"); - CodecInOut foreground; - ASSERT_TRUE(SetFromBytes(Span(foreground_bytes), &foreground)); - - ImageBlender blender; - CodecMetadata nonserialized_metadata; - ASSERT_TRUE( - nonserialized_metadata.size.Set(background.xsize(), background.ysize())); - PassesSharedState state; - state.frame_header.blending_info.mode = BlendMode::kReplace; - state.frame_header.blending_info.source = 0; - state.frame_header.nonserialized_metadata = &nonserialized_metadata; - state.metadata = &background.metadata; - state.reference_frames[0].frame = &background.Main(); - PassesDecoderState dec_state; - dec_state.shared = &state; - const FrameOrigin foreground_origin = {-50, -50}; - ImageBundle output(&background.metadata.m); - output.SetFromImage(Image3F(background.xsize(), background.ysize()), - background.Main().c_current()); - ASSERT_TRUE(blender.PrepareBlending( - &dec_state, foreground_origin, foreground.xsize(), foreground.ysize(), - &nonserialized_metadata.m.extra_channel_info, - background.Main().c_current(), Rect(background), output.color(), - Rect(*output.color()), {}, {})); - - static constexpr int kStep = 20; - for (size_t x0 = 0; x0 < foreground.xsize(); x0 += kStep) { - for (size_t y0 = 0; y0 < foreground.ysize(); y0 += kStep) { - const Rect rect = - Rect(x0, y0, kStep, kStep).Intersection(Rect(foreground.Main())); - Image3F foreground_crop(rect.xsize(), rect.ysize()); - CopyImageTo(rect, *foreground.Main().color(), Rect(foreground_crop), - &foreground_crop); - auto rect_blender = - blender.PrepareRect(rect, foreground_crop, {}, Rect(foreground_crop)); - for (size_t y = 0; y < rect.ysize(); ++y) { - ASSERT_TRUE(rect_blender.DoBlending(y)); - } - } - } - - const PaddedBytes expected_bytes = - ReadTestData("jxl/blending/grayscale_patches_on_splines.pfm"); - CodecInOut expected; - ASSERT_TRUE(SetFromBytes(Span(expected_bytes), &expected)); - VerifyRelativeError(*expected.Main().color(), *output.color(), 1. / (2 * 255), - 0); -} - } // namespace } // namespace jxl 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 @@ -74,11 +74,11 @@ // Move-only. CodecInOut(CodecInOut&&) = default; CodecInOut& operator=(CodecInOut&&) = default; size_t LastStillFrame() const { - JXL_DASSERT(frames.size() > 0); + JXL_DASSERT(!frames.empty()); size_t last = 0; for (size_t i = 0; i < frames.size(); i++) { last = i; if (frames[i].duration > 0) break; } diff --git a/third_party/jpeg-xl/lib/jxl/color_management.cc b/third_party/jpeg-xl/lib/jxl/color_management.cc --- a/third_party/jpeg-xl/lib/jxl/color_management.cc +++ b/third_party/jpeg-xl/lib/jxl/color_management.cc @@ -497,12 +497,14 @@ // The MD5 checksum must be computed on the profile with profile flags, // rendering intent, and region of the checksum itself, set to 0. // TODO(lode): manually verify with a reliable tool that this creates correct // signature (profile id) for ICC profiles. PaddedBytes icc_sum = *icc; - memset(icc_sum.data() + 44, 0, 4); - memset(icc_sum.data() + 64, 0, 4); + if (icc_sum.size() >= 64 + 4) { + memset(icc_sum.data() + 44, 0, 4); + memset(icc_sum.data() + 64, 0, 4); + } uint8_t checksum[16]; ICCComputeMD5(icc_sum, checksum); memcpy(icc->data() + 84, checksum, sizeof(checksum)); 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 @@ -115,17 +115,10 @@ ysize_dc_groups = DivCeil(ysize_blocks, group_dim); num_groups = xsize_groups * ysize_groups; num_dc_groups = xsize_dc_groups * ysize_dc_groups; } - size_t GetUpsampledXSize(bool is_color_c) { - return is_color_c ? xsize_upsampled_padded : xsize_upsampled; - } - size_t GetUpsampledYSize(bool is_color_c) { - return is_color_c ? ysize_upsampled_padded : ysize_upsampled; - } - // Image size without any upsampling, i.e. original_size / upsampling. size_t xsize; size_t ysize; // Original image size. size_t xsize_upsampled; diff --git a/third_party/jpeg-xl/lib/jxl/dec_ans.h b/third_party/jpeg-xl/lib/jxl/dec_ans.h --- a/third_party/jpeg-xl/lib/jxl/dec_ans.h +++ b/third_party/jpeg-xl/lib/jxl/dec_ans.h @@ -287,11 +287,10 @@ ReadHybridUintConfig(lz77_length_uint_, token - lz77_threshold_, br) + lz77_min_length_ - 1; return; } *value = ReadHybridUintConfig(configs[ctx], token, br); - return; } bool HuffRleOnly() { if (lz77_window_ == nullptr) return false; if (!use_prefix_code_) return false; for (size_t i = 0; i < kHuffmanTableBits; i++) { 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 @@ -15,58 +15,42 @@ #include "lib/jxl/coeff_order.h" #include "lib/jxl/common.h" #include "lib/jxl/convolve.h" #include "lib/jxl/dec_group_border.h" #include "lib/jxl/dec_noise.h" -#include "lib/jxl/dec_upsample.h" -#include "lib/jxl/filters.h" #include "lib/jxl/image.h" #include "lib/jxl/passes_state.h" #include "lib/jxl/quant_weights.h" #include "lib/jxl/render_pipeline/render_pipeline.h" +#include "lib/jxl/render_pipeline/stage_upsampling.h" #include "lib/jxl/sanitizers.h" namespace jxl { +constexpr size_t kSigmaBorder = 1; +constexpr size_t kSigmaPadding = 2; + // 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. const PassesSharedState* JXL_RESTRICT shared = &shared_storage; - // Upsamplers for all the possible upsampling factors (2 to 8). - Upsampler upsamplers[3]; - - // Storage for RNG output for noise synthesis. - Image3F noise; - - // Storage for pre-color-transform output for displayed - // save_before_color_transform frames. - Image3F pre_color_transform_frame; - // Non-empty (contains originals) if extra-channels were cropped. - std::vector pre_color_transform_ec; + // 8x upsampling stage for DC. + std::unique_ptr upsampler8x; // For ANS decoding. std::vector code; std::vector> context_map; // Multiplier to be applied to the quant matrices of the x channel. float x_dm_multiplier; float b_dm_multiplier; - // Decoded image. - Image3F decoded; - std::vector extra_channels; - - // Borders between groups. Only allocated if `decoded` is *not* allocated. - // 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. - Image3F borders_horizontal; - Image3F borders_vertical; + // Sigma values for EPF. + ImageF sigma; // RGB8 output buffer. If not nullptr, image data will be written to this // buffer instead of being written to the output ImageBundle. The image data // is assumed to have the stride given by `rgb_stride`, hence row `i` starts // at position `i * rgb_stride`. @@ -85,179 +69,33 @@ // Buffer of upsampling * kApplyImageFeaturesTileDim ones. std::vector opaque_alpha; // One row per thread std::vector> pixel_callback_rows; - // Seed for noise, to have different noise per-frame. - size_t noise_seed = 0; + // Used for seeding noise. + size_t visible_frame_index = 0; + size_t nonvisible_frame_index = 0; // Keep track of the transform types used. std::atomic used_acs{0}; // Storage for coefficients if in "accumulate" mode. std::unique_ptr coefficients = make_unique>(0, 0); - // Filter application pipeline used by ApplyImageFeatures. One entry is needed - // per thread. - std::vector filter_pipelines; - - // Input weights used by the filters. These are shared from multiple threads - // but are read-only for the filter application. - FilterWeights filter_weights; - - // Manages the status of borders. - GroupBorderAssigner group_border_assigner; - - // Rendering pipeline. TODO(veluca): eventually, this pipeline will replace - // most of the state in this struct. + // Rendering pipeline. std::unique_ptr render_pipeline; // Storage for the current frame if it can be referenced by future frames. ImageBundle frame_storage_for_referencing; - // TODO(veluca): this should eventually become "iff no global modular - // transform was applied". - bool EagerFinalizeImageRect() const { - return shared->frame_header.encoding == FrameEncoding::kVarDCT && - shared->frame_header.nonserialized_metadata->m.extra_channel_info - .empty(); - } - - // Amount of padding that will be accessed, in all directions, outside a rect - // during a call to FinalizeImageRect(). - size_t FinalizeRectPadding() const { - size_t padding = shared->frame_header.loop_filter.Padding(); - padding += shared->frame_header.upsampling == 1 ? 0 : 2; - JXL_DASSERT(padding <= kMaxFinalizeRectPadding); - for (auto ups : shared->frame_header.extra_channel_upsampling) { - if (ups > 1) { - padding = std::max(padding, size_t{2}); - } - } - // We could be making a distinction between h and w padding here, but it is - // likely not worth it. - if (!shared->frame_header.chroma_subsampling.Is444()) { - padding = std::max(padding / 2 + 1, padding); - } - return padding; - } - - // Storage for intermediate data during FinalizeRect steps. - // TODO(veluca): these buffers are larger than strictly necessary. - std::vector filter_input_storage; - std::vector padded_upsampling_input_storage; - std::vector upsampling_input_storage; - size_t upsampler_arena_size = 0; - std::vector> upsampler_storage; - // We keep four arrays, one per upsampling level, to reduce memory usage in - // the common case of no upsampling. - std::vector output_pixel_data_storage[4] = {}; - std::vector ec_temp_images; - std::vector ycbcr_temp_images; - std::vector ycbcr_out_images; - - // Buffer for decoded pixel data for a group. - std::vector group_data; - static constexpr size_t kGroupDataYBorder = kMaxFinalizeRectPadding * 2; - static constexpr size_t kGroupDataXBorder = - RoundUpToBlockDim(kMaxFinalizeRectPadding) * 2 + kBlockDim; + struct PipelineOptions { + bool use_slow_render_pipeline; + bool coalescing; + bool render_spotcolors; + }; - void EnsureStorage(size_t num_threads) { - // We need one filter_storage per thread, ensure we have at least that many. - if (shared->frame_header.loop_filter.epf_iters != 0 || - shared->frame_header.loop_filter.gab) { - if (filter_pipelines.size() < num_threads) { - filter_pipelines.resize(num_threads); - } - } - // We allocate filter_input_storage unconditionally to ensure that the image - // is allocated if we need it for DC upsampling. - for (size_t _ = filter_input_storage.size(); _ < num_threads; _++) { - // Extra padding along the x dimension to ensure memory accesses don't - // load out-of-bounds pixels. - filter_input_storage.emplace_back( - kApplyImageFeaturesTileDim + 2 * kGroupDataXBorder, - kApplyImageFeaturesTileDim + 2 * kGroupDataYBorder); - } - if (shared->frame_header.upsampling != 1) { - for (size_t _ = upsampling_input_storage.size(); _ < num_threads; _++) { - // At this point, we only need up to 2 pixels of border per side for - // upsampling, but we add an extra border for aligned access. - upsampling_input_storage.emplace_back( - kApplyImageFeaturesTileDim + 2 * kBlockDim, - kApplyImageFeaturesTileDim + 4); - padded_upsampling_input_storage.emplace_back( - kApplyImageFeaturesTileDim + 2 * kBlockDim, - kApplyImageFeaturesTileDim + 4); - } - } - const size_t arena_size = Upsampler::GetArenaSize( - kApplyImageFeaturesTileDim * shared->frame_header.upsampling); - if (arena_size > upsampler_arena_size) upsampler_storage.clear(); - for (size_t _ = upsampler_storage.size(); _ < num_threads; _++) { - upsampler_storage.emplace_back(hwy::AllocateAligned(arena_size)); - } - upsampler_arena_size = arena_size; - for (size_t _ = group_data.size(); _ < num_threads; _++) { - group_data.emplace_back(kGroupDim + 2 * kGroupDataXBorder, - kGroupDim + 2 * kGroupDataYBorder); -#if MEMORY_SANITIZER - // Avoid errors due to loading vectors on the outermost padding. - FillImage(msan::kSanitizerSentinel, &group_data.back()); -#endif - } - if (!shared->frame_header.chroma_subsampling.Is444()) { - for (size_t _ = ycbcr_temp_images.size(); _ < num_threads; _++) { - ycbcr_temp_images.emplace_back(kGroupDim + 2 * kGroupDataXBorder, - kGroupDim + 2 * kGroupDataYBorder); - ycbcr_out_images.emplace_back(kGroupDim + 2 * kGroupDataXBorder, - kGroupDim + 2 * kGroupDataYBorder); - } - } - if (rgb_output || pixel_callback) { - size_t log2_upsampling = CeilLog2Nonzero(shared->frame_header.upsampling); - for (size_t _ = output_pixel_data_storage[log2_upsampling].size(); - _ < num_threads; _++) { - output_pixel_data_storage[log2_upsampling].emplace_back( - kApplyImageFeaturesTileDim << log2_upsampling, - kApplyImageFeaturesTileDim << log2_upsampling); - } - opaque_alpha.resize( - kApplyImageFeaturesTileDim * shared->frame_header.upsampling, 1.0f); - if (pixel_callback) { - pixel_callback_rows.resize(num_threads); - for (size_t i = 0; i < pixel_callback_rows.size(); ++i) { - pixel_callback_rows[i].resize(kApplyImageFeaturesTileDim * - shared->frame_header.upsampling * - (rgb_output_is_rgba ? 4 : 3)); - } - } - } - if (shared->metadata->m.num_extra_channels * num_threads > - ec_temp_images.size()) { - ec_temp_images.resize(shared->metadata->m.num_extra_channels * - num_threads); - } - for (size_t i = 0; i < shared->metadata->m.num_extra_channels; i++) { - if (shared->frame_header.extra_channel_upsampling[i] == 1) continue; - // We need up to 2 pixels of padding on each side. On the x axis, we round - // up padding so that 0 starts at a multiple of kBlockDim. - size_t xs = kApplyImageFeaturesTileDim * shared->frame_header.upsampling / - shared->frame_header.extra_channel_upsampling[i] + - 2 * kBlockDim; - size_t ys = kApplyImageFeaturesTileDim * shared->frame_header.upsampling / - shared->frame_header.extra_channel_upsampling[i] + - 4; - for (size_t t = 0; t < num_threads; t++) { - auto& eti = - ec_temp_images[t * shared->metadata->m.num_extra_channels + i]; - if (eti.xsize() < xs || eti.ysize() < ys) { - eti = ImageF(xs, ys); - } - } - } - } + Status PreparePipeline(ImageBundle* decoded, PipelineOptions options); // Information for colour conversions. OutputEncodingInfo output_encoding_info; // Initializes decoder-specific structures using information from *shared. @@ -271,19 +109,14 @@ pixel_callback = nullptr; rgb_output_is_rgba = false; fast_xyb_srgb8_conversion = false; used_acs = 0; - group_border_assigner.Init(shared->frame_dim); - const LoopFilter& lf = shared->frame_header.loop_filter; - JXL_RETURN_IF_ERROR(filter_weights.Init(lf, shared->frame_dim)); - for (auto& fp : filter_pipelines) { - // De-initialize FilterPipelines. - fp.num_filters = 0; - } - for (size_t i = 0; i < 3; i++) { - upsamplers[i].Init(2 << i, shared->metadata->transform_data); + upsampler8x = GetUpsamplingStage(shared->metadata->transform_data, 0, 3); + if (shared->frame_header.loop_filter.epf_iters > 0) { + sigma = ImageF(shared->frame_dim.xsize_blocks + 2 * kSigmaPadding, + shared->frame_dim.ysize_blocks + 2 * kSigmaPadding); } return true; } // Initialize the decoder state after all of DC is decoded. @@ -299,62 +132,17 @@ size_t sz = shared_storage.frame_header.passes.num_passes * shared_storage.coeff_order_size; if (sz > shared_storage.coeff_orders.size()) { shared_storage.coeff_orders.resize(sz); } - if (shared->frame_header.flags & FrameHeader::kNoise && !render_pipeline) { - noise = Image3F(shared->frame_dim.xsize_upsampled_padded, - shared->frame_dim.ysize_upsampled_padded); - size_t num_x_groups = DivCeil(noise.xsize(), kGroupDim); - size_t num_y_groups = DivCeil(noise.ysize(), kGroupDim); - PROFILER_ZONE("GenerateNoise"); - auto generate_noise = [&](const uint32_t group_index, - size_t /* thread */) { - size_t gx = group_index % num_x_groups; - size_t gy = group_index / num_x_groups; - Rect rect(gx * kGroupDim, gy * kGroupDim, kGroupDim, kGroupDim, - noise.xsize(), noise.ysize()); - RandomImage3(noise_seed + group_index, rect, &noise); - }; - JXL_RETURN_IF_ERROR(RunOnPool(pool, 0, num_x_groups * num_y_groups, - ThreadPool::NoInit, generate_noise, - "Generate noise")); - { - PROFILER_ZONE("High pass noise"); - // 4 * (1 - box kernel) - WeightsSymmetric5 weights{{HWY_REP4(-3.84)}, {HWY_REP4(0.16)}, - {HWY_REP4(0.16)}, {HWY_REP4(0.16)}, - {HWY_REP4(0.16)}, {HWY_REP4(0.16)}}; - // TODO(veluca): avoid copy. - // TODO(veluca): avoid having a full copy of the image in main memory. - ImageF noise_tmp(noise.xsize(), noise.ysize()); - for (size_t c = 0; c < 3; c++) { - Symmetric5(noise.Plane(c), Rect(noise), weights, pool, &noise_tmp); - std::swap(noise.Plane(c), noise_tmp); - } - noise_seed += shared->frame_dim.num_groups; - } - } - EnsureBordersStorage(); - if (!EagerFinalizeImageRect()) { - // decoded must be padded to a multiple of kBlockDim rows since the last - // rows may be used by the filters even if they are outside the frame - // dimension. - decoded = Image3F(shared->frame_dim.xsize_padded, - shared->frame_dim.ysize_padded); - } -#if MEMORY_SANITIZER - // Avoid errors due to loading vectors on the outermost padding. - FillImage(msan::kSanitizerSentinel, &decoded); -#endif return true; } - void EnsureBordersStorage(); - - Status FinalizeGroup(size_t group_idx, size_t thread, Image3F* pixel_data, - ImageBundle* output); + // Fills the `state->filter_weights.sigma` image with the precomputed sigma + // values in the area inside `block_rect`. Accesses the AC strategy, quant + // field and epf_sharpness fields in the corresponding positions. + void ComputeSigma(const Rect& block_rect, PassesDecoderState* state); }; // Temp images required for decoding a single group. Reduces memory allocations // for large images because we only initialize min(#threads, #groups) instances. struct GroupDecCache { @@ -394,10 +182,17 @@ scratch_space = dec_group_block + max_block_area_ * 3; dec_group_qblock = int32_memory_.get(); dec_group_qblock16 = int16_memory_.get(); } + void InitDCBufferOnce() { + if (dc_buffer.xsize() == 0) { + dc_buffer = ImageF(kGroupDimInBlocks + kRenderPipelineXOffset * 2, + kGroupDimInBlocks + 4); + } + } + // Scratch space used by DecGroupImpl(). float* dec_group_block; int32_t* dec_group_qblock; int16_t* dec_group_qblock16; @@ -408,10 +203,13 @@ // TODO(veluca): figure out if we can save allocations. // AC decoding Image3I num_nzeroes[kMaxNumPasses]; + // Buffer for DC upsampling. + ImageF dc_buffer; + private: hwy::AlignedFreeUniquePtr float_memory_; hwy::AlignedFreeUniquePtr int32_memory_; hwy::AlignedFreeUniquePtr int16_memory_; size_t max_block_area_ = 0; 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 @@ -3,175 +3,198 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. #include "lib/jxl/dec_cache.h" -#include "lib/jxl/dec_reconstruct.h" +#include "lib/jxl/blending.h" +#include "lib/jxl/render_pipeline/stage_blending.h" +#include "lib/jxl/render_pipeline/stage_chroma_upsampling.h" +#include "lib/jxl/render_pipeline/stage_epf.h" +#include "lib/jxl/render_pipeline/stage_gaborish.h" +#include "lib/jxl/render_pipeline/stage_noise.h" +#include "lib/jxl/render_pipeline/stage_patches.h" +#include "lib/jxl/render_pipeline/stage_splines.h" +#include "lib/jxl/render_pipeline/stage_spot.h" +#include "lib/jxl/render_pipeline/stage_upsampling.h" +#include "lib/jxl/render_pipeline/stage_write.h" +#include "lib/jxl/render_pipeline/stage_xyb.h" +#include "lib/jxl/render_pipeline/stage_ycbcr.h" namespace jxl { -void PassesDecoderState::EnsureBordersStorage() { - if (!EagerFinalizeImageRect()) return; - size_t padding = FinalizeRectPadding(); - size_t bordery = 2 * padding; - size_t borderx = padding + group_border_assigner.PaddingX(padding); - Rect horizontal = Rect(0, 0, shared->frame_dim.xsize_padded, - bordery * shared->frame_dim.ysize_groups * 2); - if (!SameSize(horizontal, borders_horizontal)) { - borders_horizontal = Image3F(horizontal.xsize(), horizontal.ysize()); +Status PassesDecoderState::PreparePipeline(ImageBundle* decoded, + PipelineOptions options) { + const FrameHeader& frame_header = shared->frame_header; + size_t num_c = 3 + frame_header.nonserialized_metadata->m.num_extra_channels; + if ((frame_header.flags & FrameHeader::kNoise) != 0) { + num_c += 3; + } + + if (frame_header.CanBeReferenced()) { + // Necessary so that SetInputSizes() can allocate output buffers as needed. + frame_storage_for_referencing = ImageBundle(decoded->metadata()); } - Rect vertical = Rect(0, 0, borderx * shared->frame_dim.xsize_groups * 2, - shared->frame_dim.ysize_padded); - if (!SameSize(vertical, borders_vertical)) { - borders_vertical = Image3F(vertical.xsize(), vertical.ysize()); + + RenderPipeline::Builder builder(num_c); + + if (options.use_slow_render_pipeline) { + builder.UseSimpleImplementation(); } -} + + if (!frame_header.chroma_subsampling.Is444()) { + for (size_t c = 0; c < 3; c++) { + if (frame_header.chroma_subsampling.HShift(c) != 0) { + builder.AddStage(GetChromaUpsamplingStage(c, /*horizontal=*/true)); + } + if (frame_header.chroma_subsampling.VShift(c) != 0) { + builder.AddStage(GetChromaUpsamplingStage(c, /*horizontal=*/false)); + } + } + } + + if (frame_header.loop_filter.gab) { + builder.AddStage(GetGaborishStage(frame_header.loop_filter)); + } -namespace { -void SaveBorders(const Rect& block_rect, size_t hshift, size_t vshift, - size_t padding, const ImageF& plane_in, - ImageF* border_storage_h, ImageF* border_storage_v) { - constexpr size_t kGroupDataXBorder = PassesDecoderState::kGroupDataXBorder; - constexpr size_t kGroupDataYBorder = PassesDecoderState::kGroupDataYBorder; - size_t x0 = DivCeil(block_rect.x0() * kBlockDim, 1 << hshift); - size_t x1 = - DivCeil((block_rect.x0() + block_rect.xsize()) * kBlockDim, 1 << hshift); - size_t y0 = DivCeil(block_rect.y0() * kBlockDim, 1 << vshift); - size_t y1 = - DivCeil((block_rect.y0() + block_rect.ysize()) * kBlockDim, 1 << vshift); - size_t gy = block_rect.y0() / kGroupDimInBlocks; - size_t gx = block_rect.x0() / kGroupDimInBlocks; - // TODO(veluca): this is too much with chroma upsampling. It's just - // inefficient though. - size_t borderx = GroupBorderAssigner::PaddingX(padding); - size_t bordery = padding; - size_t borderx_write = padding + borderx; - size_t bordery_write = padding + bordery; - CopyImageTo( - Rect(kGroupDataXBorder, kGroupDataYBorder, x1 - x0, bordery_write), - plane_in, Rect(x0, (gy * 2) * bordery_write, x1 - x0, bordery_write), - border_storage_h); - CopyImageTo( - Rect(kGroupDataXBorder, kGroupDataYBorder + y1 - y0 - bordery_write, - x1 - x0, bordery_write), - plane_in, Rect(x0, (gy * 2 + 1) * bordery_write, x1 - x0, bordery_write), - border_storage_h); - CopyImageTo( - Rect(kGroupDataXBorder, kGroupDataYBorder, borderx_write, y1 - y0), - plane_in, Rect((gx * 2) * borderx_write, y0, borderx_write, y1 - y0), - border_storage_v); - CopyImageTo(Rect(kGroupDataXBorder + x1 - x0 - borderx_write, - kGroupDataYBorder, borderx_write, y1 - y0), - plane_in, - Rect((gx * 2 + 1) * borderx_write, y0, borderx_write, y1 - y0), - border_storage_v); -} + { + const LoopFilter& lf = frame_header.loop_filter; + if (lf.epf_iters >= 3) { + builder.AddStage(GetEPFStage(lf, sigma, 0)); + } + if (lf.epf_iters >= 1) { + builder.AddStage(GetEPFStage(lf, sigma, 1)); + } + if (lf.epf_iters >= 2) { + builder.AddStage(GetEPFStage(lf, sigma, 2)); + } + } + + bool late_ec_upsample = frame_header.upsampling != 1; + for (auto ecups : frame_header.extra_channel_upsampling) { + if (ecups != frame_header.upsampling) { + // If patches are applied, either frame_header.upsampling == 1 or + // late_ec_upsample is true. + late_ec_upsample = false; + } + } + + if (!late_ec_upsample) { + for (size_t ec = 0; ec < frame_header.extra_channel_upsampling.size(); + ec++) { + if (frame_header.extra_channel_upsampling[ec] != 1) { + builder.AddStage(GetUpsamplingStage( + frame_header.nonserialized_metadata->transform_data, 3 + ec, + CeilLog2Nonzero(frame_header.extra_channel_upsampling[ec]))); + } + } + } + + if ((frame_header.flags & FrameHeader::kPatches) != 0) { + builder.AddStage(GetPatchesStage(&shared->image_features.patches)); + } + if ((frame_header.flags & FrameHeader::kSplines) != 0) { + builder.AddStage(GetSplineStage(&shared->image_features.splines)); + } + + if (frame_header.upsampling != 1) { + size_t nb_channels = + 3 + + (late_ec_upsample ? frame_header.extra_channel_upsampling.size() : 0); + for (size_t c = 0; c < nb_channels; c++) { + builder.AddStage(GetUpsamplingStage( + frame_header.nonserialized_metadata->transform_data, c, + CeilLog2Nonzero(frame_header.upsampling))); + } + } -void LoadBorders(const Rect& block_rect, size_t hshift, size_t vshift, - const FrameDimensions& frame_dim, size_t padding, - const ImageF& border_storage_h, const ImageF& border_storage_v, - const Rect& r, ImageF* plane_out) { - constexpr size_t kGroupDataXBorder = PassesDecoderState::kGroupDataXBorder; - constexpr size_t kGroupDataYBorder = PassesDecoderState::kGroupDataYBorder; - size_t x0 = DivCeil(block_rect.x0() * kBlockDim, 1 << hshift); - size_t x1 = - DivCeil((block_rect.x0() + block_rect.xsize()) * kBlockDim, 1 << hshift); - size_t y0 = DivCeil(block_rect.y0() * kBlockDim, 1 << vshift); - size_t y1 = - DivCeil((block_rect.y0() + block_rect.ysize()) * kBlockDim, 1 << vshift); - size_t gy = block_rect.y0() / kGroupDimInBlocks; - size_t gx = block_rect.x0() / kGroupDimInBlocks; - size_t borderx = GroupBorderAssigner::PaddingX(padding); - size_t bordery = padding; - size_t borderx_write = padding + borderx; - size_t bordery_write = padding + bordery; - // Limits of the area to copy from, in image coordinates. - JXL_DASSERT(r.x0() == 0 || r.x0() >= borderx); - size_t x0src = DivCeil(r.x0() == 0 ? r.x0() : r.x0() - borderx, 1 << hshift); - // r may be such that r.x1 (namely x0() + xsize()) is within borderx of the - // right side of the image, so we use min() here. - size_t x1src = - DivCeil(std::min(r.x0() + r.xsize() + borderx, frame_dim.xsize_padded), - 1 << hshift); - JXL_DASSERT(r.y0() == 0 || r.y0() >= bordery); - size_t y0src = DivCeil(r.y0() == 0 ? r.y0() : r.y0() - bordery, 1 << vshift); - // Similar to x1, y1 might be closer than bordery from the bottom. - size_t y1src = - DivCeil(std::min(r.y0() + r.ysize() + bordery, frame_dim.ysize_padded), - 1 << vshift); - // Copy other groups' borders from the border storage. - if (y0src < y0) { - JXL_DASSERT(gy > 0); - CopyImageTo( - Rect(x0src, (gy * 2 - 1) * bordery_write, x1src - x0src, bordery_write), - border_storage_h, - Rect(kGroupDataXBorder + x0src - x0, kGroupDataYBorder - bordery_write, - x1src - x0src, bordery_write), - plane_out); + if ((frame_header.flags & FrameHeader::kNoise) != 0) { + builder.AddStage(GetConvolveNoiseStage(num_c - 3)); + builder.AddStage(GetAddNoiseStage(shared->image_features.noise_params, + shared->cmap, num_c - 3)); + } + if (frame_header.dc_level != 0) { + builder.AddStage(GetWriteToImage3FStage( + &shared_storage.dc_frames[frame_header.dc_level - 1])); } - if (y1src > y1) { - // When copying the bottom border we must not be on the bottom groups. - JXL_DASSERT(gy + 1 < frame_dim.ysize_groups); - CopyImageTo( - Rect(x0src, (gy * 2 + 2) * bordery_write, x1src - x0src, bordery_write), - border_storage_h, - Rect(kGroupDataXBorder + x0src - x0, kGroupDataYBorder + y1 - y0, - x1src - x0src, bordery_write), - plane_out); + + if (frame_header.CanBeReferenced() && + frame_header.save_before_color_transform) { + builder.AddStage(GetWriteToImageBundleStage( + &frame_storage_for_referencing, output_encoding_info.color_encoding)); + } + + bool has_alpha = false; + size_t alpha_c = 0; + for (size_t i = 0; i < decoded->metadata()->extra_channel_info.size(); i++) { + if (decoded->metadata()->extra_channel_info[i].type == + ExtraChannel::kAlpha) { + has_alpha = true; + alpha_c = 3 + i; + break; + } } - if (x0src < x0) { - JXL_DASSERT(gx > 0); - CopyImageTo( - Rect((gx * 2 - 1) * borderx_write, y0src, borderx_write, y1src - y0src), - border_storage_v, - Rect(kGroupDataXBorder - borderx_write, kGroupDataYBorder + y0src - y0, - borderx_write, y1src - y0src), - plane_out); - } - if (x1src > x1) { - // When copying the right border we must not be on the rightmost groups. - JXL_DASSERT(gx + 1 < frame_dim.xsize_groups); - CopyImageTo( - Rect((gx * 2 + 2) * borderx_write, y0src, borderx_write, y1src - y0src), - border_storage_v, - Rect(kGroupDataXBorder + x1 - x0, kGroupDataYBorder + y0src - y0, - borderx_write, y1src - y0src), - plane_out); - } -} + + size_t width = options.coalescing + ? frame_header.nonserialized_metadata->xsize() + : shared->frame_dim.xsize_upsampled; + size_t height = options.coalescing + ? frame_header.nonserialized_metadata->ysize() + : shared->frame_dim.ysize_upsampled; -} // namespace + if (fast_xyb_srgb8_conversion) { + JXL_ASSERT(!NeedsBlending(this)); + JXL_ASSERT(!frame_header.CanBeReferenced() || + frame_header.save_before_color_transform); + JXL_ASSERT(!options.render_spotcolors || + !decoded->metadata()->Find(ExtraChannel::kSpotColor)); + builder.AddStage(GetFastXYBTosRGB8Stage(rgb_output, rgb_stride, width, + height, rgb_output_is_rgba, + has_alpha, alpha_c)); + } else { + if (frame_header.color_transform == ColorTransform::kYCbCr) { + builder.AddStage(GetYCbCrStage()); + } else if (frame_header.color_transform == ColorTransform::kXYB) { + builder.AddStage(GetXYBStage(output_encoding_info)); + } // Nothing to do for kNone. + + if (options.coalescing && NeedsBlending(this)) { + builder.AddStage( + GetBlendingStage(this, output_encoding_info.color_encoding)); + } -Status PassesDecoderState::FinalizeGroup(size_t group_idx, size_t thread, - Image3F* pixel_data, - ImageBundle* output) { - // Copy the group borders to the border storage. - const Rect block_rect = shared->BlockGroupRect(group_idx); - const YCbCrChromaSubsampling& cs = shared->frame_header.chroma_subsampling; - size_t padding = FinalizeRectPadding(); - for (size_t c = 0; c < 3; c++) { - SaveBorders(block_rect, cs.HShift(c), cs.VShift(c), padding, - pixel_data->Plane(c), &borders_horizontal.Plane(c), - &borders_vertical.Plane(c)); + if (options.coalescing && frame_header.CanBeReferenced() && + !frame_header.save_before_color_transform) { + builder.AddStage(GetWriteToImageBundleStage( + &frame_storage_for_referencing, output_encoding_info.color_encoding)); + } + + if (options.render_spotcolors && + frame_header.nonserialized_metadata->m.Find(ExtraChannel::kSpotColor)) { + for (size_t i = 0; i < decoded->metadata()->extra_channel_info.size(); + i++) { + // Don't use Find() because there may be multiple spot color channels. + const ExtraChannelInfo& eci = + decoded->metadata()->extra_channel_info[i]; + if (eci.type == ExtraChannel::kSpotColor) { + builder.AddStage(GetSpotColorStage(3 + i, eci.spot_color)); + } + } + } + + if (pixel_callback) { + 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, + rgb_output_is_rgba, has_alpha, + alpha_c)); + } else { + builder.AddStage(GetWriteToImageBundleStage( + decoded, output_encoding_info.color_encoding)); + } } - Rect fir_rects[GroupBorderAssigner::kMaxToFinalize]; - size_t num_fir_rects = 0; - group_border_assigner.GroupDone(group_idx, FinalizeRectPadding(), fir_rects, - &num_fir_rects); - for (size_t i = 0; i < num_fir_rects; i++) { - const Rect& r = fir_rects[i]; - for (size_t c = 0; c < 3; c++) { - LoadBorders(block_rect, cs.HShift(c), cs.VShift(c), shared->frame_dim, - padding, borders_horizontal.Plane(c), - borders_vertical.Plane(c), r, &pixel_data->Plane(c)); - } - Rect pixel_data_rect( - kGroupDataXBorder + r.x0() - block_rect.x0() * kBlockDim, - kGroupDataYBorder + r.y0() - block_rect.y0() * kBlockDim, r.xsize(), - r.ysize()); - JXL_RETURN_IF_ERROR(FinalizeImageRect(pixel_data, pixel_data_rect, {}, this, - thread, output, r)); - } - return true; + render_pipeline = std::move(builder).Finalize(shared->frame_dim); + return render_pipeline->IsInitialized(); } } // namespace jxl 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 @@ -39,45 +39,10 @@ return true; } } // namespace -Status DecodePreview(const DecompressParams& dparams, - const CodecMetadata& metadata, - BitReader* JXL_RESTRICT reader, ThreadPool* pool, - ImageBundle* JXL_RESTRICT preview, uint64_t* dec_pixels, - const SizeConstraints* constraints) { - // No preview present in file. - if (!metadata.m.have_preview) { - if (dparams.preview == Override::kOn) { - return JXL_FAILURE("preview == kOn but no preview present"); - } - return true; - } - - // Have preview; prepare to skip or read it. - JXL_RETURN_IF_ERROR(reader->JumpToByteBoundary()); - - if (dparams.preview == Override::kOff) { - JXL_RETURN_IF_ERROR(SkipFrame(metadata, reader, /*is_preview=*/true)); - return true; - } - - // Else: default or kOn => decode preview. - PassesDecoderState dec_state; - JXL_RETURN_IF_ERROR(dec_state.output_encoding_info.Set( - metadata, ColorEncoding::LinearSRGB(metadata.m.color_encoding.IsGray()))); - JXL_RETURN_IF_ERROR(DecodeFrame(dparams, &dec_state, pool, reader, preview, - metadata, constraints, - /*is_preview=*/true)); - if (dec_pixels) { - *dec_pixels += dec_state.shared->frame_dim.xsize_upsampled * - dec_state.shared->frame_dim.ysize_upsampled; - } - return true; -} - // To avoid the complexity of file I/O and buffering, we assume the bitstream // is loaded (or for large images/sequences: mapped into) memory. Status DecodeFile(const DecompressParams& dparams, const Span file, CodecInOut* JXL_RESTRICT io, ThreadPool* pool) { @@ -122,25 +87,28 @@ if (!res) { return res; } } - JXL_RETURN_IF_ERROR(DecodePreview(dparams, io->metadata, &reader, pool, - &io->preview_frame, &io->dec_pixels, - &io->constraints)); + PassesDecoderState dec_state; + JXL_RETURN_IF_ERROR(dec_state.output_encoding_info.Set( + io->metadata, + ColorEncoding::LinearSRGB(io->metadata.m.color_encoding.IsGray()))); + + if (io->metadata.m.have_preview) { + JXL_RETURN_IF_ERROR(reader.JumpToByteBoundary()); + JXL_RETURN_IF_ERROR(DecodeFrame(dparams, &dec_state, pool, &reader, + &io->preview_frame, io->metadata, + &io->constraints, /*is_preview=*/true)); + } // Only necessary if no ICC and no preview. JXL_RETURN_IF_ERROR(reader.JumpToByteBoundary()); if (io->metadata.m.have_animation && dparams.keep_dct) { return JXL_FAILURE("Cannot decode to JPEG an animation"); } - PassesDecoderState dec_state; - JXL_RETURN_IF_ERROR(dec_state.output_encoding_info.Set( - io->metadata, - ColorEncoding::LinearSRGB(io->metadata.m.color_encoding.IsGray()))); - io->frames.clear(); Status dec_ok(false); do { io->frames.emplace_back(&io->metadata.m); if (jpeg_data) { 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 @@ -42,15 +42,10 @@ PassesDecoderState* dec_state, ThreadPool* JXL_RESTRICT pool, BitReader* JXL_RESTRICT reader, ImageBundle* decoded, const CodecMetadata& metadata, const SizeConstraints* constraints, bool is_preview = false); -// Leaves reader in the same state as DecodeFrame would. Used to skip preview. -// Also updates `dec_state` with the new frame header. -Status SkipFrame(const CodecMetadata& metadata, BitReader* JXL_RESTRICT reader, - bool is_preview = false); - // TODO(veluca): implement "forced drawing". class FrameDecoder { public: // All parameters must outlive the FrameDecoder. FrameDecoder(PassesDecoderState* dec_state, const CodecMetadata& metadata, @@ -207,11 +202,10 @@ private: Status ProcessDCGlobal(BitReader* br); Status ProcessDCGroup(size_t dc_group_id, BitReader* br); void FinalizeDC(); Status AllocateOutput(); - Status PreparePipeline(); Status ProcessACGlobal(BitReader* br); Status ProcessACGroup(size_t ac_group_id, BitReader* JXL_RESTRICT* br, size_t num_passes, size_t thread, bool force_draw, bool dc_only); void MarkSections(const SectionInfo* sections, size_t num, @@ -225,14 +219,16 @@ void 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); } - dec_state_->EnsureStorage(storage_size); use_task_id_ = num_threads > num_tasks; if (dec_state_->render_pipeline) { - dec_state_->render_pipeline->PrepareForThreads(storage_size); + dec_state_->render_pipeline->PrepareForThreads( + storage_size, + /*use_group_ids=*/modular_frame_decoder_.UsesFullImage() && + frame_header_.encoding == FrameEncoding::kVarDCT); } } size_t GetStorageLocation(size_t thread, size_t task) { if (use_task_id_) return task; @@ -248,11 +244,11 @@ bool CanDoLowMemoryPath(bool undo_orientation) const { if (undo_orientation && decoded_->metadata()->GetOrientation() != Orientation::kIdentity) { return false; } - if (ImageBlender::NeedsBlending(dec_state_)) return false; + if (NeedsBlending(dec_state_)) return false; if (frame_header_.CanBeReferenced()) return false; if (render_spotcolors_ && decoded_->metadata()->Find(ExtraChannel::kSpotColor)) { return false; } 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 @@ -35,38 +35,23 @@ #include "lib/jxl/dec_cache.h" #include "lib/jxl/dec_group.h" #include "lib/jxl/dec_modular.h" #include "lib/jxl/dec_params.h" #include "lib/jxl/dec_patch_dictionary.h" -#include "lib/jxl/dec_reconstruct.h" -#include "lib/jxl/dec_upsample.h" #include "lib/jxl/dec_xyb.h" #include "lib/jxl/epf.h" #include "lib/jxl/fields.h" -#include "lib/jxl/filters.h" #include "lib/jxl/frame_header.h" #include "lib/jxl/image.h" #include "lib/jxl/image_bundle.h" #include "lib/jxl/image_ops.h" #include "lib/jxl/jpeg/jpeg_data.h" #include "lib/jxl/loop_filter.h" #include "lib/jxl/luminance.h" #include "lib/jxl/passes_state.h" #include "lib/jxl/quant_weights.h" #include "lib/jxl/quantizer.h" -#include "lib/jxl/render_pipeline/stage_blending.h" -#include "lib/jxl/render_pipeline/stage_chroma_upsampling.h" -#include "lib/jxl/render_pipeline/stage_epf.h" -#include "lib/jxl/render_pipeline/stage_gaborish.h" -#include "lib/jxl/render_pipeline/stage_noise.h" -#include "lib/jxl/render_pipeline/stage_patches.h" -#include "lib/jxl/render_pipeline/stage_splines.h" -#include "lib/jxl/render_pipeline/stage_spot.h" -#include "lib/jxl/render_pipeline/stage_upsampling.h" -#include "lib/jxl/render_pipeline/stage_write.h" -#include "lib/jxl/render_pipeline/stage_xyb.h" -#include "lib/jxl/render_pipeline/stage_ycbcr.h" #include "lib/jxl/sanitizers.h" #include "lib/jxl/splines.h" #include "lib/jxl/toc.h" namespace jxl { @@ -97,37 +82,10 @@ JXL_ASSERT(frame_header->nonserialized_metadata != nullptr); JXL_RETURN_IF_ERROR(ReadFrameHeader(reader, frame_header)); return true; } -Status SkipFrame(const CodecMetadata& metadata, BitReader* JXL_RESTRICT reader, - bool is_preview) { - FrameHeader header(&metadata); - header.nonserialized_is_preview = is_preview; - JXL_RETURN_IF_ERROR(DecodeFrameHeader(reader, &header)); - - // Read TOC. - std::vector group_offsets; - std::vector group_sizes; - uint64_t groups_total_size; - const bool has_ac_global = true; - const FrameDimensions frame_dim = header.ToFrameDimensions(); - const size_t toc_entries = - NumTocEntries(frame_dim.num_groups, frame_dim.num_dc_groups, - header.passes.num_passes, has_ac_global); - JXL_RETURN_IF_ERROR(ReadGroupOffsets(toc_entries, reader, &group_offsets, - &group_sizes, &groups_total_size)); - - // Pretend all groups are read. - reader->SkipBits(groups_total_size * kBitsPerByte); - if (reader->TotalBitsConsumed() > reader->TotalBytes() * kBitsPerByte) { - return JXL_FAILURE("Group code extends after stream end"); - } - - return true; -} - static BitReader* GetReaderForSection( size_t num_groups, size_t num_passes, size_t group_codes_begin, const std::vector& group_offsets, const std::vector& group_sizes, BitReader* JXL_RESTRICT reader, BitReader* JXL_RESTRICT store, size_t index) { @@ -304,10 +262,19 @@ decoded->RemoveColor(); decoded->ClearExtraChannels(); decoded->duration = frame_header_.animation_frame.duration; + if (!frame_header_.nonserialized_is_preview && + (frame_header_.frame_type == kRegularFrame || + frame_header_.frame_type == kSkipProgressive)) { + ++dec_state_->visible_frame_index; + dec_state_->nonvisible_frame_index = 0; + } else { + ++dec_state_->nonvisible_frame_index; + } + // Read TOC. uint64_t groups_total_size; const bool has_ac_global = true; const size_t toc_entries = NumTocEntries(num_groups, frame_dim_.num_dc_groups, num_passes, has_ac_global); @@ -424,11 +391,11 @@ } } // Splines' draw cache uses the color correlation map. if (shared.frame_header.flags & FrameHeader::kSplines) { JXL_RETURN_IF_ERROR(shared.image_features.splines.InitializeDrawCache( - frame_dim_.xsize_upsampled_padded, frame_dim_.ysize_upsampled_padded, + frame_dim_.xsize_upsampled, frame_dim_.ysize_upsampled, dec_state_->shared->cmap)); } Status dec_status = modular_frame_decoder_.DecodeGlobalInfo( br, frame_header_, allow_partial_dc_global_); if (dec_status.IsFatalError()) return dec_status; @@ -455,12 +422,11 @@ /*zerofill=*/false, nullptr, nullptr, nullptr, allow_partial_frames_)); if (frame_header_.encoding == FrameEncoding::kVarDCT) { JXL_RETURN_IF_ERROR( modular_frame_decoder_.DecodeAcMetadata(dc_group_id, br, dec_state_)); } else if (lf.epf_iters > 0) { - FillImage(kInvSigmaNum / lf.epf_sigma_for_modular, - &dec_state_->filter_weights.sigma); + FillImage(kInvSigmaNum / lf.epf_sigma_for_modular, &dec_state_->sigma); } decoded_dc_groups_[dc_group_id] = uint8_t{true}; return true; } @@ -477,71 +443,19 @@ finalized_dc_ = true; } Status FrameDecoder::AllocateOutput() { if (allocated_) return true; - const CodecMetadata& metadata = *frame_header_.nonserialized_metadata; - if (dec_state_->rgb_output == nullptr && !dec_state_->pixel_callback) { - modular_frame_decoder_.MaybeDropFullImage(); - decoded_->SetFromImage(Image3F(frame_dim_.xsize_upsampled_padded, - frame_dim_.ysize_upsampled_padded), - dec_state_->output_encoding_info.color_encoding); - } - if (dec_state_->render_pipeline) { - // TODO(veluca): consider not reallocating ECs if not needed. - decoded_->extra_channels().clear(); - for (size_t i = 0; i < metadata.m.num_extra_channels; i++) { - decoded_->extra_channels().emplace_back( - frame_dim_.xsize_upsampled_padded, frame_dim_.ysize_upsampled_padded); - } - if (frame_header_.dc_level != 0) { - dec_state_->shared_storage.dc_frames[frame_header_.dc_level - 1] = - Image3F(frame_dim_.xsize, frame_dim_.ysize); - } - if (frame_header_.CanBeReferenced()) { - // TODO(veluca): this will need to be adapted for RGB output. - JXL_ASSERT(dec_state_->rgb_output == nullptr && - !dec_state_->pixel_callback); - dec_state_->frame_storage_for_referencing = decoded_->Copy(); - } - } else { - dec_state_->extra_channels.clear(); - if (metadata.m.num_extra_channels > 0) { - for (size_t i = 0; i < metadata.m.num_extra_channels; i++) { - uint32_t ecups = frame_header_.extra_channel_upsampling[i]; - dec_state_->extra_channels.emplace_back( - DivCeil(frame_dim_.xsize_upsampled_padded, ecups), - DivCeil(frame_dim_.ysize_upsampled_padded, ecups)); -#if JXL_MEMORY_SANITIZER - // Avoid errors due to loading vectors on the outermost padding. - // Upsample of extra channels requires this padding to be initialized. - // TODO(deymo): Remove this and use rects up to {x,y}size_upsampled - // instead of the padded one. - for (size_t y = 0; - y < DivCeil(frame_dim_.ysize_upsampled_padded, ecups); y++) { - for (size_t x = (y < DivCeil(frame_dim_.ysize_upsampled, ecups) - ? DivCeil(frame_dim_.xsize_upsampled, ecups) - : 0); - x < DivCeil(frame_dim_.xsize_upsampled_padded, ecups); x++) { - dec_state_->extra_channels.back().Row(y)[x] = - msan::kSanitizerSentinel; - } - } -#endif - } - } - } + modular_frame_decoder_.MaybeDropFullImage(); decoded_->origin = dec_state_->shared->frame_header.frame_origin; JXL_RETURN_IF_ERROR(dec_state_->InitForAC(nullptr)); allocated_ = true; return true; } Status FrameDecoder::ProcessACGlobal(BitReader* br) { JXL_CHECK(finalized_dc_); - JXL_CHECK(decoded_->HasColor() || dec_state_->rgb_output != nullptr || - !!dec_state_->pixel_callback); // Decode AC group. if (frame_header_.encoding == FrameEncoding::kVarDCT) { JXL_RETURN_IF_ERROR(dec_state_->shared_storage.matrices.Decode( br, &modular_frame_decoder_)); @@ -631,20 +545,10 @@ for (size_t j = 0; j < 64; j++) { jpeg_data->quant[i].values[j] = jpeg_data->quant[i - 1].values[j]; } } } - // Set memory buffer for pre-color-transform frame, if needed. - if (frame_header_.needs_color_transform() && - frame_header_.save_before_color_transform) { - dec_state_->pre_color_transform_frame = - Image3F(frame_dim_.xsize_upsampled, frame_dim_.ysize_upsampled); - } else { - // clear pre_color_transform_frame to ensure that previously moved-from - // images are not used. - dec_state_->pre_color_transform_frame = Image3F(); - } decoded_ac_global_ = true; return true; } Status FrameDecoder::ProcessACGroup(size_t ac_group_id, @@ -655,25 +559,23 @@ 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; - RenderPipelineInput render_pipeline_input_storage; - RenderPipelineInput* render_pipeline_input = nullptr; - if (dec_state_->render_pipeline) { - render_pipeline_input_storage = - dec_state_->render_pipeline->GetInputBuffers(ac_group_id, thread); - render_pipeline_input = &render_pipeline_input_storage; - } + RenderPipelineInput render_pipeline_input = + dec_state_->render_pipeline->GetInputBuffers(ac_group_id, thread); + + bool should_run_pipeline = true; if (frame_header_.encoding == FrameEncoding::kVarDCT) { group_dec_caches_[thread].InitOnce(frame_header_.passes.num_passes, dec_state_->used_acs); - JXL_RETURN_IF_ERROR(DecodeGroup( - br, num_passes, ac_group_id, dec_state_, &group_dec_caches_[thread], - thread, render_pipeline_input, decoded_, - decoded_passes_per_ac_group_[ac_group_id], force_draw, dc_only)); + JXL_RETURN_IF_ERROR(DecodeGroup(br, num_passes, ac_group_id, dec_state_, + &group_dec_caches_[thread], thread, + render_pipeline_input, decoded_, + 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); for (size_t i = 0; i < frame_header_.passes.num_passes; i++) { @@ -682,235 +584,56 @@ if (i >= decoded_passes_per_ac_group_[ac_group_id] && i < decoded_passes_per_ac_group_[ac_group_id] + num_passes) { JXL_RETURN_IF_ERROR(modular_frame_decoder_.DecodeGroup( mrect, br[i - decoded_passes_per_ac_group_[ac_group_id]], minShift, maxShift, ModularStreamId::ModularAC(ac_group_id, i), - /*zerofill=*/false, dec_state_, render_pipeline_input, decoded_, + /*zerofill=*/false, dec_state_, &render_pipeline_input, decoded_, allow_partial_frames_)); } else if (i >= decoded_passes_per_ac_group_[ac_group_id] + num_passes && force_draw) { JXL_RETURN_IF_ERROR(modular_frame_decoder_.DecodeGroup( mrect, nullptr, minShift, maxShift, ModularStreamId::ModularAC(ac_group_id, i), /*zerofill=*/true, - dec_state_, render_pipeline_input, decoded_, allow_partial_frames_)); + dec_state_, &render_pipeline_input, decoded_, allow_partial_frames_)); } } decoded_passes_per_ac_group_[ac_group_id] += num_passes; - if ((frame_header_.flags & FrameHeader::kNoise) != 0 && - render_pipeline_input) { + if ((frame_header_.flags & FrameHeader::kNoise) != 0) { PROFILER_ZONE("GenerateNoise"); - size_t num_x_groups = DivCeil(frame_dim_.xsize_upsampled_padded, kGroupDim); size_t noise_c_start = 3 + frame_header_.nonserialized_metadata->m.num_extra_channels; // When the color channels are downsampled, we need to generate more noise // input for the current group than just the group dimensions. std::pair rects[3]; for (size_t iy = 0; iy < frame_header_.upsampling; iy++) { for (size_t ix = 0; ix < frame_header_.upsampling; ix++) { for (size_t c = 0; c < 3; c++) { - auto r = render_pipeline_input->GetBuffer(noise_c_start + 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(); - if (frame_header_.encoding == FrameEncoding::kVarDCT) { - x1 = RoundUpTo(x1, kBlockDim * frame_header_.upsampling); - y1 = RoundUpTo(y1, kBlockDim * frame_header_.upsampling); - } rects[c].second = Rect(r.second.x0() + ix * kGroupDim, r.second.y0() + iy * kGroupDim, kGroupDim, kGroupDim, x1, y1); } - Random3Planes(dec_state_->noise_seed + - (gx * frame_header_.upsampling + ix) + - (gy * frame_header_.upsampling + iy) * num_x_groups, + Random3Planes(dec_state_->visible_frame_index, + dec_state_->nonvisible_frame_index, + (gx * frame_header_.upsampling + ix) * kGroupDim, + (gy * frame_header_.upsampling + iy) * kGroupDim, rects[0], rects[1], rects[2]); } } } - if (render_pipeline_input && !modular_frame_decoder_.UsesFullImage()) { - render_pipeline_input->Done(); + if (!modular_frame_decoder_.UsesFullImage() && !decoded_->IsJPEG() && + should_run_pipeline) { + render_pipeline_input.Done(); } return true; } -Status FrameDecoder::PreparePipeline() { - size_t num_c = 3 + frame_header_.nonserialized_metadata->m.num_extra_channels; - if ((frame_header_.flags & FrameHeader::kNoise) != 0) { - num_c += 3; - } - - RenderPipeline::Builder builder(num_c); - - if (use_slow_rendering_pipeline_) { - builder.UseSimpleImplementation(); - } else { - JXL_ABORT("Not implemented: fast pipeline"); - } - - if (!frame_header_.chroma_subsampling.Is444()) { - for (size_t c = 0; c < 3; c++) { - if (frame_header_.chroma_subsampling.HShift(c) != 0) { - builder.AddStage(GetChromaUpsamplingStage(c, /*horizontal=*/true)); - } - if (frame_header_.chroma_subsampling.VShift(c) != 0) { - builder.AddStage(GetChromaUpsamplingStage(c, /*horizontal=*/false)); - } - } - } - - if (frame_header_.loop_filter.gab) { - builder.AddStage(GetGaborishStage(frame_header_.loop_filter)); - } - - { - const LoopFilter& lf = frame_header_.loop_filter; - if (lf.epf_iters >= 3) { - builder.AddStage(GetEPFStage(lf, dec_state_->filter_weights.sigma, 0)); - } - if (lf.epf_iters >= 1) { - builder.AddStage(GetEPFStage(lf, dec_state_->filter_weights.sigma, 1)); - } - if (lf.epf_iters >= 2) { - builder.AddStage(GetEPFStage(lf, dec_state_->filter_weights.sigma, 2)); - } - } - - bool late_ec_upsample = frame_header_.upsampling != 1; - for (auto ecups : frame_header_.extra_channel_upsampling) { - if (ecups != frame_header_.upsampling) { - // If patches are applied, either frame_header.upsampling == 1 or - // late_ec_upsample is true. - late_ec_upsample = false; - } - } - - if (!late_ec_upsample) { - for (size_t ec = 0; ec < frame_header_.extra_channel_upsampling.size(); - ec++) { - if (frame_header_.extra_channel_upsampling[ec] != 1) { - builder.AddStage(GetUpsamplingStage( - frame_header_.nonserialized_metadata->transform_data, 3 + ec, - CeilLog2Nonzero(frame_header_.extra_channel_upsampling[ec]))); - } - } - } - - if ((frame_header_.flags & FrameHeader::kPatches) != 0) { - builder.AddStage( - GetPatchesStage(&dec_state_->shared->image_features.patches)); - } - if ((frame_header_.flags & FrameHeader::kSplines) != 0) { - builder.AddStage( - GetSplineStage(&dec_state_->shared->image_features.splines)); - } - - if (frame_header_.upsampling != 1) { - size_t nb_channels = - 3 + - (late_ec_upsample ? frame_header_.extra_channel_upsampling.size() : 0); - for (size_t c = 0; c < nb_channels; c++) { - builder.AddStage(GetUpsamplingStage( - frame_header_.nonserialized_metadata->transform_data, c, - CeilLog2Nonzero(frame_header_.upsampling))); - } - } - - if ((frame_header_.flags & FrameHeader::kNoise) != 0) { - builder.AddStage(GetConvolveNoiseStage(num_c - 3)); - builder.AddStage( - GetAddNoiseStage(dec_state_->shared->image_features.noise_params, - dec_state_->shared->cmap, num_c - 3)); - builder.UsesNoise(); - } - if (frame_header_.dc_level != 0) { - builder.AddStage(GetWriteToImage3FStage( - &dec_state_->shared_storage.dc_frames[frame_header_.dc_level - 1])); - } - if (!coalescing_) { - JXL_ABORT("Not implemented: skip coalescing"); - } - - if (frame_header_.CanBeReferenced() && - frame_header_.save_before_color_transform) { - builder.AddStage( - GetWriteToImageBundleStage(&dec_state_->frame_storage_for_referencing)); - } - - bool has_alpha = false; - size_t alpha_c = 0; - for (size_t i = 0; i < decoded_->metadata()->extra_channel_info.size(); i++) { - if (decoded_->metadata()->extra_channel_info[i].type == - ExtraChannel::kAlpha) { - has_alpha = true; - alpha_c = 3 + i; - break; - } - } - - // TODO(veluca): double-check when blending/no coalescing is enabled. - size_t width = coalescing_ ? frame_header_.nonserialized_metadata->xsize() - : frame_dim_.xsize_upsampled; - size_t height = coalescing_ ? frame_header_.nonserialized_metadata->ysize() - : frame_dim_.ysize_upsampled; - - if (dec_state_->fast_xyb_srgb8_conversion) { - JXL_ASSERT(!ImageBlender::NeedsBlending(dec_state_)); - JXL_ASSERT(!frame_header_.CanBeReferenced() || - frame_header_.save_before_color_transform); - JXL_ASSERT(!render_spotcolors_); - builder.AddStage(GetFastXYBTosRGB8Stage( - dec_state_->rgb_output, dec_state_->rgb_stride, width, height, - dec_state_->rgb_output_is_rgba, has_alpha, alpha_c)); - } else { - if (frame_header_.color_transform == ColorTransform::kYCbCr) { - builder.AddStage(GetYCbCrStage()); - } else if (frame_header_.color_transform == ColorTransform::kXYB) { - builder.AddStage(GetXYBStage(dec_state_->output_encoding_info)); - } // Nothing to do for kNone. - - if (ImageBlender::NeedsBlending(dec_state_)) { - builder.AddStage(GetBlendingStage( - dec_state_, dec_state_->output_encoding_info.color_encoding)); - } - - if (frame_header_.CanBeReferenced() && - !frame_header_.save_before_color_transform) { - builder.AddStage(GetWriteToImageBundleStage( - &dec_state_->frame_storage_for_referencing)); - } - - if (render_spotcolors_ && frame_header_.nonserialized_metadata->m.Find( - ExtraChannel::kSpotColor)) { - for (size_t i = 0; i < decoded_->metadata()->extra_channel_info.size(); - i++) { - // Don't use Find() because there may be multiple spot color channels. - const ExtraChannelInfo& eci = - decoded_->metadata()->extra_channel_info[i]; - if (eci.type == ExtraChannel::kSpotColor) { - builder.AddStage(GetSpotColorStage(3 + i, eci.spot_color)); - } - } - } - - if (dec_state_->pixel_callback) { - builder.AddStage(GetWriteToPixelCallbackStage( - dec_state_->pixel_callback, width, height, - dec_state_->rgb_output_is_rgba, has_alpha, alpha_c)); - } else if (dec_state_->rgb_output) { - builder.AddStage(GetWriteToU8Stage( - dec_state_->rgb_output, dec_state_->rgb_stride, width, height, - dec_state_->rgb_output_is_rgba, has_alpha, alpha_c)); - } else { - builder.AddStage(GetWriteToImageBundleStage(decoded_)); - } - } - dec_state_->render_pipeline = std::move(builder).Finalize(frame_dim_); - return dec_state_->render_pipeline->IsInitialized(); -} - void FrameDecoder::MarkSections(const SectionInfo* sections, size_t num, SectionStatus* section_status) { num_sections_done_ = num; for (size_t i = 0; i < num; i++) { if (section_status[i] == SectionStatus::kSkipped || @@ -1012,13 +735,16 @@ } if (has_error) return JXL_FAILURE("Error in DC group"); if (*std::min_element(decoded_dc_groups_.begin(), decoded_dc_groups_.end()) && !finalized_dc_) { - if (use_slow_rendering_pipeline_) { - JXL_RETURN_IF_ERROR(PreparePipeline()); - } + PassesDecoderState::PipelineOptions pipeline_options; + pipeline_options.use_slow_render_pipeline = use_slow_rendering_pipeline_; + pipeline_options.coalescing = coalescing_; + pipeline_options.render_spotcolors = render_spotcolors_; + JXL_RETURN_IF_ERROR( + dec_state_->PreparePipeline(decoded_, pipeline_options)); FinalizeDC(); JXL_RETURN_IF_ERROR(AllocateOutput()); if (pause_at_progressive_ && !single_section) { bool can_return_dc = true; if (single_section) { @@ -1047,31 +773,22 @@ return true; } } } - if (finalized_dc_) dec_state_->EnsureBordersStorage(); - if (finalized_dc_ && ac_global_sec != num && !decoded_ac_global_) { JXL_RETURN_IF_ERROR(ProcessACGlobal(sections[ac_global_sec].br)); section_status[ac_global_sec] = SectionStatus::kDone; } if (decoded_ac_global_) { - // The decoded image requires padding for filtering. ProcessACGlobal added - // the padding, however when Flush is used, the image is shrunk to the - // output size. Add the padding back here. This is a cheap operation - // since the image has the original allocated size. The memory and original - // size are already there, but for safety we require the indicated xsize and - // ysize dimensions match the working area, see PlaneRowBoundsCheck. - decoded_->ShrinkTo(frame_dim_.xsize_upsampled_padded, - frame_dim_.ysize_upsampled_padded); - // Mark all the AC groups that we received as not complete yet. for (size_t i = 0; i < ac_group_sec.size(); i++) { - if (num_ac_passes[i] == 0) continue; - dec_state_->group_border_assigner.ClearDone(i); + if (num_ac_passes[i] == 0 && !modular_frame_decoder_.UsesFullImage()) { + continue; + } + dec_state_->render_pipeline->ClearDone(i); } JXL_RETURN_IF_ERROR(RunOnPool( pool_, 0, ac_group_sec.size(), [this](size_t num_threads) { @@ -1138,11 +855,11 @@ // We don't have all AC yet: force a draw of all the missing areas. // Mark all sections as not complete. for (size_t i = 0; i < decoded_passes_per_ac_group_.size(); i++) { if (decoded_passes_per_ac_group_[i] == frame_header_.passes.num_passes) continue; - dec_state_->group_border_assigner.ClearDone(i); + dec_state_->render_pipeline->ClearDone(i); } std::atomic has_error{false}; JXL_RETURN_IF_ERROR(RunOnPool( pool_, 0, decoded_passes_per_ac_group_.size(), [this](const size_t num_threads) { @@ -1164,20 +881,14 @@ "ForceDrawGroup")); if (has_error) { return JXL_FAILURE("Drawing groups failed"); } } - // TODO(veluca): the rest of this function should be removed once we have full - // support for per-group decoding. // undo global modular transforms and copy int pixel buffers to float ones JXL_RETURN_IF_ERROR(modular_frame_decoder_.FinalizeDecoding( dec_state_, pool_, decoded_, is_finalized_)); - JXL_RETURN_IF_ERROR(FinalizeFrameDecoding(decoded_, dec_state_, pool_, - /*force_fir=*/false, - /*skip_blending=*/!coalescing_, - /*move_ec=*/is_finalized_)); num_renders_++; return true; } @@ -1268,106 +979,11 @@ JXL_RETURN_IF_ERROR(AllocateOutput()); } JXL_RETURN_IF_ERROR(Flush()); - if (!dec_state_->render_pipeline) { - if (dec_state_->shared->frame_header.CanBeReferenced() && - (frame_header_.frame_type != kRegularFrame || coalescing_)) { - size_t id = dec_state_->shared->frame_header.save_as_reference; - auto& reference_frame = dec_state_->shared_storage.reference_frames[id]; - if (dec_state_->pre_color_transform_frame.xsize() == 0) { - reference_frame.storage = decoded_->Copy(); - } else { - reference_frame.storage = ImageBundle(decoded_->metadata()); - reference_frame.storage.SetFromImage( - CopyImage(dec_state_->pre_color_transform_frame), - decoded_->c_current()); - if (decoded_->HasExtraChannels()) { - const std::vector* ecs = &dec_state_->pre_color_transform_ec; - if (ecs->empty()) ecs = &decoded_->extra_channels(); - std::vector extra_channels; - for (const auto& ec : *ecs) { - extra_channels.push_back(CopyImage(ec)); - } - reference_frame.storage.SetExtraChannels(std::move(extra_channels)); - } - } - reference_frame.frame = &reference_frame.storage; - reference_frame.ib_is_in_xyb = - dec_state_->shared->frame_header.save_before_color_transform; - if (!dec_state_->shared->frame_header.save_before_color_transform) { - const CodecMetadata* metadata = - dec_state_->shared->frame_header.nonserialized_metadata; - if (reference_frame.frame->xsize() < metadata->xsize() || - reference_frame.frame->ysize() < metadata->ysize()) { - return JXL_FAILURE( - "trying to save a reference frame that is too small: %" PRIuS - "x%" PRIuS - " " - "instead of %" PRIuS "x%" PRIuS, - reference_frame.frame->xsize(), reference_frame.frame->ysize(), - metadata->xsize(), metadata->ysize()); - } - reference_frame.storage.ShrinkTo(metadata->xsize(), metadata->ysize()); - } - } - } - - if (frame_header_.nonserialized_is_preview) { - // Fix possible larger image size (multiple of kBlockDim) - // TODO(lode): verify if and when that happens. - decoded_->ShrinkTo(frame_dim_.xsize, frame_dim_.ysize); - } else if (!decoded_->IsJPEG()) { - // A kRegularFrame is blended with the other frames, and thus results in a - // coalesced frame of size equal to image dimensions. Other frames are not - // blended, thus their final size is the size that was defined in the - // frame_header. - if (coalescing_ && (frame_header_.frame_type == kRegularFrame || - frame_header_.frame_type == kSkipProgressive)) { - decoded_->ShrinkTo( - dec_state_->shared->frame_header.nonserialized_metadata->xsize(), - dec_state_->shared->frame_header.nonserialized_metadata->ysize()); - } else { - // xsize_upsampled is the actual frame size, after any upsampling has been - // applied. - decoded_->ShrinkTo(frame_dim_.xsize_upsampled, - frame_dim_.ysize_upsampled); - } - } - - if (render_spotcolors_ && !dec_state_->render_pipeline) { - for (size_t i = 0; i < decoded_->extra_channels().size(); i++) { - // Don't use Find() because there may be multiple spot color channels. - const ExtraChannelInfo& eci = decoded_->metadata()->extra_channel_info[i]; - if (eci.type == ExtraChannel::kOptional) { - continue; - } - if (eci.type == ExtraChannel::kSpotColor) { - float scale = eci.spot_color[3]; - for (size_t c = 0; c < 3; c++) { - for (size_t y = 0; y < decoded_->ysize(); y++) { - float* JXL_RESTRICT p = decoded_->color()->Plane(c).Row(y); - const float* JXL_RESTRICT s = - decoded_->extra_channels()[i].ConstRow(y); - for (size_t x = 0; x < decoded_->xsize(); x++) { - float mix = scale * s[x]; - p[x] = mix * eci.spot_color[c] + (1.0 - mix) * p[x]; - } - } - } - } - } - } - if (dec_state_->shared->frame_header.dc_level != 0 && - !dec_state_->render_pipeline) { - dec_state_->shared_storage - .dc_frames[dec_state_->shared->frame_header.dc_level - 1] = - std::move(*decoded_->color()); - decoded_->RemoveColor(); - } - if (dec_state_->render_pipeline && frame_header_.CanBeReferenced()) { + if (frame_header_.CanBeReferenced()) { auto& info = dec_state_->shared_storage .reference_frames[frame_header_.save_as_reference]; info.storage = std::move(dec_state_->frame_storage_for_referencing); info.ib_is_in_xyb = frame_header_.save_before_color_transform; info.frame = &info.storage; diff --git a/third_party/jpeg-xl/lib/jxl/dec_group.h b/third_party/jpeg-xl/lib/jxl/dec_group.h --- a/third_party/jpeg-xl/lib/jxl/dec_group.h +++ b/third_party/jpeg-xl/lib/jxl/dec_group.h @@ -30,19 +30,21 @@ Status DecodeGroup(BitReader* JXL_RESTRICT* JXL_RESTRICT readers, size_t num_passes, size_t group_idx, PassesDecoderState* JXL_RESTRICT dec_state, GroupDecCache* JXL_RESTRICT group_dec_cache, size_t thread, - RenderPipelineInput* render_pipeline_input, + RenderPipelineInput& render_pipeline_input, ImageBundle* JXL_RESTRICT decoded, size_t first_pass, - bool force_draw, bool dc_only); + bool force_draw, bool dc_only, bool* should_run_pipeline); Status DecodeGroupForRoundtrip(const std::vector>& ac, size_t group_idx, PassesDecoderState* JXL_RESTRICT dec_state, GroupDecCache* JXL_RESTRICT group_dec_cache, - size_t thread, ImageBundle* JXL_RESTRICT decoded, + size_t thread, + RenderPipelineInput& render_pipeline_input, + ImageBundle* JXL_RESTRICT decoded, AuxOut* aux_out); } // namespace jxl #endif // LIB_JXL_DEC_GROUP_H_ 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 @@ -29,11 +29,10 @@ #include "lib/jxl/coeff_order.h" #include "lib/jxl/common.h" #include "lib/jxl/convolve.h" #include "lib/jxl/dct_scales.h" #include "lib/jxl/dec_cache.h" -#include "lib/jxl/dec_reconstruct.h" #include "lib/jxl/dec_transforms-inl.h" #include "lib/jxl/dec_xyb.h" #include "lib/jxl/entropy_coder.h" #include "lib/jxl/epf.h" #include "lib/jxl/opsin_params.h" @@ -59,13 +58,10 @@ enum DrawMode { // Render to pixels. kDraw = 0, // Don't render to pixels. kDontDraw = 1, - // Don't do IDCT or dequantization, but just postprocessing. Used for - // progressive DC. - kOnlyImageFeatures = 2, }; } // namespace jxl #endif // LIB_JXL_DEC_GROUP_CC @@ -166,17 +162,14 @@ Status DecodeGroupImpl(GetBlock* JXL_RESTRICT get_block, GroupDecCache* JXL_RESTRICT group_dec_cache, PassesDecoderState* JXL_RESTRICT dec_state, size_t thread, size_t group_idx, - RenderPipelineInput* render_pipeline_input, + RenderPipelineInput& render_pipeline_input, ImageBundle* decoded, DrawMode draw) { // TODO(veluca): investigate cache usage in this function. PROFILER_FUNC; - constexpr size_t kGroupDataXBorder = PassesDecoderState::kGroupDataXBorder; - constexpr size_t kGroupDataYBorder = PassesDecoderState::kGroupDataYBorder; - const Rect block_rect = dec_state->shared->BlockGroupRect(group_idx); const AcStrategyImage& ac_strategy = dec_state->shared->ac_strategy; const size_t xsize_blocks = block_rect.xsize(); const size_t ysize_blocks = block_rect.ysize(); @@ -188,18 +181,11 @@ const YCbCrChromaSubsampling& cs = dec_state->shared->frame_header.chroma_subsampling; size_t idct_stride[3]; for (size_t c = 0; c < 3; c++) { - if (render_pipeline_input) { - idct_stride[c] = - render_pipeline_input->GetBuffer(c).first->PixelsPerRow(); - } else { - idct_stride[c] = dec_state->EagerFinalizeImageRect() - ? dec_state->group_data[thread].PixelsPerRow() - : dec_state->decoded.PixelsPerRow(); - } + idct_stride[c] = render_pipeline_input.GetBuffer(c).first->PixelsPerRow(); } HWY_ALIGN int32_t scaled_qtable[64 * 3]; ACType ac_type = dec_state->coefficients->Type(); @@ -258,11 +244,10 @@ JXL_ASSERT(r[i].IsInside({0, 0, dec_state->shared->dc->Plane(i).xsize(), dec_state->shared->dc->Plane(i).ysize()})); } for (size_t by = 0; by < ysize_blocks; ++by) { - if (draw == kOnlyImageFeatures) break; get_block->StartRow(by); size_t sby[3] = {by >> vshift[0], by >> vshift[1], by >> vshift[2]}; const int32_t* JXL_RESTRICT row_quant = block_rect.ConstRow(dec_state->shared->raw_quant_field, by); @@ -283,22 +268,12 @@ }; float* JXL_RESTRICT idct_row[3]; int16_t* JXL_RESTRICT jpeg_row[3]; for (size_t c = 0; c < 3; c++) { - if (render_pipeline_input) { - idct_row[c] = render_pipeline_input->GetBuffer(c).second.Row( - render_pipeline_input->GetBuffer(c).first, sby[c] * kBlockDim); - } else if (dec_state->EagerFinalizeImageRect()) { - idct_row[c] = dec_state->group_data[thread].PlaneRow( - c, sby[c] * kBlockDim + kGroupDataYBorder) + - kGroupDataXBorder; - } else { - idct_row[c] = - dec_state->decoded.PlaneRow(c, (r[c].y0() + sby[c]) * kBlockDim) + - r[c].x0() * kBlockDim; - } + idct_row[c] = render_pipeline_input.GetBuffer(c).second.Row( + render_pipeline_input.GetBuffer(c).first, sby[c] * kBlockDim); if (decoded->IsJPEG()) { auto& component = decoded->jpeg_data->components[jpeg_c_map[c]]; jpeg_row[c] = component.coeffs.data() + (component.width_in_blocks * (r[c].y0() + sby[c]) + r[c].x0()) * @@ -445,15 +420,10 @@ } } if (draw == kDontDraw) { return true; } - // No ApplyImageFeatures in JPEG mode or when we need to delay it. - if (!decoded->IsJPEG() && dec_state->EagerFinalizeImageRect()) { - JXL_RETURN_IF_ERROR(dec_state->FinalizeGroup( - group_idx, thread, &dec_state->group_data[thread], decoded)); - } return true; } // NOLINTNEXTLINE(google-readability-namespace-comments) } // namespace HWY_NAMESPACE @@ -697,63 +667,85 @@ Status DecodeGroup(BitReader* JXL_RESTRICT* JXL_RESTRICT readers, size_t num_passes, size_t group_idx, PassesDecoderState* JXL_RESTRICT dec_state, GroupDecCache* JXL_RESTRICT group_dec_cache, size_t thread, - RenderPipelineInput* render_pipeline_input, + RenderPipelineInput& render_pipeline_input, ImageBundle* JXL_RESTRICT decoded, size_t first_pass, - bool force_draw, bool dc_only) { + bool force_draw, bool dc_only, bool* should_run_pipeline) { PROFILER_FUNC; DrawMode draw = (num_passes + first_pass == dec_state->shared->frame_header.passes.num_passes) || force_draw ? kDraw : kDontDraw; + if (should_run_pipeline) { + *should_run_pipeline = draw != kDontDraw; + } + if (draw == kDraw && num_passes == 0 && first_pass == 0) { + group_dec_cache->InitDCBufferOnce(); const YCbCrChromaSubsampling& cs = dec_state->shared->frame_header.chroma_subsampling; for (size_t c : {0, 1, 2}) { size_t hs = cs.HShift(c); size_t vs = cs.VShift(c); // We reuse filter_input_storage here as it is not currently in use. const Rect src_rect_precs = dec_state->shared->BlockGroupRect(group_idx); const Rect src_rect = Rect(src_rect_precs.x0() >> hs, src_rect_precs.y0() >> vs, src_rect_precs.xsize() >> hs, src_rect_precs.ysize() >> vs); - const Rect copy_rect(kBlockDim, 2, src_rect.xsize(), src_rect.ysize()); + const Rect copy_rect(kRenderPipelineXOffset, 2, src_rect.xsize(), + src_rect.ysize()); CopyImageToWithPadding(src_rect, dec_state->shared->dc->Plane(c), 2, - copy_rect, - &dec_state->filter_input_storage[thread].Plane(c)); - EnsurePaddingInPlace( - &dec_state->filter_input_storage[thread].Plane(c), copy_rect, - src_rect, DivCeil(dec_state->shared->frame_dim.xsize_blocks, 1 << hs), - DivCeil(dec_state->shared->frame_dim.ysize_blocks, 1 << vs), 2, 2); - ImageF* upsampling_dst = &dec_state->decoded.Plane(c); - Rect dst_rect(src_rect.x0() * 8, src_rect.y0() * 8, src_rect.xsize() * 8, - src_rect.ysize() * 8); - if (dec_state->EagerFinalizeImageRect()) { - upsampling_dst = &dec_state->group_data[thread].Plane(c); - dst_rect = Rect(PassesDecoderState::kGroupDataXBorder, - PassesDecoderState::kGroupDataYBorder, dst_rect.xsize(), - dst_rect.ysize()); + copy_rect, &group_dec_cache->dc_buffer); + // Mirrorpad. Interleaving left and right padding ensures that padding + // works out correctly even for images with DC size of 1. + for (size_t y = 0; y < src_rect.ysize() + 4; y++) { + size_t xend = kRenderPipelineXOffset + + (dec_state->shared->dc->Plane(c).xsize() >> hs) - + src_rect.x0(); + for (size_t ix = 0; ix < 2; ix++) { + if (src_rect.x0() == 0) { + group_dec_cache->dc_buffer.Row(y)[kRenderPipelineXOffset - ix - 1] = + group_dec_cache->dc_buffer.Row(y)[kRenderPipelineXOffset + ix]; + } + if (src_rect.x0() + src_rect.xsize() + 2 >= + (dec_state->shared->dc->xsize() >> hs)) { + group_dec_cache->dc_buffer.Row(y)[xend + ix] = + group_dec_cache->dc_buffer.Row(y)[xend - ix - 1]; + } + } } - if (render_pipeline_input) { - dst_rect = render_pipeline_input->GetBuffer(c).second; - upsampling_dst = render_pipeline_input->GetBuffer(c).first; - } + Rect dst_rect = render_pipeline_input.GetBuffer(c).second; + ImageF* upsampling_dst = render_pipeline_input.GetBuffer(c).first; JXL_ASSERT(dst_rect.IsInside(*upsampling_dst)); - dec_state->upsamplers[2].UpsampleRect( - dec_state->filter_input_storage[thread].Plane(c), copy_rect, - upsampling_dst, dst_rect, - static_cast(src_rect.y0()) - - static_cast(copy_rect.y0()), - dec_state->shared->frame_dim.ysize_blocks >> vs, - dec_state->upsampler_storage[thread].get()); + + RenderPipelineStage::RowInfo input_rows(1, std::vector(5)); + RenderPipelineStage::RowInfo output_rows(1, std::vector(8)); + for (size_t y = src_rect.y0(); y < src_rect.y0() + src_rect.ysize(); + y++) { + for (ssize_t iy = 0; iy < 5; iy++) { + input_rows[0][iy] = group_dec_cache->dc_buffer.Row( + Mirror(ssize_t(y) + iy - 2, + dec_state->shared->dc->Plane(c).ysize() >> vs) + + 2 - src_rect.y0()); + } + for (size_t iy = 0; iy < 8; iy++) { + output_rows[0][iy] = + dst_rect.Row(upsampling_dst, ((y - src_rect.y0()) << 3) + iy) - + 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); + } } - draw = kOnlyImageFeatures; + return true; } size_t histo_selector_bits = 0; if (dc_only) { JXL_ASSERT(num_passes == 0); @@ -782,22 +774,24 @@ Status DecodeGroupForRoundtrip(const std::vector>& ac, size_t group_idx, PassesDecoderState* JXL_RESTRICT dec_state, GroupDecCache* JXL_RESTRICT group_dec_cache, - size_t thread, ImageBundle* JXL_RESTRICT decoded, + size_t thread, + RenderPipelineInput& render_pipeline_input, + ImageBundle* JXL_RESTRICT decoded, AuxOut* aux_out) { PROFILER_FUNC; GetBlockFromEncoder get_block(ac, group_idx, dec_state->shared->frame_header.passes.shift); group_dec_cache->InitOnce( /*num_passes=*/0, /*used_acs=*/(1u << AcStrategy::kNumValidStrategies) - 1); - return HWY_DYNAMIC_DISPATCH(DecodeGroupImpl)(&get_block, group_dec_cache, - dec_state, thread, group_idx, - nullptr, decoded, kDraw); + return HWY_DYNAMIC_DISPATCH(DecodeGroupImpl)( + &get_block, group_dec_cache, dec_state, thread, group_idx, + render_pipeline_input, decoded, kDraw); } } // namespace jxl #endif // HWY_ONCE diff --git a/third_party/jpeg-xl/lib/jxl/dec_group_border.h b/third_party/jpeg-xl/lib/jxl/dec_group_border.h --- a/third_party/jpeg-xl/lib/jxl/dec_group_border.h +++ b/third_party/jpeg-xl/lib/jxl/dec_group_border.h @@ -22,30 +22,17 @@ // Prepare the GroupBorderAssigner to handle a given frame. void Init(const FrameDimensions& frame_dim); // Marks a group as done, and returns the (at most 3) rects to run // FinalizeImageRect on. `block_rect` must be the rect corresponding // to the given `group_id`, measured in blocks. - void GroupDone(size_t group_id, size_t padding, Rect* rects_to_finalize, - size_t* num_to_finalize); + void GroupDone(size_t group_id, size_t padx, size_t pady, + Rect* rects_to_finalize, size_t* num_to_finalize); // Marks a group as not-done, for running re-paints. void ClearDone(size_t group_id); static constexpr size_t kMaxToFinalize = 3; - // Vectors on ARM NEON are never wider than 4 floats, so rounding to multiples - // of 4 is enough. -#if defined(__ARM_NEON) || defined(__ARM_NEON__) - static constexpr size_t kPaddingXRound = 4; -#else - static constexpr size_t kPaddingXRound = kBlockDim; -#endif - - // Returns the necessary amount of padding for the X axis. - static size_t PaddingX(size_t padding) { - return RoundUpTo(padding, kPaddingXRound); - } - private: FrameDimensions frame_dim_; std::unique_ptr[]> counters_; // Constants to identify group positions relative to the corners. diff --git a/third_party/jpeg-xl/lib/jxl/dec_group_border.cc b/third_party/jpeg-xl/lib/jxl/dec_group_border.cc --- a/third_party/jpeg-xl/lib/jxl/dec_group_border.cc +++ b/third_party/jpeg-xl/lib/jxl/dec_group_border.cc @@ -56,11 +56,11 @@ // gets the responsibility of handling the corner. For borders, every border // is assigned to its top corner (for vertical borders) or to its left corner // (for horizontal borders): the order as seen on those corners will decide who // handles that border. -void GroupBorderAssigner::GroupDone(size_t group_id, size_t padding, +void GroupBorderAssigner::GroupDone(size_t group_id, size_t padx, size_t pady, Rect* rects_to_finalize, size_t* num_to_finalize) { size_t x = group_id % frame_dim_.xsize_groups; size_t y = group_id / frame_dim_.xsize_groups; Rect block_rect(x * frame_dim_.group_dim / kBlockDim, @@ -85,35 +85,32 @@ size_t top_left_status = fetch_status(top_left_idx, kBottomRight); size_t top_right_status = fetch_status(top_right_idx, kBottomLeft); size_t bottom_right_status = fetch_status(bottom_right_idx, kTopLeft); size_t bottom_left_status = fetch_status(bottom_left_idx, kTopRight); - size_t padx = PaddingX(padding); - size_t pady = padding; - size_t x1 = block_rect.x0() + block_rect.xsize(); size_t y1 = block_rect.y0() + block_rect.ysize(); bool is_last_group_x = frame_dim_.xsize_groups == x + 1; bool is_last_group_y = frame_dim_.ysize_groups == y + 1; // Start of border of neighbouring group, end of border of this group, start // of border of this group (on the other side), end of border of next group. size_t xpos[4] = { block_rect.x0() == 0 ? 0 : block_rect.x0() * kBlockDim - padx, - block_rect.x0() == 0 ? 0 - : std::min(frame_dim_.xsize_padded, - block_rect.x0() * kBlockDim + padx), - is_last_group_x ? frame_dim_.xsize_padded : x1 * kBlockDim - padx, - std::min(frame_dim_.xsize_padded, x1 * kBlockDim + padx)}; + block_rect.x0() == 0 + ? 0 + : std::min(frame_dim_.xsize, block_rect.x0() * kBlockDim + padx), + is_last_group_x ? frame_dim_.xsize : x1 * kBlockDim - padx, + std::min(frame_dim_.xsize, x1 * kBlockDim + padx)}; size_t ypos[4] = { block_rect.y0() == 0 ? 0 : block_rect.y0() * kBlockDim - pady, - block_rect.y0() == 0 ? 0 - : std::min(frame_dim_.ysize_padded, - block_rect.y0() * kBlockDim + pady), - is_last_group_y ? frame_dim_.ysize_padded : y1 * kBlockDim - pady, - std::min(frame_dim_.ysize_padded, y1 * kBlockDim + pady)}; + block_rect.y0() == 0 + ? 0 + : std::min(frame_dim_.ysize, block_rect.y0() * kBlockDim + pady), + is_last_group_y ? frame_dim_.ysize : y1 * kBlockDim - pady, + std::min(frame_dim_.ysize, y1 * kBlockDim + pady)}; *num_to_finalize = 0; auto append_rect = [&](size_t x0, size_t x1, size_t y0, size_t y1) { Rect rect(xpos[x0], ypos[y0], xpos[x1] - xpos[x0], ypos[y1] - ypos[y0]); if (rect.xsize() == 0 || rect.ysize() == 0) return; diff --git a/third_party/jpeg-xl/lib/jxl/dec_modular.h b/third_party/jpeg-xl/lib/jxl/dec_modular.h --- a/third_party/jpeg-xl/lib/jxl/dec_modular.h +++ b/third_party/jpeg-xl/lib/jxl/dec_modular.h @@ -117,12 +117,11 @@ bool UsesFullImage() const { return use_full_image; } private: Status ModularImageToDecodedRect(Image& gi, PassesDecoderState* dec_state, jxl::ThreadPool* pool, - RenderPipelineInput* render_pipeline_input, - ImageBundle* output, Rect rect, + RenderPipelineInput& render_pipeline_input, Rect modular_rect); Image full_image; std::vector global_transform; FrameDimensions frame_dim; 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 @@ -301,16 +301,17 @@ if (!allow_truncated) JXL_RETURN_IF_ERROR(status); if (status.IsFatalError()) return status; } // Undo global transforms that have been pushed to the group level if (!use_full_image) { + JXL_ASSERT(render_pipeline_input); for (auto t : global_transform) { JXL_RETURN_IF_ERROR(t.Inverse(gi, global_header.wp_header)); } - JXL_RETURN_IF_ERROR(ModularImageToDecodedRect( - gi, dec_state, nullptr, render_pipeline_input, output, - rect.Crop(dec_state->decoded), Rect(0, 0, gi.w, gi.h))); + JXL_RETURN_IF_ERROR(ModularImageToDecodedRect(gi, dec_state, nullptr, + *render_pipeline_input, + Rect(0, 0, gi.w, gi.h))); return true; } int gic = 0; for (c = beginc; c < full_image.channel.size(); c++) { Channel& fc = full_image.channel[c]; @@ -449,30 +450,18 @@ return true; } Status ModularFrameDecoder::ModularImageToDecodedRect( Image& gi, PassesDecoderState* dec_state, jxl::ThreadPool* pool, - RenderPipelineInput* render_pipeline_input, ImageBundle* output, Rect rect, - Rect modular_rect) { - auto& decoded = dec_state->decoded; + RenderPipelineInput& render_pipeline_input, Rect modular_rect) { const auto& frame_header = dec_state->shared->frame_header; const auto* metadata = frame_header.nonserialized_metadata; - if (!render_pipeline_input) { - size_t xsize = rect.xsize(); - size_t ysize = rect.ysize(); - if (!xsize || !ysize) { - return true; - } - JXL_DASSERT(rect.IsInside(decoded)); - } JXL_CHECK(gi.transform.empty()); auto get_row = [&](Rect r, size_t c, size_t y) { - return render_pipeline_input - ? render_pipeline_input->GetBuffer(c).second.Row( - render_pipeline_input->GetBuffer(c).first, y) - : r.PlaneRow(&decoded, c, y); + return render_pipeline_input.GetBuffer(c).second.Row( + render_pipeline_input.GetBuffer(c).first, y); }; size_t c = 0; if (do_color) { const bool rgb_from_gray = @@ -497,22 +486,16 @@ // TODO(eustas): could we detect it on earlier stage? if (ch_in.w == 0 || ch_in.h == 0) { return JXL_FAILURE("Empty image"); } JXL_CHECK(ch_in.hshift <= 3 && ch_in.vshift <= 3); - Rect r(rect.x0() >> ch_in.hshift, rect.y0() >> ch_in.vshift, - rect.xsize() >> ch_in.hshift, rect.ysize() >> ch_in.vshift, - DivCeil(decoded.xsize(), 1 << ch_in.hshift), - DivCeil(decoded.ysize(), 1 << ch_in.vshift)); + Rect r = render_pipeline_input.GetBuffer(c).second; Rect mr(modular_rect.x0() >> ch_in.hshift, modular_rect.y0() >> ch_in.vshift, DivCeil(modular_rect.xsize(), 1 << ch_in.hshift), DivCeil(modular_rect.ysize(), 1 << ch_in.vshift)); mr = mr.Crop(ch_in.plane); - if (render_pipeline_input) { - r = render_pipeline_input->GetBuffer(c).second; - } size_t xsize_shifted = r.xsize(); size_t ysize_shifted = r.ysize(); if (r.ysize() != mr.ysize() || r.xsize() != mr.xsize()) { return JXL_FAILURE("Dimension mismatch: trying to fit a %" PRIuS "x%" PRIuS @@ -542,12 +525,19 @@ pool, 0, ysize_shifted, ThreadPool::NoInit, [&](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); - float* const JXL_RESTRICT row_out = get_row(r, c, y); - int_to_float(row_in, row_out, xsize_shifted, bits, exp_bits); + if (rgb_from_gray) { + for (size_t cc = 0; cc < 3; cc++) { + float* const JXL_RESTRICT row_out = get_row(r, 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); + int_to_float(row_in, row_out, xsize_shifted, bits, exp_bits); + } }, "ModularIntToFloat_losslessfloat")); } else { JXL_RETURN_IF_ERROR(RunOnPool( pool, 0, ysize_shifted, ThreadPool::NoInit, @@ -573,63 +563,39 @@ } if (rgb_from_gray) { c = 1; } } - size_t num_extra_channels = render_pipeline_input - ? output->extra_channels().size() - : dec_state->extra_channels.size(); + size_t num_extra_channels = metadata->m.num_extra_channels; for (size_t ec = 0; ec < num_extra_channels; ec++, c++) { - const ExtraChannelInfo& eci = output->metadata()->extra_channel_info[ec]; + 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)); - size_t ecups = frame_header.extra_channel_upsampling[ec]; - const size_t ec_xsize = DivCeil(frame_dim.xsize_upsampled, ecups); - const size_t ec_ysize = DivCeil(frame_dim.ysize_upsampled, ecups); JXL_ASSERT(c < gi.channel.size()); Channel& ch_in = gi.channel[c]; - if (!render_pipeline_input) { - // For x0, y0 there's no need to do a DivCeil(). - JXL_DASSERT(rect.x0() % (1ul << ch_in.hshift) == 0); - JXL_DASSERT(rect.y0() % (1ul << ch_in.vshift) == 0); - } - Rect r(rect.x0() >> ch_in.hshift, rect.y0() >> ch_in.vshift, - DivCeil(rect.xsize(), 1lu << ch_in.hshift), - DivCeil(rect.ysize(), 1lu << ch_in.vshift), ec_xsize, ec_ysize); - - if (render_pipeline_input) { - r = render_pipeline_input->GetBuffer(3 + ec).second; - } + Rect r = render_pipeline_input.GetBuffer(3 + ec).second; Rect mr(modular_rect.x0() >> ch_in.hshift, modular_rect.y0() >> ch_in.vshift, DivCeil(modular_rect.xsize(), 1 << ch_in.hshift), DivCeil(modular_rect.ysize(), 1 << ch_in.vshift)); mr = mr.Crop(ch_in.plane); - if (!render_pipeline_input) { - JXL_DASSERT(r.IsInside(dec_state->extra_channels[ec])); - } for (size_t y = 0; y < r.ysize(); ++y) { float* const JXL_RESTRICT row_out = - render_pipeline_input - ? r.Row(render_pipeline_input->GetBuffer(3 + ec).first, y) - : r.Row(&dec_state->extra_channels[ec], y); + 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 (!render_pipeline_input) { - JXL_CHECK_IMAGE_INITIALIZED(dec_state->extra_channels[ec], r); - } } return true; } Status ModularFrameDecoder::FinalizeDecoding(PassesDecoderState* dec_state, @@ -649,39 +615,33 @@ for (auto t : global_transform) { JXL_RETURN_IF_ERROR(t.Inverse(gi, global_header.wp_header)); } if (gi.error) return JXL_FAILURE("Undoing transforms failed"); - auto& decoded = dec_state->decoded; - - if (dec_state->render_pipeline) { - 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(num_threads); - 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, output, - Rect(), - dec_state->shared->GroupRect(group))) { - has_error = true; - return; - } - input.Done(); - }, - "ModularToRect")); - if (has_error) { - return JXL_FAILURE("Error producing input to render pipeline"); - } - } else { - JXL_RETURN_IF_ERROR(ModularImageToDecodedRect(gi, dec_state, pool, nullptr, - output, Rect(decoded), - Rect(0, 0, gi.w, gi.h))); + 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( + 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, + dec_state->shared->GroupRect(group))) { + has_error = true; + return; + } + input.Done(); + }, + "ModularToRect")); + if (has_error) { + return JXL_FAILURE("Error producing input to render pipeline"); } return true; } static constexpr const float kAlmostZero = 1e-8f; 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 @@ -24,13 +24,16 @@ // 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 seed, const Rect& rect, Image3F* JXL_RESTRICT noise); +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 seed, const std::pair& plane0, +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); // Must only call if FrameHeader.flags.kNoise. Status DecodeNoise(BitReader* br, NoiseParams* noise_params); 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 @@ -125,13 +125,13 @@ 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), Set(D(), kScale - 1), + floor_x = IfThenElse(scaled_vx >= Set(D(), kScale + 1), Set(D(), kScale), floor_x); - frac_x = IfThenElse(scaled_vx >= Set(D(), kScale), Set(D(), 1), frac_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 @@ -249,21 +249,26 @@ msan::PoisonMemory(row_y + xsize, (xsize_v - xsize) * sizeof(float)); msan::PoisonMemory(row_b + xsize, (xsize_v - xsize) * sizeof(float)); } } -void RandomImage3(size_t seed, const Rect& rect, Image3F* JXL_RESTRICT noise) { - HWY_ALIGN Xorshift128Plus rng(seed); +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 seed, const std::pair& plane0, +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(seed); + HWY_ALIGN Xorshift128Plus rng(visible_frame_index, nonvisible_frame_index, x0, + y0); RandomImage(&rng, plane0.second, plane0.first); RandomImage(&rng, plane1.second, plane1.first); RandomImage(&rng, plane2.second, plane2.first); } @@ -282,19 +287,25 @@ return HWY_DYNAMIC_DISPATCH(AddNoise)(noise_params, noise_rect, noise, opsin_rect, cmap, opsin); } HWY_EXPORT(RandomImage3); -void RandomImage3(size_t seed, const Rect& rect, Image3F* JXL_RESTRICT noise) { - return HWY_DYNAMIC_DISPATCH(RandomImage3)(seed, rect, noise); +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 seed, const std::pair& plane0, +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) { - return HWY_DYNAMIC_DISPATCH(Random3Planes)(seed, plane0, plane1, plane2); + return HWY_DYNAMIC_DISPATCH(Random3Planes)(visible_frame_index, + nonvisible_frame_index, x0, y0, + plane0, plane1, plane2); } void DecodeFloatParam(float precision, float* val, BitReader* br) { const int absval_quant = br->ReadFixedBits<10>(); *val = absval_quant / precision; diff --git a/third_party/jpeg-xl/lib/jxl/dec_params.h b/third_party/jpeg-xl/lib/jxl/dec_params.h --- a/third_party/jpeg-xl/lib/jxl/dec_params.h +++ b/third_party/jpeg-xl/lib/jxl/dec_params.h @@ -27,13 +27,10 @@ // If true, render spot colors (otherwise only returned as extra channels) bool render_spotcolors = true; // If true, coalesce frames (otherwise return unblended frames) bool coalescing = true; - // These cannot be kOn because they need encoder support. - Override preview = Override::kDefault; - // How many passes to decode at most. By default, decode everything. uint32_t max_passes = std::numeric_limits::max(); // Alternatively, one can specify the maximum tolerable downscaling factor // with respect to the full size of the image. By default, nothing less than // the full size is requested. diff --git a/third_party/jpeg-xl/lib/jxl/dec_xyb.cc b/third_party/jpeg-xl/lib/jxl/dec_xyb.cc --- a/third_party/jpeg-xl/lib/jxl/dec_xyb.cc +++ b/third_party/jpeg-xl/lib/jxl/dec_xyb.cc @@ -110,11 +110,11 @@ // Transform YCbCr to RGB. // Could be performed in-place (i.e. Y, Cb and Cr could alias R, B and B). void YcbcrToRgb(const Image3F& ycbcr, Image3F* rgb, const Rect& rect) { JXL_CHECK_IMAGE_INITIALIZED(ycbcr, rect); - const HWY_CAPPED(float, GroupBorderAssigner::kPaddingXRound) df; + const HWY_CAPPED(float, kBlockDim) df; const size_t S = Lanes(df); // Step. const size_t xsize = rect.xsize(); const size_t ysize = rect.ysize(); if ((xsize == 0) || (ysize == 0)) return; 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 @@ -10,11 +10,10 @@ #include "lib/jxl/base/status.h" #include "lib/jxl/box_content_decoder.h" #include "lib/jxl/dec_external_image.h" #include "lib/jxl/dec_frame.h" #include "lib/jxl/dec_modular.h" -#include "lib/jxl/dec_reconstruct.h" #include "lib/jxl/decode_to_jpeg.h" #include "lib/jxl/fields.h" #include "lib/jxl/headers.h" #include "lib/jxl/icc_codec.h" #include "lib/jxl/image_bundle.h" @@ -1064,10 +1063,11 @@ ? jxl::Orientation::kIdentity : dec->metadata.m.GetOrientation(); jxl::Status status(true); if (want_extra_channel) { + 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=*/out_callback, @@ -1228,11 +1228,10 @@ jxl::Span compressed(in + dec->frame_start, size - dec->frame_start); auto reader = GetBitReader(compressed); jxl::DecompressParams dparams; - dparams.preview = want_preview ? jxl::Override::kOn : jxl::Override::kOff; dparams.render_spotcolors = dec->render_spotcolors; dparams.coalescing = true; jxl::ImageBundle ib(&dec->metadata.m); PassesDecoderState preview_dec_state; JXL_API_RETURN_IF_ERROR(preview_dec_state.output_encoding_info.Set( @@ -1563,18 +1562,24 @@ dec->image_out_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, + /*want_extra_channel=*/true, /*extra_channel_index=*/i, buffer, dec->extra_channel_output[i].buffer_size, nullptr, nullptr); if (status != JXL_DEC_SUCCESS) return status; } dec->extra_channel_output.clear(); @@ -1757,28 +1762,26 @@ } } if (dec->recon_output_jpeg == JpegReconStage::kSettingMetadata && !dec->JbrdNeedMoreBoxes()) { - using namespace jxl; - jpeg::JPEGData* jpeg_data = dec->ib->jpeg_data.get(); + jxl::jpeg::JPEGData* jpeg_data = dec->ib->jpeg_data.get(); if (dec->recon_exif_size) { - JxlDecoderStatus status = JxlToJpegDecoder::SetExif( + JxlDecoderStatus status = jxl::JxlToJpegDecoder::SetExif( dec->exif_metadata.data(), dec->exif_metadata.size(), jpeg_data); if (status != JXL_DEC_SUCCESS) return status; } if (dec->recon_xmp_size) { - JxlDecoderStatus status = JxlToJpegDecoder::SetXmp( + JxlDecoderStatus status = jxl::JxlToJpegDecoder::SetXmp( dec->xmp_metadata.data(), dec->xmp_metadata.size(), jpeg_data); if (status != JXL_DEC_SUCCESS) return status; } dec->recon_output_jpeg = JpegReconStage::kOutputting; } if (dec->recon_output_jpeg == JpegReconStage::kOutputting && !dec->JbrdNeedMoreBoxes()) { - using namespace jxl; JxlDecoderStatus status = dec->jpeg_decoder.WriteOutput(*dec->ib->jpeg_data); if (status != JXL_DEC_SUCCESS) return status; dec->recon_output_jpeg = JpegReconStage::kFinished; dec->ib.reset(); 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 @@ -195,11 +195,11 @@ } // Make the grayscale-ness of the io metadata color_encoding and the packed // image match. io.metadata.m.color_encoding = color_encoding; EXPECT_TRUE(ConvertFromExternal( - pixels, xsize, ysize, color_encoding, /*has_alpha=*/include_alpha, + pixels, xsize, ysize, color_encoding, num_channels, /*alpha_is_premultiplied=*/false, /*bits_per_sample=*/16, JXL_BIG_ENDIAN, /*flipped_y=*/false, &pool, &io.Main(), /*float_in=*/false, /*align=*/0)); jxl::PaddedBytes jpeg_data; if (jpeg_codestream != nullptr) { #if JPEGXL_ENABLE_JPEG @@ -365,12 +365,13 @@ if (use_resizable_runner) { runner_resizable = JxlResizableParallelRunnerMake(nullptr); runner = runner_resizable.get(); runner_fn = JxlResizableParallelRunner; } else { - runner_fixed = JxlThreadParallelRunnerMake( - nullptr, JxlThreadParallelRunnerDefaultNumWorkerThreads()); + size_t hw_threads = JxlThreadParallelRunnerDefaultNumWorkerThreads(); + runner_fixed = + JxlThreadParallelRunnerMake(nullptr, std::min(hw_threads, 16)); runner = runner_fixed.get(); runner_fn = JxlThreadParallelRunner; } EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderSetParallelRunner(dec, runner_fn, runner)); @@ -1220,11 +1221,12 @@ jxl::CodecInOut io; if (config.include_alpha) io.metadata.m.SetAlphaBits(16); io.SetSize(config.xsize, config.ysize); EXPECT_TRUE(ConvertFromExternal( - bytes, config.xsize, config.ysize, color_encoding, config.include_alpha, + bytes, config.xsize, config.ysize, color_encoding, + config.output_channels, /*alpha_is_premultiplied=*/false, 16, JXL_BIG_ENDIAN, /*flipped_y=*/false, nullptr, &io.Main(), /*float_in=*/false, /*align=*/0)); for (size_t i = 0; i < pixels.size(); i++) pixels[i] = 0; @@ -1540,23 +1542,24 @@ EXPECT_TRUE(color_encoding0.SetICC(GetIccTestProfile())); jxl::Span span0(pixels.data(), pixels.size()); jxl::CodecInOut io0; io0.SetSize(xsize, ysize); EXPECT_TRUE(ConvertFromExternal( - span0, xsize, ysize, color_encoding0, - /*has_alpha=*/false, false, 16, format_orig.endianness, + span0, xsize, ysize, color_encoding0, /*channels=*/3, + /*alpha_is_premultiplied=*/false, /*bits_per_sample=*/16, + format_orig.endianness, /*flipped_y=*/false, /*pool=*/nullptr, &io0.Main(), /*float_in=*/false, /*align=*/0)); jxl::ColorEncoding color_encoding1; EXPECT_TRUE(color_encoding1.SetICC(std::move(icc))); jxl::Span span1(pixels2.data(), pixels2.size()); jxl::CodecInOut io1; io1.SetSize(xsize, ysize); EXPECT_TRUE(ConvertFromExternal(span1, xsize, ysize, color_encoding1, - /*has_alpha=*/false, false, 32, - format.endianness, + channels, /*alpha_is_premultiplied=*/false, + /*bits_per_sample=*/32, format.endianness, /*flipped_y=*/false, /*pool=*/nullptr, &io1.Main(), /*float_in=*/true, /*align=*/0)); jxl::ButteraugliParams ba; EXPECT_THAT(ButteraugliDistance(io0, io1, ba, jxl::GetJxlCms(), @@ -1598,35 +1601,25 @@ jxl::ColorEncoding color_encoding0 = jxl::ColorEncoding::SRGB(false); jxl::Span span0(pixels.data(), pixels.size()); jxl::CodecInOut io0; io0.SetSize(xsize, ysize); EXPECT_TRUE(ConvertFromExternal( - span0, xsize, ysize, color_encoding0, - /*has_alpha=*/false, false, 16, format_orig.endianness, + span0, xsize, ysize, color_encoding0, /*channels=*/3, + /*alpha_is_premultiplied=*/false, /*bits_per_sample=*/16, + format_orig.endianness, /*flipped_y=*/false, /*pool=*/nullptr, &io0.Main(), /*float_in=*/false, /*align=*/0)); jxl::ColorEncoding color_encoding1 = jxl::ColorEncoding::SRGB(false); jxl::Span span1(pixels2.data(), pixels2.size()); jxl::CodecInOut io1; - if (channels == 4) { - io1.metadata.m.SetAlphaBits(8); - io1.SetSize(xsize, ysize); - EXPECT_TRUE( - ConvertFromExternal(span1, xsize, ysize, color_encoding1, - /*has_alpha=*/true, false, 8, format.endianness, - /*flipped_y=*/false, /*pool=*/nullptr, - &io1.Main(), /*float_in=*/false, /*align=*/0)); - io1.metadata.m.SetAlphaBits(0); - io1.Main().ClearExtraChannels(); - } else { - EXPECT_TRUE( - ConvertFromExternal(span1, xsize, ysize, color_encoding1, - /*has_alpha=*/false, false, 8, format.endianness, - /*flipped_y=*/false, /*pool=*/nullptr, - &io1.Main(), /*float_in=*/false, /*align=*/0)); - } + EXPECT_TRUE(ConvertFromExternal(span1, xsize, ysize, color_encoding1, + channels, /*alpha_is_premultiplied=*/false, + /*bits_per_sample=*/8, format.endianness, + /*flipped_y=*/false, /*pool=*/nullptr, + &io1.Main(), /*float_in=*/false, + /*align=*/0)); jxl::ButteraugliParams ba; EXPECT_THAT(ButteraugliDistance(io0, io1, ba, jxl::GetJxlCms(), /*distmap=*/nullptr, nullptr), IsSlightlyBelow(0.8f)); @@ -1667,35 +1660,25 @@ jxl::ColorEncoding color_encoding0 = jxl::ColorEncoding::SRGB(false); jxl::Span span0(pixels.data(), pixels.size()); jxl::CodecInOut io0; io0.SetSize(xsize, ysize); EXPECT_TRUE(ConvertFromExternal( - span0, xsize, ysize, color_encoding0, - /*has_alpha=*/false, false, 16, format_orig.endianness, + span0, xsize, ysize, color_encoding0, /*channels=*/3, + /*alpha_is_premultiplied=*/false, /*bits_per_sample=*/16, + format_orig.endianness, /*flipped_y=*/false, /*pool=*/nullptr, &io0.Main(), /*float_in=*/false, /*align=*/0)); jxl::ColorEncoding color_encoding1 = jxl::ColorEncoding::SRGB(false); jxl::Span span1(pixels2.data(), pixels2.size()); jxl::CodecInOut io1; - if (channels == 4) { - io1.metadata.m.SetAlphaBits(8); - io1.SetSize(xsize, ysize); - EXPECT_TRUE( - ConvertFromExternal(span1, xsize, ysize, color_encoding1, - /*has_alpha=*/true, false, 8, format.endianness, - /*flipped_y=*/false, /*pool=*/nullptr, - &io1.Main(), /*float_in=*/false, /*align=*/0)); - io1.metadata.m.SetAlphaBits(0); - io1.Main().ClearExtraChannels(); - } else { - EXPECT_TRUE( - ConvertFromExternal(span1, xsize, ysize, color_encoding1, - /*has_alpha=*/false, false, 8, format.endianness, - /*flipped_y=*/false, /*pool=*/nullptr, - &io1.Main(), /*float_in=*/false, /*align=*/0)); - } + EXPECT_TRUE(ConvertFromExternal(span1, xsize, ysize, color_encoding1, + channels, /*alpha_is_premultiplied=*/false, + /*bits_per_sample=*/8, format.endianness, + /*flipped_y=*/false, /*pool=*/nullptr, + &io1.Main(), /*float_in=*/false, + /*align=*/0)); jxl::ButteraugliParams ba; EXPECT_THAT(ButteraugliDistance(io0, io1, ba, jxl::GetJxlCms(), /*distmap=*/nullptr, nullptr), IsSlightlyBelow(2.6f)); @@ -2031,11 +2014,11 @@ for (size_t i = 0; i < num_frames; ++i) { jxl::ImageBundle bundle(&io.metadata.m); EXPECT_TRUE(ConvertFromExternal( jxl::Span(frames[i].data(), frames[i].size()), xsize, - ysize, jxl::ColorEncoding::SRGB(/*is_gray=*/false), /*has_alpha=*/false, + ysize, jxl::ColorEncoding::SRGB(/*is_gray=*/false), /*channels=*/3, /*alpha_is_premultiplied=*/false, /*bits_per_sample=*/16, JXL_BIG_ENDIAN, /*flipped_y=*/false, /*pool=*/nullptr, &bundle, /*float_in=*/false, /*align=*/0)); bundle.duration = frame_durations[i]; io.frames.push_back(std::move(bundle)); @@ -2135,11 +2118,11 @@ for (size_t i = 0; i < num_frames; ++i) { jxl::ImageBundle bundle(&io.metadata.m); EXPECT_TRUE(ConvertFromExternal( jxl::Span(frames[i].data(), frames[i].size()), xsize, - ysize, jxl::ColorEncoding::SRGB(/*is_gray=*/false), /*has_alpha=*/false, + ysize, jxl::ColorEncoding::SRGB(/*is_gray=*/false), /*channels=*/3, /*alpha_is_premultiplied=*/false, /*bits_per_sample=*/16, JXL_BIG_ENDIAN, /*flipped_y=*/false, /*pool=*/nullptr, &bundle, /*float_in=*/false, /*align=*/0)); bundle.duration = frame_durations[i]; io.frames.push_back(std::move(bundle)); @@ -2287,17 +2270,10 @@ EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderExtraChannelBufferSize(dec, &format, &extra_size, 0)); std::vector image(buffer_size); std::vector extra(extra_size); - size_t bytes_per_pixel = format.num_channels * - jxl::test::GetDataBits(format.data_type) / - jxl::kBitsPerByte; - size_t stride = bytes_per_pixel * info.xsize; - if (format.align > 1) { - stride = jxl::DivCeil(stride, format.align) * format.align; - } EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderSetImageOutBuffer( dec, &format, image.data(), image.size())); EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderSetExtraChannelBuffer( dec, &format, extra.data(), extra.size(), 0)); @@ -2360,11 +2336,11 @@ bundle.use_for_next_frame = true; } EXPECT_TRUE(ConvertFromExternal( jxl::Span(frames[i].data(), frames[i].size()), xsize, - ysize, jxl::ColorEncoding::SRGB(/*is_gray=*/false), /*has_alpha=*/false, + ysize, jxl::ColorEncoding::SRGB(/*is_gray=*/false), /*channels=*/3, /*alpha_is_premultiplied=*/false, /*bits_per_sample=*/16, JXL_BIG_ENDIAN, /*flipped_y=*/false, /*pool=*/nullptr, &bundle, /*float_in=*/false, /*align=*/0)); bundle.duration = frame_durations[i]; io.frames.push_back(std::move(bundle)); @@ -2497,11 +2473,11 @@ jxl::ImageBundle bundle_internal(&io.metadata.m); EXPECT_TRUE(ConvertFromExternal( jxl::Span(frame_internal.data(), frame_internal.size()), xsize, ysize, jxl::ColorEncoding::SRGB(/*is_gray=*/false), - /*has_alpha=*/false, + /*channels=*/3, /*alpha_is_premultiplied=*/false, /*bits_per_sample=*/16, JXL_BIG_ENDIAN, /*flipped_y=*/false, /*pool=*/nullptr, &bundle_internal, /*float_in=*/false, /*align=*/0)); bundle_internal.duration = 0; bundle_internal.use_for_next_frame = true; @@ -2513,11 +2489,11 @@ // Actual rendered frame frame_durations[i] = 5 + i; jxl::ImageBundle bundle(&io.metadata.m); EXPECT_TRUE(ConvertFromExternal( jxl::Span(frame.data(), frame.size()), xsize, ysize, - jxl::ColorEncoding::SRGB(/*is_gray=*/false), /*has_alpha=*/false, + jxl::ColorEncoding::SRGB(/*is_gray=*/false), /*channels=*/3, /*alpha_is_premultiplied=*/false, /*bits_per_sample=*/16, JXL_BIG_ENDIAN, /*flipped_y=*/false, /*pool=*/nullptr, &bundle, /*float_in=*/false, /*align=*/0)); bundle.duration = frame_durations[i]; // Create some variation in which frames depend on which. @@ -2724,11 +2700,11 @@ jxl::ImageBundle bundle_internal(&io.metadata.m); EXPECT_TRUE(ConvertFromExternal( jxl::Span(frame_internal.data(), frame_internal.size()), xsize / 2, ysize / 2, jxl::ColorEncoding::SRGB(/*is_gray=*/false), - /*has_alpha=*/true, + /*channels=*/4, /*alpha_is_premultiplied=*/false, /*bits_per_sample=*/16, JXL_BIG_ENDIAN, /*flipped_y=*/false, /*pool=*/nullptr, &bundle_internal, /*float_in=*/false, /*align=*/0)); bundle_internal.duration = 0; bundle_internal.use_for_next_frame = true; @@ -2745,12 +2721,11 @@ jxl::test::GetSomeTestImage(cropxsize, cropysize, 4, i * 2); // Actual rendered frame jxl::ImageBundle bundle(&io.metadata.m); EXPECT_TRUE(ConvertFromExternal( jxl::Span(frame.data(), frame.size()), cropxsize, - cropysize, jxl::ColorEncoding::SRGB(/*is_gray=*/false), - /*has_alpha=*/true, + cropysize, jxl::ColorEncoding::SRGB(/*is_gray=*/false), /*channels=*/4, /*alpha_is_premultiplied=*/false, /*bits_per_sample=*/16, JXL_BIG_ENDIAN, /*flipped_y=*/false, /*pool=*/nullptr, &bundle, /*float_in=*/false, /*align=*/0)); bundle.duration = 5 + i; frame_durations_nc.push_back(5 + i); @@ -2798,14 +2773,15 @@ for (size_t i = 0; i < num_frames + (coalescing ? 0 : 5); ++i) { EXPECT_EQ(JXL_DEC_NEED_IMAGE_OUT_BUFFER, JxlDecoderProcessInput(dec)); size_t buffer_size; EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderImageOutBufferSize(dec, &format, &buffer_size)); - if (coalescing) + if (coalescing) { EXPECT_EQ(xsize * ysize * 8, buffer_size); - else + } else { EXPECT_EQ(frame_xsize[i] * frame_ysize[i] * 8, buffer_size); + } frames[i].resize(buffer_size); EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderSetImageOutBuffer(dec, &format, frames[i].data(), frames[i].size())); EXPECT_EQ(JXL_DEC_FULL_IMAGE, JxlDecoderProcessInput(dec)); @@ -2856,18 +2832,20 @@ EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderSetImageOutBuffer(dec, &format, pixels.data(), pixels.size())); EXPECT_EQ(JXL_DEC_FULL_IMAGE, JxlDecoderProcessInput(dec)); - if (coalescing) + if (coalescing) { EXPECT_EQ(frame_header.layer_info.xsize, xsize); - else + } else { EXPECT_EQ(frame_header.layer_info.xsize, frame_xsize[i]); - if (coalescing) + } + if (coalescing) { EXPECT_EQ(frame_header.layer_info.ysize, ysize); - else + } else { EXPECT_EQ(frame_header.layer_info.ysize, frame_ysize[i]); + } EXPECT_EQ(0u, jxl::test::ComparePixels(frames[i].data(), pixels.data(), frame_header.layer_info.xsize, frame_header.layer_info.ysize, format, format)); @@ -3007,11 +2985,11 @@ jxl::test::GetSomeTestImage(cropxsize, cropysize, 4, i * 2); jxl::ImageBundle bundle(&io.metadata.m); EXPECT_TRUE(ConvertFromExternal( jxl::Span(frame.data(), frame.size()), cropxsize, cropysize, jxl::ColorEncoding::SRGB(/*is_gray=*/false), - /*has_alpha=*/true, + /*channels=*/4, /*alpha_is_premultiplied=*/false, /*bits_per_sample=*/16, JXL_BIG_ENDIAN, /*flipped_y=*/false, /*pool=*/nullptr, &bundle, /*float_in=*/false, /*align=*/0)); bundle.origin = {cropx0, cropy0}; bundle.use_for_next_frame = true; @@ -3822,11 +3800,10 @@ EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderGetBoxType(dec, type, JXL_FALSE)); if (BoxTypeEquals("brob", type)) { EXPECT_EQ(false, seen_brob_begin); seen_brob_begin = true; box_buffer.resize(8); - box_num_output = 0; JxlDecoderSetBoxBuffer(dec, box_buffer.data(), box_buffer.size()); } } else if (status == JXL_DEC_BOX_NEED_MORE_OUTPUT) { size_t remaining = JxlDecoderReleaseBoxBuffer(dec); box_num_output = box_buffer.size() - remaining; @@ -3911,11 +3888,10 @@ EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderGetBoxType(dec, type, JXL_TRUE)); if (BoxTypeEquals("Exif", type)) { EXPECT_EQ(false, seen_exif_begin); seen_exif_begin = true; box_buffer.resize(8); - box_num_output = 0; JxlDecoderSetBoxBuffer(dec, box_buffer.data(), box_buffer.size()); } } else if (status == JXL_DEC_BOX_NEED_MORE_OUTPUT) { size_t remaining = JxlDecoderReleaseBoxBuffer(dec); box_num_output = box_buffer.size() - remaining; @@ -3999,11 +3975,10 @@ JxlBoxType type; EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderGetBoxType(dec, type, JXL_FALSE)); if (BoxTypeEquals("jxlp", type)) { num_jxlp++; box_buffer.resize(8); - box_num_output = 0; JxlDecoderSetBoxBuffer(dec, box_buffer.data(), box_buffer.size()); } } else if (status == JXL_DEC_BOX_NEED_MORE_OUTPUT) { size_t remaining = JxlDecoderReleaseBoxBuffer(dec); box_num_output = box_buffer.size() - remaining; 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 @@ -31,11 +31,10 @@ #include "lib/jxl/color_management.h" #include "lib/jxl/common.h" #include "lib/jxl/convolve.h" #include "lib/jxl/dec_cache.h" #include "lib/jxl/dec_group.h" -#include "lib/jxl/dec_reconstruct.h" #include "lib/jxl/enc_butteraugli_comparator.h" #include "lib/jxl/enc_cache.h" #include "lib/jxl/enc_group.h" #include "lib/jxl/enc_modular.h" #include "lib/jxl/enc_params.h" @@ -727,21 +726,42 @@ return tile_distmap; } constexpr float kDcQuantPow = 0.57f; static const float kDcQuant = 1.12f; -static const float kAcQuant = 0.825f; +static const float kAcQuant = 0.8294f; void FindBestQuantization(const ImageBundle& linear, const Image3F& opsin, PassesEncoderState* enc_state, const JxlCmsInterface& cms, ThreadPool* pool, AuxOut* aux_out) { const CompressParams& cparams = enc_state->cparams; Quantizer& quantizer = enc_state->shared.quantizer; ImageI& raw_quant_field = enc_state->shared.raw_quant_field; ImageF& quant_field = enc_state->initial_quant_field; + // TODO(veluca): this should really be rather handled on the + // ButteraugliComparator side. + struct TemporaryShrink { + TemporaryShrink(ImageBundle& bundle, size_t xsize, size_t ysize) + : bundle(bundle), + orig_xsize(bundle.xsize()), + orig_ysize(bundle.ysize()) { + bundle.ShrinkTo(xsize, ysize); + } + TemporaryShrink(const TemporaryShrink&) = delete; + TemporaryShrink(TemporaryShrink&&) = delete; + + ~TemporaryShrink() { bundle.ShrinkTo(orig_xsize, orig_ysize); } + + ImageBundle& bundle; + size_t orig_xsize; + size_t orig_ysize; + } t(const_cast(linear), + enc_state->shared.frame_header.nonserialized_metadata->xsize(), + enc_state->shared.frame_header.nonserialized_metadata->ysize()); + const float butteraugli_target = cparams.butteraugli_distance; ButteraugliParams params = cparams.ba_params; params.intensity_target = linear.metadata()->IntensityTarget(); // Hack the default intensity target value to be 80.0, the intensity // target of sRGB images and a more reasonable viewing default than @@ -787,22 +807,22 @@ } printf("\n"); } } quantizer.SetQuantField(initial_quant_dc, quant_field, &raw_quant_field); - ImageBundle linear = RoundtripImage(opsin, enc_state, cms, pool); + ImageBundle dec_linear = RoundtripImage(opsin, enc_state, cms, pool); PROFILER_ZONE("enc Butteraugli"); float score; ImageF diffmap; - JXL_CHECK(comparator.CompareWith(linear, &diffmap, &score)); + JXL_CHECK(comparator.CompareWith(dec_linear, &diffmap, &score)); if (!lower_is_better) { score = -score; diffmap = ScaleImage(-1.0f, diffmap); } tile_distmap = TileDistMap(diffmap, 8, 0, enc_state->shared.ac_strategy); if (WantDebugOutput(aux_out)) { - aux_out->DumpImage(("dec" + ToString(i)).c_str(), *linear.color()); + aux_out->DumpImage(("dec" + ToString(i)).c_str(), *dec_linear.color()); DumpHeatmaps(aux_out, butteraugli_target, quant_field, tile_distmap, diffmap); } if (aux_out != nullptr) ++aux_out->num_butteraugli_iters; if (FLAGS_log_search_state) { @@ -1069,53 +1089,47 @@ ImageBundle decoded(&enc_state->shared.metadata->m); decoded.origin = enc_state->shared.frame_header.frame_origin; decoded.SetFromImage(Image3F(opsin.xsize(), opsin.ysize()), dec_state->output_encoding_info.color_encoding); + PassesDecoderState::PipelineOptions options; + options.use_slow_render_pipeline = false; + options.coalescing = true; + options.render_spotcolors = false; + // Same as dec_state->shared->frame_header.nonserialized_metadata->m const ImageMetadata& metadata = *decoded.metadata(); - if (!metadata.extra_channel_info.empty()) { - // Add dummy extra channels to the dec_state: FinalizeFrameDecoding moves - // these extra channels to the ImageBundle, and is required that the amount - // of extra channels matches its metadata()->extra_channel_info.size(). - // Normally we'd place these extra channels in the ImageBundle, but in this - // case FinalizeFrameDecoding is the one that does this. - std::vector extra_channels; - extra_channels.reserve(metadata.extra_channel_info.size()); - for (size_t i = 0; i < metadata.extra_channel_info.size(); i++) { - extra_channels.emplace_back(decoded.xsize(), decoded.ysize()); - // Must initialize the image with data to not affect blending with - // uninitialized memory. - ZeroFillImage(&extra_channels.back()); - } - dec_state->extra_channels = std::move(extra_channels); - } + + JXL_CHECK(dec_state->PreparePipeline(&decoded, options)); hwy::AlignedUniquePtr group_dec_caches; const auto allocate_storage = [&](const size_t num_threads) { - dec_state->EnsureStorage(num_threads); + 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) { if (dec_state->shared->frame_header.loop_filter.epf_iters > 0) { ComputeSigma(dec_state->shared->BlockGroupRect(group_index), dec_state.get()); } + RenderPipelineInput input = + dec_state->render_pipeline->GetInputBuffers(group_index, thread); JXL_CHECK(DecodeGroupForRoundtrip( enc_state->coeffs, group_index, dec_state.get(), - &group_dec_caches[thread], thread, &decoded, nullptr)); + &group_dec_caches[thread], thread, input, &decoded, nullptr)); + for (size_t c = 0; c < metadata.num_extra_channels; c++) { + std::pair ri = input.GetBuffer(3 + c); + FillPlane(0.0f, ri.first, ri.second); + } + input.Done(); }; JXL_CHECK(RunOnPool(pool, 0, num_groups, allocate_storage, process_group, "AQ loop")); - // Fine to do a JXL_ASSERT instead of error handling, since this only happens - // on the encoder side where we can't be fed with invalid data. - JXL_CHECK(FinalizeFrameDecoding(&decoded, dec_state.get(), pool, - /*force_fir=*/false, /*skip_blending=*/true, - /*move_ec=*/true)); // Ensure we don't create any new special frames. enc_state->special_frames.resize(num_special_frames); return decoded; } diff --git a/third_party/jpeg-xl/lib/jxl/enc_ans.cc b/third_party/jpeg-xl/lib/jxl/enc_ans.cc --- a/third_party/jpeg-xl/lib/jxl/enc_ans.cc +++ b/third_party/jpeg-xl/lib/jxl/enc_ans.cc @@ -554,10 +554,16 @@ std::vector* clustered_histograms, EntropyEncodingData* codes, size_t* log_alpha_size) { codes->uint_config.resize(clustered_histograms->size()); if (params.uint_method == HistogramParams::HybridUintMethod::kNone) return; + if (params.uint_method == HistogramParams::HybridUintMethod::k000) { + codes->uint_config.clear(); + codes->uint_config.resize(clustered_histograms->size(), + HybridUintConfig(0, 0, 0)); + return; + } if (params.uint_method == HistogramParams::HybridUintMethod::kContextMap) { codes->uint_config.clear(); codes->uint_config.resize(clustered_histograms->size(), HybridUintConfig(2, 0, 1)); return; @@ -1503,10 +1509,13 @@ HybridUintConfig uint_config; // Default config for clustering. // Unless we are using the kContextMap histogram option. if (params.uint_method == HistogramParams::HybridUintMethod::kContextMap) { uint_config = HybridUintConfig(2, 0, 1); } + if (params.uint_method == HistogramParams::HybridUintMethod::k000) { + uint_config = HybridUintConfig(0, 0, 0); + } if (ans_fuzzer_friendly_) { uint_config = HybridUintConfig(10, 0, 0); } for (size_t i = 0; i < tokens.size(); ++i) { if (codes->lz77.enabled) { diff --git a/third_party/jpeg-xl/lib/jxl/enc_ans_params.h b/third_party/jpeg-xl/lib/jxl/enc_ans_params.h --- a/third_party/jpeg-xl/lib/jxl/enc_ans_params.h +++ b/third_party/jpeg-xl/lib/jxl/enc_ans_params.h @@ -22,10 +22,11 @@ kBest, }; enum class HybridUintMethod { kNone, // just use kHybridUint420Config. + k000, // force the fastest option. kFast, // just try a couple of options. kContextMap, // fast choice for ctx map. kBest, }; diff --git a/third_party/jpeg-xl/lib/jxl/enc_bit_writer.cc b/third_party/jpeg-xl/lib/jxl/enc_bit_writer.cc --- a/third_party/jpeg-xl/lib/jxl/enc_bit_writer.cc +++ b/third_party/jpeg-xl/lib/jxl/enc_bit_writer.cc @@ -102,13 +102,14 @@ // Concatenate by copying bytes because both source and destination are bytes. JXL_ASSERT(BitsWritten() % kBitsPerByte == 0); size_t pos = BitsWritten() / kBitsPerByte; for (const BitWriter& writer : others) { const Span span = writer.GetSpan(); - if (span.size() != 0) + if (!span.empty()) { memcpy(storage_.data() + pos, span.data(), span.size()); - pos += span.size(); + pos += span.size(); + } } storage_[pos++] = 0; // for next Write JXL_ASSERT(pos <= storage_.size()); bits_written_ += other_bytes * kBitsPerByte; } diff --git a/third_party/jpeg-xl/lib/jxl/enc_color_management.cc b/third_party/jpeg-xl/lib/jxl/enc_color_management.cc --- a/third_party/jpeg-xl/lib/jxl/enc_color_management.cc +++ b/third_party/jpeg-xl/lib/jxl/enc_color_management.cc @@ -16,11 +16,10 @@ #include #include #include #include -#include #include #include #undef HWY_TARGET_INCLUDE #define HWY_TARGET_INCLUDE "lib/jxl/enc_color_management.cc" @@ -305,18 +304,10 @@ } // Define to 1 on OS X as a workaround for older LCMS lacking MD5. #define JXL_CMS_OLD_VERSION 0 -// cms functions (even *THR) are not thread-safe, except cmsDoTransform. -// To ensure all functions are covered without frequent lock-taking nor risk of -// recursive lock, we lock in the top-level APIs. -static std::mutex& LcmsMutex() { - static std::mutex m; - return m; -} - #if JPEGXL_ENABLE_SKCMS JXL_MUST_USE_RESULT CIExy CIExyFromXYZ(const float XYZ[3]) { const float factor = 1.f / (XYZ[0] + XYZ[1] + XYZ[2]); CIExy xy; @@ -814,13 +805,10 @@ return true; } } // namespace -// All functions that call lcms directly (except ColorSpaceTransform::Run) must -// lock LcmsMutex(). - Status ColorEncoding::SetFieldsFromICC() { // In case parsing fails, mark the ColorEncoding as invalid. SetColorSpace(ColorSpace::kUnknown); tf.SetTransferFunction(TransferFunction::kUnknown); @@ -856,11 +844,10 @@ DetectTransferFunction(profile, this); // ICC and RenderingIntent have the same values (0..3). rendering_intent = static_cast(rendering_intent32); #else // JPEGXL_ENABLE_SKCMS - std::lock_guard guard(LcmsMutex()); const cmsContext context = GetContext(); Profile profile; JXL_RETURN_IF_ERROR(DecodeProfile(context, icc_, &profile)); @@ -918,11 +905,10 @@ void JxlCmsDestroy(void* cms_data) { if (cms_data == nullptr) return; JxlCms* t = reinterpret_cast(cms_data); #if !JPEGXL_ENABLE_SKCMS - std::lock_guard guard(LcmsMutex()); TransformDeleter()(t->lcms_transform); #endif delete t; } @@ -955,11 +941,10 @@ if (!DecodeProfile(output->icc.data, output->icc.size, &t->profile_dst)) { JXL_NOTIFY_ERROR("JxlCmsInit: skcms failed to parse output ICC"); return nullptr; } #else // JPEGXL_ENABLE_SKCMS - std::lock_guard guard(LcmsMutex()); const cmsContext context = GetContext(); Profile profile_src, profile_dst; if (!DecodeProfile(context, c_src.ICC(), &profile_src)) { JXL_NOTIFY_ERROR("JxlCmsInit: lcms failed to parse input ICC"); return nullptr; diff --git a/third_party/jpeg-xl/lib/jxl/enc_external_image.h b/third_party/jpeg-xl/lib/jxl/enc_external_image.h --- a/third_party/jpeg-xl/lib/jxl/enc_external_image.h +++ b/third_party/jpeg-xl/lib/jxl/enc_external_image.h @@ -28,11 +28,11 @@ // Convert an interleaved pixel buffer to the internal ImageBundle // representation. This is the opposite of ConvertToExternal(). Status ConvertFromExternal(Span bytes, size_t xsize, size_t ysize, const ColorEncoding& c_current, - bool has_alpha, bool alpha_is_premultiplied, + 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); Status BufferToImageF(const JxlPixelFormat& pixel_format, size_t xsize, size_t ysize, const void* buffer, size_t size, 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 @@ -133,10 +133,15 @@ if (bytes.size() < bytes_to_read) { return JXL_FAILURE("Buffer size is too small"); } JXL_ASSERT(channel->xsize() == xsize); JXL_ASSERT(channel->ysize() == ysize); + // Too large buffer is likely an application bug, so also fail for that. + // Do allow padding to stride in last row though. + if (bytes.size() > row_size * ysize) { + return JXL_FAILURE("Buffer size is too large"); + } const bool little_endian = endianness == JXL_LITTLE_ENDIAN || (endianness == JXL_NATIVE_ENDIAN && IsLittleEndian()); @@ -210,11 +215,11 @@ return true; } Status ConvertFromExternal(Span bytes, size_t xsize, size_t ysize, const ColorEncoding& c_current, - bool has_alpha, bool alpha_is_premultiplied, + 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."); @@ -225,11 +230,16 @@ if (bits_per_sample == 1) { return JXL_FAILURE("packed 1-bit per sample is not yet supported"); } const size_t color_channels = c_current.Channels(); - const size_t channels = color_channels + has_alpha; + 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; @@ -246,10 +256,18 @@ return JXL_FAILURE( "Buffer size is too small: expected at least %" PRIuS " bytes (= %" PRIuS " * %" PRIuS " * %" PRIuS "), got %" PRIuS " bytes", bytes_to_read, xsize, ysize, bytes_per_pixel, bytes.size()); } + // Too large buffer is likely an application bug, so also fail for that. + // Do allow padding to stride in last row though. + if (bytes.size() > row_size * ysize) { + return JXL_FAILURE( + "Buffer size is too large: expected at most %" PRIuS " bytes (= %" PRIuS + " * %" PRIuS " * %" PRIuS "), got %" PRIuS " bytes", + row_size * ysize, xsize, ysize, bytes_per_pixel, bytes.size()); + } const bool little_endian = endianness == JXL_LITTLE_ENDIAN || (endianness == JXL_NATIVE_ENDIAN && IsLittleEndian()); const uint8_t* const in = bytes.data(); @@ -349,11 +367,11 @@ JXL_RETURN_IF_ERROR(RunOnPool( 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 + - (color_channels * bits_per_sample / jxl::kBitsPerByte); + ((channels - 1) * bits_per_sample / jxl::kBitsPerByte); float* JXL_RESTRICT row_out = alpha.Row(y); if (bits_per_sample <= 16) { if (little_endian) { for (size_t x = 0; x < xsize; ++x) { row_out[x] = LoadLEFloat16(in + i); @@ -384,11 +402,11 @@ float mul = 1. / ((1ull << bits_per_sample) - 1); JXL_RETURN_IF_ERROR(RunOnPool( 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 + color_channels * bytes_per_channel; + 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); @@ -412,10 +430,16 @@ }, "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; } @@ -446,13 +470,11 @@ JXL_RETURN_IF_ERROR( PixelFormatToExternal(pixel_format, &bitdepth, &float_in)); JXL_RETURN_IF_ERROR(ConvertFromExternal( jxl::Span(static_cast(buffer), size), - xsize, ysize, c_current, - /*has_alpha=*/pixel_format.num_channels == 2 || - pixel_format.num_channels == 4, + xsize, ysize, c_current, pixel_format.num_channels, /*alpha_is_premultiplied=*/false, bitdepth, pixel_format.endianness, /*flipped_y=*/false, pool, ib, float_in, pixel_format.align)); ib->VerifyMetadata(); return true; diff --git a/third_party/jpeg-xl/lib/jxl/enc_external_image_gbench.cc b/third_party/jpeg-xl/lib/jxl/enc_external_image_gbench.cc --- a/third_party/jpeg-xl/lib/jxl/enc_external_image_gbench.cc +++ b/third_party/jpeg-xl/lib/jxl/enc_external_image_gbench.cc @@ -26,11 +26,11 @@ for (size_t i = 0; i < kNumIter; ++i) { JXL_CHECK(ConvertFromExternal( Span(interleaved.data(), interleaved.size()), xsize, ysize, /*c_current=*/ColorEncoding::SRGB(), - /*has_alpha=*/true, + /*channels=*/4, /*alpha_is_premultiplied=*/false, /*bits_per_sample=*/8, JXL_NATIVE_ENDIAN, /*flipped_y=*/false, /*pool=*/nullptr, &ib, /*float_in=*/false, /*align=*/0)); } diff --git a/third_party/jpeg-xl/lib/jxl/enc_external_image_test.cc b/third_party/jpeg-xl/lib/jxl/enc_external_image_test.cc --- a/third_party/jpeg-xl/lib/jxl/enc_external_image_test.cc +++ b/third_party/jpeg-xl/lib/jxl/enc_external_image_test.cc @@ -26,22 +26,22 @@ ImageBundle ib(&im); const uint8_t buf[10 * 100 * 8] = {}; EXPECT_FALSE(ConvertFromExternal( Span(buf, 10), /*xsize=*/10, /*ysize=*/100, - /*c_current=*/ColorEncoding::SRGB(), /*has_alpha=*/true, + /*c_current=*/ColorEncoding::SRGB(), /*channels=*/4, /*alpha_is_premultiplied=*/false, /*bits_per_sample=*/16, JXL_BIG_ENDIAN, /*flipped_y=*/false, nullptr, &ib, /*float_in=*/false, /*align=*/0)); EXPECT_FALSE(ConvertFromExternal( Span(buf, sizeof(buf) - 1), /*xsize=*/10, /*ysize=*/100, - /*c_current=*/ColorEncoding::SRGB(), /*has_alpha=*/true, + /*c_current=*/ColorEncoding::SRGB(), /*channels=*/4, /*alpha_is_premultiplied=*/false, /*bits_per_sample=*/16, JXL_BIG_ENDIAN, /*flipped_y=*/false, nullptr, &ib, /*float_in=*/false, /*align=*/0)); EXPECT_TRUE(ConvertFromExternal( Span(buf, sizeof(buf)), /*xsize=*/10, /*ysize=*/100, /*c_current=*/ColorEncoding::SRGB(), - /*has_alpha=*/true, /*alpha_is_premultiplied=*/false, + /*channels=*/4, /*alpha_is_premultiplied=*/false, /*bits_per_sample=*/16, JXL_BIG_ENDIAN, /*flipped_y=*/false, nullptr, &ib, /*float_in=*/false, /*align=*/0)); } #endif @@ -57,11 +57,11 @@ // has_alpha is true but the ImageBundle has no alpha. Alpha channel should // be ignored. EXPECT_TRUE(ConvertFromExternal( Span(buf, sizeof(buf)), xsize, ysize, /*c_current=*/ColorEncoding::SRGB(), - /*has_alpha=*/true, /*alpha_is_premultiplied=*/false, + /*channels=*/4, /*alpha_is_premultiplied=*/false, /*bits_per_sample=*/8, JXL_BIG_ENDIAN, /*flipped_y=*/false, nullptr, &ib, /*float_in=*/false, /*align=*/0)); EXPECT_FALSE(ib.HasAlpha()); } 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 @@ -183,37 +183,24 @@ Bundle::Write(metadata->transform_data, writer, kLayerHeader, aux_out)); return true; } -Status EncodeFile(const CompressParams& cparams_orig, const CodecInOut* io, +Status EncodeFile(const CompressParams& params, const CodecInOut* io, PassesEncoderState* passes_enc_state, PaddedBytes* compressed, const JxlCmsInterface& cms, AuxOut* aux_out, ThreadPool* pool) { io->CheckMetadata(); BitWriter writer; - CompressParams cparams = cparams_orig; + CompressParams cparams = params; if (io->Main().color_transform != ColorTransform::kNone) { // Set the color transform to YCbCr or XYB if the original image is such. cparams.color_transform = io->Main().color_transform; } - // TODO(lode): move this to a common CompressParam post-initializer that is - // mandatory to be called, so that the encode API can also use it, once the - // encode API has the settings for resampling in the first place. - if (cparams.resampling == 0) { - cparams.resampling = 1; - // For very low bit rates, using 2x2 resampling gives better results on - // most photographic images, with an adjusted butteraugli score chosen to - // give roughly the same amount of bits per pixel. - if (!cparams.already_downsampled && cparams.butteraugli_distance >= 20) { - cparams.resampling = 2; - cparams.butteraugli_distance = - 6 + ((cparams.butteraugli_distance - 20) * 0.25); - } - } + JXL_RETURN_IF_ERROR(ParamsPostInit(&cparams)); std::unique_ptr metadata = jxl::make_unique(); JXL_RETURN_IF_ERROR(PrepareCodecMetadataFromIO(cparams, io, metadata.get())); JXL_RETURN_IF_ERROR(WriteHeaders(metadata.get(), &writer, aux_out)); diff --git a/third_party/jpeg-xl/lib/jxl/enc_frame.h b/third_party/jpeg-xl/lib/jxl/enc_frame.h --- a/third_party/jpeg-xl/lib/jxl/enc_frame.h +++ b/third_party/jpeg-xl/lib/jxl/enc_frame.h @@ -58,10 +58,13 @@ // extra channel info and allows more options. The non-API cjxl leaves it // empty and relies on the default behavior. std::vector extra_channel_blending_info; }; +// Checks and adjusts CompressParams when they are all initialized. +Status ParamsPostInit(CompressParams* p); + // Encodes a single frame (including its header) into a byte stream. Groups may // be processed in parallel by `pool`. metadata is the ImageMetadata encoded in // the codestream, and must be used for the FrameHeaders, do not use // ib.metadata. Status EncodeFrame(const CompressParams& cparams_orig, 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 @@ -211,11 +211,12 @@ // We don't add noise at low butteraugli distances because the original // noise is stored within the compressed image and adding noise makes things // worse. if (ApplyOverride(cparams.noise, dist >= kMinButteraugliForNoise) || - cparams.photon_noise_iso > 0) { + cparams.photon_noise_iso > 0 || + cparams.manual_noise.size() == NoiseParams::kNumNoisePoints) { flags |= FrameHeader::kNoise; } if (cparams.progressive_dc > 0 && cparams.modular_mode == false) { flags |= FrameHeader::kUseDcFrame; @@ -301,11 +302,11 @@ } frame_header->flags = FrameFlagsFromParams(cparams); // Non-photon noise is not supported in the Modular encoder for now. if (frame_header->encoding != FrameEncoding::kVarDCT && - cparams.photon_noise_iso == 0) { + cparams.photon_noise_iso == 0 && cparams.manual_noise.empty()) { frame_header->UpdateFlag(false, FrameHeader::Flags::kNoise); } JXL_RETURN_IF_ERROR(LoopFilterFromParams(cparams, frame_header)); @@ -1040,20 +1041,45 @@ AuxOut* aux_out_; std::vector group_caches_; bool doing_jpeg_recompression = false; }; +Status ParamsPostInit(CompressParams* p) { + if (!p->manual_noise.empty() && + p->manual_noise.size() != NoiseParams::kNumNoisePoints) { + return JXL_FAILURE("Invalid number of noise lut entries"); + } + if (!p->manual_xyb_factors.empty() && p->manual_xyb_factors.size() != 3) { + return JXL_FAILURE("Invalid number of XYB quantization factors"); + } + if (p->resampling <= 0) { + p->resampling = 1; + // For very low bit rates, using 2x2 resampling gives better results on + // most photographic images, with an adjusted butteraugli score chosen to + // give roughly the same amount of bits per pixel. + if (!p->already_downsampled && p->butteraugli_distance >= 20) { + p->resampling = 2; + p->butteraugli_distance = 6 + ((p->butteraugli_distance - 20) * 0.25); + } + } + if (p->ec_resampling <= 0) { + p->ec_resampling = p->resampling; + } + return true; +} + Status EncodeFrame(const CompressParams& cparams_orig, const FrameInfo& frame_info, const CodecMetadata* metadata, const ImageBundle& ib, PassesEncoderState* passes_enc_state, const JxlCmsInterface& cms, ThreadPool* pool, BitWriter* writer, AuxOut* aux_out) { ib.VerifyMetadata(); passes_enc_state->special_frames.clear(); CompressParams cparams = cparams_orig; + JXL_RETURN_IF_ERROR(ParamsPostInit(&cparams)); if (cparams.progressive_dc < 0) { if (cparams.progressive_dc != -1) { return JXL_FAILURE("Invalid progressive DC setting value (%d)", cparams.progressive_dc); @@ -1288,10 +1314,16 @@ if (cparams.photon_noise_iso > 0) { lossy_frame_encoder.State()->shared.image_features.noise_params = SimulatePhotonNoise(ib.xsize(), ib.ysize(), cparams.photon_noise_iso); } + if (cparams.manual_noise.size() == NoiseParams::kNumNoisePoints) { + for (size_t i = 0; i < NoiseParams::kNumNoisePoints; i++) { + lossy_frame_encoder.State()->shared.image_features.noise_params.lut[i] = + cparams.manual_noise[i]; + } + } if (frame_header->flags & FrameHeader::kNoise) { EncodeNoise(lossy_frame_encoder.State()->shared.image_features.noise_params, get_output(0), kLayerNoise, aux_out); } diff --git a/third_party/jpeg-xl/lib/jxl/enc_group.cc b/third_party/jpeg-xl/lib/jxl/enc_group.cc --- a/third_party/jpeg-xl/lib/jxl/enc_group.cc +++ b/third_party/jpeg-xl/lib/jxl/enc_group.cc @@ -110,11 +110,11 @@ const size_t hfix = (static_cast(y >= ysize * kBlockDim / 2) * 2 + static_cast(x >= xsize * kBlockDim / 2)); const float val = block_in[pos] * (qm[pos] * qac * qm_multiplier); float v = (std::abs(val) < thres[hfix]) ? 0 : rintf(val); const float error = std::abs(val) - std::abs(v); - hfError[hfix] += error; + hfError[hfix] += error * error; if (hfMaxError[hfix] < error) { hfMaxError[hfix] = error; hfMaxErrorIx[hfix] = pos; } if (v != 0.0f) { @@ -122,17 +122,18 @@ } block_out[pos] = static_cast(rintf(v)); } } if (c != 1) return; - // TODO(veluca): include AFV? - const size_t kPartialBlockKinds = + constexpr size_t kPartialBlockKinds = (1 << AcStrategy::Type::IDENTITY) | (1 << AcStrategy::Type::DCT2X2) | (1 << AcStrategy::Type::DCT4X4) | (1 << AcStrategy::Type::DCT4X8) | - (1 << AcStrategy::Type::DCT8X4); + (1 << AcStrategy::Type::DCT8X4) | (1 << AcStrategy::Type::AFV0) | + (1 << AcStrategy::Type::AFV1) | (1 << AcStrategy::Type::AFV2) | + (1 << AcStrategy::Type::AFV3); if ((1 << quant_kind) & kPartialBlockKinds) return; - float hfErrorLimit = 0.1f * (xsize * ysize) * kDCTBlockSize * 0.25f; + float hfErrorLimit = 0.029f * (xsize * ysize) * kDCTBlockSize * 0.25f; bool goretry = false; for (int i = 1; i < 4; ++i) { if (hfError[i] >= hfErrorLimit && hfNonZeros[i] <= (xsize + ysize) * 0.25f) { if (thres[i] >= 0.4f) { diff --git a/third_party/jpeg-xl/lib/jxl/enc_heuristics.cc b/third_party/jpeg-xl/lib/jxl/enc_heuristics.cc --- a/third_party/jpeg-xl/lib/jxl/enc_heuristics.cc +++ b/third_party/jpeg-xl/lib/jxl/enc_heuristics.cc @@ -436,16 +436,17 @@ int64_t xsize2 = output->xsize(); int64_t ysize2 = output->ysize(); for (int64_t y = 0; y < ysize2; y++) { for (int64_t x = 0; x < xsize2; x++) { auto kernel = kernel00; - if ((x & 1) && (y & 1)) + if ((x & 1) && (y & 1)) { kernel = kernel11; - else if (x & 1) + } else if (x & 1) { kernel = kernel10; - else if (y & 1) + } else if (y & 1) { kernel = kernel01; + } float sum = 0; int64_t x2 = x / 2; int64_t y2 = y / 2; // get min and max values of the original image in the support @@ -485,16 +486,17 @@ // Returns the derivative of Upsampler, with respect to input pixel x2, y2, to // output pixel x, y (ignoring the clamping). float UpsamplerDeriv(int64_t x2, int64_t y2, int64_t x, int64_t y) { auto kernel = kernel00; - if ((x & 1) && (y & 1)) + if ((x & 1) && (y & 1)) { kernel = kernel11; - else if (x & 1) + } else if (x & 1) { kernel = kernel10; - else if (y & 1) + } else if (y & 1) { kernel = kernel01; + } int64_t ix = x / 2; int64_t iy = y / 2; int64_t kx = x2 - ix + kSize / 2; int64_t ky = y2 - iy + kSize / 2; 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 @@ -100,11 +100,12 @@ } return tree; } Tree PredefinedTree(ModularOptions::TreeKind tree_kind, size_t total_pixels) { - if (tree_kind == ModularOptions::TreeKind::kJpegTranscodeACMeta) { + if (tree_kind == ModularOptions::TreeKind::kJpegTranscodeACMeta || + tree_kind == ModularOptions::TreeKind::kTrivialTreeNoPredictor) { // All the data is 0, so no need for a fancy tree. return {PropertyDecisionNode::Leaf(Predictor::Zero)}; } if (tree_kind == ModularOptions::TreeKind::kFalconACMeta) { // All the data is 0 except the quant field. TODO(veluca): make that 0 too. @@ -318,10 +319,16 @@ cparams.options.predictor = Predictor::Zero; break; } } } + if (cparams.decoding_speed_tier >= 1 && cparams.responsive && + cparams.quality_pair == std::make_pair(100.f, 100.f)) { + 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 @@ -432,13 +439,18 @@ stream_options.resize(num_streams, cparams.options); } bool do_transform(Image& image, const Transform& tr, const weighted::Header& wp_header, - jxl::ThreadPool* pool = nullptr) { + jxl::ThreadPool* pool = nullptr, bool force_jxlart = false) { Transform t = tr; - bool did_it = TransformForward(t, image, wp_header, pool); + bool did_it = true; + if (force_jxlart) { + if (!t.MetaApply(image)) return false; + } else { + did_it = TransformForward(t, image, wp_header, pool); + } if (did_it) image.transform.push_back(t); return did_it; } Status ModularFrameEncoder::ComputeEncodingData( @@ -451,11 +463,12 @@ if (do_color && frame_header.loop_filter.gab) { GaborishInverse(color, 0.9908511000000001f, pool); } if (do_color && metadata.bit_depth.bits_per_sample <= 16 && - cparams.speed_tier < SpeedTier::kCheetah) { + cparams.speed_tier < SpeedTier::kCheetah && + cparams.decoding_speed_tier < 2) { FindBestPatchDictionary(*color, enc_state, cms, nullptr, aux_out, cparams.color_transform == ColorTransform::kXYB); PatchDictionaryEncoder::SubtractFrom( enc_state->shared.image_features.patches, color); } @@ -490,11 +503,16 @@ 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}; - DequantMatricesSetCustomDC(&enc_state->shared.matrices, enc_factors); + if (cparams.manual_xyb_factors.size() == 3) { + DequantMatricesSetCustomDC(&enc_state->shared.matrices, + cparams.manual_xyb_factors.data()); + } else { + DequantMatricesSetCustomDC(&enc_state->shared.matrices, enc_factors); + } } pixel_type maxval = gi.bitdepth < 32 ? (1u << gi.bitdepth) - 1 : 0; if (do_color) { for (; c < 3; c++) { if (metadata.color_encoding.IsGray() && @@ -663,11 +681,12 @@ if (maybe_palette.lossy_palette) { maybe_palette.predictor = delta_pred; } // TODO(veluca): use a custom weighted header if using the weighted // predictor. - do_transform(gi, maybe_palette, weighted::Header(), pool); + do_transform(gi, maybe_palette, weighted::Header(), pool, + cparams.options.zero_tokens); } // all-minus-one-channel palette (RGB with separate alpha, or CMY with // separate K) if (gi.channel.size() - gi.nb_meta_channels > 3) { Transform maybe_palette_3(TransformId::kPalette); @@ -678,11 +697,12 @@ maybe_palette_3.ordered_palette = cparams.palette_colors >= 0; maybe_palette_3.lossy_palette = cparams.lossy_palette; if (maybe_palette_3.lossy_palette) { maybe_palette_3.predictor = delta_pred; } - do_transform(gi, maybe_palette_3, weighted::Header(), pool); + do_transform(gi, maybe_palette_3, weighted::Header(), pool, + cparams.options.zero_tokens); } } if (cparams.color_transform == ColorTransform::kNone && do_color && !fp && gi.channel.size() - gi.nb_meta_channels >= 3) { @@ -1101,10 +1121,21 @@ params.lz77_method = HistogramParams::LZ77Method::kLZ77; } if (cparams.decoding_speed_tier >= 1) { params.max_histograms = 12; } + if (cparams.decoding_speed_tier >= 1 && cparams.responsive) { + params.lz77_method = cparams.speed_tier >= SpeedTier::kCheetah + ? HistogramParams::LZ77Method::kRLE + : cparams.speed_tier >= SpeedTier::kKitten + ? HistogramParams::LZ77Method::kLZ77 + : HistogramParams::LZ77Method::kOptimal; + } + if (cparams.decoding_speed_tier >= 2 && cparams.responsive) { + params.uint_method = HistogramParams::HybridUintMethod::k000; + params.force_huffman = true; + } BuildAndEncodeHistograms(params, kNumTreeContexts, tree_tokens, &code, &context_map, writer, kLayerModularTree, aux_out); WriteTokens(tree_tokens[0], code, context_map, writer, kLayerModularTree, aux_out); params.image_widths = image_widths; @@ -1289,11 +1320,12 @@ } } // Local channel palette if (cparams.channel_colors_percent > 0 && quality == 100 && - !cparams.lossy_palette && cparams.speed_tier < SpeedTier::kCheetah) { + !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; compute_minmax(gi.channel[gi.nb_meta_channels + i], &min, &max); 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 @@ -244,18 +244,21 @@ } bool use_new_heuristics = false; // Down/upsample the image before encoding / after decoding by this factor. - // The resampling value can also be set to 0 to automatically choose based + // The resampling value can also be set to <= 0 to automatically choose based // on distance, however EncodeFrame doesn't support this, so it is - // required to process the CompressParams to set a valid positive resampling + // required to call PostInit() to set a valid positive resampling // value and altered butteraugli score if this is used. - size_t resampling = 1; - size_t ec_resampling = 1; + int resampling = -1; + int ec_resampling = -1; // Skip the downsampling before encoding if this is true. bool already_downsampled = false; + + std::vector manual_noise; + std::vector manual_xyb_factors; }; static constexpr float kMinButteraugliForDynamicAR = 0.5f; static constexpr float kMinButteraugliForDots = 3.0f; static constexpr float kMinButteraugliToSubtractOriginalPatches = 3.0f; 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 @@ -781,16 +781,17 @@ *state->shared.metadata, ColorEncoding::LinearSRGB( state->shared.metadata->m.color_encoding.IsGray()))); JXL_CHECK(DecodeFrame({}, &dec_state, pool, &br, &decoded, *state->shared.metadata, /*constraints=*/nullptr)); + size_t ref_xsize = + dec_state.shared_storage.reference_frames[idx].storage.color()->xsize(); // if the frame itself uses patches, we need to decode another frame - if (!dec_state.shared_storage.reference_frames[idx] - .storage.color() - ->xsize()) + if (!ref_xsize) { JXL_CHECK(DecodeFrame({}, &dec_state, pool, &br, &decoded, *state->shared.metadata, /*constraints=*/nullptr)); + } JXL_CHECK(br.Close()); state->shared.reference_frames[idx] = std::move(dec_state.shared_storage.reference_frames[idx]); } else { state->shared.reference_frames[idx].storage = std::move(ib); 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 @@ -46,11 +46,13 @@ namespace { template void AppendJxlpBoxCounter(uint32_t counter, bool last, T* output) { if (last) counter |= 0x80000000; - StoreBE32(counter, jxl::Extend(output, 4)); + for (size_t i = 0; i < 4; i++) { + output->push_back(counter >> (8 * (3 - i)) & 0xff); + } } void QueueFrame( const JxlEncoderFrameSettings* frame_settings, jxl::MemoryManagerUniquePtr& frame) { @@ -278,11 +280,11 @@ output_byte_queue.insert(output_byte_queue.end(), bytes.data(), bytes.data() + bytes.size()); bytes.clear(); } - if (store_jpeg_metadata && jpeg_metadata.size() > 0) { + if (store_jpeg_metadata && !jpeg_metadata.empty()) { jxl::AppendBoxHeader(jxl::MakeBoxType("jbrd"), jpeg_metadata.size(), false, &output_byte_queue); output_byte_queue.insert(output_byte_queue.end(), jpeg_metadata.begin(), jpeg_metadata.end()); } @@ -359,22 +361,24 @@ 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; @@ -450,10 +454,13 @@ if (!jxl::ConvertExternalToInternalColorEncoding( *color, &enc->metadata.m.color_encoding)) { return JXL_ENC_ERROR; } enc->color_encoding_set = true; + if (!enc->intensity_target_set) { + jxl::SetIntensityTarget(&enc->metadata.m); + } return JXL_ENC_SUCCESS; } JxlEncoderStatus JxlEncoderSetICCProfile(JxlEncoder* enc, const uint8_t* icc_profile, @@ -466,20 +473,23 @@ icc.assign(icc_profile, icc_profile + size); if (!enc->metadata.m.color_encoding.SetICC(std::move(icc))) { return JXL_ENC_ERROR; } enc->color_encoding_set = true; + if (!enc->intensity_target_set) { + jxl::SetIntensityTarget(&enc->metadata.m); + } return JXL_ENC_SUCCESS; } void JxlEncoderInitBasicInfo(JxlBasicInfo* info) { info->have_container = JXL_FALSE; info->xsize = 0; info->ysize = 0; info->bits_per_sample = 8; info->exponent_bits_per_sample = 0; - info->intensity_target = 255.f; + info->intensity_target = 0.f; info->min_nits = 0.f; info->relative_to_max_display = JXL_FALSE; info->linear_below = 0.f; info->uses_original_profile = JXL_FALSE; info->have_preview = JXL_FALSE; @@ -580,11 +590,20 @@ if (info->orientation > 0 && info->orientation <= 8) { enc->metadata.m.orientation = info->orientation; } else { return JXL_API_ERROR("Invalid value for orientation field"); } - enc->metadata.m.SetIntensityTarget(info->intensity_target); + if (info->intensity_target != 0) { + enc->metadata.m.SetIntensityTarget(info->intensity_target); + enc->intensity_target_set = true; + } else if (enc->color_encoding_set || enc->metadata.m.xyb_encoded) { + // If both conditions are false, JxlEncoderSetColorEncoding will be called + // later and we will get one more chance to call jxl::SetIntensityTarget, + // after the color encoding is indeed set. + jxl::SetIntensityTarget(&enc->metadata.m); + enc->intensity_target_set = true; + } enc->metadata.m.tone_mapping.min_nits = info->min_nits; enc->metadata.m.tone_mapping.relative_to_max_display = info->relative_to_max_display; enc->metadata.m.tone_mapping.linear_below = info->linear_below; enc->basic_info_set = true; @@ -778,13 +797,10 @@ // JxlEncoderFrameSettingsSetOption due to the extra channel index // argument required. if (value != -1 && value != 1 && value != 2 && value != 4 && value != 8) { return JXL_ENC_ERROR; } - // The implementation doesn't support the default choice between 1x1 and - // 2x2 for extra channels, so 1x1 is set as the default. - if (value == -1) value = 1; frame_settings->values.cparams.ec_resampling = value; return JXL_ENC_SUCCESS; case JXL_ENC_FRAME_SETTING_ALREADY_DOWNSAMPLED: if (value < 0 || value > 1) { return JXL_ENC_ERROR; @@ -992,18 +1008,20 @@ 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->wrote_bytes = false; enc->jxlp_counter = 0; enc->metadata = jxl::CodecMetadata(); enc->last_used_cparams = jxl::CompressParams(); enc->frames_closed = false; enc->boxes_closed = false; enc->basic_info_set = false; enc->color_encoding_set = false; + enc->intensity_target_set = false; enc->use_container = false; enc->use_boxes = false; enc->codestream_level = 5; } @@ -1229,10 +1247,23 @@ 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; @@ -1329,14 +1360,14 @@ size_t* avail_out) { while (*avail_out > 0 && (!enc->output_byte_queue.empty() || !enc->input_queue.empty())) { if (!enc->output_byte_queue.empty()) { size_t to_copy = std::min(*avail_out, enc->output_byte_queue.size()); - memcpy(static_cast(*next_out), enc->output_byte_queue.data(), - to_copy); + 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; 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 @@ -5,10 +5,11 @@ */ #ifndef LIB_JXL_ENCODE_INTERNAL_H_ #define LIB_JXL_ENCODE_INTERNAL_H_ +#include #include #include "jxl/encode.h" #include "jxl/memory_manager.h" #include "jxl/parallel_runner.h" @@ -60,25 +61,17 @@ bool compress_box; }; // Either a frame, or a box, not both. struct JxlEncoderQueuedInput { - JxlEncoderQueuedInput(const JxlMemoryManager& memory_manager) + explicit JxlEncoderQueuedInput(const JxlMemoryManager& memory_manager) : frame(nullptr, jxl::MemoryManagerDeleteHelper(&memory_manager)), box(nullptr, jxl::MemoryManagerDeleteHelper(&memory_manager)) {} MemoryManagerUniquePtr frame; MemoryManagerUniquePtr box; }; -namespace { -template -uint8_t* Extend(T* vec, size_t size) { - vec->resize(vec->size() + size, 0); - return vec->data() + vec->size() - size; -} -} // namespace - // Appends a JXL container box header with given type, size, and unbounded // properties to output. template void AppendBoxHeader(const jxl::BoxType& type, size_t size, bool unbounded, T* output) { @@ -89,18 +82,24 @@ if (box_size >= 0x100000000ull) { large_size = true; } } - StoreBE32(large_size ? 1 : box_size, Extend(output, 4)); - + { + const uint64_t store = large_size ? 1 : box_size; + for (size_t i = 0; i < 4; i++) { + output->push_back(store >> (8 * (3 - i)) & 0xff); + } + } for (size_t i = 0; i < 4; i++) { - output->push_back(*(type.data() + i)); + output->push_back(type[i]); } if (large_size) { - StoreBE64(box_size, Extend(output, 8)); + for (size_t i = 0; i < 8; i++) { + output->push_back(box_size >> (8 * (7 - i)) & 0xff); + } } } } // namespace jxl @@ -115,11 +114,17 @@ encoder_options; size_t num_queued_frames; size_t num_queued_boxes; std::vector input_queue; - std::vector output_byte_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(); + } // Force using the container even if not needed bool use_container; // User declared they will add metadata boxes bool use_boxes; @@ -145,10 +150,11 @@ bool frames_closed; bool boxes_closed; bool basic_info_set; bool color_encoding_set; + bool intensity_target_set; // 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 @@ -595,11 +595,11 @@ if (ftyp_box.data.size() != 12) return JXL_FAILURE("Invalid ftyp"); const char* expected = "jxl \0\0\0\0jxl "; if (memcmp(expected, ftyp_box.data.data(), 12) != 0) return JXL_FAILURE("Invalid ftyp"); - while (in->size() > 0) { + while (!in->empty()) { Box box = {}; JXL_RETURN_IF_ERROR(box.Decode(in)); if (box.data.data() == nullptr) { // The decoding encountered a box, but not enough data yet. return true; diff --git a/third_party/jpeg-xl/lib/jxl/epf.h b/third_party/jpeg-xl/lib/jxl/epf.h --- a/third_party/jpeg-xl/lib/jxl/epf.h +++ b/third_party/jpeg-xl/lib/jxl/epf.h @@ -11,11 +11,10 @@ #include #include "lib/jxl/base/data_parallel.h" #include "lib/jxl/base/status.h" #include "lib/jxl/dec_cache.h" -#include "lib/jxl/filters.h" #include "lib/jxl/passes_state.h" namespace jxl { // 4 * (sqrt(0.5)-1), so that Weight(sigma) = 0.5. @@ -27,32 +26,8 @@ // Fills the `state->filter_weights.sigma` image with the precomputed sigma // values in the area inside `block_rect`. Accesses the AC strategy, quant field // and epf_sharpness fields in the corresponding positions. void ComputeSigma(const Rect& block_rect, PassesDecoderState* state); -// Applies Gaborish + EPF to the given `image_rect` part of the image (used to -// select the sigma values). Input pixels are taken from `input:input_rect`, and -// the filtering result is written to `out:output_rect`. `dec_state->sigma` must -// be padded with `kMaxFilterPadding/kBlockDim` values along the x axis. -// All rects must have the same alignment module -// GroupBorderAssigner::kPaddingXRound pixels. -// `input_rect`, `output_rect` and `image_rect` must all have the same size. -// At least `lf.Padding()` pixels must be accessible and contain valid values -// outside of `image_rect` in `input`. Also, depending on the implementation, -// more pixels in the input up to a vector size boundary should be accessible -// but may contain uninitialized data. -// -// This function only prepares and returns the pipeline, to perform the -// filtering process it must be called on all row from -lf.Padding() to -// image_rect.ysize() + lf.Padding() . -// -// Note: if the output_rect x0 or x1 are not a multiple of kPaddingXRound more -// pixels with potentially uninitialized data will be written to the output left -// and right of the requested rect up to a multiple of kPaddingXRound pixels. -FilterPipeline* PrepareFilterPipeline( - PassesDecoderState* dec_state, const Rect& image_rect, const Image3F& input, - const Rect& input_rect, size_t image_ysize, size_t thread, - Image3F* JXL_RESTRICT out, const Rect& output_rect); - } // namespace jxl #endif // LIB_JXL_EPF_H_ diff --git a/third_party/jpeg-xl/lib/jxl/epf.cc b/third_party/jpeg-xl/lib/jxl/epf.cc --- a/third_party/jpeg-xl/lib/jxl/epf.cc +++ b/third_party/jpeg-xl/lib/jxl/epf.cc @@ -16,530 +16,25 @@ #include #include #include // std::accumulate #include -#undef HWY_TARGET_INCLUDE -#define HWY_TARGET_INCLUDE "lib/jxl/epf.cc" -#include -#include - #include "lib/jxl/ac_strategy.h" #include "lib/jxl/base/compiler_specific.h" #include "lib/jxl/base/data_parallel.h" #include "lib/jxl/base/status.h" #include "lib/jxl/common.h" #include "lib/jxl/convolve.h" #include "lib/jxl/dec_cache.h" -#include "lib/jxl/filters.h" -#include "lib/jxl/filters_internal.h" #include "lib/jxl/image.h" #include "lib/jxl/image_bundle.h" #include "lib/jxl/image_ops.h" #include "lib/jxl/loop_filter.h" #include "lib/jxl/quant_weights.h" #include "lib/jxl/quantizer.h" -HWY_BEFORE_NAMESPACE(); namespace jxl { -namespace HWY_NAMESPACE { - -// These templates are not found via ADL. -using hwy::HWY_NAMESPACE::Vec; - -// The EPF logic treats 8x8 blocks as one unit, each with their own sigma. -// It should be possible to do two blocks at a time in AVX3 vectors, at some -// increase in complexity (broadcasting sigma0/1 to lanes 0..7 and 8..15). -using DF = HWY_CAPPED(float, GroupBorderAssigner::kPaddingXRound); -using DU = HWY_CAPPED(uint32_t, GroupBorderAssigner::kPaddingXRound); - -DF df; - -JXL_INLINE Vec Weight(Vec sad, Vec inv_sigma, Vec thres) { - auto v = MulAdd(sad, inv_sigma, Set(DF(), 1.0f)); - auto v2 = v * v; - return IfThenZeroElse(v <= thres, v2); -} - -template -JXL_INLINE void AddPixelStep1(int row, const FilterRows& rows, size_t x, - Vec sad, Vec inv_sigma, - const LoopFilter& lf, Vec* JXL_RESTRICT X, - Vec* JXL_RESTRICT Y, Vec* JXL_RESTRICT B, - Vec* JXL_RESTRICT w) { - auto cx = aligned ? Load(DF(), rows.GetInputRow(row, 0) + x) - : LoadU(DF(), rows.GetInputRow(row, 0) + x); - auto cy = aligned ? Load(DF(), rows.GetInputRow(row, 1) + x) - : LoadU(DF(), rows.GetInputRow(row, 1) + x); - auto cb = aligned ? Load(DF(), rows.GetInputRow(row, 2) + x) - : LoadU(DF(), rows.GetInputRow(row, 2) + x); - - auto weight = Weight(sad, inv_sigma, Set(df, lf.epf_pass1_zeroflush)); - *w += weight; - *X = MulAdd(weight, cx, *X); - *Y = MulAdd(weight, cy, *Y); - *B = MulAdd(weight, cb, *B); -} - -template -JXL_INLINE void AddPixelStep2(int row, const FilterRows& rows, size_t x, - Vec rx, Vec ry, Vec rb, - Vec inv_sigma, const LoopFilter& lf, - Vec* JXL_RESTRICT X, Vec* JXL_RESTRICT Y, - Vec* JXL_RESTRICT B, - Vec* JXL_RESTRICT w) { - auto cx = aligned ? Load(DF(), rows.GetInputRow(row, 0) + x) - : LoadU(DF(), rows.GetInputRow(row, 0) + x); - auto cy = aligned ? Load(DF(), rows.GetInputRow(row, 1) + x) - : LoadU(DF(), rows.GetInputRow(row, 1) + x); - auto cb = aligned ? Load(DF(), rows.GetInputRow(row, 2) + x) - : LoadU(DF(), rows.GetInputRow(row, 2) + x); - - auto sad = AbsDiff(cx, rx) * Set(df, lf.epf_channel_scale[0]); - sad = MulAdd(AbsDiff(cy, ry), Set(df, lf.epf_channel_scale[1]), sad); - sad = MulAdd(AbsDiff(cb, rb), Set(df, lf.epf_channel_scale[2]), sad); - - auto weight = Weight(sad, inv_sigma, Set(df, lf.epf_pass2_zeroflush)); - - *w += weight; - *X = MulAdd(weight, cx, *X); - *Y = MulAdd(weight, cy, *Y); - *B = MulAdd(weight, cb, *B); -} - -template -void GaborishVector(const D df, const float* JXL_RESTRICT row_t, - const float* JXL_RESTRICT row_m, - const float* JXL_RESTRICT row_b, const V w0, const V w1, - const V w2, float* JXL_RESTRICT row_out) { -// Filter x0 is only aligned to blocks (8 floats = 32 bytes). For larger -// vectors, treat loads as unaligned (we manually align the Store). -#undef LoadMaybeU -#if HWY_CAP_GE512 -#define LoadMaybeU LoadU -#else -#define LoadMaybeU Load -#endif - - const auto t = LoadMaybeU(df, row_t); - const auto tl = LoadU(df, row_t - 1); - const auto tr = LoadU(df, row_t + 1); - const auto m = LoadMaybeU(df, row_m); - const auto l = LoadU(df, row_m - 1); - const auto r = LoadU(df, row_m + 1); - const auto b = LoadMaybeU(df, row_b); - const auto bl = LoadU(df, row_b - 1); - const auto br = LoadU(df, row_b + 1); - const auto sum0 = m; - const auto sum1 = (l + r) + (t + b); - const auto sum2 = (tl + tr) + (bl + br); - auto pixels = MulAdd(sum2, w2, MulAdd(sum1, w1, sum0 * w0)); - Store(pixels, df, row_out); -} - -void GaborishRow(const FilterRows& rows, const LoopFilter& /* lf */, - const FilterWeights& filter_weights, size_t x0, size_t x1, - size_t /*sigma_x_offset*/, size_t /* image_y_mod_8 */) { - JXL_DASSERT(x0 % Lanes(df) == 0); - - const float* JXL_RESTRICT gab_weights = filter_weights.gab_weights; - for (size_t c = 0; c < 3; c++) { - const float* JXL_RESTRICT row_t = rows.GetInputRow(-1, c); - const float* JXL_RESTRICT row_m = rows.GetInputRow(0, c); - const float* JXL_RESTRICT row_b = rows.GetInputRow(1, c); - float* JXL_RESTRICT row_out = rows.GetOutputRow(c); - - size_t ix = x0; - -#if HWY_CAP_GE512 - const HWY_FULL(float) dfull; // Gaborish is not block-dependent. - - // For AVX3, x0 might only be aligned to 8, not 16; if so, do a capped - // vector first to ensure full (Store-only!) alignment, then full vectors. - const uintptr_t addr = reinterpret_cast(row_out + ix); - if ((addr % 64) != 0 && ix < x1) { - const auto w0 = Set(df, gab_weights[3 * c + 0]); - const auto w1 = Set(df, gab_weights[3 * c + 1]); - const auto w2 = Set(df, gab_weights[3 * c + 2]); - GaborishVector(df, row_t + ix, row_m + ix, row_b + ix, w0, w1, w2, - row_out + ix); - ix += Lanes(df); - } - - const auto wfull0 = Set(dfull, gab_weights[3 * c + 0]); - const auto wfull1 = Set(dfull, gab_weights[3 * c + 1]); - const auto wfull2 = Set(dfull, gab_weights[3 * c + 2]); - for (; ix + Lanes(dfull) <= x1; ix += Lanes(dfull)) { - GaborishVector(dfull, row_t + ix, row_m + ix, row_b + ix, wfull0, wfull1, - wfull2, row_out + ix); - } -#endif - - // Non-AVX3 loop, or last capped vector for AVX3, if necessary - const auto w0 = Set(df, gab_weights[3 * c + 0]); - const auto w1 = Set(df, gab_weights[3 * c + 1]); - const auto w2 = Set(df, gab_weights[3 * c + 2]); - for (; ix < x1; ix += Lanes(df)) { - GaborishVector(df, row_t + ix, row_m + ix, row_b + ix, w0, w1, w2, - row_out + ix); - } - } -} - -// Step 0: 5x5 plus-shaped kernel with 5 SADs per pixel (3x3 -// plus-shaped). So this makes this filter a 7x7 filter. -void Epf0Row(const FilterRows& rows, const LoopFilter& lf, - const FilterWeights& filter_weights, size_t x0, size_t x1, - size_t sigma_x_offset, size_t image_y_mod_8) { - JXL_DASSERT(x0 % Lanes(df) == 0); - const float* JXL_RESTRICT row_sigma = rows.GetSigmaRow(); - - float sm = lf.epf_pass0_sigma_scale; - float bsm = sm * lf.epf_border_sad_mul; - - HWY_ALIGN float sad_mul[kBlockDim] = {bsm, sm, sm, sm, sm, sm, sm, bsm}; - - if (image_y_mod_8 == 0 || image_y_mod_8 == kBlockDim - 1) { - for (size_t i = 0; i < kBlockDim; i += Lanes(df)) { - Store(Set(df, bsm), df, sad_mul + i); - } - } - - for (size_t x = x0; x < x1; x += Lanes(df)) { - size_t bx = (x + sigma_x_offset) / kBlockDim; - size_t ix = (x + sigma_x_offset) % kBlockDim; - if (row_sigma[bx] < kMinSigma) { - for (size_t c = 0; c < 3; c++) { - auto px = Load(df, rows.GetInputRow(0, c) + x); - Store(px, df, rows.GetOutputRow(c) + x); - } - continue; - } - - const auto sm = Load(df, sad_mul + ix); - const auto inv_sigma = Set(DF(), row_sigma[bx]) * sm; - - decltype(Zero(df)) sads[12]; - for (size_t i = 0; i < 12; i++) sads[i] = Zero(df); - constexpr std::array sads_off[12] = { - {{-2, 0}}, {{-1, -1}}, {{-1, 0}}, {{-1, 1}}, {{0, -2}}, {{0, -1}}, - {{0, 1}}, {{0, 2}}, {{1, -1}}, {{1, 0}}, {{1, 1}}, {{2, 0}}, - }; - - // compute sads - // TODO(veluca): consider unrolling and optimizing this. - for (size_t c = 0; c < 3; c++) { - auto scale = Set(df, lf.epf_channel_scale[c]); - for (size_t i = 0; i < 12; i++) { - auto sad = Zero(df); - constexpr std::array plus_off[] = { - {{0, 0}}, {{-1, 0}}, {{0, -1}}, {{1, 0}}, {{0, 1}}}; - for (size_t j = 0; j < 5; j++) { - const auto r11 = LoadU( - df, rows.GetInputRow(plus_off[j][0], c) + x + plus_off[j][1]); - const auto c11 = - LoadU(df, rows.GetInputRow(sads_off[i][0] + plus_off[j][0], c) + - x + sads_off[i][1] + plus_off[j][1]); - sad += AbsDiff(r11, c11); - } - sads[i] = MulAdd(sad, scale, sads[i]); - } - } - const auto x_cc = LoadU(df, rows.GetInputRow(0, 0) + x); - const auto y_cc = LoadU(df, rows.GetInputRow(0, 1) + x); - const auto b_cc = LoadU(df, rows.GetInputRow(0, 2) + x); - - auto w = Set(df, 1); - auto X = x_cc; - auto Y = y_cc; - auto B = b_cc; - - for (size_t i = 0; i < 12; i++) { - AddPixelStep1(/*row=*/sads_off[i][0], rows, - x + sads_off[i][1], sads[i], inv_sigma, - lf, &X, &Y, &B, &w); - } - -#if JXL_HIGH_PRECISION - auto inv_w = Set(df, 1.0f) / w; -#else - auto inv_w = ApproximateReciprocal(w); -#endif - Store(X * inv_w, df, rows.GetOutputRow(0) + x); - Store(Y * inv_w, df, rows.GetOutputRow(1) + x); - Store(B * inv_w, df, rows.GetOutputRow(2) + x); - } -} - -// Step 1: 3x3 plus-shaped kernel with 5 SADs per pixel (also 3x3 -// plus-shaped). So this makes this filter a 5x5 filter. -void Epf1Row(const FilterRows& rows, const LoopFilter& lf, - const FilterWeights& filter_weights, size_t x0, size_t x1, - size_t sigma_x_offset, size_t image_y_mod_8) { - JXL_DASSERT(x0 % Lanes(df) == 0); - const float* JXL_RESTRICT row_sigma = rows.GetSigmaRow(); - - float sm = 1.0f; - float bsm = sm * lf.epf_border_sad_mul; - - HWY_ALIGN float sad_mul[kBlockDim] = {bsm, sm, sm, sm, sm, sm, sm, bsm}; - - if (image_y_mod_8 == 0 || image_y_mod_8 == kBlockDim - 1) { - for (size_t i = 0; i < kBlockDim; i += Lanes(df)) { - Store(Set(df, bsm), df, sad_mul + i); - } - } - - for (size_t x = x0; x < x1; x += Lanes(df)) { - size_t bx = (x + sigma_x_offset) / kBlockDim; - size_t ix = (x + sigma_x_offset) % kBlockDim; - if (row_sigma[bx] < kMinSigma) { - for (size_t c = 0; c < 3; c++) { - auto px = Load(df, rows.GetInputRow(0, c) + x); - Store(px, df, rows.GetOutputRow(c) + x); - } - continue; - } - - const auto sm = Load(df, sad_mul + ix); - const auto inv_sigma = Set(DF(), row_sigma[bx]) * sm; - auto sad0 = Zero(df); - auto sad1 = Zero(df); - auto sad2 = Zero(df); - auto sad3 = Zero(df); - - // compute sads - for (size_t c = 0; c < 3; c++) { - // center px = 22, px above = 21 - auto t = Undefined(df); - - const auto p20 = Load(df, rows.GetInputRow(-2, c) + x); - const auto p21 = Load(df, rows.GetInputRow(-1, c) + x); - auto sad0c = AbsDiff(p20, p21); // SAD 2, 1 - - const auto p11 = LoadU(df, rows.GetInputRow(-1, c) + x - 1); - auto sad1c = AbsDiff(p11, p21); // SAD 1, 2 - - const auto p31 = LoadU(df, rows.GetInputRow(-1, c) + x + 1); - auto sad2c = AbsDiff(p31, p21); // SAD 3, 2 - - const auto p02 = LoadU(df, rows.GetInputRow(0, c) + x - 2); - const auto p12 = LoadU(df, rows.GetInputRow(0, c) + x - 1); - sad1c += AbsDiff(p02, p12); // SAD 1, 2 - sad0c += AbsDiff(p11, p12); // SAD 2, 1 - - const auto p22 = LoadU(df, rows.GetInputRow(0, c) + x); - t = AbsDiff(p12, p22); - sad1c += t; // SAD 1, 2 - sad2c += t; // SAD 3, 2 - t = AbsDiff(p22, p21); - auto sad3c = t; // SAD 2, 3 - sad0c += t; // SAD 2, 1 - - const auto p32 = LoadU(df, rows.GetInputRow(0, c) + x + 1); - sad0c += AbsDiff(p31, p32); // SAD 2, 1 - t = AbsDiff(p22, p32); - sad1c += t; // SAD 1, 2 - sad2c += t; // SAD 3, 2 - - const auto p42 = LoadU(df, rows.GetInputRow(0, c) + x + 2); - sad2c += AbsDiff(p42, p32); // SAD 3, 2 - - const auto p13 = LoadU(df, rows.GetInputRow(1, c) + x - 1); - sad3c += AbsDiff(p13, p12); // SAD 2, 3 - - const auto p23 = Load(df, rows.GetInputRow(1, c) + x); - t = AbsDiff(p22, p23); - sad0c += t; // SAD 2, 1 - sad3c += t; // SAD 2, 3 - sad1c += AbsDiff(p13, p23); // SAD 1, 2 - - const auto p33 = LoadU(df, rows.GetInputRow(1, c) + x + 1); - sad2c += AbsDiff(p33, p23); // SAD 3, 2 - sad3c += AbsDiff(p33, p32); // SAD 2, 3 - - const auto p24 = Load(df, rows.GetInputRow(2, c) + x); - sad3c += AbsDiff(p24, p23); // SAD 2, 3 - - auto scale = Set(df, lf.epf_channel_scale[c]); - sad0 = MulAdd(sad0c, scale, sad0); - sad1 = MulAdd(sad1c, scale, sad1); - sad2 = MulAdd(sad2c, scale, sad2); - sad3 = MulAdd(sad3c, scale, sad3); - } - const auto x_cc = Load(df, rows.GetInputRow(0, 0) + x); - const auto y_cc = Load(df, rows.GetInputRow(0, 1) + x); - const auto b_cc = Load(df, rows.GetInputRow(0, 2) + x); - - auto w = Set(df, 1); - auto X = x_cc; - auto Y = y_cc; - auto B = b_cc; - - // Top row - AddPixelStep1(/*row=*/-1, rows, x, sad0, inv_sigma, lf, - &X, &Y, &B, &w); - // Center - AddPixelStep1(/*row=*/0, rows, x - 1, sad1, inv_sigma, - lf, &X, &Y, &B, &w); - AddPixelStep1(/*row=*/0, rows, x + 1, sad2, inv_sigma, - lf, &X, &Y, &B, &w); - // Bottom - AddPixelStep1(/*row=*/1, rows, x, sad3, inv_sigma, lf, &X, - &Y, &B, &w); -#if JXL_HIGH_PRECISION - auto inv_w = Set(df, 1.0f) / w; -#else - auto inv_w = ApproximateReciprocal(w); -#endif - Store(X * inv_w, df, rows.GetOutputRow(0) + x); - Store(Y * inv_w, df, rows.GetOutputRow(1) + x); - Store(B * inv_w, df, rows.GetOutputRow(2) + x); - } -} - -// Step 2: 3x3 plus-shaped kernel with a single reference pixel, ran on -// the output of the previous step. -void Epf2Row(const FilterRows& rows, const LoopFilter& lf, - const FilterWeights& filter_weights, size_t x0, size_t x1, - size_t sigma_x_offset, size_t image_y_mod_8) { - JXL_DASSERT(x0 % Lanes(df) == 0); - const float* JXL_RESTRICT row_sigma = rows.GetSigmaRow(); - - float sm = lf.epf_pass2_sigma_scale; - float bsm = sm * lf.epf_border_sad_mul; - - HWY_ALIGN float sad_mul[kBlockDim] = {bsm, sm, sm, sm, sm, sm, sm, bsm}; - - if (image_y_mod_8 == 0 || image_y_mod_8 == kBlockDim - 1) { - for (size_t i = 0; i < kBlockDim; i += Lanes(df)) { - Store(Set(df, bsm), df, sad_mul + i); - } - } - - for (size_t x = x0; x < x1; x += Lanes(df)) { - size_t bx = (x + sigma_x_offset) / kBlockDim; - size_t ix = (x + sigma_x_offset) % kBlockDim; - - if (row_sigma[bx] < kMinSigma) { - for (size_t c = 0; c < 3; c++) { - auto px = Load(df, rows.GetInputRow(0, c) + x); - Store(px, df, rows.GetOutputRow(c) + x); - } - continue; - } - - const auto sm = Load(df, sad_mul + ix); - const auto inv_sigma = Set(DF(), row_sigma[bx]) * sm; - - const auto x_cc = Load(df, rows.GetInputRow(0, 0) + x); - const auto y_cc = Load(df, rows.GetInputRow(0, 1) + x); - const auto b_cc = Load(df, rows.GetInputRow(0, 2) + x); - - auto w = Set(df, 1); - auto X = x_cc; - auto Y = y_cc; - auto B = b_cc; - - // Top row - AddPixelStep2(/*row=*/-1, rows, x, x_cc, y_cc, b_cc, - inv_sigma, lf, &X, &Y, &B, &w); - // Center - AddPixelStep2(/*row=*/0, rows, x - 1, x_cc, y_cc, b_cc, - inv_sigma, lf, &X, &Y, &B, &w); - AddPixelStep2(/*row=*/0, rows, x + 1, x_cc, y_cc, b_cc, - inv_sigma, lf, &X, &Y, &B, &w); - // Bottom - AddPixelStep2(/*row=*/1, rows, x, x_cc, y_cc, b_cc, - inv_sigma, lf, &X, &Y, &B, &w); - -#if JXL_HIGH_PRECISION - auto inv_w = Set(df, 1.0f) / w; -#else - auto inv_w = ApproximateReciprocal(w); -#endif - Store(X * inv_w, df, rows.GetOutputRow(0) + x); - Store(Y * inv_w, df, rows.GetOutputRow(1) + x); - Store(B * inv_w, df, rows.GetOutputRow(2) + x); - } -} - -constexpr FilterDefinition kGaborishFilter{&GaborishRow, 1}; -constexpr FilterDefinition kEpf0Filter{&Epf0Row, 3}; -constexpr FilterDefinition kEpf1Filter{&Epf1Row, 2}; -constexpr FilterDefinition kEpf2Filter{&Epf2Row, 1}; - -void FilterPipelineInit(FilterPipeline* fp, const LoopFilter& lf, - const Image3F& in, const Rect& in_rect, - const Rect& image_rect, size_t image_ysize, - Image3F* out, const Rect& out_rect) { - JXL_DASSERT(lf.gab || lf.epf_iters > 0); - // All EPF filters use sigma so we need to compute it. - fp->compute_sigma = lf.epf_iters > 0; - - fp->num_filters = 0; - fp->storage_rows_used = 0; - // First filter always uses the input image. - fp->filters[0].SetInput(&in, in_rect, image_rect, image_ysize); - - if (lf.gab) { - fp->AddStep(kGaborishFilter); - } - - if (lf.epf_iters == 1) { - fp->AddStep(kEpf1Filter); - } else if (lf.epf_iters == 2) { - fp->AddStep(kEpf1Filter); - fp->AddStep(kEpf2Filter); - } else if (lf.epf_iters == 3) { - fp->AddStep(kEpf0Filter); - fp->AddStep(kEpf1Filter); - fp->AddStep(kEpf2Filter); - } - - // At least one of the filters was enabled so "num_filters" must be non-zero. - JXL_DASSERT(fp->num_filters > 0); - - // Set the output of the last filter as the output image. - fp->filters[fp->num_filters - 1].SetOutput(out, out_rect); - - // Walk the list of filters backwards to compute how many rows are needed. - size_t col_border = 0; - for (int i = fp->num_filters - 1; i >= 0; i--) { - // Compute the region where we need to apply this filter. Depending on the - // step we might need to compute a larger portion than the original rect - // because of the border needed by other stages. This is the range of valid - // output values we produce, however we run the filter over a larger region - // to make those values multiple of Lanes(df). - const size_t x0 = - FilterPipeline::FilterStep::MaxLeftPadding(image_rect.x0()) - - col_border; - const size_t x1 = - FilterPipeline::FilterStep::MaxLeftPadding(image_rect.x0()) + - image_rect.xsize() + col_border; - - fp->filters[i].filter_x0 = x0 - x0 % Lanes(df); - fp->filters[i].filter_x1 = RoundUpTo(x1, Lanes(df)); - - // The extra border needed for future filtering. - fp->filters[i].output_col_border = col_border; - col_border += fp->filters[i].filter_def.border; - } - fp->total_border = col_border; - JXL_ASSERT(fp->total_border == lf.Padding()); - JXL_ASSERT(fp->total_border <= kMaxFilterBorder); -} - -// NOLINTNEXTLINE(google-readability-namespace-comments) -} // namespace HWY_NAMESPACE -} // namespace jxl -HWY_AFTER_NAMESPACE(); - -#if HWY_ONCE -namespace jxl { - -HWY_EXPORT(FilterPipelineInit); // Local function // Mirror n floats starting at *p and store them before p. JXL_INLINE void LeftMirror(float* p, size_t n) { for (size_t i = 0; i < n; i++) { *(p - 1 - i) = p[i]; @@ -557,16 +52,15 @@ const LoopFilter& lf = state->shared->frame_header.loop_filter; JXL_CHECK(lf.epf_iters > 0); const AcStrategyImage& ac_strategy = state->shared->ac_strategy; const float quant_scale = state->shared->quantizer.Scale(); - const size_t sigma_stride = state->filter_weights.sigma.PixelsPerRow(); + const size_t sigma_stride = state->sigma.PixelsPerRow(); const size_t sharpness_stride = state->shared->epf_sharpness.PixelsPerRow(); for (size_t by = 0; by < block_rect.ysize(); ++by) { - float* JXL_RESTRICT sigma_row = - block_rect.Row(&state->filter_weights.sigma, by); + float* JXL_RESTRICT sigma_row = block_rect.Row(&state->sigma, by); const uint8_t* JXL_RESTRICT sharpness_row = block_rect.ConstRow(state->shared->epf_sharpness, by); AcStrategyRow acs_row = ac_strategy.ConstRow(block_rect, by); const int* const JXL_RESTRICT row_quant = block_rect.ConstRow(state->shared->raw_quant_field, by); @@ -647,35 +141,6 @@ } } } } -FilterPipeline* PrepareFilterPipeline( - PassesDecoderState* dec_state, const Rect& image_rect, const Image3F& input, - const Rect& input_rect, size_t image_ysize, size_t thread, - Image3F* JXL_RESTRICT out, const Rect& output_rect) { - const LoopFilter& lf = dec_state->shared->frame_header.loop_filter; - // image_rect, input and output must all have the same kPaddingXRound - // alignment for SIMD, but it doesn't need to be 0. - JXL_DASSERT(image_rect.x0() % GroupBorderAssigner::kPaddingXRound == - input_rect.x0() % GroupBorderAssigner::kPaddingXRound); - JXL_DASSERT(image_rect.x0() % GroupBorderAssigner::kPaddingXRound == - output_rect.x0() % GroupBorderAssigner::kPaddingXRound); - - // We need enough pixels to access the padding and the rounding to - // GroupBorderAssigner::kPaddingXRound to the left of the image. - JXL_DASSERT(input_rect.x0() >= - input_rect.x0() % GroupBorderAssigner::kPaddingXRound + - lf.Padding()); - - JXL_DASSERT(image_rect.xsize() == input_rect.xsize()); - JXL_DASSERT(image_rect.xsize() == output_rect.xsize()); - FilterPipeline* fp = &(dec_state->filter_pipelines[thread]); - fp->image_rect = image_rect; - - HWY_DYNAMIC_DISPATCH(FilterPipelineInit) - (fp, lf, input, input_rect, image_rect, image_ysize, out, output_rect); - return fp; -} - } // namespace jxl -#endif // HWY_ONCE diff --git a/third_party/jpeg-xl/lib/jxl/fields.cc b/third_party/jpeg-xl/lib/jxl/fields.cc --- a/third_party/jpeg-xl/lib/jxl/fields.cc +++ b/third_party/jpeg-xl/lib/jxl/fields.cc @@ -257,11 +257,11 @@ bool all_default_ = true; }; class ReadVisitor : public VisitorBase { public: - ReadVisitor(BitReader* reader) : VisitorBase(), reader_(reader) {} + explicit ReadVisitor(BitReader* reader) : VisitorBase(), reader_(reader) {} Status Bits(const size_t bits, const uint32_t /*default_value*/, uint32_t* JXL_RESTRICT value) override { *value = BitsCoder::Read(bits, reader_); if (!reader_->AllReadsWithinBounds()) { 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 @@ -83,11 +83,12 @@ // Returns pointer to the start of a row. JXL_INLINE void* VoidRow(const size_t y) const { #if defined(ADDRESS_SANITIZER) || defined(MEMORY_SANITIZER) || \ defined(THREAD_SANITIZER) if (y >= ysize_) { - JXL_ABORT("Row(%" PRIu64 ") in (%u x %u) image\n", y, xsize_, ysize_); + JXL_ABORT("Row(%" PRIu64 ") in (%u x %u) image\n", (uint64_t)y, xsize_, + ysize_); } #endif void* row = bytes_.get() + y * bytes_per_row_; return JXL_ASSUME_ALIGNED(row, 64); diff --git a/third_party/jpeg-xl/lib/jxl/image_metadata.h b/third_party/jpeg-xl/lib/jxl/image_metadata.h --- a/third_party/jpeg-xl/lib/jxl/image_metadata.h +++ b/third_party/jpeg-xl/lib/jxl/image_metadata.h @@ -2,19 +2,21 @@ // // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // Main codestream header bundles, the metadata that applies to all frames. +// Enums must align with the C API definitions in codestream_header.h. #ifndef LIB_JXL_IMAGE_METADATA_H_ #define LIB_JXL_IMAGE_METADATA_H_ #include #include #include +#include "jxl/codestream_header.h" #include "lib/jxl/color_encoding_internal.h" #include "lib/jxl/fields.h" #include "lib/jxl/headers.h" #include "lib/jxl/jpeg/jpeg_data.h" #include "lib/jxl/opsin_params.h" @@ -24,41 +26,43 @@ // EXIF orientation of the image. This field overrides any field present in // actual EXIF metadata. The value tells which transformation the decoder must // apply after decoding to display the image with the correct orientation. enum class Orientation : uint32_t { // Values 1..8 match the EXIF definitions. - kIdentity = 1, - kFlipHorizontal, - kRotate180, - kFlipVertical, - kTranspose, - kRotate90, - kAntiTranspose, - kRotate270, + kIdentity = JXL_ORIENT_IDENTITY, + kFlipHorizontal = JXL_ORIENT_FLIP_HORIZONTAL, + kRotate180 = JXL_ORIENT_ROTATE_180, + kFlipVertical = JXL_ORIENT_FLIP_VERTICAL, + kTranspose = JXL_ORIENT_TRANSPOSE, + kRotate90 = JXL_ORIENT_ROTATE_90_CW, + kAntiTranspose = JXL_ORIENT_ANTI_TRANSPOSE, + kRotate270 = JXL_ORIENT_ROTATE_90_CCW, }; // Don't need an EnumBits because Orientation is not read via Enum(). enum class ExtraChannel : uint32_t { // First two enumerators (most common) are cheaper to encode - kAlpha, - kDepth, + kAlpha = JXL_CHANNEL_ALPHA, + kDepth = JXL_CHANNEL_DEPTH, - kSpotColor, - kSelectionMask, - kBlack, // for CMYK - kCFA, // Bayer channel - kThermal, - kReserved0, - kReserved1, - kReserved2, - kReserved3, - kReserved4, - kReserved5, - kReserved6, - kReserved7, - kUnknown, // disambiguated via name string, raise warning if unsupported - kOptional // like kUnknown but can silently be ignored + kSpotColor = JXL_CHANNEL_SPOT_COLOR, + kSelectionMask = JXL_CHANNEL_SELECTION_MASK, + kBlack = JXL_CHANNEL_BLACK, // for CMYK + kCFA = JXL_CHANNEL_CFA, // Bayer channel + kThermal = JXL_CHANNEL_THERMAL, + kReserved0 = JXL_CHANNEL_RESERVED0, + kReserved1 = JXL_CHANNEL_RESERVED1, + kReserved2 = JXL_CHANNEL_RESERVED2, + kReserved3 = JXL_CHANNEL_RESERVED3, + kReserved4 = JXL_CHANNEL_RESERVED4, + kReserved5 = JXL_CHANNEL_RESERVED5, + kReserved6 = JXL_CHANNEL_RESERVED6, + kReserved7 = JXL_CHANNEL_RESERVED7, + // disambiguated via name string, raise warning if unsupported + kUnknown = JXL_CHANNEL_UNKNOWN, + // like kUnknown but can silently be ignored + kOptional = JXL_CHANNEL_OPTIONAL }; static inline const char* EnumName(ExtraChannel /*unused*/) { return "ExtraChannel"; } static inline constexpr uint64_t EnumBits(ExtraChannel /*unused*/) { 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 @@ -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.32f)); + IsSlightlyBelow(0.2f)); } 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.32f)); + IsSlightlyBelow(0.2f)); } // Test header encoding of original bits per sample TEST(JxlTest, RoundtripImageBundleOriginalBits) { ThreadPool* pool = nullptr; diff --git a/third_party/jpeg-xl/lib/jxl/luminance.h b/third_party/jpeg-xl/lib/jxl/luminance.h --- a/third_party/jpeg-xl/lib/jxl/luminance.h +++ b/third_party/jpeg-xl/lib/jxl/luminance.h @@ -9,13 +9,15 @@ namespace jxl { // Chooses a default intensity target based on the transfer function of the // image, if known. For SDR images or images not known to be HDR, returns // kDefaultIntensityTarget, for images known to have PQ or HLG transfer function -// returns a higher value. If the image metadata already has a non-zero -// intensity target, does nothing. +// returns a higher value. class CodecInOut; void SetIntensityTarget(CodecInOut* io); +struct ImageMetadata; +void SetIntensityTarget(ImageMetadata* m); + } // namespace jxl #endif // LIB_JXL_LUMINANCE_H_ diff --git a/third_party/jpeg-xl/lib/jxl/luminance.cc b/third_party/jpeg-xl/lib/jxl/luminance.cc --- a/third_party/jpeg-xl/lib/jxl/luminance.cc +++ b/third_party/jpeg-xl/lib/jxl/luminance.cc @@ -4,24 +4,25 @@ // license that can be found in the LICENSE file. #include "lib/jxl/luminance.h" #include "lib/jxl/codec_in_out.h" -#include "lib/jxl/color_encoding_internal.h" namespace jxl { -void SetIntensityTarget(CodecInOut* io) { - if (io->metadata.m.color_encoding.tf.IsPQ()) { +void SetIntensityTarget(CodecInOut* io) { SetIntensityTarget(&io->metadata.m); } + +void SetIntensityTarget(ImageMetadata* m) { + if (m->color_encoding.tf.IsPQ()) { // Peak luminance of PQ as defined by SMPTE ST 2084:2014. - io->metadata.m.SetIntensityTarget(10000); - } else if (io->metadata.m.color_encoding.tf.IsHLG()) { + m->SetIntensityTarget(10000); + } else if (m->color_encoding.tf.IsHLG()) { // Nominal display peak luminance used as a reference by // Rec. ITU-R BT.2100-2. - io->metadata.m.SetIntensityTarget(1000); + m->SetIntensityTarget(1000); } else { // SDR - io->metadata.m.SetIntensityTarget(kDefaultIntensityTarget); + m->SetIntensityTarget(kDefaultIntensityTarget); } } } // namespace jxl diff --git a/third_party/jpeg-xl/lib/jxl/modular/modular_image.cc b/third_party/jpeg-xl/lib/jxl/modular/modular_image.cc --- a/third_party/jpeg-xl/lib/jxl/modular/modular_image.cc +++ b/third_party/jpeg-xl/lib/jxl/modular/modular_image.cc @@ -25,12 +25,12 @@ JXL_DEBUG_V(8, "Undoing transform: done"); transform.pop_back(); } } -Image::Image(size_t iw, size_t ih, int bd, int nb_chans) - : w(iw), h(ih), bitdepth(bd), nb_meta_channels(0), error(false) { +Image::Image(size_t iw, size_t ih, int bitdepth, int nb_chans) + : w(iw), h(ih), bitdepth(bitdepth), nb_meta_channels(0), error(false) { for (int i = 0; i < nb_chans; i++) channel.emplace_back(Channel(iw, ih)); } Image::Image() : w(0), h(0), bitdepth(8), nb_meta_channels(0), error(true) {} diff --git a/third_party/jpeg-xl/lib/jxl/modular/options.h b/third_party/jpeg-xl/lib/jxl/modular/options.h --- a/third_party/jpeg-xl/lib/jxl/modular/options.h +++ b/third_party/jpeg-xl/lib/jxl/modular/options.h @@ -96,10 +96,11 @@ // Kind of tree to use. // TODO(veluca): add tree kinds for JPEG recompression with CfL enabled, // general AC metadata, different DC qualities, and others. enum class TreeKind { + kTrivialTreeNoPredictor, kLearn, kJpegTranscodeACMeta, kFalconACMeta, kACMeta, kWPFixedDC, 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 @@ -551,22 +551,20 @@ 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; - bool status; - if ((lossy || nb != 1) && - input.bitdepth >= 8) { // if no channel palette special case - status = FwdPaletteIteration(input, begin_c, end_c, nb_colors, nb_deltas, - ordered, lossy, predictor, wp_header, - palette_iteration_data); + // if no channel palette special case + if ((lossy || nb != 1) && 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)); } palette_iteration_data.final_run = true; nb_colors = nb_colors_orig; nb_deltas = nb_deltas_orig; - status = - FwdPaletteIteration(input, begin_c, end_c, nb_colors, nb_deltas, ordered, - lossy, predictor, wp_header, palette_iteration_data); - return status; + return FwdPaletteIteration(input, begin_c, end_c, nb_colors, nb_deltas, + ordered, lossy, predictor, wp_header, + palette_iteration_data); } } // namespace jxl diff --git a/third_party/jpeg-xl/lib/jxl/modular/transform/rct.cc b/third_party/jpeg-xl/lib/jxl/modular/transform/rct.cc --- a/third_party/jpeg-xl/lib/jxl/modular/transform/rct.cc +++ b/third_party/jpeg-xl/lib/jxl/modular/transform/rct.cc @@ -2,22 +2,60 @@ // // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. #include "lib/jxl/modular/transform/rct.h" +#undef HWY_TARGET_INCLUDE +#define HWY_TARGET_INCLUDE "lib/jxl/modular/transform/rct.cc" +#include +#include +HWY_BEFORE_NAMESPACE(); +namespace jxl { +namespace HWY_NAMESPACE { -namespace jxl { +using hwy::HWY_NAMESPACE::ShiftRight; template void InvRCTRow(const pixel_type* in0, const pixel_type* in1, const pixel_type* in2, pixel_type* out0, pixel_type* out1, pixel_type* out2, size_t w) { static_assert(transform_type >= 0 && transform_type < 7, "Invalid transform type"); int second = transform_type >> 1; int third = transform_type & 1; - for (size_t x = 0; x < w; x++) { + + size_t x = 0; + const HWY_FULL(pixel_type) d; + const size_t N = Lanes(d); + for (; x + N - 1 < w; x += N) { + if (transform_type == 6) { + auto Y = Load(d, in0 + x); + auto Co = Load(d, in1 + x); + auto Cg = Load(d, in2 + x); + Y -= ShiftRight<1>(Cg); + auto G = Cg + Y; + Y -= ShiftRight<1>(Co); + auto R = Y + Co; + Store(R, d, out0 + x); + Store(G, d, out1 + x); + Store(Y, d, out2 + x); + } else { + auto First = Load(d, in0 + x); + auto Second = Load(d, in1 + x); + auto Third = Load(d, in2 + x); + if (third) Third += First; + if (second == 1) { + Second += First; + } else if (second == 2) { + Second += ShiftRight<1>(First + Third); + } + Store(First, d, out0 + x); + Store(Second, d, out1 + x); + Store(Third, d, out2 + x); + } + } + for (; x < w; x++) { if (transform_type == 6) { pixel_type Y = in0[x]; pixel_type Co = in1[x]; pixel_type Cg = in2[x]; pixel_type tmp = PixelAdd(Y, -(Cg >> 1)); @@ -94,6 +132,19 @@ }, "InvRCT")); return true; } +} // namespace HWY_NAMESPACE } // namespace jxl +HWY_AFTER_NAMESPACE(); + +#if HWY_ONCE +namespace jxl { + +HWY_EXPORT(InvRCT); +Status InvRCT(Image& input, size_t begin_c, size_t rct_type, ThreadPool* pool) { + return HWY_DYNAMIC_DISPATCH(InvRCT)(input, begin_c, rct_type, pool); +} + +} // namespace jxl +#endif 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 @@ -245,13 +245,14 @@ uint32_t offset; if (beginc < image.nb_meta_channels) { if (endc >= image.nb_meta_channels) { return JXL_FAILURE("Invalid squeeze: mix of meta and nonmeta channels"); } - if (!in_place) + if (!in_place) { return JXL_FAILURE( "Invalid squeeze: meta channels require in-place residuals"); + } image.nb_meta_channels += (*parameters)[i].num_c; } if (in_place) { offset = endc + 1; } else { diff --git a/third_party/jpeg-xl/lib/jxl/noise.h b/third_party/jpeg-xl/lib/jxl/noise.h --- a/third_party/jpeg-xl/lib/jxl/noise.h +++ b/third_party/jpeg-xl/lib/jxl/noise.h @@ -41,12 +41,12 @@ // TODO: instead of 1, this should be a proper Y range. constexpr float kScale = kScaleNumerator / 1; float scaled_x = std::max(0.f, x * kScale); float floor_x; float frac_x = std::modf(scaled_x, &floor_x); - if (JXL_UNLIKELY(scaled_x >= kScaleNumerator)) { - floor_x = kScaleNumerator - 1.f; + if (JXL_UNLIKELY(scaled_x >= kScaleNumerator + 1)) { + floor_x = kScaleNumerator; frac_x = 1.f; } return std::make_pair(static_cast(floor_x), frac_x); } 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 @@ -50,34 +50,23 @@ CompressParams cparams; cparams.butteraugli_distance = 2.0; cparams.speed_tier = SpeedTier::kSquirrel; DecompressParams dparams; - dparams.preview = Override::kOff; - CodecInOut io2; Roundtrip(&io, cparams, dparams, pool, &io2); - EXPECT_LE(ButteraugliDistance(io, io2, cparams.ba_params, GetJxlCms(), - /*distmap=*/nullptr, pool), - 2.5); - EXPECT_EQ(0u, io2.preview_frame.xsize()); - - dparams.preview = Override::kOn; + EXPECT_EQ(preview_xsize, io2.metadata.m.preview_size.xsize()); + EXPECT_EQ(preview_ysize, io2.metadata.m.preview_size.ysize()); + EXPECT_EQ(preview_xsize, io2.preview_frame.xsize()); + EXPECT_EQ(preview_ysize, io2.preview_frame.ysize()); - CodecInOut io3; - Roundtrip(&io, cparams, dparams, pool, &io3); - EXPECT_EQ(preview_xsize, io3.metadata.m.preview_size.xsize()); - EXPECT_EQ(preview_ysize, io3.metadata.m.preview_size.ysize()); - EXPECT_EQ(preview_xsize, io3.preview_frame.xsize()); - EXPECT_EQ(preview_ysize, io3.preview_frame.ysize()); - - EXPECT_LE(ButteraugliDistance(io.preview_frame, io3.preview_frame, + EXPECT_LE(ButteraugliDistance(io.preview_frame, io2.preview_frame, cparams.ba_params, GetJxlCms(), /*distmap=*/nullptr, pool), 2.5); EXPECT_LE( - ButteraugliDistance(io.Main(), io3.Main(), cparams.ba_params, GetJxlCms(), + ButteraugliDistance(io.Main(), io2.Main(), cparams.ba_params, GetJxlCms(), /*distmap=*/nullptr, pool), 2.5); } } // namespace diff --git a/third_party/jpeg-xl/lib/jxl/quant_weights.h b/third_party/jpeg-xl/lib/jxl/quant_weights.h --- a/third_party/jpeg-xl/lib/jxl/quant_weights.h +++ b/third_party/jpeg-xl/lib/jxl/quant_weights.h @@ -421,11 +421,11 @@ static constexpr size_t required_size_y[] = {1, 1, 1, 1, 2, 4, 2, 4, 4, 1, 1, 8, 8, 16, 16, 32, 32}; static_assert(kNum == sizeof(required_size_y) / sizeof(*required_size_y), "Update this array when adding or removing quant tables."); - Status EnsureComputed(uint32_t kind_mask); + Status EnsureComputed(uint32_t acs_mask); private: static constexpr size_t required_size_[] = { 1, 1, 1, 1, 4, 16, 2, 4, 8, 1, 1, 64, 32, 256, 128, 1024, 512}; static_assert(kNum == sizeof(required_size_) / sizeof(*required_size_), 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 @@ -6,24 +6,106 @@ #ifndef LIB_JXL_RENDER_PIPELINE_LOW_MEMORY_RENDER_PIPELINE_H_ #define LIB_JXL_RENDER_PIPELINE_LOW_MEMORY_RENDER_PIPELINE_H_ #include -#include "lib/jxl/filters.h" +#include "lib/jxl/dec_group_border.h" #include "lib/jxl/render_pipeline/render_pipeline.h" namespace jxl { // A multithreaded, low-memory rendering pipeline that only allocates a minimal // amount of buffers. -class LowMemoryRenderPipeline : public RenderPipeline { +class LowMemoryRenderPipeline final : public RenderPipeline { + private: std::vector> PrepareBuffers( size_t group_id, size_t thread_id) override; - void PrepareForThreadsInternal(size_t num) override; + void PrepareForThreadsInternal(size_t num, bool use_group_ids) override; void ProcessBuffers(size_t group_id, size_t thread_id) override; + + void ClearDone(size_t i) override { group_border_assigner_.ClearDone(i); } + + void Init() override; + + void EnsureBordersStorage(); + size_t GroupInputXSize(size_t c) const; + size_t GroupInputYSize(size_t c) const; + void RenderRect(size_t thread_id, std::vector& input_data, + Rect data_max_color_channel_rect, + Rect image_max_color_channel_rect); + void RenderPadding(size_t thread_id, Rect rect); + + void SaveBorders(size_t group_id, size_t c, const ImageF& in); + void LoadBorders(size_t group_id, size_t c, const Rect& r, ImageF* out); + + std::pair ColorDimensionsToChannelDimensions( + std::pair in, size_t c, size_t stage) const; + + std::pair BorderToStore(size_t c) const; + + bool use_group_ids_; + + // 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_; + + // Size (in color-channel-pixels) of the border around each group that might + // be assigned to that group. + std::pair group_border_; + // base_color_shift_ defines the size of groups in terms of final image + // pixels. + size_t base_color_shift_; + + // Buffer for decoded pixel data for a group, indexed by [thread][channel] or + // [group][channel] depending on `use_group_ids_`. + std::vector> group_data_; + + // Borders for storing group data. + size_t group_data_x_border_; + size_t group_data_y_border_; + + // Buffers for intermediate rows for the various stages, indexed by + // [thread][channel][stage]. + std::vector>> stage_data_; + + // Buffers for out-of-frame data, indexed by [thread]; every row is a + // different channel. + std::vector out_of_frame_data_; + + // For each stage, a non-kIgnored channel. + std::vector anyc_; + + // Size of the image at each stage. + std::vector image_rect_; + + // For each stage, for each channel, keep track of the kInOut stage that + // produced the input to that stage (which corresponds to the buffer index + // containing the data). -1 if data comes from the original input. + std::vector> stage_input_for_channel_; + + // Number of (virtual) extra rows that must be processed at each stage + // to produce sufficient output for future stages. + std::vector virtual_ypadding_for_output_; + + // Same thing for columns, except these are real columns and not virtual ones. + std::vector xpadding_for_output_; + + // First stage that doesn't have any kInOut channel. + size_t first_trailing_stage_; + + // Origin and size of the frame after switching to image dimensions. + FrameOrigin frame_origin_; + size_t full_image_xsize_; + size_t full_image_ysize_; + size_t first_image_dim_stage_; }; } // namespace jxl #endif // LIB_JXL_RENDER_PIPELINE_LOW_MEMORY_RENDER_PIPELINE_H_ 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 @@ -3,21 +3,789 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. #include "lib/jxl/render_pipeline/low_memory_render_pipeline.h" +#include +#include +#include + +#include "lib/jxl/aux_out.h" +#include "lib/jxl/base/arch_macros.h" + namespace jxl { -void LowMemoryRenderPipeline::PrepareForThreadsInternal(size_t num) { - JXL_ABORT("Not implemented"); +std::pair +LowMemoryRenderPipeline::ColorDimensionsToChannelDimensions( + std::pair in, size_t c, size_t stage) const { + std::pair ret; + std::pair shift = channel_shifts_[stage][c]; + ret.first = + ((in.first << base_color_shift_) + (1 << shift.first) - 1) >> shift.first; + ret.second = ((in.second << base_color_shift_) + (1 << shift.second) - 1) >> + shift.second; + return ret; +} + +std::pair LowMemoryRenderPipeline::BorderToStore( + size_t c) const { + auto ret = ColorDimensionsToChannelDimensions(group_border_, c, 0); + ret.first += padding_[0][c].first; + ret.second += padding_[0][c].second; + return ret; +} + +void LowMemoryRenderPipeline::SaveBorders(size_t group_id, size_t c, + const ImageF& in) { + size_t gy = group_id / frame_dimensions_.xsize_groups; + size_t gx = group_id % frame_dimensions_.xsize_groups; + size_t hshift = channel_shifts_[0][c].first; + size_t vshift = channel_shifts_[0][c].second; + size_t x0 = gx * GroupInputXSize(c); + size_t x1 = std::min((gx + 1) * GroupInputXSize(c), + DivCeil(frame_dimensions_.xsize_upsampled, 1 << hshift)); + size_t y0 = gy * GroupInputYSize(c); + size_t y1 = std::min((gy + 1) * GroupInputYSize(c), + DivCeil(frame_dimensions_.ysize_upsampled, 1 << vshift)); + + auto borders = BorderToStore(c); + size_t borderx_write = borders.first; + size_t bordery_write = borders.second; + + 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; + size_t gx = group_id % frame_dimensions_.xsize_groups; + size_t hshift = channel_shifts_[0][c].first; + size_t vshift = channel_shifts_[0][c].second; + // Coordinates of the group in the image. + size_t x0 = gx * GroupInputXSize(c); + size_t x1 = std::min((gx + 1) * GroupInputXSize(c), + DivCeil(frame_dimensions_.xsize_upsampled, 1 << hshift)); + size_t y0 = gy * GroupInputYSize(c); + size_t y1 = std::min((gy + 1) * GroupInputYSize(c), + DivCeil(frame_dimensions_.ysize_upsampled, 1 << vshift)); + + size_t paddingx = padding_[0][c].first; + size_t paddingy = padding_[0][c].second; + + auto borders = BorderToStore(c); + size_t borderx_write = borders.first; + size_t bordery_write = borders.second; + + // Limits of the area to copy from, in image coordinates. + JXL_DASSERT(r.x0() == 0 || (r.x0() << base_color_shift_) >= paddingx); + size_t x0src = DivCeil(r.x0() << base_color_shift_, 1 << hshift); + if (x0src != 0) { + x0src -= paddingx; + } + // r may be such that r.x1 (namely x0() + xsize()) is within paddingx of the + // right side of the image, so we use min() here. + size_t x1src = + DivCeil((r.x0() + r.xsize()) << base_color_shift_, 1 << hshift); + x1src = std::min(x1src + paddingx, + DivCeil(frame_dimensions_.xsize_upsampled, 1 << hshift)); + + // Similar computation for y. + JXL_DASSERT(r.y0() == 0 || (r.y0() << base_color_shift_) >= paddingy); + size_t y0src = DivCeil(r.y0() << base_color_shift_, 1 << vshift); + if (y0src != 0) { + y0src -= paddingy; + } + size_t y1src = + DivCeil((r.y0() + r.ysize()) << base_color_shift_, 1 << vshift); + y1src = std::min(y1src + paddingy, + DivCeil(frame_dimensions_.ysize_upsampled, 1 << vshift)); + + // Copy other groups' borders from the border storage. + if (y0src < y0) { + JXL_DASSERT(gy > 0); + CopyImageTo( + 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 + 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 - 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 + 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); + } +} + +size_t LowMemoryRenderPipeline::GroupInputXSize(size_t c) const { + return (frame_dimensions_.group_dim << base_color_shift_) >> + channel_shifts_[0][c].first; +} + +size_t LowMemoryRenderPipeline::GroupInputYSize(size_t c) const { + return (frame_dimensions_.group_dim << base_color_shift_) >> + channel_shifts_[0][c].second; +} + +void LowMemoryRenderPipeline::EnsureBordersStorage() { + const auto& shifts = channel_shifts_[0]; + if (borders_horizontal_.size() < shifts.size()) { + borders_horizontal_.resize(shifts.size()); + borders_vertical_.resize(shifts.size()); + } + for (size_t c = 0; c < shifts.size(); c++) { + auto borders = BorderToStore(c); + size_t borderx = borders.first; + size_t bordery = borders.second; + 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 * num_xborders, downsampled_ysize); + if (!SameSize(vertical, borders_vertical_[c])) { + borders_vertical_[c] = ImageF(vertical.xsize(), vertical.ysize()); + } + } +} + +void LowMemoryRenderPipeline::Init() { + group_border_ = {0, 0}; + base_color_shift_ = CeilLog2Nonzero(frame_dimensions_.xsize_upsampled_padded / + frame_dimensions_.xsize_padded); + + const auto& shifts = channel_shifts_[0]; + + // Ensure that each channel has enough many border pixels. + for (size_t c = 0; c < shifts.size(); c++) { + group_border_.first = + std::max(group_border_.first, + DivCeil(padding_[0][c].first << channel_shifts_[0][c].first, + 1 << base_color_shift_)); + group_border_.second = + std::max(group_border_.second, + DivCeil(padding_[0][c].second << channel_shifts_[0][c].second, + 1 << base_color_shift_)); + } + + // Ensure that all channels have an integer number of border pixels in the + // input. + for (size_t c = 0; c < shifts.size(); c++) { + if (channel_shifts_[0][c].first >= base_color_shift_) { + group_border_.first = + RoundUpTo(group_border_.first, + 1 << (channel_shifts_[0][c].first - base_color_shift_)); + } + if (channel_shifts_[0][c].second >= base_color_shift_) { + group_border_.second = + RoundUpTo(group_border_.second, + 1 << (channel_shifts_[0][c].second - base_color_shift_)); + } + } + // Ensure that the X border on color channels is a multiple of kBlockDim or + // the vector size (required for EPF stages). Vectors on ARM NEON are never + // wider than 4 floats, so rounding to multiples of 4 is enough. +#if JXL_ARCH_ARM + constexpr size_t kGroupXAlign = 4; +#else + constexpr size_t kGroupXAlign = 16; +#endif + group_border_.first = RoundUpTo(group_border_.first, kGroupXAlign); + // Allocate borders in group images that are just enough for storing the + // borders to be copied in, plus any rounding to ensure alignment. + std::pair max_border = {0, 0}; + for (size_t c = 0; c < shifts.size(); c++) { + max_border.first = std::max(BorderToStore(c).first, max_border.first); + max_border.second = std::max(BorderToStore(c).second, max_border.second); + } + group_data_x_border_ = RoundUpTo(max_border.first, kGroupXAlign); + group_data_y_border_ = max_border.second; + + EnsureBordersStorage(); + group_border_assigner_.Init(frame_dimensions_); + + for (first_trailing_stage_ = stages_.size(); first_trailing_stage_ > 0; + first_trailing_stage_--) { + bool has_inout_c = false; + for (size_t c = 0; c < shifts.size(); c++) { + if (stages_[first_trailing_stage_ - 1]->GetChannelMode(c) == + RenderPipelineChannelMode::kInOut) { + has_inout_c = true; + } + } + if (has_inout_c) { + break; + } + } + + first_image_dim_stage_ = stages_.size(); + for (size_t i = 0; i < stages_.size(); i++) { + std::vector> input_sizes(shifts.size()); + for (size_t c = 0; c < shifts.size(); c++) { + input_sizes[c] = + std::make_pair(DivCeil(frame_dimensions_.xsize_upsampled, + 1 << channel_shifts_[i][c].first), + DivCeil(frame_dimensions_.ysize_upsampled, + 1 << channel_shifts_[i][c].second)); + } + stages_[i]->SetInputSizes(input_sizes); + if (stages_[i]->SwitchToImageDimensions()) { + // We don't allow kInOut after switching to image dimensions. + JXL_ASSERT(i >= first_trailing_stage_); + first_image_dim_stage_ = i + 1; + stages_[i]->GetImageDimensions(&full_image_xsize_, &full_image_ysize_, + &frame_origin_); + break; + } + } + for (size_t i = first_image_dim_stage_; i < stages_.size(); i++) { + if (stages_[i]->SwitchToImageDimensions()) { + JXL_ABORT("Cannot switch to image dimensions multiple times"); + } + std::vector> input_sizes(shifts.size()); + for (size_t c = 0; c < shifts.size(); c++) { + input_sizes[c] = {full_image_xsize_, full_image_ysize_}; + } + stages_[i]->SetInputSizes(input_sizes); + } + + anyc_.resize(stages_.size()); + for (size_t i = 0; i < stages_.size(); i++) { + for (size_t c = 0; c < shifts.size(); c++) { + if (stages_[i]->GetChannelMode(c) != + RenderPipelineChannelMode::kIgnored) { + anyc_[i] = c; + } + } + } + + stage_input_for_channel_ = std::vector>( + stages_.size(), std::vector(shifts.size())); + for (size_t c = 0; c < shifts.size(); c++) { + int input = -1; + for (size_t i = 0; i < stages_.size(); i++) { + stage_input_for_channel_[i][c] = input; + if (stages_[i]->GetChannelMode(c) == RenderPipelineChannelMode::kInOut) { + input = i; + } + } + } + + image_rect_.resize(stages_.size()); + for (size_t i = 0; i < stages_.size(); i++) { + size_t x1 = DivCeil(frame_dimensions_.xsize_upsampled, + 1 << channel_shifts_[i][anyc_[i]].first); + size_t y1 = DivCeil(frame_dimensions_.ysize_upsampled, + 1 << channel_shifts_[i][anyc_[i]].second); + image_rect_[i] = Rect(0, 0, x1, y1); + } + + virtual_ypadding_for_output_.resize(stages_.size()); + xpadding_for_output_.resize(stages_.size()); + for (size_t c = 0; c < shifts.size(); c++) { + int ypad = 0; + int xpad = 0; + for (size_t i = stages_.size(); i-- > 0;) { + if (stages_[i]->GetChannelMode(c) != + RenderPipelineChannelMode::kIgnored) { + virtual_ypadding_for_output_[i] = + std::max(ypad, virtual_ypadding_for_output_[i]); + xpadding_for_output_[i] = std::max(xpad, xpadding_for_output_[i]); + } + if (stages_[i]->GetChannelMode(c) == RenderPipelineChannelMode::kInOut) { + ypad = (DivCeil(ypad, 1 << channel_shifts_[i][c].second) + + stages_[i]->settings_.border_y) + << channel_shifts_[i][c].second; + xpad = DivCeil(xpad, 1 << stages_[i]->settings_.shift_x) + + stages_[i]->settings_.border_x; + } + } + } +} + +void LowMemoryRenderPipeline::PrepareForThreadsInternal(size_t num, + bool use_group_ids) { + const auto& shifts = channel_shifts_[0]; + + use_group_ids_ = use_group_ids; + size_t num_buffers = use_group_ids_ ? frame_dimensions_.num_groups : num; + for (size_t t = group_data_.size(); t < num_buffers; t++) { + group_data_.emplace_back(); + group_data_[t].resize(shifts.size()); + for (size_t c = 0; c < shifts.size(); c++) { + group_data_[t][c] = ImageF(GroupInputXSize(c) + group_data_x_border_ * 2, + 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 + 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; + for (size_t i = stages_.size(); i-- > 0;) { + if (stages_[i]->GetChannelMode(c) == + RenderPipelineChannelMode::kInOut) { + size_t stage_buffer_ysize = + 2 * next_y_border + (1 << stages_[i]->settings_.shift_y); + stage_buffer_ysize = 1 << CeilLog2Nonzero(stage_buffer_ysize); + next_y_border = stages_[i]->settings_.border_y; + stage_data_[t][c][i] = ImageF(stage_buffer_xsize, stage_buffer_ysize); + } + } + } + } + if (first_image_dim_stage_ != stages_.size()) { + out_of_frame_data_.resize(num); + 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); + for (size_t t = 0; t < num; t++) { + out_of_frame_data_[t] = ImageF(out_of_frame_xsize, shifts.size()); + } + } } std::vector> LowMemoryRenderPipeline::PrepareBuffers( size_t group_id, size_t thread_id) { - JXL_ABORT("Not implemented"); - return {}; + std::vector> ret(channel_shifts_[0].size()); + const size_t gx = group_id % frame_dimensions_.xsize_groups; + const size_t gy = group_id / frame_dimensions_.xsize_groups; + for (size_t c = 0; c < channel_shifts_[0].size(); c++) { + ret[c].first = &group_data_[use_group_ids_ ? group_id : thread_id][c]; + ret[c].second = Rect(group_data_x_border_, group_data_y_border_, + GroupInputXSize(c), GroupInputYSize(c), + DivCeil(frame_dimensions_.xsize_upsampled, + 1 << channel_shifts_[0][c].first) - + gx * GroupInputXSize(c) + group_data_x_border_, + DivCeil(frame_dimensions_.ysize_upsampled, + 1 << channel_shifts_[0][c].second) - + gy * GroupInputYSize(c) + group_data_y_border_); + } + return ret; +} + +namespace { + +JXL_INLINE int GetMirroredY(int y, ssize_t group_y0, ssize_t image_ysize) { + if (group_y0 == 0 && (y < 0 || y + group_y0 >= image_ysize)) { + return Mirror(y, image_ysize); + } + if (y + group_y0 >= image_ysize) { + // Here we know that the one mirroring step is sufficient. + return 2 * image_ysize - (y + group_y0) - 1 - group_y0; + } + return y; +} + +JXL_INLINE void ApplyXMirroring(float* row, ssize_t borderx, ssize_t group_x0, + ssize_t group_xsize, ssize_t image_xsize) { + if (image_xsize <= borderx) { + if (group_x0 == 0) { + for (ssize_t ix = 0; ix < borderx; ix++) { + row[kRenderPipelineXOffset - ix - 1] = + row[kRenderPipelineXOffset + Mirror(-ix - 1, image_xsize)]; + } + } + if (group_xsize + borderx + group_x0 >= image_xsize) { + for (ssize_t ix = 0; ix < borderx; ix++) { + row[kRenderPipelineXOffset + image_xsize + ix - group_x0] = + row[kRenderPipelineXOffset + Mirror(image_xsize + ix, image_xsize) - + group_x0]; + } + } + } else { + // Here we know that the one mirroring step is sufficient. + if (group_x0 == 0) { + for (ssize_t ix = 0; ix < borderx; ix++) { + row[kRenderPipelineXOffset - ix - 1] = row[kRenderPipelineXOffset + ix]; + } + } + if (group_xsize + borderx + group_x0 >= image_xsize) { + for (ssize_t ix = 0; ix < borderx; ix++) { + row[kRenderPipelineXOffset + image_xsize - group_x0 + ix] = + row[kRenderPipelineXOffset + image_xsize - group_x0 - ix - 1]; + } + } + } +} + +} // namespace + +void LowMemoryRenderPipeline::RenderRect(size_t thread_id, + std::vector& input_data, + Rect data_max_color_channel_rect, + Rect image_max_color_channel_rect) { + // For each stage, the rect corresponding to the image area currently being + // processed. + std::vector group_rect; + group_rect.resize(stages_.size()); + for (size_t i = 0; i < stages_.size(); i++) { + size_t x0 = (image_max_color_channel_rect.x0() << base_color_shift_) >> + channel_shifts_[i][anyc_[i]].first; + size_t x1 = DivCeil(std::min(frame_dimensions_.xsize_upsampled, + (image_max_color_channel_rect.x0() + + image_max_color_channel_rect.xsize()) + << base_color_shift_), + 1 << channel_shifts_[i][anyc_[i]].first); + size_t y0 = (image_max_color_channel_rect.y0() << base_color_shift_) >> + channel_shifts_[i][anyc_[i]].second; + size_t y1 = DivCeil(std::min(frame_dimensions_.ysize_upsampled, + (image_max_color_channel_rect.y0() + + image_max_color_channel_rect.ysize()) + << base_color_shift_), + 1 << channel_shifts_[i][anyc_[i]].second); + group_rect[i] = Rect(x0, y0, x1 - x0, y1 - y0); + } + + struct RowInfo { + float* base_ptr; + int ymod_minus_1; + size_t stride; + }; + + std::vector> rows( + stages_.size() + 1, std::vector(input_data.size())); + + for (size_t i = 0; i < stages_.size(); i++) { + for (size_t c = 0; c < input_data.size(); c++) { + if (stages_[i]->GetChannelMode(c) == RenderPipelineChannelMode::kInOut) { + rows[i + 1][c].ymod_minus_1 = stage_data_[thread_id][c][i].ysize() - 1; + rows[i + 1][c].base_ptr = stage_data_[thread_id][c][i].Row(0); + rows[i + 1][c].stride = stage_data_[thread_id][c][i].PixelsPerRow(); + } + } + } + + for (size_t c = 0; c < input_data.size(); c++) { + int xoff = int(data_max_color_channel_rect.x0()) - + int(LowMemoryRenderPipeline::group_data_x_border_); + xoff = xoff * (1 << base_color_shift_) >> channel_shifts_[0][c].first; + xoff += LowMemoryRenderPipeline::group_data_x_border_; + int yoff = int(data_max_color_channel_rect.y0()) - + int(LowMemoryRenderPipeline::group_data_y_border_); + yoff = yoff * (1 << base_color_shift_) >> channel_shifts_[0][c].second; + yoff += LowMemoryRenderPipeline::group_data_y_border_; + rows[0][c].base_ptr = + input_data[c].Row(yoff) + xoff - kRenderPipelineXOffset; + rows[0][c].stride = input_data[c].PixelsPerRow(); + rows[0][c].ymod_minus_1 = -1; + } + + auto get_row_buffer = [&](int stage, int y, size_t c) { + const RowInfo& info = rows[stage + 1][c]; + return info.base_ptr + ssize_t(info.stride) * (y & info.ymod_minus_1); + }; + + std::vector input_rows(first_trailing_stage_ + + 1); + for (size_t i = 0; i < first_trailing_stage_; i++) { + input_rows[i].resize(input_data.size()); + } + input_rows[first_trailing_stage_].resize(input_data.size(), + std::vector(1)); + + // Maximum possible shift is 3. + RenderPipelineStage::RowInfo output_rows(input_data.size(), + std::vector(8)); + + // We pretend that every stage has a vertical shift of 0, i.e. it is as tall + // as the final image. + // We call each such row a "virtual" row, because it may or may not correspond + // to an actual row of the current processing stage; actual processing happens + // when vy % (1<settings_.border_y; + size_t shifty = stages_[i]->settings_.shift_y; + int y = stage_vy >> channel_shifts_[i][anyc_[i]].second; + + ssize_t image_y = ssize_t(group_rect[i].y0()) + y; + // Do not produce rows in out-of-bounds areas. + if (image_y < 0 || image_y >= ssize_t(image_rect_[i].ysize())) { + continue; + } + + // Get the actual input rows and potentially apply mirroring. + for (size_t c = 0; c < input_data.size(); c++) { + RenderPipelineChannelMode mode = stages_[i]->GetChannelMode(c); + if (mode == RenderPipelineChannelMode::kIgnored) { + continue; + } + auto make_row = [&](ssize_t iy) { + size_t mirrored_y = GetMirroredY(y + iy - bordery, group_rect[i].y0(), + image_rect_[i].ysize()); + input_rows[i][c][iy] = + get_row_buffer(stage_input_for_channel_[i][c], mirrored_y, c); + ApplyXMirroring(input_rows[i][c][iy], stages_[i]->settings_.border_x, + group_rect[i].x0(), group_rect[i].xsize(), + image_rect_[i].xsize()); + }; + // If we already have rows from a previous iteration, we can just shift + // the rows by 1 and insert the new one. + if (input_rows[i][c].size() == 2 * size_t(bordery) + 1) { + for (ssize_t iy = 0; iy < 2 * bordery; iy++) { + input_rows[i][c][iy] = input_rows[i][c][iy + 1]; + } + make_row(bordery * 2); + } else { + input_rows[i][c].resize(2 * bordery + 1); + for (ssize_t iy = 0; iy < 2 * bordery + 1; iy++) { + make_row(iy); + } + } + + // If necessary, get the output buffers. + if (mode == RenderPipelineChannelMode::kInOut) { + for (size_t iy = 0; iy < (1u << shifty); iy++) { + output_rows[c][iy] = get_row_buffer(i, y * (1 << shifty) + iy, c); + } + } + } + // Produce output rows. + stages_[i]->ProcessRow( + input_rows[i], output_rows, xpadding_for_output_[i], + group_rect[i].xsize(), group_rect[i].x0(), group_rect[i].y0() + y, + reinterpret_cast(temp_buffers_[thread_id].get())); + } + + // Process trailing stages, i.e. the final set of non-kInOut stages; they + // all have the same input buffer and no need to use any mirroring. + + int y = vy - num_extra_rows; + if (y < 0 || y >= ssize_t(frame_dimensions_.ysize_upsampled)) continue; + + for (size_t c = 0; c < input_data.size(); c++) { + input_rows[first_trailing_stage_][c][0] = get_row_buffer( + stage_input_for_channel_[first_trailing_stage_][c], y, c); + } + + for (size_t i = first_trailing_stage_; i < first_image_dim_stage_; i++) { + stages_[i]->ProcessRow( + input_rows[first_trailing_stage_], output_rows, + /*xextra=*/0, group_rect[i].xsize(), group_rect[i].x0(), + group_rect[i].y0() + y, + reinterpret_cast(temp_buffers_[thread_id].get())); + } + + if (first_image_dim_stage_ == stages_.size()) continue; + + ssize_t full_image_y = + y + frame_origin_.y0 + group_rect[first_image_dim_stage_].y0(); + if (full_image_y < 0 || full_image_y >= ssize_t(full_image_ysize_)) { + continue; + } + + ssize_t full_image_x0 = + frame_origin_.x0 + group_rect[first_image_dim_stage_].x0(); + + if (full_image_x0 < 0) { + // Skip pixels. + for (size_t c = 0; c < input_data.size(); c++) { + input_rows[first_trailing_stage_][c][0] -= full_image_x0; + } + full_image_x0 = 0; + } + ssize_t full_image_x1 = frame_origin_.x0 + + group_rect[first_image_dim_stage_].x0() + + group_rect[first_image_dim_stage_].xsize(); + full_image_x1 = std::min(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())); + } + } +} + +void LowMemoryRenderPipeline::RenderPadding(size_t thread_id, Rect rect) { + if (rect.xsize() == 0) return; + size_t numc = channel_shifts_[0].size(); + RenderPipelineStage::RowInfo input_rows(numc, std::vector(1)); + RenderPipelineStage::RowInfo output_rows; + + for (size_t c = 0; c < numc; c++) { + input_rows[c][0] = out_of_frame_data_[thread_id].Row(c); + } + + 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())); + } + } } void LowMemoryRenderPipeline::ProcessBuffers(size_t group_id, size_t thread_id) { - JXL_ABORT("Not implemented"); + std::vector& input_data = + group_data_[use_group_ids_ ? group_id : thread_id]; + + // Copy the group borders to the border storage. + for (size_t c = 0; c < input_data.size(); c++) { + SaveBorders(group_id, c, input_data[c]); + } + + 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); + + 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; + group_border_assigner_.GroupDone(group_id, group_border_.first, + group_border_.second, ready_rects, + &num_ready_rects); + for (size_t i = 0; i < num_ready_rects; i++) { + const Rect& image_max_color_channel_rect = ready_rects[i]; + for (size_t c = 0; c < input_data.size(); c++) { + LoadBorders(group_id, c, image_max_color_channel_rect, &input_data[c]); + } + Rect data_max_color_channel_rect( + group_data_x_border_ + image_max_color_channel_rect.x0() - + gx * frame_dimensions_.group_dim, + group_data_y_border_ + image_max_color_channel_rect.y0() - + gy * frame_dimensions_.group_dim, + image_max_color_channel_rect.xsize(), + image_max_color_channel_rect.ysize()); + RenderRect(thread_id, input_data, data_max_color_channel_rect, + image_max_color_channel_rect); + } } } // namespace jxl 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 @@ -6,11 +6,11 @@ #ifndef LIB_JXL_RENDER_PIPELINE_RENDER_PIPELINE_H_ #define LIB_JXL_RENDER_PIPELINE_RENDER_PIPELINE_H_ #include -#include "lib/jxl/filters.h" +#include "lib/jxl/image.h" #include "lib/jxl/render_pipeline/render_pipeline_stage.h" namespace jxl { // Interface to provide input to the rendering pipeline. When this object is @@ -59,24 +59,19 @@ // Enables using the simple (i.e. non-memory-efficient) implementation of // the pipeline. void UseSimpleImplementation() { use_simple_implementation_ = true; } - // Marks the 3 last channels as being noise input channels, which use the - // same size as color channels. - void UsesNoise() { uses_noise_ = true; } - // Finalizes setup of the pipeline. Shifts for all channels should be 0 at // this point. std::unique_ptr Finalize( FrameDimensions frame_dimensions) &&; private: std::vector> stages_; size_t num_c_; bool use_simple_implementation_ = false; - bool uses_noise_ = false; }; friend class Builder; virtual ~RenderPipeline() = default; @@ -86,12 +81,15 @@ JXL_RETURN_IF_ERROR(stage->IsInitialized()); } return true; } - // Allocates storage to run with `num` threads. - void PrepareForThreads(size_t num); + // 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); // 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. @@ -100,18 +98,22 @@ size_t PassesWithAllInput() const { return *std::min_element(group_completed_passes_.begin(), group_completed_passes_.end()); } + virtual void ClearDone(size_t i) {} + protected: std::vector> stages_; // Shifts for every channel at the input of each stage. std::vector>> channel_shifts_; - // Amount of (cumulative) padding required by each stage. - std::vector padding_; + + // Amount of (cumulative) padding required by each stage and channel, in + // either direction. + std::vector>> padding_; + FrameDimensions frame_dimensions_; - bool uses_noise_; std::vector group_completed_passes_; // Indexed by thread_id std::vector temp_buffers_; @@ -127,11 +129,14 @@ virtual void ProcessBuffers(size_t group_id, size_t thread_id) = 0; // Note that this method may be called multiple times with different (or // equal) `num`. - virtual void PrepareForThreadsInternal(size_t num) = 0; + virtual void PrepareForThreadsInternal(size_t num, bool use_group_ids) = 0; + + // Called once frame dimensions and stages are known. + virtual void Init() {} }; } // namespace jxl #endif // LIB_JXL_RENDER_PIPELINE_RENDER_PIPELINE_H_ 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 @@ -34,28 +34,33 @@ if (use_simple_implementation_) { res = jxl::make_unique(); } else { res = jxl::make_unique(); } + res->padding_.resize(stages_.size()); - std::vector channel_border(num_c_); - for (size_t i = stages_.size(); i > 0; i--) { - const auto& stage = stages_[i - 1]; + for (size_t i = stages_.size(); i-- > 0;) { + const auto& stage = stages_[i]; + res->padding_[i].resize(num_c_); + if (i + 1 == stages_.size()) { + continue; + } for (size_t c = 0; c < num_c_; c++) { if (stage->GetChannelMode(c) == RenderPipelineChannelMode::kInOut) { - channel_border[c] = DivCeil( - channel_border[c], - 1 << std::max(stage->settings_.shift_x, stage->settings_.shift_y)); - channel_border[c] += - std::max(stage->settings_.border_x, stage->settings_.border_y); + res->padding_[i][c].first = DivCeil(res->padding_[i + 1][c].first, + 1 << stage->settings_.shift_x) + + stage->settings_.border_x; + res->padding_[i][c].second = DivCeil(res->padding_[i + 1][c].second, + 1 << stage->settings_.shift_y) + + stage->settings_.border_y; + } else { + res->padding_[i][c] = res->padding_[i + 1][c]; } } - res->padding_[i - 1] = - *std::max_element(channel_border.begin(), channel_border.end()); } + res->frame_dimensions_ = frame_dimensions; - res->uses_noise_ = uses_noise_; res->group_completed_passes_.resize(frame_dimensions.num_groups); res->channel_shifts_.resize(stages_.size()); res->channel_shifts_[0].resize(num_c_); for (size_t i = 1; i < stages_.size(); i++) { auto& stage = stages_[i - 1]; @@ -73,14 +78,19 @@ if (stage->GetChannelMode(c) == RenderPipelineChannelMode::kInOut) { res->channel_shifts_[i][c].first = res->channel_shifts_[i - 1][c].first - stage->settings_.shift_x; res->channel_shifts_[i][c].second = res->channel_shifts_[i - 1][c].second - stage->settings_.shift_y; + } else { + res->channel_shifts_[i][c].first = res->channel_shifts_[i - 1][c].first; + res->channel_shifts_[i][c].second = + res->channel_shifts_[i - 1][c].second; } } } res->stages_ = std::move(stages_); + res->Init(); return res; } RenderPipelineInput RenderPipeline::GetInputBuffers(size_t group_id, size_t thread_id) { @@ -104,22 +114,22 @@ } ProcessBuffers(group_id, thread_id); } -void RenderPipeline::PrepareForThreads(size_t num) { +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); } } - PrepareForThreadsInternal(num); + PrepareForThreadsInternal(num, use_group_ids); } 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 @@ -6,17 +6,25 @@ #ifndef LIB_JXL_RENDER_PIPELINE_RENDER_PIPELINE_STAGE_H_ #define LIB_JXL_RENDER_PIPELINE_RENDER_PIPELINE_STAGE_H_ #include -#include "lib/jxl/filters.h" +#include "lib/jxl/base/arch_macros.h" +#include "lib/jxl/frame_header.h" namespace jxl { // The first pixel in the input to RenderPipelineStage will be located at // this position. Pixels before this position may be accessed as padding. +// This should be at least the RoundUpTo(maximum padding / 2, maximum vector +// size) times 2: this is realized when using Gaborish + EPF + upsampling + +// chroma subsampling. +#if JXL_ARCH_ARM constexpr size_t kRenderPipelineXOffset = 16; +#else +constexpr size_t kRenderPipelineXOffset = 32; +#endif enum class RenderPipelineChannelMode { // This channel is not modified by this stage. kIgnored = 0, // This channel is modified in-place. @@ -31,13 +39,13 @@ class RenderPipelineStage { protected: using Row = float*; using ChannelRows = std::vector; - using RowInfo = std::vector; public: + using RowInfo = std::vector; struct Settings { // Amount of padding required in the various directions by all channels // that have kInOut mode. size_t border_x = 0; size_t border_y = 0; @@ -78,13 +86,10 @@ } }; virtual ~RenderPipelineStage() = default; - protected: - virtual Status IsInitialized() const { return true; } - // Processes one row of input, producing the appropriate number of rows of // output. Input/output rows can be obtained by calls to // `GetInputRow`/`GetOutputRow`. `xsize+2*xextra` represents the total number // of pixels to be processed in the input row, where the first pixel is at // position `kRenderPipelineXOffset-xextra`. All pixels in the @@ -98,16 +103,24 @@ // 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; + protected: + explicit RenderPipelineStage(Settings settings) : settings_(settings) {} + + virtual Status IsInitialized() const { return true; } + + // 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) {} + // 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; - explicit RenderPipelineStage(Settings settings) : settings_(settings) {} - // Returns a pointer to the input row of channel `c` with offset `y`. // `y` must be in [-settings_.border_y, settings_.border_y]. `c` must be such // that `GetChannelMode(c) != kIgnored`. The returned pointer points to the // offset-ed row (i.e. kRenderPipelineXOffset has been applied). float* GetInputRow(const RowInfo& input_rows, size_t c, int offset) const { @@ -125,10 +138,28 @@ JXL_DASSERT(GetChannelMode(c) == RenderPipelineChannelMode::kInOut); JXL_DASSERT(offset <= 1ul << settings_.shift_y); return output_rows[c][offset] + kRenderPipelineXOffset; } + // Indicates whether, from this stage on, the pipeline will operate on an + // image- rather than frame-sized buffer. Only one stage in the pipeline + // should return true, and it should implement ProcessPaddingRow below too. + virtual bool SwitchToImageDimensions() const { return false; } + + // If SwitchToImageDimensions returns true, then this should set xsize and + // ysize to the image size, and frame_origin to the location of the frame + // within the image. Otherwise, this is not called at all. + virtual void GetImageDimensions(size_t* xsize, size_t* ysize, + FrameOrigin* frame_origin) const {} + + // Produces the appropriate output data outside of the frame dimensions. xpos + // and ypos are now relative to the full image. + virtual void ProcessPaddingRow(const RowInfo& output_rows, size_t xsize, + size_t xpos, size_t ypos) const {} + + virtual const char* GetName() const = 0; + Settings settings_; friend class RenderPipeline; friend class SimpleRenderPipeline; friend class LowMemoryRenderPipeline; }; 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,11 +47,46 @@ 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); + 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); +} + +TEST(RenderPipelineTest, BuildFast) { + RenderPipeline::Builder builder(/*num_c=*/1); + builder.AddStage(jxl::make_unique()); + builder.AddStage(jxl::make_unique()); + builder.AddStage(jxl::make_unique()); + 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); + std::move(builder).Finalize(frame_dimensions); +} + +TEST(RenderPipelineTest, CallAllGroupsFast) { + RenderPipeline::Builder builder(/*num_c=*/1); + builder.AddStage(jxl::make_unique()); + builder.AddStage(jxl::make_unique()); + builder.AddStage(jxl::make_unique()); + builder.UseSimpleImplementation(); + 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); 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); @@ -143,18 +178,18 @@ #if JXL_HIGH_PRECISION constexpr float kMaxError = 1e-5; #else constexpr float kMaxError = 1e-4; #endif - VerifyRelativeError(*io_default.frames[i].color(), - *io_slow_pipeline.frames[i].color(), kMaxError, - kMaxError); + Image3F def = std::move(*io_default.frames[i].color()); + Image3F pip = std::move(*io_slow_pipeline.frames[i].color()); + VerifyRelativeError(pip, def, kMaxError, kMaxError); for (size_t ec = 0; ec < io_default.frames[i].extra_channels().size(); ec++) { - VerifyRelativeError(io_default.frames[i].extra_channels()[ec], - io_slow_pipeline.frames[i].extra_channels()[ec], - kMaxError, kMaxError); + VerifyRelativeError(io_slow_pipeline.frames[i].extra_channels()[ec], + io_default.frames[i].extra_channels()[ec], kMaxError, + kMaxError); } } } Splines CreateTestSplines() { @@ -180,11 +215,11 @@ std::vector GeneratePipelineTests() { std::vector all_tests; std::pair sizes[] = { - {128, 128}, {256, 256}, {258, 258}, {533, 401}, {777, 777}, + {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"; @@ -317,36 +352,36 @@ all_tests.push_back(s); } { auto s = settings; - s.input_path = "wide-gamut-tests/R2020-sRGB-blue.png"; + s.input_path = "imagecompression.info/flower_foveon_alpha.png"; s.cparams_descr = "AlphaVarDCT"; all_tests.push_back(s); } { auto s = settings; - s.input_path = "wide-gamut-tests/R2020-sRGB-blue.png"; + s.input_path = "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 = "wide-gamut-tests/R2020-sRGB-blue.png"; + s.input_path = "imagecompression.info/flower_foveon_alpha.png"; s.cparams_descr = "AlphaLossless"; all_tests.push_back(s); } { auto s = settings; - s.input_path = "wide-gamut-tests/R2020-sRGB-blue.png"; + s.input_path = "imagecompression.info/flower_foveon_alpha.png"; s.cparams_descr = "AlphaDownsample"; s.cparams.ec_resampling = 2; all_tests.push_back(s); } @@ -413,7 +448,41 @@ JXL_GTEST_INSTANTIATE_TEST_SUITE_P(RenderPipelineTest, RenderPipelineTestParam, testing::ValuesIn(GeneratePipelineTests()), PipelineTestDescription); +TEST(RenderPipelineDecodingTest, Animation) { + FakeParallelRunner fake_pool(/*order_seed=*/123, /*num_threads=*/8); + ThreadPool pool(&JxlFakeParallelRunner, &fake_pool); + + PaddedBytes compressed = + ReadTestData("jxl/blending/cropped_traffic_light.jxl"); + + DecompressParams dparams; + CodecInOut io_default; + ASSERT_TRUE(DecodeFile(dparams, compressed, &io_default, &pool)); + CodecInOut io_slow_pipeline; + dparams.use_slow_render_pipeline = true; + ASSERT_TRUE(DecodeFile(dparams, compressed, &io_slow_pipeline, &pool)); + + ASSERT_EQ(io_default.frames.size(), io_slow_pipeline.frames.size()); + for (size_t i = 0; i < io_default.frames.size(); i++) { +#if JXL_HIGH_PRECISION + constexpr float kMaxError = 1e-5; +#else + constexpr float kMaxError = 1e-4; +#endif + + Image3F fast_pipeline = std::move(*io_default.frames[i].color()); + Image3F slow_pipeline = std::move(*io_slow_pipeline.frames[i].color()); + VerifyRelativeError(slow_pipeline, fast_pipeline, kMaxError, kMaxError); + for (size_t ec = 0; ec < io_default.frames[i].extra_channels().size(); + ec++) { + VerifyRelativeError(io_slow_pipeline.frames[i].extra_channels()[ec], + io_default.frames[i].extra_channels()[ec], kMaxError, + kMaxError); + } + } +} + } // namespace } // namespace jxl diff --git a/third_party/jpeg-xl/lib/jxl/render_pipeline/simple_render_pipeline.h b/third_party/jpeg-xl/lib/jxl/render_pipeline/simple_render_pipeline.h --- a/third_party/jpeg-xl/lib/jxl/render_pipeline/simple_render_pipeline.h +++ b/third_party/jpeg-xl/lib/jxl/render_pipeline/simple_render_pipeline.h @@ -6,11 +6,10 @@ #ifndef LIB_JXL_RENDER_PIPELINE_SIMPLE_RENDER_PIPELINE_H_ #define LIB_JXL_RENDER_PIPELINE_SIMPLE_RENDER_PIPELINE_H_ #include -#include "lib/jxl/filters.h" #include "lib/jxl/render_pipeline/render_pipeline.h" namespace jxl { // A RenderPipeline that is "obviously correct"; it may use potentially large @@ -20,21 +19,19 @@ std::vector> PrepareBuffers( size_t group_id, size_t thread_id) override; void ProcessBuffers(size_t group_id, size_t thread_id) override; - void RunPipeline(); - - void PrepareForThreadsInternal(size_t num) override; + void PrepareForThreadsInternal(size_t num, bool use_group_ids) override; // Full frame buffers. Both X and Y dimensions are padded by // kRenderPipelineXOffset. std::vector channel_data_; size_t processed_passes_ = 0; private: - Rect MakeChannelRect(size_t group_id, size_t channel, bool is_color); + Rect MakeChannelRect(size_t group_id, size_t channel); }; } // namespace jxl #endif // LIB_JXL_RENDER_PIPELINE_SIMPLE_RENDER_PIPELINE_H_ 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 @@ -3,77 +3,74 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. #include "lib/jxl/render_pipeline/simple_render_pipeline.h" +#include "hwy/base.h" +#include "lib/jxl/image_ops.h" +#include "lib/jxl/render_pipeline/render_pipeline_stage.h" +#include "lib/jxl/sanitizers.h" + namespace jxl { -void SimpleRenderPipeline::PrepareForThreadsInternal(size_t num) { +void SimpleRenderPipeline::PrepareForThreadsInternal(size_t num, + bool use_group_ids) { if (!channel_data_.empty()) { return; } auto ch_size = [](size_t frame_size, size_t shift) { return DivCeil(frame_size, 1 << shift) + kRenderPipelineXOffset * 2; }; for (size_t c = 0; c < channel_shifts_[0].size(); c++) { - bool is_color_c = - c < 3 || (uses_noise_ && c >= channel_shifts_[0].size() - 3); - channel_data_.push_back( - ImageF(ch_size(frame_dimensions_.GetUpsampledXSize(is_color_c), - channel_shifts_[0][c].first), - ch_size(frame_dimensions_.GetUpsampledYSize(is_color_c), - channel_shifts_[0][c].second))); + channel_data_.push_back(ImageF( + ch_size(frame_dimensions_.xsize_upsampled, channel_shifts_[0][c].first), + ch_size(frame_dimensions_.ysize_upsampled, + channel_shifts_[0][c].second))); msan::PoisonImage(channel_data_.back()); } } -Rect SimpleRenderPipeline::MakeChannelRect(size_t group_id, size_t channel, - bool is_color) { +Rect SimpleRenderPipeline::MakeChannelRect(size_t group_id, size_t channel) { size_t base_color_shift = CeilLog2Nonzero(frame_dimensions_.xsize_upsampled_padded / frame_dimensions_.xsize_padded); const size_t gx = group_id % frame_dimensions_.xsize_groups; const size_t gy = group_id / frame_dimensions_.xsize_groups; size_t xgroupdim = (frame_dimensions_.group_dim << base_color_shift) >> channel_shifts_[0][channel].first; size_t ygroupdim = (frame_dimensions_.group_dim << base_color_shift) >> channel_shifts_[0][channel].second; - return Rect(kRenderPipelineXOffset + gx * xgroupdim, - kRenderPipelineXOffset + gy * ygroupdim, xgroupdim, ygroupdim, - kRenderPipelineXOffset + - DivCeil(frame_dimensions_.GetUpsampledXSize(is_color), - 1 << channel_shifts_[0][channel].first), - kRenderPipelineXOffset + - DivCeil(frame_dimensions_.GetUpsampledYSize(is_color), - 1 << channel_shifts_[0][channel].second)); + return Rect( + kRenderPipelineXOffset + gx * xgroupdim, + kRenderPipelineXOffset + gy * ygroupdim, xgroupdim, ygroupdim, + kRenderPipelineXOffset + DivCeil(frame_dimensions_.xsize_upsampled, + 1 << channel_shifts_[0][channel].first), + kRenderPipelineXOffset + + DivCeil(frame_dimensions_.ysize_upsampled, + 1 << channel_shifts_[0][channel].second)); } std::vector> SimpleRenderPipeline::PrepareBuffers( size_t group_id, size_t thread_id) { std::vector> ret; for (size_t c = 0; c < channel_data_.size(); c++) { - bool is_color_c = - c < 3 || (uses_noise_ && c >= channel_shifts_[0].size() - 3); - ret.emplace_back(&channel_data_[c], - MakeChannelRect(group_id, c, is_color_c)); + ret.emplace_back(&channel_data_[c], MakeChannelRect(group_id, c)); } return ret; } void SimpleRenderPipeline::ProcessBuffers(size_t group_id, size_t thread_id) { + for (size_t c = 0; c < channel_data_.size(); c++) { + Rect r = MakeChannelRect(group_id, c); + (void)r; + JXL_CHECK_IMAGE_INITIALIZED(channel_data_[c], r); + } + if (PassesWithAllInput() <= processed_passes_) return; processed_passes_++; - for (size_t c = 0; c < channel_data_.size(); c++) { - Rect r = MakeChannelRect(group_id, c, false); - (void)r; - JXL_CHECK_IMAGE_INITIALIZED( - channel_data_[c], Rect(kRenderPipelineXOffset, kRenderPipelineXOffset, - r.xsize(), r.ysize())); - } - for (size_t stage_id = 0; stage_id < stages_.size(); stage_id++) { const auto& stage = stages_[stage_id]; // Prepare buffers for kInOut channels. std::vector new_channels(channel_data_.size()); std::vector output_channels(channel_data_.size()); @@ -87,15 +84,22 @@ for (size_t c = 0; c < channel_data_.size(); c++) { if (stage->GetChannelMode(c) != RenderPipelineChannelMode::kInOut) { continue; } + // Ensure that the newly allocated channels are large enough to avoid + // problems with padding. new_channels[c] = - ImageF((input_sizes[c].first << stage->settings_.shift_x) + - kRenderPipelineXOffset * 2, - (input_sizes[c].second << stage->settings_.shift_y) + + ImageF(frame_dimensions_.xsize_upsampled_padded + + kRenderPipelineXOffset * 2 + hwy::kMaxVectorSize * 8, + frame_dimensions_.ysize_upsampled_padded + kRenderPipelineXOffset * 2); + new_channels[c].ShrinkTo( + (input_sizes[c].first << stage->settings_.shift_x) + + kRenderPipelineXOffset * 2, + (input_sizes[c].second << stage->settings_.shift_y) + + kRenderPipelineXOffset * 2); output_channels[c] = &new_channels[c]; } auto get_row = [&](size_t c, int64_t y) { return channel_data_[c].Row(kRenderPipelineXOffset + y) + @@ -109,24 +113,30 @@ } // Horizontal mirroring. for (size_t y = 0; y < input_sizes[c].second; y++) { float* row = get_row(c, y); for (size_t ix = 0; ix < stage->settings_.border_x; ix++) { - *(row - ix - 1) = *(row + ix); + *(row - ix - 1) = row[Mirror(-ssize_t(ix) - 1, input_sizes[c].first)]; + } + for (size_t ix = 0; ix < stage->settings_.border_x; ix++) { *(row + ix + input_sizes[c].first) = - *(row + input_sizes[c].first - ix - 1); + row[Mirror(ix + input_sizes[c].first, input_sizes[c].first)]; } } // Vertical mirroring. for (int y = 0; y < static_cast(stage->settings_.border_y); y++) { memcpy(get_row(c, -y - 1) - stage->settings_.border_x, - get_row(c, y) - stage->settings_.border_x, + get_row(c, Mirror(-ssize_t(y) - 1, input_sizes[c].second)) - + stage->settings_.border_x, sizeof(float) * (input_sizes[c].first + 2 * stage->settings_.border_x)); + } + for (int y = 0; y < static_cast(stage->settings_.border_y); y++) { memcpy( get_row(c, input_sizes[c].second + y) - stage->settings_.border_x, - get_row(c, input_sizes[c].second - y - 1) - + get_row(c, + Mirror(input_sizes[c].second + y, input_sizes[c].second)) - stage->settings_.border_x, sizeof(float) * (input_sizes[c].first + 2 * stage->settings_.border_x)); } } @@ -147,10 +157,11 @@ RenderPipelineStage::RowInfo input_rows(channel_data_.size()); RenderPipelineStage::RowInfo output_rows(channel_data_.size()); // Run the pipeline. { + stage->SetInputSizes(input_sizes); int border_y = stage->settings_.border_y; for (size_t y = 0; y < ysize; y++) { // Prepare input rows. for (size_t c = 0; c < channel_data_.size(); c++) { if (stage->GetChannelMode(c) == RenderPipelineChannelMode::kIgnored) { @@ -184,14 +195,72 @@ continue; } channel_data_[c] = std::move(new_channels[c]); } for (size_t c = 0; c < channel_data_.size(); c++) { - Rect r = MakeChannelRect(group_id, c, false); - (void)r; + size_t next_stage = std::min(stage_id + 1, channel_shifts_.size() - 1); + size_t xsize = DivCeil(frame_dimensions_.xsize_upsampled, + 1 << channel_shifts_[next_stage][c].first); + size_t ysize = DivCeil(frame_dimensions_.ysize_upsampled, + 1 << channel_shifts_[next_stage][c].second); + channel_data_[c].ShrinkTo(xsize + 2 * kRenderPipelineXOffset, + ysize + 2 * kRenderPipelineXOffset); JXL_CHECK_IMAGE_INITIALIZED( - channel_data_[c], Rect(kRenderPipelineXOffset, kRenderPipelineXOffset, - r.xsize(), r.ysize())); + channel_data_[c], + Rect(kRenderPipelineXOffset, kRenderPipelineXOffset, xsize, ysize)); + } + + if (stage->SwitchToImageDimensions()) { + size_t image_xsize, image_ysize; + FrameOrigin frame_origin; + stage->GetImageDimensions(&image_xsize, &image_ysize, &frame_origin); + frame_dimensions_.Set(image_xsize, image_ysize, 0, 0, 0, false, 1); + std::vector old_channels = std::move(channel_data_); + channel_data_.clear(); + channel_data_.reserve(old_channels.size()); + for (size_t c = 0; c < old_channels.size(); c++) { + channel_data_.emplace_back(2 * kRenderPipelineXOffset + image_xsize, + 2 * kRenderPipelineXOffset + image_ysize); + } + for (size_t y = 0; y < image_ysize; ++y) { + for (size_t c = 0; c < channel_data_.size(); c++) { + output_rows[c].resize(1); + output_rows[c][0] = channel_data_[c].Row(kRenderPipelineXOffset + y); + } + // TODO(sboukortt): consider doing this only on the parts of the + // background that won't be occluded. + stage->ProcessPaddingRow(output_rows, image_xsize, 0, y); + } + ssize_t x0 = frame_origin.x0; + ssize_t y0 = frame_origin.y0; + size_t x0_fg = 0; + size_t y0_fg = 0; + if (x0 < 0) { + xsize += x0; + x0_fg -= x0; + x0 = 0; + } + if (x0 + xsize > image_xsize) { + xsize = image_xsize - x0; + } + if (y0 < 0) { + ysize += y0; + y0_fg -= x0; + y0 = 0; + } + if (y0 + ysize > image_ysize) { + ysize = image_ysize - y0; + } + const Rect rect_fg_relative_to_image = + Rect(x0, y0, xsize, ysize) + .Translate(kRenderPipelineXOffset, kRenderPipelineXOffset); + const Rect rect_fg = + Rect(x0_fg, y0_fg, xsize, ysize) + .Translate(kRenderPipelineXOffset, kRenderPipelineXOffset); + for (size_t c = 0; c < channel_data_.size(); c++) { + CopyImageTo(rect_fg, old_channels[c], rect_fg_relative_to_image, + &channel_data_[c]); + } } } } } // namespace jxl 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 @@ -30,35 +30,29 @@ info_ = state_.frame_header.blending_info; const std::vector& ec_info = state_.frame_header.extra_channel_blending_info; ImageBundle& bg = *state_.reference_frames[info_.source].frame; bg_ = &bg; - if (bg.xsize() == 0 && bg.ysize() == 0) { - // there is no background, assume it to be all zeroes - ImageBundle empty(&state_.metadata->m); - Image3F color(image_xsize_, image_ysize_); - ZeroFillImage(&color); - empty.SetFromImage(std::move(color), frame_color_encoding); - if (!ec_info.empty()) { - std::vector ec; - for (size_t i = 0; i < ec_info.size(); ++i) { - ImageF eci(image_xsize_, image_ysize_); - ZeroFillImage(&eci); - ec.push_back(std::move(eci)); - } - empty.SetExtraChannels(std::move(ec)); - } - bg = std::move(empty); + if (bg.xsize() == 0 || bg.ysize() == 0) { + zeroes_.resize(image_xsize_, 0.f); } else if (state_.reference_frames[info_.source].ib_is_in_xyb) { initialized_ = JXL_FAILURE( "Trying to blend XYB reference frame %i and non-XYB frame", info_.source); return; + } else if (std::any_of(ec_info.begin(), ec_info.end(), + [this](const BlendingInfo& info) { + const ImageBundle& bg = + *state_.reference_frames[info.source].frame; + return bg.xsize() == 0 || bg.ysize() == 0; + })) { + zeroes_.resize(image_xsize_, 0.f); } - if (bg.xsize() < image_xsize_ || bg.ysize() < image_ysize_ || - bg.origin.x0 != 0 || bg.origin.y0 != 0) { + if (bg.xsize() != 0 && bg.ysize() != 0 && + (bg.xsize() < image_xsize_ || bg.ysize() < image_ysize_ || + bg.origin.x0 != 0 || bg.origin.y0 != 0)) { initialized_ = JXL_FAILURE("Trying to use a %" PRIuS "x%" PRIuS " crop as a background", bg.xsize(), bg.ysize()); return; } @@ -120,46 +114,100 @@ frame_origin.x0 >= static_cast(image_xsize_) || bg_ypos < 0 || bg_ypos >= static_cast(image_ysize_)) { return; } if (bg_xpos < 0) { - xpos -= bg_xpos; offset -= bg_xpos; xsize += bg_xpos; bg_xpos = 0; } if (bg_xpos + xsize > image_xsize_) { xsize = std::max(0, static_cast(image_xsize_) - bg_xpos); } std::vector bg_row_ptrs_(input_rows.size()); std::vector fg_row_ptrs_(input_rows.size()); - for (size_t c = 0; c < input_rows.size(); ++c) { - bg_row_ptrs_[c] = - (c < 3 ? bg_->color()->ConstPlaneRow(c, bg_ypos) - : bg_->extra_channels()[c - 3].ConstRow(bg_ypos)) + - bg_xpos; - fg_row_ptrs_[c] = GetInputRow(input_rows, c, offset); + size_t num_c = std::min(input_rows.size(), extra_channel_info_->size() + 3); + for (size_t c = 0; c < num_c; ++c) { + fg_row_ptrs_[c] = GetInputRow(input_rows, c, 0) + offset; + if (c < 3) { + bg_row_ptrs_[c] = + bg_->xsize() != 0 && bg_->ysize() != 0 + ? bg_->color()->ConstPlaneRow(c, bg_ypos) + bg_xpos + : zeroes_.data(); + } else { + const ImageBundle& ec_bg = + *state_ + .reference_frames[state_.frame_header + .extra_channel_blending_info[c - 3] + .source] + .frame; + bg_row_ptrs_[c] = + ec_bg.xsize() != 0 && ec_bg.ysize() != 0 + ? ec_bg.extra_channels()[c - 3].ConstRow(bg_ypos) + bg_xpos + : zeroes_.data(); + } } PerformBlending(bg_row_ptrs_.data(), fg_row_ptrs_.data(), fg_row_ptrs_.data(), 0, xsize, blending_info_[0], blending_info_.data() + 1, *extra_channel_info_); } RenderPipelineChannelMode GetChannelMode(size_t c) const final { return RenderPipelineChannelMode::kInPlace; } + bool SwitchToImageDimensions() const override { return true; } + + void GetImageDimensions(size_t* xsize, size_t* ysize, + FrameOrigin* frame_origin) const override { + *xsize = image_xsize_; + *ysize = image_ysize_; + *frame_origin = state_.frame_header.frame_origin; + } + + void ProcessPaddingRow(const RowInfo& output_rows, size_t xsize, size_t xpos, + size_t ypos) const override { + if (bg_->xsize() == 0 || bg_->ysize() == 0) { + for (size_t c = 0; c < 3; ++c) { + memset(GetInputRow(output_rows, c, 0), 0, xsize * sizeof(float)); + } + } else { + for (size_t c = 0; c < 3; ++c) { + memcpy(GetInputRow(output_rows, c, 0), + bg_->color()->ConstPlaneRow(c, ypos) + xpos, + xsize * sizeof(float)); + } + } + for (size_t ec = 0; ec < extra_channel_info_->size(); ++ec) { + const ImageBundle& ec_bg = + *state_ + .reference_frames + [state_.frame_header.extra_channel_blending_info[ec].source] + .frame; + if (ec_bg.xsize() == 0 || ec_bg.ysize() == 0) { + memset(GetInputRow(output_rows, 3 + ec, 0), 0, xsize * sizeof(float)); + } else { + memcpy(GetInputRow(output_rows, 3 + ec, 0), + ec_bg.extra_channels()[ec].ConstRow(ypos) + xpos, + xsize * sizeof(float)); + } + } + } + + const char* GetName() const override { return "Blending"; } + private: const PassesSharedState& state_; BlendingInfo info_; ImageBundle* bg_; Status initialized_ = true; size_t image_xsize_; size_t image_ysize_; std::vector blending_info_; const std::vector* extra_channel_info_; + std::vector zeroes_; }; std::unique_ptr GetBlendingStage( const PassesDecoderState* dec_state, const ColorEncoding& frame_color_encoding) { 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 @@ -33,11 +33,11 @@ auto onefour = Set(df, 0.25f); const float* row_in = GetInputRow(input_rows, c_, 0); float* row_out = GetOutputRow(output_rows, c_, 0); for (ssize_t x = -xextra; x < static_cast(xsize + xextra); x += Lanes(df)) { - auto current = Load(df, row_in + x) * threefour; + auto current = LoadU(df, row_in + x) * threefour; auto prev = LoadU(df, row_in + x - 1); auto next = LoadU(df, row_in + x + 1); auto left = MulAdd(onefour, prev, current); auto right = MulAdd(onefour, next, current); StoreInterleaved(df, left, right, row_out + x * 2); @@ -47,10 +47,12 @@ RenderPipelineChannelMode GetChannelMode(size_t c) const final { return c == c_ ? RenderPipelineChannelMode::kInOut : RenderPipelineChannelMode::kIgnored; } + const char* GetName() const override { return "HChromaUps"; } + private: size_t c_; }; class VerticalChromaUpsamplingStage : public RenderPipelineStage { @@ -73,13 +75,13 @@ const float* row_bot = GetInputRow(input_rows, c_, 1); float* row_out0 = GetOutputRow(output_rows, c_, 0); float* row_out1 = GetOutputRow(output_rows, c_, 1); for (ssize_t x = -xextra; x < static_cast(xsize + xextra); x += Lanes(df)) { - auto it = Load(df, row_top + x); - auto im = Load(df, row_mid + x); - auto ib = Load(df, row_bot + x); + auto it = LoadU(df, row_top + x); + auto im = LoadU(df, row_mid + x); + auto ib = LoadU(df, row_bot + x); auto im_scaled = im * threefour; Store(MulAdd(it, onefour, im_scaled), df, row_out0 + x); Store(MulAdd(ib, onefour, im_scaled), df, row_out1 + x); } } @@ -87,10 +89,12 @@ RenderPipelineChannelMode GetChannelMode(size_t c) const final { return c == c_ ? RenderPipelineChannelMode::kInOut : RenderPipelineChannelMode::kIgnored; } + const char* GetName() const override { return "VChromaUps"; } + private: size_t c_; }; std::unique_ptr GetChromaUpsamplingStage(size_t channel, diff --git a/third_party/jpeg-xl/lib/jxl/render_pipeline/stage_epf.h b/third_party/jpeg-xl/lib/jxl/render_pipeline/stage_epf.h --- a/third_party/jpeg-xl/lib/jxl/render_pipeline/stage_epf.h +++ b/third_party/jpeg-xl/lib/jxl/render_pipeline/stage_epf.h @@ -11,10 +11,11 @@ #include #include #include +#include "lib/jxl/image.h" #include "lib/jxl/loop_filter.h" #include "lib/jxl/render_pipeline/render_pipeline_stage.h" namespace jxl { 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 @@ -23,12 +23,11 @@ // These templates are not found via ADL. using hwy::HWY_NAMESPACE::Vec; JXL_INLINE Vec Weight(Vec sad, Vec inv_sigma, Vec thres) { auto v = MulAdd(sad, inv_sigma, Set(DF(), 1.0f)); - auto v2 = v * v; - return IfThenZeroElse(v <= thres, v2); + return ZeroIfNegative(v); } // 5x5 plus-shaped kernel with 5 SADs per pixel (3x3 plus-shaped). So this makes // this filter a 7x7 filter. class EPF0Stage : public RenderPipelineStage { @@ -38,21 +37,21 @@ /*shift=*/0, /*border=*/3)), lf_(lf), sigma_(&sigma) {} template - JXL_INLINE void AddPixel(int row, const RowInfo& input_rows, ssize_t x, + JXL_INLINE void AddPixel(int row, float* JXL_RESTRICT rows[3][7], ssize_t x, Vec sad, Vec inv_sigma, Vec* JXL_RESTRICT X, Vec* JXL_RESTRICT Y, Vec* JXL_RESTRICT B, Vec* JXL_RESTRICT w) const { - auto cx = aligned ? Load(DF(), GetInputRow(input_rows, 0, row) + x) - : LoadU(DF(), GetInputRow(input_rows, 0, row) + x); - auto cy = aligned ? Load(DF(), GetInputRow(input_rows, 1, row) + x) - : LoadU(DF(), GetInputRow(input_rows, 1, row) + x); - auto cb = aligned ? Load(DF(), GetInputRow(input_rows, 2, row) + x) - : LoadU(DF(), GetInputRow(input_rows, 2, row) + x); + auto cx = aligned ? Load(DF(), rows[0][3 + row] + x) + : LoadU(DF(), rows[0][3 + row] + x); + auto cy = aligned ? Load(DF(), rows[1][3 + row] + x) + : LoadU(DF(), rows[1][3 + row] + x); + auto cb = aligned ? Load(DF(), rows[2][3 + row] + x) + : LoadU(DF(), rows[2][3 + row] + x); auto weight = Weight(sad, inv_sigma, Set(DF(), lf_.epf_pass1_zeroflush)); *w += weight; *X = MulAdd(weight, cx, *X); *Y = MulAdd(weight, cy, *Y); @@ -65,17 +64,23 @@ DF df; xextra = RoundUpTo(xextra, Lanes(df)); const float* JXL_RESTRICT row_sigma = sigma_->Row(ypos / kBlockDim + kSigmaPadding); - float sm = lf_.epf_pass0_sigma_scale; + float sm = lf_.epf_pass0_sigma_scale * 1.65; float bsm = sm * lf_.epf_border_sad_mul; HWY_ALIGN float sad_mul_center[kBlockDim] = {bsm, sm, sm, sm, sm, sm, sm, bsm}; HWY_ALIGN float sad_mul_border[kBlockDim] = {bsm, bsm, bsm, bsm, bsm, bsm, bsm, bsm}; + float* JXL_RESTRICT rows[3][7]; + for (size_t c = 0; c < 3; c++) { + for (int i = 0; i < 7; i++) { + rows[c][i] = GetInputRow(input_rows, c, i - 3); + } + } const float* sad_mul = (ypos % kBlockDim == 0 || ypos % kBlockDim == kBlockDim - 1) ? sad_mul_border : sad_mul_center; @@ -85,11 +90,11 @@ size_t bx = (x + xpos + kSigmaPadding * kBlockDim) / kBlockDim; size_t ix = (x + xpos) % kBlockDim; if (row_sigma[bx] < kMinSigma) { for (size_t c = 0; c < 3; c++) { - auto px = Load(df, GetInputRow(input_rows, c, 0) + x); + auto px = Load(df, rows[c][3 + 0] + x); Store(px, df, GetOutputRow(output_rows, c, 0) + x); } continue; } @@ -111,32 +116,30 @@ auto sad = Zero(df); constexpr std::array plus_off[] = { {{0, 0}}, {{-1, 0}}, {{0, -1}}, {{1, 0}}, {{0, 1}}}; for (size_t j = 0; j < 5; j++) { const auto r11 = - LoadU(df, GetInputRow(input_rows, c, plus_off[j][0]) + x + - plus_off[j][1]); - const auto c11 = LoadU( - df, - GetInputRow(input_rows, c, sads_off[i][0] + plus_off[j][0]) + - x + sads_off[i][1] + plus_off[j][1]); + LoadU(df, rows[c][3 + plus_off[j][0]] + x + plus_off[j][1]); + const auto c11 = + LoadU(df, rows[c][3 + sads_off[i][0] + plus_off[j][0]] + x + + sads_off[i][1] + plus_off[j][1]); sad += AbsDiff(r11, c11); } sads[i] = MulAdd(sad, scale, sads[i]); } } - const auto x_cc = Load(df, GetInputRow(input_rows, 0, 0) + x); - const auto y_cc = Load(df, GetInputRow(input_rows, 1, 0) + x); - const auto b_cc = Load(df, GetInputRow(input_rows, 2, 0) + x); + const auto x_cc = Load(df, rows[0][3 + 0] + x); + const auto y_cc = Load(df, rows[1][3 + 0] + x); + const auto b_cc = Load(df, rows[2][3 + 0] + x); auto w = Set(df, 1); auto X = x_cc; auto Y = y_cc; auto B = b_cc; for (size_t i = 0; i < 12; i++) { - AddPixel(/*row=*/sads_off[i][0], input_rows, + AddPixel(/*row=*/sads_off[i][0], rows, x + sads_off[i][1], sads[i], inv_sigma, &X, &Y, &B, &w); } #if JXL_HIGH_PRECISION auto inv_w = Set(df, 1.0f) / w; @@ -152,10 +155,12 @@ RenderPipelineChannelMode GetChannelMode(size_t c) const final { return c < 3 ? RenderPipelineChannelMode::kInOut : RenderPipelineChannelMode::kIgnored; } + const char* GetName() const override { return "EPF0"; } + private: LoopFilter lf_; const ImageF* sigma_; }; @@ -168,21 +173,21 @@ /*shift=*/0, /*border=*/2)), lf_(lf), sigma_(&sigma) {} template - JXL_INLINE void AddPixel(int row, const RowInfo& input_rows, ssize_t x, + JXL_INLINE void AddPixel(int row, float* JXL_RESTRICT rows[3][5], ssize_t x, Vec sad, Vec inv_sigma, Vec* JXL_RESTRICT X, Vec* JXL_RESTRICT Y, Vec* JXL_RESTRICT B, Vec* JXL_RESTRICT w) const { - auto cx = aligned ? Load(DF(), GetInputRow(input_rows, 0, row) + x) - : LoadU(DF(), GetInputRow(input_rows, 0, row) + x); - auto cy = aligned ? Load(DF(), GetInputRow(input_rows, 1, row) + x) - : LoadU(DF(), GetInputRow(input_rows, 1, row) + x); - auto cb = aligned ? Load(DF(), GetInputRow(input_rows, 2, row) + x) - : LoadU(DF(), GetInputRow(input_rows, 2, row) + x); + auto cx = aligned ? Load(DF(), rows[0][2 + row] + x) + : LoadU(DF(), rows[0][2 + row] + x); + auto cy = aligned ? Load(DF(), rows[1][2 + row] + x) + : LoadU(DF(), rows[1][2 + row] + x); + auto cb = aligned ? Load(DF(), rows[2][2 + row] + x) + : LoadU(DF(), rows[2][2 + row] + x); auto weight = Weight(sad, inv_sigma, Set(DF(), lf_.epf_pass1_zeroflush)); *w += weight; *X = MulAdd(weight, cx, *X); *Y = MulAdd(weight, cy, *Y); @@ -195,18 +200,25 @@ DF df; xextra = RoundUpTo(xextra, Lanes(df)); const float* JXL_RESTRICT row_sigma = sigma_->Row(ypos / kBlockDim + kSigmaPadding); - float sm = 1.0f; + float sm = 1.65f; float bsm = sm * lf_.epf_border_sad_mul; HWY_ALIGN float sad_mul_center[kBlockDim] = {bsm, sm, sm, sm, sm, sm, sm, bsm}; HWY_ALIGN float sad_mul_border[kBlockDim] = {bsm, bsm, bsm, bsm, bsm, bsm, bsm, bsm}; + float* JXL_RESTRICT rows[3][5]; + for (size_t c = 0; c < 3; c++) { + for (int i = 0; i < 5; i++) { + rows[c][i] = GetInputRow(input_rows, c, i - 2); + } + } + const float* sad_mul = (ypos % kBlockDim == 0 || ypos % kBlockDim == kBlockDim - 1) ? sad_mul_border : sad_mul_center; @@ -215,11 +227,11 @@ size_t bx = (x + xpos + kSigmaPadding * kBlockDim) / kBlockDim; size_t ix = (x + xpos) % kBlockDim; if (row_sigma[bx] < kMinSigma) { for (size_t c = 0; c < 3; c++) { - auto px = Load(df, GetInputRow(input_rows, c, 0) + x); + auto px = Load(df, rows[c][2 + 0] + x); Store(px, df, GetOutputRow(output_rows, c, 0) + x); } continue; } @@ -233,84 +245,84 @@ // compute sads for (size_t c = 0; c < 3; c++) { // center px = 22, px above = 21 auto t = Undefined(df); - const auto p20 = Load(df, GetInputRow(input_rows, c, -2) + x); - const auto p21 = Load(df, GetInputRow(input_rows, c, -1) + x); + const auto p20 = Load(df, rows[c][2 + -2] + x); + const auto p21 = Load(df, rows[c][2 + -1] + x); auto sad0c = AbsDiff(p20, p21); // SAD 2, 1 - const auto p11 = LoadU(df, GetInputRow(input_rows, c, -1) + x - 1); + const auto p11 = LoadU(df, rows[c][2 + -1] + x - 1); auto sad1c = AbsDiff(p11, p21); // SAD 1, 2 - const auto p31 = LoadU(df, GetInputRow(input_rows, c, -1) + x + 1); + const auto p31 = LoadU(df, rows[c][2 + -1] + x + 1); auto sad2c = AbsDiff(p31, p21); // SAD 3, 2 - const auto p02 = LoadU(df, GetInputRow(input_rows, c, 0) + x - 2); - const auto p12 = LoadU(df, GetInputRow(input_rows, c, 0) + x - 1); + const auto p02 = LoadU(df, rows[c][2 + 0] + x - 2); + const auto p12 = LoadU(df, rows[c][2 + 0] + x - 1); sad1c += AbsDiff(p02, p12); // SAD 1, 2 sad0c += AbsDiff(p11, p12); // SAD 2, 1 - const auto p22 = LoadU(df, GetInputRow(input_rows, c, 0) + x); + const auto p22 = LoadU(df, rows[c][2 + 0] + x); t = AbsDiff(p12, p22); sad1c += t; // SAD 1, 2 sad2c += t; // SAD 3, 2 t = AbsDiff(p22, p21); auto sad3c = t; // SAD 2, 3 sad0c += t; // SAD 2, 1 - const auto p32 = LoadU(df, GetInputRow(input_rows, c, 0) + x + 1); + const auto p32 = LoadU(df, rows[c][2 + 0] + x + 1); sad0c += AbsDiff(p31, p32); // SAD 2, 1 t = AbsDiff(p22, p32); sad1c += t; // SAD 1, 2 sad2c += t; // SAD 3, 2 - const auto p42 = LoadU(df, GetInputRow(input_rows, c, 0) + x + 2); + const auto p42 = LoadU(df, rows[c][2 + 0] + x + 2); sad2c += AbsDiff(p42, p32); // SAD 3, 2 - const auto p13 = LoadU(df, GetInputRow(input_rows, c, 1) + x - 1); + const auto p13 = LoadU(df, rows[c][2 + 1] + x - 1); sad3c += AbsDiff(p13, p12); // SAD 2, 3 - const auto p23 = Load(df, GetInputRow(input_rows, c, 1) + x); + const auto p23 = Load(df, rows[c][2 + 1] + x); t = AbsDiff(p22, p23); sad0c += t; // SAD 2, 1 sad3c += t; // SAD 2, 3 sad1c += AbsDiff(p13, p23); // SAD 1, 2 - const auto p33 = LoadU(df, GetInputRow(input_rows, c, 1) + x + 1); + const auto p33 = LoadU(df, rows[c][2 + 1] + x + 1); sad2c += AbsDiff(p33, p23); // SAD 3, 2 sad3c += AbsDiff(p33, p32); // SAD 2, 3 - const auto p24 = Load(df, GetInputRow(input_rows, c, 2) + x); + const auto p24 = Load(df, rows[c][2 + 2] + x); sad3c += AbsDiff(p24, p23); // SAD 2, 3 auto scale = Set(df, lf_.epf_channel_scale[c]); sad0 = MulAdd(sad0c, scale, sad0); sad1 = MulAdd(sad1c, scale, sad1); sad2 = MulAdd(sad2c, scale, sad2); sad3 = MulAdd(sad3c, scale, sad3); } - const auto x_cc = Load(df, GetInputRow(input_rows, 0, 0) + x); - const auto y_cc = Load(df, GetInputRow(input_rows, 1, 0) + x); - const auto b_cc = Load(df, GetInputRow(input_rows, 2, 0) + x); + const auto x_cc = Load(df, rows[0][2 + 0] + x); + const auto y_cc = Load(df, rows[1][2 + 0] + x); + const auto b_cc = Load(df, rows[2][2 + 0] + x); auto w = Set(df, 1); auto X = x_cc; auto Y = y_cc; auto B = b_cc; // Top row - AddPixel(/*row=*/-1, input_rows, x, sad0, inv_sigma, &X, - &Y, &B, &w); + AddPixel(/*row=*/-1, rows, x, sad0, inv_sigma, &X, &Y, + &B, &w); // Center - AddPixel(/*row=*/0, input_rows, x - 1, sad1, inv_sigma, - &X, &Y, &B, &w); - AddPixel(/*row=*/0, input_rows, x + 1, sad2, inv_sigma, - &X, &Y, &B, &w); + AddPixel(/*row=*/0, rows, x - 1, sad1, inv_sigma, &X, + &Y, &B, &w); + AddPixel(/*row=*/0, rows, x + 1, sad2, inv_sigma, &X, + &Y, &B, &w); // Bottom - AddPixel(/*row=*/1, input_rows, x, sad3, inv_sigma, &X, - &Y, &B, &w); + AddPixel(/*row=*/1, rows, x, sad3, inv_sigma, &X, &Y, + &B, &w); #if JXL_HIGH_PRECISION auto inv_w = Set(df, 1.0f) / w; #else auto inv_w = ApproximateReciprocal(w); #endif @@ -323,10 +335,12 @@ RenderPipelineChannelMode GetChannelMode(size_t c) const final { return c < 3 ? RenderPipelineChannelMode::kInOut : RenderPipelineChannelMode::kIgnored; } + const char* GetName() const override { return "EPF1"; } + private: LoopFilter lf_; const ImageF* sigma_; }; @@ -339,21 +353,21 @@ /*shift=*/0, /*border=*/1)), lf_(lf), sigma_(&sigma) {} template - JXL_INLINE void AddPixel(int row, const RowInfo& input_rows, ssize_t x, + JXL_INLINE void AddPixel(int row, float* JXL_RESTRICT rows[3][3], ssize_t x, Vec rx, Vec ry, Vec rb, Vec inv_sigma, Vec* JXL_RESTRICT X, Vec* JXL_RESTRICT Y, Vec* JXL_RESTRICT B, Vec* JXL_RESTRICT w) const { - auto cx = aligned ? Load(DF(), GetInputRow(input_rows, 0, row) + x) - : LoadU(DF(), GetInputRow(input_rows, 0, row) + x); - auto cy = aligned ? Load(DF(), GetInputRow(input_rows, 1, row) + x) - : LoadU(DF(), GetInputRow(input_rows, 1, row) + x); - auto cb = aligned ? Load(DF(), GetInputRow(input_rows, 2, row) + x) - : LoadU(DF(), GetInputRow(input_rows, 2, row) + x); + auto cx = aligned ? Load(DF(), rows[0][1 + row] + x) + : LoadU(DF(), rows[0][1 + row] + x); + auto cy = aligned ? Load(DF(), rows[1][1 + row] + x) + : LoadU(DF(), rows[1][1 + row] + x); + auto cb = aligned ? Load(DF(), rows[2][1 + row] + x) + : LoadU(DF(), rows[2][1 + row] + x); auto sad = AbsDiff(cx, rx) * Set(DF(), lf_.epf_channel_scale[0]); sad = MulAdd(AbsDiff(cy, ry), Set(DF(), lf_.epf_channel_scale[1]), sad); sad = MulAdd(AbsDiff(cb, rb), Set(DF(), lf_.epf_channel_scale[2]), sad); @@ -371,18 +385,25 @@ DF df; xextra = RoundUpTo(xextra, Lanes(df)); const float* JXL_RESTRICT row_sigma = sigma_->Row(ypos / kBlockDim + kSigmaPadding); - float sm = lf_.epf_pass2_sigma_scale; + float sm = lf_.epf_pass2_sigma_scale * 1.65; float bsm = sm * lf_.epf_border_sad_mul; HWY_ALIGN float sad_mul_center[kBlockDim] = {bsm, sm, sm, sm, sm, sm, sm, bsm}; HWY_ALIGN float sad_mul_border[kBlockDim] = {bsm, bsm, bsm, bsm, bsm, bsm, bsm, bsm}; + float* JXL_RESTRICT rows[3][3]; + for (size_t c = 0; c < 3; c++) { + for (int i = 0; i < 3; i++) { + rows[c][i] = GetInputRow(input_rows, c, i - 1); + } + } + const float* sad_mul = (ypos % kBlockDim == 0 || ypos % kBlockDim == kBlockDim - 1) ? sad_mul_border : sad_mul_center; @@ -391,38 +412,38 @@ size_t bx = (x + xpos + kSigmaPadding * kBlockDim) / kBlockDim; size_t ix = (x + xpos) % kBlockDim; if (row_sigma[bx] < kMinSigma) { for (size_t c = 0; c < 3; c++) { - auto px = Load(df, GetInputRow(input_rows, c, 0) + x); + auto px = Load(df, rows[c][1 + 0] + x); Store(px, df, GetOutputRow(output_rows, c, 0) + x); } continue; } const auto sm = Load(df, sad_mul + ix); const auto inv_sigma = Set(df, row_sigma[bx]) * sm; - const auto x_cc = Load(df, GetInputRow(input_rows, 0, 0) + x); - const auto y_cc = Load(df, GetInputRow(input_rows, 1, 0) + x); - const auto b_cc = Load(df, GetInputRow(input_rows, 2, 0) + x); + const auto x_cc = Load(df, rows[0][1 + 0] + x); + const auto y_cc = Load(df, rows[1][1 + 0] + x); + const auto b_cc = Load(df, rows[2][1 + 0] + x); auto w = Set(df, 1); auto X = x_cc; auto Y = y_cc; auto B = b_cc; // Top row - AddPixel(/*row=*/-1, input_rows, x, x_cc, y_cc, b_cc, + AddPixel(/*row=*/-1, rows, x, x_cc, y_cc, b_cc, inv_sigma, &X, &Y, &B, &w); // Center - AddPixel(/*row=*/0, input_rows, x - 1, x_cc, y_cc, - b_cc, inv_sigma, &X, &Y, &B, &w); - AddPixel(/*row=*/0, input_rows, x + 1, x_cc, y_cc, - b_cc, inv_sigma, &X, &Y, &B, &w); + AddPixel(/*row=*/0, rows, x - 1, x_cc, y_cc, b_cc, + inv_sigma, &X, &Y, &B, &w); + AddPixel(/*row=*/0, rows, x + 1, x_cc, y_cc, b_cc, + inv_sigma, &X, &Y, &B, &w); // Bottom - AddPixel(/*row=*/1, input_rows, x, x_cc, y_cc, b_cc, + AddPixel(/*row=*/1, rows, x, x_cc, y_cc, b_cc, inv_sigma, &X, &Y, &B, &w); #if JXL_HIGH_PRECISION auto inv_w = Set(df, 1.0f) / w; #else auto inv_w = ApproximateReciprocal(w); @@ -436,10 +457,12 @@ RenderPipelineChannelMode GetChannelMode(size_t c) const final { return c < 3 ? RenderPipelineChannelMode::kInOut : RenderPipelineChannelMode::kIgnored; } + const char* GetName() const override { return "EPF2"; } + private: LoopFilter lf_; const ImageF* sigma_; }; 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 @@ -51,37 +51,47 @@ float* JXL_RESTRICT row_b = GetInputRow(input_rows, c, 1); float* JXL_RESTRICT row_out = GetOutputRow(output_rows, c, 0); const auto w0 = Set(d, weights_[3 * c + 0]); const auto w1 = Set(d, weights_[3 * c + 1]); const auto w2 = Set(d, weights_[3 * c + 2]); +// Group data need only be aligned to a block; for >=512 bit vectors, this may +// result in unaligned loads. +#if HWY_CAP_GE512 +#define LoadMaybeU LoadU +#else +#define LoadMaybeU Load +#endif // Since GetInputRow(input_rows, c, {-1, 0, 1}) is aligned, rounding // xextra up to Lanes(d) doesn't access anything problematic. - for (int64_t x = -RoundUpTo(xextra, Lanes(d)); - x < (int64_t)(xsize + xextra); x += Lanes(d)) { - const auto t = Load(d, row_t + x); + for (ssize_t x = -RoundUpTo(xextra, Lanes(d)); + x < (ssize_t)(xsize + xextra); x += Lanes(d)) { + const auto t = LoadMaybeU(d, row_t + x); const auto tl = LoadU(d, row_t + x - 1); const auto tr = LoadU(d, row_t + x + 1); - const auto m = Load(d, row_m + x); + const auto m = LoadMaybeU(d, row_m + x); const auto l = LoadU(d, row_m + x - 1); const auto r = LoadU(d, row_m + x + 1); - const auto b = Load(d, row_b + x); + const auto b = LoadMaybeU(d, row_b + x); const auto bl = LoadU(d, row_b + x - 1); const auto br = LoadU(d, row_b + x + 1); const auto sum0 = m; const auto sum1 = (l + r) + (t + b); const auto sum2 = (tl + tr) + (bl + br); auto pixels = MulAdd(sum2, w2, MulAdd(sum1, w1, sum0 * w0)); Store(pixels, d, row_out + x); } } } +#undef LoadMaybeU RenderPipelineChannelMode GetChannelMode(size_t c) const final { return c < 3 ? RenderPipelineChannelMode::kInOut : RenderPipelineChannelMode::kIgnored; } + const char* GetName() const override { return "Gab"; } + private: float weights_[9]; }; std::unique_ptr GetGaborishStage(const LoopFilter& lf) { 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 @@ -9,10 +9,11 @@ #define HWY_TARGET_INCLUDE "lib/jxl/render_pipeline/stage_noise.cc" #include #include #include "lib/jxl/fast_math-inl.h" +#include "lib/jxl/sanitizers.h" #include "lib/jxl/transfer_functions-inl.h" HWY_BEFORE_NAMESPACE(); namespace jxl { namespace HWY_NAMESPACE { @@ -139,11 +140,11 @@ class AddNoiseStage : public RenderPipelineStage { public: AddNoiseStage(const NoiseParams& noise_params, const ColorCorrelationMap& cmap, size_t first_c) : RenderPipelineStage(RenderPipelineStage::Settings::Symmetric( - /*shift=*/0, /*border=*/2)), + /*shift=*/0, /*border=*/0)), noise_params_(noise_params), cmap_(cmap), first_c_(first_c) {} void ProcessRow(const RowInfo& input_rows, const RowInfo& output_rows, @@ -177,11 +178,11 @@ GetInputRow(input_rows, first_c_ + 2, 0); // 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)) { + for (size_t x = 0; x < xsize_v; x += Lanes(d)) { const auto vx = Load(d, row_x + x); const auto vy = Load(d, row_y + x); const auto in_g = vy - vx; const auto in_r = vy + vx; const auto noise_strength_g = NoiseStrength(noise_model, in_g * half); @@ -204,10 +205,12 @@ return c >= first_c_ ? RenderPipelineChannelMode::kInput : c < 3 ? RenderPipelineChannelMode::kInPlace : RenderPipelineChannelMode::kIgnored; } + const char* GetName() const override { return "AddNoise"; } + private: const NoiseParams& noise_params_; const ColorCorrelationMap& cmap_; size_t first_c_; }; @@ -235,12 +238,12 @@ float* JXL_RESTRICT rows[5]; for (size_t i = 0; i < 5; i++) { rows[i] = GetInputRow(input_rows, c, i - 2); } float* JXL_RESTRICT row_out = GetOutputRow(output_rows, c, 0); - for (int64_t x = -RoundUpTo(xextra, Lanes(d)); - x < (int64_t)(xsize + xextra); x += Lanes(d)) { + for (ssize_t x = -RoundUpTo(xextra, Lanes(d)); + x < (ssize_t)(xsize + xextra); x += Lanes(d)) { const auto p00 = Load(d, rows[2] + x); auto others = Zero(d); for (ssize_t i = -2; i <= 2; i++) { others += LoadU(d, rows[0] + x + i); others += LoadU(d, rows[1] + x + i); @@ -261,10 +264,12 @@ RenderPipelineChannelMode GetChannelMode(size_t c) const final { return c >= first_c_ ? RenderPipelineChannelMode::kInOut : RenderPipelineChannelMode::kIgnored; } + const char* GetName() const override { return "ConvNoise"; } + private: size_t first_c_; }; std::unique_ptr GetConvolveNoiseStage( 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 @@ -27,10 +27,12 @@ RenderPipelineChannelMode GetChannelMode(size_t c) const final { return RenderPipelineChannelMode::kInPlace; } + const char* GetName() const override { return "Patches"; } + private: const PatchDictionary& patches_; }; } // namespace 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 @@ -33,10 +33,12 @@ RenderPipelineChannelMode GetChannelMode(size_t c) const final { return c < 3 ? RenderPipelineChannelMode::kInPlace : RenderPipelineChannelMode::kIgnored; } + const char* GetName() const override { return "Splines"; } + private: const Splines& splines_; }; std::unique_ptr GetSplineStage(const Splines* splines) { 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 @@ -35,10 +35,12 @@ return c < 3 ? RenderPipelineChannelMode::kInPlace : c == spot_c_ ? RenderPipelineChannelMode::kInput : RenderPipelineChannelMode::kIgnored; } + const char* GetName() const override { return "Spot"; } + private: size_t spot_c_; const float* spot_color_; }; 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 @@ -8,10 +8,11 @@ #undef HWY_TARGET_INCLUDE #define HWY_TARGET_INCLUDE "lib/jxl/render_pipeline/stage_upsampling.cc" #include #include +#include "lib/jxl/sanitizers.h" #include "lib/jxl/simd_util-inl.h" HWY_BEFORE_NAMESPACE(); namespace jxl { namespace HWY_NAMESPACE { @@ -42,29 +43,41 @@ float* JXL_RESTRICT temp) const final { PROFILER_ZONE("Upsampling"); static HWY_FULL(float) df; size_t shift = settings_.shift_x; size_t N = 1 << shift; - xextra = RoundUpTo(xextra, Lanes(df)); - ssize_t x0 = -xextra; - ssize_t x1 = xsize + xextra; + const size_t xsize_v = RoundUpTo(xsize, Lanes(df)); + for (ssize_t iy = -2; iy <= 2; iy++) { + msan::UnpoisonMemory(GetInputRow(input_rows, c_, iy) + xsize + 2, + sizeof(float) * (xsize_v - xsize)); + } + JXL_ASSERT(xextra == 0); + ssize_t x0 = 0; + ssize_t x1 = xsize; if (N == 2) { ProcessRowImpl<2>(input_rows, output_rows, x0, x1); } if (N == 4) { ProcessRowImpl<4>(input_rows, output_rows, x0, x1); } if (N == 8) { ProcessRowImpl<8>(input_rows, output_rows, x0, x1); } + for (size_t oy = 0; oy < N; oy++) { + float* dst_row = GetOutputRow(output_rows, c_, oy); + msan::PoisonMemory(dst_row + xsize * N, + sizeof(float) * (xsize_v - xsize) * N); + } } RenderPipelineChannelMode GetChannelMode(size_t c) const final { return c == c_ ? RenderPipelineChannelMode::kInOut : RenderPipelineChannelMode::kIgnored; } + const char* GetName() const override { return "Upsample"; } + private: template JXL_INLINE float Kernel(size_t x, size_t y, ssize_t ix, ssize_t iy) const { ix += 2; iy += 2; @@ -82,27 +95,21 @@ [x % 8 < 4 ? ix : 4 - ix]; } JXL_ABORT("Invalid upsample"); } - template + template void ProcessRowImpl(const RowInfo& input_rows, const RowInfo& output_rows, - size_t xextra, size_t xsize) const { + ssize_t x0, ssize_t x1) const { static HWY_FULL(float) df; - const size_t xsize_v = RoundUpTo(xsize, Lanes(df)); - for (ssize_t iy = -2; iy <= 2; iy++) { - msan::UnpoisonMemory(GetInputRow(input_rows, c_, iy) + xsize + 2, - sizeof(float) * (xsize_v - xsize)); - } for (size_t oy = 0; oy < N; oy++) { float* dst_row = GetOutputRow(output_rows, c_, oy); - for (ssize_t x = -xextra; x < static_cast(xsize + xextra); - x += Lanes(df)) { + for (ssize_t x = x0; x < x1; x += Lanes(df)) { hwy::HWY_NAMESPACE::Vec ups[8]; for (size_t ox = 0; ox < N; ox++) { auto result = Zero(df); - auto min = Load(df, GetInputRow(input_rows, c_, 0) + x); + auto min = LoadU(df, GetInputRow(input_rows, c_, 0) + x); auto max = min; for (ssize_t iy = -2; iy <= 2; iy++) { for (ssize_t ix = -2; ix <= 2; ix++) { auto v = LoadU(df, GetInputRow(input_rows, c_, iy) + x + ix); result = MulAdd(Set(df, Kernel(ox, oy, ix, iy)), v, result); @@ -122,12 +129,10 @@ if (N == 8) { StoreInterleaved(df, ups[0], ups[1], ups[2], ups[3], ups[4], ups[5], ups[6], ups[7], dst_row + x * N); } } - msan::PoisonMemory(dst_row + xsize * N, - sizeof(float) * (xsize_v - xsize) * N); } } size_t c_; float kernel_[4][4][5][5]; 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 @@ -12,11 +12,11 @@ #include "lib/jxl/render_pipeline/render_pipeline_stage.h" namespace jxl { std::unique_ptr GetWriteToImageBundleStage( - ImageBundle* image_bundle); + ImageBundle* image_bundle, ColorEncoding color_encoding); // 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. 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 @@ -5,10 +5,11 @@ #include "lib/jxl/render_pipeline/stage_write.h" #include "lib/jxl/common.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" #include #include @@ -87,10 +88,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 { 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); const float* JXL_RESTRICT row_in_b = GetInputRow(input_rows, 2, 0); const float* JXL_RESTRICT row_in_a = @@ -101,14 +103,20 @@ D::Rebind du; auto zero = Zero(d); auto one = Set(d, 1.0f); auto mul = Set(d, 255.0f); - ssize_t x0 = -RoundUpTo(xextra, Lanes(d)); - ssize_t x1 = RoundUpTo(xsize + xextra, Lanes(d)); + ssize_t x1 = RoundUpTo(xsize, Lanes(d)); - for (ssize_t x = x0; x < x1; x += Lanes(d)) { + msan::UnpoisonMemory(row_in_r + xsize, sizeof(float) * (x1 - xsize)); + msan::UnpoisonMemory(row_in_g + xsize, sizeof(float) * (x1 - xsize)); + msan::UnpoisonMemory(row_in_b + xsize, sizeof(float) * (x1 - xsize)); + if (row_in_a) { + msan::UnpoisonMemory(row_in_a + xsize, sizeof(float) * (x1 - xsize)); + } + + for (ssize_t x = 0; x < x1; x += Lanes(d)) { auto rf = Clamp(zero, Load(d, row_in_r + x), one) * mul; auto gf = Clamp(zero, Load(d, row_in_g + x), one) * mul; auto bf = Clamp(zero, Load(d, row_in_b + x), one) * mul; auto af = row_in_a ? Clamp(zero, Load(d, row_in_a + x), one) * mul : Set(d, 255.0f); @@ -131,10 +139,12 @@ return c < 3 || (has_alpha_ && c == alpha_c_) ? RenderPipelineChannelMode::kInput : RenderPipelineChannelMode::kIgnored; } + const char* GetName() const override { return "WriteToU8"; } + private: uint8_t* rgb_; size_t stride_; size_t width_; size_t height_; @@ -163,47 +173,81 @@ HWY_EXPORT(GetWriteToU8Stage); namespace { class WriteToImageBundleStage : public RenderPipelineStage { public: - explicit WriteToImageBundleStage(ImageBundle* image_bundle) + explicit WriteToImageBundleStage(ImageBundle* image_bundle, + ColorEncoding color_encoding) : RenderPipelineStage(RenderPipelineStage::Settings()), - image_bundle_(image_bundle) {} + image_bundle_(image_bundle), + color_encoding_(std::move(color_encoding)) {} + + void SetInputSizes( + const std::vector>& input_sizes) override { +#if JXL_ENABLE_ASSERT + JXL_ASSERT(input_sizes.size() >= 3); + 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++) { + image_bundle_->extra_channels().emplace_back(input_sizes[c].first, + input_sizes[c].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 { 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)); } for (size_t ec = 0; ec < image_bundle_->extra_channels().size(); ec++) { - JXL_ASSERT(ec < image_bundle_->extra_channels().size()); - JXL_ASSERT(image_bundle_->extra_channels()[ec].xsize() <= + JXL_ASSERT(image_bundle_->extra_channels()[ec].xsize() >= xpos + xsize + xextra); memcpy(image_bundle_->extra_channels()[ec].Row(ypos) + xpos - xextra, GetInputRow(input_rows, 3 + ec, 0) - xextra, sizeof(float) * (xsize + 2 * xextra)); } } RenderPipelineChannelMode GetChannelMode(size_t c) const final { - return c < 3 + image_bundle_->extra_channels().size() - ? RenderPipelineChannelMode::kInput - : RenderPipelineChannelMode::kIgnored; + return RenderPipelineChannelMode::kInput; } + const char* GetName() const override { return "WriteIB"; } + private: ImageBundle* image_bundle_; + ColorEncoding color_encoding_; }; class WriteToImage3FStage : public RenderPipelineStage { public: explicit WriteToImage3FStage(Image3F* image) : RenderPipelineStage(RenderPipelineStage::Settings()), image_(image) {} + void SetInputSizes( + const std::vector>& input_sizes) override { +#if JXL_ENABLE_ASSERT + JXL_ASSERT(input_sizes.size() >= 3); + for (size_t c = 1; c < 3; ++c) { + JXL_ASSERT(input_sizes[c].first == input_sizes[0].first); + JXL_ASSERT(input_sizes[c].second == input_sizes[0].second); + } +#endif + *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 { for (size_t c = 0; c < 3; c++) { memcpy(image_->PlaneRow(c, ypos) + xpos - xextra, @@ -215,10 +259,12 @@ RenderPipelineChannelMode GetChannelMode(size_t c) const final { return c < 3 ? RenderPipelineChannelMode::kInput : RenderPipelineChannelMode::kIgnored; } + const char* GetName() const override { return "WriteI3F"; } + private: Image3F* image_; }; class WriteToPixelCallbackStage : public RenderPipelineStage { @@ -242,40 +288,45 @@ size_t xextra, size_t xsize, size_t xpos, size_t ypos, float* JXL_RESTRICT temp) const final { 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); + line_buffers[c] = GetInputRow(input_rows, c, 0) - xextra; } if (has_alpha_) { - line_buffers[3] = GetInputRow(input_rows, alpha_c_, 0); + line_buffers[3] = GetInputRow(input_rows, alpha_c_, 0) - xextra; } else { + // No xextra offset; opaque_alpha_ is a way to set all values to 1.0f. line_buffers[3] = opaque_alpha_.data(); } // TODO(veluca): SIMD. ssize_t limit = std::min(xextra + xsize, width_ - xpos); for (ssize_t x0 = -xextra; x0 < limit; x0 += kMaxPixelsPerCall) { size_t j = 0; size_t ix = 0; for (; ix < kMaxPixelsPerCall && ssize_t(ix) + x0 < limit; ix++) { - temp[j++] = line_buffers[0][x0 + ix]; - temp[j++] = line_buffers[1][x0 + ix]; - temp[j++] = line_buffers[2][x0 + 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][x0 + ix]; + temp[j++] = line_buffers[3][ix]; } } pixel_callback_(temp, xpos + x0, ypos, ix); + for (size_t c = 0; c < 3; c++) line_buffers[c] += kMaxPixelsPerCall; + if (has_alpha_) line_buffers[3] += kMaxPixelsPerCall; } } RenderPipelineChannelMode GetChannelMode(size_t c) const final { return c < 3 || (has_alpha_ && c == alpha_c_) ? RenderPipelineChannelMode::kInput : RenderPipelineChannelMode::kIgnored; } + const char* GetName() const override { return "WritePixelCB"; } + private: static constexpr size_t kMaxPixelsPerCall = 1024; const std::function& pixel_callback_; size_t width_; @@ -287,12 +338,13 @@ }; } // namespace std::unique_ptr GetWriteToImageBundleStage( - ImageBundle* image_bundle) { - return jxl::make_unique(image_bundle); + ImageBundle* image_bundle, ColorEncoding color_encoding) { + return jxl::make_unique(image_bundle, + std::move(color_encoding)); } std::unique_ptr GetWriteToImage3FStage(Image3F* image) { return jxl::make_unique(image); } 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 @@ -10,10 +10,11 @@ #include #include #include "lib/jxl/dec_xyb-inl.h" #include "lib/jxl/fast_math-inl.h" +#include "lib/jxl/sanitizers.h" #include "lib/jxl/transfer_functions-inl.h" HWY_BEFORE_NAMESPACE(); namespace jxl { namespace HWY_NAMESPACE { @@ -60,11 +61,11 @@ } }; struct OpHlg { explicit OpHlg(const float luminances[3], const float intensity_target) - : luminances(luminances) { + : luminances(luminances), exponent(1.0f) { if (295 <= intensity_target && intensity_target <= 305) { apply_inverse_ootf = false; return; } exponent = @@ -129,11 +130,11 @@ // value-dependent behaviour (e.g. NearestInt). Temporary unpoison last // vector tail. msan::UnpoisonMemory(row0 + xsize, sizeof(float) * (xsize_v - xsize)); msan::UnpoisonMemory(row1 + xsize, sizeof(float) * (xsize_v - xsize)); msan::UnpoisonMemory(row2 + xsize, sizeof(float) * (xsize_v - xsize)); - for (int64_t x = -xextra; x < (int64_t)(xsize + xextra); x += Lanes(d)) { + for (ssize_t x = -xextra; x < (ssize_t)(xsize + xextra); x += Lanes(d)) { const auto in_opsin_x = Load(d, row0 + x); const auto in_opsin_y = Load(d, row1 + x); const auto in_opsin_b = Load(d, row2 + x); auto r = Undefined(d); auto g = Undefined(d); @@ -153,10 +154,12 @@ RenderPipelineChannelMode GetChannelMode(size_t c) const final { return c < 3 ? RenderPipelineChannelMode::kInPlace : RenderPipelineChannelMode::kIgnored; } + const char* GetName() const override { return "XYB"; } + private: OpsinParams opsin_params_; Op op_; }; @@ -242,10 +245,12 @@ return c < 3 || (has_alpha_ && c == alpha_c_) ? RenderPipelineChannelMode::kInput : RenderPipelineChannelMode::kIgnored; } + const char* GetName() const override { return "FastXYB"; } + private: uint8_t* rgb_; size_t stride_; size_t width_; size_t height_; 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 @@ -51,10 +51,12 @@ RenderPipelineChannelMode GetChannelMode(size_t c) const final { return c < 3 ? RenderPipelineChannelMode::kInPlace : RenderPipelineChannelMode::kIgnored; } + + const char* GetName() const override { return "YCbCr"; } }; std::unique_ptr GetYCbCrStage() { return jxl::make_unique(); } 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 @@ -36,10 +36,12 @@ *(row_out + 2 * x + 1) = xout1; } } } + const char* GetName() const override { return "TEST::UpsampleXSlowStage"; } + RenderPipelineChannelMode GetChannelMode(size_t c) const final { return RenderPipelineChannelMode::kInOut; } }; @@ -70,10 +72,12 @@ } RenderPipelineChannelMode GetChannelMode(size_t c) const final { return RenderPipelineChannelMode::kInOut; } + + const char* GetName() const override { return "TEST::UpsampleYSlowStage"; } }; class Check0FinalStage : public RenderPipelineStage { public: Check0FinalStage() : RenderPipelineStage(RenderPipelineStage::Settings()) {} @@ -89,8 +93,9 @@ } RenderPipelineChannelMode GetChannelMode(size_t c) const final { return RenderPipelineChannelMode::kInput; } + const char* GetName() const override { return "TEST::Check0FinalStage"; } }; } // namespace jxl 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 @@ -36,12 +36,11 @@ const JxlPixelFormat& pixel_format, const jxl::PaddedBytes& icc_profile) { jxl::CodecInOut io; io.SetSize(xsize, ysize); - bool is_gray = - pixel_format.num_channels == 1 || pixel_format.num_channels == 2; + bool is_gray = pixel_format.num_channels < 3; bool has_alpha = pixel_format.num_channels == 2 || pixel_format.num_channels == 4; io.metadata.m.color_encoding.SetColorSpace(is_gray ? jxl::ColorSpace::kGray : jxl::ColorSpace::kRGB); @@ -96,17 +95,17 @@ } else if (pixel_format.data_type == JXL_TYPE_FLOAT) { color_encoding = jxl::ColorEncoding::LinearSRGB(is_gray); } else { color_encoding = jxl::ColorEncoding::SRGB(is_gray); } - EXPECT_TRUE( - ConvertFromExternal(jxl::Span(buf.data(), buf.size()), - xsize, ysize, color_encoding, has_alpha, - /*alpha_is_premultiplied=*/false, - /*bits_per_sample=*/bitdepth, pixel_format.endianness, - /*flipped_y=*/false, /*pool=*/nullptr, &io.Main(), - float_in, /*align=*/0)); + EXPECT_TRUE(ConvertFromExternal( + jxl::Span(buf.data(), buf.size()), xsize, ysize, + color_encoding, pixel_format.num_channels, + /*alpha_is_premultiplied=*/false, + /*bits_per_sample=*/bitdepth, pixel_format.endianness, + /*flipped_y=*/false, /*pool=*/nullptr, &io.Main(), float_in, + /*align=*/0)); return io; } template T ConvertTestPixel(const float val); diff --git a/third_party/jpeg-xl/lib/jxl/splines.h b/third_party/jpeg-xl/lib/jxl/splines.h --- a/third_party/jpeg-xl/lib/jxl/splines.h +++ b/third_party/jpeg-xl/lib/jxl/splines.h @@ -57,15 +57,15 @@ class QuantizedSpline { public: QuantizedSpline() = default; explicit QuantizedSpline(const Spline& original, - int32_t quantization_adjustment, float ytox, - float ytob); + int32_t quantization_adjustment, float y_to_x, + float y_to_b); Status Dequantize(const Spline::Point& starting_point, - int32_t quantization_adjustment, float ytox, float ytob, + int32_t quantization_adjustment, float y_to_x, float y_to_b, Spline& result) const; Status Decode(const std::vector& context_map, ANSSymbolReader* decoder, BitReader* br, size_t max_control_points, size_t* total_num_control_points); diff --git a/third_party/jpeg-xl/lib/jxl/splines.cc b/third_party/jpeg-xl/lib/jxl/splines.cc --- a/third_party/jpeg-xl/lib/jxl/splines.cc +++ b/third_party/jpeg-xl/lib/jxl/splines.cc @@ -211,10 +211,21 @@ HWY_EXPORT(SegmentsFromPoints); HWY_EXPORT(DrawSegments); namespace { +// It is not in spec, but reasonable limit to avoid overflows. +template +Status ValidateSplinePointPos(const T& x, const T& y) { + constexpr T kSplinePosLimit = 1u << 23; + if ((x >= kSplinePosLimit) || (x <= -kSplinePosLimit) || + (y >= kSplinePosLimit) || (y <= -kSplinePosLimit)) { + return JXL_FAILURE("Spline coordinates out of bounds"); + } + return true; +} + // Maximum number of spline control points per frame is // std::min(kMaxNumControlPoints, xsize * ysize / 2) constexpr size_t kMaxNumControlPoints = 1u << 20u; constexpr size_t kMaxNumControlPointsPerPixelRatio = 2; @@ -246,10 +257,11 @@ reader->ReadHybridUint(kStartingPositionContext, br, context_map); if (i != 0) { x = UnpackSigned(x) + last_x; y = UnpackSigned(y) + last_y; } + JXL_RETURN_IF_ERROR(ValidateSplinePointPos(x, y)); points->emplace_back(static_cast(x), static_cast(y)); last_x = x; last_y = y; } return true; @@ -416,34 +428,26 @@ const int32_t quantization_adjustment, const float y_to_x, const float y_to_b, Spline& result) const { result.control_points.clear(); result.control_points.reserve(control_points_.size() + 1); - int current_x = static_cast(roundf(starting_point.x)), - current_y = static_cast(roundf(starting_point.y)); - // It is not in spec, but reasonable limit to avoid overflows. - constexpr int kPosLimit = 1u << 23; - if ((current_x >= kPosLimit) || (current_x <= -kPosLimit) || - (current_y >= kPosLimit) || (current_y <= -kPosLimit)) { - return JXL_FAILURE("Spline coordinates out of bounds"); - } + float px = roundf(starting_point.x); + float py = roundf(starting_point.y); + JXL_RETURN_IF_ERROR(ValidateSplinePointPos(px, py)); + int current_x = static_cast(px); + int current_y = static_cast(py); result.control_points.push_back(Spline::Point{static_cast(current_x), static_cast(current_y)}); int current_delta_x = 0, current_delta_y = 0; for (const auto& point : control_points_) { current_delta_x += point.first; current_delta_y += point.second; - if ((current_delta_x >= kPosLimit) || (current_delta_x <= -kPosLimit) || - (current_delta_y >= kPosLimit) || (current_delta_y <= -kPosLimit)) { - return JXL_FAILURE("Spline coordinates out of bounds"); - } + JXL_RETURN_IF_ERROR( + ValidateSplinePointPos(current_delta_x, current_delta_y)); current_x += current_delta_x; current_y += current_delta_y; - if ((current_x >= kPosLimit) || (current_x <= -kPosLimit) || - (current_y >= kPosLimit) || (current_y <= -kPosLimit)) { - return JXL_FAILURE("Spline coordinates out of bounds"); - } + JXL_RETURN_IF_ERROR(ValidateSplinePointPos(current_x, current_y)); result.control_points.push_back(Spline::Point{ static_cast(current_x), static_cast(current_y)}); } const auto inv_quant = InvAdjustedQuant(quantization_adjustment); 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 @@ -367,13 +367,11 @@ io.metadata.m.SetAlphaBits(16); io.metadata.m.color_encoding = jxl::ColorEncoding::SRGB( /*is_gray=*/num_channels == 1 || num_channels == 2); EXPECT_TRUE(ConvertFromExternal( jxl::Span(buf.data(), buf.size()), xsize, ysize, - jxl::ColorEncoding::SRGB(/*is_gray=*/num_channels == 1 || - num_channels == 2), - /*has_alpha=*/num_channels == 2 || num_channels == 4, + jxl::ColorEncoding::SRGB(/*is_gray=*/num_channels < 3), num_channels, /*alpha_is_premultiplied=*/false, /*bits_per_sample=*/16, JXL_BIG_ENDIAN, /*flipped_y=*/false, /*pool=*/nullptr, /*ib=*/&io.Main(), /*float_in=*/false, 0)); return io; } diff --git a/third_party/jpeg-xl/lib/jxl/xorshift128plus-inl.h b/third_party/jpeg-xl/lib/jxl/xorshift128plus-inl.h --- a/third_party/jpeg-xl/lib/jxl/xorshift128plus-inl.h +++ b/third_party/jpeg-xl/lib/jxl/xorshift128plus-inl.h @@ -39,10 +39,23 @@ s0_[i] = SplitMix64(s1_[i - 1]); s1_[i] = SplitMix64(s0_[i]); } } + HWY_MAYBE_UNUSED Xorshift128Plus(const uint32_t seed1, const uint32_t seed2, + const uint32_t seed3, const uint32_t seed4) { + // Init state using SplitMix64 generator + s0_[0] = SplitMix64(((static_cast(seed1) << 32) + seed2) + + 0x9E3779B97F4A7C15ull); + s1_[0] = SplitMix64(((static_cast(seed3) << 32) + seed4) + + 0x9E3779B97F4A7C15ull); + for (size_t i = 1; i < N; ++i) { + s0_[i] = SplitMix64(s0_[i - 1]); + s1_[i] = SplitMix64(s1_[i - 1]); + } + } + HWY_INLINE HWY_MAYBE_UNUSED void Fill(uint64_t* HWY_RESTRICT random_bits) { #if HWY_CAP_INTEGER64 const HWY_FULL(uint64_t) d; for (size_t i = 0; i < N; i += Lanes(d)) { auto s1 = Load(d, s0_ + i); 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 @@ -6,11 +6,10 @@ # All files ending in "_gbench.cc" are considered Google benchmark files and # should be listed here. set(JPEGXL_INTERNAL_SOURCES_GBENCH extras/tone_mapping_gbench.cc jxl/dec_external_image_gbench.cc - jxl/dec_reconstruct_gbench.cc jxl/enc_external_image_gbench.cc jxl/gauss_blur_gbench.cc jxl/splines_gbench.cc jxl/tf_gbench.cc ) 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 @@ -4,11 +4,10 @@ set(TEST_FILES extras/codec_test.cc extras/dec/color_description_test.cc extras/dec/pgx_test.cc jxl/ac_strategy_test.cc - jxl/adaptive_reconstruction_test.cc jxl/alpha_test.cc jxl/ans_common_test.cc jxl/ans_test.cc jxl/bit_reader_test.cc jxl/bits_test.cc @@ -16,11 +15,10 @@ jxl/butteraugli_test.cc jxl/byte_order_test.cc jxl/coeff_order_test.cc jxl/color_encoding_internal_test.cc jxl/color_management_test.cc - jxl/compressed_image_test.cc jxl/convolve_test.cc jxl/data_parallel_test.cc jxl/dct_test.cc jxl/decode_test.cc jxl/enc_external_image_test.cc @@ -28,11 +26,10 @@ jxl/encode_test.cc jxl/entropy_coder_test.cc jxl/fast_dct_test.cc jxl/fast_math_test.cc jxl/fields_test.cc - jxl/filters_internal_test.cc jxl/gaborish_test.cc jxl/gamma_correct_test.cc jxl/gauss_blur_test.cc jxl/gradient_test.cc jxl/iaca_test.cc 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 @@ -109,15 +109,11 @@ "jxl/dec_noise.cc", "jxl/dec_noise.h", "jxl/dec_params.h", "jxl/dec_patch_dictionary.cc", "jxl/dec_patch_dictionary.h", - "jxl/dec_reconstruct.cc", - "jxl/dec_reconstruct.h", "jxl/dec_transforms-inl.h", - "jxl/dec_upsample.cc", - "jxl/dec_upsample.h", "jxl/dec_xyb-inl.h", "jxl/dec_xyb.cc", "jxl/dec_xyb.h", "jxl/decode.cc", "jxl/decode_to_jpeg.cc", @@ -139,13 +135,10 @@ "jxl/fast_dct8-inl.h", "jxl/fast_math-inl.h", "jxl/field_encodings.h", "jxl/fields.cc", "jxl/fields.h", - "jxl/filters.cc", - "jxl/filters.h", - "jxl/filters_internal.h", "jxl/frame_header.cc", "jxl/frame_header.h", "jxl/gauss_blur.cc", "jxl/gauss_blur.h", "jxl/headers.cc", @@ -364,20 +357,18 @@ ] libjxl_gbench_sources = [ "extras/tone_mapping_gbench.cc", "jxl/dec_external_image_gbench.cc", - "jxl/dec_reconstruct_gbench.cc", "jxl/enc_external_image_gbench.cc", "jxl/gauss_blur_gbench.cc", "jxl/splines_gbench.cc", "jxl/tf_gbench.cc", ] libjxl_tests_sources = [ "jxl/ac_strategy_test.cc", - "jxl/adaptive_reconstruction_test.cc", "jxl/alpha_test.cc", "jxl/ans_common_test.cc", "jxl/ans_test.cc", "jxl/bit_reader_test.cc", "jxl/bits_test.cc", @@ -385,11 +376,10 @@ "jxl/butteraugli_test.cc", "jxl/byte_order_test.cc", "jxl/coeff_order_test.cc", "jxl/color_encoding_internal_test.cc", "jxl/color_management_test.cc", - "jxl/compressed_image_test.cc", "jxl/convolve_test.cc", "jxl/data_parallel_test.cc", "jxl/dct_test.cc", "jxl/decode_test.cc", "jxl/enc_external_image_test.cc", @@ -397,11 +387,10 @@ "jxl/encode_test.cc", "jxl/entropy_coder_test.cc", "jxl/fast_dct_test.cc", "jxl/fast_math_test.cc", "jxl/fields_test.cc", - "jxl/filters_internal_test.cc", "jxl/gaborish_test.cc", "jxl/gamma_correct_test.cc", "jxl/gauss_blur_test.cc", "jxl/gradient_test.cc", "jxl/iaca_test.cc", diff --git a/third_party/jpeg-xl/plugins/gimp/file-jxl-load.cc b/third_party/jpeg-xl/plugins/gimp/file-jxl-load.cc --- a/third_party/jpeg-xl/plugins/gimp/file-jxl-load.cc +++ b/third_party/jpeg-xl/plugins/gimp/file-jxl-load.cc @@ -17,15 +17,15 @@ GimpColorProfile *profile_int = nullptr; bool is_linear = false; gint32 layer; - gpointer pixels_buffer_1; - gpointer pixels_buffer_2; - size_t buffer_size; + gpointer pixels_buffer_1 = nullptr; + gpointer pixels_buffer_2 = nullptr; + size_t buffer_size = 0; - GimpImageBaseType image_type; + GimpImageBaseType image_type = GIMP_RGB; GimpImageType layer_type = GIMP_RGB_IMAGE; GimpPrecision precision = GIMP_PRECISION_U16_GAMMA; JxlBasicInfo info = {}; JxlPixelFormat format = {}; diff --git a/third_party/jpeg-xl/plugins/gimp/file-jxl-save.cc b/third_party/jpeg-xl/plugins/gimp/file-jxl-save.cc --- a/third_party/jpeg-xl/plugins/gimp/file-jxl-save.cc +++ b/third_party/jpeg-xl/plugins/gimp/file-jxl-save.cc @@ -726,11 +726,11 @@ g_printerr(SAVE_PROC " Error: JxlEncoderSetParallelRunner failed\n"); return false; } // try to use ICC profile - if (icc.size() > 0 && !jxl_save_opts.is_gray) { + if (!icc.empty() && !jxl_save_opts.is_gray) { if (JXL_ENC_SUCCESS == JxlEncoderSetICCProfile(enc.get(), icc.data(), icc.size())) { jxl_save_opts.icc_attached = true; } else { g_printerr(SAVE_PROC " Warning: JxlEncoderSetICCProfile failed.\n"); 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 @@ -144,11 +144,11 @@ configure_file("${CMAKE_CURRENT_SOURCE_DIR}/skcms/LICENSE" ${PROJECT_BINARY_DIR}/LICENSE.skcms COPYONLY) endif () if (JPEGXL_ENABLE_VIEWERS OR NOT JPEGXL_ENABLE_SKCMS) if( NOT EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/lcms/.git" OR JPEGXL_FORCE_SYSTEM_LCMS2 ) - find_package(LCMS2 2.10) + find_package(LCMS2 2.13) if ( NOT LCMS2_FOUND ) message(FATAL_ERROR "Please install lcms2 or run git submodule update --init") endif () else() include(lcms2.cmake) diff --git a/third_party/jpeg-xl/third_party/lcms2.cmake b/third_party/jpeg-xl/third_party/lcms2.cmake --- a/third_party/jpeg-xl/third_party/lcms2.cmake +++ b/third_party/jpeg-xl/third_party/lcms2.cmake @@ -58,6 +58,20 @@ target_compile_definitions(lcms2 PRIVATE "-DCMS_PTR_ALIGNMENT=8") target_compile_definitions(lcms2 PUBLIC "-DCMS_NO_REGISTER_KEYWORD=1") +# Ensure that a thread safe alternative of gmtime is used in LCMS +include(CheckSymbolExists) +check_symbol_exists(gmtime_r "time.h" HAVE_GMTIME_R) +if (HAVE_GMTIME_R) + target_compile_definitions(lcms2 + PUBLIC "-DHAVE_GMTIME_R=1") +else() + check_symbol_exists(gmtime_s "time.h" HAVE_GMTIME_S) + if (HAVE_GMTIME_S) + target_compile_definitions(lcms2 + PUBLIC "-DHAVE_GMTIME_S=1") + endif() +endif() + set_property(TARGET lcms2 PROPERTY POSITION_INDEPENDENT_CODE ON)