diff --git a/media/libjxl/moz.build b/media/libjxl/moz.build --- a/media/libjxl/moz.build +++ b/media/libjxl/moz.build @@ -12,15 +12,12 @@ SOURCES += [ "/third_party/jpeg-xl/lib/jxl/ac_strategy.cc", "/third_party/jpeg-xl/lib/jxl/alpha.cc", "/third_party/jpeg-xl/lib/jxl/ans_common.cc", - "/third_party/jpeg-xl/lib/jxl/base/cache_aligned.cc", - "/third_party/jpeg-xl/lib/jxl/base/data_parallel.cc", - "/third_party/jpeg-xl/lib/jxl/base/padded_bytes.cc", - "/third_party/jpeg-xl/lib/jxl/base/random.cc", "/third_party/jpeg-xl/lib/jxl/blending.cc", + "/third_party/jpeg-xl/lib/jxl/cache_aligned.cc", "/third_party/jpeg-xl/lib/jxl/chroma_from_luma.cc", "/third_party/jpeg-xl/lib/jxl/cms/color_management.cc", "/third_party/jpeg-xl/lib/jxl/coeff_order.cc", "/third_party/jpeg-xl/lib/jxl/color_encoding_internal.cc", "/third_party/jpeg-xl/lib/jxl/compressed_dc.cc", @@ -73,10 +70,11 @@ "/third_party/jpeg-xl/lib/jxl/render_pipeline/low_memory_render_pipeline.cc", "/third_party/jpeg-xl/lib/jxl/render_pipeline/render_pipeline.cc", "/third_party/jpeg-xl/lib/jxl/render_pipeline/simple_render_pipeline.cc", "/third_party/jpeg-xl/lib/jxl/render_pipeline/stage_blending.cc", "/third_party/jpeg-xl/lib/jxl/render_pipeline/stage_chroma_upsampling.cc", + "/third_party/jpeg-xl/lib/jxl/render_pipeline/stage_cms.cc", "/third_party/jpeg-xl/lib/jxl/render_pipeline/stage_epf.cc", "/third_party/jpeg-xl/lib/jxl/render_pipeline/stage_from_linear.cc", "/third_party/jpeg-xl/lib/jxl/render_pipeline/stage_gaborish.cc", "/third_party/jpeg-xl/lib/jxl/render_pipeline/stage_noise.cc", "/third_party/jpeg-xl/lib/jxl/render_pipeline/stage_patches.cc", diff --git a/media/libjxl/moz.yaml b/media/libjxl/moz.yaml --- a/media/libjxl/moz.yaml +++ b/media/libjxl/moz.yaml @@ -8,13 +8,13 @@ name: libjxl description: JPEG XL image format reference implementation url: https://github.com/libjxl/libjxl - release: d3a69dbeef78f036969a2500f949f931df857e17 (2023-10-18T13:56:09Z). + release: 878c944103bb03f647f8513f4bcfecf351607bdc (2023-11-27T20:14:17Z). - revision: d3a69dbeef78f036969a2500f949f931df857e17 + revision: 878c944103bb03f647f8513f4bcfecf351607bdc license: Apache-2.0 license-file: LICENSE diff --git a/third_party/jpeg-xl/AUTHORS b/third_party/jpeg-xl/AUTHORS --- a/third_party/jpeg-xl/AUTHORS +++ b/third_party/jpeg-xl/AUTHORS @@ -30,12 +30,14 @@ Daniel Novomeský David Burnett dependabot[bot] Diego Pino Dirk Lemstra +Dmitry Baryshev Don Olmstead Dong Xu +estrogently <41487185+estrogently@users.noreply.github.com> Even Rouault Fred Brennan gi-man Gilles Devillers (GilDev) Heiko Becker 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 @@ -552,7 +552,9 @@ endmacro() set(all_tests_list) list_test_targets(all_tests_list ${CMAKE_CURRENT_SOURCE_DIR}) -add_custom_target(all_tests) -add_dependencies(all_tests ${all_tests_list}) +if(all_tests_list) + add_custom_target(all_tests) + add_dependencies(all_tests ${all_tests_list}) +endif() diff --git a/third_party/jpeg-xl/WORKSPACE b/third_party/jpeg-xl/WORKSPACE --- a/third_party/jpeg-xl/WORKSPACE +++ b/third_party/jpeg-xl/WORKSPACE @@ -35,11 +35,12 @@ build_file_content = """ cc_library( name = "skcms", srcs = [ "skcms.cc", - "skcms_internal.h", + "src/skcms_internals.h", + "src/skcms_Transform.h", "src/Transform_inl.h", ], hdrs = ["skcms.h"], visibility = ["//visibility:public"], ) diff --git a/third_party/jpeg-xl/ci.sh b/third_party/jpeg-xl/ci.sh --- a/third_party/jpeg-xl/ci.sh +++ b/third_party/jpeg-xl/ci.sh @@ -191,11 +191,11 @@ # The current hash at the top of the current branch or merge request branch (if # running from a merge request pipeline). MR_HEAD_SHA="" # The common ancestor between the current commit and the tracked branch, such -# as master. This includes a list +# as main. This includes a list MR_ANCESTOR_SHA="" # Populate MR_HEAD_SHA and MR_ANCESTOR_SHA. merge_request_commits() { { set +x; } 2>/dev/null @@ -1367,12 +1367,12 @@ -e "s/(set\\(JPEGXL_MAJOR_VERSION) [0-9]+\\)/\\1 ${major})/" \ -e "s/(set\\(JPEGXL_MINOR_VERSION) [0-9]+\\)/\\1 ${minor})/" \ -e "s/(set\\(JPEGXL_PATCH_VERSION) [0-9]+\\)/\\1 ${patch})/" \ -i lib/CMakeLists.txt sed -E \ - -e "s/(LIBJXL_VERSION: )[0-9\\.]+/\\1 ${major}.${minor}.${patch}/" \ - -e "s/(LIBJXL_ABI_VERSION: )[0-9\\.]+/\\1 ${major}.${minor}/" \ + -e "s/(LIBJXL_VERSION: )\"[0-9.]+\"/\\1\"${major}.${minor}.${patch}\"/" \ + -e "s/(LIBJXL_ABI_VERSION: )\"[0-9.]+\"/\\1\"${major}.${minor}\"/" \ -i .github/workflows/conformance.yml # Update lib.gni tools/scripts/build_cleaner.py --update diff --git a/third_party/jpeg-xl/cmake/FindHWY.cmake b/third_party/jpeg-xl/cmake/FindHWY.cmake --- a/third_party/jpeg-xl/cmake/FindHWY.cmake +++ b/third_party/jpeg-xl/cmake/FindHWY.cmake @@ -21,17 +21,17 @@ if (HWY_INCLUDE_DIR AND NOT HWY_VERSION) if (EXISTS "${HWY_INCLUDE_DIR}/hwy/highway.h") file(READ "${HWY_INCLUDE_DIR}/hwy/highway.h" HWY_VERSION_CONTENT) - string(REGEX MATCH "#define HWY_MAJOR +([0-9]+)" _dummy "${HWY_VERSION_CONTENT}") + string(REGEX MATCH "#define HWY_MAJOR +([0-9]+)" _sink "${HWY_VERSION_CONTENT}") set(HWY_VERSION_MAJOR "${CMAKE_MATCH_1}") - string(REGEX MATCH "#define +HWY_MINOR +([0-9]+)" _dummy "${HWY_VERSION_CONTENT}") + string(REGEX MATCH "#define +HWY_MINOR +([0-9]+)" _sink "${HWY_VERSION_CONTENT}") set(HWY_VERSION_MINOR "${CMAKE_MATCH_1}") - string(REGEX MATCH "#define +HWY_PATCH +([0-9]+)" _dummy "${HWY_VERSION_CONTENT}") + string(REGEX MATCH "#define +HWY_PATCH +([0-9]+)" _sink "${HWY_VERSION_CONTENT}") set(HWY_VERSION_PATCH "${CMAKE_MATCH_1}") set(HWY_VERSION "${HWY_VERSION_MAJOR}.${HWY_VERSION_MINOR}.${HWY_VERSION_PATCH}") endif () endif () diff --git a/third_party/jpeg-xl/debian/changelog b/third_party/jpeg-xl/debian/changelog --- a/third_party/jpeg-xl/debian/changelog +++ b/third_party/jpeg-xl/debian/changelog @@ -1,10 +1,16 @@ -jpeg-xl (0.9.0) UNRELEASED; urgency=medium +jpeg-xl (0.10.0) UNRELEASED; urgency=medium + + * Bump JPEG XL version to 0.10.0. + + -- JPEG XL Maintainers Tue, 21 Nov 2023 10:32:59 +0100 + +jpeg-xl (0.9.0) unstable; urgency=medium * Bump JPEG XL version to 0.9.0. - -- JPEG XL Maintainers Wed, 11 Jan 2023 16:12:35 +0000 + -- JPEG XL Maintainers Tue, 21 Nov 2023 10:32:59 +0100 jpeg-xl (0.8) unstable; urgency=medium * Bump JPEG XL version to 0.8. diff --git a/third_party/jpeg-xl/deps.sh b/third_party/jpeg-xl/deps.sh --- a/third_party/jpeg-xl/deps.sh +++ b/third_party/jpeg-xl/deps.sh @@ -13,11 +13,11 @@ # Git revisions we use for the given submodules. Update these whenever you # update a git submodule. THIRD_PARTY_BROTLI="36533a866ed1ca4b75cf049f4521e4ec5fe24727" THIRD_PARTY_HIGHWAY="ba0900a4957b929390ab73827235557959234fea" -THIRD_PARTY_SKCMS="b25b07b4b07990811de121c0356155b2ba0f4318" +THIRD_PARTY_SKCMS="42030a771244ba67f86b1c1c76a6493f873c5f91" THIRD_PARTY_SJPEG="e5ab13008bb214deb66d5f3e17ca2f8dbff150bf" THIRD_PARTY_ZLIB="cacf7f1d4e3d44d871b605da3b647f07d718623f" THIRD_PARTY_LIBPNG="a40189cf881e9f0db80511c382292a5604c3c3d1" THIRD_PARTY_LIBJPEG_TURBO="8ecba3647edb6dd940463fedf38ca33a8e2a73d1" # 2.1.5.1 diff --git a/third_party/jpeg-xl/examples/CMakeLists.txt b/third_party/jpeg-xl/examples/CMakeLists.txt --- a/third_party/jpeg-xl/examples/CMakeLists.txt +++ b/third_party/jpeg-xl/examples/CMakeLists.txt @@ -10,29 +10,31 @@ project(SAMPLE_LIBJXL LANGUAGES C CXX) # Use pkg-config to find libjxl. find_package(PkgConfig) pkg_check_modules(Jxl REQUIRED IMPORTED_TARGET libjxl) +pkg_check_modules(JxlCms REQUIRED IMPORTED_TARGET libjxl_cms) pkg_check_modules(JxlThreads REQUIRED IMPORTED_TARGET libjxl_threads) # Build the example encoder/decoder binaries using the default shared libraries # installed. add_executable(decode_oneshot decode_oneshot.cc) -target_link_libraries(decode_oneshot PkgConfig::Jxl PkgConfig::JxlThreads) +target_link_libraries(decode_oneshot PkgConfig::Jxl PkgConfig::JxlCms PkgConfig::JxlThreads) add_executable(decode_progressive decode_progressive.cc) -target_link_libraries(decode_progressive PkgConfig::Jxl PkgConfig::JxlThreads) +target_link_libraries(decode_progressive PkgConfig::Jxl PkgConfig::JxlCms PkgConfig::JxlThreads) add_executable(encode_oneshot encode_oneshot.cc) -target_link_libraries(encode_oneshot PkgConfig::Jxl PkgConfig::JxlThreads) - +target_link_libraries(encode_oneshot PkgConfig::Jxl PkgConfig::JxlCms PkgConfig::JxlThreads) # Building a static binary with the static libjxl dependencies. How to load # static library configs from pkg-config and how to build static binaries # depends on the platform, and building static binaries in general has problems. # If you don't need static binaries you can remove this section. add_library(StaticJxl INTERFACE IMPORTED GLOBAL) +list(REMOVE_ITEM Jxl_STATIC_LDFLAGS "-ljxl_cms") + set_target_properties(StaticJxl PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "${Jxl_STATIC_INCLUDE_DIR}" INTERFACE_COMPILE_OPTIONS "${Jxl_STATIC_CFLAGS_OTHER}" INTERFACE_LINK_LIBRARIES "${Jxl_STATIC_LDFLAGS}" ) @@ -46,11 +48,9 @@ INTERFACE_LINK_LIBRARIES "${JxlThreads_STATIC_LDFLAGS} -Wl,--whole-archive -lpthread -Wl,--no-whole-archive" ) add_executable(decode_oneshot_static decode_oneshot.cc) -target_link_libraries(decode_oneshot_static - -static StaticJxl StaticJxlThreads) +target_link_libraries(decode_oneshot_static -static StaticJxl StaticJxlThreads) add_executable(encode_oneshot_static encode_oneshot.cc) -target_link_libraries(encode_oneshot_static - -static StaticJxl StaticJxlThreads) +target_link_libraries(encode_oneshot_static -static StaticJxl StaticJxlThreads) diff --git a/third_party/jpeg-xl/lib/BUILD b/third_party/jpeg-xl/lib/BUILD --- a/third_party/jpeg-xl/lib/BUILD +++ b/third_party/jpeg-xl/lib/BUILD @@ -5,10 +5,11 @@ # Load sources/headers/tests lists. load( "jxl_lists.bzl", "libjxl_base_sources", + "libjxl_cms_sources", "libjxl_codec_apng_sources", "libjxl_codec_exr_sources", "libjxl_codec_gif_sources", "libjxl_codec_jpegli_sources", "libjxl_codec_jpg_sources", @@ -94,10 +95,19 @@ outs = [JXL_EXPORT_H], cmd = "echo '" + EXPORT_TEMPLATE.replace("@", "JXL") + "' > $@", compatible_with = DEFAULT_COMPATIBILITY, ) +JXL_CMS_EXPORT_H = INCLUDES_DIR + "/jxl/jxl_cms_export.h" + +genrule( + name = "create_jxl_cms_export", + outs = [JXL_CMS_EXPORT_H], + cmd = "echo '" + EXPORT_TEMPLATE.replace("@", "JXL_CMS") + "' > $@", + compatible_with = DEFAULT_COMPATIBILITY, +) + JXL_THREADS_EXPORT_H = INCLUDES_DIR + "/jxl/jxl_threads_export.h" genrule( name = "create_jxl_threads_export", outs = [JXL_THREADS_EXPORT_H], @@ -153,11 +163,14 @@ compatible_with = DEFAULT_COMPATIBILITY, ) cc_library( name = "includes", - hdrs = libjxl_public_headers + [JXL_EXPORT_H], + hdrs = libjxl_public_headers + [ + JXL_EXPORT_H, + JXL_CMS_EXPORT_H, + ], compatible_with = DEFAULT_COMPATIBILITY, strip_include_prefix = INCLUDES_DIR, deps = [":jxl_version"], ) @@ -182,24 +195,29 @@ ] + libjxl_deps_hwy, ) cc_library( name = "jpegxl", - srcs = libjxl_dec_sources + libjxl_dec_box_sources + libjxl_dec_jpeg_sources + libjxl_enc_sources, + srcs = libjxl_dec_sources + libjxl_dec_box_sources + libjxl_dec_jpeg_sources + libjxl_enc_sources + libjxl_cms_sources, compatible_with = DEFAULT_COMPATIBILITY, - defines = ["JPEGXL_ENABLE_SKCMS=1"], + defines = [ + "JPEGXL_ENABLE_SKCMS=1", + "JPEGXL_MAJOR_VERSION=" + str(libjxl_major_version), + "JPEGXL_MINOR_VERSION=" + str(libjxl_minor_version), + "JPEGXL_PATCH_VERSION=" + str(libjxl_patch_version), + ], deps = [ ":base", ":includes", ] + libjxl_deps_brotli + libjxl_deps_hwy + libjxl_deps_skcms, ) cc_library( name = "jpegxl_private", hdrs = [ path - for path in libjxl_dec_sources + libjxl_dec_box_sources + libjxl_dec_jpeg_sources + libjxl_enc_sources + for path in libjxl_dec_sources + libjxl_dec_box_sources + libjxl_dec_jpeg_sources + libjxl_enc_sources + libjxl_cms_sources if path.endswith(".h") and not path.endswith("-inl.h") ], compatible_with = DEFAULT_COMPATIBILITY, deps = [":jpegxl"], ) diff --git a/third_party/jpeg-xl/lib/CMakeLists.txt b/third_party/jpeg-xl/lib/CMakeLists.txt --- a/third_party/jpeg-xl/lib/CMakeLists.txt +++ b/third_party/jpeg-xl/lib/CMakeLists.txt @@ -2,22 +2,22 @@ # # Use of this source code is governed by a BSD-style # license that can be found in the LICENSE file. set(JPEGXL_MAJOR_VERSION 0) -set(JPEGXL_MINOR_VERSION 9) +set(JPEGXL_MINOR_VERSION 10) set(JPEGXL_PATCH_VERSION 0) set(JPEGXL_LIBRARY_VERSION "${JPEGXL_MAJOR_VERSION}.${JPEGXL_MINOR_VERSION}.${JPEGXL_PATCH_VERSION}") # This is the library API/ABI compatibility version. Changing this value makes # the shared library incompatible with previous version. A program linked # against this shared library SOVERSION will not run with an older SOVERSION. # It is important to update this value when making incompatible API/ABI changes # so that programs that depend on libjxl can update their dependencies. Semantic # versioning allows 0.y.z to have incompatible changes in minor versions. -set(JPEGXL_SO_MINOR_VERSION 9) +set(JPEGXL_SO_MINOR_VERSION 10) if (JPEGXL_MAJOR_VERSION EQUAL 0) set(JPEGXL_LIBRARY_SOVERSION "${JPEGXL_MAJOR_VERSION}.${JPEGXL_SO_MINOR_VERSION}") else() set(JPEGXL_LIBRARY_SOVERSION "${JPEGXL_MAJOR_VERSION}") @@ -122,19 +122,40 @@ ) endif() # Clang endif() # WIN32 endif() #!MSVC +if (JPEGXL_ENABLE_SKCMS) + list(APPEND JPEGXL_INTERNAL_FLAGS -DJPEGXL_ENABLE_SKCMS=1) + if (JPEGXL_BUNDLE_SKCMS) + list(APPEND JPEGXL_INTERNAL_FLAGS -DJPEGXL_BUNDLE_SKCMS=1) + endif () +endif () + # strips the -static suffix from all the elements in LIST function(strip_static OUTPUT_VAR LIB_LIST) foreach(lib IN LISTS ${LIB_LIST}) string(REGEX REPLACE "-static$" "" lib "${lib}") list(APPEND out_list "${lib}") endforeach() set(${OUTPUT_VAR} ${out_list} PARENT_SCOPE) endfunction() +# set variables for jxl_cms.cmake and jxl.cmake +if(IS_ABSOLUTE "${CMAKE_INSTALL_INCLUDEDIR}") + set(PKGCONFIG_TARGET_INCLUDES "${CMAKE_INSTALL_INCLUDEDIR}") +else() + set(PKGCONFIG_TARGET_INCLUDES "\${prefix}/${CMAKE_INSTALL_INCLUDEDIR}") +endif() +if(IS_ABSOLUTE "${CMAKE_INSTALL_LIBDIR}") + set(PKGCONFIG_TARGET_LIBS "${CMAKE_INSTALL_LIBDIR}") +else() + set(PKGCONFIG_TARGET_LIBS "\${exec_prefix}/${CMAKE_INSTALL_LIBDIR}") +endif() + +# The jxl_cms library definition. +include(jxl_cms.cmake) # The jxl library definition. include(jxl.cmake) # Other libraries outside the core jxl library. if(JPEGXL_ENABLE_TOOLS) diff --git a/third_party/jpeg-xl/lib/extras/codec.h b/third_party/jpeg-xl/lib/extras/codec.h --- a/third_party/jpeg-xl/lib/extras/codec.h +++ b/third_party/jpeg-xl/lib/extras/codec.h @@ -15,11 +15,10 @@ #include "lib/extras/dec/color_hints.h" #include "lib/extras/dec/decode.h" #include "lib/jxl/base/compiler_specific.h" #include "lib/jxl/base/data_parallel.h" -#include "lib/jxl/base/padded_bytes.h" #include "lib/jxl/base/span.h" #include "lib/jxl/base/status.h" #include "lib/jxl/codec_in_out.h" #include "lib/jxl/color_encoding_internal.h" #include "lib/jxl/field_encodings.h" // MakeBit 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 @@ -14,11 +14,10 @@ #include "lib/extras/enc/jpg.h" #include "lib/extras/enc/pgx.h" #include "lib/extras/enc/pnm.h" #include "lib/extras/packed_image.h" #include "lib/extras/packed_image_convert.h" -#include "lib/jxl/base/padded_bytes.h" #include "lib/jxl/base/status.h" #include "lib/jxl/image_bundle.h" namespace jxl { namespace { diff --git a/third_party/jpeg-xl/lib/extras/codec_test.cc b/third_party/jpeg-xl/lib/extras/codec_test.cc --- a/third_party/jpeg-xl/lib/extras/codec_test.cc +++ b/third_party/jpeg-xl/lib/extras/codec_test.cc @@ -6,19 +6,21 @@ #include "lib/extras/codec.h" #include #include +#include #include #include #include #include #include "lib/extras/dec/decode.h" #include "lib/extras/dec/pnm.h" #include "lib/extras/enc/encode.h" #include "lib/jxl/base/random.h" +#include "lib/jxl/base/span.h" #include "lib/jxl/base/status.h" #include "lib/jxl/test_utils.h" #include "lib/jxl/testing.h" namespace jxl { @@ -99,18 +101,18 @@ c.transfer_function = JXL_TRANSFER_FUNCTION_LINEAR; // Roundtrip through internal color encoding to fill in primaries and white // point CIE xy coordinates. ColorEncoding c_internal; JXL_CHECK(c_internal.FromExternal(c)); - c_internal.ToExternal(&c); + c = c_internal.ToExternal(); return c; } std::vector GenerateICC(JxlColorEncoding color_encoding) { ColorEncoding c; JXL_CHECK(c.FromExternal(color_encoding)); - JXL_CHECK(c.CreateICC()); + JXL_CHECK(!c.ICC().empty()); return c.ICC(); } void StoreRandomValue(uint8_t* out, Rng* rng, JxlPixelFormat format, size_t bits_per_sample) { @@ -263,12 +265,11 @@ ColorHints color_hints; if (params.codec == Codec::kPNM || params.codec == Codec::kPGX) { color_hints.Add("color_space", params.is_gray ? "Gra_D65_Rel_SRG" : "RGB_D65_SRG_Rel_SRG"); } - ASSERT_TRUE(DecodeBytes(Span(encoded.bitstreams[0]), - color_hints, &ppf_out)); + ASSERT_TRUE(DecodeBytes(Bytes(encoded.bitstreams[0]), color_hints, &ppf_out)); if (params.codec == Codec::kPNG && ppf_out.icc.empty()) { // Decoding a PNG may drop the ICC profile if there's a valid cICP chunk. // Rendering intent is not preserved in this case. EXPECT_EQ(ppf_in.color_encoding.color_space, ppf_out.color_encoding.color_space); @@ -346,18 +347,18 @@ if (bit_depth == 1 && (channels == 2 || channels == 4)) continue; std::string extension(kExtension[channels]); std::string filename = "jxl/flower/flower_small." + std::string(kChannels[channels]) + ".depth" + std::to_string(bit_depth) + extension; - const PaddedBytes orig = jxl::test::ReadTestData(filename); + const std::vector orig = jxl::test::ReadTestData(filename); PackedPixelFile ppf; ColorHints color_hints; color_hints.Add("color_space", channels < 3 ? "Gra_D65_Rel_SRG" : "RGB_D65_SRG_Rel_SRG"); - ASSERT_TRUE(DecodeBytes(Span(orig.data(), orig.size()), - color_hints, &ppf)); + ASSERT_TRUE( + DecodeBytes(Bytes(orig.data(), orig.size()), color_hints, &ppf)); EncodedImage encoded; auto encoder = Encoder::FromExtension(extension); ASSERT_TRUE(encoder.get()); ASSERT_TRUE(encoder->Encode(ppf, &encoded, &pool)); @@ -413,15 +414,14 @@ if (!png_encoder) { fprintf(stderr, "Skipping test because of missing codec support.\n"); return; } - const PaddedBytes original_png = jxl::test::ReadTestData( + const std::vector original_png = jxl::test::ReadTestData( "external/wesaturate/500px/tmshre_riaphotographs_srgb8.png"); PackedPixelFile ppf; - ASSERT_TRUE(extras::DecodeBytes(Span(original_png), - ColorHints(), &ppf)); + ASSERT_TRUE(extras::DecodeBytes(Bytes(original_png), ColorHints(), &ppf)); const JxlPixelFormat& format = ppf.frames.front().color.format; ASSERT_THAT( png_encoder->AcceptedFormats(), Contains(AllOf(Field(&JxlPixelFormat::num_channels, format.num_channels), @@ -431,13 +431,12 @@ ASSERT_TRUE(png_encoder->Encode(ppf, &encoded_png, pool)); EXPECT_THAT(encoded_png.icc, IsEmpty()); ASSERT_THAT(encoded_png.bitstreams, SizeIs(1)); PackedPixelFile decoded_ppf; - ASSERT_TRUE( - extras::DecodeBytes(Span(encoded_png.bitstreams.front()), - ColorHints(), &decoded_ppf)); + ASSERT_TRUE(extras::DecodeBytes(Bytes(encoded_png.bitstreams.front()), + ColorHints(), &decoded_ppf)); ASSERT_EQ(decoded_ppf.info.bits_per_sample, ppf.info.bits_per_sample); ASSERT_EQ(decoded_ppf.frames.size(), 1); VerifySameImage(ppf.frames[0].color, ppf.info.bits_per_sample, decoded_ppf.frames[0].color, diff --git a/third_party/jpeg-xl/lib/extras/dec/apng.h b/third_party/jpeg-xl/lib/extras/dec/apng.h --- a/third_party/jpeg-xl/lib/extras/dec/apng.h +++ b/third_party/jpeg-xl/lib/extras/dec/apng.h @@ -11,11 +11,10 @@ #include #include "lib/extras/dec/color_hints.h" #include "lib/extras/packed_image.h" #include "lib/jxl/base/data_parallel.h" -#include "lib/jxl/base/padded_bytes.h" #include "lib/jxl/base/span.h" #include "lib/jxl/base/status.h" namespace jxl { 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 @@ -941,11 +941,11 @@ 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. + // If all else fails, insert a placeholder 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.frame_info.layer_info.crop_x0 = px0; diff --git a/third_party/jpeg-xl/lib/extras/dec/exr.h b/third_party/jpeg-xl/lib/extras/dec/exr.h --- a/third_party/jpeg-xl/lib/extras/dec/exr.h +++ b/third_party/jpeg-xl/lib/extras/dec/exr.h @@ -9,11 +9,10 @@ // Decodes OpenEXR images in memory. #include "lib/extras/dec/color_hints.h" #include "lib/extras/packed_image.h" #include "lib/jxl/base/data_parallel.h" -#include "lib/jxl/base/padded_bytes.h" #include "lib/jxl/base/span.h" #include "lib/jxl/base/status.h" namespace jxl { diff --git a/third_party/jpeg-xl/lib/extras/dec/jpg.h b/third_party/jpeg-xl/lib/extras/dec/jpg.h --- a/third_party/jpeg-xl/lib/extras/dec/jpg.h +++ b/third_party/jpeg-xl/lib/extras/dec/jpg.h @@ -11,11 +11,10 @@ #include #include "lib/extras/codec.h" #include "lib/extras/dec/color_hints.h" #include "lib/jxl/base/data_parallel.h" -#include "lib/jxl/base/padded_bytes.h" #include "lib/jxl/base/span.h" #include "lib/jxl/base/status.h" namespace jxl { diff --git a/third_party/jpeg-xl/lib/extras/dec/jxl.cc b/third_party/jpeg-xl/lib/extras/dec/jxl.cc --- a/third_party/jpeg-xl/lib/extras/dec/jxl.cc +++ b/third_party/jpeg-xl/lib/extras/dec/jxl.cc @@ -10,10 +10,11 @@ #include #include "lib/extras/dec/color_description.h" #include "lib/extras/enc/encode.h" #include "lib/jxl/base/printf_macros.h" +#include "lib/jxl/cms/jxl_cms.h" #include "lib/jxl/exif.h" namespace jxl { namespace extras { namespace { @@ -336,10 +337,11 @@ if (ppf->info.uses_original_profile) { fprintf(stderr, "Warning: --color_space ignored because the image is " "not XYB encoded.\n"); } else { + JxlDecoderSetCms(dec, *JxlGetDefaultCms()); if (JXL_DEC_SUCCESS != JxlDecoderSetPreferredColorProfile(dec, &color_encoding)) { fprintf(stderr, "Failed to set color space.\n"); return false; } diff --git a/third_party/jpeg-xl/lib/extras/dec/pgx.h b/third_party/jpeg-xl/lib/extras/dec/pgx.h --- a/third_party/jpeg-xl/lib/extras/dec/pgx.h +++ b/third_party/jpeg-xl/lib/extras/dec/pgx.h @@ -12,11 +12,10 @@ #include #include "lib/extras/dec/color_hints.h" #include "lib/extras/packed_image.h" #include "lib/jxl/base/data_parallel.h" -#include "lib/jxl/base/padded_bytes.h" #include "lib/jxl/base/span.h" #include "lib/jxl/base/status.h" namespace jxl { diff --git a/third_party/jpeg-xl/lib/extras/dec/pgx_test.cc b/third_party/jpeg-xl/lib/extras/dec/pgx_test.cc --- a/third_party/jpeg-xl/lib/extras/dec/pgx_test.cc +++ b/third_party/jpeg-xl/lib/extras/dec/pgx_test.cc @@ -3,21 +3,22 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. #include "lib/extras/dec/pgx.h" +#include + #include "lib/extras/packed_image_convert.h" #include "lib/jxl/image_bundle.h" #include "lib/jxl/testing.h" namespace jxl { namespace extras { namespace { Span MakeSpan(const char* str) { - return Span(reinterpret_cast(str), - strlen(str)); + return Bytes(reinterpret_cast(str), strlen(str)); } TEST(CodecPGXTest, Test8bits) { std::string pgx = "PG ML + 8 2 3\npixels"; diff --git a/third_party/jpeg-xl/lib/extras/dec/pnm.h b/third_party/jpeg-xl/lib/extras/dec/pnm.h --- a/third_party/jpeg-xl/lib/extras/dec/pnm.h +++ b/third_party/jpeg-xl/lib/extras/dec/pnm.h @@ -11,15 +11,15 @@ #include #include // TODO(janwas): workaround for incorrect Win64 codegen (cause unknown) #include +#include #include "lib/extras/dec/color_hints.h" #include "lib/extras/packed_image.h" #include "lib/jxl/base/data_parallel.h" -#include "lib/jxl/base/padded_bytes.h" #include "lib/jxl/base/span.h" #include "lib/jxl/base/status.h" namespace jxl { @@ -33,9 +33,29 @@ PackedPixelFile* ppf, const SizeConstraints* constraints = nullptr); void TestCodecPNM(); +struct HeaderPNM { + size_t xsize; + size_t ysize; + bool is_gray; // PGM + bool has_alpha; // PAM + size_t bits_per_sample; + bool floating_point; + bool big_endian; + std::vector ec_types; // PAM +}; + +struct ChunkedPNMDecoder { + FILE* f; + HeaderPNM header = {}; + size_t data_start; +}; + +Status DecodeImagePNM(ChunkedPNMDecoder* dec, const ColorHints& color_hints, + PackedPixelFile* ppf); + } // namespace extras } // namespace jxl #endif // LIB_EXTRAS_DEC_PNM_H_ 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 @@ -7,31 +7,21 @@ #include #include #include +#include #include "lib/extras/size_constraints.h" #include "lib/jxl/base/bits.h" #include "lib/jxl/base/compiler_specific.h" #include "lib/jxl/base/status.h" namespace jxl { namespace extras { namespace { -struct HeaderPNM { - size_t xsize; - size_t ysize; - bool is_gray; // PGM - bool has_alpha; // PAM - size_t bits_per_sample; - bool floating_point; - bool big_endian; - std::vector ec_types; // PAM -}; - class Parser { public: explicit Parser(const Span bytes) : pos_(bytes.data()), end_(pos_ + bytes.size()) {} @@ -318,16 +308,85 @@ const uint8_t* pos_; const uint8_t* const end_; }; Span MakeSpan(const char* str) { - return Span(reinterpret_cast(str), - strlen(str)); + return Bytes(reinterpret_cast(str), strlen(str)); +} + +void ReadLinePNM(void* opaque, size_t xpos, size_t ypos, size_t xsize, + uint8_t* buffer, size_t len) { + ChunkedPNMDecoder* dec = reinterpret_cast(opaque); + const size_t bytes_per_channel = + DivCeil(dec->header.bits_per_sample, jxl::kBitsPerByte); + const size_t pixel_offset = ypos * dec->header.xsize + xpos; + const size_t num_channels = dec->header.is_gray ? 1 : 3; + const size_t offset = pixel_offset * num_channels * bytes_per_channel; + const size_t num_bytes = xsize * num_channels * bytes_per_channel; + if (fseek(dec->f, dec->data_start + offset, SEEK_SET) != 0) { + return; + } + JXL_ASSERT(num_bytes == len); + if (num_bytes != fread(buffer, 1, num_bytes, dec->f)) { + JXL_WARNING("Failed to read from PNM file\n"); + } } } // namespace +Status DecodeImagePNM(ChunkedPNMDecoder* dec, const ColorHints& color_hints, + PackedPixelFile* ppf) { + std::vector buffer(10 * 1024); + const size_t bytes_read = fread(buffer.data(), 1, buffer.size(), dec->f); + if (ferror(dec->f) || bytes_read > buffer.size()) { + return false; + } + Span span(buffer); + Parser parser(span); + HeaderPNM& header = dec->header; + const uint8_t* pos = nullptr; + if (!parser.ParseHeader(&header, &pos)) { + return false; + } + dec->data_start = pos - &buffer[0]; + + if (header.bits_per_sample == 0 || header.bits_per_sample > 16) { + return JXL_FAILURE("Invalid bits_per_sample"); + } + if (header.has_alpha || !header.ec_types.empty() || header.floating_point) { + return JXL_FAILURE("Only PGM and PPM inputs are supported"); + } + + // PPM specifies 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; + ppf->info.bits_per_sample = header.bits_per_sample; + ppf->info.exponent_bits_per_sample = 0; + ppf->info.orientation = JXL_ORIENT_IDENTITY; + ppf->info.alpha_bits = 0; + ppf->info.alpha_exponent_bits = 0; + ppf->info.num_color_channels = (header.is_gray ? 1 : 3); + ppf->info.num_extra_channels = 0; + + const JxlDataType data_type = + header.bits_per_sample > 8 ? JXL_TYPE_UINT16 : JXL_TYPE_UINT8; + const JxlPixelFormat format{ + /*num_channels=*/ppf->info.num_color_channels, + /*data_type=*/data_type, + /*endianness=*/header.big_endian ? JXL_BIG_ENDIAN : JXL_LITTLE_ENDIAN, + /*align=*/0, + }; + ppf->chunked_frames.emplace_back(header.xsize, header.ysize, format, dec, + ReadLinePNM); + return true; +} + Status DecodeImagePNM(const Span bytes, const ColorHints& color_hints, PackedPixelFile* ppf, const SizeConstraints* constraints) { Parser parser(bytes); HeaderPNM header = {}; @@ -338,13 +397,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. + // PPM specifies 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; diff --git a/third_party/jpeg-xl/lib/extras/enc/jxl.h b/third_party/jpeg-xl/lib/extras/enc/jxl.h --- a/third_party/jpeg-xl/lib/extras/enc/jxl.h +++ b/third_party/jpeg-xl/lib/extras/enc/jxl.h @@ -60,10 +60,11 @@ // Override input buffer interpretation. JxlBitDepth input_bitdepth = {JXL_BIT_DEPTH_FROM_PIXEL_FORMAT, 0, 0}; // If runner_opaque is set, the decoder uses this parallel runner. JxlParallelRunner runner = JxlThreadParallelRunner; void* runner_opaque = nullptr; + JxlEncoderOutputProcessor output_processor = {}; JxlDebugImageCallback debug_image = nullptr; void* debug_image_opaque = nullptr; JxlEncoderStats* stats = nullptr; bool allow_expert_options = false; @@ -71,10 +72,15 @@ options.emplace_back(JXLOption(id, val, 0)); } void AddFloatOption(JxlEncoderFrameSettingId id, float val) { options.emplace_back(JXLOption(id, val, 0)); } + bool HasOutputProcessor() const { + return (output_processor.get_buffer != nullptr && + output_processor.release_buffer != nullptr && + output_processor.set_finalized_position != nullptr); + } }; bool EncodeImageJXL(const JXLCompressParams& params, const PackedPixelFile& ppf, const std::vector* jpeg_bytes, std::vector* compressed); diff --git a/third_party/jpeg-xl/lib/extras/enc/jxl.cc b/third_party/jpeg-xl/lib/extras/enc/jxl.cc --- a/third_party/jpeg-xl/lib/extras/enc/jxl.cc +++ b/third_party/jpeg-xl/lib/extras/enc/jxl.cc @@ -34,10 +34,82 @@ (*option_idx)++; } return true; } +bool SetupFrame(JxlEncoder* enc, JxlEncoderFrameSettings* settings, + const JxlFrameHeader& frame_header, + const JXLCompressParams& params, const PackedPixelFile& ppf, + size_t frame_index, size_t num_alpha_channels, + size_t num_interleaved_alpha, size_t& option_idx) { + if (JXL_ENC_SUCCESS != JxlEncoderSetFrameHeader(settings, &frame_header)) { + fprintf(stderr, "JxlEncoderSetFrameHeader() failed.\n"); + return false; + } + if (!SetFrameOptions(params.options, frame_index, &option_idx, settings)) { + return false; + } + if (num_alpha_channels > 0) { + JxlExtraChannelInfo extra_channel_info; + JxlEncoderInitExtraChannelInfo(JXL_CHANNEL_ALPHA, &extra_channel_info); + extra_channel_info.bits_per_sample = ppf.info.alpha_bits; + extra_channel_info.exponent_bits_per_sample = ppf.info.alpha_exponent_bits; + if (params.premultiply != -1) { + if (params.premultiply != 0 && params.premultiply != 1) { + fprintf(stderr, "premultiply must be one of: -1, 0, 1.\n"); + return false; + } + extra_channel_info.alpha_premultiplied = params.premultiply; + } + if (JXL_ENC_SUCCESS != + JxlEncoderSetExtraChannelInfo(enc, 0, &extra_channel_info)) { + fprintf(stderr, "JxlEncoderSetExtraChannelInfo() failed.\n"); + return false; + } + // We take the extra channel blend info frame_info, but don't do + // clamping. + JxlBlendInfo extra_channel_blend_info = frame_header.layer_info.blend_info; + extra_channel_blend_info.clamp = JXL_FALSE; + JxlEncoderSetExtraChannelBlendInfo(settings, 0, &extra_channel_blend_info); + } + // Add extra channel info for the rest of the extra channels. + for (size_t i = 0; i < ppf.info.num_extra_channels; ++i) { + if (i < ppf.extra_channels_info.size()) { + const auto& ec_info = ppf.extra_channels_info[i].ec_info; + if (JXL_ENC_SUCCESS != JxlEncoderSetExtraChannelInfo( + enc, num_interleaved_alpha + i, &ec_info)) { + fprintf(stderr, "JxlEncoderSetExtraChannelInfo() failed.\n"); + return false; + } + } + } + return true; +} + +bool ReadCompressedOutput(JxlEncoder* enc, std::vector* compressed) { + compressed->clear(); + compressed->resize(4096); + uint8_t* next_out = compressed->data(); + size_t avail_out = compressed->size() - (next_out - compressed->data()); + JxlEncoderStatus result = JXL_ENC_NEED_MORE_OUTPUT; + while (result == JXL_ENC_NEED_MORE_OUTPUT) { + result = JxlEncoderProcessOutput(enc, &next_out, &avail_out); + if (result == JXL_ENC_NEED_MORE_OUTPUT) { + size_t offset = next_out - compressed->data(); + compressed->resize(compressed->size() * 2); + next_out = compressed->data() + offset; + avail_out = compressed->size() - offset; + } + } + compressed->resize(next_out - compressed->data()); + if (result != JXL_ENC_SUCCESS) { + fprintf(stderr, "JxlEncoderProcessOutput failed.\n"); + return false; + } + return true; +} + bool EncodeImageJXL(const JXLCompressParams& params, const PackedPixelFile& ppf, const std::vector* jpeg_bytes, std::vector* compressed) { auto encoder = JxlEncoderMake(/*memory_manager=*/nullptr); JxlEncoder* enc = encoder.get(); @@ -51,10 +123,17 @@ params.runner_opaque)) { fprintf(stderr, "JxlEncoderSetParallelRunner failed\n"); return false; } + if (params.HasOutputProcessor() && + JXL_ENC_SUCCESS != + JxlEncoderSetOutputProcessor(enc, params.output_processor)) { + fprintf(stderr, "JxlEncoderSetOutputProcessorfailed\n"); + return false; + } + auto settings = JxlEncoderFrameSettingsCreate(enc, nullptr); size_t option_idx = 0; if (!SetFrameOptions(params.options, 0, &option_idx, settings)) { return false; } @@ -220,57 +299,15 @@ for (size_t num_frame = 0; num_frame < ppf.frames.size(); ++num_frame) { const jxl::extras::PackedFrame& pframe = ppf.frames[num_frame]; const jxl::extras::PackedImage& pimage = pframe.color; JxlPixelFormat ppixelformat = pimage.format; - if (JXL_ENC_SUCCESS != - JxlEncoderSetFrameHeader(settings, &pframe.frame_info)) { - fprintf(stderr, "JxlEncoderSetFrameHeader() failed.\n"); - return false; - } - if (!SetFrameOptions(params.options, num_frame, &option_idx, settings)) { - return false; - } - if (num_alpha_channels > 0) { - JxlExtraChannelInfo extra_channel_info; - JxlEncoderInitExtraChannelInfo(JXL_CHANNEL_ALPHA, &extra_channel_info); - extra_channel_info.bits_per_sample = ppf.info.alpha_bits; - extra_channel_info.exponent_bits_per_sample = - ppf.info.alpha_exponent_bits; - if (params.premultiply != -1) { - if (params.premultiply != 0 && params.premultiply != 1) { - fprintf(stderr, "premultiply must be one of: -1, 0, 1.\n"); - return false; - } - extra_channel_info.alpha_premultiplied = params.premultiply; - } - if (JXL_ENC_SUCCESS != - JxlEncoderSetExtraChannelInfo(enc, 0, &extra_channel_info)) { - fprintf(stderr, "JxlEncoderSetExtraChannelInfo() failed.\n"); - return false; - } - // We take the extra channel blend info frame_info, but don't do - // clamping. - JxlBlendInfo extra_channel_blend_info = - pframe.frame_info.layer_info.blend_info; - extra_channel_blend_info.clamp = JXL_FALSE; - JxlEncoderSetExtraChannelBlendInfo(settings, 0, - &extra_channel_blend_info); - } size_t num_interleaved_alpha = (ppixelformat.num_channels - ppf.info.num_color_channels); - // Add extra channel info for the rest of the extra channels. - for (size_t i = 0; i < ppf.info.num_extra_channels; ++i) { - if (i < ppf.extra_channels_info.size()) { - const auto& ec_info = ppf.extra_channels_info[i].ec_info; - if (JXL_ENC_SUCCESS != - JxlEncoderSetExtraChannelInfo(enc, num_interleaved_alpha + i, - &ec_info)) { - fprintf(stderr, "JxlEncoderSetExtraChannelInfo() failed.\n"); - return false; - } - } + if (!SetupFrame(enc, settings, pframe.frame_info, params, ppf, num_frame, + num_alpha_channels, num_interleaved_alpha, option_idx)) { + return false; } if (JXL_ENC_SUCCESS != JxlEncoderAddImageFrame(settings, &ppixelformat, pimage.pixels(), pimage.pixels_size)) { fprintf(stderr, "JxlEncoderAddImageFrame() failed.\n"); @@ -287,30 +324,34 @@ fprintf(stderr, "JxlEncoderSetExtraChannelBuffer() failed.\n"); return false; } } } + for (size_t fi = 0; fi < ppf.chunked_frames.size(); ++fi) { + ChunkedPackedFrame& chunked_frame = ppf.chunked_frames[fi]; + size_t num_interleaved_alpha = + (chunked_frame.format.num_channels - ppf.info.num_color_channels); + if (!SetupFrame(enc, settings, chunked_frame.frame_info, params, ppf, fi, + num_alpha_channels, num_interleaved_alpha, option_idx)) { + return false; + } + const bool last_frame = fi + 1 == ppf.chunked_frames.size(); + if (JXL_ENC_SUCCESS != + JxlEncoderAddChunkedFrame(settings, last_frame, + chunked_frame.GetInputSource())) { + fprintf(stderr, "JxlEncoderAddChunkedFrame() failed.\n"); + return false; + } + } } JxlEncoderCloseInput(enc); - // Reading compressed output - compressed->clear(); - compressed->resize(4096); - uint8_t* next_out = compressed->data(); - size_t avail_out = compressed->size() - (next_out - compressed->data()); - JxlEncoderStatus result = JXL_ENC_NEED_MORE_OUTPUT; - while (result == JXL_ENC_NEED_MORE_OUTPUT) { - result = JxlEncoderProcessOutput(enc, &next_out, &avail_out); - if (result == JXL_ENC_NEED_MORE_OUTPUT) { - size_t offset = next_out - compressed->data(); - compressed->resize(compressed->size() * 2); - next_out = compressed->data() + offset; - avail_out = compressed->size() - offset; + if (params.HasOutputProcessor()) { + if (JXL_ENC_SUCCESS != JxlEncoderFlushInput(enc)) { + fprintf(stderr, "JxlEncoderAddChunkedFrame() failed.\n"); + return false; } - } - compressed->resize(next_out - compressed->data()); - if (result != JXL_ENC_SUCCESS) { - fprintf(stderr, "JxlEncoderProcessOutput failed.\n"); + } else if (!ReadCompressedOutput(enc, compressed)) { return false; } return true; } diff --git a/third_party/jpeg-xl/lib/extras/jpegli_test.cc b/third_party/jpeg-xl/lib/extras/jpegli_test.cc --- a/third_party/jpeg-xl/lib/extras/jpegli_test.cc +++ b/third_party/jpeg-xl/lib/extras/jpegli_test.cc @@ -8,12 +8,14 @@ #include "lib/extras/dec/jpegli.h" #include #include +#include #include #include +#include #include "lib/extras/dec/color_hints.h" #include "lib/extras/dec/decode.h" #include "lib/extras/dec/jpg.h" #include "lib/extras/enc/encode.h" @@ -34,18 +36,18 @@ using test::Butteraugli3Norm; using test::ButteraugliDistance; using test::TestImage; Status ReadTestImage(const std::string& pathname, PackedPixelFile* ppf) { - const PaddedBytes encoded = jxl::test::ReadTestData(pathname); + const std::vector encoded = jxl::test::ReadTestData(pathname); ColorHints color_hints; if (pathname.find(".ppm") != std::string::npos) { color_hints.Add("color_space", "RGB_D65_SRG_Rel_SRG"); } else if (pathname.find(".pgm") != std::string::npos) { color_hints.Add("color_space", "Gra_D65_Rel_SRG"); } - return DecodeBytes(Span(encoded), color_hints, ppf); + return DecodeBytes(Bytes(encoded), color_hints, ppf); } std::vector GetAppData(const std::vector& compressed) { std::vector result; size_t pos = 2; // After SOI @@ -65,11 +67,11 @@ } Status DecodeWithLibjpeg(const std::vector& compressed, PackedPixelFile* ppf, const JPGDecompressParams* dparams = nullptr) { - return DecodeImageJPG(Span(compressed), ColorHints(), ppf, + return DecodeImageJPG(Bytes(compressed), ColorHints(), ppf, /*constraints=*/nullptr, dparams); } Status EncodeWithLibjpeg(const PackedPixelFile& ppf, int quality, std::vector* compressed) { 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 @@ -18,14 +18,17 @@ #include #include #include #include +#include +#include #include #include #include "lib/jxl/base/byte_order.h" +#include "lib/jxl/base/c_callback_support.h" #include "lib/jxl/base/common.h" #include "lib/jxl/base/status.h" namespace jxl { namespace extras { @@ -188,10 +191,88 @@ PackedImage color; // Extra channel image data. std::vector extra_channels; }; +class ChunkedPackedFrame { + public: + typedef void (*ReadLine)(void* opaque, size_t xpos, size_t ypos, size_t xsize, + uint8_t* buffer, size_t len); + ChunkedPackedFrame(size_t xsize, size_t ysize, const JxlPixelFormat& format, + void* opaque, ReadLine read_line) + : xsize(xsize), + ysize(ysize), + format(format), + opaque_(opaque), + read_line_(read_line), + mtx_(new std::mutex()) {} + + JxlChunkedFrameInputSource GetInputSource() { + return JxlChunkedFrameInputSource{ + this, + METHOD_TO_C_CALLBACK(&ChunkedPackedFrame::GetColorChannelsPixelFormat), + METHOD_TO_C_CALLBACK(&ChunkedPackedFrame::GetColorChannelDataAt), + METHOD_TO_C_CALLBACK(&ChunkedPackedFrame::GetExtraChannelPixelFormat), + METHOD_TO_C_CALLBACK(&ChunkedPackedFrame::GetExtraChannelDataAt), + METHOD_TO_C_CALLBACK(&ChunkedPackedFrame::ReleaseCurrentData)}; + } + + // The Frame metadata. + JxlFrameHeader frame_info = {}; + std::string name; + + size_t xsize; + size_t ysize; + JxlPixelFormat format; + + private: + void GetColorChannelsPixelFormat(JxlPixelFormat* pixel_format) { + *pixel_format = format; + } + + const void* GetColorChannelDataAt(size_t xpos, size_t ypos, size_t xsize, + size_t ysize, size_t* row_offset) { + const std::lock_guard lock(*mtx_); + size_t bytes_per_channel = + PackedImage::BitsPerChannel(format.data_type) / jxl::kBitsPerByte; + size_t bytes_per_pixel = bytes_per_channel * format.num_channels; + *row_offset = xsize * bytes_per_pixel; + uint8_t* buffer = reinterpret_cast(malloc(ysize * (*row_offset))); + for (size_t y = 0; y < ysize; ++y) { + read_line_(opaque_, xpos, ypos + y, xsize, &buffer[y * (*row_offset)], + *row_offset); + } + buffers_.insert(buffer); + return buffer; + } + + void GetExtraChannelPixelFormat(size_t ec_index, + JxlPixelFormat* pixel_format) { + JXL_ABORT("Not implemented"); + } + + const void* GetExtraChannelDataAt(size_t ec_index, size_t xpos, size_t ypos, + size_t xsize, size_t ysize, + size_t* row_offset) { + JXL_ABORT("Not implemented"); + } + + void ReleaseCurrentData(const void* buffer) { + const std::lock_guard lock(*mtx_); + auto iter = buffers_.find(const_cast(buffer)); + if (iter != buffers_.end()) { + free(*iter); + buffers_.erase(iter); + } + } + + void* opaque_; + ReadLine read_line_; + std::set buffers_; + std::unique_ptr mtx_; +}; + // Optional metadata associated with a file class PackedMetadata { public: std::vector exif; std::vector iptc; @@ -220,13 +301,18 @@ // The icc profile of the original image. std::vector orig_icc; std::unique_ptr preview_frame; std::vector frames; + mutable std::vector chunked_frames; PackedMetadata metadata; PackedPixelFile() { JxlEncoderInitBasicInfo(&info); }; + + size_t num_frames() const { + return chunked_frames.empty() ? frames.size() : chunked_frames.size(); + } }; } // namespace extras } // namespace jxl 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 @@ -242,11 +242,11 @@ ppf->info.animation.tps_denominator = io.metadata.m.animation.tps_denominator; ppf->info.animation.num_loops = io.metadata.m.animation.num_loops; // Convert the color encoding ppf->icc.assign(c_desired.ICC().begin(), c_desired.ICC().end()); - c_desired.ToExternal(&ppf->color_encoding); + ppf->color_encoding = c_desired.ToExternal(); // Convert the extra blobs ppf->metadata.exif = io.blobs.exif; ppf->metadata.iptc = io.blobs.iptc; ppf->metadata.jumbf = io.blobs.jumbf; diff --git a/third_party/jpeg-xl/lib/include/jxl/decode.h b/third_party/jpeg-xl/lib/include/jxl/decode.h --- a/third_party/jpeg-xl/lib/include/jxl/decode.h +++ b/third_party/jpeg-xl/lib/include/jxl/decode.h @@ -875,11 +875,12 @@ * * @param dec decoder object. * @param cms structure representing a CMS implementation. See @ref * JxlCmsInterface for more details. */ -JXL_EXPORT void JxlDecoderSetCms(JxlDecoder* dec, JxlCmsInterface cms); +JXL_EXPORT JxlDecoderStatus JxlDecoderSetCms(JxlDecoder* dec, + JxlCmsInterface cms); // TODO(firsching): add a function JxlDecoderSetDefaultCms() for setting a // default in case libjxl is build with a CMS. /** * Returns the minimum size in bytes of the preview image output pixel buffer diff --git a/third_party/jpeg-xl/lib/include/jxl/types.h b/third_party/jpeg-xl/lib/include/jxl/types.h --- a/third_party/jpeg-xl/lib/include/jxl/types.h +++ b/third_party/jpeg-xl/lib/include/jxl/types.h @@ -30,10 +30,12 @@ #define JXL_BOOL int /** Portable @c true replacement. */ #define JXL_TRUE 1 /** Portable @c false replacement. */ #define JXL_FALSE 0 +/** Converts of bool-like value to either JXL_TRUE or JXL_FALSE. */ +#define TO_JXL_BOOL(C) (!!(C) ? JXL_TRUE : JXL_FALSE) /** Data type for the sample values per channel per pixel. */ typedef enum { /** Use 32-bit single-precision floating point values, with range 0.0-1.0 diff --git a/third_party/jpeg-xl/lib/jpegli/decode_api_test.cc b/third_party/jpeg-xl/lib/jpegli/decode_api_test.cc --- a/third_party/jpeg-xl/lib/jpegli/decode_api_test.cc +++ b/third_party/jpeg-xl/lib/jpegli/decode_api_test.cc @@ -2,10 +2,11 @@ // // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. #include +#include #include #include "lib/jpegli/decode.h" #include "lib/jpegli/encode.h" #include "lib/jpegli/test_utils.h" diff --git a/third_party/jpeg-xl/lib/jpegli/decode_scan.cc b/third_party/jpeg-xl/lib/jpegli/decode_scan.cc --- a/third_party/jpeg-xl/lib/jpegli/decode_scan.cc +++ b/third_party/jpeg-xl/lib/jpegli/decode_scan.cc @@ -469,11 +469,11 @@ if (start_pos + kMaxMCUByteSize > len) { SaveMCUCodingState(cinfo); } // Decode one MCU. - HWY_ALIGN_MAX coeff_t dummy_block[DCTSIZE2]; + HWY_ALIGN_MAX coeff_t sink_block[DCTSIZE2]; bool scan_ok = true; for (int i = 0; i < cinfo->comps_in_scan; ++i) { const jpeg_component_info* comp = cinfo->cur_comp_info[i]; int c = comp->component_index; const HuffmanTableEntry* dc_lut = @@ -486,15 +486,15 @@ for (int ix = 0; ix < comp->MCU_width; ++ix) { size_t block_x = m->scan_mcu_col_ * comp->MCU_width + ix; coeff_t* coeffs; if (block_x >= comp->width_in_blocks || block_y >= comp->height_in_blocks) { - // Note that it is OK that dummy_block is uninitialized because + // Note that it is OK that sink_block is uninitialized because // it will never be used in any branches, even in the RefineDCTBlock // case, because only DC scans can be interleaved and we don't use // the zero-ness of the DC coeff in the DC refinement code-path. - coeffs = dummy_block; + coeffs = sink_block; } else { coeffs = &m->coeff_rows[c][biy][block_x][0]; } if (cinfo->Ah == 0) { if (!DecodeDCTBlock(dc_lut, ac_lut, cinfo->Ss, cinfo->Se, cinfo->Al, diff --git a/third_party/jpeg-xl/lib/jpegli/entropy_coding.cc b/third_party/jpeg-xl/lib/jpegli/entropy_coding.cc --- a/third_party/jpeg-xl/lib/jpegli/entropy_coding.cc +++ b/third_party/jpeg-xl/lib/jpegli/entropy_coding.cc @@ -338,11 +338,11 @@ // each scan can contain only one color component. const bool is_interleaved = (scan_info->comps_in_scan > 1); const bool is_progressive = cinfo->progressive_mode; const int Ah = scan_info->Ah; const int Al = scan_info->Al; - HWY_ALIGN constexpr coeff_t kDummyBlock[DCTSIZE2] = {0}; + HWY_ALIGN constexpr coeff_t kSinkBlock[DCTSIZE2] = {0}; size_t restart_idx = 0; TokenArray* ta = &m->token_arrays[m->cur_token_array]; sti->token_offset = Ah > 0 ? 0 : m->total_num_tokens + ta->num_tokens; @@ -410,11 +410,11 @@ size_t block_y = mcu_y * n_blocks_y + iy; size_t block_x = mcu_x * n_blocks_x + ix; const coeff_t* block; if (block_x >= comp->width_in_blocks || block_y >= comp->height_in_blocks) { - block = kDummyBlock; + block = kSinkBlock; } else { block = &ba[i][iy][block_x][0]; } if (!is_progressive) { HWY_DYNAMIC_DISPATCH(ComputeTokensSequential) diff --git a/third_party/jpeg-xl/lib/jpegli/input_suspension_test.cc b/third_party/jpeg-xl/lib/jpegli/input_suspension_test.cc --- a/third_party/jpeg-xl/lib/jpegli/input_suspension_test.cc +++ b/third_party/jpeg-xl/lib/jpegli/input_suspension_test.cc @@ -2,10 +2,11 @@ // // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. #include +#include #include #include "lib/jpegli/decode.h" #include "lib/jpegli/test_utils.h" #include "lib/jpegli/testing.h" diff --git a/third_party/jpeg-xl/lib/jpegli/source_manager_test.cc b/third_party/jpeg-xl/lib/jpegli/source_manager_test.cc --- a/third_party/jpeg-xl/lib/jpegli/source_manager_test.cc +++ b/third_party/jpeg-xl/lib/jpegli/source_manager_test.cc @@ -2,10 +2,11 @@ // // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. #include +#include #include #include "lib/jpegli/decode.h" #include "lib/jpegli/test_utils.h" #include "lib/jpegli/testing.h" diff --git a/third_party/jpeg-xl/lib/jpegli/test_utils.cc b/third_party/jpeg-xl/lib/jpegli/test_utils.cc --- a/third_party/jpeg-xl/lib/jpegli/test_utils.cc +++ b/third_party/jpeg-xl/lib/jpegli/test_utils.cc @@ -4,10 +4,11 @@ // license that can be found in the LICENSE file. #include "lib/jpegli/test_utils.h" #include +#include #include #include "lib/jpegli/decode.h" #include "lib/jpegli/encode.h" #include "lib/jxl/base/byte_order.h" 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 @@ -29,22 +29,10 @@ set(JPEGXL_INTERNAL_LIBS ${JPEGXL_DEC_INTERNAL_LIBS} brotlienc ) -if (JPEGXL_ENABLE_SKCMS) - list(APPEND JPEGXL_INTERNAL_FLAGS -DJPEGXL_ENABLE_SKCMS=1) - if (JPEGXL_BUNDLE_SKCMS) - list(APPEND JPEGXL_INTERNAL_FLAGS -DJPEGXL_BUNDLE_SKCMS=1) - # skcms objects are later added to JPEGXL_INTERNAL_OBJECTS - else () - list(APPEND JPEGXL_INTERNAL_LIBS skcms) - endif () -else () - list(APPEND JPEGXL_INTERNAL_LIBS lcms2) -endif () - if (JPEGXL_ENABLE_TRANSCODE_JPEG) list(APPEND JPEGXL_INTERNAL_FLAGS -DJPEGXL_ENABLE_TRANSCODE_JPEG=1) else() list(APPEND JPEGXL_INTERNAL_FLAGS -DJPEGXL_ENABLE_TRANSCODE_JPEG=0) endif () @@ -90,27 +78,19 @@ ${path} COPYONLY ) endforeach() -add_library(jxl_includes INTERFACE) -target_include_directories(jxl_includes SYSTEM INTERFACE +add_library(jxl_base INTERFACE) +target_include_directories(jxl_base SYSTEM INTERFACE "$" ) -add_dependencies(jxl_includes jxl_export) - -# Base headers / utilities. -add_library(jxl_base-obj OBJECT ${JPEGXL_INTERNAL_BASE_SOURCES}) -target_compile_options(jxl_base-obj PRIVATE ${JPEGXL_INTERNAL_FLAGS}) -target_compile_options(jxl_base-obj PUBLIC ${JPEGXL_COVERAGE_FLAGS}) -set_property(TARGET jxl_base-obj PROPERTY POSITION_INDEPENDENT_CODE ON) -target_include_directories(jxl_base-obj PUBLIC +target_include_directories(jxl_base INTERFACE ${PROJECT_SOURCE_DIR} ${JXL_HWY_INCLUDE_DIRS} ) - -jxl_link_libraries(jxl_base-obj jxl_includes) +add_dependencies(jxl_base jxl_export) # Decoder-only object library add_library(jxl_dec-obj OBJECT ${JPEGXL_INTERNAL_DEC_SOURCES}) target_compile_options(jxl_dec-obj PRIVATE ${JPEGXL_INTERNAL_FLAGS}) target_compile_options(jxl_dec-obj PUBLIC ${JPEGXL_COVERAGE_FLAGS}) @@ -121,11 +101,11 @@ "$>" ) target_compile_definitions(jxl_dec-obj PUBLIC ${OBJ_COMPILE_DEFINITIONS} ) -jxl_link_libraries(jxl_dec-obj jxl_base-obj) +jxl_link_libraries(jxl_dec-obj jxl_base) # Object library. This is used to hold the set of objects and properties. add_library(jxl_enc-obj OBJECT ${JPEGXL_INTERNAL_ENC_SOURCES}) target_compile_options(jxl_enc-obj PRIVATE ${JPEGXL_INTERNAL_FLAGS}) target_compile_options(jxl_enc-obj PUBLIC ${JPEGXL_COVERAGE_FLAGS}) @@ -136,22 +116,11 @@ $ ) target_compile_definitions(jxl_enc-obj PUBLIC ${OBJ_COMPILE_DEFINITIONS} ) -jxl_link_libraries(jxl_enc-obj jxl_base-obj) - -#TODO(lode): don't depend on CMS for the core library -if (JPEGXL_ENABLE_SKCMS) - target_include_directories(jxl_enc-obj PRIVATE - $ - ) -else () - target_include_directories(jxl_enc-obj PRIVATE - $ - ) -endif () +jxl_link_libraries(jxl_enc-obj jxl_base) set_target_properties(jxl_dec-obj PROPERTIES CXX_VISIBILITY_PRESET hidden VISIBILITY_INLINES_HIDDEN 1 DEFINE_SYMBOL JXL_INTERNAL_LIBRARY_BUILD @@ -164,33 +133,40 @@ ) # Private static library. This exposes all the internal functions and is used # for tests. add_library(jxl_dec-static STATIC - $ $ + ${JXL_CMS_OBJECTS} ) -target_link_libraries(jxl_dec-static - PUBLIC ${JPEGXL_COVERAGE_FLAGS} ${JPEGXL_DEC_INTERNAL_LIBS} jxl_includes) +target_link_libraries(jxl_dec-static PUBLIC + ${JPEGXL_COVERAGE_FLAGS} + ${JPEGXL_DEC_INTERNAL_LIBS} + ${JXL_CMS_LIBS} + jxl_base +) # The list of objects in the static and shared libraries. set(JPEGXL_INTERNAL_OBJECTS - $ $ $ ) -if (JPEGXL_ENABLE_SKCMS AND JPEGXL_BUNDLE_SKCMS) - list(APPEND JPEGXL_INTERNAL_OBJECTS $) -endif() # Private static library. This exposes all the internal functions and is used # for tests. # TODO(lode): once the source files are correctly split so that it is possible # to do, remove $ here and depend on jxl_dec-static -add_library(jxl-static STATIC ${JPEGXL_INTERNAL_OBJECTS}) -target_link_libraries(jxl-static - PUBLIC ${JPEGXL_COVERAGE_FLAGS} ${JPEGXL_INTERNAL_LIBS} jxl_includes) +add_library(jxl-static STATIC + ${JPEGXL_INTERNAL_OBJECTS} + ${JXL_CMS_OBJECTS} +) +target_link_libraries(jxl-static PUBLIC + ${JPEGXL_COVERAGE_FLAGS} + ${JPEGXL_INTERNAL_LIBS} + ${JXL_CMS_LIBS} + jxl_base +) target_include_directories(jxl-static PUBLIC "$") # JXL_EXPORT is defined to "__declspec(dllimport)" automatically by CMake # in Windows builds when including headers from the C API and compiling from @@ -198,10 +174,12 @@ # however in windows this causes the function to not be found when linking # against the static library. This define JXL_EXPORT= here forces it to not # use dllimport in tests and other tools that require the static library. target_compile_definitions(jxl-static INTERFACE -DJXL_EXPORT=) target_compile_definitions(jxl_dec-static INTERFACE -DJXL_EXPORT=) +target_compile_definitions(jxl-static INTERFACE -DJXL_CMS_EXPORT=) +target_compile_definitions(jxl_dec-static INTERFACE -DJXL_CMS_EXPORT=) # TODO(deymo): Move TCMalloc linkage to the tools/ directory since the library # shouldn't do any allocs anyway. if(JPEGXL_ENABLE_TCMALLOC) pkg_check_modules(TCMallocMinimal REQUIRED IMPORTED_TARGET @@ -233,23 +211,25 @@ if (BUILD_SHARED_LIBS) # Public shared library. add_library(jxl SHARED ${JPEGXL_INTERNAL_OBJECTS}) strip_static(JPEGXL_INTERNAL_SHARED_LIBS JPEGXL_INTERNAL_LIBS) -target_link_libraries(jxl PUBLIC ${JPEGXL_COVERAGE_FLAGS} jxl_includes) +target_link_libraries(jxl PUBLIC ${JPEGXL_COVERAGE_FLAGS} jxl_base) +target_link_libraries(jxl PUBLIC jxl_cms) target_link_libraries(jxl PRIVATE ${JPEGXL_INTERNAL_SHARED_LIBS}) # Shared library include path contains only the "include/" paths. set_target_properties(jxl PROPERTIES VERSION ${JPEGXL_LIBRARY_VERSION} SOVERSION ${JPEGXL_LIBRARY_SOVERSION} LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}" RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}") # Public shared decoder library. -add_library(jxl_dec SHARED $ $) +add_library(jxl_dec SHARED $) strip_static(JPEGXL_DEC_INTERNAL_SHARED_LIBS JPEGXL_DEC_INTERNAL_LIBS) -target_link_libraries(jxl_dec PUBLIC ${JPEGXL_COVERAGE_FLAGS} jxl_includes) +target_link_libraries(jxl_dec PUBLIC ${JPEGXL_COVERAGE_FLAGS} jxl_base) +target_link_libraries(jxl_dec PUBLIC jxl_cms) target_link_libraries(jxl_dec PRIVATE ${JPEGXL_DEC_INTERNAL_SHARED_LIBS}) # Shared library include path contains only the "include/" paths. set_target_properties(jxl_dec PROPERTIES VERSION ${JPEGXL_LIBRARY_VERSION} SOVERSION ${JPEGXL_LIBRARY_SOVERSION} @@ -299,27 +279,11 @@ add_library(jxl_dec ALIAS jxl_dec-static) endif() # BUILD_SHARED_LIBS # Add a pkg-config file for libjxl. set(JPEGXL_LIBRARY_REQUIRES - "libhwy libbrotlienc libbrotlidec") -if(NOT JPEGXL_ENABLE_SKCMS) - set(JPEGXL_LIBRARY_REQUIRES "${JPEGXL_LIBRARY_REQUIRES} lcms2") -endif() - -# Allow adding prefix if CMAKE_INSTALL_INCLUDEDIR not absolute. -if(IS_ABSOLUTE "${CMAKE_INSTALL_INCLUDEDIR}") - set(PKGCONFIG_TARGET_INCLUDES "${CMAKE_INSTALL_INCLUDEDIR}") -else() - set(PKGCONFIG_TARGET_INCLUDES "\${prefix}/${CMAKE_INSTALL_INCLUDEDIR}") -endif() -# Allow adding prefix if CMAKE_INSTALL_LIBDIR not absolute. -if(IS_ABSOLUTE "${CMAKE_INSTALL_LIBDIR}") - set(PKGCONFIG_TARGET_LIBS "${CMAKE_INSTALL_LIBDIR}") -else() - set(PKGCONFIG_TARGET_LIBS "\${exec_prefix}/${CMAKE_INSTALL_LIBDIR}") -endif() + "libhwy libbrotlienc libbrotlidec libjxl_cms") configure_file("${CMAKE_CURRENT_SOURCE_DIR}/jxl/libjxl.pc.in" "libjxl.pc" @ONLY) install(FILES "${CMAKE_CURRENT_BINARY_DIR}/libjxl.pc" DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig") diff --git a/third_party/jpeg-xl/lib/jxl/ans_common.cc b/third_party/jpeg-xl/lib/jxl/ans_common.cc --- a/third_party/jpeg-xl/lib/jxl/ans_common.cc +++ b/third_party/jpeg-xl/lib/jxl/ans_common.cc @@ -23,13 +23,13 @@ } return result; } // First, all trailing non-occurring symbols are removed from the distribution; -// if this leaves the distribution empty, a dummy symbol with max weight is -// added. This ensures that the resulting distribution sums to total table size. -// Then, `entry_size` is chosen to be the largest power of two so that +// if this leaves the distribution empty, a placeholder symbol with max weight +// is added. This ensures that the resulting distribution sums to total table +// size. Then, `entry_size` is chosen to be the largest power of two so that // `table_size` = ANS_TAB_SIZE/`entry_size` is at least as big as the // distribution size. // Note that each entry will only ever contain two different symbols, and // consecutive ranges of offsets, which allows us to use a compact // representation. diff --git a/third_party/jpeg-xl/lib/jxl/base/c_callback_support.h b/third_party/jpeg-xl/lib/jxl/base/c_callback_support.h new file mode 100644 --- /dev/null +++ b/third_party/jpeg-xl/lib/jxl/base/c_callback_support.h @@ -0,0 +1,32 @@ +// Copyright (c) the JPEG XL Project Authors. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#ifndef LIB_JXL_BASE_C_CALLBACK_SUPPORT_H_ +#define LIB_JXL_BASE_C_CALLBACK_SUPPORT_H_ + +#include + +namespace jxl { +namespace detail { + +template +struct MethodToCCallbackHelper {}; + +template +struct MethodToCCallbackHelper { + template + static R Call(void *opaque, Args... args) { + return (reinterpret_cast(opaque)->*method)( + std::forward(args)...); + } +}; + +} // namespace detail +} // namespace jxl + +#define METHOD_TO_C_CALLBACK(method) \ + ::jxl::detail::MethodToCCallbackHelper::Call + +#endif // LIB_JXL_BASE_C_CALLBACK_SUPPORT_H_ diff --git a/third_party/jpeg-xl/lib/jxl/base/cache_aligned.h b/third_party/jpeg-xl/lib/jxl/base/cache_aligned.h deleted file mode 100644 --- a/third_party/jpeg-xl/lib/jxl/base/cache_aligned.h +++ /dev/null @@ -1,74 +0,0 @@ -// Copyright (c) the JPEG XL Project Authors. All rights reserved. -// -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -#ifndef LIB_JXL_BASE_CACHE_ALIGNED_H_ -#define LIB_JXL_BASE_CACHE_ALIGNED_H_ - -// Memory allocator with support for alignment + misalignment. - -#include -#include - -#include - -#include "lib/jxl/base/compiler_specific.h" - -namespace jxl { - -// Functions that depend on the cache line size. -class CacheAligned { - public: - static void PrintStats(); - - static constexpr size_t kPointerSize = sizeof(void*); - static constexpr size_t kCacheLineSize = 64; - // To avoid RFOs, match L2 fill size (pairs of lines). - static constexpr size_t kAlignment = 2 * kCacheLineSize; - // Minimum multiple for which cache set conflicts and/or loads blocked by - // preceding stores can occur. - static constexpr size_t kAlias = 2048; - - // Returns a 'random' (cyclical) offset suitable for Allocate. - static size_t NextOffset(); - - // Returns null or memory whose address is congruent to `offset` (mod kAlias). - // This reduces cache conflicts and load/store stalls, especially with large - // allocations that would otherwise have similar alignments. At least - // `payload_size` (which can be zero) bytes will be accessible. - static void* Allocate(size_t payload_size, size_t offset); - - static void* Allocate(const size_t payload_size) { - return Allocate(payload_size, NextOffset()); - } - - static void Free(const void* aligned_pointer); -}; - -// Avoids the need for a function pointer (deleter) in CacheAlignedUniquePtr. -struct CacheAlignedDeleter { - void operator()(uint8_t* aligned_pointer) const { - return CacheAligned::Free(aligned_pointer); - } -}; - -using CacheAlignedUniquePtr = std::unique_ptr; - -// Does not invoke constructors. -static inline CacheAlignedUniquePtr AllocateArray(const size_t bytes) { - return CacheAlignedUniquePtr( - static_cast(CacheAligned::Allocate(bytes)), - CacheAlignedDeleter()); -} - -static inline CacheAlignedUniquePtr AllocateArray(const size_t bytes, - const size_t offset) { - return CacheAlignedUniquePtr( - static_cast(CacheAligned::Allocate(bytes, offset)), - CacheAlignedDeleter()); -} - -} // namespace jxl - -#endif // LIB_JXL_BASE_CACHE_ALIGNED_H_ diff --git a/third_party/jpeg-xl/lib/jxl/base/cache_aligned.cc b/third_party/jpeg-xl/lib/jxl/base/cache_aligned.cc deleted file mode 100644 --- a/third_party/jpeg-xl/lib/jxl/base/cache_aligned.cc +++ /dev/null @@ -1,157 +0,0 @@ -// Copyright (c) the JPEG XL Project Authors. All rights reserved. -// -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -#include "lib/jxl/base/cache_aligned.h" - -#include -#include - -// Disabled: slower than malloc + alignment. -#define JXL_USE_MMAP 0 - -#if JXL_USE_MMAP -#include -#endif - -#include // std::max -#include -#include // kMaxVectorSize -#include - -#include "lib/jxl/base/printf_macros.h" -#include "lib/jxl/base/status.h" - -namespace jxl { -namespace { - -#pragma pack(push, 1) -struct AllocationHeader { - void* allocated; - size_t allocated_size; - uint8_t left_padding[hwy::kMaxVectorSize]; -}; -#pragma pack(pop) - -std::atomic num_allocations{0}; -std::atomic bytes_in_use{0}; -std::atomic max_bytes_in_use{0}; - -} // namespace - -// Avoids linker errors in pre-C++17 builds. -constexpr size_t CacheAligned::kPointerSize; -constexpr size_t CacheAligned::kCacheLineSize; -constexpr size_t CacheAligned::kAlignment; -constexpr size_t CacheAligned::kAlias; - -void CacheAligned::PrintStats() { - fprintf( - stderr, "Allocations: %" PRIuS " (max bytes in use: %E)\n", - static_cast(num_allocations.load(std::memory_order_relaxed)), - static_cast(max_bytes_in_use.load(std::memory_order_relaxed))); -} - -size_t CacheAligned::NextOffset() { - static std::atomic next{0}; - constexpr uint32_t kGroups = CacheAligned::kAlias / CacheAligned::kAlignment; - const uint32_t group = next.fetch_add(1, std::memory_order_relaxed) % kGroups; - return CacheAligned::kAlignment * group; -} - -void* CacheAligned::Allocate(const size_t payload_size, size_t offset) { - JXL_ASSERT(payload_size <= std::numeric_limits::max() / 2); - JXL_ASSERT((offset % kAlignment == 0) && offset <= kAlias); - - // What: | misalign | unused | AllocationHeader |payload - // Size: |<= kAlias | offset | |payload_size - // ^allocated.^aligned.^header............^payload - // The header must immediately precede payload, which must remain aligned. - // To avoid wasting space, the header resides at the end of `unused`, - // which therefore cannot be empty (offset == 0). - if (offset == 0) { - // SVE/RVV vectors can be large, so we cannot rely on them (including the - // padding at the end of AllocationHeader) to fit in kAlignment. - offset = hwy::RoundUpTo(sizeof(AllocationHeader), kAlignment); - } - -#if JXL_USE_MMAP - const size_t allocated_size = offset + payload_size; - const int flags = MAP_PRIVATE | MAP_ANONYMOUS | MAP_POPULATE; - void* allocated = - mmap(nullptr, allocated_size, PROT_READ | PROT_WRITE, flags, -1, 0); - if (allocated == MAP_FAILED) return nullptr; - const uintptr_t aligned = reinterpret_cast(allocated); -#else - const size_t allocated_size = kAlias + offset + payload_size; - void* allocated = malloc(allocated_size); - if (allocated == nullptr) return nullptr; - // Always round up even if already aligned - we already asked for kAlias - // extra bytes and there's no way to give them back. - uintptr_t aligned = reinterpret_cast(allocated) + kAlias; - static_assert((kAlias & (kAlias - 1)) == 0, "kAlias must be a power of 2"); - static_assert(kAlias >= kAlignment, "Cannot align to more than kAlias"); - aligned &= ~(kAlias - 1); -#endif - -#if 0 - // No effect. - uintptr_t page_aligned = reinterpret_cast(allocated); - page_aligned &= ~(4096 - 1); - if (madvise(reinterpret_cast(page_aligned), allocated_size, - MADV_WILLNEED) != 0) { - JXL_NOTIFY_ERROR("madvise failed"); - } -#elif 0 - // INCREASES both first and subsequent decode times. - if (mlock(allocated, allocated_size) != 0) { - JXL_NOTIFY_ERROR("mlock failed"); - } -#endif - - // Update statistics (#allocations and max bytes in use) - num_allocations.fetch_add(1, std::memory_order_relaxed); - const uint64_t prev_bytes = - bytes_in_use.fetch_add(allocated_size, std::memory_order_acq_rel); - uint64_t expected_max = max_bytes_in_use.load(std::memory_order_acquire); - for (;;) { - const uint64_t desired = - std::max(expected_max, prev_bytes + allocated_size); - if (max_bytes_in_use.compare_exchange_strong(expected_max, desired, - std::memory_order_acq_rel)) { - break; - } - } - - const uintptr_t payload = aligned + offset; // still aligned - - // Stash `allocated` and payload_size inside header for use by Free(). - AllocationHeader* header = reinterpret_cast(payload) - 1; - header->allocated = allocated; - header->allocated_size = allocated_size; - - return JXL_ASSUME_ALIGNED(reinterpret_cast(payload), 64); -} - -void CacheAligned::Free(const void* aligned_pointer) { - if (aligned_pointer == nullptr) { - return; - } - const uintptr_t payload = reinterpret_cast(aligned_pointer); - JXL_ASSERT(payload % kAlignment == 0); - const AllocationHeader* header = - reinterpret_cast(payload) - 1; - - // Subtract (2's complement negation). - bytes_in_use.fetch_add(~header->allocated_size + 1, - std::memory_order_acq_rel); - -#if JXL_USE_MMAP - munmap(header->allocated, header->allocated_size); -#else - free(header->allocated); -#endif -} - -} // namespace jxl diff --git a/third_party/jpeg-xl/lib/jxl/base/data_parallel.h b/third_party/jpeg-xl/lib/jxl/base/data_parallel.h --- a/third_party/jpeg-xl/lib/jxl/base/data_parallel.h +++ b/third_party/jpeg-xl/lib/jxl/base/data_parallel.h @@ -23,11 +23,11 @@ namespace jxl { class ThreadPool { public: ThreadPool(JxlParallelRunner runner, void* runner_opaque) - : runner_(runner ? runner : &ThreadPool::SequentialRunnerStatic), + : runner_(runner), runner_opaque_(runner ? runner_opaque : static_cast(this)) {} ThreadPool(const ThreadPool&) = delete; ThreadPool& operator&(const ThreadPool&) = delete; @@ -48,10 +48,20 @@ JXL_ASSERT(begin <= end); if (begin == end) return true; RunCallState call_state(init_func, data_func); // The runner_ uses the C convention and returns 0 in case of error, so we // convert it to a Status. + if (!runner_) { + void* jpegxl_opaque = static_cast(&call_state); + if (call_state.CallInitFunc(jpegxl_opaque, 1) != 0) { + return JXL_FAILURE("Failed to initialize thread"); + } + for (uint32_t i = begin; i < end; i++) { + call_state.CallDataFunc(jpegxl_opaque, i, 0); + } + return true; + } return (*runner_)(runner_opaque_, static_cast(&call_state), &call_state.CallInitFunc, &call_state.CallDataFunc, begin, end) == 0; } @@ -87,16 +97,10 @@ private: const InitFunc& init_func_; const DataFunc& data_func_; }; - // Default JxlParallelRunner used when no runner is provided by the - // caller. This runner doesn't use any threading and thread_id is always 0. - static JxlParallelRetCode SequentialRunnerStatic( - void* runner_opaque, void* jpegxl_opaque, JxlParallelRunInit init, - JxlParallelRunFunction func, uint32_t start_range, uint32_t end_range); - // The caller supplied runner function and its opaque void*. const JxlParallelRunner runner_; void* const runner_opaque_; }; diff --git a/third_party/jpeg-xl/lib/jxl/base/data_parallel.cc b/third_party/jpeg-xl/lib/jxl/base/data_parallel.cc deleted file mode 100644 --- a/third_party/jpeg-xl/lib/jxl/base/data_parallel.cc +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright (c) the JPEG XL Project Authors. All rights reserved. -// -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -#include "lib/jxl/base/data_parallel.h" - -namespace jxl { - -// static -JxlParallelRetCode ThreadPool::SequentialRunnerStatic( - void* runner_opaque, void* jpegxl_opaque, JxlParallelRunInit init, - JxlParallelRunFunction func, uint32_t start_range, uint32_t end_range) { - JxlParallelRetCode init_ret = (*init)(jpegxl_opaque, 1); - if (init_ret != 0) return init_ret; - - for (uint32_t i = start_range; i < end_range; i++) { - (*func)(jpegxl_opaque, i, 0); - } - return 0; -} - -} // namespace jxl diff --git a/third_party/jpeg-xl/lib/jxl/base/padded_bytes.h b/third_party/jpeg-xl/lib/jxl/base/padded_bytes.h deleted file mode 100644 --- a/third_party/jpeg-xl/lib/jxl/base/padded_bytes.h +++ /dev/null @@ -1,197 +0,0 @@ -// Copyright (c) the JPEG XL Project Authors. All rights reserved. -// -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -#ifndef LIB_JXL_BASE_PADDED_BYTES_H_ -#define LIB_JXL_BASE_PADDED_BYTES_H_ - -// std::vector replacement with padding to reduce bounds checks in WriteBits - -#include -#include -#include // memcpy - -#include // max -#include -#include // swap - -#include "lib/jxl/base/cache_aligned.h" -#include "lib/jxl/base/compiler_specific.h" -#include "lib/jxl/base/status.h" - -namespace jxl { - -// Provides a subset of the std::vector interface with some differences: -// - allows BitWriter to write 64 bits at a time without bounds checking; -// - ONLY zero-initializes the first byte (required by BitWriter); -// - ensures cache-line alignment. -class PaddedBytes { - public: - // Required for output params. - PaddedBytes() : size_(0), capacity_(0) {} - - explicit PaddedBytes(size_t size) : size_(size), capacity_(0) { - if (size != 0) IncreaseCapacityTo(size); - } - - PaddedBytes(size_t size, uint8_t value) : size_(size), capacity_(0) { - if (size != 0) { - IncreaseCapacityTo(size); - } - if (size_ != 0) { - memset(data(), value, size); - } - } - - PaddedBytes(const PaddedBytes& other) : size_(other.size_), capacity_(0) { - if (size_ != 0) IncreaseCapacityTo(size_); - if (data() != nullptr) memcpy(data(), other.data(), size_); - } - PaddedBytes& operator=(const PaddedBytes& other) { - // Self-assignment is safe. - resize(other.size()); - if (data() != nullptr) memmove(data(), other.data(), size_); - return *this; - } - - // default is not OK - need to set other.size_ to 0! - PaddedBytes(PaddedBytes&& other) noexcept - : size_(other.size_), - capacity_(other.capacity_), - data_(std::move(other.data_)) { - other.size_ = other.capacity_ = 0; - } - PaddedBytes& operator=(PaddedBytes&& other) noexcept { - size_ = other.size_; - capacity_ = other.capacity_; - data_ = std::move(other.data_); - - if (&other != this) { - other.size_ = other.capacity_ = 0; - } - return *this; - } - - void swap(PaddedBytes& other) { - std::swap(size_, other.size_); - std::swap(capacity_, other.capacity_); - std::swap(data_, other.data_); - } - - void reserve(size_t capacity) { - if (capacity > capacity_) IncreaseCapacityTo(capacity); - } - - // NOTE: unlike vector, this does not initialize the new data! - // However, we guarantee that write_bits can safely append after - // the resize, as we zero-initialize the first new byte of data. - // If size < capacity(), does not invalidate the memory. - void resize(size_t size) { - if (size > capacity_) IncreaseCapacityTo(size); - size_ = (data() == nullptr) ? 0 : size; - } - - // resize(size) plus explicit initialization of the new data with `value`. - void resize(size_t size, uint8_t value) { - size_t old_size = size_; - resize(size); - if (size_ > old_size) { - memset(data() + old_size, value, size_ - old_size); - } - } - - // Amortized constant complexity due to exponential growth. - void push_back(uint8_t x) { - if (size_ == capacity_) { - IncreaseCapacityTo(capacity_ + 1); - if (data() == nullptr) return; - } - - data_[size_++] = x; - } - - size_t size() const { return size_; } - size_t capacity() const { return capacity_; } - - uint8_t* data() { return data_.get(); } - const uint8_t* data() const { return data_.get(); } - - // std::vector operations implemented in terms of the public interface above. - - void clear() { resize(0); } - bool empty() const { return size() == 0; } - - void assign(std::initializer_list il) { - resize(il.size()); - memcpy(data(), il.begin(), il.size()); - } - - // Replaces data() with [new_begin, new_end); potentially reallocates. - void assign(const uint8_t* new_begin, const uint8_t* new_end); - - uint8_t* begin() { return data(); } - const uint8_t* begin() const { return data(); } - uint8_t* end() { return begin() + size(); } - const uint8_t* end() const { return begin() + size(); } - - uint8_t& operator[](const size_t i) { - BoundsCheck(i); - return data()[i]; - } - const uint8_t& operator[](const size_t i) const { - BoundsCheck(i); - return data()[i]; - } - - uint8_t& back() { - JXL_ASSERT(size() != 0); - return data()[size() - 1]; - } - const uint8_t& back() const { - JXL_ASSERT(size() != 0); - return data()[size() - 1]; - } - - template - void append(const T& other) { - append(reinterpret_cast(other.data()), - reinterpret_cast(other.data()) + other.size()); - } - - void append(const uint8_t* begin, const uint8_t* end) { - if (end - begin > 0) { - size_t old_size = size(); - resize(size() + (end - begin)); - memcpy(data() + old_size, begin, end - begin); - } - } - - private: - void BoundsCheck(size_t i) const { - // <= is safe due to padding and required by BitWriter. - JXL_ASSERT(i <= size()); - } - - // Copies existing data to newly allocated "data_". If allocation fails, - // data() == nullptr and size_ = capacity_ = 0. - // The new capacity will be at least 1.5 times the old capacity. This ensures - // that we avoid quadratic behaviour. - void IncreaseCapacityTo(size_t capacity); - - size_t size_; - size_t capacity_; - CacheAlignedUniquePtr data_; -}; - -template -static inline void Append(const T& s, PaddedBytes* out, - size_t* JXL_RESTRICT byte_pos) { - memcpy(out->data() + *byte_pos, s.data(), s.size()); - *byte_pos += s.size(); - JXL_CHECK(*byte_pos <= out->size()); -} - -} // namespace jxl - -#endif // LIB_JXL_BASE_PADDED_BYTES_H_ diff --git a/third_party/jpeg-xl/lib/jxl/base/padded_bytes.cc b/third_party/jpeg-xl/lib/jxl/base/padded_bytes.cc deleted file mode 100644 --- a/third_party/jpeg-xl/lib/jxl/base/padded_bytes.cc +++ /dev/null @@ -1,63 +0,0 @@ -// Copyright (c) the JPEG XL Project Authors. All rights reserved. -// -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -#include "lib/jxl/base/padded_bytes.h" - -namespace jxl { - -void PaddedBytes::IncreaseCapacityTo(size_t capacity) { - JXL_ASSERT(capacity > capacity_); - - size_t new_capacity = std::max(capacity, 3 * capacity_ / 2); - new_capacity = std::max(64, new_capacity); - - // BitWriter writes up to 7 bytes past the end. - CacheAlignedUniquePtr new_data = AllocateArray(new_capacity + 8); - if (new_data == nullptr) { - // Allocation failed, discard all data to ensure this is noticed. - size_ = capacity_ = 0; - return; - } - - if (data_ == nullptr) { - // First allocation: ensure first byte is initialized (won't be copied). - new_data[0] = 0; - } else { - // Subsequent resize: copy existing data to new location. - memcpy(new_data.get(), data_.get(), size_); - // Ensure that the first new byte is initialized, to allow write_bits to - // safely append to the newly-resized PaddedBytes. - new_data[size_] = 0; - } - - capacity_ = new_capacity; - std::swap(new_data, data_); -} - -void PaddedBytes::assign(const uint8_t* new_begin, const uint8_t* new_end) { - JXL_DASSERT(new_begin <= new_end); - const size_t new_size = static_cast(new_end - new_begin); - - // memcpy requires non-overlapping ranges, and resizing might invalidate the - // new range. Neither happens if the new range is completely to the left or - // right of the _allocated_ range (irrespective of size_). - const uint8_t* allocated_end = begin() + capacity_; - const bool outside = new_end <= begin() || new_begin >= allocated_end; - if (outside) { - resize(new_size); // grow or shrink - memcpy(data(), new_begin, new_size); - return; - } - - // There is overlap. The new size cannot be larger because we own the memory - // and the new range cannot include anything outside the allocated range. - JXL_ASSERT(new_size <= capacity_); - - // memmove allows overlap and capacity_ is sufficient. - memmove(data(), new_begin, new_size); - size_ = new_size; // shrink -} - -} // namespace jxl diff --git a/third_party/jpeg-xl/lib/jxl/base/random.h b/third_party/jpeg-xl/lib/jxl/base/random.h --- a/third_party/jpeg-xl/lib/jxl/base/random.h +++ b/third_party/jpeg-xl/lib/jxl/base/random.h @@ -12,10 +12,11 @@ #include #include #include +#include #include "lib/jxl/base/status.h" namespace jxl { struct Rng { @@ -67,19 +68,22 @@ // Bernoulli trial bool Bernoulli(float p) { return UniformF(0, 1) < p; } // State for geometric distributions. - struct GeometricDistribution { - explicit GeometricDistribution(float p); + // The stored value is inv_log_1mp + using GeometricDistribution = float; + static GeometricDistribution MakeGeometric(float p) { + return 1.0 / std::log(1 - p); + } - private: - float inv_log_1mp; - friend struct Rng; - }; - - uint32_t Geometric(const GeometricDistribution& dist); + uint32_t Geometric(const GeometricDistribution& dist) { + float f = UniformF(0, 1); + float inv_log_1mp = dist; + float log = std::log(1 - f) * inv_log_1mp; + return static_cast(log); + } template void Shuffle(T* t, size_t n) { for (size_t i = 0; i + 1 < n; i++) { size_t a = UniformU(i, n); diff --git a/third_party/jpeg-xl/lib/jxl/base/random.cc b/third_party/jpeg-xl/lib/jxl/base/random.cc deleted file mode 100644 --- a/third_party/jpeg-xl/lib/jxl/base/random.cc +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright (c) the JPEG XL Project Authors. All rights reserved. -// -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -#include "lib/jxl/base/random.h" - -#include - -namespace jxl { - -Rng::GeometricDistribution::GeometricDistribution(float p) - : inv_log_1mp(1.0 / std::log(1 - p)) {} - -uint32_t Rng::Geometric(const GeometricDistribution& dist) { - float f = UniformF(0, 1); - float log = std::log(1 - f) * dist.inv_log_1mp; - return static_cast(log); -} - -} // namespace jxl 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 @@ -7,12 +7,12 @@ #define LIB_JXL_BASE_SPAN_H_ // Span (array view) is a non-owning container that provides cheap "cut" // operations and could be used as "ArrayLike" data source for PaddedBytes. -#include - +#include +#include #include #include "lib/jxl/base/status.h" namespace jxl { @@ -62,17 +62,19 @@ len_ -= n; } // NCT == non-const-T; compiler will complain if NCT is not compatible with T. template - void AppendTo(std::vector* dst) { + void AppendTo(std::vector* dst) const { dst->insert(dst->end(), begin(), end()); } private: T* ptr_; size_t len_; }; +typedef Span Bytes; + } // namespace jxl #endif // LIB_JXL_BASE_SPAN_H_ diff --git a/third_party/jpeg-xl/lib/jxl/base/status.h b/third_party/jpeg-xl/lib/jxl/base/status.h --- a/third_party/jpeg-xl/lib/jxl/base/status.h +++ b/third_party/jpeg-xl/lib/jxl/base/status.h @@ -404,11 +404,11 @@ } } private: union Storage { - char dummy_; + char placeholder_; T data_; Storage() {} ~Storage() {} } storage_; diff --git a/third_party/jpeg-xl/lib/jxl/bit_reader_test.cc b/third_party/jpeg-xl/lib/jxl/bit_reader_test.cc --- a/third_party/jpeg-xl/lib/jxl/bit_reader_test.cc +++ b/third_party/jpeg-xl/lib/jxl/bit_reader_test.cc @@ -25,11 +25,11 @@ TEST(BitReaderTest, ExtendsWithZeroes) { for (size_t size = 4; size < 32; ++size) { std::vector data(size, 0xff); for (size_t n_bytes = 0; n_bytes < size; n_bytes++) { - BitReader br(Span(data.data(), n_bytes)); + BitReader br(Bytes(data.data(), n_bytes)); // Read all the bits for (size_t i = 0; i < n_bytes * kBitsPerByte; i++) { ASSERT_EQ(br.ReadBits(1), 1u) << "n_bytes=" << n_bytes << " i=" << i; } @@ -212,11 +212,11 @@ } } TEST(BitReaderTest, TotalCountersTest) { uint8_t buf[8] = {1, 2, 3, 4}; - BitReader reader(Span(buf, sizeof(buf))); + BitReader reader(Bytes(buf, sizeof(buf))); EXPECT_EQ(sizeof(buf), reader.TotalBytes()); EXPECT_EQ(0u, reader.TotalBitsConsumed()); reader.ReadFixedBits<1>(); EXPECT_EQ(1u, reader.TotalBitsConsumed()); @@ -238,11 +238,11 @@ TEST(BitReaderTest, MoveTest) { uint8_t buf[8] = {1, 2, 3, 4}; BitReader reader2; { - BitReader reader1(Span(buf, sizeof(buf))); + BitReader reader1(Bytes(buf, sizeof(buf))); EXPECT_EQ(0u, reader1.TotalBitsConsumed()); reader1.ReadFixedBits<16>(); EXPECT_EQ(16u, reader1.TotalBitsConsumed()); 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,10 +1,13 @@ // 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 +#include + #include "lib/extras/codec.h" #include "lib/jxl/image_test_utils.h" #include "lib/jxl/test_utils.h" #include "lib/jxl/testing.h" @@ -12,24 +15,24 @@ namespace { using ::testing::SizeIs; TEST(BlendingTest, Crops) { - const PaddedBytes compressed = + const std::vector compressed = jxl::test::ReadTestData("jxl/blending/cropped_traffic_light.jxl"); CodecInOut decoded; - ASSERT_TRUE(test::DecodeFile({}, Span(compressed), &decoded)); + ASSERT_TRUE(test::DecodeFile({}, Bytes(compressed), &decoded)); ASSERT_THAT(decoded.frames, SizeIs(4)); int i = 0; for (const ImageBundle& ib : decoded.frames) { std::ostringstream filename; filename << "jxl/blending/cropped_traffic_light_frame-" << i << ".png"; - const PaddedBytes compressed_frame = + const std::vector compressed_frame = jxl::test::ReadTestData(filename.str()); CodecInOut frame; - ASSERT_TRUE(SetFromBytes(Span(compressed_frame), &frame)); + ASSERT_TRUE(SetFromBytes(Bytes(compressed_frame), &frame)); JXL_EXPECT_OK(SamePixels(ib.color(), *frame.Main().color(), _)); ++i; } } diff --git a/third_party/jpeg-xl/lib/jxl/butteraugli/butteraugli_test.cc b/third_party/jpeg-xl/lib/jxl/butteraugli/butteraugli_test.cc --- a/third_party/jpeg-xl/lib/jxl/butteraugli/butteraugli_test.cc +++ b/third_party/jpeg-xl/lib/jxl/butteraugli/butteraugli_test.cc @@ -42,14 +42,13 @@ const PackedImage& image = ppf.frames[0].color; const JxlPixelFormat& format = image.format; const uint8_t* pixels = reinterpret_cast(image.pixels()); Image3F color(image.xsize, image.ysize); for (size_t c = 0; c < format.num_channels; ++c) { - JXL_CHECK( - ConvertFromExternal(Span(pixels, image.pixels_size), - image.xsize, image.ysize, ppf.info.bits_per_sample, - format, c, nullptr, &color.Plane(c))); + JXL_CHECK(ConvertFromExternal(Bytes(pixels, image.pixels_size), image.xsize, + image.ysize, ppf.info.bits_per_sample, format, + c, nullptr, &color.Plane(c))); } return color; } void AddUniformNoise(Image3F* img, float d, size_t seed) { diff --git a/third_party/jpeg-xl/lib/jxl/cache_aligned.h b/third_party/jpeg-xl/lib/jxl/cache_aligned.h new file mode 100644 --- /dev/null +++ b/third_party/jpeg-xl/lib/jxl/cache_aligned.h @@ -0,0 +1,67 @@ +// Copyright (c) the JPEG XL Project Authors. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#ifndef LIB_JXL_BASE_CACHE_ALIGNED_H_ +#define LIB_JXL_BASE_CACHE_ALIGNED_H_ + +// Memory allocator with support for alignment + misalignment. + +#include +#include + +#include + +#include "lib/jxl/base/compiler_specific.h" + +namespace jxl { + +// Functions that depend on the cache line size. +class CacheAligned { + public: + static void PrintStats(); + + static constexpr size_t kPointerSize = sizeof(void*); + static constexpr size_t kCacheLineSize = 64; + // To avoid RFOs, match L2 fill size (pairs of lines). + static constexpr size_t kAlignment = 2 * kCacheLineSize; + // Minimum multiple for which cache set conflicts and/or loads blocked by + // preceding stores can occur. + static constexpr size_t kAlias = 2048; + + // Returns a 'random' (cyclical) offset suitable for Allocate. + static size_t NextOffset(); + + // Returns null or memory whose address is congruent to `offset` (mod kAlias). + // This reduces cache conflicts and load/store stalls, especially with large + // allocations that would otherwise have similar alignments. At least + // `payload_size` (which can be zero) bytes will be accessible. + static void* Allocate(size_t payload_size, size_t offset); + + static void* Allocate(const size_t payload_size) { + return Allocate(payload_size, NextOffset()); + } + + static void Free(const void* aligned_pointer); +}; + +// Avoids the need for a function pointer (deleter) in CacheAlignedUniquePtr. +struct CacheAlignedDeleter { + void operator()(uint8_t* aligned_pointer) const { + return CacheAligned::Free(aligned_pointer); + } +}; + +using CacheAlignedUniquePtr = std::unique_ptr; + +// Does not invoke constructors. +static inline CacheAlignedUniquePtr AllocateArray(const size_t bytes) { + return CacheAlignedUniquePtr( + static_cast(CacheAligned::Allocate(bytes)), + CacheAlignedDeleter()); +} + +} // namespace jxl + +#endif // LIB_JXL_BASE_CACHE_ALIGNED_H_ diff --git a/third_party/jpeg-xl/lib/jxl/cache_aligned.cc b/third_party/jpeg-xl/lib/jxl/cache_aligned.cc new file mode 100644 --- /dev/null +++ b/third_party/jpeg-xl/lib/jxl/cache_aligned.cc @@ -0,0 +1,157 @@ +// 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/cache_aligned.h" + +#include +#include + +// Disabled: slower than malloc + alignment. +#define JXL_USE_MMAP 0 + +#if JXL_USE_MMAP +#include +#endif + +#include // std::max +#include +#include // kMaxVectorSize +#include + +#include "lib/jxl/base/printf_macros.h" +#include "lib/jxl/base/status.h" + +namespace jxl { +namespace { + +#pragma pack(push, 1) +struct AllocationHeader { + void* allocated; + size_t allocated_size; + uint8_t left_padding[hwy::kMaxVectorSize]; +}; +#pragma pack(pop) + +std::atomic num_allocations{0}; +std::atomic bytes_in_use{0}; +std::atomic max_bytes_in_use{0}; + +} // namespace + +// Avoids linker errors in pre-C++17 builds. +constexpr size_t CacheAligned::kPointerSize; +constexpr size_t CacheAligned::kCacheLineSize; +constexpr size_t CacheAligned::kAlignment; +constexpr size_t CacheAligned::kAlias; + +void CacheAligned::PrintStats() { + fprintf( + stderr, "Allocations: %" PRIuS " (max bytes in use: %E)\n", + static_cast(num_allocations.load(std::memory_order_relaxed)), + static_cast(max_bytes_in_use.load(std::memory_order_relaxed))); +} + +size_t CacheAligned::NextOffset() { + static std::atomic next{0}; + constexpr uint32_t kGroups = CacheAligned::kAlias / CacheAligned::kAlignment; + const uint32_t group = next.fetch_add(1, std::memory_order_relaxed) % kGroups; + return CacheAligned::kAlignment * group; +} + +void* CacheAligned::Allocate(const size_t payload_size, size_t offset) { + JXL_ASSERT(payload_size <= std::numeric_limits::max() / 2); + JXL_ASSERT((offset % kAlignment == 0) && offset <= kAlias); + + // What: | misalign | unused | AllocationHeader |payload + // Size: |<= kAlias | offset | |payload_size + // ^allocated.^aligned.^header............^payload + // The header must immediately precede payload, which must remain aligned. + // To avoid wasting space, the header resides at the end of `unused`, + // which therefore cannot be empty (offset == 0). + if (offset == 0) { + // SVE/RVV vectors can be large, so we cannot rely on them (including the + // padding at the end of AllocationHeader) to fit in kAlignment. + offset = hwy::RoundUpTo(sizeof(AllocationHeader), kAlignment); + } + +#if JXL_USE_MMAP + const size_t allocated_size = offset + payload_size; + const int flags = MAP_PRIVATE | MAP_ANONYMOUS | MAP_POPULATE; + void* allocated = + mmap(nullptr, allocated_size, PROT_READ | PROT_WRITE, flags, -1, 0); + if (allocated == MAP_FAILED) return nullptr; + const uintptr_t aligned = reinterpret_cast(allocated); +#else + const size_t allocated_size = kAlias + offset + payload_size; + void* allocated = malloc(allocated_size); + if (allocated == nullptr) return nullptr; + // Always round up even if already aligned - we already asked for kAlias + // extra bytes and there's no way to give them back. + uintptr_t aligned = reinterpret_cast(allocated) + kAlias; + static_assert((kAlias & (kAlias - 1)) == 0, "kAlias must be a power of 2"); + static_assert(kAlias >= kAlignment, "Cannot align to more than kAlias"); + aligned &= ~(kAlias - 1); +#endif + +#if 0 + // No effect. + uintptr_t page_aligned = reinterpret_cast(allocated); + page_aligned &= ~(4096 - 1); + if (madvise(reinterpret_cast(page_aligned), allocated_size, + MADV_WILLNEED) != 0) { + JXL_NOTIFY_ERROR("madvise failed"); + } +#elif 0 + // INCREASES both first and subsequent decode times. + if (mlock(allocated, allocated_size) != 0) { + JXL_NOTIFY_ERROR("mlock failed"); + } +#endif + + // Update statistics (#allocations and max bytes in use) + num_allocations.fetch_add(1, std::memory_order_relaxed); + const uint64_t prev_bytes = + bytes_in_use.fetch_add(allocated_size, std::memory_order_acq_rel); + uint64_t expected_max = max_bytes_in_use.load(std::memory_order_acquire); + for (;;) { + const uint64_t desired = + std::max(expected_max, prev_bytes + allocated_size); + if (max_bytes_in_use.compare_exchange_strong(expected_max, desired, + std::memory_order_acq_rel)) { + break; + } + } + + const uintptr_t payload = aligned + offset; // still aligned + + // Stash `allocated` and payload_size inside header for use by Free(). + AllocationHeader* header = reinterpret_cast(payload) - 1; + header->allocated = allocated; + header->allocated_size = allocated_size; + + return JXL_ASSUME_ALIGNED(reinterpret_cast(payload), 64); +} + +void CacheAligned::Free(const void* aligned_pointer) { + if (aligned_pointer == nullptr) { + return; + } + const uintptr_t payload = reinterpret_cast(aligned_pointer); + JXL_ASSERT(payload % kAlignment == 0); + const AllocationHeader* header = + reinterpret_cast(payload) - 1; + + // Subtract (2's complement negation). + bytes_in_use.fetch_add(~header->allocated_size + 1, + std::memory_order_acq_rel); + +#if JXL_USE_MMAP + munmap(header->allocated, header->allocated_size); +#else + free(header->allocated); +#endif +} + +} // namespace jxl diff --git a/third_party/jpeg-xl/lib/jxl/cms/color_encoding_cms.h b/third_party/jpeg-xl/lib/jxl/cms/color_encoding_cms.h --- a/third_party/jpeg-xl/lib/jxl/cms/color_encoding_cms.h +++ b/third_party/jpeg-xl/lib/jxl/cms/color_encoding_cms.h @@ -18,10 +18,11 @@ #include #include "lib/jxl/base/common.h" #include "lib/jxl/base/status.h" #include "lib/jxl/cms/color_management.h" +#include "lib/jxl/cms/jxl_cms.h" namespace jxl { namespace cms { using IccBytes = std::vector; @@ -503,20 +504,13 @@ // rendering intent and ICC bytes bool SameColorEncoding(const ColorEncoding& other) const { return SameColorSpace(other) && tf.IsSame(other.tf); } - Status CreateICC() { - icc.clear(); - return MaybeCreateProfile(*this, &icc); - } - // Returns true if all fields have been initialized (possibly to kUnknown). // Returns false if the ICC profile is invalid or decoding it fails. Status SetFieldsFromICC(IccBytes&& new_icc, const JxlCmsInterface& cms) { - // TODO(eustas): take icc - // TODO(eustas): clean icc on error // In case parsing fails, mark the ColorEncoding as invalid. JXL_ASSERT(!new_icc.empty()); color_space = ColorSpace::kUnknown; tf.transfer_function = TransferFunction::kUnknown; icc.clear(); @@ -531,53 +525,55 @@ JXL_RETURN_IF_ERROR(FromExternal(external)); icc = std::move(new_icc); return true; } - void ToExternal(JxlColorEncoding* external) const { - // TODO(eustas): update copy/update storage and call .ToExternal on it + JxlColorEncoding ToExternal() const { + JxlColorEncoding external = {}; if (!have_fields) { - external->color_space = JXL_COLOR_SPACE_UNKNOWN; - external->primaries = JXL_PRIMARIES_CUSTOM; - external->rendering_intent = JXL_RENDERING_INTENT_PERCEPTUAL; //? - external->transfer_function = JXL_TRANSFER_FUNCTION_UNKNOWN; - external->white_point = JXL_WHITE_POINT_CUSTOM; - return; + external.color_space = JXL_COLOR_SPACE_UNKNOWN; + external.primaries = JXL_PRIMARIES_CUSTOM; + external.rendering_intent = JXL_RENDERING_INTENT_PERCEPTUAL; //? + external.transfer_function = JXL_TRANSFER_FUNCTION_UNKNOWN; + external.white_point = JXL_WHITE_POINT_CUSTOM; + return external; } - external->color_space = static_cast(color_space); + external.color_space = static_cast(color_space); - external->white_point = static_cast(white_point); + external.white_point = static_cast(white_point); CIExy wp = GetWhitePoint(); - external->white_point_xy[0] = wp.x; - external->white_point_xy[1] = wp.y; + external.white_point_xy[0] = wp.x; + external.white_point_xy[1] = wp.y; - if (external->color_space == JXL_COLOR_SPACE_RGB || - external->color_space == JXL_COLOR_SPACE_UNKNOWN) { - external->primaries = static_cast(primaries); + if (external.color_space == JXL_COLOR_SPACE_RGB || + external.color_space == JXL_COLOR_SPACE_UNKNOWN) { + external.primaries = static_cast(primaries); PrimariesCIExy p = GetPrimaries(); - external->primaries_red_xy[0] = p.r.x; - external->primaries_red_xy[1] = p.r.y; - external->primaries_green_xy[0] = p.g.x; - external->primaries_green_xy[1] = p.g.y; - external->primaries_blue_xy[0] = p.b.x; - external->primaries_blue_xy[1] = p.b.y; + external.primaries_red_xy[0] = p.r.x; + external.primaries_red_xy[1] = p.r.y; + external.primaries_green_xy[0] = p.g.x; + external.primaries_green_xy[1] = p.g.y; + external.primaries_blue_xy[0] = p.b.x; + external.primaries_blue_xy[1] = p.b.y; } if (tf.have_gamma) { - external->transfer_function = JXL_TRANSFER_FUNCTION_GAMMA; - external->gamma = tf.GetGamma(); + external.transfer_function = JXL_TRANSFER_FUNCTION_GAMMA; + external.gamma = tf.GetGamma(); } else { - external->transfer_function = + external.transfer_function = static_cast(tf.GetTransferFunction()); - external->gamma = 0; + external.gamma = 0; } - external->rendering_intent = + external.rendering_intent = static_cast(rendering_intent); + return external; } + // NB: does not create ICC. Status FromExternal(const JxlColorEncoding& external) { // TODO(eustas): update non-serializable on call-site color_space = static_cast(external.color_space); JXL_RETURN_IF_ERROR( @@ -618,155 +614,15 @@ this->tf = tf; JXL_RETURN_IF_ERROR(RenderingIntentFromExternal(external.rendering_intent, &rendering_intent)); - // The ColorEncoding caches an ICC profile it created earlier that may no - // longer match the profile with the changed fields, so re-create it. - if (!(CreateICC())) { - // This is not an error: for example, it doesn't have ICC profile creation - // implemented for XYB. This should not be returned as error, since - // FromExternal still worked correctly, and what - // matters is that internal->ICC() will not return the wrong profile. - } + icc.clear(); return true; } }; -// These strings are baked into Description - do not change. - -static inline std::string ToString(ColorSpace color_space) { - switch (color_space) { - case ColorSpace::kRGB: - return "RGB"; - case ColorSpace::kGray: - return "Gra"; - case ColorSpace::kXYB: - return "XYB"; - case ColorSpace::kUnknown: - return "CS?"; - } - // Should not happen - visitor fails if enum is invalid. - JXL_UNREACHABLE("Invalid ColorSpace %u", static_cast(color_space)); -} - -static inline std::string ToString(WhitePoint white_point) { - switch (white_point) { - case WhitePoint::kD65: - return "D65"; - case WhitePoint::kCustom: - return "Cst"; - case WhitePoint::kE: - return "EER"; - case WhitePoint::kDCI: - return "DCI"; - } - // Should not happen - visitor fails if enum is invalid. - JXL_UNREACHABLE("Invalid WhitePoint %u", static_cast(white_point)); -} - -static inline std::string ToString(Primaries primaries) { - switch (primaries) { - case Primaries::kSRGB: - return "SRG"; - case Primaries::k2100: - return "202"; - case Primaries::kP3: - return "DCI"; - case Primaries::kCustom: - return "Cst"; - } - // Should not happen - visitor fails if enum is invalid. - JXL_UNREACHABLE("Invalid Primaries %u", static_cast(primaries)); -} - -static inline std::string ToString(TransferFunction transfer_function) { - switch (transfer_function) { - case TransferFunction::kSRGB: - return "SRG"; - case TransferFunction::kLinear: - return "Lin"; - case TransferFunction::k709: - return "709"; - case TransferFunction::kPQ: - return "PeQ"; - case TransferFunction::kHLG: - return "HLG"; - case TransferFunction::kDCI: - return "DCI"; - case TransferFunction::kUnknown: - return "TF?"; - } - // Should not happen - visitor fails if enum is invalid. - JXL_UNREACHABLE("Invalid TransferFunction %u", - static_cast(transfer_function)); -} - -static inline std::string ToString(RenderingIntent rendering_intent) { - switch (rendering_intent) { - case RenderingIntent::kPerceptual: - return "Per"; - case RenderingIntent::kRelative: - return "Rel"; - case RenderingIntent::kSaturation: - return "Sat"; - case RenderingIntent::kAbsolute: - return "Abs"; - } - // Should not happen - visitor fails if enum is invalid. - JXL_UNREACHABLE("Invalid RenderingIntent %u", - static_cast(rendering_intent)); -} - -// Returns a representation of the ColorEncoding fields (not icc). -// Example description: "RGB_D65_SRG_Rel_Lin" -static inline std::string Description(const ColorEncoding& c) { - std::string d = ToString(c.color_space); - - bool explicit_wp_tf = (c.color_space != ColorSpace::kXYB); - if (explicit_wp_tf) { - d += '_'; - if (c.white_point == WhitePoint::kCustom) { - const CIExy wp = c.GetWhitePoint(); - d += jxl::ToString(wp.x) + ';'; - d += jxl::ToString(wp.y); - } else { - d += ToString(c.white_point); - } - } - - if (c.HasPrimaries()) { - d += '_'; - if (c.primaries == Primaries::kCustom) { - const PrimariesCIExy pr = c.GetPrimaries(); - d += jxl::ToString(pr.r.x) + ';'; - d += jxl::ToString(pr.r.y) + ';'; - d += jxl::ToString(pr.g.x) + ';'; - d += jxl::ToString(pr.g.y) + ';'; - d += jxl::ToString(pr.b.x) + ';'; - d += jxl::ToString(pr.b.y); - } else { - d += ToString(c.primaries); - } - } - - d += '_'; - d += ToString(c.rendering_intent); - - if (explicit_wp_tf) { - d += '_'; - if (c.tf.have_gamma) { - d += 'g'; - d += jxl::ToString(c.tf.GetGamma()); - } else { - d += ToString(c.tf.transfer_function); - } - } - - return d; -} - } // namespace cms } // namespace jxl #endif // LIB_JXL_CMS_COLOR_ENCODING_CMS_H_ diff --git a/third_party/jpeg-xl/lib/jxl/cms/color_management.h b/third_party/jpeg-xl/lib/jxl/cms/color_management.h --- a/third_party/jpeg-xl/lib/jxl/cms/color_management.h +++ b/third_party/jpeg-xl/lib/jxl/cms/color_management.h @@ -5,15 +5,19 @@ #ifndef LIB_JXL_COLOR_MANAGEMENT_H_ #define LIB_JXL_COLOR_MANAGEMENT_H_ // ICC profiles and color space conversions. +// TODO(eustas): rename to something_internal.h; those methods are not allowed +// to be used outside of cms/ folder. + +#include #include +#include #include -#include "lib/jxl/base/compiler_specific.h" #include "lib/jxl/base/status.h" namespace jxl { enum class ExtraTF { @@ -21,27 +25,21 @@ kPQ, kHLG, kSRGB, }; -namespace cms { -struct ColorEncoding; -struct CIExy; -} // namespace cms - // NOTE: for XYB colorspace, the created profile can be used to transform a // *scaled* XYB image (created by ScaleXYB()) to another colorspace. -Status MaybeCreateProfile(const jxl::cms::ColorEncoding& c, - std::vector* JXL_RESTRICT icc); - -Status CIEXYZFromWhiteCIExy(const jxl::cms::CIExy& xy, float XYZ[3]); +Status MaybeCreateProfile(const JxlColorEncoding& c, std::vector* icc); +Status CIEXYZFromWhiteCIExy(double wx, double wy, float XYZ[3]); Status PrimariesToXYZ(float rx, float ry, float gx, float gy, float bx, float by, float wx, float wy, float matrix[9]); // Adapts whitepoint x, y to D50 Status AdaptToXYZD50(float wx, float wy, float matrix[9]); Status PrimariesToXYZD50(float rx, float ry, float gx, float gy, float bx, float by, float wx, float wy, float matrix[9]); +std::string ColorEncodingDescription(const JxlColorEncoding& c); } // namespace jxl #endif // LIB_JXL_COLOR_MANAGEMENT_H_ diff --git a/third_party/jpeg-xl/lib/jxl/cms/color_management.cc b/third_party/jpeg-xl/lib/jxl/cms/color_management.cc --- a/third_party/jpeg-xl/lib/jxl/cms/color_management.cc +++ b/third_party/jpeg-xl/lib/jxl/cms/color_management.cc @@ -10,21 +10,11 @@ #include #include #include #include -#include "lib/jxl/cms/color_encoding_cms.h" - -using jxl::cms::CIExy; -using jxl::cms::ColorEncoding; -using jxl::cms::ColorSpace; -using jxl::cms::IccBytes; -using jxl::cms::Primaries; -using jxl::cms::PrimariesCIExy; -using jxl::cms::RenderingIntent; -using jxl::cms::TransferFunction; -using jxl::cms::WhitePoint; +#include "lib/jxl/cms/jxl_cms.h" #undef HWY_TARGET_INCLUDE #define HWY_TARGET_INCLUDE "lib/jxl/cms/color_management.cc" #include #include @@ -43,22 +33,23 @@ HWY_BEFORE_NAMESPACE(); namespace jxl { namespace HWY_NAMESPACE { -Status ToneMapPixel(const ColorEncoding& c, const float in[3], +Status ToneMapPixel(const JxlColorEncoding& c, const float in[3], uint8_t pcslab_out[3]) { - const PrimariesCIExy p = c.GetPrimaries(); - const CIExy wp = c.GetWhitePoint(); float primaries_XYZ[9]; - JXL_RETURN_IF_ERROR(PrimariesToXYZ(p.r.x, p.r.y, p.g.x, p.g.y, p.b.x, p.b.y, - wp.x, wp.y, primaries_XYZ)); + JXL_RETURN_IF_ERROR(PrimariesToXYZ( + c.primaries_red_xy[0], c.primaries_red_xy[1], c.primaries_green_xy[0], + c.primaries_green_xy[1], c.primaries_blue_xy[0], c.primaries_blue_xy[1], + c.white_point_xy[0], c.white_point_xy[1], primaries_XYZ)); const float luminances[3] = {primaries_XYZ[3], primaries_XYZ[4], primaries_XYZ[5]}; float linear[3]; HWY_CAPPED(float, 1) d; - if (c.tf.IsPQ()) { + JxlTransferFunction tf = c.transfer_function; + if (tf == JXL_TRANSFER_FUNCTION_PQ) { TF_PQ tf_pq(/*display_intensity_target=*/10000.0); for (size_t i = 0; i < 3; ++i) { linear[i] = tf_pq.DisplayFromEncoded(in[i]); } } else { @@ -66,11 +57,11 @@ linear[i] = TF_HLG().DisplayFromEncoded(in[i]); } } auto r = LoadU(d, &linear[0]), g = LoadU(d, &linear[1]), b = LoadU(d, &linear[2]); - if (c.tf.IsPQ()) { + if (tf == JXL_TRANSFER_FUNCTION_PQ) { Rec2408ToneMapper tone_mapper({0, 10000}, {0, 250}, luminances); tone_mapper.ToneMap(&r, &g, &b); } else { HlgOOTF ootf(/*source_luminance=*/300, /*target_luminance=*/80, luminances); @@ -80,11 +71,12 @@ StoreU(r, d, &linear[0]); StoreU(g, d, &linear[1]); StoreU(b, d, &linear[2]); float chad[9]; - JXL_RETURN_IF_ERROR(AdaptToXYZD50(wp.x, wp.y, chad)); + JXL_RETURN_IF_ERROR( + AdaptToXYZD50(c.white_point_xy[0], c.white_point_xy[1], chad)); float to_xyzd50[9]; Mul3x3Matrix(chad, primaries_XYZ, to_xyzd50); float xyz[3] = {0, 0, 0}; for (size_t xyz_c = 0; xyz_c < 3; ++xyz_c) { @@ -167,40 +159,41 @@ // Local functions. HWY_EXPORT(ToneMapPixel); HWY_EXPORT(CreateTableCurve); -Status CIEXYZFromWhiteCIExy(const CIExy& xy, float XYZ[3]) { +Status CIEXYZFromWhiteCIExy(double wx, double wy, float XYZ[3]) { // Target Y = 1. - if (std::abs(xy.y) < 1e-12) return JXL_FAILURE("Y value is too small"); - const float factor = 1 / xy.y; - XYZ[0] = xy.x * factor; + if (std::abs(wy) < 1e-12) return JXL_FAILURE("Y value is too small"); + const float factor = 1 / wy; + XYZ[0] = wx * factor; XYZ[1] = 1; - XYZ[2] = (1 - xy.x - xy.y) * factor; + XYZ[2] = (1 - wx - wy) * factor; return true; } namespace { constexpr bool kEnable3DToneMapping = JXL_ENABLE_3D_ICC_TONEMAPPING; -bool CanToneMap(const ColorEncoding& encoding) { +bool CanToneMap(const JxlColorEncoding& encoding) { // If the color space cannot be represented by a CICP tag in the ICC profile // then the rest of the profile must unambiguously identify it; we have less // freedom to do use it for tone mapping. - return encoding.color_space == ColorSpace::kRGB && encoding.HasPrimaries() && - (encoding.tf.IsPQ() || encoding.tf.IsHLG()) && - ((encoding.primaries == Primaries::kP3 && - (encoding.white_point == WhitePoint::kD65 || - encoding.white_point == WhitePoint::kDCI)) || - (encoding.primaries != Primaries::kCustom && - encoding.white_point == WhitePoint::kD65)); + JxlTransferFunction tf = encoding.transfer_function; + JxlPrimaries p = encoding.primaries; + JxlWhitePoint wp = encoding.white_point; + return encoding.color_space == JXL_COLOR_SPACE_RGB && + (tf == JXL_TRANSFER_FUNCTION_PQ || tf == JXL_TRANSFER_FUNCTION_HLG) && + ((p == JXL_PRIMARIES_P3 && + (wp == JXL_WHITE_POINT_D65 || wp == JXL_WHITE_POINT_DCI)) || + (p != JXL_PRIMARIES_CUSTOM && wp == JXL_WHITE_POINT_D65)); } -void ICCComputeMD5(const IccBytes& data, uint8_t sum[16]) +void ICCComputeMD5(const std::vector& data, uint8_t sum[16]) JXL_NO_SANITIZE("unsigned-integer-overflow") { - IccBytes data64 = data; + std::vector data64 = data; data64.push_back(128); // Add bytes such that ((size + 8) & 63) == 0. size_t extra = ((64 - ((data64.size() + 8) & 63)) & 63); data64.resize(data64.size() + extra, 0); for (uint64_t i = 0; i < 64; i += 8) { @@ -275,55 +268,55 @@ sum[13] = d0 >> 8u; sum[14] = d0 >> 16u; sum[15] = d0 >> 24u; } -Status CreateICCChadMatrix(CIExy w, float result[9]) { +Status CreateICCChadMatrix(double wx, double wy, float result[9]) { float m[9]; - if (w.y == 0) { // WhitePoint can not be pitch-black. + if (wy == 0) { // WhitePoint can not be pitch-black. return JXL_FAILURE("Invalid WhitePoint"); } - JXL_RETURN_IF_ERROR(AdaptToXYZD50(w.x, w.y, m)); + JXL_RETURN_IF_ERROR(AdaptToXYZD50(wx, wy, m)); memcpy(result, m, sizeof(float) * 9); return true; } // Creates RGB to XYZ matrix given RGB primaries and whitepoint in xy. -Status CreateICCRGBMatrix(CIExy r, CIExy g, CIExy b, CIExy w, float result[9]) { +Status CreateICCRGBMatrix(double rx, double ry, double gx, double gy, double bx, + double by, double wx, double wy, float result[9]) { float m[9]; - JXL_RETURN_IF_ERROR( - PrimariesToXYZD50(r.x, r.y, g.x, g.y, b.x, b.y, w.x, w.y, m)); + JXL_RETURN_IF_ERROR(PrimariesToXYZD50(rx, ry, gx, gy, bx, by, wx, wy, m)); memcpy(result, m, sizeof(float) * 9); return true; } -void WriteICCUint32(uint32_t value, size_t pos, IccBytes* JXL_RESTRICT icc) { +void WriteICCUint32(uint32_t value, size_t pos, std::vector* icc) { if (icc->size() < pos + 4) icc->resize(pos + 4); (*icc)[pos + 0] = (value >> 24u) & 255; (*icc)[pos + 1] = (value >> 16u) & 255; (*icc)[pos + 2] = (value >> 8u) & 255; (*icc)[pos + 3] = value & 255; } -void WriteICCUint16(uint16_t value, size_t pos, IccBytes* JXL_RESTRICT icc) { +void WriteICCUint16(uint16_t value, size_t pos, std::vector* icc) { if (icc->size() < pos + 2) icc->resize(pos + 2); (*icc)[pos + 0] = (value >> 8u) & 255; (*icc)[pos + 1] = value & 255; } -void WriteICCUint8(uint8_t value, size_t pos, IccBytes* JXL_RESTRICT icc) { +void WriteICCUint8(uint8_t value, size_t pos, std::vector* icc) { if (icc->size() < pos + 1) icc->resize(pos + 1); (*icc)[pos] = value; } // Writes a 4-character tag -void WriteICCTag(const char* value, size_t pos, IccBytes* JXL_RESTRICT icc) { +void WriteICCTag(const char* value, size_t pos, std::vector* icc) { if (icc->size() < pos + 4) icc->resize(pos + 4); memcpy(icc->data() + pos, value, 4); } -Status WriteICCS15Fixed16(float value, size_t pos, IccBytes* JXL_RESTRICT icc) { +Status WriteICCS15Fixed16(float value, size_t pos, std::vector* icc) { // "nextafterf" for 32768.0f towards zero are: // 32767.998046875, 32767.99609375, 32767.994140625 // Even the first value works well,... bool ok = (-32767.995f <= value) && (value <= 32767.995f); if (!ok) return JXL_FAILURE("ICC value is out of range / NaN"); @@ -332,24 +325,26 @@ uint32_t u = static_cast(i); WriteICCUint32(u, pos, icc); return true; } -Status CreateICCHeader(const ColorEncoding& c, IccBytes* JXL_RESTRICT header) { +Status CreateICCHeader(const JxlColorEncoding& c, + std::vector* header) { // TODO(lode): choose color management engine name, e.g. "skia" if // integrated in skia. static const char* kCmm = "jxl "; header->resize(128, 0); WriteICCUint32(0, 0, header); // size, correct value filled in at end WriteICCTag(kCmm, 4, header); WriteICCUint32(0x04400000u, 8, header); const char* profile_type = - c.color_space == ColorSpace::kXYB ? "scnr" : "mntr"; + c.color_space == JXL_COLOR_SPACE_XYB ? "scnr" : "mntr"; WriteICCTag(profile_type, 12, header); - WriteICCTag(c.color_space == ColorSpace::kGray ? "GRAY" : "RGB ", 16, header); + WriteICCTag(c.color_space == JXL_COLOR_SPACE_GRAY ? "GRAY" : "RGB ", 16, + header); if (kEnable3DToneMapping && CanToneMap(c)) { // We are going to use a 3D LUT for tone mapping, which will be more compact // with an 8-bit LUT to CIELAB than with a 16-bit LUT to XYZ. 8-bit XYZ // would not be viable due to XYZ being linear, whereas it is fine with // CIELAB's ~cube root. @@ -387,30 +382,30 @@ return true; } void AddToICCTagTable(const char* tag, size_t offset, size_t size, - IccBytes* JXL_RESTRICT tagtable, + std::vector* tagtable, std::vector* offsets) { WriteICCTag(tag, tagtable->size(), tagtable); // writing true offset deferred to later WriteICCUint32(0, tagtable->size(), tagtable); offsets->push_back(offset); WriteICCUint32(size, tagtable->size(), tagtable); } -void FinalizeICCTag(IccBytes* JXL_RESTRICT tags, size_t* offset, size_t* size) { +void FinalizeICCTag(std::vector* tags, size_t* offset, size_t* size) { while ((tags->size() & 3) != 0) { tags->push_back(0); } *offset += *size; *size = tags->size() - *offset; } // The input text must be ASCII, writing other characters to UTF-16 is not // implemented. -void CreateICCMlucTag(const std::string& text, IccBytes* JXL_RESTRICT tags) { +void CreateICCMlucTag(const std::string& text, std::vector* tags) { WriteICCTag("mluc", tags->size(), tags); WriteICCUint32(0, tags->size(), tags); WriteICCUint32(1, tags->size(), tags); WriteICCUint32(12, tags->size(), tags); WriteICCTag("enUS", tags->size(), tags); @@ -420,68 +415,69 @@ tags->push_back(0); // prepend 0 for UTF-16 tags->push_back(text[i]); } } -Status CreateICCXYZTag(float xyz[3], IccBytes* JXL_RESTRICT tags) { +Status CreateICCXYZTag(float xyz[3], std::vector* tags) { WriteICCTag("XYZ ", tags->size(), tags); WriteICCUint32(0, tags->size(), tags); for (size_t i = 0; i < 3; ++i) { JXL_RETURN_IF_ERROR(WriteICCS15Fixed16(xyz[i], tags->size(), tags)); } return true; } -Status CreateICCChadTag(float chad[9], IccBytes* JXL_RESTRICT tags) { +Status CreateICCChadTag(float chad[9], std::vector* tags) { WriteICCTag("sf32", tags->size(), tags); WriteICCUint32(0, tags->size(), tags); for (size_t i = 0; i < 9; i++) { JXL_RETURN_IF_ERROR(WriteICCS15Fixed16(chad[i], tags->size(), tags)); } return true; } -void MaybeCreateICCCICPTag(const ColorEncoding& c, IccBytes* JXL_RESTRICT tags, - size_t* offset, size_t* size, - IccBytes* JXL_RESTRICT tagtable, +void MaybeCreateICCCICPTag(const JxlColorEncoding& c, + std::vector* tags, size_t* offset, + size_t* size, std::vector* tagtable, std::vector* offsets) { - if (c.color_space != ColorSpace::kRGB) { + if (c.color_space != JXL_COLOR_SPACE_RGB) { return; } uint8_t primaries = 0; - if (c.primaries == Primaries::kP3) { - if (c.white_point == WhitePoint::kD65) { + if (c.primaries == JXL_PRIMARIES_P3) { + if (c.white_point == JXL_WHITE_POINT_D65) { primaries = 12; - } else if (c.white_point == WhitePoint::kDCI) { + } else if (c.white_point == JXL_WHITE_POINT_DCI) { primaries = 11; } else { return; } - } else if (c.primaries != Primaries::kCustom && - c.white_point == WhitePoint::kD65) { + } else if (c.primaries != JXL_PRIMARIES_CUSTOM && + c.white_point == JXL_WHITE_POINT_D65) { primaries = static_cast(c.primaries); } else { return; } - if (c.tf.IsUnknown() || c.tf.have_gamma) { + JxlTransferFunction tf = c.transfer_function; + if (tf == JXL_TRANSFER_FUNCTION_UNKNOWN || + tf == JXL_TRANSFER_FUNCTION_GAMMA) { return; } WriteICCTag("cicp", tags->size(), tags); WriteICCUint32(0, tags->size(), tags); WriteICCUint8(primaries, tags->size(), tags); - WriteICCUint8(static_cast(c.tf.GetTransferFunction()), tags->size(), - tags); + WriteICCUint8(static_cast(tf), tags->size(), tags); // Matrix WriteICCUint8(0, tags->size(), tags); // Full range WriteICCUint8(1, tags->size(), tags); FinalizeICCTag(tags, offset, size); AddToICCTagTable("cicp", *offset, *size, tagtable, offsets); } void CreateICCCurvCurvTag(const std::vector& curve, - IccBytes* JXL_RESTRICT tags) { + std::vector* tags) { size_t pos = tags->size(); tags->resize(tags->size() + 12 + curve.size() * 2, 0); WriteICCTag("curv", pos, tags); WriteICCUint32(0, pos + 4, tags); WriteICCUint32(curve.size(), pos + 8, tags); @@ -490,22 +486,22 @@ } } // Writes 12 + 4*params.size() bytes Status CreateICCCurvParaTag(std::vector params, size_t curve_type, - IccBytes* JXL_RESTRICT tags) { + std::vector* tags) { WriteICCTag("para", tags->size(), tags); WriteICCUint32(0, tags->size(), tags); WriteICCUint16(curve_type, tags->size(), tags); WriteICCUint16(0, tags->size(), tags); for (size_t i = 0; i < params.size(); i++) { JXL_RETURN_IF_ERROR(WriteICCS15Fixed16(params[i], tags->size(), tags)); } return true; } -Status CreateICCLutAtoBTagForXYB(IccBytes* JXL_RESTRICT tags) { +Status CreateICCLutAtoBTagForXYB(std::vector* tags) { WriteICCTag("mAB ", tags->size(), tags); // 4 reserved bytes set to 0 WriteICCUint32(0, tags->size(), tags); // number of input channels WriteICCUint8(3, tags->size(), tags); @@ -584,11 +580,12 @@ JXL_RETURN_IF_ERROR(WriteICCS15Fixed16(intercept, tags->size(), tags)); } return true; } -Status CreateICCLutAtoBTagForHDR(ColorEncoding c, IccBytes* JXL_RESTRICT tags) { +Status CreateICCLutAtoBTagForHDR(JxlColorEncoding c, + std::vector* tags) { static constexpr size_t k3DLutDim = 9; WriteICCTag("mft1", tags->size(), tags); // 4 reserved bytes set to 0 WriteICCUint32(0, tags->size(), tags); // number of input channels @@ -640,11 +637,11 @@ return true; } // Some software (Apple Safari, Preview) requires this. -Status CreateICCNoOpBToATag(IccBytes* JXL_RESTRICT tags) { +Status CreateICCNoOpBToATag(std::vector* tags) { WriteICCTag("mBA ", tags->size(), tags); // 4 reserved bytes set to 0 WriteICCUint32(0, tags->size(), tags); // number of input channels WriteICCUint8(3, tags->size(), tags); @@ -668,10 +665,107 @@ JXL_RETURN_IF_ERROR(CreateICCCurvParaTag({1.0f}, 0, tags)); return true; } +// These strings are baked into Description - do not change. + +std::string ToString(JxlColorSpace color_space) { + switch (color_space) { + case JXL_COLOR_SPACE_RGB: + return "RGB"; + case JXL_COLOR_SPACE_GRAY: + return "Gra"; + case JXL_COLOR_SPACE_XYB: + return "XYB"; + case JXL_COLOR_SPACE_UNKNOWN: + return "CS?"; + } + // Should not happen - visitor fails if enum is invalid. + JXL_UNREACHABLE("Invalid ColorSpace %u", static_cast(color_space)); +} + +std::string ToString(JxlWhitePoint white_point) { + switch (white_point) { + case JXL_WHITE_POINT_D65: + return "D65"; + case JXL_WHITE_POINT_CUSTOM: + return "Cst"; + case JXL_WHITE_POINT_E: + return "EER"; + case JXL_WHITE_POINT_DCI: + return "DCI"; + } + // Should not happen - visitor fails if enum is invalid. + JXL_UNREACHABLE("Invalid WhitePoint %u", static_cast(white_point)); +} + +std::string ToString(JxlPrimaries primaries) { + switch (primaries) { + case JXL_PRIMARIES_SRGB: + return "SRG"; + case JXL_PRIMARIES_2100: + return "202"; + case JXL_PRIMARIES_P3: + return "DCI"; + case JXL_PRIMARIES_CUSTOM: + return "Cst"; + } + // Should not happen - visitor fails if enum is invalid. + JXL_UNREACHABLE("Invalid Primaries %u", static_cast(primaries)); +} + +std::string ToString(JxlTransferFunction transfer_function) { + switch (transfer_function) { + case JXL_TRANSFER_FUNCTION_SRGB: + return "SRG"; + case JXL_TRANSFER_FUNCTION_LINEAR: + return "Lin"; + case JXL_TRANSFER_FUNCTION_709: + return "709"; + case JXL_TRANSFER_FUNCTION_PQ: + return "PeQ"; + case JXL_TRANSFER_FUNCTION_HLG: + return "HLG"; + case JXL_TRANSFER_FUNCTION_DCI: + return "DCI"; + case JXL_TRANSFER_FUNCTION_UNKNOWN: + return "TF?"; + case JXL_TRANSFER_FUNCTION_GAMMA: + JXL_UNREACHABLE("Invalid TransferFunction: gamma"); + } + // Should not happen - visitor fails if enum is invalid. + JXL_UNREACHABLE("Invalid TransferFunction %u", + static_cast(transfer_function)); +} + +std::string ToString(JxlRenderingIntent rendering_intent) { + switch (rendering_intent) { + case JXL_RENDERING_INTENT_PERCEPTUAL: + return "Per"; + case JXL_RENDERING_INTENT_RELATIVE: + return "Rel"; + case JXL_RENDERING_INTENT_SATURATION: + return "Sat"; + case JXL_RENDERING_INTENT_ABSOLUTE: + return "Abs"; + } + // Should not happen - visitor fails if enum is invalid. + JXL_UNREACHABLE("Invalid RenderingIntent %u", + static_cast(rendering_intent)); +} + +/* Chromatic adaptation matrices*/ +constexpr float kBradford[9] = { + 0.8951f, 0.2664f, -0.1614f, -0.7502f, 1.7135f, + 0.0367f, 0.0389f, -0.0685f, 1.0296f, +}; +constexpr float kBradfordInv[9] = { + 0.9869929f, -0.1470543f, 0.1599627f, 0.4323053f, 0.5183603f, + 0.0492912f, -0.0085287f, 0.0400428f, 0.9684867f, +}; + } // namespace Status PrimariesToXYZ(float rx, float ry, float gx, float gy, float bx, float by, float wx, float wy, float matrix[9]) { bool ok = (wx >= 0) && (wx <= 1) && (wy > 0) && (wy <= 1); @@ -699,23 +793,13 @@ Mul3x3Matrix(primaries, a, matrix); return true; } -/* Chromatic adaptation matrices*/ -constexpr float kBradford[9] = { - 0.8951f, 0.2664f, -0.1614f, -0.7502f, 1.7135f, - 0.0367f, 0.0389f, -0.0685f, 1.0296f, -}; -constexpr float kBradfordInv[9] = { - 0.9869929f, -0.1470543f, 0.1599627f, 0.4323053f, 0.5183603f, - 0.0492912f, -0.0085287f, 0.0400428f, 0.9684867f, -}; - // Adapts whitepoint x, y to D50 Status AdaptToXYZD50(float wx, float wy, float matrix[9]) { - bool ok = (wx >= 0) || (wx <= 1) || (wy > 0) || (wy <= 1); + bool ok = (wx >= 0) && (wx <= 1) && (wy > 0) && (wy <= 1); if (!ok) { // Out of range values can cause division through zero // further down with the bradford adaptation too. return JXL_FAILURE("Invalid white point"); } @@ -757,29 +841,78 @@ Mul3x3Matrix(d50, toXYZ, matrix); return true; } -Status MaybeCreateProfile(const ColorEncoding& c, IccBytes* JXL_RESTRICT icc) { - IccBytes header, tagtable, tags; +// Returns a representation of the ColorEncoding fields (not icc). +// Example description: "RGB_D65_SRG_Rel_Lin" +std::string ColorEncodingDescription(const JxlColorEncoding& c) { + std::string d = ToString(c.color_space); + + bool explicit_wp_tf = (c.color_space != JXL_COLOR_SPACE_XYB); + if (explicit_wp_tf) { + d += '_'; + if (c.white_point == JXL_WHITE_POINT_CUSTOM) { + d += jxl::ToString(c.white_point_xy[0]) + ';'; + d += jxl::ToString(c.white_point_xy[1]); + } else { + d += ToString(c.white_point); + } + } - if (c.color_space == ColorSpace::kUnknown || c.tf.IsUnknown()) { + if ((c.color_space != JXL_COLOR_SPACE_GRAY) && + (c.color_space != JXL_COLOR_SPACE_XYB)) { + d += '_'; + if (c.primaries == JXL_PRIMARIES_CUSTOM) { + d += jxl::ToString(c.primaries_red_xy[0]) + ';'; + d += jxl::ToString(c.primaries_red_xy[1]) + ';'; + d += jxl::ToString(c.primaries_green_xy[0]) + ';'; + d += jxl::ToString(c.primaries_green_xy[1]) + ';'; + d += jxl::ToString(c.primaries_blue_xy[0]) + ';'; + d += jxl::ToString(c.primaries_blue_xy[1]); + } else { + d += ToString(c.primaries); + } + } + + d += '_'; + d += ToString(c.rendering_intent); + + if (explicit_wp_tf) { + JxlTransferFunction tf = c.transfer_function; + d += '_'; + if (tf == JXL_TRANSFER_FUNCTION_GAMMA) { + d += 'g'; + d += jxl::ToString(c.gamma); + } else { + d += ToString(tf); + } + } + return d; +} + +Status MaybeCreateProfile(const JxlColorEncoding& c, + std::vector* icc) { + std::vector header, tagtable, tags; + JxlTransferFunction tf = c.transfer_function; + if (c.color_space == JXL_COLOR_SPACE_UNKNOWN || + tf == JXL_TRANSFER_FUNCTION_UNKNOWN) { return false; // Not an error } switch (c.color_space) { - case ColorSpace::kRGB: - case ColorSpace::kGray: - case ColorSpace::kXYB: + case JXL_COLOR_SPACE_RGB: + case JXL_COLOR_SPACE_GRAY: + case JXL_COLOR_SPACE_XYB: break; // OK default: return JXL_FAILURE("Invalid CS %u", static_cast(c.color_space)); } - if (c.color_space == ColorSpace::kXYB && - c.rendering_intent != RenderingIntent::kPerceptual) { + if (c.color_space == JXL_COLOR_SPACE_XYB && + c.rendering_intent != JXL_RENDERING_INTENT_PERCEPTUAL) { return JXL_FAILURE( "Only perceptual rendering intent implemented for XYB " "ICC profile."); } @@ -789,49 +922,52 @@ // tag count, deferred to later WriteICCUint32(0, tagtable.size(), &tagtable); size_t tag_offset = 0, tag_size = 0; - CreateICCMlucTag(Description(c), &tags); + CreateICCMlucTag(ColorEncodingDescription(c), &tags); FinalizeICCTag(&tags, &tag_offset, &tag_size); AddToICCTagTable("desc", tag_offset, tag_size, &tagtable, &offsets); const std::string copyright = "CC0"; CreateICCMlucTag(copyright, &tags); FinalizeICCTag(&tags, &tag_offset, &tag_size); AddToICCTagTable("cprt", tag_offset, tag_size, &tagtable, &offsets); // TODO(eustas): isn't it the other way round: gray image has d50 WhitePoint? - if (c.color_space == ColorSpace::kGray) { + if (c.color_space == JXL_COLOR_SPACE_GRAY) { float wtpt[3]; - JXL_RETURN_IF_ERROR(CIEXYZFromWhiteCIExy(c.GetWhitePoint(), wtpt)); + JXL_RETURN_IF_ERROR( + CIEXYZFromWhiteCIExy(c.white_point_xy[0], c.white_point_xy[1], wtpt)); JXL_RETURN_IF_ERROR(CreateICCXYZTag(wtpt, &tags)); } else { float d50[3] = {0.964203, 1.0, 0.824905}; JXL_RETURN_IF_ERROR(CreateICCXYZTag(d50, &tags)); } FinalizeICCTag(&tags, &tag_offset, &tag_size); AddToICCTagTable("wtpt", tag_offset, tag_size, &tagtable, &offsets); - if (c.color_space != ColorSpace::kGray) { + if (c.color_space != JXL_COLOR_SPACE_GRAY) { // Chromatic adaptation matrix float chad[9]; - JXL_RETURN_IF_ERROR(CreateICCChadMatrix(c.GetWhitePoint(), chad)); + JXL_RETURN_IF_ERROR( + CreateICCChadMatrix(c.white_point_xy[0], c.white_point_xy[1], chad)); JXL_RETURN_IF_ERROR(CreateICCChadTag(chad, &tags)); FinalizeICCTag(&tags, &tag_offset, &tag_size); AddToICCTagTable("chad", tag_offset, tag_size, &tagtable, &offsets); } - if (c.color_space == ColorSpace::kRGB) { + if (c.color_space == JXL_COLOR_SPACE_RGB) { MaybeCreateICCCICPTag(c, &tags, &tag_offset, &tag_size, &tagtable, &offsets); - const PrimariesCIExy primaries = c.GetPrimaries(); float m[9]; - JXL_RETURN_IF_ERROR(CreateICCRGBMatrix(primaries.r, primaries.g, - primaries.b, c.GetWhitePoint(), m)); + JXL_RETURN_IF_ERROR(CreateICCRGBMatrix( + c.primaries_red_xy[0], c.primaries_red_xy[1], c.primaries_green_xy[0], + c.primaries_green_xy[1], c.primaries_blue_xy[0], c.primaries_blue_xy[1], + c.white_point_xy[0], c.white_point_xy[1], m)); float r[3] = {m[0], m[3], m[6]}; float g[3] = {m[1], m[4], m[7]}; float b[3] = {m[2], m[5], m[8]}; JXL_RETURN_IF_ERROR(CreateICCXYZTag(r, &tags)); @@ -845,11 +981,11 @@ JXL_RETURN_IF_ERROR(CreateICCXYZTag(b, &tags)); FinalizeICCTag(&tags, &tag_offset, &tag_size); AddToICCTagTable("bXYZ", tag_offset, tag_size, &tagtable, &offsets); } - if (c.color_space == ColorSpace::kXYB) { + if (c.color_space == JXL_COLOR_SPACE_XYB) { JXL_RETURN_IF_ERROR(CreateICCLutAtoBTagForXYB(&tags)); FinalizeICCTag(&tags, &tag_offset, &tag_size); AddToICCTagTable("A2B0", tag_offset, tag_size, &tagtable, &offsets); JXL_RETURN_IF_ERROR(CreateICCNoOpBToATag(&tags)); FinalizeICCTag(&tags, &tag_offset, &tag_size); @@ -860,50 +996,49 @@ AddToICCTagTable("A2B0", tag_offset, tag_size, &tagtable, &offsets); JXL_RETURN_IF_ERROR(CreateICCNoOpBToATag(&tags)); FinalizeICCTag(&tags, &tag_offset, &tag_size); AddToICCTagTable("B2A0", tag_offset, tag_size, &tagtable, &offsets); } else { - if (c.tf.have_gamma) { - float gamma = 1.0 / c.tf.GetGamma(); + if (tf == JXL_TRANSFER_FUNCTION_GAMMA) { + float gamma = 1.0 / c.gamma; JXL_RETURN_IF_ERROR(CreateICCCurvParaTag({gamma}, 0, &tags)); - } else if (c.color_space != ColorSpace::kXYB) { - switch (c.tf.transfer_function) { - case TransferFunction::kHLG: + } else if (c.color_space != JXL_COLOR_SPACE_XYB) { + switch (tf) { + case JXL_TRANSFER_FUNCTION_HLG: CreateICCCurvCurvTag(HWY_DYNAMIC_DISPATCH(CreateTableCurve)( 64, ExtraTF::kHLG, CanToneMap(c)), &tags); break; - case TransferFunction::kPQ: + case JXL_TRANSFER_FUNCTION_PQ: CreateICCCurvCurvTag(HWY_DYNAMIC_DISPATCH(CreateTableCurve)( 64, ExtraTF::kPQ, CanToneMap(c)), &tags); break; - case TransferFunction::kSRGB: + case JXL_TRANSFER_FUNCTION_SRGB: JXL_RETURN_IF_ERROR(CreateICCCurvParaTag( {2.4, 1.0 / 1.055, 0.055 / 1.055, 1.0 / 12.92, 0.04045}, 3, &tags)); break; - case TransferFunction::k709: + case JXL_TRANSFER_FUNCTION_709: JXL_RETURN_IF_ERROR(CreateICCCurvParaTag( {1.0 / 0.45, 1.0 / 1.099, 0.099 / 1.099, 1.0 / 4.5, 0.081}, 3, &tags)); break; - case TransferFunction::kLinear: + case JXL_TRANSFER_FUNCTION_LINEAR: JXL_RETURN_IF_ERROR( CreateICCCurvParaTag({1.0, 1.0, 0.0, 1.0, 0.0}, 3, &tags)); break; - case TransferFunction::kDCI: + case JXL_TRANSFER_FUNCTION_DCI: JXL_RETURN_IF_ERROR( CreateICCCurvParaTag({2.6, 1.0, 0.0, 1.0, 0.0}, 3, &tags)); break; default: - JXL_UNREACHABLE("Unknown TF %u", - static_cast(c.tf.transfer_function)); + JXL_UNREACHABLE("Unknown TF %u", static_cast(tf)); } } FinalizeICCTag(&tags, &tag_offset, &tag_size); - if (c.color_space == ColorSpace::kGray) { + if (c.color_space == JXL_COLOR_SPACE_GRAY) { AddToICCTagTable("kTRC", tag_offset, tag_size, &tagtable, &offsets); } else { AddToICCTagTable("rTRC", tag_offset, tag_size, &tagtable, &offsets); AddToICCTagTable("gTRC", tag_offset, tag_size, &tagtable, &offsets); AddToICCTagTable("bTRC", tag_offset, tag_size, &tagtable, &offsets); @@ -919,18 +1054,18 @@ // ICC profile size WriteICCUint32(header.size() + tagtable.size() + tags.size(), 0, &header); *icc = header; - Span(tagtable).AppendTo(icc); - Span(tags).AppendTo(icc); + Bytes(tagtable).AppendTo(icc); + Bytes(tags).AppendTo(icc); // 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. - IccBytes icc_sum = *icc; + std::vector icc_sum = *icc; if (icc_sum.size() >= 64 + 4) { memset(icc_sum.data() + 44, 0, 4); memset(icc_sum.data() + 64, 0, 4); } uint8_t checksum[16]; diff --git a/third_party/jpeg-xl/lib/jxl/cms/jxl_cms.h b/third_party/jpeg-xl/lib/jxl/cms/jxl_cms.h --- a/third_party/jpeg-xl/lib/jxl/cms/jxl_cms.h +++ b/third_party/jpeg-xl/lib/jxl/cms/jxl_cms.h @@ -7,16 +7,37 @@ #define LIB_JXL_JXL_CMS_H_ // ICC profiles and color space conversions. #include +#include +#include +#include +#include +#include #ifdef __cplusplus extern "C" { #endif -const JxlCmsInterface* JxlGetDefaultCms(); +JXL_CMS_EXPORT const JxlCmsInterface* JxlGetDefaultCms(); +JXL_CMS_EXPORT JXL_BOOL JxlCmsCreateProfile(const JxlColorEncoding* c, + uint8_t** out, size_t* out_size); +JXL_CMS_EXPORT JXL_BOOL JxlCmsCIEXYZFromWhiteCIExy(double wx, double wy, + float XYZ[3]); +JXL_CMS_EXPORT JXL_BOOL JxlCmsAdaptToXYZD50(float wx, float wy, + float matrix[9]); +JXL_CMS_EXPORT JXL_BOOL JxlCmsPrimariesToXYZ(float rx, float ry, float gx, + float gy, float bx, float by, + float wx, float wy, + float matrix[9]); +JXL_CMS_EXPORT JXL_BOOL JxlCmsPrimariesToXYZD50(float rx, float ry, float gx, + float gy, float bx, float by, + float wx, float wy, + float matrix[9]); +JXL_CMS_EXPORT size_t JxlCmsColorEncodingDescription(const JxlColorEncoding* c, + char out[320]); #ifdef __cplusplus } #endif diff --git a/third_party/jpeg-xl/lib/jxl/cms/jxl_cms.cc b/third_party/jpeg-xl/lib/jxl/cms/jxl_cms.cc --- a/third_party/jpeg-xl/lib/jxl/cms/jxl_cms.cc +++ b/third_party/jpeg-xl/lib/jxl/cms/jxl_cms.cc @@ -27,11 +27,11 @@ #include "lib/jxl/base/compiler_specific.h" #include "lib/jxl/base/matrix_ops.h" #include "lib/jxl/base/printf_macros.h" #include "lib/jxl/base/span.h" #include "lib/jxl/base/status.h" -#include "lib/jxl/cms/color_management.h" // MaybeCreateProfile CIEXYZFromWhiteCIExy ExtraTF +#include "lib/jxl/cms/color_management.h" #include "lib/jxl/cms/transfer_functions-inl.h" #include "lib/jxl/color_encoding_internal.h" #if JPEGXL_ENABLE_SKCMS #include "lib/jxl/cms/jxl_skcms.h" #else // JPEGXL_ENABLE_SKCMS @@ -457,11 +457,12 @@ {{0.9869929, -0.1470543, 0.1599627}, {0.4323053, 0.5183603, 0.0492912}, {-0.0085287, 0.0400428, 0.9684867}}}; static constexpr float kWpD50XYZ[3] = {0.96420288, 1.0, 0.82490540}; float wp_unadapted_XYZ[3]; - JXL_RETURN_IF_ERROR(CIEXYZFromWhiteCIExy(wp_unadapted, wp_unadapted_XYZ)); + JXL_RETURN_IF_ERROR( + CIEXYZFromWhiteCIExy(wp_unadapted.x, wp_unadapted.y, wp_unadapted_XYZ)); float wp_D50_LMS[3], wp_unadapted_LMS[3]; MatrixProduct(kLMSFromXYZ, kWpD50XYZ, wp_D50_LMS); MatrixProduct(kLMSFromXYZ, wp_unadapted_XYZ, wp_unadapted_LMS); inverse_CHAD = {{{wp_unadapted_LMS[0] / wp_D50_LMS[0], 0, 0}, {0, wp_unadapted_LMS[1] / wp_D50_LMS[1], 0}, @@ -516,11 +517,11 @@ if (gamma[0] != 0 && std::abs(gamma[0] - gamma[1]) < 1e-4f && std::abs(gamma[1] - gamma[2]) < 1e-4f) { if (c->tf.SetGamma(gamma[0])) { skcms_ICCProfile profile_test; IccBytes bytes; - if (MaybeCreateProfile(*c, &bytes) && + if (MaybeCreateProfile(c->ToExternal(), &bytes) && DecodeProfile(bytes.data(), bytes.size(), &profile_test) && skcms_ApproximatelyEqualProfiles(&profile, &profile_test)) { return; } } @@ -532,11 +533,11 @@ c->tf.SetTransferFunction(tf); skcms_ICCProfile profile_test; IccBytes bytes; - if (MaybeCreateProfile(*c, &bytes) && + if (MaybeCreateProfile(c->ToExternal(), &bytes) && DecodeProfile(bytes.data(), bytes.size(), &profile_test) && skcms_ApproximatelyEqualProfiles(&profile, &profile_test)) { return; } } @@ -573,12 +574,11 @@ Status ProfileEquivalentToICC(const cmsContext context, const Profile& profile1, const IccBytes& icc, const ColorEncoding& c) { const uint32_t type_src = Type64(c); Profile profile2; - JXL_RETURN_IF_ERROR( - DecodeProfile(context, Span(icc), &profile2)); + JXL_RETURN_IF_ERROR(DecodeProfile(context, Bytes(icc), &profile2)); Profile profile_xyz; JXL_RETURN_IF_ERROR(CreateProfileXYZ(context, &profile_xyz)); const uint32_t intent = INTENT_RELATIVE_COLORIMETRIC; @@ -756,11 +756,11 @@ } } if (gamma != 0 && c->tf.SetGamma(gamma)) { IccBytes icc_test; - if (MaybeCreateProfile(*c, &icc_test) && + if (MaybeCreateProfile(c->ToExternal(), &icc_test) && ProfileEquivalentToICC(context, profile, icc_test, *c)) { return; } } @@ -769,11 +769,11 @@ if (tf == TransferFunction::kUnknown) continue; c->tf.SetTransferFunction(tf); IccBytes icc_test; - if (MaybeCreateProfile(*c, &icc_test) && + if (MaybeCreateProfile(c->ToExternal(), &icc_test) && ProfileEquivalentToICC(context, profile, icc_test, *c)) { return; } } @@ -823,12 +823,12 @@ // // From there, by multiplying each total by its corresponding y, we get Y for // that primary. float white_XYZ[3]; - JXL_RETURN_IF_ERROR( - CIEXYZFromWhiteCIExy(encoding.GetWhitePoint(), white_XYZ)); + CIExy wp = encoding.GetWhitePoint(); + JXL_RETURN_IF_ERROR(CIEXYZFromWhiteCIExy(wp.x, wp.y, white_XYZ)); const PrimariesCIExy primaries = encoding.GetPrimaries(); double chromaticities[3][3] = { {primaries.r.x, primaries.g.x, primaries.b.x}, {primaries.r.y, primaries.g.y, primaries.b.y}, @@ -978,11 +978,11 @@ if (profile.has_CICP && ApplyCICP(profile.CICP.color_primaries, profile.CICP.transfer_characteristics, profile.CICP.matrix_coefficients, profile.CICP.video_full_range_flag, &c_enc)) { - c_enc.ToExternal(c); + *c = c_enc.ToExternal(); return true; } c_enc.color_space = ColorSpaceFromProfile(profile); *cmyk = (profile.data_color_space == skcms_Signature_CMYK); @@ -999,12 +999,12 @@ #else // JPEGXL_ENABLE_SKCMS const cmsContext context = GetContext(); Profile profile; - JXL_RETURN_IF_ERROR(DecodeProfile( - context, Span(icc_data, icc_size), &profile)); + JXL_RETURN_IF_ERROR( + DecodeProfile(context, Bytes(icc_data, icc_size), &profile)); const cmsUInt32Number rendering_intent32 = cmsGetHeaderRenderingIntent(profile.get()); if (rendering_intent32 > 3) { return JXL_FAILURE("Invalid rendering intent %u\n", rendering_intent32); @@ -1018,18 +1018,18 @@ uint8_t cicp_buffer[kCICPSize]; if (cmsReadRawTag(profile.get(), kCICPSignature, cicp_buffer, kCICPSize) == kCICPSize && ApplyCICP(cicp_buffer[8], cicp_buffer[9], cicp_buffer[10], cicp_buffer[11], &c_enc)) { - c_enc.ToExternal(c); + *c = c_enc.ToExternal(); return true; } c_enc.color_space = ColorSpaceFromProfile(profile); if (cmsGetColorSpace(profile.get()) == cmsSigCmykData) { *cmyk = JXL_TRUE; - c_enc.ToExternal(c); + *c = c_enc.ToExternal(); return true; } const cmsCIEXYZ wp_unadapted = UnadaptedWhitePoint(context, profile, c_enc); JXL_RETURN_IF_ERROR(c_enc.SetWhitePoint(CIExyFromXYZ(wp_unadapted))); @@ -1041,11 +1041,11 @@ // Relies on color_space/white point/primaries being set already. DetectTransferFunction(context, profile, &c_enc); #endif // JPEGXL_ENABLE_SKCMS - c_enc.ToExternal(c); + *c = c_enc.ToExternal(); return true; } } // namespace @@ -1116,15 +1116,15 @@ return nullptr; } #else // JPEGXL_ENABLE_SKCMS const cmsContext context = GetContext(); Profile profile_src, profile_dst; - if (!DecodeProfile(context, Span(c_src.icc), &profile_src)) { + if (!DecodeProfile(context, Bytes(c_src.icc), &profile_src)) { JXL_NOTIFY_ERROR("JxlCmsInit: lcms failed to parse input ICC"); return nullptr; } - if (!DecodeProfile(context, Span(c_dst.icc), &profile_dst)) { + if (!DecodeProfile(context, Bytes(c_dst.icc), &profile_dst)) { JXL_NOTIFY_ERROR("JxlCmsInit: lcms failed to parse output ICC"); return nullptr; } #endif // JPEGXL_ENABLE_SKCMS @@ -1162,15 +1162,15 @@ skcms_ICCProfile new_src; #else // JPEGXL_ENABLE_SKCMS Profile new_src; #endif // JPEGXL_ENABLE_SKCMS // Only enable ExtraTF if profile creation succeeded. - if (MaybeCreateProfile(c_linear_src, &icc_src) && + if (MaybeCreateProfile(c_linear_src.ToExternal(), &icc_src) && #if JPEGXL_ENABLE_SKCMS DecodeProfile(icc_src.data(), icc_src.size(), &new_src)) { #else // JPEGXL_ENABLE_SKCMS - DecodeProfile(context, Span(icc_src), &new_src)) { + DecodeProfile(context, Bytes(icc_src), &new_src)) { #endif // JPEGXL_ENABLE_SKCMS #if JXL_CMS_VERBOSE printf("Special HLG/PQ/sRGB -> linear\n"); #endif #if JPEGXL_ENABLE_SKCMS @@ -1203,15 +1203,15 @@ skcms_ICCProfile new_dst; #else // JPEGXL_ENABLE_SKCMS Profile new_dst; #endif // JPEGXL_ENABLE_SKCMS // Only enable ExtraTF if profile creation succeeded. - if (MaybeCreateProfile(c_linear_dst, &icc_dst) && + if (MaybeCreateProfile(c_linear_dst.ToExternal(), &icc_dst) && #if JPEGXL_ENABLE_SKCMS DecodeProfile(icc_dst.data(), icc_dst.size(), &new_dst)) { #else // JPEGXL_ENABLE_SKCMS - DecodeProfile(context, Span(icc_dst), &new_dst)) { + DecodeProfile(context, Bytes(icc_dst), &new_dst)) { #endif // JPEGXL_ENABLE_SKCMS #if JXL_CMS_VERBOSE printf("Special linear -> HLG/PQ/sRGB\n"); #endif #if JPEGXL_ENABLE_SKCMS @@ -1244,11 +1244,11 @@ #if JPEGXL_ENABLE_SKCMS if (!skcms_MakeUsableAsDestination(&t->profile_dst)) { JXL_NOTIFY_ERROR( "Failed to make %s usable as a color transform destination", - Description(c_dst).c_str()); + ColorEncodingDescription(c_dst.ToExternal()).c_str()); return nullptr; } #endif // JPEGXL_ENABLE_SKCMS // Not including alpha channel (copied separately). @@ -1315,11 +1315,13 @@ return t->buf_dst[thread]; } } // namespace -extern "C" const JxlCmsInterface* JxlGetDefaultCms() { +extern "C" { + +const JxlCmsInterface* JxlGetDefaultCms() { static constexpr JxlCmsInterface kInterface = { /*set_fields_data=*/nullptr, /*set_fields_from_icc=*/&JxlCmsSetFieldsFromICC, /*init_data=*/const_cast(static_cast(&kInterface)), /*init=*/&JxlCmsInit, @@ -1328,7 +1330,58 @@ /*run=*/&DoColorSpaceTransform, /*destroy=*/&JxlCmsDestroy}; return &kInterface; } +JXL_BOOL JxlCmsCIEXYZFromWhiteCIExy(double wx, double wy, float XYZ[3]) { + return TO_JXL_BOOL(CIEXYZFromWhiteCIExy(wx, wy, XYZ)); +} + +JXL_BOOL JxlCmsPrimariesToXYZ(float rx, float ry, float gx, float gy, float bx, + float by, float wx, float wy, float matrix[9]) { + return TO_JXL_BOOL(PrimariesToXYZ(rx, ry, gx, gy, bx, by, wx, wy, matrix)); +} + +// Adapts whitepoint x, y to D50 +JXL_BOOL JxlCmsAdaptToXYZD50(float wx, float wy, float matrix[9]) { + return TO_JXL_BOOL(AdaptToXYZD50(wx, wy, matrix)); +} + +JXL_BOOL JxlCmsPrimariesToXYZD50(float rx, float ry, float gx, float gy, + float bx, float by, float wx, float wy, + float matrix[9]) { + return TO_JXL_BOOL(PrimariesToXYZD50(rx, ry, gx, gy, bx, by, wx, wy, matrix)); +} + +// Returns a representation of the ColorEncoding fields (not icc). +// Example description: "RGB_D65_SRG_Rel_Lin" +size_t JxlCmsColorEncodingDescription(const JxlColorEncoding* c, + char out[320]) { + out[0] = 0; + std::string d = ColorEncodingDescription(*c); + size_t len = d.size(); + if (len >= 320) { // Impossible + return 0; + } + memcpy(out, d.c_str(), len + 1); + return len; +} + +JXL_BOOL JxlCmsCreateProfile(const JxlColorEncoding* c, uint8_t** out, + size_t* out_size) { + std::vector icc; + *out = nullptr; + *out_size = 0; + if (!MaybeCreateProfile(*c, &icc)) { + return JXL_FALSE; + } + *out = reinterpret_cast(malloc(icc.size())); + if (!*out) return JXL_FALSE; + memcpy(*out, icc.data(), icc.size()); + *out_size = icc.size(); + return JXL_TRUE; +} + +} // extern "C" + } // namespace jxl #endif // HWY_ONCE diff --git a/third_party/jpeg-xl/lib/jxl/coeff_order.cc b/third_party/jpeg-xl/lib/jxl/coeff_order.cc --- a/third_party/jpeg-xl/lib/jxl/coeff_order.cc +++ b/third_party/jpeg-xl/lib/jxl/coeff_order.cc @@ -9,11 +9,10 @@ #include #include #include "lib/jxl/ans_params.h" -#include "lib/jxl/base/padded_bytes.h" #include "lib/jxl/base/span.h" #include "lib/jxl/coeff_order_fwd.h" #include "lib/jxl/dec_ans.h" #include "lib/jxl/dec_bit_reader.h" #include "lib/jxl/entropy_coder.h" diff --git a/third_party/jpeg-xl/lib/jxl/color_encoding_internal.h b/third_party/jpeg-xl/lib/jxl/color_encoding_internal.h --- a/third_party/jpeg-xl/lib/jxl/color_encoding_internal.h +++ b/third_party/jpeg-xl/lib/jxl/color_encoding_internal.h @@ -12,17 +12,19 @@ #include #include #include #include +#include // free #include #include #include #include "lib/jxl/base/compiler_specific.h" #include "lib/jxl/base/status.h" #include "lib/jxl/cms/color_encoding_cms.h" +#include "lib/jxl/cms/jxl_cms.h" #include "lib/jxl/field_encodings.h" namespace jxl { using IccBytes = ::jxl::cms::IccBytes; @@ -124,11 +126,22 @@ static const ColorEncoding& SRGB(bool is_gray = false); static const ColorEncoding& LinearSRGB(bool is_gray = false); // Returns true if an ICC profile was successfully created from fields. // Must be called after modifying fields. Defined in color_management.cc. - Status CreateICC() { return storage_.CreateICC(); } + Status CreateICC() { + storage_.icc.clear(); + uint8_t* new_icc_ptr; + size_t new_icc_size; + const JxlColorEncoding external = ToExternal(); + if (!JxlCmsCreateProfile(&external, &new_icc_ptr, &new_icc_size)) { + return JXL_FAILURE("Failed to create ICC profile"); + } + storage_.icc.assign(new_icc_ptr, new_icc_ptr + new_icc_size); + free(new_icc_ptr); + return true; + } // Returns non-empty and valid ICC profile, unless: // - WantICC() == true and SetICC() was not yet called; // - after a failed call to SetSRGB(), SetICC(), or CreateICC(). const IccBytes& ICC() const { return storage_.icc; } @@ -247,15 +260,15 @@ return storage_.SameColorEncoding(other.storage_); } mutable bool all_default; - void ToExternal(JxlColorEncoding* external) const { - storage_.ToExternal(external); - } + JxlColorEncoding ToExternal() const { return storage_.ToExternal(); } Status FromExternal(const JxlColorEncoding& external) { - return storage_.FromExternal(external); + JXL_RETURN_IF_ERROR(storage_.FromExternal(external)); + (void)CreateICC(); + return true; } const jxl::cms::ColorEncoding& View() const { return storage_; } std::string Description() const; private: @@ -278,11 +291,14 @@ Customxy green_; Customxy blue_; }; static inline std::string Description(const ColorEncoding& c) { - return Description(c.View()); + char data[320]; + const JxlColorEncoding external = c.View().ToExternal(); + JxlCmsColorEncodingDescription(&external, data); + return data; } static inline std::ostream& operator<<(std::ostream& os, const ColorEncoding& c) { return os << Description(c); } @@ -305,17 +321,17 @@ xsize_ = xsize; JxlColorProfile input_profile; icc_src_ = c_src.ICC(); input_profile.icc.data = icc_src_.data(); input_profile.icc.size = icc_src_.size(); - c_src.ToExternal(&input_profile.color_encoding); + input_profile.color_encoding = c_src.ToExternal(); input_profile.num_channels = c_src.IsCMYK() ? 4 : c_src.Channels(); JxlColorProfile output_profile; icc_dst_ = c_dst.ICC(); output_profile.icc.data = icc_dst_.data(); output_profile.icc.size = icc_dst_.size(); - c_dst.ToExternal(&output_profile.color_encoding); + output_profile.color_encoding = c_dst.ToExternal(); if (c_dst.IsCMYK()) return JXL_FAILURE("Conversion to CMYK is not supported"); output_profile.num_channels = c_dst.Channels(); cms_data_ = cms_.init(cms_.init_data, num_threads, xsize, &input_profile, &output_profile, intensity_target); diff --git a/third_party/jpeg-xl/lib/jxl/color_encoding_internal.cc b/third_party/jpeg-xl/lib/jxl/color_encoding_internal.cc --- a/third_party/jpeg-xl/lib/jxl/color_encoding_internal.cc +++ b/third_party/jpeg-xl/lib/jxl/color_encoding_internal.cc @@ -7,11 +7,11 @@ #include #include "lib/jxl/base/common.h" #include "lib/jxl/cms/color_encoding_cms.h" -#include "lib/jxl/cms/color_management.h" // MaybeCreateProfile +#include "lib/jxl/cms/jxl_cms.h" #include "lib/jxl/fields.h" #include "lib/jxl/pack_signed.h" namespace jxl { @@ -77,12 +77,15 @@ storage_.icc.size(), &c, &cmyk)) { return; } if (cmyk) return; - IccBytes new_icc; - if (!MaybeCreateProfile(storage_, &new_icc)) return; + uint8_t* new_icc_ptr; + size_t new_icc_size; + if (!JxlCmsCreateProfile(&c, &new_icc_ptr, &new_icc_size)) return; + IccBytes new_icc(new_icc_ptr, new_icc_ptr + new_icc_size); + free(new_icc_ptr); want_icc_ = false; } Customxy::Customxy() { Bundle::Init(this); } diff --git a/third_party/jpeg-xl/lib/jxl/color_encoding_internal_test.cc b/third_party/jpeg-xl/lib/jxl/color_encoding_internal_test.cc --- a/third_party/jpeg-xl/lib/jxl/color_encoding_internal_test.cc +++ b/third_party/jpeg-xl/lib/jxl/color_encoding_internal_test.cc @@ -94,11 +94,10 @@ EXPECT_TRUE(c2.tf.have_gamma); } TEST(ColorEncodingTest, InternalExternalConversion) { ColorEncoding source_internal; - JxlColorEncoding external = {}; ColorEncoding destination_internal; for (int i = 0; i < 100; i++) { source_internal.color_space = static_cast(rand() % 4); CIExy wp; @@ -118,11 +117,11 @@ jxl::cms::CustomTransferFunction tf; EXPECT_TRUE(tf.SetGamma((float(rand()) / float((RAND_MAX)) * 0.5) + 0.25)); source_internal.tf = tf; source_internal.rendering_intent = static_cast(rand() % 4); - source_internal.ToExternal(&external); + JxlColorEncoding external = source_internal.ToExternal(); EXPECT_TRUE(destination_internal.FromExternal(external)); EXPECT_EQ(source_internal.color_space, destination_internal.color_space); EXPECT_EQ(source_internal.white_point, destination_internal.white_point); CIExy src_wp = source_internal.GetWhitePoint(); diff --git a/third_party/jpeg-xl/lib/jxl/color_management_test.cc b/third_party/jpeg-xl/lib/jxl/color_management_test.cc --- a/third_party/jpeg-xl/lib/jxl/color_management_test.cc +++ b/third_party/jpeg-xl/lib/jxl/color_management_test.cc @@ -5,18 +5,18 @@ #include #include #include +#include #include #include #include #include "lib/jxl/base/common.h" #include "lib/jxl/base/compiler_specific.h" #include "lib/jxl/base/data_parallel.h" -#include "lib/jxl/base/padded_bytes.h" #include "lib/jxl/base/random.h" #include "lib/jxl/base/span.h" #include "lib/jxl/cms/color_encoding_cms.h" #include "lib/jxl/cms/jxl_cms.h" #include "lib/jxl/cms/opsin_params.h" @@ -230,14 +230,14 @@ PrimariesAre(CIExyIs(0.64, 0.33), CIExyIs(0.30, 0.60), CIExyIs(0.15, 0.06))); } TEST_F(ColorManagementTest, D2700Chromaticity) { - PaddedBytes icc_data = + std::vector icc_data = jxl::test::ReadTestData("jxl/color_management/sRGB-D2700.icc"); IccBytes icc; - Span(icc_data).AppendTo(&icc); + Bytes(icc_data).AppendTo(&icc); ColorEncoding sRGB_D2700; ASSERT_TRUE(sRGB_D2700.SetICC(std::move(icc), JxlGetDefaultCms())); EXPECT_THAT(sRGB_D2700.GetWhitePoint(), CIExyIs(0.45986, 0.41060)); // The illuminant-relative chromaticities of this profile's primaries are the @@ -247,14 +247,14 @@ PrimariesAre(CIExyIs(0.64, 0.33), CIExyIs(0.30, 0.60), CIExyIs(0.15, 0.06))); } TEST_F(ColorManagementTest, D2700ToSRGB) { - PaddedBytes icc_data = + std::vector icc_data = jxl::test::ReadTestData("jxl/color_management/sRGB-D2700.icc"); IccBytes icc; - Span(icc_data).AppendTo(&icc); + Bytes(icc_data).AppendTo(&icc); ColorEncoding sRGB_D2700; ASSERT_TRUE(sRGB_D2700.SetICC(std::move(icc), JxlGetDefaultCms())); ColorSpaceTransform transform(*JxlGetDefaultCms()); ASSERT_TRUE(transform.Init(sRGB_D2700, ColorEncoding::SRGB(), diff --git a/third_party/jpeg-xl/lib/jxl/compressed_dc.cc b/third_party/jpeg-xl/lib/jxl/compressed_dc.cc --- a/third_party/jpeg-xl/lib/jxl/compressed_dc.cc +++ b/third_party/jpeg-xl/lib/jxl/compressed_dc.cc @@ -24,11 +24,10 @@ #include "lib/jxl/ac_strategy.h" #include "lib/jxl/ans_params.h" #include "lib/jxl/base/bits.h" #include "lib/jxl/base/compiler_specific.h" #include "lib/jxl/base/data_parallel.h" -#include "lib/jxl/base/padded_bytes.h" #include "lib/jxl/base/status.h" #include "lib/jxl/chroma_from_luma.h" #include "lib/jxl/dec_ans.h" #include "lib/jxl/dec_bit_reader.h" #include "lib/jxl/dec_cache.h" 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 @@ -17,12 +17,12 @@ #include "lib/jxl/ans_common.h" #include "lib/jxl/ans_params.h" #include "lib/jxl/base/bits.h" #include "lib/jxl/base/byte_order.h" -#include "lib/jxl/base/cache_aligned.h" #include "lib/jxl/base/compiler_specific.h" +#include "lib/jxl/cache_aligned.h" #include "lib/jxl/dec_bit_reader.h" #include "lib/jxl/dec_huffman.h" #include "lib/jxl/field_encodings.h" namespace jxl { diff --git a/third_party/jpeg-xl/lib/jxl/dec_bit_reader.h b/third_party/jpeg-xl/lib/jxl/dec_bit_reader.h --- a/third_party/jpeg-xl/lib/jxl/dec_bit_reader.h +++ b/third_party/jpeg-xl/lib/jxl/dec_bit_reader.h @@ -225,11 +225,11 @@ Span GetSpan() const { JXL_DASSERT(first_byte_ != nullptr); JXL_ASSERT(TotalBitsConsumed() % kBitsPerByte == 0); const size_t offset = TotalBitsConsumed() / kBitsPerByte; // no remainder JXL_ASSERT(offset <= TotalBytes()); - return Span(first_byte_ + offset, TotalBytes() - offset); + return Bytes(first_byte_ + offset, TotalBytes() - offset); } // Returns whether all the bits read so far have been within the input bounds. // When reading past the EOF, the Read*() and Consume() functions return zeros // but flag a failure when calling Close() without checking this function. 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 @@ -14,10 +14,11 @@ #include "lib/jxl/ac_strategy.h" #include "lib/jxl/base/common.h" // kMaxNumPasses #include "lib/jxl/coeff_order.h" #include "lib/jxl/convolve.h" +#include "lib/jxl/dec_ans.h" #include "lib/jxl/dec_group_border.h" #include "lib/jxl/dec_noise.h" #include "lib/jxl/image.h" #include "lib/jxl/passes_state.h" #include "lib/jxl/quant_weights.h" 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 @@ -7,10 +7,11 @@ #include "lib/jxl/blending.h" #include "lib/jxl/common.h" // JXL_HIGH_PRECISION #include "lib/jxl/render_pipeline/stage_blending.h" #include "lib/jxl/render_pipeline/stage_chroma_upsampling.h" +#include "lib/jxl/render_pipeline/stage_cms.h" #include "lib/jxl/render_pipeline/stage_epf.h" #include "lib/jxl/render_pipeline/stage_from_linear.h" #include "lib/jxl/render_pipeline/stage_gaborish.h" #include "lib/jxl/render_pipeline/stage_noise.h" #include "lib/jxl/render_pipeline/stage_patches.h" @@ -198,22 +199,54 @@ auto tone_mapping_stage = GetToneMappingStage(output_encoding_info); if (tone_mapping_stage) { if (!linear) { auto to_linear_stage = GetToLinearStage(output_encoding_info); if (!to_linear_stage) { - return JXL_FAILURE( - "attempting to perform tone mapping on colorspace not " - "convertible to linear"); + if (!output_encoding_info.cms_set) { + return JXL_FAILURE("Cannot tonemap this colorspace without a CMS"); + } + auto cms_stage = GetCmsStage(output_encoding_info); + if (cms_stage) { + builder.AddStage(std::move(cms_stage)); + } + } else { + builder.AddStage(std::move(to_linear_stage)); } - builder.AddStage(std::move(to_linear_stage)); linear = true; } builder.AddStage(std::move(tone_mapping_stage)); } if (linear) { - builder.AddStage(GetFromLinearStage(output_encoding_info)); + const size_t channels_src = + (output_encoding_info.orig_color_encoding.IsCMYK() + ? 4 + : output_encoding_info.orig_color_encoding.Channels()); + const size_t channels_dst = + output_encoding_info.color_encoding.Channels(); + bool mixing_color_and_grey = (channels_dst != channels_src); + if ((output_encoding_info.color_encoding_is_original) || + (!output_encoding_info.cms_set) || mixing_color_and_grey) { + // in those cases we only need a linear stage in other cases we attempt + // to obtain an cms stage: the cases are + // - output_encoding_info.color_encoding_is_original: no cms stage + // needed because it would be a no-op + // - !output_encoding_info.cms_set: can't use the cms, so no point in + // trying to add a cms stage + // - mixing_color_and_grey: cms stage can't handle that + // TODO(firsching): remove "mixing_color_and_grey" condition after + // adding support for greyscale to cms stage. + builder.AddStage(GetFromLinearStage(output_encoding_info)); + } else { + if (!output_encoding_info.linear_color_encoding.CreateICC()) { + return JXL_FAILURE("Failed to create ICC"); + } + auto cms_stage = GetCmsStage(output_encoding_info); + if (cms_stage) { + builder.AddStage(std::move(cms_stage)); + } + } linear = false; } if (main_output.callback.IsPresent() || main_output.buffer) { builder.AddStage(GetWriteToOutputStage(main_output, width, height, diff --git a/third_party/jpeg-xl/lib/jxl/dec_context_map.cc b/third_party/jpeg-xl/lib/jxl/dec_context_map.cc --- a/third_party/jpeg-xl/lib/jxl/dec_context_map.cc +++ b/third_party/jpeg-xl/lib/jxl/dec_context_map.cc @@ -52,24 +52,24 @@ std::fill(context_map->begin(), context_map->end(), 0); } } else { bool use_mtf = input->ReadFixedBits<1>(); ANSCode code; - std::vector dummy_ctx_map; + std::vector sink_ctx_map; // Usage of LZ77 is disallowed if decoding only two symbols. This doesn't // make sense in non-malicious bitstreams, and could cause a stack overflow // in malicious bitstreams by making every context map require its own // context map. JXL_RETURN_IF_ERROR( - DecodeHistograms(input, 1, &code, &dummy_ctx_map, + DecodeHistograms(input, 1, &code, &sink_ctx_map, /*disallow_lz77=*/context_map->size() <= 2)); ANSSymbolReader reader(&code, input); size_t i = 0; uint32_t maxsym = 0; while (i < context_map->size()) { uint32_t sym = reader.ReadHybridUintInlined( - 0, input, dummy_ctx_map); + 0, input, sink_ctx_map); maxsym = sym > maxsym ? sym : maxsym; (*context_map)[i] = sym; i++; } if (maxsym >= kMaxClusters) { diff --git a/third_party/jpeg-xl/lib/jxl/dec_external_image.cc b/third_party/jpeg-xl/lib/jxl/dec_external_image.cc --- a/third_party/jpeg-xl/lib/jxl/dec_external_image.cc +++ b/third_party/jpeg-xl/lib/jxl/dec_external_image.cc @@ -19,11 +19,10 @@ #include #include #include "lib/jxl/alpha.h" #include "lib/jxl/base/byte_order.h" -#include "lib/jxl/base/cache_aligned.h" #include "lib/jxl/base/common.h" #include "lib/jxl/base/compiler_specific.h" #include "lib/jxl/base/printf_macros.h" #include "lib/jxl/sanitizers.h" 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 @@ -80,11 +80,11 @@ ImageBundle* decoded, const CodecMetadata& metadata, bool use_slow_rendering_pipeline) { FrameDecoder frame_decoder(dec_state, metadata, pool, use_slow_rendering_pipeline); - BitReader reader(Span(next_in, avail_in)); + BitReader reader(Bytes(next_in, avail_in)); JXL_RETURN_IF_ERROR(frame_decoder.InitFrame(&reader, decoded, /*is_preview=*/false)); JXL_RETURN_IF_ERROR(frame_decoder.InitFrameOutput()); JXL_RETURN_IF_ERROR(reader.AllReadsWithinBounds()); @@ -100,12 +100,11 @@ std::vector section_status; size_t pos = header_bytes; size_t index = 0; for (auto toc_entry : frame_decoder.Toc()) { JXL_RETURN_IF_ERROR(pos + toc_entry.size <= avail_in); - auto br = make_unique( - Span(next_in + pos, toc_entry.size)); + auto br = make_unique(Bytes(next_in + pos, toc_entry.size)); section_info.emplace_back( FrameDecoder::SectionInfo{br.get(), toc_entry.id, index++}); section_closers.emplace_back( make_unique(br.get(), &close_ok)); section_readers.emplace_back(std::move(br)); @@ -158,13 +157,12 @@ } else { ++dec_state_->nonvisible_frame_index; } // Read TOC. - const bool has_ac_global = true; - const size_t toc_entries = NumTocEntries(num_groups, frame_dim_.num_dc_groups, - num_passes, has_ac_global); + const size_t toc_entries = + NumTocEntries(num_groups, frame_dim_.num_dc_groups, num_passes); std::vector sizes; std::vector permutation; JXL_RETURN_IF_ERROR(ReadToc(toc_entries, br, &sizes, &permutation)); bool have_permutation = !permutation.empty(); toc_.resize(toc_entries); 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 @@ -4,26 +4,21 @@ // license that can be found in the LICENSE file. #ifndef LIB_JXL_DEC_GROUP_H_ #define LIB_JXL_DEC_GROUP_H_ -#include -#include - +#include +#include #include #include "lib/jxl/base/compiler_specific.h" #include "lib/jxl/base/status.h" -#include "lib/jxl/chroma_from_luma.h" -#include "lib/jxl/coeff_order_fwd.h" #include "lib/jxl/dct_util.h" -#include "lib/jxl/dec_ans.h" #include "lib/jxl/dec_bit_reader.h" #include "lib/jxl/dec_cache.h" -#include "lib/jxl/frame_header.h" -#include "lib/jxl/image.h" -#include "lib/jxl/quantizer.h" +#include "lib/jxl/image_bundle.h" +#include "lib/jxl/render_pipeline/render_pipeline.h" namespace jxl { struct AuxOut; diff --git a/third_party/jpeg-xl/lib/jxl/dec_xyb.h b/third_party/jpeg-xl/lib/jxl/dec_xyb.h --- a/third_party/jpeg-xl/lib/jxl/dec_xyb.h +++ b/third_party/jpeg-xl/lib/jxl/dec_xyb.h @@ -6,10 +6,12 @@ #ifndef LIB_JXL_DEC_XYB_H_ #define LIB_JXL_DEC_XYB_H_ // XYB -> linear sRGB. +#include + #include "lib/jxl/base/compiler_specific.h" #include "lib/jxl/base/data_parallel.h" #include "lib/jxl/base/status.h" #include "lib/jxl/color_encoding_internal.h" #include "lib/jxl/dec_bit_reader.h" @@ -39,11 +41,15 @@ bool default_transform; bool xyb_encoded; // // Fields depending on output color encoding // + // The requested color encoding. ColorEncoding color_encoding; + // This is expected as the output of the conversion from XYB. + // It is equal to `color_encoding`, but with a linear tone response curve. + ColorEncoding linear_color_encoding; bool color_encoding_is_original; // Contains an opsin matrix that converts to the primaries of the output // encoding. OpsinParams opsin_params; bool all_default_opsin; @@ -53,10 +59,12 @@ // for PQ tone mapping. // Default to sRGB's. float luminances[3]; // Used for the HLG inverse OOTF and PQ tone mapping. float desired_intensity_target; + bool cms_set = false; + JxlCmsInterface color_management_system; Status SetFromMetadata(const CodecMetadata& metadata); Status MaybeSetColorEncoding(const ColorEncoding& c_desired); private: 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 @@ -14,10 +14,11 @@ #include "lib/jxl/base/compiler_specific.h" #include "lib/jxl/base/matrix_ops.h" #include "lib/jxl/base/status.h" #include "lib/jxl/cms/opsin_params.h" +#include "lib/jxl/color_encoding_internal.h" #include "lib/jxl/dec_group_border.h" #include "lib/jxl/dec_xyb-inl.h" #include "lib/jxl/fields.h" #include "lib/jxl/image.h" #include "lib/jxl/opsin_params.h" @@ -248,10 +249,12 @@ return SetColorEncoding(c_desired); } Status OutputEncodingInfo::SetColorEncoding(const ColorEncoding& c_desired) { color_encoding = c_desired; + linear_color_encoding = color_encoding; + linear_color_encoding.Tf().SetTransferFunction(TransferFunction::kLinear); color_encoding_is_original = orig_color_encoding.SameColorEncoding(c_desired); // Compute the opsin inverse matrix and luminances based on primaries and // white point. float inverse_matrix[9]; @@ -264,23 +267,26 @@ !c_desired.IsGray()) { float srgb_to_xyzd50[9]; const auto& srgb = ColorEncoding::SRGB(/*is_gray=*/false); PrimariesCIExy p = srgb.GetPrimaries(); CIExy w = srgb.GetWhitePoint(); - JXL_CHECK(PrimariesToXYZD50(p.r.x, p.r.y, p.g.x, p.g.y, p.b.x, p.b.y, w.x, - w.y, srgb_to_xyzd50)); + JXL_CHECK(JxlCmsPrimariesToXYZD50(p.r.x, p.r.y, p.g.x, p.g.y, p.b.x, p.b.y, + w.x, w.y, srgb_to_xyzd50)); float original_to_xyz[3][3]; p = c_desired.GetPrimaries(); w = c_desired.GetWhitePoint(); - JXL_RETURN_IF_ERROR(PrimariesToXYZ(p.r.x, p.r.y, p.g.x, p.g.y, p.b.x, p.b.y, - w.x, w.y, &original_to_xyz[0][0])); + if (!JxlCmsPrimariesToXYZ(p.r.x, p.r.y, p.g.x, p.g.y, p.b.x, p.b.y, w.x, + w.y, &original_to_xyz[0][0])) { + return JXL_FAILURE("PrimariesToXYZ failed"); + } memcpy(luminances, original_to_xyz[1], sizeof luminances); if (xyb_encoded) { float adapt_to_d50[9]; - JXL_RETURN_IF_ERROR(AdaptToXYZD50(c_desired.GetWhitePoint().x, - c_desired.GetWhitePoint().y, - adapt_to_d50)); + if (!JxlCmsAdaptToXYZD50(c_desired.GetWhitePoint().x, + c_desired.GetWhitePoint().y, adapt_to_d50)) { + return JXL_FAILURE("AdaptToXYZD50 failed"); + } float xyzd50_to_original[9]; Mul3x3Matrix(adapt_to_d50, &original_to_xyz[0][0], xyzd50_to_original); JXL_RETURN_IF_ERROR(Inv3x3Matrix(xyzd50_to_original)); float srgb_to_original[9]; Mul3x3Matrix(xyzd50_to_original, srgb_to_xyzd50, srgb_to_original); 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 @@ -15,10 +15,11 @@ #include "lib/jxl/base/byte_order.h" #include "lib/jxl/base/common.h" #include "lib/jxl/base/span.h" #include "lib/jxl/base/status.h" +#include "lib/jxl/padded_bytes.h" // JPEGXL_ENABLE_BOXES, JPEGXL_ENABLE_TRANSCODE_JPEG #include "lib/jxl/common.h" #if JPEGXL_ENABLE_BOXES || JPEGXL_ENABLE_TRANSCODE_JPEG @@ -623,19 +624,19 @@ size_t avail_codestream = AvailableCodestream(); if (codestream_copy.empty()) { if (avail_codestream == 0) { return RequestMoreInput(); } - *span = jxl::Span(next_in, avail_codestream); + *span = jxl::Bytes(next_in, avail_codestream); return JXL_DEC_SUCCESS; } else { codestream_copy.insert(codestream_copy.end(), next_in + codestream_unconsumed, next_in + avail_codestream); codestream_unconsumed = avail_codestream; - *span = jxl::Span(codestream_copy.data() + codestream_pos, - codestream_copy.size() - codestream_pos); + *span = jxl::Bytes(codestream_copy.data() + codestream_pos, + codestream_copy.size() - codestream_pos); return JXL_DEC_SUCCESS; } } // Whether the decoder can use more codestream input for a purpose it needs. @@ -1073,11 +1074,11 @@ } if (decoded_icc.empty()) { return JXL_DEC_ERROR; } IccBytes icc; - Span(decoded_icc).AppendTo(&icc); + Bytes(decoded_icc).AppendTo(&icc); dec->metadata.m.color_encoding.SetICCRaw(std::move(icc)); } dec->got_all_headers = true; JXL_API_RETURN_IF_ERROR(reader->JumpToByteBoundary()); @@ -1115,12 +1116,11 @@ size_t id = toc[i].id; size_t size = toc[i].size; if (OutOfBounds(pos, size, span.size())) { break; } - auto br = - new jxl::BitReader(jxl::Span(span.data() + pos, size)); + auto br = new jxl::BitReader(jxl::Bytes(span.data() + pos, size)); section_info.emplace_back(jxl::FrameDecoder::SectionInfo{br, id, i}); section_status.emplace_back(); pos += size; } jxl::Status status = dec->frame_dec->ProcessSections( @@ -1593,18 +1593,18 @@ } size_t box_start = pos; // Box size, including this header itself. *box_size = LoadBE32(in + pos); pos += 4; + memcpy(type, in + pos, 4); + pos += 4; if (*box_size == 1) { *header_size = 16; - if (OutOfBounds(pos, 12, size)) return JXL_DEC_NEED_MORE_INPUT; + if (OutOfBounds(pos, 8, size)) return JXL_DEC_NEED_MORE_INPUT; *box_size = LoadBE64(in + pos); pos += 8; } - memcpy(type, in + pos, 4); - pos += 4; *header_size = pos - box_start; if (*box_size > 0 && *box_size < *header_size) { return JXL_INPUT_ERROR("invalid box size"); } if (file_pos + *box_size < file_pos) { @@ -2229,11 +2229,11 @@ if (jxl_color_encoding->WantICC()) return JXL_DEC_ERROR; // Indicate no encoded profile available. if (color_encoding) { - jxl_color_encoding->ToExternal(color_encoding); + *color_encoding = jxl_color_encoding->ToExternal(); } return JXL_DEC_SUCCESS; } @@ -2336,10 +2336,20 @@ } return JXL_DEC_SUCCESS; } +JXL_EXPORT JxlDecoderStatus JxlDecoderSetCms(JxlDecoder* dec, + const JxlCmsInterface cms) { + if (!dec->passes_state) { + dec->passes_state.reset(new jxl::PassesDecoderState()); + } + dec->passes_state->output_encoding_info.color_management_system = cms; + dec->passes_state->output_encoding_info.cms_set = true; + return JXL_DEC_SUCCESS; +} + JXL_EXPORT JxlDecoderStatus JxlDecoderPreviewOutBufferSize( const JxlDecoder* dec, const JxlPixelFormat* format, size_t* size) { size_t bits; JxlDecoderStatus status = PrepareSizeCheck(dec, format, &bits); if (status != JXL_DEC_SUCCESS) return status; @@ -2537,12 +2547,12 @@ destroy_callback == nullptr) { return JXL_API_ERROR("All callbacks are required"); } // Perform error checking for invalid format. - size_t bits_dummy; - JxlDecoderStatus status = PrepareSizeCheck(dec, format, &bits_dummy); + size_t bits_sink; + JxlDecoderStatus status = PrepareSizeCheck(dec, format, &bits_sink); if (status != JXL_DEC_SUCCESS) return status; dec->image_out_buffer_set = true; dec->image_out_init_callback = init_callback; dec->image_out_run_callback = run_callback; @@ -2651,32 +2661,66 @@ return JXL_DEC_SUCCESS; } JxlDecoderStatus JxlDecoderSetPreferredColorProfile( JxlDecoder* dec, const JxlColorEncoding* color_encoding) { + return JxlDecoderSetOutputColorProfile(dec, color_encoding, + /*icc_data=*/nullptr, /*icc_size=*/0); +} + +JxlDecoderStatus JxlDecoderSetOutputColorProfile( + JxlDecoder* dec, const JxlColorEncoding* color_encoding, + const uint8_t* icc_data, size_t icc_size) { + if ((color_encoding != nullptr) && (icc_data != nullptr)) { + return JXL_API_ERROR("cannot set both color_encoding and icc_data"); + } + if ((color_encoding == nullptr) && (icc_data == nullptr)) { + return JXL_API_ERROR("one of color_encoding and icc_data must be set"); + } if (!dec->got_all_headers) { return JXL_API_ERROR("color info not yet available"); } if (dec->post_headers) { return JXL_API_ERROR("too late to set the color encoding"); } - if (dec->image_metadata.color_encoding.IsGray() && - color_encoding->color_space != JXL_COLOR_SPACE_GRAY && - dec->image_out_buffer_set && dec->image_out_format.num_channels < 3) { - return JXL_API_ERROR("Number of channels is too low for color output"); - } - if (color_encoding->color_space == JXL_COLOR_SPACE_UNKNOWN) { - return JXL_API_ERROR("Unknown output colorspace"); + if ((!dec->passes_state->output_encoding_info.cms_set) && + (icc_data != nullptr)) { + return JXL_API_ERROR( + "must set color management system via JxlDecoderSetCms"); } - jxl::ColorEncoding c_out; - JXL_API_RETURN_IF_ERROR(c_out.FromExternal(*color_encoding)); - JXL_API_RETURN_IF_ERROR(!c_out.ICC().empty()); auto& output_encoding = dec->passes_state->output_encoding_info; - if (!c_out.SameColorEncoding(output_encoding.color_encoding)) { - JXL_API_RETURN_IF_ERROR(output_encoding.MaybeSetColorEncoding(c_out)); - dec->image_metadata.color_encoding = output_encoding.color_encoding; + if (color_encoding) { + if (dec->image_metadata.color_encoding.IsGray() && + color_encoding->color_space != JXL_COLOR_SPACE_GRAY && + dec->image_out_buffer_set && dec->image_out_format.num_channels < 3) { + return JXL_API_ERROR("Number of channels is too low for color output"); + } + if (color_encoding->color_space == JXL_COLOR_SPACE_UNKNOWN) { + return JXL_API_ERROR("Unknown output colorspace"); + } + jxl::ColorEncoding c_out; + JXL_API_RETURN_IF_ERROR(c_out.FromExternal(*color_encoding)); + JXL_API_RETURN_IF_ERROR(!c_out.ICC().empty()); + if (!c_out.SameColorEncoding(output_encoding.color_encoding)) { + JXL_API_RETURN_IF_ERROR(output_encoding.MaybeSetColorEncoding(c_out)); + dec->image_metadata.color_encoding = output_encoding.color_encoding; + } + return JXL_DEC_SUCCESS; } + // icc_data != nullptr + // TODO(firsching): implement setting output color profile from icc_data. + jxl::ColorEncoding c_dst; + std::vector padded_icc; + padded_icc.assign(icc_data, icc_data + icc_size); + if (!c_dst.SetICC(std::move(padded_icc), + &output_encoding.color_management_system)) { + return JXL_API_ERROR( + "setting output color profile from icc_data not yet implemented."); + } + JXL_API_RETURN_IF_ERROR( + (int)output_encoding.MaybeSetColorEncoding(std::move(c_dst))); + return JXL_DEC_SUCCESS; } JxlDecoderStatus JxlDecoderSetDesiredIntensityTarget( JxlDecoder* dec, float desired_intensity_target) { 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 @@ -1,56 +1,60 @@ // Copyright (c) the JPEG XL Project Authors. All rights reserved. // // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +#include #include #include #include #include #include -#include -#include - + +#include +#include +#include #include #include +#include #include #include #include "lib/extras/codec.h" #include "lib/extras/dec/color_description.h" #include "lib/jxl/base/byte_order.h" -#include "lib/jxl/base/padded_bytes.h" #include "lib/jxl/base/span.h" #include "lib/jxl/base/status.h" +#include "lib/jxl/cms/color_encoding_cms.h" #include "lib/jxl/cms/jxl_cms.h" #include "lib/jxl/color_encoding_internal.h" +#include "lib/jxl/dec_bit_reader.h" #include "lib/jxl/dec_external_image.h" #include "lib/jxl/enc_aux_out.h" #include "lib/jxl/enc_butteraugli_comparator.h" #include "lib/jxl/enc_external_image.h" #include "lib/jxl/enc_fields.h" -#include "lib/jxl/enc_file.h" #include "lib/jxl/enc_icc_codec.h" #include "lib/jxl/enc_progressive_split.h" #include "lib/jxl/encode_internal.h" #include "lib/jxl/fields.h" #include "lib/jxl/frame_dimensions.h" #include "lib/jxl/frame_header.h" #include "lib/jxl/headers.h" #include "lib/jxl/icc_codec.h" #include "lib/jxl/image_metadata.h" #include "lib/jxl/jpeg/enc_jpeg_data.h" +#include "lib/jxl/padded_bytes.h" #include "lib/jxl/test_image.h" #include "lib/jxl/test_utils.h" #include "lib/jxl/testing.h" #include "lib/jxl/toc.h" //////////////////////////////////////////////////////////////////////////////// namespace { -void AppendU32BE(uint32_t u32, jxl::PaddedBytes* bytes) { +void AppendU32BE(uint32_t u32, std::vector* bytes) { bytes->push_back(u32 >> 24); bytes->push_back(u32 >> 16); bytes->push_back(u32 >> 8); bytes->push_back(u32 >> 0); } @@ -159,18 +163,18 @@ namespace jxl { namespace { void AppendTestBox(const char* type, const char* contents, size_t contents_size, - bool unbounded, PaddedBytes* bytes) { + bool unbounded, std::vector* bytes) { AppendU32BE(contents_size + 8, bytes); bytes->push_back(type[0]); bytes->push_back(type[1]); bytes->push_back(type[2]); bytes->push_back(type[3]); const uint8_t* contents_u = reinterpret_cast(contents); - bytes->append(contents_u, contents_u + contents_size); + Bytes(contents_u, contents_size).AppendTo(bytes); } enum PreviewMode { kNoPreview, kSmallPreview, @@ -213,11 +217,11 @@ PreviewMode preview_mode = kNoPreview; bool add_intrinsic_size = false; bool add_icc_profile = false; float intensity_target = 0.0; std::string color_space; - PaddedBytes* jpeg_codestream = nullptr; + std::vector* jpeg_codestream = nullptr; const ProgressiveMode* progressive_mode = nullptr; }; // Input pixels always given as 16-bit RGBA, 8 bytes per pixel. // include_alpha determines if the encoded image should contain the alpha @@ -227,13 +231,13 @@ // returned by GetIccTestProfile, without the JXL fields, this requires the // image is RGB, not grayscale. // Providing jpeg_codestream will populate the jpeg_codestream with compressed // JPEG bytes, and make it possible to reconstruct those exact JPEG bytes using // the return value _if_ add_container indicates a box format. -PaddedBytes CreateTestJXLCodestream(Span pixels, size_t xsize, - size_t ysize, size_t num_channels, - const TestCodestreamParams& params) { +std::vector CreateTestJXLCodestream( + Span pixels, size_t xsize, size_t ysize, size_t num_channels, + const TestCodestreamParams& params) { // Compress the pixels with JPEG XL. bool grayscale = (num_channels <= 2); bool include_alpha = !(num_channels & 1) && params.jpeg_codestream == nullptr; size_t bitdepth = params.jpeg_codestream == nullptr ? 16 : 8; CodecInOut io; @@ -250,11 +254,10 @@ EXPECT_TRUE(color_encoding.FromExternal(c)); EXPECT_EQ(color_encoding.IsGray(), grayscale); } else { color_encoding = jxl::ColorEncoding::SRGB(/*is_gray=*/grayscale); } - ThreadPool pool(nullptr, nullptr); io.metadata.m.SetUintSamples(bitdepth); if (include_alpha) { io.metadata.m.SetAlphaBits(bitdepth); } if (params.intensity_target != 0) { @@ -264,23 +267,22 @@ JXL_BIG_ENDIAN, 0}; // 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, - /*bits_per_sample=*/16, format, &pool, - &io.Main())); - jxl::PaddedBytes jpeg_data; + /*bits_per_sample=*/16, format, + /* pool */ nullptr, &io.Main())); + std::vector jpeg_data; if (params.jpeg_codestream != nullptr) { if (jxl::extras::CanDecode(jxl::extras::Codec::kJPG)) { std::vector jpeg_bytes; io.jpeg_quality = 70; EXPECT_TRUE(Encode(io, extras::Codec::kJPG, io.metadata.m.color_encoding, - /*bits_per_sample=*/8, &jpeg_bytes, &pool)); - params.jpeg_codestream->append(jpeg_bytes.data(), - jpeg_bytes.data() + jpeg_bytes.size()); + /*bits_per_sample=*/8, &jpeg_bytes)); + Bytes(jpeg_bytes).AppendTo(params.jpeg_codestream); EXPECT_TRUE(jxl::jpeg::DecodeImageJPG( - jxl::Span(jpeg_bytes.data(), jpeg_bytes.size()), &io)); + jxl::Bytes(jpeg_bytes.data(), jpeg_bytes.size()), &io)); EXPECT_TRUE( EncodeJPEGData(*io.Main().jpeg_data, &jpeg_data, params.cparams)); io.metadata.m.xyb_encoded = false; } else { JXL_ABORT( @@ -296,18 +298,16 @@ } if (params.add_intrinsic_size) { EXPECT_TRUE(io.metadata.m.intrinsic_size.Set(xsize / 3, ysize / 3)); } io.metadata.m.orientation = params.orientation; - AuxOut aux_out; - PaddedBytes compressed; + std::vector compressed; PassesEncoderState enc_state; if (params.progressive_mode) { enc_state.progressive_splitter.SetProgressiveMode(*params.progressive_mode); } - EXPECT_TRUE(EncodeFile(params.cparams, &io, &enc_state, &compressed, - *JxlGetDefaultCms(), &aux_out, &pool)); + EXPECT_TRUE(test::EncodeFile(params.cparams, &io, &enc_state, &compressed)); CodeStreamBoxFormat add_container = params.box_format; if (add_container != kCSBF_None) { // Header with signature box and ftyp box. const uint8_t header[] = {0, 0, 0, 0xc, 0x4a, 0x58, 0x4c, 0x20, 0xd, 0xa, 0x87, 0xa, 0, 0, 0, 0x14, @@ -328,20 +328,20 @@ std::vector compressed1(compressed.data() + third, compressed.data() + 2 * third); std::vector compressed2(compressed.data() + 2 * third, compressed.data() + compressed.size()); - PaddedBytes c; - c.append(header, header + sizeof(header)); + std::vector c; + Bytes(header).AppendTo(&c); if (params.jpeg_codestream != nullptr) { jxl::AppendBoxHeader(jxl::MakeBoxType("jbrd"), jpeg_data.size(), false, &c); - c.append(jpeg_data.data(), jpeg_data.data() + jpeg_data.size()); + Bytes(jpeg_data).AppendTo(&c); } uint32_t jxlp_index = 0; if (add_container == kCSBF_Multi_First_Empty) { - // Dummy (empty) codestream part + // Empty placeholder codestream part AppendU32BE(12, &c); c.push_back('j'); c.push_back('x'); c.push_back('l'); c.push_back('p'); @@ -352,15 +352,15 @@ c.push_back('j'); c.push_back('x'); c.push_back('l'); c.push_back('p'); AppendU32BE(jxlp_index++, &c); - c.append(compressed0.data(), compressed0.data() + compressed0.size()); + Bytes(compressed0).AppendTo(&c); // A few non-codestream boxes in between AppendTestBox(unk1_box_type, unk1_box_contents, unk1_box_size, false, &c); AppendTestBox(unk2_box_type, unk2_box_contents, unk2_box_size, false, &c); - // Dummy (empty) codestream part + // Empty placeholder codestream part AppendU32BE(12, &c); c.push_back('j'); c.push_back('x'); c.push_back('l'); c.push_back('p'); @@ -370,11 +370,11 @@ c.push_back('j'); c.push_back('x'); c.push_back('l'); c.push_back('p'); AppendU32BE(jxlp_index++, &c); - c.append(compressed1.data(), compressed1.data() + compressed1.size()); + Bytes(compressed1).AppendTo(&c); // Third (last) codestream part AppendU32BE(add_container == kCSBF_Multi_Zero_Terminated ? 0 : (compressed2.size() + 12), &c); @@ -385,13 +385,13 @@ if (add_container != kCSBF_Multi_Last_Empty_Other) { AppendU32BE(jxlp_index++ | 0x80000000, &c); } else { AppendU32BE(jxlp_index++, &c); } - c.append(compressed2.data(), compressed2.data() + compressed2.size()); + Bytes(compressed2).AppendTo(&c); if (add_container == kCSBF_Multi_Last_Empty_Other) { - // Dummy (empty) codestream part + // Empty placeholder codestream part AppendU32BE(12, &c); c.push_back('j'); c.push_back('x'); c.push_back('l'); c.push_back('p'); @@ -407,29 +407,29 @@ AppendTestBox(unk3_box_type, unk3_box_contents, unk3_box_size, true, &c); } compressed.swap(c); } else { - PaddedBytes c; - c.append(header, header + sizeof(header)); + std::vector c; + Bytes(header).AppendTo(&c); if (params.jpeg_codestream != nullptr) { jxl::AppendBoxHeader(jxl::MakeBoxType("jbrd"), jpeg_data.size(), false, &c); - c.append(jpeg_data.data(), jpeg_data.data() + jpeg_data.size()); + Bytes(jpeg_data).AppendTo(&c); } if (add_container == kCSBF_Brob_Exif) { - c.append(box_brob_exif, box_brob_exif + box_brob_exif_size); + Bytes(box_brob_exif, box_brob_exif_size).AppendTo(&c); } AppendU32BE(add_container == kCSBF_Single_Zero_Terminated ? 0 : (compressed.size() + 8), &c); c.push_back('j'); c.push_back('x'); c.push_back('l'); c.push_back('c'); - c.append(compressed.data(), compressed.data() + compressed.size()); + Bytes(compressed).AppendTo(&c); if (add_container == kCSBF_Single_Other) { AppendTestBox(unk1_box_type, unk1_box_contents, unk1_box_size, false, &c); } compressed.swap(c); @@ -452,11 +452,11 @@ Span compressed, const JxlPixelFormat& format, bool use_callback, bool set_buffer_early, bool use_resizable_runner, bool require_boxes, bool expect_success, - PaddedBytes* icc = nullptr) { + std::vector* icc = nullptr) { JxlThreadParallelRunnerPtr runner_fixed; JxlResizableParallelRunnerPtr runner_resizable; JxlParallelRunner runner_fn; void* runner; @@ -1103,12 +1103,11 @@ EXPECT_EQ(JXL_TRANSFER_FUNCTION_LINEAR, pixel_encoding.transfer_function); // Test after setting the preferred color profile to non-linear sRGB: // for XYB images with ICC profile, this setting is expected to take effect. jxl::ColorEncoding temp_jxl_srgb = jxl::ColorEncoding::SRGB(false); - JxlColorEncoding pixel_encoding_srgb; - temp_jxl_srgb.ToExternal(&pixel_encoding_srgb); + JxlColorEncoding pixel_encoding_srgb = temp_jxl_srgb.ToExternal(); EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderSetPreferredColorProfile(dec, &pixel_encoding_srgb)); EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderGetColorAsEncodedProfile( dec, JXL_COLOR_PROFILE_TARGET_DATA, &pixel_encoding)); @@ -1137,12 +1136,11 @@ // Test setting another different preferred profile, to verify that the // returned JXL_COLOR_PROFILE_TARGET_DATA ICC profile is correctly // updated. jxl::ColorEncoding temp_jxl_linear = jxl::ColorEncoding::LinearSRGB(false); - JxlColorEncoding pixel_encoding_linear; - temp_jxl_linear.ToExternal(&pixel_encoding_linear); + JxlColorEncoding pixel_encoding_linear = temp_jxl_linear.ToExternal(); EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderSetPreferredColorProfile(dec, &pixel_encoding_linear)); EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderGetColorAsEncodedProfile( @@ -1287,26 +1285,26 @@ params.cparams.ec_resampling = config.upsampling; params.box_format = config.add_container; params.orientation = config.orientation; params.preview_mode = config.preview_mode; params.add_intrinsic_size = config.add_intrinsic_size; - jxl::PaddedBytes compressed = jxl::CreateTestJXLCodestream( - jxl::Span(pixels.data(), pixels.size()), config.xsize, - config.ysize, orig_channels, params); + std::vector compressed = jxl::CreateTestJXLCodestream( + jxl::Bytes(pixels.data(), pixels.size()), config.xsize, config.ysize, + orig_channels, params); JxlPixelFormat format = {config.output_channels, config.data_type, config.endianness, 0}; bool swap_xy = !config.keep_orientation && (config.orientation > 4); size_t xsize = swap_xy ? config.ysize : config.xsize; size_t ysize = swap_xy ? config.xsize : config.ysize; - std::vector pixels2 = jxl::DecodeWithAPI( - dec, jxl::Span(compressed.data(), compressed.size()), - format, config.use_callback, config.set_buffer_early, - config.use_resizable_runner, /*require_boxes=*/false, - /*expect_success=*/true); + std::vector pixels2 = + jxl::DecodeWithAPI(dec, jxl::Bytes(compressed.data(), compressed.size()), + format, config.use_callback, config.set_buffer_early, + config.use_resizable_runner, /*require_boxes=*/false, + /*expect_success=*/true); JxlDecoderReset(dec); EXPECT_EQ(num_pixels * config.output_channels * jxl::test::GetDataBits(config.data_type) / jxl::kBitsPerByte, pixels2.size()); @@ -1561,21 +1559,20 @@ params.cparams.SetLossless(); params.cparams.speed_tier = jxl::SpeedTier::kThunder; params.add_icc_profile = true; // For variation: some have container and no preview, others have preview // and no container. - jxl::PaddedBytes compressed = jxl::CreateTestJXLCodestream( - jxl::Span(pixels.data(), pixels.size()), xsize, ysize, 4, - params); + std::vector compressed = jxl::CreateTestJXLCodestream( + jxl::Bytes(pixels.data(), pixels.size()), xsize, ysize, 4, params); for (uint32_t channels = 3; channels <= 4; ++channels) { { JxlPixelFormat format = {channels, JXL_TYPE_UINT8, JXL_LITTLE_ENDIAN, 0}; std::vector pixels2 = jxl::DecodeWithAPI( - dec, jxl::Span(compressed.data(), compressed.size()), - format, /*use_callback=*/false, /*set_buffer_early=*/false, + dec, jxl::Bytes(compressed.data(), compressed.size()), format, + /*use_callback=*/false, /*set_buffer_early=*/false, /*use_resizable_runner=*/false, /*require_boxes=*/false, /*expect_success=*/true); JxlDecoderReset(dec); EXPECT_EQ(num_pixels * channels, pixels2.size()); EXPECT_EQ(0u, @@ -1585,12 +1582,12 @@ { JxlPixelFormat format = {channels, JXL_TYPE_UINT16, JXL_LITTLE_ENDIAN, 0}; // Test with the container for one of the pixel formats. std::vector pixels2 = jxl::DecodeWithAPI( - dec, jxl::Span(compressed.data(), compressed.size()), - format, /*use_callback=*/true, /*set_buffer_early=*/true, + dec, jxl::Bytes(compressed.data(), compressed.size()), format, + /*use_callback=*/true, /*set_buffer_early=*/true, /*use_resizable_runner=*/false, /*require_boxes=*/false, /*expect_success=*/true); JxlDecoderReset(dec); EXPECT_EQ(num_pixels * channels * 2, pixels2.size()); EXPECT_EQ(0u, @@ -1600,12 +1597,12 @@ { JxlPixelFormat format = {channels, JXL_TYPE_FLOAT, JXL_LITTLE_ENDIAN, 0}; std::vector pixels2 = jxl::DecodeWithAPI( - dec, jxl::Span(compressed.data(), compressed.size()), - format, /*use_callback=*/false, /*set_buffer_early=*/false, + dec, jxl::Bytes(compressed.data(), compressed.size()), format, + /*use_callback=*/false, /*set_buffer_early=*/false, /*use_resizable_runner=*/false, /*require_boxes=*/false, /*expect_success=*/true); JxlDecoderReset(dec); EXPECT_EQ(num_pixels * channels * 4, pixels2.size()); EXPECT_EQ(0u, @@ -1624,21 +1621,20 @@ size_t num_pixels = xsize * ysize; std::vector pixels = jxl::test::GetSomeTestImage(xsize, ysize, 3, 0); JxlPixelFormat format_orig = {3, JXL_TYPE_UINT16, JXL_BIG_ENDIAN, 0}; jxl::TestCodestreamParams params; params.add_icc_profile = true; - jxl::PaddedBytes compressed = jxl::CreateTestJXLCodestream( - jxl::Span(pixels.data(), pixels.size()), xsize, ysize, 3, - params); + std::vector compressed = jxl::CreateTestJXLCodestream( + jxl::Bytes(pixels.data(), pixels.size()), xsize, ysize, 3, params); uint32_t channels = 3; JxlPixelFormat format = {channels, JXL_TYPE_FLOAT, JXL_LITTLE_ENDIAN, 0}; - jxl::PaddedBytes icc_data; + std::vector icc_data; std::vector pixels2 = jxl::DecodeWithAPI( - dec, jxl::Span(compressed.data(), compressed.size()), - format, /*use_callback=*/false, /*set_buffer_early=*/true, + dec, jxl::Bytes(compressed.data(), compressed.size()), format, + /*use_callback=*/false, /*set_buffer_early=*/true, /*use_resizable_runner=*/false, /*require_boxes=*/false, /*expect_success=*/true, /*icc=*/&icc_data); JxlDecoderReset(dec); EXPECT_EQ(num_pixels * channels * 4, pixels2.size()); @@ -1653,11 +1649,11 @@ /*bits_per_sample=*/16, format_orig, /*pool=*/nullptr, &io0.Main())); jxl::ColorEncoding color_encoding1; jxl::IccBytes icc; - jxl::Span(icc_data).AppendTo(&icc); + jxl::Bytes(icc_data).AppendTo(&icc); EXPECT_TRUE(color_encoding1.SetICC(std::move(icc), JxlGetDefaultCms())); jxl::Span span1(pixels2.data(), pixels2.size()); jxl::CodecInOut io1; io1.SetSize(xsize, ysize); EXPECT_TRUE(ConvertFromExternal(span1, xsize, ysize, color_encoding1, @@ -1706,22 +1702,20 @@ in.metadata.m.color_encoding = color_in; in.metadata.m.SetIntensityTarget(intensity_in); JxlPixelFormat format_in = {static_cast(color_in.Channels()), JXL_TYPE_UINT16, JXL_BIG_ENDIAN, 0}; EXPECT_TRUE(jxl::ConvertFromExternal( - jxl::Span(pixels_in.data(), pixels_in.size()), xsize, - ysize, color_in, + jxl::Bytes(pixels_in.data(), pixels_in.size()), xsize, ysize, color_in, /*bits_per_sample=*/16, format_in, /*pool=*/nullptr, &in.Main())); jxl::CodecInOut out; out.metadata.m.color_encoding = color_out; out.metadata.m.SetIntensityTarget(intensity_out); JxlPixelFormat format_out = {static_cast(color_out.Channels()), JXL_TYPE_UINT16, JXL_BIG_ENDIAN, 0}; EXPECT_TRUE(jxl::ConvertFromExternal( - jxl::Span(pixels_out.data(), pixels_out.size()), xsize, - ysize, color_out, + jxl::Bytes(pixels_out.data(), pixels_out.size()), xsize, ysize, color_out, /*bits_per_sample=*/16, format_out, /*pool=*/nullptr, &out.Main())); return ButteraugliDistance(in.frames, out.frames, jxl::ButteraugliParams(), *JxlGetDefaultCms(), nullptr, nullptr); } @@ -1743,13 +1737,12 @@ float intensity_in = c_in.Tf().IsPQ() ? 10000 : 255; printf("Testing input color space %s\n", color_space_in.c_str()); jxl::TestCodestreamParams params; params.color_space = color_space_in; params.intensity_target = intensity_in; - jxl::PaddedBytes data = jxl::CreateTestJXLCodestream( - jxl::Span(pixels.data(), pixels.size()), xsize, ysize, 3, - params); + std::vector data = jxl::CreateTestJXLCodestream( + jxl::Bytes(pixels.data(), pixels.size()), xsize, ysize, 3, params); JxlDecoder* dec = JxlDecoderCreate(nullptr); EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderSubscribeEvents(dec, events)); EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderSetInput(dec, data.data(), data.size())); EXPECT_EQ(JXL_DEC_BASIC_INFO, JxlDecoderProcessInput(dec)); JxlBasicInfo info; @@ -1772,32 +1765,37 @@ JxlDecoderDestroy(dec); } namespace { void SetPreferredColorProfileTest( - const jxl::test::ColorEncodingDescriptor& from) { + const jxl::test::ColorEncodingDescriptor& from, bool icc_dst, + bool use_cms) { size_t xsize = 123, ysize = 77; int events = JXL_DEC_BASIC_INFO | JXL_DEC_COLOR_ENCODING | JXL_DEC_FULL_IMAGE; jxl::ColorEncoding c_in = jxl::test::ColorEncodingFromDescriptor(from); if (c_in.GetRenderingIntent() != jxl::RenderingIntent::kRelative) return; if (c_in.GetWhitePointType() != jxl::WhitePoint::kD65) return; uint32_t num_channels = c_in.Channels(); std::vector pixels = jxl::test::GetSomeTestImage(xsize, ysize, num_channels, 0); + JxlPixelFormat format = {num_channels, JXL_TYPE_UINT16, JXL_BIG_ENDIAN, 0}; std::string color_space_in = Description(c_in); float intensity_in = c_in.Tf().IsPQ() ? 10000 : 255; jxl::TestCodestreamParams params; params.color_space = color_space_in; params.intensity_target = intensity_in; - jxl::PaddedBytes data = jxl::CreateTestJXLCodestream( - jxl::Span(pixels.data(), pixels.size()), xsize, ysize, - num_channels, params); + std::vector data = + jxl::CreateTestJXLCodestream(jxl::Bytes(pixels.data(), pixels.size()), + xsize, ysize, num_channels, params); auto all_encodings = jxl::test::AllEncodings(); - all_encodings.push_back( - {jxl::ColorSpace::kXYB, jxl::WhitePoint::kD65, jxl::Primaries::kCustom, - jxl::TransferFunction::kUnknown, jxl::RenderingIntent::kPerceptual}); + // TODO(firsching): understand why XYB does not work together with icc_dst. + if (!icc_dst) { + all_encodings.push_back( + {jxl::ColorSpace::kXYB, jxl::WhitePoint::kD65, jxl::Primaries::kCustom, + jxl::TransferFunction::kUnknown, jxl::RenderingIntent::kPerceptual}); + } for (const auto& c1 : all_encodings) { jxl::ColorEncoding c_out = jxl::test::ColorEncodingFromDescriptor(c1); float intensity_out = intensity_in; if (c_out.GetColorSpace() != jxl::ColorSpace::kXYB) { if (c_out.GetRenderingIntent() != jxl::RenderingIntent::kRelative) { @@ -1805,11 +1803,11 @@ } if ((c_in.GetPrimariesType() == jxl::Primaries::k2100 && c_out.GetPrimariesType() != jxl::Primaries::k2100) || (c_in.GetPrimariesType() == jxl::Primaries::kP3 && c_out.GetPrimariesType() == jxl::Primaries::kSRGB)) { - // Converting to a narrower gamut does not work without gammut mapping. + // Converting to a narrower gamut does not work without gamut mapping. continue; } } if (c_out.Tf().IsHLG() && intensity_out > 300) { // The Linear->HLG OOTF function at this intensity level can push @@ -1831,25 +1829,46 @@ EXPECT_EQ(xsize, info.xsize); EXPECT_EQ(ysize, info.ysize); EXPECT_FALSE(info.uses_original_profile); EXPECT_EQ(JXL_DEC_COLOR_ENCODING, JxlDecoderProcessInput(dec)); EXPECT_EQ(GetOrigProfile(dec), color_space_in); - EXPECT_EQ(GetDataProfile(dec), color_space_in); JxlColorEncoding encoding_out; EXPECT_TRUE(jxl::ParseDescription(color_space_out, &encoding_out)); if (c_out.GetColorSpace() == jxl::ColorSpace::kXYB && (c_in.GetPrimariesType() != jxl::Primaries::kSRGB || c_in.Tf().IsPQ())) { EXPECT_EQ(JXL_DEC_ERROR, JxlDecoderSetPreferredColorProfile(dec, &encoding_out)); JxlDecoderDestroy(dec); continue; } - EXPECT_EQ(JXL_DEC_SUCCESS, - JxlDecoderSetPreferredColorProfile(dec, &encoding_out)); + if (use_cms) { + JxlDecoderSetCms(dec, *JxlGetDefaultCms()); + } + if (icc_dst) { + jxl::ColorEncoding internal_encoding_out; + EXPECT_TRUE(internal_encoding_out.FromExternal(encoding_out)); + EXPECT_TRUE(internal_encoding_out.CreateICC()); + std::vector rewritten_icc = internal_encoding_out.ICC(); + + EXPECT_EQ(use_cms ? JXL_DEC_SUCCESS : JXL_DEC_ERROR, + JxlDecoderSetOutputColorProfile( + dec, nullptr, rewritten_icc.data(), rewritten_icc.size())); + if (!use_cms) { + // continue if we don't have a cms here + JxlDecoderDestroy(dec); + continue; + } + } else { + EXPECT_EQ(JXL_DEC_SUCCESS, + JxlDecoderSetPreferredColorProfile(dec, &encoding_out)); + } EXPECT_EQ(GetOrigProfile(dec), color_space_in); - EXPECT_EQ(GetDataProfile(dec), color_space_out); + if (icc_dst) { + } else { + EXPECT_EQ(GetDataProfile(dec), color_space_out); + } EXPECT_EQ(JXL_DEC_NEED_IMAGE_OUT_BUFFER, JxlDecoderProcessInput(dec)); size_t buffer_size; JxlPixelFormat out_format = format; out_format.num_channels = c_out.Channels(); EXPECT_EQ(JXL_DEC_SUCCESS, @@ -1858,10 +1877,11 @@ EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderSetImageOutBuffer( dec, &out_format, out.data(), out.size())); EXPECT_EQ(JXL_DEC_FULL_IMAGE, JxlDecoderProcessInput(dec)); double dist = ButteraugliDistance(xsize, ysize, pixels, c_in, intensity_in, out, c_out, intensity_out); + if (c_in.GetWhitePointType() == c_out.GetWhitePointType()) { EXPECT_LT(dist, 1.29); } else { EXPECT_LT(dist, 4.0); } @@ -1873,16 +1893,127 @@ TEST(DecodeTest, SetPreferredColorProfileTestFromGray) { jxl::test::ColorEncodingDescriptor gray = { jxl::ColorSpace::kGray, jxl::WhitePoint::kD65, jxl::Primaries::kSRGB, jxl::TransferFunction::kSRGB, jxl::RenderingIntent::kRelative}; - SetPreferredColorProfileTest(gray); + SetPreferredColorProfileTest(gray, true, true); + SetPreferredColorProfileTest(gray, false, true); + SetPreferredColorProfileTest(gray, true, false); + SetPreferredColorProfileTest(gray, false, false); +} + +static std::string DecodeAllEncodingsVariantsTestName( + const ::testing::TestParamInfo< + std::tuple>& info) { + const auto& encoding = std::get<0>(info.param); + bool icc_dst = std::get<1>(info.param); + bool use_cms = std::get<2>(info.param); + + std::string encoding_name = + Description(ColorEncodingFromDescriptor(encoding)); + + return "From_" + encoding_name + + (icc_dst ? "_with_icc_dst" : "_without_icc_dst") + + (use_cms ? "_with_cms" : "_without_cms"); +} + +class DecodeAllEncodingsVariantsTest + : public ::testing::TestWithParam< + std::tuple> {}; +JXL_GTEST_INSTANTIATE_TEST_SUITE_P( + DecodeAllEncodingsVariantsTestInstantiation, DecodeAllEncodingsVariantsTest, + ::testing::Combine(::testing::ValuesIn(jxl::test::AllEncodings()), + ::testing::Bool(), ::testing::Bool()), + DecodeAllEncodingsVariantsTestName); +TEST_P(DecodeAllEncodingsVariantsTest, SetPreferredColorProfileTest) { + const auto& from = std::get<0>(GetParam()); + bool icc_dst = std::get<1>(GetParam()); + bool use_cms = std::get<2>(GetParam()); + SetPreferredColorProfileTest(from, icc_dst, use_cms); } -TEST_P(DecodeAllEncodingsTest, SetPreferredColorProfileTest) { - const auto& from = GetParam(); - SetPreferredColorProfileTest(from); +void DecodeImageWithColorEncoding(const std::vector& compressed, + jxl::ColorEncoding& color_encoding, + bool with_cms, std::vector& out, + JxlBasicInfo& info) { + JxlDecoder* dec = JxlDecoderCreate(nullptr); + int events = JXL_DEC_BASIC_INFO | JXL_DEC_COLOR_ENCODING | JXL_DEC_FULL_IMAGE; + EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderSubscribeEvents(dec, events)); + EXPECT_EQ(JXL_DEC_SUCCESS, + JxlDecoderSetInput(dec, compressed.data(), compressed.size())); + EXPECT_EQ(JXL_DEC_BASIC_INFO, JxlDecoderProcessInput(dec)); + EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderGetBasicInfo(dec, &info)); + EXPECT_EQ(JXL_DEC_COLOR_ENCODING, JxlDecoderProcessInput(dec)); + std::string color_space_in = GetOrigProfile(dec); + if (with_cms) { + JxlDecoderSetCms(dec, *JxlGetDefaultCms()); + EXPECT_TRUE(color_encoding.CreateICC()); + std::vector rewritten_icc = color_encoding.ICC(); + EXPECT_EQ(JXL_DEC_SUCCESS, + JxlDecoderSetOutputColorProfile( + dec, nullptr, rewritten_icc.data(), rewritten_icc.size())); + } else { + JxlColorEncoding external_color_encoding = color_encoding.ToExternal(); + EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderSetOutputColorProfile( + dec, &external_color_encoding, nullptr, 0)); + } + EXPECT_EQ(JXL_DEC_NEED_IMAGE_OUT_BUFFER, JxlDecoderProcessInput(dec)); + + size_t buffer_size; + JxlPixelFormat format = {3, JXL_TYPE_UINT16, JXL_BIG_ENDIAN, 0}; + + JxlPixelFormat out_format = format; + out_format.num_channels = color_encoding.Channels(); + EXPECT_EQ(JXL_DEC_SUCCESS, + JxlDecoderImageOutBufferSize(dec, &out_format, &buffer_size)); + out.resize(buffer_size); + EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderSetImageOutBuffer( + dec, &out_format, out.data(), out.size())); + EXPECT_EQ(JXL_DEC_FULL_IMAGE, JxlDecoderProcessInput(dec)); + JxlDecoderDestroy(dec); +} + +class DecodeAllEncodingsWithCMSTest + : public ::testing::TestWithParam {}; + +JXL_GTEST_INSTANTIATE_TEST_SUITE_P( + AllEncodings, DecodeAllEncodingsWithCMSTest, + testing::ValuesIn(jxl::test::AllEncodings())); + +TEST_P(DecodeAllEncodingsWithCMSTest, DecodeWithCMS) { + auto all_encodings = jxl::test::AllEncodings(); + uint32_t num_channels = 3; + size_t xsize = 177, ysize = 123; + std::vector pixels = + jxl::test::GetSomeTestImage(xsize, ysize, num_channels, 0); + jxl::TestCodestreamParams params; + std::vector data = + jxl::CreateTestJXLCodestream(jxl::Bytes(pixels.data(), pixels.size()), + xsize, ysize, num_channels, params); + + jxl::ColorEncoding color_encoding = + jxl::test::ColorEncodingFromDescriptor(GetParam()); + fprintf(stderr, "color_description: %s\n", + Description(color_encoding).c_str()); + + std::vector out_with_cms; + JxlBasicInfo info_with_cms; + DecodeImageWithColorEncoding(data, color_encoding, true, out_with_cms, + info_with_cms); + + std::vector out_without_cms; + JxlBasicInfo info_without_cms; + DecodeImageWithColorEncoding(data, color_encoding, false, out_without_cms, + info_without_cms); + + EXPECT_EQ(info_with_cms.xsize, info_without_cms.xsize); + EXPECT_EQ(info_with_cms.ysize, info_without_cms.ysize); + EXPECT_EQ(out_with_cms.size(), out_without_cms.size()); + double dist = ButteraugliDistance(xsize, ysize, out_with_cms, color_encoding, + 255, out_without_cms, color_encoding, 255); + + EXPECT_LT(dist, .1); } // Tests the case of lossy sRGB image without alpha channel, decoded to RGB8 // and to RGBA8 TEST(DecodeTest, PixelTestOpaqueSrgbLossy) { @@ -1892,19 +2023,19 @@ size_t xsize = 123, ysize = 77; size_t num_pixels = xsize * ysize; std::vector pixels = jxl::test::GetSomeTestImage(xsize, ysize, 3, 0); JxlPixelFormat format_orig = {3, JXL_TYPE_UINT16, JXL_BIG_ENDIAN, 0}; - jxl::PaddedBytes compressed = jxl::CreateTestJXLCodestream( - jxl::Span(pixels.data(), pixels.size()), xsize, ysize, 3, + std::vector compressed = jxl::CreateTestJXLCodestream( + jxl::Bytes(pixels.data(), pixels.size()), xsize, ysize, 3, jxl::TestCodestreamParams()); JxlPixelFormat format = {channels, JXL_TYPE_UINT8, JXL_LITTLE_ENDIAN, 0}; std::vector pixels2 = jxl::DecodeWithAPI( - dec, jxl::Span(compressed.data(), compressed.size()), - format, /*use_callback=*/true, /*set_buffer_early=*/false, + dec, jxl::Bytes(compressed.data(), compressed.size()), format, + /*use_callback=*/true, /*set_buffer_early=*/false, /*use_resizable_runner=*/false, /*require_boxes=*/false, /*expect_success*/ true); JxlDecoderReset(dec); EXPECT_EQ(num_pixels * channels, pixels2.size()); @@ -1943,19 +2074,18 @@ std::vector pixels = jxl::test::GetSomeTestImage(xsize, ysize, 3, 0); JxlPixelFormat format_orig = {3, JXL_TYPE_UINT16, JXL_BIG_ENDIAN, 0}; jxl::TestCodestreamParams params; params.cparams.noise = jxl::Override::kOn; - jxl::PaddedBytes compressed = jxl::CreateTestJXLCodestream( - jxl::Span(pixels.data(), pixels.size()), xsize, ysize, 3, - params); + std::vector compressed = jxl::CreateTestJXLCodestream( + jxl::Bytes(pixels.data(), pixels.size()), xsize, ysize, 3, params); JxlPixelFormat format = {channels, JXL_TYPE_UINT8, JXL_LITTLE_ENDIAN, 0}; std::vector pixels2 = jxl::DecodeWithAPI( - dec, jxl::Span(compressed.data(), compressed.size()), - format, /*use_callback=*/false, /*set_buffer_early=*/true, + dec, jxl::Bytes(compressed.data(), compressed.size()), format, + /*use_callback=*/false, /*set_buffer_early=*/true, /*use_resizable_runner=*/false, /*require_boxes=*/false, /*expect_success=*/true); JxlDecoderReset(dec); EXPECT_EQ(num_pixels * channels, pixels2.size()); @@ -1993,13 +2123,12 @@ for (int i = 0; i < kCSBF_NUM_ENTRIES; ++i) { JxlDecoder* dec = JxlDecoderCreate(NULL); jxl::TestCodestreamParams params; params.box_format = (CodeStreamBoxFormat)i; printf("Testing empty input with box format %d\n", (int)params.box_format); - jxl::PaddedBytes compressed = jxl::CreateTestJXLCodestream( - jxl::Span(pixels.data(), pixels.size()), xsize, ysize, 3, - params); + std::vector compressed = jxl::CreateTestJXLCodestream( + jxl::Bytes(pixels.data(), pixels.size()), xsize, ysize, 3, params); const int events = JXL_DEC_BASIC_INFO | JXL_DEC_FULL_IMAGE | JXL_DEC_COLOR_ENCODING; EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderSubscribeEvents(dec, events)); EXPECT_EQ(JXL_DEC_NEED_MORE_INPUT, JxlDecoderProcessInput(dec)); EXPECT_EQ(JXL_DEC_SUCCESS, @@ -2037,23 +2166,22 @@ // jxlp box. last_unknown_box_size = 12 + unk3_box_size + 8; } jxl::TestCodestreamParams params; params.box_format = box_format; - jxl::PaddedBytes compressed = jxl::CreateTestJXLCodestream( - jxl::Span(pixels.data(), pixels.size()), xsize, ysize, 3, - params); + std::vector compressed = jxl::CreateTestJXLCodestream( + jxl::Bytes(pixels.data(), pixels.size()), xsize, ysize, 3, params); // Add some more bytes after compressed data. compressed.push_back(0); compressed.push_back(1); compressed.push_back(2); JxlDecoder* dec = JxlDecoderCreate(NULL); uint32_t channels = 3; JxlPixelFormat format = {channels, JXL_TYPE_FLOAT, JXL_LITTLE_ENDIAN, 0}; std::vector pixels2 = jxl::DecodeWithAPI( - dec, jxl::Span(compressed.data(), compressed.size()), - format, /*use_callback=*/false, /*set_buffer_early=*/true, + dec, jxl::Bytes(compressed.data(), compressed.size()), format, + /*use_callback=*/false, /*set_buffer_early=*/true, /*use_resizable_runner=*/false, /*require_boxes=*/false, /*expect_success=*/true); size_t unconsumed_bytes = JxlDecoderReleaseInput(dec); EXPECT_EQ(last_unknown_box_size + 3, unconsumed_bytes); EXPECT_EQ(num_pixels * channels * 4, pixels2.size()); @@ -2073,23 +2201,22 @@ bool expect_success = (box_format == kCSBF_None || box_format == kCSBF_Single_Zero_Terminated || box_format == kCSBF_Multi_Zero_Terminated); jxl::TestCodestreamParams params; params.box_format = box_format; - jxl::PaddedBytes compressed = jxl::CreateTestJXLCodestream( - jxl::Span(pixels.data(), pixels.size()), xsize, ysize, 3, - params); + std::vector compressed = jxl::CreateTestJXLCodestream( + jxl::Bytes(pixels.data(), pixels.size()), xsize, ysize, 3, params); // Add some more bytes after compressed data. compressed.push_back(0); compressed.push_back(1); compressed.push_back(2); JxlDecoder* dec = JxlDecoderCreate(NULL); uint32_t channels = 3; JxlPixelFormat format = {channels, JXL_TYPE_FLOAT, JXL_LITTLE_ENDIAN, 0}; std::vector pixels2 = jxl::DecodeWithAPI( - dec, jxl::Span(compressed.data(), compressed.size()), - format, /*use_callback=*/false, /*set_buffer_early=*/true, + dec, jxl::Bytes(compressed.data(), compressed.size()), format, + /*use_callback=*/false, /*set_buffer_early=*/true, /*use_resizable_runner=*/false, /*require_boxes=*/true, expect_success); size_t unconsumed_bytes = JxlDecoderReleaseInput(dec); EXPECT_EQ(3, unconsumed_bytes); EXPECT_EQ(num_pixels * channels * 4, pixels2.size()); JxlDecoderDestroy(dec); @@ -2104,26 +2231,24 @@ for (int i = 0; i < kCSBF_NUM_ENTRIES; ++i) { CodeStreamBoxFormat first_box_format = (CodeStreamBoxFormat)i; if (first_box_format == kCSBF_Multi_Other_Zero_Terminated) continue; jxl::TestCodestreamParams params1; params1.box_format = first_box_format; - jxl::PaddedBytes compressed1 = jxl::CreateTestJXLCodestream( - jxl::Span(pixels.data(), pixels.size()), xsize, ysize, 3, - params1); + std::vector compressed1 = jxl::CreateTestJXLCodestream( + jxl::Bytes(pixels.data(), pixels.size()), xsize, ysize, 3, params1); for (int j = 0; j < kCSBF_NUM_ENTRIES; ++j) { CodeStreamBoxFormat second_box_format = (CodeStreamBoxFormat)j; if (second_box_format == kCSBF_Multi_Other_Zero_Terminated) continue; printf("Testing with box format pair %d, %d\n", (int)first_box_format, (int)second_box_format); jxl::TestCodestreamParams params2; params2.box_format = second_box_format; - jxl::PaddedBytes compressed2 = jxl::CreateTestJXLCodestream( - jxl::Span(pixels.data(), pixels.size()), xsize, ysize, - 3, params2); - jxl::PaddedBytes concat; - concat.append(compressed1); - concat.append(compressed2); + std::vector compressed2 = jxl::CreateTestJXLCodestream( + jxl::Bytes(pixels.data(), pixels.size()), xsize, ysize, 3, params2); + std::vector concat; + jxl::Bytes(compressed1).AppendTo(&concat); + jxl::Bytes(compressed2).AppendTo(&concat); uint32_t channels = 3; JxlPixelFormat format = {channels, JXL_TYPE_FLOAT, JXL_LITTLE_ENDIAN, 0}; size_t remaining = concat.size(); for (int part = 0; part < 2; ++part) { printf(" Decoding part %d\n", part + 1); @@ -2132,12 +2257,12 @@ bool expect_success = (part == 0 || second_box_format == kCSBF_None || second_box_format == kCSBF_Single_Zero_Terminated || second_box_format == kCSBF_Multi_Zero_Terminated); std::vector pixels2 = jxl::DecodeWithAPI( - dec, jxl::Span(concat.data() + pos, remaining), - format, /*use_callback=*/false, /*set_buffer_early=*/true, + dec, jxl::Bytes(concat.data() + pos, remaining), format, + /*use_callback=*/false, /*set_buffer_early=*/true, /*use_resizable_runner=*/false, /*require_boxes=*/true, expect_success); EXPECT_EQ(num_pixels * channels * 4, pixels2.size()); remaining = JxlDecoderReleaseInput(dec); JxlDecoderDestroy(dec); @@ -2165,23 +2290,23 @@ } std::vector pixels2; pixels2.resize(pixels.size()); - jxl::PaddedBytes jpeg_output(64); + std::vector jpeg_output(64); size_t used_jpeg_output = 0; - std::vector codestreams(kCSBF_NUM_ENTRIES); - std::vector jpeg_codestreams(kCSBF_NUM_ENTRIES); + std::vector> codestreams(kCSBF_NUM_ENTRIES); + std::vector> jpeg_codestreams(kCSBF_NUM_ENTRIES); for (size_t i = 0; i < kCSBF_NUM_ENTRIES; ++i) { params.box_format = (CodeStreamBoxFormat)i; if (reconstructible_jpeg) { params.jpeg_codestream = &jpeg_codestreams[i]; } - codestreams[i] = jxl::CreateTestJXLCodestream( - jxl::Span(pixels.data(), pixels.size()), xsize, ysize, - channels, params); + codestreams[i] = + jxl::CreateTestJXLCodestream(jxl::Bytes(pixels.data(), pixels.size()), + xsize, ysize, channels, params); } // Test multiple step sizes, to test different combinations of the streaming // box parsing. std::vector increments = {1, 3, 17, 23, 120, 700, 1050}; @@ -2190,11 +2315,11 @@ for (size_t i = 0; i < kCSBF_NUM_ENTRIES; ++i) { if (reconstructible_jpeg && (CodeStreamBoxFormat)i == CodeStreamBoxFormat::kCSBF_None) { continue; } - const jxl::PaddedBytes& data = codestreams[i]; + const std::vector& data = codestreams[i]; const uint8_t* next_in = data.data(); size_t avail_in = 0; JxlDecoder* dec = JxlDecoderCreate(nullptr); @@ -2340,13 +2465,12 @@ JxlPixelFormat format_orig = {3, JXL_TYPE_UINT16, JXL_BIG_ENDIAN, 0}; for (jxl::PreviewMode mode : {jxl::kSmallPreview, jxl::kBigPreview}) { jxl::TestCodestreamParams params; params.preview_mode = mode; - jxl::PaddedBytes compressed = jxl::CreateTestJXLCodestream( - jxl::Span(pixels.data(), pixels.size()), xsize, ysize, 3, - params); + std::vector compressed = jxl::CreateTestJXLCodestream( + jxl::Bytes(pixels.data(), pixels.size()), xsize, ysize, 3, params); JxlPixelFormat format = {3, JXL_TYPE_UINT8, JXL_LITTLE_ENDIAN, 0}; JxlDecoder* dec = JxlDecoderCreate(NULL); const uint8_t* next_in = compressed.data(); @@ -2365,13 +2489,12 @@ JxlDecoderPreviewOutBufferSize(dec, &format, &buffer_size)); jxl::ColorEncoding c_srgb = jxl::ColorEncoding::SRGB(false); jxl::CodecInOut io0; EXPECT_TRUE(jxl::ConvertFromExternal( - jxl::Span(pixels.data(), pixels.size()), xsize, ysize, - c_srgb, /*bits_per_sample=*/16, format_orig, /*pool=*/nullptr, - &io0.Main())); + jxl::Bytes(pixels.data(), pixels.size()), xsize, ysize, c_srgb, + /*bits_per_sample=*/16, format_orig, /*pool=*/nullptr, &io0.Main())); GeneratePreview(params.preview_mode, &io0.Main()); size_t xsize_preview = io0.Main().xsize(); size_t ysize_preview = io0.Main().ysize(); EXPECT_EQ(xsize_preview, info.preview.xsize); @@ -2386,15 +2509,15 @@ preview.size())); EXPECT_EQ(JXL_DEC_PREVIEW_IMAGE, JxlDecoderProcessInput(dec)); jxl::CodecInOut io1; - EXPECT_TRUE(jxl::ConvertFromExternal( - jxl::Span(preview.data(), preview.size()), xsize_preview, - ysize_preview, c_srgb, - /*bits_per_sample=*/8, format, - /*pool=*/nullptr, &io1.Main())); + EXPECT_TRUE( + jxl::ConvertFromExternal(jxl::Bytes(preview.data(), preview.size()), + xsize_preview, ysize_preview, c_srgb, + /*bits_per_sample=*/8, format, + /*pool=*/nullptr, &io1.Main())); jxl::ButteraugliParams ba; // TODO(lode): this ButteraugliDistance silently returns 0 (dangerous for // tests) if xsize or ysize is < 8, no matter how different the images, a // tiny size that could happen for a preview. ButteraugliDiffmap does @@ -2417,23 +2540,22 @@ jxl::TestCodestreamParams params; // Lossless to verify pixels exactly after roundtrip. params.cparams.SetLossless(); params.cparams.speed_tier = jxl::SpeedTier::kThunder; - jxl::PaddedBytes compressed = jxl::CreateTestJXLCodestream( - jxl::Span(pixels.data(), pixels.size()), xsize, ysize, 4, - params); + std::vector compressed = jxl::CreateTestJXLCodestream( + jxl::Bytes(pixels.data(), pixels.size()), xsize, ysize, 4, params); size_t align = 17; JxlPixelFormat format = {3, JXL_TYPE_UINT8, JXL_LITTLE_ENDIAN, align}; // On purpose not using jxl::RoundUpTo to test it independently. size_t expected_line_bytes = (1 * 3 * xsize + align - 1) / align * align; for (int use_callback = 0; use_callback <= 1; ++use_callback) { std::vector pixels2 = jxl::DecodeWithAPI( - jxl::Span(compressed.data(), compressed.size()), format, - use_callback, /*set_buffer_early=*/false, + jxl::Bytes(compressed.data(), compressed.size()), format, use_callback, + /*set_buffer_early=*/false, /*use_resizable_runner=*/false, /*require_boxes=*/false, /*expect_success=*/true); EXPECT_EQ(expected_line_bytes * ysize, pixels2.size()); EXPECT_EQ(0u, jxl::test::ComparePixels(pixels.data(), pixels2.data(), xsize, ysize, format_orig, format)); @@ -2464,26 +2586,24 @@ 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), + jxl::Bytes(frames[i].data(), frames[i].size()), xsize, ysize, + jxl::ColorEncoding::SRGB(/*is_gray=*/false), /*bits_per_sample=*/16, format, /*pool=*/nullptr, &bundle)); bundle.duration = frame_durations[i]; io.frames.push_back(std::move(bundle)); } jxl::CompressParams cparams; cparams.SetLossless(); // Lossless to verify pixels exactly after roundtrip. cparams.speed_tier = jxl::SpeedTier::kThunder; - jxl::AuxOut aux_out; - jxl::PaddedBytes compressed; + std::vector compressed; jxl::PassesEncoderState enc_state; - EXPECT_TRUE(jxl::EncodeFile(cparams, &io, &enc_state, &compressed, - *JxlGetDefaultCms(), &aux_out, nullptr)); + EXPECT_TRUE(jxl::test::EncodeFile(cparams, &io, &enc_state, &compressed)); // Decode and test the animation frames JxlDecoder* dec = JxlDecoderCreate(NULL); const uint8_t* next_in = compressed.data(); @@ -2567,26 +2687,24 @@ 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), + jxl::Bytes(frames[i].data(), frames[i].size()), xsize, ysize, + jxl::ColorEncoding::SRGB(/*is_gray=*/false), /*bits_per_sample=*/16, format, /*pool=*/nullptr, &bundle)); bundle.duration = frame_durations[i]; io.frames.push_back(std::move(bundle)); } jxl::CompressParams cparams; cparams.SetLossless(); // Lossless to verify pixels exactly after roundtrip. cparams.speed_tier = jxl::SpeedTier::kThunder; - jxl::AuxOut aux_out; - jxl::PaddedBytes compressed; + std::vector compressed; jxl::PassesEncoderState enc_state; - EXPECT_TRUE(jxl::EncodeFile(cparams, &io, &enc_state, &compressed, - *JxlGetDefaultCms(), &aux_out, nullptr)); + EXPECT_TRUE(jxl::test::EncodeFile(cparams, &io, &enc_state, &compressed)); // Decode and test the animation frames const size_t step_size = 16; @@ -2685,13 +2803,12 @@ jxl::TestCodestreamParams params; // Lossless to verify pixels exactly after roundtrip. params.cparams.SetLossless(); params.cparams.speed_tier = jxl::SpeedTier::kThunder; - jxl::PaddedBytes compressed = jxl::CreateTestJXLCodestream( - jxl::Span(pixels.data(), pixels.size()), xsize, ysize, 4, - params); + std::vector compressed = jxl::CreateTestJXLCodestream( + jxl::Bytes(pixels.data(), pixels.size()), xsize, ysize, 4, params); size_t align = 17; JxlPixelFormat format = {3, JXL_TYPE_UINT8, JXL_LITTLE_ENDIAN, align}; JxlDecoder* dec = JxlDecoderCreate(NULL); @@ -2785,28 +2902,26 @@ // Mark some frames as referenceable, others not. 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), + jxl::Bytes(frames[i].data(), frames[i].size()), xsize, ysize, + jxl::ColorEncoding::SRGB(/*is_gray=*/false), /*bits_per_sample=*/16, format, /*pool=*/nullptr, &bundle)); bundle.duration = frame_durations[i]; io.frames.push_back(std::move(bundle)); } jxl::CompressParams cparams; cparams.speed_tier = jxl::SpeedTier::kThunder; - jxl::AuxOut aux_out; - jxl::PaddedBytes compressed; + std::vector compressed; jxl::PassesEncoderState enc_state; jxl::PassDefinition passes[] = {{2, 0, 4}, {4, 0, 4}, {8, 2, 2}, {8, 0, 1}}; jxl::ProgressiveMode progressive_mode{passes}; enc_state.progressive_splitter.SetProgressiveMode(progressive_mode); - EXPECT_TRUE(jxl::EncodeFile(cparams, &io, &enc_state, &compressed, - *JxlGetDefaultCms(), &aux_out, nullptr)); + EXPECT_TRUE(jxl::test::EncodeFile(cparams, &io, &enc_state, &compressed)); JxlDecoder* dec = JxlDecoderCreate(NULL); const uint8_t* next_in = compressed.data(); size_t avail_in = compressed.size(); @@ -2899,26 +3014,24 @@ // Mark some frames as referenceable, others not. 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), + jxl::Bytes(frames[i].data(), frames[i].size()), xsize, ysize, + jxl::ColorEncoding::SRGB(/*is_gray=*/false), /*bits_per_sample=*/16, format, /*pool=*/nullptr, &bundle)); bundle.duration = frame_durations[i]; io.frames.push_back(std::move(bundle)); } jxl::CompressParams cparams; cparams.SetLossless(); // Lossless to verify pixels exactly after roundtrip. cparams.speed_tier = jxl::SpeedTier::kThunder; - jxl::AuxOut aux_out; - jxl::PaddedBytes compressed; + std::vector compressed; jxl::PassesEncoderState enc_state; - EXPECT_TRUE(jxl::EncodeFile(cparams, &io, &enc_state, &compressed, - *JxlGetDefaultCms(), &aux_out, nullptr)); + EXPECT_TRUE(jxl::test::EncodeFile(cparams, &io, &enc_state, &compressed)); // Decode and test the animation frames JxlDecoder* dec = JxlDecoderCreate(NULL); const uint8_t* next_in = compressed.data(); @@ -3033,13 +3146,12 @@ // An internal frame with 0 duration, and use_for_next_frame, this is a // frame that is not rendered and not output by the API, but on which the // rendered frames depend 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), + jxl::Bytes(frame_internal.data(), frame_internal.size()), xsize, + ysize, jxl::ColorEncoding::SRGB(/*is_gray=*/false), /*bits_per_sample=*/16, format, /*pool=*/nullptr, &bundle_internal)); bundle_internal.duration = 0; bundle_internal.use_for_next_frame = true; io.frames.push_back(std::move(bundle_internal)); @@ -3048,15 +3160,15 @@ std::vector frame = jxl::test::GetSomeTestImage(xsize, ysize, 3, i * 2); // 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), - /*bits_per_sample=*/16, format, - /*pool=*/nullptr, &bundle)); + EXPECT_TRUE(ConvertFromExternal(jxl::Bytes(frame.data(), frame.size()), + xsize, ysize, + jxl::ColorEncoding::SRGB(/*is_gray=*/false), + /*bits_per_sample=*/16, format, + /*pool=*/nullptr, &bundle)); bundle.duration = frame_durations[i]; // Create some variation in which frames depend on which. if (i != 3 && i != 9 && i != 10) { bundle.use_for_next_frame = true; } @@ -3070,15 +3182,13 @@ } jxl::CompressParams cparams; cparams.SetLossless(); // Lossless to verify pixels exactly after roundtrip. cparams.speed_tier = jxl::SpeedTier::kThunder; - jxl::AuxOut aux_out; - jxl::PaddedBytes compressed; + std::vector compressed; jxl::PassesEncoderState enc_state; - EXPECT_TRUE(jxl::EncodeFile(cparams, &io, &enc_state, &compressed, - *JxlGetDefaultCms(), &aux_out, nullptr)); + EXPECT_TRUE(jxl::test::EncodeFile(cparams, &io, &enc_state, &compressed)); // Independently decode all frames without any skipping, to create the // expected blended frames, for the actual tests below to compare with. { JxlDecoder* dec = JxlDecoderCreate(NULL); @@ -3257,13 +3367,12 @@ // An internal frame with 0 duration, and use_for_next_frame, this is a // frame that is not rendered and not output by default by the API, but on // which the rendered frames depend 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), + jxl::Bytes(frame_internal.data(), frame_internal.size()), xsize / 2, + ysize / 2, jxl::ColorEncoding::SRGB(/*is_gray=*/false), /*bits_per_sample=*/16, format, /*pool=*/nullptr, &bundle_internal)); bundle_internal.duration = 0; bundle_internal.use_for_next_frame = true; bundle_internal.origin = {13, 17}; @@ -3277,15 +3386,15 @@ std::vector frame = 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), - /*bits_per_sample=*/16, format, - /*pool=*/nullptr, &bundle)); + EXPECT_TRUE(ConvertFromExternal(jxl::Bytes(frame.data(), frame.size()), + cropxsize, cropysize, + jxl::ColorEncoding::SRGB(/*is_gray=*/false), + /*bits_per_sample=*/16, format, + /*pool=*/nullptr, &bundle)); bundle.duration = 5 + i; frame_durations_nc.push_back(5 + i); frame_durations_c.push_back(5 + i); frame_xsize.push_back(cropxsize); frame_ysize.push_back(cropysize); @@ -3304,15 +3413,13 @@ } jxl::CompressParams cparams; cparams.SetLossless(); // Lossless to verify pixels exactly after roundtrip. cparams.speed_tier = jxl::SpeedTier::kThunder; - jxl::AuxOut aux_out; - jxl::PaddedBytes compressed; + std::vector compressed; jxl::PassesEncoderState enc_state; - EXPECT_TRUE(jxl::EncodeFile(cparams, &io, &enc_state, &compressed, - *JxlGetDefaultCms(), &aux_out, nullptr)); + EXPECT_TRUE(jxl::test::EncodeFile(cparams, &io, &enc_state, &compressed)); // try both with and without coalescing for (auto coalescing : {JXL_TRUE, JXL_FALSE}) { // Independently decode all frames without any skipping, to create the // expected blended frames, for the actual tests below to compare with. { @@ -3540,12 +3647,12 @@ std::vector frame = 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), + jxl::Bytes(frame.data(), frame.size()), cropxsize, cropysize, + jxl::ColorEncoding::SRGB(/*is_gray=*/false), /*bits_per_sample=*/16, format, /*pool=*/nullptr, &bundle)); bundle.origin = {cropx0, cropy0}; bundle.use_for_next_frame = true; io.frames.push_back(std::move(bundle)); @@ -3554,15 +3661,13 @@ jxl::CompressParams cparams; cparams .SetLossless(); // Lossless to verify pixels exactly after roundtrip. cparams.speed_tier = jxl::SpeedTier::kThunder; cparams.resampling = resampling; - jxl::AuxOut aux_out; - jxl::PaddedBytes compressed; + std::vector compressed; jxl::PassesEncoderState enc_state; - EXPECT_TRUE(jxl::EncodeFile(cparams, &io, &enc_state, &compressed, - *JxlGetDefaultCms(), &aux_out, nullptr)); + EXPECT_TRUE(jxl::test::EncodeFile(cparams, &io, &enc_state, &compressed)); // 0 is merged frame as decoded with coalescing enabled (default) // 1-3 are non-coalesced frames as decoded with coalescing disabled // 4 is the manually merged frame std::vector frames[5]; @@ -3673,11 +3778,11 @@ size_t jbrd_end = 0; std::vector box_start; std::vector frames; }; -void AnalyzeCodestream(const jxl::PaddedBytes& data, +void AnalyzeCodestream(const std::vector& data, StreamPositions* streampos) { // Unbox data to codestream and mark where it is broken up by boxes. std::vector codestream; std::vector> breakpoints; bool codestream_end = false; @@ -3725,25 +3830,22 @@ offset += breakpoints[bp++].second; } return pos + offset; }; // Analyze the unboxed codestream. - jxl::BitReader br( - jxl::Span(codestream.data(), codestream.size())); + jxl::BitReader br(jxl::Bytes(codestream.data(), codestream.size())); ASSERT_EQ(br.ReadFixedBits<16>(), 0x0AFF); jxl::CodecMetadata metadata; ASSERT_TRUE(ReadSizeHeader(&br, &metadata.size)); ASSERT_TRUE(ReadImageMetadata(&br, &metadata.m)); streampos->basic_info = add_offset(br.TotalBitsConsumed() / jxl::kBitsPerByte); metadata.transform_data.nonserialized_xyb_encoded = metadata.m.xyb_encoded; ASSERT_TRUE(jxl::Bundle::Read(&br, &metadata.transform_data)); if (metadata.m.color_encoding.WantICC()) { - jxl::PaddedBytes icc_data; - ASSERT_TRUE(jxl::ReadICC(&br, &icc_data)); - jxl::IccBytes icc; - jxl::Span(icc_data).AppendTo(&icc); + std::vector icc; + ASSERT_TRUE(jxl::test::ReadICC(&br, &icc)); ASSERT_TRUE(!icc.empty()); metadata.m.color_encoding.SetICCRaw(std::move(icc)); } ASSERT_TRUE(br.JumpToByteBoundary()); bool has_preview = metadata.m.have_preview; @@ -3758,13 +3860,13 @@ ASSERT_TRUE(ReadFrameHeader(&br, &frame_header)); p.header_end = add_offset(jxl::DivCeil(br.TotalBitsConsumed(), jxl::kBitsPerByte)); jxl::FrameDimensions frame_dim = frame_header.ToFrameDimensions(); uint64_t groups_total_size; - const size_t toc_entries = jxl::NumTocEntries( - frame_dim.num_groups, frame_dim.num_dc_groups, - frame_header.passes.num_passes, /*has_ac_global=*/true); + const size_t toc_entries = + jxl::NumTocEntries(frame_dim.num_groups, frame_dim.num_dc_groups, + frame_header.passes.num_passes); std::vector section_offsets; std::vector section_sizes; ASSERT_TRUE(ReadGroupOffsets(toc_entries, &br, §ion_offsets, §ion_sizes, &groups_total_size)); EXPECT_EQ(br.TotalBitsConsumed() % jxl::kBitsPerByte, 0); @@ -3788,11 +3890,11 @@ ExpectedFlushState expect_flush; }; void VerifyProgression(size_t xsize, size_t ysize, uint32_t num_channels, const std::vector& pixels, - const jxl::PaddedBytes& data, + const std::vector& data, std::vector breakpoints) { // Size large enough for multiple groups, required to have progressive stages. ASSERT_LT(256, xsize); ASSERT_LT(256, ysize); std::vector pixels2; @@ -3868,13 +3970,13 @@ std::vector pixels = jxl::test::GetSomeTestImage(xsize, ysize, num_channels, 0); jxl::TestCodestreamParams params; params.cparams.progressive_dc = 1; params.preview_mode = jxl::kSmallPreview; - jxl::PaddedBytes data = jxl::CreateTestJXLCodestream( - jxl::Span(pixels.data(), pixels.size()), xsize, ysize, - num_channels, params); + std::vector data = + jxl::CreateTestJXLCodestream(jxl::Bytes(pixels.data(), pixels.size()), + xsize, ysize, num_channels, params); StreamPositions streampos; AnalyzeCodestream(data, &streampos); const std::vector& fp = streampos.frames; // We have preview, dc frame and regular frame. EXPECT_EQ(3, fp.size()); @@ -3904,13 +4006,13 @@ jxl::test::GetSomeTestImage(xsize, ysize, num_channels, 0); jxl::TestCodestreamParams params; params.cparams.SetLossless(); params.cparams.speed_tier = jxl::SpeedTier::kThunder; params.cparams.responsive = 1; - jxl::PaddedBytes data = jxl::CreateTestJXLCodestream( - jxl::Span(pixels.data(), pixels.size()), xsize, ysize, - num_channels, params); + std::vector data = + jxl::CreateTestJXLCodestream(jxl::Bytes(pixels.data(), pixels.size()), + xsize, ysize, num_channels, params); StreamPositions streampos; AnalyzeCodestream(data, &streampos); const std::vector& fp = streampos.frames; // We have preview, dc frame and regular frame. EXPECT_EQ(1, fp.size()); @@ -3929,11 +4031,11 @@ {data.size() - 1, SAME_FLUSH}, // partial AC group 3 {data.size(), NEW_FLUSH}}; // full image VerifyProgression(xsize, ysize, num_channels, pixels, data, breakpoints); } -void VerifyFilePosition(size_t expected_pos, const jxl::PaddedBytes& data, +void VerifyFilePosition(size_t expected_pos, const std::vector& data, JxlDecoder* dec) { size_t remaining = JxlDecoderReleaseInput(dec); size_t pos = data.size() - remaining; EXPECT_EQ(expected_pos, pos); EXPECT_EQ(JXL_DEC_SUCCESS, @@ -3949,13 +4051,13 @@ printf("Testing with box format %d\n", i); jxl::TestCodestreamParams params; params.cparams.progressive_dc = 1; params.preview_mode = jxl::kSmallPreview; params.box_format = (CodeStreamBoxFormat)i; - jxl::PaddedBytes data = jxl::CreateTestJXLCodestream( - jxl::Span(pixels.data(), pixels.size()), xsize, ysize, - num_channels, params); + std::vector data = + jxl::CreateTestJXLCodestream(jxl::Bytes(pixels.data(), pixels.size()), + xsize, ysize, num_channels, params); JxlPixelFormat format = {num_channels, JXL_TYPE_UINT16, JXL_BIG_ENDIAN, 0}; StreamPositions streampos; AnalyzeCodestream(data, &streampos); const std::vector& fp = streampos.frames; // We have preview, dc frame and regular frame. @@ -4038,19 +4140,19 @@ size_t channels = 3; std::vector pixels = jxl::test::GetSomeTestImage(xsize, ysize, channels, /*seed=*/0); for (int i = 1; i < kCSBF_NUM_ENTRIES; ++i) { printf("Testing with box format %d\n", i); - jxl::PaddedBytes jpeg_codestream; + std::vector jpeg_codestream; jxl::TestCodestreamParams params; params.cparams.color_transform = jxl::ColorTransform::kNone; params.jpeg_codestream = &jpeg_codestream; params.preview_mode = jxl::kSmallPreview; params.box_format = (CodeStreamBoxFormat)i; - jxl::PaddedBytes data = jxl::CreateTestJXLCodestream( - jxl::Span(pixels.data(), pixels.size()), xsize, ysize, - channels, params); + std::vector data = + jxl::CreateTestJXLCodestream(jxl::Bytes(pixels.data(), pixels.size()), + xsize, ysize, channels, params); JxlPixelFormat format = {3, JXL_TYPE_UINT16, JXL_BIG_ENDIAN, 0}; StreamPositions streampos; AnalyzeCodestream(data, &streampos); const std::vector& fp = streampos.frames; // We have preview and regular frame. @@ -4133,13 +4235,13 @@ fflush(stdout); jxl::TestCodestreamParams params; params.cparams.progressive_dc = 1; params.box_format = (CodeStreamBoxFormat)i; params.preview_mode = jxl::kSmallPreview; - jxl::PaddedBytes data = jxl::CreateTestJXLCodestream( - jxl::Span(pixels.data(), pixels.size()), xsize, ysize, - num_channels, params); + std::vector data = + jxl::CreateTestJXLCodestream(jxl::Bytes(pixels.data(), pixels.size()), + xsize, ysize, num_channels, params); JxlPixelFormat format = {num_channels, JXL_TYPE_UINT16, JXL_BIG_ENDIAN, 0}; StreamPositions streampos; AnalyzeCodestream(data, &streampos); const std::vector& fp = streampos.frames; // We have preview, dc frame and regular frame. @@ -4228,13 +4330,13 @@ uint32_t num_channels = 3; std::vector pixels = jxl::test::GetSomeTestImage(xsize, ysize, num_channels, 0); jxl::TestCodestreamParams params; params.preview_mode = jxl::kSmallPreview; - jxl::PaddedBytes data = jxl::CreateTestJXLCodestream( - jxl::Span(pixels.data(), pixels.size()), xsize, ysize, - num_channels, params); + std::vector data = + jxl::CreateTestJXLCodestream(jxl::Bytes(pixels.data(), pixels.size()), + xsize, ysize, num_channels, params); JxlPixelFormat format = {num_channels, JXL_TYPE_UINT16, JXL_BIG_ENDIAN, 0}; std::vector pixels2; pixels2.resize(pixels.size()); @@ -4303,13 +4405,13 @@ uint32_t num_channels = 3; std::vector pixels = jxl::test::GetSomeTestImage(xsize, ysize, num_channels, 0); jxl::TestCodestreamParams params; params.preview_mode = jxl::kSmallPreview; - jxl::PaddedBytes data = jxl::CreateTestJXLCodestream( - jxl::Span(pixels.data(), pixels.size()), xsize, ysize, - num_channels, params); + std::vector data = + jxl::CreateTestJXLCodestream(jxl::Bytes(pixels.data(), pixels.size()), + xsize, ysize, num_channels, params); JxlPixelFormat format = {num_channels, JXL_TYPE_UINT16, JXL_BIG_ENDIAN, 0}; std::vector pixels2; pixels2.resize(pixels.size()); @@ -4389,13 +4491,13 @@ uint32_t num_channels = 4; std::vector pixels = jxl::test::GetSomeTestImage(xsize, ysize, num_channels, 0); jxl::TestCodestreamParams params; params.preview_mode = jxl::kSmallPreview; - jxl::PaddedBytes data = jxl::CreateTestJXLCodestream( - jxl::Span(pixels.data(), pixels.size()), xsize, ysize, - num_channels, params); + std::vector data = + jxl::CreateTestJXLCodestream(jxl::Bytes(pixels.data(), pixels.size()), + xsize, ysize, num_channels, params); JxlPixelFormat format = {num_channels, JXL_TYPE_UINT16, JXL_BIG_ENDIAN, 0}; std::vector pixels2; pixels2.resize(pixels.size()); @@ -4461,13 +4563,13 @@ jxl::test::GetSomeTestImage(xsize, ysize, num_channels, 0); jxl::TestCodestreamParams params; params.cparams.resampling = 2; params.cparams.ec_resampling = 4; params.preview_mode = jxl::kSmallPreview; - jxl::PaddedBytes data = jxl::CreateTestJXLCodestream( - jxl::Span(pixels.data(), pixels.size()), xsize, ysize, - num_channels, params); + std::vector data = + jxl::CreateTestJXLCodestream(jxl::Bytes(pixels.data(), pixels.size()), + xsize, ysize, num_channels, params); JxlPixelFormat format = {num_channels, JXL_TYPE_UINT16, JXL_BIG_ENDIAN, 0}; std::vector pixels2; pixels2.resize(pixels.size()); @@ -4537,13 +4639,13 @@ params.cparams.SetLossless(); params.cparams.speed_tier = jxl::SpeedTier::kThunder; params.cparams.responsive = 1; params.cparams.modular_group_size_shift = 1; params.preview_mode = jxl::kSmallPreview; - jxl::PaddedBytes data = jxl::CreateTestJXLCodestream( - jxl::Span(pixels.data(), pixels.size()), xsize, ysize, - num_channels, params); + std::vector data = + jxl::CreateTestJXLCodestream(jxl::Bytes(pixels.data(), pixels.size()), + xsize, ysize, num_channels, params); JxlPixelFormat format = {num_channels, JXL_TYPE_UINT16, JXL_BIG_ENDIAN, 0}; std::vector pixels2; pixels2.resize(pixels.size()); @@ -4632,12 +4734,11 @@ jxl::test::GetSomeTestImage(xsize, ysize, num_channels, 0); JxlPixelFormat format = {num_channels, JXL_TYPE_UINT16, JXL_BIG_ENDIAN, 0}; jxl::ColorEncoding color_encoding = jxl::ColorEncoding::SRGB(false); jxl::CodecInOut io; EXPECT_TRUE(jxl::ConvertFromExternal( - jxl::Span(pixels.data(), pixels.size()), xsize, ysize, - color_encoding, + jxl::Bytes(pixels.data(), pixels.size()), xsize, ysize, color_encoding, /*bits_per_sample=*/16, format, /*pool=*/nullptr, &io.Main())); jxl::TestCodestreamParams params; if (lossless) { params.cparams.SetLossless(); @@ -4647,13 +4748,13 @@ jxl::PassDefinition passes[] = { {2, 0, 4}, {4, 0, 4}, {8, 2, 2}, {8, 1, 2}, {8, 0, 1}}; const int kNumPasses = 5; jxl::ProgressiveMode progressive_mode{passes}; params.progressive_mode = &progressive_mode; - jxl::PaddedBytes data = jxl::CreateTestJXLCodestream( - jxl::Span(pixels.data(), pixels.size()), xsize, ysize, - num_channels, params); + std::vector data = + jxl::CreateTestJXLCodestream(jxl::Bytes(pixels.data(), pixels.size()), + xsize, ysize, num_channels, params); for (size_t increment : {(size_t)1, data.size()}) { printf( "Testing with single_group=%d, lossless=%d, " "num_channels=%d, prog_detail=%d, increment=%d\n", @@ -4749,12 +4850,12 @@ jxl::ButteraugliParams ba; std::vector distances(kNumPasses + 1); for (int p = 0;; p = next_pass(p)) { jxl::CodecInOut io1; EXPECT_TRUE(jxl::ConvertFromExternal( - jxl::Span(passes[p].data(), passes[p].size()), xsize, - ysize, color_encoding, + jxl::Bytes(passes[p].data(), passes[p].size()), xsize, ysize, + color_encoding, /*bits_per_sample=*/16, format, /*pool=*/nullptr, &io1.Main())); distances[p] = ButteraugliDistance( io.frames, io1.frames, ba, *JxlGetDefaultCms(), nullptr, nullptr); if (p == kNumPasses) break; @@ -4773,12 +4874,12 @@ } } } } -void VerifyJPEGReconstruction(const jxl::PaddedBytes& container, - const jxl::PaddedBytes& jpeg_bytes) { +void VerifyJPEGReconstruction(jxl::Span container, + jxl::Span jpeg_bytes) { JxlDecoderPtr dec = JxlDecoderMake(nullptr); EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderSubscribeEvents( dec.get(), JXL_DEC_JPEG_RECONSTRUCTION | JXL_DEC_FULL_IMAGE)); JxlDecoderSetInput(dec.get(), container.data(), container.size()); @@ -4809,28 +4910,26 @@ size_t xsize = 123; size_t ysize = 77; size_t channels = 3; std::vector pixels = jxl::test::GetSomeTestImage(xsize, ysize, channels, /*seed=*/0); - jxl::PaddedBytes jpeg_codestream; + std::vector jpeg_codestream; jxl::TestCodestreamParams params; params.cparams.color_transform = jxl::ColorTransform::kNone; params.box_format = kCSBF_Single; params.jpeg_codestream = &jpeg_codestream; params.preview_mode = jxl::kSmallPreview; - jxl::PaddedBytes compressed = jxl::CreateTestJXLCodestream( - jxl::Span(pixels.data(), pixels.size()), xsize, ysize, - channels, params); - VerifyJPEGReconstruction(compressed, jpeg_codestream); + std::vector compressed = jxl::CreateTestJXLCodestream( + jxl::Bytes(pixels.data(), pixels.size()), xsize, ysize, channels, params); + VerifyJPEGReconstruction(jxl::Bytes(compressed), jxl::Bytes(jpeg_codestream)); } TEST(DecodeTest, JXL_TRANSCODE_JPEG_TEST(JPEGReconstructionTest)) { const std::string jpeg_path = "jxl/flower/flower.png.im_q85_420.jpg"; - const jxl::PaddedBytes orig = jxl::test::ReadTestData(jpeg_path); + const std::vector orig = jxl::test::ReadTestData(jpeg_path); jxl::CodecInOut orig_io; - ASSERT_TRUE( - jxl::jpeg::DecodeImageJPG(jxl::Span(orig), &orig_io)); + ASSERT_TRUE(jxl::jpeg::DecodeImageJPG(jxl::Bytes(orig), &orig_io)); orig_io.metadata.m.xyb_encoded = false; jxl::BitWriter writer; ASSERT_TRUE(WriteCodestreamHeaders(&orig_io.metadata, &writer, nullptr)); writer.ZeroPadToByte(); jxl::PassesEncoderState enc_state; @@ -4839,41 +4938,40 @@ ASSERT_TRUE(jxl::EncodeFrame(cparams, jxl::FrameInfo{}, &orig_io.metadata, orig_io.Main(), &enc_state, *JxlGetDefaultCms(), /*pool=*/nullptr, &writer, /*aux_out=*/nullptr)); - jxl::PaddedBytes jpeg_data; + std::vector jpeg_data; ASSERT_TRUE( EncodeJPEGData(*orig_io.Main().jpeg_data.get(), &jpeg_data, cparams)); - jxl::PaddedBytes container; - container.append(jxl::kContainerHeader.begin(), jxl::kContainerHeader.end()); + std::vector container; + jxl::Bytes(jxl::kContainerHeader).AppendTo(&container); jxl::AppendBoxHeader(jxl::MakeBoxType("jbrd"), jpeg_data.size(), false, &container); - container.append(jpeg_data.data(), jpeg_data.data() + jpeg_data.size()); + jxl::Bytes(jpeg_data).AppendTo(&container); jxl::AppendBoxHeader(jxl::MakeBoxType("jxlc"), 0, true, &container); jxl::PaddedBytes codestream = std::move(writer).TakeBytes(); - container.append(codestream.data(), codestream.data() + codestream.size()); - VerifyJPEGReconstruction(container, orig); + jxl::Bytes(codestream).AppendTo(&container); + VerifyJPEGReconstruction(jxl::Bytes(container), jxl::Bytes(orig)); } TEST(DecodeTest, JXL_TRANSCODE_JPEG_TEST(JPEGReconstructionMetadataTest)) { const std::string jpeg_path = "jxl/jpeg_reconstruction/1x1_exif_xmp.jpg"; const std::string jxl_path = "jxl/jpeg_reconstruction/1x1_exif_xmp.jxl"; - const jxl::PaddedBytes jpeg = jxl::test::ReadTestData(jpeg_path); - const jxl::PaddedBytes jxl = jxl::test::ReadTestData(jxl_path); - VerifyJPEGReconstruction(jxl, jpeg); + const std::vector jpeg = jxl::test::ReadTestData(jpeg_path); + const std::vector jxl = jxl::test::ReadTestData(jxl_path); + VerifyJPEGReconstruction(jxl::Bytes(jxl), jxl::Bytes(jpeg)); } TEST(DecodeTest, ContinueFinalNonEssentialBoxTest) { size_t xsize = 80, ysize = 90; std::vector pixels = jxl::test::GetSomeTestImage(xsize, ysize, 4, 0); jxl::TestCodestreamParams params; params.box_format = kCSBF_Multi_Other_Terminated; params.add_icc_profile = true; - jxl::PaddedBytes compressed = jxl::CreateTestJXLCodestream( - jxl::Span(pixels.data(), pixels.size()), xsize, ysize, 4, - params); + std::vector compressed = jxl::CreateTestJXLCodestream( + jxl::Bytes(pixels.data(), pixels.size()), xsize, ysize, 4, params); StreamPositions streampos; AnalyzeCodestream(compressed, &streampos); // The non-essential final box size including 8-byte header size_t final_box_size = unk3_box_size + 8; @@ -4928,11 +5026,11 @@ } } // namespace TEST(DecodeTest, ExtentedBoxSizeTest) { const std::string jxl_path = "jxl/boxes/square-extended-size-container.jxl"; - const jxl::PaddedBytes orig = jxl::test::ReadTestData(jxl_path); + const std::vector orig = jxl::test::ReadTestData(jxl_path); JxlDecoder* dec = JxlDecoderCreate(nullptr); EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderSubscribeEvents(dec, JXL_DEC_BOX)); JxlBoxType type; @@ -4961,13 +5059,12 @@ size_t xsize = 1, ysize = 1; std::vector pixels = jxl::test::GetSomeTestImage(xsize, ysize, 4, 0); jxl::TestCodestreamParams params; params.box_format = kCSBF_Multi_Other_Terminated; params.add_icc_profile = true; - jxl::PaddedBytes compressed = jxl::CreateTestJXLCodestream( - jxl::Span(pixels.data(), pixels.size()), xsize, ysize, 4, - params); + std::vector compressed = jxl::CreateTestJXLCodestream( + jxl::Bytes(pixels.data(), pixels.size()), xsize, ysize, 4, params); JxlDecoder* dec = JxlDecoderCreate(nullptr); EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderSubscribeEvents(dec, JXL_DEC_BOX)); @@ -5036,13 +5133,12 @@ jxl::TestCodestreamParams params; // Lossless to verify pixels exactly after roundtrip. params.cparams.SetLossless(); params.box_format = kCSBF_Brob_Exif; params.add_icc_profile = true; - jxl::PaddedBytes compressed = jxl::CreateTestJXLCodestream( - jxl::Span(pixels.data(), pixels.size()), xsize, ysize, 4, - params); + std::vector compressed = jxl::CreateTestJXLCodestream( + jxl::Bytes(pixels.data(), pixels.size()), xsize, ysize, 4, params); // Test raw brob box, not brotli-decompressing for (int streaming = 0; streaming < 2; ++streaming) { JxlDecoder* dec = JxlDecoderCreate(nullptr); @@ -5220,13 +5316,12 @@ jxl::TestCodestreamParams params; params.cparams.SetLossless(); params.cparams.speed_tier = jxl::SpeedTier::kThunder; params.box_format = kCSBF_Multi; params.add_icc_profile = true; - jxl::PaddedBytes compressed = jxl::CreateTestJXLCodestream( - jxl::Span(pixels.data(), pixels.size()), xsize, ysize, 4, - params); + std::vector compressed = jxl::CreateTestJXLCodestream( + jxl::Bytes(pixels.data(), pixels.size()), xsize, ysize, 4, params); std::vector extracted_codestream; { JxlDecoder* dec = JxlDecoderCreate(nullptr); @@ -5365,11 +5460,10 @@ JxlDecoderDestroy(dec); } } TEST(DecodeTest, SpotColorTest) { - jxl::ThreadPool* pool = nullptr; jxl::CodecInOut io; size_t xsize = 55, ysize = 257; io.metadata.m.color_encoding = jxl::ColorEncoding::LinearSRGB(); jxl::Image3F main(xsize, ysize); jxl::ImageF spot(xsize, ysize); @@ -5403,15 +5497,15 @@ cparams.speed_tier = jxl::SpeedTier::kLightning; cparams.modular_mode = true; cparams.color_transform = jxl::ColorTransform::kNone; cparams.butteraugli_distance = 0.f; - jxl::PaddedBytes compressed; + std::vector compressed; std::unique_ptr enc_state = jxl::make_unique(); - EXPECT_TRUE(jxl::EncodeFile(cparams, &io, enc_state.get(), &compressed, - *JxlGetDefaultCms(), nullptr, pool)); + EXPECT_TRUE( + jxl::test::EncodeFile(cparams, &io, enc_state.get(), &compressed)); for (size_t render_spot = 0; render_spot < 2; render_spot++) { JxlPixelFormat format = {3, JXL_TYPE_UINT8, JXL_LITTLE_ENDIAN, 0}; JxlDecoder* dec = JxlDecoderCreate(NULL); diff --git a/third_party/jpeg-xl/lib/jxl/decode_to_jpeg.cc b/third_party/jpeg-xl/lib/jxl/decode_to_jpeg.cc --- a/third_party/jpeg-xl/lib/jxl/decode_to_jpeg.cc +++ b/third_party/jpeg-xl/lib/jxl/decode_to_jpeg.cc @@ -19,27 +19,27 @@ "box"); } Span to_decode; if (box_until_eof_) { // Until EOF means consume all data. - to_decode = Span(*next_in, *avail_in); + to_decode = Bytes(*next_in, *avail_in); *next_in += *avail_in; *avail_in = 0; } else { // Defined size means consume min(available, needed). size_t avail_recon_in = std::min(*avail_in, box_size_ - buffer_.size()); - to_decode = Span(*next_in, avail_recon_in); + to_decode = Bytes(*next_in, avail_recon_in); *next_in += avail_recon_in; *avail_in -= avail_recon_in; } bool old_data_exists = !buffer_.empty(); if (old_data_exists) { // Append incoming data to buffer if we already had data in the buffer. buffer_.insert(buffer_.end(), to_decode.data(), to_decode.data() + to_decode.size()); - to_decode = Span(buffer_.data(), buffer_.size()); + to_decode = Bytes(buffer_.data(), buffer_.size()); } if (!box_until_eof_ && to_decode.size() > box_size_) { JXL_UNREACHABLE("JPEG reconstruction data to decode larger than expected"); } if (box_until_eof_ || to_decode.size() == box_size_) { diff --git a/third_party/jpeg-xl/lib/jxl/enc_ac_strategy.h b/third_party/jpeg-xl/lib/jxl/enc_ac_strategy.h --- a/third_party/jpeg-xl/lib/jxl/enc_ac_strategy.h +++ b/third_party/jpeg-xl/lib/jxl/enc_ac_strategy.h @@ -4,18 +4,15 @@ // license that can be found in the LICENSE file. #ifndef LIB_JXL_ENC_AC_STRATEGY_H_ #define LIB_JXL_ENC_AC_STRATEGY_H_ -#include +#include -#include "lib/jxl/ac_strategy.h" -#include "lib/jxl/base/data_parallel.h" +#include "lib/jxl/base/compiler_specific.h" #include "lib/jxl/base/status.h" -#include "lib/jxl/dec_ans.h" #include "lib/jxl/enc_cache.h" -#include "lib/jxl/enc_params.h" #include "lib/jxl/image.h" #include "lib/jxl/quant_weights.h" // `FindBestAcStrategy` uses heuristics to choose which AC strategy should be // used in each block, as well as the initial quantization field. diff --git a/third_party/jpeg-xl/lib/jxl/enc_ans.h b/third_party/jpeg-xl/lib/jxl/enc_ans.h --- a/third_party/jpeg-xl/lib/jxl/enc_ans.h +++ b/third_party/jpeg-xl/lib/jxl/enc_ans.h @@ -7,26 +7,18 @@ #define LIB_JXL_ENC_ANS_H_ // Library to encode the ANS population counts to the bit-stream and encode // symbols based on the respective distributions. -#include -#include -#include -#include - -#include +#include +#include #include -#include "lib/jxl/ans_common.h" #include "lib/jxl/ans_params.h" -#include "lib/jxl/base/compiler_specific.h" -#include "lib/jxl/base/status.h" #include "lib/jxl/dec_ans.h" #include "lib/jxl/enc_ans_params.h" #include "lib/jxl/enc_bit_writer.h" -#include "lib/jxl/huffman_table.h" namespace jxl { struct AuxOut; diff --git a/third_party/jpeg-xl/lib/jxl/enc_bit_writer.h b/third_party/jpeg-xl/lib/jxl/enc_bit_writer.h --- a/third_party/jpeg-xl/lib/jxl/enc_bit_writer.h +++ b/third_party/jpeg-xl/lib/jxl/enc_bit_writer.h @@ -14,13 +14,13 @@ #include #include #include "lib/jxl/base/common.h" #include "lib/jxl/base/compiler_specific.h" -#include "lib/jxl/base/padded_bytes.h" #include "lib/jxl/base/span.h" #include "lib/jxl/base/status.h" +#include "lib/jxl/padded_bytes.h" namespace jxl { struct AuxOut; @@ -43,11 +43,11 @@ size_t BitsWritten() const { return bits_written_; } Span GetSpan() const { // Callers must ensure byte alignment to avoid uninitialized bits. JXL_ASSERT(bits_written_ % kBitsPerByte == 0); - return Span(storage_.data(), bits_written_ / kBitsPerByte); + return Bytes(storage_.data(), bits_written_ / kBitsPerByte); } // Example usage: bytes = std::move(writer).TakeBytes(); Useful for the // top-level encoder which returns PaddedBytes, not a BitWriter. // *this must be an rvalue reference and is invalid afterwards. diff --git a/third_party/jpeg-xl/lib/jxl/enc_cache.cc b/third_party/jpeg-xl/lib/jxl/enc_cache.cc --- a/third_party/jpeg-xl/lib/jxl/enc_cache.cc +++ b/third_party/jpeg-xl/lib/jxl/enc_cache.cc @@ -11,11 +11,10 @@ #include #include "lib/jxl/ac_strategy.h" #include "lib/jxl/base/common.h" #include "lib/jxl/base/compiler_specific.h" -#include "lib/jxl/base/padded_bytes.h" #include "lib/jxl/base/span.h" #include "lib/jxl/color_encoding_internal.h" #include "lib/jxl/compressed_dc.h" #include "lib/jxl/dct_scales.h" #include "lib/jxl/dct_util.h" @@ -112,13 +111,14 @@ // (but EncodeFrame will skip RGB->XYB conversion anyway) ib.SetFromImage( std::move(dc), ColorEncoding::LinearSRGB(shared.metadata->m.color_encoding.IsGray())); if (!ib.metadata()->extra_channel_info.empty()) { - // Add dummy extra channels to the patch image: dc_level frames do not yet - // support extra channels, but the codec expects that the amount of extra - // channels in frames matches that in the metadata of the codestream. + // Add placeholder extra channels to the patch image: dc_level frames do + // not yet support extra channels, but the codec expects that the amount + // of extra channels in frames matches that in the metadata of the + // codestream. std::vector extra_channels; extra_channels.reserve(ib.metadata()->extra_channel_info.size()); for (size_t i = 0; i < ib.metadata()->extra_channel_info.size(); i++) { extra_channels.emplace_back(ib.xsize(), ib.ysize()); // Must initialize the image with data to not affect blending with diff --git a/third_party/jpeg-xl/lib/jxl/enc_chroma_from_luma.h b/third_party/jpeg-xl/lib/jxl/enc_chroma_from_luma.h --- a/third_party/jpeg-xl/lib/jxl/enc_chroma_from_luma.h +++ b/third_party/jpeg-xl/lib/jxl/enc_chroma_from_luma.h @@ -7,27 +7,16 @@ #define LIB_JXL_ENC_CHROMA_FROM_LUMA_H_ // Chroma-from-luma, computed using heuristics to determine the best linear // model for the X and B channels from the Y channel. -#include -#include - +#include #include -#include -#include "lib/jxl/base/compiler_specific.h" -#include "lib/jxl/base/data_parallel.h" -#include "lib/jxl/base/status.h" +#include "lib/jxl/ac_strategy.h" #include "lib/jxl/chroma_from_luma.h" -#include "lib/jxl/dec_ans.h" -#include "lib/jxl/dec_bit_reader.h" -#include "lib/jxl/enc_ans.h" #include "lib/jxl/enc_bit_writer.h" -#include "lib/jxl/entropy_coder.h" -#include "lib/jxl/field_encodings.h" -#include "lib/jxl/fields.h" #include "lib/jxl/image.h" #include "lib/jxl/quant_weights.h" #include "lib/jxl/simd_util.h" namespace jxl { diff --git a/third_party/jpeg-xl/lib/jxl/enc_chroma_from_luma.cc b/third_party/jpeg-xl/lib/jxl/enc_chroma_from_luma.cc --- a/third_party/jpeg-xl/lib/jxl/enc_chroma_from_luma.cc +++ b/third_party/jpeg-xl/lib/jxl/enc_chroma_from_luma.cc @@ -18,16 +18,16 @@ #include #include #include "lib/jxl/base/bits.h" #include "lib/jxl/base/common.h" -#include "lib/jxl/base/padded_bytes.h" #include "lib/jxl/base/span.h" #include "lib/jxl/base/status.h" #include "lib/jxl/cms/opsin_params.h" #include "lib/jxl/dec_transforms-inl.h" #include "lib/jxl/enc_aux_out.h" +#include "lib/jxl/enc_params.h" #include "lib/jxl/enc_transforms-inl.h" #include "lib/jxl/entropy_coder.h" #include "lib/jxl/image_ops.h" #include "lib/jxl/modular/encoding/encoding.h" #include "lib/jxl/quantizer.h" diff --git a/third_party/jpeg-xl/lib/jxl/enc_coeff_order.cc b/third_party/jpeg-xl/lib/jxl/enc_coeff_order.cc --- a/third_party/jpeg-xl/lib/jxl/enc_coeff_order.cc +++ b/third_party/jpeg-xl/lib/jxl/enc_coeff_order.cc @@ -8,11 +8,10 @@ #include #include #include #include "lib/jxl/ans_params.h" -#include "lib/jxl/base/padded_bytes.h" #include "lib/jxl/base/span.h" #include "lib/jxl/coeff_order.h" #include "lib/jxl/coeff_order_fwd.h" #include "lib/jxl/dec_ans.h" #include "lib/jxl/dec_bit_reader.h" diff --git a/third_party/jpeg-xl/lib/jxl/enc_context_map.cc b/third_party/jpeg-xl/lib/jxl/enc_context_map.cc --- a/third_party/jpeg-xl/lib/jxl/enc_context_map.cc +++ b/third_party/jpeg-xl/lib/jxl/enc_context_map.cc @@ -69,23 +69,23 @@ } std::vector transformed_symbols = MoveToFrontTransform(context_map); std::vector> tokens(1), mtf_tokens(1); EntropyEncodingData codes; - std::vector dummy_context_map; + std::vector sink_context_map; for (size_t i = 0; i < context_map.size(); i++) { tokens[0].emplace_back(0, context_map[i]); } for (size_t i = 0; i < transformed_symbols.size(); i++) { mtf_tokens[0].emplace_back(0, transformed_symbols[i]); } HistogramParams params; params.uint_method = HistogramParams::HybridUintMethod::kContextMap; size_t ans_cost = BuildAndEncodeHistograms( - params, 1, tokens, &codes, &dummy_context_map, nullptr, 0, nullptr); + params, 1, tokens, &codes, &sink_context_map, nullptr, 0, nullptr); size_t mtf_cost = BuildAndEncodeHistograms( - params, 1, mtf_tokens, &codes, &dummy_context_map, nullptr, 0, nullptr); + params, 1, mtf_tokens, &codes, &sink_context_map, nullptr, 0, nullptr); bool use_mtf = mtf_cost < ans_cost; // Rebuild token list. tokens[0].clear(); for (size_t i = 0; i < transformed_symbols.size(); i++) { tokens[0].emplace_back(0, @@ -100,13 +100,13 @@ writer->Write(entry_bits, context_map[i]); } } else { writer->Write(1, 0); writer->Write(1, use_mtf); // Use/don't use MTF. - BuildAndEncodeHistograms(params, 1, tokens, &codes, &dummy_context_map, + BuildAndEncodeHistograms(params, 1, tokens, &codes, &sink_context_map, writer, layer, aux_out); - WriteTokens(tokens[0], codes, dummy_context_map, writer); + WriteTokens(tokens[0], codes, sink_context_map, writer); } } void EncodeBlockCtxMap(const BlockCtxMap& block_ctx_map, BitWriter* writer, AuxOut* aux_out) { diff --git a/third_party/jpeg-xl/lib/jxl/enc_debug_image.cc b/third_party/jpeg-xl/lib/jxl/enc_debug_image.cc --- a/third_party/jpeg-xl/lib/jxl/enc_debug_image.cc +++ b/third_party/jpeg-xl/lib/jxl/enc_debug_image.cc @@ -42,12 +42,11 @@ template void DumpImageT(const CompressParams& cparams, const char* label, const ColorEncoding& color_encoding, const Image3& image) { if (!cparams.debug_image) return; Image3F float_image = ConvertToFloat(image); - JxlColorEncoding color; - color_encoding.ToExternal(&color); + JxlColorEncoding color = color_encoding.ToExternal(); size_t num_pixels = 3 * image.xsize() * image.ysize(); std::vector pixels(num_pixels); const ImageF* channels[3]; for (int c = 0; c < 3; ++c) { channels[c] = &float_image.Plane(c); 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 @@ -11,11 +11,10 @@ #include #include #include #include "lib/jxl/base/data_parallel.h" -#include "lib/jxl/base/padded_bytes.h" #include "lib/jxl/base/status.h" #include "lib/jxl/color_encoding_internal.h" #include "lib/jxl/image.h" #include "lib/jxl/image_bundle.h" 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 @@ -156,23 +156,23 @@ Status BufferToImageF(const JxlPixelFormat& pixel_format, size_t xsize, size_t ysize, const void* buffer, size_t size, ThreadPool* pool, ImageF* channel) { size_t bitdepth = JxlDataTypeBytes(pixel_format.data_type) * kBitsPerByte; return ConvertFromExternal( - jxl::Span(static_cast(buffer), size), - xsize, ysize, bitdepth, pixel_format, 0, pool, channel); + jxl::Bytes(static_cast(buffer), size), xsize, ysize, + bitdepth, pixel_format, 0, pool, channel); } Status BufferToImageBundle(const JxlPixelFormat& pixel_format, uint32_t xsize, uint32_t ysize, const void* buffer, size_t size, jxl::ThreadPool* pool, const jxl::ColorEncoding& c_current, jxl::ImageBundle* ib) { size_t bitdepth = JxlDataTypeBytes(pixel_format.data_type) * kBitsPerByte; JXL_RETURN_IF_ERROR(ConvertFromExternal( - jxl::Span(static_cast(buffer), size), - xsize, ysize, c_current, bitdepth, pixel_format, pool, ib)); + jxl::Bytes(static_cast(buffer), size), xsize, ysize, + c_current, bitdepth, pixel_format, pool, ib)); 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 @@ -23,12 +23,11 @@ std::vector interleaved(xsize * ysize * 4); JxlPixelFormat format = {4, JXL_TYPE_UINT8, JXL_NATIVE_ENDIAN, 0}; for (auto _ : state) { for (size_t i = 0; i < kNumIter; ++i) { JXL_CHECK(ConvertFromExternal( - Span(interleaved.data(), interleaved.size()), xsize, - ysize, + Bytes(interleaved.data(), interleaved.size()), xsize, ysize, /*c_current=*/ColorEncoding::SRGB(), /*bits_per_sample=*/8, format, /*pool=*/nullptr, &ib)); } } 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 @@ -24,20 +24,20 @@ im.SetAlphaBits(8); ImageBundle ib(&im); JxlPixelFormat format = {4, JXL_TYPE_UINT16, JXL_BIG_ENDIAN, 0}; const uint8_t buf[10 * 100 * 8] = {}; + EXPECT_FALSE(ConvertFromExternal(Bytes(buf, 10), /*xsize=*/10, /*ysize=*/100, + /*c_current=*/ColorEncoding::SRGB(), + /*bits_per_sample=*/16, format, nullptr, + &ib)); EXPECT_FALSE(ConvertFromExternal( - Span(buf, 10), /*xsize=*/10, /*ysize=*/100, - /*c_current=*/ColorEncoding::SRGB(), - /*bits_per_sample=*/16, format, nullptr, &ib)); - EXPECT_FALSE(ConvertFromExternal( - Span(buf, sizeof(buf) - 1), /*xsize=*/10, /*ysize=*/100, + Bytes(buf, sizeof(buf) - 1), /*xsize=*/10, /*ysize=*/100, /*c_current=*/ColorEncoding::SRGB(), /*bits_per_sample=*/16, format, nullptr, &ib)); EXPECT_TRUE( - ConvertFromExternal(Span(buf, sizeof(buf)), /*xsize=*/10, + ConvertFromExternal(Bytes(buf, sizeof(buf)), /*xsize=*/10, /*ysize=*/100, /*c_current=*/ColorEncoding::SRGB(), /*bits_per_sample=*/16, format, nullptr, &ib)); } #endif @@ -51,12 +51,11 @@ const uint8_t buf[xsize * ysize * 4] = {}; JxlPixelFormat format = {4, JXL_TYPE_UINT8, JXL_BIG_ENDIAN, 0}; // has_alpha is true but the ImageBundle has no alpha. Alpha channel should // be ignored. - EXPECT_TRUE(ConvertFromExternal(Span(buf, sizeof(buf)), xsize, - ysize, + EXPECT_TRUE(ConvertFromExternal(Bytes(buf, sizeof(buf)), xsize, ysize, /*c_current=*/ColorEncoding::SRGB(), /*bits_per_sample=*/8, format, nullptr, &ib)); EXPECT_FALSE(ib.HasAlpha()); } diff --git a/third_party/jpeg-xl/lib/jxl/enc_fast_lossless.h b/third_party/jpeg-xl/lib/jxl/enc_fast_lossless.h --- a/third_party/jpeg-xl/lib/jxl/enc_fast_lossless.h +++ b/third_party/jpeg-xl/lib/jxl/enc_fast_lossless.h @@ -15,14 +15,36 @@ #else #define FJXL_STANDALONE 1 #endif #endif +#if !FJXL_STANDALONE +#include +#endif + #ifdef __cplusplus extern "C" { #endif +#if FJXL_STANDALONE +// Simplified version of the streaming input source from jxl/encode.h +// We only need this part to wrap the full image buffer in the standalone mode +// and this way we don't need to depend on the jxl headers. +struct JxlChunkedFrameInputSource { + void* opaque; + const void* (*get_color_channel_data_at)(void* opaque, size_t xpos, + size_t ypos, size_t xsize, + size_t ysize, size_t* row_offset); + void (*release_buffer)(void* opaque, const void* buf); +}; +// The standalone version does not use this struct, but we define it here so +// that we don't have to clutter all the function signatures with defines. +struct JxlEncoderOutputProcessorWrapper { + int unused; +}; +#endif + // Simple encoding API. // A FJxlParallelRunner must call fun(opaque, i) for all i from 0 to count. It // may do so in parallel. typedef void(FJxlParallelRunner)(void* runner_opaque, void* opaque, @@ -44,13 +66,21 @@ struct JxlFastLosslessFrameState; // Returned JxlFastLosslessFrameState must be freed by calling // JxlFastLosslessFreeFrameState. JxlFastLosslessFrameState* JxlFastLosslessPrepareFrame( - const unsigned char* rgba, size_t width, size_t row_stride, size_t height, - size_t nb_chans, size_t bitdepth, int big_endian, int effort, - void* runner_opaque, FJxlParallelRunner runner); + JxlChunkedFrameInputSource input, size_t width, size_t height, + size_t nb_chans, size_t bitdepth, int big_endian, int effort); + +#if !FJXL_STANDALONE +class JxlEncoderOutputProcessorWrapper; +#endif + +void JxlFastLosslessProcessFrame( + JxlFastLosslessFrameState* frame_state, bool is_last, void* runner_opaque, + FJxlParallelRunner runner, + JxlEncoderOutputProcessorWrapper* output_processor); // Prepare the (image/frame) header. You may encode animations by concatenating // the output of multiple frames, of which the first one has add_image_header = // 1 and subsequent ones have add_image_header = 0, and all frames but the last // one have is_last = 0. @@ -80,6 +110,12 @@ #ifdef __cplusplus } // extern "C" #endif +#if !FJXL_STANDALONE +void JxlFastLosslessOutputFrame( + JxlFastLosslessFrameState* frame_state, + JxlEncoderOutputProcessorWrapper* output_process); +#endif + #endif // LIB_JXL_ENC_FAST_LOSSLESS_H_ diff --git a/third_party/jpeg-xl/lib/jxl/enc_fast_lossless.cc b/third_party/jpeg-xl/lib/jxl/enc_fast_lossless.cc --- a/third_party/jpeg-xl/lib/jxl/enc_fast_lossless.cc +++ b/third_party/jpeg-xl/lib/jxl/enc_fast_lossless.cc @@ -15,10 +15,14 @@ #include #include #include #include +#if !FJXL_STANDALONE +#include "lib/jxl/encode_internal.h" +#endif + // Enable NEON and AVX2/AVX512 if not asked to do otherwise and the compilers // support it. #if defined(__aarch64__) || defined(_M_ARM64) #include @@ -168,303 +172,82 @@ size_t bytes_written = 0; size_t bits_in_buffer = 0; uint64_t buffer = 0; }; -} // namespace - -extern "C" { - -struct JxlFastLosslessFrameState { - size_t width; - size_t height; - size_t nb_chans; - size_t bitdepth; - BitWriter header; - std::vector> group_data; - size_t current_bit_writer = 0; - size_t bit_writer_byte_pos = 0; - size_t bits_in_buffer = 0; - uint64_t bit_buffer = 0; +size_t SectionSize(const std::array& group_data) { + size_t sz = 0; + for (size_t j = 0; j < 4; j++) { + const auto& writer = group_data[j]; + sz += writer.bytes_written * 8 + writer.bits_in_buffer; + } + sz = (sz + 7) / 8; + return sz; +} + +constexpr size_t kMaxFrameHeaderSize = 5; + +constexpr size_t kGroupSizeOffset[4] = { + static_cast(0), + static_cast(1024), + static_cast(17408), + static_cast(4211712), }; - -size_t JxlFastLosslessOutputSize(const JxlFastLosslessFrameState* frame) { - size_t total_size_groups = 0; - for (size_t i = 0; i < frame->group_data.size(); i++) { - size_t sz = 0; - for (size_t j = 0; j < frame->nb_chans; j++) { - const auto& writer = frame->group_data[i][j]; - sz += writer.bytes_written * 8 + writer.bits_in_buffer; - } - sz = (sz + 7) / 8; - total_size_groups += sz; - } - return frame->header.bytes_written + total_size_groups; +constexpr size_t kTOCBits[4] = {12, 16, 24, 32}; + +size_t TOCBucket(size_t group_size) { + size_t bucket = 0; + while (bucket < 3 && group_size >= kGroupSizeOffset[bucket + 1]) ++bucket; + return bucket; } -size_t JxlFastLosslessMaxRequiredOutput( - const JxlFastLosslessFrameState* frame) { - return JxlFastLosslessOutputSize(frame) + 32; +size_t TOCSize(const std::vector& group_sizes) { + size_t toc_bits = 0; + for (size_t i = 0; i < group_sizes.size(); i++) { + toc_bits += kTOCBits[TOCBucket(group_sizes[i])]; + } + return (toc_bits + 7) / 8; } -void JxlFastLosslessPrepareHeader(JxlFastLosslessFrameState* frame, - int add_image_header, int is_last) { - BitWriter* output = &frame->header; - output->Allocate(1000 + frame->group_data.size() * 32); - - std::vector group_sizes(frame->group_data.size()); - for (size_t i = 0; i < frame->group_data.size(); i++) { - size_t sz = 0; - for (size_t j = 0; j < frame->nb_chans; j++) { - const auto& writer = frame->group_data[i][j]; - sz += writer.bytes_written * 8 + writer.bits_in_buffer; - } - sz = (sz + 7) / 8; - group_sizes[i] = sz; - } - - bool have_alpha = (frame->nb_chans == 2 || frame->nb_chans == 4); - -#if FJXL_STANDALONE - if (add_image_header) { - // Signature - output->Write(16, 0x0AFF); - - // Size header, hand-crafted. - // Not small - output->Write(1, 0); - - auto wsz = [output](size_t size) { - if (size - 1 < (1 << 9)) { - output->Write(2, 0b00); - output->Write(9, size - 1); - } else if (size - 1 < (1 << 13)) { - output->Write(2, 0b01); - output->Write(13, size - 1); - } else if (size - 1 < (1 << 18)) { - output->Write(2, 0b10); - output->Write(18, size - 1); - } else { - output->Write(2, 0b11); - output->Write(30, size - 1); - } - }; - - wsz(frame->height); - - // No special ratio. - output->Write(3, 0); - - wsz(frame->width); - - // Hand-crafted ImageMetadata. - output->Write(1, 0); // all_default - output->Write(1, 0); // extra_fields - output->Write(1, 0); // bit_depth.floating_point_sample - if (frame->bitdepth == 8) { - output->Write(2, 0b00); // bit_depth.bits_per_sample = 8 - } else if (frame->bitdepth == 10) { - output->Write(2, 0b01); // bit_depth.bits_per_sample = 10 - } else if (frame->bitdepth == 12) { - output->Write(2, 0b10); // bit_depth.bits_per_sample = 12 - } else { - output->Write(2, 0b11); // 1 + u(6) - output->Write(6, frame->bitdepth - 1); - } - if (frame->bitdepth <= 14) { - output->Write(1, 1); // 16-bit-buffer sufficient - } else { - output->Write(1, 0); // 16-bit-buffer NOT sufficient - } - if (have_alpha) { - output->Write(2, 0b01); // One extra channel - output->Write(1, 1); // ... all_default (ie. 8-bit alpha) - } else { - output->Write(2, 0b00); // No extra channel - } - output->Write(1, 0); // Not XYB - if (frame->nb_chans > 2) { - output->Write(1, 1); // color_encoding.all_default (sRGB) - } else { - output->Write(1, 0); // color_encoding.all_default false - output->Write(1, 0); // color_encoding.want_icc false - output->Write(2, 1); // grayscale - output->Write(2, 1); // D65 - output->Write(1, 0); // no gamma transfer function - output->Write(2, 0b10); // tf: 2 + u(4) - output->Write(4, 11); // tf of sRGB - output->Write(2, 1); // relative rendering intent - } - output->Write(2, 0b00); // No extensions. - - output->Write(1, 1); // all_default transform data - - // No ICC, no preview. Frame should start at byte boundery. - output->ZeroPadToByte(); - } -#else - assert(!add_image_header); -#endif - - // Handcrafted frame header. - output->Write(1, 0); // all_default - output->Write(2, 0b00); // regular frame - output->Write(1, 1); // modular - output->Write(2, 0b00); // default flags - output->Write(1, 0); // not YCbCr - output->Write(2, 0b00); // no upsampling - if (have_alpha) { - output->Write(2, 0b00); // no alpha upsampling - } - output->Write(2, 0b01); // default group size - output->Write(2, 0b00); // exactly one pass - output->Write(1, 0); // no custom size or origin - output->Write(2, 0b00); // kReplace blending mode - if (have_alpha) { - output->Write(2, 0b00); // kReplace blending mode for alpha channel - } - output->Write(1, is_last); // is_last - output->Write(2, 0b00); // a frame has no name - output->Write(1, 0); // loop filter is not all_default - output->Write(1, 0); // no gaborish - output->Write(2, 0); // 0 EPF iters - output->Write(2, 0b00); // No LF extensions - output->Write(2, 0b00); // No FH extensions - - output->Write(1, 0); // No TOC permutation - output->ZeroPadToByte(); // TOC is byte-aligned. - for (size_t i = 0; i < frame->group_data.size(); i++) { - size_t sz = group_sizes[i]; - if (sz < (1 << 10)) { - output->Write(2, 0b00); - output->Write(10, sz); - } else if (sz - 1024 < (1 << 14)) { - output->Write(2, 0b01); - output->Write(14, sz - 1024); - } else if (sz - 17408 < (1 << 22)) { - output->Write(2, 0b10); - output->Write(22, sz - 17408); - } else { - output->Write(2, 0b11); - output->Write(30, sz - 4211712); - } - } - output->ZeroPadToByte(); // Groups are byte-aligned. +size_t FrameHeaderSize(bool have_alpha, bool is_last) { + size_t nbits = 28 + (have_alpha ? 4 : 0) + (is_last ? 0 : 2); + return (nbits + 7) / 8; } -#if FJXL_ENABLE_AVX512 -__attribute__((target("avx512vbmi2"))) static size_t AppendBytesWithBitOffset( - const uint8_t* data, size_t n, size_t bit_buffer_nbits, - unsigned char* output, uint64_t& bit_buffer) { - if (n < 128) { - return 0; - } - - size_t i = 0; - __m512i shift = _mm512_set1_epi64(64 - bit_buffer_nbits); - __m512i carry = _mm512_set1_epi64(bit_buffer << (64 - bit_buffer_nbits)); - - for (; i + 64 <= n; i += 64) { - __m512i current = _mm512_loadu_si512(data + i); - __m512i previous_u64 = _mm512_alignr_epi64(current, carry, 7); - carry = current; - __m512i out = _mm512_shrdv_epi64(previous_u64, current, shift); - _mm512_storeu_si512(output + i, out); - } - - bit_buffer = data[i - 1] >> (8 - bit_buffer_nbits); - - return i; +void ComputeAcGroupDataOffset(size_t dc_global_size, size_t num_dc_groups, + size_t num_ac_groups, size_t& min_dc_global_size, + size_t& ac_group_offset) { + // Max AC group size is 768 kB, so max AC group TOC bits is 24. + size_t ac_toc_max_bits = num_ac_groups * 24; + size_t ac_toc_min_bits = num_ac_groups * 12; + size_t max_padding = 1 + (ac_toc_max_bits - ac_toc_min_bits + 7) / 8; + min_dc_global_size = dc_global_size; + size_t dc_global_bucket = TOCBucket(min_dc_global_size); + while (TOCBucket(min_dc_global_size + max_padding) > dc_global_bucket) { + dc_global_bucket = TOCBucket(min_dc_global_size + max_padding); + min_dc_global_size = kGroupSizeOffset[dc_global_bucket]; + } + assert(TOCBucket(min_dc_global_size) == dc_global_bucket); + assert(TOCBucket(min_dc_global_size + max_padding) == dc_global_bucket); + size_t max_toc_bits = + kTOCBits[dc_global_bucket] + 12 * (1 + num_dc_groups) + ac_toc_max_bits; + size_t max_toc_size = (max_toc_bits + 7) / 8; + ac_group_offset = kMaxFrameHeaderSize + max_toc_size + min_dc_global_size; } -#endif - -size_t JxlFastLosslessWriteOutput(JxlFastLosslessFrameState* frame, - unsigned char* output, size_t output_size) { - assert(output_size >= 32); - unsigned char* initial_output = output; - size_t (*append_bytes_with_bit_offset)(const uint8_t*, size_t, size_t, - unsigned char*, uint64_t&) = nullptr; - -#if FJXL_ENABLE_AVX512 - if (__builtin_cpu_supports("avx512vbmi2")) { - append_bytes_with_bit_offset = AppendBytesWithBitOffset; - } -#endif - - while (true) { - size_t& cur = frame->current_bit_writer; - size_t& bw_pos = frame->bit_writer_byte_pos; - if (cur >= 1 + frame->group_data.size() * frame->nb_chans) { - return output - initial_output; - } - if (output_size <= 8) { - return output - initial_output; - } - size_t nbc = frame->nb_chans; - const BitWriter& writer = - cur == 0 ? frame->header - : frame->group_data[(cur - 1) / nbc][(cur - 1) % nbc]; - size_t full_byte_count = - std::min(output_size - 8, writer.bytes_written - bw_pos); - if (frame->bits_in_buffer == 0) { - memcpy(output, writer.data.get() + bw_pos, full_byte_count); - } else { - size_t i = 0; - if (append_bytes_with_bit_offset) { - i += append_bytes_with_bit_offset( - writer.data.get() + bw_pos, full_byte_count, frame->bits_in_buffer, - output, frame->bit_buffer); - } -#if defined(__BYTE_ORDER__) && (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) - // Copy 8 bytes at a time until we reach the border. - for (; i + 8 < full_byte_count; i += 8) { - uint64_t chunk; - memcpy(&chunk, writer.data.get() + bw_pos + i, 8); - uint64_t out = frame->bit_buffer | (chunk << frame->bits_in_buffer); - memcpy(output + i, &out, 8); - frame->bit_buffer = chunk >> (64 - frame->bits_in_buffer); - } -#endif - for (; i < full_byte_count; i++) { - AddBits(8, writer.data.get()[bw_pos + i], output + i, - frame->bits_in_buffer, frame->bit_buffer); - } - } - output += full_byte_count; - output_size -= full_byte_count; - bw_pos += full_byte_count; - if (bw_pos == writer.bytes_written) { - auto write = [&](size_t num, uint64_t bits) { - size_t n = AddBits(num, bits, output, frame->bits_in_buffer, - frame->bit_buffer); - output += n; - output_size -= n; - }; - if (writer.bits_in_buffer) { - write(writer.bits_in_buffer, writer.buffer); - } - bw_pos = 0; - cur++; - if ((cur - 1) % nbc == 0 && frame->bits_in_buffer != 0) { - write(8 - frame->bits_in_buffer, 0); - } - } - } + +size_t ComputeDcGlobalPadding(const std::vector& group_sizes, + size_t ac_group_data_offset, + size_t min_dc_global_size, bool have_alpha, + bool is_last) { + std::vector new_group_sizes = group_sizes; + new_group_sizes[0] = min_dc_global_size; + size_t toc_size = TOCSize(new_group_sizes); + size_t actual_offset = + FrameHeaderSize(have_alpha, is_last) + toc_size + group_sizes[0]; + return ac_group_data_offset - actual_offset; } -void JxlFastLosslessFreeFrameState(JxlFastLosslessFrameState* frame) { - delete frame; -} - -} // extern "C" - -#endif - -#ifdef FJXL_SELF_INCLUDE - -namespace { - constexpr size_t kNumRawSymbols = 19; constexpr size_t kNumLZ77 = 33; constexpr size_t kLZ77CacheSize = 32; constexpr size_t kLZ77Offset = 224; @@ -481,19 +264,18 @@ struct PrefixCode { uint8_t raw_nbits[kNumRawSymbols] = {}; uint8_t raw_bits[kNumRawSymbols] = {}; - alignas(64) uint8_t raw_nbits_simd[16] = {}; - alignas(64) uint8_t raw_bits_simd[16] = {}; - uint8_t lz77_nbits[kNumLZ77] = {}; uint16_t lz77_bits[kNumLZ77] = {}; uint64_t lz77_cache_bits[kLZ77CacheSize] = {}; uint8_t lz77_cache_nbits[kLZ77CacheSize] = {}; + size_t numraw; + static uint16_t BitReverse(size_t nbits, uint16_t bits) { constexpr uint16_t kNibbleLookup[16] = { 0b0000, 0b1000, 0b0100, 0b1100, 0b0010, 0b1010, 0b0110, 0b1110, 0b0001, 0b1001, 0b0101, 0b1101, 0b0011, 0b1011, 0b0111, 0b1111, }; @@ -652,11 +434,11 @@ PrefixCode(BitDepth, uint64_t* raw_counts, uint64_t* lz77_counts) { // "merge" together all the lz77 counts in a single symbol for the level 1 // table (containing just the raw symbols, up to length 7). uint64_t level1_counts[kNumRawSymbols + 1]; memcpy(level1_counts, raw_counts, kNumRawSymbols * sizeof(uint64_t)); - size_t numraw = kNumRawSymbols; + numraw = kNumRawSymbols; while (numraw > 0 && level1_counts[numraw - 1] == 0) numraw--; level1_counts[numraw] = 0; for (size_t i = 0; i < kNumLZ77; i++) { level1_counts[numraw] += lz77_counts[i]; @@ -684,12 +466,10 @@ level2_nbits[i] ? level1_nbits[numraw] + level2_nbits[i] : 0; } ComputeCanonicalCode(raw_nbits, raw_bits, numraw, lz77_nbits, lz77_bits, kNumLZ77); - BitDepth::PrepareForSimd(raw_nbits, raw_bits, numraw, raw_nbits_simd, - raw_bits_simd); // Prepare lz77 cache for (size_t count = 0; count < kLZ77CacheSize; count++) { unsigned token, nbits, bits; EncodeHybridUintLZ77(count, &token, &nbits, &bits); @@ -698,10 +478,11 @@ (((bits << lz77_nbits[token]) | lz77_bits[token]) << raw_nbits[0]) | raw_bits[0]; } } + // Max bits written: 2 + 72 + 95 + 24 + 165 = 286 void WriteTo(BitWriter* writer) const { uint64_t code_length_counts[18] = {}; code_length_counts[17] = 3 + 2 * (kNumLZ77 - 1); for (size_t i = 0; i < kNumRawSymbols; i++) { code_length_counts[raw_nbits[i]]++; @@ -727,10 +508,11 @@ // Encode lengths of code lengths. size_t num_code_lengths = 18; while (code_length_nbits[code_length_order[num_code_lengths - 1]] == 0) { num_code_lengths--; } + // Max bits written in this loop: 18 * 4 = 72 for (size_t i = 0; i < num_code_lengths; i++) { int symbol = code_length_nbits[code_length_order[i]]; writer->Write(code_length_length_nbits[symbol], code_length_length_bits[symbol]); } @@ -739,10 +521,11 @@ // the actual codes for data. uint16_t code_length_bits[18] = {}; ComputeCanonicalCode(nullptr, nullptr, 0, code_length_nbits, code_length_bits, 18); // Encode raw bit code lengths. + // Max bits written in this loop: 19 * 5 = 95 for (size_t i = 0; i < kNumRawSymbols; i++) { writer->Write(code_length_nbits[raw_nbits[i]], code_length_bits[raw_nbits[i]]); } size_t num_lz77 = kNumLZ77; @@ -751,24 +534,339 @@ } // Encode 0s until 224 (start of LZ77 symbols). This is in total 224-19 = // 205. static_assert(kLZ77Offset == 224, ""); static_assert(kNumRawSymbols == 19, ""); - writer->Write(code_length_nbits[17], code_length_bits[17]); - writer->Write(3, 0b010); // 5 - writer->Write(code_length_nbits[17], code_length_bits[17]); - writer->Write(3, 0b000); // (5-2)*8 + 3 = 27 - writer->Write(code_length_nbits[17], code_length_bits[17]); - writer->Write(3, 0b010); // (27-2)*8 + 5 = 205 + { + // Max bits in this block: 24 + writer->Write(code_length_nbits[17], code_length_bits[17]); + writer->Write(3, 0b010); // 5 + writer->Write(code_length_nbits[17], code_length_bits[17]); + writer->Write(3, 0b000); // (5-2)*8 + 3 = 27 + writer->Write(code_length_nbits[17], code_length_bits[17]); + writer->Write(3, 0b010); // (27-2)*8 + 5 = 205 + } // Encode LZ77 symbols, with values 224+i. + // Max bits written in this loop: 33 * 5 = 165 for (size_t i = 0; i < num_lz77; i++) { writer->Write(code_length_nbits[lz77_nbits[i]], code_length_bits[lz77_nbits[i]]); } } }; +} // namespace + +extern "C" { + +struct JxlFastLosslessFrameState { + JxlChunkedFrameInputSource input; + size_t width; + size_t height; + size_t num_groups_x; + size_t num_groups_y; + size_t num_dc_groups_x; + size_t num_dc_groups_y; + size_t nb_chans; + size_t bitdepth; + int big_endian; + int effort; + bool collided; + PrefixCode hcode[4]; + std::vector lookup; + BitWriter header; + std::vector> group_data; + std::vector group_sizes; + size_t ac_group_data_offset = 0; + size_t min_dc_global_size = 0; + size_t current_bit_writer = 0; + size_t bit_writer_byte_pos = 0; + size_t bits_in_buffer = 0; + uint64_t bit_buffer = 0; + bool process_done = false; +}; + +size_t JxlFastLosslessOutputSize(const JxlFastLosslessFrameState* frame) { + size_t total_size_groups = 0; + for (size_t i = 0; i < frame->group_data.size(); i++) { + total_size_groups += SectionSize(frame->group_data[i]); + } + return frame->header.bytes_written + total_size_groups; +} + +size_t JxlFastLosslessMaxRequiredOutput( + const JxlFastLosslessFrameState* frame) { + return JxlFastLosslessOutputSize(frame) + 32; +} + +void JxlFastLosslessPrepareHeader(JxlFastLosslessFrameState* frame, + int add_image_header, int is_last) { + BitWriter* output = &frame->header; + output->Allocate(1000 + frame->group_sizes.size() * 32); + + bool have_alpha = (frame->nb_chans == 2 || frame->nb_chans == 4); + +#if FJXL_STANDALONE + if (add_image_header) { + // Signature + output->Write(16, 0x0AFF); + + // Size header, hand-crafted. + // Not small + output->Write(1, 0); + + auto wsz = [output](size_t size) { + if (size - 1 < (1 << 9)) { + output->Write(2, 0b00); + output->Write(9, size - 1); + } else if (size - 1 < (1 << 13)) { + output->Write(2, 0b01); + output->Write(13, size - 1); + } else if (size - 1 < (1 << 18)) { + output->Write(2, 0b10); + output->Write(18, size - 1); + } else { + output->Write(2, 0b11); + output->Write(30, size - 1); + } + }; + + wsz(frame->height); + + // No special ratio. + output->Write(3, 0); + + wsz(frame->width); + + // Hand-crafted ImageMetadata. + output->Write(1, 0); // all_default + output->Write(1, 0); // extra_fields + output->Write(1, 0); // bit_depth.floating_point_sample + if (frame->bitdepth == 8) { + output->Write(2, 0b00); // bit_depth.bits_per_sample = 8 + } else if (frame->bitdepth == 10) { + output->Write(2, 0b01); // bit_depth.bits_per_sample = 10 + } else if (frame->bitdepth == 12) { + output->Write(2, 0b10); // bit_depth.bits_per_sample = 12 + } else { + output->Write(2, 0b11); // 1 + u(6) + output->Write(6, frame->bitdepth - 1); + } + if (frame->bitdepth <= 14) { + output->Write(1, 1); // 16-bit-buffer sufficient + } else { + output->Write(1, 0); // 16-bit-buffer NOT sufficient + } + if (have_alpha) { + output->Write(2, 0b01); // One extra channel + output->Write(1, 1); // ... all_default (ie. 8-bit alpha) + } else { + output->Write(2, 0b00); // No extra channel + } + output->Write(1, 0); // Not XYB + if (frame->nb_chans > 2) { + output->Write(1, 1); // color_encoding.all_default (sRGB) + } else { + output->Write(1, 0); // color_encoding.all_default false + output->Write(1, 0); // color_encoding.want_icc false + output->Write(2, 1); // grayscale + output->Write(2, 1); // D65 + output->Write(1, 0); // no gamma transfer function + output->Write(2, 0b10); // tf: 2 + u(4) + output->Write(4, 11); // tf of sRGB + output->Write(2, 1); // relative rendering intent + } + output->Write(2, 0b00); // No extensions. + + output->Write(1, 1); // all_default transform data + + // No ICC, no preview. Frame should start at byte boundery. + output->ZeroPadToByte(); + } +#else + assert(!add_image_header); +#endif + // Handcrafted frame header. + output->Write(1, 0); // all_default + output->Write(2, 0b00); // regular frame + output->Write(1, 1); // modular + output->Write(2, 0b00); // default flags + output->Write(1, 0); // not YCbCr + output->Write(2, 0b00); // no upsampling + if (have_alpha) { + output->Write(2, 0b00); // no alpha upsampling + } + output->Write(2, 0b01); // default group size + output->Write(2, 0b00); // exactly one pass + output->Write(1, 0); // no custom size or origin + output->Write(2, 0b00); // kReplace blending mode + if (have_alpha) { + output->Write(2, 0b00); // kReplace blending mode for alpha channel + } + output->Write(1, is_last); // is_last + if (!is_last) { + output->Write(2, 0b00); // can not be saved as reference + } + output->Write(2, 0b00); // a frame has no name + output->Write(1, 0); // loop filter is not all_default + output->Write(1, 0); // no gaborish + output->Write(2, 0); // 0 EPF iters + output->Write(2, 0b00); // No LF extensions + output->Write(2, 0b00); // No FH extensions + + output->Write(1, 0); // No TOC permutation + output->ZeroPadToByte(); // TOC is byte-aligned. + assert(add_image_header || output->bytes_written <= kMaxFrameHeaderSize); + for (size_t i = 0; i < frame->group_sizes.size(); i++) { + size_t sz = frame->group_sizes[i]; + size_t bucket = TOCBucket(sz); + output->Write(2, bucket); + output->Write(kTOCBits[bucket] - 2, sz - kGroupSizeOffset[bucket]); + } + output->ZeroPadToByte(); // Groups are byte-aligned. +} + +#if !FJXL_STANDALONE +void JxlFastLosslessOutputAlignedSection( + const BitWriter& bw, JxlEncoderOutputProcessorWrapper* output_processor) { + assert(bw.bits_in_buffer == 0); + const uint8_t* data = bw.data.get(); + size_t remaining_len = bw.bytes_written; + while (remaining_len > 0) { + auto retval = output_processor->GetBuffer(1, remaining_len); + assert(retval.status()); + auto buffer = std::move(retval).value(); + size_t n = std::min(buffer.size(), remaining_len); + if (n == 0) break; + memcpy(buffer.data(), data, n); + buffer.advance(n); + data += n; + remaining_len -= n; + }; +} + +void JxlFastLosslessOutputHeaders( + JxlFastLosslessFrameState* frame_state, + JxlEncoderOutputProcessorWrapper* output_processor) { + JxlFastLosslessOutputAlignedSection(frame_state->header, output_processor); + JxlFastLosslessOutputAlignedSection(frame_state->group_data[0][0], + output_processor); +} +#endif + +#if FJXL_ENABLE_AVX512 +__attribute__((target("avx512vbmi2"))) static size_t AppendBytesWithBitOffset( + const uint8_t* data, size_t n, size_t bit_buffer_nbits, + unsigned char* output, uint64_t& bit_buffer) { + if (n < 128) { + return 0; + } + + size_t i = 0; + __m512i shift = _mm512_set1_epi64(64 - bit_buffer_nbits); + __m512i carry = _mm512_set1_epi64(bit_buffer << (64 - bit_buffer_nbits)); + + for (; i + 64 <= n; i += 64) { + __m512i current = _mm512_loadu_si512(data + i); + __m512i previous_u64 = _mm512_alignr_epi64(current, carry, 7); + carry = current; + __m512i out = _mm512_shrdv_epi64(previous_u64, current, shift); + _mm512_storeu_si512(output + i, out); + } + + bit_buffer = data[i - 1] >> (8 - bit_buffer_nbits); + + return i; +} +#endif + +size_t JxlFastLosslessWriteOutput(JxlFastLosslessFrameState* frame, + unsigned char* output, size_t output_size) { + assert(output_size >= 32); + unsigned char* initial_output = output; + size_t (*append_bytes_with_bit_offset)(const uint8_t*, size_t, size_t, + unsigned char*, uint64_t&) = nullptr; + +#if FJXL_ENABLE_AVX512 + if (__builtin_cpu_supports("avx512vbmi2")) { + append_bytes_with_bit_offset = AppendBytesWithBitOffset; + } +#endif + + while (true) { + size_t& cur = frame->current_bit_writer; + size_t& bw_pos = frame->bit_writer_byte_pos; + if (cur >= 1 + frame->group_data.size() * frame->nb_chans) { + return output - initial_output; + } + if (output_size <= 9) { + return output - initial_output; + } + size_t nbc = frame->nb_chans; + const BitWriter& writer = + cur == 0 ? frame->header + : frame->group_data[(cur - 1) / nbc][(cur - 1) % nbc]; + size_t full_byte_count = + std::min(output_size - 9, writer.bytes_written - bw_pos); + if (frame->bits_in_buffer == 0) { + memcpy(output, writer.data.get() + bw_pos, full_byte_count); + } else { + size_t i = 0; + if (append_bytes_with_bit_offset) { + i += append_bytes_with_bit_offset( + writer.data.get() + bw_pos, full_byte_count, frame->bits_in_buffer, + output, frame->bit_buffer); + } +#if defined(__BYTE_ORDER__) && (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) + // Copy 8 bytes at a time until we reach the border. + for (; i + 8 < full_byte_count; i += 8) { + uint64_t chunk; + memcpy(&chunk, writer.data.get() + bw_pos + i, 8); + uint64_t out = frame->bit_buffer | (chunk << frame->bits_in_buffer); + memcpy(output + i, &out, 8); + frame->bit_buffer = chunk >> (64 - frame->bits_in_buffer); + } +#endif + for (; i < full_byte_count; i++) { + AddBits(8, writer.data.get()[bw_pos + i], output + i, + frame->bits_in_buffer, frame->bit_buffer); + } + } + output += full_byte_count; + output_size -= full_byte_count; + bw_pos += full_byte_count; + if (bw_pos == writer.bytes_written) { + auto write = [&](size_t num, uint64_t bits) { + size_t n = AddBits(num, bits, output, frame->bits_in_buffer, + frame->bit_buffer); + output += n; + output_size -= n; + }; + if (writer.bits_in_buffer) { + write(writer.bits_in_buffer, writer.buffer); + } + bw_pos = 0; + cur++; + if ((cur - 1) % nbc == 0 && frame->bits_in_buffer != 0) { + write(8 - frame->bits_in_buffer, 0); + } + } + } +} + +void JxlFastLosslessFreeFrameState(JxlFastLosslessFrameState* frame) { + delete frame; +} + +} // extern "C" + +#endif + +#ifdef FJXL_SELF_INCLUDE + +namespace { + template struct VecPair { T low; T hi; }; @@ -2104,51 +2202,55 @@ bits_lo.Store(bits_out); bits_hi.Store(bits_out + SIMDVec32::kLanes); } FJXL_INLINE void HuffmanSIMDUpTo13(const uint16_t* tokens, - const PrefixCode& code, uint16_t* nbits_out, - uint16_t* bits_out) { + const uint8_t* raw_nbits_simd, + const uint8_t* raw_bits_simd, + uint16_t* nbits_out, uint16_t* bits_out) { SIMDVec16 tok = SIMDVec16::Load(tokens).PrepareForU8Lookup(); - tok.U8Lookup(code.raw_nbits_simd).Store(nbits_out); - tok.U8Lookup(code.raw_bits_simd).Store(bits_out); + tok.U8Lookup(raw_nbits_simd).Store(nbits_out); + tok.U8Lookup(raw_bits_simd).Store(bits_out); } -FJXL_INLINE void HuffmanSIMD14(const uint16_t* tokens, const PrefixCode& code, +FJXL_INLINE void HuffmanSIMD14(const uint16_t* tokens, + const uint8_t* raw_nbits_simd, + const uint8_t* raw_bits_simd, uint16_t* nbits_out, uint16_t* bits_out) { SIMDVec16 token_cap = SIMDVec16::Val(15); SIMDVec16 tok = SIMDVec16::Load(tokens); SIMDVec16 tok_index = tok.Min(token_cap).PrepareForU8Lookup(); - SIMDVec16 huff_bits_pre = tok_index.U8Lookup(code.raw_bits_simd); + SIMDVec16 huff_bits_pre = tok_index.U8Lookup(raw_bits_simd); // Set the highest bit when token == 16; the Huffman code is constructed in // such a way that the code for token 15 is the same as the code for 16, // except for the highest bit. Mask16 needs_high_bit = tok.Eq(SIMDVec16::Val(16)); SIMDVec16 huff_bits = needs_high_bit.IfThenElse( huff_bits_pre.Or(SIMDVec16::Val(128)), huff_bits_pre); huff_bits.Store(bits_out); - tok_index.U8Lookup(code.raw_nbits_simd).Store(nbits_out); + tok_index.U8Lookup(raw_nbits_simd).Store(nbits_out); } FJXL_INLINE void HuffmanSIMDAbove14(const uint16_t* tokens, - const PrefixCode& code, uint16_t* nbits_out, - uint16_t* bits_out) { + const uint8_t* raw_nbits_simd, + const uint8_t* raw_bits_simd, + uint16_t* nbits_out, uint16_t* bits_out) { SIMDVec16 tok = SIMDVec16::Load(tokens); // We assume `tok` fits in a *signed* 16-bit integer. Mask16 above = tok.Gt(SIMDVec16::Val(12)); // 13, 14 -> 13 // 15, 16 -> 14 // 17, 18 -> 15 SIMDVec16 remap_tok = above.IfThenElse(tok.HAdd(SIMDVec16::Val(13)), tok); SIMDVec16 tok_index = remap_tok.PrepareForU8Lookup(); - SIMDVec16 huff_bits_pre = tok_index.U8Lookup(code.raw_bits_simd); + SIMDVec16 huff_bits_pre = tok_index.U8Lookup(raw_bits_simd); // Set the highest bit when token == 14, 16, 18. Mask16 needs_high_bit = above.And(tok.Eq(tok.And(SIMDVec16::Val(0xFFFE)))); SIMDVec16 huff_bits = needs_high_bit.IfThenElse( huff_bits_pre.Or(SIMDVec16::Val(128)), huff_bits_pre); huff_bits.Store(bits_out); - tok_index.U8Lookup(code.raw_nbits_simd).Store(nbits_out); + tok_index.U8Lookup(raw_nbits_simd).Store(nbits_out); } FJXL_INLINE void StoreSIMDUpTo8(const uint16_t* nbits_tok, const uint16_t* bits_tok, const uint16_t* nbits_huff, @@ -2474,30 +2576,30 @@ assert(n <= 16); memcpy(nbits_simd, nbits, 16); memcpy(bits_simd, bits, 16); } - static void EncodeChunk(upixel_t* residuals, size_t n, size_t skip, - const PrefixCode& code, BitWriter& output) { #ifdef FJXL_GENERIC_SIMD + static void EncodeChunkSimd(upixel_t* residuals, size_t n, size_t skip, + const uint8_t* raw_nbits_simd, + const uint8_t* raw_bits_simd, BitWriter& output) { Bits32 bits32[kChunkSize / SIMDVec16::kLanes]; alignas(64) uint16_t bits[SIMDVec16::kLanes]; alignas(64) uint16_t nbits[SIMDVec16::kLanes]; alignas(64) uint16_t bits_huff[SIMDVec16::kLanes]; alignas(64) uint16_t nbits_huff[SIMDVec16::kLanes]; alignas(64) uint16_t token[SIMDVec16::kLanes]; for (size_t i = 0; i < kChunkSize; i += SIMDVec16::kLanes) { TokenizeSIMD(residuals + i, token, nbits, bits); - HuffmanSIMDUpTo13(token, code, nbits_huff, bits_huff); + HuffmanSIMDUpTo13(token, raw_nbits_simd, raw_bits_simd, nbits_huff, + bits_huff); StoreSIMDUpTo8(nbits, bits, nbits_huff, bits_huff, std::max(n, i) - i, std::max(skip, i) - i, bits32 + i / SIMDVec16::kLanes); } StoreToWriter(bits32, output); - return; + } #endif - GenericEncodeChunk(residuals, n, skip, code, output); - } size_t NumSymbols(bool doing_ycocg_or_large_palette) const { // values gain 1 bit for YCoCg, 1 bit for prediction. // Maximum symbol is 1 + effective bit depth of residuals. if (doing_ycocg_or_large_palette) { @@ -2536,31 +2638,31 @@ assert(n <= 16); memcpy(nbits_simd, nbits, 16); memcpy(bits_simd, bits, 16); } - static void EncodeChunk(upixel_t* residuals, size_t n, size_t skip, - const PrefixCode& code, BitWriter& output) { #ifdef FJXL_GENERIC_SIMD + static void EncodeChunkSimd(upixel_t* residuals, size_t n, size_t skip, + const uint8_t* raw_nbits_simd, + const uint8_t* raw_bits_simd, BitWriter& output) { Bits32 bits32[2 * kChunkSize / SIMDVec16::kLanes]; alignas(64) uint16_t bits[SIMDVec16::kLanes]; alignas(64) uint16_t nbits[SIMDVec16::kLanes]; alignas(64) uint16_t bits_huff[SIMDVec16::kLanes]; alignas(64) uint16_t nbits_huff[SIMDVec16::kLanes]; alignas(64) uint16_t token[SIMDVec16::kLanes]; for (size_t i = 0; i < kChunkSize; i += SIMDVec16::kLanes) { TokenizeSIMD(residuals + i, token, nbits, bits); - HuffmanSIMDUpTo13(token, code, nbits_huff, bits_huff); + HuffmanSIMDUpTo13(token, raw_nbits_simd, raw_bits_simd, nbits_huff, + bits_huff); StoreSIMDUpTo14(nbits, bits, nbits_huff, bits_huff, std::max(n, i) - i, std::max(skip, i) - i, bits32 + 2 * i / SIMDVec16::kLanes); } StoreToWriter<2 * kChunkSize / SIMDVec16::kLanes>(bits32, output); - return; + } #endif - GenericEncodeChunk(residuals, n, skip, code, output); - } size_t NumSymbols(bool doing_ycocg_or_large_palette) const { // values gain 1 bit for YCoCg, 1 bit for prediction. // Maximum symbol is 1 + effective bit depth of residuals. if (doing_ycocg_or_large_palette) { @@ -2603,31 +2705,31 @@ CheckHuffmanBitsSIMD(bits[15], nbits[15], bits[16], nbits[16]); memcpy(nbits_simd, nbits, 16); memcpy(bits_simd, bits, 16); } - static void EncodeChunk(upixel_t* residuals, size_t n, size_t skip, - const PrefixCode& code, BitWriter& output) { #ifdef FJXL_GENERIC_SIMD + static void EncodeChunkSimd(upixel_t* residuals, size_t n, size_t skip, + const uint8_t* raw_nbits_simd, + const uint8_t* raw_bits_simd, BitWriter& output) { Bits32 bits32[2 * kChunkSize / SIMDVec16::kLanes]; alignas(64) uint16_t bits[SIMDVec16::kLanes]; alignas(64) uint16_t nbits[SIMDVec16::kLanes]; alignas(64) uint16_t bits_huff[SIMDVec16::kLanes]; alignas(64) uint16_t nbits_huff[SIMDVec16::kLanes]; alignas(64) uint16_t token[SIMDVec16::kLanes]; for (size_t i = 0; i < kChunkSize; i += SIMDVec16::kLanes) { TokenizeSIMD(residuals + i, token, nbits, bits); - HuffmanSIMD14(token, code, nbits_huff, bits_huff); + HuffmanSIMD14(token, raw_nbits_simd, raw_bits_simd, nbits_huff, + bits_huff); StoreSIMDUpTo14(nbits, bits, nbits_huff, bits_huff, std::max(n, i) - i, std::max(skip, i) - i, bits32 + 2 * i / SIMDVec16::kLanes); } StoreToWriter<2 * kChunkSize / SIMDVec16::kLanes>(bits32, output); - return; + } #endif - GenericEncodeChunk(residuals, n, skip, code, output); - } size_t NumSymbols(bool) const { return 17; } }; constexpr uint8_t Exactly14Bits::kMinRawLength[]; constexpr uint8_t Exactly14Bits::kMaxRawLength[]; @@ -2668,31 +2770,31 @@ bits_simd[14] = bits[15]; nbits_simd[15] = nbits[17]; bits_simd[15] = bits[17]; } - static void EncodeChunk(upixel_t* residuals, size_t n, size_t skip, - const PrefixCode& code, BitWriter& output) { #ifdef FJXL_GENERIC_SIMD + static void EncodeChunkSimd(upixel_t* residuals, size_t n, size_t skip, + const uint8_t* raw_nbits_simd, + const uint8_t* raw_bits_simd, BitWriter& output) { Bits32 bits32[2 * kChunkSize / SIMDVec16::kLanes]; alignas(64) uint32_t bits[SIMDVec16::kLanes]; alignas(64) uint32_t nbits[SIMDVec16::kLanes]; alignas(64) uint16_t bits_huff[SIMDVec16::kLanes]; alignas(64) uint16_t nbits_huff[SIMDVec16::kLanes]; alignas(64) uint16_t token[SIMDVec16::kLanes]; for (size_t i = 0; i < kChunkSize; i += SIMDVec16::kLanes) { TokenizeSIMD(residuals + i, token, nbits, bits); - HuffmanSIMDAbove14(token, code, nbits_huff, bits_huff); + HuffmanSIMDAbove14(token, raw_nbits_simd, raw_bits_simd, nbits_huff, + bits_huff); StoreSIMDAbove14(nbits, bits, nbits_huff, bits_huff, std::max(n, i) - i, std::max(skip, i) - i, bits32 + 2 * i / SIMDVec16::kLanes); } StoreToWriter<2 * kChunkSize / SIMDVec16::kLanes>(bits32, output); - return; + } #endif - GenericEncodeChunk(residuals, n, skip, code, output); - } size_t NumSymbols(bool) const { return 19; } }; constexpr uint8_t MoreThan14Bits::kMinRawLength[]; constexpr uint8_t MoreThan14Bits::kMaxRawLength[]; @@ -2714,10 +2816,11 @@ output->Write(2, 0); output->Write(2, 1); output->Write(2, 2); output->Write(2, 3); output->Write(1, 0); // First tree encoding option + // Huffman table + extra bits for the tree. uint8_t symbol_bits[6] = {0b00, 0b10, 0b001, 0b101, 0b0011, 0b0111}; uint8_t symbol_nbits[6] = {2, 2, 3, 3, 4, 4}; // Write a tree with a leaf per channel, and gradient predictor for every // leaf. @@ -2790,10 +2893,14 @@ } } template struct ChunkEncoder { + void PrepareForSimd() { + BitDepth::PrepareForSimd(code->raw_nbits, code->raw_bits, code->numraw, + raw_nbits_simd, raw_bits_simd); + } FJXL_INLINE static void EncodeRle(size_t count, const PrefixCode& code, BitWriter& output) { if (count == 0) return; count -= kLZ77MinLength + 1; if (count < kLZ77CacheSize) { @@ -2809,17 +2916,24 @@ } FJXL_INLINE void Chunk(size_t run, typename BitDepth::upixel_t* residuals, size_t skip, size_t n) { EncodeRle(run, *code, *output); - BitDepth::EncodeChunk(residuals, n, skip, *code, *output); +#ifdef FJXL_GENERIC_SIMD + BitDepth::EncodeChunkSimd(residuals, n, skip, raw_nbits_simd, raw_bits_simd, + *output); +#else + GenericEncodeChunk(residuals, n, skip, *code, *output); +#endif } inline void Finalize(size_t run) { EncodeRle(run, *code, *output); } const PrefixCode* code; BitWriter* output; + alignas(64) uint8_t raw_nbits_simd[16] = {}; + alignas(64) uint8_t raw_bits_simd[16] = {}; }; template struct ChunkSampleCollector { FJXL_INLINE void Rle(size_t count, uint64_t* lz77_counts) { @@ -3283,10 +3397,11 @@ ChannelRowProcessor, BitDepth> row_encoders[4]; for (size_t c = 0; c < nb_chans; c++) { row_encoders[c].t = &encoders[c]; encoders[c].output = &output[c]; encoders[c].code = &code[c]; + encoders[c].PrepareForSimd(); } ProcessImageArea, BitDepth>>( rgba, x0, y0, xs, 0, ys, row_stride, bitdepth, nb_chans, big_endian, row_encoders); } @@ -3373,10 +3488,11 @@ ChannelRowProcessor, UpTo8Bits> row_encoder; row_encoder.t = &encoder; encoder.output = &output; encoder.code = &code[is_single_group ? 1 : 0]; + encoder.PrepareForSimd(); ProcessImageAreaPalette< ChannelRowProcessor, UpTo8Bits>>( rgba, x0, y0, xs, 0, ys, row_stride, lookup, nb_chans, &row_encoder); } @@ -3451,10 +3567,11 @@ ChunkEncoder encoder; ChannelRowProcessor, UpTo8Bits> row_encoder; row_encoder.t = &encoder; encoder.output = output; encoder.code = &code[0]; + encoder.PrepareForSimd(); int16_t p[4][32 + 1024] = {}; uint8_t prgba[4]; size_t i = 0; size_t have_zero = 0; if (palette[pcolors - 1] == 0) have_zero = 1; @@ -3513,35 +3630,45 @@ palette[index] = p; } return collided; } +// TODO(szabadka): Add some parameter to indicate whether the input is +// truly streaming. template -JxlFastLosslessFrameState* LLEnc(const unsigned char* rgba, size_t width, - size_t stride, size_t height, - BitDepth bitdepth, size_t nb_chans, - bool big_endian, int effort, - void* runner_opaque, - FJxlParallelRunner runner) { +JxlFastLosslessFrameState* LLPrepare(JxlChunkedFrameInputSource input, + size_t width, size_t height, + BitDepth bitdepth, size_t nb_chans, + bool big_endian, int effort) { assert(width != 0); assert(height != 0); - assert(stride >= nb_chans * BitDepth::kInputBytes * width); // Count colors to try palette std::vector palette(kHashSize); std::vector lookup(kHashSize); lookup[0] = 0; int pcolors = 0; bool collided = effort < 2 || bitdepth.bitdepth != 8; - for (size_t y = 0; y < height && !collided; y++) { - const unsigned char* r = rgba + stride * y; - if (nb_chans == 1) collided = detect_palette<1>(r, width, palette); - if (nb_chans == 2) collided = detect_palette<2>(r, width, palette); - if (nb_chans == 3) collided = detect_palette<3>(r, width, palette); - if (nb_chans == 4) collided = detect_palette<4>(r, width, palette); - } - + for (size_t y0 = 0; y0 < height && !collided; y0 += 256) { + size_t ys = std::min(height - y0, 256); + for (size_t x0 = 0; x0 < width && !collided; x0 += 256) { + size_t xs = std::min(width - x0, 256); + size_t stride; + // TODO(szabadka): Add RAII wrapper around this. + const void* buffer = input.get_color_channel_data_at(input.opaque, x0, y0, + xs, ys, &stride); + auto rgba = reinterpret_cast(buffer); + for (size_t y = 0; y < ys && !collided; y++) { + const unsigned char* r = rgba + stride * y; + if (nb_chans == 1) collided = detect_palette<1>(r, xs, palette); + if (nb_chans == 2) collided = detect_palette<2>(r, xs, palette); + if (nb_chans == 3) collided = detect_palette<3>(r, xs, palette); + if (nb_chans == 4) collided = detect_palette<4>(r, xs, palette); + } + input.release_buffer(input.opaque, buffer); + } + } int nb_entries = 0; if (!collided) { pcolors = 1; // always have all-zero as a palette color bool have_color = false; uint8_t minG = 255, maxG = 0; @@ -3600,23 +3727,29 @@ uint64_t lz77_counts[4][kNumLZ77] = {}; bool onegroup = num_groups_x == 1 && num_groups_y == 1; // sample the middle (effort * 2) rows of every group + // TODO(szabadka): Optimize sampling rule for low-memory code-path. for (size_t g = 0; g < num_groups_y * num_groups_x; g++) { size_t xg = g % num_groups_x; size_t yg = g / num_groups_x; - int y_offset = yg * 256; - int y_max = std::min(height - yg * 256, 256); - int y_begin = y_offset + std::max(0, y_max - 2 * effort) / 2; - int y_count = - std::min(2 * effort * y_max / 256, y_offset + y_max - y_begin - 1); - int x_max = - std::min(width - xg * 256, 256) / kChunkSize * kChunkSize; - CollectSamples(rgba, xg * 256, y_begin, x_max, stride, y_count, raw_counts, + size_t y0 = yg * 256; + size_t x0 = xg * 256; + size_t ys = std::min(height - y0, 256); + size_t xs = std::min(width - x0, 256); + size_t stride; + const void* buffer = + input.get_color_channel_data_at(input.opaque, x0, y0, xs, ys, &stride); + auto rgba = reinterpret_cast(buffer); + int y_begin = std::max(0, ys - 2 * effort) / 2; + int y_count = std::min(2 * effort * ys / 256, y0 + ys - y_begin - 1); + int x_max = xs / kChunkSize * kChunkSize; + CollectSamples(rgba, 0, y_begin, x_max, stride, y_count, raw_counts, lz77_counts, onegroup, !collided, bitdepth, nb_chans, big_endian, lookup.data()); + input.release_buffer(input.opaque, buffer); } // TODO(veluca): can probably improve this and make it bitdepth-dependent. uint64_t base_raw_counts[kNumRawSymbols] = { 3843, 852, 1270, 1214, 1014, 727, 481, 300, 159, 51, @@ -3655,84 +3788,204 @@ for (size_t i = 0; i < kNumLZ77; i++) { lz77_counts[c][i] = (lz77_counts[c][i] << 8) + base_lz77_counts[i]; } } - alignas(64) PrefixCode hcode[4]; + JxlFastLosslessFrameState* frame_state = new JxlFastLosslessFrameState(); for (size_t i = 0; i < 4; i++) { - hcode[i] = PrefixCode(bitdepth, raw_counts[i], lz77_counts[i]); - } - - size_t num_groups = onegroup ? 1 - : (2 + num_dc_groups_x * num_dc_groups_y + - num_groups_x * num_groups_y); - - JxlFastLosslessFrameState* frame_state = new JxlFastLosslessFrameState(); - + frame_state->hcode[i] = PrefixCode(bitdepth, raw_counts[i], lz77_counts[i]); + } + + size_t num_dc_groups = num_dc_groups_x * num_dc_groups_y; + size_t num_ac_groups = num_groups_x * num_groups_y; + size_t num_groups = onegroup ? 1 : (2 + num_dc_groups + num_ac_groups); + frame_state->input = input; frame_state->width = width; frame_state->height = height; + frame_state->num_groups_x = num_groups_x; + frame_state->num_groups_y = num_groups_y; + frame_state->num_dc_groups_x = num_dc_groups_x; + frame_state->num_dc_groups_y = num_dc_groups_y; frame_state->nb_chans = nb_chans; frame_state->bitdepth = bitdepth.bitdepth; + frame_state->big_endian = big_endian; + frame_state->effort = effort; + frame_state->collided = collided; + frame_state->lookup = lookup; frame_state->group_data = std::vector>(num_groups); + frame_state->group_sizes.resize(num_groups); if (collided) { - PrepareDCGlobal(onegroup, width, height, nb_chans, hcode, + PrepareDCGlobal(onegroup, width, height, nb_chans, frame_state->hcode, &frame_state->group_data[0][0]); } else { - PrepareDCGlobalPalette(onegroup, width, height, nb_chans, hcode, palette, - pcolors, &frame_state->group_data[0][0]); - } - - auto run_one = [&](size_t g) { - size_t xg = g % num_groups_x; - size_t yg = g / num_groups_x; - size_t group_id = - onegroup ? 0 : (2 + num_dc_groups_x * num_dc_groups_y + g); - size_t xs = std::min(width - xg * 256, 256); - size_t ys = std::min(height - yg * 256, 256); - size_t x0 = xg * 256; - size_t y0 = yg * 256; - auto& gd = frame_state->group_data[group_id]; - if (collided) { - WriteACSection(rgba, x0, y0, xs, ys, stride, onegroup, bitdepth, nb_chans, - big_endian, hcode, gd); - - } else { - WriteACSectionPalette(rgba, x0, y0, xs, ys, stride, onegroup, hcode, - lookup.data(), nb_chans, gd[0]); - } - }; - - runner( - runner_opaque, &run_one, - +[](void* r, size_t i) { (*reinterpret_cast(r))(i); }, - num_groups_x * num_groups_y); + PrepareDCGlobalPalette(onegroup, width, height, nb_chans, + frame_state->hcode, palette, pcolors, + &frame_state->group_data[0][0]); + } + frame_state->group_sizes[0] = SectionSize(frame_state->group_data[0]); + if (!onegroup) { + ComputeAcGroupDataOffset(frame_state->group_sizes[0], num_dc_groups, + num_ac_groups, frame_state->min_dc_global_size, + frame_state->ac_group_data_offset); + } return frame_state; } -JxlFastLosslessFrameState* JxlFastLosslessEncodeImpl( - const unsigned char* rgba, size_t width, size_t stride, size_t height, - size_t nb_chans, size_t bitdepth, bool big_endian, int effort, - void* runner_opaque, FJxlParallelRunner runner) { +template +void LLProcess(JxlFastLosslessFrameState* frame_state, bool is_last, + BitDepth bitdepth, void* runner_opaque, + FJxlParallelRunner runner, + JxlEncoderOutputProcessorWrapper* output_processor) { +#if !FJXL_STANDALONE + if (frame_state->process_done) { + JxlFastLosslessPrepareHeader(frame_state, /*add_image_header=*/0, is_last); + if (output_processor) { + JxlFastLosslessOutputFrame(frame_state, output_processor); + } + return; + } +#endif + // The maximum number of groups that we process concurrently here. + // TODO(szabadka) Use the number of threads or some outside parameter for the + // maximum memory usage instead. + constexpr size_t kMaxLocalGroups = 16; + bool onegroup = frame_state->group_sizes.size() == 1; + bool streaming = !onegroup && output_processor; + size_t total_groups = frame_state->num_groups_x * frame_state->num_groups_y; + size_t max_groups = streaming ? kMaxLocalGroups : total_groups; + size_t start_pos = 0; +#if !FJXL_STANDALONE + if (streaming) { + start_pos = output_processor->CurrentPosition(); + output_processor->Seek(start_pos + frame_state->ac_group_data_offset); + } +#endif + for (size_t offset = 0; offset < total_groups; offset += max_groups) { + size_t num_groups = std::min(max_groups, total_groups - offset); + JxlFastLosslessFrameState local_frame_state; + if (streaming) { + local_frame_state.group_data = + std::vector>(num_groups); + } + auto run_one = [&](size_t i) { + size_t g = offset + i; + size_t xg = g % frame_state->num_groups_x; + size_t yg = g / frame_state->num_groups_x; + size_t num_dc_groups = + frame_state->num_dc_groups_x * frame_state->num_dc_groups_y; + size_t group_id = onegroup ? 0 : (2 + num_dc_groups + g); + size_t xs = std::min(frame_state->width - xg * 256, 256); + size_t ys = std::min(frame_state->height - yg * 256, 256); + size_t x0 = xg * 256; + size_t y0 = yg * 256; + size_t stride; + JxlChunkedFrameInputSource input = frame_state->input; + const void* buffer = input.get_color_channel_data_at(input.opaque, x0, y0, + xs, ys, &stride); + const unsigned char* rgba = + reinterpret_cast(buffer); + + auto& gd = streaming ? local_frame_state.group_data[i] + : frame_state->group_data[group_id]; + if (frame_state->collided) { + WriteACSection(rgba, 0, 0, xs, ys, stride, onegroup, bitdepth, + frame_state->nb_chans, frame_state->big_endian, + frame_state->hcode, gd); + } else { + WriteACSectionPalette(rgba, 0, 0, xs, ys, stride, onegroup, + frame_state->hcode, frame_state->lookup.data(), + frame_state->nb_chans, gd[0]); + } + frame_state->group_sizes[group_id] = SectionSize(gd); + input.release_buffer(input.opaque, buffer); + }; + runner( + runner_opaque, &run_one, + +[](void* r, size_t i) { + (*reinterpret_cast(r))(i); + }, + num_groups); +#if !FJXL_STANDALONE + if (streaming) { + local_frame_state.nb_chans = frame_state->nb_chans; + local_frame_state.current_bit_writer = 1; + JxlFastLosslessOutputFrame(&local_frame_state, output_processor); + } +#endif + } +#if !FJXL_STANDALONE + if (streaming) { + size_t end_pos = output_processor->CurrentPosition(); + output_processor->Seek(start_pos); + frame_state->group_data.resize(1); + bool have_alpha = frame_state->nb_chans == 2 || frame_state->nb_chans == 4; + size_t padding = ComputeDcGlobalPadding( + frame_state->group_sizes, frame_state->ac_group_data_offset, + frame_state->min_dc_global_size, have_alpha, is_last); + + for (size_t i = 0; i < padding; ++i) { + frame_state->group_data[0][0].Write(8, 0); + } + frame_state->group_sizes[0] += padding; + JxlFastLosslessPrepareHeader(frame_state, /*add_image_header=*/0, is_last); + assert(frame_state->ac_group_data_offset == + JxlFastLosslessOutputSize(frame_state)); + JxlFastLosslessOutputHeaders(frame_state, output_processor); + output_processor->Seek(end_pos); + } else if (output_processor) { + assert(onegroup); + JxlFastLosslessPrepareHeader(frame_state, /*add_image_header=*/0, is_last); + if (output_processor) { + JxlFastLosslessOutputFrame(frame_state, output_processor); + } + } + frame_state->process_done = true; +#endif +} + +JxlFastLosslessFrameState* JxlFastLosslessPrepareImpl( + JxlChunkedFrameInputSource input, size_t width, size_t height, + size_t nb_chans, size_t bitdepth, bool big_endian, int effort) { assert(bitdepth > 0); assert(nb_chans <= 4); assert(nb_chans != 0); if (bitdepth <= 8) { - return LLEnc(rgba, width, stride, height, UpTo8Bits(bitdepth), nb_chans, - big_endian, effort, runner_opaque, runner); + return LLPrepare(input, width, height, UpTo8Bits(bitdepth), nb_chans, + big_endian, effort); } if (bitdepth <= 13) { - return LLEnc(rgba, width, stride, height, From9To13Bits(bitdepth), nb_chans, - big_endian, effort, runner_opaque, runner); + return LLPrepare(input, width, height, From9To13Bits(bitdepth), nb_chans, + big_endian, effort); } if (bitdepth == 14) { - return LLEnc(rgba, width, stride, height, Exactly14Bits(bitdepth), nb_chans, - big_endian, effort, runner_opaque, runner); - } - return LLEnc(rgba, width, stride, height, MoreThan14Bits(bitdepth), nb_chans, - big_endian, effort, runner_opaque, runner); + return LLPrepare(input, width, height, Exactly14Bits(bitdepth), nb_chans, + big_endian, effort); + } + return LLPrepare(input, width, height, MoreThan14Bits(bitdepth), nb_chans, + big_endian, effort); +} + +void JxlFastLosslessProcessFrameImpl( + JxlFastLosslessFrameState* frame_state, bool is_last, void* runner_opaque, + FJxlParallelRunner runner, + JxlEncoderOutputProcessorWrapper* output_processor) { + const size_t bitdepth = frame_state->bitdepth; + if (bitdepth <= 8) { + LLProcess(frame_state, is_last, UpTo8Bits(bitdepth), runner_opaque, runner, + output_processor); + } else if (bitdepth <= 13) { + LLProcess(frame_state, is_last, From9To13Bits(bitdepth), runner_opaque, + runner, output_processor); + } else if (bitdepth == 14) { + LLProcess(frame_state, is_last, Exactly14Bits(bitdepth), runner_opaque, + runner, output_processor); + } else { + LLProcess(frame_state, is_last, MoreThan14Bits(bitdepth), runner_opaque, + runner, output_processor); + } } } // namespace #endif // FJXL_SELF_INCLUDE @@ -3810,36 +4063,91 @@ #endif extern "C" { #if FJXL_STANDALONE +class FJxlFrameInput { + public: + FJxlFrameInput(const unsigned char* rgba, size_t row_stride, size_t nb_chans, + size_t bitdepth) + : rgba_(rgba), + row_stride_(row_stride), + bytes_per_pixel_(bitdepth <= 8 ? nb_chans : 2 * nb_chans) {} + + JxlChunkedFrameInputSource GetInputSource() { + return JxlChunkedFrameInputSource{ + this, + GetDataAt, + [](void*, const void*) {}}; + } + + private: + static const void* GetDataAt(void* opaque, size_t xpos, size_t ypos, + size_t xsize, size_t ysize, size_t* row_offset) { + FJxlFrameInput* self = static_cast(opaque); + *row_offset = self->row_stride_; + return self->rgba_ + ypos * (*row_offset) + xpos * self->bytes_per_pixel_; + } + + const uint8_t* rgba_; + size_t row_stride_; + size_t bytes_per_pixel_; +}; + size_t JxlFastLosslessEncode(const unsigned char* rgba, size_t width, size_t row_stride, size_t height, size_t nb_chans, size_t bitdepth, int big_endian, int effort, unsigned char** output, void* runner_opaque, FJxlParallelRunner runner) { - auto frame_state = JxlFastLosslessPrepareFrame( - rgba, width, row_stride, height, nb_chans, bitdepth, big_endian, effort, - runner_opaque, runner); + FJxlFrameInput input(rgba, row_stride, nb_chans, bitdepth); + auto frame_state = + JxlFastLosslessPrepareFrame(input.GetInputSource(), width, height, + nb_chans, bitdepth, big_endian, effort); + JxlFastLosslessProcessFrame(frame_state, /*is_last=*/true, runner_opaque, + runner, nullptr); JxlFastLosslessPrepareHeader(frame_state, /*add_image_header=*/1, /*is_last=*/1); size_t output_size = JxlFastLosslessMaxRequiredOutput(frame_state); *output = (unsigned char*)malloc(output_size); size_t written = 0; size_t total = 0; while ((written = JxlFastLosslessWriteOutput(frame_state, *output + total, output_size - total)) != 0) { total += written; } + JxlFastLosslessFreeFrameState(frame_state); return total; } #endif JxlFastLosslessFrameState* JxlFastLosslessPrepareFrame( - const unsigned char* rgba, size_t width, size_t row_stride, size_t height, - size_t nb_chans, size_t bitdepth, int big_endian, int effort, - void* runner_opaque, FJxlParallelRunner runner) { + JxlChunkedFrameInputSource input, size_t width, size_t height, + size_t nb_chans, size_t bitdepth, int big_endian, int effort) { +#if FJXL_ENABLE_AVX512 + if (__builtin_cpu_supports("avx512cd") && + __builtin_cpu_supports("avx512vbmi") && + __builtin_cpu_supports("avx512bw") && __builtin_cpu_supports("avx512f") && + __builtin_cpu_supports("avx512vl")) { + return AVX512::JxlFastLosslessPrepareImpl(input, width, height, nb_chans, + bitdepth, big_endian, effort); + } +#endif +#if FJXL_ENABLE_AVX2 + if (__builtin_cpu_supports("avx2")) { + return AVX2::JxlFastLosslessPrepareImpl(input, width, height, nb_chans, + bitdepth, big_endian, effort); + } +#endif + + return default_implementation::JxlFastLosslessPrepareImpl( + input, width, height, nb_chans, bitdepth, big_endian, effort); +} + +void JxlFastLosslessProcessFrame( + JxlFastLosslessFrameState* frame_state, bool is_last, void* runner_opaque, + FJxlParallelRunner runner, + JxlEncoderOutputProcessorWrapper* output_processor) { auto trivial_runner = +[](void*, void* opaque, void fun(void*, size_t), size_t count) { for (size_t i = 0; i < count; i++) { fun(opaque, i); } @@ -3852,26 +4160,42 @@ #if FJXL_ENABLE_AVX512 if (__builtin_cpu_supports("avx512cd") && __builtin_cpu_supports("avx512vbmi") && __builtin_cpu_supports("avx512bw") && __builtin_cpu_supports("avx512f") && __builtin_cpu_supports("avx512vl")) { - return AVX512::JxlFastLosslessEncodeImpl(rgba, width, row_stride, height, - nb_chans, bitdepth, big_endian, - effort, runner_opaque, runner); + return AVX512::JxlFastLosslessProcessFrameImpl( + frame_state, is_last, runner_opaque, runner, output_processor); } #endif #if FJXL_ENABLE_AVX2 if (__builtin_cpu_supports("avx2")) { - return AVX2::JxlFastLosslessEncodeImpl(rgba, width, row_stride, height, - nb_chans, bitdepth, big_endian, - effort, runner_opaque, runner); + return AVX2::JxlFastLosslessProcessFrameImpl( + frame_state, is_last, runner_opaque, runner, output_processor); } #endif - return default_implementation::JxlFastLosslessEncodeImpl( - rgba, width, row_stride, height, nb_chans, bitdepth, big_endian, effort, - runner_opaque, runner); + return default_implementation::JxlFastLosslessProcessFrameImpl( + frame_state, is_last, runner_opaque, runner, output_processor); } } // extern "C" +#if !FJXL_STANDALONE +void JxlFastLosslessOutputFrame( + JxlFastLosslessFrameState* frame_state, + JxlEncoderOutputProcessorWrapper* output_processor) { + size_t fl_size = JxlFastLosslessOutputSize(frame_state); + size_t written = 0; + while (written < fl_size) { + auto retval = output_processor->GetBuffer(32, fl_size - written); + assert(retval.status()); + auto buffer = std::move(retval).value(); + size_t n = + JxlFastLosslessWriteOutput(frame_state, buffer.data(), buffer.size()); + if (n == 0) break; + buffer.advance(n); + written += n; + }; +} +#endif + #endif // FJXL_SELF_INCLUDE diff --git a/third_party/jpeg-xl/lib/jxl/enc_file.h b/third_party/jpeg-xl/lib/jxl/enc_file.h deleted file mode 100644 --- a/third_party/jpeg-xl/lib/jxl/enc_file.h +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright (c) the JPEG XL Project Authors. All rights reserved. -// -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -#ifndef LIB_JXL_ENC_FILE_H_ -#define LIB_JXL_ENC_FILE_H_ - -// Facade for JXL encoding. - -#include "lib/jxl/base/data_parallel.h" -#include "lib/jxl/base/padded_bytes.h" -#include "lib/jxl/base/status.h" -#include "lib/jxl/enc_cache.h" -#include "lib/jxl/enc_params.h" - -namespace jxl { - -struct AuxOut; -class CodecInOut; - -// Compresses pixels from `io` (given in any ColorEncoding). -// `io->metadata.m.original` must be set. -Status EncodeFile(const CompressParams& params, const CodecInOut* io, - PassesEncoderState* passes_enc_state, PaddedBytes* compressed, - const JxlCmsInterface& cms, AuxOut* aux_out = nullptr, - ThreadPool* pool = nullptr); - -} // namespace jxl - -#endif // LIB_JXL_ENC_FILE_H_ diff --git a/third_party/jpeg-xl/lib/jxl/enc_file.cc b/third_party/jpeg-xl/lib/jxl/enc_file.cc deleted file mode 100644 --- a/third_party/jpeg-xl/lib/jxl/enc_file.cc +++ /dev/null @@ -1,141 +0,0 @@ -// Copyright (c) the JPEG XL Project Authors. All rights reserved. -// -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -#include "lib/jxl/enc_file.h" - -#include - -#include -#include -#include - -#include "lib/jxl/base/compiler_specific.h" -#include "lib/jxl/codec_in_out.h" -#include "lib/jxl/coeff_order_fwd.h" -#include "lib/jxl/color_encoding_internal.h" -#include "lib/jxl/enc_aux_out.h" -#include "lib/jxl/enc_bit_writer.h" -#include "lib/jxl/enc_cache.h" -#include "lib/jxl/enc_fields.h" -#include "lib/jxl/enc_frame.h" -#include "lib/jxl/enc_icc_codec.h" -#include "lib/jxl/frame_header.h" -#include "lib/jxl/headers.h" -#include "lib/jxl/image_bundle.h" - -namespace jxl { - -namespace { - -Status PrepareCodecMetadataFromIO(const CompressParams& cparams, - const CodecInOut* io, - CodecMetadata* metadata) { - *metadata = io->metadata; - size_t ups = 1; - if (cparams.already_downsampled) ups = cparams.resampling; - - JXL_RETURN_IF_ERROR(metadata->size.Set(io->xsize() * ups, io->ysize() * ups)); - - // Keep ICC profile in lossless modes because a reconstructed profile may be - // slightly different (quantization). - // Also keep ICC in JPEG reconstruction mode as we need byte-exact profiles. - if (!cparams.IsLossless() && !io->Main().IsJPEG() && cparams.cms_set) { - metadata->m.color_encoding.DecideIfWantICC(cparams.cms); - } - - metadata->m.xyb_encoded = - cparams.color_transform == ColorTransform::kXYB ? true : false; - - // TODO(firsching): move this EncodeFile to test_utils / re-implement this - // using API functions - return true; -} - -} // namespace - -Status EncodePreview(const CompressParams& cparams, const ImageBundle& ib, - const CodecMetadata* metadata, const JxlCmsInterface& cms, - ThreadPool* pool, BitWriter* JXL_RESTRICT writer) { - BitWriter preview_writer; - // TODO(janwas): also support generating preview by downsampling - if (ib.HasColor()) { - AuxOut aux_out; - PassesEncoderState passes_enc_state; - // TODO(lode): check if we want all extra channels and matching xyb_encoded - // for the preview, such that using the main ImageMetadata object for - // encoding this frame is warrented. - FrameInfo frame_info; - frame_info.is_preview = true; - JXL_RETURN_IF_ERROR(EncodeFrame(cparams, frame_info, metadata, ib, - &passes_enc_state, cms, pool, - &preview_writer, &aux_out)); - preview_writer.ZeroPadToByte(); - } - - if (preview_writer.BitsWritten() != 0) { - writer->ZeroPadToByte(); - writer->AppendByteAligned(preview_writer); - } - - return true; -} - -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 = 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; - } - - 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(WriteCodestreamHeaders(metadata.get(), &writer, aux_out)); - - // Only send ICC (at least several hundred bytes) if fields aren't enough. - if (metadata->m.color_encoding.WantICC()) { - JXL_RETURN_IF_ERROR(WriteICC(metadata->m.color_encoding.ICC(), &writer, - kLayerHeader, aux_out)); - } - - if (metadata->m.have_preview) { - JXL_RETURN_IF_ERROR(EncodePreview(cparams, io->preview_frame, - metadata.get(), cms, pool, &writer)); - } - - // Each frame should start on byte boundaries. - BitWriter::Allotment allotment(&writer, 8); - writer.ZeroPadToByte(); - allotment.ReclaimAndCharge(&writer, kLayerHeader, aux_out); - - for (size_t i = 0; i < io->frames.size(); i++) { - FrameInfo info; - info.is_last = i == io->frames.size() - 1; - if (io->frames[i].use_for_next_frame) { - info.save_as_reference = 1; - } - JXL_RETURN_IF_ERROR(EncodeFrame(cparams, info, metadata.get(), - io->frames[i], passes_enc_state, cms, pool, - &writer, aux_out)); - } - - // Clean up passes_enc_state in case it gets reused. - for (size_t i = 0; i < 4; i++) { - passes_enc_state->shared.dc_frames[i] = Image3F(); - passes_enc_state->shared.reference_frames[i].frame = ImageBundle(); - } - - *compressed = std::move(writer).TakeBytes(); - return true; -} - -} // namespace jxl 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 @@ -22,11 +22,10 @@ #include "lib/jxl/base/bits.h" #include "lib/jxl/base/common.h" #include "lib/jxl/base/compiler_specific.h" #include "lib/jxl/base/data_parallel.h" #include "lib/jxl/base/override.h" -#include "lib/jxl/base/padded_bytes.h" #include "lib/jxl/base/status.h" #include "lib/jxl/chroma_from_luma.h" #include "lib/jxl/coeff_order.h" #include "lib/jxl/coeff_order_fwd.h" #include "lib/jxl/color_encoding_internal.h" @@ -1229,24 +1228,28 @@ metadata_linear->xyb_encoded = (cparams.color_transform == ColorTransform::kXYB); metadata_linear->color_encoding = c_linear; ImageBundle linear_storage(metadata_linear.get()); - std::vector aux_outs; + std::vector> aux_outs; // LossyFrameEncoder stores a reference to a std::function // so we need to keep the std::function being referenced // alive while lossy_frame_encoder is used. We could make resize_aux_outs a // lambda type by making LossyFrameEncoder a template instead, but this is // simpler. const std::function resize_aux_outs = [&aux_outs, aux_out](const size_t num_threads) -> Status { - if (aux_out != nullptr) { - size_t old_size = aux_outs.size(); - for (size_t i = num_threads; i < old_size; i++) { - aux_out->Assimilate(aux_outs[i]); + if (aux_out == nullptr) { + aux_outs.resize(num_threads); + } else { + while (aux_outs.size() > num_threads) { + aux_out->Assimilate(*aux_outs.back()); + aux_outs.pop_back(); } - aux_outs.resize(num_threads); + while (num_threads > aux_outs.size()) { + aux_outs.emplace_back(jxl::make_unique()); + } } return true; }; LossyFrameEncoder lossy_frame_encoder(cparams, *frame_header, @@ -1344,22 +1347,20 @@ const size_t num_passes = passes_enc_state->progressive_splitter.GetNumPasses(); // DC global info + DC groups + AC global info + AC groups * // num_passes. - const bool has_ac_global = true; - std::vector group_codes(NumTocEntries(frame_dim.num_groups, - frame_dim.num_dc_groups, - num_passes, has_ac_global)); + std::vector group_codes( + NumTocEntries(frame_dim.num_groups, frame_dim.num_dc_groups, num_passes)); const size_t global_ac_index = frame_dim.num_dc_groups + 1; const bool is_small_image = frame_dim.num_groups == 1 && num_passes == 1; const auto get_output = [&](const size_t index) { return &group_codes[is_small_image ? 0 : index]; }; auto ac_group_code = [&](size_t pass, size_t group) { return get_output(AcGroupIndex(pass, group, frame_dim.num_groups, - frame_dim.num_dc_groups, has_ac_global)); + frame_dim.num_dc_groups)); }; if (frame_header->flags & FrameHeader::kPatches) { PatchDictionaryEncoder::Encode( lossy_frame_encoder.State()->shared.image_features.patches, @@ -1398,11 +1399,11 @@ JXL_RETURN_IF_ERROR(modular_frame_encoder->EncodeStream( get_output(0), aux_out, kLayerModularGlobal, ModularStreamId::Global())); const auto process_dc_group = [&](const uint32_t group_index, const size_t thread) { - AuxOut* my_aux_out = aux_out ? &aux_outs[thread] : nullptr; + AuxOut* my_aux_out = aux_outs[thread].get(); BitWriter* output = get_output(group_index + 1); if (frame_header->encoding == FrameEncoding::kVarDCT && !(frame_header->flags & FrameHeader::kUseDcFrame)) { BitWriter::Allotment allotment(output, 2); output->Write(2, modular_frame_encoder->extra_dc_precision[group_index]); @@ -1439,11 +1440,11 @@ } std::atomic num_errors{0}; const auto process_group = [&](const uint32_t group_index, const size_t thread) { - AuxOut* my_aux_out = aux_out ? &aux_outs[thread] : nullptr; + AuxOut* my_aux_out = aux_outs[thread].get(); for (size_t i = 0; i < num_passes; i++) { if (frame_header->encoding == FrameEncoding::kVarDCT) { if (!lossy_frame_encoder.EncodeACGroup( i, group_index, ac_group_code(i, group_index), my_aux_out)) { diff --git a/third_party/jpeg-xl/lib/jxl/enc_gamma_correct.h b/third_party/jpeg-xl/lib/jxl/enc_gamma_correct.h --- a/third_party/jpeg-xl/lib/jxl/enc_gamma_correct.h +++ b/third_party/jpeg-xl/lib/jxl/enc_gamma_correct.h @@ -4,11 +4,11 @@ // license that can be found in the LICENSE file. #ifndef LIB_JXL_ENC_GAMMA_CORRECT_H_ #define LIB_JXL_ENC_GAMMA_CORRECT_H_ -// Deprecated: sRGB transfer function. Use color_management.h instead. +// Deprecated: sRGB transfer function. Use JxlCms instead. #include #include "lib/jxl/base/compiler_specific.h" diff --git a/third_party/jpeg-xl/lib/jxl/enc_icc_codec.h b/third_party/jpeg-xl/lib/jxl/enc_icc_codec.h --- a/third_party/jpeg-xl/lib/jxl/enc_icc_codec.h +++ b/third_party/jpeg-xl/lib/jxl/enc_icc_codec.h @@ -12,18 +12,18 @@ #include #include #include "lib/jxl/base/compiler_specific.h" -#include "lib/jxl/base/padded_bytes.h" #include "lib/jxl/base/status.h" #include "lib/jxl/dec_bit_reader.h" #include "lib/jxl/enc_bit_writer.h" namespace jxl { struct AuxOut; +class PaddedBytes; // Should still be called if `icc.empty()` - if so, writes only 1 bit. Status WriteICC(const std::vector& icc, BitWriter* JXL_RESTRICT writer, size_t layer, AuxOut* JXL_RESTRICT aux_out); diff --git a/third_party/jpeg-xl/lib/jxl/enc_icc_codec.cc b/third_party/jpeg-xl/lib/jxl/enc_icc_codec.cc --- a/third_party/jpeg-xl/lib/jxl/enc_icc_codec.cc +++ b/third_party/jpeg-xl/lib/jxl/enc_icc_codec.cc @@ -14,10 +14,11 @@ #include "lib/jxl/base/byte_order.h" #include "lib/jxl/enc_ans.h" #include "lib/jxl/enc_aux_out.h" #include "lib/jxl/fields.h" #include "lib/jxl/icc_codec_common.h" +#include "lib/jxl/padded_bytes.h" namespace jxl { namespace { // Unshuffles or de-interleaves bytes, for example with width 2, turns @@ -103,11 +104,12 @@ PaddedBytes data; EncodeVarInt(size, result); // Header - PaddedBytes header = ICCInitialHeaderPrediction(); + PaddedBytes header; + header.append(ICCInitialHeaderPrediction()); EncodeUint32(0, size, &header); for (size_t i = 0; i < kICCHeaderSize && i < size; i++) { ICCPredictHeader(icc, size, header.data(), i); data.push_back(icc[i] - header[i]); } diff --git a/third_party/jpeg-xl/lib/jxl/enc_image_bundle.cc b/third_party/jpeg-xl/lib/jxl/enc_image_bundle.cc --- a/third_party/jpeg-xl/lib/jxl/enc_image_bundle.cc +++ b/third_party/jpeg-xl/lib/jxl/enc_image_bundle.cc @@ -11,11 +11,10 @@ #include #include #include "lib/jxl/alpha.h" #include "lib/jxl/base/byte_order.h" -#include "lib/jxl/base/padded_bytes.h" #include "lib/jxl/color_encoding_internal.h" #include "lib/jxl/fields.h" #include "lib/jxl/image_bundle.h" namespace jxl { 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 @@ -14,11 +14,10 @@ #include #include #include #include "lib/jxl/base/compiler_specific.h" -#include "lib/jxl/base/padded_bytes.h" #include "lib/jxl/base/printf_macros.h" #include "lib/jxl/base/status.h" #include "lib/jxl/compressed_dc.h" #include "lib/jxl/dec_ans.h" #include "lib/jxl/enc_aux_out.h" 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 @@ -758,13 +758,13 @@ // no simple way to express that yet. patch_frame_info.ib_needs_color_transform = false; ib.SetFromImage(std::move(*reference_frame), state->shared.metadata->m.color_encoding); if (!ib.metadata()->extra_channel_info.empty()) { - // Add dummy extra channels to the patch image: patch encoding does not yet - // support extra channels, but the codec expects that the amount of extra - // channels in frames matches that in the metadata of the codestream. + // Add placeholder extra channels to the patch image: patch encoding does + // not yet support extra channels, but the codec expects that the amount of + // extra channels in frames matches that in the metadata of the codestream. std::vector extra_channels; extra_channels.reserve(ib.metadata()->extra_channel_info.size()); for (size_t i = 0; i < ib.metadata()->extra_channel_info.size(); i++) { extra_channels.emplace_back(ib.xsize(), ib.ysize()); // Must initialize the image with data to not affect blending with diff --git a/third_party/jpeg-xl/lib/jxl/enc_splines.h b/third_party/jpeg-xl/lib/jxl/enc_splines.h --- a/third_party/jpeg-xl/lib/jxl/enc_splines.h +++ b/third_party/jpeg-xl/lib/jxl/enc_splines.h @@ -4,24 +4,14 @@ // license that can be found in the LICENSE file. #ifndef LIB_JXL_ENC_SPLINES_H_ #define LIB_JXL_ENC_SPLINES_H_ -#include -#include - -#include -#include +#include -#include "lib/jxl/ans_params.h" -#include "lib/jxl/base/status.h" -#include "lib/jxl/chroma_from_luma.h" -#include "lib/jxl/dec_ans.h" -#include "lib/jxl/dec_bit_reader.h" -#include "lib/jxl/enc_ans.h" +#include "lib/jxl/enc_ans_params.h" #include "lib/jxl/enc_bit_writer.h" -#include "lib/jxl/entropy_coder.h" #include "lib/jxl/image.h" #include "lib/jxl/splines.h" namespace jxl { 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 @@ -14,29 +14,28 @@ #include #include "lib/jxl/base/byte_order.h" #include "lib/jxl/base/common.h" #include "lib/jxl/base/data_parallel.h" -#include "lib/jxl/base/padded_bytes.h" #include "lib/jxl/base/printf_macros.h" #include "lib/jxl/base/span.h" #include "lib/jxl/base/status.h" #include "lib/jxl/cms/jxl_cms.h" #include "lib/jxl/codec_in_out.h" #include "lib/jxl/enc_aux_out.h" #include "lib/jxl/enc_bit_writer.h" #include "lib/jxl/enc_external_image.h" #include "lib/jxl/enc_fast_lossless.h" #include "lib/jxl/enc_fields.h" -#include "lib/jxl/enc_file.h" #include "lib/jxl/enc_icc_codec.h" #include "lib/jxl/enc_params.h" #include "lib/jxl/encode_internal.h" #include "lib/jxl/exif.h" #include "lib/jxl/jpeg/enc_jpeg_data.h" #include "lib/jxl/luminance.h" #include "lib/jxl/memory_manager_internal.h" +#include "lib/jxl/padded_bytes.h" #include "lib/jxl/sanitizers.h" struct JxlErrorOrStatus { // NOLINTNEXTLINE(google-explicit-constructor) operator jxl::Status() const { @@ -348,16 +347,18 @@ jxl::Status JxlEncoderStruct::AppendBox(const jxl::BoxType& type, bool unbounded, size_t box_max_size, const WriteBox& write_box) { size_t current_position = output_processor.CurrentPosition(); bool large_box = false; + size_t box_header_size = 0; if (box_max_size >= jxl::kLargeBoxContentSizeThreshold && !unbounded) { - output_processor.Seek(current_position + jxl::kLargeBoxHeaderSize); + box_header_size = jxl::kLargeBoxHeaderSize; large_box = true; } else { - output_processor.Seek(current_position + jxl::kSmallBoxHeaderSize); + box_header_size = jxl::kSmallBoxHeaderSize; } + output_processor.Seek(current_position + box_header_size); size_t box_contents_start = output_processor.CurrentPosition(); JXL_RETURN_IF_ERROR(write_box()); size_t box_contents_end = output_processor.CurrentPosition(); output_processor.Seek(current_position); JXL_ASSERT(box_contents_end >= box_contents_start); @@ -370,13 +371,15 @@ // We need to release the buffer before Seek. { JXL_ASSIGN_OR_RETURN( auto buffer, output_processor.GetBuffer(box_contents_start - current_position)); - buffer.advance(jxl::WriteBoxHeader(type, - box_contents_end - box_contents_start, - unbounded, large_box, buffer.data())); + const size_t n = + jxl::WriteBoxHeader(type, box_contents_end - box_contents_start, + unbounded, large_box, buffer.data()); + JXL_ASSERT(n == box_header_size); + buffer.advance(n); } output_processor.Seek(box_contents_end); output_processor.SetFinalizedPosition(); return jxl::OkStatus(); } @@ -408,17 +411,22 @@ return JPEGXL_MAJOR_VERSION * 1000000 + JPEGXL_MINOR_VERSION * 1000 + JPEGXL_PATCH_VERSION; } namespace { + +void WriteJxlpBoxCounter(uint32_t counter, bool last, uint8_t* buffer) { + if (last) counter |= 0x80000000; + for (size_t i = 0; i < 4; i++) { + buffer[i] = counter >> (8 * (3 - i)) & 0xff; + } +} + void WriteJxlpBoxCounter(uint32_t counter, bool last, JxlOutputProcessorBuffer& buffer) { - if (last) counter |= 0x80000000; uint8_t buf[4]; - for (size_t i = 0; i < 4; i++) { - buf[i] = counter >> (8 * (3 - i)) & 0xff; - } + WriteJxlpBoxCounter(counter, last, buf); buffer.append(buf, 4); } void QueueFrame( const JxlEncoderFrameSettings* frame_settings, @@ -820,10 +828,12 @@ } } wrote_bytes = true; } + output_processor.SetFinalizedPosition(); + // Choose frame or box processing: exactly one of the two unique pointers (box // or frame) in the input queue item is non-null. if (input.frame || input.fast_lossless_frame) { jxl::MemoryManagerUniquePtr input_frame = std::move(input.frame); @@ -869,16 +879,31 @@ // they're the default value of 0 here. duration = 0; timecode = 0; } - bool last_frame = frames_closed && !num_queued_frames; + const bool last_frame = frames_closed && !num_queued_frames; + + // TODO(szabadka): Make this conditional on some upper bound of the + // compressed image size that can be calculated at this point. + size_t box_header_size = jxl::kLargeBoxHeaderSize; + bool use_large_box = true; - jxl::BitWriter writer; + const size_t frame_start_pos = output_processor.CurrentPosition(); + if (MustUseContainer()) { + if (!last_frame || jxlp_counter > 0) { + // If this is the last frame and no jxlp boxes were used yet, it's + // slightly more efficient to write a jxlc box since it has 4 bytes + // less overhead. + box_header_size += 4; // jxlp_counter field + } + output_processor.Seek(frame_start_pos + box_header_size); + } + const size_t frame_codestream_start = output_processor.CurrentPosition(); - std::function append_frame_codestream; - size_t codestream_upper_bound = 0; + JXL_RETURN_IF_ERROR(AppendData(output_processor, bytes)); + bytes.clear(); if (input_frame) { jxl::PassesEncoderState enc_state; frame_index_box.AddFrame(codestream_bytes_written_end_of_frame, duration, @@ -936,11 +961,11 @@ if (input_frame->option_values.header.layer_info.have_crop) { ib.origin.x0 = input_frame->option_values.header.layer_info.crop_x0; ib.origin.y0 = input_frame->option_values.header.layer_info.crop_y0; } - JXL_ASSERT(writer.BitsWritten() == 0); + jxl::BitWriter writer; if (!jxl::EncodeFrame(input_frame->option_values.cparams, frame_info, &metadata, input_frame->frame, &enc_state, cms, thread_pool.get(), &writer, input_frame->option_values.aux_out)) { return JXL_API_ERROR(this, JXL_ENC_ERR_GENERIC, @@ -952,74 +977,64 @@ jxl::DivCeil(writer.BitsWritten(), 8); // Possibly bytes already contains the codestream header: in case this is // the first frame, and the codestream header was not encoded as jxlp // above. - bytes.append(std::move(writer).TakeBytes()); - codestream_upper_bound = bytes.size(); - append_frame_codestream = [&bytes, this]() { - return AppendData(output_processor, bytes); - }; + JXL_RETURN_IF_ERROR( + AppendData(output_processor, std::move(writer).TakeBytes())); } else { JXL_CHECK(fast_lossless_frame); - JxlFastLosslessPrepareHeader(fast_lossless_frame.get(), - /*add_image_header=*/0, last_frame); - size_t fl_size = JxlFastLosslessOutputSize(fast_lossless_frame.get()); - codestream_upper_bound = fl_size + bytes.size(); - append_frame_codestream = [&bytes, &fast_lossless_frame, fl_size, - this]() { - if (!bytes.empty()) { - JXL_RETURN_IF_ERROR(AppendData(output_processor, bytes)); - } - size_t written = 0; - while (true) { - JXL_ASSIGN_OR_RETURN( - auto buffer, output_processor.GetBuffer(32, fl_size - written)); - size_t n = JxlFastLosslessWriteOutput(fast_lossless_frame.get(), - buffer.data(), buffer.size()); - if (n == 0) break; - buffer.advance(n); - written += n; - }; - return jxl::OkStatus(); + auto runner = +[](void* void_pool, void* opaque, void fun(void*, size_t), + size_t count) { + auto* pool = reinterpret_cast(void_pool); + JXL_CHECK(jxl::RunOnPool( + pool, 0, count, jxl::ThreadPool::NoInit, + [&](size_t i, size_t) { fun(opaque, i); }, "Encode fast lossless")); }; + JxlFastLosslessProcessFrame(fast_lossless_frame.get(), last_frame, + thread_pool.get(), runner, &output_processor); } + const size_t frame_codestream_end = output_processor.CurrentPosition(); + const size_t frame_codestream_size = + frame_codestream_end - frame_codestream_start; + if (MustUseContainer()) { + output_processor.Seek(frame_start_pos); + std::vector box_header(box_header_size); if (last_frame && jxlp_counter == 0) { - // If this is the last frame and no jxlp boxes were used yet, it's - // slightly more efficient to write a jxlc box since it has 4 bytes - // less overhead. - JXL_RETURN_IF_ERROR( - AppendBox(jxl::MakeBoxType("jxlc"), /*unbounded=*/false, - codestream_upper_bound, append_frame_codestream)); +#if JXL_ENABLE_ASSERT + const size_t n = +#endif + jxl::WriteBoxHeader(jxl::MakeBoxType("jxlc"), frame_codestream_size, + /*unbounded=*/false, use_large_box, + &box_header[0]); + JXL_ASSERT(n == box_header_size); } else { - JXL_RETURN_IF_ERROR(AppendBox( - jxl::MakeBoxType("jxlp"), /*unbounded=*/false, - codestream_upper_bound + 4, [&]() { - { - JXL_ASSIGN_OR_RETURN(auto buffer, - output_processor.GetBuffer(4)); - WriteJxlpBoxCounter(jxlp_counter++, last_frame, buffer); - } - return append_frame_codestream(); - })); +#if JXL_ENABLE_ASSERT + const size_t n = +#endif + jxl::WriteBoxHeader( + jxl::MakeBoxType("jxlp"), frame_codestream_size + 4, + /*unbounded=*/false, use_large_box, &box_header[0]); + JXL_ASSERT(n == box_header_size - 4); + WriteJxlpBoxCounter(jxlp_counter++, last_frame, + &box_header[box_header_size - 4]); } - } else { - JXL_RETURN_IF_ERROR(append_frame_codestream()); - output_processor.SetFinalizedPosition(); + JXL_RETURN_IF_ERROR(AppendData(output_processor, box_header)); + JXL_ASSERT(output_processor.CurrentPosition() == frame_codestream_start); + output_processor.Seek(frame_codestream_end); } - + output_processor.SetFinalizedPosition(); if (input_frame) { last_used_cparams = input_frame->option_values.cparams; } if (last_frame && frame_index_box.StoreFrameIndexBox()) { std::vector index_box_content; EncodeFrameIndexBox(frame_index_box, index_box_content); - JXL_RETURN_IF_ERROR( - AppendBoxWithContents(jxl::MakeBoxType("jxli"), - jxl::Span(index_box_content))); + JXL_RETURN_IF_ERROR(AppendBoxWithContents(jxl::MakeBoxType("jxli"), + jxl::Bytes(index_box_content))); } } else { // Not a frame, so is a box instead jxl::MemoryManagerUniquePtr box = std::move(input.box); @@ -2003,11 +2018,11 @@ return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_API_USAGE, "Frame input is already closed"); } jxl::CodecInOut io; - if (!jxl::jpeg::DecodeImageJPG(jxl::Span(buffer, size), &io)) { + if (!jxl::jpeg::DecodeImageJPG(jxl::Bytes(buffer, size), &io)) { return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_BAD_INPUT, "Error during decode of input JPEG"); } if (!frame_settings->enc->color_encoding_set) { @@ -2071,19 +2086,18 @@ return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_API_USAGE, "Need to preserve EXIF and XMP to allow JPEG " "bitstream reconstruction"); } jxl::jpeg::JPEGData data_in = *io.Main().jpeg_data; - jxl::PaddedBytes jpeg_data; + std::vector jpeg_data; if (!jxl::jpeg::EncodeJPEGData(data_in, &jpeg_data, frame_settings->values.cparams)) { return JXL_API_ERROR( frame_settings->enc, JXL_ENC_ERR_JBRD, "JPEG bitstream reconstruction data cannot be encoded"); } - frame_settings->enc->jpeg_metadata = std::vector( - jpeg_data.data(), jpeg_data.data() + jpeg_data.size()); + frame_settings->enc->jpeg_metadata = jpeg_data; } auto queued_frame = jxl::MemoryManagerMakeUnique( &frame_settings->enc->memory_manager, // JxlEncoderQueuedFrame is a struct with no constructors, so we use the @@ -2185,24 +2199,28 @@ } return true; } -JxlEncoderStatus JxlEncoderAddImageFrame( - const JxlEncoderFrameSettings* frame_settings, - const JxlPixelFormat* pixel_format, const void* buffer, size_t size) { +namespace { +JxlEncoderStatus JxlEncoderAddImageFrameInternal( + const JxlEncoderFrameSettings* frame_settings, size_t xsize, size_t ysize, + bool streaming, jxl::JxlEncoderChunkedFrameAdapter frame_data) { + JxlChunkedFrameInputSource input = frame_data.GetInputSource(); + JxlPixelFormat pixel_format = {4, JXL_TYPE_UINT8, JXL_NATIVE_ENDIAN, 0}; + input.get_color_channels_pixel_format(input.opaque, &pixel_format); if (!frame_settings->enc->basic_info_set) { // Basic Info must be set. Otherwise, this is an API misuse. return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_API_USAGE, "Basic info or color encoding not set yet"); } if (frame_settings->enc->frames_closed) { return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_API_USAGE, "Frame input already closed"); } - if (pixel_format->num_channels < 3) { + if (pixel_format.num_channels < 3) { if (frame_settings->enc->basic_info.num_color_channels != 1) { return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_API_USAGE, "Grayscale pixel format input for an RGB image"); } } else { @@ -2212,54 +2230,53 @@ } } bool has_alpha = frame_settings->enc->metadata.m.HasAlpha(); - size_t xsize, ysize; - if (GetCurrentDimensions(frame_settings, xsize, ysize) != JXL_ENC_SUCCESS) { - return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_GENERIC, - "bad dimensions"); - } - // All required conditions to do fast-lossless. - if (CanDoFastLossless(frame_settings, pixel_format, has_alpha)) { - const size_t bytes_per_pixel = - pixel_format->data_type == JxlDataType::JXL_TYPE_UINT8 - ? pixel_format->num_channels - : pixel_format->num_channels * 2; - const size_t last_row_size = xsize * bytes_per_pixel; - const size_t align = pixel_format->align; - const size_t row_size = - (align > 1 ? jxl::DivCeil(last_row_size, align) * align - : last_row_size); - const size_t bytes_to_read = row_size * (ysize - 1) + last_row_size; - if (bytes_to_read > size) { - return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_API_USAGE, - "provided image buffer too small"); - } + if (CanDoFastLossless(frame_settings, &pixel_format, has_alpha)) { const bool big_endian = - pixel_format->endianness == JXL_BIG_ENDIAN || - (pixel_format->endianness == JXL_NATIVE_ENDIAN && !IsLittleEndian()); + pixel_format.endianness == JXL_BIG_ENDIAN || + (pixel_format.endianness == JXL_NATIVE_ENDIAN && !IsLittleEndian()); auto runner = +[](void* void_pool, void* opaque, void fun(void*, size_t), size_t count) { auto* pool = reinterpret_cast(void_pool); JXL_CHECK(jxl::RunOnPool( pool, 0, count, jxl::ThreadPool::NoInit, [&](size_t i, size_t) { fun(opaque, i); }, "Encode fast lossless")); }; - QueueFastLosslessFrame( - frame_settings, - JxlFastLosslessPrepareFrame( - reinterpret_cast(buffer), xsize, row_size, - ysize, pixel_format->num_channels, - frame_settings->enc->metadata.m.bit_depth.bits_per_sample, - big_endian, /*effort=*/2, frame_settings->enc->thread_pool.get(), - runner)); + auto frame_state = JxlFastLosslessPrepareFrame( + input, xsize, ysize, pixel_format.num_channels, + frame_settings->enc->metadata.m.bit_depth.bits_per_sample, big_endian, + /*effort=*/2); + if (!streaming) { + JxlFastLosslessProcessFrame(frame_state, /*is_last=*/false, + frame_settings->enc->thread_pool.get(), + runner, nullptr); + } + QueueFastLosslessFrame(frame_settings, frame_state); return JxlErrorOrStatus::Success(); } + size_t row_offset; + const void* buffer = input.get_color_channel_data_at( + input.opaque, 0, 0, xsize, ysize, &row_offset); + if (!buffer) { + return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_GENERIC, + "no buffer for color channels given"); + } + size_t bytes_per_pixel = + pixel_format.num_channels * BitsPerChannel(pixel_format.data_type) / 8; + std::vector color_data(bytes_per_pixel * xsize * ysize); + for (size_t y = 0; y < ysize; y++) { + memcpy(color_data.data() + y * xsize * bytes_per_pixel, + static_cast(buffer) + y * row_offset, + bytes_per_pixel * xsize); + } + input.release_buffer(input.opaque, buffer); + auto queued_frame = jxl::MemoryManagerMakeUnique( &frame_settings->enc->memory_manager, // JxlEncoderQueuedFrame is a struct with no constructors, so we use the // default move constructor there. jxl::JxlEncoderQueuedFrame{ @@ -2273,21 +2290,20 @@ "No frame queued?"); } jxl::ColorEncoding c_current; if (!frame_settings->enc->color_encoding_set) { - if ((pixel_format->data_type == JXL_TYPE_FLOAT) || - (pixel_format->data_type == JXL_TYPE_FLOAT16)) { - c_current = - jxl::ColorEncoding::LinearSRGB(pixel_format->num_channels < 3); + if ((pixel_format.data_type == JXL_TYPE_FLOAT) || + (pixel_format.data_type == JXL_TYPE_FLOAT16)) { + c_current = jxl::ColorEncoding::LinearSRGB(pixel_format.num_channels < 3); } else { - c_current = jxl::ColorEncoding::SRGB(pixel_format->num_channels < 3); + c_current = jxl::ColorEncoding::SRGB(pixel_format.num_channels < 3); } } else { c_current = frame_settings->enc->metadata.m.color_encoding; } - uint32_t num_channels = pixel_format->num_channels; + uint32_t num_channels = pixel_format.num_channels; size_t has_interleaved_alpha = static_cast(num_channels == 2 || num_channels == 4); if (has_interleaved_alpha > frame_settings->enc->metadata.m.num_extra_channels) { return JXL_API_ERROR( @@ -2322,21 +2338,20 @@ queued_frame->frame.blend = frame_settings->values.header.layer_info.blend_info.source > 0; if (JXL_ENC_SUCCESS != VerifyInputBitDepth(frame_settings->values.image_bit_depth, - *pixel_format)) { + pixel_format)) { return JXL_API_ERROR_NOSET("Invalid input bit depth"); } size_t bits_per_sample = GetBitDepth(frame_settings->values.image_bit_depth, - frame_settings->enc->metadata.m, *pixel_format); - const uint8_t* uint8_buffer = reinterpret_cast(buffer); - if (!jxl::ConvertFromExternal( - jxl::Span(uint8_buffer, size), xsize, ysize, c_current, - bits_per_sample, *pixel_format, - frame_settings->enc->thread_pool.get(), &(queued_frame->frame))) { + frame_settings->enc->metadata.m, pixel_format); + if (!jxl::ConvertFromExternal(jxl::Bytes(color_data), xsize, ysize, c_current, + bits_per_sample, pixel_format, + frame_settings->enc->thread_pool.get(), + &(queued_frame->frame))) { return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_API_USAGE, "Invalid input buffer"); } if (frame_settings->values.lossless && frame_settings->enc->metadata.m.xyb_encoded) { @@ -2348,46 +2363,49 @@ frame_settings->enc->codestream_level; QueueFrame(frame_settings, queued_frame); return JxlErrorOrStatus::Success(); } +} // namespace + +JxlEncoderStatus JxlEncoderAddImageFrame( + const JxlEncoderFrameSettings* frame_settings, + const JxlPixelFormat* pixel_format, const void* buffer, size_t size) { + size_t xsize, ysize; + if (GetCurrentDimensions(frame_settings, xsize, ysize) != JXL_ENC_SUCCESS) { + return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_GENERIC, + "bad dimensions"); + } + jxl::JxlEncoderChunkedFrameAdapter frame_data; + if (!frame_data.SetFromBuffer(reinterpret_cast(buffer), size, + *pixel_format, xsize, ysize)) { + return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_API_USAGE, + "provided image buffer too small"); + } + return JxlEncoderAddImageFrameInternal(frame_settings, xsize, ysize, + /*streaming=*/false, frame_data); +} JxlEncoderStatus JxlEncoderAddChunkedFrame( const JxlEncoderFrameSettings* frame_settings, JXL_BOOL is_last_frame, JxlChunkedFrameInputSource chunked_frame_input) { - size_t xsize; - size_t ysize; + size_t xsize, ysize; if (GetCurrentDimensions(frame_settings, xsize, ysize) != JXL_ENC_SUCCESS) { return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_GENERIC, "bad dimensions"); } - // TODO(veluca): implement this without immediately buffering the whole frame. + bool streaming = frame_settings->enc->output_processor.OutputProcessorSet(); + jxl::JxlEncoderChunkedFrameAdapter frame_data; + frame_data.SetInputSource(chunked_frame_input); + auto status = JxlEncoderAddImageFrameInternal(frame_settings, xsize, ysize, + streaming, frame_data); + if (status != JXL_ENC_SUCCESS) return status; + // In the next line,`color_pixel_format` gets overwritten JxlPixelFormat color_pixel_format = {4, JXL_TYPE_UINT8, JXL_NATIVE_ENDIAN, 0}; chunked_frame_input.get_color_channels_pixel_format( chunked_frame_input.opaque, &color_pixel_format); - size_t bytes_per_pixel = color_pixel_format.num_channels * - BitsPerChannel(color_pixel_format.data_type) / 8; - std::vector color_data(bytes_per_pixel * xsize * ysize); - size_t row_offset = 0; - const void* buffer = chunked_frame_input.get_color_channel_data_at( - chunked_frame_input.opaque, 0, 0, xsize, ysize, &row_offset); - if (!buffer) { - return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_GENERIC, - "no buffer for color channels given"); - } - auto stride = xsize * bytes_per_pixel; - for (size_t y = 0; y < ysize; y++) { - memcpy(color_data.data() + y * stride, - static_cast(buffer) + y * row_offset, - bytes_per_pixel * xsize); - } - chunked_frame_input.release_buffer(chunked_frame_input.opaque, buffer); - - auto status = JxlEncoderAddImageFrame(frame_settings, &color_pixel_format, - color_data.data(), color_data.size()); - if (status != JXL_ENC_SUCCESS) return status; bool already_have_alpha = color_pixel_format.num_channels == 2 || color_pixel_format.num_channels == 4; for (size_t ec = 0; ec < frame_settings->enc->metadata.m.num_extra_channels; ec++) { if (frame_settings->enc->metadata.m.extra_channel_info[ec].type == @@ -2406,10 +2424,11 @@ chunked_frame_input.opaque, ec, &pixel_format); size_t bytes_per_pixel = pixel_format.num_channels * BitsPerChannel(pixel_format.data_type) / 8; std::vector data(bytes_per_pixel * xsize * ysize); auto stride = xsize * bytes_per_pixel; + size_t row_offset; const void* buffer = chunked_frame_input.get_extra_channel_data_at( chunked_frame_input.opaque, ec, 0, 0, xsize, ysize, &row_offset); if (!buffer) { return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_GENERIC, "no buffer for extra channel given"); @@ -2426,11 +2445,11 @@ } if (is_last_frame) { JxlEncoderCloseInput(frame_settings->enc); } - if (frame_settings->enc->output_processor.OutputProcessorSet()) { + if (streaming) { return JxlEncoderFlushInput(frame_settings->enc); } return JxlErrorOrStatus::Success(); } @@ -2519,12 +2538,12 @@ size_t bits_per_sample = GetBitDepth( frame_settings->values.image_bit_depth, frame_settings->enc->metadata.m.extra_channel_info[index], ec_format); const uint8_t* uint8_buffer = reinterpret_cast(buffer); auto queued_frame = frame_settings->enc->input_queue.back().frame.get(); - if (!jxl::ConvertFromExternal(jxl::Span(uint8_buffer, size), - xsize, ysize, bits_per_sample, ec_format, 0, + if (!jxl::ConvertFromExternal(jxl::Bytes(uint8_buffer, size), xsize, ysize, + bits_per_sample, ec_format, 0, frame_settings->enc->thread_pool.get(), &queued_frame->frame.extra_channels()[index])) { return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_API_USAGE, "Failed to set buffer for extra channel"); } @@ -2658,16 +2677,16 @@ return JxlErrorOrStatus::Success(); } void JxlColorEncodingSetToSRGB(JxlColorEncoding* color_encoding, JXL_BOOL is_gray) { - jxl::ColorEncoding::SRGB(is_gray).ToExternal(color_encoding); + *color_encoding = jxl::ColorEncoding::SRGB(is_gray).ToExternal(); } void JxlColorEncodingSetToLinearSRGB(JxlColorEncoding* color_encoding, JXL_BOOL is_gray) { - jxl::ColorEncoding::LinearSRGB(is_gray).ToExternal(color_encoding); + *color_encoding = jxl::ColorEncoding::LinearSRGB(is_gray).ToExternal(); } void JxlEncoderAllowExpertOptions(JxlEncoder* enc) { enc->allow_expert_options = true; } 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 @@ -17,17 +17,19 @@ #include #include #include #include +#include "lib/jxl/base/c_callback_support.h" +#include "lib/jxl/base/common.h" #include "lib/jxl/base/data_parallel.h" -#include "lib/jxl/base/padded_bytes.h" #include "lib/jxl/base/status.h" #include "lib/jxl/enc_aux_out.h" #include "lib/jxl/enc_fast_lossless.h" #include "lib/jxl/enc_frame.h" #include "lib/jxl/memory_manager_internal.h" +#include "lib/jxl/padded_bytes.h" namespace jxl { /* Frame index box 'jxli' will start with Varint() for NF: has type Varint(): number of frames listed in the index. @@ -138,10 +140,100 @@ 'l', ' ', 0, 0, 0, 0, 'j', 'x', 'l', ' '}; constexpr std::array kLevelBoxHeader = {0, 0, 0, 0x9, 'j', 'x', 'l', 'l'}; +// Common adapter for an existing frame input source or a whole-image input +// buffer. +class JxlEncoderChunkedFrameAdapter { + public: + void SetInputSource(JxlChunkedFrameInputSource input_source) { + input_source_ = input_source; + } + + bool SetFromBuffer(const uint8_t* buffer, size_t size, JxlPixelFormat format, + size_t xsize, size_t ysize) { + buffer_ = buffer; + format_ = format; + xsize_ = xsize; + ysize_ = ysize; + if (format.data_type == JXL_TYPE_UINT8) { + bytes_per_pixel_ = format_.num_channels; + } else if (format.data_type == JXL_TYPE_UINT16 || + format.data_type == JXL_TYPE_FLOAT16) { + bytes_per_pixel_ = 2 * format.num_channels; + } else if (format.data_type == JXL_TYPE_FLOAT) { + bytes_per_pixel_ = 4 * format.num_channels; + } else { + return false; + } + const size_t last_row_size = xsize_ * bytes_per_pixel_; + const size_t align = format_.align; + stride_ = (align > 1 ? jxl::DivCeil(last_row_size, align) * align + : last_row_size); + const size_t min_buffer_size = stride_ * (ysize_ - 1) + last_row_size; + return min_buffer_size <= size; + } + + JxlChunkedFrameInputSource GetInputSource() { + if (buffer_) { + return JxlChunkedFrameInputSource{ + this, + METHOD_TO_C_CALLBACK( + &JxlEncoderChunkedFrameAdapter::GetColorChannelsPixelFormat), + METHOD_TO_C_CALLBACK( + &JxlEncoderChunkedFrameAdapter::GetColorChannelDataAt), + METHOD_TO_C_CALLBACK( + &JxlEncoderChunkedFrameAdapter::GetExtraChannelPixelFormat), + METHOD_TO_C_CALLBACK( + &JxlEncoderChunkedFrameAdapter::GetExtraChannelDataAt), + METHOD_TO_C_CALLBACK( + &JxlEncoderChunkedFrameAdapter::ReleaseCurrentData)}; + } + return input_source_; + } + + bool HasBuffer() const { return buffer_; } + + private: + void GetColorChannelsPixelFormat(JxlPixelFormat* pixel_format) { + *pixel_format = format_; + } + + const void* GetColorChannelDataAt(size_t xpos, size_t ypos, size_t xsize, + size_t ysize, size_t* row_offset) { + JXL_ASSERT(ypos + ysize <= ysize_); + JXL_ASSERT(xpos + xsize <= xsize_); + *row_offset = stride_; + return buffer_ + ypos * stride_ + xpos * bytes_per_pixel_; + } + + void GetExtraChannelPixelFormat(size_t ec_index, + JxlPixelFormat* pixel_format) { + JXL_ABORT("Not implemented."); + } + + const void* GetExtraChannelDataAt(size_t ec_index, size_t xpos, size_t ypos, + size_t xsize, size_t ysize, + size_t* row_offset) { + JXL_ABORT("Not implemented"); + } + + void ReleaseCurrentData(const void* buffer) { + // No dynamic memory is allocated in GetColorChannelDataAt or + // GetExtraChannelDataAt. Therefore, no cleanup is required here. + } + + JxlChunkedFrameInputSource input_source_; + const uint8_t* buffer_ = nullptr; + JxlPixelFormat format_; + size_t xsize_; + size_t ysize_; + size_t bytes_per_pixel_; + size_t stride_; +}; + struct JxlEncoderQueuedFrame { JxlEncoderFrameSettingsValues option_values; ImageBundle frame; std::vector ec_initialized; }; 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 @@ -9,10 +9,11 @@ #include #include #include #include +#include #include #include #include #include #include @@ -22,19 +23,38 @@ #include "jxl/types.h" #include "lib/extras/codec.h" #include "lib/extras/dec/jxl.h" #include "lib/extras/metrics.h" #include "lib/extras/packed_image.h" +#include "lib/jxl/base/c_callback_support.h" #include "lib/jxl/cms/jxl_cms.h" #include "lib/jxl/common.h" // JXL_HIGH_PRECISION #include "lib/jxl/encode_internal.h" #include "lib/jxl/jpeg/dec_jpeg_data.h" #include "lib/jxl/jpeg/dec_jpeg_data_writer.h" #include "lib/jxl/test_image.h" #include "lib/jxl/test_utils.h" #include "lib/jxl/testing.h" +namespace { +bool SameDecodedPixels(const std::vector& compressed0, + const std::vector& compressed1) { + jxl::extras::JXLDecompressParams dparams; + dparams.accepted_formats = { + {3, JXL_TYPE_UINT16, JXL_LITTLE_ENDIAN, 0}, + {4, JXL_TYPE_UINT16, JXL_LITTLE_ENDIAN, 0}, + }; + jxl::extras::PackedPixelFile ppf0; + EXPECT_TRUE(DecodeImageJXL(compressed0.data(), compressed0.size(), dparams, + nullptr, &ppf0, nullptr)); + jxl::extras::PackedPixelFile ppf1; + EXPECT_TRUE(DecodeImageJXL(compressed1.data(), compressed1.size(), dparams, + nullptr, &ppf1, nullptr)); + return jxl::test::SamePixels(ppf0, ppf1); +} +} // namespace + TEST(EncodeTest, AddFrameAfterCloseInputTest) { JxlEncoderPtr enc = JxlEncoderMake(nullptr); EXPECT_NE(nullptr, enc.get()); JxlEncoderCloseInput(enc.get()); @@ -71,11 +91,11 @@ EXPECT_NE(nullptr, enc.get()); JxlEncoderCloseInput(enc.get()); const std::string jpeg_path = "jxl/flower/flower.png.im_q85_420.jpg"; - const jxl::PaddedBytes orig = jxl::test::ReadTestData(jpeg_path); + const std::vector orig = jxl::test::ReadTestData(jpeg_path); JxlEncoderFrameSettings* frame_settings = JxlEncoderFrameSettingsCreate(enc.get(), NULL); EXPECT_EQ(JXL_ENC_ERROR, @@ -198,12 +218,11 @@ compressed.resize(next_out - compressed.data()); EXPECT_LE(compressed.size(), max_compressed_size); EXPECT_EQ(JXL_ENC_SUCCESS, process_result); jxl::CodecInOut decoded_io; EXPECT_TRUE(jxl::test::DecodeFile( - {}, jxl::Span(compressed.data(), compressed.size()), - &decoded_io)); + {}, jxl::Bytes(compressed.data(), compressed.size()), &decoded_io)); EXPECT_LE( ComputeDistance2(input_io.Main(), decoded_io.Main(), *JxlGetDefaultCms()), #if JXL_HIGH_PRECISION 1.84); @@ -651,11 +670,11 @@ // Extended types are not used in JXL. However, the box format itself // supports this so they are handled correctly. char extended_type[16] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; // Box data. - jxl::Span data = jxl::Span(nullptr, 0); + jxl::Span data = jxl::Bytes(nullptr, 0); // If the size is not given, the datasize extends to the end of the file. // If this field is false, the size field is not encoded when the box is // serialized. bool data_size_given = true; @@ -695,20 +714,18 @@ if (box_size > in->size()) { // The box is fine, but the input is too short. return true; } data_size_given = true; - data = jxl::Span(in->data() + header_size, - box_size - header_size); + data = jxl::Bytes(in->data() + header_size, box_size - header_size); } else { data_size_given = false; - data = jxl::Span(in->data() + header_size, - in->size() - header_size); + data = jxl::Bytes(in->data() + header_size, in->size() - header_size); } - *in = jxl::Span(in->data() + header_size + data.size(), - in->size() - header_size - data.size()); + *in = jxl::Bytes(in->data() + header_size + data.size(), + in->size() - header_size - data.size()); return true; } }; struct Container { @@ -802,11 +819,11 @@ compressed.resize(next_out - compressed.data()); EXPECT_EQ(JXL_ENC_SUCCESS, process_result); Container container = {}; jxl::Span encoded_span = - jxl::Span(compressed.data(), compressed.size()); + jxl::Bytes(compressed.data(), compressed.size()); EXPECT_TRUE(container.Decode(&encoded_span)); EXPECT_EQ(0u, encoded_span.size()); bool found_jxlc = false; bool found_jxlp = false; // The encoder is allowed to either emit a jxlc or one or more jxlp. @@ -875,11 +892,11 @@ compressed.resize(next_out - compressed.data()); EXPECT_EQ(JXL_ENC_SUCCESS, process_result); Container container = {}; jxl::Span encoded_span = - jxl::Span(compressed.data(), compressed.size()); + jxl::Bytes(compressed.data(), compressed.size()); EXPECT_TRUE(container.Decode(&encoded_span)); EXPECT_EQ(0u, encoded_span.size()); EXPECT_EQ(0, memcmp("jxll", container.boxes[0].type, 4)); } @@ -912,11 +929,11 @@ EXPECT_EQ(JXL_ENC_ERROR, JxlEncoderSetBasicInfo(enc.get(), &basic_info)); } TEST(EncodeTest, JXL_TRANSCODE_JPEG_TEST(JPEGReconstructionTest)) { const std::string jpeg_path = "jxl/flower/flower.png.im_q85_420.jpg"; - const jxl::PaddedBytes orig = jxl::test::ReadTestData(jpeg_path); + const std::vector orig = jxl::test::ReadTestData(jpeg_path); JxlEncoderPtr enc = JxlEncoderMake(nullptr); JxlEncoderFrameSettings* frame_settings = JxlEncoderFrameSettingsCreate(enc.get(), NULL); @@ -952,11 +969,11 @@ EXPECT_EQ(0, memcmp(decoded_jpeg_bytes.data(), orig.data(), orig.size())); } TEST(EncodeTest, JXL_TRANSCODE_JPEG_TEST(ProgressiveJPEGReconstructionTest)) { const std::string jpeg_path = "jxl/flower/flower.png.im_q85_420.jpg"; - const jxl::PaddedBytes orig = jxl::test::ReadTestData(jpeg_path); + const std::vector orig = jxl::test::ReadTestData(jpeg_path); JxlEncoderPtr enc = JxlEncoderMake(nullptr); JxlEncoderFrameSettings* frame_settings = JxlEncoderFrameSettingsCreate(enc.get(), NULL); @@ -1461,13 +1478,13 @@ for (int skip_color_encoding = 0; skip_color_encoding < 2; skip_color_encoding++) { // cannot set color encoding if basic info is not set if (skip_basic_info && !skip_color_encoding) continue; const std::string jpeg_path = "jxl/flower/flower_cropped.jpg"; - const jxl::PaddedBytes orig = jxl::test::ReadTestData(jpeg_path); + const std::vector orig = jxl::test::ReadTestData(jpeg_path); jxl::CodecInOut orig_io; - ASSERT_TRUE(SetFromBytes(jxl::Span(orig), &orig_io, + ASSERT_TRUE(SetFromBytes(jxl::Bytes(orig), &orig_io, /*pool=*/nullptr)); JxlEncoderPtr enc = JxlEncoderMake(nullptr); JxlEncoderFrameSettings* frame_settings = JxlEncoderFrameSettingsCreate(enc.get(), NULL); @@ -1509,12 +1526,11 @@ compressed.resize(next_out - compressed.data()); EXPECT_EQ(JXL_ENC_SUCCESS, process_result); jxl::CodecInOut decoded_io; EXPECT_TRUE(jxl::test::DecodeFile( - {}, jxl::Span(compressed.data(), compressed.size()), - &decoded_io)); + {}, jxl::Bytes(compressed.data(), compressed.size()), &decoded_io)); EXPECT_LE(ComputeDistance2(orig_io.Main(), decoded_io.Main(), *JxlGetDefaultCms()), 3.5); } @@ -1527,29 +1543,21 @@ JxlStreamingAdapter(JxlEncoder* encoder, bool return_large_buffers, bool can_seek) : return_large_buffers_(return_large_buffers) { struct JxlEncoderOutputProcessor output_processor; output_processor.opaque = this; - output_processor.get_buffer = [](void* opaque, size_t* size) { - return static_cast(opaque)->GetBuffer(size); - }; + output_processor.get_buffer = + METHOD_TO_C_CALLBACK(&JxlStreamingAdapter::GetBuffer); if (can_seek) { - output_processor.seek = [](void* opaque, uint64_t position) { - return static_cast(opaque)->Seek(position); - }; + output_processor.seek = METHOD_TO_C_CALLBACK(&JxlStreamingAdapter::Seek); } else { output_processor.seek = nullptr; } - output_processor.set_finalized_position = [](void* opaque, - uint64_t finalized_position) { - return static_cast(opaque)->SetFinalizedPosition( - finalized_position); - }; - output_processor.release_buffer = [](void* opaque, size_t written_bytes) { - return static_cast(opaque)->ReleaseBuffer( - written_bytes); - }; + output_processor.set_finalized_position = + METHOD_TO_C_CALLBACK(&JxlStreamingAdapter::SetFinalizedPosition); + output_processor.release_buffer = + METHOD_TO_C_CALLBACK(&JxlStreamingAdapter::ReleaseBuffer); EXPECT_EQ(JxlEncoderSetOutputProcessor(encoder, output_processor), JXL_ENC_SUCCESS); } std::vector output() && { @@ -1595,23 +1603,96 @@ size_t position_ = 0; size_t finalized_position_ = 0; bool return_large_buffers_; }; +class JxlChunkedFrameInputSourceAdapter { + private: + static const void* GetDataAt(const jxl::extras::PackedImage& image, + size_t xpos, size_t ypos, size_t* row_offset) { + JxlDataType data_type = image.format.data_type; + size_t num_channels = image.format.num_channels; + size_t bytes_per_pixel = + num_channels * jxl::extras::PackedImage::BitsPerChannel(data_type) / 8; + *row_offset = image.stride; + return static_cast(image.pixels()) + bytes_per_pixel * xpos + + ypos * image.stride; + } + + public: + // Constructor to wrap the image data or any other state + explicit JxlChunkedFrameInputSourceAdapter( + jxl::extras::PackedImage color_channel, + jxl::extras::PackedImage extra_channel) + : colorchannel_(std::move(color_channel)), + extra_channel_(std::move(extra_channel)) {} + + void GetColorChannelsPixelFormat(JxlPixelFormat* pixel_format) { + *pixel_format = colorchannel_.format; + } + + const void* GetColorChannelDataAt(size_t xpos, size_t ypos, size_t xsize, + size_t ysize, size_t* row_offset) { + return GetDataAt(colorchannel_, xpos, ypos, row_offset); + } + + void GetExtraChannelPixelFormat(size_t ec_index, + JxlPixelFormat* pixel_format) { + // In this test, we we the same color channel data, so `ec_index` is never + // used + *pixel_format = extra_channel_.format; + } + + const void* GetExtraChannelDataAt(size_t ec_index, size_t xpos, size_t ypos, + size_t xsize, size_t ysize, + size_t* row_offset) { + // In this test, we we the same color channel data, so `ec_index` is never + // used + return GetDataAt(extra_channel_, xpos, ypos, row_offset); + } + + void ReleaseCurrentData(const void* buffer) { + // No dynamic memory is allocated in GetColorChannelDataAt or + // GetExtraChannelDataAt. Therefore, no cleanup is required here. + } + + JxlChunkedFrameInputSource GetInputSource() { + return JxlChunkedFrameInputSource{ + this, + METHOD_TO_C_CALLBACK( + &JxlChunkedFrameInputSourceAdapter::GetColorChannelsPixelFormat), + METHOD_TO_C_CALLBACK( + &JxlChunkedFrameInputSourceAdapter::GetColorChannelDataAt), + METHOD_TO_C_CALLBACK( + &JxlChunkedFrameInputSourceAdapter::GetExtraChannelPixelFormat), + METHOD_TO_C_CALLBACK( + &JxlChunkedFrameInputSourceAdapter::GetExtraChannelDataAt), + METHOD_TO_C_CALLBACK( + &JxlChunkedFrameInputSourceAdapter::ReleaseCurrentData)}; + } + + private: + const jxl::extras::PackedImage colorchannel_; + const jxl::extras::PackedImage extra_channel_; +}; + struct StreamingTestParam { size_t bitmask; bool use_container() const { return bitmask & 0x1; } bool return_large_buffers() const { return bitmask & 0x2; } bool multiple_frames() const { return bitmask & 0x4; } bool fast_lossless() const { return bitmask & 0x8; } bool can_seek() const { return bitmask & 0x10; } bool with_extra_channels() const { return bitmask & 0x20; } bool color_includes_alpha() const { return bitmask & 0x40; } + bool onegroup() const { return bitmask & 0x80; } + + bool is_lossless() const { return fast_lossless(); } static std::vector All() { std::vector params; - for (size_t bitmask = 0; bitmask < 128; bitmask++) { + for (size_t bitmask = 0; bitmask < 256; bitmask++) { params.push_back(StreamingTestParam{bitmask}); } return params; } }; @@ -1642,10 +1723,15 @@ if (p.color_includes_alpha()) { out << "ColorIncludesAlpha_"; } else { out << "ColorWithoutAlpha_"; } + if (p.onegroup()) { + out << "OneGroup_"; + } else { + out << "MultiGroup_"; + } return out; } } // namespace @@ -1660,293 +1746,247 @@ .SetAllBitDepths(bits_per_sample); image.AddFrame().RandomFill(); } static void SetUpBasicInfo(JxlBasicInfo& basic_info, size_t xsize, size_t ysize, size_t number_extra_channels, - bool include_alpha) { + bool include_alpha, bool is_lossless) { basic_info.xsize = xsize; basic_info.ysize = ysize; basic_info.num_extra_channels = number_extra_channels + include_alpha; + basic_info.uses_original_profile = is_lossless; } - static void SetupEncoder(JxlEncoderStruct* enc, const StreamingTestParam& p, + static void SetupEncoder(JxlEncoderFrameSettings* frame_settings, + const StreamingTestParam& p, const JxlBasicInfo& basic_info, - size_t number_extra_channels, - const jxl::extras::PackedImage& frame, - bool add_image_frames) { - JxlEncoderFrameSettings* frame_settings = - JxlEncoderFrameSettingsCreate(enc, NULL); + size_t number_extra_channels) { + JxlEncoderStruct* enc = frame_settings->enc; + EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetBasicInfo(enc, &basic_info)); if (p.fast_lossless()) { - JxlEncoderSetFrameLossless(frame_settings, JXL_TRUE); - JxlEncoderFrameSettingsSetOption(frame_settings, - JXL_ENC_FRAME_SETTING_EFFORT, 1); + EXPECT_EQ(JXL_ENC_SUCCESS, + JxlEncoderSetFrameLossless(frame_settings, JXL_TRUE)); + EXPECT_EQ(JXL_ENC_SUCCESS, + JxlEncoderFrameSettingsSetOption( + frame_settings, JXL_ENC_FRAME_SETTING_EFFORT, 1)); } - EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetBasicInfo(enc, &basic_info)); JxlColorEncoding color_encoding; JxlColorEncodingSetToSRGB(&color_encoding, /*is_gray=*/false); EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetColorEncoding(enc, &color_encoding)); if (p.use_container()) { - JxlEncoderSetCodestreamLevel(enc, 10); + EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetCodestreamLevel(enc, 10)); } for (size_t i = 0; i < number_extra_channels; i++) { JxlExtraChannelInfo channel_info; JxlExtraChannelType channel_type = JXL_CHANNEL_THERMAL; JxlEncoderInitExtraChannelInfo(channel_type, &channel_info); EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetExtraChannelInfo(enc, i, &channel_info)); } - size_t frame_count = static_cast(add_image_frames) * - (1 + static_cast(p.multiple_frames())); + } + + static void SetupInputNonStreaming(JxlEncoderFrameSettings* frame_settings, + const StreamingTestParam& p, + size_t number_extra_channels, + const jxl::extras::PackedImage& frame, + const jxl::extras::PackedImage& ec_frame) { + size_t frame_count = static_cast(p.multiple_frames()) + 1; for (size_t i = 0; i < frame_count; i++) { + { + // Copy pixel data here because it is only guaranteed to be available + // during the call to JxlEncoderAddImageFrame(). + std::vector pixels(frame.pixels_size); + memcpy(&pixels[0], frame.pixels(), pixels.size()); + EXPECT_EQ(JXL_ENC_SUCCESS, + JxlEncoderAddImageFrame(frame_settings, &frame.format, + pixels.data(), pixels.size())); + } + for (size_t i = 0; i < number_extra_channels; i++) { + // Copy pixel data here because it is only guaranteed to be available + // during the call to JxlEncoderSetExtraChannelBuffer(). + std::vector ec_pixels(ec_frame.pixels_size); + memcpy(&ec_pixels[0], ec_frame.pixels(), ec_pixels.size()); + EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetExtraChannelBuffer( + frame_settings, &ec_frame.format, + ec_pixels.data(), ec_pixels.size(), i)); + } + } + JxlEncoderCloseInput(frame_settings->enc); + } + + static void SetupInputStreaming(JxlEncoderFrameSettings* frame_settings, + const StreamingTestParam& p, + size_t number_extra_channels, + const jxl::extras::PackedImage& frame, + const jxl::extras::PackedImage& ec_frame) { + size_t frame_count = static_cast(p.multiple_frames()) + 1; + for (size_t i = 0; i < frame_count; i++) { + // Create local copy of pixels and adapter because they are only + // guarantted to be available during the JxlEncoderAddChunkedFrame() call. + JxlChunkedFrameInputSourceAdapter chunked_frame_adapter(frame.Copy(), + ec_frame.Copy()); EXPECT_EQ(JXL_ENC_SUCCESS, - JxlEncoderAddImageFrame(frame_settings, &frame.format, - frame.pixels(), frame.pixels_size)); - } - if (add_image_frames) { - JxlEncoderCloseInput(enc); + JxlEncoderAddChunkedFrame( + // should only set `JXL_TRUE` in the lass pass of the loop + frame_settings, i + 1 == frame_count ? JXL_TRUE : JXL_FALSE, + chunked_frame_adapter.GetInputSource())); } } }; TEST_P(EncoderStreamingTest, OutputCallback) { const StreamingTestParam p = GetParam(); - size_t xsize = 257; - size_t ysize = 259; - jxl::test::TestImage image; - SetupImage(p, xsize, ysize, 3, p.use_container() ? 16 : 8, image); - const auto& frame = image.ppf().frames[0].color; - JxlBasicInfo basic_info = image.ppf().info; - SetUpBasicInfo(basic_info, xsize, ysize, 0, false); - - std::vector compressed = std::vector(64); - // without sreaming - { - JxlEncoderPtr enc = JxlEncoderMake(nullptr); - ASSERT_NE(nullptr, enc.get()); - SetupEncoder(enc.get(), p, basic_info, 0, frame, true); - uint8_t* next_out = compressed.data(); - size_t avail_out = compressed.size(); - ProcessEncoder(enc.get(), compressed, next_out, avail_out); - } - // with streaming - { - JxlEncoderPtr enc = JxlEncoderMake(nullptr); - ASSERT_NE(nullptr, enc.get()); - JxlStreamingAdapter streaming_adapter(enc.get(), p.return_large_buffers(), - p.can_seek()); - SetupEncoder(enc.get(), p, basic_info, 0, frame, true); - EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderFlushInput(enc.get())); - streaming_adapter.CheckFinalWatermarkPosition(); - EXPECT_EQ(std::move(streaming_adapter).output(), compressed); - } -} - -class JxlChunkedFrameInputSourceAdapter { - private: - static const void* GetDataAt(const jxl::extras::PackedPixelFile& ppf, - size_t xpos, size_t ypos, size_t* row_offset) { - JxlDataType data_type = ppf.frames[0].color.format.data_type; - size_t num_channels = ppf.frames[0].color.format.num_channels; - size_t bytes_per_pixel = - num_channels * jxl::extras::PackedImage::BitsPerChannel(data_type) / 8; - *row_offset = ppf.frames[0].color.stride; - return static_cast(ppf.frames[0].color.pixels()) + - bytes_per_pixel * xpos + ypos * ppf.frames[0].color.stride; - } - - public: - // Constructor to wrap the image data or any other state - explicit JxlChunkedFrameInputSourceAdapter( - jxl::extras::PackedPixelFile color_channel, - jxl::extras::PackedPixelFile extra_channel) - : colorchannel_(std::move(color_channel)), - extra_channel_(std::move(extra_channel)) {} - - static void GetColorChannelsPixelFormat(void* opaque, - JxlPixelFormat* pixel_format) { - JxlChunkedFrameInputSourceAdapter* self = - static_cast(opaque); - *pixel_format = self->colorchannel_.frames[0].color.format; - } - - static const void* GetColorChannelDataAt(void* opaque, size_t xpos, - size_t ypos, size_t xsize, - size_t ysize, size_t* row_offset) { - JxlChunkedFrameInputSourceAdapter* self = - static_cast(opaque); - return GetDataAt(self->colorchannel_, xpos, ypos, row_offset); - } - - static void GetExtraChannelPixelFormat(void* opaque, size_t ec_index, - JxlPixelFormat* pixel_format) { - // In this test, we we the same color channel data, so `ec_index` is never - // used - JxlChunkedFrameInputSourceAdapter* self = - static_cast(opaque); - *pixel_format = self->extra_channel_.frames[0].color.format; - } - - static const void* GetExtraChannelDataAt(void* opaque, size_t ec_index, - size_t xpos, size_t ypos, - size_t xsize, size_t ysize, - size_t* row_offset) { - // In this test, we we the same color channel data, so `ec_index` is never - // used - JxlChunkedFrameInputSourceAdapter* self = - static_cast(opaque); - return GetDataAt(self->extra_channel_, xpos, ypos, row_offset); - } - - static void ReleaseCurrentData(void* opaque, const void* buffer) { - // No dynamic memory is allocated in GetColorChannelDataAt or - // GetExtraChannelDataAt. Therefore, no cleanup is required here. - } - - JxlChunkedFrameInputSource GetInputSource() { - return JxlChunkedFrameInputSource{ - this, - JxlChunkedFrameInputSourceAdapter::GetColorChannelsPixelFormat, - JxlChunkedFrameInputSourceAdapter::GetColorChannelDataAt, - JxlChunkedFrameInputSourceAdapter::GetExtraChannelPixelFormat, - JxlChunkedFrameInputSourceAdapter::GetExtraChannelDataAt, - JxlChunkedFrameInputSourceAdapter::ReleaseCurrentData}; - } - - private: - const jxl::extras::PackedPixelFile colorchannel_; - const jxl::extras::PackedPixelFile extra_channel_; -}; - -TEST_P(EncoderStreamingTest, ChunkedFrame) { - const StreamingTestParam p = GetParam(); - size_t xsize = 257; - size_t ysize = 259; + size_t xsize = p.onegroup() ? 17 : 257; + size_t ysize = p.onegroup() ? 19 : 259; size_t number_extra_channels = p.with_extra_channels() ? 5 : 0; jxl::test::TestImage image; SetupImage(p, xsize, ysize, p.color_includes_alpha() ? 4 : 3, p.use_container() ? 16 : 8, image); jxl::test::TestImage ec_image; SetupImage(p, xsize, ysize, 1, 8, ec_image); const auto& frame = image.ppf().frames[0].color; const auto& ec_frame = ec_image.ppf().frames[0].color; JxlBasicInfo basic_info = image.ppf().info; SetUpBasicInfo(basic_info, xsize, ysize, number_extra_channels, - p.color_includes_alpha()); + p.color_includes_alpha(), p.is_lossless()); + + std::vector compressed = std::vector(64); + // without sreaming + { + JxlEncoderPtr enc = JxlEncoderMake(nullptr); + ASSERT_NE(nullptr, enc.get()); + JxlEncoderFrameSettings* frame_settings = + JxlEncoderFrameSettingsCreate(enc.get(), NULL); + SetupEncoder(frame_settings, p, basic_info, number_extra_channels); + SetupInputNonStreaming(frame_settings, p, number_extra_channels, frame, + ec_frame); + uint8_t* next_out = compressed.data(); + size_t avail_out = compressed.size(); + ProcessEncoder(enc.get(), compressed, next_out, avail_out); + } + + std::vector streaming_compressed; + // with streaming + { + JxlEncoderPtr enc = JxlEncoderMake(nullptr); + ASSERT_NE(nullptr, enc.get()); + JxlEncoderFrameSettings* frame_settings = + JxlEncoderFrameSettingsCreate(enc.get(), NULL); + SetupEncoder(frame_settings, p, basic_info, number_extra_channels); + SetupInputNonStreaming(frame_settings, p, number_extra_channels, frame, + ec_frame); + JxlStreamingAdapter streaming_adapter(enc.get(), p.return_large_buffers(), + p.can_seek()); + EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderFlushInput(enc.get())); + streaming_adapter.CheckFinalWatermarkPosition(); + streaming_compressed = std::move(streaming_adapter).output(); + } + + EXPECT_TRUE(SameDecodedPixels(compressed, streaming_compressed)); + EXPECT_LE(streaming_compressed.size(), compressed.size() + 16); +} + +TEST_P(EncoderStreamingTest, ChunkedFrame) { + const StreamingTestParam p = GetParam(); + size_t xsize = p.onegroup() ? 17 : 257; + size_t ysize = p.onegroup() ? 19 : 259; + size_t number_extra_channels = p.with_extra_channels() ? 5 : 0; + jxl::test::TestImage image; + SetupImage(p, xsize, ysize, p.color_includes_alpha() ? 4 : 3, + p.use_container() ? 16 : 8, image); + jxl::test::TestImage ec_image; + SetupImage(p, xsize, ysize, 1, 8, ec_image); + const auto& frame = image.ppf().frames[0].color; + const auto& ec_frame = ec_image.ppf().frames[0].color; + JxlBasicInfo basic_info = image.ppf().info; + SetUpBasicInfo(basic_info, xsize, ysize, number_extra_channels, + p.color_includes_alpha(), p.is_lossless()); std::vector compressed = std::vector(64); std::vector streaming_compressed = std::vector(64); // without streaming { JxlEncoderPtr enc = JxlEncoderMake(nullptr); ASSERT_NE(nullptr, enc.get()); - SetupEncoder(enc.get(), p, basic_info, number_extra_channels, frame, false); JxlEncoderFrameSettings* frame_settings = JxlEncoderFrameSettingsCreate(enc.get(), NULL); - EXPECT_EQ(JXL_ENC_SUCCESS, - JxlEncoderAddImageFrame(frame_settings, &frame.format, - frame.pixels(), frame.pixels_size)); - for (size_t i = 0; i < number_extra_channels; i++) { - EXPECT_EQ(JXL_ENC_SUCCESS, - JxlEncoderSetExtraChannelBuffer( - frame_settings, &ec_frame.format, ec_frame.pixels(), - ec_frame.pixels_size, i)); - } - JxlEncoderCloseInput(enc.get()); + SetupEncoder(frame_settings, p, basic_info, number_extra_channels); + SetupInputNonStreaming(frame_settings, p, number_extra_channels, frame, + ec_frame); uint8_t* next_out = compressed.data(); size_t avail_out = compressed.size(); ProcessEncoder(enc.get(), compressed, next_out, avail_out); } // with streaming { JxlEncoderPtr enc = JxlEncoderMake(nullptr); ASSERT_NE(nullptr, enc.get()); - SetupEncoder(enc.get(), p, basic_info, number_extra_channels, frame, false); JxlEncoderFrameSettings* frame_settings = JxlEncoderFrameSettingsCreate(enc.get(), NULL); - JxlChunkedFrameInputSourceAdapter chunked_frame_adapter( - std::move(image.ppf()), std::move(ec_image.ppf())); - EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderAddChunkedFrame( - frame_settings, JXL_TRUE, - chunked_frame_adapter.GetInputSource())); - // JxlEncoderCloseInput(enc.get()); + SetupEncoder(frame_settings, p, basic_info, number_extra_channels); + SetupInputStreaming(frame_settings, p, number_extra_channels, frame, + ec_frame); uint8_t* next_out = streaming_compressed.data(); size_t avail_out = streaming_compressed.size(); ProcessEncoder(enc.get(), streaming_compressed, next_out, avail_out); } - EXPECT_EQ(streaming_compressed, compressed); + + EXPECT_TRUE(SameDecodedPixels(compressed, streaming_compressed)); + EXPECT_LE(streaming_compressed.size(), compressed.size() + 16); } TEST_P(EncoderStreamingTest, ChunkedAndOutputCallback) { const StreamingTestParam p = GetParam(); - size_t xsize = 257; - size_t ysize = 259; + size_t xsize = p.onegroup() ? 17 : 257; + size_t ysize = p.onegroup() ? 19 : 259; size_t number_extra_channels = p.with_extra_channels() ? 5 : 0; jxl::test::TestImage image; SetupImage(p, xsize, ysize, p.color_includes_alpha() ? 4 : 3, p.use_container() ? 16 : 8, image); jxl::test::TestImage ec_image; SetupImage(p, xsize, ysize, 1, 8, ec_image); - const auto& frame = image.ppf().frames[0].color; const auto& ec_frame = ec_image.ppf().frames[0].color; JxlBasicInfo basic_info = image.ppf().info; SetUpBasicInfo(basic_info, xsize, ysize, number_extra_channels, - p.color_includes_alpha()); + p.color_includes_alpha(), p.is_lossless()); std::vector compressed = std::vector(64); // without streaming { JxlEncoderPtr enc = JxlEncoderMake(nullptr); ASSERT_NE(nullptr, enc.get()); - SetupEncoder(enc.get(), p, basic_info, number_extra_channels, frame, false); JxlEncoderFrameSettings* frame_settings = JxlEncoderFrameSettingsCreate(enc.get(), NULL); - size_t frame_count = static_cast(p.multiple_frames()) + 1; - for (size_t i = 0; i < frame_count; i++) { - EXPECT_EQ(JXL_ENC_SUCCESS, - JxlEncoderAddImageFrame(frame_settings, &frame.format, - frame.pixels(), frame.pixels_size)); - for (size_t i = 0; i < number_extra_channels; i++) { - EXPECT_EQ(JXL_ENC_SUCCESS, - JxlEncoderSetExtraChannelBuffer( - frame_settings, &ec_frame.format, ec_frame.pixels(), - ec_frame.pixels_size, i)); - } - } - JxlEncoderCloseInput(enc.get()); + SetupEncoder(frame_settings, p, basic_info, number_extra_channels); + SetupInputNonStreaming(frame_settings, p, number_extra_channels, frame, + ec_frame); uint8_t* next_out = compressed.data(); size_t avail_out = compressed.size(); ProcessEncoder(enc.get(), compressed, next_out, avail_out); } + std::vector streaming_compressed; // with streaming { JxlEncoderPtr enc = JxlEncoderMake(nullptr); ASSERT_NE(nullptr, enc.get()); - SetupEncoder(enc.get(), p, basic_info, number_extra_channels, frame, false); JxlEncoderFrameSettings* frame_settings = JxlEncoderFrameSettingsCreate(enc.get(), NULL); + SetupEncoder(frame_settings, p, basic_info, number_extra_channels); JxlStreamingAdapter streaming_adapter = JxlStreamingAdapter(enc.get(), p.return_large_buffers(), p.can_seek()); + SetupInputStreaming(frame_settings, p, number_extra_channels, frame, + ec_frame); + streaming_adapter.CheckFinalWatermarkPosition(); + streaming_compressed = std::move(streaming_adapter).output(); + } - JxlChunkedFrameInputSourceAdapter chunked_frame_adapter( - std::move(image.ppf()), std::move(ec_image.ppf())); - size_t frame_count = static_cast(p.multiple_frames()) + 1; - for (size_t i = 0; i < frame_count; i++) { - EXPECT_EQ(JXL_ENC_SUCCESS, - JxlEncoderAddChunkedFrame( - // should only set `JXL_TRUE` in the lass pass of the loop - frame_settings, i + 1 == frame_count ? JXL_TRUE : JXL_FALSE, - chunked_frame_adapter.GetInputSource())); - } - - streaming_adapter.CheckFinalWatermarkPosition(); - EXPECT_EQ(std::move(streaming_adapter).output(), compressed); - } + EXPECT_TRUE(SameDecodedPixels(compressed, streaming_compressed)); + EXPECT_LE(streaming_compressed.size(), compressed.size() + 16); } JXL_GTEST_INSTANTIATE_TEST_SUITE_P( EncoderStreamingTest, EncoderStreamingTest, testing::ValuesIn(StreamingTestParam::All())); diff --git a/third_party/jpeg-xl/lib/jxl/entropy_coder_test.cc b/third_party/jpeg-xl/lib/jxl/entropy_coder_test.cc --- a/third_party/jpeg-xl/lib/jxl/entropy_coder_test.cc +++ b/third_party/jpeg-xl/lib/jxl/entropy_coder_test.cc @@ -22,11 +22,11 @@ int32_t unpacked = UnpackSigned(packed); EXPECT_EQ(i, unpacked); } } -struct DummyBitReader { +struct MockBitReader { uint32_t nbits, bits; void Consume(uint32_t nbits) {} uint32_t PeekBits(uint32_t n) { EXPECT_EQ(n, nbits); return bits; @@ -43,11 +43,11 @@ for (size_t i = 0; i < kNumIntegers; i++) { integers[i] = rng.UniformU(0, limit + 1); config.Encode(integers[i], &token[i], &nbits[i], &bits[i]); } for (size_t i = 0; i < kNumIntegers; i++) { - DummyBitReader br{nbits[i], bits[i]}; + MockBitReader br{nbits[i], bits[i]}; EXPECT_EQ(integers[i], ANSSymbolReader::ReadHybridUintConfig(config, token[i], &br)); } } diff --git a/third_party/jpeg-xl/lib/jxl/exif.h b/third_party/jpeg-xl/lib/jxl/exif.h --- a/third_party/jpeg-xl/lib/jxl/exif.h +++ b/third_party/jpeg-xl/lib/jxl/exif.h @@ -9,11 +9,10 @@ // Basic parsing of Exif (just enough for the render-impacting things // like orientation) #include -#include "lib/jxl/base/padded_bytes.h" #include "lib/jxl/image_metadata.h" namespace jxl { constexpr uint16_t kExifOrientationTag = 274; diff --git a/third_party/jpeg-xl/lib/jxl/frame_header.h b/third_party/jpeg-xl/lib/jxl/frame_header.h --- a/third_party/jpeg-xl/lib/jxl/frame_header.h +++ b/third_party/jpeg-xl/lib/jxl/frame_header.h @@ -15,11 +15,10 @@ #include #include #include "lib/jxl/base/compiler_specific.h" #include "lib/jxl/base/override.h" -#include "lib/jxl/base/padded_bytes.h" #include "lib/jxl/base/status.h" #include "lib/jxl/coeff_order_fwd.h" #include "lib/jxl/common.h" // kMaxNumPasses #include "lib/jxl/dec_bit_reader.h" #include "lib/jxl/fields.h" diff --git a/third_party/jpeg-xl/lib/jxl/gradient_test.cc b/third_party/jpeg-xl/lib/jxl/gradient_test.cc --- a/third_party/jpeg-xl/lib/jxl/gradient_test.cc +++ b/third_party/jpeg-xl/lib/jxl/gradient_test.cc @@ -13,16 +13,14 @@ #include "lib/jxl/base/common.h" #include "lib/jxl/base/compiler_specific.h" #include "lib/jxl/base/data_parallel.h" #include "lib/jxl/base/override.h" -#include "lib/jxl/base/padded_bytes.h" #include "lib/jxl/cms/jxl_cms.h" #include "lib/jxl/codec_in_out.h" #include "lib/jxl/color_encoding_internal.h" #include "lib/jxl/enc_cache.h" -#include "lib/jxl/enc_file.h" #include "lib/jxl/enc_params.h" #include "lib/jxl/image.h" #include "lib/jxl/image_bundle.h" #include "lib/jxl/image_ops.h" #include "lib/jxl/test_utils.h" @@ -157,17 +155,14 @@ io.metadata.m.color_encoding = ColorEncoding::SRGB(); io.SetFromImage(std::move(gradient), io.metadata.m.color_encoding); CodecInOut io2; - PaddedBytes compressed; - AuxOut* aux_out = nullptr; + std::vector compressed; PassesEncoderState enc_state; - EXPECT_TRUE(EncodeFile(cparams, &io, &enc_state, &compressed, - *JxlGetDefaultCms(), aux_out, pool)); - EXPECT_TRUE( - test::DecodeFile({}, Span(compressed), &io2, pool)); + EXPECT_TRUE(test::EncodeFile(cparams, &io, &enc_state, &compressed, pool)); + EXPECT_TRUE(test::DecodeFile({}, Bytes(compressed), &io2, pool)); EXPECT_TRUE(io2.Main().TransformTo(io2.metadata.m.color_encoding, *JxlGetDefaultCms(), pool)); if (use_gradient) { // Test that the gradient map worked. For that, we take a second derivative diff --git a/third_party/jpeg-xl/lib/jxl/icc_codec.h b/third_party/jpeg-xl/lib/jxl/icc_codec.h --- a/third_party/jpeg-xl/lib/jxl/icc_codec.h +++ b/third_party/jpeg-xl/lib/jxl/icc_codec.h @@ -6,18 +6,18 @@ #ifndef LIB_JXL_ICC_CODEC_H_ #define LIB_JXL_ICC_CODEC_H_ // Compressed representation of ICC profiles. -#include -#include +#include +#include #include "lib/jxl/base/compiler_specific.h" -#include "lib/jxl/base/padded_bytes.h" #include "lib/jxl/base/status.h" #include "lib/jxl/dec_ans.h" #include "lib/jxl/dec_bit_reader.h" +#include "lib/jxl/padded_bytes.h" namespace jxl { struct ICCReader { Status Init(BitReader* reader, size_t output_limit); @@ -37,17 +37,10 @@ ANSCode code_; ANSSymbolReader ans_reader_; PaddedBytes decompressed_; }; -// `icc` may be empty afterwards - if so, call CreateProfile. Does not append, -// clears any original data that was in icc. -// If `output_limit` is not 0, then returns error if resulting profile would be -// longer than `output_limit` -Status ReadICC(BitReader* JXL_RESTRICT reader, PaddedBytes* JXL_RESTRICT icc, - size_t output_limit = 0); - // Exposed only for testing Status PredictICC(const uint8_t* icc, size_t size, PaddedBytes* result); // Exposed only for testing Status UnpredictICC(const uint8_t* enc, size_t size, PaddedBytes* result); diff --git a/third_party/jpeg-xl/lib/jxl/icc_codec.cc b/third_party/jpeg-xl/lib/jxl/icc_codec.cc --- a/third_party/jpeg-xl/lib/jxl/icc_codec.cc +++ b/third_party/jpeg-xl/lib/jxl/icc_codec.cc @@ -13,10 +13,11 @@ #include "lib/jxl/base/byte_order.h" #include "lib/jxl/dec_ans.h" #include "lib/jxl/fields.h" #include "lib/jxl/icc_codec_common.h" +#include "lib/jxl/padded_bytes.h" namespace jxl { namespace { // Shuffles or interleaves bytes, for example with width 2, turns "ABCDabcd" @@ -106,11 +107,12 @@ JXL_RETURN_IF_ERROR(CheckOutOfBounds(pos, csize, size)); size_t commands_end = cpos + csize; pos = commands_end; // pos in data stream // Header - PaddedBytes header = ICCInitialHeaderPrediction(); + PaddedBytes header; + header.append(ICCInitialHeaderPrediction()); EncodeUint32(0, osize, &header); for (size_t i = 0; i <= kICCHeaderSize; i++) { if (result->size() == osize) { if (cpos != commands_end) return JXL_FAILURE("Not all commands used"); if (pos != size) return JXL_FAILURE("Not all data used"); @@ -388,14 +390,6 @@ if (reader->AllReadsWithinBounds()) return true; return JXL_STATUS(StatusCode::kNotEnoughBytes, "Not enough bytes for reading ICC profile"); } -Status ReadICC(BitReader* JXL_RESTRICT reader, PaddedBytes* JXL_RESTRICT icc, - size_t output_limit) { - ICCReader icc_reader; - JXL_RETURN_IF_ERROR(icc_reader.Init(reader, output_limit)); - JXL_RETURN_IF_ERROR(icc_reader.Process(reader, icc)); - return true; -} - } // namespace jxl diff --git a/third_party/jpeg-xl/lib/jxl/icc_codec_common.h b/third_party/jpeg-xl/lib/jxl/icc_codec_common.h --- a/third_party/jpeg-xl/lib/jxl/icc_codec_common.h +++ b/third_party/jpeg-xl/lib/jxl/icc_codec_common.h @@ -6,20 +6,21 @@ #ifndef LIB_JXL_ICC_CODEC_COMMON_H_ #define LIB_JXL_ICC_CODEC_COMMON_H_ // Compressed representation of ICC profiles. -#include -#include +#include +#include +#include -#include - -#include "lib/jxl/base/padded_bytes.h" +#include "lib/jxl/base/span.h" #include "lib/jxl/base/status.h" namespace jxl { +class PaddedBytes; + static constexpr size_t kICCHeaderSize = 128; typedef std::array Tag; static const Tag kAcspTag = {{'a', 'c', 's', 'p'}}; @@ -92,11 +93,11 @@ // Checks if a + b > size, taking possible integer overflow into account. Status CheckOutOfBounds(size_t a, size_t b, size_t size); Status CheckIs32Bit(uint64_t v); -PaddedBytes ICCInitialHeaderPrediction(); +const Span ICCInitialHeaderPrediction(); void ICCPredictHeader(const uint8_t* icc, size_t size, uint8_t* header, size_t pos); uint8_t LinearPredictICCValue(const uint8_t* data, size_t start, size_t i, size_t stride, size_t width, int order); size_t ICCANSContext(size_t i, size_t b1, size_t b2); diff --git a/third_party/jpeg-xl/lib/jxl/icc_codec_common.cc b/third_party/jpeg-xl/lib/jxl/icc_codec_common.cc --- a/third_party/jpeg-xl/lib/jxl/icc_codec_common.cc +++ b/third_party/jpeg-xl/lib/jxl/icc_codec_common.cc @@ -11,10 +11,11 @@ #include #include #include "lib/jxl/base/byte_order.h" #include "lib/jxl/fields.h" +#include "lib/jxl/padded_bytes.h" namespace jxl { namespace { static uint8_t ByteKind1(uint8_t b) { if ('a' <= b && b <= 'z') return 0; @@ -91,33 +92,23 @@ static constexpr const uint64_t kUpper32 = ~static_cast(0xFFFFFFFF); if ((v & kUpper32) != 0) return JXL_FAILURE("32-bit value expected"); return true; } -PaddedBytes ICCInitialHeaderPrediction() { - PaddedBytes result(kICCHeaderSize); - for (size_t i = 0; i < kICCHeaderSize; i++) { - result[i] = 0; - } - result[8] = 4; - EncodeKeyword(kMntrTag, result.data(), result.size(), 12); - EncodeKeyword(kRgb_Tag, result.data(), result.size(), 16); - EncodeKeyword(kXyz_Tag, result.data(), result.size(), 20); - EncodeKeyword(kAcspTag, result.data(), result.size(), 36); - result[68] = 0; - result[69] = 0; - result[70] = 246; - result[71] = 214; - result[72] = 0; - result[73] = 1; - result[74] = 0; - result[75] = 0; - result[76] = 0; - result[77] = 0; - result[78] = 211; - result[79] = 45; - return result; +const uint8_t kIccInitialHeaderPrediction[kICCHeaderSize] = { + 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 'm', 'n', 't', 'r', + 'R', 'G', 'B', ' ', 'X', 'Y', 'Z', ' ', 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 'a', 'c', 's', 'p', 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 246, 214, 0, 1, 0, 0, 0, 0, 211, 45, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +}; + +const Span ICCInitialHeaderPrediction() { + return Bytes(kIccInitialHeaderPrediction); } void ICCPredictHeader(const uint8_t* icc, size_t size, uint8_t* header, size_t pos) { if (pos == 8 && size >= 8) { diff --git a/third_party/jpeg-xl/lib/jxl/icc_codec_test.cc b/third_party/jpeg-xl/lib/jxl/icc_codec_test.cc --- a/third_party/jpeg-xl/lib/jxl/icc_codec_test.cc +++ b/third_party/jpeg-xl/lib/jxl/icc_codec_test.cc @@ -5,26 +5,28 @@ #include "lib/jxl/icc_codec.h" #include #include +#include #include "lib/jxl/base/span.h" #include "lib/jxl/color_encoding_internal.h" #include "lib/jxl/enc_icc_codec.h" +#include "lib/jxl/test_utils.h" #include "lib/jxl/testing.h" namespace jxl { namespace { void TestProfile(const IccBytes& icc) { BitWriter writer; ASSERT_TRUE(WriteICC(icc, &writer, 0, nullptr)); writer.ZeroPadToByte(); - PaddedBytes dec; + std::vector dec; BitReader reader(writer.GetSpan()); - ASSERT_TRUE(ReadICC(&reader, &dec)); + ASSERT_TRUE(test::ReadICC(&reader, &dec)); ASSERT_TRUE(reader.Close()); EXPECT_EQ(icc.size(), dec.size()); if (icc.size() == dec.size()) { for (size_t i = 0; i < icc.size(); i++) { EXPECT_EQ(icc[i], dec[i]); @@ -33,11 +35,11 @@ } } void TestProfile(const std::string& icc) { IccBytes data; - Span(icc).AppendTo(&data); + Bytes(icc).AppendTo(&data); TestProfile(data); } // Valid profile from one of the images output by the decoder. static const unsigned char kTestProfile[] = { @@ -135,11 +137,11 @@ TestProfile(profile); } { IccBytes profile; - Span(kTestProfile, sizeof(kTestProfile)).AppendTo(&profile); + Bytes(kTestProfile, sizeof(kTestProfile)).AppendTo(&profile); TestProfile(profile); } // Test substrings of full profile { @@ -188,14 +190,14 @@ 0xc1, 0xa7, 0x59, 0xe3, 0x0, }; // Tests that the decoded kEncodedTestProfile matches kTestProfile. TEST(IccCodecTest, EncodedIccProfile) { - jxl::BitReader reader(jxl::Span(kEncodedTestProfile, - sizeof(kEncodedTestProfile))); - jxl::PaddedBytes dec; - ASSERT_TRUE(ReadICC(&reader, &dec)); + jxl::BitReader reader( + jxl::Bytes(kEncodedTestProfile, sizeof(kEncodedTestProfile))); + std::vector dec; + ASSERT_TRUE(test::ReadICC(&reader, &dec)); ASSERT_TRUE(reader.Close()); EXPECT_EQ(sizeof(kTestProfile), dec.size()); if (sizeof(kTestProfile) == dec.size()) { for (size_t i = 0; i < dec.size(); i++) { EXPECT_EQ(kTestProfile[i], dec[i]); 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 @@ -15,13 +15,13 @@ #include #include #include // std::move -#include "lib/jxl/base/cache_aligned.h" #include "lib/jxl/base/compiler_specific.h" #include "lib/jxl/base/status.h" +#include "lib/jxl/cache_aligned.h" namespace jxl { // Helper function to create rows that are multiples of SIMD vector size. size_t VectorSize(); diff --git a/third_party/jpeg-xl/lib/jxl/image_bundle.cc b/third_party/jpeg-xl/lib/jxl/image_bundle.cc --- a/third_party/jpeg-xl/lib/jxl/image_bundle.cc +++ b/third_party/jpeg-xl/lib/jxl/image_bundle.cc @@ -7,11 +7,10 @@ #include #include #include "lib/jxl/base/byte_order.h" -#include "lib/jxl/base/padded_bytes.h" #include "lib/jxl/base/printf_macros.h" #include "lib/jxl/fields.h" namespace jxl { diff --git a/third_party/jpeg-xl/lib/jxl/image_metadata.cc b/third_party/jpeg-xl/lib/jxl/image_metadata.cc --- a/third_party/jpeg-xl/lib/jxl/image_metadata.cc +++ b/third_party/jpeg-xl/lib/jxl/image_metadata.cc @@ -8,11 +8,10 @@ #include #include #include "lib/jxl/alpha.h" #include "lib/jxl/base/byte_order.h" -#include "lib/jxl/base/padded_bytes.h" #include "lib/jxl/cms/opsin_params.h" #include "lib/jxl/fields.h" #include "lib/jxl/frame_header.h" #include "lib/jxl/quantizer.h" diff --git a/third_party/jpeg-xl/lib/jxl/jpeg/dec_jpeg_data.cc b/third_party/jpeg-xl/lib/jxl/jpeg/dec_jpeg_data.cc --- a/third_party/jpeg-xl/lib/jxl/jpeg/dec_jpeg_data.cc +++ b/third_party/jpeg-xl/lib/jxl/jpeg/dec_jpeg_data.cc @@ -118,12 +118,12 @@ } JXL_RETURN_IF_ERROR(br_read(jpeg_data->tail_data)); // Check if there is more decompressed output. size_t available_out = 1; - uint64_t dummy; - uint8_t* next_out = reinterpret_cast(&dummy); + uint64_t sink; + uint8_t* next_out = reinterpret_cast(&sink); result = BrotliDecoderDecompressStream(brotli_dec, &available_in, &in, &available_out, &next_out, nullptr); if (available_out == 0 || result == BrotliDecoderResult::BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT) { return JXL_FAILURE("Excess data in compressed stream"); diff --git a/third_party/jpeg-xl/lib/jxl/jpeg/dec_jpeg_data_writer.cc b/third_party/jpeg-xl/lib/jxl/jpeg/dec_jpeg_data_writer.cc --- a/third_party/jpeg-xl/lib/jxl/jpeg/dec_jpeg_data_writer.cc +++ b/third_party/jpeg-xl/lib/jxl/jpeg/dec_jpeg_data_writer.cc @@ -6,10 +6,11 @@ #include "lib/jxl/jpeg/dec_jpeg_data_writer.h" #include #include /* for memset, memcpy */ +#include #include #include #include #include #include @@ -200,22 +201,30 @@ // Emit all buffered data to the bit stream using the given Huffman code and // bit writer. static JXL_INLINE void Flush(DCTCodingState* s, JpegBitWriter* bw) { if (s->eob_run_ > 0) { + Reserve(bw, 16); int nbits = FloorLog2Nonzero(s->eob_run_); int symbol = nbits << 4u; WriteSymbol(symbol, s->cur_ac_huff_, bw); if (nbits > 0) { WriteBits(bw, nbits, s->eob_run_ & ((1 << nbits) - 1)); } s->eob_run_ = 0; } + const size_t kStride = 124; // (515 - 16) / 2 / 2 size_t num_words = s->refinement_bits_count_ >> 4; - for (size_t i = 0; i < num_words; ++i) { - WriteBits(bw, 16, s->refinement_bits_[i]); + size_t i = 0; + while (i < num_words) { + size_t limit = std::min(i + kStride, num_words); + Reserve(bw, 512); + for (; i < limit; ++i) { + WriteBits(bw, 16, s->refinement_bits_[i]); + } } + Reserve(bw, 16); size_t tail = s->refinement_bits_count_ & 0xF; if (tail) { WriteBits(bw, tail, s->refinement_bits_.back()); } s->refinement_bits_.clear(); @@ -834,12 +843,10 @@ if (want_ac && !ac_huff->initialized) { return SerializationStatus::ERROR; } int n_blocks_y = is_interleaved ? c.v_samp_factor : 1; int n_blocks_x = is_interleaved ? c.h_samp_factor : 1; - // compressed size per block cannot be more than 512 bytes per component - Reserve(bw, 512 * n_blocks_y * n_blocks_x); for (int iy = 0; iy < n_blocks_y; ++iy) { for (int ix = 0; ix < n_blocks_x; ++ix) { int block_y = ss.mcu_y * n_blocks_y + iy; int block_x = mcu_x * n_blocks_x + ix; int block_idx = block_y * c.width_in_blocks + block_x; @@ -854,10 +861,12 @@ ++ss.extra_zero_runs_pos; ss.next_extra_zero_run_index = get_next_extra_zero_run_index(); } const coeff_t* coeffs = &c.coeffs[block_idx << 6]; bool ok; + // compressed size per block cannot be more than 512 bytes + Reserve(bw, 512); if (kMode == 0) { ok = EncodeDCTBlockSequential(coeffs, dc_huff, ac_huff, num_zero_runs, ss.last_dc_coeff + si.comp_idx, bw); } else if (kMode == 1) { diff --git a/third_party/jpeg-xl/lib/jxl/jpeg/enc_jpeg_data.h b/third_party/jpeg-xl/lib/jxl/jpeg/enc_jpeg_data.h --- a/third_party/jpeg-xl/lib/jxl/jpeg/enc_jpeg_data.h +++ b/third_party/jpeg-xl/lib/jxl/jpeg/enc_jpeg_data.h @@ -4,19 +4,23 @@ // license that can be found in the LICENSE file. #ifndef LIB_JXL_JPEG_ENC_JPEG_DATA_H_ #define LIB_JXL_JPEG_ENC_JPEG_DATA_H_ -#include "lib/jxl/base/padded_bytes.h" -#include "lib/jxl/codec_in_out.h" +#include +#include + #include "lib/jxl/color_encoding_internal.h" #include "lib/jxl/enc_params.h" #include "lib/jxl/jpeg/jpeg_data.h" namespace jxl { + +class CodecInOut; + namespace jpeg { -Status EncodeJPEGData(JPEGData& jpeg_data, PaddedBytes* bytes, +Status EncodeJPEGData(JPEGData& jpeg_data, std::vector* bytes, const CompressParams& cparams); void SetColorEncodingFromJpegData(const jpeg::JPEGData& jpg, ColorEncoding* color_encoding); diff --git a/third_party/jpeg-xl/lib/jxl/jpeg/enc_jpeg_data.cc b/third_party/jpeg-xl/lib/jxl/jpeg/enc_jpeg_data.cc --- a/third_party/jpeg-xl/lib/jxl/jpeg/enc_jpeg_data.cc +++ b/third_party/jpeg-xl/lib/jxl/jpeg/enc_jpeg_data.cc @@ -5,10 +5,11 @@ #include "lib/jxl/jpeg/enc_jpeg_data.h" #include +#include "lib/jxl/codec_in_out.h" #include "lib/jxl/enc_fields.h" #include "lib/jxl/image_bundle.h" #include "lib/jxl/jpeg/enc_jpeg_data_reader.h" #include "lib/jxl/luminance.h" #include "lib/jxl/sanitizers.h" @@ -229,25 +230,18 @@ } else { color_encoding->SetICCRaw(std::move(icc_profile)); } } -Status EncodeJPEGData(JPEGData& jpeg_data, PaddedBytes* bytes, +Status EncodeJPEGData(JPEGData& jpeg_data, std::vector* bytes, const CompressParams& cparams) { + bytes->clear(); jpeg_data.app_marker_type.resize(jpeg_data.app_data.size(), AppMarkerType::kUnknown); JXL_RETURN_IF_ERROR(DetectIccProfile(jpeg_data)); JXL_RETURN_IF_ERROR(DetectBlobs(jpeg_data)); - BitWriter writer; - JXL_RETURN_IF_ERROR(Bundle::Write(jpeg_data, &writer, 0, nullptr)); - writer.ZeroPadToByte(); - *bytes = std::move(writer).TakeBytes(); - BrotliEncoderState* brotli_enc = - BrotliEncoderCreateInstance(nullptr, nullptr, nullptr); - int effort = cparams.brotli_effort; - if (effort < 0) effort = 11 - static_cast(cparams.speed_tier); - BrotliEncoderSetParameter(brotli_enc, BROTLI_PARAM_QUALITY, effort); + size_t total_data = 0; for (size_t i = 0; i < jpeg_data.app_data.size(); i++) { if (jpeg_data.app_marker_type[i] != AppMarkerType::kUnknown) { continue; } @@ -258,14 +252,29 @@ } for (size_t i = 0; i < jpeg_data.inter_marker_data.size(); i++) { total_data += jpeg_data.inter_marker_data[i].size(); } total_data += jpeg_data.tail_data.size(); + size_t brotli_capacity = BrotliEncoderMaxCompressedSize(total_data); + + BitWriter writer; + JXL_RETURN_IF_ERROR(Bundle::Write(jpeg_data, &writer, 0, nullptr)); + writer.ZeroPadToByte(); + { + PaddedBytes serialized_jpeg_data = std::move(writer).TakeBytes(); + bytes->reserve(serialized_jpeg_data.size() + brotli_capacity); + Bytes(serialized_jpeg_data).AppendTo(bytes); + } + + BrotliEncoderState* brotli_enc = + BrotliEncoderCreateInstance(nullptr, nullptr, nullptr); + int effort = cparams.brotli_effort; + if (effort < 0) effort = 11 - static_cast(cparams.speed_tier); + BrotliEncoderSetParameter(brotli_enc, BROTLI_PARAM_QUALITY, effort); size_t initial_size = bytes->size(); - size_t brotli_capacity = BrotliEncoderMaxCompressedSize(total_data); BrotliEncoderSetParameter(brotli_enc, BROTLI_PARAM_SIZE_HINT, total_data); - bytes->resize(bytes->size() + brotli_capacity); + bytes->resize(initial_size + brotli_capacity); size_t enc_size = 0; auto br_append = [&](const std::vector& data, bool last) { size_t available_in = data.size(); const uint8_t* in = data.data(); uint8_t* out = &(*bytes)[initial_size + enc_size]; 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 @@ -6,10 +6,11 @@ #include "lib/extras/dec/jxl.h" #include #include +#include #include #include #include #include #include @@ -20,18 +21,17 @@ #include "lib/extras/packed_image.h" #include "lib/jxl/alpha.h" #include "lib/jxl/base/compiler_specific.h" #include "lib/jxl/base/data_parallel.h" #include "lib/jxl/base/override.h" -#include "lib/jxl/base/padded_bytes.h" +#include "lib/jxl/base/span.h" #include "lib/jxl/cms/jxl_cms.h" #include "lib/jxl/codec_in_out.h" #include "lib/jxl/color_encoding_internal.h" #include "lib/jxl/common.h" // JXL_HIGH_PRECISION #include "lib/jxl/enc_butteraugli_comparator.h" #include "lib/jxl/enc_cache.h" -#include "lib/jxl/enc_file.h" #include "lib/jxl/enc_params.h" #include "lib/jxl/fake_parallel_runner_testonly.h" #include "lib/jxl/image.h" #include "lib/jxl/image_bundle.h" #include "lib/jxl/image_ops.h" @@ -54,10 +54,11 @@ using extras::JXLCompressParams; using extras::JXLDecompressParams; using extras::PackedPixelFile; using test::ButteraugliDistance; using test::ComputeDistance2; +using test::ReadTestData; using test::Roundtrip; using test::TestImage; using test::ThreadPoolForTests; #define JXL_TEST_NL 0 // Disabled in code @@ -94,12 +95,12 @@ } #endif TEST(JxlTest, RoundtripTinyFast) { ThreadPool* pool = nullptr; - const PaddedBytes orig = jxl::test::ReadTestData( - "external/wesaturate/500px/u76c0g_bliznaca_srgb8.png"); + const std::vector orig = + ReadTestData("external/wesaturate/500px/u76c0g_bliznaca_srgb8.png"); TestImage t; t.DecodeFromBytes(orig).ClearMetadata().SetDimensions(32, 32); JXLCompressParams cparams; cparams.AddOption(JXL_ENC_FRAME_SETTING_EFFORT, 7); @@ -109,12 +110,12 @@ EXPECT_NEAR(Roundtrip(t.ppf(), cparams, {}, pool, &ppf_out), 181, 15); } TEST(JxlTest, RoundtripSmallD1) { ThreadPool* pool = nullptr; - const PaddedBytes orig = jxl::test::ReadTestData( - "external/wesaturate/500px/u76c0g_bliznaca_srgb8.png"); + const std::vector orig = + ReadTestData("external/wesaturate/500px/u76c0g_bliznaca_srgb8.png"); TestImage t; t.DecodeFromBytes(orig).ClearMetadata(); size_t xsize = t.ppf().info.xsize / 8; size_t ysize = t.ppf().info.ysize / 8; t.SetDimensions(xsize, ysize); @@ -136,12 +137,12 @@ EXPECT_EQ(ppf_out.info.intensity_target, t.ppf().info.intensity_target); } } TEST(JxlTest, RoundtripResample2) { ThreadPool* pool = nullptr; - const PaddedBytes orig = jxl::test::ReadTestData( - "external/wesaturate/500px/u76c0g_bliznaca_srgb8.png"); + const std::vector orig = + ReadTestData("external/wesaturate/500px/u76c0g_bliznaca_srgb8.png"); TestImage t; t.DecodeFromBytes(orig).ClearMetadata(); JXLCompressParams cparams; cparams.AddOption(JXL_ENC_FRAME_SETTING_RESAMPLING, 2); @@ -152,12 +153,12 @@ EXPECT_THAT(ComputeDistance2(t.ppf(), ppf_out), IsSlightlyBelow(90)); } TEST(JxlTest, RoundtripResample2Slow) { ThreadPool* pool = nullptr; - const PaddedBytes orig = jxl::test::ReadTestData( - "external/wesaturate/500px/u76c0g_bliznaca_srgb8.png"); + const std::vector orig = + ReadTestData("external/wesaturate/500px/u76c0g_bliznaca_srgb8.png"); TestImage t; t.DecodeFromBytes(orig).ClearMetadata(); JXLCompressParams cparams; cparams.AddOption(JXL_ENC_FRAME_SETTING_RESAMPLING, 2); @@ -169,11 +170,11 @@ EXPECT_THAT(ComputeDistance2(t.ppf(), ppf_out), IsSlightlyBelow(250)); } TEST(JxlTest, RoundtripResample2MT) { ThreadPoolForTests pool(4); - const PaddedBytes orig = jxl::test::ReadTestData("jxl/flower/flower.png"); + const std::vector orig = ReadTestData("jxl/flower/flower.png"); // image has to be large enough to have multiple groups after downsampling TestImage t; t.DecodeFromBytes(orig).ClearMetadata(); JXLCompressParams cparams; @@ -188,11 +189,11 @@ // Roundtrip the image using a parallel runner that executes single-threaded but // in random order. TEST(JxlTest, RoundtripOutOfOrderProcessing) { FakeParallelRunner fake_pool(/*order_seed=*/123, /*num_threads=*/8); ThreadPool pool(&JxlFakeParallelRunner, &fake_pool); - const PaddedBytes orig = jxl::test::ReadTestData("jxl/flower/flower.png"); + const std::vector orig = ReadTestData("jxl/flower/flower.png"); TestImage t; t.DecodeFromBytes(orig).ClearMetadata(); // Image size is selected so that the block border needed is larger than the // amount of pixels available on the next block. t.SetDimensions(513, 515); @@ -207,11 +208,11 @@ } TEST(JxlTest, RoundtripOutOfOrderProcessingBorder) { FakeParallelRunner fake_pool(/*order_seed=*/47, /*num_threads=*/8); ThreadPool pool(&JxlFakeParallelRunner, &fake_pool); - const PaddedBytes orig = jxl::test::ReadTestData("jxl/flower/flower.png"); + const std::vector orig = ReadTestData("jxl/flower/flower.png"); TestImage t; t.DecodeFromBytes(orig).ClearMetadata(); // Image size is selected so that the block border needed is larger than the // amount of pixels available on the next block. t.SetDimensions(513, 515); @@ -226,12 +227,12 @@ EXPECT_LE(ButteraugliDistance(t.ppf(), ppf_out), 2.9); } TEST(JxlTest, RoundtripResample4) { ThreadPool* pool = nullptr; - const PaddedBytes orig = jxl::test::ReadTestData( - "external/wesaturate/500px/u76c0g_bliznaca_srgb8.png"); + const std::vector orig = + ReadTestData("external/wesaturate/500px/u76c0g_bliznaca_srgb8.png"); TestImage t; t.DecodeFromBytes(orig).ClearMetadata(); JXLCompressParams cparams; cparams.AddOption(JXL_ENC_FRAME_SETTING_RESAMPLING, 4); @@ -241,12 +242,12 @@ EXPECT_THAT(ButteraugliDistance(t.ppf(), ppf_out), IsSlightlyBelow(22)); } TEST(JxlTest, RoundtripResample8) { ThreadPool* pool = nullptr; - const PaddedBytes orig = jxl::test::ReadTestData( - "external/wesaturate/500px/u76c0g_bliznaca_srgb8.png"); + const std::vector orig = + ReadTestData("external/wesaturate/500px/u76c0g_bliznaca_srgb8.png"); TestImage t; t.DecodeFromBytes(orig).ClearMetadata(); JXLCompressParams cparams; cparams.AddOption(JXL_ENC_FRAME_SETTING_RESAMPLING, 8); @@ -256,12 +257,12 @@ EXPECT_THAT(ButteraugliDistance(t.ppf(), ppf_out), IsSlightlyBelow(50)); } TEST(JxlTest, RoundtripUnalignedD2) { ThreadPool* pool = nullptr; - const PaddedBytes orig = jxl::test::ReadTestData( - "external/wesaturate/500px/u76c0g_bliznaca_srgb8.png"); + const std::vector orig = + ReadTestData("external/wesaturate/500px/u76c0g_bliznaca_srgb8.png"); TestImage t; t.DecodeFromBytes(orig).ClearMetadata(); size_t xsize = t.ppf().info.xsize / 12; size_t ysize = t.ppf().info.ysize / 7; t.SetDimensions(xsize, ysize); @@ -273,11 +274,11 @@ EXPECT_NEAR(Roundtrip(t.ppf(), cparams, {}, pool, &ppf_out), 506, 30); EXPECT_THAT(ButteraugliDistance(t.ppf(), ppf_out), IsSlightlyBelow(1.72)); } TEST(JxlTest, RoundtripMultiGroup) { - const PaddedBytes orig = jxl::test::ReadTestData("jxl/flower/flower.png"); + const std::vector orig = ReadTestData("jxl/flower/flower.png"); TestImage t; t.DecodeFromBytes(orig).ClearMetadata().SetDimensions(600, 1024); auto test = [&](jxl::SpeedTier speed_tier, float target_distance, size_t expected_size, float expected_distance) { @@ -300,13 +301,13 @@ 2.0f, 39620u, 15.5); } TEST(JxlTest, RoundtripRGBToGrayscale) { ThreadPoolForTests pool(4); - const PaddedBytes orig = jxl::test::ReadTestData("jxl/flower/flower.png"); + const std::vector orig = ReadTestData("jxl/flower/flower.png"); CodecInOut io; - ASSERT_TRUE(SetFromBytes(Span(orig), &io, &pool)); + ASSERT_TRUE(SetFromBytes(Bytes(orig), &io, &pool)); io.ShrinkTo(600, 1024); CompressParams cparams; cparams.butteraugli_distance = 1.0f; cparams.speed_tier = SpeedTier::kFalcon; @@ -346,11 +347,11 @@ IsSlightlyBelow(1.36)); } TEST(JxlTest, RoundtripLargeFast) { ThreadPoolForTests pool(8); - const PaddedBytes orig = jxl::test::ReadTestData("jxl/flower/flower.png"); + const std::vector orig = ReadTestData("jxl/flower/flower.png"); TestImage t; t.DecodeFromBytes(orig).ClearMetadata(); JXLCompressParams cparams; cparams.AddOption(JXL_ENC_FRAME_SETTING_EFFORT, 7); // kSquirrel @@ -360,12 +361,12 @@ EXPECT_THAT(ComputeDistance2(t.ppf(), ppf_out), IsSlightlyBelow(75)); } TEST(JxlTest, RoundtripDotsForceEpf) { ThreadPoolForTests pool(8); - const PaddedBytes orig = jxl::test::ReadTestData( - "external/wesaturate/500px/cvo9xd_keong_macan_srgb8.png"); + const std::vector orig = + ReadTestData("external/wesaturate/500px/cvo9xd_keong_macan_srgb8.png"); TestImage t; t.DecodeFromBytes(orig).ClearMetadata(); JXLCompressParams cparams; cparams.AddOption(JXL_ENC_FRAME_SETTING_EFFORT, 7); // kSquirrel @@ -380,11 +381,11 @@ // Checks for differing size/distance in two consecutive runs of distance 2, // which involves additional processing including adaptive reconstruction. // Failing this may be a sign of race conditions or invalid memory accesses. TEST(JxlTest, RoundtripD2Consistent) { ThreadPoolForTests pool(8); - const PaddedBytes orig = jxl::test::ReadTestData("jxl/flower/flower.png"); + const std::vector orig = ReadTestData("jxl/flower/flower.png"); TestImage t; t.DecodeFromBytes(orig).ClearMetadata(); JXLCompressParams cparams; cparams.AddOption(JXL_ENC_FRAME_SETTING_EFFORT, 7); // kSquirrel @@ -410,11 +411,11 @@ } } // Same as above, but for full image, testing multiple groups. TEST(JxlTest, RoundtripLargeConsistent) { - const PaddedBytes orig = jxl::test::ReadTestData("jxl/flower/flower.png"); + const std::vector orig = ReadTestData("jxl/flower/flower.png"); TestImage t; t.DecodeFromBytes(orig).ClearMetadata(); JXLCompressParams cparams; cparams.AddOption(JXL_ENC_FRAME_SETTING_EFFORT, 7); // kSquirrel @@ -442,12 +443,12 @@ EXPECT_EQ(std::get<1>(result2), std::get<1>(result3)); } TEST(JxlTest, RoundtripSmallNL) { ThreadPool* pool = nullptr; - const PaddedBytes orig = jxl::test::ReadTestData( - "external/wesaturate/500px/u76c0g_bliznaca_srgb8.png"); + const std::vector orig = + ReadTestData("external/wesaturate/500px/u76c0g_bliznaca_srgb8.png"); TestImage t; t.DecodeFromBytes(orig).ClearMetadata(); size_t xsize = t.ppf().info.xsize / 8; size_t ysize = t.ppf().info.ysize / 8; t.SetDimensions(xsize, ysize); @@ -457,12 +458,12 @@ EXPECT_THAT(ButteraugliDistance(t.ppf(), ppf_out), IsSlightlyBelow(0.82)); } TEST(JxlTest, RoundtripNoGaborishNoAR) { ThreadPool* pool = nullptr; - const PaddedBytes orig = jxl::test::ReadTestData( - "external/wesaturate/500px/u76c0g_bliznaca_srgb8.png"); + const std::vector orig = + ReadTestData("external/wesaturate/500px/u76c0g_bliznaca_srgb8.png"); TestImage t; t.DecodeFromBytes(orig).ClearMetadata(); JXLCompressParams cparams; cparams.AddOption(JXL_ENC_FRAME_SETTING_EPF, 0); @@ -473,12 +474,12 @@ EXPECT_THAT(ButteraugliDistance(t.ppf(), ppf_out), IsSlightlyBelow(1.8)); } TEST(JxlTest, RoundtripSmallNoGaborish) { ThreadPool* pool = nullptr; - const PaddedBytes orig = jxl::test::ReadTestData( - "external/wesaturate/500px/u76c0g_bliznaca_srgb8.png"); + const std::vector orig = + ReadTestData("external/wesaturate/500px/u76c0g_bliznaca_srgb8.png"); TestImage t; t.DecodeFromBytes(orig).ClearMetadata(); size_t xsize = t.ppf().info.xsize / 8; size_t ysize = t.ppf().info.ysize / 8; t.SetDimensions(xsize, ysize); @@ -615,34 +616,32 @@ } } #endif TEST(JxlTest, RoundtripGrayscale) { - const PaddedBytes orig = jxl::test::ReadTestData( + const std::vector orig = ReadTestData( "external/wesaturate/500px/cvo9xd_keong_macan_grayscale.png"); CodecInOut io; - ASSERT_TRUE(SetFromBytes(Span(orig), &io)); + ASSERT_TRUE(SetFromBytes(Bytes(orig), &io)); ASSERT_NE(io.xsize(), 0u); io.ShrinkTo(128, 128); EXPECT_TRUE(io.Main().IsGray()); EXPECT_EQ(8u, io.metadata.m.bit_depth.bits_per_sample); EXPECT_FALSE(io.metadata.m.bit_depth.floating_point_sample); EXPECT_EQ(0u, io.metadata.m.bit_depth.exponent_bits_per_sample); EXPECT_TRUE(io.metadata.m.color_encoding.Tf().IsSRGB()); PassesEncoderState enc_state; - AuxOut* aux_out = nullptr; { CompressParams cparams; cparams.butteraugli_distance = 1.0; - PaddedBytes compressed; - EXPECT_TRUE(EncodeFile(cparams, &io, &enc_state, &compressed, - *JxlGetDefaultCms(), aux_out)); + std::vector compressed; + EXPECT_TRUE(test::EncodeFile(cparams, &io, &enc_state, &compressed)); CodecInOut io2; - EXPECT_TRUE(test::DecodeFile({}, Span(compressed), &io2)); + EXPECT_TRUE(test::DecodeFile({}, Bytes(compressed), &io2)); EXPECT_TRUE(io2.Main().IsGray()); EXPECT_LE(compressed.size(), 7000u); EXPECT_THAT(ButteraugliDistance(io.frames, io2.frames, ButteraugliParams(), *JxlGetDefaultCms(), @@ -654,15 +653,14 @@ // different jxl codepaths trigger. { CompressParams cparams; cparams.butteraugli_distance = 8.0; - PaddedBytes compressed; - EXPECT_TRUE(EncodeFile(cparams, &io, &enc_state, &compressed, - *JxlGetDefaultCms(), aux_out)); + std::vector compressed; + EXPECT_TRUE(test::EncodeFile(cparams, &io, &enc_state, &compressed)); CodecInOut io2; - EXPECT_TRUE(test::DecodeFile({}, Span(compressed), &io2)); + EXPECT_TRUE(test::DecodeFile({}, Bytes(compressed), &io2)); EXPECT_TRUE(io2.Main().IsGray()); EXPECT_LE(compressed.size(), 1300u); EXPECT_THAT(ButteraugliDistance(io.frames, io2.frames, ButteraugliParams(), *JxlGetDefaultCms(), @@ -672,19 +670,17 @@ { CompressParams cparams; cparams.butteraugli_distance = 1.0; - PaddedBytes compressed; - EXPECT_TRUE(EncodeFile(cparams, &io, &enc_state, &compressed, - *JxlGetDefaultCms(), aux_out)); + std::vector compressed; + EXPECT_TRUE(test::EncodeFile(cparams, &io, &enc_state, &compressed)); CodecInOut io2; JXLDecompressParams dparams; dparams.color_space = "RGB_D65_SRG_Rel_SRG"; - EXPECT_TRUE( - test::DecodeFile(dparams, Span(compressed), &io2)); + EXPECT_TRUE(test::DecodeFile(dparams, Bytes(compressed), &io2)); EXPECT_FALSE(io2.Main().IsGray()); EXPECT_LE(compressed.size(), 7000u); EXPECT_THAT(ButteraugliDistance(io.frames, io2.frames, ButteraugliParams(), *JxlGetDefaultCms(), @@ -692,14 +688,14 @@ IsSlightlyBelow(1.6)); } } TEST(JxlTest, RoundtripAlpha) { - const PaddedBytes orig = jxl::test::ReadTestData( - "external/wesaturate/500px/tmshre_riaphotographs_alpha.png"); + const std::vector orig = + ReadTestData("external/wesaturate/500px/tmshre_riaphotographs_alpha.png"); CodecInOut io; - ASSERT_TRUE(SetFromBytes(Span(orig), &io)); + ASSERT_TRUE(SetFromBytes(Bytes(orig), &io)); ASSERT_NE(io.xsize(), 0u); ASSERT_TRUE(io.metadata.m.HasAlpha()); ASSERT_TRUE(io.Main().HasAlpha()); io.ShrinkTo(300, 300); @@ -710,25 +706,22 @@ EXPECT_EQ(8u, io.metadata.m.bit_depth.bits_per_sample); EXPECT_FALSE(io.metadata.m.bit_depth.floating_point_sample); EXPECT_EQ(0u, io.metadata.m.bit_depth.exponent_bits_per_sample); EXPECT_TRUE(io.metadata.m.color_encoding.Tf().IsSRGB()); PassesEncoderState enc_state; - AuxOut* aux_out = nullptr; - PaddedBytes compressed; - EXPECT_TRUE(EncodeFile(cparams, &io, &enc_state, &compressed, - *JxlGetDefaultCms(), aux_out)); + std::vector compressed; + EXPECT_TRUE(test::EncodeFile(cparams, &io, &enc_state, &compressed)); EXPECT_LE(compressed.size(), 10077u); for (bool use_image_callback : {false, true}) { for (bool unpremul_alpha : {false, true}) { CodecInOut io2; JXLDecompressParams dparams; dparams.use_image_callback = use_image_callback; dparams.unpremultiply_alpha = unpremul_alpha; - EXPECT_TRUE( - test::DecodeFile(dparams, Span(compressed), &io2)); + EXPECT_TRUE(test::DecodeFile(dparams, Bytes(compressed), &io2)); EXPECT_THAT(ButteraugliDistance(io.frames, io2.frames, ButteraugliParams(), *JxlGetDefaultCms(), /*distmap=*/nullptr), IsSlightlyBelow(1.15)); } @@ -789,15 +782,15 @@ return true; } } // namespace TEST(JxlTest, RoundtripAlphaPremultiplied) { - const PaddedBytes orig = jxl::test::ReadTestData( - "external/wesaturate/500px/tmshre_riaphotographs_alpha.png"); + const std::vector orig = + ReadTestData("external/wesaturate/500px/tmshre_riaphotographs_alpha.png"); CodecInOut io, io_nopremul; - ASSERT_TRUE(SetFromBytes(Span(orig), &io)); - ASSERT_TRUE(SetFromBytes(Span(orig), &io_nopremul)); + ASSERT_TRUE(SetFromBytes(Bytes(orig), &io)); + ASSERT_TRUE(SetFromBytes(Bytes(orig), &io_nopremul)); ASSERT_NE(io.xsize(), 0u); ASSERT_TRUE(io.metadata.m.HasAlpha()); ASSERT_TRUE(io.Main().HasAlpha()); io.ShrinkTo(300, 300); @@ -812,14 +805,12 @@ EXPECT_TRUE(io.Main().AlphaIsPremultiplied()); EXPECT_FALSE(io_nopremul.Main().AlphaIsPremultiplied()); PassesEncoderState enc_state; - AuxOut* aux_out = nullptr; - PaddedBytes compressed; - EXPECT_TRUE(EncodeFile(cparams, &io, &enc_state, &compressed, - *JxlGetDefaultCms(), aux_out)); + std::vector compressed; + EXPECT_TRUE(test::EncodeFile(cparams, &io, &enc_state, &compressed)); EXPECT_LE(compressed.size(), 10000u); for (bool use_image_callback : {false, true}) { for (bool unpremul_alpha : {false, true}) { for (bool use_uint8 : {false, true}) { @@ -835,12 +826,11 @@ dparams.unpremultiply_alpha = unpremul_alpha; if (use_uint8) { dparams.accepted_formats = { {4, JXL_TYPE_UINT8, JXL_LITTLE_ENDIAN, 0}}; } - EXPECT_TRUE( - test::DecodeFile(dparams, Span(compressed), &io2)); + EXPECT_TRUE(test::DecodeFile(dparams, Bytes(compressed), &io2)); EXPECT_EQ(unpremul_alpha, !io2.Main().AlphaIsPremultiplied()); if (!unpremul_alpha) { EXPECT_THAT( ButteraugliDistance(io.frames, io2.frames, ButteraugliParams(), @@ -860,12 +850,12 @@ } } TEST(JxlTest, RoundtripAlphaResampling) { ThreadPool* pool = nullptr; - const PaddedBytes orig = jxl::test::ReadTestData( - "external/wesaturate/500px/tmshre_riaphotographs_alpha.png"); + const std::vector orig = + ReadTestData("external/wesaturate/500px/tmshre_riaphotographs_alpha.png"); TestImage t; t.DecodeFromBytes(orig).ClearMetadata(); ASSERT_NE(t.ppf().info.xsize, 0); ASSERT_TRUE(t.ppf().info.alpha_bits > 0); @@ -879,12 +869,12 @@ EXPECT_THAT(ButteraugliDistance(t.ppf(), ppf_out), IsSlightlyBelow(5.2)); } TEST(JxlTest, RoundtripAlphaResamplingOnlyAlpha) { ThreadPool* pool = nullptr; - const PaddedBytes orig = jxl::test::ReadTestData( - "external/wesaturate/500px/tmshre_riaphotographs_alpha.png"); + const std::vector orig = + ReadTestData("external/wesaturate/500px/tmshre_riaphotographs_alpha.png"); TestImage t; t.DecodeFromBytes(orig).ClearMetadata(); ASSERT_NE(t.ppf().info.xsize, 0); ASSERT_TRUE(t.ppf().info.alpha_bits > 0); @@ -897,12 +887,12 @@ EXPECT_THAT(ButteraugliDistance(t.ppf(), ppf_out), IsSlightlyBelow(1.49)); } TEST(JxlTest, RoundtripAlphaNonMultipleOf8) { ThreadPool* pool = nullptr; - const PaddedBytes orig = jxl::test::ReadTestData( - "external/wesaturate/500px/tmshre_riaphotographs_alpha.png"); + const std::vector orig = + ReadTestData("external/wesaturate/500px/tmshre_riaphotographs_alpha.png"); TestImage t; t.DecodeFromBytes(orig).ClearMetadata().SetDimensions(12, 12); ASSERT_NE(t.ppf().info.xsize, 0); ASSERT_TRUE(t.ppf().info.alpha_bits > 0); EXPECT_EQ(t.ppf().frames[0].color.format.data_type, JXL_TYPE_UINT8); @@ -958,12 +948,12 @@ } } // namespace TEST(JxlTest, JXL_SLOW_TEST(RoundtripLossless8)) { ThreadPoolForTests pool(8); - const PaddedBytes orig = jxl::test::ReadTestData( - "external/wesaturate/500px/tmshre_riaphotographs_srgb8.png"); + const std::vector orig = + ReadTestData("external/wesaturate/500px/tmshre_riaphotographs_srgb8.png"); TestImage t; t.DecodeFromBytes(orig).ClearMetadata(); JXLCompressParams cparams = CompressParamsForLossless(); @@ -972,12 +962,12 @@ EXPECT_EQ(ComputeDistance2(t.ppf(), ppf_out), 0.0); } TEST(JxlTest, JXL_SLOW_TEST(RoundtripLossless8ThunderGradient)) { ThreadPoolForTests pool(8); - const PaddedBytes orig = jxl::test::ReadTestData( - "external/wesaturate/500px/tmshre_riaphotographs_srgb8.png"); + const std::vector orig = + ReadTestData("external/wesaturate/500px/tmshre_riaphotographs_srgb8.png"); TestImage t; t.DecodeFromBytes(orig).ClearMetadata(); JXLCompressParams cparams = CompressParamsForLossless(); cparams.AddOption(JXL_ENC_FRAME_SETTING_EFFORT, 2); // kThunder @@ -988,12 +978,12 @@ EXPECT_EQ(ComputeDistance2(t.ppf(), ppf_out), 0.0); } TEST(JxlTest, JXL_SLOW_TEST(RoundtripLossless8LightningGradient)) { ThreadPoolForTests pool(8); - const PaddedBytes orig = jxl::test::ReadTestData( - "external/wesaturate/500px/tmshre_riaphotographs_srgb8.png"); + const std::vector orig = + ReadTestData("external/wesaturate/500px/tmshre_riaphotographs_srgb8.png"); TestImage t; t.DecodeFromBytes(orig).ClearMetadata(); JXLCompressParams cparams = CompressParamsForLossless(); cparams.AddOption(JXL_ENC_FRAME_SETTING_EFFORT, 1); // kLightning @@ -1005,12 +995,12 @@ EXPECT_EQ(ComputeDistance2(t.ppf(), ppf_out), 0.0); } TEST(JxlTest, JXL_SLOW_TEST(RoundtripLossless8Falcon)) { ThreadPoolForTests pool(8); - const PaddedBytes orig = jxl::test::ReadTestData( - "external/wesaturate/500px/tmshre_riaphotographs_srgb8.png"); + const std::vector orig = + ReadTestData("external/wesaturate/500px/tmshre_riaphotographs_srgb8.png"); TestImage t; t.DecodeFromBytes(orig).ClearMetadata(); JXLCompressParams cparams = CompressParamsForLossless(); cparams.AddOption(JXL_ENC_FRAME_SETTING_EFFORT, 3); // kFalcon @@ -1020,12 +1010,12 @@ EXPECT_EQ(ComputeDistance2(t.ppf(), ppf_out), 0.0); } TEST(JxlTest, RoundtripLossless8Alpha) { ThreadPool* pool = nullptr; - const PaddedBytes orig = jxl::test::ReadTestData( - "external/wesaturate/500px/tmshre_riaphotographs_alpha.png"); + const std::vector orig = + ReadTestData("external/wesaturate/500px/tmshre_riaphotographs_alpha.png"); TestImage t; t.DecodeFromBytes(orig).ClearMetadata(); ASSERT_EQ(t.ppf().info.alpha_bits, 8); EXPECT_EQ(t.ppf().frames[0].color.format.data_type, JXL_TYPE_UINT8); @@ -1113,12 +1103,12 @@ EXPECT_TRUE(test::SameAlpha(t.ppf(), ppf_out)); } TEST(JxlTest, RoundtripDots) { ThreadPool* pool = nullptr; - const PaddedBytes orig = jxl::test::ReadTestData( - "external/wesaturate/500px/cvo9xd_keong_macan_srgb8.png"); + const std::vector orig = + ReadTestData("external/wesaturate/500px/cvo9xd_keong_macan_srgb8.png"); TestImage t; t.DecodeFromBytes(orig).ClearMetadata(); ASSERT_NE(t.ppf().info.xsize, 0); EXPECT_EQ(t.ppf().info.bits_per_sample, 8); EXPECT_EQ(t.ppf().color_encoding.transfer_function, @@ -1134,12 +1124,12 @@ EXPECT_THAT(ButteraugliDistance(t.ppf(), ppf_out), IsSlightlyBelow(0.35)); } TEST(JxlTest, RoundtripNoise) { ThreadPool* pool = nullptr; - const PaddedBytes orig = jxl::test::ReadTestData( - "external/wesaturate/500px/u76c0g_bliznaca_srgb8.png"); + const std::vector orig = + ReadTestData("external/wesaturate/500px/u76c0g_bliznaca_srgb8.png"); TestImage t; t.DecodeFromBytes(orig).ClearMetadata(); ASSERT_NE(t.ppf().info.xsize, 0); EXPECT_EQ(t.ppf().info.bits_per_sample, 8); EXPECT_EQ(t.ppf().color_encoding.transfer_function, @@ -1154,11 +1144,11 @@ EXPECT_THAT(ButteraugliDistance(t.ppf(), ppf_out), IsSlightlyBelow(1.35)); } TEST(JxlTest, RoundtripLossless8Gray) { ThreadPool* pool = nullptr; - const PaddedBytes orig = jxl::test::ReadTestData( + const std::vector orig = ReadTestData( "external/wesaturate/500px/cvo9xd_keong_macan_grayscale.png"); TestImage t; t.SetColorEncoding("Gra_D65_Rel_SRG").DecodeFromBytes(orig).ClearMetadata(); EXPECT_EQ(t.ppf().color_encoding.color_space, JXL_COLOR_SPACE_GRAY); EXPECT_EQ(t.ppf().info.bits_per_sample, 8); @@ -1179,11 +1169,11 @@ if (!jxl::extras::CanDecode(jxl::extras::Codec::kGIF)) { fprintf(stderr, "Skipping test because of missing GIF decoder.\n"); return; } ThreadPool* pool = nullptr; - const PaddedBytes orig = jxl::test::ReadTestData("jxl/traffic_light.gif"); + const std::vector orig = ReadTestData("jxl/traffic_light.gif"); TestImage t; t.DecodeFromBytes(orig).ClearMetadata(); EXPECT_EQ(4, t.ppf().frames.size()); JXLDecompressParams dparams; @@ -1207,11 +1197,11 @@ if (!jxl::extras::CanDecode(jxl::extras::Codec::kGIF)) { fprintf(stderr, "Skipping test because of missing GIF decoder.\n"); return; } ThreadPool* pool = nullptr; - const PaddedBytes orig = jxl::test::ReadTestData("jxl/traffic_light.gif"); + const std::vector orig = ReadTestData("jxl/traffic_light.gif"); TestImage t; t.DecodeFromBytes(orig).ClearMetadata(); EXPECT_EQ(4, t.ppf().frames.size()); JXLCompressParams cparams = CompressParamsForLossless(); @@ -1232,11 +1222,11 @@ if (!jxl::extras::CanDecode(jxl::extras::Codec::kGIF)) { fprintf(stderr, "Skipping test because of missing GIF decoder.\n"); return; } ThreadPool* pool = nullptr; - const PaddedBytes orig = jxl::test::ReadTestData("jxl/animation_patches.gif"); + const std::vector orig = ReadTestData("jxl/animation_patches.gif"); TestImage t; t.DecodeFromBytes(orig).ClearMetadata(); ASSERT_EQ(2u, t.ppf().frames.size()); @@ -1253,15 +1243,13 @@ EXPECT_EQ(ppf_out.frames.size(), t.ppf().frames.size()); // >10 with broken patches EXPECT_THAT(ButteraugliDistance(t.ppf(), ppf_out), IsSlightlyBelow(0.8999)); } -size_t RoundtripJpeg(const PaddedBytes& jpeg_in, ThreadPool* pool) { - std::vector jpeg_bytes(jpeg_in.data(), - jpeg_in.data() + jpeg_in.size()); +size_t RoundtripJpeg(const std::vector& jpeg_in, ThreadPool* pool) { std::vector compressed; - EXPECT_TRUE(extras::EncodeImageJXL({}, extras::PackedPixelFile(), &jpeg_bytes, + EXPECT_TRUE(extras::EncodeImageJXL({}, extras::PackedPixelFile(), &jpeg_in, &compressed)); jxl::JXLDecompressParams dparams; test::DefaultAcceptedFormats(dparams); test::SetThreadParallelRunner(dparams, pool); @@ -1281,11 +1269,11 @@ } } return compressed.size(); } -void RoundtripJpegToPixels(const PaddedBytes& jpeg_in, +void RoundtripJpegToPixels(const std::vector& jpeg_in, JXLDecompressParams dparams, ThreadPool* pool, PackedPixelFile* ppf_out) { std::vector jpeg_bytes(jpeg_in.data(), jpeg_in.data() + jpeg_in.size()); std::vector compressed; @@ -1298,21 +1286,21 @@ nullptr, ppf_out, nullptr)); } TEST(JxlTest, JXL_TRANSCODE_JPEG_TEST(RoundtripJpegRecompression444)) { ThreadPoolForTests pool(8); - const PaddedBytes orig = - jxl::test::ReadTestData("jxl/flower/flower.png.im_q85_444.jpg"); + const std::vector orig = + ReadTestData("jxl/flower/flower.png.im_q85_444.jpg"); // JPEG size is 696,659 bytes. - EXPECT_NEAR(RoundtripJpeg(orig, &pool), 568940u, 10); + EXPECT_NEAR(RoundtripJpeg(orig, &pool), 568940u, 20); } TEST(JxlTest, JXL_TRANSCODE_JPEG_TEST(RoundtripJpegRecompressionToPixels)) { TEST_LIBJPEG_SUPPORT(); ThreadPoolForTests pool(8); - const PaddedBytes orig = - jxl::test::ReadTestData("jxl/flower/flower.png.im_q85_444.jpg"); + const std::vector orig = + ReadTestData("jxl/flower/flower.png.im_q85_444.jpg"); TestImage t; t.DecodeFromBytes(orig); PackedPixelFile ppf_out; RoundtripJpegToPixels(orig, {}, &pool, &ppf_out); @@ -1320,12 +1308,12 @@ } TEST(JxlTest, JXL_TRANSCODE_JPEG_TEST(RoundtripJpegRecompressionToPixels420)) { TEST_LIBJPEG_SUPPORT(); ThreadPoolForTests pool(8); - const PaddedBytes orig = - jxl::test::ReadTestData("jxl/flower/flower.png.im_q85_420.jpg"); + const std::vector orig = + ReadTestData("jxl/flower/flower.png.im_q85_420.jpg"); TestImage t; t.DecodeFromBytes(orig); PackedPixelFile ppf_out; RoundtripJpegToPixels(orig, {}, &pool, &ppf_out); @@ -1334,12 +1322,12 @@ TEST(JxlTest, JXL_TRANSCODE_JPEG_TEST(RoundtripJpegRecompressionToPixels420EarlyFlush)) { TEST_LIBJPEG_SUPPORT(); ThreadPoolForTests pool(8); - const PaddedBytes orig = - jxl::test::ReadTestData("jxl/flower/flower.png.im_q85_420.jpg"); + const std::vector orig = + ReadTestData("jxl/flower/flower.png.im_q85_420.jpg"); TestImage t; t.DecodeFromBytes(orig); JXLDecompressParams dparams; dparams.max_downsampling = 8; @@ -1351,12 +1339,12 @@ TEST(JxlTest, JXL_TRANSCODE_JPEG_TEST(RoundtripJpegRecompressionToPixels420Mul16)) { TEST_LIBJPEG_SUPPORT(); ThreadPoolForTests pool(8); - const PaddedBytes orig = - jxl::test::ReadTestData("jxl/flower/flower_cropped.jpg"); + const std::vector orig = + ReadTestData("jxl/flower/flower_cropped.jpg"); TestImage t; t.DecodeFromBytes(orig); PackedPixelFile ppf_out; RoundtripJpegToPixels(orig, {}, &pool, &ppf_out); @@ -1365,118 +1353,118 @@ TEST(JxlTest, JXL_TRANSCODE_JPEG_TEST(RoundtripJpegRecompressionToPixels_asymmetric)) { TEST_LIBJPEG_SUPPORT(); ThreadPoolForTests pool(8); - const PaddedBytes orig = - jxl::test::ReadTestData("jxl/flower/flower.png.im_q85_asymmetric.jpg"); + const std::vector orig = + ReadTestData("jxl/flower/flower.png.im_q85_asymmetric.jpg"); TestImage t; t.DecodeFromBytes(orig); PackedPixelFile ppf_out; RoundtripJpegToPixels(orig, {}, &pool, &ppf_out); EXPECT_THAT(ComputeDistance2(t.ppf(), ppf_out), IsSlightlyBelow(10)); } TEST(JxlTest, JXL_TRANSCODE_JPEG_TEST(RoundtripJpegRecompressionGray)) { ThreadPoolForTests pool(8); - const PaddedBytes orig = - jxl::test::ReadTestData("jxl/flower/flower.png.im_q85_gray.jpg"); + const std::vector orig = + ReadTestData("jxl/flower/flower.png.im_q85_gray.jpg"); // JPEG size is 456,528 bytes. EXPECT_NEAR(RoundtripJpeg(orig, &pool), 387496u, 200); } TEST(JxlTest, JXL_TRANSCODE_JPEG_TEST(RoundtripJpegRecompression420)) { ThreadPoolForTests pool(8); - const PaddedBytes orig = - jxl::test::ReadTestData("jxl/flower/flower.png.im_q85_420.jpg"); + const std::vector orig = + ReadTestData("jxl/flower/flower.png.im_q85_420.jpg"); // JPEG size is 546,797 bytes. EXPECT_NEAR(RoundtripJpeg(orig, &pool), 455560u, 10); } TEST(JxlTest, JXL_TRANSCODE_JPEG_TEST(RoundtripJpegRecompression_luma_subsample)) { ThreadPoolForTests pool(8); - const PaddedBytes orig = jxl::test::ReadTestData( - "jxl/flower/flower.png.im_q85_luma_subsample.jpg"); + const std::vector orig = + ReadTestData("jxl/flower/flower.png.im_q85_luma_subsample.jpg"); // JPEG size is 400,724 bytes. EXPECT_NEAR(RoundtripJpeg(orig, &pool), 325354u, 10); } TEST(JxlTest, JXL_TRANSCODE_JPEG_TEST(RoundtripJpegRecompression444_12)) { // 444 JPEG that has an interesting sampling-factor (1x2, 1x2, 1x2). ThreadPoolForTests pool(8); - const PaddedBytes orig = - jxl::test::ReadTestData("jxl/flower/flower.png.im_q85_444_1x2.jpg"); + const std::vector orig = + ReadTestData("jxl/flower/flower.png.im_q85_444_1x2.jpg"); // JPEG size is 703,874 bytes. EXPECT_NEAR(RoundtripJpeg(orig, &pool), 569679u, 10); } TEST(JxlTest, JXL_TRANSCODE_JPEG_TEST(RoundtripJpegRecompression422)) { ThreadPoolForTests pool(8); - const PaddedBytes orig = - jxl::test::ReadTestData("jxl/flower/flower.png.im_q85_422.jpg"); + const std::vector orig = + ReadTestData("jxl/flower/flower.png.im_q85_422.jpg"); // JPEG size is 522,057 bytes. EXPECT_NEAR(RoundtripJpeg(orig, &pool), 499282u, 10); } TEST(JxlTest, JXL_TRANSCODE_JPEG_TEST(RoundtripJpegRecompression440)) { ThreadPoolForTests pool(8); - const PaddedBytes orig = - jxl::test::ReadTestData("jxl/flower/flower.png.im_q85_440.jpg"); + const std::vector orig = + ReadTestData("jxl/flower/flower.png.im_q85_440.jpg"); // JPEG size is 603,623 bytes. EXPECT_NEAR(RoundtripJpeg(orig, &pool), 501151u, 10); } TEST(JxlTest, JXL_TRANSCODE_JPEG_TEST(RoundtripJpegRecompression_asymmetric)) { // 2x vertical downsample of one chroma channel, 2x horizontal downsample of // the other. ThreadPoolForTests pool(8); - const PaddedBytes orig = - jxl::test::ReadTestData("jxl/flower/flower.png.im_q85_asymmetric.jpg"); + const std::vector orig = + ReadTestData("jxl/flower/flower.png.im_q85_asymmetric.jpg"); // JPEG size is 604,601 bytes. EXPECT_NEAR(RoundtripJpeg(orig, &pool), 500602u, 10); } TEST(JxlTest, JXL_TRANSCODE_JPEG_TEST(RoundtripJpegRecompression420Progr)) { ThreadPoolForTests pool(8); - const PaddedBytes orig = - jxl::test::ReadTestData("jxl/flower/flower.png.im_q85_420_progr.jpg"); + const std::vector orig = + ReadTestData("jxl/flower/flower.png.im_q85_420_progr.jpg"); // JPEG size is 522,057 bytes. EXPECT_NEAR(RoundtripJpeg(orig, &pool), 455499u, 10); } TEST(JxlTest, JXL_TRANSCODE_JPEG_TEST(RoundtripJpegRecompressionMetadata)) { ThreadPoolForTests pool(8); - const PaddedBytes orig = - jxl::test::ReadTestData("jxl/jpeg_reconstruction/1x1_exif_xmp.jpg"); + const std::vector orig = + ReadTestData("jxl/jpeg_reconstruction/1x1_exif_xmp.jpg"); // JPEG size is 4290 bytes EXPECT_NEAR(RoundtripJpeg(orig, &pool), 1400u, 30); } TEST(JxlTest, JXL_TRANSCODE_JPEG_TEST(RoundtripJpegRecompressionRestarts)) { ThreadPoolForTests pool(8); - const PaddedBytes orig = - jxl::test::ReadTestData("jxl/jpeg_reconstruction/bicycles_restarts.jpg"); + const std::vector orig = + ReadTestData("jxl/jpeg_reconstruction/bicycles_restarts.jpg"); // JPEG size is 87478 bytes EXPECT_NEAR(RoundtripJpeg(orig, &pool), 76125u, 30); } TEST(JxlTest, JXL_TRANSCODE_JPEG_TEST(RoundtripJpegRecompressionOrientationICC)) { ThreadPoolForTests pool(8); - const PaddedBytes orig = - jxl::test::ReadTestData("jxl/jpeg_reconstruction/sideways_bench.jpg"); + const std::vector orig = + ReadTestData("jxl/jpeg_reconstruction/sideways_bench.jpg"); // JPEG size is 15252 bytes EXPECT_NEAR(RoundtripJpeg(orig, &pool), 12000u, 470); // TODO(jon): investigate why 'Cross-compiling i686-linux-gnu' produces a // larger result } TEST(JxlTest, RoundtripProgressive) { ThreadPoolForTests pool(4); - const PaddedBytes orig = jxl::test::ReadTestData("jxl/flower/flower.png"); + const std::vector orig = ReadTestData("jxl/flower/flower.png"); TestImage t; t.DecodeFromBytes(orig).ClearMetadata().SetDimensions(600, 1024); JXLCompressParams cparams; cparams.AddOption(JXL_ENC_FRAME_SETTING_PROGRESSIVE_DC, 1); @@ -1488,11 +1476,11 @@ EXPECT_THAT(ButteraugliDistance(t.ppf(), ppf_out), IsSlightlyBelow(1.4)); } TEST(JxlTest, RoundtripProgressiveLevel2Slow) { ThreadPoolForTests pool(8); - const PaddedBytes orig = jxl::test::ReadTestData("jxl/flower/flower.png"); + const std::vector orig = ReadTestData("jxl/flower/flower.png"); TestImage t; t.DecodeFromBytes(orig).ClearMetadata().SetDimensions(600, 1024); JXLCompressParams cparams; cparams.AddOption(JXL_ENC_FRAME_SETTING_EFFORT, 9); // kTortoise @@ -1544,11 +1532,11 @@ if (bit_depth == 1 && (channels == 2 || channels == 4)) continue; std::string extension(kExtension[channels]); std::string filename = "jxl/flower/flower_small." + std::string(kChannels[channels]) + ".depth" + std::to_string(bit_depth) + extension; - const PaddedBytes orig = jxl::test::ReadTestData(filename); + const std::vector orig = ReadTestData(filename); test::TestImage t; if (channels < 3) t.SetColorEncoding("Gra_D65_Rel_SRG"); t.DecodeFromBytes(orig); JXLCompressParams cparams = CompressParamsForLossless(); @@ -1576,11 +1564,11 @@ class JxlTest : public ::testing::TestWithParam {}; TEST_P(JxlTest, LosslessSmallFewColors) { ThreadPoolForTests pool(8); - const PaddedBytes orig = jxl::test::ReadTestData(GetParam()); + const std::vector orig = ReadTestData(GetParam()); TestImage t; t.DecodeFromBytes(orig).ClearMetadata(); JXLCompressParams cparams; cparams.distance = 0; diff --git a/third_party/jpeg-xl/lib/jxl/libjxl_cms.pc.in b/third_party/jpeg-xl/lib/jxl/libjxl_cms.pc.in new file mode 100644 --- /dev/null +++ b/third_party/jpeg-xl/lib/jxl/libjxl_cms.pc.in @@ -0,0 +1,13 @@ +prefix=@CMAKE_INSTALL_PREFIX@ +exec_prefix=${prefix} +libdir=@PKGCONFIG_TARGET_LIBS@ +includedir=@PKGCONFIG_TARGET_INCLUDES@ + +Name: libjxl_cms +Description: CMS support library for libjxl +Version: @JPEGXL_LIBRARY_VERSION@ +Requires.private: @JPEGXL_CMS_LIBRARY_REQUIRES@ +Libs: @JXL_CMS_PK_LIBS@ -L${libdir} -ljxl_cms +Libs.private: -lm +Cflags: -I${includedir} +Cflags.private: -DJXL_STATIC_DEFINE diff --git a/third_party/jpeg-xl/lib/jxl/modular/encoding/enc_encoding.h b/third_party/jpeg-xl/lib/jxl/modular/encoding/enc_encoding.h --- a/third_party/jpeg-xl/lib/jxl/modular/encoding/enc_encoding.h +++ b/third_party/jpeg-xl/lib/jxl/modular/encoding/enc_encoding.h @@ -4,31 +4,25 @@ // license that can be found in the LICENSE file. #ifndef LIB_JXL_MODULAR_ENCODING_ENC_ENCODING_H_ #define LIB_JXL_MODULAR_ENCODING_ENC_ENCODING_H_ -#include -#include - +#include #include -#include "lib/jxl/base/compiler_specific.h" -#include "lib/jxl/base/padded_bytes.h" -#include "lib/jxl/base/span.h" -#include "lib/jxl/dec_ans.h" #include "lib/jxl/enc_ans.h" #include "lib/jxl/enc_bit_writer.h" #include "lib/jxl/image.h" -#include "lib/jxl/modular/encoding/context_predict.h" +#include "lib/jxl/modular/encoding/dec_ma.h" #include "lib/jxl/modular/encoding/enc_ma.h" -#include "lib/jxl/modular/encoding/encoding.h" -#include "lib/jxl/modular/modular_image.h" #include "lib/jxl/modular/options.h" -#include "lib/jxl/modular/transform/transform.h" namespace jxl { +struct AuxOut; +struct GroupHeader; + Tree LearnTree(TreeSamples &&tree_samples, size_t total_pixels, const ModularOptions &options, const std::vector &multiplier_info = {}, StaticPropRange static_prop_range = {}); diff --git a/third_party/jpeg-xl/lib/jxl/modular/encoding/enc_encoding.cc b/third_party/jpeg-xl/lib/jxl/modular/encoding/enc_encoding.cc --- a/third_party/jpeg-xl/lib/jxl/modular/encoding/enc_encoding.cc +++ b/third_party/jpeg-xl/lib/jxl/modular/encoding/enc_encoding.cc @@ -459,15 +459,16 @@ std::vector diff_samples; std::vector group_pixel_count; std::vector channel_pixel_count; CollectPixelSamples(image, options, 0, group_pixel_count, channel_pixel_count, pixel_samples, diff_samples); - std::vector dummy_multiplier_info; + std::vector placeholder_multiplier_info; StaticPropRange range; tree_samples_storage.PreQuantizeProperties( - range, dummy_multiplier_info, group_pixel_count, channel_pixel_count, - pixel_samples, diff_samples, options.max_property_values); + range, placeholder_multiplier_info, group_pixel_count, + channel_pixel_count, pixel_samples, diff_samples, + options.max_property_values); } for (size_t i = 0; i < nb_channels; i++) { if (!image.channel[i].w || !image.channel[i].h) { continue; // skip empty channels } diff --git a/third_party/jpeg-xl/lib/jxl/modular/encoding/enc_ma.cc b/third_party/jpeg-xl/lib/jxl/modular/encoding/enc_ma.cc --- a/third_party/jpeg-xl/lib/jxl/modular/encoding/enc_ma.cc +++ b/third_party/jpeg-xl/lib/jxl/modular/encoding/enc_ma.cc @@ -920,11 +920,11 @@ channel_pixel_count.resize(image.channel.size()); } Rng rng(group_id); // Sample 10% of the final number of samples for property quantization. float fraction = std::min(options.nb_repeats * 0.1, 0.99); - Rng::GeometricDistribution dist(fraction); + Rng::GeometricDistribution dist = Rng::MakeGeometric(fraction); size_t total_pixels = 0; std::vector channel_ids; for (size_t i = 0; i < image.channel.size(); i++) { if (image.channel[i].w <= 1 || image.channel[i].h == 0) { continue; // skip empty or width-1 channels. diff --git a/third_party/jpeg-xl/lib/jxl/modular/encoding/encoding.h b/third_party/jpeg-xl/lib/jxl/modular/encoding/encoding.h --- a/third_party/jpeg-xl/lib/jxl/modular/encoding/encoding.h +++ b/third_party/jpeg-xl/lib/jxl/modular/encoding/encoding.h @@ -4,26 +4,30 @@ // license that can be found in the LICENSE file. #ifndef LIB_JXL_MODULAR_ENCODING_ENCODING_H_ #define LIB_JXL_MODULAR_ENCODING_ENCODING_H_ -#include -#include - #include +#include +#include #include -#include "lib/jxl/dec_ans.h" +#include "lib/jxl/base/compiler_specific.h" +#include "lib/jxl/base/status.h" +#include "lib/jxl/field_encodings.h" #include "lib/jxl/image.h" #include "lib/jxl/modular/encoding/context_predict.h" #include "lib/jxl/modular/encoding/dec_ma.h" #include "lib/jxl/modular/modular_image.h" #include "lib/jxl/modular/options.h" #include "lib/jxl/modular/transform/transform.h" namespace jxl { +struct ANSCode; +class BitReader; + // Valid range of properties for using lookup tables instead of trees. constexpr int32_t kPropRangeFast = 512; struct GroupHeader : public Fields { GroupHeader(); diff --git a/third_party/jpeg-xl/lib/jxl/modular/encoding/encoding.cc b/third_party/jpeg-xl/lib/jxl/modular/encoding/encoding.cc --- a/third_party/jpeg-xl/lib/jxl/modular/encoding/encoding.cc +++ b/third_party/jpeg-xl/lib/jxl/modular/encoding/encoding.cc @@ -10,10 +10,12 @@ #include #include "lib/jxl/base/printf_macros.h" #include "lib/jxl/base/scope_guard.h" +#include "lib/jxl/dec_ans.h" +#include "lib/jxl/dec_bit_reader.h" #include "lib/jxl/modular/encoding/context_predict.h" #include "lib/jxl/modular/options.h" #include "lib/jxl/pack_signed.h" namespace jxl { @@ -91,11 +93,12 @@ cur_child = global_tree[cur_child].lchild; } else { cur_child = global_tree[cur_child].rchild; } } - // We ended up in a leaf, add a dummy decision and two copies of the leaf. + // We ended up in a leaf, add a placeholder decision and two copies of the + // leaf. if (global_tree[cur_child].property == -1) { flat.properties[i] = 0; flat.splitvals[i] = 0; nodes.push(cur_child); nodes.push(cur_child); 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 @@ -458,16 +458,16 @@ image.channel[c].h = (h + 1) / 2; if (image.channel[c].vshift >= 0) image.channel[c].vshift++; h = h - (h + 1) / 2; } image.channel[c].shrink(); - Channel dummy(w, h); - dummy.hshift = image.channel[c].hshift; - dummy.vshift = image.channel[c].vshift; + Channel placeholder(w, h); + placeholder.hshift = image.channel[c].hshift; + placeholder.vshift = image.channel[c].vshift; image.channel.insert(image.channel.begin() + offset + (c - beginc), - std::move(dummy)); + std::move(placeholder)); JXL_DEBUG_V(8, "MetaSqueeze applied, current image: %s", image.DebugString().c_str()); } } return true; diff --git a/third_party/jpeg-xl/lib/jxl/modular_test.cc b/third_party/jpeg-xl/lib/jxl/modular_test.cc --- a/third_party/jpeg-xl/lib/jxl/modular_test.cc +++ b/third_party/jpeg-xl/lib/jxl/modular_test.cc @@ -1,58 +1,59 @@ // 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 - #include +#include #include #include #include #include "lib/extras/codec.h" #include "lib/extras/dec/jxl.h" #include "lib/extras/metrics.h" #include "lib/jxl/base/compiler_specific.h" #include "lib/jxl/base/data_parallel.h" #include "lib/jxl/base/override.h" -#include "lib/jxl/base/padded_bytes.h" +#include "lib/jxl/base/span.h" #include "lib/jxl/cms/jxl_cms.h" #include "lib/jxl/codec_in_out.h" #include "lib/jxl/color_encoding_internal.h" #include "lib/jxl/enc_aux_out.h" #include "lib/jxl/enc_butteraugli_comparator.h" #include "lib/jxl/enc_cache.h" #include "lib/jxl/enc_fields.h" -#include "lib/jxl/enc_file.h" #include "lib/jxl/enc_params.h" #include "lib/jxl/enc_toc.h" #include "lib/jxl/image.h" #include "lib/jxl/image_bundle.h" #include "lib/jxl/image_ops.h" #include "lib/jxl/image_test_utils.h" #include "lib/jxl/modular/encoding/enc_encoding.h" #include "lib/jxl/modular/encoding/encoding.h" #include "lib/jxl/modular/encoding/ma_common.h" +#include "lib/jxl/padded_bytes.h" #include "lib/jxl/test_utils.h" #include "lib/jxl/testing.h" namespace jxl { namespace { + +using test::ReadTestData; using test::Roundtrip; void TestLosslessGroups(size_t group_size_shift) { - const PaddedBytes orig = jxl::test::ReadTestData("jxl/flower/flower.png"); + const std::vector orig = ReadTestData("jxl/flower/flower.png"); CompressParams cparams; cparams.SetLossless(); cparams.modular_group_size_shift = group_size_shift; CodecInOut io_out; CodecInOut io; - ASSERT_TRUE(SetFromBytes(Span(orig), &io)); + ASSERT_TRUE(SetFromBytes(Bytes(orig), &io)); io.ShrinkTo(io.xsize() / 4, io.ysize() / 4); size_t compressed_size; JXL_EXPECT_OK(Roundtrip(&io, cparams, {}, &io_out, _, &compressed_size)); EXPECT_LE(compressed_size, 280000u); @@ -68,12 +69,12 @@ TEST(ModularTest, JXL_TSAN_SLOW_TEST(RoundtripLosslessGroups1024)) { TestLosslessGroups(3); } TEST(ModularTest, RoundtripLosslessCustomWP_PermuteRCT) { - const PaddedBytes orig = jxl::test::ReadTestData( - "external/wesaturate/500px/u76c0g_bliznaca_srgb8.png"); + const std::vector orig = + ReadTestData("external/wesaturate/500px/u76c0g_bliznaca_srgb8.png"); CompressParams cparams; cparams.SetLossless(); // 9 = permute to GBR, to test the special case of permutation-only cparams.colorspace = 9; // slowest speed so different WP modes are tried @@ -81,32 +82,32 @@ cparams.options.predictor = {Predictor::Weighted}; CodecInOut io_out; CodecInOut io; - ASSERT_TRUE(SetFromBytes(Span(orig), &io)); + ASSERT_TRUE(SetFromBytes(Bytes(orig), &io)); io.ShrinkTo(100, 100); size_t compressed_size; JXL_EXPECT_OK(Roundtrip(&io, cparams, {}, &io_out, _, &compressed_size)); EXPECT_LE(compressed_size, 10169u); JXL_EXPECT_OK(SamePixels(*io.Main().color(), *io_out.Main().color(), _)); } TEST(ModularTest, RoundtripLossyDeltaPalette) { - const PaddedBytes orig = jxl::test::ReadTestData( - "external/wesaturate/500px/u76c0g_bliznaca_srgb8.png"); + const std::vector orig = + ReadTestData("external/wesaturate/500px/u76c0g_bliznaca_srgb8.png"); CompressParams cparams; cparams.modular_mode = true; cparams.color_transform = jxl::ColorTransform::kNone; cparams.lossy_palette = true; cparams.palette_colors = 0; CodecInOut io_out; CodecInOut io; - ASSERT_TRUE(SetFromBytes(Span(orig), &io)); + ASSERT_TRUE(SetFromBytes(Bytes(orig), &io)); io.ShrinkTo(300, 100); size_t compressed_size; JXL_EXPECT_OK(Roundtrip(&io, cparams, {}, &io_out, _, &compressed_size)); EXPECT_LE(compressed_size, 6800u); @@ -114,22 +115,22 @@ *JxlGetDefaultCms(), /*distmap=*/nullptr), IsSlightlyBelow(1.5)); } TEST(ModularTest, RoundtripLossyDeltaPaletteWP) { - const PaddedBytes orig = jxl::test::ReadTestData( - "external/wesaturate/500px/u76c0g_bliznaca_srgb8.png"); + const std::vector orig = + ReadTestData("external/wesaturate/500px/u76c0g_bliznaca_srgb8.png"); CompressParams cparams; cparams.SetLossless(); cparams.lossy_palette = true; cparams.palette_colors = 0; cparams.options.predictor = jxl::Predictor::Weighted; CodecInOut io_out; CodecInOut io; - ASSERT_TRUE(SetFromBytes(Span(orig), &io)); + ASSERT_TRUE(SetFromBytes(Bytes(orig), &io)); io.ShrinkTo(300, 100); size_t compressed_size; JXL_EXPECT_OK(Roundtrip(&io, cparams, {}, &io_out, _, &compressed_size)); EXPECT_LE(compressed_size, 7000u); @@ -138,21 +139,21 @@ /*distmap=*/nullptr), IsSlightlyBelow(10.1)); } TEST(ModularTest, RoundtripLossy) { - const PaddedBytes orig = jxl::test::ReadTestData( - "external/wesaturate/500px/u76c0g_bliznaca_srgb8.png"); + const std::vector orig = + ReadTestData("external/wesaturate/500px/u76c0g_bliznaca_srgb8.png"); CompressParams cparams; cparams.modular_mode = true; cparams.butteraugli_distance = 2.f; cparams.SetCms(*JxlGetDefaultCms()); CodecInOut io_out; CodecInOut io; - ASSERT_TRUE(SetFromBytes(Span(orig), &io)); + ASSERT_TRUE(SetFromBytes(Bytes(orig), &io)); size_t compressed_size; JXL_EXPECT_OK(Roundtrip(&io, cparams, {}, &io_out, _, &compressed_size)); EXPECT_LE(compressed_size, 30000u); EXPECT_THAT(ButteraugliDistance(io.frames, io_out.frames, ButteraugliParams(), @@ -160,20 +161,20 @@ /*distmap=*/nullptr), IsSlightlyBelow(2.3)); } TEST(ModularTest, RoundtripLossy16) { - const PaddedBytes orig = jxl::test::ReadTestData( - "external/raw.pixls/DJI-FC6310-16bit_709_v4_krita.png"); + const std::vector orig = + ReadTestData("external/raw.pixls/DJI-FC6310-16bit_709_v4_krita.png"); CompressParams cparams; cparams.modular_mode = true; cparams.butteraugli_distance = 2.f; CodecInOut io_out; CodecInOut io; - ASSERT_TRUE(SetFromBytes(Span(orig), &io)); + ASSERT_TRUE(SetFromBytes(Bytes(orig), &io)); JXL_CHECK(!io.metadata.m.have_preview); JXL_CHECK(io.frames.size() == 1); JXL_CHECK( io.frames[0].TransformTo(ColorEncoding::SRGB(), *JxlGetDefaultCms())); io.metadata.m.color_encoding = ColorEncoding::SRGB(); @@ -228,14 +229,14 @@ } } } TEST(ModularTest, RoundtripLosslessCustomSqueeze) { - const PaddedBytes orig = jxl::test::ReadTestData( - "external/wesaturate/500px/tmshre_riaphotographs_srgb8.png"); + const std::vector orig = + ReadTestData("external/wesaturate/500px/tmshre_riaphotographs_srgb8.png"); CodecInOut io; - ASSERT_TRUE(SetFromBytes(Span(orig), &io)); + ASSERT_TRUE(SetFromBytes(Bytes(orig), &io)); CompressParams cparams; cparams.modular_mode = true; cparams.color_transform = jxl::ColorTransform::kNone; cparams.butteraugli_distance = 0.f; @@ -295,14 +296,14 @@ int bitdepth = config.bitdepth; int responsive = config.responsive; ThreadPool* pool = nullptr; Rng generator(123); - const PaddedBytes orig = jxl::test::ReadTestData( - "external/wesaturate/500px/u76c0g_bliznaca_srgb8.png"); + const std::vector orig = + ReadTestData("external/wesaturate/500px/u76c0g_bliznaca_srgb8.png"); CodecInOut io1; - ASSERT_TRUE(SetFromBytes(Span(orig), &io1, pool)); + ASSERT_TRUE(SetFromBytes(Bytes(orig), &io1, pool)); // vary the dimensions a bit, in case of bugs related to // even vs odd width or height. size_t xsize = 423 + bitdepth; size_t ysize = 467 + bitdepth; diff --git a/third_party/jpeg-xl/lib/jxl/padded_bytes.h b/third_party/jpeg-xl/lib/jxl/padded_bytes.h new file mode 100644 --- /dev/null +++ b/third_party/jpeg-xl/lib/jxl/padded_bytes.h @@ -0,0 +1,216 @@ +// Copyright (c) the JPEG XL Project Authors. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#ifndef LIB_JXL_BASE_PADDED_BYTES_H_ +#define LIB_JXL_BASE_PADDED_BYTES_H_ + +// std::vector replacement with padding to reduce bounds checks in WriteBits + +#include +#include +#include // memcpy + +#include // max +#include +#include // swap + +#include "lib/jxl/base/compiler_specific.h" +#include "lib/jxl/base/status.h" +#include "lib/jxl/cache_aligned.h" + +namespace jxl { + +// Provides a subset of the std::vector interface with some differences: +// - allows BitWriter to write 64 bits at a time without bounds checking; +// - ONLY zero-initializes the first byte (required by BitWriter); +// - ensures cache-line alignment. +class PaddedBytes { + public: + // Required for output params. + PaddedBytes() : size_(0), capacity_(0) {} + + explicit PaddedBytes(size_t size) : size_(size), capacity_(0) { + reserve(size); + } + + PaddedBytes(size_t size, uint8_t value) : size_(size), capacity_(0) { + reserve(size); + if (size_ != 0) { + memset(data(), value, size); + } + } + + PaddedBytes(const PaddedBytes& other) : size_(other.size_), capacity_(0) { + reserve(size_); + if (data() != nullptr) memcpy(data(), other.data(), size_); + } + PaddedBytes& operator=(const PaddedBytes& other) { + // Self-assignment is safe. + resize(other.size()); + if (data() != nullptr) memmove(data(), other.data(), size_); + return *this; + } + + // default is not OK - need to set other.size_ to 0! + PaddedBytes(PaddedBytes&& other) noexcept + : size_(other.size_), + capacity_(other.capacity_), + data_(std::move(other.data_)) { + other.size_ = other.capacity_ = 0; + } + PaddedBytes& operator=(PaddedBytes&& other) noexcept { + size_ = other.size_; + capacity_ = other.capacity_; + data_ = std::move(other.data_); + + if (&other != this) { + other.size_ = other.capacity_ = 0; + } + return *this; + } + + void swap(PaddedBytes& other) { + std::swap(size_, other.size_); + std::swap(capacity_, other.capacity_); + std::swap(data_, other.data_); + } + + // If current capacity is greater than requested, then no-op. Otherwise + // copies existing data to newly allocated "data_". If allocation fails, + // data() == nullptr and size_ = capacity_ = 0. + // The new capacity will be at least 1.5 times the old capacity. This ensures + // that we avoid quadratic behaviour. + void reserve(size_t capacity) { + if (capacity <= capacity_) return; + + size_t new_capacity = std::max(capacity, 3 * capacity_ / 2); + new_capacity = std::max(64, new_capacity); + + // BitWriter writes up to 7 bytes past the end. + CacheAlignedUniquePtr new_data = AllocateArray(new_capacity + 8); + if (new_data == nullptr) { + // Allocation failed, discard all data to ensure this is noticed. + size_ = capacity_ = 0; + return; + } + + if (data_ == nullptr) { + // First allocation: ensure first byte is initialized (won't be copied). + new_data[0] = 0; + } else { + // Subsequent resize: copy existing data to new location. + memcpy(new_data.get(), data_.get(), size_); + // Ensure that the first new byte is initialized, to allow write_bits to + // safely append to the newly-resized PaddedBytes. + new_data[size_] = 0; + } + + capacity_ = new_capacity; + std::swap(new_data, data_); + } + + // NOTE: unlike vector, this does not initialize the new data! + // However, we guarantee that write_bits can safely append after + // the resize, as we zero-initialize the first new byte of data. + // If size < capacity(), does not invalidate the memory. + void resize(size_t size) { + reserve(size); + size_ = (data() == nullptr) ? 0 : size; + } + + // resize(size) plus explicit initialization of the new data with `value`. + void resize(size_t size, uint8_t value) { + size_t old_size = size_; + resize(size); + if (size_ > old_size) { + memset(data() + old_size, value, size_ - old_size); + } + } + + // Amortized constant complexity due to exponential growth. + void push_back(uint8_t x) { + if (size_ == capacity_) { + reserve(capacity_ + 1); + if (data() == nullptr) return; + } + + data_[size_++] = x; + } + + size_t size() const { return size_; } + size_t capacity() const { return capacity_; } + + uint8_t* data() { return data_.get(); } + const uint8_t* data() const { return data_.get(); } + + // std::vector operations implemented in terms of the public interface above. + + void clear() { resize(0); } + bool empty() const { return size() == 0; } + + void assign(std::initializer_list il) { + resize(il.size()); + memcpy(data(), il.begin(), il.size()); + } + + uint8_t* begin() { return data(); } + const uint8_t* begin() const { return data(); } + uint8_t* end() { return begin() + size(); } + const uint8_t* end() const { return begin() + size(); } + + uint8_t& operator[](const size_t i) { + BoundsCheck(i); + return data()[i]; + } + const uint8_t& operator[](const size_t i) const { + BoundsCheck(i); + return data()[i]; + } + + uint8_t& back() { + JXL_ASSERT(size() != 0); + return data()[size() - 1]; + } + const uint8_t& back() const { + JXL_ASSERT(size() != 0); + return data()[size() - 1]; + } + + template + void append(const T& other) { + append(reinterpret_cast(other.data()), + reinterpret_cast(other.data()) + other.size()); + } + + void append(const uint8_t* begin, const uint8_t* end) { + if (end - begin > 0) { + size_t old_size = size(); + resize(size() + (end - begin)); + memcpy(data() + old_size, begin, end - begin); + } + } + + private: + void BoundsCheck(size_t i) const { + // <= is safe due to padding and required by BitWriter. + JXL_ASSERT(i <= size()); + } + + size_t size_; + size_t capacity_; + CacheAlignedUniquePtr data_; +}; + +template +static inline void Append(const T& s, PaddedBytes* out, + size_t* JXL_RESTRICT byte_pos) { + memcpy(out->data() + *byte_pos, s.data(), s.size()); + *byte_pos += s.size(); + JXL_CHECK(*byte_pos <= out->size()); +} + +} // namespace jxl + +#endif // LIB_JXL_BASE_PADDED_BYTES_H_ diff --git a/third_party/jpeg-xl/lib/jxl/padded_bytes_test.cc b/third_party/jpeg-xl/lib/jxl/padded_bytes_test.cc --- a/third_party/jpeg-xl/lib/jxl/padded_bytes_test.cc +++ b/third_party/jpeg-xl/lib/jxl/padded_bytes_test.cc @@ -1,11 +1,11 @@ // Copyright (c) the JPEG XL Project Authors. All rights reserved. // // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -#include "lib/jxl/base/padded_bytes.h" +#include "lib/jxl/padded_bytes.h" #include // iota #include #include "lib/jxl/testing.h" @@ -58,69 +58,7 @@ } EXPECT_EQ(170u, pb.size()); EXPECT_GT(pb.capacity(), 170u); } -// Can assign() a subset of the valid data. -TEST(PaddedBytesTest, TestAssignFromWithin) { - PaddedBytes pb; - pb.reserve(256); - for (size_t i = 0; i < 256; ++i) { - pb.push_back(i); - } - pb.assign(pb.data() + 64, pb.data() + 192); - EXPECT_EQ(128u, pb.size()); - for (size_t i = 0; i < 128; ++i) { - EXPECT_EQ(i + 64, pb[i]); - } -} - -// Can assign() a range with both valid and previously-allocated data. -TEST(PaddedBytesTest, TestAssignReclaim) { - PaddedBytes pb; - pb.reserve(256); - for (size_t i = 0; i < 256; ++i) { - pb.push_back(i); - } - - const uint8_t* mem = pb.data(); - pb.resize(200); - // Just shrank without reallocating - EXPECT_EQ(mem, pb.data()); - EXPECT_EQ(256u, pb.capacity()); - - // Reclaim part of initial allocation - pb.assign(pb.data() + 100, pb.data() + 240); - EXPECT_EQ(140u, pb.size()); - - for (size_t i = 0; i < 140; ++i) { - EXPECT_EQ(i + 100, pb[i]); - } -} - -// Can assign() smaller and larger ranges outside the current allocation. -TEST(PaddedBytesTest, TestAssignOutside) { - PaddedBytes pb; - pb.resize(400); - std::iota(pb.begin(), pb.end(), 1); - - std::vector small(64); - std::iota(small.begin(), small.end(), 500); - - pb.assign(small.data(), small.data() + small.size()); - EXPECT_EQ(64u, pb.size()); - for (size_t i = 0; i < 64; ++i) { - EXPECT_EQ((i + 500) & 0xFF, pb[i]); - } - - std::vector large(1000); - std::iota(large.begin(), large.end(), 600); - - pb.assign(large.data(), large.data() + large.size()); - EXPECT_EQ(1000u, pb.size()); - for (size_t i = 0; i < 1000; ++i) { - EXPECT_EQ((i + 600) & 0xFF, pb[i]); - } -} - } // namespace } // namespace jxl diff --git a/third_party/jpeg-xl/lib/jxl/passes_test.cc b/third_party/jpeg-xl/lib/jxl/passes_test.cc --- a/third_party/jpeg-xl/lib/jxl/passes_test.cc +++ b/third_party/jpeg-xl/lib/jxl/passes_test.cc @@ -11,35 +11,35 @@ #include "lib/extras/codec.h" #include "lib/jxl/base/compiler_specific.h" #include "lib/jxl/base/data_parallel.h" #include "lib/jxl/base/override.h" -#include "lib/jxl/base/padded_bytes.h" +#include "lib/jxl/base/span.h" #include "lib/jxl/cms/jxl_cms.h" #include "lib/jxl/color_encoding_internal.h" #include "lib/jxl/enc_aux_out.h" #include "lib/jxl/enc_butteraugli_comparator.h" #include "lib/jxl/enc_cache.h" -#include "lib/jxl/enc_file.h" #include "lib/jxl/enc_params.h" #include "lib/jxl/image_bundle.h" #include "lib/jxl/image_ops.h" #include "lib/jxl/test_utils.h" #include "lib/jxl/testing.h" namespace jxl { +using test::ReadTestData; using test::Roundtrip; using test::ThreadPoolForTests; namespace { TEST(PassesTest, RoundtripSmallPasses) { - const PaddedBytes orig = jxl::test::ReadTestData( - "external/wesaturate/500px/u76c0g_bliznaca_srgb8.png"); + const std::vector orig = + ReadTestData("external/wesaturate/500px/u76c0g_bliznaca_srgb8.png"); CodecInOut io; - ASSERT_TRUE(SetFromBytes(Span(orig), &io)); + ASSERT_TRUE(SetFromBytes(Bytes(orig), &io)); io.ShrinkTo(io.xsize() / 8, io.ysize() / 8); CompressParams cparams; cparams.butteraugli_distance = 1.0; cparams.progressive_mode = true; @@ -52,14 +52,14 @@ /*distmap=*/nullptr), IsSlightlyBelow(0.8222)); } TEST(PassesTest, RoundtripUnalignedPasses) { - const PaddedBytes orig = jxl::test::ReadTestData( - "external/wesaturate/500px/u76c0g_bliznaca_srgb8.png"); + const std::vector orig = + ReadTestData("external/wesaturate/500px/u76c0g_bliznaca_srgb8.png"); CodecInOut io; - ASSERT_TRUE(SetFromBytes(Span(orig), &io)); + ASSERT_TRUE(SetFromBytes(Bytes(orig), &io)); io.ShrinkTo(io.xsize() / 12, io.ysize() / 7); CompressParams cparams; cparams.butteraugli_distance = 2.0; cparams.progressive_mode = true; @@ -72,15 +72,15 @@ /*distmap=*/nullptr), IsSlightlyBelow(1.72)); } TEST(PassesTest, RoundtripMultiGroupPasses) { - const PaddedBytes orig = jxl::test::ReadTestData("jxl/flower/flower.png"); + const std::vector orig = ReadTestData("jxl/flower/flower.png"); CodecInOut io; { ThreadPoolForTests pool(4); - ASSERT_TRUE(SetFromBytes(Span(orig), &io, &pool)); + ASSERT_TRUE(SetFromBytes(Bytes(orig), &io, &pool)); } io.ShrinkTo(600, 1024); // partial X, full Y group auto test = [&](float target_distance, float threshold) { ThreadPoolForTests pool(4); @@ -101,13 +101,13 @@ auto run2 = std::async(std::launch::async, test, 2.0f, 0.0f); } TEST(PassesTest, RoundtripLargeFastPasses) { ThreadPoolForTests pool(8); - const PaddedBytes orig = jxl::test::ReadTestData("jxl/flower/flower.png"); + const std::vector orig = ReadTestData("jxl/flower/flower.png"); CodecInOut io; - ASSERT_TRUE(SetFromBytes(Span(orig), &io, &pool)); + ASSERT_TRUE(SetFromBytes(Bytes(orig), &io, &pool)); CompressParams cparams; cparams.speed_tier = SpeedTier::kSquirrel; cparams.progressive_mode = true; cparams.SetCms(*JxlGetDefaultCms()); @@ -120,13 +120,13 @@ // Checks for differing size/distance in two consecutive runs of distance 2, // which involves additional processing including adaptive reconstruction. // Failing this may be a sign of race conditions or invalid memory accesses. TEST(PassesTest, RoundtripProgressiveConsistent) { ThreadPoolForTests pool(8); - const PaddedBytes orig = jxl::test::ReadTestData("jxl/flower/flower.png"); + const std::vector orig = ReadTestData("jxl/flower/flower.png"); CodecInOut io; - ASSERT_TRUE(SetFromBytes(Span(orig), &io, &pool)); + ASSERT_TRUE(SetFromBytes(Bytes(orig), &io, &pool)); CompressParams cparams; cparams.speed_tier = SpeedTier::kSquirrel; cparams.progressive_mode = true; cparams.butteraugli_distance = 2.0; @@ -158,25 +158,23 @@ } } TEST(PassesTest, AllDownsampleFeasible) { ThreadPoolForTests pool(8); - const PaddedBytes orig = jxl::test::ReadTestData( - "external/wesaturate/500px/u76c0g_bliznaca_srgb8.png"); + const std::vector orig = + ReadTestData("external/wesaturate/500px/u76c0g_bliznaca_srgb8.png"); CodecInOut io; - ASSERT_TRUE(SetFromBytes(Span(orig), &io, &pool)); + ASSERT_TRUE(SetFromBytes(Bytes(orig), &io, &pool)); - PaddedBytes compressed; - AuxOut aux; + std::vector compressed; CompressParams cparams; cparams.speed_tier = SpeedTier::kSquirrel; cparams.progressive_mode = true; cparams.butteraugli_distance = 1.0; PassesEncoderState enc_state; - ASSERT_TRUE(EncodeFile(cparams, &io, &enc_state, &compressed, - *JxlGetDefaultCms(), &aux, &pool)); + ASSERT_TRUE(test::EncodeFile(cparams, &io, &enc_state, &compressed, &pool)); EXPECT_LE(compressed.size(), 240000u); float target_butteraugli[9] = {}; target_butteraugli[1] = 2.5f; target_butteraugli[2] = 16.0f; @@ -191,12 +189,11 @@ auto check = [&](const uint32_t task, size_t /* thread */) -> void { const size_t downsampling = downsamplings[task]; extras::JXLDecompressParams dparams; dparams.max_downsampling = downsampling; CodecInOut output; - ASSERT_TRUE( - test::DecodeFile(dparams, Span(compressed), &output)); + ASSERT_TRUE(test::DecodeFile(dparams, Bytes(compressed), &output)); EXPECT_EQ(output.xsize(), io.xsize()) << "downsampling = " << downsampling; EXPECT_EQ(output.ysize(), io.ysize()) << "downsampling = " << downsampling; EXPECT_LE(ButteraugliDistance(io.frames, output.frames, ButteraugliParams(), *JxlGetDefaultCms(), /*distmap=*/nullptr, nullptr), @@ -207,25 +204,23 @@ check, "TestDownsampling")); } TEST(PassesTest, AllDownsampleFeasibleQProgressive) { ThreadPoolForTests pool(8); - const PaddedBytes orig = jxl::test::ReadTestData( - "external/wesaturate/500px/u76c0g_bliznaca_srgb8.png"); + const std::vector orig = + ReadTestData("external/wesaturate/500px/u76c0g_bliznaca_srgb8.png"); CodecInOut io; - ASSERT_TRUE(SetFromBytes(Span(orig), &io, &pool)); + ASSERT_TRUE(SetFromBytes(Bytes(orig), &io, &pool)); - PaddedBytes compressed; - AuxOut aux; + std::vector compressed; CompressParams cparams; cparams.speed_tier = SpeedTier::kSquirrel; cparams.qprogressive_mode = true; cparams.butteraugli_distance = 1.0; PassesEncoderState enc_state; - ASSERT_TRUE(EncodeFile(cparams, &io, &enc_state, &compressed, - *JxlGetDefaultCms(), &aux, &pool)); + ASSERT_TRUE(test::EncodeFile(cparams, &io, &enc_state, &compressed, &pool)); EXPECT_LE(compressed.size(), 220000u); float target_butteraugli[9] = {}; target_butteraugli[1] = 3.0f; @@ -240,12 +235,11 @@ auto check = [&](const uint32_t task, size_t /* thread */) -> void { const size_t downsampling = downsamplings[task]; extras::JXLDecompressParams dparams; dparams.max_downsampling = downsampling; CodecInOut output; - ASSERT_TRUE( - test::DecodeFile(dparams, Span(compressed), &output)); + ASSERT_TRUE(test::DecodeFile(dparams, Bytes(compressed), &output)); EXPECT_EQ(output.xsize(), io.xsize()) << "downsampling = " << downsampling; EXPECT_EQ(output.ysize(), io.ysize()) << "downsampling = " << downsampling; EXPECT_LE(ButteraugliDistance(io.frames, output.frames, ButteraugliParams(), *JxlGetDefaultCms(), /*distmap=*/nullptr), @@ -256,48 +250,44 @@ check, "TestQProgressive")); } TEST(PassesTest, ProgressiveDownsample2DegradesCorrectlyGrayscale) { ThreadPoolForTests pool(8); - const PaddedBytes orig = jxl::test::ReadTestData( + const std::vector orig = ReadTestData( "external/wesaturate/500px/cvo9xd_keong_macan_grayscale.png"); CodecInOut io_orig; - ASSERT_TRUE(SetFromBytes(Span(orig), &io_orig, &pool)); + ASSERT_TRUE(SetFromBytes(Bytes(orig), &io_orig, &pool)); Rect rect(0, 0, io_orig.xsize(), 128); // need 2 DC groups for the DC frame to actually be progressive. Image3F large(4242, rect.ysize()); ZeroFillImage(&large); CopyImageTo(rect, *io_orig.Main().color(), rect, &large); CodecInOut io; io.metadata = io_orig.metadata; io.SetFromImage(std::move(large), io_orig.Main().c_current()); - PaddedBytes compressed; - AuxOut aux; + std::vector compressed; CompressParams cparams; cparams.speed_tier = SpeedTier::kSquirrel; cparams.progressive_dc = 1; cparams.responsive = true; cparams.qprogressive_mode = true; cparams.butteraugli_distance = 1.0; PassesEncoderState enc_state; - ASSERT_TRUE(EncodeFile(cparams, &io, &enc_state, &compressed, - *JxlGetDefaultCms(), &aux, &pool)); + ASSERT_TRUE(test::EncodeFile(cparams, &io, &enc_state, &compressed, &pool)); EXPECT_LE(compressed.size(), 10000u); extras::JXLDecompressParams dparams; dparams.max_downsampling = 1; CodecInOut output; - ASSERT_TRUE( - test::DecodeFile(dparams, Span(compressed), &output)); + ASSERT_TRUE(test::DecodeFile(dparams, Bytes(compressed), &output)); dparams.max_downsampling = 2; CodecInOut output_d2; - ASSERT_TRUE( - test::DecodeFile(dparams, Span(compressed), &output_d2)); + ASSERT_TRUE(test::DecodeFile(dparams, Bytes(compressed), &output_d2)); // 0 if reading all the passes, ~15 if skipping the 8x pass. float butteraugli_distance_down2_full = ButteraugliDistance( output.frames, output_d2.frames, ButteraugliParams(), *JxlGetDefaultCms(), /*distmap=*/nullptr); @@ -306,46 +296,42 @@ EXPECT_GE(butteraugli_distance_down2_full, 1.0f); } TEST(PassesTest, ProgressiveDownsample2DegradesCorrectly) { ThreadPoolForTests pool(8); - const PaddedBytes orig = jxl::test::ReadTestData("jxl/flower/flower.png"); + const std::vector orig = ReadTestData("jxl/flower/flower.png"); CodecInOut io_orig; - ASSERT_TRUE(SetFromBytes(Span(orig), &io_orig, &pool)); + ASSERT_TRUE(SetFromBytes(Bytes(orig), &io_orig, &pool)); Rect rect(0, 0, io_orig.xsize(), 128); // need 2 DC groups for the DC frame to actually be progressive. Image3F large(4242, rect.ysize()); ZeroFillImage(&large); CopyImageTo(rect, *io_orig.Main().color(), rect, &large); CodecInOut io; io.SetFromImage(std::move(large), io_orig.Main().c_current()); - PaddedBytes compressed; - AuxOut aux; + std::vector compressed; CompressParams cparams; cparams.speed_tier = SpeedTier::kSquirrel; cparams.progressive_dc = 1; cparams.responsive = true; cparams.qprogressive_mode = true; cparams.butteraugli_distance = 1.0; PassesEncoderState enc_state; - ASSERT_TRUE(EncodeFile(cparams, &io, &enc_state, &compressed, - *JxlGetDefaultCms(), &aux, &pool)); + ASSERT_TRUE(test::EncodeFile(cparams, &io, &enc_state, &compressed, &pool)); EXPECT_LE(compressed.size(), 220000u); extras::JXLDecompressParams dparams; dparams.max_downsampling = 1; CodecInOut output; - ASSERT_TRUE( - test::DecodeFile(dparams, Span(compressed), &output)); + ASSERT_TRUE(test::DecodeFile(dparams, Bytes(compressed), &output)); dparams.max_downsampling = 2; CodecInOut output_d2; - ASSERT_TRUE( - test::DecodeFile(dparams, Span(compressed), &output_d2)); + ASSERT_TRUE(test::DecodeFile(dparams, Bytes(compressed), &output_d2)); // 0 if reading all the passes, ~15 if skipping the 8x pass. float butteraugli_distance_down2_full = ButteraugliDistance( output.frames, output_d2.frames, ButteraugliParams(), *JxlGetDefaultCms(), /*distmap=*/nullptr); @@ -354,41 +340,38 @@ EXPECT_GE(butteraugli_distance_down2_full, 1.0f); } TEST(PassesTest, NonProgressiveDCImage) { ThreadPoolForTests pool(8); - const PaddedBytes orig = jxl::test::ReadTestData("jxl/flower/flower.png"); + const std::vector orig = ReadTestData("jxl/flower/flower.png"); CodecInOut io; - ASSERT_TRUE(SetFromBytes(Span(orig), &io, &pool)); + ASSERT_TRUE(SetFromBytes(Bytes(orig), &io, &pool)); - PaddedBytes compressed; - AuxOut aux; + std::vector compressed; CompressParams cparams; cparams.speed_tier = SpeedTier::kSquirrel; cparams.progressive_mode = false; cparams.butteraugli_distance = 2.0; PassesEncoderState enc_state; - ASSERT_TRUE(EncodeFile(cparams, &io, &enc_state, &compressed, - *JxlGetDefaultCms(), &aux, &pool)); + ASSERT_TRUE(test::EncodeFile(cparams, &io, &enc_state, &compressed, &pool)); // Even in non-progressive mode, it should be possible to return a DC-only // image. extras::JXLDecompressParams dparams; dparams.max_downsampling = 100; CodecInOut output; - ASSERT_TRUE(test::DecodeFile(dparams, Span(compressed), - &output, &pool)); + ASSERT_TRUE(test::DecodeFile(dparams, Bytes(compressed), &output, &pool)); EXPECT_EQ(output.xsize(), io.xsize()); EXPECT_EQ(output.ysize(), io.ysize()); } TEST(PassesTest, RoundtripSmallNoGaborishPasses) { - const PaddedBytes orig = jxl::test::ReadTestData( - "external/wesaturate/500px/u76c0g_bliznaca_srgb8.png"); + const std::vector orig = + ReadTestData("external/wesaturate/500px/u76c0g_bliznaca_srgb8.png"); CodecInOut io; - ASSERT_TRUE(SetFromBytes(Span(orig), &io)); + ASSERT_TRUE(SetFromBytes(Bytes(orig), &io)); io.ShrinkTo(io.xsize() / 8, io.ysize() / 8); CompressParams cparams; cparams.gaborish = Override::kOff; cparams.butteraugli_distance = 1.0; diff --git a/third_party/jpeg-xl/lib/jxl/patch_dictionary_test.cc b/third_party/jpeg-xl/lib/jxl/patch_dictionary_test.cc --- a/third_party/jpeg-xl/lib/jxl/patch_dictionary_test.cc +++ b/third_party/jpeg-xl/lib/jxl/patch_dictionary_test.cc @@ -1,10 +1,13 @@ // 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 +#include + #include "lib/extras/codec.h" #include "lib/jxl/cms/jxl_cms.h" #include "lib/jxl/enc_butteraugli_comparator.h" #include "lib/jxl/enc_params.h" #include "lib/jxl/image_test_utils.h" @@ -12,16 +15,17 @@ #include "lib/jxl/testing.h" namespace jxl { namespace { -using ::jxl::test::Roundtrip; +using test::ReadTestData; +using test::Roundtrip; TEST(PatchDictionaryTest, GrayscaleModular) { - const PaddedBytes orig = jxl::test::ReadTestData("jxl/grayscale_patches.png"); + const std::vector orig = ReadTestData("jxl/grayscale_patches.png"); CodecInOut io; - ASSERT_TRUE(SetFromBytes(Span(orig), &io)); + ASSERT_TRUE(SetFromBytes(Bytes(orig), &io)); CompressParams cparams; cparams.SetLossless(); cparams.patches = jxl::Override::kOn; @@ -33,13 +37,13 @@ JXL_ASSERT_OK(VerifyRelativeError(*io.Main().color(), *io2.Main().color(), 1e-7f, 0, _)); } TEST(PatchDictionaryTest, GrayscaleVarDCT) { - const PaddedBytes orig = jxl::test::ReadTestData("jxl/grayscale_patches.png"); + const std::vector orig = ReadTestData("jxl/grayscale_patches.png"); CodecInOut io; - ASSERT_TRUE(SetFromBytes(Span(orig), &io)); + ASSERT_TRUE(SetFromBytes(Bytes(orig), &io)); CompressParams cparams; cparams.patches = jxl::Override::kOn; CodecInOut io2; 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 @@ -1,40 +1,41 @@ // 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 - +#include +#include #include +#include #include "lib/extras/codec.h" #include "lib/jxl/base/compiler_specific.h" #include "lib/jxl/base/data_parallel.h" #include "lib/jxl/base/override.h" -#include "lib/jxl/base/padded_bytes.h" +#include "lib/jxl/base/span.h" #include "lib/jxl/cms/jxl_cms.h" #include "lib/jxl/codec_in_out.h" #include "lib/jxl/color_encoding_internal.h" #include "lib/jxl/enc_butteraugli_comparator.h" #include "lib/jxl/enc_cache.h" -#include "lib/jxl/enc_file.h" #include "lib/jxl/enc_params.h" #include "lib/jxl/headers.h" #include "lib/jxl/image_bundle.h" #include "lib/jxl/test_utils.h" #include "lib/jxl/testing.h" namespace jxl { namespace { +using test::ReadTestData; using test::Roundtrip; TEST(PreviewTest, RoundtripGivenPreview) { - const PaddedBytes orig = jxl::test::ReadTestData( - "external/wesaturate/500px/u76c0g_bliznaca_srgb8.png"); + const std::vector orig = + ReadTestData("external/wesaturate/500px/u76c0g_bliznaca_srgb8.png"); CodecInOut io; - ASSERT_TRUE(SetFromBytes(Span(orig), &io)); + ASSERT_TRUE(SetFromBytes(Bytes(orig), &io)); io.ShrinkTo(io.xsize() / 8, io.ysize() / 8); // Same as main image io.preview_frame = io.Main().Copy(); const size_t preview_xsize = 15; const size_t preview_ysize = 27; 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 @@ -13,11 +13,10 @@ #include #include #include #include "lib/jxl/ac_strategy.h" -#include "lib/jxl/base/cache_aligned.h" #include "lib/jxl/base/compiler_specific.h" #include "lib/jxl/base/span.h" #include "lib/jxl/base/status.h" #include "lib/jxl/dec_bit_reader.h" #include "lib/jxl/image.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 @@ -358,11 +358,10 @@ } 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()); 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 @@ -3,26 +3,24 @@ // 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/render_pipeline.h" -#include -#include - #include +#include +#include #include #include #include "lib/extras/codec.h" #include "lib/jxl/base/common.h" -#include "lib/jxl/base/padded_bytes.h" +#include "lib/jxl/base/span.h" #include "lib/jxl/cms/jxl_cms.h" #include "lib/jxl/color_encoding_internal.h" #include "lib/jxl/common.h" // JXL_HIGH_PRECISION, JPEGXL_ENABLE_TRANSCODE_JPEG #include "lib/jxl/dec_frame.h" #include "lib/jxl/enc_cache.h" -#include "lib/jxl/enc_file.h" #include "lib/jxl/enc_params.h" #include "lib/jxl/fake_parallel_runner_testonly.h" #include "lib/jxl/frame_dimensions.h" #include "lib/jxl/icc_codec.h" #include "lib/jxl/image_test_utils.h" @@ -45,14 +43,12 @@ JXL_RETURN_IF_ERROR(ReadImageMetadata(&reader, &io->metadata.m)); io->metadata.transform_data.nonserialized_xyb_encoded = io->metadata.m.xyb_encoded; JXL_RETURN_IF_ERROR(Bundle::Read(&reader, &io->metadata.transform_data)); if (io->metadata.m.color_encoding.WantICC()) { - PaddedBytes icc_data; - JXL_RETURN_IF_ERROR(ReadICC(&reader, &icc_data)); - IccBytes icc; - Span(icc_data).AppendTo(&icc); + std::vector icc; + JXL_RETURN_IF_ERROR(test::ReadICC(&reader, &icc)); JXL_RETURN_IF_ERROR(io->metadata.m.color_encoding.SetICC( std::move(icc), JxlGetDefaultCms())); } PassesDecoderState dec_state; JXL_RETURN_IF_ERROR( @@ -183,17 +179,17 @@ // Use a parallel runner that randomly shuffles tasks to detect possible // border handling bugs. FakeParallelRunner fake_pool(/*order_seed=*/123, /*num_threads=*/8); ThreadPool pool(&JxlFakeParallelRunner, &fake_pool); - const PaddedBytes orig = jxl::test::ReadTestData(config.input_path); + const std::vector orig = jxl::test::ReadTestData(config.input_path); CodecInOut io; if (config.jpeg_transcode) { - ASSERT_TRUE(jpeg::DecodeImageJPG(Span(orig), &io)); + ASSERT_TRUE(jpeg::DecodeImageJPG(Bytes(orig), &io)); } else { - ASSERT_TRUE(SetFromBytes(Span(orig), &io, &pool)); + ASSERT_TRUE(SetFromBytes(Bytes(orig), &io, &pool)); } io.ShrinkTo(config.xsize, config.ysize); if (config.add_spot_color) { jxl::ImageF spot(config.xsize, config.ysize); @@ -218,22 +214,22 @@ std::vector ec; ec.push_back(std::move(spot)); io.frames[0].SetExtraChannels(std::move(ec)); } - PaddedBytes compressed; + std::vector compressed; PassesEncoderState enc_state; enc_state.shared.image_features.splines = config.splines; - ASSERT_TRUE(EncodeFile(config.cparams, &io, &enc_state, &compressed, - *JxlGetDefaultCms(), /*aux_out=*/nullptr, &pool)); + ASSERT_TRUE( + test::EncodeFile(config.cparams, &io, &enc_state, &compressed, &pool)); CodecInOut io_default; - ASSERT_TRUE(DecodeFile(Span(compressed), + ASSERT_TRUE(DecodeFile(Bytes(compressed), /*use_slow_pipeline=*/false, &io_default, &pool)); CodecInOut io_slow_pipeline; - ASSERT_TRUE(DecodeFile(Span(compressed), + ASSERT_TRUE(DecodeFile(Bytes(compressed), /*use_slow_pipeline=*/true, &io_slow_pipeline, &pool)); ASSERT_EQ(io_default.frames.size(), io_slow_pipeline.frames.size()); for (size_t i = 0; i < io_default.frames.size(); i++) { #if JXL_HIGH_PRECISION @@ -532,18 +528,18 @@ TEST(RenderPipelineDecodingTest, Animation) { FakeParallelRunner fake_pool(/*order_seed=*/123, /*num_threads=*/8); ThreadPool pool(&JxlFakeParallelRunner, &fake_pool); - PaddedBytes compressed = + std::vector compressed = jxl::test::ReadTestData("jxl/blending/cropped_traffic_light.jxl"); CodecInOut io_default; - ASSERT_TRUE(DecodeFile(Span(compressed), + ASSERT_TRUE(DecodeFile(Bytes(compressed), /*use_slow_pipeline=*/false, &io_default, &pool)); CodecInOut io_slow_pipeline; - ASSERT_TRUE(DecodeFile(Span(compressed), + ASSERT_TRUE(DecodeFile(Bytes(compressed), /*use_slow_pipeline=*/true, &io_slow_pipeline, &pool)); ASSERT_EQ(io_default.frames.size(), io_slow_pipeline.frames.size()); for (size_t i = 0; i < io_default.frames.size(); i++) { #if JXL_HIGH_PRECISION diff --git a/third_party/jpeg-xl/lib/jxl/render_pipeline/stage_cms.h b/third_party/jpeg-xl/lib/jxl/render_pipeline/stage_cms.h new file mode 100644 --- /dev/null +++ b/third_party/jpeg-xl/lib/jxl/render_pipeline/stage_cms.h @@ -0,0 +1,21 @@ +// Copyright (c) the JPEG XL Project Authors. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#ifndef LIB_JXL_RENDER_PIPELINE_STAGE_CMS_H_ +#define LIB_JXL_RENDER_PIPELINE_STAGE_CMS_H_ + +#include + +#include "lib/jxl/dec_xyb.h" +#include "lib/jxl/render_pipeline/render_pipeline_stage.h" + +namespace jxl { + +std::unique_ptr GetCmsStage( + const OutputEncodingInfo& output_encoding_info); + +} // namespace jxl + +#endif // LIB_JXL_RENDER_PIPELINE_STAGE_CMS_H_ diff --git a/third_party/jpeg-xl/lib/jxl/render_pipeline/stage_cms.cc b/third_party/jpeg-xl/lib/jxl/render_pipeline/stage_cms.cc new file mode 100644 --- /dev/null +++ b/third_party/jpeg-xl/lib/jxl/render_pipeline/stage_cms.cc @@ -0,0 +1,134 @@ +// Copyright (c) the JPEG XL Project Authors. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#include "lib/jxl/render_pipeline/stage_cms.h" + +#include + +#include "jxl/cms_interface.h" +#include "jxl/color_encoding.h" +#include "lib/jxl/color_encoding_internal.h" +#include "lib/jxl/common.h" +#include "lib/jxl/dec_xyb.h" + +#undef HWY_TARGET_INCLUDE +#define HWY_TARGET_INCLUDE "lib/jxl/render_pipeline/stage_cms.cc" +#include +#include + +#include "lib/jxl/dec_xyb-inl.h" +#include "lib/jxl/sanitizers.h" + +HWY_BEFORE_NAMESPACE(); +namespace jxl { +namespace HWY_NAMESPACE { + +class CmsStage : public RenderPipelineStage { + public: + explicit CmsStage(OutputEncodingInfo output_encoding_info) + : RenderPipelineStage(RenderPipelineStage::Settings()), + output_encoding_info_(std::move(output_encoding_info)) { + c_src_ = output_encoding_info_.linear_color_encoding; + } + + bool IsNeeded() const { + const size_t channels_src = (c_src_.IsCMYK() ? 4 : c_src_.Channels()); + const size_t channels_dst = output_encoding_info_.color_encoding.Channels(); + const bool not_mixing_color_and_grey = + (channels_src == channels_dst || + (channels_src == 4 && channels_dst == 3)); + return (output_encoding_info_.cms_set) && + !c_src_.SameColorEncoding(output_encoding_info_.color_encoding) && + not_mixing_color_and_grey; + } + + void ProcessRow(const RowInfo& input_rows, const RowInfo& output_rows, + size_t xextra, size_t xsize, size_t xpos, size_t ypos, + size_t thread_id) const final { + JXL_ASSERT(xsize == xsize_); + // TODO(firsching): handle grey case seperately + // interleave + float* JXL_RESTRICT row0 = GetInputRow(input_rows, 0, 0); + float* JXL_RESTRICT row1 = GetInputRow(input_rows, 1, 0); + float* JXL_RESTRICT row2 = GetInputRow(input_rows, 2, 0); + float* mutable_buf_src = color_space_transform->BufSrc(thread_id); + + for (size_t x = 0; x < xsize; x++) { + mutable_buf_src[3 * x + 0] = row0[x]; + mutable_buf_src[3 * x + 1] = row1[x]; + mutable_buf_src[3 * x + 2] = row2[x]; + } + const float* buf_src = mutable_buf_src; + float* JXL_RESTRICT buf_dst = color_space_transform->BufDst(thread_id); + if (!color_space_transform->Run(thread_id, buf_src, buf_dst)) { + // TODO(firsching): somehow mark failing here? + return; + } + // de-interleave + for (size_t x = 0; x < xsize; x++) { + row0[x] = buf_dst[3 * x + 0]; + row1[x] = buf_dst[3 * x + 1]; + row2[x] = buf_dst[3 * x + 2]; + } + } + RenderPipelineChannelMode GetChannelMode(size_t c) const final { + return c < 3 ? RenderPipelineChannelMode::kInPlace + : RenderPipelineChannelMode::kIgnored; + } + + const char* GetName() const override { return "Cms"; } + + private: + OutputEncodingInfo output_encoding_info_; + size_t xsize_; + std::unique_ptr color_space_transform; + ColorEncoding c_src_; + + 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 + xsize_ = input_sizes[0].first; + } + + Status PrepareForThreads(size_t num_threads) override { + color_space_transform = jxl::make_unique( + output_encoding_info_.color_management_system); + JXL_RETURN_IF_ERROR(color_space_transform->Init( + c_src_, output_encoding_info_.color_encoding, + output_encoding_info_.desired_intensity_target, xsize_, num_threads)); + return true; + } +}; + +std::unique_ptr GetCmsStage( + const OutputEncodingInfo& output_encoding_info) { + auto stage = jxl::make_unique(output_encoding_info); + if (!stage->IsNeeded()) return nullptr; + return stage; +} + +// NOLINTNEXTLINE(google-readability-namespace-comments) +} // namespace HWY_NAMESPACE +} // namespace jxl +HWY_AFTER_NAMESPACE(); + +#if HWY_ONCE +namespace jxl { + +HWY_EXPORT(GetCmsStage); + +std::unique_ptr GetCmsStage( + const OutputEncodingInfo& output_encoding_info) { + return HWY_DYNAMIC_DISPATCH(GetCmsStage)(output_encoding_info); +} + +} // namespace jxl +#endif diff --git a/third_party/jpeg-xl/lib/jxl/render_pipeline/stage_tone_mapping.cc b/third_party/jpeg-xl/lib/jxl/render_pipeline/stage_tone_mapping.cc --- a/third_party/jpeg-xl/lib/jxl/render_pipeline/stage_tone_mapping.cc +++ b/third_party/jpeg-xl/lib/jxl/render_pipeline/stage_tone_mapping.cc @@ -26,28 +26,29 @@ if (output_encoding_info_.desired_intensity_target == output_encoding_info_.orig_intensity_target) { // No tone mapping requested. return; } - const auto& tf = output_encoding_info_.orig_color_encoding.Tf(); - if (tf.IsPQ() && output_encoding_info_.desired_intensity_target < - output_encoding_info_.orig_intensity_target) { + const auto& orig_tf = output_encoding_info_.orig_color_encoding.Tf(); + const auto& dest_tf = output_encoding_info_.color_encoding.Tf(); + if (orig_tf.IsPQ() && output_encoding_info_.desired_intensity_target < + output_encoding_info_.orig_intensity_target) { tone_mapper_ = jxl::make_unique( /*source_range=*/std::pair( 0, output_encoding_info_.orig_intensity_target), /*target_range=*/ std::pair( 0, output_encoding_info_.desired_intensity_target), output_encoding_info_.luminances); - } else if (tf.IsHLG() && !tf.IsHLG()) { + } else if (orig_tf.IsHLG() && !dest_tf.IsHLG()) { hlg_ootf_ = jxl::make_unique( /*source_luminance=*/output_encoding_info_.orig_intensity_target, /*target_luminance=*/output_encoding_info_.desired_intensity_target, output_encoding_info_.luminances); } - if (tf.IsPQ() && (tone_mapper_ || hlg_ootf_)) { + if (dest_tf.IsPQ() && (tone_mapper_ || hlg_ootf_)) { to_intensity_target_ = 10000.f / output_encoding_info_.orig_intensity_target; from_desired_intensity_target_ = output_encoding_info_.desired_intensity_target / 10000.f; } 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 @@ -17,10 +17,11 @@ #include #include "lib/extras/codec.h" #include "lib/jxl/base/span.h" #include "lib/jxl/cms/jxl_cms.h" +#include "lib/jxl/dec_bit_reader.h" #include "lib/jxl/dec_external_image.h" #include "lib/jxl/enc_butteraugli_comparator.h" #include "lib/jxl/enc_comparator.h" #include "lib/jxl/enc_external_image.h" #include "lib/jxl/encode_internal.h" @@ -35,11 +36,11 @@ // icc_profile can be empty to automatically deduce profile from the pixel // format, or filled in to force this ICC profile jxl::CodecInOut ConvertTestImage(const std::vector& buf, const size_t xsize, const size_t ysize, const JxlPixelFormat& pixel_format, - const jxl::PaddedBytes& icc_profile) { + const jxl::Bytes& icc_profile) { jxl::CodecInOut io; io.SetSize(xsize, ysize); bool is_gray = pixel_format.num_channels < 3; bool has_alpha = @@ -86,23 +87,22 @@ << pixel_format.data_type << " not yet implemented."; } jxl::ColorEncoding color_encoding; if (!icc_profile.empty()) { jxl::IccBytes icc_profile_copy; - jxl::Span(icc_profile).AppendTo(&icc_profile_copy); + icc_profile.AppendTo(&icc_profile_copy); EXPECT_TRUE( color_encoding.SetICC(std::move(icc_profile_copy), JxlGetDefaultCms())); } 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, - /*bits_per_sample=*/bitdepth, pixel_format, - /*pool=*/nullptr, &io.Main())); + EXPECT_TRUE(ConvertFromExternal(jxl::Bytes(buf.data(), buf.size()), xsize, + ysize, color_encoding, + /*bits_per_sample=*/bitdepth, pixel_format, + /*pool=*/nullptr, &io.Main())); return io; } template T ConvertTestPixel(float val); @@ -229,12 +229,11 @@ } } if (alpha_in_extra_channels_vector && !has_interleaved_alpha) { jxl::ImageF alpha_channel(xsize, ysize); EXPECT_TRUE(jxl::ConvertFromExternal( - jxl::Span(extra_channel_bytes.data(), - extra_channel_bytes.size()), + jxl::Bytes(extra_channel_bytes.data(), extra_channel_bytes.size()), xsize, ysize, basic_info.bits_per_sample, extra_channel_pixel_format, 0, /*pool=*/nullptr, &alpha_channel)); original_io.metadata.m.SetAlphaBits(basic_info.bits_per_sample); original_io.Main().SetAlpha(std::move(alpha_channel)); @@ -354,11 +353,11 @@ size_t icc_profile_size; EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderGetICCProfileSize(dec, JXL_COLOR_PROFILE_TARGET_DATA, &icc_profile_size)); - jxl::PaddedBytes icc_profile(icc_profile_size); + std::vector icc_profile(icc_profile_size); EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderGetColorAsICCProfile( dec, JXL_COLOR_PROFILE_TARGET_DATA, icc_profile.data(), icc_profile.size())); std::vector decoded_bytes(buffer_size); @@ -411,11 +410,11 @@ JxlDecoderDestroy(dec); jxl::CodecInOut decoded_io = ConvertTestImage( decoded_bytes, xsize, ysize, output_pixel_format_with_extra_channel_alpha, - icc_profile); + jxl::Bytes(icc_profile)); if (already_downsampled) { jxl::Image3F* color = decoded_io.Main().color(); jxl::DownsampleImage(color, resampling); if (decoded_io.Main().HasAlpha()) { @@ -648,11 +647,11 @@ size_t icc_profile_size; EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderGetICCProfileSize(dec, JXL_COLOR_PROFILE_TARGET_DATA, &icc_profile_size)); - jxl::PaddedBytes icc_profile(icc_profile_size); + std::vector icc_profile(icc_profile_size); EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderGetColorAsICCProfile( dec, JXL_COLOR_PROFILE_TARGET_DATA, icc_profile.data(), icc_profile.size())); std::vector decoded_bytes(buffer_size); @@ -665,12 +664,12 @@ EXPECT_EQ(JXL_DEC_FULL_IMAGE, JxlDecoderProcessInput(dec)); JxlDecoderDestroy(dec); - jxl::CodecInOut decoded_io = - ConvertTestImage(decoded_bytes, xsize, ysize, pixel_format, icc_profile); + jxl::CodecInOut decoded_io = ConvertTestImage( + decoded_bytes, xsize, ysize, pixel_format, jxl::Bytes(icc_profile)); jxl::ButteraugliParams ba; float butteraugli_score = ButteraugliDistance( original_io.frames, decoded_io.frames, ba, *JxlGetDefaultCms(), /*distmap=*/nullptr, nullptr); @@ -783,11 +782,11 @@ size_t icc_profile_size; EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderGetICCProfileSize(dec, JXL_COLOR_PROFILE_TARGET_DATA, &icc_profile_size)); - jxl::PaddedBytes icc_profile(icc_profile_size); + std::vector icc_profile(icc_profile_size); EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderGetColorAsICCProfile( dec, JXL_COLOR_PROFILE_TARGET_DATA, icc_profile.data(), icc_profile.size())); std::vector decoded_bytes(buffer_size * nb_frames); @@ -801,12 +800,13 @@ buffer_size)); EXPECT_EQ(JXL_DEC_FULL_IMAGE, JxlDecoderProcessInput(dec)); } JxlDecoderDestroy(dec); - jxl::CodecInOut decoded_io = ConvertTestImage( - decoded_bytes, xsize, ysize * nb_frames, pixel_format, icc_profile); + jxl::CodecInOut decoded_io = + ConvertTestImage(decoded_bytes, xsize, ysize * nb_frames, pixel_format, + jxl::Bytes(icc_profile)); jxl::ButteraugliParams ba; float butteraugli_score = ButteraugliDistance( original_io.frames, decoded_io.frames, ba, *JxlGetDefaultCms(), /*distmap=*/nullptr, nullptr); @@ -851,14 +851,14 @@ }; TEST(RoundtripTest, TestICCProfile) { // JxlEncoderSetICCProfile parses the ICC profile, so a valid profile is // needed. The profile should be passed correctly through the roundtrip. - jxl::BitReader reader(jxl::Span(kEncodedTestProfile, - sizeof(kEncodedTestProfile))); - jxl::PaddedBytes icc; - ASSERT_TRUE(ReadICC(&reader, &icc)); + jxl::BitReader reader( + jxl::Bytes(kEncodedTestProfile, sizeof(kEncodedTestProfile))); + std::vector icc; + ASSERT_TRUE(jxl::test::ReadICC(&reader, &icc)); ASSERT_TRUE(reader.Close()); JxlPixelFormat format = JxlPixelFormat{3, JXL_TYPE_UINT8, JXL_NATIVE_ENDIAN, 0}; @@ -919,11 +919,11 @@ size_t dec_icc_size; EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderGetICCProfileSize(dec, JXL_COLOR_PROFILE_TARGET_ORIGINAL, &dec_icc_size)); EXPECT_EQ(icc.size(), dec_icc_size); - jxl::PaddedBytes dec_icc(dec_icc_size); + std::vector dec_icc(dec_icc_size); EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderGetColorAsICCProfile( dec, JXL_COLOR_PROFILE_TARGET_ORIGINAL, dec_icc.data(), dec_icc.size())); std::vector decoded_bytes(buffer_size); @@ -942,14 +942,13 @@ } TEST(RoundtripTest, JXL_TRANSCODE_JPEG_TEST(TestJPEGReconstruction)) { TEST_LIBJPEG_SUPPORT(); const std::string jpeg_path = "jxl/flower/flower.png.im_q85_420.jpg"; - const jxl::PaddedBytes orig = jxl::test::ReadTestData(jpeg_path); + const std::vector orig = jxl::test::ReadTestData(jpeg_path); jxl::CodecInOut orig_io; - ASSERT_TRUE( - SetFromBytes(jxl::Span(orig), &orig_io, /*pool=*/nullptr)); + ASSERT_TRUE(SetFromBytes(jxl::Bytes(orig), &orig_io, /*pool=*/nullptr)); JxlEncoderPtr enc = JxlEncoderMake(nullptr); JxlEncoderFrameSettings* frame_settings = JxlEncoderFrameSettingsCreate(enc.get(), NULL); diff --git a/third_party/jpeg-xl/lib/jxl/speed_tier_test.cc b/third_party/jpeg-xl/lib/jxl/speed_tier_test.cc --- a/third_party/jpeg-xl/lib/jxl/speed_tier_test.cc +++ b/third_party/jpeg-xl/lib/jxl/speed_tier_test.cc @@ -1,20 +1,21 @@ // Copyright (c) the JPEG XL Project Authors. All rights reserved. // // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +#include #include +#include #include "lib/extras/codec.h" #include "lib/jxl/base/data_parallel.h" -#include "lib/jxl/base/padded_bytes.h" +#include "lib/jxl/base/span.h" #include "lib/jxl/cms/jxl_cms.h" #include "lib/jxl/codec_in_out.h" #include "lib/jxl/enc_butteraugli_comparator.h" #include "lib/jxl/enc_cache.h" -#include "lib/jxl/enc_file.h" #include "lib/jxl/enc_params.h" #include "lib/jxl/image.h" #include "lib/jxl/image_test_utils.h" #include "lib/jxl/test_utils.h" #include "lib/jxl/testing.h" @@ -78,15 +79,15 @@ /*shrink8=*/true}, SpeedTierTestParams{SpeedTier::kGlacier, /*shrink8=*/true})); TEST_P(SpeedTierTest, Roundtrip) { - const PaddedBytes orig = jxl::test::ReadTestData( + const std::vector orig = jxl::test::ReadTestData( "external/wesaturate/500px/u76c0g_bliznaca_srgb8.png"); CodecInOut io; test::ThreadPoolForTests pool(8); - ASSERT_TRUE(SetFromBytes(Span(orig), &io, &pool)); + ASSERT_TRUE(SetFromBytes(Bytes(orig), &io, &pool)); const SpeedTierTestParams& params = GetParam(); if (params.speed_tier == SpeedTier::kGlacier) { // just a few pixels will already take enough time at this setting 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 @@ -4,26 +4,26 @@ // license that can be found in the LICENSE file. #ifndef LIB_JXL_SPLINES_H_ #define LIB_JXL_SPLINES_H_ -#include -#include - +#include +#include +#include #include #include -#include "lib/jxl/ans_params.h" +#include "lib/jxl/base/compiler_specific.h" #include "lib/jxl/base/status.h" #include "lib/jxl/chroma_from_luma.h" -#include "lib/jxl/dec_ans.h" -#include "lib/jxl/dec_bit_reader.h" -#include "lib/jxl/entropy_coder.h" #include "lib/jxl/image.h" namespace jxl { +class ANSSymbolReader; +class BitReader; + static constexpr float kDesiredRenderingDistance = 1.f; enum SplineEntropyContexts : size_t { kQuantizationAdjustmentContext = 0, kStartingPositionContext, 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 @@ -13,10 +13,12 @@ #include "lib/jxl/base/printf_macros.h" #include "lib/jxl/base/status.h" #include "lib/jxl/chroma_from_luma.h" #include "lib/jxl/common.h" // JXL_HIGH_PRECISION #include "lib/jxl/dct_scales.h" +#include "lib/jxl/dec_ans.h" +#include "lib/jxl/dec_bit_reader.h" #include "lib/jxl/pack_signed.h" #undef HWY_TARGET_INCLUDE #define HWY_TARGET_INCLUDE "lib/jxl/splines.cc" #include diff --git a/third_party/jpeg-xl/lib/jxl/splines_test.cc b/third_party/jpeg-xl/lib/jxl/splines_test.cc --- a/third_party/jpeg-xl/lib/jxl/splines_test.cc +++ b/third_party/jpeg-xl/lib/jxl/splines_test.cc @@ -3,12 +3,16 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. #include "lib/jxl/splines.h" +#include +#include + #include "lib/extras/codec.h" #include "lib/jxl/base/printf_macros.h" +#include "lib/jxl/base/span.h" #include "lib/jxl/cms/jxl_cms.h" #include "lib/jxl/enc_aux_out.h" #include "lib/jxl/enc_butteraugli_comparator.h" #include "lib/jxl/enc_splines.h" #include "lib/jxl/image_test_utils.h" @@ -26,10 +30,11 @@ << " control points)"; } namespace { +using test::ReadTestData; using ::testing::AllOf; using ::testing::Field; using ::testing::FloatNear; using ::testing::Pointwise; @@ -275,12 +280,12 @@ splines.InitializeDrawCache(image.xsize(), image.ysize(), *cmap)); } TEST(SplinesTest, Drawing) { CodecInOut io_expected; - const PaddedBytes orig = jxl::test::ReadTestData("jxl/splines.pfm"); - ASSERT_TRUE(SetFromBytes(Span(orig), &io_expected, + const std::vector orig = ReadTestData("jxl/splines.pfm"); + ASSERT_TRUE(SetFromBytes(Bytes(orig), &io_expected, /*pool=*/nullptr)); std::vector control_points{{9, 54}, {118, 159}, {97, 3}, {10, 40}, {150, 25}, {120, 300}}; // Use values that survive quant/decorellation roundtrip. @@ -321,19 +326,18 @@ *io_expected.Main().color(), *io_actual.Main().color(), 1e-2f, 1e-1f, _)); } TEST(SplinesTest, ClearedEveryFrame) { CodecInOut io_expected; - const PaddedBytes bytes_expected = - jxl::test::ReadTestData("jxl/spline_on_first_frame.png"); - ASSERT_TRUE(SetFromBytes(Span(bytes_expected), &io_expected, + const std::vector bytes_expected = + ReadTestData("jxl/spline_on_first_frame.png"); + ASSERT_TRUE(SetFromBytes(Bytes(bytes_expected), &io_expected, /*pool=*/nullptr)); CodecInOut io_actual; - const PaddedBytes bytes_actual = - jxl::test::ReadTestData("jxl/spline_on_first_frame.jxl"); - ASSERT_TRUE( - test::DecodeFile({}, Span(bytes_actual), &io_actual)); + const std::vector bytes_actual = + ReadTestData("jxl/spline_on_first_frame.jxl"); + ASSERT_TRUE(test::DecodeFile({}, Bytes(bytes_actual), &io_actual)); ASSERT_TRUE(io_actual.frames[0].TransformTo(ColorEncoding::SRGB(), *JxlGetDefaultCms())); for (size_t c = 0; c < 3; ++c) { for (size_t y = 0; y < io_actual.ysize(); ++y) { diff --git a/third_party/jpeg-xl/lib/jxl/test_image.h b/third_party/jpeg-xl/lib/jxl/test_image.h --- a/third_party/jpeg-xl/lib/jxl/test_image.h +++ b/third_party/jpeg-xl/lib/jxl/test_image.h @@ -7,17 +7,17 @@ #define LIB_JXL_TEST_IMAGE_H_ #include #include #include -#include +#include #include #include #include "lib/extras/packed_image.h" -#include "lib/jxl/base/padded_bytes.h" +#include "lib/jxl/base/span.h" namespace jxl { namespace test { // Returns a test image with some autogenerated pixel content, using 16 bits per @@ -30,11 +30,11 @@ public: TestImage(); extras::PackedPixelFile& ppf() { return ppf_; } - TestImage& DecodeFromBytes(const PaddedBytes& bytes); + TestImage& DecodeFromBytes(const std::vector& bytes); TestImage& ClearMetadata(); TestImage& SetDimensions(size_t xsize, size_t ysize); diff --git a/third_party/jpeg-xl/lib/jxl/test_image.cc b/third_party/jpeg-xl/lib/jxl/test_image.cc --- a/third_party/jpeg-xl/lib/jxl/test_image.cc +++ b/third_party/jpeg-xl/lib/jxl/test_image.cc @@ -217,17 +217,16 @@ SetChannels(3); SetAllBitDepths(8); SetColorEncoding("RGB_D65_SRG_Rel_SRG"); } -TestImage& TestImage::DecodeFromBytes(const PaddedBytes& bytes) { +TestImage& TestImage::DecodeFromBytes(const std::vector& bytes) { ColorEncoding c_enc; JXL_CHECK(c_enc.FromExternal(ppf_.color_encoding)); extras::ColorHints color_hints; color_hints.Add("color_space", Description(c_enc)); - JXL_CHECK( - extras::DecodeBytes(Span(bytes), color_hints, &ppf_)); + JXL_CHECK(extras::DecodeBytes(Bytes(bytes), color_hints, &ppf_)); return *this; } TestImage& TestImage::ClearMetadata() { ppf_.metadata = extras::PackedMetadata(); @@ -309,12 +308,12 @@ TestImage& TestImage::SetColorEncoding(const std::string& description) { JXL_CHECK(ParseDescription(description, &ppf_.color_encoding)); ColorEncoding c_enc; JXL_CHECK(c_enc.FromExternal(ppf_.color_encoding)); - JXL_CHECK(c_enc.CreateICC()); IccBytes icc = c_enc.ICC(); + JXL_CHECK(!icc.empty()); ppf_.icc.assign(icc.begin(), icc.end()); return *this; } TestImage& TestImage::CoalesceGIFAnimationWithAlpha() { 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 @@ -10,22 +10,21 @@ // Macros and functions useful for tests. #include #include -#include -#include +#include +#include #include #include #include "lib/extras/dec/decode.h" #include "lib/extras/dec/jxl.h" #include "lib/extras/enc/jxl.h" #include "lib/extras/packed_image.h" #include "lib/jxl/base/data_parallel.h" -#include "lib/jxl/base/padded_bytes.h" #include "lib/jxl/base/span.h" #include "lib/jxl/base/status.h" #include "lib/jxl/codec_in_out.h" #include "lib/jxl/color_encoding_internal.h" #include "lib/jxl/enc_params.h" @@ -39,15 +38,19 @@ } while (0) namespace jxl { struct AuxOut; +class CodecInOut; +class PaddedBytes; +struct PassesEncoderState; +class ThreadPool; namespace test { std::string GetTestDataPath(const std::string& filename); -PaddedBytes ReadTestData(const std::string& filename); +std::vector ReadTestData(const std::string& filename); void JxlBasicInfoSetFromPixelFormat(JxlBasicInfo* basic_info, const JxlPixelFormat* pixel_format); void DefaultAcceptedFormats(extras::JXLDecompressParams& dparams); @@ -65,12 +68,11 @@ ThreadPool* pool = nullptr); bool Roundtrip(const CodecInOut* io, const CompressParams& cparams, extras::JXLDecompressParams dparams, CodecInOut* JXL_RESTRICT io2, std::stringstream& failures, - size_t* compressed_size = nullptr, ThreadPool* pool = nullptr, - AuxOut* aux_out = nullptr); + size_t* compressed_size = nullptr, ThreadPool* pool = nullptr); // Returns compressed size [bytes]. size_t Roundtrip(const extras::PackedPixelFile& ppf_in, extras::JXLCompressParams cparams, extras::JXLDecompressParams dparams, ThreadPool* pool, @@ -169,15 +171,28 @@ private: JxlThreadParallelRunnerPtr runner_; std::unique_ptr pool_; }; +// `icc` may be empty afterwards - if so, call CreateProfile. Does not append, +// clears any original data that was in icc. +// If `output_limit` is not 0, then returns error if resulting profile would be +// longer than `output_limit` +Status ReadICC(BitReader* JXL_RESTRICT reader, + std::vector* JXL_RESTRICT icc, size_t output_limit = 0); + +// Compresses pixels from `io` (given in any ColorEncoding). +// `io->metadata.m.original` must be set. +Status EncodeFile(const CompressParams& params, const CodecInOut* io, + PassesEncoderState* passes_enc_state, + std::vector* compressed, ThreadPool* pool = nullptr); + } // namespace test -bool operator==(const jxl::PaddedBytes& a, const jxl::PaddedBytes& b); +bool operator==(const jxl::Bytes& a, const jxl::Bytes& b); -// Allow using EXPECT_EQ on jxl::PaddedBytes -bool operator!=(const jxl::PaddedBytes& a, const jxl::PaddedBytes& b); +// Allow using EXPECT_EQ on jxl::Bytes +bool operator!=(const jxl::Bytes& a, const jxl::Bytes& b); } // namespace jxl #endif // LIB_JXL_TEST_UTILS_H_ diff --git a/third_party/jpeg-xl/lib/jxl/test_utils.cc b/third_party/jpeg-xl/lib/jxl/test_utils.cc --- a/third_party/jpeg-xl/lib/jxl/test_utils.cc +++ b/third_party/jpeg-xl/lib/jxl/test_utils.cc @@ -3,24 +3,42 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. #include "lib/jxl/test_utils.h" +#include + +#include #include #include #include +#include #include #include "lib/extras/metrics.h" #include "lib/extras/packed_image_convert.h" +#include "lib/jxl/base/compiler_specific.h" +#include "lib/jxl/base/data_parallel.h" #include "lib/jxl/base/float.h" #include "lib/jxl/base/printf_macros.h" +#include "lib/jxl/base/status.h" #include "lib/jxl/cms/jxl_cms.h" +#include "lib/jxl/codec_in_out.h" +#include "lib/jxl/enc_aux_out.h" +#include "lib/jxl/enc_bit_writer.h" #include "lib/jxl/enc_butteraugli_comparator.h" #include "lib/jxl/enc_cache.h" #include "lib/jxl/enc_external_image.h" -#include "lib/jxl/enc_file.h" +#include "lib/jxl/enc_fields.h" +#include "lib/jxl/enc_frame.h" +#include "lib/jxl/enc_icc_codec.h" +#include "lib/jxl/enc_params.h" +#include "lib/jxl/frame_header.h" +#include "lib/jxl/icc_codec.h" +#include "lib/jxl/image.h" +#include "lib/jxl/image_bundle.h" +#include "lib/jxl/padded_bytes.h" #if !defined(TEST_DATA_PATH) #include "tools/cpp/runfiles/runfiles.h" #endif @@ -38,24 +56,22 @@ std::string root(JPEGXL_ROOT_PACKAGE "/testdata/"); return kRunfiles->Rlocation(root + filename); } #endif -PaddedBytes ReadTestData(const std::string& filename) { +std::vector ReadTestData(const std::string& filename) { std::string full_path = GetTestDataPath(filename); fprintf(stderr, "ReadTestData %s\n", full_path.c_str()); std::ifstream file(full_path, std::ios::binary); std::vector str((std::istreambuf_iterator(file)), std::istreambuf_iterator()); JXL_CHECK(file.good()); const uint8_t* raw = reinterpret_cast(str.data()); std::vector data(raw, raw + str.size()); printf("Test data %s is %d bytes long.\n", filename.c_str(), static_cast(data.size())); - PaddedBytes result; - result.append(data); - return result; + return data; } void DefaultAcceptedFormats(extras::JXLDecompressParams& dparams) { if (dparams.accepted_formats.empty()) { for (const uint32_t num_channels : {1, 2, 3, 4}) { @@ -149,16 +165,16 @@ } // namespace bool Roundtrip(const CodecInOut* io, const CompressParams& cparams, extras::JXLDecompressParams dparams, CodecInOut* JXL_RESTRICT io2, std::stringstream& failures, - size_t* compressed_size, ThreadPool* pool, AuxOut* aux_out) { + size_t* compressed_size, ThreadPool* pool) { DefaultAcceptedFormats(dparams); if (compressed_size) { *compressed_size = static_cast(-1); } - PaddedBytes compressed; + std::vector compressed; std::vector original_metadata_encodings; std::vector original_current_encodings; std::vector metadata_encodings_1; std::vector metadata_encodings_2; @@ -176,22 +192,21 @@ original_current_encodings.push_back(ib.c_current()); } std::unique_ptr enc_state = jxl::make_unique(); - JXL_CHECK(EncodeFile(cparams, io, enc_state.get(), &compressed, - *JxlGetDefaultCms(), aux_out, pool)); + JXL_CHECK(test::EncodeFile(cparams, io, enc_state.get(), &compressed, pool)); for (const ImageBundle& ib1 : io->frames) { metadata_encodings_1.push_back(ib1.metadata()->color_encoding); } // Should still be in the same color space after encoding. CheckSameEncodings(metadata_encodings_1, original_metadata_encodings, "original vs after encoding", failures); - JXL_CHECK(DecodeFile(dparams, Span(compressed), io2, pool)); + JXL_CHECK(DecodeFile(dparams, Bytes(compressed), io2, pool)); JXL_CHECK(io2->frames.size() == io->frames.size()); for (const ImageBundle& ib2 : io2->frames) { metadata_encodings_2.push_back(ib2.metadata()->color_encoding); current_encodings_2.push_back(ib2.c_current()); @@ -272,11 +287,11 @@ io.metadata.m.color_encoding = jxl::ColorEncoding::SRGB( /*is_gray=*/num_channels == 1 || num_channels == 2); JxlPixelFormat format = {static_cast(num_channels), JXL_TYPE_UINT16, JXL_BIG_ENDIAN, 0}; JXL_CHECK(ConvertFromExternal( - jxl::Span(buf.data(), buf.size()), xsize, ysize, + jxl::Bytes(buf.data(), buf.size()), xsize, ysize, jxl::ColorEncoding::SRGB(/*is_gray=*/num_channels < 3), /*bits_per_sample=*/16, format, /*pool=*/nullptr, /*ib=*/&io.Main())); return io; @@ -647,19 +662,141 @@ } } return true; } +Status ReadICC(BitReader* JXL_RESTRICT reader, + std::vector* JXL_RESTRICT icc, size_t output_limit) { + icc->clear(); + ICCReader icc_reader; + PaddedBytes icc_buffer; + JXL_RETURN_IF_ERROR(icc_reader.Init(reader, output_limit)); + JXL_RETURN_IF_ERROR(icc_reader.Process(reader, &icc_buffer)); + Bytes(icc_buffer).AppendTo(icc); + return true; +} + +namespace { // For EncodeFile +Status PrepareCodecMetadataFromIO(const CompressParams& cparams, + const CodecInOut* io, + CodecMetadata* metadata) { + *metadata = io->metadata; + size_t ups = 1; + if (cparams.already_downsampled) ups = cparams.resampling; + + JXL_RETURN_IF_ERROR(metadata->size.Set(io->xsize() * ups, io->ysize() * ups)); + + // Keep ICC profile in lossless modes because a reconstructed profile may be + // slightly different (quantization). + // Also keep ICC in JPEG reconstruction mode as we need byte-exact profiles. + if (!cparams.IsLossless() && !io->Main().IsJPEG() && cparams.cms_set) { + metadata->m.color_encoding.DecideIfWantICC(cparams.cms); + } + + metadata->m.xyb_encoded = + cparams.color_transform == ColorTransform::kXYB ? true : false; + + // TODO(firsching): move this EncodeFile to test_utils / re-implement this + // using API functions + return true; +} + +Status EncodePreview(const CompressParams& cparams, const ImageBundle& ib, + const CodecMetadata* metadata, const JxlCmsInterface& cms, + ThreadPool* pool, BitWriter* JXL_RESTRICT writer) { + BitWriter preview_writer; + // TODO(janwas): also support generating preview by downsampling + if (ib.HasColor()) { + AuxOut aux_out; + PassesEncoderState passes_enc_state; + // TODO(lode): check if we want all extra channels and matching xyb_encoded + // for the preview, such that using the main ImageMetadata object for + // encoding this frame is warrented. + FrameInfo frame_info; + frame_info.is_preview = true; + JXL_RETURN_IF_ERROR(EncodeFrame(cparams, frame_info, metadata, ib, + &passes_enc_state, cms, pool, + &preview_writer, &aux_out)); + preview_writer.ZeroPadToByte(); + } + + if (preview_writer.BitsWritten() != 0) { + writer->ZeroPadToByte(); + writer->AppendByteAligned(preview_writer); + } + + return true; +} + +} // namespace + +Status EncodeFile(const CompressParams& params, const CodecInOut* io, + PassesEncoderState* passes_enc_state, + std::vector* compressed, ThreadPool* pool) { + compressed->clear(); + const JxlCmsInterface& cms = *JxlGetDefaultCms(); + io->CheckMetadata(); + BitWriter writer; + + 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; + } + + 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( + WriteCodestreamHeaders(metadata.get(), &writer, /*aux_out*/ nullptr)); + + // Only send ICC (at least several hundred bytes) if fields aren't enough. + if (metadata->m.color_encoding.WantICC()) { + JXL_RETURN_IF_ERROR(WriteICC(metadata->m.color_encoding.ICC(), &writer, + kLayerHeader, /* aux_out */ nullptr)); + } + + if (metadata->m.have_preview) { + JXL_RETURN_IF_ERROR(EncodePreview(cparams, io->preview_frame, + metadata.get(), cms, pool, &writer)); + } + + // Each frame should start on byte boundaries. + BitWriter::Allotment allotment(&writer, 8); + writer.ZeroPadToByte(); + allotment.ReclaimAndCharge(&writer, kLayerHeader, /* aux_out */ nullptr); + + for (size_t i = 0; i < io->frames.size(); i++) { + FrameInfo info; + info.is_last = i == io->frames.size() - 1; + if (io->frames[i].use_for_next_frame) { + info.save_as_reference = 1; + } + JXL_RETURN_IF_ERROR(EncodeFrame(cparams, info, metadata.get(), + io->frames[i], passes_enc_state, cms, pool, + &writer, /* aux_out */ nullptr)); + } + + // Clean up passes_enc_state in case it gets reused. + for (size_t i = 0; i < 4; i++) { + passes_enc_state->shared.dc_frames[i] = Image3F(); + passes_enc_state->shared.reference_frames[i].frame = ImageBundle(); + } + + PaddedBytes output = std::move(writer).TakeBytes(); + Bytes(output).AppendTo(compressed); + return true; +} + } // namespace test -bool operator==(const jxl::PaddedBytes& a, const jxl::PaddedBytes& b) { +bool operator==(const jxl::Bytes& a, const jxl::Bytes& b) { if (a.size() != b.size()) return false; if (memcmp(a.data(), b.data(), a.size()) != 0) return false; return true; } -// Allow using EXPECT_EQ on jxl::PaddedBytes -bool operator!=(const jxl::PaddedBytes& a, const jxl::PaddedBytes& b) { - return !(a == b); -} +// Allow using EXPECT_EQ on jxl::Bytes +bool operator!=(const jxl::Bytes& a, const jxl::Bytes& b) { return !(a == b); } } // namespace jxl diff --git a/third_party/jpeg-xl/lib/jxl/toc.h b/third_party/jpeg-xl/lib/jxl/toc.h --- a/third_party/jpeg-xl/lib/jxl/toc.h +++ b/third_party/jpeg-xl/lib/jxl/toc.h @@ -26,20 +26,18 @@ size_t MaxBits(const size_t num_sizes); // TODO(veluca): move these to FrameDimensions. static JXL_INLINE size_t AcGroupIndex(size_t pass, size_t group, - size_t num_groups, size_t num_dc_groups, - bool has_ac_global) { - return 1 + num_dc_groups + static_cast(has_ac_global) + - pass * num_groups + group; + size_t num_groups, size_t num_dc_groups) { + return 2 + num_dc_groups + pass * num_groups + group; } static JXL_INLINE size_t NumTocEntries(size_t num_groups, size_t num_dc_groups, - size_t num_passes, bool has_ac_global) { + size_t num_passes) { if (num_groups == 1 && num_passes == 1) return 1; - return AcGroupIndex(0, 0, num_groups, num_dc_groups, has_ac_global) + + return AcGroupIndex(0, 0, num_groups, num_dc_groups) + num_groups * num_passes; } Status ReadToc(size_t toc_entries, BitReader* JXL_RESTRICT reader, std::vector* JXL_RESTRICT sizes, diff --git a/third_party/jpeg-xl/lib/jxl_cms.cmake b/third_party/jpeg-xl/lib/jxl_cms.cmake new file mode 100644 --- /dev/null +++ b/third_party/jpeg-xl/lib/jxl_cms.cmake @@ -0,0 +1,93 @@ +# 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(compatibility.cmake) +include(jxl_lists.cmake) + +# Headers for exporting/importing public headers +include(GenerateExportHeader) + +# CMake does not allow generate_export_header for INTERFACE library, so we +# add this stub library just for file generation. +add_library(jxl_cms_export OBJECT "jxl/cms/jxl_cms.h") +set_target_properties(jxl_cms_export PROPERTIES + CXX_VISIBILITY_PRESET hidden + VISIBILITY_INLINES_HIDDEN 1 + DEFINE_SYMBOL JXL_INTERNAL_LIBRARY_BUILD + LINKER_LANGUAGE CXX +) +generate_export_header(jxl_cms_export + BASE_NAME JXL_CMS + EXPORT_FILE_NAME include/jxl/jxl_cms_export.h) + +add_library(jxl_cms-obj OBJECT + ${JPEGXL_INTERNAL_CMS_SOURCES} +) +target_compile_options(jxl_cms-obj PRIVATE "${JPEGXL_INTERNAL_FLAGS}") +set_target_properties(jxl_cms-obj PROPERTIES POSITION_INDEPENDENT_CODE ON) +jxl_link_libraries(jxl_cms-obj jxl_base) +target_include_directories(jxl_cms-obj PRIVATE + ${JXL_HWY_INCLUDE_DIRS} +) + +add_dependencies(jxl_cms-obj jxl_cms_export) + +target_include_directories(jxl_cms-obj PUBLIC + "$") + +set(JXL_CMS_OBJECTS $) +set(JXL_CMS_LIBS "") +set(JXL_CMS_PK_LIBS "") + +if (JPEGXL_ENABLE_SKCMS) + target_include_directories(jxl_cms-obj PRIVATE + $ + ) + if (JPEGXL_BUNDLE_SKCMS) + list(APPEND JXL_CMS_OBJECTS $) + else() + message(ERROR "Non-bundles skcms is not currently supported") + set(JXL_CMS_LIBS "skcms") + set(JXL_CMS_PK_LIBS "-lskcms") + endif() +else() + if (NOT JPEGXL_FORCE_SYSTEM_LCMS2) + target_include_directories(jxl_cms-obj PRIVATE + $ + ) + list(APPEND JXL_CMS_OBJECTS $) + else() + target_include_directories(jxl_cms-obj PRIVATE + $ + ) + set(JXL_CMS_LIBS "lcms2") + set(JXL_CMS_PK_LIBS "-llcms2") + endif() +endif() + +target_link_libraries(jxl_cms-obj PUBLIC ${JXL_CMS_LIBS}) + +if (BUILD_SHARED_LIBS) + add_library(jxl_cms SHARED ${JXL_CMS_OBJECTS}) + target_link_libraries(jxl_cms INTERFACE ${JXL_CMS_LIBS}) + target_link_libraries(jxl_cms PRIVATE hwy) + + set_target_properties(jxl_cms PROPERTIES + VERSION ${JPEGXL_LIBRARY_VERSION} + SOVERSION ${JPEGXL_LIBRARY_SOVERSION} + LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}" + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}") + + install(TARGETS jxl_cms + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}) +endif() # BUILD_SHARED_LIBS + +set(JPEGXL_CMS_LIBRARY_REQUIRES "") +configure_file("${CMAKE_CURRENT_SOURCE_DIR}/jxl/libjxl_cms.pc.in" + "libjxl_cms.pc" @ONLY) +install(FILES "${CMAKE_CURRENT_BINARY_DIR}/libjxl_cms.pc" + DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig") diff --git a/third_party/jpeg-xl/lib/jxl_lists.bzl b/third_party/jpeg-xl/lib/jxl_lists.bzl --- a/third_party/jpeg-xl/lib/jxl_lists.bzl +++ b/third_party/jpeg-xl/lib/jxl_lists.bzl @@ -8,34 +8,41 @@ libjxl_base_sources = [ "jxl/base/arch_macros.h", "jxl/base/bits.h", "jxl/base/byte_order.h", - "jxl/base/cache_aligned.cc", - "jxl/base/cache_aligned.h", + "jxl/base/c_callback_support.h", "jxl/base/common.h", "jxl/base/compiler_specific.h", - "jxl/base/data_parallel.cc", "jxl/base/data_parallel.h", "jxl/base/fast_math-inl.h", "jxl/base/float.h", "jxl/base/iaca.h", "jxl/base/matrix_ops.h", "jxl/base/os_macros.h", "jxl/base/override.h", - "jxl/base/padded_bytes.cc", - "jxl/base/padded_bytes.h", "jxl/base/printf_macros.h", - "jxl/base/random.cc", "jxl/base/random.h", "jxl/base/rational_polynomial-inl.h", "jxl/base/sanitizer_definitions.h", "jxl/base/scope_guard.h", "jxl/base/span.h", "jxl/base/status.h", ] +libjxl_cms_sources = [ + "jxl/cms/color_encoding_cms.h", + "jxl/cms/color_management.cc", + "jxl/cms/color_management.h", + "jxl/cms/jxl_cms.cc", + "jxl/cms/jxl_cms.h", + "jxl/cms/jxl_skcms.h", + "jxl/cms/opsin_params.h", + "jxl/cms/tone_mapping-inl.h", + "jxl/cms/transfer_functions-inl.h", +] + libjxl_codec_apng_sources = [ "extras/dec/apng.cc", "extras/dec/apng.h", "extras/enc/apng.cc", "extras/enc/apng.h", @@ -120,18 +127,14 @@ "jxl/ans_common.cc", "jxl/ans_common.h", "jxl/ans_params.h", "jxl/blending.cc", "jxl/blending.h", + "jxl/cache_aligned.cc", + "jxl/cache_aligned.h", "jxl/chroma_from_luma.cc", "jxl/chroma_from_luma.h", - "jxl/cms/color_encoding_cms.h", - "jxl/cms/color_management.cc", - "jxl/cms/color_management.h", - "jxl/cms/opsin_params.h", - "jxl/cms/tone_mapping-inl.h", - "jxl/cms/transfer_functions-inl.h", "jxl/codec_in_out.h", "jxl/coeff_order.cc", "jxl/coeff_order.h", "jxl/coeff_order_fwd.h", "jxl/color_encoding_internal.cc", @@ -243,10 +246,11 @@ "jxl/modular/transform/transform.h", "jxl/noise.h", "jxl/opsin_params.cc", "jxl/opsin_params.h", "jxl/pack_signed.h", + "jxl/padded_bytes.h", "jxl/passes_state.cc", "jxl/passes_state.h", "jxl/patch_dictionary_internal.h", "jxl/quant_weights.cc", "jxl/quant_weights.h", @@ -262,10 +266,12 @@ "jxl/render_pipeline/simple_render_pipeline.h", "jxl/render_pipeline/stage_blending.cc", "jxl/render_pipeline/stage_blending.h", "jxl/render_pipeline/stage_chroma_upsampling.cc", "jxl/render_pipeline/stage_chroma_upsampling.h", + "jxl/render_pipeline/stage_cms.cc", + "jxl/render_pipeline/stage_cms.h", "jxl/render_pipeline/stage_epf.cc", "jxl/render_pipeline/stage_epf.h", "jxl/render_pipeline/stage_from_linear.cc", "jxl/render_pipeline/stage_from_linear.h", "jxl/render_pipeline/stage_gaborish.cc", @@ -344,12 +350,10 @@ "jxl/enc_external_image.h", "jxl/enc_fast_lossless.cc", "jxl/enc_fast_lossless.h", "jxl/enc_fields.cc", "jxl/enc_fields.h", - "jxl/enc_file.cc", - "jxl/enc_file.h", "jxl/enc_frame.cc", "jxl/enc_frame.h", "jxl/enc_gaborish.cc", "jxl/enc_gaborish.h", "jxl/enc_gamma_correct.h", @@ -411,13 +415,10 @@ "jxl/modular/transform/enc_rct.h", "jxl/modular/transform/enc_squeeze.cc", "jxl/modular/transform/enc_squeeze.h", "jxl/modular/transform/enc_transform.cc", "jxl/modular/transform/enc_transform.h", - "jxl/cms/jxl_cms.cc", - "jxl/cms/jxl_cms.h", - "jxl/cms/jxl_skcms.h", ] libjxl_extras_for_tools_sources = [ "extras/codec.cc", "extras/codec.h", @@ -546,11 +547,11 @@ "jpegli/libjpeg_wrapper.cc", ] libjxl_major_version = 0 -libjxl_minor_version = 9 +libjxl_minor_version = 10 libjxl_patch_version = 0 libjxl_public_headers = [ "include/jxl/cms_interface.h", diff --git a/third_party/jpeg-xl/lib/jxl_lists.cmake b/third_party/jpeg-xl/lib/jxl_lists.cmake --- a/third_party/jpeg-xl/lib/jxl_lists.cmake +++ b/third_party/jpeg-xl/lib/jxl_lists.cmake @@ -8,34 +8,41 @@ set(JPEGXL_INTERNAL_BASE_SOURCES jxl/base/arch_macros.h jxl/base/bits.h jxl/base/byte_order.h - jxl/base/cache_aligned.cc - jxl/base/cache_aligned.h + jxl/base/c_callback_support.h jxl/base/common.h jxl/base/compiler_specific.h - jxl/base/data_parallel.cc jxl/base/data_parallel.h jxl/base/fast_math-inl.h jxl/base/float.h jxl/base/iaca.h jxl/base/matrix_ops.h jxl/base/os_macros.h jxl/base/override.h - jxl/base/padded_bytes.cc - jxl/base/padded_bytes.h jxl/base/printf_macros.h - jxl/base/random.cc jxl/base/random.h jxl/base/rational_polynomial-inl.h jxl/base/sanitizer_definitions.h jxl/base/scope_guard.h jxl/base/span.h jxl/base/status.h ) +set(JPEGXL_INTERNAL_CMS_SOURCES + jxl/cms/color_encoding_cms.h + jxl/cms/color_management.cc + jxl/cms/color_management.h + jxl/cms/jxl_cms.cc + jxl/cms/jxl_cms.h + jxl/cms/jxl_skcms.h + jxl/cms/opsin_params.h + jxl/cms/tone_mapping-inl.h + jxl/cms/transfer_functions-inl.h +) + set(JPEGXL_INTERNAL_CODEC_APNG_SOURCES extras/dec/apng.cc extras/dec/apng.h extras/enc/apng.cc extras/enc/apng.h @@ -120,18 +127,14 @@ jxl/ans_common.cc jxl/ans_common.h jxl/ans_params.h jxl/blending.cc jxl/blending.h + jxl/cache_aligned.cc + jxl/cache_aligned.h jxl/chroma_from_luma.cc jxl/chroma_from_luma.h - jxl/cms/color_encoding_cms.h - jxl/cms/color_management.cc - jxl/cms/color_management.h - jxl/cms/opsin_params.h - jxl/cms/tone_mapping-inl.h - jxl/cms/transfer_functions-inl.h jxl/codec_in_out.h jxl/coeff_order.cc jxl/coeff_order.h jxl/coeff_order_fwd.h jxl/color_encoding_internal.cc @@ -243,10 +246,11 @@ jxl/modular/transform/transform.h jxl/noise.h jxl/opsin_params.cc jxl/opsin_params.h jxl/pack_signed.h + jxl/padded_bytes.h jxl/passes_state.cc jxl/passes_state.h jxl/patch_dictionary_internal.h jxl/quant_weights.cc jxl/quant_weights.h @@ -262,10 +266,12 @@ jxl/render_pipeline/simple_render_pipeline.h jxl/render_pipeline/stage_blending.cc jxl/render_pipeline/stage_blending.h jxl/render_pipeline/stage_chroma_upsampling.cc jxl/render_pipeline/stage_chroma_upsampling.h + jxl/render_pipeline/stage_cms.cc + jxl/render_pipeline/stage_cms.h jxl/render_pipeline/stage_epf.cc jxl/render_pipeline/stage_epf.h jxl/render_pipeline/stage_from_linear.cc jxl/render_pipeline/stage_from_linear.h jxl/render_pipeline/stage_gaborish.cc @@ -344,12 +350,10 @@ jxl/enc_external_image.h jxl/enc_fast_lossless.cc jxl/enc_fast_lossless.h jxl/enc_fields.cc jxl/enc_fields.h - jxl/enc_file.cc - jxl/enc_file.h jxl/enc_frame.cc jxl/enc_frame.h jxl/enc_gaborish.cc jxl/enc_gaborish.h jxl/enc_gamma_correct.h @@ -411,13 +415,10 @@ jxl/modular/transform/enc_rct.h jxl/modular/transform/enc_squeeze.cc jxl/modular/transform/enc_squeeze.h jxl/modular/transform/enc_transform.cc jxl/modular/transform/enc_transform.h - jxl/cms/jxl_cms.cc - jxl/cms/jxl_cms.h - jxl/cms/jxl_skcms.h ) set(JPEGXL_INTERNAL_EXTRAS_FOR_TOOLS_SOURCES extras/codec.cc extras/codec.h 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 @@ -104,11 +104,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 OR JPEGXL_ENABLE_PLUGINS) if( NOT EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/lcms/.git" OR JPEGXL_FORCE_SYSTEM_LCMS2 ) - find_package(LCMS2 2.13) + find_package(LCMS2 2.12) 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 @@ -10,11 +10,11 @@ # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -add_library(lcms2 STATIC EXCLUDE_FROM_ALL +add_library(lcms2-obj OBJECT EXCLUDE_FROM_ALL lcms/src/cmsalpha.c lcms/src/cmscam02.c lcms/src/cmscgats.c lcms/src/cmscnvrt.c lcms/src/cmserr.c @@ -39,39 +39,43 @@ lcms/src/cmsvirt.c lcms/src/cmswtpnt.c lcms/src/cmsxform.c lcms/src/lcms2_internal.h ) -target_include_directories(lcms2 +target_include_directories(lcms2-obj PUBLIC "${CMAKE_CURRENT_LIST_DIR}/lcms/include") # This warning triggers with gcc-8. if (CMAKE_C_COMPILER_ID MATCHES "GNU") -target_compile_options(lcms2 - PRIVATE - # gcc-only flags. - -Wno-stringop-truncation - -Wno-strict-aliasing -) + target_compile_options(lcms2-obj + PRIVATE + # gcc-only flags. + -Wno-stringop-truncation + -Wno-strict-aliasing + ) endif() # By default LCMS uses sizeof(void*) for memory alignment, but in arm 32-bits we # can't access doubles not aligned to 8 bytes. This forces the alignment to 8 # bytes. -target_compile_definitions(lcms2 +target_compile_definitions(lcms2-obj PRIVATE "-DCMS_PTR_ALIGNMENT=8") -target_compile_definitions(lcms2 +target_compile_definitions(lcms2-obj 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 + target_compile_definitions(lcms2-obj PUBLIC "-DHAVE_GMTIME_R=1") else() check_symbol_exists(gmtime_s "time.h" HAVE_GMTIME_S) if (HAVE_GMTIME_S) - target_compile_definitions(lcms2 + target_compile_definitions(lcms2-obj PUBLIC "-DHAVE_GMTIME_S=1") endif() endif() -set_property(TARGET lcms2 PROPERTY POSITION_INDEPENDENT_CODE ON) +set_property(TARGET lcms2-obj PROPERTY POSITION_INDEPENDENT_CODE ON) + +add_library(lcms2 STATIC EXCLUDE_FROM_ALL $) +target_include_directories(lcms2 + PUBLIC $) diff --git a/third_party/jpeg-xl/third_party/skcms.cmake b/third_party/jpeg-xl/third_party/skcms.cmake --- a/third_party/jpeg-xl/third_party/skcms.cmake +++ b/third_party/jpeg-xl/third_party/skcms.cmake @@ -13,21 +13,14 @@ # limitations under the License. add_library(skcms-obj OBJECT EXCLUDE_FROM_ALL skcms/skcms.cc) target_include_directories(skcms-obj PUBLIC "${CMAKE_CURRENT_LIST_DIR}/skcms/") -# This library is meant to be compiled/used by external libs (such as plugins) -# that need to use skcms. We use a wrapper for libjxl. -add_library(skcms-interface INTERFACE) -target_sources(skcms-interface INTERFACE ${CMAKE_CURRENT_LIST_DIR}/skcms/skcms.cc) -target_include_directories(skcms-interface INTERFACE ${CMAKE_CURRENT_LIST_DIR}/skcms) - include(CheckCXXCompilerFlag) check_cxx_compiler_flag("-Wno-psabi" CXX_WPSABI_SUPPORTED) if(CXX_WPSABI_SUPPORTED) target_compile_options(skcms-obj PRIVATE -Wno-psabi) - target_compile_options(skcms-interface INTERFACE -Wno-psabi) endif() if(JPEGXL_BUNDLE_SKCMS) target_compile_options(skcms-obj PRIVATE -DJPEGXL_BUNDLE_SKCMS=1) if(MSVC) @@ -43,9 +36,11 @@ POSITION_INDEPENDENT_CODE ON CXX_VISIBILITY_PRESET hidden VISIBILITY_INLINES_HIDDEN 1 ) -add_library(skcms STATIC EXCLUDE_FROM_ALL $) -target_include_directories(skcms - PUBLIC $) +if(NOT JPEGXL_BUNDLE_SKCMS) + add_library(skcms STATIC EXCLUDE_FROM_ALL $) + target_include_directories(skcms + PUBLIC $) +endif()